gamut-format 1.0.0__py3-none-win_amd64.whl

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.
gamut/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """gamut: files for parametric simulation studies and their models.
2
+
3
+ Every call goes to the shared core (a C library); this package is its
4
+ Python face, with NumPy as its one dependency.
5
+ """
6
+
7
+ from ._lib import GamutError, format_string as _format, path as _path, version as _version
8
+ from ._study import Study, create, open
9
+ from ._validate import Finding, Report, validate
10
+ from ._models import Nearest, create_model, evaluate, register_model_type
11
+ from ._files import append, extract, write_copy
12
+
13
+ __version__ = _version()
14
+ FORMAT = _format()
15
+
16
+
17
+ def library_path() -> str:
18
+ """The core library this package loaded. MATLAB and Julia can use the
19
+ same file (it carries its own HDF5 in a wheel)."""
20
+ return _path
21
+
22
+
23
+ __all__ = ["FORMAT", "Finding", "GamutError", "Nearest", "Report", "Study", "append", "create",
24
+ "create_model", "evaluate", "extract", "library_path", "open", "register_model_type",
25
+ "validate", "write_copy"]
gamut/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """``python -m gamut``: the same command line as the ``gamut`` script."""
2
+ import sys
3
+
4
+ from gamut.cli import main
5
+
6
+ sys.exit(main())
gamut/_files.py ADDED
@@ -0,0 +1,44 @@
1
+ """Copies between files, done by the core: each validates its source and
2
+ checks everything before the first row is written."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import ctypes
7
+ import os
8
+ from typing import Any
9
+
10
+ from . import _lib
11
+ from ._lib import Selection, b, call
12
+
13
+
14
+ def _path(p: Any) -> bytes:
15
+ return b(os.fspath(p))
16
+
17
+
18
+ def append(dst: str, src: str) -> int:
19
+ """Adds the rows of `src` to `dst` in place and returns how many. The
20
+ rows get new ids continuing dst's; category tables merge by name. Both
21
+ files need the same declarations (bounds may differ), the same mesh,
22
+ and one geometry for a group member found in both."""
23
+ n = ctypes.c_int64()
24
+ call("gamut_append", _path(dst), _path(src), ctypes.byref(n))
25
+ return n.value
26
+
27
+
28
+ def extract(src: str, dst: str, rows: Any) -> None:
29
+ """Writes the selected rows of `src` (positions, a slice, or a strictly
30
+ increasing array) as a new file `dst`: ids kept, every table whole, a
31
+ changing mesh's instances reduced to those the rows use."""
32
+ sel = Selection(rows, "rows")
33
+ if isinstance(rows, slice):
34
+ from ._study import open as _open
35
+ with _open(os.fspath(src)) as f:
36
+ sel.resolve(f.row_count)
37
+ call("gamut_extract", _path(src), _path(dst), sel.pointer())
38
+
39
+
40
+ def write_copy(src: str, dst: str) -> None:
41
+ """Rewrites `src` as `dst` with the default layout (chunks and
42
+ compression), as for a file another writer chunked finely; the copy
43
+ keeps the file's identity (file_id, created)."""
44
+ call("gamut_write_copy", _path(src), _path(dst))
gamut/_lib.py ADDED
@@ -0,0 +1,314 @@
1
+ """The shared core, loaded with ctypes: the C interface of gamut.h and the
2
+ conversions every call needs. Nothing here is public."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import ctypes
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any, Iterable, Sequence
11
+
12
+ import numpy as np
13
+
14
+ GAMUT_AS_GIVEN = 0
15
+ GAMUT_UINT8, GAMUT_INT8, GAMUT_INT32, GAMUT_INT64, GAMUT_FLOAT32, GAMUT_FLOAT64 = 1, 2, 3, 4, 5, 6
16
+ SEL_ALL, SEL_RANGE, SEL_INDEX = 0, 1, 2
17
+
18
+ _DTYPE_CODE = {
19
+ np.dtype(np.uint8): GAMUT_UINT8,
20
+ np.dtype(np.int8): GAMUT_INT8,
21
+ np.dtype(np.int32): GAMUT_INT32,
22
+ np.dtype(np.int64): GAMUT_INT64,
23
+ np.dtype(np.float32): GAMUT_FLOAT32,
24
+ np.dtype(np.float64): GAMUT_FLOAT64,
25
+ }
26
+ _CODE_DTYPE = {v: k for k, v in _DTYPE_CODE.items()}
27
+
28
+
29
+ class GamutError(Exception):
30
+ """The one error: `rule` is the spec's rule id ("E7") or empty, `path`
31
+ a concept path or empty, `message` what went wrong and how to fix it."""
32
+
33
+ def __init__(self, rule: str, message: str, path: str = "") -> None:
34
+ super().__init__(f"{rule}: {message}" if rule else message)
35
+ self.rule = rule
36
+ self.message = message
37
+ self.path = path
38
+
39
+
40
+ class _Error(ctypes.Structure):
41
+ _fields_ = [("rule", ctypes.c_char * 8), ("path", ctypes.c_char * 512),
42
+ ("message", ctypes.c_char * 2048)]
43
+
44
+
45
+ class _Sel(ctypes.Structure):
46
+ _fields_ = [("kind", ctypes.c_int), ("start", ctypes.c_int64),
47
+ ("stop", ctypes.c_int64), ("index", ctypes.POINTER(ctypes.c_int64)),
48
+ ("count", ctypes.c_int64)]
49
+
50
+
51
+ def _library_name() -> str:
52
+ if sys.platform == "darwin":
53
+ return "libgamut.dylib"
54
+ if sys.platform.startswith("win"):
55
+ return "gamut.dll"
56
+ return "libgamut.so"
57
+
58
+
59
+ def _find_library() -> str:
60
+ explicit = os.environ.get("GAMUT_LIBRARY")
61
+ if explicit:
62
+ return explicit
63
+ here = Path(__file__).resolve().parent
64
+ for candidate in (here / "_native" / _library_name(),
65
+ here.parents[1] / "build" / _library_name()):
66
+ if candidate.exists():
67
+ return str(candidate)
68
+ raise ImportError(
69
+ f"the Gamut core library ({_library_name()}) was not found; "
70
+ "set GAMUT_LIBRARY to its path, or build it from a checkout: "
71
+ "cmake -S . -B build && cmake --build build")
72
+
73
+
74
+ path = _find_library()
75
+ _lib = ctypes.CDLL(path)
76
+
77
+ _P = ctypes.c_void_p
78
+ _S = ctypes.c_char_p
79
+ _I = ctypes.c_int
80
+ _L = ctypes.c_int64
81
+ _D = ctypes.c_double
82
+ _E = ctypes.POINTER(_Error)
83
+ _SEL = ctypes.POINTER(_Sel)
84
+ _PP = ctypes.POINTER(ctypes.c_void_p)
85
+ _SS = ctypes.POINTER(ctypes.c_char_p)
86
+
87
+ _SIGNATURES: dict[str, tuple[Any, list[Any]]] = {
88
+ "gamut_version": (_S, []),
89
+ "gamut_format": (_S, []),
90
+ "gamut_units_parse": (_I, [_S]),
91
+ "gamut_create": (_I, [_S, _S, _S, _S, _PP, _E]),
92
+ "gamut_open_append": (_I, [_S, _PP, _E]),
93
+ "gamut_set_attribute": (_I, [_P, _S, _S, _E]),
94
+ "gamut_declare_parameter": (_I, [_P, _S, _S, _S, _I, _I, _D, _D, _S, _E]),
95
+ "gamut_declare_label": (_I, [_P, _S, _S, _SS, _L, _E]),
96
+ "gamut_declare_result": (_I, [_P, _S, _S, _S, _I, _S, _S, _I, _D, _S, _S, _S, _S, _I, _S, _S, _E]),
97
+ "gamut_declare_mesh_label": (_I, [_P, _S, _S, _SS, _L, _E]),
98
+ "gamut_declare_coordinates": (_I, [_P, _S, _I, _I, _S, _S, _E]),
99
+ "gamut_create_model": (_I, [_S, _S, _S, _S, _PP, _E]),
100
+ "gamut_tree_new": (_I, [_PP, _E]),
101
+ "gamut_tree_free": (None, [_P]),
102
+ "gamut_tree_set_null": (_I, [_P, _S, _E]),
103
+ "gamut_tree_set_bool": (_I, [_P, _S, _I, _E]),
104
+ "gamut_tree_set_int": (_I, [_P, _S, _L, _E]),
105
+ "gamut_tree_set_float": (_I, [_P, _S, _D, _E]),
106
+ "gamut_tree_set_string": (_I, [_P, _S, _S, _E]),
107
+ "gamut_tree_set_array": (_I, [_P, _S, _I, _P, _I, ctypes.POINTER(_L), _E]),
108
+ "gamut_tree_set_strings": (_I, [_P, _S, _SS, _L, _E]),
109
+ "gamut_tree_set_dict": (_I, [_P, _S, _E]),
110
+ "gamut_tree_keys": (_I, [_P, _S, _PP, _E]),
111
+ "gamut_tree_kind": (_I, [_P, _S, ctypes.POINTER(_I), _E]),
112
+ "gamut_tree_scalar": (_I, [_P, _S, ctypes.POINTER(_L), ctypes.POINTER(_D), _E]),
113
+ "gamut_tree_strings": (_I, [_P, _S, _PP, _E]),
114
+ "gamut_tree_array_info": (_I, [_P, _S, ctypes.POINTER(_I), ctypes.POINTER(_I), ctypes.POINTER(_L), _E]),
115
+ "gamut_tree_array": (_I, [_P, _S, _P, _L, _E]),
116
+ "gamut_add_model": (_I, [_P, _S, _S, _SS, _L, _P, _S, _E]),
117
+ "gamut_model_numbers": (_I, [_P, _S, _PP, _E]),
118
+ "gamut_nearest_check": (_I, [_P, _E]),
119
+ "gamut_nearest_output_size": (_I, [_P, _S, ctypes.POINTER(_L), _E]),
120
+ "gamut_nearest_call": (_I, [_P, _SS, ctypes.POINTER(ctypes.POINTER(_D)), _I, _L, _S, ctypes.POINTER(_D), _L, _E]),
121
+ "gamut_evaluate_begin": (_I, [_P, _S, _PP, _E]),
122
+ "gamut_set_constant": (_I, [_P, _S, _S, _I, _D, _L, _E]),
123
+ "gamut_set_generalisation_group": (_I, [_P, _S, _E]),
124
+ "gamut_set_compression": (_I, [_P, _I, _I, _E]),
125
+ "gamut_geometry_begin": (_I, [_P, _E]),
126
+ "gamut_geometry_points": (_I, [_P, _I, _P, _L, _I, _E]),
127
+ "gamut_geometry_cells": (_I, [_P, _P, _L, _P, _P, _L, _E]),
128
+ "gamut_geometry_label": (_I, [_P, _S, _P, _L, _E]),
129
+ "gamut_set_mesh": (_I, [_P, _E]),
130
+ "gamut_batch_begin": (_I, [_P, _L, _E]),
131
+ "gamut_batch_values": (_I, [_P, _S, _I, _P, _L, _E]),
132
+ "gamut_batch_names": (_I, [_P, _S, _SS, _L, _E]),
133
+ "gamut_batch_geometry_for_row": (_I, [_P, _E]),
134
+ "gamut_batch_geometry_for_member": (_I, [_P, _S, _E]),
135
+ "gamut_batch_commit": (_I, [_P, ctypes.POINTER(_L), _E]),
136
+ "gamut_flush": (_I, [_P, _E]),
137
+ "gamut_set_note_text": (_I, [_P, _S, _S, _E]),
138
+ "gamut_set_note_int": (_I, [_P, _S, _L, _E]),
139
+ "gamut_set_note_float": (_I, [_P, _S, _D, _E]),
140
+ "gamut_set_private": (_I, [_P, _S, ctypes.c_char_p, _L, _E]),
141
+ "gamut_notes": (_I, [_P, _PP, _PP, _PP, _E]),
142
+ "gamut_private_names": (_I, [_P, _PP, _E]),
143
+ "gamut_get_private": (_I, [_P, _S, _P, _L, ctypes.POINTER(_L), _E]),
144
+ "gamut_close": (_I, [_P, _E]),
145
+ "gamut_append": (_I, [_S, _S, ctypes.POINTER(_L), _E]),
146
+ "gamut_extract": (_I, [_S, _S, _SEL, _E]),
147
+ "gamut_write_copy": (_I, [_S, _S, _E]),
148
+ "gamut_abandon": (None, [_P, _I]),
149
+ "gamut_open": (_I, [_S, _PP, _E]),
150
+ "gamut_set_threads": (_I, [_P, _I, _E]),
151
+ "gamut_info": (_I, [_P, _S, _PP, _E]),
152
+ "gamut_names": (_I, [_P, _S, _PP, _E]),
153
+ "gamut_describe": (_I, [_P, _S, _PP, _PP, _E]),
154
+ "gamut_shape": (_I, [_P, _S, _SEL, _SEL, ctypes.POINTER(_L), ctypes.POINTER(_I), _E]),
155
+ "gamut_read": (_I, [_P, _S, _SEL, _SEL, _I, _P, _L, _E]),
156
+ "gamut_label_names": (_I, [_P, _S, _SEL, _PP, _E]),
157
+ "gamut_table": (_I, [_P, _S, _PP, _E]),
158
+ "gamut_select": (_I, [_P, _S, ctypes.POINTER(_L), _L, ctypes.POINTER(_L), _E]),
159
+ "gamut_validate": (_I, [_S, _PP, _E]),
160
+ "gamut_report_count": (_L, [_P]),
161
+ "gamut_report_get": (_I, [_P, _L, _SS, _SS, _SS, _SS]),
162
+ "gamut_report_free": (None, [_P]),
163
+ "gamut_strings_count": (_L, [_P]),
164
+ "gamut_strings_get": (_S, [_P, _L]),
165
+ "gamut_strings_free": (None, [_P]),
166
+ }
167
+
168
+ for _name, (_res, _args) in _SIGNATURES.items():
169
+ _fn = getattr(_lib, _name)
170
+ _fn.restype = _res
171
+ _fn.argtypes = _args
172
+
173
+
174
+ def b(text: str | None) -> bytes | None:
175
+ """UTF-8 for the C side; None stays NULL. A C string ends at its first
176
+ NUL, so text holding one is refused rather than cut short."""
177
+ if text is None:
178
+ return None
179
+ raw = str(text).encode("utf-8")
180
+ if b"\0" in raw:
181
+ raise GamutError("", f"{text!r} holds a NUL character, which no name or text may hold")
182
+ return raw
183
+
184
+
185
+ def call(name: str, *args: Any) -> None:
186
+ """Calls a gamut_* function that returns a status, raising GamutError."""
187
+ err = _Error()
188
+ status = getattr(_lib, name)(*args, ctypes.byref(err))
189
+ if status != 0:
190
+ raise GamutError(err.rule.decode("utf-8", "replace"),
191
+ err.message.decode("utf-8", "replace"),
192
+ err.path.decode("utf-8", "replace"))
193
+
194
+
195
+ def strings_out(name: str, *args: Any) -> list[str]:
196
+ """Calls a function whose last output is a gamut_strings list."""
197
+ handle = ctypes.c_void_p()
198
+ call(name, *args, ctypes.byref(handle))
199
+ try:
200
+ n = _lib.gamut_strings_count(handle)
201
+ return [_lib.gamut_strings_get(handle, i).decode("utf-8") for i in range(n)]
202
+ finally:
203
+ _lib.gamut_strings_free(handle)
204
+
205
+
206
+ def two_strings_out(name: str, *args: Any) -> tuple[list[str], list[str]]:
207
+ keys, values = ctypes.c_void_p(), ctypes.c_void_p()
208
+ call(name, *args, ctypes.byref(keys), ctypes.byref(values))
209
+ try:
210
+ out = []
211
+ for h in (keys, values):
212
+ n = _lib.gamut_strings_count(h)
213
+ out.append([_lib.gamut_strings_get(h, i).decode("utf-8") for i in range(n)])
214
+ return out[0], out[1]
215
+ finally:
216
+ _lib.gamut_strings_free(keys)
217
+ _lib.gamut_strings_free(values)
218
+
219
+
220
+ def string_array(items: Sequence[str]) -> Any:
221
+ arr = (ctypes.c_char_p * max(len(items), 1))()
222
+ for i, s in enumerate(items):
223
+ arr[i] = b(s)
224
+ return arr
225
+
226
+
227
+ def dtype_code(values: np.ndarray) -> int:
228
+ try:
229
+ return _DTYPE_CODE[values.dtype]
230
+ except KeyError:
231
+ raise GamutError("E4", f"values of dtype {values.dtype} cannot be stored; "
232
+ "use float32, float64, int32 or int64") from None
233
+
234
+
235
+ def as_array(values: Any) -> np.ndarray:
236
+ """A C-contiguous numeric array; Python ints become int64, floats float64."""
237
+ arr = np.asarray(values)
238
+ if arr.dtype == np.bool_:
239
+ raise GamutError("", "booleans are label values; declare the label with "
240
+ "categories ['false', 'true'] (or as bool)")
241
+ if arr.dtype.kind == "i" and arr.dtype not in _DTYPE_CODE:
242
+ arr = arr.astype(np.int64)
243
+ if arr.dtype.kind == "u" and arr.dtype != np.uint8:
244
+ arr = arr.astype(np.int64)
245
+ if arr.dtype.kind == "f" and arr.dtype not in _DTYPE_CODE:
246
+ arr = arr.astype(np.float64)
247
+ return np.ascontiguousarray(arr)
248
+
249
+
250
+ class Selection:
251
+ """A row, node or cell selection in the core's form."""
252
+
253
+ def __init__(self, sel: Any, what: str) -> None:
254
+ self.sel = _Sel()
255
+ self.single = False
256
+ self._keep: np.ndarray | None = None
257
+ if sel is None:
258
+ self.sel.kind = SEL_ALL
259
+ elif isinstance(sel, slice):
260
+ if sel.step not in (None, 1):
261
+ raise GamutError("", f"{what}: a slice must have step 1")
262
+ self.sel.kind = SEL_RANGE
263
+ self._slice = sel
264
+ elif isinstance(sel, (int, np.integer)) and not isinstance(sel, (bool, np.bool_)):
265
+ self.single = True
266
+ self._keep = np.array([int(sel)], dtype=np.int64)
267
+ self.sel.kind = SEL_INDEX
268
+ else:
269
+ given = np.asarray(sel)
270
+ if given.size and given.dtype.kind not in "iu":
271
+ raise GamutError("", f"{what}: positions are integers, a slice or None; "
272
+ f"got {given.dtype} values")
273
+ self._keep = np.ascontiguousarray(given.astype(np.int64).ravel())
274
+ self.sel.kind = SEL_INDEX
275
+ self._slice = sel if isinstance(sel, slice) else None
276
+ if self._keep is not None:
277
+ self.sel.index = self._keep.ctypes.data_as(ctypes.POINTER(ctypes.c_int64))
278
+ self.sel.count = len(self._keep)
279
+
280
+ def resolve(self, length: int) -> None:
281
+ """Turns a slice into a range now that the length is known."""
282
+ if self._slice is not None:
283
+ start, stop, _ = self._slice.indices(length)
284
+ self.sel.start, self.sel.stop = start, max(start, stop)
285
+
286
+ def pointer(self) -> Any:
287
+ return ctypes.byref(self.sel)
288
+
289
+
290
+ def read(handle: Any, name: str, rows: Selection, sites: Selection, dtype: np.dtype) -> np.ndarray:
291
+ shape = (ctypes.c_int64 * 4)()
292
+ rank = ctypes.c_int()
293
+ call("gamut_shape", handle, b(name), rows.pointer(), sites.pointer(), shape, ctypes.byref(rank))
294
+ dims = tuple(shape[i] for i in range(rank.value))
295
+ out = np.empty(dims, dtype=dtype)
296
+ call("gamut_read", handle, b(name), rows.pointer(), sites.pointer(), _DTYPE_CODE[np.dtype(dtype)],
297
+ out.ctypes.data_as(ctypes.c_void_p), out.nbytes)
298
+ return out
299
+
300
+
301
+ def units_parse(units: str) -> bool:
302
+ return bool(_lib.gamut_units_parse(b(units)))
303
+
304
+
305
+ def version() -> str:
306
+ return _lib.gamut_version().decode()
307
+
308
+
309
+ def format_string() -> str:
310
+ return _lib.gamut_format().decode()
311
+
312
+
313
+ def iterable(x: Any) -> bool:
314
+ return isinstance(x, Iterable) and not isinstance(x, (str, bytes))