From 2b803abcac92090688e2c5bcc0c3aea8354f1d49 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 12 Jan 2026 01:49:10 +0100 Subject: [PATCH] BitContradiction mode modified to check for contradictions from both sides. --- .../BitContradictionSolverTests.cs | 26 +- .../ByDivisor/BitContradictionSolver.cs | 532 ++++++++++++------ .../BitContradictionTailCarryLut8.cs | 88 +++ ...erDivisorByDivisorCpuTesterWithTemplate.cs | 39 +- 4 files changed, 484 insertions(+), 201 deletions(-) create mode 100644 PerfectNumbers.Core/ByDivisor/BitContradictionTailCarryLut8.cs diff --git a/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs b/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs index f4936f7..a986c85 100644 --- a/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs +++ b/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs @@ -10,50 +10,46 @@ public class BitContradictionSolverTests [Fact] public void ComputeColumnBounds_TracksForcedAndPossibleOnes() { - BitState[] a = [BitState.One, BitState.Unknown]; - BitState[] q = [BitState.One, BitState.One]; + bool[] a = [true, false]; + bool[] q = [true, true]; ColumnBounds bounds = ComputeColumnBounds(a, q, 1); bounds.ForcedOnes.Should().Be(1); - bounds.PossibleOnes.Should().Be(2); + bounds.PossibleOnes.Should().Be(1); } [Fact] public void TryPropagateCarry_FailsWhenParityUnreachable() { - CarryRange carry = CarryRange.Single(0); - bool success = TryPropagateCarry( - carry, + currentCarry: CarryRange.Single(0), forcedOnes: 0, possibleOnes: 0, requiredResultBit: 1, - out CarryRange next, + out var nextCarry, out ContradictionReason reason); success.Should().BeFalse(); reason.Should().Be(ContradictionReason.ParityUnreachable); - next.Min.Should().Be(0); - next.Max.Should().Be(0); + nextCarry.Min.Should().Be(0); + nextCarry.Max.Should().Be(0); } [Fact] public void TryPropagateCarry_ComputesNextCarryRange() { - CarryRange carry = new(0, 1); - bool success = TryPropagateCarry( - carry, + currentCarry: new CarryRange(0, 1), forcedOnes: 1, possibleOnes: 3, requiredResultBit: 1, - out CarryRange next, + out var nextCarry, out ContradictionReason reason); success.Should().BeTrue(); reason.Should().Be(ContradictionReason.None); - next.Min.Should().Be(0); - next.Max.Should().Be(1); + nextCarry.Min.Should().Be(0); + nextCarry.Max.Should().Be(1); } } diff --git a/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs b/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs index 606466a..6f860e6 100644 --- a/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs +++ b/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace PerfectNumbers.Core.ByDivisor; @@ -9,57 +10,38 @@ namespace PerfectNumbers.Core.ByDivisor; /// internal static class BitContradictionSolver { - internal enum BitState + private const int PingPongBlockColumns = 64; + private const int TailCarryBatchColumns = 1024; + private const int TailCarryBatchRepeats = 1; + + [ThreadStatic] + private static Dictionary? _aBitsBuffer; + [ThreadStatic] + private static TopDownPruneFailure? _lastTopDownFailure; + + internal readonly struct TopDownPruneFailure(long column, long carryMin, long carryMax, long unknown) { - Zero = 0, - One = 1, - Unknown = 2, + public readonly long Column = column; + public readonly long CarryMin = carryMin; + public readonly long CarryMax = carryMax; + public readonly long Unknown = unknown; } - internal readonly struct ColumnBounds - { - public readonly long ForcedOnes; - public readonly long PossibleOnes; + internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure; - public ColumnBounds(long forcedOnes, long possibleOnes) - { - if (forcedOnes < 0) - { - throw new ArgumentOutOfRangeException(nameof(forcedOnes)); - } + internal readonly struct ColumnBounds(long forcedOnes, long possibleOnes) + { + public readonly long ForcedOnes = forcedOnes; + public readonly long PossibleOnes = possibleOnes; + } - if (possibleOnes < forcedOnes) - { - throw new ArgumentOutOfRangeException(nameof(possibleOnes), "possibleOnes must be >= forcedOnes."); - } + internal readonly struct CarryRange(long min, long max) + { + public readonly long Min = min; + public readonly long Max = max; - ForcedOnes = forcedOnes; - PossibleOnes = possibleOnes; - } - } - - internal readonly struct CarryRange - { - public readonly long Min; - public readonly long Max; - - public CarryRange(long min, long max) - { - if (min < 0) - { - throw new ArgumentOutOfRangeException(nameof(min)); - } - - if (max < min) - { - throw new ArgumentOutOfRangeException(nameof(max), "max must be >= min."); - } - - Min = min; - Max = max; - } - - public static CarryRange Single(long value) => new(value, value); + public static CarryRange Single(long value) => new(value, value); + public static readonly CarryRange Zero = Single(0); } internal enum ContradictionReason @@ -71,33 +53,27 @@ internal static class BitContradictionSolver } internal static ColumnBounds ComputeColumnBounds( - ReadOnlySpan multiplicand, - ReadOnlySpan multiplier, + ReadOnlySpan multiplicand, + ReadOnlySpan multiplier, int columnIndex) { - if (columnIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(columnIndex)); - } - long forced = 0; long possible = 0; + int multiplicandLength = multiplicand.Length; + int multiplierLength = multiplier.Length; for (int i = 0; i <= columnIndex; i++) { - BitState a = i < multiplicand.Length ? multiplicand[i] : BitState.Zero; - BitState q = columnIndex - i < multiplier.Length ? multiplier[columnIndex - i] : BitState.Zero; - - if (a == BitState.Zero || q == BitState.Zero) + bool a = i < multiplicandLength && multiplicand[i]; + bool q = columnIndex - i < multiplierLength && multiplier[columnIndex - i]; + if (!a && !q) { continue; } - if (a == BitState.One && q == BitState.One) + if (a && q) { forced++; - possible++; - continue; } possible++; @@ -114,26 +90,21 @@ internal static class BitContradictionSolver out CarryRange nextCarry, out ContradictionReason reason) { - if (forcedOnes < 0 || possibleOnes < forcedOnes) - { - throw new ArgumentOutOfRangeException(nameof(possibleOnes), "possibleOnes must be >= forcedOnes and both non-negative."); - } - long minSum = currentCarry.Min + forcedOnes; long maxSum = currentCarry.Max + possibleOnes; int parity = requiredResultBit & 1; - long minAligned = AlignUpToParity(minSum, parity); - long maxAligned = AlignDownToParity(maxSum, parity); + minSum = AlignUpToParity(minSum, parity); + maxSum = AlignDownToParity(maxSum, parity); - if (minAligned > maxAligned) + if (minSum > maxSum) { nextCarry = default; reason = ContradictionReason.ParityUnreachable; return false; } - nextCarry = new CarryRange((minAligned - requiredResultBit) >> 1, (maxAligned - requiredResultBit) >> 1); + nextCarry = new CarryRange((minSum - requiredResultBit) >> 1, (maxSum - requiredResultBit) >> 1); reason = ContradictionReason.None; return true; } @@ -149,65 +120,164 @@ internal static class BitContradictionSolver out ContradictionReason reason) { divides = false; - reason = ContradictionReason.None; + _lastTopDownFailure = null; - if (p == 0 || qOneOffsets.Length == 0) - { - reason = ContradictionReason.ParityUnreachable; - return true; - } + // These conditions will never be true on EvenPerfectBitScanner execution path. + // if (p == 0 || qOneOffsets.Length == 0) + // { + // reason = ContradictionReason.ParityUnreachable; + // return true; + // } + // While the scanner supports p >= 31, it's a tiny subset of all checked exponents. No need to support it. // Exact small-case evaluation to short-circuit both success and failure deterministically. - int highestBit = qOneOffsets[^1]; - if (p <= 64 && highestBit < 63) - { - ulong q = 0; - foreach (int bit in qOneOffsets) - { - q |= 1UL << bit; - } + // int highestBit = qOneOffsets[^1]; + // if (p <= 64 && highestBit < 63) + // { + // ulong q = 0; + // foreach (int bit in qOneOffsets) + // { + // q |= 1UL << bit; + // } - ulong mersenne = (1UL << (int)p) - 1UL; - divides = q != 0 && (mersenne % q == 0); - reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable; - return true; - } + // ulong mersenne = (1UL << (int)p) - 1UL; + // divides = q != 0 && (mersenne % q == 0); + // reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable; + // return true; + // } - int maxOffset = qOneOffsets[^1]; + int qOneOffsetsLength = qOneOffsets.Length; + int maxOffset = qOneOffsets[qOneOffsetsLength - 1]; if ((ulong)(maxOffset + 1) >= p) { - divides = false; reason = ContradictionReason.TruncatedLength; return true; } - if (qOneOffsets[0] != 0) + // q from EvenPerfectBitScanner will always have the correct 2kp+1 form. + // if (qOneOffsets[0] != 0) + // { + // reason = ContradictionReason.ParityUnreachable; + // return true; + // } + + Dictionary aBits = _aBitsBuffer ??= new Dictionary(capacity: qOneOffsetsLength << 2); + // aBits.EnsureCapacity(qOneOffsetsLength << 2); + aBits.Clear(); + long maxKnownA = -1L; + long pLong = (long)p; + long maxAllowedA = pLong - (maxOffset + 1L); + if (maxAllowedA < 0) + { + reason = ContradictionReason.TruncatedLength; + return true; + } + + if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure)) + { + reason = ContradictionReason.ParityUnreachable; + return true; + } + + long lowColumn = 0; + long highColumn = pLong - 1; + CarryRange carryLow = CarryRange.Zero; + CarryRange carryHigh = CarryRange.Zero; + bool processTop = true; + + while (lowColumn <= highColumn) + { + int remaining = (int)(highColumn - lowColumn + 1); + int blockSize = remaining < PingPongBlockColumns ? remaining : PingPongBlockColumns; + + if (processTop) + { + if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, out carryHigh, out _lastTopDownFailure)) + { + reason = ContradictionReason.ParityUnreachable; + return true; + } + + highColumn -= blockSize; + } + else + { + if (!TryProcessBottomUpBlock(qOneOffsets, aBits, maxAllowedA, lowColumn, blockSize, ref carryLow, ref maxKnownA, out reason)) + { + return true; + } + + lowColumn += blockSize; + } + + processTop = !processTop; + } + + long carryMin = carryLow.Min > carryHigh.Min ? carryLow.Min : carryHigh.Min; + long carryMax = carryLow.Max < carryHigh.Max ? carryLow.Max : carryHigh.Max; + if (carryMin > carryMax) { divides = false; reason = ContradictionReason.ParityUnreachable; return true; } - var aBits = new Dictionary(capacity: qOneOffsets.Length * 4); - long carryMin = 0; - long carryMax = 0; - long maxKnownA = -1; - long maxAllowedA = (long)p - (maxOffset + 1L); - if (maxAllowedA < 0) + divides = true; + reason = ContradictionReason.None; + return true; + } + + internal static bool TryCheckDivisibilityFromOneOffsets( + ReadOnlySpan qOneOffsets, + ulong p, + out bool divides) => TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static bool TryAdvanceZeroColumns(ref CarryRange carry, int columnCount) + { + long carryMin = carry.Min; + long carryMax = carry.Max; + while (columnCount > 0) { - divides = false; - reason = ContradictionReason.TruncatedLength; - return true; + int step = columnCount > 62 ? 62 : columnCount; + long add = (1L << step) - 1L; + long nextMin = (carryMin + add) >> step; + long nextMax = carryMax >> step; + if (nextMin > nextMax) + { + return false; + } + + carryMin = nextMin; + carryMax = nextMax; + columnCount -= step; } - for (long column = 0; column < (long)p; column++) - { - long forced = 0; - long unknown = 0; - long chosenIndex = -1; + carry = new CarryRange(carryMin, carryMax); + return true; + } - foreach (int offset in qOneOffsets) + private static bool TryProcessBottomUpBlock( + ReadOnlySpan qOneOffsets, + Dictionary aBits, + long maxAllowedA, + long startColumn, + int columnCount, + ref CarryRange carry, + ref long maxKnownA, + out ContradictionReason reason) + { + long endColumn = startColumn + columnCount; + int qOneOffsetsLength = qOneOffsets.Length; + for (long column = startColumn; column < endColumn; column++) + { + long forced = 0L; + long unknown = 0L; + long chosenIndex = -1L; + + for (int offsetIndex = 0; offsetIndex < qOneOffsetsLength; offsetIndex++) { + int offset = qOneOffsets[offsetIndex]; long aIndex = column - offset; if (aIndex < 0) { @@ -229,29 +299,51 @@ internal static class BitContradictionSolver else { unknown++; - if (chosenIndex == -1) + if (chosenIndex == -1L) { chosenIndex = aIndex; } } } - long minSum = carryMin + forced; - long maxSum = carryMax + forced + unknown; + long minSum = carry.Min; + long maxSum = carry.Max; + if (unknown == 1 && ((minSum ^ maxSum) & 1) == 0) + { + int requiredUnknownParity = 1 ^ ((int)minSum & 1) ^ ((int)forced & 1); + if (requiredUnknownParity != 0) + { + if (chosenIndex >= 0) + { + aBits[chosenIndex] = 1; + forced++; + unknown = 0; + if (chosenIndex > maxKnownA) + { + maxKnownA = chosenIndex; + } + } + } + else + { + unknown = 0; + } + } + + minSum += forced; + maxSum += forced + unknown; const int requiredParity = 1; if (minSum == maxSum) { if ((minSum & 1) != requiredParity) { - divides = false; reason = ContradictionReason.ParityUnreachable; - return true; + return false; } long nextCarry = (minSum - requiredParity) >> 1; - carryMin = nextCarry; - carryMax = nextCarry; + carry = CarryRange.Single(nextCarry); } else { @@ -259,8 +351,8 @@ internal static class BitContradictionSolver { if (unknown == 0) { - divides = false; - return true; + reason = ContradictionReason.ParityUnreachable; + return false; } if (chosenIndex >= 0) @@ -276,25 +368,22 @@ internal static class BitContradictionSolver } if (!TryPropagateCarry( - new CarryRange(carryMin, carryMax), + carry, forced, forced + unknown, requiredParity, out var next, out var propagateReason)) { - divides = false; reason = propagateReason; - return true; + return false; } - carryMin = next.Min; - carryMax = next.Max; + carry = next; } - if (chosenIndex >= 0 && !aBits.ContainsKey(chosenIndex)) + if (chosenIndex >= 0 && !aBits.TryAdd(chosenIndex, 1)) { - aBits[chosenIndex] = 1; if (chosenIndex > maxKnownA) { maxKnownA = chosenIndex; @@ -302,74 +391,155 @@ internal static class BitContradictionSolver } } - // Tail: keep propagating until carry collapses to zero. No fixed budget; carry halves every column when no ones remain. - for (long column = (long)p; carryMax > 0; column++) - { - long forced = 0; - long unknown = 0; - foreach (int offset in qOneOffsets) - { - long aIndex = column - offset; - if (aIndex < 0) - { - break; - } - - if (aIndex > maxAllowedA) - { - continue; // forced zero - } - - if (aBits.TryGetValue(aIndex, out sbyte aVal)) - { - if (aVal == 1) - { - forced++; - } - } - else - { - unknown++; - } - } - - if (!TryPropagateCarry( - new CarryRange(carryMin, carryMax), - forced, - forced + unknown, - requiredResultBit: 0, - out var next, - out var reasonTail)) - { - divides = false; - reason = reasonTail; - return true; - } - - carryMin = next.Min; - carryMax = next.Max; - } - - divides = carryMin == 0 && carryMax == 0; - reason = divides ? ContradictionReason.None : ContradictionReason.TailNotZero; + reason = ContradictionReason.None; return true; } - internal static bool TryCheckDivisibilityFromOneOffsets( + private static bool TryProcessTopDownBlock( ReadOnlySpan qOneOffsets, - ulong p, - out bool divides) + int qOneOffsetsLength, + long maxAllowedA, + long startHighColumn, + int columnCount, + CarryRange carryOut, + out CarryRange nextCarryOut, + out TopDownPruneFailure? failure) { - return TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _); + failure = null; + long carryOutMin = carryOut.Min, + carryOutMax = carryOut.Max, + value; + int windowStart = qOneOffsetsLength; + int windowEnd = qOneOffsetsLength; + long endColumn = startHighColumn - columnCount + 1; + + for (long column = startHighColumn; column >= endColumn; column--) + { + // value is used as high column index + value = column; + while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value) + { + windowEnd--; + } + + // value is used as low column index + value = column - maxAllowedA; + if (value < 0) + { + value = 0; + } + + while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value) + { + windowStart--; + } + + if (windowStart > windowEnd) + { + windowStart = windowEnd; + } + + // value is unknown bits count here + value = windowEnd - windowStart; + // carryOutMin now becomes nextCarryMin to limit registry pressure + carryOutMin = (carryOutMin << 1) + 1 - value; + // carryOutMax now becomes nextCarryMax to limit registry pressure + carryOutMax = (carryOutMax << 1) + 1; + if (carryOutMin < 0) + { + carryOutMin = 0; + } + + if (carryOutMax < 0) + { + carryOutMax = 0; + } + + if (carryOutMin > carryOutMax) + { + failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); + nextCarryOut = default; + return false; + } + } + + nextCarryOut = new CarryRange(carryOutMin, carryOutMax); + return true; } - private static long AlignUpToParity(long value, int parity) - { - return (value & 1) == parity ? value : value + 1; - } + private static long AlignUpToParity(long value, int parity) => (value & 1) == parity ? value : value + 1; - private static long AlignDownToParity(long value, int parity) + private static long AlignDownToParity(long value, int parity) => (value & 1) == parity ? value : value - 1; + + private static bool TryTopDownCarryPrune( + ReadOnlySpan qOneOffsets, + int qOneOffsetsLength, + long pLong, + long maxAllowedA, + out TopDownPruneFailure? failure) { - return (value & 1) == parity ? value : value - 1; + failure = null; + long carryOutMin = 0, + carryOutMax = 0, + value; + int windowStart = qOneOffsetsLength; + int windowEnd = qOneOffsetsLength; + + for (long column = pLong - 1; column >= 0; column--) + { + // value is used as high column index + value = column; + while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value) + { + windowEnd--; + } + + // value is used as low column index + value = column - maxAllowedA; + if (value < 0) + { + value = 0; + } + + while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value) + { + windowStart--; + } + + if (windowStart > windowEnd) + { + windowStart = windowEnd; + } + + // value is used as unknown bits count here + value = windowEnd - windowStart; + // carryOutMin now becomes nextCarryMin to limit registry pressure + carryOutMin = (carryOutMin << 1) + 1 - value; + // carryOutMax now becomes nextCarryMax to limit registry pressure + carryOutMax = (carryOutMax << 1) + 1; + if (carryOutMin < 0) + { + carryOutMin = 0; + } + + if (carryOutMax < 0) + { + carryOutMax = 0; + } + + if (carryOutMin > carryOutMax) + { + failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); + return false; + } + } + + if (carryOutMin > 0) + { + failure = new TopDownPruneFailure(0, carryOutMin, carryOutMax, 0); + return false; + } + + return true; } } diff --git a/PerfectNumbers.Core/ByDivisor/BitContradictionTailCarryLut8.cs b/PerfectNumbers.Core/ByDivisor/BitContradictionTailCarryLut8.cs new file mode 100644 index 0000000..e835c5b --- /dev/null +++ b/PerfectNumbers.Core/ByDivisor/BitContradictionTailCarryLut8.cs @@ -0,0 +1,88 @@ +using System; + +namespace PerfectNumbers.Core.ByDivisor; + +public static class BitContradictionTailCarryLut8 +{ + private const int Rows = 8; + private const int Columns = 8; + private const int WindowBits = Rows + Columns - 1; + private const int CarryStates = Rows + 1; + private const byte FullMask = (1 << Rows) - 1; + + private static ushort[]? _table; + private static readonly ushort[]?[] _tablesByMask = new ushort[(1 << Rows)][]; + + public static void WarmUp() + { + _table ??= BuildTable(FullMask); + } + + public static void WarmUpAll() + { + for (int mask = 0; mask < (1 << Rows); mask++) + { + _ = GetTable((byte)mask); + } + } + + public static ReadOnlySpan Table => _table ?? throw new InvalidOperationException("BitContradictionTailCarryLut8 not initialized."); + + public static ReadOnlySpan GetTable(byte rowMask) + { + ushort[]? table = _tablesByMask[rowMask]; + if (table is null) + { + table = BuildTable(rowMask); + _tablesByMask[rowMask] = table; + if (rowMask == FullMask) + { + _table ??= table; + } + } + + return table; + } + + private static ushort[] BuildTable(byte rowMask) + { + int entries = (1 << WindowBits) * CarryStates; + var table = new ushort[entries]; + int entryIndex = 0; + + for (int window = 0; window < (1 << WindowBits); window++) + { + for (int carryIn = 0; carryIn < CarryStates; carryIn++) + { + int carry = carryIn; + ushort result = 0; + + for (int column = 0; column < Columns; column++) + { + int ones = 0; + for (int shift = 0; shift < Rows; shift++) + { + if (((rowMask >> shift) & 1) == 0) + { + continue; + } + + int windowIndex = column - shift + (Rows - 1); + if ((uint)windowIndex < WindowBits && ((window >> windowIndex) & 1) != 0) + { + ones++; + } + } + + int sum = carry + ones; + result |= (ushort)((sum & 1) << column); + carry = sum >> 1; + } + + table[entryIndex++] = (ushort)(result | (carry << 8)); + } + } + + return table; + } +} diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs index 1c41004..cc99055 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs @@ -1,7 +1,8 @@ // #define DivisorSet_BitContradiction -#define DivisorSet_BitTree +// #define DivisorSet_BitTree using System; +using System.Diagnostics; using System.Numerics; using System.IO; using System.Text; @@ -1396,6 +1397,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryExactBitTreeCheck(ulong exponent, BigInteger divisor, out bool divides) { + Stopwatch sw = Stopwatch.StartNew(); int qBitLength = GetBitLengthPortable(divisor); int aBitLength = GetBitLengthPortable(exponent); @@ -1501,11 +1503,13 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN return true; } } + sw.Stop(); ArrayPool.Shared.Return(aWindowArray); divides = carry == 0; - Console.WriteLine($"[bittree] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}"); + var elapsed = sw.Elapsed; + Console.WriteLine($"[bittree ({elapsed})] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}"); if (!divides) { string message = $"[bittree] p={exponent} ruled out divisor={divisor} (parity/carry exhaustion)"; @@ -4317,10 +4321,18 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN BigInteger currentK = minK; bool hasLimit = !allowedMax.IsZero; BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero; + int stepMod8 = (int)(step % 8); + int kRem8 = (int)(currentK % 8); + BigInteger divisor = (step * currentK) + BigInteger.One; while (true) { - BigInteger divisor = (step * currentK) + BigInteger.One; + int remainder8 = (stepMod8 * kRem8 + 1) & 7; + if (remainder8 != 1 && remainder8 != 7) + { + goto MOVE_NEXT; + } + if ( divisor <= ulong.MaxValue && !PrimeTesterByLastDigit.IsPrimeCpu((ulong)divisor) @@ -4422,6 +4434,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN } currentK += BigInteger.One; + divisor += step; + kRem8 = (kRem8 + 1) & 7; } return false; @@ -4482,7 +4496,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides) { + Stopwatch sw = Stopwatch.StartNew(); divides = false; + const ulong DebugTopDownDivisor = 1209708008767UL; if (prime == 0 || divisor <= BigInteger.One) { @@ -4542,14 +4558,27 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN } } + sw.Stop(); if (decided && !divides) { + if (reason == BitContradictionSolver.ContradictionReason.ParityUnreachable && + divisor <= ulong.MaxValue && + (ulong)divisor == DebugTopDownDivisor) + { + var failure = BitContradictionSolver.LastTopDownFailure; + if (failure.HasValue) + { + var info = failure.Value; + Console.WriteLine($"[bit-contradiction] top-down prune failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] unknown={info.Unknown}"); + } + } + string message = $"[bit-contradiction] p={prime} ruled out divisor={divisor} ({reason})"; - Console.WriteLine(message); + // Console.WriteLine(message); _bitContradictionStateRepository?.Upsert(prime, message); } - Console.WriteLine($"[bitcontradiction] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides}"); + Console.WriteLine($"[bitcontradiction ({sw.Elapsed})] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides} Reason={reason}"); return decided; } #endif