cppkh-interface 0.1.2__tar.gz → 0.1.3__tar.gz
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-0.1.2 → cppkh_interface-0.1.3}/PKG-INFO +18 -19
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/README.md +16 -13
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/cppkh_interface/data/src/main.cpp +41 -23
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/cppkh_interface/main.py +205 -156
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/pyproject.toml +4 -9
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/build_backend/cppkh_interface_build_backend.py +0 -0
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/cppkh_interface/__init__.py +0 -0
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/cppkh_interface/__main__.py +0 -0
- {cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/cppkh_interface/py.typed +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cppkh-interface
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary: Python interface for cppkh
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Dependency-free Python interface for cppkh with runtime C++ compilation.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Author: GGN_2015
|
|
7
7
|
Author-email: neko@jlulug.org
|
|
@@ -12,10 +12,6 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Requires-Dist: cpp-simple-interface (>=0.1.2)
|
|
16
|
-
Requires-Dist: pd-code-de-r1
|
|
17
|
-
Requires-Dist: pd-code-delete-nugatory
|
|
18
|
-
Requires-Dist: pd-code-sanity (>=0.0.1)
|
|
19
15
|
Project-URL: Documentation, https://github.com/GGN-2015/cppkh/blob/main/docs/PYTHON_PACKAGE.md
|
|
20
16
|
Project-URL: Homepage, https://github.com/GGN-2015/cppkh
|
|
21
17
|
Project-URL: Repository, https://github.com/GGN-2015/cppkh
|
|
@@ -26,9 +22,9 @@ Description-Content-Type: text/markdown
|
|
|
26
22
|
`cppkh-interface` is a Python package for computing integer Khovanov homology
|
|
27
23
|
with the C++ `cppkh` implementation.
|
|
28
24
|
|
|
29
|
-
Version `0.1.
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
Version `0.1.3` has no runtime Python-package dependencies. Link crossing signs,
|
|
26
|
+
PD validation, R1 removal, and nugatory-crossing removal all use the bundled
|
|
27
|
+
canonical `cppkh` C++ source and its SageMath-compatible orientation rules.
|
|
32
28
|
|
|
33
29
|
The package is compatible with the main `javakh-interface` function:
|
|
34
30
|
|
|
@@ -42,13 +38,13 @@ print(cppkh_interface.solve_many_khovanov([pd_code, pd_code]))
|
|
|
42
38
|
|
|
43
39
|
Unlike wrappers that ship a prebuilt DLL or shared object, this package ships
|
|
44
40
|
the `cppkh` C++ source file in built distributions and compiles a local
|
|
45
|
-
executable on first use
|
|
46
|
-
is cached for later calls.
|
|
41
|
+
executable on first use using only Python's standard library. The compiled
|
|
42
|
+
executable is cached for later calls.
|
|
47
43
|
|
|
48
44
|
In the repository checkout, the package does not keep a committed backup copy
|
|
49
45
|
of the C++ source. The build backend copies `../../src/main.cpp` into the
|
|
50
|
-
package data directory only while
|
|
51
|
-
|
|
46
|
+
package data directory only while the PEP 517 build is running, then removes
|
|
47
|
+
that temporary copy.
|
|
52
48
|
|
|
53
49
|
## Install
|
|
54
50
|
|
|
@@ -56,17 +52,18 @@ running, then removes that temporary copy.
|
|
|
56
52
|
pip install cppkh-interface
|
|
57
53
|
```
|
|
58
54
|
|
|
59
|
-
A
|
|
60
|
-
|
|
55
|
+
A C++14 compiler must be available at runtime. The package looks at
|
|
56
|
+
`CPPKH_INTERFACE_CXX`, then `CXX`, then searches `PATH` for `g++`, `clang++`, or
|
|
57
|
+
`c++`. To select a compiler explicitly:
|
|
61
58
|
|
|
62
59
|
```sh
|
|
63
|
-
|
|
60
|
+
CPPKH_INTERFACE_CXX=clang++ python your_script.py
|
|
64
61
|
```
|
|
65
62
|
|
|
66
63
|
Windows PowerShell:
|
|
67
64
|
|
|
68
65
|
```powershell
|
|
69
|
-
$env:
|
|
66
|
+
$env:CPPKH_INTERFACE_CXX = "C:\path\to\g++.exe"
|
|
70
67
|
python your_script.py
|
|
71
68
|
```
|
|
72
69
|
|
|
@@ -75,11 +72,13 @@ python your_script.py
|
|
|
75
72
|
From this directory:
|
|
76
73
|
|
|
77
74
|
```sh
|
|
78
|
-
|
|
75
|
+
python -m build
|
|
79
76
|
poetry publish
|
|
80
77
|
```
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
Do not use `poetry build` or `poetry publish --build`: Poetry's direct builder
|
|
80
|
+
bypasses the source-synchronizing PEP 517 backend. Build first with
|
|
81
|
+
`python -m build`, inspect/test the wheel, then publish the existing artifacts.
|
|
83
82
|
|
|
84
83
|
For local testing:
|
|
85
84
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
`cppkh-interface` is a Python package for computing integer Khovanov homology
|
|
4
4
|
with the C++ `cppkh` implementation.
|
|
5
5
|
|
|
6
|
-
Version `0.1.
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Version `0.1.3` has no runtime Python-package dependencies. Link crossing signs,
|
|
7
|
+
PD validation, R1 removal, and nugatory-crossing removal all use the bundled
|
|
8
|
+
canonical `cppkh` C++ source and its SageMath-compatible orientation rules.
|
|
9
9
|
|
|
10
10
|
The package is compatible with the main `javakh-interface` function:
|
|
11
11
|
|
|
@@ -19,13 +19,13 @@ print(cppkh_interface.solve_many_khovanov([pd_code, pd_code]))
|
|
|
19
19
|
|
|
20
20
|
Unlike wrappers that ship a prebuilt DLL or shared object, this package ships
|
|
21
21
|
the `cppkh` C++ source file in built distributions and compiles a local
|
|
22
|
-
executable on first use
|
|
23
|
-
is cached for later calls.
|
|
22
|
+
executable on first use using only Python's standard library. The compiled
|
|
23
|
+
executable is cached for later calls.
|
|
24
24
|
|
|
25
25
|
In the repository checkout, the package does not keep a committed backup copy
|
|
26
26
|
of the C++ source. The build backend copies `../../src/main.cpp` into the
|
|
27
|
-
package data directory only while
|
|
28
|
-
|
|
27
|
+
package data directory only while the PEP 517 build is running, then removes
|
|
28
|
+
that temporary copy.
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
|
@@ -33,17 +33,18 @@ running, then removes that temporary copy.
|
|
|
33
33
|
pip install cppkh-interface
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
A
|
|
37
|
-
|
|
36
|
+
A C++14 compiler must be available at runtime. The package looks at
|
|
37
|
+
`CPPKH_INTERFACE_CXX`, then `CXX`, then searches `PATH` for `g++`, `clang++`, or
|
|
38
|
+
`c++`. To select a compiler explicitly:
|
|
38
39
|
|
|
39
40
|
```sh
|
|
40
|
-
|
|
41
|
+
CPPKH_INTERFACE_CXX=clang++ python your_script.py
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Windows PowerShell:
|
|
44
45
|
|
|
45
46
|
```powershell
|
|
46
|
-
$env:
|
|
47
|
+
$env:CPPKH_INTERFACE_CXX = "C:\path\to\g++.exe"
|
|
47
48
|
python your_script.py
|
|
48
49
|
```
|
|
49
50
|
|
|
@@ -52,11 +53,13 @@ python your_script.py
|
|
|
52
53
|
From this directory:
|
|
53
54
|
|
|
54
55
|
```sh
|
|
55
|
-
|
|
56
|
+
python -m build
|
|
56
57
|
poetry publish
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
Do not use `poetry build` or `poetry publish --build`: Poetry's direct builder
|
|
61
|
+
bypasses the source-synchronizing PEP 517 backend. Build first with
|
|
62
|
+
`python -m build`, inspect/test the wheel, then publish the existing artifacts.
|
|
60
63
|
|
|
61
64
|
For local testing:
|
|
62
65
|
|
|
@@ -298,13 +298,14 @@ static BigInt coeffProduct(const BigInt& a, const BigInt& b) {
|
|
|
298
298
|
return a * b;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
struct Options {
|
|
302
|
-
int matrixThreads = 1;
|
|
303
|
-
bool reorderCrossings = true;
|
|
304
|
-
bool progress = true;
|
|
305
|
-
bool
|
|
306
|
-
bool
|
|
307
|
-
|
|
301
|
+
struct Options {
|
|
302
|
+
int matrixThreads = 1;
|
|
303
|
+
bool reorderCrossings = true;
|
|
304
|
+
bool progress = true;
|
|
305
|
+
bool simplifyR1 = true;
|
|
306
|
+
bool simplifyNugatory = true;
|
|
307
|
+
bool profile = false;
|
|
308
|
+
};
|
|
308
309
|
|
|
309
310
|
static Options g_options;
|
|
310
311
|
typedef std::vector<std::vector<int> > PDCode;
|
|
@@ -3186,12 +3187,17 @@ static PDCode eraseOneNugatory(PDCode pd, size_t index, std::vector<int>* signs)
|
|
|
3186
3187
|
return renumberFullDfs(bad);
|
|
3187
3188
|
}
|
|
3188
3189
|
|
|
3189
|
-
static PDCode simplifyPDCode(PDCode pd, std::vector<int>* signs
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3190
|
+
static PDCode simplifyPDCode(PDCode pd, std::vector<int>* signs,
|
|
3191
|
+
bool simplifyR1, bool simplifyNugatory) {
|
|
3192
|
+
// The nugatory-crossing operation historically used by the Python API also
|
|
3193
|
+
// removes R1 crossings, so nugatory-only mode still starts with R1 cleanup.
|
|
3194
|
+
if (simplifyR1 || simplifyNugatory) pd = eraseR1(pd, signs);
|
|
3195
|
+
if (simplifyNugatory) {
|
|
3196
|
+
while (true) {
|
|
3197
|
+
int index = findNugatory(pd);
|
|
3198
|
+
if (index < 0) break;
|
|
3199
|
+
pd = eraseOneNugatory(pd, static_cast<size_t>(index), signs);
|
|
3200
|
+
}
|
|
3195
3201
|
}
|
|
3196
3202
|
return pd;
|
|
3197
3203
|
}
|
|
@@ -3401,7 +3407,7 @@ static std::string computePD(const std::vector<std::vector<int> >& pd) {
|
|
|
3401
3407
|
flushCobCache();
|
|
3402
3408
|
g_smallArena.reset();
|
|
3403
3409
|
std::vector<int> signs = getSigns(pd);
|
|
3404
|
-
PDCode working =
|
|
3410
|
+
PDCode working = simplifyPDCode(pd, &signs, g_options.simplifyR1, g_options.simplifyNugatory);
|
|
3405
3411
|
Komplex k = generateFast(working, signs);
|
|
3406
3412
|
ProfileScope khScope(g_profile.kh);
|
|
3407
3413
|
return k.KhForZ();
|
|
@@ -3436,9 +3442,10 @@ static std::string formatCrossingSigns(const PDCode& pd) {
|
|
|
3436
3442
|
}
|
|
3437
3443
|
|
|
3438
3444
|
static std::string simplifiedPDString(const PDCode& pd) {
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3445
|
+
getSigns(pd);
|
|
3446
|
+
PDCode working = simplifyPDCode(pd, nullptr, g_options.simplifyR1, g_options.simplifyNugatory);
|
|
3447
|
+
return formatPDCode(working);
|
|
3448
|
+
}
|
|
3442
3449
|
|
|
3443
3450
|
} // namespace kh
|
|
3444
3451
|
|
|
@@ -3490,7 +3497,8 @@ CPPKH_API char* cppkh_compute_pd_ex(const char* pd_code, int simplify_pd, int re
|
|
|
3490
3497
|
CppkhOptionsGuard guard;
|
|
3491
3498
|
kh::g_options.progress = false;
|
|
3492
3499
|
kh::g_options.profile = false;
|
|
3493
|
-
kh::g_options.
|
|
3500
|
+
kh::g_options.simplifyR1 = simplify_pd != 0;
|
|
3501
|
+
kh::g_options.simplifyNugatory = simplify_pd != 0;
|
|
3494
3502
|
kh::g_options.reorderCrossings = reorder_crossings != 0;
|
|
3495
3503
|
std::string result = kh::computePD(parsed[0].second);
|
|
3496
3504
|
return cppkhDuplicateString(result);
|
|
@@ -3517,7 +3525,8 @@ CPPKH_API char* cppkh_compute_pd_batch_ex(const char* pd_codes, int simplify_pd,
|
|
|
3517
3525
|
CppkhOptionsGuard guard;
|
|
3518
3526
|
kh::g_options.progress = false;
|
|
3519
3527
|
kh::g_options.profile = false;
|
|
3520
|
-
kh::g_options.
|
|
3528
|
+
kh::g_options.simplifyR1 = simplify_pd != 0;
|
|
3529
|
+
kh::g_options.simplifyNugatory = simplify_pd != 0;
|
|
3521
3530
|
kh::g_options.reorderCrossings = reorder_crossings != 0;
|
|
3522
3531
|
|
|
3523
3532
|
std::ostringstream out;
|
|
@@ -3547,7 +3556,8 @@ CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
|
|
|
3547
3556
|
if (parsed.empty()) throw std::runtime_error("no PD code found");
|
|
3548
3557
|
if (parsed.size() != 1) throw std::runtime_error("cppkh_simplify_pd expects exactly one PD code");
|
|
3549
3558
|
CppkhOptionsGuard guard;
|
|
3550
|
-
kh::g_options.
|
|
3559
|
+
kh::g_options.simplifyR1 = true;
|
|
3560
|
+
kh::g_options.simplifyNugatory = true;
|
|
3551
3561
|
std::string result = kh::simplifiedPDString(parsed[0].second);
|
|
3552
3562
|
return cppkhDuplicateString(result);
|
|
3553
3563
|
} catch (const std::exception& e) {
|
|
@@ -3562,7 +3572,7 @@ CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
|
|
|
3562
3572
|
|
|
3563
3573
|
#ifndef CPPKH_SHARED_LIBRARY
|
|
3564
3574
|
static void usage() {
|
|
3565
|
-
std::cout << "Usage: cppkh [--pd-file FILE] [--pd-dir DIR] [--pd-code CODE] [--ordered] [--threads N|auto] [--quiet] [--profile] [--no-simplify-pd] [--print-simplified-pd] [--print-crossing-signs]\n";
|
|
3575
|
+
std::cout << "Usage: cppkh [--pd-file FILE] [--pd-dir DIR] [--pd-code CODE] [--ordered] [--threads N|auto] [--quiet] [--profile] [--no-simplify-pd] [--simplify-r1|--no-simplify-r1] [--simplify-nugatory|--no-simplify-nugatory] [--print-simplified-pd] [--print-crossing-signs]\n";
|
|
3566
3576
|
std::cout << "Thread backend: " << KH_THREAD_BACKEND_NAME << "\n";
|
|
3567
3577
|
std::cout << "Detected CPU threads: " << kh::detectHardwareThreads() << "\n";
|
|
3568
3578
|
std::cout << "PD simplification: R1 removal then nugatory crossing removal is enabled by default.\n";
|
|
@@ -3591,8 +3601,16 @@ int main(int argc, char** argv) {
|
|
|
3591
3601
|
} else if (a == "--ordered" || a == "-O") kh::g_options.reorderCrossings = false;
|
|
3592
3602
|
else if (a == "--quiet" || a == "-q") kh::g_options.progress = false;
|
|
3593
3603
|
else if (a == "--profile") kh::g_options.profile = true;
|
|
3594
|
-
else if (a == "--no-simplify-pd" || a == "--raw-pd")
|
|
3595
|
-
|
|
3604
|
+
else if (a == "--no-simplify-pd" || a == "--raw-pd") {
|
|
3605
|
+
kh::g_options.simplifyR1 = false;
|
|
3606
|
+
kh::g_options.simplifyNugatory = false;
|
|
3607
|
+
} else if (a == "--simplify-pd") {
|
|
3608
|
+
kh::g_options.simplifyR1 = true;
|
|
3609
|
+
kh::g_options.simplifyNugatory = true;
|
|
3610
|
+
} else if (a == "--no-simplify-r1") kh::g_options.simplifyR1 = false;
|
|
3611
|
+
else if (a == "--simplify-r1") kh::g_options.simplifyR1 = true;
|
|
3612
|
+
else if (a == "--no-simplify-nugatory") kh::g_options.simplifyNugatory = false;
|
|
3613
|
+
else if (a == "--simplify-nugatory") kh::g_options.simplifyNugatory = true;
|
|
3596
3614
|
else if (a == "--print-simplified-pd") printSimplifiedPD = true;
|
|
3597
3615
|
else if (a == "--print-crossing-signs") printCrossingSigns = true;
|
|
3598
3616
|
else if (a == "--threads" || a == "-j") {
|
|
@@ -9,22 +9,17 @@ import pathlib
|
|
|
9
9
|
import platform
|
|
10
10
|
import re
|
|
11
11
|
import shlex
|
|
12
|
+
import shutil
|
|
12
13
|
import subprocess
|
|
13
14
|
import sys
|
|
14
15
|
import tempfile
|
|
16
|
+
import uuid
|
|
15
17
|
from importlib import resources
|
|
16
18
|
from typing import Optional, Sequence, Union
|
|
17
19
|
|
|
18
|
-
import cpp_simple_interface
|
|
19
|
-
import pd_code_de_r1
|
|
20
|
-
import pd_code_delete_nugatory
|
|
21
|
-
import pd_code_sanity
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
PathLike = Union[str, os.PathLike]
|
|
25
20
|
PdInput = Union[str, Sequence[Sequence[int]]]
|
|
26
21
|
PdManyInput = Union[str, Sequence[PdInput]]
|
|
27
|
-
|
|
22
|
+
_configured_cxx: Optional[str] = None
|
|
28
23
|
|
|
29
24
|
|
|
30
25
|
class CppkhInterfaceError(RuntimeError):
|
|
@@ -77,13 +72,6 @@ def _as_crossings(pd_code: PdInput) -> list[list[int]]:
|
|
|
77
72
|
return crossings
|
|
78
73
|
|
|
79
74
|
|
|
80
|
-
def _check_sanity(crossings: list[list[int]]) -> None:
|
|
81
|
-
if crossings == []:
|
|
82
|
-
return
|
|
83
|
-
if not pd_code_sanity.sanity(crossings):
|
|
84
|
-
raise TypeError("pd_code does not satisfy PD-code sanity checks")
|
|
85
|
-
|
|
86
|
-
|
|
87
75
|
def normalize_pd_code(pd_code: PdInput) -> str:
|
|
88
76
|
"""Normalize a supported PD-code value into standard ``PD[X[...],...]`` text."""
|
|
89
77
|
|
|
@@ -154,25 +142,84 @@ def _exe_suffix() -> str:
|
|
|
154
142
|
return ".exe" if platform.system() == "Windows" else ""
|
|
155
143
|
|
|
156
144
|
|
|
157
|
-
def
|
|
158
|
-
|
|
159
|
-
if not
|
|
145
|
+
def _compiler_parts(command: str) -> list[str]:
|
|
146
|
+
command = command.strip()
|
|
147
|
+
if not command:
|
|
160
148
|
return []
|
|
161
|
-
|
|
162
|
-
candidates = []
|
|
163
|
-
unquoted = compiler
|
|
149
|
+
unquoted = command
|
|
164
150
|
if len(unquoted) >= 2 and unquoted[0] == unquoted[-1] and unquoted[0] in ("'", '"'):
|
|
165
151
|
unquoted = unquoted[1:-1]
|
|
166
|
-
|
|
152
|
+
if pathlib.Path(unquoted).is_file():
|
|
153
|
+
return [unquoted]
|
|
154
|
+
try:
|
|
155
|
+
parts = shlex.split(command, posix=os.name != "nt")
|
|
156
|
+
except ValueError as exc:
|
|
157
|
+
raise CppkhInterfaceError(f"invalid C++ compiler command: {command!r}") from exc
|
|
158
|
+
return [part.strip("\"'") for part in parts if part.strip("\"'")]
|
|
159
|
+
|
|
167
160
|
|
|
161
|
+
def _subprocess_kwargs() -> dict:
|
|
162
|
+
kwargs = {
|
|
163
|
+
"stdout": subprocess.PIPE,
|
|
164
|
+
"stderr": subprocess.PIPE,
|
|
165
|
+
"text": True,
|
|
166
|
+
"encoding": "utf-8",
|
|
167
|
+
"errors": "replace",
|
|
168
|
+
}
|
|
169
|
+
if platform.system() == "Windows":
|
|
170
|
+
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
171
|
+
return kwargs
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _probe_compiler(parts: Sequence[str]) -> subprocess.CompletedProcess:
|
|
175
|
+
if not parts:
|
|
176
|
+
raise CppkhInterfaceError("empty C++ compiler command")
|
|
168
177
|
try:
|
|
169
|
-
|
|
170
|
-
except
|
|
171
|
-
|
|
178
|
+
result = subprocess.run([*parts, "--version"], timeout=15, **_subprocess_kwargs())
|
|
179
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
180
|
+
raise CppkhInterfaceError(f"could not run C++ compiler {' '.join(parts)!r}: {exc}") from exc
|
|
181
|
+
if result.returncode != 0:
|
|
182
|
+
detail = (result.stderr or result.stdout or "").strip()
|
|
183
|
+
raise CppkhInterfaceError(detail or f"C++ compiler {' '.join(parts)!r} is not usable")
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _resolve_compiler(cxx: Optional[str] = None) -> list[str]:
|
|
188
|
+
global _configured_cxx
|
|
189
|
+
|
|
190
|
+
explicit = cxx or _configured_cxx
|
|
191
|
+
if not explicit:
|
|
192
|
+
explicit = os.environ.get("CPPKH_INTERFACE_CXX") or os.environ.get("CXX")
|
|
193
|
+
if explicit:
|
|
194
|
+
parts = _compiler_parts(explicit)
|
|
195
|
+
_probe_compiler(parts)
|
|
196
|
+
if cxx:
|
|
197
|
+
_configured_cxx = cxx
|
|
198
|
+
return parts
|
|
199
|
+
|
|
200
|
+
errors = []
|
|
201
|
+
for name in ("g++", "clang++", "c++"):
|
|
202
|
+
candidate = shutil.which(name)
|
|
203
|
+
if not candidate:
|
|
204
|
+
continue
|
|
205
|
+
parts = [candidate]
|
|
206
|
+
try:
|
|
207
|
+
_probe_compiler(parts)
|
|
208
|
+
return parts
|
|
209
|
+
except CppkhInterfaceError as exc:
|
|
210
|
+
errors.append(str(exc))
|
|
211
|
+
detail = f" ({'; '.join(errors)})" if errors else ""
|
|
212
|
+
raise CppkhInterfaceError(
|
|
213
|
+
"no usable C++ compiler was found. Install a C++14 compiler or set "
|
|
214
|
+
f"CPPKH_INTERFACE_CXX/CXX to its command{detail}"
|
|
215
|
+
)
|
|
216
|
+
|
|
172
217
|
|
|
218
|
+
def _compiler_runtime_path_entries(compiler: Sequence[str]) -> list[str]:
|
|
173
219
|
paths = []
|
|
174
|
-
for candidate in
|
|
175
|
-
|
|
220
|
+
for candidate in compiler:
|
|
221
|
+
resolved = shutil.which(candidate) or candidate
|
|
222
|
+
path = pathlib.Path(resolved)
|
|
176
223
|
if path.exists() and path.is_file():
|
|
177
224
|
parent = str(path.resolve().parent)
|
|
178
225
|
if parent not in paths:
|
|
@@ -180,65 +227,84 @@ def _compiler_runtime_path_entries() -> list[str]:
|
|
|
180
227
|
return paths
|
|
181
228
|
|
|
182
229
|
|
|
183
|
-
def
|
|
230
|
+
def _compiler_identity(compiler: Sequence[str]) -> str:
|
|
231
|
+
result = _probe_compiler(compiler)
|
|
232
|
+
executable = shutil.which(compiler[0]) or compiler[0]
|
|
233
|
+
return "\0".join([str(pathlib.Path(executable).resolve()), *compiler[1:], result.stdout.strip()])
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _cache_key(source_bytes: bytes, flags: Sequence[str], compiler: Sequence[str]) -> str:
|
|
184
237
|
digest = hashlib.sha256()
|
|
185
238
|
digest.update(source_bytes)
|
|
186
239
|
digest.update("\0".join(flags).encode("utf-8"))
|
|
187
|
-
digest.update(
|
|
240
|
+
digest.update(_compiler_identity(compiler).encode("utf-8"))
|
|
188
241
|
digest.update(platform.platform().encode("utf-8"))
|
|
189
242
|
return digest.hexdigest()[:20]
|
|
190
243
|
|
|
191
244
|
|
|
245
|
+
def _compile_source(
|
|
246
|
+
compiler: Sequence[str],
|
|
247
|
+
source: pathlib.Path,
|
|
248
|
+
output: pathlib.Path,
|
|
249
|
+
flags: Sequence[str],
|
|
250
|
+
) -> subprocess.CompletedProcess:
|
|
251
|
+
command = [*compiler, *flags, str(source), "-o", str(output)]
|
|
252
|
+
try:
|
|
253
|
+
return subprocess.run(command, **_subprocess_kwargs())
|
|
254
|
+
except OSError as exc:
|
|
255
|
+
raise CppkhInterfaceError(f"could not start C++ compiler: {exc}") from exc
|
|
256
|
+
|
|
257
|
+
|
|
192
258
|
def compile_cppkh(
|
|
193
259
|
*,
|
|
194
260
|
force: bool = False,
|
|
195
261
|
cxx: Optional[str] = None,
|
|
196
262
|
extra_flags: Optional[Sequence[str]] = None,
|
|
197
263
|
) -> pathlib.Path:
|
|
198
|
-
"""Compile the packaged C++ source
|
|
199
|
-
|
|
200
|
-
if cxx:
|
|
201
|
-
cpp_simple_interface.set_gpp_filepath(cxx)
|
|
264
|
+
"""Compile the packaged C++ source and return the cached executable path."""
|
|
202
265
|
|
|
203
266
|
with _resource_source_path() as source:
|
|
204
267
|
source_path = pathlib.Path(source)
|
|
205
268
|
source_bytes = source_path.read_bytes()
|
|
269
|
+
compiler = _resolve_compiler(cxx)
|
|
206
270
|
flags = _default_compile_flags()
|
|
207
271
|
if extra_flags:
|
|
208
272
|
flags.extend(str(flag) for flag in extra_flags)
|
|
209
273
|
|
|
210
274
|
cache = _cache_dir()
|
|
211
|
-
exe = cache / f"cppkh-{_cache_key(source_bytes, flags)}{_exe_suffix()}"
|
|
275
|
+
exe = cache / f"cppkh-{_cache_key(source_bytes, flags, compiler)}{_exe_suffix()}"
|
|
212
276
|
if exe.exists() and not force:
|
|
213
277
|
return exe
|
|
214
278
|
|
|
215
|
-
tmp_exe = cache / f"{exe.name}.tmp-{os.getpid()}{_exe_suffix()}"
|
|
216
|
-
if tmp_exe.exists():
|
|
217
|
-
tmp_exe.unlink()
|
|
218
|
-
|
|
219
|
-
success, message = cpp_simple_interface.compile_cpp_files(
|
|
220
|
-
[str(source_path)],
|
|
221
|
-
str(tmp_exe),
|
|
222
|
-
other_flags=flags,
|
|
223
|
-
)
|
|
224
|
-
if not success and "-march=native" in flags:
|
|
225
|
-
fallback_flags = [flag for flag in flags if flag != "-march=native"]
|
|
226
|
-
success, message = cpp_simple_interface.compile_cpp_files(
|
|
227
|
-
[str(source_path)],
|
|
228
|
-
str(tmp_exe),
|
|
229
|
-
other_flags=fallback_flags,
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
if not success:
|
|
233
|
-
raise CppkhInterfaceError(message)
|
|
234
|
-
if not tmp_exe.exists():
|
|
235
|
-
raise CppkhInterfaceError(f"compiled executable was not created: {tmp_exe}")
|
|
236
|
-
os.replace(tmp_exe, exe)
|
|
279
|
+
tmp_exe = cache / f"{exe.name}.tmp-{os.getpid()}-{uuid.uuid4().hex}{_exe_suffix()}"
|
|
237
280
|
try:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
281
|
+
result = _compile_source(compiler, source_path, tmp_exe, flags)
|
|
282
|
+
if result.returncode != 0 and "-march=native" in flags:
|
|
283
|
+
fallback_flags = [flag for flag in flags if flag != "-march=native"]
|
|
284
|
+
result = _compile_source(compiler, source_path, tmp_exe, fallback_flags)
|
|
285
|
+
if result.returncode != 0:
|
|
286
|
+
detail = (result.stderr or result.stdout or "").strip()
|
|
287
|
+
raise CppkhInterfaceError(detail or "C++ compilation failed")
|
|
288
|
+
if not tmp_exe.exists():
|
|
289
|
+
raise CppkhInterfaceError(f"compiled executable was not created: {tmp_exe}")
|
|
290
|
+
|
|
291
|
+
if exe.exists() and not force:
|
|
292
|
+
return exe
|
|
293
|
+
try:
|
|
294
|
+
os.replace(tmp_exe, exe)
|
|
295
|
+
except OSError:
|
|
296
|
+
if force or not exe.exists():
|
|
297
|
+
raise
|
|
298
|
+
try:
|
|
299
|
+
exe.chmod(exe.stat().st_mode | 0o755)
|
|
300
|
+
except OSError:
|
|
301
|
+
pass
|
|
302
|
+
return exe
|
|
303
|
+
finally:
|
|
304
|
+
try:
|
|
305
|
+
tmp_exe.unlink()
|
|
306
|
+
except OSError:
|
|
307
|
+
pass
|
|
242
308
|
|
|
243
309
|
|
|
244
310
|
def get_cppkh_executable() -> pathlib.Path:
|
|
@@ -252,7 +318,8 @@ def _run_cppkh_document(
|
|
|
252
318
|
*,
|
|
253
319
|
encoding: Optional[str] = None,
|
|
254
320
|
threads: Union[str, int] = "1",
|
|
255
|
-
|
|
321
|
+
de_r1: bool = False,
|
|
322
|
+
de_k8: bool = False,
|
|
256
323
|
print_simplified_pd: bool = False,
|
|
257
324
|
) -> list[str]:
|
|
258
325
|
exe = compile_cppkh()
|
|
@@ -270,8 +337,8 @@ def _run_cppkh_document(
|
|
|
270
337
|
"--threads",
|
|
271
338
|
str(threads),
|
|
272
339
|
]
|
|
273
|
-
if
|
|
274
|
-
|
|
340
|
+
command.append("--simplify-r1" if de_r1 else "--no-simplify-r1")
|
|
341
|
+
command.append("--simplify-nugatory" if de_k8 else "--no-simplify-nugatory")
|
|
275
342
|
if print_simplified_pd:
|
|
276
343
|
command.append("--print-simplified-pd")
|
|
277
344
|
|
|
@@ -283,7 +350,7 @@ def _run_cppkh_document(
|
|
|
283
350
|
"errors": "replace",
|
|
284
351
|
}
|
|
285
352
|
env = os.environ.copy()
|
|
286
|
-
runtime_paths = _compiler_runtime_path_entries()
|
|
353
|
+
runtime_paths = _compiler_runtime_path_entries(_resolve_compiler())
|
|
287
354
|
if runtime_paths:
|
|
288
355
|
env["PATH"] = os.pathsep.join(runtime_paths + [env.get("PATH", "")])
|
|
289
356
|
kwargs["env"] = env
|
|
@@ -303,7 +370,8 @@ def _run_cppkh_document(
|
|
|
303
370
|
raise CppkhInterfaceError(detail or f"cppkh exited with code {result.returncode}")
|
|
304
371
|
|
|
305
372
|
if print_simplified_pd:
|
|
306
|
-
|
|
373
|
+
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
|
374
|
+
return [line.split("\t", 1)[-1] for line in lines]
|
|
307
375
|
|
|
308
376
|
matches = re.findall(r'"([^"]*)"', result.stdout)
|
|
309
377
|
if not matches:
|
|
@@ -316,14 +384,16 @@ def _run_cppkh(
|
|
|
316
384
|
*,
|
|
317
385
|
encoding: Optional[str] = None,
|
|
318
386
|
threads: Union[str, int] = "1",
|
|
319
|
-
|
|
387
|
+
de_r1: bool = False,
|
|
388
|
+
de_k8: bool = False,
|
|
320
389
|
print_simplified_pd: bool = False,
|
|
321
390
|
) -> str:
|
|
322
391
|
results = _run_cppkh_document(
|
|
323
392
|
pd_text,
|
|
324
393
|
encoding=encoding,
|
|
325
394
|
threads=threads,
|
|
326
|
-
|
|
395
|
+
de_r1=de_r1,
|
|
396
|
+
de_k8=de_k8,
|
|
327
397
|
print_simplified_pd=print_simplified_pd,
|
|
328
398
|
)
|
|
329
399
|
if len(results) != 1:
|
|
@@ -331,37 +401,40 @@ def _run_cppkh(
|
|
|
331
401
|
return results[0]
|
|
332
402
|
|
|
333
403
|
|
|
334
|
-
def
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
crossings = pd_code_de_r1.de_r1(crossings)
|
|
339
|
-
if de_k8:
|
|
340
|
-
crossings = pd_code_delete_nugatory.erase_all_nugatory(crossings)
|
|
341
|
-
return crossings
|
|
404
|
+
def _prepare_many_for_cppkh(pd_codes: PdManyInput) -> str:
|
|
405
|
+
if isinstance(pd_codes, str):
|
|
406
|
+
return pd_codes.strip()
|
|
407
|
+
return "\n".join(normalize_pd_code(pd_code) for pd_code in pd_codes)
|
|
342
408
|
|
|
343
409
|
|
|
344
|
-
def
|
|
345
|
-
|
|
410
|
+
def _compute_one(
|
|
411
|
+
pd_code: PdInput,
|
|
346
412
|
*,
|
|
413
|
+
encoding: Optional[str],
|
|
347
414
|
de_r1: bool,
|
|
348
415
|
de_k8: bool,
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
416
|
+
show_real_pdcode: bool,
|
|
417
|
+
threads: Union[str, int],
|
|
418
|
+
) -> str:
|
|
419
|
+
document = normalize_pd_code(pd_code)
|
|
420
|
+
if show_real_pdcode:
|
|
421
|
+
real_pd = _run_cppkh(
|
|
422
|
+
document,
|
|
423
|
+
encoding=encoding,
|
|
424
|
+
threads=threads,
|
|
425
|
+
de_r1=de_r1,
|
|
426
|
+
de_k8=de_k8,
|
|
427
|
+
print_simplified_pd=True,
|
|
354
428
|
)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return "\n".join(prepared), use_cpp_simplify
|
|
429
|
+
display_pd = real_pd if de_r1 and de_k8 else _as_crossings(real_pd)
|
|
430
|
+
print(f"Real PD code after de_r1 and de_k8: {display_pd}")
|
|
431
|
+
return _run_cppkh(
|
|
432
|
+
document,
|
|
433
|
+
encoding=encoding,
|
|
434
|
+
threads=threads,
|
|
435
|
+
de_r1=de_r1,
|
|
436
|
+
de_k8=de_k8,
|
|
437
|
+
)
|
|
365
438
|
|
|
366
439
|
|
|
367
440
|
def solve_khovanov(
|
|
@@ -373,30 +446,14 @@ def solve_khovanov(
|
|
|
373
446
|
) -> str:
|
|
374
447
|
"""Compute Khovanov homology with a javakh-interface compatible signature."""
|
|
375
448
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
394
|
-
crossings = _prepare_crossings(pd_code, de_r1=de_r1, de_k8=de_k8)
|
|
395
|
-
if show_real_pdcode:
|
|
396
|
-
print(f"Real PD code after de_r1 and de_k8: {crossings}")
|
|
397
|
-
if crossings == []:
|
|
398
|
-
return UNKNOT_RESULT
|
|
399
|
-
return _run_cppkh(_format_pd(crossings), encoding=encoding)
|
|
449
|
+
return _compute_one(
|
|
450
|
+
pd_code,
|
|
451
|
+
encoding=encoding,
|
|
452
|
+
de_r1=de_r1,
|
|
453
|
+
de_k8=de_k8,
|
|
454
|
+
show_real_pdcode=show_real_pdcode,
|
|
455
|
+
threads="1",
|
|
456
|
+
)
|
|
400
457
|
|
|
401
458
|
|
|
402
459
|
def solve_many_khovanov(
|
|
@@ -414,22 +471,26 @@ def solve_many_khovanov(
|
|
|
414
471
|
nugatory crossing removal for the whole batch.
|
|
415
472
|
"""
|
|
416
473
|
|
|
417
|
-
document
|
|
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()}")
|
|
474
|
+
document = _prepare_many_for_cppkh(pd_codes)
|
|
430
475
|
if not document:
|
|
431
476
|
return []
|
|
432
|
-
|
|
477
|
+
if show_real_pdcode:
|
|
478
|
+
simplified = _run_cppkh_document(
|
|
479
|
+
document,
|
|
480
|
+
encoding=encoding,
|
|
481
|
+
threads=threads,
|
|
482
|
+
de_r1=de_r1,
|
|
483
|
+
de_k8=de_k8,
|
|
484
|
+
print_simplified_pd=True,
|
|
485
|
+
)
|
|
486
|
+
print(f"Real PD code after de_r1 and de_k8: {simplified}")
|
|
487
|
+
return _run_cppkh_document(
|
|
488
|
+
document,
|
|
489
|
+
encoding=encoding,
|
|
490
|
+
threads=threads,
|
|
491
|
+
de_r1=de_r1,
|
|
492
|
+
de_k8=de_k8,
|
|
493
|
+
)
|
|
433
494
|
|
|
434
495
|
|
|
435
496
|
def compute_pd(
|
|
@@ -443,31 +504,14 @@ def compute_pd(
|
|
|
443
504
|
) -> str:
|
|
444
505
|
"""Compute Khovanov homology using the same defaults as solve_khovanov."""
|
|
445
506
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
465
|
-
crossings = _prepare_crossings(pd_code, de_r1=de_r1, de_k8=de_k8)
|
|
466
|
-
if show_real_pdcode:
|
|
467
|
-
print(f"Real PD code after de_r1 and de_k8: {crossings}")
|
|
468
|
-
if crossings == []:
|
|
469
|
-
return UNKNOT_RESULT
|
|
470
|
-
return _run_cppkh(_format_pd(crossings), encoding=encoding, threads=threads)
|
|
507
|
+
return _compute_one(
|
|
508
|
+
pd_code,
|
|
509
|
+
encoding=encoding,
|
|
510
|
+
de_r1=de_r1,
|
|
511
|
+
de_k8=de_k8,
|
|
512
|
+
show_real_pdcode=show_real_pdcode,
|
|
513
|
+
threads=threads,
|
|
514
|
+
)
|
|
471
515
|
|
|
472
516
|
|
|
473
517
|
def compute_many_pd(
|
|
@@ -494,7 +538,12 @@ def compute_many_pd(
|
|
|
494
538
|
def simplify_pd(pd_code: PdInput, *, de_r1: bool = True, de_k8: bool = True) -> str:
|
|
495
539
|
"""Return the normalized PD string after optional R1 and nugatory simplification."""
|
|
496
540
|
|
|
497
|
-
return
|
|
541
|
+
return _run_cppkh(
|
|
542
|
+
normalize_pd_code(pd_code),
|
|
543
|
+
de_r1=de_r1,
|
|
544
|
+
de_k8=de_k8,
|
|
545
|
+
print_simplified_pd=True,
|
|
546
|
+
)
|
|
498
547
|
|
|
499
548
|
|
|
500
549
|
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cppkh-interface"
|
|
3
|
-
version = "0.1.
|
|
4
|
-
description = "Python interface for cppkh
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
description = "Dependency-free Python interface for cppkh with runtime C++ compilation."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "GGN_2015", email = "neko@jlulug.org"}
|
|
7
7
|
]
|
|
8
8
|
license = "MIT"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
-
dependencies = [
|
|
12
|
-
"cpp-simple-interface>=0.1.2",
|
|
13
|
-
"pd-code-sanity>=0.0.1",
|
|
14
|
-
"pd-code-delete-nugatory",
|
|
15
|
-
"pd-code-de-r1"
|
|
16
|
-
]
|
|
11
|
+
dependencies = []
|
|
17
12
|
|
|
18
13
|
[project.urls]
|
|
19
14
|
Homepage = "https://github.com/GGN-2015/cppkh"
|
|
@@ -31,6 +26,6 @@ include = [
|
|
|
31
26
|
]
|
|
32
27
|
|
|
33
28
|
[build-system]
|
|
34
|
-
requires = ["poetry-core>=2.0.0,<3.0.0"
|
|
29
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
35
30
|
build-backend = "cppkh_interface_build_backend"
|
|
36
31
|
backend-path = ["build_backend"]
|
{cppkh_interface-0.1.2 → cppkh_interface-0.1.3}/build_backend/cppkh_interface_build_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|