xobjects 0.5.7__tar.gz → 0.5.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {xobjects-0.5.7 → xobjects-0.5.9}/LICENSE +1 -1
  2. xobjects-0.5.9/PKG-INFO +39 -0
  3. xobjects-0.5.9/README.md +10 -0
  4. xobjects-0.5.9/pyproject.toml +50 -0
  5. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_capi.py +248 -39
  6. xobjects-0.5.9/tests/test_common.py +179 -0
  7. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_shared_memory.py +1 -1
  8. xobjects-0.5.9/xobjects/_version.py +1 -0
  9. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/capi.py +80 -17
  10. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/context.py +30 -11
  11. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/context_cpu.py +11 -8
  12. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/context_cupy.py +7 -4
  13. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/context_pyopencl.py +24 -11
  14. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/general.py +1 -2
  15. xobjects-0.5.9/xobjects.egg-info/PKG-INFO +39 -0
  16. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects.egg-info/SOURCES.txt +3 -0
  17. xobjects-0.5.9/xobjects.egg-info/entry_points.txt +2 -0
  18. xobjects-0.5.7/PKG-INFO +0 -22
  19. xobjects-0.5.7/pyproject.toml +0 -4
  20. xobjects-0.5.7/xobjects/_version.py +0 -1
  21. xobjects-0.5.7/xobjects.egg-info/PKG-INFO +0 -22
  22. {xobjects-0.5.7 → xobjects-0.5.9}/setup.cfg +0 -0
  23. {xobjects-0.5.7 → xobjects-0.5.9}/setup.py +0 -0
  24. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_align.py +0 -0
  25. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_array.py +0 -0
  26. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_buffer.py +0 -0
  27. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_chunk.py +0 -0
  28. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_context_opencl.py +0 -0
  29. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_hybrid_class.py +0 -0
  30. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_kernel.py +0 -0
  31. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_linked_array.py +0 -0
  32. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_nplike_arrays.py +0 -0
  33. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_ref.py +0 -0
  34. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_scalars.py +0 -0
  35. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_strides.py +0 -0
  36. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_string.py +0 -0
  37. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_struct.py +0 -0
  38. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_to_dict.py +0 -0
  39. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_typeutils.py +0 -0
  40. {xobjects-0.5.7 → xobjects-0.5.9}/tests/test_unionref.py +0 -0
  41. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/__init__.py +0 -0
  42. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/_patch_pyopencl_array.py +0 -0
  43. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/array.py +0 -0
  44. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/hybrid_class.py +0 -0
  45. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/linkedarray.py +0 -0
  46. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/ref.py +0 -0
  47. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/scalar.py +0 -0
  48. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/specialize_source.py +0 -0
  49. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/string.py +0 -0
  50. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/struct.py +0 -0
  51. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/test_helpers.py +0 -0
  52. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/typeutils.py +0 -0
  53. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects/union.py +0 -0
  54. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects.egg-info/dependency_links.txt +0 -0
  55. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects.egg-info/requires.txt +0 -0
  56. {xobjects-0.5.7 → xobjects-0.5.9}/xobjects.egg-info/top_level.txt +0 -0
@@ -198,4 +198,4 @@
198
198
  distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
- limitations under the License.
201
+ limitations under the License.
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: xobjects
3
+ Version: 0.5.9
4
+ Summary: In-memory serialization and code generator for CPU and GPU
5
+ Home-page: https://xsuite.readthedocs.io/
6
+ Download-URL: https://pypi.python.org/pypi/xobjects
7
+ Author: Riccardo De Maria
8
+ Author-email: Riccardo De Maria <riccardo.de.maria@cern.ch>
9
+ License-Expression: Apache-2.0
10
+ Project-URL: Homepage, https://xsuite.readthedocs.io/
11
+ Project-URL: Bug Tracker, https://github.com/xsuite/xsuite/issues
12
+ Project-URL: Documentation, https://xsuite.readthedocs.io/
13
+ Project-URL: Source Code, https://github.com/xsuite/xobjects
14
+ Project-URL: Download, https://pypi.python.org/pypi/xobjects
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy
19
+ Requires-Dist: cffi
20
+ Requires-Dist: scipy
21
+ Provides-Extra: tests
22
+ Requires-Dist: pytest; extra == "tests"
23
+ Requires-Dist: pytest-mock; extra == "tests"
24
+ Dynamic: author
25
+ Dynamic: download-url
26
+ Dynamic: home-page
27
+ Dynamic: license-file
28
+ Dynamic: requires-python
29
+
30
+ # XObjects
31
+
32
+ Library to create and manipulate serialized object in CPU and GPU memory efficiently.
33
+
34
+ The library defines:
35
+ - Contexts: create buffers and manage computations e.g. BytearrayContext, CLContext, ...
36
+ - Buffers: reference to internal memory buffer with an allocator e.g.
37
+ BytearrayContext, CLBuffer, ...
38
+ - Data types: define users define types to create and manipulate objects on
39
+ buffers and generate C api, e.g. Float64, Int64, String, Struct, Array, Union
@@ -0,0 +1,10 @@
1
+ # XObjects
2
+
3
+ Library to create and manipulate serialized object in CPU and GPU memory efficiently.
4
+
5
+ The library defines:
6
+ - Contexts: create buffers and manage computations e.g. BytearrayContext, CLContext, ...
7
+ - Buffers: reference to internal memory buffer with an allocator e.g.
8
+ BytearrayContext, CLBuffer, ...
9
+ - Data types: define users define types to create and manipulate objects on
10
+ buffers and generate C api, e.g. Float64, Int64, String, Struct, Array, Union
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=77.0"
4
+ ]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "xobjects"
9
+ dynamic = ["version"]
10
+ description = "In-memory serialization and code generator for CPU and GPU"
11
+ readme = "README.md"
12
+ authors = [
13
+ { name = "Riccardo De Maria", email = "riccardo.de.maria@cern.ch" }
14
+ ]
15
+ license = "Apache-2.0"
16
+ requires-python = ">=3.9"
17
+ dependencies = [
18
+ "numpy",
19
+ "cffi",
20
+ "scipy"
21
+ ]
22
+ [project.urls]
23
+ Homepage = "https://xsuite.readthedocs.io/"
24
+ "Bug Tracker" = "https://github.com/xsuite/xsuite/issues"
25
+ Documentation = "https://xsuite.readthedocs.io/"
26
+ "Source Code" = "https://github.com/xsuite/xobjects"
27
+ "Download" = "https://pypi.python.org/pypi/xobjects"
28
+
29
+ [project.optional-dependencies]
30
+ tests = ["pytest", "pytest-mock"]
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["."]
34
+ include = ["xobjects*"]
35
+
36
+ [tool.setuptools.dynamic]
37
+ version = {attr = "xobjects._version.__version__"}
38
+
39
+ [tool.black]
40
+ line-length = 79
41
+ target-version = ['py310', 'py311', 'py312']
42
+ include = '\.pyi?$'
43
+
44
+ [project.entry-points.xobjects]
45
+ include = "xobjects"
46
+
47
+ [pytest]
48
+ markers = [
49
+ "context_dependent: marks test as one that depends on the execution context",
50
+ ]
@@ -11,7 +11,7 @@ import numpy as np
11
11
  import cffi
12
12
 
13
13
  import xobjects as xo
14
-
14
+ from xobjects.test_helpers import for_all_test_contexts
15
15
 
16
16
  ffi = cffi.FFI()
17
17
 
@@ -158,6 +158,70 @@ def test_array_dynamic_type_init_get_set(array_cls, example_shape):
158
158
  assert arr[ii].field1[idx_in_field] == 13 * vv
159
159
 
160
160
 
161
+ @for_all_test_contexts
162
+ @pytest.mark.parametrize(
163
+ "array_type",
164
+ [
165
+ xo.UInt64[3, 5, 7],
166
+ xo.UInt64[:, :, :],
167
+ xo.UInt64[:, 5, :],
168
+ ],
169
+ )
170
+ def test_array_get_shape(test_context, array_type):
171
+ source = """
172
+ #include "xobjects/headers/common.h"
173
+
174
+ GPUKERN void get_nd_and_shape(
175
+ ARRAY_TYPE arr,
176
+ GPUGLMEM int64_t* out_nd,
177
+ GPUGLMEM int64_t* out_shape
178
+ ) {
179
+ *out_nd = ARRAY_TYPE_nd(arr);
180
+ ARRAY_TYPE_shape(arr, out_shape);
181
+ }
182
+ """.replace(
183
+ "ARRAY_TYPE", array_type.__name__
184
+ )
185
+
186
+ kernels = {
187
+ "get_nd_and_shape": xo.Kernel(
188
+ c_name="get_nd_and_shape",
189
+ args=[
190
+ xo.Arg(array_type, name="arr"),
191
+ xo.Arg(xo.Int64, pointer=True, name="out_nd"),
192
+ xo.Arg(xo.Int64, pointer=True, name="out_shape"),
193
+ ],
194
+ ),
195
+ }
196
+
197
+ test_context.add_kernels(
198
+ sources=[source],
199
+ kernels=kernels,
200
+ )
201
+
202
+ instance = array_type(
203
+ np.array(range(3 * 5 * 7)).reshape((3, 5, 7)),
204
+ _context=test_context,
205
+ )
206
+
207
+ expected_nd = 3
208
+ result_nd = test_context.zeros((1,), dtype=np.int64)
209
+
210
+ expected_shape = [3, 5, 7]
211
+ result_shape = test_context.zeros((expected_nd,), dtype=np.int64)
212
+
213
+ test_context.kernels.get_nd_and_shape(
214
+ arr=instance,
215
+ out_nd=result_nd,
216
+ out_shape=result_shape,
217
+ )
218
+
219
+ assert result_nd[0] == expected_nd
220
+ assert result_shape[0] == expected_shape[0]
221
+ assert result_shape[1] == expected_shape[1]
222
+ assert result_shape[2] == expected_shape[2]
223
+
224
+
161
225
  def test_struct1():
162
226
  kernels = Struct1._gen_kernels()
163
227
  ctx = xo.ContextCpu()
@@ -539,45 +603,190 @@ def test_getp1_dyn_length_dyn_type_string_array():
539
603
  assert ord(ffi.cast("char *", s2)[8 + ii]) == ch
540
604
 
541
605
 
542
- def test_gpu_api():
543
- for ctx in xo.context.get_test_contexts():
544
- src_code = """
545
- /*gpufun*/
546
- void myfun(double x, double y,
547
- double* z){
548
- z[0] = x * y;
549
- }
606
+ @for_all_test_contexts
607
+ def test_gpu_api(test_context):
608
+ src_code = """
609
+ /*gpufun*/
610
+ void myfun(double x, double y,
611
+ double* z){
612
+ z[0] = x * y;
613
+ }
550
614
 
551
- /*gpukern*/
552
- void my_mul(const int n,
553
- /*gpuglmem*/ const double* x1,
554
- /*gpuglmem*/ const double* x2,
555
- /*gpuglmem*/ double* y) {
556
- int tid = 0 //vectorize_over tid n
557
- double z;
558
- myfun(x1[tid], x2[tid], &z);
559
- y[tid] = z;
560
- //end_vectorize
615
+ /*gpukern*/
616
+ void my_mul(const int n,
617
+ /*gpuglmem*/ const double* x1,
618
+ /*gpuglmem*/ const double* x2,
619
+ /*gpuglmem*/ double* y) {
620
+ int tid = 0 //vectorize_over tid n
621
+ double z;
622
+ myfun(x1[tid], x2[tid], &z);
623
+ y[tid] = z;
624
+ //end_vectorize
625
+ }
626
+ """
627
+
628
+ kernel_descriptions = {
629
+ "my_mul": xo.Kernel(
630
+ args=[
631
+ xo.Arg(xo.Int32, name="n"),
632
+ xo.Arg(xo.Float64, pointer=True, const=True, name="x1"),
633
+ xo.Arg(xo.Float64, pointer=True, const=True, name="x2"),
634
+ xo.Arg(xo.Float64, pointer=True, const=False, name="y"),
635
+ ],
636
+ n_threads="n",
637
+ ),
638
+ }
639
+
640
+ test_context.add_kernels(
641
+ sources=[src_code],
642
+ kernels=kernel_descriptions,
643
+ save_source_as=None,
644
+ compile=True,
645
+ extra_classes=[xo.String[:]],
646
+ )
647
+
648
+
649
+ @for_all_test_contexts
650
+ def test_array_of_arrays(test_context):
651
+ cell_ids = [3, 5, 7]
652
+ particle_per_cell = [
653
+ [1, 8],
654
+ [9, 3, 2],
655
+ [4, 5, 6, 7],
656
+ ]
657
+
658
+ class Cells(xo.Struct):
659
+ ids = xo.Int64[:]
660
+ particles = xo.Int64[:][:]
661
+
662
+ cells = Cells(
663
+ ids=cell_ids, particles=particle_per_cell, _context=test_context
664
+ )
665
+
666
+ # Data layout (displayed as uint64):
667
+ #
668
+ # [0] 216 (cells size)
669
+ # [8] 56 (offset field 2 -- particles field)
670
+ # [16] cell_ids data:
671
+ # [0] 40 (cell_ids size)
672
+ # [8] 3 (cell_ids length)
673
+ # [16] {3, 5, 7} (cell_ids elements)
674
+ # [56] particles data:
675
+ # [0] 160 (particles size)
676
+ # [8] 3 (particles length)
677
+ # [16] 40 (offset particles[0])
678
+ # [24] 72 (offset particles[1])
679
+ # [32] 112 (offset particles[2])
680
+ # [40] particles[0] data:
681
+ # [0] 32 (particles[0] size)
682
+ # [8] 2 (particles[0] length)
683
+ # [16] {1, 8} (particles[0] elements)
684
+ # [72] particles[1] data:
685
+ # [0] 40 (particles[1] size)
686
+ # [8] 3 (particles[1] length)
687
+ # [16] {9, 3, 2} (particles[1
688
+ # [112] particles[2] data:
689
+ # [0] 48 (particles[2] size)
690
+ # [8] 4 (particles[2] length)
691
+ # [16] {4, 5, 6, 7} (particles[2] elements)
692
+
693
+ src = r"""
694
+ #include "xobjects/headers/common.h"
695
+
696
+ static const int MAX_PARTICLES = 4;
697
+ static const int MAX_CELLS = 3;
698
+
699
+ GPUKERN void loop_over(
700
+ Cells cells,
701
+ GPUGLMEM uint64_t* out_counts,
702
+ GPUGLMEM uint64_t* out_vals,
703
+ GPUGLMEM uint8_t* success
704
+ )
705
+ {
706
+ int64_t num_cells = Cells_len_ids(cells);
707
+
708
+ for (int64_t i = 0; i < num_cells; i++) {
709
+ int64_t id = Cells_get_ids(cells, i);
710
+ int64_t count = Cells_len1_particles(cells, i);
711
+
712
+ if (i >= MAX_CELLS) {
713
+ *success = 0;
714
+ continue;
561
715
  }
562
- """
563
-
564
- kernel_descriptions = {
565
- "my_mul": xo.Kernel(
566
- args=[
567
- xo.Arg(xo.Int32, name="n"),
568
- xo.Arg(xo.Float64, pointer=True, const=True, name="x1"),
569
- xo.Arg(xo.Float64, pointer=True, const=True, name="x2"),
570
- xo.Arg(xo.Float64, pointer=True, const=False, name="y"),
571
- ],
572
- n_threads="n",
573
- ),
716
+
717
+ out_counts[i] = count;
718
+
719
+ ArrNInt64 particles = Cells_getp1_particles(cells, i);
720
+ uint32_t num_particles = ArrNInt64_len(particles);
721
+
722
+ VECTORIZE_OVER(j, num_particles);
723
+ int64_t val = ArrNInt64_get(particles, j);
724
+
725
+ if (j >= MAX_PARTICLES) {
726
+ *success = 0;
727
+ } else {
728
+ out_vals[i * MAX_PARTICLES + j] = val;
729
+ }
730
+ END_VECTORIZE;
574
731
  }
732
+ }
733
+
734
+ GPUKERN void kernel_Cells_get_particles(
735
+ Cells obj,
736
+ int64_t i0,
737
+ int64_t i1,
738
+ GPUGLMEM int64_t* out
739
+ ) {
740
+ *out = Cells_get_particles(obj, i0, i1);
741
+ }
742
+ """
743
+
744
+ kernels = {
745
+ "loop_over": xo.Kernel(
746
+ args=[
747
+ xo.Arg(Cells, name="cells"),
748
+ xo.Arg(xo.UInt64, pointer=True, name="out_counts"),
749
+ xo.Arg(xo.UInt64, pointer=True, name="out_vals"),
750
+ xo.Arg(xo.UInt8, pointer=True, name="success"),
751
+ ],
752
+ n_threads=4,
753
+ ),
754
+ "kernel_Cells_get_particles": xo.Kernel(
755
+ args=[
756
+ xo.Arg(Cells, name="obj"),
757
+ xo.Arg(xo.Int64, name="i0"),
758
+ xo.Arg(xo.Int64, name="i1"),
759
+ xo.Arg(xo.Int64, pointer=True, name="out"),
760
+ ],
761
+ ),
762
+ }
763
+
764
+ test_context.add_kernels(
765
+ sources=[src],
766
+ kernels=kernels,
767
+ )
575
768
 
576
- ctx.add_kernels(
577
- sources=[src_code],
578
- kernels=kernel_descriptions,
579
- # save_src_as=f'_test_{name}.c')
580
- save_source_as=None,
581
- compile=True,
582
- extra_classes=[xo.String[:]],
583
- )
769
+ counts = test_context.zeros(len(cell_ids), dtype=np.uint64)
770
+ vals = test_context.zeros(12, dtype=np.uint64)
771
+ success = test_context.zeros((1,), dtype=np.uint8) + 1
772
+
773
+ for i, _ in enumerate(particle_per_cell):
774
+ for j, expected in enumerate(particle_per_cell[i]):
775
+ result = test_context.zeros(shape=(1,), dtype=np.int64)
776
+ test_context.kernels.kernel_Cells_get_particles(
777
+ obj=cells, i0=i, i1=j, out=result
778
+ )
779
+ assert result[0] == expected
780
+
781
+ test_context.kernels.loop_over(
782
+ cells=cells,
783
+ out_counts=counts,
784
+ out_vals=vals,
785
+ success=success,
786
+ )
787
+ counts = test_context.nparray_from_context_array(counts)
788
+ vals = test_context.nparray_from_context_array(vals)
789
+
790
+ assert success[0] == 1
791
+ assert np.all(counts == [2, 3, 4])
792
+ assert np.all(vals == [1, 8, 0, 0, 9, 3, 2, 0, 4, 5, 6, 7])
@@ -0,0 +1,179 @@
1
+ # copyright ################################# #
2
+ # This file is part of the Xobjects Package. #
3
+ # Copyright (c) CERN, 2025. #
4
+ # ########################################### #
5
+
6
+ import xobjects as xo
7
+ import numpy as np
8
+ import pytest
9
+
10
+ from xobjects.test_helpers import for_all_test_contexts
11
+
12
+
13
+ @for_all_test_contexts
14
+ def test_common_atomicadd(test_context):
15
+ src = r"""
16
+ #include "xobjects/headers/common.h"
17
+ #include "xobjects/headers/atomicadd.h"
18
+
19
+ GPUKERN void test_atomic_add(GPUGLMEM double* out, int32_t iterations)
20
+ {
21
+ VECTORIZE_OVER(i, iterations);
22
+ // If on CPU do some work to avoid the loop being optimized out
23
+ #if defined(XO_CONTEXT_CPU_OPENMP)
24
+ usleep(10);
25
+ #endif
26
+ atomicAdd(out, 1.0);
27
+ END_VECTORIZE;
28
+ }
29
+ """
30
+
31
+ n_threads = 1
32
+ if type(test_context).__name__ in {"ContextCupy", "ContextPyopencl"}:
33
+ n_threads = 1000
34
+ elif (
35
+ test_context.omp_num_threads == "auto"
36
+ or test_context.omp_num_threads > 1
37
+ ):
38
+ n_threads = 8
39
+
40
+ test_context.add_kernels(
41
+ sources=[src],
42
+ kernels={
43
+ "test_atomic_add": xo.Kernel(
44
+ c_name="test_atomic_add",
45
+ args=[
46
+ xo.Arg(xo.Float64, pointer=True, name="out"),
47
+ xo.Arg(xo.Int32, name="iterations"),
48
+ ],
49
+ n_threads=n_threads,
50
+ )
51
+ },
52
+ )
53
+
54
+ expected = 1000
55
+ result = np.array([0], dtype=np.float64)
56
+ result_ctx = test_context.nparray_to_context_array(result)
57
+ test_context.kernels.test_atomic_add(out=result_ctx, iterations=expected)
58
+ result = test_context.nparray_from_context_array(result_ctx)
59
+
60
+ assert result == expected
61
+
62
+
63
+ @for_all_test_contexts
64
+ @pytest.mark.parametrize(
65
+ "overload", [True, False], ids=["overload", "no_overload"]
66
+ )
67
+ @pytest.mark.parametrize(
68
+ "ctype",
69
+ [
70
+ xo.Int8,
71
+ xo.Int16,
72
+ xo.Int32,
73
+ xo.Int64,
74
+ xo.UInt8,
75
+ xo.UInt16,
76
+ xo.UInt32,
77
+ xo.UInt64,
78
+ xo.Float32,
79
+ xo.Float64,
80
+ ],
81
+ )
82
+ def test_atomic(overload, ctype, test_context):
83
+ if overload:
84
+ func_name = "atomicAdd"
85
+ else:
86
+ func_name = f'atomicAdd_{ctype.__name__.lower()[0]}{ctype.__name__.split("t")[1]}'
87
+
88
+ class TestAtomic(xo.Struct):
89
+ val = ctype
90
+ _extra_c_sources = [
91
+ f"""
92
+ #include "xobjects/headers/common.h"
93
+ #include "xobjects/headers/atomicadd.h"
94
+
95
+ GPUKERN
96
+ void run_atomic_test(TestAtomic el, GPUGLMEM {ctype._c_type}* increments,
97
+ GPUGLMEM {ctype._c_type}* retvals, int length) {{
98
+ VECTORIZE_OVER(ii, length);
99
+ GPUGLMEM {ctype._c_type}* val = TestAtomic_getp_val(el);
100
+ {ctype._c_type} ret = {func_name}(val, increments[ii]);
101
+ retvals[ii] = ret;
102
+ END_VECTORIZE;
103
+ }}
104
+ """
105
+ ]
106
+
107
+ kernels = {
108
+ "run_atomic_test": xo.Kernel(
109
+ c_name="run_atomic_test",
110
+ args=[
111
+ xo.Arg(TestAtomic, name="el"),
112
+ xo.Arg(ctype, pointer=True, name="increments"),
113
+ xo.Arg(ctype, pointer=True, name="retvals"),
114
+ xo.Arg(xo.Int32, name="length"),
115
+ ],
116
+ n_threads="length",
117
+ )
118
+ }
119
+
120
+ atomic = TestAtomic(_context=test_context, val=0)
121
+ assert atomic.val == 0
122
+
123
+ test_context.add_kernels(kernels=kernels)
124
+
125
+ # Test with all increments = 1, so we can check the return values easily.
126
+ num_steps = 10000
127
+ if ctype.__name__.startswith("Int") or ctype.__name__.startswith("Uint"):
128
+ # Less steps to avoid overflow
129
+ num_steps = min(num_steps, 2 ** (8 * ctype._size - 1) - 1)
130
+ increments = test_context.zeros(shape=(num_steps,), dtype=ctype._dtype) + 1
131
+ retvals = test_context.zeros(shape=(num_steps,), dtype=ctype._dtype)
132
+ test_context.kernels.run_atomic_test(
133
+ el=atomic, increments=increments, retvals=retvals, length=num_steps
134
+ )
135
+ assert atomic.val == num_steps
136
+ retvals = np.sort(test_context.nparray_from_context_array(retvals))
137
+ assert np.allclose(
138
+ retvals,
139
+ np.arange(num_steps, dtype=ctype._dtype),
140
+ atol=1.0e-15,
141
+ rtol=1.0e-15,
142
+ )
143
+
144
+ # Test with random increments, where we now only can check the total sum
145
+ # (retvals can be anything). Watch out: overflow is undefined behaviour,
146
+ # except for unsigned integers, so we skip this test for signed integers.
147
+ atomic.val = 0
148
+ retvals = test_context.zeros(shape=(num_steps,), dtype=ctype._dtype)
149
+ if ctype.__name__.startswith("Uint"):
150
+ low = 0
151
+ high = 2 ** (8 * ctype._size) - 1
152
+ increments = np.random.randint(
153
+ low, high + 1, size=num_steps, dtype=ctype._dtype
154
+ )
155
+ increments = test_context.nparray_to_context_array(increments)
156
+ test_context.kernels.run_atomic_test(
157
+ el=atomic, increments=increments, retvals=retvals, length=num_steps
158
+ )
159
+ increments = test_context.nparray_from_context_array(increments)
160
+ assert atomic.val == (
161
+ np.sum(increments).item() % (2 ** (8 * ctype._size))
162
+ )
163
+
164
+ elif ctype.__name__.startswith("Float"):
165
+ increments = np.zeros(shape=(num_steps,), dtype=ctype._dtype)
166
+ increments += np.random.uniform(0, 10, size=num_steps)
167
+ increments = test_context.nparray_to_context_array(increments)
168
+ test_context.kernels.run_atomic_test(
169
+ el=atomic, increments=increments, retvals=retvals, length=num_steps
170
+ )
171
+ increments = test_context.nparray_from_context_array(increments)
172
+ if ctype == xo.Float32:
173
+ assert np.isclose(
174
+ atomic.val, np.sum(increments), atol=10.0, rtol=1.0e-4
175
+ )
176
+ else:
177
+ assert np.isclose(
178
+ atomic.val, np.sum(increments), atol=1.0e-6, rtol=1.0e-12
179
+ )
@@ -53,7 +53,7 @@ def test_shared_memory():
53
53
 
54
54
  // sum s[0] += s[1]
55
55
  if (tid == 0){
56
- sdata[tid] += sdata[tid + 1];
56
+ sdata[tid] += sdata[tid + 1];
57
57
 
58
58
  // write sum from shared to global mem
59
59
  atomicAdd(&result[tid], sdata[tid]);
@@ -0,0 +1 @@
1
+ __version__ = "0.5.9"
@@ -5,7 +5,7 @@
5
5
 
6
6
  from .context import Kernel, Arg
7
7
 
8
- from .scalar import Int64, Void, Int8, is_scalar
8
+ from .scalar import UInt32, Int64, Void, is_scalar
9
9
  from .struct import is_field, is_struct
10
10
  from .array import is_index, is_array
11
11
  from .ref import is_unionref, is_ref
@@ -103,9 +103,10 @@ def get_layers(parts):
103
103
 
104
104
 
105
105
  def int_from_obj(offset, conf):
106
- inttype = gen_pointer(conf.get("inttype", "int64_t") + "*", conf)
107
- chartype = gen_pointer(conf.get("chartype", "char") + "*", conf)
108
- return f"*({inttype})(({chartype}) obj+{offset})"
106
+ """Generate code to read the integer at location obj + offset (bytes)."""
107
+ int_pointer_type = gen_pointer(conf.get("inttype", "int64_t") + "*", conf)
108
+ char_pointer_type = gen_pointer(conf.get("chartype", "char") + "*", conf)
109
+ return f"*({int_pointer_type})(({char_pointer_type}) obj+{offset})"
109
110
 
110
111
 
111
112
  def Field_get_c_offset(self, conf):
@@ -146,7 +147,7 @@ def Index_get_c_offset(part, conf, icount):
146
147
  out.append(f" offset+={soffset};")
147
148
  else:
148
149
  lookup_field_offset = f"offset+{soffset}"
149
- out.append(f" offset={int_from_obj(lookup_field_offset, conf)};")
150
+ out.append(f" offset+={int_from_obj(lookup_field_offset, conf)};")
150
151
  return out
151
152
 
152
153
 
@@ -206,16 +207,17 @@ def gen_fun_kernel(cls, path, action, const, extra, ret, add_nindex=False):
206
207
  def gen_c_pointed(target: Arg, conf):
207
208
  size = gen_c_size_from_arg(target, conf)
208
209
  ret = gen_c_type_from_arg(target, conf)
210
+
209
211
  if target.pointer or is_compound(target.atype) or is_string(target.atype):
210
212
  chartype = gen_pointer(conf.get("chartype", "char") + "*", conf)
211
213
  return f"({ret})(({chartype}) obj+offset)"
212
- else:
213
- rettype = gen_pointer(ret + "*", conf)
214
- if size == 1:
215
- return f"*(({rettype}) obj+offset)"
216
- else:
217
- chartype = gen_pointer(conf.get("chartype", "char") + "*", conf)
218
- return f"*({rettype})(({chartype}) obj+offset)"
214
+
215
+ rettype = gen_pointer(ret + "*", conf)
216
+ if size == 1:
217
+ return f"*(({rettype}) obj+offset)"
218
+
219
+ chartype = gen_pointer(conf.get("chartype", "char") + "*", conf)
220
+ return f"*({rettype})(({chartype}) obj+offset)"
219
221
 
220
222
 
221
223
  def gen_method_get(cls, path, conf):
@@ -364,13 +366,74 @@ def gen_method_size(cls, path, conf):
364
366
 
365
367
 
366
368
  def gen_method_shape(cls, path, conf):
367
- "return shape in an array"
368
- return None, None
369
+ array_type = path[-1]
370
+
371
+ out_shape_arg = Arg(Int64, name="out_shape", pointer=True)
372
+
373
+ kernel = gen_fun_kernel(
374
+ cls,
375
+ path,
376
+ const=False,
377
+ action="shape",
378
+ extra=[out_shape_arg],
379
+ ret=None,
380
+ add_nindex=True,
381
+ )
382
+ decl = gen_c_decl_from_kernel(kernel, conf)
383
+
384
+ lst = [decl + "{"]
385
+
386
+ nd = len(array_type._shape)
387
+
388
+ if array_type._is_static_shape:
389
+ terms = [str(dim) for dim in array_type._shape]
390
+ else:
391
+ lst.append(gen_method_offset(path, conf))
392
+ arrarg = Arg(Int64, pointer=True)
393
+ pointed = gen_c_pointed(arrarg, conf)
394
+ typearr = gen_pointer("int64_t*", conf)
395
+ lst.append(f" {typearr} arr = {pointed};")
396
+ dim_len_idx = 1
397
+ terms = []
398
+ for dim_len in array_type._shape:
399
+ if dim_len:
400
+ terms.append(str(dim_len))
401
+ else:
402
+ terms.append(f"arr[{dim_len_idx}]")
403
+ dim_len_idx += 1
404
+
405
+ for dim_idx in range(nd):
406
+ lst.append(f" out_shape[{dim_idx}] = {terms[dim_idx]};")
407
+
408
+ lst.append("}")
409
+
410
+ return "\n".join(lst), kernel
369
411
 
370
412
 
371
413
  def gen_method_nd(cls, path, conf):
372
414
  "return length of shape"
373
- return None, None
415
+ array_type = path[-1]
416
+
417
+ retarg = Arg(UInt32)
418
+
419
+ kernel = gen_fun_kernel(
420
+ cls,
421
+ path,
422
+ const=False,
423
+ action="nd",
424
+ extra=[],
425
+ ret=retarg,
426
+ add_nindex=True,
427
+ )
428
+ decl = gen_c_decl_from_kernel(kernel, conf)
429
+
430
+ lst = [decl + "{"]
431
+
432
+ nd = len(array_type._shape)
433
+ lst.append(f" return {nd};")
434
+ lst.append("}")
435
+
436
+ return "\n".join(lst), kernel
374
437
 
375
438
 
376
439
  def gen_method_strides(cls, path, conf):
@@ -507,8 +570,8 @@ def methods_from_path(cls, path, conf):
507
570
 
508
571
  if is_array(lasttype):
509
572
  out.append(gen_method_len(cls, path, conf))
510
- # out.append(gen_method_shape(cls, path, conf))
511
- # out.append(gen_method_nd(cls, path, conf))
573
+ out.append(gen_method_shape(cls, path, conf))
574
+ out.append(gen_method_nd(cls, path, conf))
512
575
  # out.append(gen_method_strides(cls, path, conf))
513
576
  # out.append(gen_method_getpos(cls, path, conf))
514
577
 
@@ -375,12 +375,30 @@ class XContext(ABC):
375
375
  return sources
376
376
 
377
377
  @abstractmethod
378
- def nparray_to_context_array(self, arr):
379
- return arr
378
+ def nparray_to_context_array(self, arr, copy=False):
379
+ """Obtain an array on the context, given a numpy array.
380
+
381
+ Args:
382
+ arr: numpy array
383
+ copy: if True, always create a copy in the context. If False,
384
+ try to avoid copy if possible (not guaranteed).
385
+
386
+ Returns:
387
+ array on the context
388
+ """
380
389
 
381
390
  @abstractmethod
382
- def nparray_from_context_array(self, dev_arr):
383
- return dev_arr
391
+ def nparray_from_context_array(self, dev_arr, copy=False):
392
+ """Obtain a numpy array, given an array on the context.
393
+
394
+ Args:
395
+ arr: array on the context
396
+ copy: if True, always create a copy in the context. If False,
397
+ try to avoid copy if possible (not guaranteed).
398
+
399
+ Returns:
400
+ Numpy array
401
+ """
384
402
 
385
403
  @property
386
404
  @abstractmethod
@@ -650,14 +668,15 @@ def get_context_from_string(ctxstr):
650
668
 
651
669
  if ctxstr is None:
652
670
  return xo.ContextCpu()
671
+
672
+ ll = ctxstr.split(":")
673
+ if len(ll) <= 1:
674
+ ctxtype = ll[0]
675
+ option = []
653
676
  else:
654
- ll = ctxstr.split(":")
655
- if len(ll) <= 1:
656
- ctxtype = ll[0]
657
- option = []
658
- else:
659
- ctxtype, options = ctxstr.split(":")
660
- option = options.split(",")
677
+ ctxtype, options = ctxstr.split(":")
678
+ option = options.split(",")
679
+
661
680
  if ctxtype == "ContextCpu":
662
681
  if len(option) == 0:
663
682
  return xo.ContextCpu()
@@ -543,7 +543,7 @@ class ContextCpu(XContext):
543
543
 
544
544
  return None
545
545
 
546
- def nparray_to_context_array(self, arr):
546
+ def nparray_to_context_array(self, arr, copy=False):
547
547
  """
548
548
  Moves a numpy array to the device memory. No action is performed by
549
549
  this function in the CPU context. The method is provided
@@ -551,14 +551,16 @@ class ContextCpu(XContext):
551
551
 
552
552
  Args:
553
553
  arr (numpy.ndarray): Array to be transferred
554
+ copy (bool): If True, a copy of the array is made.
554
555
 
555
556
  Returns:
556
- numpy.ndarray: The same array (no copy!).
557
-
557
+ numpy.ndarray: Numpy array with the same data, original or a copy.
558
558
  """
559
+ if copy:
560
+ arr = np.copy(arr)
559
561
  return arr
560
562
 
561
- def nparray_from_context_array(self, dev_arr):
563
+ def nparray_from_context_array(self, dev_arr, copy=False):
562
564
  """
563
565
  Moves an array to the device to a numpy array. No action is performed by
564
566
  this function in the CPU context. The method is provided so that the CPU
@@ -566,10 +568,13 @@ class ContextCpu(XContext):
566
568
 
567
569
  Args:
568
570
  dev_arr (numpy.ndarray): Array to be transferred
569
- Returns:
570
- numpy.ndarray: The same array (no copy!)
571
+ copy (bool): If True, a copy of the array is made.
571
572
 
573
+ Returns:
574
+ numpy.ndarray: Numpy array with the same data, original or a copy.
572
575
  """
576
+ if copy:
577
+ dev_arr = np.copy(dev_arr)
573
578
  return dev_arr
574
579
 
575
580
  @property
@@ -579,7 +584,6 @@ class ContextCpu(XContext):
579
584
  through ``nplike_lib`` to keep compatibility with the other contexts.
580
585
 
581
586
  """
582
-
583
587
  return np
584
588
 
585
589
  @property
@@ -589,7 +593,6 @@ class ContextCpu(XContext):
589
593
  through ``splike_lib`` to keep compatibility with the other contexts.
590
594
 
591
595
  """
592
-
593
596
  return sp
594
597
 
595
598
  def synchronize(self):
@@ -487,29 +487,32 @@ class ContextCupy(XContext):
487
487
  def __str__(self):
488
488
  return f"{type(self).__name__}:{cupy.cuda.get_device_id()}"
489
489
 
490
- def nparray_to_context_array(self, arr):
490
+ def nparray_to_context_array(self, arr, copy=False):
491
491
  """
492
492
  Copies a numpy array to the device memory.
493
493
 
494
494
  Args:
495
495
  arr (numpy.ndarray): Array to be transferred
496
+ copy (bool): This parameter is ignored for CUDA, as the data lives
497
+ on a different device.
496
498
 
497
499
  Returns:
498
500
  cupy.ndarray:The same array copied to the device.
499
-
500
501
  """
501
502
  dev_arr = cupy.array(arr)
502
503
  return dev_arr
503
504
 
504
- def nparray_from_context_array(self, dev_arr):
505
+ def nparray_from_context_array(self, dev_arr, copy=False):
505
506
  """
506
507
  Copies an array to the device to a numpy array.
507
508
 
508
509
  Args:
509
510
  dev_arr (cupy.ndarray): Array to be transferred.
511
+ copy (bool): This parameter is ignored for CUDA, as the data lives
512
+ on a different device.
513
+
510
514
  Returns:
511
515
  numpy.ndarray: The same data copied to a numpy array.
512
-
513
516
  """
514
517
  return dev_arr.get()
515
518
 
@@ -81,6 +81,8 @@ if _enabled:
81
81
 
82
82
 
83
83
  class ContextPyopencl(XContext):
84
+ context_cache = {}
85
+
84
86
  @property
85
87
  def nplike_array_type(self):
86
88
  return cla.Array
@@ -128,20 +130,29 @@ class ContextPyopencl(XContext):
128
130
  super().__init__()
129
131
 
130
132
  # TODO assume one device only
131
- if device is None:
133
+ if device in self.context_cache:
134
+ self.platform, self.device, self.context = self.context_cache[
135
+ device
136
+ ]
137
+ elif device is None:
132
138
  self.context = cl.create_some_context(interactive=False)
133
139
  self.device = self.context.devices[0]
134
140
  self.platform = self.device.platform
135
141
  else:
136
142
  if isinstance(device, str):
137
- platform, device = map(int, device.split("."))
143
+ platform, device_ = map(int, device.split("."))
138
144
  self.platform = cl.get_platforms()[platform]
139
- self.device = self.platform.get_devices()[device]
145
+ self.device = self.platform.get_devices()[device_]
140
146
  else:
141
147
  self.device = device
142
148
  self.platform = device.platform
143
149
 
144
150
  self.context = cl.Context([self.device])
151
+ self.context_cache[device] = (
152
+ self.platform,
153
+ self.device,
154
+ self.context,
155
+ )
145
156
 
146
157
  self.queue = cl.CommandQueue(self.context)
147
158
 
@@ -253,28 +264,30 @@ class ContextPyopencl(XContext):
253
264
  device_id = self.platform.get_devices().index(self.device)
254
265
  return f"{type(self).__name__}:{platform_id}.{device_id}"
255
266
 
256
- def nparray_to_context_array(self, arr):
257
- """
258
- Copies a numpy array to the device memory.
267
+ def nparray_to_context_array(self, arr, copy=False):
268
+ """Copies a numpy array to the device memory.
269
+
259
270
  Args:
260
271
  arr (numpy.ndarray): Array to be transferred
272
+ copy (bool): This parameter is ignored for OpenCL, as the data lives
273
+ on a different device.
261
274
 
262
275
  Returns:
263
276
  pyopencl.array.Array:The same array copied to the device.
264
-
265
277
  """
266
278
  dev_arr = cla.to_device(self.queue, arr)
267
279
  return dev_arr
268
280
 
269
- def nparray_from_context_array(self, dev_arr):
270
- """
271
- Copies an array to the device to a numpy array.
281
+ def nparray_from_context_array(self, dev_arr, copy=False):
282
+ """Copies an array to the device to a numpy array.
272
283
 
273
284
  Args:
274
285
  dev_arr (pyopencl.array.Array): Array to be transferred.
286
+ copy (bool): This parameter is ignored for OpenCL, as the data lives
287
+ on a different device.
288
+
275
289
  Returns:
276
290
  numpy.ndarray: The same data copied to a numpy array.
277
-
278
291
  """
279
292
  return dev_arr.get()
280
293
 
@@ -18,9 +18,8 @@ _print = Print()
18
18
 
19
19
 
20
20
  def assert_allclose(a, b, rtol=0, atol=0, max_outliers=0):
21
-
22
21
  try:
23
- if a == b: # if this passes we return without raising
22
+ if a == b: # if this passes we return without raising
24
23
  return
25
24
  except:
26
25
  pass
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: xobjects
3
+ Version: 0.5.9
4
+ Summary: In-memory serialization and code generator for CPU and GPU
5
+ Home-page: https://xsuite.readthedocs.io/
6
+ Download-URL: https://pypi.python.org/pypi/xobjects
7
+ Author: Riccardo De Maria
8
+ Author-email: Riccardo De Maria <riccardo.de.maria@cern.ch>
9
+ License-Expression: Apache-2.0
10
+ Project-URL: Homepage, https://xsuite.readthedocs.io/
11
+ Project-URL: Bug Tracker, https://github.com/xsuite/xsuite/issues
12
+ Project-URL: Documentation, https://xsuite.readthedocs.io/
13
+ Project-URL: Source Code, https://github.com/xsuite/xobjects
14
+ Project-URL: Download, https://pypi.python.org/pypi/xobjects
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy
19
+ Requires-Dist: cffi
20
+ Requires-Dist: scipy
21
+ Provides-Extra: tests
22
+ Requires-Dist: pytest; extra == "tests"
23
+ Requires-Dist: pytest-mock; extra == "tests"
24
+ Dynamic: author
25
+ Dynamic: download-url
26
+ Dynamic: home-page
27
+ Dynamic: license-file
28
+ Dynamic: requires-python
29
+
30
+ # XObjects
31
+
32
+ Library to create and manipulate serialized object in CPU and GPU memory efficiently.
33
+
34
+ The library defines:
35
+ - Contexts: create buffers and manage computations e.g. BytearrayContext, CLContext, ...
36
+ - Buffers: reference to internal memory buffer with an allocator e.g.
37
+ BytearrayContext, CLBuffer, ...
38
+ - Data types: define users define types to create and manipulate objects on
39
+ buffers and generate C api, e.g. Float64, Int64, String, Struct, Array, Union
@@ -1,4 +1,5 @@
1
1
  LICENSE
2
+ README.md
2
3
  pyproject.toml
3
4
  setup.cfg
4
5
  setup.py
@@ -7,6 +8,7 @@ tests/test_array.py
7
8
  tests/test_buffer.py
8
9
  tests/test_capi.py
9
10
  tests/test_chunk.py
11
+ tests/test_common.py
10
12
  tests/test_context_opencl.py
11
13
  tests/test_hybrid_class.py
12
14
  tests/test_kernel.py
@@ -44,5 +46,6 @@ xobjects/union.py
44
46
  xobjects.egg-info/PKG-INFO
45
47
  xobjects.egg-info/SOURCES.txt
46
48
  xobjects.egg-info/dependency_links.txt
49
+ xobjects.egg-info/entry_points.txt
47
50
  xobjects.egg-info/requires.txt
48
51
  xobjects.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [xobjects]
2
+ include = xobjects
xobjects-0.5.7/PKG-INFO DELETED
@@ -1,22 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: xobjects
3
- Version: 0.5.7
4
- Summary: In-memory serialization and code generator for CPU and GPU
5
- Home-page: https://xsuite.readthedocs.io/
6
- Download-URL: https://pypi.python.org/pypi/xobjects
7
- Author: Riccardo De Maria
8
- Author-email: riccardo.de.maria@cern.ch
9
- License: Apache 2.0
10
- Project-URL: Bug Tracker, https://github.com/xsuite/xsuite/issues
11
- Project-URL: Documentation, https://xsuite.readthedocs.io/
12
- Project-URL: Source Code, https://github.com/xsuite/xobjects
13
- Requires-Python: >=3.7
14
- License-File: LICENSE
15
- Requires-Dist: numpy
16
- Requires-Dist: cffi
17
- Requires-Dist: scipy
18
- Provides-Extra: tests
19
- Requires-Dist: pytest; extra == "tests"
20
- Requires-Dist: pytest-mock; extra == "tests"
21
-
22
- In-memory serialization and code generator for CPU and GPU
@@ -1,4 +0,0 @@
1
- [tool.black]
2
- line-length = 79
3
- target-version = ['py36', 'py37', 'py38']
4
- include = '\.pyi?$'
@@ -1 +0,0 @@
1
- __version__ = "0.5.7"
@@ -1,22 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: xobjects
3
- Version: 0.5.7
4
- Summary: In-memory serialization and code generator for CPU and GPU
5
- Home-page: https://xsuite.readthedocs.io/
6
- Download-URL: https://pypi.python.org/pypi/xobjects
7
- Author: Riccardo De Maria
8
- Author-email: riccardo.de.maria@cern.ch
9
- License: Apache 2.0
10
- Project-URL: Bug Tracker, https://github.com/xsuite/xsuite/issues
11
- Project-URL: Documentation, https://xsuite.readthedocs.io/
12
- Project-URL: Source Code, https://github.com/xsuite/xobjects
13
- Requires-Python: >=3.7
14
- License-File: LICENSE
15
- Requires-Dist: numpy
16
- Requires-Dist: cffi
17
- Requires-Dist: scipy
18
- Provides-Extra: tests
19
- Requires-Dist: pytest; extra == "tests"
20
- Requires-Dist: pytest-mock; extra == "tests"
21
-
22
- In-memory serialization and code generator for CPU and GPU
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes