BitContradiction performance improvements.

This commit is contained in:
mlemanczyk committed 2026-01-23 16:25:04 +01:00
1 parent 1719b3350d
commit 9886d8bbb2
1 file changed
+225 -171
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Numerics; using System.Numerics;
using Open.Collections;
namespace PerfectNumbers.Core.ByDivisor; namespace PerfectNumbers.Core.ByDivisor;
@@ -147,7 +144,7 @@ internal static class BitContradictionSolverWithAMultiplier
private static Dictionary<int, int[]>? _pow2DiffModCache; private static Dictionary<int, int[]>? _pow2DiffModCache;
[ThreadStatic] [ThreadStatic]
private static PrivateWorkSet? _privateWorkSet; private static PrivateWorkSet _privateWorkSet;
private readonly struct QModCacheKey(long hash, int mod) private readonly struct QModCacheKey(long hash, int mod)
{ {
@@ -876,8 +873,8 @@ internal static class BitContradictionSolverWithAMultiplier
if (b < a) continue; if (b < a) continue;
const long TwicePlusOneOverflowLimit = (long.MaxValue - 1) >> 1; const long TwicePlusOneOverflowLimit = (long.MaxValue - 1) >> 1;
// Jeśli carryOut jest już zbyt duże, top-down robi się bezużyteczny w long. // When carryOut is already too big, top-down becomes useless in long.
// Prefilter MUSI pozostać konserwatywny => przerywamy filtrowanie. // We want to be conservative to avoid rejecting correct divisors => stop filtering with this filter.
if (a > TwicePlusOneOverflowLimit || b > TwicePlusOneOverflowLimit) if (a > TwicePlusOneOverflowLimit || b > TwicePlusOneOverflowLimit)
{ {
return true; return true;
@@ -1044,8 +1041,6 @@ internal static class BitContradictionSolverWithAMultiplier
return (int)(sum % mod); return (int)(sum % mod);
} }
private static bool ExistsCongruentInRange(BigInteger minA, BigInteger maxA, int residue, int mod) private static bool ExistsCongruentInRange(BigInteger minA, BigInteger maxA, int residue, int mod)
{ {
if (minA > maxA) if (minA > maxA)
@@ -1204,6 +1199,7 @@ internal static class BitContradictionSolverWithAMultiplier
return true; return true;
} }
private static int ModPow2Minus1(ulong p, int mod) private static int ModPow2Minus1(ulong p, int mod)
{ {
int pow = PowMod2(p, mod); int pow = PowMod2(p, mod);
@@ -1289,6 +1285,7 @@ internal static class BitContradictionSolverWithAMultiplier
_initialized = true; _initialized = true;
// Start with a small size and grow on demand. // Start with a small size and grow on demand.
_stableUnknownDelta8ByUnknown = new int[64][]; _stableUnknownDelta8ByUnknown = new int[64][];
_privateWorkSet = new PrivateWorkSet();
} }
/// <summary> /// <summary>
@@ -1308,7 +1305,7 @@ internal static class BitContradictionSolverWithAMultiplier
out bool divides) out bool divides)
#endif #endif
{ {
PrivateWorkSet workSet = _privateWorkSet ??= new PrivateWorkSet(); PrivateWorkSet workSet = _privateWorkSet;
divides = false; divides = false;
@@ -1355,7 +1352,8 @@ internal static class BitContradictionSolverWithAMultiplier
int qOneOffsetsLength = qOneOffsets.Length; int qOneOffsetsLength = qOneOffsets.Length;
int maxOffset = qOneOffsets[qOneOffsetsLength - 1]; int maxOffset = qOneOffsets[qOneOffsetsLength - 1];
if ((ulong)(maxOffset + 1) >= p) int qBitLen = maxOffset + 1;
if ((ulong)qBitLen >= p)
{ {
#if DETAILED_LOG #if DETAILED_LOG
reason = ContradictionReason.TruncatedLength; reason = ContradictionReason.TruncatedLength;
@@ -1371,23 +1369,26 @@ internal static class BitContradictionSolverWithAMultiplier
// } // }
const int ForcedALowBits = 1024; const int ForcedALowBits = 1024;
int windowSize = maxOffset + 1; int windowSize = qBitLen < ForcedALowBits ? ForcedALowBits : qBitLen;
if (windowSize < ForcedALowBits) windowSize = ForcedALowBits;
windowSize = (windowSize + 63) & ~63; windowSize = (windowSize + 63) & ~63;
int wordCount = (windowSize + 63) >> 6; int aWordCount = (windowSize + 63) >> 6;
workSet.EnsureCapacity(wordCount, qWordCount: 0, runCapacity: qOneOffsetsLength); int qWordCount = (qBitLen + 63) >> 6;
workSet.EnsureCapacity(aWordCount, qWordCount, runCapacity: qOneOffsetsLength);
ulong[] knownOne0 = workSet.KnownOne0; ulong[] knownOne0 = workSet.KnownOne0;
ulong[] knownOne1 = workSet.KnownOne1; ulong[] knownOne1 = workSet.KnownOne1;
ulong[] knownZero0 = workSet.KnownZero0; ulong[] knownZero0 = workSet.KnownZero0;
ulong[] knownZero1 = workSet.KnownZero1; ulong[] knownZero1 = workSet.KnownZero1;
Array.Clear(knownOne0, 0, wordCount); ulong[] qMaskWords = workSet.QMaskWords;
Array.Clear(knownOne1, 0, wordCount);
Array.Clear(knownZero0, 0, wordCount); Array.Clear(knownOne0, 0, aWordCount);
Array.Clear(knownZero1, 0, wordCount); Array.Clear(knownOne1, 0, aWordCount);
Array.Clear(knownZero0, 0, aWordCount);
Array.Clear(knownZero1, 0, aWordCount);
Array.Clear(qMaskWords, 0, qWordCount);
int segmentBase = 0; int segmentBase = 0;
int maxKnownA = -1;
int pLong = (int)p; int pLong = (int)p;
int maxAllowedA = pLong - (maxOffset + 1); int maxAllowedA = pLong - qBitLen;
if (maxAllowedA < 0) if (maxAllowedA < 0)
{ {
#if DETAILED_LOG #if DETAILED_LOG
@@ -1399,49 +1400,52 @@ internal static class BitContradictionSolverWithAMultiplier
// Force low bits of 'a' from a ≡ -q^{-1} (mod 2^1024). // 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). // This is a necessary condition for q | (2^p - 1) when p >= 1024 (true here).
const int ForcedBits = ForcedALowBits; const int ForcedBits = ForcedALowBits;
const int ForcedBitsMinusOne = ForcedALowBits - 1;
BigInteger mask = Pow2Provider.BigIntegerMinusOne(ForcedBits); BigInteger mask = Pow2Provider.BigIntegerMinusOne(ForcedBits);
// Build q (low 1024 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; BigInteger qMod2k = BigInteger.Zero;
for (int i = 0; i < qOneOffsetsLength; i++) int i, t;
for (i = 0; i < qOneOffsetsLength; i++)
{ {
int bit = qOneOffsets[i]; t = qOneOffsets[i];
if (bit >= ForcedBits) break; qMod2k |= Pow2Provider.BigInteger(t);
qMod2k |= Pow2Provider.BigInteger(bit); qMaskWords[t >> 6] |= 1UL << (t & 63);
} }
// q must be odd for any Mersenne divisor candidate. // q must be odd for any Mersenne divisor candidate, and we always generate odd q candidates. No need to check it.
if (qMod2k.IsEven) // if (qMod2k.IsEven)
{ // {
Console.WriteLine("Pruned by qMod2k.IsEven"); // Console.WriteLine("Pruned by qMod2k.IsEven");
#if DETAILED_LOG // #if DETAILED_LOG
reason = ContradictionReason.ParityUnreachable; // reason = ContradictionReason.ParityUnreachable;
#endif // #endif
return true; // return true;
} // }
// Compute inv = q^{-1} mod 2^1024 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. BigInteger aLow = qMod2k; // OK seed for odd q.
int iters = 0;
for (int bits = 1; bits < ForcedBits; bits <<= 1) iters++;
for (int it = 0; it < iters; it++) // maxKnownA stands for bits here. We're reusing the variable to limit registry pressure.
int maxKnownA;
for (maxKnownA = 1; maxKnownA < ForcedBits; maxKnownA <<= 1)
{ {
inv = (inv * (2 - qMod2k * inv)) & mask; aLow = (aLow * (BigIntegerNumbers.Two - qMod2k * aLow)) & mask;
} }
// a ≡ -inv (mod 2^512) // a ≡ -aLow (mod 2^512)
BigInteger aLow = (-inv) & mask; aLow = (-aLow) & mask;
// Apply constraints for bits i in [0..min(511, maxAllowedA)] into knownOne0/knownZero0. // Apply constraints for bits i in [0..min(511, maxAllowedA)] into knownOne0/knownZero0.
// NOTE: now windowSize >= 512, so wordCount is enough to store these bits. // NOTE: now windowSize >= 512, so wordCount is enough to store these bits.
int maxFixed = maxAllowedA < (ForcedBits - 1) ? maxAllowedA : (ForcedBits - 1); int maxFixed = maxAllowedA < ForcedBitsMinusOne ? maxAllowedA : ForcedBitsMinusOne;
for (int i = 0; i <= maxFixed; i++) int word;
for (i = 0; i <= maxFixed; i++)
{ {
bool bitIsOne = !aLow.IsZero && ((aLow >> i) & BigInteger.One) != 0; bool bitIsOne = !aLow.IsZero && ((aLow >> i) & BigInteger.One) != 0;
int word = i >> 6; word = i >> 6;
ulong mask64 = 1UL << (i & 63); ulong mask64 = 1UL << (i & 63);
if (bitIsOne) if (bitIsOne)
@@ -1475,7 +1479,11 @@ internal static class BitContradictionSolverWithAMultiplier
} }
// We have learned bits up to maxFixed in the current segment. // We have learned bits up to maxFixed in the current segment.
if (maxFixed > maxKnownA) maxKnownA = maxFixed; maxKnownA = -1;
if (maxFixed > maxKnownA)
{
maxKnownA = maxFixed;
}
// ------------------------------------------------------------ // ------------------------------------------------------------
// Cheap low-column prefilter (prefix-style): // Cheap low-column prefilter (prefix-style):
@@ -1484,45 +1492,55 @@ internal static class BitContradictionSolverWithAMultiplier
// we can propagate carry deterministically and reject immediately on contradiction. // we can propagate carry deterministically and reject immediately on contradiction.
// This is significantly cheaper than entering the full row-aware window DP. // This is significantly cheaper than entering the full row-aware window DP.
// ------------------------------------------------------------ // ------------------------------------------------------------
const int PrefilterColumns = 512; // 256..512 is a good trade-off; keep <= 1024 forced bits
int preMax = PrefilterColumns - 1;
if (preMax > maxFixed)
{ {
const int PrefilterColumns = 512; // 256..512 is a good trade-off; keep <= 1024 forced bits preMax = maxFixed;
int preMax = PrefilterColumns - 1; }
if (preMax > maxFixed) preMax = maxFixed;
if (preMax > maxAllowedA) preMax = maxAllowedA;
// Do NOT clamp to maxOffset: for columns above maxOffset the full set of q=1 positions
// still contributes, and aLow still fixes the corresponding a-bits.
// Only run if we have at least a few columns to check. if (preMax > maxAllowedA)
if (preMax >= 8) {
preMax = maxAllowedA;
}
// Do NOT clamp to maxOffset: for columns above maxOffset the full set of q=1 positions
// still contributes, and aLow still fixes the corresponding a-bits.
// Only run if we have at least a few columns to check.
int col;
if (preMax >= 8)
{
CarryRange preCarry = CarryRange.Zero;
for (col = 0; col <= preMax; col++)
{ {
CarryRange preCarry = CarryRange.Zero; // Required result bit for M_p in columns [0..p-1] is 1.
for (int col = 0; col <= preMax; col++) const int requiredBit = 1;
{
// Required result bit for M_p in columns [0..p-1] is 1.
const int requiredBit = 1;
// Compute forced ones exactly: sum of a_{col - t} for all q_t=1 with t<=col. // Compute forced ones exactly: sum of a_{col - t} for all q_t=1 with t<=col.
int forced = 0; int forced = 0;
for (int i = 0; i < qOneOffsetsLength; i++) for (i = 0; i < qOneOffsetsLength; i++)
{
t = qOneOffsets[i];
if (t > col)
{ {
int t = qOneOffsets[i]; break;
if (t > col) break;
int aIndex = col - t;
// For this prefilter we only run where aIndex is guaranteed within [0..maxFixed].
int w = aIndex >> 6;
ulong m64 = 1UL << (aIndex & 63);
if ((knownOne0[w] & m64) != 0) forced++;
} }
// remaining==0 here, so possible==forced. Propagate carry range deterministically. int aIndex = col - t;
if (!TryPropagateCarry(ref preCarry, forced, forced, requiredBit)) // For this prefilter we only run where aIndex is guaranteed within [0..maxFixed].
{ word = aIndex >> 6;
ulong m64 = 1UL << (aIndex & 63);
if ((knownOne0[word] & m64) != 0) forced++;
}
// remaining==0 here, so possible==forced. Propagate carry range deterministically.
if (!TryPropagateCarry(ref preCarry, forced, forced, requiredBit))
{
#if DETAILED_LOG #if DETAILED_LOG
reason = ContradictionReason.ParityUnreachable; reason = ContradictionReason.ParityUnreachable;
#endif #endif
divides = false; divides = false;
return true; return true;
}
} }
} }
} }
@@ -1581,25 +1599,24 @@ internal static class BitContradictionSolverWithAMultiplier
// } // }
// int startIndex = usedPrefixCache ? prefixCount : 1; // int startIndex = usedPrefixCache ? prefixCount : 1;
int startIndex = 1; for (i = 1; i < qOneOffsetsLength; i++)
for (int i = startIndex; i < qOneOffsetsLength; i++)
{ {
int offset = qOneOffsets[i]; col = qOneOffsets[i];
if (offset == prev + 1) if (col == prev + 1)
{ {
currentLength++; currentLength++;
runLengths[runCount - 1] = currentLength; runLengths[runCount - 1] = currentLength;
} }
else else
{ {
runStarts[runCount] = offset; runStarts[runCount] = col;
runLengths[runCount] = 1; runLengths[runCount] = 1;
runCount++; runCount++;
currentStart = offset; currentStart = col;
currentLength = 1; currentLength = 1;
} }
prev = offset; prev = col;
} }
// if (!usedPrefixCache && prefixCount > 1) // if (!usedPrefixCache && prefixCount > 1)
@@ -1676,33 +1693,25 @@ internal static class BitContradictionSolverWithAMultiplier
} }
// Row-aware masks for q and a-window (supports q > 64 bits) // Row-aware masks for q and a-window (supports q > 64 bits)
int qBitLen = maxOffset + 1;
int qWordCount = (qBitLen + 63) >> 6;
workSet.EnsureCapacity(wordCount, qWordCount, qOneOffsetsLength);
ulong[] qMaskWords = workSet.QMaskWords;
Array.Clear(qMaskWords, 0, qWordCount);
for (int i = 0; i < qOneOffsetsLength; i++)
{
int t = qOneOffsets[i];
qMaskWords[t >> 6] |= 1UL << (t & 63);
}
ulong lastWordMask = (qBitLen & 63) == 0 ? ulong.MaxValue : ((1UL << (qBitLen & 63)) - 1UL); ulong lastWordMask = (qBitLen & 63) == 0 ? ulong.MaxValue : ((1UL << (qBitLen & 63)) - 1UL);
// Initialize window for column 0: mark negative a indices as zero (t>0).
// Sliding window over a: bit t corresponds to a_{column - t}. // Sliding window over a: bit t corresponds to a_{column - t}.
ulong[] aOneWin = workSet.AOneWin; ulong[] aOneWin = workSet.AOneWin;
ulong[] aZeroWin = workSet.AZeroWin; ulong[] aZeroWin = workSet.AZeroWin;
Array.Clear(aOneWin, 0, qWordCount); Array.Clear(aOneWin, 0, qWordCount);
Array.Clear(aZeroWin, 0, qWordCount); Array.Clear(aZeroWin, 0, qWordCount);
int windowColumn = 0; int windowColumn = 0;
// Initialize window for column 0: mark negative a indices as zero (t>0). Clearing is only needed when the
// arrays ever come from a pool, instead of creation.
// Array.Clear(aOneWin);
// Array.Clear(aZeroWin);
// Insert a_0 status into bit0 // Insert a_0 status into bit0
int st = GetAKnownStateRowAware(0, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); int st = GetAKnownStateRowAware(0, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1);
if (st == 1) aOneWin[0] |= 1UL; if (st == 1)
else if (st == 2) aZeroWin[0] |= 1UL; {
aOneWin[0] |= 1UL;
}
else if (st == 2)
{
aZeroWin[0] |= 1UL;
}
int lowColumn = 0; int lowColumn = 0;
int highColumn = pLong - 1; int highColumn = pLong - 1;
@@ -1805,7 +1814,7 @@ internal static class BitContradictionSolverWithAMultiplier
lowColumn, blockSize, lowColumn, blockSize,
ref carryLow, ref maxKnownA, ref segmentBase, ref carryLow, ref maxKnownA, ref segmentBase,
ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1, ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1,
windowSize, wordCount, aOneWin, aZeroWin, ref windowColumn)) windowSize, aWordCount, aOneWin, aZeroWin, ref windowColumn))
#endif #endif
{ {
#if DETAILED_LOG #if DETAILED_LOG
@@ -1921,7 +1930,7 @@ internal static class BitContradictionSolverWithAMultiplier
// [ThreadStatic] // [ThreadStatic]
// private static Dictionary<int, int[]> _stableUnknownDelta8; // private static Dictionary<int, int[]> _stableUnknownDelta8;
// Indeksowane przez "unknown" (zwykle qOneOffsetsLength). Elementy inicjalizowane leniwie. // Indexed by "unknown" (usually qOneOffsetsLength). Elements are lazy initialized.
private static volatile int[][]? _stableUnknownDelta8ByUnknown; private static volatile int[][]? _stableUnknownDelta8ByUnknown;
private static int[] GetStableUnknownDelta(int unknown) private static int[] GetStableUnknownDelta(int unknown)
@@ -2177,13 +2186,22 @@ internal static class BitContradictionSolverWithAMultiplier
{ {
ulong known = oneWin[w] | zeroWin[w]; ulong known = oneWin[w] | zeroWin[w];
ulong unk = qMask[w] & ~known; ulong unk = qMask[w] & ~known;
if (unk == 0) continue; if (unk == 0)
{
continue;
}
int pc = BitOperations.PopCount(unk); int pc = BitOperations.PopCount(unk);
found += pc; found += pc;
if (found > 1) return false; if (found > 1)
{
return false;
}
int bit = BitOperations.TrailingZeroCount(unk); int bit = BitOperations.TrailingZeroCount(unk);
offset = (w << 6) + bit; offset = (w << 6) + bit;
} }
return found == 1; return found == 1;
} }
@@ -2198,7 +2216,11 @@ internal static class BitContradictionSolverWithAMultiplier
in ulong[] knownZero1) in ulong[] knownZero1)
{ {
// 0 unknown, 1 one, 2 zero // 0 unknown, 1 one, 2 zero
if (aIndex < 0) return 2; if (aIndex < 0)
{
return 2;
}
int rel, w; int rel, w;
ulong m; ulong m;
if (aIndex >= segmentBase && aIndex < segmentBase + windowSize) if (aIndex >= segmentBase && aIndex < segmentBase + windowSize)
@@ -2206,8 +2228,16 @@ internal static class BitContradictionSolverWithAMultiplier
rel = aIndex - segmentBase; rel = aIndex - segmentBase;
w = rel >> 6; w = rel >> 6;
m = 1UL << (rel & 63); m = 1UL << (rel & 63);
if ((knownOne0[w] & m) != 0) return 1; if ((knownOne0[w] & m) != 0)
if ((knownZero0[w] & m) != 0) return 2; {
return 1;
}
if ((knownZero0[w] & m) != 0)
{
return 2;
}
return 0; return 0;
} }
@@ -2217,8 +2247,16 @@ internal static class BitContradictionSolverWithAMultiplier
rel = aIndex - w; rel = aIndex - w;
w = rel >> 6; w = rel >> 6;
m = 1UL << (rel & 63); m = 1UL << (rel & 63);
if ((knownOne1[w] & m) != 0) return 1; if ((knownOne1[w] & m) != 0)
if ((knownZero1[w] & m) != 0) return 2; {
return 1;
}
if ((knownZero1[w] & m) != 0)
{
return 2;
}
return 0; return 0;
} }
@@ -2340,19 +2378,22 @@ internal static class BitContradictionSolverWithAMultiplier
#endif #endif
{ {
bool needRebuildWindow = false; bool needRebuildWindow = false;
int endColumn = startColumn + columnCount; // columnCount is endColumn from here. We're reusing the variable to limit registry pressure.
int qOneOffsetsLength = qOneOffsets.Length; columnCount += startColumn;
int maxOffset = qOneOffsets[qOneOffsetsLength - 1]; int stableUnknown = qOneOffsets.Length;
int stableUnknown = qOneOffsetsLength; int maxOffset = qOneOffsets[stableUnknown - 1];
ulong[] temp; ulong[] temp;
// Ensure window is aligned. // Ensure window is aligned.
ArgumentOutOfRangeException.ThrowIfNotEqual(windowColumn, startColumn); ArgumentOutOfRangeException.ThrowIfNotEqual(windowColumn, startColumn);
int maxOffsetPlusOne = maxOffset + 1; int maxOffsetPlusOne = maxOffset + 1,
int qWordCountMinusOne = qWordCount - 1; qWordCountMinusOne = qWordCount - 1,
while (startColumn < endColumn) endColumnMinusOne = columnCount - 1;
while (startColumn < columnCount)
{ {
int aIndex, remaining, state, step;
// Segment rollover based on current column (because we may set aIndex within [startColumn-maxOffset..startColumn]). // Segment rollover based on current column (because we may set aIndex within [startColumn-maxOffset..startColumn]).
int bound = (startColumn / windowSize) * windowSize; int bound = (startColumn / windowSize) * windowSize;
@@ -2382,30 +2423,26 @@ internal static class BitContradictionSolverWithAMultiplier
Array.Clear(aZeroWin, 0, qWordCount); Array.Clear(aZeroWin, 0, qWordCount);
// Build window for current startColumn: bit t corresponds to a_{startColumn - t} // Build window for current startColumn: bit t corresponds to a_{startColumn - t}
for (int t = 0; t <= maxOffset; t++) for (step = 0; step <= maxOffset; step++)
{ {
int aIndex = startColumn - t; aIndex = startColumn - step;
int st = aIndex < 0 || aIndex > maxAllowedA state = aIndex < 0 || aIndex > maxAllowedA
? 2 ? 2
: GetAKnownStateRowAware(aIndex, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); : GetAKnownStateRowAware(aIndex, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1);
if (st == 1) if (state == 1)
{ {
aOneWin[t >> 6] |= 1UL << (t & 63); aOneWin[step >> 6] |= 1UL << (step & 63);
} }
else if (st == 2) else if (state == 2)
{ {
aZeroWin[t >> 6] |= 1UL << (t & 63); aZeroWin[step >> 6] |= 1UL << (step & 63);
} }
} }
if (qWordCount > 0) if (qWordCount > 0)
{ {
aOneWin[qWordCountMinusOne] &= lastWordMask; aOneWin[qWordCountMinusOne] &= lastWordMask;
}
if (qWordCount > 0)
{
aZeroWin[qWordCountMinusOne] &= lastWordMask; aZeroWin[qWordCountMinusOne] &= lastWordMask;
} }
@@ -2423,14 +2460,14 @@ internal static class BitContradictionSolverWithAMultiplier
// StableUnknown batching is valid only if the window contains no known bits in the q-active positions. // StableUnknown batching is valid only if the window contains no known bits in the q-active positions.
if (startColumn >= maxOffset && startColumn <= maxAllowedA) if (startColumn >= maxOffset && startColumn <= maxAllowedA)
{ {
int remainingCols = endColumn - 1; remaining = endColumnMinusOne;
if (remainingCols > maxAllowedA) if (remaining > maxAllowedA)
{ {
remainingCols = maxAllowedA; remaining = maxAllowedA;
} }
remainingCols = remainingCols - startColumn + 1; remaining = remaining - startColumn + 1;
if (remainingCols > 0) if (remaining > 0)
{ {
// Additionally, ensure current window has no known bits (should be true if maxKnownA condition holds). // 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. // Conservative batching: ignore row-aware window state and assume all q positions are unknown.
@@ -2440,13 +2477,12 @@ internal static class BitContradictionSolverWithAMultiplier
_statBatchCols += remainingCols; _statBatchCols += remainingCols;
#endif #endif
while (remainingCols > 0) while (remaining >= TailCarryBatchColumns)
{ {
int step = remainingCols > TailCarryBatchColumns ? TailCarryBatchColumns : remainingCols;
#if DETAILED_LOG #if DETAILED_LOG
if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step, out var propagateReason)) if (!TryAdvanceStableUnknown(ref carry, stableUnknown, TailCarryBatchColumns, out var propagateReason))
#else #else
if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step)) if (!TryAdvanceStableUnknown(ref carry, stableUnknown, TailCarryBatchColumns))
#endif #endif
{ {
#if DETAILED_LOG #if DETAILED_LOG
@@ -2455,9 +2491,27 @@ internal static class BitContradictionSolverWithAMultiplier
return false; return false;
} }
startColumn += step; startColumn += TailCarryBatchColumns;
windowColumn = startColumn;
remaining -= TailCarryBatchColumns;
}
if (remaining > 0)
{
#if DETAILED_LOG
if (!TryAdvanceStableUnknown(ref carry, stableUnknown, remainingCols, out var propagateReason))
#else
if (!TryAdvanceStableUnknown(ref carry, stableUnknown, remaining))
#endif
{
#if DETAILED_LOG
reason = propagateReason;
#endif
return false;
}
startColumn += remaining;
windowColumn = startColumn; windowColumn = startColumn;
remainingCols -= step;
} }
// Reset window to unknown (no known bits) because we skipped updating it across columns. // Reset window to unknown (no known bits) because we skipped updating it across columns.
@@ -2481,27 +2535,27 @@ internal static class BitContradictionSolverWithAMultiplier
// if (qWordCount > 0) aZeroWin[qWordCount - 1] &= lastWordMask; // if (qWordCount > 0) aZeroWin[qWordCount - 1] &= lastWordMask;
// } // }
ComputeForcedRemainingAndClean_Prefix(qMaskWords, aOneWin, aZeroWin, qWordsEff, qLastEff, out int forced, out int remaining); ComputeForcedRemainingAndClean_Prefix(qMaskWords, aOneWin, aZeroWin, qWordsEff, qLastEff, out int forced, out remaining);
// If exactly one unknown contribution among q=1 positions AND carry parity is fixed, // If exactly one unknown contribution among q=1 positions AND carry parity is fixed,
// we can force the corresponding a bit to satisfy the required result bit parity. // we can force the corresponding a bit to satisfy the required result bit parity.
if (remaining == 1 && ((carry.Min ^ carry.Max) & 1L) == 0) if (remaining == 1 && ((carry.Min ^ carry.Max) & 1L) == 0)
{ {
if (TryFindSingleUnknownInQ_Prefix(qMaskWords, aOneWin, aZeroWin, qWordsEff, qLastEff, out int t)) if (TryFindSingleUnknownInQ_Prefix(qMaskWords, aOneWin, aZeroWin, qWordsEff, qLastEff, out step))
{ {
int aIndex = startColumn - t; aIndex = startColumn - step;
// Required result bit for M_p in columns [0..p-1] is 1. // Required result bit for M_p in columns [0..p-1] is 1.
const int requiredBit = 1; const int requiredBit = 1;
// We need (carryIn + forced + x) % 2 == requiredBit, where x ∈ {0,1}. // We need (carryIn + forced + x) % 2 == requiredBit, where x ∈ {0,1}.
int carryParity = (int)(carry.Min & 1L); // fixed parity because Min/Max same parity int b = (int)(carry.Min & 1L); // fixed parity because Min/Max same parity
int x = (requiredBit ^ carryParity ^ (forced & 1)) & 1; // We're reusing qWordsEff as w from here to limit registry pressure
bool needOne = x != 0; qWordsEff = (requiredBit ^ b ^ (forced & 1)) & 1;
bool needOne = qWordsEff != 0;
int w, b;
// Check contradiction with already-known value. // Check contradiction with already-known value.
int state = GetAKnownStateRowAware(aIndex, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); state = GetAKnownStateRowAware(aIndex, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1);
if (needOne) if (needOne)
{ {
if (state == 2) if (state == 2)
@@ -2515,10 +2569,10 @@ internal static class BitContradictionSolverWithAMultiplier
SetAKnownRowAware(aIndex, true, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); SetAKnownRowAware(aIndex, true, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1);
// Reflect in window at offset t // Reflect in window at offset t
w = t >> 6; qWordsEff = step >> 6;
b = t & 63; b = step & 63;
aOneWin[w] |= 1UL << b; aOneWin[qWordsEff] |= 1UL << b;
forced += 1; forced++;
} }
else else
{ {
@@ -2532,9 +2586,9 @@ internal static class BitContradictionSolverWithAMultiplier
SetAKnownRowAware(aIndex, false, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); SetAKnownRowAware(aIndex, false, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1);
w = t >> 6; qWordsEff = step >> 6;
b = t & 63; b = step & 63;
aZeroWin[w] |= 1UL << b; aZeroWin[qWordsEff] |= 1UL << b;
} }
remaining = 0; remaining = 0;
@@ -2546,8 +2600,8 @@ internal static class BitContradictionSolverWithAMultiplier
} }
// Now propagate carry using forced and possible (=forced+remaining). // Now propagate carry using forced and possible (=forced+remaining).
int possible = forced + remaining; remaining += forced;
if (!TryPropagateCarry(ref carry, forced, possible, 1)) if (!TryPropagateCarry(ref carry, forced, remaining, 1))
{ {
#if DETAILED_LOG #if DETAILED_LOG
reason = ContradictionReason.ParityUnreachable; reason = ContradictionReason.ParityUnreachable;
@@ -2556,25 +2610,25 @@ internal static class BitContradictionSolverWithAMultiplier
} }
// Advance to next column: shift window, then insert status of a_{startColumn+1} into bit0. // Advance to next column: shift window, then insert status of a_{startColumn+1} into bit0.
int nextA = startColumn + 1; // remaining is nextA from here. We're reusing variable to limit registry pressure.
remaining = startColumn + 1;
ShiftLeft1InPlace(aOneWin, aZeroWin, qWordCount, lastWordMask); ShiftLeft1InPlace(aOneWin, aZeroWin, qWordCount, lastWordMask);
// Insert bit0 for nextA // Insert bit0 for nextA
int nextState; if (remaining > maxAllowedA)
if (nextA > maxAllowedA)
{ {
nextState = 2; state = 2;
} }
else else
{ {
nextState = GetAKnownStateRowAware(nextA, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); state = GetAKnownStateRowAware(remaining, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1);
} }
if (nextState == 1) if (state == 1)
{ {
aOneWin[0] |= 1UL; aOneWin[0] |= 1UL;
} }
else if (nextState == 2) else if (state == 2)
{ {
aZeroWin[0] |= 1UL; aZeroWin[0] |= 1UL;
} }
@@ -2988,17 +3042,17 @@ internal static class BitContradictionSolverWithAMultiplier
} }
} }
// if (topChanged) // if (topChanged)
// { // {
// var qHash = ComputeQOffsetsHash(qOneOffsets); // var qHash = ComputeQOffsetsHash(qOneOffsets);
// if (!TryDynamicModularRangePrune(qOneOffsets, qHash, prime, maxAllowedA, top0KnownOne, top0KnownZero, top1KnownOne, zeroTailStart)) // if (!TryDynamicModularRangePrune(qOneOffsets, qHash, prime, maxAllowedA, top0KnownOne, top0KnownZero, top1KnownOne, zeroTailStart))
// { // {
// #if DETAILED_LOG // #if DETAILED_LOG
// reason = ContradictionReason.ParityUnreachable; // reason = ContradictionReason.ParityUnreachable;
// #endif // #endif
// return false; // return false;
// } // }
// } // }
if ((top0KnownZero && top0KnownOne) || (top1KnownZero && top1KnownOne)) if ((top0KnownZero && top0KnownOne) || (top1KnownZero && top1KnownOne))
{ {
@@ -3169,7 +3223,7 @@ internal static class BitContradictionSolverWithAMultiplier
chunkEnd = endColumn; chunkEnd = endColumn;
} }
int steps = (int)(column - chunkEnd + 1); int steps = column - chunkEnd + 1;
if (steps > 1) if (steps > 1)
{ {
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps)) if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))