cppkh-interface 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -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 simplifyPD = true;
306
- bool profile = false;
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
- int nedges = static_cast<int>(edges.size());
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
- std::vector<int> xsigns(pd.size());
2824
- for (size_t i = 0; i < pd.size(); ++i) {
2825
- if (pd[i][1] - pd[i][3] == 1 || pd[i][3] - pd[i][1] > 1) xsigns[i] = 1;
2826
- else if (pd[i][3] - pd[i][1] == 1 || pd[i][1] - pd[i][3] > 1) xsigns[i] = -1;
2827
- else throw std::runtime_error("error finding crossing signs");
2828
- }
2829
- return xsigns;
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
- bool hasR1 = true;
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
- pd = eraseR1(pd);
3108
- while (true) {
3109
- int index = findNugatory(pd);
3110
- if (index < 0) break;
3111
- pd = eraseOneNugatory(pd, static_cast<size_t>(index));
3112
- }
3113
- return pd;
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
- std::vector<int> edges;
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
- PDCode working = g_options.simplifyPD ? simplifyPDCode(pd) : pd;
3319
- Komplex k = generateFast(working, getSigns(working));
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 simplifiedPDString(const PDCode& pd) {
3341
- PDCode working = g_options.simplifyPD ? simplifyPDCode(pd) : pd;
3342
- return formatPDCode(working);
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.simplifyPD = simplify_pd != 0;
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.simplifyPD = simplify_pd != 0;
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.simplifyPD = true;
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: javakh_cpp [--pd-file FILE] [--pd-dir DIR] [--pd-code CODE] [--ordered] [--threads N|auto] [--quiet] [--profile] [--no-simplify-pd] [--print-simplified-pd]\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";
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") kh::g_options.simplifyPD = false;
3496
- else if (a == "--simplify-pd") kh::g_options.simplifyPD = true;
3497
- else if (a == "--print-simplified-pd") printSimplifiedPD = true;
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 << "\"" << kh::computePD(jobs[i].second) << "\"\n";
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();
cppkh_interface/main.py CHANGED
@@ -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
- UNKNOT_RESULT = "q^-1*t^0*Z[0] + q^1*t^0*Z[0]"
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 _compiler_runtime_path_entries() -> list[str]:
158
- compiler = cpp_simple_interface.get_gpp_filepath().strip()
159
- if not compiler:
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
- candidates.append(unquoted)
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
- candidates.extend(shlex.split(compiler, posix=True))
170
- except ValueError:
171
- pass
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 candidates:
175
- path = pathlib.Path(candidate)
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 _cache_key(source_bytes: bytes, flags: Sequence[str]) -> str:
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(cpp_simple_interface.get_gpp_filepath().encode("utf-8"))
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 with cpp-simple-interface and return the executable path."""
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
- exe.chmod(exe.stat().st_mode | 0o755)
239
- except OSError:
240
- pass
241
- return exe
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
- simplify_pd: bool = False,
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 not simplify_pd:
274
- command.append("--no-simplify-pd")
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
- return [line.strip() for line in result.stdout.splitlines() if line.strip()]
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
- simplify_pd: bool = False,
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
- simplify_pd=simplify_pd,
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 _prepare_crossings(pd_code: PdInput, de_r1: bool, de_k8: bool) -> list[list[int]]:
335
- crossings = _as_crossings(pd_code)
336
- _check_sanity(crossings)
337
- if de_r1:
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 _prepare_many_for_cppkh(
345
- pd_codes: PdManyInput,
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
- ) -> 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."
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
- 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
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
- 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
-
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, 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()}")
474
+ document = _prepare_many_for_cppkh(pd_codes)
430
475
  if not document:
431
476
  return []
432
- return _run_cppkh_document(document, encoding=encoding, threads=threads, simplify_pd=use_cpp_simplify)
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
- 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
-
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 _format_pd(_prepare_crossings(pd_code, de_r1=de_r1, de_k8=de_k8))
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,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cppkh-interface
3
- Version: 0.1.1
4
- Summary: Python interface for cppkh Khovanov homology computation with runtime C++ compilation.
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 through `cpp-simple-interface`. The compiled executable
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 `poetry build` or `poetry publish --build` is
47
- running, then removes that temporary copy.
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 `g++` compatible compiler must be available at runtime. To select a compiler,
56
- set `CXX` before importing or calling the package:
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
- CXX=clang++ python your_script.py
60
+ CPPKH_INTERFACE_CXX=clang++ python your_script.py
60
61
  ```
61
62
 
62
63
  Windows PowerShell:
63
64
 
64
65
  ```powershell
65
- $env:CXX = "C:\path\to\g++.exe"
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
- poetry build
75
+ python -m build
75
76
  poetry publish
76
77
  ```
77
78
 
78
- Use `poetry publish --build` to build and upload in one command.
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
 
@@ -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=yYj01WZZn12pKlTPq-bsPrc0JxgoWzZUnC-1qsDItkQ,17724
4
+ cppkh_interface/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ cppkh_interface-0.1.3.dist-info/entry_points.txt,sha256=CsnHBNaO_RJe4eFXmEZpSxWu-c1aF0RpjVFaEGlIm4Y,61
6
+ cppkh_interface-0.1.3.dist-info/METADATA,sha256=OpUBi9Go11i9yz4fCeRFfJ6sw_AcOnf9YmphsNNYxz4,2802
7
+ cppkh_interface-0.1.3.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
8
+ cppkh_interface/data/src/main.cpp,sha256=M5yNMOYBT559L2FvCEm7_PQi9u-bIQXRYGDg3XXVF9I,149049
9
+ cppkh_interface-0.1.3.dist-info/RECORD,,
@@ -1,9 +0,0 @@
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,,