Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1f8a92b
Move some macros that should be in xobjects to it; black
szymonlopaciuk Aug 7, 2025
15af308
Move the new implementation of atomic add from Xtrack
szymonlopaciuk Nov 10, 2025
1ecb860
Fix style
szymonlopaciuk Nov 12, 2025
34b3c69
Add a copy flag to the np.array <-> context array conversion functions
szymonlopaciuk Nov 12, 2025
d5606b3
Bugfix: capi get/set methods broken for nested arrays
szymonlopaciuk Nov 12, 2025
04301c5
Implement the get_shape and get_nd methods in the C API
szymonlopaciuk Nov 13, 2025
c2795aa
Fixes in tests
szymonlopaciuk Nov 19, 2025
84ba92c
Fixes for gpu
szymonlopaciuk Nov 19, 2025
23373aa
fixes for gpu
szymonlopaciuk Nov 19, 2025
b30e666
fix in tests
szymonlopaciuk Nov 19, 2025
5e3b8ca
fixes for gpu
szymonlopaciuk Nov 19, 2025
9d44e27
fixes for gpu
szymonlopaciuk Nov 19, 2025
bc0e6b2
fixes for gpu
szymonlopaciuk Nov 19, 2025
52a981f
fixes for gpu, built in kernels cannot be passed to add_kernels on gpu
szymonlopaciuk Nov 19, 2025
0ebe917
fixes on gpu
szymonlopaciuk Nov 19, 2025
1c508e8
fixes for gpu
szymonlopaciuk Nov 19, 2025
4e126a3
set context
szymonlopaciuk Nov 19, 2025
35df65f
fixes for gpu
szymonlopaciuk Nov 19, 2025
7944487
fixes for gpu
szymonlopaciuk Nov 19, 2025
414e8a1
fix test_array_get_shape
szymonlopaciuk Nov 19, 2025
6e74279
more fixes
szymonlopaciuk Nov 19, 2025
5941a9a
more fixes
szymonlopaciuk Nov 19, 2025
db420e9
right context
szymonlopaciuk Nov 19, 2025
d7156d8
final fixes
szymonlopaciuk Nov 19, 2025
de1ebd6
final final fixes
szymonlopaciuk Nov 19, 2025
153ead2
Fix typo
szymonlopaciuk Nov 21, 2025
f234f88
fixes in tests for opencl
szymonlopaciuk Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ cov_html
.vscode
.pytest_cache
.coverage
.idea
*.c
6 changes: 3 additions & 3 deletions Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ Buffer:

Types can be composed of:
- scalar: numbers, String
- compound: Struct, Array, Ref, UnionRef
- compound: Struct, Array, Ref, UnionRef

### Scalars
- examples: Float64, Int64, ...
- create: Float64(3.14)
- memory layout
- data
- data

### String:
- create: String(string_or_int)
- memory layout
- size
- data
- data


### Struct
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
41 changes: 40 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "xobjects"
dynamic = ["version"]
description = "In-memory serialization and code generator for CPU and GPU"
readme = ""
authors = [
{ name = "Riccardo De Maria", email = "[email protected]" }
]
license = { text = "Apache 2.0" }
requires-python = ">=3.7"
dependencies = [
"numpy",
"cffi",
"scipy"
]
[project.urls]
Homepage = "https://xsuite.readthedocs.io/"
"Bug Tracker" = "https://github.com/xsuite/xsuite/issues"
Documentation = "https://xsuite.readthedocs.io/"
"Source Code" = "https://github.com/xsuite/xobjects"
"Download" = "https://pypi.python.org/pypi/xobjects"

[project.optional-dependencies]
tests = ["pytest", "pytest-mock"]

[tool.setuptools.packages.find]
where = ["."]
include = ["xobjects"]

[tool.setuptools.dynamic]
version = {attr = "xobjects._version.__version__"}

[tool.black]
line-length = 79
target-version = ['py36', 'py37', 'py38']
target-version = ['py310', 'py311', 'py312']
include = '\.pyi?$'

[project.entry-points.xobjects]
include = "xobjects"
287 changes: 248 additions & 39 deletions tests/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import cffi

import xobjects as xo

from xobjects.test_helpers import for_all_test_contexts

ffi = cffi.FFI()

Expand Down Expand Up @@ -158,6 +158,70 @@ def test_array_dynamic_type_init_get_set(array_cls, example_shape):
assert arr[ii].field1[idx_in_field] == 13 * vv


@for_all_test_contexts
@pytest.mark.parametrize(
"array_type",
[
xo.UInt64[3, 5, 7],
xo.UInt64[:, :, :],
xo.UInt64[:, 5, :],
],
)
def test_array_get_shape(test_context, array_type):
source = """
#include "xobjects/headers/common.h"

GPUKERN void get_nd_and_shape(
ARRAY_TYPE arr,
GPUGLMEM int64_t* out_nd,
GPUGLMEM int64_t* out_shape
) {
*out_nd = ARRAY_TYPE_nd(arr);
ARRAY_TYPE_shape(arr, out_shape);
}
""".replace(
"ARRAY_TYPE", array_type.__name__
)

kernels = {
"get_nd_and_shape": xo.Kernel(
c_name="get_nd_and_shape",
args=[
xo.Arg(array_type, name="arr"),
xo.Arg(xo.Int64, pointer=True, name="out_nd"),
xo.Arg(xo.Int64, pointer=True, name="out_shape"),
],
),
}

test_context.add_kernels(
sources=[source],
kernels=kernels,
)

instance = array_type(
np.array(range(3 * 5 * 7)).reshape((3, 5, 7)),
_context=test_context,
)

expected_nd = 3
result_nd = test_context.zeros((1,), dtype=np.int64)

expected_shape = [3, 5, 7]
result_shape = test_context.zeros((expected_nd,), dtype=np.int64)

test_context.kernels.get_nd_and_shape(
arr=instance,
out_nd=result_nd,
out_shape=result_shape,
)

assert result_nd[0] == expected_nd
assert result_shape[0] == expected_shape[0]
assert result_shape[1] == expected_shape[1]
assert result_shape[2] == expected_shape[2]


def test_struct1():
kernels = Struct1._gen_kernels()
ctx = xo.ContextCpu()
Expand Down Expand Up @@ -539,45 +603,190 @@ def test_getp1_dyn_length_dyn_type_string_array():
assert ord(ffi.cast("char *", s2)[8 + ii]) == ch


def test_gpu_api():
for ctx in xo.context.get_test_contexts():
src_code = """
/*gpufun*/
void myfun(double x, double y,
double* z){
z[0] = x * y;
}
@for_all_test_contexts
def test_gpu_api(test_context):
src_code = """
/*gpufun*/
void myfun(double x, double y,
double* z){
z[0] = x * y;
}

/*gpukern*/
void my_mul(const int n,
/*gpuglmem*/ const double* x1,
/*gpuglmem*/ const double* x2,
/*gpuglmem*/ double* y) {
int tid = 0 //vectorize_over tid n
double z;
myfun(x1[tid], x2[tid], &z);
y[tid] = z;
//end_vectorize
/*gpukern*/
void my_mul(const int n,
/*gpuglmem*/ const double* x1,
/*gpuglmem*/ const double* x2,
/*gpuglmem*/ double* y) {
int tid = 0 //vectorize_over tid n
double z;
myfun(x1[tid], x2[tid], &z);
y[tid] = z;
//end_vectorize
}
"""

kernel_descriptions = {
"my_mul": xo.Kernel(
args=[
xo.Arg(xo.Int32, name="n"),
xo.Arg(xo.Float64, pointer=True, const=True, name="x1"),
xo.Arg(xo.Float64, pointer=True, const=True, name="x2"),
xo.Arg(xo.Float64, pointer=True, const=False, name="y"),
],
n_threads="n",
),
}

test_context.add_kernels(
sources=[src_code],
kernels=kernel_descriptions,
save_source_as=None,
compile=True,
extra_classes=[xo.String[:]],
)


@for_all_test_contexts
def test_array_of_arrays(test_context):
cell_ids = [3, 5, 7]
particle_per_cell = [
[1, 8],
[9, 3, 2],
[4, 5, 6, 7],
]

class Cells(xo.Struct):
ids = xo.Int64[:]
particles = xo.Int64[:][:]

cells = Cells(
ids=cell_ids, particles=particle_per_cell, _context=test_context
)

# Data layout (displayed as uint64):
#
# [0] 216 (cells size)
# [8] 56 (offset field 2 -- particles field)
# [16] cell_ids data:
# [0] 40 (cell_ids size)
# [8] 3 (cell_ids length)
# [16] {3, 5, 7} (cell_ids elements)
# [56] particles data:
# [0] 160 (particles size)
# [8] 3 (particles length)
# [16] 40 (offset particles[0])
# [24] 72 (offset particles[1])
# [32] 112 (offset particles[2])
# [40] particles[0] data:
# [0] 32 (particles[0] size)
# [8] 2 (particles[0] length)
# [16] {1, 8} (particles[0] elements)
# [72] particles[1] data:
# [0] 40 (particles[1] size)
# [8] 3 (particles[1] length)
# [16] {9, 3, 2} (particles[1
# [112] particles[2] data:
# [0] 48 (particles[2] size)
# [8] 4 (particles[2] length)
# [16] {4, 5, 6, 7} (particles[2] elements)

src = r"""
#include "xobjects/headers/common.h"

static const int MAX_PARTICLES = 4;
static const int MAX_CELLS = 3;

GPUKERN void loop_over(
Cells cells,
GPUGLMEM uint64_t* out_counts,
GPUGLMEM uint64_t* out_vals,
GPUGLMEM uint8_t* success
)
{
int64_t num_cells = Cells_len_ids(cells);

for (int64_t i = 0; i < num_cells; i++) {
int64_t id = Cells_get_ids(cells, i);
int64_t count = Cells_len1_particles(cells, i);

if (i >= MAX_CELLS) {
*success = 0;
continue;
}
"""

kernel_descriptions = {
"my_mul": xo.Kernel(
args=[
xo.Arg(xo.Int32, name="n"),
xo.Arg(xo.Float64, pointer=True, const=True, name="x1"),
xo.Arg(xo.Float64, pointer=True, const=True, name="x2"),
xo.Arg(xo.Float64, pointer=True, const=False, name="y"),
],
n_threads="n",
),

out_counts[i] = count;

ArrNInt64 particles = Cells_getp1_particles(cells, i);
uint32_t num_particles = ArrNInt64_len(particles);

VECTORIZE_OVER(j, num_particles);
int64_t val = ArrNInt64_get(particles, j);

if (j >= MAX_PARTICLES) {
*success = 0;
} else {
out_vals[i * MAX_PARTICLES + j] = val;
}
END_VECTORIZE;
}
}

GPUKERN void kernel_Cells_get_particles(
Cells obj,
int64_t i0,
int64_t i1,
GPUGLMEM int64_t* out
) {
*out = Cells_get_particles(obj, i0, i1);
}
"""

kernels = {
"loop_over": xo.Kernel(
args=[
xo.Arg(Cells, name="cells"),
xo.Arg(xo.UInt64, pointer=True, name="out_counts"),
xo.Arg(xo.UInt64, pointer=True, name="out_vals"),
xo.Arg(xo.UInt8, pointer=True, name="success"),
],
n_threads=4,
),
"kernel_Cells_get_particles": xo.Kernel(
args=[
xo.Arg(Cells, name="obj"),
xo.Arg(xo.Int64, name="i0"),
xo.Arg(xo.Int64, name="i1"),
xo.Arg(xo.Int64, pointer=True, name="out"),
],
),
}

test_context.add_kernels(
sources=[src],
kernels=kernels,
)

ctx.add_kernels(
sources=[src_code],
kernels=kernel_descriptions,
# save_src_as=f'_test_{name}.c')
save_source_as=None,
compile=True,
extra_classes=[xo.String[:]],
)
counts = test_context.zeros(len(cell_ids), dtype=np.uint64)
vals = test_context.zeros(12, dtype=np.uint64)
success = test_context.zeros((1,), dtype=np.uint8) + 1

for i, _ in enumerate(particle_per_cell):
for j, expected in enumerate(particle_per_cell[i]):
result = test_context.zeros(shape=(1,), dtype=np.int64)
test_context.kernels.kernel_Cells_get_particles(
obj=cells, i0=i, i1=j, out=result
)
assert result[0] == expected

test_context.kernels.loop_over(
cells=cells,
out_counts=counts,
out_vals=vals,
success=success,
)
counts = test_context.nparray_from_context_array(counts)
vals = test_context.nparray_from_context_array(vals)

assert success[0] == 1
assert np.all(counts == [2, 3, 4])
assert np.all(vals == [1, 8, 0, 0, 9, 3, 2, 0, 4, 5, 6, 7])
Loading
Loading