tinycwrap 0.0.6__tar.gz → 0.1.1__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.
- tinycwrap-0.1.1/PKG-INFO +181 -0
- tinycwrap-0.1.1/README.md +171 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/pyproject.toml +1 -1
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tests/test_kernels.py +77 -2
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap/__init__.py +1 -1
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap/cmodule.py +452 -73
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap/parsing.py +15 -2
- tinycwrap-0.1.1/tinycwrap.egg-info/PKG-INFO +181 -0
- tinycwrap-0.0.6/PKG-INFO +0 -13
- tinycwrap-0.0.6/README.md +0 -3
- tinycwrap-0.0.6/tinycwrap.egg-info/PKG-INFO +0 -13
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/setup.cfg +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tests/test_geom.py +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tests/test_path.py +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap.egg-info/SOURCES.txt +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap.egg-info/dependency_links.txt +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap.egg-info/requires.txt +0 -0
- {tinycwrap-0.0.6 → tinycwrap-0.1.1}/tinycwrap.egg-info/top_level.txt +0 -0
tinycwrap-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinycwrap
|
|
3
|
+
Version: 0.1.1
|
|
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
|
+
Contract: len_x=len(x);
|
|
33
|
+
*/
|
|
34
|
+
{
|
|
35
|
+
double acc = 0.0;
|
|
36
|
+
for (int i = 0; i < len_x; ++i)
|
|
37
|
+
acc += x[i] * y[i];
|
|
38
|
+
return acc;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Wrap and call it from Python:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import numpy as np
|
|
46
|
+
from tinycwrap import CModule
|
|
47
|
+
|
|
48
|
+
cm = CModule("kernels.c") # builds the shared library, creates wrappers
|
|
49
|
+
|
|
50
|
+
x = np.arange(5, dtype=np.float64)
|
|
51
|
+
y = np.ones_like(x)
|
|
52
|
+
|
|
53
|
+
cm.dot(x, y) # -> 10.0, len_x auto-filled by the contract
|
|
54
|
+
print(cm.dot.__doc__) # docstring comes from the C comment
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
See `examples/` and `tests/` for more involved cases.
|
|
58
|
+
|
|
59
|
+
## C coding conventions
|
|
60
|
+
|
|
61
|
+
TinyCWrap infers how to build wrappers from simple C conventions:
|
|
62
|
+
|
|
63
|
+
- **Inputs vs outputs**
|
|
64
|
+
- `const T *arg` -> input NumPy array.
|
|
65
|
+
- `T *arg` -> output/in-place array (prefer naming it `out_*` when possible).
|
|
66
|
+
- `T arg[N]` in the signature -> fixed-size array (input if `const`, otherwise output).
|
|
67
|
+
- Plain scalars (`double`, `int`, ...) -> Python scalars.
|
|
68
|
+
- **Length parameters**: integers such as `len_x`, `n`, `size_x` can be auto-filled if you declare a contract (see below). Otherwise pass them explicitly from Python.
|
|
69
|
+
- **Docstrings**: the block comment immediately after the function header becomes the Python docstring.
|
|
70
|
+
- **Structs**: `typedef struct { ... } Name;` definitions in your headers/sources become Python classes with a `.dtype` and NumPy-backed storage.
|
|
71
|
+
- **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.
|
|
72
|
+
|
|
73
|
+
## Contracts: tell the wrapper how to size things
|
|
74
|
+
|
|
75
|
+
Contracts are declared inside the doc comment with `Contract:` (or `Contracts:`). Separate multiple rules with semicolons. Supported forms:
|
|
76
|
+
|
|
77
|
+
- `len_x=len(x)` — mark `len_x` as the length of array `x`. If you omit `len_x` in Python, the wrapper fills it.
|
|
78
|
+
- `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.
|
|
79
|
+
- `len(out)=len_x` — allocate a 1D output array.
|
|
80
|
+
- `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).
|
|
81
|
+
- `own(return)` — the returned pointer is owned by the caller; the wrapper will `free` it after copying to NumPy (requires a `len(return)=...` contract).
|
|
82
|
+
|
|
83
|
+
Examples pulled from the test suite:
|
|
84
|
+
|
|
85
|
+
```c
|
|
86
|
+
void mat_add(const double *a, const double *b, int n, int m, double *out)
|
|
87
|
+
/* Elementwise addition
|
|
88
|
+
Contract: n,m=shape(a); shape(out)=n,m;
|
|
89
|
+
*/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```c
|
|
93
|
+
void merge_sorted(const double *a, const double *b, int len_a, int len_b,
|
|
94
|
+
double *out, int *out_len)
|
|
95
|
+
/* Merge unique sorted values
|
|
96
|
+
Contract: len_a=len(a); len_b=len(b);
|
|
97
|
+
len(out)=len_a+len_b; postlen(out)=out_len;
|
|
98
|
+
*/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```c
|
|
102
|
+
double *alloc_random_array(int *out_len)
|
|
103
|
+
/* Return a freshly malloc'ed array
|
|
104
|
+
Contract: len(return)=out_len; own(return);
|
|
105
|
+
*/
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Rules are parsed case-insensitively; whitespace does not matter.
|
|
109
|
+
|
|
110
|
+
## Python wrapper behavior
|
|
111
|
+
|
|
112
|
+
- **Automatic allocation**: any `out_*` argument defaults to `None` in Python; TinyCWrap allocates it based on contracts, fixed array sizes, or by matching the shape of a related input (`out_x` matches `x` when no contract is present).
|
|
113
|
+
- **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).
|
|
114
|
+
- **Optional length arguments**: integer length parameters inferred from contracts default to `None` in the wrapper signature. If you pass them, they are cast to `int`; if not, the expression from the contract is evaluated.
|
|
115
|
+
- **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.
|
|
116
|
+
- **Scalar pointer outputs**: pointers to integer-like types (e.g., `int *out_len`) are returned as plain Python integers alongside other outputs.
|
|
117
|
+
- **Structs**: for `typedef struct` declarations TinyCWrap generates a Python class:
|
|
118
|
+
- fields are accessible as properties backed by a NumPy structured dtype (`Name.dtype`);
|
|
119
|
+
- scalar struct outputs return `Name` objects;
|
|
120
|
+
- 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=...)`;
|
|
121
|
+
- 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;
|
|
122
|
+
- `_data` holds the underlying structured array, `Name.zeros(n)` returns a raw array of that dtype, and `NameArray.zeros(n)` returns the typed wrapper.
|
|
123
|
+
- **Reloading**: inside IPython, pass `reload=True` (default) to auto-recompile before each cell if the C sources changed.
|
|
124
|
+
|
|
125
|
+
## Worked examples
|
|
126
|
+
|
|
127
|
+
### Two outputs and automatic lengths
|
|
128
|
+
|
|
129
|
+
```c
|
|
130
|
+
void split_vectors(const double *inp, int len_inp,
|
|
131
|
+
double *out_even, double *out_odd)
|
|
132
|
+
/* Split even/odd elements
|
|
133
|
+
Contract: len_inp=len(inp); len(out_even)=len_inp/2; len(out_odd)=len_inp/2;
|
|
134
|
+
*/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
data = np.array([0, 1, 2, 3, 4, 5], dtype=np.float64)
|
|
139
|
+
out_even, out_odd = cm.split_vectors(data) # lengths inferred, arrays allocated
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Structs and struct arrays
|
|
143
|
+
|
|
144
|
+
```c
|
|
145
|
+
typedef struct {
|
|
146
|
+
double real;
|
|
147
|
+
double imag;
|
|
148
|
+
} ComplexPair;
|
|
149
|
+
|
|
150
|
+
double complex_magnitude(const ComplexPair *z);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
cp = cm.ComplexPair(real=3.0, imag=4.0)
|
|
155
|
+
cm.complex_magnitude(cp) # -> 5.0
|
|
156
|
+
|
|
157
|
+
# struct arrays are NumPy dtypes; useful when C expects pointers to arrays of structs
|
|
158
|
+
pairs = cm.ComplexPair.zeros(2)
|
|
159
|
+
pairs["real"] = [1.0, 2.0]
|
|
160
|
+
pairs["imag"] = [0.0, -1.0]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Owned return value
|
|
164
|
+
|
|
165
|
+
```c
|
|
166
|
+
double *alloc_random_array(int *out_len)
|
|
167
|
+
/* Contract: len(return)=out_len; own(return); */
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
arr, n = cm.alloc_random_array() # returns NumPy array, frees the C buffer
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Tips
|
|
175
|
+
|
|
176
|
+
- Keep function declarations (or headers) visible at the top level; TinyCWrap scans the C files and headers you pass.
|
|
177
|
+
- If a length cannot be inferred from a contract, you must pass it explicitly from Python.
|
|
178
|
+
- Use `cm.<func>.__source__` to inspect the wrapper code if something behaves unexpectedly.
|
|
179
|
+
- For debugging parsed signatures/contracts call `cm._debug_specs()`.
|
|
180
|
+
|
|
181
|
+
That is all you need to start turning small C helpers into ergonomic Python callables.
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
Contract: len_x=len(x);
|
|
23
|
+
*/
|
|
24
|
+
{
|
|
25
|
+
double acc = 0.0;
|
|
26
|
+
for (int i = 0; i < len_x; ++i)
|
|
27
|
+
acc += x[i] * y[i];
|
|
28
|
+
return acc;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Wrap and call it from Python:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import numpy as np
|
|
36
|
+
from tinycwrap import CModule
|
|
37
|
+
|
|
38
|
+
cm = CModule("kernels.c") # builds the shared library, creates wrappers
|
|
39
|
+
|
|
40
|
+
x = np.arange(5, dtype=np.float64)
|
|
41
|
+
y = np.ones_like(x)
|
|
42
|
+
|
|
43
|
+
cm.dot(x, y) # -> 10.0, len_x auto-filled by the contract
|
|
44
|
+
print(cm.dot.__doc__) # docstring comes from the C comment
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
See `examples/` and `tests/` for more involved cases.
|
|
48
|
+
|
|
49
|
+
## C coding conventions
|
|
50
|
+
|
|
51
|
+
TinyCWrap infers how to build wrappers from simple C conventions:
|
|
52
|
+
|
|
53
|
+
- **Inputs vs outputs**
|
|
54
|
+
- `const T *arg` -> input NumPy array.
|
|
55
|
+
- `T *arg` -> output/in-place array (prefer naming it `out_*` when possible).
|
|
56
|
+
- `T arg[N]` in the signature -> fixed-size array (input if `const`, otherwise output).
|
|
57
|
+
- Plain scalars (`double`, `int`, ...) -> Python scalars.
|
|
58
|
+
- **Length parameters**: integers such as `len_x`, `n`, `size_x` can be auto-filled if you declare a contract (see below). Otherwise pass them explicitly from Python.
|
|
59
|
+
- **Docstrings**: the block comment immediately after the function header becomes the Python docstring.
|
|
60
|
+
- **Structs**: `typedef struct { ... } Name;` definitions in your headers/sources become Python classes with a `.dtype` and NumPy-backed storage.
|
|
61
|
+
- **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.
|
|
62
|
+
|
|
63
|
+
## Contracts: tell the wrapper how to size things
|
|
64
|
+
|
|
65
|
+
Contracts are declared inside the doc comment with `Contract:` (or `Contracts:`). Separate multiple rules with semicolons. Supported forms:
|
|
66
|
+
|
|
67
|
+
- `len_x=len(x)` — mark `len_x` as the length of array `x`. If you omit `len_x` in Python, the wrapper fills it.
|
|
68
|
+
- `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.
|
|
69
|
+
- `len(out)=len_x` — allocate a 1D output array.
|
|
70
|
+
- `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).
|
|
71
|
+
- `own(return)` — the returned pointer is owned by the caller; the wrapper will `free` it after copying to NumPy (requires a `len(return)=...` contract).
|
|
72
|
+
|
|
73
|
+
Examples pulled from the test suite:
|
|
74
|
+
|
|
75
|
+
```c
|
|
76
|
+
void mat_add(const double *a, const double *b, int n, int m, double *out)
|
|
77
|
+
/* Elementwise addition
|
|
78
|
+
Contract: n,m=shape(a); shape(out)=n,m;
|
|
79
|
+
*/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```c
|
|
83
|
+
void merge_sorted(const double *a, const double *b, int len_a, int len_b,
|
|
84
|
+
double *out, int *out_len)
|
|
85
|
+
/* Merge unique sorted values
|
|
86
|
+
Contract: len_a=len(a); len_b=len(b);
|
|
87
|
+
len(out)=len_a+len_b; postlen(out)=out_len;
|
|
88
|
+
*/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```c
|
|
92
|
+
double *alloc_random_array(int *out_len)
|
|
93
|
+
/* Return a freshly malloc'ed array
|
|
94
|
+
Contract: len(return)=out_len; own(return);
|
|
95
|
+
*/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Rules are parsed case-insensitively; whitespace does not matter.
|
|
99
|
+
|
|
100
|
+
## Python wrapper behavior
|
|
101
|
+
|
|
102
|
+
- **Automatic allocation**: any `out_*` argument defaults to `None` in Python; TinyCWrap allocates it based on contracts, fixed array sizes, or by matching the shape of a related input (`out_x` matches `x` when no contract is present).
|
|
103
|
+
- **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).
|
|
104
|
+
- **Optional length arguments**: integer length parameters inferred from contracts default to `None` in the wrapper signature. If you pass them, they are cast to `int`; if not, the expression from the contract is evaluated.
|
|
105
|
+
- **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.
|
|
106
|
+
- **Scalar pointer outputs**: pointers to integer-like types (e.g., `int *out_len`) are returned as plain Python integers alongside other outputs.
|
|
107
|
+
- **Structs**: for `typedef struct` declarations TinyCWrap generates a Python class:
|
|
108
|
+
- fields are accessible as properties backed by a NumPy structured dtype (`Name.dtype`);
|
|
109
|
+
- scalar struct outputs return `Name` objects;
|
|
110
|
+
- 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=...)`;
|
|
111
|
+
- 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;
|
|
112
|
+
- `_data` holds the underlying structured array, `Name.zeros(n)` returns a raw array of that dtype, and `NameArray.zeros(n)` returns the typed wrapper.
|
|
113
|
+
- **Reloading**: inside IPython, pass `reload=True` (default) to auto-recompile before each cell if the C sources changed.
|
|
114
|
+
|
|
115
|
+
## Worked examples
|
|
116
|
+
|
|
117
|
+
### Two outputs and automatic lengths
|
|
118
|
+
|
|
119
|
+
```c
|
|
120
|
+
void split_vectors(const double *inp, int len_inp,
|
|
121
|
+
double *out_even, double *out_odd)
|
|
122
|
+
/* Split even/odd elements
|
|
123
|
+
Contract: len_inp=len(inp); len(out_even)=len_inp/2; len(out_odd)=len_inp/2;
|
|
124
|
+
*/
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
data = np.array([0, 1, 2, 3, 4, 5], dtype=np.float64)
|
|
129
|
+
out_even, out_odd = cm.split_vectors(data) # lengths inferred, arrays allocated
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Structs and struct arrays
|
|
133
|
+
|
|
134
|
+
```c
|
|
135
|
+
typedef struct {
|
|
136
|
+
double real;
|
|
137
|
+
double imag;
|
|
138
|
+
} ComplexPair;
|
|
139
|
+
|
|
140
|
+
double complex_magnitude(const ComplexPair *z);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
cp = cm.ComplexPair(real=3.0, imag=4.0)
|
|
145
|
+
cm.complex_magnitude(cp) # -> 5.0
|
|
146
|
+
|
|
147
|
+
# struct arrays are NumPy dtypes; useful when C expects pointers to arrays of structs
|
|
148
|
+
pairs = cm.ComplexPair.zeros(2)
|
|
149
|
+
pairs["real"] = [1.0, 2.0]
|
|
150
|
+
pairs["imag"] = [0.0, -1.0]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Owned return value
|
|
154
|
+
|
|
155
|
+
```c
|
|
156
|
+
double *alloc_random_array(int *out_len)
|
|
157
|
+
/* Contract: len(return)=out_len; own(return); */
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
arr, n = cm.alloc_random_array() # returns NumPy array, frees the C buffer
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Tips
|
|
165
|
+
|
|
166
|
+
- Keep function declarations (or headers) visible at the top level; TinyCWrap scans the C files and headers you pass.
|
|
167
|
+
- If a length cannot be inferred from a contract, you must pass it explicitly from Python.
|
|
168
|
+
- Use `cm.<func>.__source__` to inspect the wrapper code if something behaves unexpectedly.
|
|
169
|
+
- For debugging parsed signatures/contracts call `cm._debug_specs()`.
|
|
170
|
+
|
|
171
|
+
That is all you need to start turning small C helpers into ergonomic Python callables.
|
|
@@ -33,6 +33,20 @@ def test_scale_explicit_output(cm):
|
|
|
33
33
|
np.testing.assert_allclose(scaled_explicit, x * 2.0)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def test_mat_add_shape_contract(cm):
|
|
37
|
+
a = np.arange(6, dtype=np.float64).reshape(2, 3)
|
|
38
|
+
b = np.ones_like(a)
|
|
39
|
+
out = cm.mat_add(a, b)
|
|
40
|
+
assert out.shape == (2, 3)
|
|
41
|
+
np.testing.assert_allclose(out, a + b)
|
|
42
|
+
|
|
43
|
+
flat = np.zeros(a.size, dtype=np.float64)
|
|
44
|
+
reshaped = cm.mat_add(a, b, out=flat)
|
|
45
|
+
assert reshaped.shape == (2, 3)
|
|
46
|
+
np.testing.assert_allclose(reshaped, a + b)
|
|
47
|
+
np.testing.assert_allclose(flat.reshape(a.shape), a + b)
|
|
48
|
+
|
|
49
|
+
|
|
36
50
|
def test_struct_wrapper(cm):
|
|
37
51
|
cp = cm.ComplexPair(real=1.5, imag=-2.5)
|
|
38
52
|
assert repr(cp) == "ComplexPair(real=1.5, imag=-2.5)"
|
|
@@ -52,6 +66,53 @@ def test_struct_argument(cm):
|
|
|
52
66
|
assert np.isclose(mag_sq, 5.0)
|
|
53
67
|
|
|
54
68
|
|
|
69
|
+
def test_struct_pointer_inplace_no_return(cm):
|
|
70
|
+
cp = cm.ComplexPair(real=2.0, imag=-3.0)
|
|
71
|
+
res = cm.scale_complex(cp, 2.0)
|
|
72
|
+
assert res is None
|
|
73
|
+
assert cp.real == 4.0
|
|
74
|
+
assert cp.imag == -6.0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_struct_scalar_output_returns_struct_object(cm):
|
|
78
|
+
a = cm.ComplexPair(real=2.0, imag=4.0)
|
|
79
|
+
b = cm.ComplexPair(real=6.0, imag=10.0)
|
|
80
|
+
mid = cm.midpoint_complex(a, b)
|
|
81
|
+
assert isinstance(mid, cm.ComplexPair)
|
|
82
|
+
assert mid.real == 4.0
|
|
83
|
+
assert mid.imag == 7.0
|
|
84
|
+
|
|
85
|
+
out = cm.ComplexPair()
|
|
86
|
+
res = cm.midpoint_complex(a, b, out_z=out)
|
|
87
|
+
assert res is None
|
|
88
|
+
assert out.real == 4.0
|
|
89
|
+
assert out.imag == 7.0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_struct_array_output_returns_array_wrapper(cm):
|
|
93
|
+
pairs = cm.make_complex_pairs()
|
|
94
|
+
assert isinstance(pairs, cm.ComplexPairArray)
|
|
95
|
+
assert repr(pairs) == "<Array ComplexPair[3]>"
|
|
96
|
+
assert pairs.shape == (3,)
|
|
97
|
+
np.testing.assert_allclose(pairs.real, np.array([0.0, 1.0, 2.0]))
|
|
98
|
+
np.testing.assert_allclose(pairs.imag, np.array([0.0, -1.0, -2.0]))
|
|
99
|
+
np.testing.assert_allclose(pairs["real"], pairs.real)
|
|
100
|
+
assert isinstance(pairs[1], cm.ComplexPair)
|
|
101
|
+
assert pairs[1].real == 1.0
|
|
102
|
+
assert pairs[1].imag == -1.0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_struct_array_initialization(cm):
|
|
106
|
+
from_array_like = cm.ComplexPairArray([(1.0, 2.0), (3.0, 4.0)])
|
|
107
|
+
assert isinstance(from_array_like[0], cm.ComplexPair)
|
|
108
|
+
np.testing.assert_allclose(from_array_like.real, [1.0, 3.0])
|
|
109
|
+
np.testing.assert_allclose(from_array_like.imag, [2.0, 4.0])
|
|
110
|
+
|
|
111
|
+
from_fields = cm.ComplexPairArray(real=[5.0, 6.0], imag=-1.0)
|
|
112
|
+
np.testing.assert_allclose(from_fields.real, [5.0, 6.0])
|
|
113
|
+
np.testing.assert_allclose(from_fields.imag, [-1.0, -1.0])
|
|
114
|
+
|
|
115
|
+
|
|
55
116
|
def test_struct_array_member(cm):
|
|
56
117
|
p = cm.Particle()
|
|
57
118
|
p.pos = [1.0, 2.0, 3.0]
|
|
@@ -88,10 +149,24 @@ def test_merge_sorted(cm):
|
|
|
88
149
|
assert out_len_val == 4
|
|
89
150
|
|
|
90
151
|
|
|
152
|
+
def test_owned_array(cm):
|
|
153
|
+
arr, n = cm.alloc_random_array()
|
|
154
|
+
assert arr.shape[0] == n
|
|
155
|
+
assert n >= 3
|
|
156
|
+
assert np.all(arr[:n] >= 1.0)
|
|
157
|
+
|
|
158
|
+
|
|
91
159
|
def test_struct_output_array(cm):
|
|
92
160
|
n = 4
|
|
93
161
|
particles = cm.Particle.zeros(n)
|
|
94
|
-
|
|
95
|
-
|
|
162
|
+
res = cm.make_particles(3.0, out_p=particles, len_p=n)
|
|
163
|
+
assert res is None
|
|
96
164
|
np.testing.assert_allclose(particles["pos"], np.ones((n, 3)))
|
|
97
165
|
np.testing.assert_allclose(particles["vel"], np.array([[3.0, 0.0, 0.0]] * n))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_struct_output_array_auto_return(cm):
|
|
169
|
+
n = 3
|
|
170
|
+
particles = cm.make_particles(2.5, len_p=n)
|
|
171
|
+
np.testing.assert_allclose(particles["pos"], np.ones((n, 3)))
|
|
172
|
+
np.testing.assert_allclose(particles["vel"], np.array([[2.5, 0.0, 0.0]] * n))
|