tinycwrap 0.0.7__tar.gz → 0.1.2__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.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinycwrap
3
+ Version: 0.1.2
4
+ Summary: Lightweight C-to-Python wrapper generator using CFFI and NumPy
5
+ Author: TinyCWrap Contributors
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: numpy>=1.24
9
+ Requires-Dist: cffi>=1.15
10
+
11
+ # tinycwrap
12
+
13
+ TinyCWrap is a lightweight helper around CFFI that:
14
+
15
+ - compiles one or more C sources into a shared library,
16
+ - auto-generates the `cdef` from your function/struct declarations,
17
+ - builds NumPy-friendly Python wrappers that take/return arrays and structs,
18
+ - optionally hot-reloads when the C file changes (inside IPython).
19
+
20
+ ## Quick start
21
+
22
+ ```bash
23
+ pip install tinycwrap
24
+ ```
25
+
26
+ Write a small C file (only non-`static` functions are exported):
27
+
28
+ ```c
29
+ /* kernels.c */
30
+ double dot(const double *x, const double *y, int len_x)
31
+ /* Return dot product between x and y */
32
+ {
33
+ double acc = 0.0;
34
+ for (int i = 0; i < len_x; ++i)
35
+ acc += x[i] * y[i];
36
+ return acc;
37
+ }
38
+ ```
39
+
40
+ Wrap and call it from Python:
41
+
42
+ ```python
43
+ import numpy as np
44
+ from tinycwrap import CModule
45
+
46
+ cm = CModule("kernels.c") # builds the shared library, creates wrappers
47
+
48
+ x = np.arange(5, dtype=np.float64)
49
+ y = np.ones_like(x)
50
+
51
+ cm.dot(x, y) # -> 10.0, len_x auto-filled by convention
52
+ print(cm.dot.__doc__) # docstring comes from the C comment
53
+ ```
54
+
55
+ See `examples/` and `tests/` for more involved cases.
56
+
57
+ ## C coding conventions
58
+
59
+ TinyCWrap infers how to build wrappers from simple C conventions:
60
+
61
+ - **Inputs vs outputs**
62
+ - `const T *arg` -> input NumPy array.
63
+ - `T *arg` -> output/in-place array (prefer naming it `out_*` when possible).
64
+ - `T arg[N]` in the signature -> fixed-size array (input if `const`, otherwise output).
65
+ - Plain scalars (`double`, `int`, ...) -> Python scalars.
66
+ - **Length parameters**: integers named like `len_x`, `n_x`, or `size_x` are auto-filled from array `x` by convention. Otherwise pass them explicitly from Python or declare a contract (see below).
67
+ - **Docstrings**: the block comment immediately after the function header becomes the Python docstring.
68
+ - **Structs**: `typedef struct { ... } Name;` definitions in your headers/sources become Python classes with a `.dtype` and NumPy-backed storage.
69
+ - **Compilation**: extra sources can be passed (`CModule("main.c", "helper.c")`), and extra include dirs via `include_dirs=[...]`. Default compiler flags are `-O3 -shared -fPIC -march=native -mtune=native` plus the NumPy include path.
70
+
71
+ ## Contracts: tell the wrapper how to size things
72
+
73
+ Contracts are declared inside the doc comment with `Contract:` (or `Contracts:`). Separate multiple rules with semicolons. Supported forms:
74
+
75
+ - `len_x=len(x)` — mark `len_x` as the length of array `x`. If you omit `len_x` in Python, the wrapper fills it.
76
+ - `shape(out)=n,m` — allocate `out` with shape `(n, m)`. You can define `n,m=shape(a)` to capture the shape of an input first.
77
+ - `len(out)=len_x` — allocate a 1D output array.
78
+ - `postlen(out)=out_len` — slice the output after the call using an integer pointer result `out_len` (useful when the C code writes less than the allocated length).
79
+ - `own(return)` — the returned pointer is owned by the caller; the wrapper will `free` it after copying to NumPy (requires a `len(return)=...` contract).
80
+ - `expose(len_x)` — opt out of naming-convention inference for `len_x`, keeping it as a required Python argument.
81
+
82
+ TinyCWrap also infers low-risk contracts from names:
83
+
84
+ - `len_x`, `n_x`, or `size_x` are treated as `len(x)` when `x` is an array argument.
85
+ - `out_x` is allocated with `shape(x)` when input array `x` exists.
86
+ - `out_x` is allocated with `len_x`, `n_x`, or `size_x` when no input `x` exists but such a length argument does.
87
+
88
+ Explicit contracts override inferred contracts.
89
+
90
+ Examples pulled from the test suite:
91
+
92
+ ```c
93
+ void mat_add(const double *a, const double *b, int n, int m, double *out)
94
+ /* Elementwise addition
95
+ Contract: n,m=shape(a); shape(out)=n,m;
96
+ */
97
+ ```
98
+
99
+ ```c
100
+ void merge_sorted(const double *a, const double *b, int len_a, int len_b,
101
+ double *out, int *out_len)
102
+ /* Merge unique sorted values
103
+ Contract: len_a=len(a); len_b=len(b);
104
+ len(out)=len_a+len_b; postlen(out)=out_len;
105
+ */
106
+ ```
107
+
108
+ ```c
109
+ double *alloc_random_array(int *out_len)
110
+ /* Return a freshly malloc'ed array
111
+ Contract: len(return)=out_len; own(return);
112
+ */
113
+ ```
114
+
115
+ Rules are parsed case-insensitively; whitespace does not matter.
116
+
117
+ ## Python wrapper behavior
118
+
119
+ - **Automatic allocation**: any `out_*` argument defaults to `None` in Python; TinyCWrap allocates it based on contracts, naming conventions, fixed array sizes, or by matching the shape of a related input (`out_x` matches `x` when no contract is present).
120
+ - **Struct pointer outputs**: non-const struct pointers are treated as in/out; when you pass an object/array explicitly, it is mutated in place and not returned. When you pass `None`, TinyCWrap allocates and returns the struct (or struct array).
121
+ - **Optional length arguments**: integer length parameters inferred from contracts or naming conventions default to `None` in the wrapper signature. If you pass them, they are cast to `int`; if not, the inferred expression is evaluated.
122
+ - **Post contracts**: when a contract uses `postlen(...)` or `post shape(...)`, the wrapper slices/reshapes outputs after the C call using the values written by the C function.
123
+ - **Scalar pointer outputs**: pointers to integer-like types (e.g., `int *out_len`) are returned as plain Python integers alongside other outputs.
124
+ - **Structs**: for `typedef struct` declarations TinyCWrap generates a Python class:
125
+ - fields are accessible as properties backed by a NumPy structured dtype (`Name.dtype`);
126
+ - scalar struct outputs return `Name` objects;
127
+ - struct-array outputs return compact `NameArray` wrappers, with field arrays as properties (`points.x`), scalar items as `Name` objects (`points[0]`), and Python construction as `NameArray(array_like)` or `NameArray(x=..., y=...)`;
128
+ - pointer fields paired with `len_<field>` automatically allocate NumPy arrays when you instantiate the struct with `len_field` or when you pass an array for that field;
129
+ - `_data` holds the underlying structured array, `Name.zeros(n)` returns a raw array of that dtype, and `NameArray.zeros(n)` returns the typed wrapper.
130
+ - **Reloading**: inside IPython, pass `reload=True` (default) to auto-recompile before each cell if the C sources changed.
131
+
132
+ ## Worked examples
133
+
134
+ ### Two outputs and automatic lengths
135
+
136
+ ```c
137
+ void split_vectors(const double *inp, int len_inp,
138
+ double *out_even, double *out_odd)
139
+ /* Split even/odd elements
140
+ Contract: len_inp=len(inp); len(out_even)=len_inp/2; len(out_odd)=len_inp/2;
141
+ */
142
+ ```
143
+
144
+ ```python
145
+ data = np.array([0, 1, 2, 3, 4, 5], dtype=np.float64)
146
+ out_even, out_odd = cm.split_vectors(data) # lengths inferred, arrays allocated
147
+ ```
148
+
149
+ ### Structs and struct arrays
150
+
151
+ ```c
152
+ typedef struct {
153
+ double real;
154
+ double imag;
155
+ } ComplexPair;
156
+
157
+ double complex_magnitude(const ComplexPair *z);
158
+ ```
159
+
160
+ ```python
161
+ cp = cm.ComplexPair(real=3.0, imag=4.0)
162
+ cm.complex_magnitude(cp) # -> 5.0
163
+
164
+ # struct arrays are NumPy dtypes; useful when C expects pointers to arrays of structs
165
+ pairs = cm.ComplexPair.zeros(2)
166
+ pairs["real"] = [1.0, 2.0]
167
+ pairs["imag"] = [0.0, -1.0]
168
+ ```
169
+
170
+ ### Owned return value
171
+
172
+ ```c
173
+ double *alloc_random_array(int *out_len)
174
+ /* Contract: len(return)=out_len; own(return); */
175
+ ```
176
+
177
+ ```python
178
+ arr, n = cm.alloc_random_array() # returns NumPy array, frees the C buffer
179
+ ```
180
+
181
+ ## Tips
182
+
183
+ - Keep function declarations (or headers) visible at the top level; TinyCWrap scans the C files and headers you pass.
184
+ - If a length cannot be inferred from a naming convention or contract, you must pass it explicitly from Python.
185
+ - Use `cm.<func>.__source__` to inspect the wrapper code if something behaves unexpectedly.
186
+ - For debugging parsed signatures/contracts call `cm._debug_specs()`.
187
+
188
+ That is all you need to start turning small C helpers into ergonomic Python callables.
@@ -0,0 +1,178 @@
1
+ # tinycwrap
2
+
3
+ TinyCWrap is a lightweight helper around CFFI that:
4
+
5
+ - compiles one or more C sources into a shared library,
6
+ - auto-generates the `cdef` from your function/struct declarations,
7
+ - builds NumPy-friendly Python wrappers that take/return arrays and structs,
8
+ - optionally hot-reloads when the C file changes (inside IPython).
9
+
10
+ ## Quick start
11
+
12
+ ```bash
13
+ pip install tinycwrap
14
+ ```
15
+
16
+ Write a small C file (only non-`static` functions are exported):
17
+
18
+ ```c
19
+ /* kernels.c */
20
+ double dot(const double *x, const double *y, int len_x)
21
+ /* Return dot product between x and y */
22
+ {
23
+ double acc = 0.0;
24
+ for (int i = 0; i < len_x; ++i)
25
+ acc += x[i] * y[i];
26
+ return acc;
27
+ }
28
+ ```
29
+
30
+ Wrap and call it from Python:
31
+
32
+ ```python
33
+ import numpy as np
34
+ from tinycwrap import CModule
35
+
36
+ cm = CModule("kernels.c") # builds the shared library, creates wrappers
37
+
38
+ x = np.arange(5, dtype=np.float64)
39
+ y = np.ones_like(x)
40
+
41
+ cm.dot(x, y) # -> 10.0, len_x auto-filled by convention
42
+ print(cm.dot.__doc__) # docstring comes from the C comment
43
+ ```
44
+
45
+ See `examples/` and `tests/` for more involved cases.
46
+
47
+ ## C coding conventions
48
+
49
+ TinyCWrap infers how to build wrappers from simple C conventions:
50
+
51
+ - **Inputs vs outputs**
52
+ - `const T *arg` -> input NumPy array.
53
+ - `T *arg` -> output/in-place array (prefer naming it `out_*` when possible).
54
+ - `T arg[N]` in the signature -> fixed-size array (input if `const`, otherwise output).
55
+ - Plain scalars (`double`, `int`, ...) -> Python scalars.
56
+ - **Length parameters**: integers named like `len_x`, `n_x`, or `size_x` are auto-filled from array `x` by convention. Otherwise pass them explicitly from Python or declare a contract (see below).
57
+ - **Docstrings**: the block comment immediately after the function header becomes the Python docstring.
58
+ - **Structs**: `typedef struct { ... } Name;` definitions in your headers/sources become Python classes with a `.dtype` and NumPy-backed storage.
59
+ - **Compilation**: extra sources can be passed (`CModule("main.c", "helper.c")`), and extra include dirs via `include_dirs=[...]`. Default compiler flags are `-O3 -shared -fPIC -march=native -mtune=native` plus the NumPy include path.
60
+
61
+ ## Contracts: tell the wrapper how to size things
62
+
63
+ Contracts are declared inside the doc comment with `Contract:` (or `Contracts:`). Separate multiple rules with semicolons. Supported forms:
64
+
65
+ - `len_x=len(x)` — mark `len_x` as the length of array `x`. If you omit `len_x` in Python, the wrapper fills it.
66
+ - `shape(out)=n,m` — allocate `out` with shape `(n, m)`. You can define `n,m=shape(a)` to capture the shape of an input first.
67
+ - `len(out)=len_x` — allocate a 1D output array.
68
+ - `postlen(out)=out_len` — slice the output after the call using an integer pointer result `out_len` (useful when the C code writes less than the allocated length).
69
+ - `own(return)` — the returned pointer is owned by the caller; the wrapper will `free` it after copying to NumPy (requires a `len(return)=...` contract).
70
+ - `expose(len_x)` — opt out of naming-convention inference for `len_x`, keeping it as a required Python argument.
71
+
72
+ TinyCWrap also infers low-risk contracts from names:
73
+
74
+ - `len_x`, `n_x`, or `size_x` are treated as `len(x)` when `x` is an array argument.
75
+ - `out_x` is allocated with `shape(x)` when input array `x` exists.
76
+ - `out_x` is allocated with `len_x`, `n_x`, or `size_x` when no input `x` exists but such a length argument does.
77
+
78
+ Explicit contracts override inferred contracts.
79
+
80
+ Examples pulled from the test suite:
81
+
82
+ ```c
83
+ void mat_add(const double *a, const double *b, int n, int m, double *out)
84
+ /* Elementwise addition
85
+ Contract: n,m=shape(a); shape(out)=n,m;
86
+ */
87
+ ```
88
+
89
+ ```c
90
+ void merge_sorted(const double *a, const double *b, int len_a, int len_b,
91
+ double *out, int *out_len)
92
+ /* Merge unique sorted values
93
+ Contract: len_a=len(a); len_b=len(b);
94
+ len(out)=len_a+len_b; postlen(out)=out_len;
95
+ */
96
+ ```
97
+
98
+ ```c
99
+ double *alloc_random_array(int *out_len)
100
+ /* Return a freshly malloc'ed array
101
+ Contract: len(return)=out_len; own(return);
102
+ */
103
+ ```
104
+
105
+ Rules are parsed case-insensitively; whitespace does not matter.
106
+
107
+ ## Python wrapper behavior
108
+
109
+ - **Automatic allocation**: any `out_*` argument defaults to `None` in Python; TinyCWrap allocates it based on contracts, naming conventions, fixed array sizes, or by matching the shape of a related input (`out_x` matches `x` when no contract is present).
110
+ - **Struct pointer outputs**: non-const struct pointers are treated as in/out; when you pass an object/array explicitly, it is mutated in place and not returned. When you pass `None`, TinyCWrap allocates and returns the struct (or struct array).
111
+ - **Optional length arguments**: integer length parameters inferred from contracts or naming conventions default to `None` in the wrapper signature. If you pass them, they are cast to `int`; if not, the inferred expression is evaluated.
112
+ - **Post contracts**: when a contract uses `postlen(...)` or `post shape(...)`, the wrapper slices/reshapes outputs after the C call using the values written by the C function.
113
+ - **Scalar pointer outputs**: pointers to integer-like types (e.g., `int *out_len`) are returned as plain Python integers alongside other outputs.
114
+ - **Structs**: for `typedef struct` declarations TinyCWrap generates a Python class:
115
+ - fields are accessible as properties backed by a NumPy structured dtype (`Name.dtype`);
116
+ - scalar struct outputs return `Name` objects;
117
+ - struct-array outputs return compact `NameArray` wrappers, with field arrays as properties (`points.x`), scalar items as `Name` objects (`points[0]`), and Python construction as `NameArray(array_like)` or `NameArray(x=..., y=...)`;
118
+ - pointer fields paired with `len_<field>` automatically allocate NumPy arrays when you instantiate the struct with `len_field` or when you pass an array for that field;
119
+ - `_data` holds the underlying structured array, `Name.zeros(n)` returns a raw array of that dtype, and `NameArray.zeros(n)` returns the typed wrapper.
120
+ - **Reloading**: inside IPython, pass `reload=True` (default) to auto-recompile before each cell if the C sources changed.
121
+
122
+ ## Worked examples
123
+
124
+ ### Two outputs and automatic lengths
125
+
126
+ ```c
127
+ void split_vectors(const double *inp, int len_inp,
128
+ double *out_even, double *out_odd)
129
+ /* Split even/odd elements
130
+ Contract: len_inp=len(inp); len(out_even)=len_inp/2; len(out_odd)=len_inp/2;
131
+ */
132
+ ```
133
+
134
+ ```python
135
+ data = np.array([0, 1, 2, 3, 4, 5], dtype=np.float64)
136
+ out_even, out_odd = cm.split_vectors(data) # lengths inferred, arrays allocated
137
+ ```
138
+
139
+ ### Structs and struct arrays
140
+
141
+ ```c
142
+ typedef struct {
143
+ double real;
144
+ double imag;
145
+ } ComplexPair;
146
+
147
+ double complex_magnitude(const ComplexPair *z);
148
+ ```
149
+
150
+ ```python
151
+ cp = cm.ComplexPair(real=3.0, imag=4.0)
152
+ cm.complex_magnitude(cp) # -> 5.0
153
+
154
+ # struct arrays are NumPy dtypes; useful when C expects pointers to arrays of structs
155
+ pairs = cm.ComplexPair.zeros(2)
156
+ pairs["real"] = [1.0, 2.0]
157
+ pairs["imag"] = [0.0, -1.0]
158
+ ```
159
+
160
+ ### Owned return value
161
+
162
+ ```c
163
+ double *alloc_random_array(int *out_len)
164
+ /* Contract: len(return)=out_len; own(return); */
165
+ ```
166
+
167
+ ```python
168
+ arr, n = cm.alloc_random_array() # returns NumPy array, frees the C buffer
169
+ ```
170
+
171
+ ## Tips
172
+
173
+ - Keep function declarations (or headers) visible at the top level; TinyCWrap scans the C files and headers you pass.
174
+ - If a length cannot be inferred from a naming convention or contract, you must pass it explicitly from Python.
175
+ - Use `cm.<func>.__source__` to inspect the wrapper code if something behaves unexpectedly.
176
+ - For debugging parsed signatures/contracts call `cm._debug_specs()`.
177
+
178
+ That is all you need to start turning small C helpers into ergonomic Python callables.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinycwrap"
7
- version = "0.0.7"
7
+ version = "0.1.2"
8
8
  description = "Lightweight C-to-Python wrapper generator using CFFI and NumPy"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -16,23 +16,44 @@ def cm():
16
16
  def test_dot(cm):
17
17
  x = np.array([1.0, 2.0, 3.0], dtype=np.float64)
18
18
  y = np.array([4.0, 5.0, 6.0], dtype=np.float64)
19
- assert cm.dot(x, y, len_x=len(x)) == np.dot(x, y)
19
+ assert cm.dot(x, y) == np.dot(x, y)
20
20
 
21
21
 
22
22
  def test_scale_auto_output(cm):
23
23
  x = np.arange(10, dtype=np.float64)
24
- scaled_auto = cm.scale(x, 1.1, len_x=len(x))
24
+ scaled_auto = cm.scale(x, 1.1)
25
25
  np.testing.assert_allclose(scaled_auto, x * 1.1)
26
26
 
27
27
 
28
28
  def test_scale_explicit_output(cm):
29
29
  x = np.arange(10, dtype=np.float64)
30
30
  out = np.empty_like(x)
31
- scaled_explicit = cm.scale(x, 2.0, len_x=len(x), out_x=out)
31
+ scaled_explicit = cm.scale(x, 2.0, out_x=out)
32
32
  np.testing.assert_allclose(out, x * 2.0)
33
33
  np.testing.assert_allclose(scaled_explicit, x * 2.0)
34
34
 
35
35
 
36
+ def test_expose_keeps_inferred_length_visible(cm):
37
+ x = np.arange(5, dtype=np.float64)
38
+ assert cm.prefix_sum(x, len_x=3) == 3.0
39
+ with pytest.raises(TypeError):
40
+ cm.prefix_sum(x)
41
+
42
+
43
+ def test_mat_add_shape_contract(cm):
44
+ a = np.arange(6, dtype=np.float64).reshape(2, 3)
45
+ b = np.ones_like(a)
46
+ out = cm.mat_add(a, b)
47
+ assert out.shape == (2, 3)
48
+ np.testing.assert_allclose(out, a + b)
49
+
50
+ flat = np.zeros(a.size, dtype=np.float64)
51
+ reshaped = cm.mat_add(a, b, out=flat)
52
+ assert reshaped.shape == (2, 3)
53
+ np.testing.assert_allclose(reshaped, a + b)
54
+ np.testing.assert_allclose(flat.reshape(a.shape), a + b)
55
+
56
+
36
57
  def test_struct_wrapper(cm):
37
58
  cp = cm.ComplexPair(real=1.5, imag=-2.5)
38
59
  assert repr(cp) == "ComplexPair(real=1.5, imag=-2.5)"
@@ -52,6 +73,53 @@ def test_struct_argument(cm):
52
73
  assert np.isclose(mag_sq, 5.0)
53
74
 
54
75
 
76
+ def test_struct_pointer_inplace_no_return(cm):
77
+ cp = cm.ComplexPair(real=2.0, imag=-3.0)
78
+ res = cm.scale_complex(cp, 2.0)
79
+ assert res is None
80
+ assert cp.real == 4.0
81
+ assert cp.imag == -6.0
82
+
83
+
84
+ def test_struct_scalar_output_returns_struct_object(cm):
85
+ a = cm.ComplexPair(real=2.0, imag=4.0)
86
+ b = cm.ComplexPair(real=6.0, imag=10.0)
87
+ mid = cm.midpoint_complex(a, b)
88
+ assert isinstance(mid, cm.ComplexPair)
89
+ assert mid.real == 4.0
90
+ assert mid.imag == 7.0
91
+
92
+ out = cm.ComplexPair()
93
+ res = cm.midpoint_complex(a, b, out_z=out)
94
+ assert res is None
95
+ assert out.real == 4.0
96
+ assert out.imag == 7.0
97
+
98
+
99
+ def test_struct_array_output_returns_array_wrapper(cm):
100
+ pairs = cm.make_complex_pairs()
101
+ assert isinstance(pairs, cm.ComplexPairArray)
102
+ assert repr(pairs) == "<Array ComplexPair[3]>"
103
+ assert pairs.shape == (3,)
104
+ np.testing.assert_allclose(pairs.real, np.array([0.0, 1.0, 2.0]))
105
+ np.testing.assert_allclose(pairs.imag, np.array([0.0, -1.0, -2.0]))
106
+ np.testing.assert_allclose(pairs["real"], pairs.real)
107
+ assert isinstance(pairs[1], cm.ComplexPair)
108
+ assert pairs[1].real == 1.0
109
+ assert pairs[1].imag == -1.0
110
+
111
+
112
+ def test_struct_array_initialization(cm):
113
+ from_array_like = cm.ComplexPairArray([(1.0, 2.0), (3.0, 4.0)])
114
+ assert isinstance(from_array_like[0], cm.ComplexPair)
115
+ np.testing.assert_allclose(from_array_like.real, [1.0, 3.0])
116
+ np.testing.assert_allclose(from_array_like.imag, [2.0, 4.0])
117
+
118
+ from_fields = cm.ComplexPairArray(real=[5.0, 6.0], imag=-1.0)
119
+ np.testing.assert_allclose(from_fields.real, [5.0, 6.0])
120
+ np.testing.assert_allclose(from_fields.imag, [-1.0, -1.0])
121
+
122
+
55
123
  def test_struct_array_member(cm):
56
124
  p = cm.Particle()
57
125
  p.pos = [1.0, 2.0, 3.0]
@@ -68,7 +136,7 @@ def test_struct_array_argument(cm):
68
136
  particles["vel"][0] = [1.0, 0.0, 0.0]
69
137
  particles["pos"][1] = [0.0, 0.0, 0.0]
70
138
  particles["vel"][1] = [0.0, 2.0, 0.0]
71
- ke = cm.kinetic_energy(particles, len_p=len(particles))
139
+ ke = cm.kinetic_energy(particles)
72
140
  assert np.isclose(ke, 0.5 * (1.0 + 4.0))
73
141
 
74
142
 
@@ -98,7 +166,14 @@ def test_owned_array(cm):
98
166
  def test_struct_output_array(cm):
99
167
  n = 4
100
168
  particles = cm.Particle.zeros(n)
101
- out_particles = cm.make_particles(3.0, out_p=particles, len_p=n)
102
- np.testing.assert_array_equal(out_particles, particles)
169
+ res = cm.make_particles(3.0, out_p=particles, len_p=n)
170
+ assert res is None
103
171
  np.testing.assert_allclose(particles["pos"], np.ones((n, 3)))
104
172
  np.testing.assert_allclose(particles["vel"], np.array([[3.0, 0.0, 0.0]] * n))
173
+
174
+
175
+ def test_struct_output_array_auto_return(cm):
176
+ n = 3
177
+ particles = cm.make_particles(2.5, len_p=n)
178
+ np.testing.assert_allclose(particles["pos"], np.ones((n, 3)))
179
+ np.testing.assert_allclose(particles["vel"], np.array([[2.5, 0.0, 0.0]] * n))
@@ -6,6 +6,6 @@ from .cmodule import CModule
6
6
  try:
7
7
  __version__ = version("tinycwrap")
8
8
  except PackageNotFoundError:
9
- __version__ = "0.0.7"
9
+ __version__ = "0.1.2"
10
10
 
11
11
  __all__ = ["CModule", "__version__"]