cppkh-interface 0.1.3__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  #include <algorithm>
2
2
  #include <array>
3
3
  #include <cctype>
4
- #include <chrono>
5
- #include <cstdint>
6
- #include <cstdlib>
7
- #include <cstring>
8
- #include <dirent.h>
4
+ #include <chrono>
5
+ #include <cstdint>
6
+ #include <cstdlib>
7
+ #include <cstring>
8
+ #include <dirent.h>
9
9
  #include <fstream>
10
10
  #include <functional>
11
11
  #include <iostream>
@@ -298,14 +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 simplifyR1 = true;
306
- bool simplifyNugatory = true;
307
- bool profile = false;
308
- };
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
+ };
309
309
 
310
310
  static Options g_options;
311
311
  typedef std::vector<std::vector<int> > PDCode;
@@ -682,8 +682,8 @@ static bool operator==(const ArenaAllocator<T>&, const ArenaAllocator<U>&) { ret
682
682
  template <typename T, typename U>
683
683
  static bool operator!=(const ArenaAllocator<T>&, const ArenaAllocator<U>&) { return false; }
684
684
 
685
- typedef std::vector<Small, ArenaAllocator<Small> > SmallVec;
686
- typedef std::vector<int, ArenaAllocator<int> > ArenaIntVec;
685
+ typedef std::vector<Small, ArenaAllocator<Small> > SmallVec;
686
+ typedef std::vector<int, ArenaAllocator<int> > ArenaIntVec;
687
687
 
688
688
  template <size_t InlineN>
689
689
  struct IntBuffer {
@@ -875,9 +875,9 @@ struct Cobordism {
875
875
  mutable std::string keyCache;
876
876
  mutable bool hashReady = false;
877
877
  mutable uint64_t hashCache = 0;
878
- mutable ArenaIntVec boundaryOffset;
879
- mutable SmallVec boundaryItems;
880
- mutable ArenaIntVec edgeOffset;
878
+ mutable ArenaIntVec boundaryOffset;
879
+ mutable SmallVec boundaryItems;
880
+ mutable ArenaIntVec edgeOffset;
881
881
  mutable SmallVec edgeItems;
882
882
 
883
883
  Cobordism() = default;
@@ -1003,10 +1003,10 @@ struct Cobordism {
1003
1003
  CobPtr composeHorizontal(int start, const CobPtr& cc, int cstart, int nc) const;
1004
1004
  };
1005
1005
 
1006
- template <typename... Args>
1007
- static CobPtr makeCob(Args&&... args) {
1008
- return std::make_shared<Cobordism>(std::forward<Args>(args)...);
1009
- }
1006
+ template <typename... Args>
1007
+ static CobPtr makeCob(Args&&... args) {
1008
+ return std::make_shared<Cobordism>(std::forward<Args>(args)...);
1009
+ }
1010
1010
 
1011
1011
  static SimpleMutex g_cobMutex;
1012
1012
  static std::unordered_map<uint64_t, std::vector<CobPtr> > g_cobCache;
@@ -1495,11 +1495,11 @@ CobPtr Cobordism::composeHorizontal(int start, const CobPtr& cc, int cstart, int
1495
1495
  return cacheCob(ret);
1496
1496
  }
1497
1497
 
1498
- struct TermList {
1499
- typedef std::pair<CobPtr, BigInt> Term;
1500
- size_t n = 0;
1501
- Term first;
1502
- std::vector<Term, ArenaAllocator<Term> > rest;
1498
+ struct TermList {
1499
+ typedef std::pair<CobPtr, BigInt> Term;
1500
+ size_t n = 0;
1501
+ Term first;
1502
+ std::vector<Term, ArenaAllocator<Term> > rest;
1503
1503
 
1504
1504
  bool empty() const { return n == 0; }
1505
1505
  size_t size() const { return n; }
@@ -1617,24 +1617,24 @@ struct LCCC {
1617
1617
  return ret;
1618
1618
  }
1619
1619
 
1620
- LCCC multiplied(const BigInt& c) const {
1621
- LCCC r;
1622
- if (c.isZero()) return r;
1623
- r.terms.reserve(terms.size());
1624
- for (size_t i = 0; i < terms.size(); ++i) r.terms.push_back(std::make_pair(terms[i].first, coeffProduct(terms[i].second, c)));
1625
- r.alreadyReduced = alreadyReduced;
1626
- return r;
1627
- }
1628
-
1629
- void multiplyInPlace(const BigInt& c) {
1630
- if (c == BI_ONE) return;
1631
- alreadyReduced = false;
1632
- if (c == BI_MINUS_ONE) {
1633
- for (size_t i = 0; i < terms.size(); ++i) terms[i].second = -terms[i].second;
1634
- } else {
1635
- for (size_t i = 0; i < terms.size(); ++i) terms[i].second = coeffProduct(terms[i].second, c);
1636
- }
1637
- }
1620
+ LCCC multiplied(const BigInt& c) const {
1621
+ LCCC r;
1622
+ if (c.isZero()) return r;
1623
+ r.terms.reserve(terms.size());
1624
+ for (size_t i = 0; i < terms.size(); ++i) r.terms.push_back(std::make_pair(terms[i].first, coeffProduct(terms[i].second, c)));
1625
+ r.alreadyReduced = alreadyReduced;
1626
+ return r;
1627
+ }
1628
+
1629
+ void multiplyInPlace(const BigInt& c) {
1630
+ if (c == BI_ONE) return;
1631
+ alreadyReduced = false;
1632
+ if (c == BI_MINUS_ONE) {
1633
+ for (size_t i = 0; i < terms.size(); ++i) terms[i].second = -terms[i].second;
1634
+ } else {
1635
+ for (size_t i = 0; i < terms.size(); ++i) terms[i].second = coeffProduct(terms[i].second, c);
1636
+ }
1637
+ }
1638
1638
 
1639
1639
  LCCC composeVertical(const LCCC& other) const {
1640
1640
  KH_PROFILE(lcccComposeVertical);
@@ -1678,25 +1678,25 @@ struct LCCC {
1678
1678
  if (isZero()) return LCCC();
1679
1679
  if (alreadyReduced) return *this;
1680
1680
  LCCC ret;
1681
- for (size_t ti = 0; ti < terms.size(); ++ti) {
1682
- CobPtr cc = terms[ti].first;
1683
- BigInt num = terms[ti].second;
1684
- bool canonical = cc->hpower == 0 && cc->ncc == cc->nbc;
1685
- if (canonical) {
1686
- for (int i = 0; i < cc->nbc; ++i) {
1687
- if (cc->connectedComponent[i] != i || cc->genus[i] != 0 || cc->dots[i] > 1) {
1688
- canonical = false;
1689
- break;
1690
- }
1691
- }
1692
- if (canonical) {
1693
- if (ret.isZero()) ret.terms.push_back(std::make_pair(cc, num));
1694
- else ret.addTerm(cc, num);
1695
- continue;
1696
- }
1697
- }
1698
- cc->reverseMaps();
1699
- SmallVec dots(cc->nbc, 0);
1681
+ for (size_t ti = 0; ti < terms.size(); ++ti) {
1682
+ CobPtr cc = terms[ti].first;
1683
+ BigInt num = terms[ti].second;
1684
+ bool canonical = cc->hpower == 0 && cc->ncc == cc->nbc;
1685
+ if (canonical) {
1686
+ for (int i = 0; i < cc->nbc; ++i) {
1687
+ if (cc->connectedComponent[i] != i || cc->genus[i] != 0 || cc->dots[i] > 1) {
1688
+ canonical = false;
1689
+ break;
1690
+ }
1691
+ }
1692
+ if (canonical) {
1693
+ if (ret.isZero()) ret.terms.push_back(std::make_pair(cc, num));
1694
+ else ret.addTerm(cc, num);
1695
+ continue;
1696
+ }
1697
+ }
1698
+ cc->reverseMaps();
1699
+ SmallVec dots(cc->nbc, 0);
1700
1700
  SmallVec genus(cc->nbc, 0);
1701
1701
  WorkInts moreWork(cc->ncc);
1702
1702
  int moreWorkCount = 0;
@@ -1733,12 +1733,12 @@ struct LCCC {
1733
1733
  else ret.addTerm(cached, num);
1734
1734
  continue;
1735
1735
  }
1736
- std::vector<SmallVec, ArenaAllocator<SmallVec> > neckCutting;
1737
- neckCutting.push_back(dots);
1738
- for (int wi = 0; wi < moreWorkCount; ++wi) {
1739
- int concomp = moreWork[wi];
1740
- int nbc = cc->boundaryCount(concomp);
1741
- std::vector<SmallVec, ArenaAllocator<SmallVec> > next;
1736
+ std::vector<SmallVec, ArenaAllocator<SmallVec> > neckCutting;
1737
+ neckCutting.push_back(dots);
1738
+ for (int wi = 0; wi < moreWorkCount; ++wi) {
1739
+ int concomp = moreWork[wi];
1740
+ int nbc = cc->boundaryCount(concomp);
1741
+ std::vector<SmallVec, ArenaAllocator<SmallVec> > next;
1742
1742
  next.reserve(neckCutting.size() * static_cast<size_t>(nbc));
1743
1743
  for (size_t j = 0; j < neckCutting.size(); ++j) {
1744
1744
  SmallVec base = neckCutting[j];
@@ -1777,8 +1777,8 @@ struct SmoothingColumn {
1777
1777
  }
1778
1778
  };
1779
1779
 
1780
- typedef std::pair<int, LCCC> MatrixEntry;
1781
- typedef std::vector<MatrixEntry, ArenaAllocator<MatrixEntry> > MatrixRow;
1780
+ typedef std::pair<int, LCCC> MatrixEntry;
1781
+ typedef std::vector<MatrixEntry, ArenaAllocator<MatrixEntry> > MatrixRow;
1782
1782
 
1783
1783
  static MatrixRow::iterator findRowEntry(MatrixRow& row, int column) {
1784
1784
  return std::lower_bound(row.begin(), row.end(), column,
@@ -1976,88 +1976,88 @@ struct CobMatrix {
1976
1976
  return result;
1977
1977
  }
1978
1978
 
1979
- CobMatrix extractColumns(const std::vector<int>& columns) {
1980
- int oldSourceN = source.n;
1981
- std::vector<int> selectedIndex(oldSourceN, -1);
1982
- std::vector<char> selected(oldSourceN, 0);
1983
- for (size_t i = 0; i < columns.size(); ++i) {
1984
- selectedIndex[columns[i]] = static_cast<int>(i);
1985
- selected[columns[i]] = 1;
1986
- }
1987
- std::vector<int> removedBefore(oldSourceN + 1, 0);
1988
- for (int c = 0; c < oldSourceN; ++c) removedBefore[c + 1] = removedBefore[c] + (selected[c] ? 1 : 0);
1989
- SmoothingColumn sc(static_cast<int>(columns.size()));
1990
- for (size_t i = 0; i < columns.size(); ++i) {
1991
- sc.smoothings[i] = source.smoothings[columns[i]];
1992
- sc.numbers[i] = source.numbers[columns[i]];
1993
- }
1994
- std::vector<CapPtr> newSmoothings;
1995
- std::vector<int> newNumbers;
1996
- newSmoothings.reserve(oldSourceN - columns.size());
1997
- newNumbers.reserve(oldSourceN - columns.size());
1998
- for (int c = 0; c < oldSourceN; ++c) {
1999
- if (!selected[c]) {
2000
- newSmoothings.push_back(source.smoothings[c]);
2001
- newNumbers.push_back(source.numbers[c]);
2002
- }
2003
- }
2004
- source.smoothings.swap(newSmoothings);
2005
- source.numbers.swap(newNumbers);
2006
- source.n -= static_cast<int>(columns.size());
2007
- CobMatrix result(sc, target);
2008
- for (size_t r = 0; r < entries.size(); ++r) {
2009
- MatrixRow remaining;
2010
- remaining.reserve(entries[r].size());
2011
- for (size_t e = 0; e < entries[r].size(); ++e) {
2012
- int oldColumn = entries[r][e].first;
2013
- if (selected[oldColumn]) {
2014
- putRowEntry(result.entries[r], selectedIndex[oldColumn], entries[r][e].second);
2015
- } else {
2016
- entries[r][e].first = oldColumn - removedBefore[oldColumn];
2017
- remaining.push_back(std::move(entries[r][e]));
2018
- }
2019
- }
2020
- entries[r].swap(remaining);
2021
- }
2022
- return result;
2023
- }
2024
-
2025
- CobMatrix extractRows(const std::vector<int>& rows) {
2026
- int oldTargetN = target.n;
2027
- std::vector<int> selectedIndex(oldTargetN, -1);
2028
- std::vector<char> selected(oldTargetN, 0);
2029
- for (size_t i = 0; i < rows.size(); ++i) {
2030
- selectedIndex[rows[i]] = static_cast<int>(i);
2031
- selected[rows[i]] = 1;
2032
- }
2033
- SmoothingColumn sc(static_cast<int>(rows.size()));
2034
- for (size_t i = 0; i < rows.size(); ++i) {
2035
- sc.smoothings[i] = target.smoothings[rows[i]];
2036
- sc.numbers[i] = target.numbers[rows[i]];
2037
- }
2038
- std::vector<CapPtr> newSmoothings;
2039
- std::vector<int> newNumbers;
2040
- newSmoothings.reserve(oldTargetN - rows.size());
2041
- newNumbers.reserve(oldTargetN - rows.size());
2042
- for (int r = 0; r < oldTargetN; ++r) {
2043
- if (!selected[r]) {
2044
- newSmoothings.push_back(target.smoothings[r]);
2045
- newNumbers.push_back(target.numbers[r]);
2046
- }
2047
- }
2048
- target.smoothings.swap(newSmoothings);
2049
- target.numbers.swap(newNumbers);
2050
- target.n -= static_cast<int>(rows.size());
2051
- CobMatrix result(source, sc);
2052
- std::vector<MatrixRow> remaining;
2053
- remaining.reserve(oldTargetN - rows.size());
2054
- for (int r = 0; r < oldTargetN; ++r) {
2055
- if (selected[r]) result.entries[selectedIndex[r]] = std::move(entries[r]);
2056
- else remaining.push_back(std::move(entries[r]));
2057
- }
2058
- entries.swap(remaining);
2059
- return result;
2060
- }
1979
+ CobMatrix extractColumns(const std::vector<int>& columns) {
1980
+ int oldSourceN = source.n;
1981
+ std::vector<int> selectedIndex(oldSourceN, -1);
1982
+ std::vector<char> selected(oldSourceN, 0);
1983
+ for (size_t i = 0; i < columns.size(); ++i) {
1984
+ selectedIndex[columns[i]] = static_cast<int>(i);
1985
+ selected[columns[i]] = 1;
1986
+ }
1987
+ std::vector<int> removedBefore(oldSourceN + 1, 0);
1988
+ for (int c = 0; c < oldSourceN; ++c) removedBefore[c + 1] = removedBefore[c] + (selected[c] ? 1 : 0);
1989
+ SmoothingColumn sc(static_cast<int>(columns.size()));
1990
+ for (size_t i = 0; i < columns.size(); ++i) {
1991
+ sc.smoothings[i] = source.smoothings[columns[i]];
1992
+ sc.numbers[i] = source.numbers[columns[i]];
1993
+ }
1994
+ std::vector<CapPtr> newSmoothings;
1995
+ std::vector<int> newNumbers;
1996
+ newSmoothings.reserve(oldSourceN - columns.size());
1997
+ newNumbers.reserve(oldSourceN - columns.size());
1998
+ for (int c = 0; c < oldSourceN; ++c) {
1999
+ if (!selected[c]) {
2000
+ newSmoothings.push_back(source.smoothings[c]);
2001
+ newNumbers.push_back(source.numbers[c]);
2002
+ }
2003
+ }
2004
+ source.smoothings.swap(newSmoothings);
2005
+ source.numbers.swap(newNumbers);
2006
+ source.n -= static_cast<int>(columns.size());
2007
+ CobMatrix result(sc, target);
2008
+ for (size_t r = 0; r < entries.size(); ++r) {
2009
+ MatrixRow remaining;
2010
+ remaining.reserve(entries[r].size());
2011
+ for (size_t e = 0; e < entries[r].size(); ++e) {
2012
+ int oldColumn = entries[r][e].first;
2013
+ if (selected[oldColumn]) {
2014
+ putRowEntry(result.entries[r], selectedIndex[oldColumn], entries[r][e].second);
2015
+ } else {
2016
+ entries[r][e].first = oldColumn - removedBefore[oldColumn];
2017
+ remaining.push_back(std::move(entries[r][e]));
2018
+ }
2019
+ }
2020
+ entries[r].swap(remaining);
2021
+ }
2022
+ return result;
2023
+ }
2024
+
2025
+ CobMatrix extractRows(const std::vector<int>& rows) {
2026
+ int oldTargetN = target.n;
2027
+ std::vector<int> selectedIndex(oldTargetN, -1);
2028
+ std::vector<char> selected(oldTargetN, 0);
2029
+ for (size_t i = 0; i < rows.size(); ++i) {
2030
+ selectedIndex[rows[i]] = static_cast<int>(i);
2031
+ selected[rows[i]] = 1;
2032
+ }
2033
+ SmoothingColumn sc(static_cast<int>(rows.size()));
2034
+ for (size_t i = 0; i < rows.size(); ++i) {
2035
+ sc.smoothings[i] = target.smoothings[rows[i]];
2036
+ sc.numbers[i] = target.numbers[rows[i]];
2037
+ }
2038
+ std::vector<CapPtr> newSmoothings;
2039
+ std::vector<int> newNumbers;
2040
+ newSmoothings.reserve(oldTargetN - rows.size());
2041
+ newNumbers.reserve(oldTargetN - rows.size());
2042
+ for (int r = 0; r < oldTargetN; ++r) {
2043
+ if (!selected[r]) {
2044
+ newSmoothings.push_back(target.smoothings[r]);
2045
+ newNumbers.push_back(target.numbers[r]);
2046
+ }
2047
+ }
2048
+ target.smoothings.swap(newSmoothings);
2049
+ target.numbers.swap(newNumbers);
2050
+ target.n -= static_cast<int>(rows.size());
2051
+ CobMatrix result(source, sc);
2052
+ std::vector<MatrixRow> remaining;
2053
+ remaining.reserve(oldTargetN - rows.size());
2054
+ for (int r = 0; r < oldTargetN; ++r) {
2055
+ if (selected[r]) result.entries[selectedIndex[r]] = std::move(entries[r]);
2056
+ else remaining.push_back(std::move(entries[r]));
2057
+ }
2058
+ entries.swap(remaining);
2059
+ return result;
2060
+ }
2061
2061
  };
2062
2062
 
2063
2063
  struct IntMatrix {
@@ -2480,19 +2480,19 @@ bool Komplex::reductionLemma(int i) {
2480
2480
  }
2481
2481
 
2482
2482
  void Komplex::reductionLemma(int i, int j, int k, const BigInt& n) {
2483
- CapPtr isoSource = columns[i].smoothings[k];
2484
- CapPtr isoTarget = columns[i + 1].smoothings[j];
2485
- CobMatrix m = std::move(matrices[i]);
2486
- CobMatrix delta = m.extractRow(j);
2483
+ CapPtr isoSource = columns[i].smoothings[k];
2484
+ CapPtr isoTarget = columns[i + 1].smoothings[j];
2485
+ CobMatrix m = std::move(matrices[i]);
2486
+ CobMatrix delta = m.extractRow(j);
2487
2487
  delta.extractColumn(k);
2488
2488
  CobMatrix gamma = m.extractColumn(k);
2489
- BigInt coeff = (n == BI_ONE ? BI_MINUS_ONE : BI_ONE);
2490
- for (size_t row = 0; row < gamma.entries.size(); ++row) {
2491
- for (size_t e = 0; e < gamma.entries[row].size(); ++e) {
2492
- if (gamma.entries[row][e].first == 0) gamma.entries[row][e].second.multiplyInPlace(coeff);
2493
- }
2494
- }
2495
- CobMatrix gpd = gamma.compose(delta);
2489
+ BigInt coeff = (n == BI_ONE ? BI_MINUS_ONE : BI_ONE);
2490
+ for (size_t row = 0; row < gamma.entries.size(); ++row) {
2491
+ for (size_t e = 0; e < gamma.entries[row].size(); ++e) {
2492
+ if (gamma.entries[row][e].first == 0) gamma.entries[row][e].second.multiplyInPlace(coeff);
2493
+ }
2494
+ }
2495
+ CobMatrix gpd = gamma.compose(delta);
2496
2496
  gpd.add(m);
2497
2497
  matrices[i] = gpd;
2498
2498
  columns[i] = delta.source;
@@ -2505,30 +2505,30 @@ void Komplex::reductionLemma(int i, int j, int k, const BigInt& n) {
2505
2505
  }
2506
2506
  }
2507
2507
 
2508
- std::vector<Komplex::Isomorphism> Komplex::findBlock(const CobMatrix& m) const {
2509
- std::vector<Isomorphism> isos;
2510
- std::vector<char> disallowedColumns(m.source.n, 0);
2511
- std::vector<char> isomorphismColumns(m.source.n, 0);
2512
- std::vector<int> potentialDisallowedColumns;
2513
- for (int j = 0; j < static_cast<int>(m.entries.size()); ++j) {
2514
- bool foundIsomorphismOnRow = false;
2515
- bool rowForbidden = false;
2516
- bool hasRowCandidate = false;
2517
- Isomorphism rowCandidate;
2518
- potentialDisallowedColumns.clear();
2519
- potentialDisallowedColumns.reserve(m.entries[j].size());
2520
- for (const auto& kv : m.entries[j]) {
2521
- int k = kv.first;
2522
- const LCCC& lc = kv.second;
2523
- if (isomorphismColumns[k]) rowForbidden = true;
2524
- if (disallowedColumns[k]) continue;
2525
- if (foundIsomorphismOnRow) {
2526
- disallowedColumns[k] = 1;
2527
- } else {
2528
- potentialDisallowedColumns.push_back(k);
2529
- if (!hasRowCandidate && lc.numberOfTerms() == 1) {
2530
- CobPtr cc = lc.firstTerm();
2531
- BigInt n = lc.firstCoefficient();
2508
+ std::vector<Komplex::Isomorphism> Komplex::findBlock(const CobMatrix& m) const {
2509
+ std::vector<Isomorphism> isos;
2510
+ std::vector<char> disallowedColumns(m.source.n, 0);
2511
+ std::vector<char> isomorphismColumns(m.source.n, 0);
2512
+ std::vector<int> potentialDisallowedColumns;
2513
+ for (int j = 0; j < static_cast<int>(m.entries.size()); ++j) {
2514
+ bool foundIsomorphismOnRow = false;
2515
+ bool rowForbidden = false;
2516
+ bool hasRowCandidate = false;
2517
+ Isomorphism rowCandidate;
2518
+ potentialDisallowedColumns.clear();
2519
+ potentialDisallowedColumns.reserve(m.entries[j].size());
2520
+ for (const auto& kv : m.entries[j]) {
2521
+ int k = kv.first;
2522
+ const LCCC& lc = kv.second;
2523
+ if (isomorphismColumns[k]) rowForbidden = true;
2524
+ if (disallowedColumns[k]) continue;
2525
+ if (foundIsomorphismOnRow) {
2526
+ disallowedColumns[k] = 1;
2527
+ } else {
2528
+ potentialDisallowedColumns.push_back(k);
2529
+ if (!hasRowCandidate && lc.numberOfTerms() == 1) {
2530
+ CobPtr cc = lc.firstTerm();
2531
+ BigInt n = lc.firstCoefficient();
2532
2532
  if (!(n == BI_ONE || n == BI_MINUS_ONE)) continue;
2533
2533
  if (!cc->isIsomorphism()) continue;
2534
2534
  rowCandidate = Isomorphism(j, k, n);
@@ -2536,17 +2536,17 @@ std::vector<Komplex::Isomorphism> Komplex::findBlock(const CobMatrix& m) const {
2536
2536
  foundIsomorphismOnRow = true;
2537
2537
  }
2538
2538
  }
2539
- }
2540
- if (!rowForbidden && hasRowCandidate) {
2541
- isos.push_back(rowCandidate);
2542
- isomorphismColumns[rowCandidate.column] = 1;
2543
- for (size_t p = 0; p < potentialDisallowedColumns.size(); ++p) {
2544
- disallowedColumns[potentialDisallowedColumns[p]] = 1;
2545
- }
2546
- }
2547
- }
2548
- return isos;
2549
- }
2539
+ }
2540
+ if (!rowForbidden && hasRowCandidate) {
2541
+ isos.push_back(rowCandidate);
2542
+ isomorphismColumns[rowCandidate.column] = 1;
2543
+ for (size_t p = 0; p < potentialDisallowedColumns.size(); ++p) {
2544
+ disallowedColumns[potentialDisallowedColumns[p]] = 1;
2545
+ }
2546
+ }
2547
+ }
2548
+ return isos;
2549
+ }
2550
2550
 
2551
2551
  void Komplex::blockReductionLemma(int i, const std::vector<Isomorphism>& block) {
2552
2552
  KH_PROFILE(blockReductionApply);
@@ -2561,20 +2561,20 @@ void Komplex::blockReductionLemma(int i, const std::vector<Isomorphism>& block)
2561
2561
  cols.push_back(iso.column);
2562
2562
  coeffs.push_back(iso.coefficient);
2563
2563
  }
2564
- CobMatrix m = std::move(matrices[i]);
2564
+ CobMatrix m = std::move(matrices[i]);
2565
2565
  CobMatrix delta = m.extractRows(rows);
2566
2566
  delta.extractColumns(cols);
2567
2567
  CobMatrix gamma = m.extractColumns(cols);
2568
- for (size_t k = 0; k < block.size(); ++k) {
2569
- BigInt coeff = (coeffs[k] == BI_ONE ? BI_MINUS_ONE : BI_ONE);
2570
- for (size_t row = 0; row < gamma.entries.size(); ++row) {
2571
- MatrixRow::iterator it = findRowEntry(gamma.entries[row], static_cast<int>(k));
2572
- if (it != gamma.entries[row].end() && it->first == static_cast<int>(k)) {
2573
- it->second.multiplyInPlace(coeff);
2574
- }
2575
- }
2576
- }
2577
- CobMatrix gpd = gamma.compose(delta);
2568
+ for (size_t k = 0; k < block.size(); ++k) {
2569
+ BigInt coeff = (coeffs[k] == BI_ONE ? BI_MINUS_ONE : BI_ONE);
2570
+ for (size_t row = 0; row < gamma.entries.size(); ++row) {
2571
+ MatrixRow::iterator it = findRowEntry(gamma.entries[row], static_cast<int>(k));
2572
+ if (it != gamma.entries[row].end() && it->first == static_cast<int>(k)) {
2573
+ it->second.multiplyInPlace(coeff);
2574
+ }
2575
+ }
2576
+ }
2577
+ CobMatrix gpd = gamma.compose(delta);
2578
2578
  gpd.add(m);
2579
2579
  matrices[i] = gpd;
2580
2580
  columns[i] = delta.source;
@@ -2759,12 +2759,12 @@ std::string Komplex::KhForZ() {
2759
2759
  return ret;
2760
2760
  }
2761
2761
 
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());
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());
2768
2768
  int best = -1, nconbest = -1;
2769
2769
  std::vector<int> rbest(depth, 0);
2770
2770
  for (size_t i = 0; i < pd.size(); ++i) if (!done[i]) {
@@ -2823,91 +2823,91 @@ static int takeNextCrossing(const std::vector<int>&, const std::vector<std::vect
2823
2823
  throw std::runtime_error("no crossing left");
2824
2824
  }
2825
2825
 
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
- }
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
+ }
2911
2911
 
2912
2912
  static bool sanityPD(const PDCode& pd) {
2913
2913
  std::map<int, int> counts;
@@ -2990,17 +2990,17 @@ static PDCode renumberR1Order(PDCode pd) {
2990
2990
  return pd;
2991
2991
  }
2992
2992
 
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;
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;
2997
2997
  while (hasR1) {
2998
2998
  hasR1 = false;
2999
2999
  for (size_t i = 0; i < pd.size(); ++i) {
3000
3000
  if (uniqueCount(pd[i]) <= 3) {
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));
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));
3004
3004
  std::vector<int> singles;
3005
3005
  for (int v : crossing) {
3006
3006
  if (std::count(crossing.begin(), crossing.end(), v) == 1) singles.push_back(v);
@@ -3155,9 +3155,9 @@ static PDCode renumberFullDfs(PDCode pd) {
3155
3155
  return pd;
3156
3156
  }
3157
3157
 
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");
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");
3161
3161
  int ax = pd[index][0];
3162
3162
  int bx = pd[index][1];
3163
3163
  int cx = pd[index][2];
@@ -3179,28 +3179,28 @@ static PDCode eraseOneNugatory(PDCode pd, size_t index, std::vector<int>* signs)
3179
3179
  if (!loopSet.count(ax) || !loopSet.count(bx) || !loopSet.count(cx) || !loopSet.count(dx)) {
3180
3180
  throw std::runtime_error("nugatory crossing arcs are not in one component");
3181
3181
  }
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));
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));
3185
3185
  bad = replaceArcValue(bad, ax, cx);
3186
3186
  bad = replaceArcValue(bad, dx, bx);
3187
3187
  return renumberFullDfs(bad);
3188
3188
  }
3189
3189
 
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
- }
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
+ }
3204
3204
 
3205
3205
  static int komplexSize(const Komplex& k) {
3206
3206
  int total = 0;
@@ -3208,8 +3208,8 @@ static int komplexSize(const Komplex& k) {
3208
3208
  return total;
3209
3209
  }
3210
3210
 
3211
- static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std::vector<int>& xsigns) {
3212
- KH_PROFILE(generateFast);
3211
+ static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std::vector<int>& xsigns) {
3212
+ KH_PROFILE(generateFast);
3213
3213
  if (pd.empty()) {
3214
3214
  Komplex kom(1);
3215
3215
  kom.columns[0] = SmoothingColumn(1);
@@ -3217,13 +3217,13 @@ static Komplex generateFast(const std::vector<std::vector<int> >& pd, const std:
3217
3217
  kom.reduce();
3218
3218
  return kom;
3219
3219
  }
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;
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;
3227
3227
  int firstdepth = pd.size() > 4 ? 3 : static_cast<int>(pd.size()) - 1;
3228
3228
  std::vector<int> firstdummy(firstdepth + 1, 0);
3229
3229
  int first = g_options.reorderCrossings ? chooseXingRecursive(edges, pd, in, done, firstdepth, firstdummy)
@@ -3403,17 +3403,83 @@ static std::vector<std::string> listInputFiles(const std::string& dir) {
3403
3403
  return files;
3404
3404
  }
3405
3405
 
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);
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);
3412
3412
  ProfileScope khScope(g_profile.kh);
3413
3413
  return k.KhForZ();
3414
3414
  }
3415
3415
 
3416
- static std::string formatPDCode(const PDCode& pd) {
3416
+ static std::string computePDWithSigns(const std::vector<std::vector<int> >& pd,
3417
+ const std::vector<int>& xsigns) {
3418
+ if (xsigns.size() != pd.size()) throw std::runtime_error("sign count does not match PD crossing count");
3419
+ for (int sign : xsigns) {
3420
+ if (sign != 1 && sign != -1) throw std::runtime_error("crossing signs must be +1 or -1");
3421
+ }
3422
+ flushCobCache();
3423
+ g_smallArena.reset();
3424
+ Komplex k = generateFast(pd, xsigns);
3425
+ ProfileScope khScope(g_profile.kh);
3426
+ return k.KhForZ();
3427
+ }
3428
+
3429
+ static std::string trimAscii(const std::string& s) {
3430
+ size_t first = 0;
3431
+ while (first < s.size() && std::isspace(static_cast<unsigned char>(s[first]))) ++first;
3432
+ size_t last = s.size();
3433
+ while (last > first && std::isspace(static_cast<unsigned char>(s[last - 1]))) --last;
3434
+ return s.substr(first, last - first);
3435
+ }
3436
+
3437
+ static std::vector<int> parseSignsLine(const std::string& line) {
3438
+ std::vector<int> signs;
3439
+ size_t pos = 0;
3440
+ while (pos < line.size()) {
3441
+ char ch = line[pos];
3442
+ if (std::isspace(static_cast<unsigned char>(ch)) || ch == ',' ||
3443
+ ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == ';') {
3444
+ ++pos;
3445
+ continue;
3446
+ }
3447
+ int sign = 1;
3448
+ bool sawExplicitSign = false;
3449
+ if (ch == '+' || ch == '-') {
3450
+ sign = (ch == '-') ? -1 : 1;
3451
+ sawExplicitSign = true;
3452
+ ++pos;
3453
+ }
3454
+ int value = 0;
3455
+ bool sawDigit = false;
3456
+ while (pos < line.size() && std::isdigit(static_cast<unsigned char>(line[pos]))) {
3457
+ sawDigit = true;
3458
+ value = value * 10 + (line[pos++] - '0');
3459
+ }
3460
+ if (!sawExplicitSign && !sawDigit) throw std::runtime_error("invalid crossing sign token");
3461
+ if (sawDigit && value != 1) throw std::runtime_error("crossing signs must be +1 or -1");
3462
+ signs.push_back(sign);
3463
+ }
3464
+ return signs;
3465
+ }
3466
+
3467
+ static std::vector<std::vector<int> > parseSignsDocument(const std::string& text) {
3468
+ std::vector<std::vector<int> > result;
3469
+ if (trimAscii(text).empty()) {
3470
+ result.push_back(std::vector<int>());
3471
+ return result;
3472
+ }
3473
+ std::istringstream lines(text);
3474
+ std::string line;
3475
+ while (std::getline(lines, line)) {
3476
+ if (!trimAscii(line).empty()) result.push_back(parseSignsLine(line));
3477
+ }
3478
+ if (result.empty()) result.push_back(std::vector<int>());
3479
+ return result;
3480
+ }
3481
+
3482
+ static std::string formatPDCode(const PDCode& pd) {
3417
3483
  std::ostringstream out;
3418
3484
  out << "PD[";
3419
3485
  for (size_t i = 0; i < pd.size(); ++i) {
@@ -3426,164 +3492,198 @@ static std::string formatPDCode(const PDCode& pd) {
3426
3492
  out << "]";
3427
3493
  }
3428
3494
  out << "]";
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
- }
3449
-
3450
- } // namespace kh
3451
-
3452
- #if defined(CPPKH_SHARED_LIBRARY)
3453
- #if defined(_WIN32)
3454
- #define CPPKH_API extern "C" __declspec(dllexport)
3455
- #else
3456
- #define CPPKH_API extern "C" __attribute__((visibility("default")))
3457
- #endif
3458
-
3459
- static thread_local std::string g_cppkhLastError;
3460
-
3461
- static char* cppkhDuplicateString(const std::string& value) {
3462
- char* out = static_cast<char*>(std::malloc(value.size() + 1));
3463
- if (!out) {
3464
- g_cppkhLastError = "out of memory";
3465
- return nullptr;
3466
- }
3467
- std::memcpy(out, value.c_str(), value.size() + 1);
3468
- return out;
3469
- }
3470
-
3471
- struct CppkhOptionsGuard {
3472
- kh::Options oldOptions;
3473
- CppkhOptionsGuard() : oldOptions(kh::g_options) {}
3474
- ~CppkhOptionsGuard() { kh::g_options = oldOptions; }
3475
- };
3476
-
3477
- CPPKH_API const char* cppkh_version() {
3478
- return "cppkh/1";
3479
- }
3480
-
3481
- CPPKH_API const char* cppkh_last_error() {
3482
- return g_cppkhLastError.c_str();
3483
- }
3484
-
3485
- CPPKH_API void cppkh_free(char* value) {
3486
- std::free(value);
3487
- }
3488
-
3489
- CPPKH_API char* cppkh_compute_pd_ex(const char* pd_code, int simplify_pd, int reorder_crossings) {
3490
- try {
3491
- g_cppkhLastError.clear();
3492
- if (!pd_code) throw std::runtime_error("pd_code is null");
3493
- std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_code, "ctypes");
3494
- if (parsed.empty()) throw std::runtime_error("no PD code found");
3495
- if (parsed.size() != 1) throw std::runtime_error("cppkh_compute_pd_ex expects exactly one PD code");
3496
-
3497
- CppkhOptionsGuard guard;
3498
- kh::g_options.progress = false;
3499
- kh::g_options.profile = false;
3500
- kh::g_options.simplifyR1 = simplify_pd != 0;
3501
- kh::g_options.simplifyNugatory = simplify_pd != 0;
3502
- kh::g_options.reorderCrossings = reorder_crossings != 0;
3503
- std::string result = kh::computePD(parsed[0].second);
3504
- return cppkhDuplicateString(result);
3505
- } catch (const std::exception& e) {
3506
- g_cppkhLastError = e.what();
3507
- return nullptr;
3508
- } catch (...) {
3509
- g_cppkhLastError = "unknown error";
3510
- return nullptr;
3511
- }
3512
- }
3513
-
3514
- CPPKH_API char* cppkh_compute_pd(const char* pd_code) {
3515
- return cppkh_compute_pd_ex(pd_code, 1, 1);
3516
- }
3517
-
3518
- CPPKH_API char* cppkh_compute_pd_batch_ex(const char* pd_codes, int simplify_pd, int reorder_crossings) {
3519
- try {
3520
- g_cppkhLastError.clear();
3521
- if (!pd_codes) throw std::runtime_error("pd_codes is null");
3522
- std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_codes, "ctypes-batch");
3523
- if (parsed.empty()) throw std::runtime_error("no PD code found");
3524
-
3525
- CppkhOptionsGuard guard;
3526
- kh::g_options.progress = false;
3527
- kh::g_options.profile = false;
3528
- kh::g_options.simplifyR1 = simplify_pd != 0;
3529
- kh::g_options.simplifyNugatory = simplify_pd != 0;
3530
- kh::g_options.reorderCrossings = reorder_crossings != 0;
3531
-
3532
- std::ostringstream out;
3533
- for (size_t i = 0; i < parsed.size(); ++i) {
3534
- if (i) out << "\n";
3535
- out << kh::computePD(parsed[i].second);
3536
- }
3537
- return cppkhDuplicateString(out.str());
3538
- } catch (const std::exception& e) {
3539
- g_cppkhLastError = e.what();
3540
- return nullptr;
3541
- } catch (...) {
3542
- g_cppkhLastError = "unknown error";
3543
- return nullptr;
3544
- }
3545
- }
3546
-
3547
- CPPKH_API char* cppkh_compute_pd_batch(const char* pd_codes) {
3548
- return cppkh_compute_pd_batch_ex(pd_codes, 1, 1);
3549
- }
3550
-
3551
- CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
3552
- try {
3553
- g_cppkhLastError.clear();
3554
- if (!pd_code) throw std::runtime_error("pd_code is null");
3555
- std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_code, "ctypes");
3556
- if (parsed.empty()) throw std::runtime_error("no PD code found");
3557
- if (parsed.size() != 1) throw std::runtime_error("cppkh_simplify_pd expects exactly one PD code");
3558
- CppkhOptionsGuard guard;
3559
- kh::g_options.simplifyR1 = true;
3560
- kh::g_options.simplifyNugatory = true;
3561
- std::string result = kh::simplifiedPDString(parsed[0].second);
3562
- return cppkhDuplicateString(result);
3563
- } catch (const std::exception& e) {
3564
- g_cppkhLastError = e.what();
3565
- return nullptr;
3566
- } catch (...) {
3567
- g_cppkhLastError = "unknown error";
3568
- return nullptr;
3569
- }
3570
- }
3571
- #endif
3572
-
3573
- #ifndef CPPKH_SHARED_LIBRARY
3574
- static void 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";
3576
- std::cout << "Thread backend: " << KH_THREAD_BACKEND_NAME << "\n";
3577
- std::cout << "Detected CPU threads: " << kh::detectHardwareThreads() << "\n";
3578
- std::cout << "PD simplification: R1 removal then nugatory crossing removal is enabled by default.\n";
3579
- }
3580
-
3581
- int main(int argc, char** argv) {
3495
+ return out.str();
3496
+ }
3497
+
3498
+ static std::string formatCrossingSigns(const PDCode& pd) {
3499
+ std::vector<int> signs = getSigns(pd);
3500
+ std::ostringstream out;
3501
+ out << "[";
3502
+ for (size_t i = 0; i < signs.size(); ++i) {
3503
+ if (i) out << ",";
3504
+ out << signs[i];
3505
+ }
3506
+ out << "]";
3507
+ return out.str();
3508
+ }
3509
+
3510
+ static std::string simplifiedPDString(const PDCode& pd) {
3511
+ getSigns(pd);
3512
+ PDCode working = simplifyPDCode(pd, nullptr, g_options.simplifyR1, g_options.simplifyNugatory);
3513
+ return formatPDCode(working);
3514
+ }
3515
+
3516
+ } // namespace kh
3517
+
3518
+ #if defined(CPPKH_SHARED_LIBRARY)
3519
+ #if defined(_WIN32)
3520
+ #define CPPKH_API extern "C" __declspec(dllexport)
3521
+ #else
3522
+ #define CPPKH_API extern "C" __attribute__((visibility("default")))
3523
+ #endif
3524
+
3525
+ static thread_local std::string g_cppkhLastError;
3526
+
3527
+ static char* cppkhDuplicateString(const std::string& value) {
3528
+ char* out = static_cast<char*>(std::malloc(value.size() + 1));
3529
+ if (!out) {
3530
+ g_cppkhLastError = "out of memory";
3531
+ return nullptr;
3532
+ }
3533
+ std::memcpy(out, value.c_str(), value.size() + 1);
3534
+ return out;
3535
+ }
3536
+
3537
+ struct CppkhOptionsGuard {
3538
+ kh::Options oldOptions;
3539
+ CppkhOptionsGuard() : oldOptions(kh::g_options) {}
3540
+ ~CppkhOptionsGuard() { kh::g_options = oldOptions; }
3541
+ };
3542
+
3543
+ CPPKH_API const char* cppkh_version() {
3544
+ return "cppkh/1";
3545
+ }
3546
+
3547
+ CPPKH_API const char* cppkh_last_error() {
3548
+ return g_cppkhLastError.c_str();
3549
+ }
3550
+
3551
+ CPPKH_API void cppkh_free(char* value) {
3552
+ std::free(value);
3553
+ }
3554
+
3555
+ CPPKH_API char* cppkh_compute_pd_ex(const char* pd_code, int simplify_pd, int reorder_crossings) {
3556
+ try {
3557
+ g_cppkhLastError.clear();
3558
+ if (!pd_code) throw std::runtime_error("pd_code is null");
3559
+ std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_code, "ctypes");
3560
+ if (parsed.empty()) throw std::runtime_error("no PD code found");
3561
+ if (parsed.size() != 1) throw std::runtime_error("cppkh_compute_pd_ex expects exactly one PD code");
3562
+
3563
+ CppkhOptionsGuard guard;
3564
+ kh::g_options.progress = false;
3565
+ kh::g_options.profile = false;
3566
+ kh::g_options.simplifyR1 = simplify_pd != 0;
3567
+ kh::g_options.simplifyNugatory = simplify_pd != 0;
3568
+ kh::g_options.reorderCrossings = reorder_crossings != 0;
3569
+ std::string result = kh::computePD(parsed[0].second);
3570
+ return cppkhDuplicateString(result);
3571
+ } catch (const std::exception& e) {
3572
+ g_cppkhLastError = e.what();
3573
+ return nullptr;
3574
+ } catch (...) {
3575
+ g_cppkhLastError = "unknown error";
3576
+ return nullptr;
3577
+ }
3578
+ }
3579
+
3580
+ CPPKH_API char* cppkh_compute_pd(const char* pd_code) {
3581
+ return cppkh_compute_pd_ex(pd_code, 1, 1);
3582
+ }
3583
+
3584
+ CPPKH_API char* cppkh_compute_pd_batch_ex(const char* pd_codes, int simplify_pd, int reorder_crossings) {
3585
+ try {
3586
+ g_cppkhLastError.clear();
3587
+ if (!pd_codes) throw std::runtime_error("pd_codes is null");
3588
+ std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_codes, "ctypes-batch");
3589
+ if (parsed.empty()) throw std::runtime_error("no PD code found");
3590
+
3591
+ CppkhOptionsGuard guard;
3592
+ kh::g_options.progress = false;
3593
+ kh::g_options.profile = false;
3594
+ kh::g_options.simplifyR1 = simplify_pd != 0;
3595
+ kh::g_options.simplifyNugatory = simplify_pd != 0;
3596
+ kh::g_options.reorderCrossings = reorder_crossings != 0;
3597
+
3598
+ std::ostringstream out;
3599
+ for (size_t i = 0; i < parsed.size(); ++i) {
3600
+ if (i) out << "\n";
3601
+ out << kh::computePD(parsed[i].second);
3602
+ }
3603
+ return cppkhDuplicateString(out.str());
3604
+ } catch (const std::exception& e) {
3605
+ g_cppkhLastError = e.what();
3606
+ return nullptr;
3607
+ } catch (...) {
3608
+ g_cppkhLastError = "unknown error";
3609
+ return nullptr;
3610
+ }
3611
+ }
3612
+
3613
+ CPPKH_API char* cppkh_compute_pd_batch(const char* pd_codes) {
3614
+ return cppkh_compute_pd_batch_ex(pd_codes, 1, 1);
3615
+ }
3616
+
3617
+ CPPKH_API char* cppkh_compute_pd_signed_variants_ex(const char* pd_code,
3618
+ const char* signs_text,
3619
+ int reorder_crossings) {
3582
3620
  try {
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;
3621
+ g_cppkhLastError.clear();
3622
+ if (!pd_code) throw std::runtime_error("pd_code is null");
3623
+ if (!signs_text) throw std::runtime_error("signs_text is null");
3624
+ std::vector<std::pair<std::string, kh::PDCode> > parsed =
3625
+ kh::parsePDDocument(pd_code, "ctypes-signed");
3626
+ if (parsed.size() != 1) {
3627
+ throw std::runtime_error("signed variants expect exactly one PD code");
3628
+ }
3629
+ std::vector<std::vector<int> > signs = kh::parseSignsDocument(signs_text);
3630
+ CppkhOptionsGuard guard;
3631
+ kh::g_options.progress = false;
3632
+ kh::g_options.profile = false;
3633
+ kh::g_options.simplifyR1 = false;
3634
+ kh::g_options.simplifyNugatory = false;
3635
+ kh::g_options.reorderCrossings = reorder_crossings != 0;
3636
+ std::ostringstream out;
3637
+ for (size_t i = 0; i < signs.size(); ++i) {
3638
+ if (i) out << "\n";
3639
+ out << kh::computePDWithSigns(parsed[0].second, signs[i]);
3640
+ }
3641
+ return cppkhDuplicateString(out.str());
3642
+ } catch (const std::exception& e) {
3643
+ g_cppkhLastError = e.what();
3644
+ return nullptr;
3645
+ } catch (...) {
3646
+ g_cppkhLastError = "unknown error";
3647
+ return nullptr;
3648
+ }
3649
+ }
3650
+
3651
+ CPPKH_API char* cppkh_simplify_pd(const char* pd_code) {
3652
+ try {
3653
+ g_cppkhLastError.clear();
3654
+ if (!pd_code) throw std::runtime_error("pd_code is null");
3655
+ std::vector<std::pair<std::string, kh::PDCode> > parsed = kh::parsePDDocument(pd_code, "ctypes");
3656
+ if (parsed.empty()) throw std::runtime_error("no PD code found");
3657
+ if (parsed.size() != 1) throw std::runtime_error("cppkh_simplify_pd expects exactly one PD code");
3658
+ CppkhOptionsGuard guard;
3659
+ kh::g_options.simplifyR1 = true;
3660
+ kh::g_options.simplifyNugatory = true;
3661
+ std::string result = kh::simplifiedPDString(parsed[0].second);
3662
+ return cppkhDuplicateString(result);
3663
+ } catch (const std::exception& e) {
3664
+ g_cppkhLastError = e.what();
3665
+ return nullptr;
3666
+ } catch (...) {
3667
+ g_cppkhLastError = "unknown error";
3668
+ return nullptr;
3669
+ }
3670
+ }
3671
+ #endif
3672
+
3673
+ #ifndef CPPKH_SHARED_LIBRARY
3674
+ static void usage() {
3675
+ 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";
3676
+ std::cout << "Thread backend: " << KH_THREAD_BACKEND_NAME << "\n";
3677
+ std::cout << "Detected CPU threads: " << kh::detectHardwareThreads() << "\n";
3678
+ std::cout << "PD simplification: R1 removal then nugatory crossing removal is enabled by default.\n";
3679
+ }
3680
+
3681
+ int main(int argc, char** argv) {
3682
+ try {
3683
+ std::vector<std::string> files;
3684
+ std::vector<std::pair<std::string, std::vector<std::vector<int> > > > jobs;
3685
+ bool printSimplifiedPD = false;
3686
+ bool printCrossingSigns = false;
3587
3687
  for (int i = 1; i < argc; ++i) {
3588
3688
  std::string a = argv[i];
3589
3689
  if (a == "--help" || a == "-h") { usage(); return 0; }
@@ -3601,18 +3701,18 @@ int main(int argc, char** argv) {
3601
3701
  } else if (a == "--ordered" || a == "-O") kh::g_options.reorderCrossings = false;
3602
3702
  else if (a == "--quiet" || a == "-q") kh::g_options.progress = false;
3603
3703
  else if (a == "--profile") kh::g_options.profile = 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;
3704
+ else if (a == "--no-simplify-pd" || a == "--raw-pd") {
3705
+ kh::g_options.simplifyR1 = false;
3706
+ kh::g_options.simplifyNugatory = false;
3707
+ } else if (a == "--simplify-pd") {
3708
+ kh::g_options.simplifyR1 = true;
3709
+ kh::g_options.simplifyNugatory = true;
3710
+ } else if (a == "--no-simplify-r1") kh::g_options.simplifyR1 = false;
3711
+ else if (a == "--simplify-r1") kh::g_options.simplifyR1 = true;
3712
+ else if (a == "--no-simplify-nugatory") kh::g_options.simplifyNugatory = false;
3713
+ else if (a == "--simplify-nugatory") kh::g_options.simplifyNugatory = true;
3714
+ else if (a == "--print-simplified-pd") printSimplifiedPD = true;
3715
+ else if (a == "--print-crossing-signs") printCrossingSigns = true;
3616
3716
  else if (a == "--threads" || a == "-j") {
3617
3717
  if (++i >= argc) throw std::runtime_error("--threads needs a number");
3618
3718
  std::string v = argv[i];
@@ -3639,10 +3739,10 @@ int main(int argc, char** argv) {
3639
3739
  kh::g_profile.reset();
3640
3740
  profileStart = kh::profileNowNs();
3641
3741
  }
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";
3742
+ if (label) std::cout << jobs[i].first << "\t";
3743
+ if (printSimplifiedPD) std::cout << kh::simplifiedPDString(jobs[i].second) << "\n";
3744
+ else if (printCrossingSigns) std::cout << kh::formatCrossingSigns(jobs[i].second) << "\n";
3745
+ else std::cout << "\"" << kh::computePD(jobs[i].second) << "\"\n";
3646
3746
  if (kh::g_options.profile) {
3647
3747
  kh::g_profile.totalNs = kh::profileNowNs() - profileStart;
3648
3748
  kh::printProfile();
@@ -3651,7 +3751,7 @@ int main(int argc, char** argv) {
3651
3751
  return 0;
3652
3752
  } catch (const std::exception& e) {
3653
3753
  std::cerr << "error: " << e.what() << "\n";
3654
- return 1;
3655
- }
3656
- }
3657
- #endif
3754
+ return 1;
3755
+ }
3756
+ }
3757
+ #endif