plot-io 0.12.0__py3-none-win_arm64.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.
- plot_io/__init__.py +73 -0
- plot_io/_methods.py +1691 -0
- plot_io/_native/__init__.py +199 -0
- plot_io/_native/__main__.py +197 -0
- plot_io/_native/_api.py +457 -0
- plot_io/_native/bin/windows-arm64.dll +0 -0
- plot_io/_native/kernels.py +335 -0
- plot_io/_native/manifest.json +49 -0
- plot_io/axis.py +1347 -0
- plot_io/boxes.py +1156 -0
- plot_io/canvas.py +389 -0
- plot_io/color/__init__.py +3 -0
- plot_io/color/data.py +1646 -0
- plot_io/color/maps.py +922 -0
- plot_io/color/values.py +295 -0
- plot_io/color/variants.py +1246 -0
- plot_io/colorbar.py +1426 -0
- plot_io/columns.py +614 -0
- plot_io/decorations.py +920 -0
- plot_io/defaults.py +809 -0
- plot_io/density.py +113 -0
- plot_io/ext.py +68 -0
- plot_io/facets.py +240 -0
- plot_io/figure.py +788 -0
- plot_io/formatters.py +97 -0
- plot_io/genome/__init__.py +17 -0
- plot_io/genome/adapters.py +356 -0
- plot_io/genome/combine.py +123 -0
- plot_io/genome/contacts.py +508 -0
- plot_io/genome/intervals.py +750 -0
- plot_io/genome/lanes.py +191 -0
- plot_io/genome/layout.py +184 -0
- plot_io/genome/links.py +364 -0
- plot_io/genome/pack.py +27 -0
- plot_io/genome/readers.py +145 -0
- plot_io/genome/regions.py +228 -0
- plot_io/genome/scale.py +349 -0
- plot_io/genome/series.py +344 -0
- plot_io/genome/signal.py +621 -0
- plot_io/genome/sites.py +782 -0
- plot_io/genome/sources.py +459 -0
- plot_io/genome/testing.py +161 -0
- plot_io/geometry.py +289 -0
- plot_io/groups.py +308 -0
- plot_io/guides.py +503 -0
- plot_io/helpers/__init__.py +17 -0
- plot_io/helpers/clustermap.py +202 -0
- plot_io/helpers/dendrogram.py +172 -0
- plot_io/helpers/grids.py +304 -0
- plot_io/helpers/qq.py +334 -0
- plot_io/helpers/raincloud.py +249 -0
- plot_io/helpers/tree.py +214 -0
- plot_io/helpers/volcano.py +505 -0
- plot_io/hints.py +125 -0
- plot_io/kernels.py +740 -0
- plot_io/labels.py +566 -0
- plot_io/layers/__init__.py +100 -0
- plot_io/layers/base.py +630 -0
- plot_io/layers/context.py +87 -0
- plot_io/layers/core.py +446 -0
- plot_io/layers/geoms.py +1362 -0
- plot_io/layers/grammar.py +586 -0
- plot_io/layers/paint.py +614 -0
- plot_io/layers/pipeline.py +170 -0
- plot_io/layers/positions.py +431 -0
- plot_io/layers/stats.py +1088 -0
- plot_io/layout.py +1381 -0
- plot_io/legend.py +605 -0
- plot_io/levels.py +509 -0
- plot_io/limits.py +124 -0
- plot_io/lines.py +42 -0
- plot_io/mapped.py +522 -0
- plot_io/markers.py +110 -0
- plot_io/markup.py +128 -0
- plot_io/numbers.py +354 -0
- plot_io/occupancy.py +295 -0
- plot_io/plot.py +597 -0
- plot_io/presets/__init__.py +51 -0
- plot_io/presets/_util.py +153 -0
- plot_io/presets/area.py +145 -0
- plot_io/presets/bar.py +261 -0
- plot_io/presets/box.py +400 -0
- plot_io/presets/catpoints.py +177 -0
- plot_io/presets/contour.py +353 -0
- plot_io/presets/curves.py +30 -0
- plot_io/presets/dots.py +355 -0
- plot_io/presets/ecdf.py +97 -0
- plot_io/presets/errorbar.py +106 -0
- plot_io/presets/errors.py +296 -0
- plot_io/presets/fill.py +74 -0
- plot_io/presets/heatmap.py +322 -0
- plot_io/presets/hexbin.py +198 -0
- plot_io/presets/hist.py +391 -0
- plot_io/presets/hist2d.py +87 -0
- plot_io/presets/kde.py +162 -0
- plot_io/presets/kde2d.py +147 -0
- plot_io/presets/line.py +143 -0
- plot_io/presets/pie.py +502 -0
- plot_io/presets/regression.py +115 -0
- plot_io/presets/scatter.py +194 -0
- plot_io/presets/stem.py +100 -0
- plot_io/presets/violin.py +323 -0
- plot_io/py.typed +0 -0
- plot_io/registry.py +82 -0
- plot_io/render.py +480 -0
- plot_io/rows.py +491 -0
- plot_io/scales.py +103 -0
- plot_io/seams.py +138 -0
- plot_io/settings.py +807 -0
- plot_io/shapes.py +66 -0
- plot_io/sharing.py +232 -0
- plot_io/sizemaps.py +97 -0
- plot_io/spec.py +240 -0
- plot_io/states.py +216 -0
- plot_io/styles/__init__.py +307 -0
- plot_io/styles/library.py +397 -0
- plot_io/tables.py +138 -0
- plot_io/text.py +412 -0
- plot_io/ticks.py +400 -0
- plot_io/times.py +287 -0
- plot_io/titles.py +225 -0
- plot_io/transform.py +114 -0
- plot_io/types.py +13604 -0
- plot_io/units.py +768 -0
- plot_io/validators.py +1029 -0
- plot_io/values.py +190 -0
- plot_io-0.12.0.dist-info/METADATA +215 -0
- plot_io-0.12.0.dist-info/RECORD +130 -0
- plot_io-0.12.0.dist-info/WHEEL +4 -0
- plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""plot_io's bundled C library ``plio``: sources (``src/``), built libraries (``bin/``),
|
|
2
|
+
``manifest.json`` and the bindings generated from ``src/plio_api.h`` (``_api.py``).
|
|
3
|
+
Building is described in ``README.md``; ``python -m plot_io._native verify`` checks the
|
|
4
|
+
installed library.
|
|
5
|
+
|
|
6
|
+
The library is loaded on first use, from the first of: ``PLOT_IO_NATIVE_LIB`` (a sanitizer
|
|
7
|
+
build, any other build), ``bin/<platform tag>.<ext>`` (a release binary), ``bin/hosted.<ext>``
|
|
8
|
+
(built with the local C compiler when no release binary fits). At load the library's
|
|
9
|
+
signature table (``plio_signatures``) must equal the bindings': names, argument kinds, and
|
|
10
|
+
the sizes and field offsets of the parameter structures. Calls release the GIL; memory
|
|
11
|
+
comes from Python's raw allocator, passed in a per-thread context that also holds a buffer
|
|
12
|
+
for error messages.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import ctypes
|
|
18
|
+
import os
|
|
19
|
+
import platform
|
|
20
|
+
import sys
|
|
21
|
+
import sysconfig
|
|
22
|
+
import threading
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from . import _api
|
|
26
|
+
|
|
27
|
+
__all__ = ["NativeLibraryError", "lib", "ctx", "platform_tag", "library_path", "check", "take",
|
|
28
|
+
"ptr", "api"]
|
|
29
|
+
|
|
30
|
+
api = _api
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NativeLibraryError(RuntimeError):
|
|
34
|
+
"""The bundled C library is not available on this platform or failed to load."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def platform_tag() -> str | None:
|
|
38
|
+
"""``<os>-<arch>`` of the bundled library for this interpreter, or ``None``."""
|
|
39
|
+
if sys.maxsize <= 2 ** 32:
|
|
40
|
+
return None
|
|
41
|
+
machine = platform.machine().lower()
|
|
42
|
+
if sys.platform.startswith("win"):
|
|
43
|
+
plat = sysconfig.get_platform()
|
|
44
|
+
return {"win-amd64": "windows-x86_64", "win-arm64": "windows-arm64"}.get(plat)
|
|
45
|
+
if sys.platform == "darwin":
|
|
46
|
+
return "macos-arm64" if machine in ("arm64", "aarch64") else None
|
|
47
|
+
if sys.platform.startswith("linux"):
|
|
48
|
+
arch = {"x86_64": "x86_64", "amd64": "x86_64", "aarch64": "aarch64",
|
|
49
|
+
"arm64": "aarch64"}.get(machine)
|
|
50
|
+
return f"linux-{arch}" if arch else None
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_EXT = {"linux": ".so", "windows": ".dll", "macos": ".dylib"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _hosted_ext() -> str:
|
|
58
|
+
return ".dll" if sys.platform.startswith("win") else ".dylib" if sys.platform == "darwin" else ".so"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def library_path() -> str | None:
|
|
62
|
+
"""Path of the library that would be loaded: ``PLOT_IO_NATIVE_LIB``, else the platform's
|
|
63
|
+
binary in ``bin/``, else ``bin/hosted.<ext>``; ``None`` when there is none."""
|
|
64
|
+
env = os.environ.get("PLOT_IO_NATIVE_LIB")
|
|
65
|
+
if env:
|
|
66
|
+
return env
|
|
67
|
+
bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
|
|
68
|
+
tag = platform_tag()
|
|
69
|
+
if tag is not None:
|
|
70
|
+
path = os.path.join(bin_dir, tag + _EXT[tag.split("-")[0]])
|
|
71
|
+
if os.path.exists(path):
|
|
72
|
+
return path
|
|
73
|
+
hosted = os.path.join(bin_dir, "hosted" + _hosted_ext())
|
|
74
|
+
return hosted if os.path.exists(hosted) else None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_lock = threading.Lock()
|
|
78
|
+
_lib: Any = None
|
|
79
|
+
_error: str | None = None
|
|
80
|
+
_local = threading.local()
|
|
81
|
+
_MESSAGE_SIZE = 256
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def signature_problems(handle: Any) -> list[str]:
|
|
85
|
+
"""How the library's signature table differs from the bindings (empty: it matches)."""
|
|
86
|
+
text, numbers = ctypes.c_void_p(), ctypes.c_void_p()
|
|
87
|
+
n = handle.plio_signatures(None, ctypes.byref(text), ctypes.byref(numbers))
|
|
88
|
+
if not text.value or not numbers.value:
|
|
89
|
+
return ["no signature table"]
|
|
90
|
+
got = ctypes.string_at(text.value).decode("ascii").splitlines()
|
|
91
|
+
want = _api.SIGNATURE_TEXT.splitlines()
|
|
92
|
+
out = [f"library has {line!r}" for line in got if line not in want]
|
|
93
|
+
out += [f"library lacks {line!r}" for line in want if line not in got]
|
|
94
|
+
if out or n != len(got):
|
|
95
|
+
return out or [f"{n} signature lines for {len(got)} names"]
|
|
96
|
+
values = (ctypes.c_int64 * n).from_address(numbers.value)
|
|
97
|
+
for line, value in zip(got, values):
|
|
98
|
+
kind, name = line.split()[:2]
|
|
99
|
+
if kind == "s":
|
|
100
|
+
expected = ctypes.sizeof(_api.STRUCTS[name])
|
|
101
|
+
elif kind == "m":
|
|
102
|
+
struct, field = name.split(".")
|
|
103
|
+
expected = getattr(_api.STRUCTS[struct], field).offset
|
|
104
|
+
else:
|
|
105
|
+
continue
|
|
106
|
+
if value != expected:
|
|
107
|
+
out.append(f"{name}: {value} in the library, {expected} in the bindings")
|
|
108
|
+
return out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _load() -> None:
|
|
112
|
+
global _lib, _error
|
|
113
|
+
path = library_path()
|
|
114
|
+
if path is None:
|
|
115
|
+
_error = (f"plot_io's C library is not installed for this platform "
|
|
116
|
+
f"({sys.platform}, {platform.machine()}): install plot_io from the sdist "
|
|
117
|
+
f"with a C compiler (pip install --no-binary plot_io plot_io), or set "
|
|
118
|
+
f"PLOT_IO_NATIVE_LIB to a build of it")
|
|
119
|
+
return
|
|
120
|
+
try:
|
|
121
|
+
handle = ctypes.CDLL(path)
|
|
122
|
+
except OSError as exc:
|
|
123
|
+
_error = f"cannot load plot_io's C library {path}: {exc}"
|
|
124
|
+
return
|
|
125
|
+
try:
|
|
126
|
+
for name, (restype, argtypes) in _api.FUNCTIONS.items():
|
|
127
|
+
fn = getattr(handle, name)
|
|
128
|
+
fn.restype = restype
|
|
129
|
+
fn.argtypes = argtypes
|
|
130
|
+
except AttributeError as exc:
|
|
131
|
+
_error = f"plot_io's C library {path} is incomplete: {exc}"
|
|
132
|
+
return
|
|
133
|
+
abi = handle.plio_abi_version()
|
|
134
|
+
if abi != _api.ABI_VERSION:
|
|
135
|
+
_error = f"plot_io's C library {path} has ABI {abi}, {_api.ABI_VERSION} expected"
|
|
136
|
+
return
|
|
137
|
+
problems = signature_problems(handle)
|
|
138
|
+
if problems:
|
|
139
|
+
_error = (f"plot_io's C library {path} does not match its bindings: "
|
|
140
|
+
+ "; ".join(problems[:5]))
|
|
141
|
+
return
|
|
142
|
+
_lib = handle
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def lib() -> Any:
|
|
146
|
+
"""The loaded library; raises :class:`NativeLibraryError` when it is unavailable."""
|
|
147
|
+
if _lib is None:
|
|
148
|
+
with _lock:
|
|
149
|
+
if _lib is None and _error is None:
|
|
150
|
+
_load()
|
|
151
|
+
if _lib is None:
|
|
152
|
+
raise NativeLibraryError(_error)
|
|
153
|
+
return _lib
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def ctx() -> Any:
|
|
157
|
+
"""The calling thread's context (pointer), the first argument of every function."""
|
|
158
|
+
c = getattr(_local, "ctx", None)
|
|
159
|
+
if c is None:
|
|
160
|
+
lib()
|
|
161
|
+
pyapi = ctypes.pythonapi
|
|
162
|
+
buf = ctypes.create_string_buffer(_MESSAGE_SIZE)
|
|
163
|
+
c = ctypes.pointer(_api.Ctx(
|
|
164
|
+
malloc=ctypes.cast(pyapi.PyMem_RawMalloc, ctypes.c_void_p).value,
|
|
165
|
+
realloc=ctypes.cast(pyapi.PyMem_RawRealloc, ctypes.c_void_p).value,
|
|
166
|
+
free=ctypes.cast(pyapi.PyMem_RawFree, ctypes.c_void_p).value,
|
|
167
|
+
message=ctypes.addressof(buf), message_size=_MESSAGE_SIZE))
|
|
168
|
+
_local.ctx, _local.buf = c, buf
|
|
169
|
+
else:
|
|
170
|
+
_local.buf[0] = 0
|
|
171
|
+
return c
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def check(rc: int, what: str = "plot_io native call", exc: type[Exception] = ValueError) -> int:
|
|
175
|
+
"""``rc`` when it is not negative; otherwise raise ``exc`` (MemoryError when out of
|
|
176
|
+
memory) with the library's message, else the code's."""
|
|
177
|
+
if rc >= 0:
|
|
178
|
+
return rc
|
|
179
|
+
buf = getattr(_local, "buf", None)
|
|
180
|
+
detail = buf.value.decode("utf-8", "replace") if buf is not None and buf.value else ""
|
|
181
|
+
text = detail or _api.ERRORS.get(rc, f"error {rc}")
|
|
182
|
+
if rc == _api.E_NOMEM:
|
|
183
|
+
raise MemoryError(f"{what}: out of memory")
|
|
184
|
+
raise exc(f"{what}: {text}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def take(p: Any, n: int) -> bytes:
|
|
188
|
+
"""Copy ``n`` bytes of a library-allocated result and release it."""
|
|
189
|
+
if not p:
|
|
190
|
+
return b""
|
|
191
|
+
try:
|
|
192
|
+
return ctypes.string_at(p, n)
|
|
193
|
+
finally:
|
|
194
|
+
_lib.plio_free(ctx(), p)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def ptr(a: Any) -> int | None:
|
|
198
|
+
"""Address of an ``array.array``'s data (``None`` when empty or ``None``)."""
|
|
199
|
+
return a.buffer_info()[0] if a is not None and len(a) else None
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""``python -m plot_io._native verify``: check the C library this installation loads.
|
|
2
|
+
|
|
3
|
+
Prints the platform, the file loaded, its SHA-256 and the manifest's ABI and source hash,
|
|
4
|
+
then runs three steps:
|
|
5
|
+
|
|
6
|
+
1. **loads**: the loader's checks (ABI version, the library's signature table against the
|
|
7
|
+
bindings);
|
|
8
|
+
2. **manifest**: the file's SHA-256 is the manifest's entry of its name (a release binary,
|
|
9
|
+
or a file named like one through ``PLOT_IO_NATIVE_LIB``); a hosted build or another
|
|
10
|
+
override has no reference hash, which is not a failure;
|
|
11
|
+
3. **self-test**: a density, a group summary, a sort, a colour map lookup and the library's
|
|
12
|
+
math at 16 points, each output's SHA-256 compared with the one the release binaries give
|
|
13
|
+
(every target computes identical results).
|
|
14
|
+
|
|
15
|
+
Exit status 0 when every step passes, 1 otherwise (the failing steps are named).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import array
|
|
21
|
+
import hashlib
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
25
|
+
import sys
|
|
26
|
+
from typing import Callable
|
|
27
|
+
|
|
28
|
+
# SHA-256 of each self-test's output, from the release binaries
|
|
29
|
+
EXPECTED = {
|
|
30
|
+
"density": "c5d3b904c127c54a6ed045c800f14f6c6809bf66d0a392f8706865ad211c56db",
|
|
31
|
+
"group summary": "695de6b532cd9a9b58927bb5fb4168e9e4abd0e8e0fdc948ba4a8c27b211bc5c",
|
|
32
|
+
"sort": "c198461a79f1cd31a257c64cc9a19817a110d53c6308b655465915c6841f858e",
|
|
33
|
+
"colour map": "f39c20a0456f7c9cacefd4c632e1d9107c53b8b8ee3d4d7c8d949fa78d1d7404",
|
|
34
|
+
"math": "ada4f741f1482fe9e39f5364e179f8000b4bcc4b5c2ca25862c3167181cffbe8",
|
|
35
|
+
"linkage": "60d86a1bd7b6a55b5f82f8461a806782b3e2a8da825ed9221f16e2446c55f524",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _values(n: int) -> array.array:
|
|
40
|
+
"""Fixed values: a few clusters, ties, a NaN."""
|
|
41
|
+
out = array.array("d")
|
|
42
|
+
for i in range(n):
|
|
43
|
+
out.append(((i * 37) % 23) * 0.25 + (i % 5) * 1.5 - 3.0 + (2.75 if i % 7 == 0 else 0.0))
|
|
44
|
+
out[n // 2] = float("nan")
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _density() -> bytes:
|
|
49
|
+
from . import kernels
|
|
50
|
+
|
|
51
|
+
v = array.array("d", [x for x in _values(64) if x == x])
|
|
52
|
+
w = array.array("d", [1.0 + (i % 3) * 0.5 for i in range(len(v))])
|
|
53
|
+
return (kernels.kde(v, None, -4.0, 6.0, 32, 0.6, False).tobytes()
|
|
54
|
+
+ kernels.kde(v, w, -4.0, 6.0, 32, 0.45, True).tobytes())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _group_summary() -> bytes:
|
|
58
|
+
from . import kernels
|
|
59
|
+
|
|
60
|
+
v = _values(48) # 12 rows x 4 columns
|
|
61
|
+
groups = array.array("q", [i % 3 - (i == 5) for i in range(12)])
|
|
62
|
+
return b"".join(a.tobytes() for a in kernels.group_summary(v, 12, 4, groups, 3, 0.5))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _sort() -> bytes:
|
|
66
|
+
from . import kernels
|
|
67
|
+
|
|
68
|
+
v = _values(200)
|
|
69
|
+
v[3], v[17] = float("inf"), -0.0
|
|
70
|
+
return kernels.argsort(v, False).tobytes() + kernels.argsort(v, True).tobytes()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _colour_map() -> bytes:
|
|
74
|
+
from . import kernels
|
|
75
|
+
|
|
76
|
+
stops = [(0.0, (0.1, 0.2, 0.6, 1.0)), (0.4, (0.9, 0.9, 0.3, 0.8)), (1.0, (0.7, 0.1, 0.1, 1.0))]
|
|
77
|
+
ts = array.array("d", [i / 15 for i in range(16)])
|
|
78
|
+
out = b""
|
|
79
|
+
for mode in ("linear", "smooth", "spline"):
|
|
80
|
+
for space in ("srgb", "oklab"):
|
|
81
|
+
out += kernels.colormap_eval(stops, mode, space, ts).tobytes()
|
|
82
|
+
table = array.array("d", [c / 7 for c in range(4 * 8)])
|
|
83
|
+
vals = array.array("d", [-0.5, 0.0, 0.1, 0.33, 0.5, 0.77, 1.0, 1.5, float("nan")])
|
|
84
|
+
return out + kernels.colors_at(vals, table, None, (0, 0, 0, 1), (1, 1, 1, 1),
|
|
85
|
+
(0.5, 0.5, 0.5, 0)).tobytes()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _math() -> bytes:
|
|
89
|
+
import ctypes
|
|
90
|
+
|
|
91
|
+
from . import _api, ctx, lib, ptr
|
|
92
|
+
|
|
93
|
+
xs = array.array("d", [-7.5, -2.25, -1.0, -0.5, -1e-3, 0.0, 1e-3, 0.3, 0.5, 0.75, 1.0,
|
|
94
|
+
1.5, 2.5, 3.14159, 10.0, 123.456])
|
|
95
|
+
ys = array.array("d", [0.5 + i * 0.37 for i in range(16)])
|
|
96
|
+
positive = (_api.MATH_SQRT, _api.MATH_LOG, _api.MATH_POW)
|
|
97
|
+
out = b""
|
|
98
|
+
for fn in range(_api.MATH_COS + 1):
|
|
99
|
+
res = array.array("d", bytes(8 * len(xs)))
|
|
100
|
+
# sqrt, log and pow on positive arguments: a NaN's sign bit is the compiler's
|
|
101
|
+
x = xs if fn not in positive else array.array("d", [abs(v) + 0.25 for v in xs])
|
|
102
|
+
lib().plio_math_eval(ctx(), fn, ptr(x), ptr(ys), ctypes.c_void_p(ptr(res)), len(xs))
|
|
103
|
+
out += res.tobytes()
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _linkage() -> bytes:
|
|
108
|
+
from . import kernels
|
|
109
|
+
|
|
110
|
+
v = array.array("d", [x for x in _values(61) if x == x]) # 20 rows x 3
|
|
111
|
+
out = b""
|
|
112
|
+
for method, metric in (("single", "cityblock"), ("average", "correlation"),
|
|
113
|
+
("ward", "euclidean"), ("centroid", "euclidean")):
|
|
114
|
+
out += kernels.linkage(v, 20, 3, method, metric).tobytes()
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
TESTS: dict[str, Callable[[], bytes]] = {
|
|
119
|
+
"density": _density, "group summary": _group_summary, "sort": _sort,
|
|
120
|
+
"colour map": _colour_map, "math": _math, "linkage": _linkage,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def self_test() -> dict[str, str]:
|
|
125
|
+
"""The SHA-256 of each self-test's output (an exception's text when one fails)."""
|
|
126
|
+
out = {}
|
|
127
|
+
for name, fn in TESTS.items():
|
|
128
|
+
try:
|
|
129
|
+
out[name] = hashlib.sha256(fn()).hexdigest()
|
|
130
|
+
except Exception as exc: # a failing call is a failed step
|
|
131
|
+
out[name] = f"{type(exc).__name__}: {exc}"
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _sha256(path: str) -> str:
|
|
136
|
+
with open(path, "rb") as f:
|
|
137
|
+
return hashlib.sha256(f.read()).hexdigest()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def verify() -> int:
|
|
141
|
+
from . import NativeLibraryError, library_path, lib, platform_tag
|
|
142
|
+
|
|
143
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
144
|
+
try:
|
|
145
|
+
with open(os.path.join(here, "manifest.json")) as f:
|
|
146
|
+
manifest = json.load(f)
|
|
147
|
+
except OSError:
|
|
148
|
+
manifest = {}
|
|
149
|
+
path = library_path()
|
|
150
|
+
print(f"platform {platform_tag() or 'none'} ({sys.platform}, {platform.machine()})")
|
|
151
|
+
print(f"file {path or 'none'}")
|
|
152
|
+
digest = _sha256(path) if path and os.path.isfile(path) else None
|
|
153
|
+
print(f"sha256 {digest or '-'}")
|
|
154
|
+
print(f"manifest ABI {manifest.get('abi', '-')}, sources {manifest.get('source_sha256', '-')}")
|
|
155
|
+
failed = []
|
|
156
|
+
try:
|
|
157
|
+
lib()
|
|
158
|
+
print("loads ok")
|
|
159
|
+
except NativeLibraryError as exc:
|
|
160
|
+
print(f"loads FAIL: {exc}")
|
|
161
|
+
failed.append("loads")
|
|
162
|
+
entry = manifest.get("files", {}).get(os.path.basename(path or ""))
|
|
163
|
+
if digest is None:
|
|
164
|
+
print("manifest FAIL: no library file")
|
|
165
|
+
failed.append("manifest")
|
|
166
|
+
elif entry is None:
|
|
167
|
+
print("manifest no reference hash (not a release binary)")
|
|
168
|
+
elif entry.get("sha256") == digest:
|
|
169
|
+
print("manifest ok")
|
|
170
|
+
else:
|
|
171
|
+
print(f"manifest FAIL: the manifest says {entry.get('sha256')}")
|
|
172
|
+
failed.append("manifest")
|
|
173
|
+
if "loads" not in failed:
|
|
174
|
+
results = self_test()
|
|
175
|
+
bad = [n for n, h in results.items() if h != EXPECTED[n]]
|
|
176
|
+
for n in bad:
|
|
177
|
+
print(f"self-test FAIL: {n}: {results[n]}")
|
|
178
|
+
if bad:
|
|
179
|
+
failed.append("self-test")
|
|
180
|
+
else:
|
|
181
|
+
print(f"self-test ok ({len(results)} checks)")
|
|
182
|
+
if failed:
|
|
183
|
+
print(f"failed: {', '.join(failed)}")
|
|
184
|
+
return 1
|
|
185
|
+
return 0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main(argv: list[str] | None = None) -> int:
|
|
189
|
+
args = sys.argv[1:] if argv is None else argv
|
|
190
|
+
if args != ["verify"]:
|
|
191
|
+
print("usage: python -m plot_io._native verify", file=sys.stderr)
|
|
192
|
+
return 2
|
|
193
|
+
return verify()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
sys.exit(main())
|