diff --git a/PerfectNumbers.Core/ByDivisor/BitContradictionSolverWithAMultiplier.cs b/PerfectNumbers.Core/ByDivisor/BitContradictionSolverWithAMultiplier.cs
index c404ec6..32c5685 100644
--- a/PerfectNumbers.Core/ByDivisor/BitContradictionSolverWithAMultiplier.cs
+++ b/PerfectNumbers.Core/ByDivisor/BitContradictionSolverWithAMultiplier.cs
@@ -57,6 +57,25 @@ internal static class BitContradictionSolverWithAMultiplier
private static readonly int[] ModLocalPrefilterModuli = { 65537, 65521 };
#if DETAILED_LOG
+ [ThreadStatic] private static long _statStableAdvanceCalls;
+ [ThreadStatic] private static long _statStableAdvanceCols;
+ [ThreadStatic] private static long _statStableAdvanceColsBatched;
+ [ThreadStatic] private static long _statScalarCols;
+
+ [ThreadStatic] private static long _statDeltaRequests;
+ [ThreadStatic] private static long _statDeltaHits;
+ [ThreadStatic] private static long _statDeltaBuilds;
+
+ [ThreadStatic] private static long _statStableAdvanceBigShift; // ile razy columnCount>=64 w TryAdvanceStableUnknown
+ [ThreadStatic] private static long _statBatchEntry;
+ [ThreadStatic] private static long _statBatchCols;
+ [ThreadStatic] private static long _statCleanChecks;
+ [ThreadStatic] private static long _statCleanFalse;
+ [ThreadStatic] private static int _statFirstDirtyColumn;
+ [ThreadStatic] private static int _statFirstDirtyWord;
+ [ThreadStatic] private static ulong _statFirstDirtyQ;
+ [ThreadStatic] private static ulong _statFirstDirtyKnown;
+
public readonly struct TopDownPruneFailure(long column, long carryMin, long carryMax, long unknown)
{
public readonly long Column = column;
@@ -1238,38 +1257,26 @@ internal static class BitContradictionSolverWithAMultiplier
// _privateWorkSet = new();
// _stableUnknownDelta8 = new();
_initialized = true;
+ // Start with a small size and grow on demand.
+ _stableUnknownDelta8ByUnknown = new int[64][];
}
- // #if DETAILED_LOG
- [ThreadStatic] private static long _statStableAdvanceCalls;
- [ThreadStatic] private static long _statStableAdvanceCols;
- [ThreadStatic] private static long _statStableAdvanceColsBatched;
- [ThreadStatic] private static long _statScalarCols;
-
- [ThreadStatic] private static long _statDeltaRequests;
- [ThreadStatic] private static long _statDeltaHits;
- [ThreadStatic] private static long _statDeltaBuilds;
-
- [ThreadStatic] private static long _statStableAdvanceBigShift; // ile razy columnCount>=64 w TryAdvanceStableUnknown
- [ThreadStatic] private static long _statBatchEntry;
- [ThreadStatic] private static long _statBatchCols;
- [ThreadStatic] private static long _statCleanChecks;
- [ThreadStatic] private static long _statCleanFalse;
- [ThreadStatic] private static int _statFirstDirtyColumn;
- [ThreadStatic] private static int _statFirstDirtyWord;
- [ThreadStatic] private static ulong _statFirstDirtyQ;
- [ThreadStatic] private static ulong _statFirstDirtyKnown;
- // #endif
-
///
/// Full column-wise propagation for a given set of q one-bit offsets (LSB=0) and exponent p.
/// Returns true if the solver could decide locally (divides or contradiction).
///
+#if DETAILED_LOG
internal static bool TryCheckDivisibilityFromOneOffsets(
ReadOnlySpan qOneOffsets,
ulong p,
out bool divides,
out ContradictionReason reason)
+#else
+ internal static bool TryCheckDivisibilityFromOneOffsets(
+ ReadOnlySpan qOneOffsets,
+ ulong p,
+ out bool divides)
+#endif
{
// PrivateWorkSet workSet = _privateWorkSet;
@@ -1277,8 +1284,6 @@ internal static class BitContradictionSolverWithAMultiplier
#if DETAILED_LOG
_lastTopDownFailure = null;
-#endif
- // #if DETAILED_LOG
_statStableAdvanceCalls = 0;
_statStableAdvanceCols = 0;
_statStableAdvanceColsBatched = 0;
@@ -1290,7 +1295,7 @@ internal static class BitContradictionSolverWithAMultiplier
_statCleanChecks = 0;
_statCleanFalse = 0;
_statFirstDirtyColumn = -1;
- // #endif
+#endif
_dynamicModChecks = 0;
_dynamicModFailures = 0;
@@ -1322,8 +1327,11 @@ internal static class BitContradictionSolverWithAMultiplier
int maxOffset = qOneOffsets[qOneOffsetsLength - 1];
if ((ulong)(maxOffset + 1) >= p)
{
- reason = ContradictionReason.TruncatedLength;
- return true;
+ #if DETAILED_LOG
+ reason = ContradictionReason.TruncatedLength;
+ #endif
+ return false;
+ // return true;
}
// q from EvenPerfectBitScanner will always have the correct 2kp+1 form.
@@ -1352,17 +1360,19 @@ internal static class BitContradictionSolverWithAMultiplier
int maxAllowedA = pLong - (maxOffset + 1);
if (maxAllowedA < 0)
{
- reason = ContradictionReason.TruncatedLength;
- return true;
+ #if DETAILED_LOG
+ reason = ContradictionReason.TruncatedLength;
+ #endif
+ return false;
}
- // Force low bits of 'a' from a ≡ -q^{-1} (mod 2^512).
- // This is a necessary condition for q | (2^p - 1) when p >= 512 (true here).
- const int ForcedBits = ForcedALowBits; // 512
+ // Force low bits of 'a' from a ≡ -q^{-1} (mod 2^1024).
+ // This is a necessary condition for q | (2^p - 1) when p >= 1024 (true here).
+ const int ForcedBits = ForcedALowBits;
BigInteger mask = (BigInteger.One << ForcedBits) - BigInteger.One;
- // Build q (low 512 bits) from qOneOffsets. q is small anyway, but do it generically.
+ // Build q (low 1024 bits) from qOneOffsets. q is small anyway, but do it generically.
BigInteger qMod2k = BigInteger.Zero;
for (int i = 0; i < qOneOffsetsLength; i++)
{
@@ -1375,11 +1385,13 @@ internal static class BitContradictionSolverWithAMultiplier
if (qMod2k.IsEven)
{
Console.WriteLine("Pruned by qMod2k.IsEven");
- reason = ContradictionReason.ParityUnreachable;
- return true;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
+ return false;
}
- // Compute inv = q^{-1} mod 2^512 using Hensel/Newton iteration in Z/2^kZ.
+ // Compute inv = q^{-1} mod 2^1024 using Hensel/Newton iteration in Z/2^kZ.
BigInteger inv = qMod2k; // OK seed for odd q.
int iters = 0;
for (int bits = 1; bits < ForcedBits; bits <<= 1) iters++;
@@ -1406,9 +1418,12 @@ internal static class BitContradictionSolverWithAMultiplier
{
if ((knownZero0[word] & mask64) != 0)
{
- Console.WriteLine("Pruned by knownZero1[word] & mask64");
- reason = ContradictionReason.ParityUnreachable;
- return true;
+ #if DETAILED_LOG
+ // TODO: This doesn't kickoff. Is it necessary?
+ Console.WriteLine("Pruned by knownZero1[word] & mask64");
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
+ return false;
}
knownOne0[word] |= mask64;
@@ -1417,9 +1432,12 @@ internal static class BitContradictionSolverWithAMultiplier
{
if ((knownOne0[word] & mask64) != 0)
{
- Console.WriteLine("Pruned by knownOne1[word] & mask64");
- reason = ContradictionReason.ParityUnreachable;
- return true;
+ #if DETAILED_LOG
+ // TODO: This doesn't kickoff. Is it necessary?
+ Console.WriteLine("Pruned by knownOne1[word] & mask64");
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
+ return false;
}
knownZero0[word] |= mask64;
@@ -1517,9 +1535,11 @@ internal static class BitContradictionSolverWithAMultiplier
// It can reject entire classes of q with sparse/awkward high-bit structure before the full DP runs.
if (!TryHighBitTopCarryPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
{
- Console.WriteLine("Pruned with TryHighBitTopCarryPrefilter");
- reason = ContradictionReason.ParityUnreachable;
- return true;
+ #if DETAILED_LOG
+ Console.WriteLine("Pruned with TryHighBitTopCarryPrefilter");
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
+ return false;
}
// var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong);
@@ -1560,18 +1580,20 @@ internal static class BitContradictionSolverWithAMultiplier
#endif
// }
- // if (ShouldRunTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
- // {
- // #if DETAILED_LOG
- // if (!TryTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
- // #else
- // if (!TryTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
- // #endif
- // {
- // reason = ContradictionReason.ParityUnreachable;
- // return true;
- // }
- // }
+ if (ShouldRunTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
+ {
+ #if DETAILED_LOG
+ if (!TryTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
+ #else
+ if (!TryTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
+ #endif
+ {
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
+ return false;
+ }
+ }
// Row-aware masks for q and a-window (supports q > 64 bits)
int qBitLen = maxOffset + 1;
@@ -1582,6 +1604,7 @@ internal static class BitContradictionSolverWithAMultiplier
int t = qOneOffsets[i];
qMaskWords[t >> 6] |= 1UL << (t & 63);
}
+
ulong lastWordMask = (qBitLen & 63) == 0 ? ulong.MaxValue : ((1UL << (qBitLen & 63)) - 1UL);
// Sliding window over a: bit t corresponds to a_{column - t}.
ulong[] aOneWin = new ulong[qWordCount];
@@ -1684,24 +1707,28 @@ internal static class BitContradictionSolverWithAMultiplier
{
int remaining = highColumn - lowColumn + 1;
int blockSize = remaining < PingPongBlockColumns ? remaining : PingPongBlockColumns;
- if (!TryProcessBottomUpBlockRowAware(
- qOneOffsets, qMaskWords, qWordCount, lastWordMask, maxAllowedA,
- lowColumn, blockSize,
- ref carryLow, ref maxKnownA, ref segmentBase,
- ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1,
- windowSize,
- aOneWin, aZeroWin, ref windowColumn,
- out reason))
+ #if DETAILED_LOG
+ if (!TryProcessBottomUpBlockRowAware(
+ qOneOffsets, qMaskWords, qWordCount, lastWordMask, maxAllowedA,
+ lowColumn, blockSize,
+ ref carryLow, ref maxKnownA, ref segmentBase,
+ ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1,
+ windowSize, aOneWin, aZeroWin, ref windowColumn,
+ out reason))
+ #else
+ if (!TryProcessBottomUpBlockRowAware(
+ qOneOffsets, qMaskWords, qWordCount, lastWordMask, maxAllowedA,
+ lowColumn, blockSize,
+ ref carryLow, ref maxKnownA, ref segmentBase,
+ ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1,
+ windowSize, aOneWin, aZeroWin, ref windowColumn))
+ #endif
{
- Console.WriteLine(
- $"[STATS] stableCalls={_statStableAdvanceCalls} stableCols={_statStableAdvanceColsBatched} " +
- $"scalarCols={_statScalarCols} bigShift={_statStableAdvanceBigShift} " +
- $"deltaReq={_statDeltaRequests} deltaHits={_statDeltaHits} deltaBuilds={_statDeltaBuilds}");
- Console.WriteLine(
- $"[CLEAN] checks={_statCleanChecks} false={_statCleanFalse} " +
- $"firstDirtyCol={_statFirstDirtyColumn} word={_statFirstDirtyWord} " +
- $"qWord=0x{_statFirstDirtyQ:X16} knownWord=0x{_statFirstDirtyKnown:X16}");
- return true;
+ #if DETAILED_LOG
+ PrintStats();
+ #endif
+
+ return false;
}
lowColumn += blockSize;
@@ -1722,24 +1749,19 @@ internal static class BitContradictionSolverWithAMultiplier
// }
divides = true;
- reason = ContradictionReason.None;
- // #if DETAILED_LOG
- Console.WriteLine(
- $"[STATS] stableCalls={_statStableAdvanceCalls} stableCols={_statStableAdvanceColsBatched} " +
- $"scalarCols={_statScalarCols} bigShift={_statStableAdvanceBigShift} " +
- $"deltaReq={_statDeltaRequests} deltaHits={_statDeltaHits} deltaBuilds={_statDeltaBuilds}");
- Console.WriteLine(
- $"[CLEAN] checks={_statCleanChecks} false={_statCleanFalse} " +
- $"firstDirtyCol={_statFirstDirtyColumn} word={_statFirstDirtyWord} " +
- $"qWord=0x{_statFirstDirtyQ:X16} knownWord=0x{_statFirstDirtyKnown:X16}");
- // #endif
+ #if DETAILED_LOG
+ reason = ContradictionReason.None;
+ PrintStats();
+ #endif
return true;
}
+#if DETAILED_LOG
internal static bool TryCheckDivisibilityFromOneOffsets(
ReadOnlySpan qOneOffsets,
ulong p,
out bool divides) => TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _);
+#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong BuildQLow64(ReadOnlySpan qOneOffsets)
@@ -1763,41 +1785,49 @@ internal static class BitContradictionSolverWithAMultiplier
{
// qOdd must be odd.
// Start with x ≡ 1 (mod 2). A common fast seed is x = qOdd.
- ulong x = qOdd;
-
// Each iteration doubles correct bits. 6 iterations are enough for 64 bits: 1->2->4->8->16->32->64.
unchecked
{
- x *= 2UL - qOdd * x;
- x *= 2UL - qOdd * x;
- x *= 2UL - qOdd * x;
- x *= 2UL - qOdd * x;
- x *= 2UL - qOdd * x;
- x *= 2UL - qOdd * x;
+ qOdd *= 2UL - qOdd * qOdd;
+ qOdd *= 2UL - qOdd * qOdd;
+ qOdd *= 2UL - qOdd * qOdd;
+ qOdd *= 2UL - qOdd * qOdd;
+ qOdd *= 2UL - qOdd * qOdd;
+ qOdd *= 2UL - qOdd * qOdd;
}
- return x;
+ return qOdd;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool TryAdvanceZeroColumns(ref CarryRange carry, int columnCount)
{
+ const int Step = 30;
long carryMin = carry.Min;
long carryMax = carry.Max;
- while (columnCount > 0)
+ int add;
+ while (columnCount >= Step)
{
- int step = columnCount > 30 ? 30 : columnCount;
- int add = (1 << step) - 1;
- long nextMin = (carryMin + add) >> step;
- long nextMax = carryMax >> step;
- if (nextMin > nextMax)
+ add = (1 << Step) - 1;
+ carryMin = (carryMin + add) >> Step;
+ carryMax >>= Step;
+ if (carryMin > carryMax)
{
return false;
}
- carryMin = nextMin;
- carryMax = nextMax;
- columnCount -= step;
+ columnCount -= Step;
+ }
+
+ if (columnCount > 0)
+ {
+ add = (1 << columnCount) - 1;
+ carryMin = (carryMin + add) >> columnCount;
+ carryMax >>= columnCount;
+ if (carryMin > carryMax)
+ {
+ return false;
+ }
}
carry = new CarryRange(carryMin, carryMax);
@@ -1808,33 +1838,27 @@ internal static class BitContradictionSolverWithAMultiplier
// private static Dictionary _stableUnknownDelta8;
// Indeksowane przez "unknown" (zwykle qOneOffsetsLength). Elementy inicjalizowane leniwie.
-
private static volatile int[][]? _stableUnknownDelta8ByUnknown;
- private static int[] GetStableUnknownDelta8(int unknown)
+ private static int[] GetStableUnknownDelta(int unknown)
{
- // #if DETAILED_LOG
- _statDeltaRequests++;
- // #endif
+ #if DETAILED_LOG
+ _statDeltaRequests++;
+ #endif
// Lazily allocate the outer array. Keep it volatile to publish safely.
- var cache = _stableUnknownDelta8ByUnknown;
- if (cache == null)
- {
- // Start with a small size and grow on demand.
- cache = new int[64][];
- _stableUnknownDelta8ByUnknown = cache;
- }
+ int[][] cache = _stableUnknownDelta8ByUnknown!;
// Ensure capacity for this "unknown".
- if ((uint)unknown >= (uint)cache.Length)
+ int cacheLength = cache.Length;
+ if ((uint)unknown >= (uint)cacheLength)
{
// Grow to at least unknown+1, doubling for amortized O(1).
- int newLen = cache.Length;
+ int newLen = cacheLength;
while (newLen <= unknown) newLen <<= 1;
var newCache = new int[newLen][];
- Array.Copy(cache, newCache, cache.Length);
+ Array.Copy(cache, newCache, cacheLength);
// Publish the grown cache (racy publish is OK; contents are immutable after creation).
_stableUnknownDelta8ByUnknown = newCache;
@@ -1845,37 +1869,47 @@ internal static class BitContradictionSolverWithAMultiplier
var cached = cache[unknown];
if (cached != null)
{
- // #if DETAILED_LOG
- _statDeltaHits++;
- // #endif
+ #if DETAILED_LOG
+ _statDeltaHits++;
+ #endif
return cached;
}
// Compute.
- const int PrefixLength = 256;
+ const int PrefixLength = 1 << ColumnsAtOnce;
int[] delta = new int[PrefixLength];
for (int low = 0; low < PrefixLength; low++)
{
long value = low;
- // 8 iterations correspond to processing 8 columns at once.
- for (int i = 0; i < 8; i++)
+ // 16 iterations correspond to processing 16 columns at once. It was slower with: 32, 128, 1024 and rolled.
+ for (int i = 0; i < ColumnsAtOnce; i += 4)
{
- // Only the LSB of 'value' matters; avoid long->int truncation except for bit0.
int parity = (int)(value & 1L);
int sMax = (((unknown ^ parity) & 1) != 0) ? unknown : (unknown - 1);
+ value = (value + sMax - 1) >> 1;
+ parity = (int)(value & 1L);
+ sMax = (((unknown ^ parity) & 1) != 0) ? unknown : (unknown - 1);
+ value = (value + sMax - 1) >> 1;
+
+ parity = (int)(value & 1L);
+ sMax = (((unknown ^ parity) & 1) != 0) ? unknown : (unknown - 1);
+ value = (value + sMax - 1) >> 1;
+
+ parity = (int)(value & 1L);
+ sMax = (((unknown ^ parity) & 1) != 0) ? unknown : (unknown - 1);
value = (value + sMax - 1) >> 1;
}
- delta[low] = (int)((value << 8) - low);
+ delta[low] = (int)((value << ColumnsAtOnce) - low);
}
+ #if DETAILED_LOG
+ _statDeltaBuilds++;
+ #endif
// Publish into cache. If a race computes it twice, that's acceptable (same deterministic result).
- // #if DETAILED_LOG
- _statDeltaBuilds++;
- // #endif
cache[unknown] = delta;
return delta;
}
@@ -1906,20 +1940,26 @@ internal static class BitContradictionSolverWithAMultiplier
// return delta;
// }
+ const int ColumnsAtOnce = 24;
+
+#if DETAILED_LOG
private static bool TryAdvanceStableUnknown(ref CarryRange carry, int unknown, int columnCount, out ContradictionReason reason)
+#else
+ private static bool TryAdvanceStableUnknown(ref CarryRange carry, int unknown, int columnCount)
+#endif
{
- // #if DETAILED_LOG
- _statStableAdvanceCalls++;
- _statStableAdvanceCols += columnCount;
- if (columnCount >= 64) _statStableAdvanceBigShift++;
- // #endif
+ #if DETAILED_LOG
+ _statStableAdvanceCalls++;
+ _statStableAdvanceCols += columnCount;
+ if (columnCount >= 64) _statStableAdvanceBigShift++;
+ #endif
if (columnCount <= 0)
{
- reason = ContradictionReason.None;
- // #if DETAILED_LOG
- _statStableAdvanceColsBatched += columnCount;
- // #endif
+ #if DETAILED_LOG
+ reason = ContradictionReason.None;
+ _statStableAdvanceColsBatched += columnCount;
+ #endif
return true;
}
@@ -1927,27 +1967,30 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (!TryAdvanceZeroColumns(ref carry, columnCount))
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
- // #if DETAILED_LOG
- _statStableAdvanceColsBatched += columnCount;
- // #endif
- reason = ContradictionReason.None;
+ #if DETAILED_LOG
+ _statStableAdvanceColsBatched += columnCount;
+ reason = ContradictionReason.None;
+ #endif
return true;
}
long carryMin = columnCount >= 63 ? 0 : (carry.Min >> columnCount);
long carryMax = carry.Max;
int remaining = columnCount;
- int[] delta = GetStableUnknownDelta8(unknown);
+ int[] delta = GetStableUnknownDelta(unknown);
- while (remaining >= 8)
+ const int CarryDecimalMask = (1 << ColumnsAtOnce) - 1;
+ while (remaining >= ColumnsAtOnce)
{
- int low = (int)(carryMax & 255);
- carryMax = (carryMax + delta[low]) >> 8;
- remaining -= 8;
+ int low = (int)(carryMax & CarryDecimalMask);
+ carryMax = (carryMax + delta[low]) >> ColumnsAtOnce;
+ remaining -= ColumnsAtOnce;
}
while (remaining > 0)
@@ -1959,15 +2002,17 @@ internal static class BitContradictionSolverWithAMultiplier
if (carryMax < carryMin)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
- // #if DETAILED_LOG
- _statStableAdvanceColsBatched += columnCount;
- // #endif
+ #if DETAILED_LOG
+ _statStableAdvanceColsBatched += columnCount;
+ reason = ContradictionReason.None;
+ #endif
carry = new CarryRange(carryMin, carryMax);
- reason = ContradictionReason.None;
return true;
}
@@ -1986,6 +2031,7 @@ internal static class BitContradictionSolverWithAMultiplier
words[i] = (w << 1) | carry;
carry = newCarry;
}
+
if (wordCount > 0)
{
words[wordCount - 1] &= lastWordMask;
@@ -2145,6 +2191,7 @@ internal static class BitContradictionSolverWithAMultiplier
return found == 1;
}
+#if DETAILED_LOG
private static bool TryProcessBottomUpBlockRowAware(
ReadOnlySpan qOneOffsets,
ulong[] qMaskWords,
@@ -2165,6 +2212,27 @@ internal static class BitContradictionSolverWithAMultiplier
ulong[] aZeroWin,
ref int windowColumn,
out ContradictionReason reason)
+#else
+ private static bool TryProcessBottomUpBlockRowAware(
+ ReadOnlySpan qOneOffsets,
+ ulong[] qMaskWords,
+ int qWordCount,
+ ulong lastWordMask,
+ int maxAllowedA,
+ int startColumn,
+ int columnCount,
+ ref CarryRange carry,
+ ref int maxKnownA,
+ ref int segmentBase,
+ ref ulong[] knownOne0,
+ ref ulong[] knownOne1,
+ ref ulong[] knownZero0,
+ ref ulong[] knownZero1,
+ int windowSize,
+ ulong[] aOneWin,
+ ulong[] aZeroWin,
+ ref int windowColumn)
+#endif
{
bool needRebuildWindow = false;
int endColumn = startColumn + columnCount;
@@ -2245,16 +2313,24 @@ internal static class BitContradictionSolverWithAMultiplier
// Additionally, ensure current window has no known bits (should be true if maxKnownA condition holds).
// Conservative batching: ignore row-aware window state and assume all q positions are unknown.
// This cannot create false negatives; it may only reduce pruning power.
- _statBatchEntry++;
- _statBatchCols += remainingCols;
+ #if DETAILED_LOG
+ _statBatchEntry++;
+ _statBatchCols += remainingCols;
+ #endif
int rem = remainingCols;
while (rem > 0)
{
int step = rem > TailCarryBatchColumns ? TailCarryBatchColumns : rem;
- if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step, out var propagateReason))
+ #if DETAILED_LOG
+ if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step, out var propagateReason))
+ #else
+ if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step))
+ #endif
{
- reason = propagateReason;
+ #if DETAILED_LOG
+ reason = propagateReason;
+ #endif
return false;
}
@@ -2308,7 +2384,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (state == 2)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2324,7 +2402,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (state == 1)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2343,7 +2423,9 @@ internal static class BitContradictionSolverWithAMultiplier
int possible = forced + remaining;
if (!TryPropagateCarry(ref carry, forced, possible, 1))
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2366,16 +2448,19 @@ internal static class BitContradictionSolverWithAMultiplier
startColumn++;
windowColumn = startColumn;
- // #if DETAILED_LOG
- _statScalarCols++;
- // #endif
+ #if DETAILED_LOG
+ _statScalarCols++;
+ #endif
}
- reason = ContradictionReason.None;
+ #if DETAILED_LOG
+ reason = ContradictionReason.None;
+ #endif
return true;
}
private static bool TryProcessBottomUpBlock(
+ ulong prime,
ReadOnlySpan qOneOffsets,
in int[] runStarts,
in int[] runLengths,
@@ -2399,8 +2484,11 @@ internal static class BitContradictionSolverWithAMultiplier
ref bool top1KnownOne,
ref bool top0KnownZero,
ref bool top1KnownZero,
- ref int zeroTailStart,
- out ContradictionReason reason)
+ ref int zeroTailStart
+#if DETAILED_LOG
+ , out ContradictionReason reason
+#endif
+ )
{
int endColumn = startColumn + columnCount;
int qOneOffsetsLength = qOneOffsets.Length;
@@ -2425,17 +2513,39 @@ internal static class BitContradictionSolverWithAMultiplier
{
runStartIdx = 0;
runEndIdx = runCount;
- while (remaining > 0)
+ while (remaining >= TailCarryBatchColumns)
{
- int step = remaining > TailCarryBatchColumns ? TailCarryBatchColumns : remaining;
- if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step, out var propagateReason))
+ #if DETAILED_LOG
+ if (!TryAdvanceStableUnknown(ref carry, stableUnknown, TailCarryBatchColumns, out var propagateReason))
+ #else
+ if (!TryAdvanceStableUnknown(ref carry, stableUnknown, TailCarryBatchColumns))
+ #endif
{
- reason = propagateReason;
+ #if DETAILED_LOG
+ reason = propagateReason;
+ #endif
return false;
}
- startColumn += step;
- remaining -= step;
+ startColumn += TailCarryBatchColumns;
+ remaining -= TailCarryBatchColumns;
+ }
+
+ if (remaining > 0)
+ {
+ #if DETAILED_LOG
+ if (!TryAdvanceStableUnknown(ref carry, stableUnknown, remaining, out var propagateReason))
+ #else
+ if (!TryAdvanceStableUnknown(ref carry, stableUnknown, remaining))
+ #endif
+ {
+ #if DETAILED_LOG
+ reason = propagateReason;
+ #endif
+ return false;
+ }
+
+ remaining = 0;
}
bound = (startColumn / windowSize) * windowSize;
@@ -2469,7 +2579,7 @@ internal static class BitContradictionSolverWithAMultiplier
int forced = 0;
remaining = 0;
int chosenIndex = -1;
- // bool topChanged = false;
+ bool topChanged = false;
bound = (startColumn / windowSize) * windowSize;
if (bound != segmentBase)
{
@@ -2681,23 +2791,27 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (top0KnownZero)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
top0KnownOne = true;
- // topChanged = true;
+ topChanged = true;
}
else if (chosenIndex == topIndex1)
{
if (top1KnownZero)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
top1KnownOne = true;
- // topChanged = true;
+ topChanged = true;
}
}
}
@@ -2716,7 +2830,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (top0KnownOne)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2727,7 +2843,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (top1KnownOne)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2737,18 +2855,23 @@ internal static class BitContradictionSolverWithAMultiplier
}
}
- // if (topChanged)
- // {
- // if (!TryDynamicModularRangePrune(qOneOffsets, qHash, p, maxAllowedA, top0KnownOne, top0KnownZero, top1KnownOne, top1KnownZero, zeroTailStart))
- // {
- // reason = ContradictionReason.ParityUnreachable;
- // return false;
- // }
- // }
+ if (topChanged)
+ {
+ var qHash = ComputeQOffsetsHash(qOneOffsets);
+ if (!TryDynamicModularRangePrune(qOneOffsets, qHash, prime, maxAllowedA, top0KnownOne, top0KnownZero, top1KnownOne, top1KnownZero, zeroTailStart))
+ {
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
+ return false;
+ }
+ }
if ((top0KnownZero && top0KnownOne) || (top1KnownZero && top1KnownOne))
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2756,13 +2879,17 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (top0KnownZero)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
}
else if (top0KnownZero && top1KnownZero)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2774,7 +2901,9 @@ internal static class BitContradictionSolverWithAMultiplier
long carryMax = AlignDownToParity(carry.Max, known0Len);
if (carryMin > carryMax)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2791,7 +2920,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if ((minSum & 1) != requiredParity)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2802,7 +2933,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if ((minSum & 1) != requiredParity && remaining == 0)
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
@@ -2812,18 +2945,22 @@ internal static class BitContradictionSolverWithAMultiplier
forced + remaining,
requiredParity))
{
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
return false;
}
}
startColumn++;
- // #if DETAILED_LOG
- _statScalarCols++;
- // #endif
+ #if DETAILED_LOG
+ _statScalarCols++;
+ #endif
}
- reason = ContradictionReason.None;
+ #if DETAILED_LOG
+ reason = ContradictionReason.None;
+ #endif
return true;
}
@@ -2852,9 +2989,9 @@ internal static class BitContradictionSolverWithAMultiplier
out CarryRange nextCarryOut)
#endif
{
-#if DETAILED_LOG
- failure = null;
-#endif
+ #if DETAILED_LOG
+ failure = null;
+ #endif
long carryOutMin = carryOut.Min,
carryOutMax = carryOut.Max;
int value;
@@ -2904,9 +3041,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
{
-#if DETAILED_LOG
- failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
-#endif
+ #if DETAILED_LOG
+ failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
+ #endif
nextCarryOut = default;
return false;
}
@@ -2930,9 +3067,9 @@ internal static class BitContradictionSolverWithAMultiplier
{
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
{
-#if DETAILED_LOG
- failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
-#endif
+ #if DETAILED_LOG
+ failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
+ #endif
nextCarryOut = default;
return false;
}
@@ -2958,9 +3095,9 @@ internal static class BitContradictionSolverWithAMultiplier
if (carryOutMin > carryOutMax)
{
-#if DETAILED_LOG
- failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
-#endif
+ #if DETAILED_LOG
+ failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
+ #endif
nextCarryOut = default;
return false;
}
diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs
index 46056fb..bb7d433 100644
--- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs
+++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs
@@ -1,4 +1,4 @@
-// #define DivisorSet_BitContradiction
+#define DivisorSet_BitContradiction
// #define DivisorSet_BitTree
using System;
@@ -4773,7 +4773,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
// Console.WriteLine($"Switching to bit-contradiction mode for {prime}");
// Iterative column-wise propagation without storing all columns; we only keep assigned bits of 'a'.
- int[] oneOffsets = _qBits;
+ int[] oneOffsets = _qBits!;
// Span oneOffsets = stackalloc int[qBitLength];
int oneCount = 0;
for (int i = 0; i < qBitLength; i++)
@@ -4792,9 +4792,14 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
ReadOnlySpan oneOffsetsSlice = oneOffsets[..oneCount];
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(true);
- bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, out divides, out var reason);
+ #if DETAILED_LOG
+ bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, out divides, out var reason);
+ #else
+ bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, out divides);
+ #endif
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(false);
// bool decided = BitContradictionSolverBaseline.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
+ ArgumentOutOfRangeException.ThrowIfNotEqual(decided, divides);
if (decided && divides)
{
// Verify to avoid false positives on structurally admissible but non-dividing q.
@@ -4803,7 +4808,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
if (!dividesExact)
{
divides = false;
- reason = ContradictionReason.ParityUnreachable;
+ #if DETAILED_LOG
+ reason = ContradictionReason.ParityUnreachable;
+ #endif
}
}
if (decided && !divides)