cppkh-interface 0.1.1__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.1 → cppkh_interface-0.1.3}/PKG-INFO +19 -16
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/README.md +17 -10
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/cppkh_interface/data/src/main.cpp +189 -70
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/cppkh_interface/main.py +205 -156
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/pyproject.toml +4 -9
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/build_backend/cppkh_interface_build_backend.py +0 -0
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/cppkh_interface/__init__.py +0 -0
- {cppkh_interface-0.1.1 → cppkh_interface-0.1.3}/cppkh_interface/__main__.py +0 -0
- {cppkh_interface-0.1.1 → 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,6 +22,10 @@ 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
|
|
|
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.
|
|
28
|
+
|
|
29
29
|
The package is compatible with the main `javakh-interface` function:
|
|
30
30
|
|
|
31
31
|
```python
|
|
@@ -38,13 +38,13 @@ print(cppkh_interface.solve_many_khovanov([pd_code, pd_code]))
|
|
|
38
38
|
|
|
39
39
|
Unlike wrappers that ship a prebuilt DLL or shared object, this package ships
|
|
40
40
|
the `cppkh` C++ source file in built distributions and compiles a local
|
|
41
|
-
executable on first use
|
|
42
|
-
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.
|
|
43
43
|
|
|
44
44
|
In the repository checkout, the package does not keep a committed backup copy
|
|
45
45
|
of the C++ source. The build backend copies `../../src/main.cpp` into the
|
|
46
|
-
package data directory only while
|
|
47
|
-
|
|
46
|
+
package data directory only while the PEP 517 build is running, then removes
|
|
47
|
+
that temporary copy.
|
|
48
48
|
|
|
49
49
|
## Install
|
|
50
50
|
|
|
@@ -52,17 +52,18 @@ running, then removes that temporary copy.
|
|
|
52
52
|
pip install cppkh-interface
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
A
|
|
56
|
-
|
|
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:
|
|
57
58
|
|
|
58
59
|
```sh
|
|
59
|
-
|
|
60
|
+
CPPKH_INTERFACE_CXX=clang++ python your_script.py
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
Windows PowerShell:
|
|
63
64
|
|
|
64
65
|
```powershell
|
|
65
|
-
$env:
|
|
66
|
+
$env:CPPKH_INTERFACE_CXX = "C:\path\to\g++.exe"
|
|
66
67
|
python your_script.py
|
|
67
68
|
```
|
|
68
69
|
|
|
@@ -71,11 +72,13 @@ python your_script.py
|
|
|
71
72
|
From this directory:
|
|
72
73
|
|
|
73
74
|
```sh
|
|
74
|
-
|
|
75
|
+
python -m build
|
|
75
76
|
poetry publish
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
|
|
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.
|
|
79
82
|
|
|
80
83
|
For local testing:
|
|
81
84
|
|
|
@@ -3,6 +3,10 @@
|
|
|
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.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
|
+
|
|
6
10
|
The package is compatible with the main `javakh-interface` function:
|
|
7
11
|
|
|
8
12
|
```python
|
|
@@ -15,13 +19,13 @@ print(cppkh_interface.solve_many_khovanov([pd_code, pd_code]))
|
|
|
15
19
|
|
|
16
20
|
Unlike wrappers that ship a prebuilt DLL or shared object, this package ships
|
|
17
21
|
the `cppkh` C++ source file in built distributions and compiles a local
|
|
18
|
-
executable on first use
|
|
19
|
-
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.
|
|
20
24
|
|
|
21
25
|
In the repository checkout, the package does not keep a committed backup copy
|
|
22
26
|
of the C++ source. The build backend copies `../../src/main.cpp` into the
|
|
23
|
-
package data directory only while
|
|
24
|
-
|
|
27
|
+
package data directory only while the PEP 517 build is running, then removes
|
|
28
|
+
that temporary copy.
|
|
25
29
|
|
|
26
30
|
## Install
|
|
27
31
|
|
|
@@ -29,17 +33,18 @@ running, then removes that temporary copy.
|
|
|
29
33
|
pip install cppkh-interface
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
A
|
|
33
|
-
|
|
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:
|
|
34
39
|
|
|
35
40
|
```sh
|
|
36
|
-
|
|
41
|
+
CPPKH_INTERFACE_CXX=clang++ python your_script.py
|
|
37
42
|
```
|
|
38
43
|
|
|
39
44
|
Windows PowerShell:
|
|
40
45
|
|
|
41
46
|
```powershell
|
|
42
|
-
$env:
|
|
47
|
+
$env:CPPKH_INTERFACE_CXX = "C:\path\to\g++.exe"
|
|
43
48
|
python your_script.py
|
|
44
49
|
```
|
|
45
50
|
|
|
@@ -48,11 +53,13 @@ python your_script.py
|
|
|
48
53
|
From this directory:
|
|
49
54
|
|
|
50
55
|
```sh
|
|
51
|
-
|
|
56
|
+
python -m build
|
|
52
57
|
poetry publish
|
|
53
58
|
```
|
|
54
59
|
|
|
55
|
-
|
|
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.
|
|
56
63
|
|
|
57
64
|
For local testing:
|
|
58
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;
|
|
@@ -2758,9 +2759,12 @@ std::string Komplex::KhForZ() {
|
|
|
2758
2759
|
return ret;
|
|
2759
2760
|
}
|
|
2760
2761
|
|
|
2761
|
-
static int chooseXingRecursive(const std::vector<int>& edges, const std::vector<std::vector<int> >& pd,
|
|
2762
|
-
std::vector<char>& in, std::vector<char>& done, int depth, std::vector<int>& retmax) {
|
|
2763
|
-
|
|
2762
|
+
static int chooseXingRecursive(const std::vector<int>& edges, const std::vector<std::vector<int> >& pd,
|
|
2763
|
+
std::vector<char>& in, std::vector<char>& done, int depth, std::vector<int>& retmax) {
|
|
2764
|
+
// Choose the next crossing for the growing tangle. The heuristic maximizes
|
|
2765
|
+
// already-attached boundary arcs, with a short lookahead to keep later
|
|
2766
|
+
// intermediate girth smaller. It affects runtime, not the homology result.
|
|
2767
|
+
int nedges = static_cast<int>(edges.size());
|
|
2764
2768
|
int best = -1, nconbest = -1;
|
|
2765
2769
|
std::vector<int> rbest(depth, 0);
|
|
2766
2770
|
for (size_t i = 0; i < pd.size(); ++i) if (!done[i]) {
|
|
@@ -2819,15 +2823,91 @@ static int takeNextCrossing(const std::vector<int>&, const std::vector<std::vect
|
|
|
2819
2823
|
throw std::runtime_error("no crossing left");
|
|
2820
2824
|
}
|
|
2821
2825
|
|
|
2822
|
-
static std::vector<int> getSigns(const std::vector<std::vector<int> >& pd) {
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2826
|
+
static std::vector<int> getSigns(const std::vector<std::vector<int> >& pd) {
|
|
2827
|
+
const int nodeCount = static_cast<int>(pd.size() * 4);
|
|
2828
|
+
if (nodeCount == 0) return std::vector<int>();
|
|
2829
|
+
|
|
2830
|
+
int maxLabel = -1;
|
|
2831
|
+
for (size_t i = 0; i < pd.size(); ++i) {
|
|
2832
|
+
if (pd[i].size() != 4) throw std::runtime_error("PD crossing must have four entries");
|
|
2833
|
+
for (int label : pd[i]) {
|
|
2834
|
+
if (label < 0) throw std::runtime_error("PD arc labels must be positive");
|
|
2835
|
+
maxLabel = std::max(maxLabel, label);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
std::vector<int> first(maxLabel + 1, -1), second(maxLabel + 1, -1);
|
|
2840
|
+
for (size_t i = 0; i < pd.size(); ++i) {
|
|
2841
|
+
for (int slot = 0; slot < 4; ++slot) {
|
|
2842
|
+
int label = pd[i][slot];
|
|
2843
|
+
int node = static_cast<int>(4 * i) + slot;
|
|
2844
|
+
if (first[label] == -1) first[label] = node;
|
|
2845
|
+
else if (second[label] == -1) second[label] = node;
|
|
2846
|
+
else throw std::runtime_error("PD arc label appears more than twice");
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
std::vector<int> other(nodeCount, -1);
|
|
2851
|
+
for (int label = 0; label <= maxLabel; ++label) {
|
|
2852
|
+
if (first[label] == -1) continue;
|
|
2853
|
+
if (second[label] == -1) throw std::runtime_error("PD arc label does not appear twice");
|
|
2854
|
+
other[first[label]] = second[label];
|
|
2855
|
+
other[second[label]] = first[label];
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// 1 means that this edge leaves the crossing at this incidence, and 0
|
|
2859
|
+
// means that it enters. Opposite incidences at a crossing and the two
|
|
2860
|
+
// incidences of one edge always have opposite directions.
|
|
2861
|
+
std::vector<signed char> outgoing(nodeCount, -1);
|
|
2862
|
+
std::vector<int> queue;
|
|
2863
|
+
queue.reserve(nodeCount);
|
|
2864
|
+
auto orientComponent = [&](int seed, signed char direction) {
|
|
2865
|
+
if (outgoing[seed] != -1) {
|
|
2866
|
+
if (outgoing[seed] != direction) throw std::runtime_error("inconsistent PD orientation");
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
queue.clear();
|
|
2870
|
+
outgoing[seed] = direction;
|
|
2871
|
+
queue.push_back(seed);
|
|
2872
|
+
for (size_t head = 0; head < queue.size(); ++head) {
|
|
2873
|
+
int node = queue[head];
|
|
2874
|
+
int crossing = node / 4;
|
|
2875
|
+
int slot = node % 4;
|
|
2876
|
+
int neighbors[2] = {4 * crossing + ((slot + 2) % 4), other[node]};
|
|
2877
|
+
for (int next : neighbors) {
|
|
2878
|
+
if (next < 0) throw std::runtime_error("broken PD edge incidence");
|
|
2879
|
+
signed char nextDirection = static_cast<signed char>(1 - outgoing[node]);
|
|
2880
|
+
if (outgoing[next] == -1) {
|
|
2881
|
+
outgoing[next] = nextDirection;
|
|
2882
|
+
queue.push_back(next);
|
|
2883
|
+
} else if (outgoing[next] != nextDirection) {
|
|
2884
|
+
throw std::runtime_error("inconsistent PD orientation");
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
};
|
|
2889
|
+
|
|
2890
|
+
// In SageMath's PD convention slot 0 is the incoming under-edge and slot
|
|
2891
|
+
// 2 is the outgoing under-edge. These seeds orient every component that
|
|
2892
|
+
// passes under at least once.
|
|
2893
|
+
for (size_t i = 0; i < pd.size(); ++i) orientComponent(static_cast<int>(4 * i + 2), 1);
|
|
2894
|
+
|
|
2895
|
+
// A component which is over at every crossing has no orientation encoded
|
|
2896
|
+
// by the PD tuples. Match SageMath's deterministic traversal by taking the
|
|
2897
|
+
// first occurrence of the smallest still-unoriented edge as a tail.
|
|
2898
|
+
for (int label = 0; label <= maxLabel; ++label) {
|
|
2899
|
+
if (first[label] != -1 && outgoing[first[label]] == -1) orientComponent(first[label], 1);
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
std::vector<int> xsigns(pd.size());
|
|
2903
|
+
for (size_t i = 0; i < pd.size(); ++i) {
|
|
2904
|
+
const std::vector<int>& crossing = pd[i];
|
|
2905
|
+
if (crossing[0] == crossing[3] || crossing[2] == crossing[1]) xsigns[i] = -1;
|
|
2906
|
+
else if (crossing[3] == crossing[2] || crossing[0] == crossing[1]) xsigns[i] = 1;
|
|
2907
|
+
else xsigns[i] = outgoing[4 * i + 3] ? -1 : 1;
|
|
2908
|
+
}
|
|
2909
|
+
return xsigns;
|
|
2910
|
+
}
|
|
2831
2911
|
|
|
2832
2912
|
static bool sanityPD(const PDCode& pd) {
|
|
2833
2913
|
std::map<int, int> counts;
|
|
@@ -2910,15 +2990,17 @@ static PDCode renumberR1Order(PDCode pd) {
|
|
|
2910
2990
|
return pd;
|
|
2911
2991
|
}
|
|
2912
2992
|
|
|
2913
|
-
static PDCode eraseR1(PDCode pd) {
|
|
2914
|
-
if (!sanityPD(pd)) throw std::runtime_error("invalid PD code: every arc label must appear exactly twice");
|
|
2915
|
-
|
|
2993
|
+
static PDCode eraseR1(PDCode pd, std::vector<int>* signs) {
|
|
2994
|
+
if (!sanityPD(pd)) throw std::runtime_error("invalid PD code: every arc label must appear exactly twice");
|
|
2995
|
+
if (signs && signs->size() != pd.size()) throw std::runtime_error("crossing sign count does not match PD code");
|
|
2996
|
+
bool hasR1 = true;
|
|
2916
2997
|
while (hasR1) {
|
|
2917
2998
|
hasR1 = false;
|
|
2918
2999
|
for (size_t i = 0; i < pd.size(); ++i) {
|
|
2919
3000
|
if (uniqueCount(pd[i]) <= 3) {
|
|
2920
|
-
std::vector<int> crossing = pd[i];
|
|
2921
|
-
pd.erase(pd.begin() + static_cast<std::ptrdiff_t>(i));
|
|
3001
|
+
std::vector<int> crossing = pd[i];
|
|
3002
|
+
pd.erase(pd.begin() + static_cast<std::ptrdiff_t>(i));
|
|
3003
|
+
if (signs) signs->erase(signs->begin() + static_cast<std::ptrdiff_t>(i));
|
|
2922
3004
|
std::vector<int> singles;
|
|
2923
3005
|
for (int v : crossing) {
|
|
2924
3006
|
if (std::count(crossing.begin(), crossing.end(), v) == 1) singles.push_back(v);
|
|
@@ -3073,8 +3155,9 @@ static PDCode renumberFullDfs(PDCode pd) {
|
|
|
3073
3155
|
return pd;
|
|
3074
3156
|
}
|
|
3075
3157
|
|
|
3076
|
-
static PDCode eraseOneNugatory(PDCode pd, size_t index) {
|
|
3077
|
-
if (uniqueCount(pd[index]) != 4) throw std::runtime_error("nugatory erase requires R1-free PD code");
|
|
3158
|
+
static PDCode eraseOneNugatory(PDCode pd, size_t index, std::vector<int>* signs) {
|
|
3159
|
+
if (uniqueCount(pd[index]) != 4) throw std::runtime_error("nugatory erase requires R1-free PD code");
|
|
3160
|
+
if (signs && signs->size() != pd.size()) throw std::runtime_error("crossing sign count does not match PD code");
|
|
3078
3161
|
int ax = pd[index][0];
|
|
3079
3162
|
int bx = pd[index][1];
|
|
3080
3163
|
int cx = pd[index][2];
|
|
@@ -3096,22 +3179,28 @@ static PDCode eraseOneNugatory(PDCode pd, size_t index) {
|
|
|
3096
3179
|
if (!loopSet.count(ax) || !loopSet.count(bx) || !loopSet.count(cx) || !loopSet.count(dx)) {
|
|
3097
3180
|
throw std::runtime_error("nugatory crossing arcs are not in one component");
|
|
3098
3181
|
}
|
|
3099
|
-
PDCode bad = pd;
|
|
3100
|
-
bad.erase(bad.begin() + static_cast<std::ptrdiff_t>(index));
|
|
3182
|
+
PDCode bad = pd;
|
|
3183
|
+
bad.erase(bad.begin() + static_cast<std::ptrdiff_t>(index));
|
|
3184
|
+
if (signs) signs->erase(signs->begin() + static_cast<std::ptrdiff_t>(index));
|
|
3101
3185
|
bad = replaceArcValue(bad, ax, cx);
|
|
3102
3186
|
bad = replaceArcValue(bad, dx, bx);
|
|
3103
3187
|
return renumberFullDfs(bad);
|
|
3104
3188
|
}
|
|
3105
3189
|
|
|
3106
|
-
static PDCode simplifyPDCode(PDCode pd
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
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
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
return pd;
|
|
3203
|
+
}
|
|
3115
3204
|
|
|
3116
3205
|
static int komplexSize(const Komplex& k) {
|
|
3117
3206
|
int total = 0;
|
|
@@ -3119,8 +3208,8 @@ static int komplexSize(const Komplex& k) {
|
|
|
3119
3208
|
return total;
|
|
3120
3209
|
}
|
|
3121
3210
|
|
|
3122
|
-
static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std::vector<int>& xsigns) {
|
|
3123
|
-
KH_PROFILE(generateFast);
|
|
3211
|
+
static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std::vector<int>& xsigns) {
|
|
3212
|
+
KH_PROFILE(generateFast);
|
|
3124
3213
|
if (pd.empty()) {
|
|
3125
3214
|
Komplex kom(1);
|
|
3126
3215
|
kom.columns[0] = SmoothingColumn(1);
|
|
@@ -3128,11 +3217,13 @@ static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std:
|
|
|
3128
3217
|
kom.reduce();
|
|
3129
3218
|
return kom;
|
|
3130
3219
|
}
|
|
3131
|
-
std::vector<char> in(pd.size() * 4, 0), done(pd.size(), 0);
|
|
3132
|
-
std::vector<std::vector<int> > pd1(1, std::vector<int>{0,1,2,3});
|
|
3133
|
-
Komplex kplus(pd1, std::vector<int>{1}, 4);
|
|
3134
|
-
Komplex kminus(pd1, std::vector<int>{-1}, 4);
|
|
3135
|
-
|
|
3220
|
+
std::vector<char> in(pd.size() * 4, 0), done(pd.size(), 0);
|
|
3221
|
+
std::vector<std::vector<int> > pd1(1, std::vector<int>{0,1,2,3});
|
|
3222
|
+
Komplex kplus(pd1, std::vector<int>{1}, 4);
|
|
3223
|
+
Komplex kminus(pd1, std::vector<int>{-1}, 4);
|
|
3224
|
+
// Build the full complex by composing one crossing complex at a time and
|
|
3225
|
+
// reducing after every composition, following JavaKh's tangle-growth path.
|
|
3226
|
+
std::vector<int> edges;
|
|
3136
3227
|
int firstdepth = pd.size() > 4 ? 3 : static_cast<int>(pd.size()) - 1;
|
|
3137
3228
|
std::vector<int> firstdummy(firstdepth + 1, 0);
|
|
3138
3229
|
int first = g_options.reorderCrossings ? chooseXingRecursive(edges, pd, in, done, firstdepth, firstdummy)
|
|
@@ -3312,16 +3403,17 @@ static std::vector<std::string> listInputFiles(const std::string& dir) {
|
|
|
3312
3403
|
return files;
|
|
3313
3404
|
}
|
|
3314
3405
|
|
|
3315
|
-
static std::string computePD(const std::vector<std::vector<int> >& pd) {
|
|
3316
|
-
flushCobCache();
|
|
3317
|
-
g_smallArena.reset();
|
|
3318
|
-
|
|
3319
|
-
|
|
3406
|
+
static std::string computePD(const std::vector<std::vector<int> >& pd) {
|
|
3407
|
+
flushCobCache();
|
|
3408
|
+
g_smallArena.reset();
|
|
3409
|
+
std::vector<int> signs = getSigns(pd);
|
|
3410
|
+
PDCode working = simplifyPDCode(pd, &signs, g_options.simplifyR1, g_options.simplifyNugatory);
|
|
3411
|
+
Komplex k = generateFast(working, signs);
|
|
3320
3412
|
ProfileScope khScope(g_profile.kh);
|
|
3321
3413
|
return k.KhForZ();
|
|
3322
3414
|
}
|
|
3323
3415
|
|
|
3324
|
-
static std::string formatPDCode(const PDCode& pd) {
|
|
3416
|
+
static std::string formatPDCode(const PDCode& pd) {
|
|
3325
3417
|
std::ostringstream out;
|
|
3326
3418
|
out << "PD[";
|
|
3327
3419
|
for (size_t i = 0; i < pd.size(); ++i) {
|
|
@@ -3334,13 +3426,26 @@ static std::string formatPDCode(const PDCode& pd) {
|
|
|
3334
3426
|
out << "]";
|
|
3335
3427
|
}
|
|
3336
3428
|
out << "]";
|
|
3337
|
-
return out.str();
|
|
3338
|
-
}
|
|
3339
|
-
|
|
3340
|
-
static std::string
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3429
|
+
return out.str();
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
static std::string formatCrossingSigns(const PDCode& pd) {
|
|
3433
|
+
std::vector<int> signs = getSigns(pd);
|
|
3434
|
+
std::ostringstream out;
|
|
3435
|
+
out << "[";
|
|
3436
|
+
for (size_t i = 0; i < signs.size(); ++i) {
|
|
3437
|
+
if (i) out << ",";
|
|
3438
|
+
out << signs[i];
|
|
3439
|
+
}
|
|
3440
|
+
out << "]";
|
|
3441
|
+
return out.str();
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
static std::string simplifiedPDString(const PDCode& pd) {
|
|
3445
|
+
getSigns(pd);
|
|
3446
|
+
PDCode working = simplifyPDCode(pd, nullptr, g_options.simplifyR1, g_options.simplifyNugatory);
|
|
3447
|
+
return formatPDCode(working);
|
|
3448
|
+
}
|
|
3344
3449
|
|
|
3345
3450
|
} // namespace kh
|
|
3346
3451
|
|
|
@@ -3392,7 +3497,8 @@ CPPKH_API char* cppkh_compute_pd_ex(const char* pd_code, int simplify_pd, int re
|
|
|
3392
3497
|
CppkhOptionsGuard guard;
|
|
3393
3498
|
kh::g_options.progress = false;
|
|
3394
3499
|
kh::g_options.profile = false;
|
|
3395
|
-
kh::g_options.
|
|
3500
|
+
kh::g_options.simplifyR1 = simplify_pd != 0;
|
|
3501
|
+
kh::g_options.simplifyNugatory = simplify_pd != 0;
|
|
3396
3502
|
kh::g_options.reorderCrossings = reorder_crossings != 0;
|
|
3397
3503
|
std::string result = kh::computePD(parsed[0].second);
|
|
3398
3504
|
return cppkhDuplicateString(result);
|
|
@@ -3419,7 +3525,8 @@ CPPKH_API char* cppkh_compute_pd_batch_ex(const char* pd_codes, int simplify_pd,
|
|
|
3419
3525
|
CppkhOptionsGuard guard;
|
|
3420
3526
|
kh::g_options.progress = false;
|
|
3421
3527
|
kh::g_options.profile = false;
|
|
3422
|
-
kh::g_options.
|
|
3528
|
+
kh::g_options.simplifyR1 = simplify_pd != 0;
|
|
3529
|
+
kh::g_options.simplifyNugatory = simplify_pd != 0;
|
|
3423
3530
|
kh::g_options.reorderCrossings = reorder_crossings != 0;
|
|
3424
3531
|
|
|
3425
3532
|
std::ostringstream out;
|
|
@@ -3449,7 +3556,8 @@ CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
|
|
|
3449
3556
|
if (parsed.empty()) throw std::runtime_error("no PD code found");
|
|
3450
3557
|
if (parsed.size() != 1) throw std::runtime_error("cppkh_simplify_pd expects exactly one PD code");
|
|
3451
3558
|
CppkhOptionsGuard guard;
|
|
3452
|
-
kh::g_options.
|
|
3559
|
+
kh::g_options.simplifyR1 = true;
|
|
3560
|
+
kh::g_options.simplifyNugatory = true;
|
|
3453
3561
|
std::string result = kh::simplifiedPDString(parsed[0].second);
|
|
3454
3562
|
return cppkhDuplicateString(result);
|
|
3455
3563
|
} catch (const std::exception& e) {
|
|
@@ -3464,7 +3572,7 @@ CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
|
|
|
3464
3572
|
|
|
3465
3573
|
#ifndef CPPKH_SHARED_LIBRARY
|
|
3466
3574
|
static void usage() {
|
|
3467
|
-
std::cout << "Usage:
|
|
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";
|
|
3468
3576
|
std::cout << "Thread backend: " << KH_THREAD_BACKEND_NAME << "\n";
|
|
3469
3577
|
std::cout << "Detected CPU threads: " << kh::detectHardwareThreads() << "\n";
|
|
3470
3578
|
std::cout << "PD simplification: R1 removal then nugatory crossing removal is enabled by default.\n";
|
|
@@ -3472,9 +3580,10 @@ static void usage() {
|
|
|
3472
3580
|
|
|
3473
3581
|
int main(int argc, char** argv) {
|
|
3474
3582
|
try {
|
|
3475
|
-
std::vector<std::string> files;
|
|
3476
|
-
std::vector<std::pair<std::string, std::vector<std::vector<int> > > > jobs;
|
|
3477
|
-
bool printSimplifiedPD = false;
|
|
3583
|
+
std::vector<std::string> files;
|
|
3584
|
+
std::vector<std::pair<std::string, std::vector<std::vector<int> > > > jobs;
|
|
3585
|
+
bool printSimplifiedPD = false;
|
|
3586
|
+
bool printCrossingSigns = false;
|
|
3478
3587
|
for (int i = 1; i < argc; ++i) {
|
|
3479
3588
|
std::string a = argv[i];
|
|
3480
3589
|
if (a == "--help" || a == "-h") { usage(); return 0; }
|
|
@@ -3492,9 +3601,18 @@ int main(int argc, char** argv) {
|
|
|
3492
3601
|
} else if (a == "--ordered" || a == "-O") kh::g_options.reorderCrossings = false;
|
|
3493
3602
|
else if (a == "--quiet" || a == "-q") kh::g_options.progress = false;
|
|
3494
3603
|
else if (a == "--profile") kh::g_options.profile = true;
|
|
3495
|
-
else if (a == "--no-simplify-pd" || a == "--raw-pd")
|
|
3496
|
-
|
|
3497
|
-
|
|
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;
|
|
3614
|
+
else if (a == "--print-simplified-pd") printSimplifiedPD = true;
|
|
3615
|
+
else if (a == "--print-crossing-signs") printCrossingSigns = true;
|
|
3498
3616
|
else if (a == "--threads" || a == "-j") {
|
|
3499
3617
|
if (++i >= argc) throw std::runtime_error("--threads needs a number");
|
|
3500
3618
|
std::string v = argv[i];
|
|
@@ -3521,9 +3639,10 @@ int main(int argc, char** argv) {
|
|
|
3521
3639
|
kh::g_profile.reset();
|
|
3522
3640
|
profileStart = kh::profileNowNs();
|
|
3523
3641
|
}
|
|
3524
|
-
if (label) std::cout << jobs[i].first << "\t";
|
|
3525
|
-
if (printSimplifiedPD) std::cout << kh::simplifiedPDString(jobs[i].second) << "\n";
|
|
3526
|
-
else std::cout <<
|
|
3642
|
+
if (label) std::cout << jobs[i].first << "\t";
|
|
3643
|
+
if (printSimplifiedPD) std::cout << kh::simplifiedPDString(jobs[i].second) << "\n";
|
|
3644
|
+
else if (printCrossingSigns) std::cout << kh::formatCrossingSigns(jobs[i].second) << "\n";
|
|
3645
|
+
else std::cout << "\"" << kh::computePD(jobs[i].second) << "\"\n";
|
|
3527
3646
|
if (kh::g_options.profile) {
|
|
3528
3647
|
kh::g_profile.totalNs = kh::profileNowNs() - profileStart;
|
|
3529
3648
|
kh::printProfile();
|
|
@@ -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.1 → 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
|