BitContradiction cleanup and performance improvements. LUT increased to increase steps.

This commit is contained in:
mlemanczyk committed 2026-01-18 01:02:06 +01:00
1 parent 41e421e1eb
commit 1a97cdf754
2 files changed
+357 -213

No files matched your search

@@ -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
/// <summary>
/// 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).
/// </summary>
#if DETAILED_LOG
internal static bool TryCheckDivisibilityFromOneOffsets(
ReadOnlySpan<int> qOneOffsets,
ulong p,
out bool divides,
out ContradictionReason reason)
#else
internal static bool TryCheckDivisibilityFromOneOffsets(
ReadOnlySpan<int> 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<int> qOneOffsets,
ulong p,
out bool divides) => TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _);
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong BuildQLow64(ReadOnlySpan<int> 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<int, int[]> _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<int> 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<int> 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<int> 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;
}
@@ -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<int> oneOffsets = stackalloc int[qBitLength];
int oneCount = 0;
for (int i = 0; i < qBitLength; i++)
@@ -4792,9 +4792,14 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
ReadOnlySpan<int> 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)