tinycwrap 0.0.2__tar.gz → 0.0.4__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.0.2 → tinycwrap-0.0.4}/PKG-INFO +1 -1
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/pyproject.toml +1 -1
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tests/test_geom.py +15 -0
- tinycwrap-0.0.4/tests/test_path.py +54 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap/__init__.py +1 -1
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap/cmodule.py +98 -34
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap/parsing.py +1 -9
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap.egg-info/PKG-INFO +1 -1
- tinycwrap-0.0.2/tests/test_path.py +0 -23
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/README.md +0 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/setup.cfg +0 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tests/test_examples.py +0 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap.egg-info/SOURCES.txt +0 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap.egg-info/dependency_links.txt +0 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap.egg-info/requires.txt +0 -0
- {tinycwrap-0.0.2 → tinycwrap-0.0.4}/tinycwrap.egg-info/top_level.txt +0 -0
|
@@ -27,12 +27,18 @@ def test_circle_points_length_contract(cg):
|
|
|
27
27
|
assert pts.shape == (101,)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
|
|
30
31
|
def test_return_struct_array_member(cg):
|
|
31
32
|
seg = cg.G2DSegment()
|
|
32
33
|
seg.type = 1
|
|
33
34
|
seg.data = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0]
|
|
34
35
|
assert seg.type == 1
|
|
35
36
|
np.testing.assert_allclose(seg.data, [0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0])
|
|
37
|
+
seg= cg.G2DSegment(type=2, data=[9.0]*8)
|
|
38
|
+
assert seg.type == 2
|
|
39
|
+
np.testing.assert_allclose(seg.data, [9.0]*8)
|
|
40
|
+
|
|
41
|
+
|
|
36
42
|
|
|
37
43
|
def test_geom2d_return_strct(cg):
|
|
38
44
|
seg = cg.geom2d_line_segment_from_start_length(0.1, 0.2, 0.5, 0.4, 0.3)
|
|
@@ -52,3 +58,12 @@ def test_docstring_contains_contract(cg):
|
|
|
52
58
|
doc = cg.geom2d_rectangle_to_path.__doc__
|
|
53
59
|
assert "Contract: len(out_segments)=4" in doc
|
|
54
60
|
assert "Auto-wrapped C function `geom2d_rectangle_to_path`." in doc
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_polygon_length(cg):
|
|
64
|
+
pts = cg.G2DPoints(len_points=3)
|
|
65
|
+
pts.points[0]["x"] = 0.0; pts.points[0]["y"] = 0.0
|
|
66
|
+
pts.points[1]["x"] = 1.0; pts.points[1]["y"] = 0.0
|
|
67
|
+
pts.points[2]["x"] = 1.0; pts.points[2]["y"] = 1.0
|
|
68
|
+
length = cg.geom2d_polygon_length(pts.points)
|
|
69
|
+
assert length == 2.0
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from tinycwrap import CModule
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture(scope="module")
|
|
10
|
+
def cp():
|
|
11
|
+
return CModule(Path("tests/t1/base.c"), Path("tests/t1/path.c"))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_path_get_steps_contract(cp):
|
|
15
|
+
seg = cp.G2DSegment()
|
|
16
|
+
# line from (0,0) to (1,0)
|
|
17
|
+
seg.type = 0
|
|
18
|
+
seg.data = [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
|
19
|
+
segments = np.array([seg._data], dtype=cp.G2DSegment.dtype)
|
|
20
|
+
steps = cp.geom2d_path_get_steps(segments, ds_min=0.25)
|
|
21
|
+
expected_len = cp.geom2d_path_get_len_steps(segments, len_segments=len(segments), ds_min=0.25)
|
|
22
|
+
assert len(steps) == expected_len
|
|
23
|
+
assert np.isclose(steps[-1], cp.geom2d_path_get_length(segments, len_segments=1))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_path_struct_pointer_array(cp):
|
|
27
|
+
path = cp.G2DPath(len_segments=3)
|
|
28
|
+
assert path.len_segments == 3
|
|
29
|
+
assert isinstance(path.segments, np.ndarray)
|
|
30
|
+
assert path.segments.shape == (3,)
|
|
31
|
+
assert path.segments.dtype == cp.G2DSegment.dtype
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_return_class_path(cp):
|
|
35
|
+
path=cp.G2DPath(len_segments=1)
|
|
36
|
+
cp.geom2d_path_from_circle(1.0, path)
|
|
37
|
+
assert path.len_segments == 1
|
|
38
|
+
assert path.segments.shape == (1,)
|
|
39
|
+
seg = path.segments[0]
|
|
40
|
+
assert seg['type'] == 1 # circle segment
|
|
41
|
+
np.testing.assert_allclose(seg['data'][:3], [0.0, 0.0, 1.0]) # cx,cy,radius
|
|
42
|
+
|
|
43
|
+
def test_build_pointer_from_data(cp):
|
|
44
|
+
segments=cp.geom2d_rectangle_to_path(2.0, 3.0)
|
|
45
|
+
assert segments.shape == (4,)
|
|
46
|
+
path=cp.G2DPath(segments=segments, len_segments=len(segments))
|
|
47
|
+
assert path.len_segments == 4
|
|
48
|
+
assert path.segments.shape == (4,)
|
|
49
|
+
|
|
50
|
+
def test_repr_pointer_array(cp):
|
|
51
|
+
path = cp.G2DPath(len_segments=2)
|
|
52
|
+
repr_str = repr(path)
|
|
53
|
+
assert "segments=<segments* 0x" in repr_str
|
|
54
|
+
assert "len_segments=2" in repr_str
|
|
@@ -462,8 +462,19 @@ class CModule:
|
|
|
462
462
|
for sname, sspec in self._struct_specs.items():
|
|
463
463
|
try:
|
|
464
464
|
dtype_fields = []
|
|
465
|
+
pointer_meta = []
|
|
465
466
|
for f in sspec.fields:
|
|
466
|
-
|
|
467
|
+
try:
|
|
468
|
+
base_dtype = numpy_dtype_for_base_type(f.base_type)
|
|
469
|
+
except TypeError:
|
|
470
|
+
base_dtype = None
|
|
471
|
+
if f.is_pointer:
|
|
472
|
+
dtype_fields.append((f.name, np.uintp))
|
|
473
|
+
pointer_meta.append(f)
|
|
474
|
+
if base_dtype is None:
|
|
475
|
+
continue
|
|
476
|
+
if base_dtype is None:
|
|
477
|
+
continue
|
|
467
478
|
if f.array_len:
|
|
468
479
|
dtype_fields.append((f.name, (base_dtype, (f.array_len,))))
|
|
469
480
|
else:
|
|
@@ -472,19 +483,75 @@ class CModule:
|
|
|
472
483
|
except TypeError:
|
|
473
484
|
continue
|
|
474
485
|
|
|
475
|
-
def make_struct_class(spec, dtype):
|
|
476
|
-
slots = ("_data",)
|
|
486
|
+
def make_struct_class(spec, dtype, _struct_dtypes=self._struct_dtypes):
|
|
487
|
+
slots = ("_data", "_children")
|
|
477
488
|
|
|
478
489
|
def __init__(self, **kwargs):
|
|
479
490
|
data = np.zeros((), dtype=dtype)
|
|
491
|
+
children = {}
|
|
492
|
+
# handle pointer+len convention
|
|
493
|
+
for f in spec.fields:
|
|
494
|
+
if f.is_pointer and f.name in dtype.names:
|
|
495
|
+
len_field = f"len_{f.name}"
|
|
496
|
+
target_dtype = _struct_dtypes.get(f.base_type)
|
|
497
|
+
provided = kwargs.get(f.name)
|
|
498
|
+
if provided is not None and target_dtype is not None:
|
|
499
|
+
arr = np.ascontiguousarray(provided, dtype=target_dtype)
|
|
500
|
+
children[f.name] = arr
|
|
501
|
+
data[f.name] = arr.ctypes.data
|
|
502
|
+
if len_field in dtype.names and len_field not in kwargs:
|
|
503
|
+
data[len_field] = len(arr)
|
|
504
|
+
elif len_field in kwargs and target_dtype is not None:
|
|
505
|
+
llen = int(kwargs[len_field])
|
|
506
|
+
arr = np.zeros(llen, dtype=target_dtype)
|
|
507
|
+
children[f.name] = arr
|
|
508
|
+
data[f.name] = arr.ctypes.data
|
|
509
|
+
data[len_field] = llen
|
|
510
|
+
elif provided is not None:
|
|
511
|
+
arr = np.ascontiguousarray(provided)
|
|
512
|
+
children[f.name] = arr
|
|
513
|
+
data[f.name] = arr.ctypes.data
|
|
514
|
+
if len_field in dtype.names and len_field not in kwargs:
|
|
515
|
+
data[len_field] = len(arr)
|
|
516
|
+
elif target_dtype is not None and len_field in dtype.names:
|
|
517
|
+
llen = int(kwargs.get(len_field, 1))
|
|
518
|
+
arr = np.zeros(llen, dtype=target_dtype)
|
|
519
|
+
children[f.name] = arr
|
|
520
|
+
data[f.name] = arr.ctypes.data
|
|
521
|
+
data[len_field] = llen
|
|
480
522
|
for k in dtype.names:
|
|
481
523
|
if k in kwargs:
|
|
482
|
-
|
|
524
|
+
val = kwargs[k]
|
|
525
|
+
field_info = dtype.fields[k][0]
|
|
526
|
+
if field_info.shape != ():
|
|
527
|
+
arr = np.asarray(val, dtype=field_info.base)
|
|
528
|
+
if arr.shape != field_info.shape:
|
|
529
|
+
raise ValueError(f"Field {k} expects shape {field_info.shape}, got {arr.shape}")
|
|
530
|
+
np.copyto(data[k], arr)
|
|
531
|
+
else:
|
|
532
|
+
if k in children:
|
|
533
|
+
data[k] = children[k].ctypes.data
|
|
534
|
+
elif isinstance(val, np.ndarray):
|
|
535
|
+
if val.dtype.kind == "V" and val.shape != ():
|
|
536
|
+
arr = np.ascontiguousarray(val)
|
|
537
|
+
data[k] = arr.ctypes.data
|
|
538
|
+
if f"len_{k}" in dtype.names and f"len_{k}" not in kwargs:
|
|
539
|
+
data[f"len_{k}"] = len(arr)
|
|
540
|
+
else:
|
|
541
|
+
data[k] = val
|
|
542
|
+
else:
|
|
543
|
+
data[k] = val
|
|
483
544
|
object.__setattr__(self, "_data", data)
|
|
545
|
+
object.__setattr__(self, "_children", children)
|
|
484
546
|
|
|
485
547
|
def __repr__(self):
|
|
486
548
|
parts_list = []
|
|
549
|
+
pointer_lengths = getattr(self, "_pointer_lengths", {})
|
|
487
550
|
for name in dtype.names:
|
|
551
|
+
if name in pointer_lengths:
|
|
552
|
+
ptr_val = int(self._data[name])
|
|
553
|
+
parts_list.append(f"{name}=<{name}* 0x{ptr_val:x}>")
|
|
554
|
+
continue
|
|
488
555
|
val = self._data[name]
|
|
489
556
|
if val.shape == ():
|
|
490
557
|
parts_list.append(f"{name}={val.item()!r}")
|
|
@@ -502,13 +569,16 @@ class CModule:
|
|
|
502
569
|
"zeros": staticmethod(
|
|
503
570
|
lambda n, _dtype=dtype: np.zeros(n, dtype=_dtype)
|
|
504
571
|
),
|
|
572
|
+
"_pointer_lengths": {f.name: f"len_{f.name}" for f in spec.fields if f.is_pointer},
|
|
505
573
|
}
|
|
506
574
|
|
|
507
575
|
for fname in dtype.names:
|
|
508
576
|
field_info = dtype.fields[fname][0]
|
|
509
577
|
is_scalar = field_info.shape == ()
|
|
510
578
|
|
|
511
|
-
def getter(self, fname=fname, is_scalar=is_scalar):
|
|
579
|
+
def getter(self, fname=fname, is_scalar=is_scalar, field_info=field_info):
|
|
580
|
+
if fname in getattr(self, "_children", {}):
|
|
581
|
+
return self._children[fname]
|
|
512
582
|
val = self._data[fname]
|
|
513
583
|
return val.item() if is_scalar else val
|
|
514
584
|
|
|
@@ -519,6 +589,11 @@ class CModule:
|
|
|
519
589
|
field_info=field_info,
|
|
520
590
|
is_scalar=is_scalar,
|
|
521
591
|
):
|
|
592
|
+
if fname in getattr(self, "_children", {}):
|
|
593
|
+
arr = np.ascontiguousarray(value, dtype=self._children[fname].dtype)
|
|
594
|
+
self._children[fname] = arr
|
|
595
|
+
self._data[fname] = arr.ctypes.data
|
|
596
|
+
return
|
|
522
597
|
if is_scalar:
|
|
523
598
|
self._data[fname] = value
|
|
524
599
|
else:
|
|
@@ -594,20 +669,26 @@ class CModule:
|
|
|
594
669
|
struct_names = set(self._struct_specs.keys())
|
|
595
670
|
params: list[str] = []
|
|
596
671
|
len_params: list[str] = []
|
|
672
|
+
optional_seen = False
|
|
597
673
|
for a in fspec.args:
|
|
598
674
|
if a.is_array_out and a.name.lower().startswith("out"):
|
|
599
675
|
params.append(f"{a.name}=None")
|
|
676
|
+
optional_seen = True
|
|
600
677
|
elif a.is_length_param:
|
|
601
678
|
len_params.append(f"{a.name}=None")
|
|
679
|
+
optional_seen = True
|
|
602
680
|
elif (
|
|
603
681
|
a.is_pointer
|
|
604
682
|
and a.base_type in struct_names
|
|
605
683
|
and not a.is_const
|
|
606
|
-
and a.name.lower().startswith("out")
|
|
607
684
|
):
|
|
608
685
|
params.append(f"{a.name}=None")
|
|
686
|
+
optional_seen = True
|
|
609
687
|
else:
|
|
610
|
-
|
|
688
|
+
if optional_seen:
|
|
689
|
+
params.append(f"{a.name}=None")
|
|
690
|
+
else:
|
|
691
|
+
params.append(a.name)
|
|
611
692
|
|
|
612
693
|
params.extend(len_params)
|
|
613
694
|
signature = ", ".join(params)
|
|
@@ -725,11 +806,10 @@ class CModule:
|
|
|
725
806
|
if a.is_array_in:
|
|
726
807
|
const_prefix = "const " if a.is_const else ""
|
|
727
808
|
if a.base_type in struct_names:
|
|
728
|
-
cls_expr = f"_struct_classes['{a.base_type}']"
|
|
729
809
|
dtype_expr = f"_struct_dtypes['{a.base_type}']"
|
|
730
810
|
pre_lines += [
|
|
731
|
-
f" if
|
|
732
|
-
f" arr_{a.name} = {a.name}
|
|
811
|
+
f" if hasattr({a.name}, '_data') and getattr({a.name}, '_data', None) is not None and getattr({a.name}, '_data').dtype == {dtype_expr}:",
|
|
812
|
+
f" arr_{a.name} = getattr({a.name}, '_data')",
|
|
733
813
|
" else:",
|
|
734
814
|
f" arr_{a.name} = np.ascontiguousarray({a.name}, dtype={dtype_expr})",
|
|
735
815
|
f" ptr_{a.name} = _self._ffi.cast('{const_prefix}{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
|
|
@@ -834,11 +914,10 @@ class CModule:
|
|
|
834
914
|
)
|
|
835
915
|
elif a.is_pointer and a.base_type in struct_names:
|
|
836
916
|
dtype_expr = f"_struct_dtypes['{a.base_type}']"
|
|
837
|
-
cls_expr = f"_struct_classes['{a.base_type}']"
|
|
838
917
|
if a.is_const:
|
|
839
918
|
out_lines += [
|
|
840
|
-
f" if
|
|
841
|
-
f" arr_{a.name} = {a.name}
|
|
919
|
+
f" if hasattr({a.name}, '_data') and getattr({a.name}, '_data', None) is not None and getattr({a.name}, '_data').dtype == {dtype_expr}:",
|
|
920
|
+
f" arr_{a.name} = getattr({a.name}, '_data')",
|
|
842
921
|
" else:",
|
|
843
922
|
f" arr_{a.name} = np.ascontiguousarray({a.name}, dtype={dtype_expr})",
|
|
844
923
|
f" ptr_{a.name} = _self._ffi.cast('{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
|
|
@@ -846,33 +925,18 @@ class CModule:
|
|
|
846
925
|
else:
|
|
847
926
|
out_lines += [
|
|
848
927
|
f" if {a.name} is None:",
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
]
|
|
854
|
-
elif a.name in contract_map:
|
|
855
|
-
expr_py = _expr_py(contract_map[a.name])
|
|
856
|
-
out_lines += [
|
|
857
|
-
f" arr_{a.name} = np.zeros(int({expr_py}), dtype={dtype_expr})"
|
|
858
|
-
]
|
|
859
|
-
elif a.array_len is None and a.name not in contract_map:
|
|
860
|
-
out_lines += [
|
|
861
|
-
f" arr_{a.name} = np.zeros((), dtype={dtype_expr})"
|
|
862
|
-
]
|
|
863
|
-
else:
|
|
864
|
-
out_lines += [
|
|
865
|
-
f" raise ValueError('{fspec.name}: provide {a.name} or a Contract for its length')"
|
|
866
|
-
]
|
|
867
|
-
out_lines += [
|
|
868
|
-
f" elif isinstance({a.name}, {cls_expr}):",
|
|
928
|
+
f" obj_{a.name} = _struct_classes['{a.base_type}']()",
|
|
929
|
+
f" arr_{a.name} = obj_{a.name}._data",
|
|
930
|
+
f" elif hasattr({a.name}, '_data') and getattr({a.name}, '_data', None) is not None and getattr({a.name}, '_data').dtype == {dtype_expr}:",
|
|
931
|
+
f" obj_{a.name} = {a.name}",
|
|
869
932
|
f" arr_{a.name} = {a.name}._data",
|
|
870
933
|
" else:",
|
|
871
934
|
f" arr_{a.name} = np.ascontiguousarray({a.name}, dtype={dtype_expr})",
|
|
935
|
+
f" obj_{a.name} = None",
|
|
872
936
|
f" ptr_{a.name} = _self._ffi.cast('{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
|
|
873
937
|
]
|
|
874
938
|
output_names.append(a.name)
|
|
875
|
-
output_vars.append(f"arr_{a.name}")
|
|
939
|
+
output_vars.append(f"obj_{a.name} if obj_{a.name} is not None else arr_{a.name}")
|
|
876
940
|
arg_call_args[idx] = f"ptr_{a.name}"
|
|
877
941
|
elif a.is_scalar and not a.is_length_param and arg_call_args[idx] is None:
|
|
878
942
|
scalar_lines.append(f" {a.name} = {a.name}")
|
|
@@ -200,10 +200,6 @@ def _parse_structs_with_pycparser(cdef: str) -> dict[str, StructSpec]:
|
|
|
200
200
|
for decl in struct.decls:
|
|
201
201
|
ctype, is_ptr, is_const, arr_len = _ctype_from_decl(decl.type)
|
|
202
202
|
base = base_type_from_ctype(ctype)
|
|
203
|
-
try:
|
|
204
|
-
numpy_dtype_for_base_type(base)
|
|
205
|
-
except TypeError:
|
|
206
|
-
continue
|
|
207
203
|
fields.append(
|
|
208
204
|
StructField(
|
|
209
205
|
name=decl.name,
|
|
@@ -311,7 +307,7 @@ def _parse_structs_regex(cdef: str) -> dict[str, StructSpec]:
|
|
|
311
307
|
if not line:
|
|
312
308
|
continue
|
|
313
309
|
line = re.sub(r"/\*.*?\*/", "", line).strip()
|
|
314
|
-
m_field = re.match(r"(
|
|
310
|
+
m_field = re.match(r"(.+?\*?)\s*([A-Za-z_]\w*)(\s*\[(\d+)\])?$", line)
|
|
315
311
|
if not m_field:
|
|
316
312
|
continue
|
|
317
313
|
raw_ctype = m_field.group(1).strip()
|
|
@@ -320,10 +316,6 @@ def _parse_structs_regex(cdef: str) -> dict[str, StructSpec]:
|
|
|
320
316
|
is_pointer = "*" in raw_ctype
|
|
321
317
|
is_const = "const" in raw_ctype
|
|
322
318
|
base = base_type_from_ctype(raw_ctype)
|
|
323
|
-
try:
|
|
324
|
-
numpy_dtype_for_base_type(base)
|
|
325
|
-
except TypeError:
|
|
326
|
-
continue
|
|
327
319
|
fields.append(
|
|
328
320
|
StructField(
|
|
329
321
|
name=fname,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
from tinycwrap import CModule
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@pytest.fixture(scope="module")
|
|
10
|
-
def cp():
|
|
11
|
-
return CModule(Path("tests/t1/base.c"), Path("tests/t1/path.c"))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_path_get_steps_contract(cp):
|
|
15
|
-
seg = cp.G2DSegment()
|
|
16
|
-
# line from (0,0) to (1,0)
|
|
17
|
-
seg.type = 0
|
|
18
|
-
seg.data = [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
|
19
|
-
segments = np.array([seg._data], dtype=cp.G2DSegment.dtype)
|
|
20
|
-
steps = cp.geom2d_path_get_steps(segments, ds_min=0.25)
|
|
21
|
-
expected_len = cp.geom2d_path_get_len_steps(segments, len_segments=len(segments), ds_min=0.25)
|
|
22
|
-
assert len(steps) == expected_len
|
|
23
|
-
assert np.isclose(steps[-1], cp.geom2d_path_get_length(segments, len_segments=1))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|