BitContradiction solvers improvements to increase early exclusions.

This commit is contained in:
mlemanczyk committed 2026-01-13 04:20:34 +01:00
1 parent c6dea4afcc
commit d0dbb8f92a
3 files changed
+466 -139

No files matched your search

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core.ByDivisor; namespace PerfectNumbers.Core.ByDivisor;
@@ -14,9 +15,85 @@ internal static class BitContradictionSolverWithAMultiplier
private const int TailCarryBatchColumns = 1024; private const int TailCarryBatchColumns = 1024;
private const int TailCarryBatchRepeats = 1; private const int TailCarryBatchRepeats = 1;
private const int ModLocalPrefilterMaxABits = 20;
private static readonly int[] ModLocalPrefilterModuli = { 65537, 65521 };
[ThreadStatic] [ThreadStatic]
private static TopDownPruneFailure? _lastTopDownFailure; private static TopDownPruneFailure? _lastTopDownFailure;
private static bool _debugEnabled = true; private static bool _debugEnabled = true;
private const int TopDownCacheCapacity = 16;
private readonly struct TopDownCacheKey(long hash, int length, long maxAllowedA, long pLong)
{
public readonly long Hash = hash;
public readonly int Length = length;
public readonly long MaxAllowedA = maxAllowedA;
public readonly long PLong = pLong;
public bool Equals(TopDownCacheKey other) => Hash == other.Hash && Length == other.Length && MaxAllowedA == other.MaxAllowedA && PLong == other.PLong;
public override bool Equals(object? obj) => obj is TopDownCacheKey other && Equals(other);
public override int GetHashCode() => HashCode.Combine(Hash, Length, MaxAllowedA, PLong);
}
private readonly struct TopDownCacheValue(bool success, TopDownPruneFailure? failure)
{
public readonly bool Success = success;
public readonly TopDownPruneFailure? Failure = failure;
}
[ThreadStatic]
private static Dictionary<TopDownCacheKey, TopDownCacheValue>? _topDownCache;
[ThreadStatic]
private static List<TopDownCacheKey>? _topDownCacheOrder;
private static long ComputeQOffsetsHash(ReadOnlySpan<int> qOneOffsets)
{
const long fnvOffset = unchecked((long)1469598103934665603);
const long fnvPrime = 1099511628211;
long hash = fnvOffset;
for (int i = 0; i < qOneOffsets.Length; i++)
{
hash ^= qOneOffsets[i];
hash *= fnvPrime;
}
return hash;
}
private static bool TryGetTopDownCache(in TopDownCacheKey key, out TopDownCacheValue value)
{
if (_topDownCache == null)
{
value = default;
return false;
}
return _topDownCache.TryGetValue(key, out value);
}
private static void StoreTopDownCache(in TopDownCacheKey key, in TopDownCacheValue value)
{
_topDownCache ??= new Dictionary<TopDownCacheKey, TopDownCacheValue>();
_topDownCacheOrder ??= new List<TopDownCacheKey>(TopDownCacheCapacity + 1);
if (_topDownCache.ContainsKey(key))
{
_topDownCache[key] = value;
return;
}
if (_topDownCacheOrder.Count >= TopDownCacheCapacity)
{
var evict = _topDownCacheOrder[0];
_topDownCacheOrder.RemoveAt(0);
_topDownCache.Remove(evict);
}
_topDownCache[key] = value;
_topDownCacheOrder.Add(key);
}
private static int _parityZeroLogCount; private static int _parityZeroLogCount;
internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure; internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure;
@@ -80,6 +157,109 @@ internal static class BitContradictionSolverWithAMultiplier
return true; return true;
} }
private static bool TryModularPrefilter(
ReadOnlySpan<int> qOneOffsets,
ulong p,
long maxAllowedA)
{
if (maxAllowedA < 0 || maxAllowedA > ModLocalPrefilterMaxABits)
{
return true;
}
int bitCount = (int)(maxAllowedA + 1);
if (bitCount <= 0 || bitCount >= 62)
{
return true;
}
long maxAValue = (1L << bitCount) - 1L;
for (int i = 0; i < ModLocalPrefilterModuli.Length; i++)
{
int mod = ModLocalPrefilterModuli[i];
int qMod = ComputeQMod(qOneOffsets, mod);
int target = ModPow2Minus1(p, mod);
if (qMod == 0)
{
if (target != 0)
{
return false;
}
continue;
}
int inverse = ModPow(qMod, mod - 2, mod);
long x = (long)((long)target * inverse % mod);
if (x > maxAValue)
{
return false;
}
}
return true;
}
private static int ComputeQMod(ReadOnlySpan<int> qOneOffsets, int mod)
{
long sum = 0;
for (int i = 0; i < qOneOffsets.Length; i++)
{
sum += PowMod2((ulong)qOneOffsets[i], mod);
if (sum >= mod)
{
sum %= mod;
}
}
return (int)(sum % mod);
}
private static int ModPow2Minus1(ulong p, int mod)
{
int pow = PowMod2(p, mod);
int result = pow - 1;
return result < 0 ? result + mod : result;
}
private static int PowMod2(ulong exp, int mod)
{
long result = 1 % mod;
long baseVal = 2 % mod;
ulong value = exp;
while (value > 0)
{
if ((value & 1) != 0)
{
result = (result * baseVal) % mod;
}
baseVal = (baseVal * baseVal) % mod;
value >>= 1;
}
return (int)result;
}
private static int ModPow(int value, int exp, int mod)
{
long result = 1 % mod;
long baseVal = value % mod;
int e = exp;
while (e > 0)
{
if ((e & 1) != 0)
{
result = (result * baseVal) % mod;
}
baseVal = (baseVal * baseVal) % mod;
e >>= 1;
}
return (int)result;
}
/// <summary> /// <summary>
/// Full column-wise propagation for a given set of q one-bit offsets (LSB=0) and exponent p. /// 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). /// Returns true if the solver could decide locally (divides or contradiction).
@@ -145,16 +325,44 @@ internal static class BitContradictionSolverWithAMultiplier
return true; return true;
} }
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure)) if (!TryModularPrefilter(qOneOffsets, p, maxAllowedA))
{ {
reason = ContradictionReason.ParityUnreachable; reason = ContradictionReason.ParityUnreachable;
return true; return true;
} }
long qHash = ComputeQOffsetsHash(qOneOffsets);
var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong);
if (TryGetTopDownCache(in cacheKey, out var cached))
{
_lastTopDownFailure = cached.Failure;
if (!cached.Success)
{
reason = ContradictionReason.ParityUnreachable;
return true;
}
}
else
{
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
{
StoreTopDownCache(in cacheKey, new TopDownCacheValue(false, _lastTopDownFailure));
reason = ContradictionReason.ParityUnreachable;
return true;
}
StoreTopDownCache(in cacheKey, new TopDownCacheValue(true, null));
}
long lowColumn = 0; long lowColumn = 0;
long highColumn = pLong - 1; long highColumn = pLong - 1;
CarryRange carryLow = CarryRange.Zero; CarryRange carryLow = CarryRange.Zero;
CarryRange carryHigh = CarryRange.Zero; CarryRange carryHigh = CarryRange.Zero;
int bottomWindowStart = 0;
int bottomWindowEnd = 0;
int topWindowStart = qOneOffsetsLength;
int topWindowEnd = qOneOffsetsLength;
bool processTop = true; bool processTop = true;
while (lowColumn <= highColumn) while (lowColumn <= highColumn)
@@ -164,7 +372,7 @@ internal static class BitContradictionSolverWithAMultiplier
if (processTop) if (processTop)
{ {
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, out carryHigh, out _lastTopDownFailure)) if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh, out _lastTopDownFailure))
{ {
reason = ContradictionReason.ParityUnreachable; reason = ContradictionReason.ParityUnreachable;
return true; return true;
@@ -174,7 +382,7 @@ internal static class BitContradictionSolverWithAMultiplier
} }
else else
{ {
if (!TryProcessBottomUpBlock(qOneOffsets, maxAllowedA, lowColumn, blockSize, ref carryLow, ref maxKnownA, ref segmentBase, segmentState0, segmentState1, out reason)) if (!TryProcessBottomUpBlock(qOneOffsets, maxAllowedA, lowColumn, blockSize, ref carryLow, ref maxKnownA, ref segmentBase, ref bottomWindowStart, ref bottomWindowEnd, segmentState0, segmentState1, out reason))
{ {
return true; return true;
} }
@@ -237,6 +445,8 @@ internal static class BitContradictionSolverWithAMultiplier
ref CarryRange carry, ref CarryRange carry,
ref long maxKnownA, ref long maxKnownA,
ref long segmentBase, ref long segmentBase,
ref int windowStartIdx,
ref int windowEndIdx,
sbyte[] segmentState0, sbyte[] segmentState0,
sbyte[] segmentState1, sbyte[] segmentState1,
out ContradictionReason reason) out ContradictionReason reason)
@@ -244,8 +454,53 @@ internal static class BitContradictionSolverWithAMultiplier
long endColumn = startColumn + columnCount; long endColumn = startColumn + columnCount;
int qOneOffsetsLength = qOneOffsets.Length; int qOneOffsetsLength = qOneOffsets.Length;
int windowSize = segmentState0.Length; int windowSize = segmentState0.Length;
for (long column = startColumn; column < endColumn; column++) int maxOffset = qOneOffsets[qOneOffsetsLength - 1];
long stableUnknown = qOneOffsetsLength;
long column = startColumn;
while (column < endColumn)
{ {
if (column >= maxOffset && column <= maxAllowedA && maxKnownA < column - maxOffset)
{
long batchEnd = endColumn - 1;
if (batchEnd > maxAllowedA)
{
batchEnd = maxAllowedA;
}
long remaining = batchEnd - column + 1;
if (remaining > 0)
{
windowStartIdx = 0;
windowEndIdx = qOneOffsetsLength;
while (remaining > 0)
{
int step = remaining > TailCarryBatchColumns ? TailCarryBatchColumns : (int)remaining;
for (int i = 0; i < step; i++)
{
if (!TryPropagateCarry(carry, 0, stableUnknown, 1, out var next, out var propagateReason))
{
reason = propagateReason;
return false;
}
carry = next;
}
column += step;
remaining -= step;
}
long batchBase = (column / windowSize) * windowSize;
if (batchBase != segmentBase)
{
Array.Clear(segmentState0);
Array.Clear(segmentState1);
segmentBase = batchBase;
}
continue;
}
}
long forced = 0L; long forced = 0L;
long unknown = 0L; long unknown = 0L;
long chosenIndex = -1L; long chosenIndex = -1L;
@@ -266,7 +521,18 @@ internal static class BitContradictionSolverWithAMultiplier
segmentBase = newBase; segmentBase = newBase;
} }
for (int offsetIndex = 0; offsetIndex < qOneOffsetsLength; offsetIndex++) while (windowEndIdx < qOneOffsetsLength && qOneOffsets[windowEndIdx] <= column)
{
windowEndIdx++;
}
long lowerBound = column - maxAllowedA;
while (windowStartIdx < windowEndIdx && qOneOffsets[windowStartIdx] < lowerBound)
{
windowStartIdx++;
}
for (int offsetIndex = windowStartIdx; offsetIndex < windowEndIdx; offsetIndex++)
{ {
int offset = qOneOffsets[offsetIndex]; int offset = qOneOffsets[offsetIndex];
long aIndex = column - offset; long aIndex = column - offset;
@@ -418,6 +684,7 @@ internal static class BitContradictionSolverWithAMultiplier
carry = next; carry = next;
} }
column++;
} }
reason = ContradictionReason.None; reason = ContradictionReason.None;
@@ -431,6 +698,8 @@ internal static class BitContradictionSolverWithAMultiplier
long startHighColumn, long startHighColumn,
int columnCount, int columnCount,
CarryRange carryOut, CarryRange carryOut,
ref int windowStart,
ref int windowEnd,
out CarryRange nextCarryOut, out CarryRange nextCarryOut,
out TopDownPruneFailure? failure) out TopDownPruneFailure? failure)
{ {
@@ -438,8 +707,6 @@ internal static class BitContradictionSolverWithAMultiplier
long carryOutMin = carryOut.Min, long carryOutMin = carryOut.Min,
carryOutMax = carryOut.Max, carryOutMax = carryOut.Max,
value; value;
int windowStart = qOneOffsetsLength;
int windowEnd = qOneOffsetsLength;
long endColumn = startHighColumn - columnCount + 1; long endColumn = startHighColumn - columnCount + 1;
long column = startHighColumn; long column = startHighColumn;
@@ -560,11 +827,11 @@ internal static class BitContradictionSolverWithAMultiplier
out TopDownPruneFailure? failure) out TopDownPruneFailure? failure)
{ {
failure = null; failure = null;
int windowStart = qOneOffsetsLength;
int windowEnd = qOneOffsetsLength;
long carryOutMin = 0, long carryOutMin = 0,
carryOutMax = 0, carryOutMax = 0,
value; value;
int windowStart = qOneOffsetsLength;
int windowEnd = qOneOffsetsLength;
long column = pLong - 1; long column = pLong - 1;
while (column >= 0) while (column >= 0)
@@ -52,6 +52,79 @@ internal static class BitContradictionSolverWithQMultiplier
[ThreadStatic] [ThreadStatic]
private static bool _debugEnabled; private static bool _debugEnabled;
private const int TopDownCacheCapacity = 16;
private readonly struct TopDownCacheKey(long hash, int length, long maxAllowedA, long pLong)
{
public readonly long Hash = hash;
public readonly int Length = length;
public readonly long MaxAllowedA = maxAllowedA;
public readonly long PLong = pLong;
public bool Equals(TopDownCacheKey other) => Hash == other.Hash && Length == other.Length && MaxAllowedA == other.MaxAllowedA && PLong == other.PLong;
public override bool Equals(object? obj) => obj is TopDownCacheKey other && Equals(other);
public override int GetHashCode() => HashCode.Combine(Hash, Length, MaxAllowedA, PLong);
}
private readonly struct TopDownCacheValue(bool success, TopDownPruneFailure? failure)
{
public readonly bool Success = success;
public readonly TopDownPruneFailure? Failure = failure;
}
[ThreadStatic]
private static Dictionary<TopDownCacheKey, TopDownCacheValue>? _topDownCache;
[ThreadStatic]
private static List<TopDownCacheKey>? _topDownCacheOrder;
private static long ComputeQOffsetsHash(ReadOnlySpan<int> qOneOffsets)
{
const long fnvOffset = unchecked((long)1469598103934665603);
const long fnvPrime = 1099511628211;
long hash = fnvOffset;
for (int i = 0; i < qOneOffsets.Length; i++)
{
hash ^= qOneOffsets[i];
hash *= fnvPrime;
}
return hash;
}
private static bool TryGetTopDownCache(in TopDownCacheKey key, out TopDownCacheValue value)
{
if (_topDownCache == null)
{
value = default;
return false;
}
return _topDownCache.TryGetValue(key, out value);
}
private static void StoreTopDownCache(in TopDownCacheKey key, in TopDownCacheValue value)
{
_topDownCache ??= new Dictionary<TopDownCacheKey, TopDownCacheValue>();
_topDownCacheOrder ??= new List<TopDownCacheKey>(TopDownCacheCapacity + 1);
if (_topDownCache.ContainsKey(key))
{
_topDownCache[key] = value;
return;
}
if (_topDownCacheOrder.Count >= TopDownCacheCapacity)
{
var evict = _topDownCacheOrder[0];
_topDownCacheOrder.RemoveAt(0);
_topDownCache.Remove(evict);
}
_topDownCache[key] = value;
_topDownCacheOrder.Add(key);
}
internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure; internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure;
internal static BottomUpFailure? LastBottomUpFailure => _lastBottomUpFailure; internal static BottomUpFailure? LastBottomUpFailure => _lastBottomUpFailure;
@@ -188,17 +261,40 @@ internal static class BitContradictionSolverWithQMultiplier
return true; return true;
} }
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure)) long qHash = ComputeQOffsetsHash(qOneOffsets);
var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong);
if (TryGetTopDownCache(in cacheKey, out var cached))
{
_lastTopDownFailure = cached.Failure;
if (!cached.Success)
{ {
reason = ContradictionReason.ParityUnreachable; reason = ContradictionReason.ParityUnreachable;
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})"); Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
return true; return true;
} }
}
else
{
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
{
StoreTopDownCache(in cacheKey, new TopDownCacheValue(false, _lastTopDownFailure));
reason = ContradictionReason.ParityUnreachable;
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
return true;
}
StoreTopDownCache(in cacheKey, new TopDownCacheValue(true, null));
}
long lowColumn = 0; long lowColumn = 0;
long highColumn = pLong - 1; long highColumn = pLong - 1;
CarryRange carryLow = CarryRange.Zero; CarryRange carryLow = CarryRange.Zero;
CarryRange carryHigh = CarryRange.Zero; CarryRange carryHigh = CarryRange.Zero;
int bottomWindowStart = 0;
int bottomWindowEnd = 0;
int topWindowStart = qOneOffsetsLength;
int topWindowEnd = qOneOffsetsLength;
bool processTop = true; bool processTop = true;
while (lowColumn <= highColumn) while (lowColumn <= highColumn)
@@ -208,7 +304,7 @@ internal static class BitContradictionSolverWithQMultiplier
if (processTop) if (processTop)
{ {
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, out carryHigh, out _lastTopDownFailure)) if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh, out _lastTopDownFailure))
{ {
reason = ContradictionReason.ParityUnreachable; reason = ContradictionReason.ParityUnreachable;
return true; return true;
@@ -218,7 +314,7 @@ internal static class BitContradictionSolverWithQMultiplier
} }
else else
{ {
if (!TryProcessBottomUpBlock(qOneOffsets, maxAllowedA, lowColumn, blockSize, ref carryLow, ref segmentBase, segmentState0, segmentState1, out reason)) if (!TryProcessBottomUpBlock(qOneOffsets, maxAllowedA, lowColumn, blockSize, ref carryLow, ref segmentBase, ref bottomWindowStart, ref bottomWindowEnd, segmentState0, segmentState1, out reason))
{ {
return true; return true;
} }
@@ -281,6 +377,8 @@ internal static class BitContradictionSolverWithQMultiplier
int columnCount, int columnCount,
ref CarryRange carry, ref CarryRange carry,
ref long segmentBase, ref long segmentBase,
ref int windowStartIdx,
ref int windowEndIdx,
sbyte[] segmentState0, sbyte[] segmentState0,
sbyte[] segmentState1, sbyte[] segmentState1,
out ContradictionReason reason) out ContradictionReason reason)
@@ -310,7 +408,18 @@ internal static class BitContradictionSolverWithQMultiplier
segmentBase = newBase; segmentBase = newBase;
} }
for (int offsetIndex = 0; offsetIndex < qOneOffsetsLength; offsetIndex++) while (windowEndIdx < qOneOffsetsLength && qOneOffsets[windowEndIdx] <= column)
{
windowEndIdx++;
}
long lowerBound = column - maxAllowedA;
while (windowStartIdx < windowEndIdx && qOneOffsets[windowStartIdx] < lowerBound)
{
windowStartIdx++;
}
for (int offsetIndex = windowStartIdx; offsetIndex < windowEndIdx; offsetIndex++)
{ {
int offset = qOneOffsets[offsetIndex]; int offset = qOneOffsets[offsetIndex];
long aIndex = column - offset; long aIndex = column - offset;
@@ -480,6 +589,8 @@ internal static class BitContradictionSolverWithQMultiplier
long startHighColumn, long startHighColumn,
int columnCount, int columnCount,
CarryRange carryOut, CarryRange carryOut,
ref int windowStart,
ref int windowEnd,
out CarryRange nextCarryOut, out CarryRange nextCarryOut,
out TopDownPruneFailure? failure) out TopDownPruneFailure? failure)
{ {
@@ -487,8 +598,6 @@ internal static class BitContradictionSolverWithQMultiplier
long carryOutMin = carryOut.Min, long carryOutMin = carryOut.Min,
carryOutMax = carryOut.Max, carryOutMax = carryOut.Max,
value; value;
int windowStart = qOneOffsetsLength;
int windowEnd = qOneOffsetsLength;
long endColumn = startHighColumn - columnCount + 1; long endColumn = startHighColumn - columnCount + 1;
long column = startHighColumn; long column = startHighColumn;
@@ -612,6 +721,7 @@ internal static class BitContradictionSolverWithQMultiplier
long carryOutMin = 0, long carryOutMin = 0,
carryOutMax = 0, carryOutMax = 0,
value; value;
int windowStart = qOneOffsetsLength; int windowStart = qOneOffsetsLength;
int windowEnd = qOneOffsetsLength; int windowEnd = qOneOffsetsLength;
@@ -1398,7 +1398,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryExactBitTreeCheck(ulong exponent, BigInteger divisor, out bool divides) private bool TryExactBitTreeCheck(ulong exponent, BigInteger divisor, out bool divides)
{ {
#if DETAILED_LOG
Stopwatch sw = Stopwatch.StartNew(); Stopwatch sw = Stopwatch.StartNew();
#endif
int qBitLength = GetBitLengthPortable(divisor); int qBitLength = GetBitLengthPortable(divisor);
int aBitLength = GetBitLengthPortable(exponent); int aBitLength = GetBitLengthPortable(exponent);
@@ -1504,13 +1506,17 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return true; return true;
} }
} }
#if DETAILED_LOG
sw.Stop(); sw.Stop();
#endif
ArrayPool<byte>.Shared.Return(aWindowArray); ArrayPool<byte>.Shared.Return(aWindowArray);
divides = carry == 0; divides = carry == 0;
#if DETAILED_LOG
var elapsed = sw.Elapsed; var elapsed = sw.Elapsed;
Console.WriteLine($"[bittree ({elapsed})] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}"); Console.WriteLine($"[bittree ({elapsed})] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}");
#endif
if (!divides) if (!divides)
{ {
string message = $"[bittree] p={exponent} ruled out divisor={divisor} (parity/carry exhaustion)"; string message = $"[bittree] p={exponent} ruled out divisor={divisor} (parity/carry exhaustion)";
@@ -4326,6 +4332,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
int kRem8 = (int)(currentK % 8); int kRem8 = (int)(currentK % 8);
BigInteger divisor = (step * currentK) + BigInteger.One; BigInteger divisor = (step * currentK) + BigInteger.One;
Stopwatch sw = new Stopwatch();
while (true) while (true)
{ {
int remainder8 = (stepMod8 * kRem8 + 1) & 7; int remainder8 = (stepMod8 * kRem8 + 1) & 7;
@@ -4342,80 +4349,14 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
goto MOVE_NEXT; goto MOVE_NEXT;
} }
bool divides;
// divides = BigInteger.ModPow(BigIntegerNumbers.Two, prime, divisor) == 1;
ulong effectiveExponent = 0UL; ulong effectiveExponent = 0UL;
// if (divisor <= ulong.MaxValue) sw.Reset();
// { sw.Start();
// ulong divisor64 = (ulong)divisor; bool decided = TryExactBitContradictionCheck(prime, divisor, effectiveExponent, out divides);
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64); sw.Stop();
// #if DEVICE_GPU
// bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
// divisor64,
// Accelerator,
// prime,
// divisorData,
// out ulong computedCycle,
// out bool primeOrderFailed);
// #elif DEVICE_HYBRID
// bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
// divisor64,
// Accelerator,
// prime,
// divisorData,
// out ulong computedCycle,
// out bool primeOrderFailed);
// #else
// bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
// divisor64,
// prime,
// divisorData,
// out ulong computedCycle,
// out bool primeOrderFailed);
// #endif
// ulong divisorCycle;
// if (!computed || computedCycle == 0UL)
// {
// #if DEVICE_GPU
// divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
// divisor64,
// Accelerator,
// divisorData,
// skipPrimeOrderHeuristic: primeOrderFailed);
// #elif DEVICE_HYBRID
// divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
// divisor64,
// Accelerator,
// divisorData,
// skipPrimeOrderHeuristic: primeOrderFailed);
// #else
// divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
// divisor64,
// divisorData,
// skipPrimeOrderHeuristic: primeOrderFailed);
// #endif
// }
// else
// {
// divisorCycle = computedCycle;
// }
// if (divisorCycle > 0UL && (prime % divisorCycle) != 0UL)
// {
// if (hasLimit && currentK >= maxK)
// {
// break;
// }
// currentK += BigInteger.One;
// continue;
// }
// if (divisorCycle > 0UL)
// {
// effectiveExponent = divisorCycle;
// }
// }
bool decided = TryExactBitContradictionCheck(prime, divisor, effectiveExponent, out bool divides);
if (!decided) if (!decided)
{ {
throw new InvalidOperationException($"BitContradiction check did not decide for p={prime}, divisor={divisor} (k={currentK})."); throw new InvalidOperationException($"BitContradiction check did not decide for p={prime}, divisor={divisor} (k={currentK}).");
@@ -4426,6 +4367,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
foundDivisor = divisor; foundDivisor = divisor;
return true; return true;
} }
else {
Console.WriteLine($"Prime {prime} ({sw.Elapsed}), excluded {divisor}");
}
RecordState(currentK); RecordState(currentK);
MOVE_NEXT: MOVE_NEXT:
@@ -4441,63 +4385,65 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return false; return false;
bool fits64 = minK <= ulong.MaxValue; // bool fits64 = minK <= ulong.MaxValue;
bool limitFits64 = allowedMax.IsZero || allowedMax <= ulong.MaxValue; // bool limitFits64 = allowedMax.IsZero || allowedMax <= ulong.MaxValue;
if (fits64 && limitFits64) // if (fits64 && limitFits64)
{ // {
ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (ulong)allowedMax; // ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (ulong)allowedMax;
return CheckDivisors64Bit( // return CheckDivisors64Bit(
prime, // prime,
primeDecimalMask, // primeDecimalMask,
allowedMax64, // allowedMax64,
(ulong)minK, // (ulong)minK,
out processedAll, // out processedAll,
out ulong found64); // out ulong found64);
} // }
if (fits64 && allowedMax > ulong.MaxValue) // if (fits64 && allowedMax > ulong.MaxValue)
{ // {
ulong allowedMax64 = ulong.MaxValue; // ulong allowedMax64 = ulong.MaxValue;
bool composite64 = CheckDivisors64Bit( // bool composite64 = CheckDivisors64Bit(
prime, // prime,
primeDecimalMask, // primeDecimalMask,
allowedMax64, // allowedMax64,
(ulong)minK, // (ulong)minK,
out processedAll, // out processedAll,
out ulong found64); // out ulong found64);
if (composite64) // if (composite64)
{ // {
foundDivisor = found64; // foundDivisor = found64;
return true; // return true;
} // }
BigInteger iterations = ((BigInteger)(allowedMax64 - (((ulong)prime << 1) * (ulong)minK) - 1UL) / (ulong)step) + BigInteger.One; // BigInteger iterations = ((BigInteger)(allowedMax64 - (((ulong)prime << 1) * (ulong)minK) - 1UL) / (ulong)step) + BigInteger.One;
BigInteger nextK = minK + iterations; // BigInteger nextK = minK + iterations;
return CheckDivisorsLarge( // return CheckDivisorsLarge(
prime, // prime,
primeDecimalMask, // primeDecimalMask,
allowedMax, // allowedMax,
nextK, // nextK,
step, // step,
out processedAll, // out processedAll,
out foundDivisor); // out foundDivisor);
} // }
return CheckDivisorsLarge( // return CheckDivisorsLarge(
prime, // prime,
primeDecimalMask, // primeDecimalMask,
allowedMax, // allowedMax,
minK, // minK,
step, // step,
out processedAll, // out processedAll,
out foundDivisor); // out foundDivisor);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides) private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides)
{ {
#if DETAILED_LOG
Stopwatch sw = Stopwatch.StartNew(); Stopwatch sw = Stopwatch.StartNew();
#endif
divides = false; divides = false;
// const ulong DebugTopDownDivisor = 1209708008767UL; // const ulong DebugTopDownDivisor = 1209708008767UL;
// const ulong DebugTopDownDivisor = 1007991562955719UL; // const ulong DebugTopDownDivisor = 1007991562955719UL;
@@ -4551,7 +4497,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
ReadOnlySpan<int> oneOffsetsSlice = oneOffsets[..oneCount]; ReadOnlySpan<int> oneOffsetsSlice = oneOffsets[..oneCount];
ulong exponent = effectiveExponent > 0UL ? effectiveExponent : prime; ulong exponent = effectiveExponent > 0UL ? effectiveExponent : prime;
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(true); // BitContradictionSolverWithQMultiplier.SetDebugEnabled(true);
bool decided = BitContradictionSolverWithQMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason); bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(false); // BitContradictionSolverWithQMultiplier.SetDebugEnabled(false);
// bool decided = BitContradictionSolverBaseline.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason); // bool decided = BitContradictionSolverBaseline.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
if (decided && divides) if (decided && divides)
@@ -4566,9 +4512,12 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
} }
} }
#if DETAILED_LOG
sw.Stop(); sw.Stop();
#endif
if (decided && !divides) if (decided && !divides)
{ {
#if DETAILED_LOG
if (reason == ContradictionReason.ParityUnreachable) if (reason == ContradictionReason.ParityUnreachable)
{ {
var failure = BitContradictionSolverWithQMultiplier.LastTopDownFailure; var failure = BitContradictionSolverWithQMultiplier.LastTopDownFailure;
@@ -4585,13 +4534,14 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
Console.WriteLine($"[bit-contradiction] bottom-up failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] forced={info.Forced} unknown={info.Unknown}"); Console.WriteLine($"[bit-contradiction] bottom-up failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] forced={info.Forced} unknown={info.Unknown}");
} }
} }
#endif
string message = $"[bit-contradiction] p={prime} ruled out divisor={divisor} ({reason})"; _bitContradictionStateRepository?.Upsert(prime, "excluded");
// Console.WriteLine(message);
_bitContradictionStateRepository?.Upsert(prime, message);
} }
#if DETAILED_LOG
Console.WriteLine($"[bitcontradiction ({sw.Elapsed})] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides} Reason={reason}"); Console.WriteLine($"[bitcontradiction ({sw.Elapsed})] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides} Reason={reason}");
#endif
return decided; return decided;
} }
#endif #endif