cppkh-interface 0.1.1__tar.gz → 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cppkh-interface
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python interface for cppkh Khovanov homology computation with runtime C++ compilation.
5
5
  License-Expression: MIT
6
6
  Author: GGN_2015
@@ -26,6 +26,10 @@ Description-Content-Type: text/markdown
26
26
  `cppkh-interface` is a Python package for computing integer Khovanov homology
27
27
  with the C++ `cppkh` implementation.
28
28
 
29
+ Version `0.1.2` resolves link crossing signs by tracing directed PD edge
30
+ incidences with the SageMath convention. It does not infer signs from numeric
31
+ arc-label ordering.
32
+
29
33
  The package is compatible with the main `javakh-interface` function:
30
34
 
31
35
  ```python
@@ -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.2` resolves link crossing signs by tracing directed PD edge
7
+ incidences with the SageMath convention. It does not infer signs from numeric
8
+ arc-label ordering.
9
+
6
10
  The package is compatible with the main `javakh-interface` function:
7
11
 
8
12
  ```python
@@ -2758,9 +2758,12 @@ std::string Komplex::KhForZ() {
2758
2758
  return ret;
2759
2759
  }
2760
2760
 
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());
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
+ // Choose the next crossing for the growing tangle. The heuristic maximizes
2764
+ // already-attached boundary arcs, with a short lookahead to keep later
2765
+ // intermediate girth smaller. It affects runtime, not the homology result.
2766
+ int nedges = static_cast<int>(edges.size());
2764
2767
  int best = -1, nconbest = -1;
2765
2768
  std::vector<int> rbest(depth, 0);
2766
2769
  for (size_t i = 0; i < pd.size(); ++i) if (!done[i]) {
@@ -2819,15 +2822,91 @@ static int takeNextCrossing(const std::vector<int>&, const std::vector<std::vect
2819
2822
  throw std::runtime_error("no crossing left");
2820
2823
  }
2821
2824
 
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
- }
2825
+ static std::vector<int> getSigns(const std::vector<std::vector<int> >& pd) {
2826
+ const int nodeCount = static_cast<int>(pd.size() * 4);
2827
+ if (nodeCount == 0) return std::vector<int>();
2828
+
2829
+ int maxLabel = -1;
2830
+ for (size_t i = 0; i < pd.size(); ++i) {
2831
+ if (pd[i].size() != 4) throw std::runtime_error("PD crossing must have four entries");
2832
+ for (int label : pd[i]) {
2833
+ if (label < 0) throw std::runtime_error("PD arc labels must be positive");
2834
+ maxLabel = std::max(maxLabel, label);
2835
+ }
2836
+ }
2837
+
2838
+ std::vector<int> first(maxLabel + 1, -1), second(maxLabel + 1, -1);
2839
+ for (size_t i = 0; i < pd.size(); ++i) {
2840
+ for (int slot = 0; slot < 4; ++slot) {
2841
+ int label = pd[i][slot];
2842
+ int node = static_cast<int>(4 * i) + slot;
2843
+ if (first[label] == -1) first[label] = node;
2844
+ else if (second[label] == -1) second[label] = node;
2845
+ else throw std::runtime_error("PD arc label appears more than twice");
2846
+ }
2847
+ }
2848
+
2849
+ std::vector<int> other(nodeCount, -1);
2850
+ for (int label = 0; label <= maxLabel; ++label) {
2851
+ if (first[label] == -1) continue;
2852
+ if (second[label] == -1) throw std::runtime_error("PD arc label does not appear twice");
2853
+ other[first[label]] = second[label];
2854
+ other[second[label]] = first[label];
2855
+ }
2856
+
2857
+ // 1 means that this edge leaves the crossing at this incidence, and 0
2858
+ // means that it enters. Opposite incidences at a crossing and the two
2859
+ // incidences of one edge always have opposite directions.
2860
+ std::vector<signed char> outgoing(nodeCount, -1);
2861
+ std::vector<int> queue;
2862
+ queue.reserve(nodeCount);
2863
+ auto orientComponent = [&](int seed, signed char direction) {
2864
+ if (outgoing[seed] != -1) {
2865
+ if (outgoing[seed] != direction) throw std::runtime_error("inconsistent PD orientation");
2866
+ return;
2867
+ }
2868
+ queue.clear();
2869
+ outgoing[seed] = direction;
2870
+ queue.push_back(seed);
2871
+ for (size_t head = 0; head < queue.size(); ++head) {
2872
+ int node = queue[head];
2873
+ int crossing = node / 4;
2874
+ int slot = node % 4;
2875
+ int neighbors[2] = {4 * crossing + ((slot + 2) % 4), other[node]};
2876
+ for (int next : neighbors) {
2877
+ if (next < 0) throw std::runtime_error("broken PD edge incidence");
2878
+ signed char nextDirection = static_cast<signed char>(1 - outgoing[node]);
2879
+ if (outgoing[next] == -1) {
2880
+ outgoing[next] = nextDirection;
2881
+ queue.push_back(next);
2882
+ } else if (outgoing[next] != nextDirection) {
2883
+ throw std::runtime_error("inconsistent PD orientation");
2884
+ }
2885
+ }
2886
+ }
2887
+ };
2888
+
2889
+ // In SageMath's PD convention slot 0 is the incoming under-edge and slot
2890
+ // 2 is the outgoing under-edge. These seeds orient every component that
2891
+ // passes under at least once.
2892
+ for (size_t i = 0; i < pd.size(); ++i) orientComponent(static_cast<int>(4 * i + 2), 1);
2893
+
2894
+ // A component which is over at every crossing has no orientation encoded
2895
+ // by the PD tuples. Match SageMath's deterministic traversal by taking the
2896
+ // first occurrence of the smallest still-unoriented edge as a tail.
2897
+ for (int label = 0; label <= maxLabel; ++label) {
2898
+ if (first[label] != -1 && outgoing[first[label]] == -1) orientComponent(first[label], 1);
2899
+ }
2900
+
2901
+ std::vector<int> xsigns(pd.size());
2902
+ for (size_t i = 0; i < pd.size(); ++i) {
2903
+ const std::vector<int>& crossing = pd[i];
2904
+ if (crossing[0] == crossing[3] || crossing[2] == crossing[1]) xsigns[i] = -1;
2905
+ else if (crossing[3] == crossing[2] || crossing[0] == crossing[1]) xsigns[i] = 1;
2906
+ else xsigns[i] = outgoing[4 * i + 3] ? -1 : 1;
2907
+ }
2908
+ return xsigns;
2909
+ }
2831
2910
 
2832
2911
  static bool sanityPD(const PDCode& pd) {
2833
2912
  std::map<int, int> counts;
@@ -2910,15 +2989,17 @@ static PDCode renumberR1Order(PDCode pd) {
2910
2989
  return pd;
2911
2990
  }
2912
2991
 
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;
2992
+ static PDCode eraseR1(PDCode pd, std::vector<int>* signs) {
2993
+ if (!sanityPD(pd)) throw std::runtime_error("invalid PD code: every arc label must appear exactly twice");
2994
+ if (signs && signs->size() != pd.size()) throw std::runtime_error("crossing sign count does not match PD code");
2995
+ bool hasR1 = true;
2916
2996
  while (hasR1) {
2917
2997
  hasR1 = false;
2918
2998
  for (size_t i = 0; i < pd.size(); ++i) {
2919
2999
  if (uniqueCount(pd[i]) <= 3) {
2920
- std::vector<int> crossing = pd[i];
2921
- pd.erase(pd.begin() + static_cast<std::ptrdiff_t>(i));
3000
+ std::vector<int> crossing = pd[i];
3001
+ pd.erase(pd.begin() + static_cast<std::ptrdiff_t>(i));
3002
+ if (signs) signs->erase(signs->begin() + static_cast<std::ptrdiff_t>(i));
2922
3003
  std::vector<int> singles;
2923
3004
  for (int v : crossing) {
2924
3005
  if (std::count(crossing.begin(), crossing.end(), v) == 1) singles.push_back(v);
@@ -3073,8 +3154,9 @@ static PDCode renumberFullDfs(PDCode pd) {
3073
3154
  return pd;
3074
3155
  }
3075
3156
 
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");
3157
+ static PDCode eraseOneNugatory(PDCode pd, size_t index, std::vector<int>* signs) {
3158
+ if (uniqueCount(pd[index]) != 4) throw std::runtime_error("nugatory erase requires R1-free PD code");
3159
+ if (signs && signs->size() != pd.size()) throw std::runtime_error("crossing sign count does not match PD code");
3078
3160
  int ax = pd[index][0];
3079
3161
  int bx = pd[index][1];
3080
3162
  int cx = pd[index][2];
@@ -3096,22 +3178,23 @@ static PDCode eraseOneNugatory(PDCode pd, size_t index) {
3096
3178
  if (!loopSet.count(ax) || !loopSet.count(bx) || !loopSet.count(cx) || !loopSet.count(dx)) {
3097
3179
  throw std::runtime_error("nugatory crossing arcs are not in one component");
3098
3180
  }
3099
- PDCode bad = pd;
3100
- bad.erase(bad.begin() + static_cast<std::ptrdiff_t>(index));
3181
+ PDCode bad = pd;
3182
+ bad.erase(bad.begin() + static_cast<std::ptrdiff_t>(index));
3183
+ if (signs) signs->erase(signs->begin() + static_cast<std::ptrdiff_t>(index));
3101
3184
  bad = replaceArcValue(bad, ax, cx);
3102
3185
  bad = replaceArcValue(bad, dx, bx);
3103
3186
  return renumberFullDfs(bad);
3104
3187
  }
3105
3188
 
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
- }
3189
+ static PDCode simplifyPDCode(PDCode pd, std::vector<int>* signs = nullptr) {
3190
+ pd = eraseR1(pd, signs);
3191
+ while (true) {
3192
+ int index = findNugatory(pd);
3193
+ if (index < 0) break;
3194
+ pd = eraseOneNugatory(pd, static_cast<size_t>(index), signs);
3195
+ }
3196
+ return pd;
3197
+ }
3115
3198
 
3116
3199
  static int komplexSize(const Komplex& k) {
3117
3200
  int total = 0;
@@ -3119,8 +3202,8 @@ static int komplexSize(const Komplex& k) {
3119
3202
  return total;
3120
3203
  }
3121
3204
 
3122
- static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std::vector<int>& xsigns) {
3123
- KH_PROFILE(generateFast);
3205
+ static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std::vector<int>& xsigns) {
3206
+ KH_PROFILE(generateFast);
3124
3207
  if (pd.empty()) {
3125
3208
  Komplex kom(1);
3126
3209
  kom.columns[0] = SmoothingColumn(1);
@@ -3128,11 +3211,13 @@ static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std:
3128
3211
  kom.reduce();
3129
3212
  return kom;
3130
3213
  }
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;
3214
+ std::vector<char> in(pd.size() * 4, 0), done(pd.size(), 0);
3215
+ std::vector<std::vector<int> > pd1(1, std::vector<int>{0,1,2,3});
3216
+ Komplex kplus(pd1, std::vector<int>{1}, 4);
3217
+ Komplex kminus(pd1, std::vector<int>{-1}, 4);
3218
+ // Build the full complex by composing one crossing complex at a time and
3219
+ // reducing after every composition, following JavaKh's tangle-growth path.
3220
+ std::vector<int> edges;
3136
3221
  int firstdepth = pd.size() > 4 ? 3 : static_cast<int>(pd.size()) - 1;
3137
3222
  std::vector<int> firstdummy(firstdepth + 1, 0);
3138
3223
  int first = g_options.reorderCrossings ? chooseXingRecursive(edges, pd, in, done, firstdepth, firstdummy)
@@ -3312,16 +3397,17 @@ static std::vector<std::string> listInputFiles(const std::string& dir) {
3312
3397
  return files;
3313
3398
  }
3314
3399
 
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));
3400
+ static std::string computePD(const std::vector<std::vector<int> >& pd) {
3401
+ flushCobCache();
3402
+ g_smallArena.reset();
3403
+ std::vector<int> signs = getSigns(pd);
3404
+ PDCode working = g_options.simplifyPD ? simplifyPDCode(pd, &signs) : pd;
3405
+ Komplex k = generateFast(working, signs);
3320
3406
  ProfileScope khScope(g_profile.kh);
3321
3407
  return k.KhForZ();
3322
3408
  }
3323
3409
 
3324
- static std::string formatPDCode(const PDCode& pd) {
3410
+ static std::string formatPDCode(const PDCode& pd) {
3325
3411
  std::ostringstream out;
3326
3412
  out << "PD[";
3327
3413
  for (size_t i = 0; i < pd.size(); ++i) {
@@ -3334,10 +3420,22 @@ static std::string formatPDCode(const PDCode& pd) {
3334
3420
  out << "]";
3335
3421
  }
3336
3422
  out << "]";
3337
- return out.str();
3338
- }
3339
-
3340
- static std::string simplifiedPDString(const PDCode& pd) {
3423
+ return out.str();
3424
+ }
3425
+
3426
+ static std::string formatCrossingSigns(const PDCode& pd) {
3427
+ std::vector<int> signs = getSigns(pd);
3428
+ std::ostringstream out;
3429
+ out << "[";
3430
+ for (size_t i = 0; i < signs.size(); ++i) {
3431
+ if (i) out << ",";
3432
+ out << signs[i];
3433
+ }
3434
+ out << "]";
3435
+ return out.str();
3436
+ }
3437
+
3438
+ static std::string simplifiedPDString(const PDCode& pd) {
3341
3439
  PDCode working = g_options.simplifyPD ? simplifyPDCode(pd) : pd;
3342
3440
  return formatPDCode(working);
3343
3441
  }
@@ -3464,7 +3562,7 @@ CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
3464
3562
 
3465
3563
  #ifndef CPPKH_SHARED_LIBRARY
3466
3564
  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";
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";
3468
3566
  std::cout << "Thread backend: " << KH_THREAD_BACKEND_NAME << "\n";
3469
3567
  std::cout << "Detected CPU threads: " << kh::detectHardwareThreads() << "\n";
3470
3568
  std::cout << "PD simplification: R1 removal then nugatory crossing removal is enabled by default.\n";
@@ -3472,9 +3570,10 @@ static void usage() {
3472
3570
 
3473
3571
  int main(int argc, char** argv) {
3474
3572
  try {
3475
- std::vector<std::string> files;
3476
- std::vector<std::pair<std::string, std::vector<std::vector<int> > > > jobs;
3477
- bool printSimplifiedPD = false;
3573
+ std::vector<std::string> files;
3574
+ std::vector<std::pair<std::string, std::vector<std::vector<int> > > > jobs;
3575
+ bool printSimplifiedPD = false;
3576
+ bool printCrossingSigns = false;
3478
3577
  for (int i = 1; i < argc; ++i) {
3479
3578
  std::string a = argv[i];
3480
3579
  if (a == "--help" || a == "-h") { usage(); return 0; }
@@ -3493,8 +3592,9 @@ int main(int argc, char** argv) {
3493
3592
  else if (a == "--quiet" || a == "-q") kh::g_options.progress = false;
3494
3593
  else if (a == "--profile") kh::g_options.profile = true;
3495
3594
  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;
3595
+ else if (a == "--simplify-pd") kh::g_options.simplifyPD = true;
3596
+ else if (a == "--print-simplified-pd") printSimplifiedPD = true;
3597
+ else if (a == "--print-crossing-signs") printCrossingSigns = true;
3498
3598
  else if (a == "--threads" || a == "-j") {
3499
3599
  if (++i >= argc) throw std::runtime_error("--threads needs a number");
3500
3600
  std::string v = argv[i];
@@ -3521,9 +3621,10 @@ int main(int argc, char** argv) {
3521
3621
  kh::g_profile.reset();
3522
3622
  profileStart = kh::profileNowNs();
3523
3623
  }
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";
3624
+ if (label) std::cout << jobs[i].first << "\t";
3625
+ if (printSimplifiedPD) std::cout << kh::simplifiedPDString(jobs[i].second) << "\n";
3626
+ else if (printCrossingSigns) std::cout << kh::formatCrossingSigns(jobs[i].second) << "\n";
3627
+ else std::cout << "\"" << kh::computePD(jobs[i].second) << "\"\n";
3527
3628
  if (kh::g_options.profile) {
3528
3629
  kh::g_profile.totalNs = kh::profileNowNs() - profileStart;
3529
3630
  kh::printProfile();
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cppkh-interface"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "Python interface for cppkh Khovanov homology computation with runtime C++ compilation."
5
5
  authors = [
6
6
  {name = "GGN_2015", email = "neko@jlulug.org"}