Skip to content

Commit b69e743

Browse files
Merge pull request #7 from vm6502q/row-solver
Row solver
2 parents a175744 + e1978b0 commit b69e743

File tree

3 files changed

+120
-106
lines changed

3 files changed

+120
-106
lines changed

FindAFactor/_find_a_factor.cpp

Lines changed: 118 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -720,28 +720,6 @@ size_t GetGearIncrement(std::vector<boost::dynamic_bitset<size_t>> *inc_seqs) {
720720
return wheelIncrement;
721721
}
722722

723-
////////////////////////////////////////////////////////////////////////////////////////////////////////////
724-
// WRITTEN WITH HELP FROM ELARA (GPT) BELOW //
725-
////////////////////////////////////////////////////////////////////////////////////////////////////////////
726-
727-
// Utility to perform modular exponentiation
728-
inline BigInteger modExp(BigInteger base, BigInteger exp, const BigInteger &mod) {
729-
BigInteger result = 1U;
730-
while (exp) {
731-
if (exp & 1U) {
732-
result = (result * base) % mod;
733-
}
734-
base = (base * base) % mod;
735-
exp >>= 1U;
736-
}
737-
738-
return result;
739-
}
740-
741-
////////////////////////////////////////////////////////////////////////////////////////////////////////////
742-
// WRITTEN WITH HELP FROM ELARA (GPT) ABOVE //
743-
////////////////////////////////////////////////////////////////////////////////////////////////////////////
744-
745723
struct Factorizer {
746724
std::mutex batchMutex;
747725
BigInteger toFactorSqr;
@@ -822,15 +800,15 @@ struct Factorizer {
822800
// Make the candidate NOT a multiple on the wheels.
823801
BigInteger candidate = forwardFn(backwardFn(y * y - toFactor));
824802
// This actually just goes ahead and FORCES
825-
// the number into a "close-by" smooth perfect square.
803+
// the number into a "close-by" smooth number.
826804
candidate = makeSmooth(candidate);
827805
// We want two numbers multiplied together to be larger than toFactor.
828806
if (candidate < toFactorSqrt) {
829807
continue;
830808
}
831809
// The residue (mod N) also needs to be smooth (but not a perfect square).
832810
const boost::dynamic_bitset<size_t> rfv = factorizationParityVector(candidate % toFactor);
833-
if (!(rfv.size())) {
811+
if (rfv.empty()) {
834812
// The number is useless to us.
835813
continue;
836814
}
@@ -857,18 +835,91 @@ struct Factorizer {
857835
}
858836
}
859837

838+
struct GaussianEliminationResult {
839+
std::vector<bool> marks;
840+
std::vector<std::pair<boost::dynamic_bitset<size_t>, size_t>> solutionColumns;
841+
842+
GaussianEliminationResult(const size_t& sz)
843+
: marks(sz, false)
844+
{
845+
// Intentionally left blank
846+
}
847+
};
848+
849+
// Special thanks to https://github.com/NachiketUN/Quadratic-Sieve-Algorithm
850+
std::vector<size_t> findDependentRows(const GaussianEliminationResult& ger, const size_t& solutionColumnId)
851+
{
852+
std::vector<size_t> solutionVec;
853+
std::vector<size_t> indices;
854+
855+
// Get the first free row from Gaussian elimination results
856+
const boost::dynamic_bitset<size_t>& freeRow = ger.solutionColumns[solutionColumnId].first;
857+
858+
// Find the indices where free_row has true values.
859+
for (size_t i = 0; i < freeRow.size(); i++) {
860+
if (freeRow[i]) {
861+
indices.push_back(i);
862+
}
863+
}
864+
865+
// Find dependent rows from the original matrix
866+
for (size_t r = 0; r < primes.size(); r++) {
867+
for (size_t i : indices) {
868+
if (smoothNumberValues[i][r] && ger.marks[r]) {
869+
solutionVec.push_back(r);
870+
break;
871+
}
872+
}
873+
}
874+
875+
// Add the chosen row from Gaussian elimination solution
876+
solutionVec.push_back(ger.solutionColumns[solutionColumnId].second);
877+
878+
return solutionVec;
879+
}
880+
881+
BigInteger solveCongruence(const std::vector<size_t>& solutionVec)
882+
{
883+
// Compute x and y
884+
BigInteger x = 1;
885+
for (size_t idx : solutionVec) {
886+
x *= smoothNumberKeys[idx];
887+
}
888+
// If we square the result, it shouldn't ruin the fact
889+
// that the residue is a perfect square.
890+
const BigInteger y = sqrt((x * x) % this->toFactor);
891+
892+
// Check congruence of squares
893+
BigInteger factor = gcd(this->toFactor, x + y);
894+
if ((factor != 1U) && (factor != this->toFactor)) {
895+
return factor;
896+
}
897+
898+
if (x != y) {
899+
// Try x - y as well
900+
factor = gcd(this->toFactor, x - y);
901+
if ((factor != 1U) && (factor != this->toFactor)) {
902+
return factor;
903+
}
904+
}
905+
906+
// Failed to find a factor
907+
return 1U;
908+
}
909+
860910
// Perform Gaussian elimination on a binary matrix
861-
void gaussianElimination() {
911+
GaussianEliminationResult gaussianElimination() {
862912
const unsigned cpuCount = CpuCount;
863913
auto mColIt = smoothNumberValues.begin();
864914
auto nColIt = smoothNumberKeys.begin();
865915
const size_t rows = smoothNumberValues.size();
916+
GaussianEliminationResult result(primes.size());
917+
866918
for (size_t col = 0U; col < primes.size(); ++col) {
867919
auto mRowIt = mColIt;
868920
auto nRowIt = nColIt;
869921

870-
// Check all rows below the outer loop row.
871-
int64_t pivot = -1;
922+
// Look for a pivot row in this column
872923
for (size_t row = col; row < rows; ++row) {
873924
if ((*mRowIt)[col]) {
874925
// Swapping matrix rows corresponds
@@ -877,19 +928,21 @@ struct Factorizer {
877928
std::swap(*mColIt, *mRowIt);
878929
std::swap(*nColIt, *nRowIt);
879930
}
880-
pivot = row;
931+
// Mark this column as having a pivot.
932+
result.marks[col] = true;
881933
break;
882934
}
883935
++nRowIt;
884936
++mRowIt;
885937
}
886938

887-
if (pivot != -1) {
888-
// We pivot.
939+
if (result.marks[col]) {
940+
// Pivot found, now eliminate entries in this column
889941
const boost::dynamic_bitset<size_t> &cm = *mColIt;
890942
const BigInteger &cn = *nColIt;
891943
mRowIt = smoothNumberValues.begin();
892944
nRowIt = smoothNumberKeys.begin();
945+
893946
for (unsigned cpu = 0U; (cpu < CpuCount) && (cpu < rows); ++cpu) {
894947
dispatch.dispatch([cpu, &cpuCount, &col, &rows, &cm, &cn, nRowIt, mRowIt]() -> bool {
895948
auto mrIt = mRowIt;
@@ -913,7 +966,6 @@ struct Factorizer {
913966
std::advance(nrIt, cpuCount);
914967
std::advance(mrIt, cpuCount);
915968
}
916-
917969
return false;
918970
});
919971
// Next inner-loop row (all at once by dispatch).
@@ -923,93 +975,52 @@ struct Factorizer {
923975
// All dispatched work must complete.
924976
dispatch.finish();
925977
}
926-
927-
// Next outer-loop row.
978+
// Next outer-loop row
928979
++mColIt;
929980
++nColIt;
930981
}
982+
983+
// Step 2: Identify free rows
984+
auto it = smoothNumberValues.begin();
985+
for (size_t i = 0U; i < primes.size(); i++) {
986+
if (!result.marks[i]) {
987+
boost::dynamic_bitset<size_t> r(rows);
988+
for (size_t j = 0U; j < rows; ++j) {
989+
r[j] = smoothNumberValues[j][i];
990+
}
991+
// We find a free column, so the corresponding row
992+
// in the reduced matrix is a solution row.
993+
result.solutionColumns.emplace_back(r, i);
994+
}
995+
}
996+
997+
if (result.solutionColumns.empty()) {
998+
throw std::runtime_error("No solution found. Need more smooth numbers.");
999+
}
1000+
1001+
return result;
9311002
}
9321003

9331004
////////////////////////////////////////////////////////////////////////////////////////////////////////////
9341005
// WRITTEN WITH HELP FROM ELARA (GPT) ABOVE //
9351006
////////////////////////////////////////////////////////////////////////////////////////////////////////////
9361007

9371008
BigInteger solveForFactor() {
938-
// Every single row is actually already a smooth perfect square by construction.
9391009
// Gaussian elimination is used to create a perfect square of the residues.
940-
gaussianElimination();
941-
942-
// Find a congruence of squares:
943-
auto ikit = smoothNumberKeys.begin();
944-
auto ivit = smoothNumberValues.begin();
945-
BigInteger result = 1U;
946-
for (size_t i = 0U; i < smoothNumberKeys.size(); ++i) {
947-
// For lock_guard scope
948-
if (true) {
949-
std::lock_guard<std::mutex> lock(batchMutex);
950-
if (!isIncomplete) {
951-
break;
952-
}
953-
dispatch.dispatch([this, i, ikit, ivit, &result]() -> bool {
954-
auto jkit = ikit;
955-
auto jvit = ivit;
956-
957-
for (size_t j = i; isIncomplete && (j < this->smoothNumberKeys.size()); ++j) {
958-
if ((*ivit) == (*jvit)) {
959-
// The rows have the same value. Hence, multiplied together,
960-
// they form a perfect square residue we can check for congruence.
961-
962-
// Compute x and y
963-
const BigInteger x = ((*ikit) * (*jkit)) % this->toFactor;
964-
const BigInteger y = modExp(x, this->toFactor >> 1U, this->toFactor);
965-
966-
// Check congruence of squares
967-
BigInteger factor = gcd(this->toFactor, x + y);
968-
if ((factor != 1U) && (factor != this->toFactor)) {
969-
std::lock_guard<std::mutex> lock(this->batchMutex);
970-
isIncomplete = false;
971-
result = factor;
972-
973-
return true;
974-
}
975-
976-
if (x != y) {
977-
// Try x - y as well
978-
factor = gcd(this->toFactor, x - y);
979-
if ((factor != 1U) && (factor != this->toFactor)) {
980-
std::lock_guard<std::mutex> lock(this->batchMutex);
981-
isIncomplete = false;
982-
result = factor;
983-
984-
return true;
985-
}
986-
}
987-
}
988-
989-
// Next inner-loop row (synchronously).
990-
++jkit;
991-
++jvit;
992-
}
993-
994-
return false;
995-
});
1010+
if (smoothNumberKeys.empty()) {
1011+
throw std::runtime_error("No smooth numbers found. Sieve more.");
1012+
}
1013+
GaussianEliminationResult result = gaussianElimination();
1014+
for (size_t i = 0U; i < result.solutionColumns.size(); ++i) {
1015+
const BigInteger factor = solveCongruence(findDependentRows(result, i));
1016+
if ((factor != 1U) && (factor != toFactor)) {
1017+
return factor;
9961018
}
997-
// If we are still dispatching items in the queue,
998-
// they probably won't have completed, but let
999-
// them take a turn with the mutex.
1000-
1001-
// Next outer-loop row (all dispatched at once).
1002-
++ikit;
1003-
++ivit;
10041019
}
10051020

1006-
// The result has not yet been found.
1007-
// A succesful item will dump the queue.
1008-
// If it's already dumped, this does nothing.
1009-
dispatch.finish();
1010-
1011-
// Depending on row count, a successful result should be nearly guaranteed.
1012-
return result;
1021+
// Depending on row count, a successful result should be nearly guaranteed,
1022+
// but we default to no solution.
1023+
return 1U;
10131024
}
10141025

10151026
// Produce a smooth number with its factorization vector.
@@ -1201,6 +1212,9 @@ std::string find_a_factor(std::string toFactorStr, size_t method, size_t nodeCou
12011212
smoothPrimes.push_back(p);
12021213
}
12031214
}
1215+
if (smoothPrimes.empty()) {
1216+
throw std::runtime_error("No smooth primes found under bound. (Increase the smoothness bound multiplier.)");
1217+
}
12041218
// From 1, this is a period for wheel factorization
12051219
size_t biggestWheel = 1ULL;
12061220
for (const size_t &wp : gearFactorizationPrimes) {

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
88

99
[project]
1010
name = "FindAFactor"
11-
version = "5.0.5"
11+
version = "5.0.6"
1212
requires-python = ">=3.8"
1313
description = "Find any nontrivial factor of a number"
1414
readme = {file = "README.txt", content-type = "text/markdown"}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def build_extension(self, ext):
4040

4141
setup(
4242
name='FindAFactor',
43-
version='5.0.5',
43+
version='5.0.6',
4444
author='Dan Strano',
4545
author_email='stranoj@gmail.com',
4646
description='Find any nontrivial factor of a number',

0 commit comments

Comments
 (0)