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/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 several explicit crossing-sign variants in one native call."""
378
- crossings = _as_crossings(pd_code)
379
- _check_sanity(crossings)
380
- rows = [list(row) for row in signs]
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.0
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
- Version `0.1.3` has no runtime Python-package dependencies. Link crossing signs,
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.4.0
2
+ Generator: poetry-core 2.4.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,