diff --git a/EvenPerfectBitScanner/CliArguments.cs b/EvenPerfectBitScanner/CliArguments.cs index 50f5c90..af98eeb 100644 --- a/EvenPerfectBitScanner/CliArguments.cs +++ b/EvenPerfectBitScanner/CliArguments.cs @@ -13,6 +13,7 @@ namespace EvenPerfectBitScanner; Percentile, Additive, TopDown, + BitContradiction, } internal readonly struct CliArguments @@ -330,23 +331,27 @@ namespace EvenPerfectBitScanner; { byDivisorKIncrementMode = ByDivisorKIncrementMode.Predictive; } - else if (value.Equals("percentile", StringComparison.OrdinalIgnoreCase)) - { - byDivisorKIncrementMode = ByDivisorKIncrementMode.Percentile; - } - else if (value.Equals("additive", StringComparison.OrdinalIgnoreCase)) - { - byDivisorKIncrementMode = ByDivisorKIncrementMode.Additive; - } - else if (value.Equals("topdown", StringComparison.OrdinalIgnoreCase)) - { - byDivisorKIncrementMode = ByDivisorKIncrementMode.TopDown; - } - else - { - Console.WriteLine("Invalid value for --bydivisor-k-increment. Use sequential, pow2groups, predictive, percentile, additive, or topdown."); - return default; - } + else if (value.Equals("percentile", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.Percentile; + } + else if (value.Equals("additive", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.Additive; + } + else if (value.Equals("topdown", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.TopDown; + } + else if (value.Equals("bitcontradiction", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.BitContradiction; + } + else + { + Console.WriteLine("Invalid value for --bydivisor-k-increment. Use sequential, pow2groups, predictive, percentile, additive, topdown, or bitcontradiction."); + return default; + } continue; } @@ -710,7 +715,7 @@ namespace EvenPerfectBitScanner; Console.WriteLine(" --increment=bit|add exponent increment method (default add)"); Console.WriteLine(" --threads= number of worker threads"); Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method"); - Console.WriteLine(" --bydivisor-k-increment=sequential|pow2groups|predictive|percentile|additive|topdown k iteration strategy for --mersenne=bydivisor (default sequential)"); + Console.WriteLine(" --bydivisor-k-increment=sequential|pow2groups|predictive|percentile|additive|topdown|bitcontradiction k iteration strategy for --mersenne=bydivisor (default sequential)"); Console.WriteLine(" --bydivisor-special-range= widen pow2groups special values to +/- n percent (default 0 = points)"); Console.WriteLine(" --max-k= max k for Mersenne tests (q = 2*p*k + 1), including --mersenne=bydivisor"); Console.WriteLine(" --mersenne-device=cpu|gpu|hybrid Device for Mersenne method (default gpu)"); diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 2ae57a7..a28a1fe 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -29,6 +29,9 @@ using ByDivisorGpuOrderAdditive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorB using ByDivisorCpuOrderTopDown = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForTopDownDivisorSetForCpuOrder; using ByDivisorHybridOrderTopDown = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForTopDownDivisorSetForHybridOrder; using ByDivisorGpuOrderTopDown = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForTopDownDivisorSetForGpuOrder; +using ByDivisorCpuOrderBitContradiction = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForCpuOrder; +using ByDivisorHybridOrderBitContradiction = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForHybridOrder; +using ByDivisorGpuOrderBitContradiction = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForGpuOrder; using System.Globalization; namespace EvenPerfectBitScanner; @@ -58,6 +61,9 @@ private static ThreadLocal? MersenneTesters; private static ByDivisorCpuOrderTopDown _byDivisorCpuOrderTesterTopDown; private static ByDivisorHybridOrderTopDown _byDivisorHybridOrderTesterTopDown; private static ByDivisorGpuOrderTopDown _byDivisorGpuOrderTesterTopDown; + private static ByDivisorCpuOrderBitContradiction _byDivisorCpuOrderTesterBitContradiction; + private static ByDivisorHybridOrderBitContradiction _byDivisorHybridOrderTesterBitContradiction; + private static ByDivisorGpuOrderBitContradiction _byDivisorGpuOrderTesterBitContradiction; private static Dictionary? _byDivisorPreviousResults; private static string? _byDivisorModelPath; private static bool _byDivisorModeUsesModel; @@ -538,6 +544,7 @@ private static ThreadLocal? MersenneTesters; ByDivisorKIncrementMode.Percentile => "percentile.kstate.faster", ByDivisorKIncrementMode.Additive => "additive.kstate.faster", ByDivisorKIncrementMode.TopDown => "topdown.kstate.faster", + ByDivisorKIncrementMode.BitContradiction => "bitcontradiction.kstate.faster", _ => "sequential.kstate.faster", }; string lmdbPath = Path.Combine(checksDir, Path.ChangeExtension(kRepoFile, ".lmdb")); @@ -545,17 +552,18 @@ private static ThreadLocal? MersenneTesters; EnvironmentConfiguration.ByDivisorCheapKStateRepository = KStateRepository.Open(checksDir, "cheaplimit.kstate.faster", Path.Combine(checksDir, "cheaplimit.kstate.lmdb"), checksDir); string pow2MinusLmdb = Path.Combine(checksDir, "pow2minus1.lmdb"); EnvironmentConfiguration.ByDivisorPow2Minus1Repository ??= Pow2Minus1StateRepository.Open(Path.Combine(checksDir, "pow2minus1.faster"), pow2MinusLmdb, checksDir); + EnvironmentConfiguration.BitContradictionStateRepository ??= BitContradictionStateRepository.Open(checksDir); - if (kIncrementMode == ByDivisorKIncrementMode.Pow2Groups) - { - EnvironmentConfiguration.ByDivisorSpecialStateRepository = KStateRepository.Open(checksDir, "pow2groups.special.kstate.faster", Path.Combine(checksDir, "pow2groups.special.kstate.lmdb"), checksDir); - EnvironmentConfiguration.ByDivisorGroupsStateRepository = KStateRepository.Open(checksDir, "pow2groups.groups.kstate.faster", Path.Combine(checksDir, "pow2groups.groups.kstate.lmdb"), checksDir); - } - else - { - EnvironmentConfiguration.ByDivisorSpecialStateRepository = null; - EnvironmentConfiguration.ByDivisorGroupsStateRepository = null; - } + if (kIncrementMode == ByDivisorKIncrementMode.Pow2Groups) + { + EnvironmentConfiguration.ByDivisorSpecialStateRepository = KStateRepository.Open(checksDir, "pow2groups.special.kstate.faster", Path.Combine(checksDir, "pow2groups.special.kstate.lmdb"), checksDir); + EnvironmentConfiguration.ByDivisorGroupsStateRepository = KStateRepository.Open(checksDir, "pow2groups.groups.kstate.faster", Path.Combine(checksDir, "pow2groups.groups.kstate.lmdb"), checksDir); + } + else + { + EnvironmentConfiguration.ByDivisorSpecialStateRepository = null; + EnvironmentConfiguration.ByDivisorGroupsStateRepository = null; + } } bool useFilter = !testMode && !string.IsNullOrEmpty(filterFile); @@ -730,6 +738,19 @@ private static ThreadLocal? MersenneTesters; threadCount, byDivisorPrimeRange); break; + case ByDivisorKIncrementMode.BitContradiction: + Console.WriteLine("Using by-divisor bit-contradiction increment (CPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorCpuOrderTesterBitContradiction, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; default: MersenneNumberDivisorByDivisorTester.Run( byDivisorCandidates, @@ -800,6 +821,19 @@ private static ThreadLocal? MersenneTesters; threadCount, byDivisorPrimeRange); break; + case ByDivisorKIncrementMode.BitContradiction: + Console.WriteLine("Using by-divisor bit-contradiction increment (Hybrid order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorHybridOrderTesterBitContradiction, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; default: MersenneNumberDivisorByDivisorTester.Run( byDivisorCandidates, @@ -870,6 +904,19 @@ private static ThreadLocal? MersenneTesters; threadCount, byDivisorPrimeRange); break; + case ByDivisorKIncrementMode.BitContradiction: + Console.WriteLine("Using by-divisor bit-contradiction increment (GPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorGpuOrderTesterBitContradiction, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; default: MersenneNumberDivisorByDivisorTester.Run( byDivisorCandidates, @@ -1279,6 +1326,7 @@ private static ThreadLocal? MersenneTesters; ByDivisorKIncrementMode.Percentile => _byDivisorCpuOrderTesterPercentile.IsPrime(p, out detailedCheck, out _), ByDivisorKIncrementMode.Additive => _byDivisorCpuOrderTesterAdditive.IsPrime(p, out detailedCheck, out _), ByDivisorKIncrementMode.TopDown => _byDivisorCpuOrderTesterTopDown.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.BitContradiction => _byDivisorCpuOrderTesterBitContradiction.IsPrime(p, out detailedCheck, out _), _ => _byDivisorCpuOrderTester.IsPrime(p, out detailedCheck, out _), }; } @@ -1292,6 +1340,7 @@ private static ThreadLocal? MersenneTesters; ByDivisorKIncrementMode.Percentile => _byDivisorHybridOrderTesterPercentile.IsPrime(p, out detailedCheck, out _), ByDivisorKIncrementMode.Additive => _byDivisorHybridOrderTesterAdditive.IsPrime(p, out detailedCheck, out _), ByDivisorKIncrementMode.TopDown => _byDivisorHybridOrderTesterTopDown.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.BitContradiction => _byDivisorHybridOrderTesterBitContradiction.IsPrime(p, out detailedCheck, out _), _ => _byDivisorHybridOrderTester.IsPrime(p, out detailedCheck, out _), }; } @@ -1303,6 +1352,7 @@ private static ThreadLocal? MersenneTesters; ByDivisorKIncrementMode.Percentile => _byDivisorGpuOrderTesterPercentile.IsPrime(p, out detailedCheck, out _), ByDivisorKIncrementMode.Additive => _byDivisorGpuOrderTesterAdditive.IsPrime(p, out detailedCheck, out _), ByDivisorKIncrementMode.TopDown => _byDivisorGpuOrderTesterTopDown.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.BitContradiction => _byDivisorGpuOrderTesterBitContradiction.IsPrime(p, out detailedCheck, out _), _ => _byDivisorGpuOrderTester.IsPrime(p, out detailedCheck, out _), }; } diff --git a/PerfectNumbers.Core.Tests/BitContradictionIntegrationTests.cs b/PerfectNumbers.Core.Tests/BitContradictionIntegrationTests.cs new file mode 100644 index 0000000..e1c9f52 --- /dev/null +++ b/PerfectNumbers.Core.Tests/BitContradictionIntegrationTests.cs @@ -0,0 +1,78 @@ +using System.Numerics; +using FluentAssertions; +using PerfectNumbers.Core.ByDivisor; +using Xunit; + +namespace PerfectNumbers.Core.Tests; + +public class BitContradictionIntegrationTests +{ + // 2^11 - 1 = 2047 = 23 * 89 + [Fact] + public void BitContradictionAcceptsKnownDivisorWithDecision() + { + BigInteger divisor = 23; + ulong p = 11; + bool decided = Invoke(divisor, p, out bool divides); + decided.Should().BeTrue("small p should be decided exactly"); + divides.Should().BeTrue(); + } + + [Fact] + public void BitContradictionRejectsEvenDivisorQuickly() + { + BigInteger divisor = 24; + ulong p = 11; + bool decided = Invoke(divisor, p, out bool divides); + decided.Should().BeTrue(); + divides.Should().BeFalse(); + } + + [Fact] + public void BitContradictionRejectsTooLongDivisor() + { + // q bit length >= p => impossible + BigInteger divisor = (BigInteger.One << 15) + 1; + ulong p = 11; + bool decided = Invoke(divisor, p, out bool divides); + decided.Should().BeTrue(); + divides.Should().BeFalse(); + } + + [Fact] + public void BitContradictionRejectsNonDivisorWithDecision() + { + // 2^11 - 1 is not divisible by 25 + BigInteger divisor = 25; + ulong p = 11; + bool decided = Invoke(divisor, p, out bool divides); + decided.Should().BeTrue("small p should be decided exactly"); + divides.Should().BeFalse(); + } + + private static bool Invoke(BigInteger divisor, ulong p, out bool divides) + { + int bitLen = GetBitLength(divisor); + Span offsets = stackalloc int[bitLen]; + int count = 0; + for (int i = 0; i < bitLen; i++) + { + if (((divisor >> i) & BigInteger.One) == BigInteger.One) + { + offsets[count++] = i; + } + } + + return BitContradictionSolver.TryCheckDivisibilityFromOneOffsets(offsets[..count], p, out divides); + } + + private static int GetBitLength(BigInteger value) + { + if (value.IsZero) + { + return 0; + } + + return (int)Math.Floor(BigInteger.Log(value, 2.0)) + 1; + } +} diff --git a/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs b/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs new file mode 100644 index 0000000..f4936f7 --- /dev/null +++ b/PerfectNumbers.Core.Tests/BitContradictionSolverTests.cs @@ -0,0 +1,59 @@ +using FluentAssertions; +using PerfectNumbers.Core.ByDivisor; +using Xunit; +using static PerfectNumbers.Core.ByDivisor.BitContradictionSolver; + +namespace PerfectNumbers.Core.Tests; + +public class BitContradictionSolverTests +{ + [Fact] + public void ComputeColumnBounds_TracksForcedAndPossibleOnes() + { + BitState[] a = [BitState.One, BitState.Unknown]; + BitState[] q = [BitState.One, BitState.One]; + + ColumnBounds bounds = ComputeColumnBounds(a, q, 1); + + bounds.ForcedOnes.Should().Be(1); + bounds.PossibleOnes.Should().Be(2); + } + + [Fact] + public void TryPropagateCarry_FailsWhenParityUnreachable() + { + CarryRange carry = CarryRange.Single(0); + + bool success = TryPropagateCarry( + carry, + forcedOnes: 0, + possibleOnes: 0, + requiredResultBit: 1, + out CarryRange next, + out ContradictionReason reason); + + success.Should().BeFalse(); + reason.Should().Be(ContradictionReason.ParityUnreachable); + next.Min.Should().Be(0); + next.Max.Should().Be(0); + } + + [Fact] + public void TryPropagateCarry_ComputesNextCarryRange() + { + CarryRange carry = new(0, 1); + + bool success = TryPropagateCarry( + carry, + forcedOnes: 1, + possibleOnes: 3, + requiredResultBit: 1, + out CarryRange next, + out ContradictionReason reason); + + success.Should().BeTrue(); + reason.Should().Be(ContradictionReason.None); + next.Min.Should().Be(0); + next.Max.Should().Be(1); + } +} diff --git a/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs b/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs new file mode 100644 index 0000000..606466a --- /dev/null +++ b/PerfectNumbers.Core/ByDivisor/BitContradictionSolver.cs @@ -0,0 +1,375 @@ +using System; + +namespace PerfectNumbers.Core.ByDivisor; + +/// +/// Helper utilities for the BitContradiction divisor search. These methods implement the +/// interval-based carry propagation described in the BitContradictions plan document. +/// The solver itself will build on top of these primitives. +/// +internal static class BitContradictionSolver +{ + internal enum BitState + { + Zero = 0, + One = 1, + Unknown = 2, + } + + internal readonly struct ColumnBounds + { + public readonly long ForcedOnes; + public readonly long PossibleOnes; + + public ColumnBounds(long forcedOnes, long possibleOnes) + { + if (forcedOnes < 0) + { + throw new ArgumentOutOfRangeException(nameof(forcedOnes)); + } + + if (possibleOnes < forcedOnes) + { + throw new ArgumentOutOfRangeException(nameof(possibleOnes), "possibleOnes must be >= forcedOnes."); + } + + 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); + } + + internal enum ContradictionReason + { + None, + ParityUnreachable, + TruncatedLength, + TailNotZero, + } + + internal static ColumnBounds ComputeColumnBounds( + ReadOnlySpan multiplicand, + ReadOnlySpan multiplier, + int columnIndex) + { + if (columnIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + long forced = 0; + long possible = 0; + + 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) + { + continue; + } + + if (a == BitState.One && q == BitState.One) + { + forced++; + possible++; + continue; + } + + possible++; + } + + return new ColumnBounds(forced, possible); + } + + internal static bool TryPropagateCarry( + CarryRange currentCarry, + long forcedOnes, + long possibleOnes, + int requiredResultBit, + 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); + + if (minAligned > maxAligned) + { + nextCarry = default; + reason = ContradictionReason.ParityUnreachable; + return false; + } + + nextCarry = new CarryRange((minAligned - requiredResultBit) >> 1, (maxAligned - requiredResultBit) >> 1); + reason = ContradictionReason.None; + return true; + } + + /// + /// 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). + /// + internal static bool TryCheckDivisibilityFromOneOffsets( + ReadOnlySpan qOneOffsets, + ulong p, + out bool divides, + out ContradictionReason reason) + { + divides = false; + reason = ContradictionReason.None; + + if (p == 0 || qOneOffsets.Length == 0) + { + reason = ContradictionReason.ParityUnreachable; + return true; + } + + // 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; + } + + ulong mersenne = (1UL << (int)p) - 1UL; + divides = q != 0 && (mersenne % q == 0); + reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable; + return true; + } + + int maxOffset = qOneOffsets[^1]; + if ((ulong)(maxOffset + 1) >= p) + { + divides = false; + reason = ContradictionReason.TruncatedLength; + return true; + } + + if (qOneOffsets[0] != 0) + { + 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 = false; + reason = ContradictionReason.TruncatedLength; + return true; + } + + for (long column = 0; column < (long)p; column++) + { + long forced = 0; + long unknown = 0; + long chosenIndex = -1; + + foreach (int offset in qOneOffsets) + { + long aIndex = column - offset; + if (aIndex < 0) + { + break; + } + + if (aIndex > maxAllowedA) + { + continue; // a must be zero here + } + + if (aBits.TryGetValue(aIndex, out sbyte aVal)) + { + if (aVal == 1) + { + forced++; + } + } + else + { + unknown++; + if (chosenIndex == -1) + { + chosenIndex = aIndex; + } + } + } + + long minSum = carryMin + forced; + long maxSum = carryMax + forced + unknown; + const int requiredParity = 1; + + if (minSum == maxSum) + { + if ((minSum & 1) != requiredParity) + { + divides = false; + reason = ContradictionReason.ParityUnreachable; + return true; + } + + long nextCarry = (minSum - requiredParity) >> 1; + carryMin = nextCarry; + carryMax = nextCarry; + } + else + { + if ((minSum & 1) != requiredParity) + { + if (unknown == 0) + { + divides = false; + return true; + } + + if (chosenIndex >= 0) + { + aBits[chosenIndex] = 1; + forced++; + unknown--; + if (chosenIndex > maxKnownA) + { + maxKnownA = chosenIndex; + } + } + } + + if (!TryPropagateCarry( + new CarryRange(carryMin, carryMax), + forced, + forced + unknown, + requiredParity, + out var next, + out var propagateReason)) + { + divides = false; + reason = propagateReason; + return true; + } + + carryMin = next.Min; + carryMax = next.Max; + } + + if (chosenIndex >= 0 && !aBits.ContainsKey(chosenIndex)) + { + aBits[chosenIndex] = 1; + if (chosenIndex > maxKnownA) + { + maxKnownA = chosenIndex; + } + } + } + + // 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; + return true; + } + + internal static bool TryCheckDivisibilityFromOneOffsets( + ReadOnlySpan qOneOffsets, + ulong p, + out bool divides) + { + return TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _); + } + + private static long AlignUpToParity(long value, int parity) + { + return (value & 1) == parity ? value : value + 1; + } + + private static long AlignDownToParity(long value, int parity) + { + return (value & 1) == parity ? value : value - 1; + } +} diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs index d50afd7..17d3f4c 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs @@ -19,6 +19,7 @@ public enum DivisorSet Percentile, Additive, TopDown, + BitContradiction, } [EnumDependentTemplate(typeof(DivisorSet))] @@ -49,6 +50,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN private GpuUInt128WorkSet _divisorScanGpuWorkSet; private static readonly ConcurrentDictionary _checkedPow2MinusOne = new(); private ulong _currentPrime; +#if DivisorSet_BitContradiction + private static Persistence.BitContradictionStateRepository? _bitContradictionStateRepository; +#endif #if DivisorSet_TopDown private BigInteger _topDownCursor; #endif @@ -328,6 +332,9 @@ private ulong _currentPrime; _groupsResumeK = _groupsLastSavedK + BigInteger.One; _currentPow2Phase = Pow2Phase.None; _preparedSpecialsInitialized = false; +#endif +#if DivisorSet_BitContradiction + _bitContradictionStateRepository = EnvironmentConfiguration.BitContradictionStateRepository; #endif } @@ -432,6 +439,17 @@ private ulong _currentPrime; // Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} using pow2-groups plan; model skipped."); return compositePow2; #else +#if DivisorSet_BitContradiction + bool compositeBitContradiction = CheckDivisorsBitContradiction( + prime, + primeDecimalMask, + allowedMax, + normalizedMinK, + step, + out processedAll, + out foundDivisor); + return compositeBitContradiction; +#endif #if DivisorSet_TopDown bool compositeTopDown = CheckDivisorsTopDown( prime, @@ -3309,6 +3327,176 @@ private ulong _currentPrime; } #endif +#if DivisorSet_BitContradiction + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private bool CheckDivisorsBitContradiction( + ulong prime, + ushort primeDecimalMask, + BigInteger allowedMax, + BigInteger minK, + BigInteger step, + out bool processedAll, + out BigInteger foundDivisor) + { + processedAll = true; + foundDivisor = BigInteger.Zero; + + BigInteger currentK = minK; + bool hasLimit = !allowedMax.IsZero; + BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero; + + while (true) + { + BigInteger divisor = (step * currentK) + BigInteger.One; + bool decided = TryExactBitContradictionCheck(prime, divisor, out bool divides); + if (!decided) + { + throw new InvalidOperationException($"BitContradiction check did not decide for p={prime}, divisor={divisor} (k={currentK})."); + } + + if (divides) + { + foundDivisor = divisor; + return true; + } + + if (hasLimit && currentK >= maxK) + { + break; + } + + currentK += BigInteger.One; + } + + return false; + + bool fits64 = minK <= ulong.MaxValue; + bool limitFits64 = allowedMax.IsZero || allowedMax <= ulong.MaxValue; + + if (fits64 && limitFits64) + { + ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (ulong)allowedMax; + return CheckDivisors64Bit( + prime, + primeDecimalMask, + allowedMax64, + (ulong)minK, + out processedAll, + out ulong found64); + } + + if (fits64 && allowedMax > ulong.MaxValue) + { + ulong allowedMax64 = ulong.MaxValue; + bool composite64 = CheckDivisors64Bit( + prime, + primeDecimalMask, + allowedMax64, + (ulong)minK, + out processedAll, + out ulong found64); + if (composite64) + { + foundDivisor = found64; + return true; + } + + BigInteger iterations = ((BigInteger)(allowedMax64 - (((ulong)prime << 1) * (ulong)minK) - 1UL) / (ulong)step) + BigInteger.One; + BigInteger nextK = minK + iterations; + return CheckDivisorsLarge( + prime, + primeDecimalMask, + allowedMax, + nextK, + step, + out processedAll, + out foundDivisor); + } + + return CheckDivisorsLarge( + prime, + primeDecimalMask, + allowedMax, + minK, + step, + out processedAll, + out foundDivisor); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, out bool divides) + { + divides = false; + + if (prime == 0 || divisor <= BigInteger.One) + { + Console.WriteLine($"[bitcontradiction] invalid inputs {prime} / {divisor}"); + return false; + } + + int qBitLength = GetBitLengthPortable(divisor); + if (qBitLength > (int)prime) + { + divides = false; + return true; + } + + // Fast structural contradictions only: q must be odd and the implied maximum length of a must be positive. + if ((divisor & BigInteger.One) == BigInteger.Zero) + { + divides = false; + return true; + } + + if (prime <= (ulong)qBitLength) + { + divides = false; + return true; + } + + // Iterative column-wise propagation without storing all columns; we only keep assigned bits of 'a'. + Span oneOffsets = stackalloc int[qBitLength]; + int oneCount = 0; + for (int i = 0; i < qBitLength; i++) + { + if (((divisor >> i) & BigInteger.One) == BigInteger.One) + { + oneOffsets[oneCount++] = i; + } + } + + if (oneCount == 0) + { + divides = false; + return true; + } + + ReadOnlySpan oneOffsetsSlice = oneOffsets[..oneCount]; + bool decided = BitContradictionSolver.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, out divides, out var reason); + if (decided && divides) + { + // Verify to avoid false positives on structurally admissible but non-dividing q. + bool structuralOk = ((divisor - BigInteger.One) % (((BigInteger)prime) << 1)) == BigInteger.Zero; + bool dividesExact = structuralOk && BigInteger.ModPow(2, prime, divisor) == BigInteger.One; + if (!dividesExact) + { + divides = false; + reason = BitContradictionSolver.ContradictionReason.ParityUnreachable; + } + } + + if (decided && !divides) + { + string message = $"[bit-contradiction] p={prime} ruled out divisor={divisor} ({reason})"; + Console.WriteLine(message); + _bitContradictionStateRepository?.Upsert(prime, message); + } + + Console.WriteLine($"[bitcontradiction] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides}"); + return decided; + } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private void MarkPow2MinusOneChecked() { diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs index 5050517..d6ce59e 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs @@ -23,6 +23,10 @@ using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTeste using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder; using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder; using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder; +#elif DivisorSet_BitContradiction +using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForCpuOrder; +using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForHybridOrder; +using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForGpuOrder; #else using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder; using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForHybridOrder; @@ -85,6 +89,8 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate extension = ".additive"; #elif DivisorSet_TopDown extension = ".topdown"; +#elif DivisorSet_BitContradiction + extension = ".bitcontradiction"; #endif string stateFile = prime.ToString(CultureInfo.InvariantCulture) + extension; diff --git a/PerfectNumbers.Core/EnvironmentConfiguration.cs b/PerfectNumbers.Core/EnvironmentConfiguration.cs index 43c2115..1a0f1c2 100644 --- a/PerfectNumbers.Core/EnvironmentConfiguration.cs +++ b/PerfectNumbers.Core/EnvironmentConfiguration.cs @@ -29,6 +29,7 @@ public static class EnvironmentConfiguration public static Persistence.KStateRepository? ByDivisorSpecialStateRepository; public static Persistence.KStateRepository? ByDivisorGroupsStateRepository; public static Persistence.Pow2Minus1StateRepository? ByDivisorPow2Minus1Repository; + public static Persistence.BitContradictionStateRepository? BitContradictionStateRepository; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Initialize() diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index 2d6dd3d..fed08ef 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -18,6 +18,9 @@ using MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder = PerfectNum using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder; using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder; using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForCpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForHybridOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForBitContradictionDivisorSetForGpuOrder; namespace PerfectNumbers.Core; @@ -54,6 +57,37 @@ public static class MersenneNumberDivisorByDivisorTester Action pr) => RunCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionCpuOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionCpuOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunBitContradictionCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Run( List candidates, @@ -178,6 +212,37 @@ public static class MersenneNumberDivisorByDivisorTester Action pr) => RunHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionHybridOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionHybridOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunBitContradictionHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Run( List candidates, @@ -302,6 +367,37 @@ public static class MersenneNumberDivisorByDivisorTester Action pr) => RunGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionGpuOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionGpuOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunBitContradictionGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Run( List candidates, @@ -2431,6 +2527,150 @@ public static class MersenneNumberDivisorByDivisorTester } } + private static void RunBitContradictionGpuOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionGpuOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionGpuOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount <= 1 || totalCount <= partitionSize) + { + var session = new Cpu.MersenneNumberDivisorByDivisorPrimeScanSessionWithForBitContradictionDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + for (int i = 0; i < totalCount; i++) + { + session.ProcessPrime(filteredPrimes[i]); + } + + return; + } + + var options = new ParallelOptions + { + MaxDegreeOfParallelism = workerCount, + }; + + Parallel.For( + 0, + (int)Math.Ceiling((double)totalCount / partitionSize), + options, + new Action(partitionIndex => + { + int startIndex = partitionIndex * partitionSize; + int count = Math.Min(partitionSize, totalCount - startIndex); + var session = new Cpu.MersenneNumberDivisorByDivisorPrimeScanSessionWithForBitContradictionDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + for (int i = 0; i < count; i++) + { + session.ProcessPrime(filteredPrimes[startIndex + i]); + } + })); + } + + private static void RunBitContradictionHybridOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionHybridOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionHybridOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount <= 1 || totalCount <= partitionSize) + { + var session = new Cpu.MersenneNumberDivisorByDivisorPrimeScanSessionWithForBitContradictionDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + for (int i = 0; i < totalCount; i++) + { + session.ProcessPrime(filteredPrimes[i]); + } + + return; + } + + var options = new ParallelOptions + { + MaxDegreeOfParallelism = workerCount, + }; + + Parallel.For( + 0, + (int)Math.Ceiling((double)totalCount / partitionSize), + options, + new Action(partitionIndex => + { + int startIndex = partitionIndex * partitionSize; + int count = Math.Min(partitionSize, totalCount - startIndex); + var session = new Cpu.MersenneNumberDivisorByDivisorPrimeScanSessionWithForBitContradictionDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + for (int i = 0; i < count; i++) + { + session.ProcessPrime(filteredPrimes[startIndex + i]); + } + })); + } + + private static void RunBitContradictionCpuOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionCpuOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithBitContradictionCpuOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount <= 1 || totalCount <= partitionSize) + { + var session = new Cpu.MersenneNumberDivisorByDivisorPrimeScanSessionWithForBitContradictionDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + for (int i = 0; i < totalCount; i++) + { + session.ProcessPrime(filteredPrimes[i]); + } + + return; + } + + var options = new ParallelOptions + { + MaxDegreeOfParallelism = workerCount, + }; + + Parallel.For( + 0, + (int)Math.Ceiling((double)totalCount / partitionSize), + options, + new Action(partitionIndex => + { + int startIndex = partitionIndex * partitionSize; + int count = Math.Min(partitionSize, totalCount - startIndex); + var session = new Cpu.MersenneNumberDivisorByDivisorPrimeScanSessionWithForBitContradictionDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + for (int i = 0; i < count; i++) + { + session.ProcessPrime(filteredPrimes[startIndex + i]); + } + })); + } + private static void RunHybridOrderFilteredPow2( List filteredPrimes, in MersenneNumberDivisorByDivisorCpuTesterWithForPow2GroupsDivisorSetForHybridOrder prototype, diff --git a/PerfectNumbers.Core/Persistence/BitContradictionStateRepository.cs b/PerfectNumbers.Core/Persistence/BitContradictionStateRepository.cs new file mode 100644 index 0000000..53bd153 --- /dev/null +++ b/PerfectNumbers.Core/Persistence/BitContradictionStateRepository.cs @@ -0,0 +1,71 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using FASTER.core; + +namespace PerfectNumbers.Core.Persistence; + +/// +/// Stores per-prime BitContradiction solver state (arbitrary string payload) using Microsoft.FASTER.Core. +/// No LMDB migration is performed for this repository. +/// +public sealed class BitContradictionStateRepository : IDisposable +{ + private readonly FasterKV _store; + private readonly IDevice _logDevice; + private readonly IDevice _objectLogDevice; + private static readonly Encoding Utf8 = Encoding.UTF8; + + public string DatabasePath { get; } + + public static BitContradictionStateRepository Open(string directory, string repositoryFileName = "bitcontradiction.solver.faster") + { + Directory.CreateDirectory(directory); + string dbPath = Path.Combine(directory, repositoryFileName); + return new BitContradictionStateRepository(dbPath); + } + + private BitContradictionStateRepository(string path) + { + DatabasePath = path; + _logDevice = Devices.CreateLogDevice(path + ".log"); + _objectLogDevice = Devices.CreateLogDevice(path + ".obj.log"); + _store = new FasterKV( + 1L << 20, + new LogSettings + { + LogDevice = _logDevice, + ObjectLogDevice = _objectLogDevice, + }); + } + + public void Upsert(ulong prime, string state) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + string keyString = prime.ToString(CultureInfo.InvariantCulture); + session.Upsert(keyString, state); + session.CompletePending(true); + } + + public bool TryGet(ulong prime, out string? state) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + string keyString = prime.ToString(CultureInfo.InvariantCulture); + if (session.Read(keyString, out string? stored).Found) + { + state = stored; + return true; + } + + state = null; + return false; + } + + public void Dispose() + { + _store.Dispose(); + _logDevice.Dispose(); + _objectLogDevice.Dispose(); + } +} diff --git a/docs/BitContradictions.md b/docs/BitContradictions.md new file mode 100644 index 0000000..4dcfd27 --- /dev/null +++ b/docs/BitContradictions.md @@ -0,0 +1,32 @@ +# Bit-Contradiction Divisor Scan + +Plan and notes for the `DivisorSet.BitContradiction` / `--bydivisor-k-increment=bitcontradiction` mode. + +## Goal +- Divisor search via joint reconstruction of bits in \(q = 1 + 2pk\) and column-wise carries in \(q \cdot a = 2^p - 1\). +- Early local contradictions (parity reachability, carry budgets, length limits for \(a\)) without recursion. +- Report primes \(p\) whose search space exhausts with no successful branch first. + +## Operational assumptions +- \(p\) is odd; we target prime divisors \(q \equiv 1 \pmod{2p}\). +- Work in \(k\)-space (since \(q = 2pk + 1\)). +- Iterative DFS/BFS (explicit stack/queue), no recursion. + +## Implementation steps +- [x] Add `BitContradiction` mode to CLI (`--bydivisor-k-increment`) and state repositories/files. +- [x] Wire mode through CPU/Hybrid/GPU paths with `.bitcontradiction` state files. +- [x] Add core interval/propagation primitives (carry ranges, column bounds) with unit tests. +- [x] Wire BitContradiction mode into the scan path with a bit-level divisibility pre-check for small \(p\) before falling back to the standard divisor scan. +- [x] Replace the pre-check with a full forward-only column solver (no fixed column cap), reusable in tests. +- [x] Emit detailed log when the solver prunes a divisor with a concrete contradiction reason. +- [x] Unit tests on small \(p\) covering both success and no-divisor paths. +- [ ] (Optional) Integrate class-model stats to influence \(k_t\) branching order if helpful. +- [ ] Persist solver-specific state (FASTER) in `Checks\bitcontradiction.solver.faster` if we decide to retain per-prime solver telemetry. + +## Current minimal version +- Mode is selectable and persists its own state in FASTER/`.bitcontradiction` files. +- Uses the stable by-divisor scan pipeline as a placeholder until the dedicated solver is completed. + +## Next steps +- Implement interval-based propagation and iterative DFS on \(k\) bits (no recursion). +- Add tests for carry/length contradictions and for detecting a real divisor.