@@ -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-
745723struct 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) {
0 commit comments