cppkh-interface 0.1.0__py3-none-any.whl → 0.1.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.
@@ -1,19 +1,25 @@
1
1
  from .main import (
2
2
  CppkhInterfaceError,
3
3
  compile_cppkh,
4
+ compute_many_pd,
4
5
  compute_pd,
5
6
  get_cppkh_executable,
6
7
  normalize_pd_code,
8
+ normalize_pd_codes,
7
9
  simplify_pd,
10
+ solve_many_khovanov,
8
11
  solve_khovanov,
9
12
  )
10
13
 
11
14
  __all__ = [
12
15
  "CppkhInterfaceError",
13
16
  "compile_cppkh",
17
+ "compute_many_pd",
14
18
  "compute_pd",
15
19
  "get_cppkh_executable",
16
20
  "normalize_pd_code",
21
+ "normalize_pd_codes",
17
22
  "simplify_pd",
23
+ "solve_many_khovanov",
18
24
  "solve_khovanov",
19
25
  ]
@@ -3409,6 +3409,38 @@ CPPKH_API char* cppkh_compute_pd(const char* pd_code) {
3409
3409
  return cppkh_compute_pd_ex(pd_code, 1, 1);
3410
3410
  }
3411
3411
 
3412
+ CPPKH_API char* cppkh_compute_pd_batch_ex(const char* pd_codes, int simplify_pd, int reorder_crossings) {
3413
+ try {
3414
+ g_cppkhLastError.clear();
3415
+ if (!pd_codes) throw std::runtime_error("pd_codes is null");
3416
+ std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_codes, "ctypes-batch");
3417
+ if (parsed.empty()) throw std::runtime_error("no PD code found");
3418
+
3419
+ CppkhOptionsGuard guard;
3420
+ kh::g_options.progress = false;
3421
+ kh::g_options.profile = false;
3422
+ kh::g_options.simplifyPD = simplify_pd != 0;
3423
+ kh::g_options.reorderCrossings = reorder_crossings != 0;
3424
+
3425
+ std::ostringstream out;
3426
+ for (size_t i = 0; i < parsed.size(); ++i) {
3427
+ if (i) out << "\n";
3428
+ out << kh::computePD(parsed[i].second);
3429
+ }
3430
+ return cppkhDuplicateString(out.str());
3431
+ } catch (const std::exception& e) {
3432
+ g_cppkhLastError = e.what();
3433
+ return nullptr;
3434
+ } catch (...) {
3435
+ g_cppkhLastError = "unknown error";
3436
+ return nullptr;
3437
+ }
3438
+ }
3439
+
3440
+ CPPKH_API char* cppkh_compute_pd_batch(const char* pd_codes) {
3441
+ return cppkh_compute_pd_batch_ex(pd_codes, 1, 1);
3442
+ }
3443
+
3412
3444
  CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
3413
3445
  try {
3414
3446
  g_cppkhLastError.clear();
cppkh_interface/main.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import ast
5
+ import contextlib
5
6
  import hashlib
6
7
  import os
7
8
  import pathlib
@@ -22,6 +23,7 @@ import pd_code_sanity
22
23
 
23
24
  PathLike = Union[str, os.PathLike]
24
25
  PdInput = Union[str, Sequence[Sequence[int]]]
26
+ PdManyInput = Union[str, Sequence[PdInput]]
25
27
  UNKNOT_RESULT = "q^-1*t^0*Z[0] + q^1*t^0*Z[0]"
26
28
 
27
29
 
@@ -88,9 +90,37 @@ def normalize_pd_code(pd_code: PdInput) -> str:
88
90
  return _format_pd(_as_crossings(pd_code))
89
91
 
90
92
 
93
+ def normalize_pd_codes(pd_codes: PdManyInput) -> str:
94
+ """Normalize one or more PD codes into a newline-separated PD document."""
95
+
96
+ if isinstance(pd_codes, str):
97
+ return pd_codes.strip()
98
+ return "\n".join(normalize_pd_code(pd_code) for pd_code in pd_codes)
99
+
100
+
101
+ @contextlib.contextmanager
91
102
  def _resource_source_path():
92
103
  source = resources.files("cppkh_interface") / "data" / "src" / "main.cpp"
93
- return resources.as_file(source)
104
+ try:
105
+ with resources.as_file(source) as resource_path:
106
+ if pathlib.Path(resource_path).exists():
107
+ yield pathlib.Path(resource_path)
108
+ return
109
+ except FileNotFoundError:
110
+ pass
111
+
112
+ current = pathlib.Path(__file__).resolve()
113
+ for parent in current.parents:
114
+ candidate = parent / "src" / "main.cpp"
115
+ if candidate.exists():
116
+ yield candidate
117
+ return
118
+
119
+ raise CppkhInterfaceError(
120
+ "cppkh C++ source was not found. Installed wheels include it under "
121
+ "cppkh_interface/data/src/main.cpp; editable checkouts use the "
122
+ "repository root src/main.cpp."
123
+ )
94
124
 
95
125
 
96
126
  def _cache_dir() -> pathlib.Path:
@@ -217,17 +247,19 @@ def get_cppkh_executable() -> pathlib.Path:
217
247
  return compile_cppkh()
218
248
 
219
249
 
220
- def _run_cppkh(
250
+ def _run_cppkh_document(
221
251
  pd_text: str,
222
252
  *,
223
253
  encoding: Optional[str] = None,
224
254
  threads: Union[str, int] = "1",
255
+ simplify_pd: bool = False,
225
256
  print_simplified_pd: bool = False,
226
- ) -> str:
257
+ ) -> list[str]:
227
258
  exe = compile_cppkh()
228
259
  with tempfile.NamedTemporaryFile("w", suffix=".pd", encoding="utf-8", delete=False) as handle:
229
260
  handle.write(pd_text)
230
- handle.write("\n")
261
+ if pd_text and not pd_text.endswith("\n"):
262
+ handle.write("\n")
231
263
  pd_file = handle.name
232
264
 
233
265
  command = [
@@ -237,8 +269,9 @@ def _run_cppkh(
237
269
  "--quiet",
238
270
  "--threads",
239
271
  str(threads),
240
- "--no-simplify-pd",
241
272
  ]
273
+ if not simplify_pd:
274
+ command.append("--no-simplify-pd")
242
275
  if print_simplified_pd:
243
276
  command.append("--print-simplified-pd")
244
277
 
@@ -270,12 +303,32 @@ def _run_cppkh(
270
303
  raise CppkhInterfaceError(detail or f"cppkh exited with code {result.returncode}")
271
304
 
272
305
  if print_simplified_pd:
273
- return result.stdout.strip()
306
+ return [line.strip() for line in result.stdout.splitlines() if line.strip()]
274
307
 
275
308
  matches = re.findall(r'"([^"]*)"', result.stdout)
276
309
  if not matches:
277
310
  raise CppkhInterfaceError(f"result not found in cppkh output: {result.stdout!r}")
278
- return matches[-1]
311
+ return matches
312
+
313
+
314
+ def _run_cppkh(
315
+ pd_text: str,
316
+ *,
317
+ encoding: Optional[str] = None,
318
+ threads: Union[str, int] = "1",
319
+ simplify_pd: bool = False,
320
+ print_simplified_pd: bool = False,
321
+ ) -> str:
322
+ results = _run_cppkh_document(
323
+ pd_text,
324
+ encoding=encoding,
325
+ threads=threads,
326
+ simplify_pd=simplify_pd,
327
+ print_simplified_pd=print_simplified_pd,
328
+ )
329
+ if len(results) != 1:
330
+ raise CppkhInterfaceError(f"expected exactly one result, got {len(results)}")
331
+ return results[0]
279
332
 
280
333
 
281
334
  def _prepare_crossings(pd_code: PdInput, de_r1: bool, de_k8: bool) -> list[list[int]]:
@@ -288,6 +341,29 @@ def _prepare_crossings(pd_code: PdInput, de_r1: bool, de_k8: bool) -> list[list[
288
341
  return crossings
289
342
 
290
343
 
344
+ def _prepare_many_for_cppkh(
345
+ pd_codes: PdManyInput,
346
+ *,
347
+ de_r1: bool,
348
+ de_k8: bool,
349
+ ) -> tuple[str, bool]:
350
+ if de_r1 != de_k8:
351
+ raise ValueError(
352
+ "cppkh batch mode supports de_r1 and de_k8 only as a pair. "
353
+ "Use both True for backend R1+nugatory simplification or both False for raw PD input."
354
+ )
355
+ if isinstance(pd_codes, str):
356
+ return pd_codes.strip(), bool(de_r1 and de_k8)
357
+
358
+ prepared = []
359
+ use_cpp_simplify = bool(de_r1 and de_k8)
360
+ for pd_code in pd_codes:
361
+ crossings = _as_crossings(pd_code)
362
+ _check_sanity(crossings)
363
+ prepared.append(_format_pd(crossings))
364
+ return "\n".join(prepared), use_cpp_simplify
365
+
366
+
291
367
  def solve_khovanov(
292
368
  pd_code: PdInput,
293
369
  encoding: Optional[str] = None,
@@ -297,6 +373,24 @@ def solve_khovanov(
297
373
  ) -> str:
298
374
  """Compute Khovanov homology with a javakh-interface compatible signature."""
299
375
 
376
+ if de_r1 == de_k8:
377
+ crossings = _as_crossings(pd_code)
378
+ _check_sanity(crossings)
379
+ if show_real_pdcode:
380
+ if de_r1:
381
+ simplified = _run_cppkh_document(
382
+ _format_pd(crossings),
383
+ encoding=encoding,
384
+ simplify_pd=True,
385
+ print_simplified_pd=True,
386
+ )
387
+ print(f"Real PD code after de_r1 and de_k8: {simplified[0] if simplified else ''}")
388
+ else:
389
+ print(f"Real PD code after de_r1 and de_k8: {crossings}")
390
+ if crossings == []:
391
+ return UNKNOT_RESULT
392
+ return _run_cppkh(_format_pd(crossings), encoding=encoding, simplify_pd=de_r1)
393
+
300
394
  crossings = _prepare_crossings(pd_code, de_r1=de_r1, de_k8=de_k8)
301
395
  if show_real_pdcode:
302
396
  print(f"Real PD code after de_r1 and de_k8: {crossings}")
@@ -305,6 +399,39 @@ def solve_khovanov(
305
399
  return _run_cppkh(_format_pd(crossings), encoding=encoding)
306
400
 
307
401
 
402
+ def solve_many_khovanov(
403
+ pd_codes: PdManyInput,
404
+ encoding: Optional[str] = None,
405
+ de_r1: bool = True,
406
+ de_k8: bool = True,
407
+ show_real_pdcode: bool = False,
408
+ threads: Union[str, int] = "1",
409
+ ) -> list[str]:
410
+ """Compute many PD codes in one cppkh process.
411
+
412
+ With the default ``de_r1=True`` and ``de_k8=True`` settings, the raw PD
413
+ document is passed directly to cppkh and the C++ simplifier handles R1 then
414
+ nugatory crossing removal for the whole batch.
415
+ """
416
+
417
+ document, use_cpp_simplify = _prepare_many_for_cppkh(pd_codes, de_r1=de_r1, de_k8=de_k8)
418
+ if show_real_pdcode:
419
+ if use_cpp_simplify:
420
+ simplified = _run_cppkh_document(
421
+ document,
422
+ encoding=encoding,
423
+ threads=threads,
424
+ simplify_pd=True,
425
+ print_simplified_pd=True,
426
+ )
427
+ print(f"Real PD code after de_r1 and de_k8: {simplified}")
428
+ else:
429
+ print(f"Real PD code after de_r1 and de_k8: {document.splitlines()}")
430
+ if not document:
431
+ return []
432
+ return _run_cppkh_document(document, encoding=encoding, threads=threads, simplify_pd=use_cpp_simplify)
433
+
434
+
308
435
  def compute_pd(
309
436
  pd_code: PdInput,
310
437
  *,
@@ -316,6 +443,25 @@ def compute_pd(
316
443
  ) -> str:
317
444
  """Compute Khovanov homology using the same defaults as solve_khovanov."""
318
445
 
446
+ if de_r1 == de_k8:
447
+ crossings = _as_crossings(pd_code)
448
+ _check_sanity(crossings)
449
+ if show_real_pdcode:
450
+ if de_r1:
451
+ simplified = _run_cppkh_document(
452
+ _format_pd(crossings),
453
+ encoding=encoding,
454
+ threads=threads,
455
+ simplify_pd=True,
456
+ print_simplified_pd=True,
457
+ )
458
+ print(f"Real PD code after de_r1 and de_k8: {simplified[0] if simplified else ''}")
459
+ else:
460
+ print(f"Real PD code after de_r1 and de_k8: {crossings}")
461
+ if crossings == []:
462
+ return UNKNOT_RESULT
463
+ return _run_cppkh(_format_pd(crossings), encoding=encoding, threads=threads, simplify_pd=de_r1)
464
+
319
465
  crossings = _prepare_crossings(pd_code, de_r1=de_r1, de_k8=de_k8)
320
466
  if show_real_pdcode:
321
467
  print(f"Real PD code after de_r1 and de_k8: {crossings}")
@@ -324,6 +470,27 @@ def compute_pd(
324
470
  return _run_cppkh(_format_pd(crossings), encoding=encoding, threads=threads)
325
471
 
326
472
 
473
+ def compute_many_pd(
474
+ pd_codes: PdManyInput,
475
+ *,
476
+ encoding: Optional[str] = None,
477
+ de_r1: bool = True,
478
+ de_k8: bool = True,
479
+ show_real_pdcode: bool = False,
480
+ threads: Union[str, int] = "1",
481
+ ) -> list[str]:
482
+ """Compute many PD codes in one cached cppkh executable invocation."""
483
+
484
+ return solve_many_khovanov(
485
+ pd_codes,
486
+ encoding=encoding,
487
+ de_r1=de_r1,
488
+ de_k8=de_k8,
489
+ show_real_pdcode=show_real_pdcode,
490
+ threads=threads,
491
+ )
492
+
493
+
327
494
  def simplify_pd(pd_code: PdInput, *, de_r1: bool = True, de_k8: bool = True) -> str:
328
495
  """Return the normalized PD string after optional R1 and nugatory simplification."""
329
496
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cppkh-interface
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python interface for cppkh Khovanov homology computation with runtime C++ compilation.
5
5
  License-Expression: MIT
6
6
  Author: GGN_2015
@@ -33,11 +33,18 @@ import cppkh_interface
33
33
 
34
34
  pd_code = [[1, 5, 2, 4], [3, 1, 4, 6], [5, 3, 6, 2]]
35
35
  print(cppkh_interface.solve_khovanov(pd_code, de_r1=True, de_k8=True))
36
+ print(cppkh_interface.solve_many_khovanov([pd_code, pd_code]))
36
37
  ```
37
38
 
38
39
  Unlike wrappers that ship a prebuilt DLL or shared object, this package ships
39
- the `cppkh` C++ source file and compiles a local executable on first use through
40
- `cpp-simple-interface`. The compiled executable is cached for later calls.
40
+ the `cppkh` C++ source file in built distributions and compiles a local
41
+ executable on first use through `cpp-simple-interface`. The compiled executable
42
+ is cached for later calls.
43
+
44
+ In the repository checkout, the package does not keep a committed backup copy
45
+ of the C++ source. The build backend copies `../../src/main.cpp` into the
46
+ package data directory only while `poetry build` or `poetry publish --build` is
47
+ running, then removes that temporary copy.
41
48
 
42
49
  ## Install
43
50
 
@@ -68,6 +75,8 @@ poetry build
68
75
  poetry publish
69
76
  ```
70
77
 
78
+ Use `poetry publish --build` to build and upload in one command.
79
+
71
80
  For local testing:
72
81
 
73
82
  ```sh
@@ -0,0 +1,9 @@
1
+ cppkh_interface/__init__.py,sha256=u6y6hezL7izQ_yVE9JhjU7znxICVzQNgLUPR3ObKLB0,489
2
+ cppkh_interface/__main__.py,sha256=fdA1QWBe5LNX0fbtaJXa1-dwEhrg_4sS_5UnjaajcMo,80
3
+ cppkh_interface/main.py,sha256=oIhYl-kh-2Ql_zxS1uHggww5Am3KRWUL2QXNGK1ayDM,16674
4
+ cppkh_interface/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ cppkh_interface-0.1.1.dist-info/entry_points.txt,sha256=CsnHBNaO_RJe4eFXmEZpSxWu-c1aF0RpjVFaEGlIm4Y,61
6
+ cppkh_interface-0.1.1.dist-info/METADATA,sha256=2UIZgh_EM5hBfhBZ7huIx21nDDhclwcZRx0tKkCSS8k,2499
7
+ cppkh_interface-0.1.1.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
8
+ cppkh_interface/data/src/main.cpp,sha256=lNg4DhWOcQNWCZoVfXGIObAiPHYWD2i7Pctd5445QT8,143069
9
+ cppkh_interface-0.1.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.4.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,9 +0,0 @@
1
- cppkh_interface/__init__.py,sha256=WJNYCnxkp3e-ebXMgoo20S5N3eJxJGlkqbiW844M0VQ,343
2
- cppkh_interface/__main__.py,sha256=fdA1QWBe5LNX0fbtaJXa1-dwEhrg_4sS_5UnjaajcMo,80
3
- cppkh_interface/data/src/main.cpp,sha256=T56Ftt152CN5a23juzwSAOXnCnLhdeL-0Wgj7Cbo3dw,141886
4
- cppkh_interface/main.py,sha256=WOwZd9owRN5K8wall_PW7JyRP0FYITBfOiHUrBwvsxY,11077
5
- cppkh_interface/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
- cppkh_interface-0.1.0.dist-info/entry_points.txt,sha256=CsnHBNaO_RJe4eFXmEZpSxWu-c1aF0RpjVFaEGlIm4Y,61
7
- cppkh_interface-0.1.0.dist-info/METADATA,sha256=bmeNcT8rRTjyDrODAeurHKEMq6tZR-bqcEn6WBQTcAQ,2071
8
- cppkh_interface-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
9
- cppkh_interface-0.1.0.dist-info/RECORD,,