cppkh-interface 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.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.
- cppkh_interface/data/src/main.cpp +3757 -0
- cppkh_interface/main.py +58 -52
- {cppkh_interface-0.2.0.dist-info → cppkh_interface-0.2.1.dist-info}/METADATA +19 -2
- cppkh_interface-0.2.1.dist-info/RECORD +9 -0
- {cppkh_interface-0.2.0.dist-info → cppkh_interface-0.2.1.dist-info}/WHEEL +1 -1
- cppkh_interface-0.2.0.dist-info/RECORD +0 -8
- {cppkh_interface-0.2.0.dist-info → cppkh_interface-0.2.1.dist-info}/entry_points.txt +0 -0
cppkh_interface/main.py
CHANGED
|
@@ -14,8 +14,8 @@ import shutil
|
|
|
14
14
|
import subprocess
|
|
15
15
|
import sys
|
|
16
16
|
import tempfile
|
|
17
|
-
import uuid
|
|
18
|
-
import threading
|
|
17
|
+
import uuid
|
|
18
|
+
import threading
|
|
19
19
|
from importlib import resources
|
|
20
20
|
from typing import Optional, Sequence, Union
|
|
21
21
|
|
|
@@ -46,7 +46,7 @@ def _parse_x_crossings(text: str) -> Optional[list[list[int]]]:
|
|
|
46
46
|
return crossings if crossings else None
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def _as_crossings(pd_code: PdInput) -> list[list[int]]:
|
|
49
|
+
def _as_crossings(pd_code: PdInput) -> list[list[int]]:
|
|
50
50
|
if isinstance(pd_code, str):
|
|
51
51
|
body = pd_code.strip()
|
|
52
52
|
if ":" in body:
|
|
@@ -71,16 +71,16 @@ def _as_crossings(pd_code: PdInput) -> list[list[int]]:
|
|
|
71
71
|
if len(values) != 4:
|
|
72
72
|
raise ValueError(f"PD crossing must have four entries: {crossing!r}")
|
|
73
73
|
crossings.append([int(item) for item in values])
|
|
74
|
-
return crossings
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _check_sanity(crossings: list[list[int]]) -> None:
|
|
78
|
-
counts = {}
|
|
79
|
-
for crossing in crossings:
|
|
80
|
-
for label in crossing:
|
|
81
|
-
counts[label] = counts.get(label, 0) + 1
|
|
82
|
-
if any(count != 2 for count in counts.values()):
|
|
83
|
-
raise TypeError("each PD label must occur exactly twice")
|
|
74
|
+
return crossings
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _check_sanity(crossings: list[list[int]]) -> None:
|
|
78
|
+
counts = {}
|
|
79
|
+
for crossing in crossings:
|
|
80
|
+
for label in crossing:
|
|
81
|
+
counts[label] = counts.get(label, 0) + 1
|
|
82
|
+
if any(count != 2 for count in counts.values()):
|
|
83
|
+
raise TypeError("each PD label must occur exactly twice")
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def normalize_pd_code(pd_code: PdInput) -> str:
|
|
@@ -153,14 +153,14 @@ def _exe_suffix() -> str:
|
|
|
153
153
|
return ".exe" if platform.system() == "Windows" else ""
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
def _shared_suffix() -> str:
|
|
157
|
-
return ".dll" if platform.system() == "Windows" else (".dylib" if platform.system() == "Darwin" else ".so")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _compiler_parts(command: str) -> list[str]:
|
|
161
|
-
command = command.strip()
|
|
162
|
-
if not command:
|
|
163
|
-
return []
|
|
156
|
+
def _shared_suffix() -> str:
|
|
157
|
+
return ".dll" if platform.system() == "Windows" else (".dylib" if platform.system() == "Darwin" else ".so")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _compiler_parts(command: str) -> list[str]:
|
|
161
|
+
command = command.strip()
|
|
162
|
+
if not command:
|
|
163
|
+
return []
|
|
164
164
|
unquoted = command
|
|
165
165
|
if len(unquoted) >= 2 and unquoted[0] == unquoted[-1] and unquoted[0] in ("'", '"'):
|
|
166
166
|
unquoted = unquoted[1:-1]
|
|
@@ -230,9 +230,9 @@ def _resolve_compiler(cxx: Optional[str] = None) -> list[str]:
|
|
|
230
230
|
)
|
|
231
231
|
|
|
232
232
|
|
|
233
|
-
def _compiler_runtime_path_entries(compiler: Sequence[str]) -> list[str]:
|
|
234
|
-
paths = []
|
|
235
|
-
for candidate in compiler:
|
|
233
|
+
def _compiler_runtime_path_entries(compiler: Sequence[str]) -> list[str]:
|
|
234
|
+
paths = []
|
|
235
|
+
for candidate in compiler:
|
|
236
236
|
resolved = shutil.which(candidate) or candidate
|
|
237
237
|
path = pathlib.Path(resolved)
|
|
238
238
|
if path.exists() and path.is_file():
|
|
@@ -322,30 +322,30 @@ def compile_cppkh(
|
|
|
322
322
|
pass
|
|
323
323
|
|
|
324
324
|
|
|
325
|
-
def compile_cppkh_shared(*, force: bool = False) -> pathlib.Path:
|
|
325
|
+
def compile_cppkh_shared(*, force: bool = False) -> pathlib.Path:
|
|
326
326
|
"""Compile and cache the cppkh C API shared library."""
|
|
327
327
|
with _resource_source_path() as source:
|
|
328
|
-
source_path = pathlib.Path(source)
|
|
329
|
-
compiler = _resolve_compiler()
|
|
330
|
-
flags = _default_compile_flags() + ["-shared", "-DCPPKH_SHARED_LIBRARY"]
|
|
331
|
-
cache = _cache_dir()
|
|
332
|
-
library = cache / f"cppkh-{_cache_key(source_path.read_bytes(), flags, compiler)}{_shared_suffix()}"
|
|
333
|
-
if library.exists() and not force:
|
|
334
|
-
return library
|
|
335
|
-
temporary = cache / f"{library.name}.tmp-{os.getpid()}-{uuid.uuid4().hex}{_shared_suffix()}"
|
|
336
|
-
try:
|
|
337
|
-
result = _compile_source(compiler, source_path, temporary, flags)
|
|
338
|
-
if result.returncode != 0 and "-march=native" in flags:
|
|
339
|
-
result = _compile_source(compiler, source_path, temporary, [flag for flag in flags if flag != "-march=native"])
|
|
340
|
-
if result.returncode != 0:
|
|
341
|
-
raise CppkhInterfaceError((result.stderr or result.stdout or "C++ shared compilation failed").strip())
|
|
342
|
-
os.replace(temporary, library)
|
|
343
|
-
return library
|
|
344
|
-
finally:
|
|
345
|
-
try:
|
|
346
|
-
temporary.unlink()
|
|
347
|
-
except OSError:
|
|
348
|
-
pass
|
|
328
|
+
source_path = pathlib.Path(source)
|
|
329
|
+
compiler = _resolve_compiler()
|
|
330
|
+
flags = _default_compile_flags() + ["-shared", "-DCPPKH_SHARED_LIBRARY"]
|
|
331
|
+
cache = _cache_dir()
|
|
332
|
+
library = cache / f"cppkh-{_cache_key(source_path.read_bytes(), flags, compiler)}{_shared_suffix()}"
|
|
333
|
+
if library.exists() and not force:
|
|
334
|
+
return library
|
|
335
|
+
temporary = cache / f"{library.name}.tmp-{os.getpid()}-{uuid.uuid4().hex}{_shared_suffix()}"
|
|
336
|
+
try:
|
|
337
|
+
result = _compile_source(compiler, source_path, temporary, flags)
|
|
338
|
+
if result.returncode != 0 and "-march=native" in flags:
|
|
339
|
+
result = _compile_source(compiler, source_path, temporary, [flag for flag in flags if flag != "-march=native"])
|
|
340
|
+
if result.returncode != 0:
|
|
341
|
+
raise CppkhInterfaceError((result.stderr or result.stdout or "C++ shared compilation failed").strip())
|
|
342
|
+
os.replace(temporary, library)
|
|
343
|
+
return library
|
|
344
|
+
finally:
|
|
345
|
+
try:
|
|
346
|
+
temporary.unlink()
|
|
347
|
+
except OSError:
|
|
348
|
+
pass
|
|
349
349
|
|
|
350
350
|
|
|
351
351
|
_shared_lock = threading.Lock()
|
|
@@ -357,7 +357,7 @@ def _load_shared_library():
|
|
|
357
357
|
global _shared_library
|
|
358
358
|
if _shared_library is not None:
|
|
359
359
|
return _shared_library
|
|
360
|
-
runtime_paths = _compiler_runtime_path_entries(_resolve_compiler())
|
|
360
|
+
runtime_paths = _compiler_runtime_path_entries(_resolve_compiler())
|
|
361
361
|
if runtime_paths:
|
|
362
362
|
os.environ["PATH"] = os.pathsep.join(runtime_paths + [os.environ.get("PATH", "")])
|
|
363
363
|
library_path = compile_cppkh_shared()
|
|
@@ -373,11 +373,17 @@ def _load_shared_library():
|
|
|
373
373
|
return library
|
|
374
374
|
|
|
375
375
|
|
|
376
|
-
def compute_signed_variants(pd_code: PdInput, signs: Sequence[Sequence[int]]) -> list[str]:
|
|
377
|
-
"""Compute
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
376
|
+
def compute_signed_variants(pd_code: PdInput, signs: Sequence[Sequence[int]]) -> list[str]:
|
|
377
|
+
"""Compute explicit crossing-sign variants in one native call.
|
|
378
|
+
|
|
379
|
+
This additive API does not simplify the PD code because every sign row
|
|
380
|
+
corresponds positionally to the original crossing list.
|
|
381
|
+
"""
|
|
382
|
+
crossings = _as_crossings(pd_code)
|
|
383
|
+
_check_sanity(crossings)
|
|
384
|
+
rows = [list(row) for row in signs]
|
|
385
|
+
if not rows:
|
|
386
|
+
raise ValueError("at least one crossing-sign row is required")
|
|
381
387
|
if any(len(row) != len(crossings) or any(sign not in (-1, 1) for sign in row) for row in rows):
|
|
382
388
|
raise ValueError("each sign row must contain one +1/-1 value per crossing")
|
|
383
389
|
pd_text = _format_pd(crossings).encode("utf-8")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cppkh-interface
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Dependency-free Python interface for cppkh with runtime C++ compilation.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Author: GGN_2015
|
|
@@ -22,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
`cppkh-interface` is a Python package for computing integer Khovanov homology
|
|
23
23
|
with the C++ `cppkh` implementation.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The package has no runtime Python-package dependencies. Link crossing signs,
|
|
26
26
|
PD validation, R1 removal, and nugatory-crossing removal all use the bundled
|
|
27
27
|
canonical `cppkh` C++ source and its SageMath-compatible orientation rules.
|
|
28
28
|
|
|
@@ -36,6 +36,23 @@ print(cppkh_interface.solve_khovanov(pd_code, de_r1=True, de_k8=True))
|
|
|
36
36
|
print(cppkh_interface.solve_many_khovanov([pd_code, pd_code]))
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
For a multi-component oriented link, callers can compute several explicit
|
|
40
|
+
crossing-sign variants without changing the existing APIs:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from cppkh_interface import compute_signed_variants
|
|
44
|
+
|
|
45
|
+
hopf = [[2, 3, 1, 4], [4, 1, 3, 2]]
|
|
46
|
+
results = compute_signed_variants(hopf, [[-1, -1], [1, 1]])
|
|
47
|
+
assert len(results) == 2
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Each sign row must contain exactly one `-1` or `1` for every crossing. This
|
|
51
|
+
operation deliberately disables PD simplification because removing a crossing
|
|
52
|
+
would invalidate the positional sign mapping. `solve_khovanov` and
|
|
53
|
+
`solve_many_khovanov` retain their original signatures, inferred-sign behavior,
|
|
54
|
+
and simplification defaults.
|
|
55
|
+
|
|
39
56
|
Unlike wrappers that ship a prebuilt DLL or shared object, this package ships
|
|
40
57
|
the `cppkh` C++ source file in built distributions and compiles a local
|
|
41
58
|
executable on first use using only Python's standard library. The compiled
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cppkh_interface/__init__.py,sha256=3UnzgTQISAEQ49eCYzNO7avSVE69a-9OmAmYoyXiN3Q,632
|
|
2
|
+
cppkh_interface/__main__.py,sha256=V_N9eIkVUScS7MAKc_ZQlPTpjOKo9C9y_OU24bKGjzE,84
|
|
3
|
+
cppkh_interface/main.py,sha256=3mB2PSaqsWsdTkNvIPbo2ddd4WAM8gFvd-2ZyS4oUmg,22492
|
|
4
|
+
cppkh_interface/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
5
|
+
cppkh_interface-0.2.1.dist-info/entry_points.txt,sha256=CsnHBNaO_RJe4eFXmEZpSxWu-c1aF0RpjVFaEGlIm4Y,61
|
|
6
|
+
cppkh_interface-0.2.1.dist-info/METADATA,sha256=xOOQwSwQmm6bJwG91-bKOtkp0v5U2v-owE08KN-ZfeE,3452
|
|
7
|
+
cppkh_interface-0.2.1.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
8
|
+
cppkh_interface/data/src/main.cpp,sha256=B4vC99VK4uObcne_ri7yKZE1G5Ra3c0dcmAQPVhZaQM,153584
|
|
9
|
+
cppkh_interface-0.2.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
cppkh_interface/__init__.py,sha256=3UnzgTQISAEQ49eCYzNO7avSVE69a-9OmAmYoyXiN3Q,632
|
|
2
|
-
cppkh_interface/__main__.py,sha256=V_N9eIkVUScS7MAKc_ZQlPTpjOKo9C9y_OU24bKGjzE,84
|
|
3
|
-
cppkh_interface/main.py,sha256=m8e62e9x37fAYRL6V040fiX8aCxYDBtdnnXplPSQph8,22229
|
|
4
|
-
cppkh_interface/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
5
|
-
cppkh_interface-0.2.0.dist-info/entry_points.txt,sha256=CsnHBNaO_RJe4eFXmEZpSxWu-c1aF0RpjVFaEGlIm4Y,61
|
|
6
|
-
cppkh_interface-0.2.0.dist-info/METADATA,sha256=W2Z_pWAAW-QXNNvdtKGZj2Xq20VDeYKlmo-GfnViCEE,2802
|
|
7
|
-
cppkh_interface-0.2.0.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
|
|
8
|
-
cppkh_interface-0.2.0.dist-info/RECORD,,
|
|
File without changes
|