using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Numerics; using Open.Collections; namespace PerfectNumbers.Core.ByDivisor; // internal readonly struct PrivateWorkSet // { // private const int MaxSupportedBitLength = 200_000_000; // public PrivateWorkSet() // { // int windowSize = MaxSupportedBitLength + 1; // QBits = new int[windowSize]; // int wordCount = (windowSize + 63) >> 6; // KnownOne0 = new ulong[wordCount]; // KnownOne1 = new ulong[wordCount]; // KnownZero0 = new ulong[wordCount]; // KnownZero1 = new ulong[wordCount]; // RunStarts = new int[windowSize]; // RunLengths = new int[windowSize]; // StableUnknownDelta8Cache = []; // } // internal readonly int[] QBits; // internal readonly int[] RunStarts; // internal readonly int[] RunLengths; // internal readonly ulong[] KnownOne0; // internal readonly ulong[] KnownOne1; // internal readonly ulong[] KnownZero0; // internal readonly ulong[] KnownZero1; // internal readonly Dictionary StableUnknownDelta8Cache; // } /// /// 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 BitContradictionSolverWithAMultiplier { private const int PingPongBlockColumns = 4096; private const int TailCarryBatchColumns = 16384; private const int TailCarryBatchRepeats = 1; private const int TopDownBorrowColumns = 4096; private const int TopDownBorrowMinUnknown = 12; private const int PrefixRunCacheOffsets = 256; private const int PrefixRunCacheCapacity = 256; private const int ModLocalPrefilterMaxABits = 20; private const int TopDownCacheCapacity = 16; private const int HighBitCarryPrefilterColumns = 16; private const int MiniPingPongPrefilterColumns = 60; private static readonly int[] ModLocalPrefilterModuli = { 65537, 65521 }; #if DETAILED_LOG public readonly struct TopDownPruneFailure(long column, long carryMin, long carryMax, long unknown) { public readonly long Column = column; public readonly long CarryMin = carryMin; public readonly long CarryMax = carryMax; public readonly long Unknown = unknown; } private readonly struct TopDownCacheValue(bool success, TopDownPruneFailure? failure) { public readonly bool Success = success; public readonly TopDownPruneFailure? Failure = failure; } [ThreadStatic] private static Dictionary? _topDownCache; [ThreadStatic] private static TopDownPruneFailure? _lastTopDownFailure; internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure; #else [ThreadStatic] private static Dictionary? _topDownCache; #endif [ThreadStatic] private static long _dynamicModChecks; [ThreadStatic] private static long _dynamicModFailures; [ThreadStatic] private static Dictionary? _qModCache; [ThreadStatic] private static List? _topDownCacheOrder; [ThreadStatic] private static Dictionary? _prefixRunCache; [ThreadStatic] private static List? _prefixRunCacheOrder; [ThreadStatic] private static Dictionary? _pow2DiffModCache; private readonly struct QModCacheKey(long hash, int mod) { public readonly long Hash = hash; public readonly int Mod = mod; public bool Equals(QModCacheKey other) => Hash == other.Hash && Mod == other.Mod; public override bool Equals(object? obj) => obj is QModCacheKey other && Equals(other); public override int GetHashCode() => HashCode.Combine(Hash, Mod); } 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 PrefixRunCacheEntry( int prefixCount, int runCount, int lastOffset, int lastRunStart, int lastRunLength, int[] runStarts, int[] runLengths) { public readonly int PrefixCount = prefixCount; public readonly int RunCount = runCount; public readonly int LastOffset = lastOffset; public readonly int LastRunStart = lastRunStart; public readonly int LastRunLength = lastRunLength; public readonly int[] RunStarts = runStarts; public readonly int[] RunLengths = runLengths; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ComputeForcedRemainingAndClean( ulong[] qMask, ulong[] oneWin, ulong[] zeroWin, int wordCount, out int forced, out int remaining, out bool clean) { forced = 0; remaining = 0; clean = true; for (int i = 0; i < wordCount; i++) { ulong q = qMask[i]; ulong one = oneWin[i]; ulong known = one | zeroWin[i]; ulong forcedBits = q & one; ulong unknownBits = q & ~known; forced += BitOperations.PopCount(forcedBits); remaining += BitOperations.PopCount(unknownBits); if ((q & known) != 0) clean = false; } } private static long ComputeQOffsetsHash(in ReadOnlySpan qOneOffsets) { const long fnvOffset = unchecked(1469598103934665603); const long fnvPrime = 1099511628211; long hash = fnvOffset; int qOneOffsetsLength = qOneOffsets.Length; for (int i = 0; i < qOneOffsetsLength; i++) { hash ^= qOneOffsets[i]; hash *= fnvPrime; } return hash; } private static int GetQModCached(ReadOnlySpan qOneOffsets, long qHash, int mod) { Dictionary? cache = _qModCache; if (cache is null) { cache = []; _qModCache = cache; } var key = new QModCacheKey(qHash, mod); if (cache.TryGetValue(key, out int cached)) { return cached; } int value = ComputeQMod(qOneOffsets, mod); cache[key] = value; return value; } private static long ComputeQOffsetsPrefixHash(in ReadOnlySpan qOneOffsets, int count) { const long fnvOffset = unchecked(1469598103934665603); const long fnvPrime = 1099511628211; long hash = fnvOffset; for (int i = 0; i < count; i++) { hash ^= qOneOffsets[i]; hash *= fnvPrime; } hash ^= count; hash *= fnvPrime; return hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsCleanInQ(in ulong[] qMask, in ulong[] oneWin, in ulong[] zeroWin, int wordCount) { for (int i = 0; i < wordCount; i++) { if ((qMask[i] & (oneWin[i] | zeroWin[i])) != 0) return false; } return true; } private static bool TryGetPrefixRunCache(long key, out PrefixRunCacheEntry entry) { var cache = _prefixRunCache; if (cache is null) { cache = []; _prefixRunCache = cache; } return cache.TryGetValue(key, out entry); } private static void StorePrefixRunCache(long key, in PrefixRunCacheEntry entry) { _prefixRunCacheOrder ??= new List(PrefixRunCacheCapacity + 1); if (_prefixRunCache!.ContainsKey(key)) { _prefixRunCache[key] = entry; return; } if (_prefixRunCacheOrder.Count >= PrefixRunCacheCapacity) { long evict = _prefixRunCacheOrder[0]; _prefixRunCacheOrder.RemoveAt(0); _prefixRunCache.Remove(evict); } _prefixRunCache[key] = entry; _prefixRunCacheOrder.Add(key); } private static PrefixRunCacheEntry BuildPrefixRunCache(in ReadOnlySpan qOneOffsets, int prefixCount) { // TODO: Remove per-call allocations int[] starts = new int[prefixCount]; int[] lengths = new int[prefixCount]; int runCount = 0; int prev = qOneOffsets[0]; int currentStart = prev; int currentLength = 1; for (int i = 1; i < prefixCount; i++) { int offset = qOneOffsets[i]; if (offset == prev + 1) { currentLength++; } else { starts[runCount] = currentStart; lengths[runCount] = currentLength; runCount++; currentStart = offset; currentLength = 1; } prev = offset; } starts[runCount] = currentStart; lengths[runCount] = currentLength; runCount++; int[] cachedStarts = new int[runCount]; int[] cachedLengths = new int[runCount]; Array.Copy(starts, cachedStarts, runCount); Array.Copy(lengths, cachedLengths, runCount); return new PrefixRunCacheEntry( prefixCount, runCount, prev, currentStart, currentLength, cachedStarts, cachedLengths); } #if DETAILED_LOG private static bool TryGetTopDownCache(in TopDownCacheKey key, out TopDownCacheValue value) #else private static bool TryGetTopDownCache(in TopDownCacheKey key, out bool value) #endif { // TODO: Remove this check after Initialize if (_topDownCache == null) { value = default; return false; } return _topDownCache.TryGetValue(key, out value); } #if DETAILED_LOG private static void StoreTopDownCache(in TopDownCacheKey key, in TopDownCacheValue value) #else private static void StoreTopDownCache(in TopDownCacheKey key, in bool value) #endif { // TODO: Remove this check after Initialize _topDownCache ??= []; _topDownCacheOrder ??= new List(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 ColumnBounds ComputeColumnBounds( ReadOnlySpan multiplicand, ReadOnlySpan multiplier, int columnIndex) { int forced = 0; int possible = 0; int multiplicandLength = multiplicand.Length; int multiplierLength = multiplier.Length; for (int i = 0; i <= columnIndex; i++) { bool a = i < multiplicandLength && multiplicand[i]; bool q = columnIndex - i < multiplierLength && multiplier[columnIndex - i]; if (!a && !q) { continue; } if (a && q) { forced++; } possible++; } return new ColumnBounds(forced, possible); } internal static bool TryPropagateCarry( ref CarryRange currentCarry, int forcedOnes, int possibleOnes, int requiredResultBit) { long minSum = currentCarry.Min + forcedOnes; long maxSum = currentCarry.Max + possibleOnes; int parity = requiredResultBit & 1; minSum = AlignUpToParity(minSum, parity); maxSum = AlignDownToParity(maxSum, parity); if (minSum > maxSum) { return false; } currentCarry = new CarryRange((minSum - requiredResultBit) >> 1, (maxSum - requiredResultBit) >> 1); return true; } /// /// Mini ping-pong (Option B): compute a conservative allowed carry range after walking /// top-down for a given number of columns starting at . /// /// Semantics: /// - We treat "carryOut at column = startColumn+1" as the starting set (seed). /// - Each step processes one column (carryOut -> carryIn) moving downward. /// - After columns, the resulting set corresponds to carry-in /// at column (startColumn - steps + 1). /// private static bool TryHighBitCarryRangeToColumnJ( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, int pLong, int maxAllowedA, int startColumn, // e.g. p-1, then J1-1, ... int steps, // number of columns to process downward long startCarryOutMin, // seed carryOut range long startCarryOutMax, out long allowedMin, out long allowedMax) { allowedMin = 0; allowedMax = long.MaxValue; if (steps <= 0) { // No processing: carry-in == seed (conservative) allowedMin = startCarryOutMin < 0 ? 0 : startCarryOutMin; allowedMax = startCarryOutMax < 0 ? 0 : startCarryOutMax; if (allowedMax < allowedMin) (allowedMin, allowedMax) = (0, long.MaxValue); return true; } // Clamp startColumn to legal high column range. If outside, be conservative. int maxHigh = pLong - 1; if (startColumn > maxHigh || startColumn < 0) { allowedMin = 0; allowedMax = long.MaxValue; return true; } // Determine last processed column. int endColumn = startColumn - (steps - 1); if (endColumn < 0) { // Processing would go past column 0. We can clamp steps to reach column 0 exactly. steps = startColumn + 1; endColumn = 0; if (steps <= 0) { allowedMin = 0; allowedMax = 0; return true; } } // Two fixed interval buffers on stack: each interval is [lo, hi], disjoint and sorted. Span lo0 = stackalloc long[64]; Span hi0 = stackalloc long[64]; Span lo1 = stackalloc long[64]; Span hi1 = stackalloc long[64]; Span outLo = lo0, outHi = hi0; Span inLo = lo1, inHi = hi1; int outCount = 1; outLo[0] = startCarryOutMin; outHi[0] = startCarryOutMax; int inCount = 0; bool flip = false; [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool AddInterval(ref int count, Span loArr, Span hiArr, long lo, long hi) { if (hi < lo) return true; if (lo < 0) lo = 0; if (hi < 0) return true; if (count == 0) { loArr[0] = lo; hiArr[0] = hi; count = 1; return true; } long lastHi = hiArr[count - 1]; if (lo <= lastHi + 1) { if (hi > lastHi) hiArr[count - 1] = hi; return true; } if ((uint)count >= (uint)loArr.Length) { // Too many intervals: stop filtering conservatively. return false; } loArr[count] = lo; hiArr[count] = hi; count++; return true; } for (int step = 0; step < steps; step++) { int column = startColumn - step; // n = count of q-offsets t such that 0 <= column - t <= maxAllowedA // <=> column - maxAllowedA <= t <= column int lowerT; long lowerTLong = (long)column - (long)maxAllowedA; if (lowerTLong <= 0) lowerT = 0; else if (lowerTLong >= int.MaxValue) lowerT = int.MaxValue; else lowerT = (int)lowerTLong; int upperT = column <= 0 ? 0 : column; int left = LowerBound(qOneOffsets, lowerT); int right = UpperBound(qOneOffsets, upperT); int n = right - left; if (n < 0) return false; inCount = 0; for (int i = 0; i < outCount; i++) { long a = outLo[i]; long b = outHi[i]; if (b < 0) continue; if (a < 0) a = 0; if (b < a) continue; const long TwicePlusOneOverflowLimit = (long.MaxValue - 1) >> 1; // If carryOut is too large for safe 2*x+1 in long, we must remain conservative. if (a > TwicePlusOneOverflowLimit || b > TwicePlusOneOverflowLimit) { allowedMin = 0; allowedMax = long.MaxValue; return true; } long rMin = (a << 1) + 1; long rMax = (b << 1) + 1; // carryIn ∈ [ max(0, r-n), r ] if (n <= 0) { if (!AddInterval(ref inCount, inLo, inHi, rMin, rMax)) { allowedMin = 0; allowedMax = long.MaxValue; return true; } continue; } long lo = rMin - n; if (lo < 0) lo = 0; if (!AddInterval(ref inCount, inLo, inHi, lo, rMax)) { allowedMin = 0; allowedMax = long.MaxValue; return true; } } if (inCount <= 0) { return false; } // Swap buffers for next step. outCount = inCount; flip = !flip; if (!flip) { outLo = lo0; outHi = hi0; inLo = lo1; inHi = hi1; } else { outLo = lo1; outHi = hi1; inLo = lo0; inHi = hi0; } } // After steps, 'out' corresponds to carry-in at the last processed column (endColumn). allowedMin = outLo[0]; allowedMax = outHi[outCount - 1]; return true; } private static bool TryModularPrefilter( ReadOnlySpan qOneOffsets, long qHash, 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; int modLocalPrefilterModuliLength = ModLocalPrefilterModuli.Length; for (int i = 0; i < modLocalPrefilterModuliLength; i++) { int mod = ModLocalPrefilterModuli[i]; int qMod = GetQModCached(qOneOffsets, qHash, 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)target * inverse % mod; if (x > maxAValue) { return false; } } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int LowerBound(ReadOnlySpan values, int target) { int lo = 0; int hi = values.Length; while (lo < hi) { int mid = lo + ((hi - lo) >> 1); if (values[mid] < target) { lo = mid + 1; } else { hi = mid; } } return lo; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int UpperBound(ReadOnlySpan values, int target) { int lo = 0; int hi = values.Length; while (lo < hi) { int mid = lo + ((hi - lo) >> 1); if (values[mid] <= target) { lo = mid + 1; } else { hi = mid; } } return lo; } /// /// Extremely small, allocation-free prefilter over the last few columns (near p-1). /// /// It uses only the fact that the product must have no carry beyond column p-1 (i.e. carry-out from /// column p-1 is zero) and that bits 0..p-1 of M_p are all ones. /// /// This is intentionally conservative (never rejects a valid divisor). In practice it can still /// remove some q with "awkward" high-bit geometry where the required carry values near the top /// become impossible given the availability of contributions implied by qOneOffsets and maxAllowedA. /// private static bool TryHighBitTopCarryPrefilter( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, long pLong, long maxAllowedA) { // We process last K columns: (p-1), (p-2), ... int columns = HighBitCarryPrefilterColumns; long maxPossibleColumns = pLong - 1; if (columns > maxPossibleColumns) columns = (int)maxPossibleColumns; // Two fixed interval buffers on stack. // Each interval is [lo, hi], disjoint and sorted. // 32 is intentionally small: in practice this stays tiny (often 1-2 intervals). Span lo0 = stackalloc long[64]; Span hi0 = stackalloc long[64]; Span lo1 = stackalloc long[64]; Span hi1 = stackalloc long[64]; // "out" = carryOut set for current column (start at top with {0}) Span outLo = lo0, outHi = hi0; Span inLo = lo1, inHi = hi1; int outCount = 1; outLo[0] = 0; outHi[0] = 0; int inCount = 0; bool flip = false; [MethodImpl(MethodImplOptions.AggressiveInlining)] static void AddInterval(ref int count, Span loArr, Span hiArr, long lo, long hi) { if (hi < lo) return; if (lo < 0) lo = 0; if (hi < 0) return; if (count == 0) { loArr[0] = lo; hiArr[0] = hi; count = 1; return; } // Merge into last interval if overlapping/adjacent. long lastHi = hiArr[count - 1]; if (lo <= lastHi + 1) { if (hi > lastHi) hiArr[count - 1] = hi; return; } // Append if ((uint)count >= (uint)loArr.Length) { // If this ever happens, we conservatively stop filtering (do not reject). // Returning "true" keeps correctness (prefilter is optional). count = 0; return; } loArr[count] = lo; hiArr[count] = hi; count++; } for (int step = 0; step < columns; step++) { long column = maxPossibleColumns - step; if (column < 0) break; // n = count of q-offsets t such that 0 <= column - t <= maxAllowedA // <=> column - maxAllowedA <= t <= column long lowerTLong = column - maxAllowedA; int lowerT = lowerTLong <= 0 ? 0 : (lowerTLong >= int.MaxValue ? int.MaxValue : (int)lowerTLong); int upperT = column <= 0 ? 0 : (column >= int.MaxValue ? int.MaxValue : (int)column); int left = LowerBound(qOneOffsets, lowerT); int right = UpperBound(qOneOffsets, upperT); int n = right - left; if (n < 0) { // Console.WriteLine("n < 0 - excluding in pre-filter"); return false; } // Build carryIn set into inLo/inHi inCount = 0; // For M_p, bit in columns [0..p-1] is 1: // carryIn + ones = 1 + 2*carryOut // carryIn = (2*carryOut + 1) - ones, with ones in [0..min(n, 2*carryOut+1)]. // // That yields carryIn interval: // r = 2*carryOut + 1 // carryIn ∈ [ r - min(n, r), r ] = [ max(0, r-n), r ]. // // Union over carryOut intervals is merged cheaply. for (int i = 0; i < outCount; i++) { long a = outLo[i]; long b = outHi[i]; if (b < 0) continue; if (a < 0) a = 0; if (b < a) continue; const long TwicePlusOneOverflowLimit = (long.MaxValue - 1) >> 1; // Jeśli carryOut jest już zbyt duże, top-down robi się bezużyteczny w long. // Prefilter MUSI pozostać konserwatywny => przerywamy filtrowanie. if (a > TwicePlusOneOverflowLimit || b > TwicePlusOneOverflowLimit) { return true; } // r ranges from rMin..rMax over carryOut in [a..b] long rMin = (a << 1) + 1; long rMax = (b << 1) + 1; if (n <= 0) { // ones=0 only -> carryIn=r (odd sequence). Conservatively interval [rMin..rMax]. AddInterval(ref inCount, inLo, inHi, rMin, rMax); if (inCount == 0) return true; // overflow of intervals -> stop filtering continue; } if (n == 1) { // ones in {0,1} -> carryIn in {r, r-1}. Conservatively [rMin-1 .. rMax]. AddInterval(ref inCount, inLo, inHi, rMin - 1, rMax); if (inCount == 0) return true; continue; } // n >= 2 : union becomes dense enough that interval bounds are safe conservative approximations. if (rMax <= n) { // For all r<=n, lower bound is 0. AddInterval(ref inCount, inLo, inHi, 0, rMax); if (inCount == 0) return true; } else if (rMin > n) { // Always r>n => [r-n .. r] AddInterval(ref inCount, inLo, inHi, rMin - n, rMax); if (inCount == 0) return true; } else { // Crossing: part uses [0..r], later [r-n..r]. For n>=2 union is [0..rMax]. AddInterval(ref inCount, inLo, inHi, 0, rMax); if (inCount == 0) return true; } } if (inCount <= 0) { // Console.WriteLine("inCount <= 0 - excluding in pre-filter"); return false; } // Prepare for next column: carryOut := carryIn (shift down one column) outCount = inCount; // Toggle which buffers are used for out vs in. // Avoid tuple swap / temp Span to satisfy Span lifetime rules. flip = !flip; if (!flip) { outLo = lo0; outHi = hi0; inLo = lo1; inHi = hi1; } else { outLo = lo1; outHi = hi1; inLo = lo0; inHi = hi0; } } return true; } private static int[] GetPow2DiffLut(int mod) { _pow2DiffModCache ??= new Dictionary(); if (_pow2DiffModCache.TryGetValue(mod, out int[]? lut)) { return lut; } int[] values = new int[256]; values[0] = mod > 1 ? 1 : 0; for (int i = 1; i < values.Length; i++) { values[i] = (values[i - 1] << 1) % mod; } _pow2DiffModCache[mod] = values; return values; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ComputeForcedRemainingAndClean_Prefix( ulong[] qMask, ulong[] oneWin, ulong[] zeroWin, int words, ulong lastMask, out int forced, out int remaining, out bool clean) { forced = 0; remaining = 0; clean = true; for (int i = 0; i < words; i++) { ulong q = qMask[i]; if (i == words - 1) q &= lastMask; ulong one = oneWin[i]; ulong known = one | zeroWin[i]; ulong forcedBits = q & one; ulong unknownBits = q & ~known; forced += BitOperations.PopCount(forcedBits); remaining += BitOperations.PopCount(unknownBits); if ((q & known) != 0) clean = false; } } private static int ComputeQMod(ReadOnlySpan qOneOffsets, int mod) { int count = qOneOffsets.Length; if (count == 0) { return 0; } int[] diffLut = GetPow2DiffLut(mod); int diffLutLength = diffLut.Length; long sum = 0; int prevOffset = qOneOffsets[0]; long pow = PowMod2((ulong)prevOffset, mod); sum += pow; for (int i = 1; i < count; i++) { int offset = qOneOffsets[i]; int diff = offset - prevOffset; if (diff <= 0) { prevOffset = offset; continue; } if (diff < diffLutLength) { pow = pow * diffLut[diff] % mod; } else { pow = pow * PowMod2((ulong)diff, mod) % mod; } sum += pow; if (sum >= mod) { sum %= mod; } prevOffset = offset; } return (int)(sum % mod); } private static bool ExistsCongruentInRange(BigInteger minA, BigInteger maxA, int residue, int mod) { if (minA > maxA) { return false; } int minMod = (int)((minA % mod + mod) % mod); int delta = residue - minMod; if (delta < 0) { delta += mod; } BigInteger candidate = minA + delta; return candidate <= maxA; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void GetQPrefixMask(int startColumn, int qBitLen, int qWordCount, out int words, out ulong lastMask) { // We want only offsets t <= startColumn. if (startColumn >= qBitLen - 1) { words = qWordCount; lastMask = ulong.MaxValue; // caller can still & lastWordMask separately return; } int maxT = startColumn; // inclusive if (maxT < 0) { words = 0; lastMask = 0; return; } int lastWord = maxT >> 6; words = lastWord + 1; int bit = maxT & 63; lastMask = bit == 63 ? ulong.MaxValue : ((1UL << (bit + 1)) - 1UL); } private static void ComputeARangeFromTopConstraints( long maxAllowedA, bool top0KnownOne, bool top0KnownZero, bool top1KnownOne, bool top1KnownZero, long zeroTailStart, out BigInteger minA, out BigInteger maxA) { _dynamicModChecks++; long effectiveTop = maxAllowedA; if (zeroTailStart <= effectiveTop + 1) { effectiveTop = zeroTailStart - 1; } if (effectiveTop < 0) { minA = BigInteger.Zero; maxA = BigInteger.Zero; return; } int shift = effectiveTop + 1 > int.MaxValue ? int.MaxValue : (int)(effectiveTop + 1); maxA = (BigInteger.One << shift) - BigInteger.One; minA = BigInteger.Zero; if (effectiveTop == maxAllowedA) { if (top0KnownOne) { int minShift = maxAllowedA > int.MaxValue ? int.MaxValue : (int)maxAllowedA; minA = BigInteger.One << minShift; } else if (top0KnownZero && top1KnownOne && maxAllowedA - 1 >= 0) { long index = maxAllowedA - 1; int minShift = index > int.MaxValue ? int.MaxValue : (int)index; minA = BigInteger.One << minShift; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsCleanInQ_Prefix(ulong[] qMask, ulong[] oneWin, ulong[] zeroWin, int words, ulong lastMask) { for (int i = 0; i < words; i++) { ulong q = qMask[i]; if (i == words - 1) q &= lastMask; if ((q & (oneWin[i] | zeroWin[i])) != 0) return false; } return true; } private static bool TryDynamicModularRangePrune( ReadOnlySpan qOneOffsets, long qHash, ulong p, long maxAllowedA, bool top0KnownOne, bool top0KnownZero, bool top1KnownOne, bool top1KnownZero, long zeroTailStart) { _dynamicModChecks++; long effectiveTop = maxAllowedA; if (zeroTailStart <= effectiveTop + 1) { effectiveTop = zeroTailStart - 1; } if (effectiveTop > 4096) { return true; } ComputeARangeFromTopConstraints( maxAllowedA, top0KnownOne, top0KnownZero, top1KnownOne, top1KnownZero, zeroTailStart, out BigInteger minA, out BigInteger maxA); int modLocalPrefilterModuliLength = ModLocalPrefilterModuli.Length; for (int i = 0; i < modLocalPrefilterModuliLength; i++) { int mod = ModLocalPrefilterModuli[i]; int qMod = GetQModCached(qOneOffsets, qHash, mod); int target = ModPow2Minus1(p, mod); if (qMod == 0) { if (target != 0) { return false; } continue; } int inverse = ModPow(qMod, mod - 2, mod); int aResidue = (int)((long)target * inverse % mod); if (!ExistsCongruentInRange(minA, maxA, aResidue, mod)) { _dynamicModFailures++; return false; } } return true; } 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; } // internal static bool TryCheckDivisibilityFromDivisor(ulong q, ulong p, out bool divides, out ContradictionReason reason) // { // divides = false; // if (q == 0UL) // { // reason = ContradictionReason.ParityUnreachable; // return true; // } // Span oneOffsets = stackalloc int[64]; // int count = 0; // ulong remaining = q; // int baseIndex = 0; // while (remaining != 0UL) // { // int tz = BitOperations.TrailingZeroCount(remaining); // baseIndex += tz; // oneOffsets[count++] = baseIndex; // remaining >>= tz + 1; // baseIndex++; // } // return TryCheckDivisibilityFromOneOffsets(oneOffsets.Slice(0, count), p, out divides, out reason); // } [ThreadStatic] private static bool _initialized; // [ThreadStatic] // private static PrivateWorkSet _privateWorkSet; public static void Initialize() { if (_initialized) { return; } // _privateWorkSet = new(); // _stableUnknownDelta8 = new(); _initialized = true; } // #if DETAILED_LOG [ThreadStatic] private static long _statStableAdvanceCalls; [ThreadStatic] private static long _statStableAdvanceCols; [ThreadStatic] private static long _statStableAdvanceColsBatched; [ThreadStatic] private static long _statScalarCols; [ThreadStatic] private static long _statDeltaRequests; [ThreadStatic] private static long _statDeltaHits; [ThreadStatic] private static long _statDeltaBuilds; [ThreadStatic] private static long _statStableAdvanceBigShift; // ile razy columnCount>=64 w TryAdvanceStableUnknown [ThreadStatic] private static long _statBatchEntry; [ThreadStatic] private static long _statBatchCols; [ThreadStatic] private static long _statCleanChecks; [ThreadStatic] private static long _statCleanFalse; [ThreadStatic] private static int _statFirstDirtyColumn; [ThreadStatic] private static int _statFirstDirtyWord; [ThreadStatic] private static ulong _statFirstDirtyQ; [ThreadStatic] private static ulong _statFirstDirtyKnown; // #endif /// /// 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) { // PrivateWorkSet workSet = _privateWorkSet; divides = false; #if DETAILED_LOG _lastTopDownFailure = null; #endif // #if DETAILED_LOG _statStableAdvanceCalls = 0; _statStableAdvanceCols = 0; _statStableAdvanceColsBatched = 0; _statScalarCols = 0; _statDeltaRequests = 0; _statDeltaHits = 0; _statDeltaBuilds = 0; _statStableAdvanceBigShift = 0; _statCleanChecks = 0; _statCleanFalse = 0; _statFirstDirtyColumn = -1; // #endif _dynamicModChecks = 0; _dynamicModFailures = 0; // 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; // } // ulong mersenne = (1UL << (int)p) - 1UL; // divides = q != 0 && (mersenne % q == 0); // reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable; // return true; // } int qOneOffsetsLength = qOneOffsets.Length; int maxOffset = qOneOffsets[qOneOffsetsLength - 1]; if ((ulong)(maxOffset + 1) >= p) { reason = ContradictionReason.TruncatedLength; return true; } // q from EvenPerfectBitScanner will always have the correct 2kp+1 form. // if (qOneOffsets[0] != 0) // { // reason = ContradictionReason.ParityUnreachable; // return true; // } const int ForcedALowBits = 1024; int windowSize = maxOffset + 1; if (windowSize < ForcedALowBits) windowSize = ForcedALowBits; windowSize = (windowSize + 63) & ~63; int wordCount = (windowSize + 63) >> 6; // var knownOne0 = workSet.KnownOne0[..wordCount]; // var knownOne1 = workSet.KnownOne1[..wordCount]; // var knownZero0 = workSet.KnownZero0[..wordCount]; // var knownZero1 = workSet.KnownZero1[..wordCount]; ulong[] knownOne0 = new ulong[wordCount]; ulong[] knownOne1 = new ulong[wordCount]; ulong[] knownZero0 = new ulong[wordCount]; ulong[] knownZero1 = new ulong[wordCount]; int segmentBase = 0; int maxKnownA = -1; int pLong = (int)p; int maxAllowedA = pLong - (maxOffset + 1); if (maxAllowedA < 0) { reason = ContradictionReason.TruncatedLength; return true; } // Force low bits of 'a' from a ≡ -q^{-1} (mod 2^512). // This is a necessary condition for q | (2^p - 1) when p >= 512 (true here). const int ForcedBits = ForcedALowBits; // 512 BigInteger mask = (BigInteger.One << ForcedBits) - BigInteger.One; // Build q (low 512 bits) from qOneOffsets. q is small anyway, but do it generically. BigInteger qMod2k = BigInteger.Zero; for (int i = 0; i < qOneOffsetsLength; i++) { int bit = qOneOffsets[i]; if (bit >= ForcedBits) break; qMod2k |= (BigInteger.One << bit); } // q must be odd for any Mersenne divisor candidate. if (qMod2k.IsEven) { Console.WriteLine("Pruned by qMod2k.IsEven"); reason = ContradictionReason.ParityUnreachable; return true; } // Compute inv = q^{-1} mod 2^512 using Hensel/Newton iteration in Z/2^kZ. BigInteger inv = qMod2k; // OK seed for odd q. int iters = 0; for (int bits = 1; bits < ForcedBits; bits <<= 1) iters++; for (int it = 0; it < iters; it++) { inv = (inv * (2 - qMod2k * inv)) & mask; } // a ≡ -inv (mod 2^512) BigInteger aLow = (-inv) & mask; // Apply constraints for bits i in [0..min(511, maxAllowedA)] into knownOne0/knownZero0. // NOTE: now windowSize >= 512, so wordCount is enough to store these bits. int maxFixed = maxAllowedA < (ForcedBits - 1) ? maxAllowedA : (ForcedBits - 1); for (int i = 0; i <= maxFixed; i++) { bool bitIsOne = !aLow.IsZero && ((aLow >> i) & BigInteger.One) != 0; int word = i >> 6; ulong mask64 = 1UL << (i & 63); if (bitIsOne) { if ((knownZero0[word] & mask64) != 0) { Console.WriteLine("Pruned by knownZero1[word] & mask64"); reason = ContradictionReason.ParityUnreachable; return true; } knownOne0[word] |= mask64; } else { if ((knownOne0[word] & mask64) != 0) { Console.WriteLine("Pruned by knownOne1[word] & mask64"); reason = ContradictionReason.ParityUnreachable; return true; } knownZero0[word] |= mask64; } } // We have learned bits up to maxFixed in the current segment. if (maxFixed > maxKnownA) maxKnownA = maxFixed; int topIndex0 = maxAllowedA; int topIndex1 = maxAllowedA - 1; bool top0KnownOne = topIndex0 == 0; bool top1KnownOne = topIndex1 == 0; // bool top0KnownZero = false; // bool top1KnownZero = false; int zeroTailStart = maxAllowedA + 1; // long qHash = ComputeQOffsetsHash(qOneOffsets); // if (!TryModularPrefilter(qOneOffsets, qHash, p, maxAllowedA)) // { // reason = ContradictionReason.ParityUnreachable; // return true; // } int[] runStarts = new int[qOneOffsetsLength]; int[] runLengths = new int[qOneOffsetsLength]; // Span runStarts = qOneOffsetsLength <= 256 ? stackalloc int[qOneOffsetsLength] : new int[qOneOffsetsLength]; // Span runLengths = qOneOffsetsLength <= 256 ? stackalloc int[qOneOffsetsLength] : new int[qOneOffsetsLength]; // Span runStarts = workSet.RunStarts; // Span runLengths = workSet.RunLengths; int prev = qOneOffsets[0]; int currentStart = prev; int currentLength = 1; int runCount = 1; runStarts[0] = currentStart; runLengths[0] = currentLength; // int prefixCount = qOneOffsetsLength; // bool usedPrefixCache = false; // long prefixHash = 0; // if (PrefixRunCacheOffsets > 1 && qOneOffsetsLength > 1) // { // prefixCount = qOneOffsetsLength < PrefixRunCacheOffsets ? qOneOffsetsLength : PrefixRunCacheOffsets; // prefixHash = ComputeQOffsetsPrefixHash(qOneOffsets, prefixCount); // if (TryGetPrefixRunCache(prefixHash, out PrefixRunCacheEntry cachedPrefix) && cachedPrefix.PrefixCount == prefixCount) // { // cachedPrefix.RunStarts.CopyTo(runStarts); // cachedPrefix.RunLengths.CopyTo(runLengths); // runCount = cachedPrefix.RunCount; // prev = cachedPrefix.LastOffset; // currentStart = cachedPrefix.LastRunStart; // currentLength = cachedPrefix.LastRunLength; // usedPrefixCache = true; // } // else // { // usedPrefixCache = false; // } // } // int startIndex = usedPrefixCache ? prefixCount : 1; int startIndex = 1; for (int i = startIndex; i < qOneOffsetsLength; i++) { int offset = qOneOffsets[i]; if (offset == prev + 1) { currentLength++; runLengths[runCount - 1] = currentLength; } else { runStarts[runCount] = offset; runLengths[runCount] = 1; runCount++; currentStart = offset; currentLength = 1; } prev = offset; } // if (!usedPrefixCache && prefixCount > 1) // { // PrefixRunCacheEntry prefixEntry = BuildPrefixRunCache(qOneOffsets, prefixCount); // long key = prefixHash != 0 ? prefixHash : ComputeQOffsetsPrefixHash(qOneOffsets, prefixCount); // StorePrefixRunCache(key, prefixEntry); // } // Ultra-cheap high-bit feasibility check based on the forced top carry-out (no bit beyond p-1). // This is a tiny reverse-carry DP over the last few columns (p-1, p-2, ...), using only the // geometric availability of contributions implied by qOneOffsets and maxAllowedA. // It can reject entire classes of q with sparse/awkward high-bit structure before the full DP runs. if (!TryHighBitTopCarryPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA)) { Console.WriteLine("Pruned with TryHighBitTopCarryPrefilter"); reason = ContradictionReason.ParityUnreachable; return true; } // var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong); // if (TryGetTopDownCache(in cacheKey, out var cached)) // { // #if DETAILED_LOG // _lastTopDownFailure = cached.Failure; // if (!cached.Success) // #else // if (!cached) // #endif // { // reason = ContradictionReason.ParityUnreachable; // return true; // } // } // else // { // #if DETAILED_LOG // if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure)) // #else // if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA)) // #endif // { // #if DETAILED_LOG // // StoreTopDownCache(in cacheKey, new TopDownCacheValue(false, _lastTopDownFailure)); // #else // // StoreTopDownCache(in cacheKey, false); // #endif // reason = ContradictionReason.ParityUnreachable; // return true; // } #if DETAILED_LOG // StoreTopDownCache(in cacheKey, new TopDownCacheValue(true, null)); #else // StoreTopDownCache(in cacheKey, true); #endif // } // if (ShouldRunTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA)) // { // #if DETAILED_LOG // if (!TryTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure)) // #else // if (!TryTopDownBorrowPrefilter(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA)) // #endif // { // reason = ContradictionReason.ParityUnreachable; // return true; // } // } // Row-aware masks for q and a-window (supports q > 64 bits) int qBitLen = maxOffset + 1; int qWordCount = (qBitLen + 63) >> 6; ulong[] qMaskWords = new ulong[qWordCount]; for (int i = 0; i < qOneOffsetsLength; i++) { int t = qOneOffsets[i]; qMaskWords[t >> 6] |= 1UL << (t & 63); } ulong lastWordMask = (qBitLen & 63) == 0 ? ulong.MaxValue : ((1UL << (qBitLen & 63)) - 1UL); // Sliding window over a: bit t corresponds to a_{column - t}. ulong[] aOneWin = new ulong[qWordCount]; ulong[] aZeroWin = new ulong[qWordCount]; int windowColumn = 0; // Initialize window for column 0: mark negative a indices as zero (t>0). Clearing is only needed when the // arrays ever come from a pool, instead of creation. // Array.Clear(aOneWin); // Array.Clear(aZeroWin); // Insert a_0 status into bit0 int st = GetAKnownStateRowAware(0, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); if (st == 1) aOneWin[0] |= 1UL; else if (st == 2) aZeroWin[0] |= 1UL; int lowColumn = 0; int highColumn = pLong - 1; CarryRange carryLow = CarryRange.Zero; // int bottomRunStart = 0; // int bottomRunEnd = 0; // ----------------------------- // Option B mini ping-pong prefilter (chunk + reseed) // ----------------------------- // int chunk = MiniPingPongPrefilterColumns; // u Ciebie 60 // int maxDepth = 60; // ustaw tyle, ile chcesz testować (np. 960) // if (maxDepth > highColumn) maxDepth = highColumn; // long seedMin = 0; // long seedMax = 0; // int currentStartColumn = pLong - 1; // zaczynamy od samej góry (kolumna p-1) // for (int depth = chunk; depth <= maxDepth; depth += chunk) // { // int targetJ = pLong - depth; // if (targetJ <= 0 || targetJ > highColumn) break; // // 1) Bottom-up DP only up to column J (process columns lowColumn..J-1). // while (lowColumn < targetJ) // { // int remaining = targetJ - lowColumn; // int blockSize = remaining < PingPongBlockColumns ? remaining : PingPongBlockColumns; // if (!TryProcessBottomUpBlockRowAware( // qOneOffsets, qMaskWords, qWordCount, lastWordMask, maxAllowedA, // lowColumn, blockSize, // ref carryLow, ref maxKnownA, ref segmentBase, // ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1, // windowSize, // aOneWin, aZeroWin, ref windowColumn, // out reason)) // { // return true; // } // lowColumn += blockSize; // } // // 2) Top-down for exactly 'chunk' columns, seeded from previous intersection. // if (!TryHighBitCarryRangeToColumnJ( // qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, // startColumn: currentStartColumn, // steps: chunk, // startCarryOutMin: seedMin, // startCarryOutMax: seedMax, // out long topMin, out long topMax)) // { // Console.WriteLine("Pruned with TryHighBitCarryRangeToColumnJ"); // reason = ContradictionReason.ParityUnreachable; // return true; // } // // 3) Intersect with bottom-up carry at the SAME targetJ. // long interMin = carryLow.Min > topMin ? carryLow.Min : topMin; // long interMax = carryLow.Max < topMax ? carryLow.Max : topMax; // if (interMin > interMax) // { // Console.WriteLine("Pruned with interMin > interMax"); // reason = ContradictionReason.ParityUnreachable; // return true; // } // // 4) Reseed for the next chunk: carryOut above next block is our intersection. // seedMin = interMin; // seedMax = interMax; // // Next chunk starts right above the current targetJ. // currentStartColumn = targetJ - 1; // // Also tighten carryLow (optional but consistent). // carryLow = new CarryRange(interMin, interMax); // // Safety: if we fell off the bottom, stop. // if (currentStartColumn < 0) break; // } // ----------------------------- // Continue full bottom-up DP for remaining columns // ----------------------------- while (lowColumn <= highColumn) { int remaining = highColumn - lowColumn + 1; int blockSize = remaining < PingPongBlockColumns ? remaining : PingPongBlockColumns; if (!TryProcessBottomUpBlockRowAware( qOneOffsets, qMaskWords, qWordCount, lastWordMask, maxAllowedA, lowColumn, blockSize, ref carryLow, ref maxKnownA, ref segmentBase, ref knownOne0, ref knownOne1, ref knownZero0, ref knownZero1, windowSize, aOneWin, aZeroWin, ref windowColumn, out reason)) { Console.WriteLine( $"[STATS] stableCalls={_statStableAdvanceCalls} stableCols={_statStableAdvanceColsBatched} " + $"scalarCols={_statScalarCols} bigShift={_statStableAdvanceBigShift} " + $"deltaReq={_statDeltaRequests} deltaHits={_statDeltaHits} deltaBuilds={_statDeltaBuilds}"); Console.WriteLine( $"[CLEAN] checks={_statCleanChecks} false={_statCleanFalse} " + $"firstDirtyCol={_statFirstDirtyColumn} word={_statFirstDirtyWord} " + $"qWord=0x{_statFirstDirtyQ:X16} knownWord=0x{_statFirstDirtyKnown:X16}"); return true; } lowColumn += blockSize; } // 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; // } // if (_debugEnabled) // { // Console.WriteLine($"[bit-contradiction] AMultiplier dynamic-mod checks={_dynamicModChecks} failures={_dynamicModFailures}"); // } divides = true; reason = ContradictionReason.None; // #if DETAILED_LOG Console.WriteLine( $"[STATS] stableCalls={_statStableAdvanceCalls} stableCols={_statStableAdvanceColsBatched} " + $"scalarCols={_statScalarCols} bigShift={_statStableAdvanceBigShift} " + $"deltaReq={_statDeltaRequests} deltaHits={_statDeltaHits} deltaBuilds={_statDeltaBuilds}"); Console.WriteLine( $"[CLEAN] checks={_statCleanChecks} false={_statCleanFalse} " + $"firstDirtyCol={_statFirstDirtyColumn} word={_statFirstDirtyWord} " + $"qWord=0x{_statFirstDirtyQ:X16} knownWord=0x{_statFirstDirtyKnown:X16}"); // #endif return true; } internal static bool TryCheckDivisibilityFromOneOffsets( ReadOnlySpan qOneOffsets, ulong p, out bool divides) => TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong BuildQLow64(ReadOnlySpan qOneOffsets) { ulong q = 0; for (int i = 0; i < qOneOffsets.Length; i++) { int bit = qOneOffsets[i]; if ((uint)bit >= 64u) break; q |= 1UL << bit; } return q; } /// /// Modular inverse of odd q modulo 2^64. /// Uses Newton/Hensel lifting: x_{k+1} = x_k * (2 - q*x_k) mod 2^64. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong ModInversePow2_64(ulong qOdd) { // qOdd must be odd. // Start with x ≡ 1 (mod 2). A common fast seed is x = qOdd. ulong x = qOdd; // Each iteration doubles correct bits. 6 iterations are enough for 64 bits: 1->2->4->8->16->32->64. unchecked { x *= 2UL - qOdd * x; x *= 2UL - qOdd * x; x *= 2UL - qOdd * x; x *= 2UL - qOdd * x; x *= 2UL - qOdd * x; x *= 2UL - qOdd * x; } return x; } [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) { int step = columnCount > 30 ? 30 : columnCount; int add = (1 << step) - 1; long nextMin = (carryMin + add) >> step; long nextMax = carryMax >> step; if (nextMin > nextMax) { return false; } carryMin = nextMin; carryMax = nextMax; columnCount -= step; } carry = new CarryRange(carryMin, carryMax); return true; } // [ThreadStatic] // private static Dictionary _stableUnknownDelta8; // Indeksowane przez "unknown" (zwykle qOneOffsetsLength). Elementy inicjalizowane leniwie. private static volatile int[][]? _stableUnknownDelta8ByUnknown; private static int[] GetStableUnknownDelta8(int unknown) { // #if DETAILED_LOG _statDeltaRequests++; // #endif // Lazily allocate the outer array. Keep it volatile to publish safely. var cache = _stableUnknownDelta8ByUnknown; if (cache == null) { // Start with a small size and grow on demand. cache = new int[64][]; _stableUnknownDelta8ByUnknown = cache; } // Ensure capacity for this "unknown". if ((uint)unknown >= (uint)cache.Length) { // Grow to at least unknown+1, doubling for amortized O(1). int newLen = cache.Length; while (newLen <= unknown) newLen <<= 1; var newCache = new int[newLen][]; Array.Copy(cache, newCache, cache.Length); // Publish the grown cache (racy publish is OK; contents are immutable after creation). _stableUnknownDelta8ByUnknown = newCache; cache = newCache; } // Fast path: already computed. var cached = cache[unknown]; if (cached != null) { // #if DETAILED_LOG _statDeltaHits++; // #endif return cached; } // Compute. const int PrefixLength = 256; int[] delta = new int[PrefixLength]; for (int low = 0; low < PrefixLength; low++) { long value = low; // 8 iterations correspond to processing 8 columns at once. for (int i = 0; i < 8; i++) { // Only the LSB of 'value' matters; avoid long->int truncation except for bit0. int parity = (int)(value & 1L); int sMax = (((unknown ^ parity) & 1) != 0) ? unknown : (unknown - 1); value = (value + sMax - 1) >> 1; } delta[low] = (int)((value << 8) - low); } // Publish into cache. If a race computes it twice, that's acceptable (same deterministic result). // #if DETAILED_LOG _statDeltaBuilds++; // #endif cache[unknown] = delta; return delta; } // private static int[] GetStableUnknownDelta8(int unknown) // { // var stableUnknownDelta8Cache = _stableUnknownDelta8; // if (stableUnknownDelta8Cache.TryGetValue(unknown, out int[]? cached)) // { // return cached!; // } // const int PrefixLength = 131072; // int[] delta = new int[PrefixLength]; // for (int low = 0; low < PrefixLength; low++) // { // long value = low; // for (int i = 0; i < 8; i++) // { // int sMax = (((unknown ^ (int)value) & 1) != 0) ? unknown : unknown - 1; // value = (value + sMax - 1) >> 1; // } // delta[low] = (int)((value << 8) - low); // } // stableUnknownDelta8Cache[unknown] = delta; // return delta; // } private static bool TryAdvanceStableUnknown(ref CarryRange carry, int unknown, int columnCount, out ContradictionReason reason) { // #if DETAILED_LOG _statStableAdvanceCalls++; _statStableAdvanceCols += columnCount; if (columnCount >= 64) _statStableAdvanceBigShift++; // #endif if (columnCount <= 0) { reason = ContradictionReason.None; // #if DETAILED_LOG _statStableAdvanceColsBatched += columnCount; // #endif return true; } if (unknown <= 0) { if (!TryAdvanceZeroColumns(ref carry, columnCount)) { reason = ContradictionReason.ParityUnreachable; return false; } // #if DETAILED_LOG _statStableAdvanceColsBatched += columnCount; // #endif reason = ContradictionReason.None; return true; } long carryMin = columnCount >= 63 ? 0 : (carry.Min >> columnCount); long carryMax = carry.Max; int remaining = columnCount; int[] delta = GetStableUnknownDelta8(unknown); while (remaining >= 8) { int low = (int)(carryMax & 255); carryMax = (carryMax + delta[low]) >> 8; remaining -= 8; } while (remaining > 0) { int sMax = (((unknown ^ (int)carryMax) & 1) != 0) ? unknown : unknown - 1; carryMax = (carryMax + sMax - 1) >> 1; remaining--; } if (carryMax < carryMin) { reason = ContradictionReason.ParityUnreachable; return false; } // #if DETAILED_LOG _statStableAdvanceColsBatched += columnCount; // #endif carry = new CarryRange(carryMin, carryMax); reason = ContradictionReason.None; return true; } // ----------------------------- // Row-aware bottom-up block processing // ----------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ShiftLeft1InPlace(ulong[] words, int wordCount, ulong lastWordMask) { ulong carry = 0; for (int i = 0; i < wordCount; i++) { ulong w = words[i]; ulong newCarry = w >> 63; words[i] = (w << 1) | carry; carry = newCarry; } if (wordCount > 0) { words[wordCount - 1] &= lastWordMask; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool AnyAndNonZero(ulong[] a, ulong[] b, int wordCount) { for (int i = 0; i < wordCount; i++) { if ((a[i] & b[i]) != 0) return true; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int PopCountAnd(ulong[] a, ulong[] b, int wordCount) { int c = 0; for (int i = 0; i < wordCount; i++) { c += BitOperations.PopCount(a[i] & b[i]); } return c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int PopCountUnknownInQ(ulong[] qMask, ulong[] oneWin, ulong[] zeroWin, int wordCount) { int c = 0; for (int i = 0; i < wordCount; i++) { ulong known = oneWin[i] | zeroWin[i]; ulong unkQ = qMask[i] & ~known; c += BitOperations.PopCount(unkQ); } return c; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryFindSingleUnknownInQ(ulong[] qMask, ulong[] oneWin, ulong[] zeroWin, int wordCount, out int offset) { // Returns true if exactly one unknown bit among positions where q has 1. offset = -1; int found = 0; for (int w = 0; w < wordCount; w++) { ulong known = oneWin[w] | zeroWin[w]; ulong unk = qMask[w] & ~known; if (unk == 0) continue; int pc = BitOperations.PopCount(unk); found += pc; if (found > 1) return false; int bit = BitOperations.TrailingZeroCount(unk); offset = (w << 6) + bit; } return found == 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetAKnownStateRowAware( int aIndex, int segmentBase, int windowSize, ulong[] knownOne0, ulong[] knownZero0, ulong[] knownOne1, ulong[] knownZero1) { // 0 unknown, 1 one, 2 zero if (aIndex < 0) return 2; if (aIndex >= segmentBase && aIndex < segmentBase + windowSize) { int rel = aIndex - segmentBase; int w = rel >> 6; ulong m = 1UL << (rel & 63); if ((knownOne0[w] & m) != 0) return 1; if ((knownZero0[w] & m) != 0) return 2; return 0; } int seg1Start = segmentBase - windowSize; if (aIndex >= seg1Start && aIndex < segmentBase) { int rel = aIndex - seg1Start; int w = rel >> 6; ulong m = 1UL << (rel & 63); if ((knownOne1[w] & m) != 0) return 1; if ((knownZero1[w] & m) != 0) return 2; return 0; } return 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void SetAKnownRowAware( int aIndex, bool valueOne, int segmentBase, int windowSize, ulong[] knownOne0, ulong[] knownZero0, ulong[] knownOne1, ulong[] knownZero1) { if (aIndex < 0) return; if (aIndex >= segmentBase && aIndex < segmentBase + windowSize) { int rel = aIndex - segmentBase; int w = rel >> 6; ulong m = 1UL << (rel & 63); if (valueOne) knownOne0[w] |= m; else knownZero0[w] |= m; return; } int seg1Start = segmentBase - windowSize; if (aIndex >= seg1Start && aIndex < segmentBase) { int rel = aIndex - seg1Start; int w = rel >> 6; ulong m = 1UL << (rel & 63); if (valueOne) knownOne1[w] |= m; else knownZero1[w] |= m; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryFindSingleUnknownInQ_Prefix( ulong[] qMask, ulong[] oneWin, ulong[] zeroWin, int words, ulong lastMask, out int offset) { offset = -1; int found = 0; for (int w = 0; w < words; w++) { ulong q = qMask[w]; if (w == words - 1) q &= lastMask; ulong known = oneWin[w] | zeroWin[w]; ulong unk = q & ~known; if (unk == 0) continue; int pc = BitOperations.PopCount(unk); found += pc; if (found > 1) return false; int bit = BitOperations.TrailingZeroCount(unk); offset = (w << 6) + bit; } return found == 1; } private static bool TryProcessBottomUpBlockRowAware( ReadOnlySpan qOneOffsets, ulong[] qMaskWords, int qWordCount, ulong lastWordMask, int maxAllowedA, int startColumn, int columnCount, ref CarryRange carry, ref int maxKnownA, ref int segmentBase, ref ulong[] knownOne0, ref ulong[] knownOne1, ref ulong[] knownZero0, ref ulong[] knownZero1, int windowSize, ulong[] aOneWin, ulong[] aZeroWin, ref int windowColumn, out ContradictionReason reason) { bool needRebuildWindow = false; int endColumn = startColumn + columnCount; int qOneOffsetsLength = qOneOffsets.Length; int maxOffset = qOneOffsets[qOneOffsetsLength - 1]; int stableUnknown = qOneOffsetsLength; ulong[] temp; // Ensure window is aligned. ArgumentOutOfRangeException.ThrowIfNotEqual(windowColumn, startColumn); while (startColumn < endColumn) { // Segment rollover based on current column (because we may set aIndex within [startColumn-maxOffset..startColumn]). int bound = (startColumn / windowSize) * windowSize; if (startColumn == 0 && segmentBase != 0) throw new Exception("segmentBase must start at 0 for row-aware"); if (bound != segmentBase) { if (bound == segmentBase + windowSize) { temp = knownOne0; knownOne0 = knownOne1; knownOne1 = temp; temp = knownZero0; knownZero0 = knownZero1; knownZero1 = temp; Array.Clear(knownOne0); Array.Clear(knownZero0); } else { Array.Clear(knownOne0); Array.Clear(knownOne1); Array.Clear(knownZero0); Array.Clear(knownZero1); // Also clear window because it may reference bits outside cache. needRebuildWindow = true; } segmentBase = bound; if (needRebuildWindow) { Array.Clear(aOneWin); Array.Clear(aZeroWin); // Build window for current startColumn: bit t corresponds to a_{startColumn - t} for (int t = 0; t <= maxOffset; t++) { int aIndex = startColumn - t; int st = aIndex < 0 ? 2 : (aIndex > maxAllowedA ? 2 : GetAKnownStateRowAware(aIndex, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1)); if (st == 1) aOneWin[t >> 6] |= 1UL << (t & 63); else if (st == 2) aZeroWin[t >> 6] |= 1UL << (t & 63); } if (qWordCount > 0) aOneWin[qWordCount - 1] &= lastWordMask; if (qWordCount > 0) aZeroWin[qWordCount - 1] &= lastWordMask; needRebuildWindow = false; } } int qWordsEff = qWordCount; ulong qLastEff = lastWordMask; if (startColumn < maxOffset) { GetQPrefixMask(startColumn, maxOffset + 1, qWordCount, out qWordsEff, out ulong prefixLast); qLastEff = prefixLast; } // StableUnknown batching is valid only if the window contains no known bits in the q-active positions. if (startColumn >= maxOffset && startColumn <= maxAllowedA) { int remainingCols = endColumn - 1; if (remainingCols > maxAllowedA) remainingCols = maxAllowedA; remainingCols = remainingCols - startColumn + 1; if (remainingCols > 0) { // Additionally, ensure current window has no known bits (should be true if maxKnownA condition holds). // Conservative batching: ignore row-aware window state and assume all q positions are unknown. // This cannot create false negatives; it may only reduce pruning power. _statBatchEntry++; _statBatchCols += remainingCols; int rem = remainingCols; while (rem > 0) { int step = rem > TailCarryBatchColumns ? TailCarryBatchColumns : rem; if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step, out var propagateReason)) { reason = propagateReason; return false; } startColumn += step; windowColumn = startColumn; rem -= step; } // Reset window to unknown (no known bits) because we skipped updating it across columns. Array.Clear(aOneWin); Array.Clear(aZeroWin); continue; } } // Mark out-of-range (negative) a indices as zero in this column. // if (startColumn < maxOffset) // { // int firstOut = startColumn + 1; // for (int t = firstOut; t <= maxOffset; t++) // { // int w = t >> 6; // int b = t & 63; // aZeroWin[w] |= 1UL << b; // } // if (qWordCount > 0) aZeroWin[qWordCount - 1] &= lastWordMask; // } ComputeForcedRemainingAndClean_Prefix(qMaskWords, aOneWin, aZeroWin, qWordsEff, qLastEff, out int forced, out int remaining, out _); // If exactly one unknown contribution among q=1 positions AND carry parity is fixed, // we can force the corresponding a bit to satisfy the required result bit parity. if (remaining == 1 && ((carry.Min ^ carry.Max) & 1L) == 0) { if (TryFindSingleUnknownInQ_Prefix(qMaskWords, aOneWin, aZeroWin, qWordsEff, qLastEff, out int t)) { int aIndex = startColumn - t; // Required result bit for M_p in columns [0..p-1] is 1. const int requiredBit = 1; // We need (carryIn + forced + x) % 2 == requiredBit, where x ∈ {0,1}. int carryParity = (int)(carry.Min & 1L); // fixed parity because Min/Max same parity int x = (requiredBit ^ carryParity ^ (forced & 1)) & 1; bool needOne = x != 0; // Check contradiction with already-known value. int state = GetAKnownStateRowAware(aIndex, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); if (needOne) { if (state == 2) { reason = ContradictionReason.ParityUnreachable; return false; } SetAKnownRowAware(aIndex, true, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); // Reflect in window at offset t int w = t >> 6; int b = t & 63; aOneWin[w] |= 1UL << b; forced += 1; } else { if (state == 1) { reason = ContradictionReason.ParityUnreachable; return false; } SetAKnownRowAware(aIndex, false, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); int w = t >> 6; int b = t & 63; aZeroWin[w] |= 1UL << b; } remaining = 0; if (aIndex > maxKnownA) maxKnownA = aIndex; } } // Now propagate carry using forced and possible (=forced+remaining). int possible = forced + remaining; if (!TryPropagateCarry(ref carry, forced, possible, 1)) { reason = ContradictionReason.ParityUnreachable; return false; } // Advance to next column: shift window, then insert status of a_{startColumn+1} into bit0. int nextA = startColumn + 1; ShiftLeft1InPlace(aOneWin, qWordCount, lastWordMask); ShiftLeft1InPlace(aZeroWin, qWordCount, lastWordMask); // Insert bit0 for nextA int nextState; if (nextA > maxAllowedA) nextState = 2; else nextState = GetAKnownStateRowAware(nextA, segmentBase, windowSize, knownOne0, knownZero0, knownOne1, knownZero1); if (nextState == 1) aOneWin[0] |= 1UL; else if (nextState == 2) aZeroWin[0] |= 1UL; startColumn++; windowColumn = startColumn; // #if DETAILED_LOG _statScalarCols++; // #endif } reason = ContradictionReason.None; return true; } private static bool TryProcessBottomUpBlock( ReadOnlySpan qOneOffsets, in int[] runStarts, in int[] runLengths, int runCount, int maxAllowedA, int startColumn, int columnCount, ref CarryRange carry, ref int maxKnownA, ref int segmentBase, ref int runStartIdx, ref int runEndIdx, ulong[] knownOne0, ulong[] knownOne1, ulong[] knownZero0, ulong[] knownZero1, int windowSize, int topIndex0, int topIndex1, ref bool top0KnownOne, ref bool top1KnownOne, ref bool top0KnownZero, ref bool top1KnownZero, ref int zeroTailStart, out ContradictionReason reason) { int endColumn = startColumn + columnCount; int qOneOffsetsLength = qOneOffsets.Length; int maxOffset = qOneOffsets[qOneOffsetsLength - 1]; int stableUnknown = qOneOffsetsLength; int bound, runStart, runEnd; ulong[] temp; // Span temp; while (startColumn < endColumn) { int remaining; if (startColumn >= maxOffset && startColumn <= maxAllowedA && maxKnownA < startColumn - maxOffset) { remaining = endColumn - 1; if (remaining > maxAllowedA) { remaining = maxAllowedA; } remaining = remaining - startColumn + 1; if (remaining >= 16) { runStartIdx = 0; runEndIdx = runCount; while (remaining > 0) { int step = remaining > TailCarryBatchColumns ? TailCarryBatchColumns : remaining; if (!TryAdvanceStableUnknown(ref carry, stableUnknown, step, out var propagateReason)) { reason = propagateReason; return false; } startColumn += step; remaining -= step; } bound = (startColumn / windowSize) * windowSize; if (bound != segmentBase) { if (bound == segmentBase + windowSize) { // rollover forward by one segment: swap and clear only the new "current" segment temp = knownOne0; knownOne0 = knownOne1; knownOne1 = temp; temp = knownZero0; knownZero0 = knownZero1; knownZero1 = temp; Array.Clear(knownOne0); Array.Clear(knownZero0); } else { // jumped more than one segment: we genuinely lost locality; clear both Array.Clear(knownOne0); Array.Clear(knownOne1); Array.Clear(knownZero0); Array.Clear(knownZero1); } segmentBase = bound; } continue; } } int forced = 0; remaining = 0; int chosenIndex = -1; // bool topChanged = false; bound = (startColumn / windowSize) * windowSize; if (bound != segmentBase) { if (bound == segmentBase + windowSize) { temp = knownOne0; knownOne0 = knownOne1; knownOne1 = temp; temp = knownZero0; knownZero0 = knownZero1; knownZero1 = temp; // knownOne0.Clear(); // knownZero0.Clear(); Array.Clear(knownOne0); Array.Clear(knownZero0); } else { // knownOne0.Clear(); // knownOne1.Clear(); // knownZero0.Clear(); // knownZero1.Clear(); Array.Clear(knownOne0); Array.Clear(knownOne1); Array.Clear(knownZero0); Array.Clear(knownZero1); } segmentBase = bound; } bound = startColumn - maxAllowedA; while (runEndIdx < runCount && runStarts[runEndIdx] <= startColumn) { runEndIdx++; } while (runStartIdx < runEndIdx) { runStart = runStarts[runStartIdx]; runEnd = runStart + runLengths[runStartIdx] - 1; if (runEnd < bound) { runStartIdx++; continue; } break; } int known0Len, runIndex; int seg0Start = segmentBase; for (runIndex = runStartIdx; runIndex < runEndIdx; runIndex++) { runStart = runStarts[runIndex]; if (runStart > startColumn) { break; } runEnd = runStart + runLengths[runIndex] - 1; if (runEnd > startColumn) { runEnd = startColumn; } if (runStart < bound) { runStart = bound; } if (runStart > runEnd) { continue; } int aStart = startColumn - runEnd; runEnd = startColumn - runStart; if (aStart > maxAllowedA) { continue; } if (runEnd > maxAllowedA) { runEnd = maxAllowedA; } int rangeLength = runEnd - aStart + 1; if (rangeLength <= 0) { continue; } int seg0End = seg0Start + windowSize - 1; int seg1Start = seg0Start - windowSize; int seg1End = seg0Start - 1; int overlapStart = aStart > seg0Start ? aStart : seg0Start; int overlap0End = runEnd < seg0End ? runEnd : seg0End; // seg1End is now overlap1End to limit registry pressure seg1End = runEnd < seg1End ? runEnd : seg1End; known0Len = 0; int ones; if (overlapStart <= overlap0End) { runStart = overlapStart - seg0Start; // overlap0Start is now len to limit registry pressure overlapStart = overlap0End - overlapStart + 1; ones = CountBitsInRange(knownOne0, runStart, overlapStart); // overlap0End is now zeros to limit registry pressure overlap0End = CountBitsInRange(knownZero0, runStart, overlapStart); forced += ones; remaining += overlapStart - ones - overlap0End; known0Len = overlapStart; if (chosenIndex == -1 && overlapStart > ones + overlap0End && TryFindFirstUnknown(knownOne0, knownZero0, runStart, overlapStart, out int idx)) { chosenIndex = seg0Start + idx; } } // overlap0End is now knownTotal to limit registry pressure overlap0End = 0; overlapStart = aStart > seg1Start ? aStart : seg1Start; if (overlapStart <= seg1End) { runStart = overlapStart - seg1Start; // overlap1Start is now len to limit registry pressure overlapStart = seg1End - overlapStart + 1; ones = CountBitsInRange(knownOne1, runStart, overlapStart); // seg1End is now zeros to limit registry pressure seg1End = CountBitsInRange(knownZero1, runStart, overlapStart); forced += ones; remaining += overlapStart - ones - seg1End; overlap0End = overlapStart; if (chosenIndex == -1 && overlapStart > ones + seg1End && TryFindFirstUnknown(knownOne1, knownZero1, runStart, overlapStart, out int idx)) { chosenIndex = seg1Start + idx; } } overlap0End = known0Len + overlap0End; if (overlap0End < rangeLength) { remaining += rangeLength - overlap0End; } if (zeroTailStart <= runEnd) { // overlap0End is now tailStart to limit registry pressure overlap0End = aStart > zeroTailStart ? aStart : zeroTailStart; if (overlap0End <= runEnd) { rangeLength = runEnd - overlap0End + 1; if (rangeLength > 0) { remaining -= rangeLength; if (remaining < 0) { remaining = 0; } } } } } long minSum = carry.Min; long maxSum = carry.Max; ulong mask; if (remaining == 1 && ((minSum ^ maxSum) & 1) == 0) { // known0Len is now requiredUnknownParity to limit registry pressure known0Len = 1 ^ ((int)minSum & 1) ^ (forced & 1); if (known0Len != 0) { if (chosenIndex >= 0) { if (chosenIndex >= seg0Start) { bound = chosenIndex - seg0Start; // runIndex is now word to limit registry pressure runIndex = bound >> 6; mask = 1UL << (bound & 63); knownOne0[runIndex] |= mask; knownZero0[runIndex] &= ~mask; } else { bound = chosenIndex - (seg0Start - windowSize); // runIndex is now word to limit registry pressure runIndex = bound >> 6; mask = 1UL << (bound & 63); knownOne1[runIndex] |= mask; knownZero1[runIndex] &= ~mask; } forced++; remaining = 0; if (chosenIndex > maxKnownA) { maxKnownA = chosenIndex; } if (chosenIndex == topIndex0) { if (top0KnownZero) { reason = ContradictionReason.ParityUnreachable; return false; } top0KnownOne = true; // topChanged = true; } else if (chosenIndex == topIndex1) { if (top1KnownZero) { reason = ContradictionReason.ParityUnreachable; return false; } top1KnownOne = true; // topChanged = true; } } } else { // if (_debugEnabled && _parityZeroLogCount < 8) // { // _parityZeroLogCount++; // Console.WriteLine($"[bit-contradiction] unknown==1 parity forces zero at column={column} carry=[{minSum},{maxSum}] forced={forced} chosenIndex={chosenIndex}"); // } // Treat the single unknown as 0 for parity. remaining = 0; if (chosenIndex == topIndex0) { if (top0KnownOne) { reason = ContradictionReason.ParityUnreachable; return false; } top0KnownZero = true; UpdateZeroTailFromTop(ref zeroTailStart, topIndex0, topIndex1, top0KnownZero, top1KnownZero); } else if (chosenIndex == topIndex1) { if (top1KnownOne) { reason = ContradictionReason.ParityUnreachable; return false; } top1KnownZero = true; UpdateZeroTailFromTop(ref zeroTailStart, topIndex0, topIndex1, top0KnownZero, top1KnownZero); } } } // if (topChanged) // { // if (!TryDynamicModularRangePrune(qOneOffsets, qHash, p, maxAllowedA, top0KnownOne, top0KnownZero, top1KnownOne, top1KnownZero, zeroTailStart)) // { // reason = ContradictionReason.ParityUnreachable; // return false; // } // } if ((top0KnownZero && top0KnownOne) || (top1KnownZero && top1KnownOne)) { reason = ContradictionReason.ParityUnreachable; return false; } if (topIndex1 < 0) { if (top0KnownZero) { reason = ContradictionReason.ParityUnreachable; return false; } } else if (top0KnownZero && top1KnownZero) { reason = ContradictionReason.ParityUnreachable; return false; } if (remaining == 0) { // known0Len is now requiredCarryParity to limit registry pressure known0Len = 1 ^ (forced & 1); long carryMin = AlignUpToParity(carry.Min, known0Len); long carryMax = AlignDownToParity(carry.Max, known0Len); if (carryMin > carryMax) { reason = ContradictionReason.ParityUnreachable; return false; } carry = new CarryRange(carryMin, carryMax); minSum = carryMin; maxSum = carryMax; } minSum += forced; maxSum += forced + remaining; const int requiredParity = 1; if (minSum == maxSum) { if ((minSum & 1) != requiredParity) { reason = ContradictionReason.ParityUnreachable; return false; } minSum = (minSum - requiredParity) >> 1; carry = CarryRange.Single(minSum); } else { if ((minSum & 1) != requiredParity && remaining == 0) { reason = ContradictionReason.ParityUnreachable; return false; } if (!TryPropagateCarry( ref carry, forced, forced + remaining, requiredParity)) { reason = ContradictionReason.ParityUnreachable; return false; } } startColumn++; // #if DETAILED_LOG _statScalarCols++; // #endif } reason = ContradictionReason.None; return true; } #if DETAILED_LOG private static bool TryProcessTopDownBlock( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, long maxAllowedA, long startHighColumn, int columnCount, CarryRange carryOut, ref int windowStart, ref int windowEnd, out CarryRange nextCarryOut, out TopDownPruneFailure? failure) #else private static bool TryProcessTopDownBlock( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, int maxAllowedA, int startHighColumn, int columnCount, CarryRange carryOut, ref int windowStart, ref int windowEnd, out CarryRange nextCarryOut) #endif { #if DETAILED_LOG failure = null; #endif long carryOutMin = carryOut.Min, carryOutMax = carryOut.Max; int value; int endColumn = startHighColumn - columnCount + 1; int column = startHighColumn; while (column >= endColumn) { // 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; if (value == 0) { int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue; int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue; int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart; if (chunkEnd < endColumn) { chunkEnd = endColumn; } int steps = (int)(column - chunkEnd + 1); if (steps > 1) { if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps)) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); #endif nextCarryOut = default; return false; } column = chunkEnd - 1; continue; } } else if (value == 1) { int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue; int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue; int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart; if (chunkEnd < endColumn) { chunkEnd = endColumn; } int steps = (int)(column - chunkEnd + 1); if (steps > 1) { if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps)) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); #endif nextCarryOut = default; return false; } column = chunkEnd - 1; continue; } } // 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) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); #endif nextCarryOut = default; return false; } column--; } nextCarryOut = new CarryRange(carryOutMin, carryOutMax); return true; } private static long AlignUpToParity(long value, int parity) => (value & 1L) == parity ? value : value + 1L; private static long AlignDownToParity(long value, int parity) => (value & 1L) == parity ? value : value - 1L; #if DETAILED_LOG private static bool TryTopDownCarryPrune( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, int pLong, int maxAllowedA, out TopDownPruneFailure? failure) #else private static bool TryTopDownCarryPrune( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, int pLong, int maxAllowedA) #endif { #if DETAILED_LOG failure = null; #endif int windowStart = qOneOffsetsLength; int windowEnd = qOneOffsetsLength; long carryOutMin = 0, carryOutMax = 0; int value; int column = pLong - 1; while (column >= 0) { // 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; if (value == 0) { int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue; int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue; int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart; if (chunkEnd < 0) { chunkEnd = 0; } int steps = (int)(column - chunkEnd + 1); if (steps > 1) { if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps)) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); #endif return false; } column = chunkEnd - 1; continue; } } else if (value == 1) { int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue; int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue; int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart; if (chunkEnd < 0) { chunkEnd = 0; } int steps = (int)(column - chunkEnd + 1); if (steps > 1) { if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps)) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); #endif return false; } column = chunkEnd - 1; continue; } } // 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) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value); #endif return false; } column--; } if (carryOutMin > 0) { #if DETAILED_LOG failure = new TopDownPruneFailure(0, carryOutMin, carryOutMax, 0); #endif return false; } return true; } #if DETAILED_LOG private static bool TryTopDownBorrowPrefilter( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, long pLong, long maxAllowedA, out TopDownPruneFailure? failure) #else private static bool TryTopDownBorrowPrefilter( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, int pLong, int maxAllowedA) #endif { #if DETAILED_LOG failure = null; #endif CarryRange borrow = CarryRange.Zero; int windowStart = qOneOffsetsLength; int windowEnd = qOneOffsetsLength; int column = pLong - 1; int endColumn = column - (TopDownBorrowColumns - 1); if (endColumn < 0) { endColumn = 0; } while (column >= endColumn) { int value = column; while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value) { windowEnd--; } value = column - maxAllowedA; if (value < 0) { value = 0; } while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value) { windowStart--; } if (windowStart > windowEnd) { windowStart = windowEnd; } int unknown = windowEnd - windowStart; if (!TryPropagateCarry(ref borrow, 0, unknown, 1)) { #if DETAILED_LOG failure = new TopDownPruneFailure(column, borrow.Min, borrow.Max, unknown); #endif return false; } column--; } return true; } private static bool ShouldRunTopDownBorrowPrefilter( ReadOnlySpan qOneOffsets, int qOneOffsetsLength, long pLong, long maxAllowedA) { if (qOneOffsetsLength < TopDownBorrowMinUnknown) { return false; } long column = pLong - 1; long start = column - maxAllowedA; if (start < 0) { start = 0; } int lower = LowerBound(qOneOffsets, (int)start); int upper = UpperBound(qOneOffsets, (int)column); int unknownTop = upper - lower; return unknownTop >= TopDownBorrowMinUnknown; } private static bool AdvanceTopDownUnknown1(ref long carryOutMin, ref long carryOutMax, int steps) { while (steps > 0) { int step = steps > 30 ? 30 : steps; int pow = 1 << step; if (carryOutMin > (int.MaxValue >> step) || carryOutMax > ((int.MaxValue - (pow - 1)) >> step)) { carryOutMin = 0; carryOutMax = int.MaxValue; return true; } carryOutMin <<= step; carryOutMax = (carryOutMax << step) + (pow - 1); steps -= step; } return true; } private static bool AdvanceTopDownUnknown0(ref long carryOutMin, ref long carryOutMax, int steps) { while (steps > 0) { int step = steps > 30 ? 30 : steps; int add = (1 << step) - 1; if (carryOutMin > ((int.MaxValue - add) >> step) || carryOutMax > ((int.MaxValue - add) >> step)) { carryOutMin = 0; carryOutMax = int.MaxValue; return true; } carryOutMin = (carryOutMin << step) + add; carryOutMax = (carryOutMax << step) + add; steps -= step; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int CountBitsInRange(ulong[] bits, int start, int length) { if (length <= 0) { return 0; } int end = start + length - 1; int firstWord = start >> 6; int lastWord = end >> 6; int startBit = start & 63; int endBit = end & 63; if (firstWord == lastWord) { ulong mask = (endBit == 63 ? ulong.MaxValue : ((1UL << (endBit + 1)) - 1UL)) & (ulong.MaxValue << startBit); return BitOperations.PopCount(bits[firstWord] & mask); } int count = 0; ulong firstMask = ulong.MaxValue << startBit; count += BitOperations.PopCount(bits[firstWord] & firstMask); for (int word = firstWord + 1; word < lastWord; word++) { count += BitOperations.PopCount(bits[word]); } ulong lastMask = endBit == 63 ? ulong.MaxValue : ((1UL << (endBit + 1)) - 1UL); count += BitOperations.PopCount(bits[lastWord] & lastMask); return count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryFindFirstUnknown(in ulong[] knownOne, in ulong[] knownZero, int start, int length, out int index) { if (length <= 0) { index = -1; return false; } int end = start + length - 1; int firstWord = start >> 6; int lastWord = end >> 6; int startBit = start & 63; int endBit = end & 63; ulong firstMask = ulong.MaxValue << startBit; ulong lastMask = endBit == 63 ? ulong.MaxValue : ((1UL << (endBit + 1)) - 1UL); for (int word = firstWord; word <= lastWord; word++) { ulong mask = word == firstWord ? firstMask : ulong.MaxValue; if (word == lastWord) { mask &= lastMask; } ulong known = (knownOne[word] | knownZero[word]) & mask; ulong unknown = mask & ~known; if (unknown != 0) { int bit = BitOperations.TrailingZeroCount(unknown); index = (word << 6) + bit; return true; } } index = -1; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UpdateZeroTailFromTop(ref int zeroTailStart, int topIndex0, int topIndex1, bool top0KnownZero, bool top1KnownZero) { if (!top0KnownZero) { return; } if (zeroTailStart > topIndex0) { zeroTailStart = topIndex0; } if (top1KnownZero && topIndex1 == topIndex0 - 1 && zeroTailStart > topIndex1) { zeroTailStart = topIndex1; } } }