Files
even-perfect-numbers-scanner/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs

5514 lines
142 KiB
C#

// #define DivisorSet_BitContradiction
// #define DivisorSet_BitTree
// #define DEVICE_GPU
using System;
using System.Diagnostics;
using System.Numerics;
using System.IO;
using System.Text;
using System.Runtime.CompilerServices;
using System.Globalization;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Buffers;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Kernels;
using PerfectNumbers.Core.Gpu.Accelerators;
using PerfectNumbers.Core.ByDivisor;
using PerfectNumbers.Core.Persistence;
namespace PerfectNumbers.Core.Cpu;
public enum DivisorSet
{
Sequential,
Pow2Groups,
Predictive,
Percentile,
Additive,
TopDown,
BitContradiction,
BitTree,
}
[EnumDependentTemplate(typeof(DivisorSet))]
[DeviceDependentTemplate(typeof(ComputationDevice))]
[NameSuffix("Order")]
public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneNumberDivisorByDivisorTester
{
public BigInteger DivisorLimit;
public BigInteger MinK = EnvironmentConfiguration.MinK;
#if DEVICE_GPU || DEVICE_HYBRID
public readonly PrimeOrderCalculatorAccelerator Accelerator = PrimeOrderCalculatorAccelerator.Rent(1);
#endif
#pragma warning disable CS8618 // StateFilePath is always set on EvenPerfectBitScanner execution path when ResumeFromState is called.
private string StateFilePath;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
private int _stateCounter;
private BigInteger _lastSavedK;
private bool _pow2MinusOneChecked;
private Pow2Minus1StateRepository? _pow2MinusOneRepository;
private KStateRepository _kRepository = null!;
private KStateRepository? _cheapRepository;
private BigInteger _cheapLastSavedK;
private BigInteger _cheapResumeK;
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
private ulong _currentPrime;
#if WITH_PRUNE_STATS
private readonly TimeSpan _slowCalculationThreshold = TimeSpan.FromMilliseconds(700);
#endif
#if DEVICE_GPU && DivisorSet_BitContradiction
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsBitContradictionGpu(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger minK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
processedAll = false;
foundDivisor = BigInteger.Zero;
BigInteger currentK = minK;
#if OVERRIDE_MIN_K
// Regression test convenience: ensure we actually scan the range that contains the known divisor for p=138000001.
if (currentK > 1)
{
Console.WriteLine($"[GPU OVERRIDE] p={prime} forcing minK from {currentK} to 1 for known-divisor regression test.");
currentK = BigInteger.One;
}
#endif
const int TargetBatchIndex = 273;
bool hasLimit = !allowedMax.IsZero;
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
BigInteger maxQ = (BigInteger.One << BitContradictionKernels.MaxQBitLength) - BigInteger.One;
BigInteger maxKByBits = (maxQ - BigInteger.One) / step;
if (!hasLimit || maxKByBits < maxK)
{
maxK = maxKByBits;
}
if (maxK < currentK)
{
processedAll = hasLimit;
return false;
}
PrimeOrderCalculatorAccelerator gpu = Accelerator;
int maxThreadCount = PerfectNumberConstants.BitContradictionGpuThreads;
int batchSize = PerfectNumberConstants.BitContradictionGpuBatchCount;
BigInteger batchSizeBig = batchSize;
int wordCount = BitContradictionKernels.MaxQWordCount;
int inputCapacity = maxThreadCount * wordCount;
var buffers = gpu.EnsureBitContradictionCapacity(inputCapacity, inputCapacity, maxThreadCount);
var stream = AcceleratorStreamPool.Rent(gpu.AcceleratorIndex);
var kernels = GpuKernelPool.GetOrAddKernels(gpu.AcceleratorIndex, stream, KernelType.BitContradictionScan);
var kernel = kernels.BitContradiction!;
int[] found = new int[maxThreadCount];
ulong[] batchIndexWords = new ulong[inputCapacity];
ArrayView1D<int, Stride1D.Dense> delta8KeysView = buffers.Delta8CacheKeysView;
ArrayView1D<int, Stride1D.Dense> delta8CacheView = buffers.Delta8CacheValuesView;
Span<int> temp = stackalloc int[(int)delta8KeysView.Length];
temp.Fill(int.MinValue);
delta8KeysView.CopyFromCPU(stream, temp);
#if INVERSION_VALIDATION
///
/// INVERSION VALIDATION
///
{
// --- Inputs (known test case)
ulong p = 138000001UL;
ulong k = 4383UL;
// --- ILGPU setup (minimal)
var accelerator = gpu.Accelerator;
// --- Compile kernel (Extent=1)
// Kernel signature must be: (Index1D, ulong p, ulong k, ArrayView1D<ulong, Stride1D.Dense> outBuf)
var kernel2 = accelerator.LoadAutoGroupedStreamKernel<
Index1D,
ulong,
ulong,
ArrayView1D<ulong, Stride1D.Dense>
>(BitContradictionKernels.BitContradictionKernelExportInverse1024);
// --- Output buffer (64 ulongs is enough: 16 qPrefix + 16 inv + 16 aLow + meta)
using var outBufGpu = accelerator.Allocate1D<ulong>(64);
var outCpu = new ulong[64];
// Optional: clear buffer
for (int i = 0; i < outCpu.Length; i++) outCpu[i] = 0;
outBufGpu.View.CopyFromCPU(stream, outCpu);
// --- Run kernel
kernel2(1, p, k, outBufGpu.View);
stream.Synchronize();
// --- Copy back
outBufGpu.View.CopyToCPU(stream, outCpu);
stream.Synchronize();
// --- Parse meta
ulong pOut = outCpu[48];
ulong kOut = outCpu[49];
ulong qOut = outCpu[50];
ulong marker = outCpu[51];
Console.WriteLine($"[GPU EXPORT] pOut={pOut} kOut={kOut} qOut={qOut} marker=0x{marker:X}");
// --- Helpers: build BigInteger from 16x ulong limbs (little-endian)
BigInteger FromU64Le(int startIndex)
{
// +1 byte to force positive sign for BigInteger ctor
byte[] bytes = new byte[16 * 8 + 1];
for (int i = 0; i < 16; i++)
{
ulong w = outCpu[startIndex + i];
int o = i * 8;
bytes[o + 0] = (byte)(w);
bytes[o + 1] = (byte)(w >> 8);
bytes[o + 2] = (byte)(w >> 16);
bytes[o + 3] = (byte)(w >> 24);
bytes[o + 4] = (byte)(w >> 32);
bytes[o + 5] = (byte)(w >> 40);
bytes[o + 6] = (byte)(w >> 48);
bytes[o + 7] = (byte)(w >> 56);
}
return new BigInteger(bytes);
}
var qPrefix = FromU64Le(0);
var inv = FromU64Le(16);
var aLow = FromU64Le(32);
var mask = (BigInteger.One << 1024) - 1;
// --- Validate inverse: (qPrefix * inv) mod 2^1024 == 1
var prod = (qPrefix * inv) & mask;
bool invOk = prod == BigInteger.One;
// --- Validate aLow: aLow == (-inv) mod 2^1024
var expectedALow = (mask + BigInteger.One - (inv & mask)) & mask;
bool aLowOk = ((aLow & mask) == expectedALow);
// --- Print concise diagnostics
Console.WriteLine($"[CPU VALIDATION] invOk={invOk} aLowOk={aLowOk}");
// Print low 64 bits (quick sanity)
ulong prodLow64 = (ulong)(prod & ((BigInteger.One << 64) - 1));
ulong invLow64 = (ulong)(inv & ((BigInteger.One << 64) - 1));
ulong aLowLow64 = (ulong)(aLow & ((BigInteger.One << 64) - 1));
Console.WriteLine($"[LOW64] qPrefix=0x{(ulong)(qPrefix & ((System.Numerics.BigInteger.One<<64)-1)):X}");
Console.WriteLine($"[LOW64] inv=0x{invLow64:X} aLow=0x{aLowLow64:X} prod=0x{prodLow64:X}");
// Optional: hard fail for CI/regression
if (!invOk || !aLowOk)
throw new System.InvalidOperationException($"Inverse export validation failed: invOk={invOk}, aLowOk={aLowOk}");
}
#endif
#if SELF_TEST
///
/// SELF-TEST
///
{
ulong p = 138000001UL;
ulong k = 4383UL;
var accelerator = gpu.Accelerator;
Console.WriteLine($"Compiling kernel for {prime}");
var kernel3 = accelerator.LoadAutoGroupedStreamKernel<
Index1D,
ulong,
ulong,
int,
ArrayView1D<ulong, Stride1D.Dense>
>(BitContradictionKernels.BitContradictionKernelDebugDpSingleQ);
var launcher3 = KernelUtil.GetKernel(kernel3).CreateLauncherDelegate<
Action<AcceleratorStream, Index1D, ulong, ulong, int, ArrayView1D<ulong, Stride1D.Dense>>>();
Console.WriteLine($"Preparing buffers for {prime}");
using var outGpu = accelerator.Allocate1D<ulong>(128);
var outCpu = new ulong[128];
Array.Clear(outCpu, 0, outCpu.Length);
outGpu.View.CopyFromCPU(stream, outCpu);
// First run: disable batching (scalar DP)
Console.WriteLine($"Launching GPU kernel for {prime}");
launcher3(stream, 1, p, k, 1, outGpu.View);
outGpu.View.CopyToCPU(stream, outCpu);
stream.Synchronize();
Console.WriteLine($"[DP DEBUG] marker=0x{outCpu[0]:X} p={outCpu[1]} k={outCpu[2]} q={outCpu[3]} qBitLen={outCpu[4]} offsetCount={outCpu[6]} ok={outCpu[9]} carryMin={outCpu[10]} carryMax={outCpu[11]}");
// Print first 10 step records
Console.WriteLine($"[DP DEBUG] marker=0x{outCpu[0]:X} p={outCpu[1]} k={outCpu[2]} q={outCpu[3]} qBitLen={outCpu[4]} offsetCount={outCpu[6]} ok={outCpu[9]} carryMin={outCpu[10]} carryMax={outCpu[11]} disableBatching={outCpu[12]}");
Console.WriteLine($"[DP EXTRA] maxAllowedA={outCpu[14]} maxFixed={outCpu[15]} windowSize={outCpu[16]} aWordCount={outCpu[17]}");
Console.WriteLine($"[DP FIRST] firstForced={unchecked((long)outCpu[18])} firstRemaining={unchecked((long)outCpu[19])} firstQWordsEff={outCpu[20]} firstQLastEff=0x{outCpu[21]:X} firstCarryMin={unchecked((long)outCpu[22])} firstCarryMax={unchecked((long)outCpu[23])}");
Console.WriteLine($"[DP SNAP] aLow0=0x{outCpu[24]:X} knownOne0=0x{outCpu[25]:X} knownZero0=0x{outCpu[26]:X} aOneWin0=0x{outCpu[27]:X} aZeroWin0=0x{outCpu[28]:X} qMask0=0x{outCpu[29]:X} lastMask=0x{outCpu[30]:X} off0={outCpu[31]} offLast={outCpu[32]}");
if (outCpu[0] != 0xD00DF00DUL)
throw new Exception("DP debug kernel did not run as expected.");
}
#endif
BigInteger currentBatch = (currentK - BigInteger.One) / batchSizeBig;
currentK = (currentBatch * batchSizeBig) + BigInteger.One;
BigInteger maxBatch = (maxK - BigInteger.One) / batchSizeBig;
while (true)
{
if (hasLimit && currentBatch > maxBatch)
{
processedAll = true;
break;
}
BigInteger remainingBatches = maxBatch - currentBatch + BigInteger.One;
if (remainingBatches <= BigInteger.Zero)
{
processedAll = hasLimit;
break;
}
Console.WriteLine($"Preparing data for{prime}, batch {currentBatch}");
int threadCount = maxThreadCount;
if (remainingBatches < threadCount)
{
threadCount = (int)remainingBatches;
}
if (currentBatch > ulong.MaxValue)
{
throw new InvalidOperationException($"BitContradiction GPU batch index overflow for p={prime}, batch={currentBatch}");
}
BigInteger maxBatchIndex = currentBatch + (threadCount - 1);
if (maxBatchIndex > ulong.MaxValue)
{
throw new InvalidOperationException($"BitContradiction GPU batch index overflow for p={prime}, batch={currentBatch}, threads={threadCount}");
}
int countQ = batchSize;
for (int t = 0; t < threadCount; t++)
{
BigInteger batchIndex = currentBatch + t;
WriteBigIntegerToWords(batchIndex, batchIndexWords.AsSpan(t * wordCount, wordCount));
}
Console.WriteLine($"Copying data to GPU for {prime}, batch {currentBatch}");
buffers.BatchIndexWordsView.CopyFromCPU(stream, batchIndexWords.AsSpan(0, threadCount * wordCount));
// #if DETAILED_LOG
Console.WriteLine($"Launching GPU kernel for {prime}, batch {currentBatch}");
// #endif
kernel(stream, 1, prime, buffers.BatchIndexWordsView, countQ, buffers.FoundQWordsView,
// kernel(stream, threadCount, prime, buffers.BatchIndexWords, countQ, buffers.FoundQWords,
delta8KeysView, delta8CacheView);
buffers.FoundQWordsView.CopyToCPU(stream, found.AsSpan(0, threadCount));
stream.Synchronize();
_consolePrintOut++;
if (_consolePrintOut >= PerfectNumberConstants.ConsoleInterval)
{
Console.WriteLine($"GPU kernel for {prime} complete, batch {currentBatch}");
Console.WriteLine($"[GPU FOUND RAW] batch={currentBatch} found[0]={found[0]}");
_consolePrintOut = 0;
}
#if DETAILED_LOG || SELF_TEST
#endif
// TODO: This loop is incorrect. We have a loop over the threads outside from here
for (int i = 0; i < threadCount; i++)
{
int foundIndex = found[i];
if (foundIndex >= 0)
{
BigInteger batchIndex = currentBatch + i;
BigInteger candidateK = (batchIndex * batchSizeBig) + BigInteger.One + foundIndex;
if (hasLimit && candidateK > maxK)
{
processedAll = true;
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return false;
}
BigInteger candidateQ = (step * candidateK) + BigInteger.One;
// Span<ulong> debugQWords = stackalloc ulong[BitContradictionKernels.MaxQWordCount];
// if (!BitContradictionKernels.TryBuildQWordsFromBatchIndex(prime, batchIndexWords.AsSpan(i * wordCount, wordCount), wordCount, foundIndex, debugQWords, out int debugQBitLen))
// {
// throw new InvalidOperationException($"BitContradiction GPU qWords overflow for p={prime}, k={candidateK}, batch={batchIndex}, index={foundIndex}");
// }
// BigInteger debugQ = ReadBigIntegerFromWords(debugQWords);
// if (debugQ != candidateQ)
// {
// throw new InvalidOperationException($"BitContradiction GPU qWords mismatch for p={prime}, k={candidateK}, batch={batchIndex}, index={foundIndex}, cpuQ={candidateQ}, gpuQ={debugQ}, gpuQBits={debugQBitLen}");
// }
// #if DivisorSet_BitContradiction
// int[]? qBits = _qBits;
// if (qBits == null || qBits.Length < (int)prime)
// {
// qBits = new int[(int)prime];
// _qBits = qBits;
// }
// bool cpuDecided = TryExactBitContradictionCheck(prime, candidateQ, candidateK, out bool cpuDivides);
// if (!cpuDecided)
// {
// throw new InvalidOperationException($"BitContradiction CPU undecided for p={prime}, q={candidateQ}, k={candidateK}, batch={batchIndex}");
// }
// if (!cpuDivides)
// {
// throw new InvalidOperationException($"BitContradiction CPU/GPU mismatch for p={prime}, q={candidateQ}, k={candidateK}, batch={batchIndex}");
// }
// #endif
// bool dividesExact = BigInteger.ModPow(2, prime, candidateQ) == BigInteger.One;
// if (!dividesExact)
// {
// throw new InvalidOperationException($"BitContradiction CPU/GPU false positive for p={prime}, q={candidateQ}, k={candidateK}, batch={batchIndex}");
#if DETAILED_LOG
if (prime == 138000001UL)
// --- Fast self-check for known divisor: run scan kernel with ONE thread and SMALL countQ.
// This avoids waiting for other threads that would scan huge ranges and never hit the target.
{
const int expectedFoundIndex = 4382; // k=4383 => index=k-1 in batch 0
countQ = 6000; // must be > 4382, keep small to finish quickly
Array.Clear(found, 0, found.Length);
Array.Clear(batchIndexWords, 0, batchIndexWords.Length);
// batchIndex = 0
WriteBigIntegerToWords(BigInteger.Zero, batchIndexWords.AsSpan(0, wordCount));
buffers.BatchIndexWords.CopyFromCPU(stream, batchIndexWords.AsSpan(0, threadCount * wordCount));
Console.WriteLine($"[GPU SELFTEST] launching scan kernel (threads={threadCount}, countQ={countQ})");
kernel(
stream,
threadCount,
prime,
buffers.BatchIndexWords,
countQ,
buffers.FoundQWords,
#if DETAILED_LOG
debugView,
#endif
delta8Keys.View,
delta8Cache.View);
stream.Synchronize();
buffers.FoundQWords.CopyToCPU(stream, found.AsSpan(0, threadCount));
stream.Synchronize();
Console.WriteLine($"[GPU SELFTEST] found[0]={found[0]} (expected {expectedFoundIndex})");
if (found[0] != expectedFoundIndex)
{
throw new InvalidOperationException($"[GPU SELFTEST FAILED] got={found[0]} expected={expectedFoundIndex}");
}
// Self-test succeeded => stop here (do not start full scan which may hit TDR during debugging).
processedAll = false;
foundDivisor = BigInteger.Zero;
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return false;
}
// {
// ulong[] debugWords = new ulong[debugWordsPerSlot];
// debugView.SubView(i * debugWordsPerSlot, debugWordsPerSlot).CopyToCPU(stream, debugWords);
// BigInteger gpuQ = ReadBigIntegerFromWords(debugWords.AsSpan(0, 16));
// BigInteger gpuInv = ReadBigIntegerFromWords(debugWords.AsSpan(16, 16));
// BigInteger gpuALow = ReadBigIntegerFromWords(debugWords.AsSpan(32, 16));
// ulong gpuQBits = debugWords[48];
// ulong gpuQWordCount = debugWords[49];
// ulong gpuQMask0 = debugWords[50];
// ulong gpuQMask1 = debugWords[51];
// ulong gpuKnownOne0 = debugWords[52];
// ulong gpuKnownZero0 = debugWords[53];
// ulong gpuAOneWin0 = debugWords[54];
// ulong gpuAZeroWin0 = debugWords[55];
// long gpuCarryMin = unchecked((long)debugWords[56]);
// long gpuCarryMax = unchecked((long)debugWords[57]);
// long gpuMaxAllowedA = unchecked((long)debugWords[58]);
// long gpuMaxFixed = unchecked((long)debugWords[59]);
// long gpuWindowSize = unchecked((long)debugWords[60]);
// long gpuAWordCount = unchecked((long)debugWords[61]);
// long gpuMaxKnownA = unchecked((long)debugWords[62]);
// long gpuMaxFixedPrefilter = unchecked((long)debugWords[63]);
// ulong preKnownOne0 = debugWords[64];
// ulong preKnownZero0 = debugWords[65];
// ulong preAOneWin0 = debugWords[66];
// ulong preAZeroWin0 = debugWords[67];
// long preCarryMin = unchecked((long)debugWords[68]);
// long preCarryMax = unchecked((long)debugWords[69]);
// long preSegmentBase = unchecked((long)debugWords[70]);
// long preWindowColumn = unchecked((long)debugWords[71]);
// long gpuOffsetCount = unchecked((long)debugWords[72]);
// long gpuMaxOffset = unchecked((long)debugWords[73]);
// long gpuFirstForced = unchecked((long)debugWords[74]);
// long gpuFirstRemaining = unchecked((long)debugWords[75]);
// long gpuFirstQWordsEff = unchecked((long)debugWords[76]);
// ulong gpuFirstQLastEff = debugWords[77];
// long gpuFirstCarryMin = unchecked((long)debugWords[78]);
// long gpuFirstCarryMax = unchecked((long)debugWords[79]);
// throw new InvalidOperationException($"BitContradiction GPU debug for p={prime}, q={candidateQ}, k={candidateK}, qBits={gpuQBits}, qWordCount={gpuQWordCount}, qMask0=0x{gpuQMask0:X16}, qMask1=0x{gpuQMask1:X16}, knownOne0=0x{gpuKnownOne0:X16}, knownZero0=0x{gpuKnownZero0:X16}, aOneWin0=0x{gpuAOneWin0:X16}, aZeroWin0=0x{gpuAZeroWin0:X16}, carryMin={gpuCarryMin}, carryMax={gpuCarryMax}, maxAllowedA={gpuMaxAllowedA}, maxFixed={gpuMaxFixed}, windowSize={gpuWindowSize}, aWordCount={gpuAWordCount}, maxKnownA={gpuMaxKnownA}, maxFixedPrefilter={gpuMaxFixedPrefilter}, preKnownOne0=0x{preKnownOne0:X16}, preKnownZero0=0x{preKnownZero0:X16}, preAOneWin0=0x{preAOneWin0:X16}, preAZeroWin0=0x{preAZeroWin0:X16}, preCarryMin={preCarryMin}, preCarryMax={preCarryMax}, preSegmentBase={preSegmentBase}, preWindowColumn={preWindowColumn}, offsetCount={gpuOffsetCount}, maxOffset={gpuMaxOffset}, firstForced={gpuFirstForced}, firstRemaining={gpuFirstRemaining}, firstQWordsEff={gpuFirstQWordsEff}, firstQLastEff=0x{gpuFirstQLastEff:X16}, firstCarryMin={gpuFirstCarryMin}, firstCarryMax={gpuFirstCarryMax}, gpuQ={gpuQ}, gpuInv={gpuInv}, gpuALow={gpuALow}");
// }
#endif
foundDivisor = candidateQ;
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return true;
}
}
if (currentBatch == TargetBatchIndex)
{
Console.WriteLine($"No divisor found up to batch index = {currentBatch}, with the target batch index = {TargetBatchIndex}");
Environment.Exit(1);
}
ContinueOuter:
RecordState(currentK);
if (hasLimit && currentK > maxK)
{
processedAll = true;
break;
}
currentBatch += threadCount;
currentK = (currentBatch * batchSizeBig) + BigInteger.One;
}
AcceleratorStreamPool.Return(gpu.AcceleratorIndex, stream);
return false;
}
private static void WriteBigIntegerToWords(BigInteger value, Span<ulong> words)
{
Span<byte> bytes = stackalloc byte[BitContradictionKernels.MaxQWordCount * 8];
if (!value.TryWriteBytes(bytes, out int written, isUnsigned: true, isBigEndian: false))
{
written = 0;
}
int wordCount = words.Length;
for (int w = 0; w < wordCount; w++)
{
ulong word = 0UL;
int byteIndex = w << 3;
int limit = written - byteIndex;
if (limit > 0)
{
if (limit > 8)
{
limit = 8;
}
for (int b = 0; b < limit; b++)
{
word |= (ulong)bytes[byteIndex + b] << (b << 3);
}
}
words[w] = word;
}
}
private static BigInteger ReadBigIntegerFromWords(ReadOnlySpan<ulong> words)
{
Span<byte> bytes = stackalloc byte[BitContradictionKernels.MaxQWordCount * 8];
int wordCount = words.Length;
for (int w = 0; w < wordCount; w++)
{
ulong word = words[w];
int byteIndex = w << 3;
bytes[byteIndex] = (byte)word;
bytes[byteIndex + 1] = (byte)(word >> 8);
bytes[byteIndex + 2] = (byte)(word >> 16);
bytes[byteIndex + 3] = (byte)(word >> 24);
bytes[byteIndex + 4] = (byte)(word >> 32);
bytes[byteIndex + 5] = (byte)(word >> 40);
bytes[byteIndex + 6] = (byte)(word >> 48);
bytes[byteIndex + 7] = (byte)(word >> 56);
}
return new BigInteger(bytes, isUnsigned: true, isBigEndian: false);
}
#endif
[ThreadStatic]
private static Dictionary<ulong, ulong>? _pow2DivisorPowModCache;
#if DETAILED_LOG
[ThreadStatic]
private static long _pow2CacheHits;
[ThreadStatic]
private static long _pow2CacheMisses;
[ThreadStatic]
private static long _pow2CacheLookups;
[ThreadStatic]
private static long _pow2CacheEligible;
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ResetPow2Cache()
{
_pow2DivisorPowModCache?.Clear();
#if DETAILED_LOG
_pow2CacheHits = 0;
_pow2CacheMisses = 0;
_pow2CacheLookups = 0;
_pow2CacheEligible = 0;
#endif
}
#if DivisorSet_BitContradiction
private static Persistence.BitContradictionStateRepository? _bitContradictionStateRepository;
#endif
#if DivisorSet_BitTree
private static Persistence.BitContradictionStateRepository? _bitTreeStateRepository;
#endif
#if DivisorSet_TopDown
private BigInteger _topDownCursor;
#endif
#if DivisorSet_Pow2Groups
private const int PercentScale = DivisorByDivisorConfig.PercentScale;
private static readonly int[] SpecialPercentPromille = DivisorByDivisorConfig.SpecialPercentPromille;
private static readonly (int Start, int End)[] PercentGroupsPromille = DivisorByDivisorConfig.PercentGroupsPromille;
private static long _specialHits;
private static long _groupHits;
private enum Pow2Phase
{
None,
Special,
Groups,
}
private Pow2Phase _currentPow2Phase;
private string _specialStateFilePath = string.Empty;
private string _groupsStateFilePath = string.Empty;
private int _specialStateCounter;
private int _groupsStateCounter;
private BigInteger _specialLastSavedK;
private BigInteger _groupsLastSavedK;
private BigInteger _specialResumeK;
private BigInteger _groupsResumeK;
private bool _preparedSpecialsInitialized;
private int _preparedSpecialRangePromille;
private int[] _preparedSpecialSingles = Array.Empty<int>();
private (int Start, int End)[] _preparedSpecialRanges = Array.Empty<(int Start, int End)>();
private KStateRepository? _specialRepository;
private KStateRepository? _groupsRepository;
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsMersenneValue(ulong prime, in BigInteger divisor)
{
if (divisor <= BigInteger.One || prime == 0UL)
{
return false;
}
BigInteger plusOne = divisor + BigInteger.One;
if (plusOne <= BigInteger.One || !IsPowerOfTwoBig(plusOne))
{
return false;
}
int exponentBits = GetBitLengthPortable(plusOne) - 1;
if (exponentBits <= 0)
{
return false;
}
return prime == (ulong)exponentBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetBitLengthPortable(in BigInteger value)
{
// if (value.IsZero)
// {
// return 0;
// }
// byte[] bytes = value.ToByteArray(isUnsigned: true, isBigEndian: false);
// byte msb = bytes[^1];
// return ((bytes.Length - 1) * 8) + (BitOperations.Log2(msb) + 1);
return (int)(value.GetBitLength());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsPowerOfTwoBig(in BigInteger value)
{
return value > BigInteger.One && (value & (value - BigInteger.One)) == BigInteger.Zero;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetMersenneStopK(ulong prime, in BigInteger step, out BigInteger stopK, out BigInteger tailStartK)
{
stopK = BigInteger.Zero;
tailStartK = BigInteger.Zero;
if (prime == 0UL || prime > int.MaxValue)
{
return false;
}
int bits = checked((int)prime);
BigInteger mersenne = (BigInteger.One << bits) - BigInteger.One;
BigInteger denominator = step;
if (denominator <= BigInteger.Zero)
{
return false;
}
stopK = (mersenne - BigInteger.One) / denominator;
if (stopK < BigInteger.One)
{
return false;
}
int tailCount = PerfectNumberConstants.ByDivisorMersenneTailCount;
if (tailCount < 0)
{
tailCount = 0;
}
BigInteger tail = stopK - tailCount;
tailStartK = tail > BigInteger.One ? tail : BigInteger.One;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryClampMaxKForMersenneTail(ulong prime, in BigInteger step, ref BigInteger maxKAllowed)
{
if (maxKAllowed <= BigInteger.Zero)
{
return false;
}
if (!TryGetMersenneStopK(prime, step, out BigInteger stopK, out BigInteger tailStartK))
{
return false;
}
if (maxKAllowed < tailStartK)
{
return false;
}
BigInteger cap = stopK - BigInteger.One;
if (cap < BigInteger.One)
{
return false;
}
if (maxKAllowed > cap)
{
maxKAllowed = cap;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private bool TryBuildCheapRange(in BigInteger minK, in BigInteger chunkSize, in BigInteger maxKAllowed, out BigInteger start, out BigInteger end)
{
start = minK > BigInteger.One ? minK : BigInteger.One;
if (_cheapResumeK > start)
{
start = _cheapResumeK;
}
if (start > maxKAllowed)
{
end = BigInteger.Zero;
return false;
}
BigInteger span = chunkSize > BigInteger.One ? chunkSize - BigInteger.One : BigInteger.Zero;
end = start + span;
if (end > maxKAllowed)
{
end = maxKAllowed;
}
return end >= start;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void RecordCheapState(in BigInteger k)
{
if (_currentPrime == 0UL || _cheapRepository is null)
{
return;
}
if (k <= _cheapLastSavedK)
{
return;
}
_cheapRepository.Upsert(_currentPrime, k);
_cheapLastSavedK = k;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ResetStateTracking()
{
ResetPow2Cache();
_stateCounter = 0;
_pow2MinusOneChecked = false;
_currentPrime = 0UL;
_cheapLastSavedK = BigInteger.Zero;
_cheapResumeK = BigInteger.One;
#if DivisorSet_TopDown
_topDownCursor = BigInteger.Zero;
#endif
#if DivisorSet_Pow2Groups
_specialStateCounter = 0;
_groupsStateCounter = 0;
_currentPow2Phase = Pow2Phase.None;
_preparedSpecialsInitialized = false;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
{
ResetPow2Cache();
StateFilePath = stateFile;
_lastSavedK = lastSavedK;
MinK = minK;
_stateCounter = 0;
_kRepository = EnvironmentConfiguration.ByDivisorKStateRepository ?? throw new InvalidOperationException("By-divisor k-state repository not initialized.");
_pow2MinusOneRepository = EnvironmentConfiguration.ByDivisorPow2Minus1Repository;
_cheapRepository = EnvironmentConfiguration.ByDivisorCheapKStateRepository;
_cheapLastSavedK = BigInteger.Zero;
_cheapResumeK = MinK;
string stateName = Path.GetFileNameWithoutExtension(stateFile);
if (ulong.TryParse(stateName, NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime))
{
_currentPrime = parsedPrime;
if (_kRepository.TryGet(parsedPrime, out BigInteger storedK) && storedK > BigInteger.Zero)
{
_lastSavedK = storedK;
}
_pow2MinusOneChecked = _pow2MinusOneRepository?.IsChecked(parsedPrime) ?? false;
if (_cheapRepository is not null && _cheapRepository.TryGet(parsedPrime, out BigInteger cheapStored) && cheapStored > BigInteger.Zero)
{
_cheapLastSavedK = cheapStored;
_cheapResumeK = cheapStored + BigInteger.One;
}
}
#if DivisorSet_TopDown
_topDownCursor = _lastSavedK;
#endif
#if DivisorSet_Pow2Groups
_specialRepository = EnvironmentConfiguration.ByDivisorSpecialStateRepository;
_groupsRepository = EnvironmentConfiguration.ByDivisorGroupsStateRepository;
_specialStateCounter = 0;
_groupsStateCounter = 0;
_specialLastSavedK = lastSavedK;
_groupsLastSavedK = lastSavedK;
if (_currentPrime != 0UL)
{
if (_specialRepository is not null && _specialRepository.TryGet(_currentPrime, out BigInteger storedSpecial) && storedSpecial > BigInteger.Zero)
{
_specialLastSavedK = storedSpecial;
}
if (_groupsRepository is not null && _groupsRepository.TryGet(_currentPrime, out BigInteger storedGroups) && storedGroups > BigInteger.Zero)
{
_groupsLastSavedK = storedGroups;
}
}
_specialResumeK = _specialLastSavedK + BigInteger.One;
_groupsResumeK = _groupsLastSavedK + BigInteger.One;
_currentPow2Phase = Pow2Phase.None;
_preparedSpecialsInitialized = false;
#endif
#if DivisorSet_BitContradiction
_bitContradictionStateRepository = EnvironmentConfiguration.BitContradictionStateRepository;
#endif
#if DivisorSet_BitTree
_bitTreeStateRepository = EnvironmentConfiguration.BitContradictionStateRepository;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{
_currentPrime = prime;
ResetPow2Cache();
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
// if (TryFindPowerOfTwoMinusOneDivisor(prime, allowedMax, out divisor))
// {
// divisorsExhausted = true;
// return false;
// }
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
// if (allowedMax < 3UL)
// {
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
// divisorsExhausted = true;
// return true;
// }
bool composite = CheckDivisors(
prime,
allowedMax,
MinK,
out divisorsExhausted,
out divisor);
if (composite)
{
divisorsExhausted = true;
return false;
}
return true;
}
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{
BigInteger divisorLimitBigInteger = MersenneNumberDivisorByDivisorTester.ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
DivisorLimit = divisorLimitBigInteger;
ulong divisorLimit = MersenneNumberDivisorByDivisorTester.GetAllowedDivisorLimitForSpan(divisorLimitBigInteger);
int length = primes.Length;
for (int index = 0; index < length; index++)
{
allowedMaxValues[index] = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisor(primes[index], divisorLimit);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public readonly IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
{
#if DEVICE_GPU
var session = new MersenneCpuDivisorScanSessionWithGpuOrder();
session.Configure(gpu);
return session;
#elif DEVICE_HYBRID
var session = new MersenneCpuDivisorScanSessionWithHybridOrder();
session.Configure(gpu);
return session;
#else
return new MersenneCpuDivisorScanSessionWithCpuOrder();
#endif
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors(
ulong prime,
BigInteger allowedMax,
BigInteger minK,
out bool processedAll,
out BigInteger foundDivisor)
{
// TODO: Is minK ever < 1? Can this be optimized - normalizedMinK is always >= 1 after this
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
BigInteger step = ((BigInteger)prime) << 1;
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
bool primeLastIsSeven = (prime & 3UL) == 3UL;
ushort primeDecimalMask = DivisorGenerator.GetDecimalMask(primeLastIsSeven);
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue,
unlimited = allowedMax.IsZero,
// This checks if the limit fits 64-bit
composite64 = unlimited || allowedMax <= ulong.MaxValue;
ulong allowedMax64,
foundDivisor64;
#if DivisorSet_Pow2Groups
bool compositePow2 = CheckDivisorsPow2Groups(
prime,
primeDecimalMask,
allowedMax,
normalizedMinK,
step,
out processedAll,
out foundDivisor);
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} using pow2-groups plan; model skipped.");
return compositePow2;
#else
#if DivisorSet_BitContradiction
BitContradictionSolverWithAMultiplier.Initialize();
bool compositeBitContradiction = CheckDivisorsBitContradiction(
prime,
primeDecimalMask,
allowedMax,
normalizedMinK,
step,
out processedAll,
out foundDivisor);
return compositeBitContradiction;
#endif
#if DivisorSet_BitTree
bool compositeBitTree = CheckDivisorsBitTree(
prime,
primeDecimalMask,
allowedMax,
normalizedMinK,
step,
out processedAll,
out foundDivisor);
return compositeBitTree;
#endif
#if DivisorSet_TopDown
bool compositeTopDown = CheckDivisorsTopDown(
prime,
allowedMax,
normalizedMinK,
out processedAll,
out foundDivisor);
return compositeTopDown;
#endif
#if DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive
ByDivisorClassModel? classifier = EnvironmentConfiguration.ByDivisorClassModel;
if (classifier is not null)
{
bool compositePlanned = TryExecutePlannedScan(
classifier,
prime,
allowedMax,
normalizedMinK,
out processedAll,
out foundDivisor);
if (compositePlanned)
{
return true;
}
if (processedAll)
{
return false;
}
}
else
{
Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} model unavailable, falling back to sequential scan.");
}
#endif
if (fits64 && composite64)
{
allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
// composite64 = CheckDivisors64Bit(
// prime,
// primeDecimalMask,
// allowedMax64,
// (ulong)normalizedMinK,
// out processedAll,
// out foundDivisor64);
composite64 = CheckDivisorsSequentialPow2Cache64(
prime,
primeDecimalMask,
allowedMax64,
(ulong)normalizedMinK,
out processedAll,
out foundDivisor64);
foundDivisor = foundDivisor64;
return composite64;
}
if (fits64 && allowedMax > ulong.MaxValue)
{
allowedMax64 = ulong.MaxValue;
// composite64 = CheckDivisors64Bit(
// prime,
// primeDecimalMask,
// allowedMax64,
// (ulong)normalizedMinK,
// out processedAll,
// out foundDivisor64);
composite64 = CheckDivisorsSequentialPow2Cache64(
prime,
primeDecimalMask,
allowedMax64,
(ulong)normalizedMinK,
out processedAll,
out foundDivisor64);
if (composite64)
{
foundDivisor = foundDivisor64;
return true;
}
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
BigInteger nextK = normalizedMinK + iterations;
return CheckDivisorsLarge(
prime,
primeDecimalMask,
allowedMax,
nextK,
step,
out processedAll,
out foundDivisor);
}
// return CheckDivisorsLarge(
// prime,
// primeDecimalMask,
// allowedMax,
// normalizedMinK,
// step,
// out processedAll,
// out foundDivisor);
return CheckDivisorsSequentialPow2CacheBig(
prime,
primeDecimalMask,
allowedMax,
normalizedMinK,
step,
out processedAll,
out foundDivisor);
#endif
}
#if DivisorSet_Pow2Groups
private static readonly (int, int)[] _emptyPow2Groups = Array.Empty<(int, int)>();
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsPow2Groups(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger minK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
processedAll = true;
if (allowedMax.IsZero)
{
processedAll = false;
return false;
}
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
if (maxKAllowed < normalizedMinK)
{
return false;
}
ReadOnlySpan<int> specialPercents = SpecialPercentPromille;
ReadOnlySpan<(int Start, int End)> groups = PercentGroupsPromille;
// Segment 1: ulong range
if (normalizedMinK <= BigIntegerNumbers.UlongMaxValue)
{
ulong startK64 = (ulong)normalizedMinK;
ulong maxK64 = maxKAllowed > BigIntegerNumbers.UlongMaxValue ? ulong.MaxValue : (ulong)maxKAllowed;
ulong allowedMax64 = allowedMax > BigIntegerNumbers.UlongMaxValue ? ulong.MaxValue : (ulong)allowedMax;
bool specialFound = ProcessPow2Phase(
prime,
allowedMax64,
prime << 1,
_specialResumeK > BigInteger.Zero ? _specialResumeK : startK64,
startK64,
Pow2Phase.Special,
_emptyPow2Groups,
specialPercents,
ref processedAll,
out ulong foundDiv64);
if (specialFound)
{
foundDivisor = foundDiv64;
return true;
}
bool groupFound = ProcessPow2Phase(
prime,
allowedMax64,
prime << 1,
_groupsResumeK > BigInteger.Zero ? _groupsResumeK : startK64,
startK64,
Pow2Phase.Groups,
groups,
ReadOnlySpan<int>.Empty,
ref processedAll,
out foundDiv64);
if (groupFound)
{
foundDivisor = foundDiv64;
return true;
}
}
// Segment 2: UInt128 range
BigInteger start128 = BigInteger.Max(normalizedMinK, BigIntegerNumbers.UlongMaxValuePlusOne);
if (start128 <= maxKAllowed && start128 <= BigIntegerNumbers.UInt128MaxValue)
{
BigInteger end128 = BigInteger.Min(maxKAllowed, BigIntegerNumbers.UInt128MaxValue);
bool specialFound128 = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
allowedMax,
step,
_specialResumeK > start128 ? _specialResumeK : start128,
normalizedMinK,
start128,
end128,
Pow2Phase.Special,
_emptyPow2Groups,
specialPercents,
ref processedAll,
out BigInteger foundBig);
if (specialFound128)
{
foundDivisor = foundBig;
return true;
}
bool groupFound128 = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
allowedMax,
step,
_groupsResumeK > start128 ? _groupsResumeK : start128,
normalizedMinK,
start128,
end128,
Pow2Phase.Groups,
groups,
ReadOnlySpan<int>.Empty,
ref processedAll,
out foundBig);
if (groupFound128)
{
foundDivisor = foundBig;
return true;
}
}
// Segment 3: BigInteger range
BigInteger startBig = BigInteger.Max(normalizedMinK, BigIntegerNumbers.UInt128MaxValuePlusOne);
if (startBig <= maxKAllowed)
{
bool specialFoundBig = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
allowedMax,
step,
_specialResumeK > startBig ? _specialResumeK : startBig,
normalizedMinK,
startBig,
maxKAllowed,
Pow2Phase.Special,
_emptyPow2Groups,
specialPercents,
ref processedAll,
out BigInteger foundBig);
if (specialFoundBig)
{
foundDivisor = foundBig;
return true;
}
bool groupFoundBig = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
allowedMax,
step,
_groupsResumeK > startBig ? _groupsResumeK : startBig,
normalizedMinK,
startBig,
maxKAllowed,
Pow2Phase.Groups,
groups,
ReadOnlySpan<int>.Empty,
ref processedAll,
out foundBig);
if (groupFoundBig)
{
foundDivisor = foundBig;
return true;
}
}
return false;
}
#endif
#if DivisorSet_BitTree
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsBitTree(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger minK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
processedAll = true;
foundDivisor = BigInteger.Zero;
// Default: frontier/windowed scan over (q,a) space.
MersenneCombinedDivisorScannerFullStreamExtended.QNibbleGenerator16.SelfTest(138000001);
// MersenneBitTreeDivisorScannerWithAMultiplier.ScanResult scanResult = MersenneBitTreeDivisorScannerWithAMultiplier.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
// MersenneCombinedDivisorScannerFullStreamExtended.ScanResult scanResult = MersenneCombinedDivisorScannerFullStreamExtended.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
// MersenneResidueNibbleAutomatonScanner.ScanResult scanResult = MersenneResidueNibbleAutomatonScanner.TryFindDivisorByResidueAutomaton(prime, 4096, false, out BigInteger branchDivisor);
MersenneShiftedPJumpResidueScanner64.ScanResult scanResult = MersenneShiftedPJumpResidueScanner64.TryFindDivisorBySparseShiftU(prime, 4096, 10, out BigInteger branchDivisor);
// if (TryExactBitTreeCheck(prime, allowedMax, minK, step, out BigInteger branchDivisor))
// if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.FoundDivisor)
// if (scanResult == MersenneResidueNibbleAutomatonScanner.ScanResult.FoundDivisor)
if (scanResult == MersenneShiftedPJumpResidueScanner64.ScanResult.FoundDivisor)
{
foundDivisor = branchDivisor;
return true;
}
else if (scanResult == MersenneShiftedPJumpResidueScanner64.ScanResult.RuledOut)
// else if (scanResult == MersenneResidueNibbleAutomatonScanner.ScanResult.Candidate)
// else if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.Candidate)
{
processedAll = true;
}
return false;
}
private bool TryBitTreeBranchScan(ulong prime, BigInteger allowedMax, out BigInteger divisor)
{
divisor = BigInteger.Zero;
if (prime == 0UL)
{
return false;
}
int pBits = checked((int)prime);
var stack = new Stack<(int Index, int Carry, BigInteger Q, BigInteger A)>();
// q0 = 1, a0 = 1 (to make r0 = 1)
stack.Push((Index: 1, Carry: 0, Q: BigInteger.One, A: BigInteger.One));
while (stack.Count > 0)
{
var state = stack.Pop();
int j = state.Index;
int carry = state.Carry;
BigInteger qVal = state.Q;
BigInteger aVal = state.A;
if (j >= pBits)
{
if (carry == 0)
{
if (allowedMax.IsZero || qVal <= allowedMax)
{
divisor = qVal;
return true;
}
}
continue;
}
for (int qBit = 0; qBit <= 1; qBit++)
{
BigInteger qCandidate = qVal;
if (qBit == 1)
{
qCandidate |= (BigInteger.One << j);
}
// Sum over assigned bits up to column j (a_i and q_{j-i} known up to j).
int sum = carry;
for (int i = 0; i <= j; i++)
{
bool aBit = ((aVal >> i) & BigInteger.One) == BigInteger.One;
bool qBitVal = ((qCandidate >> (j - i)) & BigInteger.One) == BigInteger.One;
if (aBit && qBitVal)
{
sum += 1;
}
}
// Required result bit = 1 for columns < p
if ((sum & 1) != 1)
{
continue;
}
// Set a_j to satisfy parity (already implied by sum&1==1)
BigInteger aNext = aVal;
if (((aVal >> j) & BigInteger.One) == BigInteger.Zero)
{
// keep as 0; sum already odd
}
int carryNext = (sum - 1) >> 1;
if (!allowedMax.IsZero && qCandidate > allowedMax)
{
continue;
}
stack.Push((j + 1, carryNext, qCandidate, aNext));
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static bool TryBitTreeFrontierScan(ulong prime, in BigInteger allowedMax, out BigInteger divisor)
{
divisor = BigInteger.Zero;
const int WindowSize = 200_000_000;
const int MemoryFrontierLimit = 1_000_000;
const int DiskBatchMax = 100_000;
int pBits;
try
{
pBits = checked((int)prime);
}
catch (OverflowException)
{
Console.WriteLine($"[bittree-frontier] exponent too large to check p={prime}");
return false;
}
int maxQBits = allowedMax.IsZero ? pBits : GetBitLengthPortable(allowedMax);
if (maxQBits <= 0)
{
return false;
}
int hardLimit = pBits + maxQBits + 2;
Span<int> initialOnes = stackalloc int[1];
initialOnes[0] = 0;
var frontier = new Queue<BitTreeFrontierState>(capacity: MemoryFrontierLimit);
BigInteger initialQ = BigInteger.One;
BigInteger initialA = BigInteger.One;
frontier.Enqueue(new BitTreeFrontierState(
column: 1,
carry: 0,
baseIndex: 0,
qSuffix: initialQ,
aSuffix: initialA,
maxQIndex: 0,
lastNonZeroA: 0,
qOnes: initialOnes.ToArray()));
using BitTreeFrontierRepository frontierRepo = BitTreeFrontierRepository.Open(Path.Combine("Checks"), "bittree.frontier.faster");
Console.WriteLine($"[bittree-frontier] p={prime} active_paths=1 column=1");
long prunedPaths = 0;
long pruneLogThreshold = 1_000_000;
const int ColumnLogStep = 1_000_000;
int lastLoggedColumn = 1;
void TrackPrune(int column)
{
prunedPaths++;
if (prunedPaths >= pruneLogThreshold)
{
Console.WriteLine($"[bittree-frontier] p={prime} pruned_paths={prunedPaths} column={column}");
pruneLogThreshold <<= 1;
}
if (column - lastLoggedColumn >= ColumnLogStep)
{
lastLoggedColumn = column;
Console.WriteLine($"[bittree-frontier] p={prime} exploring_column={column}");
}
}
int maxFrontier = 1;
int printCount = 0;
static byte[] SerializeState(in BitTreeFrontierState state)
{
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true);
bw.Write(state.Column);
bw.Write(state.Carry);
bw.Write(state.BaseIndex);
bw.Write(state.MaxQIndex);
bw.Write(state.LastNonZeroA);
bw.Write(state.QOnes.Length);
for (int i = 0; i < state.QOnes.Length; i++)
{
bw.Write(state.QOnes[i]);
}
byte[] qBytes = state.QSuffix.ToByteArray(isUnsigned: true, isBigEndian: false);
bw.Write(qBytes.Length);
bw.Write(qBytes);
byte[] aBytes = state.ASuffix.ToByteArray(isUnsigned: true, isBigEndian: false);
bw.Write(aBytes.Length);
bw.Write(aBytes);
bw.Flush();
return ms.ToArray();
}
static BitTreeFrontierState DeserializeState(BinaryReader br)
{
int column = br.ReadInt32();
int carry = br.ReadInt32();
int baseIndex = br.ReadInt32();
int maxQIndex = br.ReadInt32();
int lastNonZeroA = br.ReadInt32();
int onesLength = br.ReadInt32();
int[] qOnes = new int[onesLength];
for (int i = 0; i < onesLength; i++)
{
qOnes[i] = br.ReadInt32();
}
int qLen = br.ReadInt32();
byte[] qBytes = br.ReadBytes(qLen);
int aLen = br.ReadInt32();
byte[] aBytes = br.ReadBytes(aLen);
BigInteger qSuffix = new BigInteger(qBytes, isUnsigned: true, isBigEndian: false);
BigInteger aSuffix = new BigInteger(aBytes, isUnsigned: true, isBigEndian: false);
return new BitTreeFrontierState(column, carry, baseIndex, qSuffix, aSuffix, maxQIndex, lastNonZeroA, qOnes);
}
static byte[] SerializeBatch(IEnumerable<BitTreeFrontierState> states)
{
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms, Encoding.UTF8, leaveOpen: true);
foreach (var s in states)
{
byte[] payload = SerializeState(in s);
bw.Write(payload.Length);
bw.Write(payload);
}
bw.Flush();
return ms.ToArray();
}
static List<BitTreeFrontierState> DeserializeBatch(byte[] batch)
{
var list = new List<BitTreeFrontierState>();
using var ms = new MemoryStream(batch);
using var br = new BinaryReader(ms, Encoding.UTF8, leaveOpen: true);
while (ms.Position < ms.Length)
{
int len = br.ReadInt32();
byte[] payload = br.ReadBytes(len);
using var itemStream = new MemoryStream(payload);
using var itemReader = new BinaryReader(itemStream, Encoding.UTF8, leaveOpen: true);
list.Add(DeserializeState(itemReader));
}
return list;
}
void SpillFrontierIfNeeded()
{
if (frontier.Count <= MemoryFrontierLimit)
{
return;
}
int toSpill = frontier.Count - MemoryFrontierLimit;
if (toSpill <= 0 || toSpill < DiskBatchMax)
{
return;
}
var batchStates = new List<BitTreeFrontierState>(toSpill);
for (int i = 0; i < toSpill && frontier.Count > 0; i++)
{
batchStates.Add(frontier.Dequeue());
}
byte[] batchBytes = SerializeBatch(batchStates);
frontierRepo.AppendBatch(batchBytes);
// Console.WriteLine($"[bittree-frontier] spilled {batchStates.Count} states to disk; in-memory={frontier.Count}");
}
bool ReloadFrontierFromRepo()
{
if (frontier.Count > 0)
{
return true;
}
if (frontierRepo.TryDequeueBatch(1, out List<byte[]> batches) && batches.Count > 0)
{
foreach (byte[] b in batches)
{
foreach (var s in DeserializeBatch(b))
{
frontier.Enqueue(s);
}
}
Console.WriteLine($"[bittree-frontier] reloaded {frontier.Count} states from disk.");
return frontier.Count > 0;
}
return false;
}
while (frontier.Count > 0)
{
BitTreeFrontierState state = frontier.Dequeue();
if (state.Column > hardLimit)
{
TrackPrune(state.Column);
continue;
}
state = SlideWindow(state, WindowSize);
for (int qBitChoice = 0; qBitChoice <= 1; qBitChoice++)
{
BigInteger qCandidate = state.QSuffix;
int maxQIndex = state.MaxQIndex;
int[] qOnes = state.QOnes;
if (qBitChoice == 1)
{
int bitIndex = state.Column - state.BaseIndex;
qCandidate |= BigInteger.One << bitIndex;
maxQIndex = Math.Max(maxQIndex, state.Column);
int[] extended = new int[qOnes.Length + 1];
Array.Copy(qOnes, extended, qOnes.Length);
extended[qOnes.Length] = state.Column;
qOnes = extended;
}
BigInteger absoluteQ = qCandidate << state.BaseIndex;
if (!allowedMax.IsZero && absoluteQ > allowedMax)
{
TrackPrune(state.Column);
continue;
}
int qBitLength = maxQIndex + 1;
if (qBitLength > maxQBits)
{
TrackPrune(state.Column);
continue;
}
int maxAIndexAllowed = pBits - qBitLength;
if (maxAIndexAllowed < 0)
{
TrackPrune(state.Column);
continue;
}
int sum = state.Carry;
for (int i = 0; i < qOnes.Length; i++)
{
int offset = qOnes[i];
if (offset == 0 || offset > state.Column)
{
continue;
}
int aIndex = state.Column - offset;
int aBitIndex = aIndex - state.BaseIndex;
if (aBitIndex >= 0 && aBitIndex < WindowSize && ((state.ASuffix >> aBitIndex) & BigInteger.One) == BigInteger.One)
{
sum += 1;
}
}
int requiredParity = state.Column < pBits ? 1 : 0;
int parity = sum & 1;
int aBit = parity == requiredParity ? 0 : 1;
BigInteger aCandidate = state.ASuffix;
int aBitIndexCurrent = state.Column - state.BaseIndex;
if (aBit == 1)
{
if (aBitIndexCurrent >= WindowSize)
{
TrackPrune(state.Column);
continue;
}
aCandidate |= BigInteger.One << aBitIndexCurrent;
}
int lastNonZeroA = aBit == 1 ? state.Column : state.LastNonZeroA;
if (lastNonZeroA > maxAIndexAllowed)
{
TrackPrune(state.Column);
continue;
}
int columnTotal = sum + aBit;
int carry = columnTotal >> 1;
bool doneWithOnes = lastNonZeroA < 0 || (state.Column + 1 - lastNonZeroA) > maxQIndex;
if (state.Column + 1 >= pBits && carry == 0 && doneWithOnes)
{
divisor = absoluteQ;
return true;
}
if (state.Column + 1 > hardLimit)
{
TrackPrune(state.Column);
continue;
}
int[] nextOnes = qOnes;
if (qBitChoice == 1)
{
nextOnes = new int[qOnes.Length + 1];
Array.Copy(qOnes, nextOnes, qOnes.Length);
nextOnes[^1] = state.Column;
}
frontier.Enqueue(new BitTreeFrontierState(
column: state.Column + 1,
carry: carry,
baseIndex: state.BaseIndex,
qSuffix: qCandidate,
aSuffix: aCandidate,
maxQIndex: maxQIndex,
lastNonZeroA: lastNonZeroA,
qOnes: nextOnes));
SpillFrontierIfNeeded();
if (frontier.Count > maxFrontier)
{
maxFrontier = frontier.Count;
printCount++;
if (printCount == 10_000_000)
{
printCount = 0;
Console.WriteLine($"[bittree-frontier] p={prime} active_paths={maxFrontier} column={state.Column + 1}");
}
}
}
if (frontier.Count == 0 && !ReloadFrontierFromRepo())
{
break;
}
}
return false;
}
private readonly struct BitTreeFrontierState
{
public BitTreeFrontierState(
int column,
int carry,
int baseIndex,
BigInteger qSuffix,
BigInteger aSuffix,
int maxQIndex,
int lastNonZeroA,
int[] qOnes)
{
Column = column;
Carry = carry;
BaseIndex = baseIndex;
QSuffix = qSuffix;
ASuffix = aSuffix;
MaxQIndex = maxQIndex;
LastNonZeroA = lastNonZeroA;
QOnes = qOnes;
}
public readonly int Column;
public readonly int Carry;
public readonly int BaseIndex;
public readonly BigInteger QSuffix;
public readonly BigInteger ASuffix;
public readonly int MaxQIndex;
public readonly int LastNonZeroA;
public readonly int[] QOnes;
}
private readonly struct BitTreeBacktrackFrame
{
public BitTreeBacktrackFrame(BitTreeFrontierState state, int nextChoice)
{
State = state;
NextChoice = nextChoice;
}
public BitTreeFrontierState State { get; }
public int NextChoice { get; }
public BitTreeBacktrackFrame WithNextChoice(int nextChoice)
{
return new BitTreeBacktrackFrame(State, nextChoice);
}
}
private static BitTreeFrontierState SlideWindow(in BitTreeFrontierState state, int windowSize)
{
int span = state.Column - state.BaseIndex + 1;
if (span < windowSize)
{
return state;
}
int shift = span - (windowSize - 1);
int newBase = state.BaseIndex + shift;
BigInteger qSuffix = state.QSuffix >> shift;
BigInteger aSuffix = state.ASuffix >> shift;
List<int> filtered = null;
for (int i = 0; i < state.QOnes.Length; i++)
{
int pos = state.QOnes[i];
if (pos >= newBase)
{
filtered ??= new List<int>(state.QOnes.Length - i);
filtered.Add(pos);
}
}
int[] qOnes = filtered == null ? Array.Empty<int>() : filtered.ToArray();
int lastNonZeroA = state.LastNonZeroA;
if (lastNonZeroA < newBase)
{
lastNonZeroA = -1;
}
return new BitTreeFrontierState(
column: state.Column,
carry: state.Carry,
baseIndex: newBase,
qSuffix: qSuffix,
aSuffix: aSuffix,
maxQIndex: state.MaxQIndex,
lastNonZeroA: lastNonZeroA,
qOnes: qOnes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private bool TryExactBitTreeCheck(
ulong prime,
BigInteger allowedMax,
BigInteger currentK,
BigInteger step,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
bool hasLimit = !allowedMax.IsZero;
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
ulong computedCycle;
bool decided;
bool divides;
BigInteger divisor = (step * currentK) + BigInteger.One;
while (true)
{
// if (divisor <= ulong.MaxValue)
// {
// ulong divisor64 = (ulong)divisor;
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
// // We're reusing divides variable to limit registry pressure as primeOrderFailed flag.
// #if DEVICE_GPU
// decided = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
// divisor64,
// Accelerator,
// prime,
// divisorData,
// out computedCycle,
// out divides);
// #elif DEVICE_HYBRID
// decided = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
// divisor64,
// Accelerator,
// prime,
// divisorData,
// out computedCycle,
// out divides);
// #else
// decided = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
// divisor64,
// prime,
// divisorData,
// out computedCycle,
// out divides);
// #endif
// if (!decided)
// {
// #if DEVICE_GPU
// computedCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
// divisor64,
// Accelerator,
// divisorData,
// skipPrimeOrderHeuristic: divides);
// #elif DEVICE_HYBRID
// computedCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
// divisor64,
// Accelerator,
// divisorData,
// skipPrimeOrderHeuristic: divides);
// #else
// computedCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
// divisor64,
// divisorData,
// skipPrimeOrderHeuristic: divides);
// #endif
// }
// if (computedCycle > 0UL)
// {
// if ((prime % computedCycle) != 0UL)
// {
// goto MOVE_NEXT;
// }
// }
// else
// {
// computedCycle = prime;
// }
// }
// else
// {
computedCycle = prime;
// }
decided = TryExactBitTreeCheck(computedCycle, divisor, currentK, out divides);
if (!decided)
{
throw new InvalidOperationException($"BitTree check did not decide for p={prime}, divisor={divisor} (k={currentK}).");
}
if (divides)
{
foundDivisor = divisor;
return true;
}
ContinueOuter:
RecordState(currentK);
MOVE_NEXT:
if (hasLimit && currentK >= maxK)
{
break;
}
currentK += BigInteger.One;
divisor += step;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryExactBitTreeCheck(ulong exponent, BigInteger divisor, BigInteger currentK, out bool divides)
{
#if DETAILED_LOG
Stopwatch sw = Stopwatch.StartNew();
#endif
int qBitLength = GetBitLengthPortable(divisor);
int aBitLength = GetBitLengthPortable(exponent);
ArrayPool<int> intPool = ThreadStaticPools.IntPool;
int pBits = checked((int)exponent);
int[] oneOffsetsArr = intPool.Rent(qBitLength);
int oneCount = 0;
int i;
for (i = 0; i < qBitLength; i++)
{
if (((divisor >> i) & BigInteger.One) == BigInteger.One)
{
oneOffsetsArr[oneCount++] = i;
}
}
// This condition will never trigger on EvenPerfectBitScanner execution paths. We always provide valid q != 0.
// if (oneCount == 0)
// {
// divides = false;
// intPool.Return(oneOffsetsArr);
// return true;
// }
// i's in oneOffsetsArr are built in ascending order by i. We don't need to sort it.
// Array.Sort(oneOffsetsArr, 0, oneCount);
ReadOnlySpan<int> oneOffsets = oneOffsetsArr.AsSpan(0, oneCount);
int maxOffset = oneOffsets[^1];
int windowSize = qBitLength;
byte[] aWindowArray = ArrayPool<byte>.Shared.Rent(windowSize);
Span<byte> aWindow = aWindowArray.AsSpan(0, windowSize);
aWindow.Clear();
long lastNonZeroA = -1;
int carry = 0;
int column = 0;
while (true)
{
bool targetOne = column < pBits;
int sum = carry;
for (i = 0; i < oneCount; i++)
{
int offset = oneOffsets[i];
if (offset == 0)
{
continue; // a_bit for current column, handled below
}
if (offset > column)
{
break;
}
int aIndex = column - offset;
byte aBit = aWindow[aIndex % windowSize];
sum += aBit;
}
int requiredParity = targetOne ? 1 : 0;
int parity = sum & 1;
byte aCurrent = (byte)(parity == requiredParity ? 0 : 1);
int columnTotal = sum + aCurrent;
if ((columnTotal & 1) != requiredParity)
{
ArrayPool<byte>.Shared.Return(aWindowArray);
divides = false;
intPool.Return(oneOffsetsArr);
return true;
}
aWindow[column % windowSize] = aCurrent;
if (aCurrent == 1)
{
lastNonZeroA = column;
}
carry = columnTotal >> 1;
column++;
bool doneWithOnes = lastNonZeroA < 0 || column - lastNonZeroA > maxOffset;
if (column >= pBits && carry == 0 && doneWithOnes)
{
break;
}
if (column > pBits + maxOffset + 2 && carry == 0 && doneWithOnes)
{
break;
}
if (column > pBits + maxOffset + aBitLength + 2)
{
ArrayPool<byte>.Shared.Return(aWindowArray);
divides = false;
intPool.Return(oneOffsetsArr);
return true;
}
}
#if DETAILED_LOG
sw.Stop();
#endif
ArrayPool<byte>.Shared.Return(aWindowArray);
divides = carry == 0;
#if DETAILED_LOG
var elapsed = sw.Elapsed;
Console.WriteLine($"[bittree ({elapsed})] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}");
#endif
if (!divides)
{
string message = $"[bittree] p={exponent} ruled out divisor={divisor} (parity/carry exhaustion)";
_bitTreeStateRepository?.Upsert(exponent, message);
}
intPool.Return(oneOffsetsArr);
return true;
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private bool TryExactBitTreeCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides)
// {
// divides = false;
// // These conditions below will never be met on EvenPerfectBitScanner execution paths.
// // if (prime == 0 || divisor <= BigInteger.One)
// // {
// // Console.WriteLine($"[bittree] invalid inputs {prime} / {divisor}");
// // return false;
// // }
// // if ((divisor & BigInteger.One) == BigInteger.Zero)
// // {
// // divides = false;
// // return true;
// // }
// int qBitLength = GetBitLengthPortable(divisor);
// if (prime <= (ulong)qBitLength)
// {
// divides = false;
// return true;
// }
// ulong exponent64 = effectiveExponent > 0UL ? effectiveExponent : prime;
// int pBits;
// try
// {
// pBits = checked((int)exponent);
// }
// catch (OverflowException)
// {
// Console.WriteLine($"[bittree] exponent too large to check p={exponent}");
// return false;
// }
// int[] oneOffsetsArr = new int[qBitLength];
// int oneCount = 0;
// for (int i = 0; i < qBitLength; i++)
// {
// if (((divisor >> i) & BigInteger.One) == BigInteger.One)
// {
// oneOffsetsArr[oneCount++] = i;
// }
// }
// if (oneCount == 0)
// {
// divides = false;
// return true;
// }
// Array.Sort(oneOffsetsArr, 0, oneCount);
// ReadOnlySpan<int> oneOffsets = oneOffsetsArr.AsSpan(0, oneCount);
// int maxOffset = oneOffsets[^1];
// int windowSize = qBitLength;
// bool rented = windowSize <= 1024;
// byte[] aWindowArray = rented ? ArrayPool<byte>.Shared.Rent(1024) : new byte[windowSize];
// Span<byte> aWindow = aWindowArray.AsSpan(0, windowSize);
// aWindow.Clear();
// long lastNonZeroA = -1;
// int carry = 0;
// int column = 0;
// while (true)
// {
// bool targetOne = column < pBits;
// int sum = carry;
// for (int idx = 0; idx < oneCount; idx++)
// {
// int offset = oneOffsets[idx];
// if (offset == 0)
// {
// continue; // a_bit for current column, handled below
// }
// if (offset > column)
// {
// break;
// }
// int aIndex = column - offset;
// byte aBit = aWindow[aIndex % windowSize];
// sum += aBit;
// }
// int requiredParity = targetOne ? 1 : 0;
// int parity = sum & 1;
// byte aCurrent = (byte)(parity == requiredParity ? 0 : 1);
// int columnTotal = sum + aCurrent;
// if ((columnTotal & 1) != requiredParity)
// {
// if (rented)
// {
// ArrayPool<byte>.Shared.Return(aWindowArray);
// }
// divides = false;
// return true;
// }
// aWindow[column % windowSize] = aCurrent;
// if (aCurrent == 1)
// {
// lastNonZeroA = column;
// }
// carry = columnTotal >> 1;
// column++;
// bool doneWithOnes = lastNonZeroA < 0 || column - lastNonZeroA > maxOffset;
// if (column >= pBits && carry == 0 && doneWithOnes)
// {
// break;
// }
// if (column > pBits + maxOffset + 2 && carry == 0 && doneWithOnes)
// {
// break;
// }
// if (column > pBits + maxOffset + windowSize + 2)
// {
// if (rented)
// {
// ArrayPool<byte>.Shared.Return(aWindowArray);
// }
// divides = false;
// return true;
// }
// }
// if (rented)
// {
// ArrayPool<byte>.Shared.Return(aWindowArray);
// }
// divides = carry == 0;
// Console.WriteLine($"[bittree] Prime={prime}, Divisor={divisor} Decided=True Divides={divides}");
// if (!divides)
// {
// string message = $"[bittree] p={prime} ruled out divisor={divisor} (parity/carry exhaustion)";
// _bitTreeStateRepository?.Upsert(prime, message);
// }
// return true;
// }
#endif
#if !DivisorSet_Pow2Groups && (DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive)
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool TryExecutePlannedScan(
ByDivisorClassModel model,
ulong prime,
BigInteger allowedMax,
BigInteger minK,
out bool processedAll,
out BigInteger foundDivisor)
{
processedAll = false;
foundDivisor = BigInteger.Zero;
bool coversRange = true;
if (minK > ulong.MaxValue)
{
// Console.WriteLine($"[by-divisor-plan] p={prime} using BigInteger plan (minK={minK}).");
return TryExecutePlannedScanBig(model, prime, allowedMax, minK, out processedAll, out foundDivisor);
}
ulong allowedMax64;
if (allowedMax.IsZero)
{
allowedMax64 = ulong.MaxValue;
}
else if (allowedMax > ulong.MaxValue)
{
// Console.WriteLine($"[by-divisor-plan] p={prime} using BigInteger plan (allowedMax={allowedMax}).");
return TryExecutePlannedScanBig(model, prime, allowedMax, minK, out processedAll, out foundDivisor);
}
else
{
allowedMax64 = (ulong)allowedMax;
}
bool composite = TryExecutePlannedScan64(
model,
prime,
allowedMax64,
(ulong)minK,
out processedAll,
out ulong foundDivisor64);
if (composite)
{
foundDivisor = foundDivisor64;
return true;
}
if (processedAll)
{
foundDivisor = BigInteger.Zero;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool TryExecutePlannedScanBig(
ByDivisorClassModel model,
ulong prime,
in BigInteger allowedMax,
in BigInteger minK,
out bool processedAll,
out BigInteger foundDivisor)
{
processedAll = true;
foundDivisor = BigInteger.Zero;
if (allowedMax.IsZero)
{
processedAll = false;
return false;
}
BigInteger step = ((BigInteger)prime) << 1;
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
if (maxKAllowed <= BigInteger.Zero || minK > maxKAllowed)
{
processedAll = false;
return false;
}
ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning;
ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax, tuning);
int suffix = (int)(prime & 1023UL);
_ = suffix;
// ByDivisorClassEntry entry = model.Entries[suffix];
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2} maxK={maxKAllowed}");
BigInteger cheapChunk = BigInteger.Min((BigInteger)plan.CheapLimit, maxKAllowed);
BigInteger cheapStart = BigInteger.Zero;
BigInteger cheapEnd = BigInteger.Zero;
if (TryBuildCheapRange(minK, cheapChunk, maxKAllowed, out cheapStart, out cheapEnd))
{
BigInteger currentK = cheapStart;
bool compositeCheap = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
cheapStart,
cheapEnd,
out bool processedRange,
out BigInteger foundRange,
ref currentK);
if (compositeCheap)
{
RecordCheapState(currentK);
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
RecordCheapState(cheapEnd);
}
BigInteger startPlan = plan.Start <= 0d ? minK : new BigInteger(Math.Ceiling(plan.Start));
if (startPlan < minK)
{
startPlan = minK;
}
BigInteger target = plan.Target <= 0d ? startPlan + BigInteger.One : new BigInteger(Math.Ceiling(plan.Target));
if (target < startPlan)
{
target = startPlan + BigInteger.One;
}
BigInteger current = (cheapEnd > BigInteger.Zero ? cheapEnd + BigInteger.One : minK);
if (current < startPlan)
{
current = startPlan;
}
#if DivisorSet_Predictive
bool loggedProgressive = false;
while (current <= maxKAllowed)
{
if (!loggedProgressive)
{
// Console.WriteLine($"[by-divisor-progressive] p={prime} startK={current} target={target} rho={(current < target ? plan.Rho1 : plan.Rho2):F2} maxK={maxKAllowed}");
loggedProgressive = true;
}
BigInteger startRange = current;
BigInteger endRange = current;
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
startRange,
endRange,
out bool processedRange,
out BigInteger foundRange,
ref startRange);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
double multiplier = current < target ? plan.Rho1 : plan.Rho2;
BigInteger advanced = new BigInteger(Math.Ceiling((double)current * multiplier));
if (advanced <= current)
{
advanced = current + BigInteger.One;
}
if (advanced > maxKAllowed)
{
break;
}
current = advanced;
}
#elif DivisorSet_Percentile
BigInteger k10 = plan.K10 > 0d ? new BigInteger(Math.Ceiling(plan.K10)) : current;
BigInteger k25 = plan.K25 > 0d ? new BigInteger(Math.Ceiling(plan.K25)) : k10 + BigInteger.One;
BigInteger k50 = plan.K50 > 0d ? new BigInteger(Math.Ceiling(plan.K50)) : k25 + BigInteger.One;
BigInteger k75 = plan.K75 > 0d ? new BigInteger(Math.Ceiling(plan.K75)) : k50 + BigInteger.One;
BigInteger[] stops = new BigInteger[]
{
k25 - BigInteger.One,
k50 - BigInteger.One,
k75 - BigInteger.One,
};
BigInteger startRangeK = current > k10 ? current : k10;
for (int i = 0; i < stops.Length; i++)
{
BigInteger endRange = stops[i];
if (endRange < startRangeK)
{
continue;
}
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
startRangeK,
endRange,
out bool processedRange,
out BigInteger foundRange,
ref startRangeK);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
startRangeK = endRange + BigInteger.One;
if (startRangeK > maxKAllowed)
{
return false;
}
}
// After percentile sweeps, escalate geometrically.
BigInteger startGeo = startRangeK;
if (startGeo < k75)
{
startGeo = k75;
}
bool loggedPercentile = false;
while (startGeo <= maxKAllowed)
{
if (!loggedPercentile)
{
loggedPercentile = true;
}
BigInteger scanStart = startGeo;
BigInteger scanEnd = scanStart;
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
scanStart,
scanEnd,
out bool processedRange,
out BigInteger foundRange,
ref scanStart);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
BigInteger advanced = new BigInteger(Math.Ceiling((double)startGeo * plan.Rho2));
if (advanced <= startGeo)
{
advanced = startGeo + BigInteger.One;
}
if (advanced > maxKAllowed)
{
break;
}
startGeo = advanced;
}
#else
BigInteger delta = plan.DeltaK > 0d ? new BigInteger(Math.Ceiling(plan.DeltaK)) : BigInteger.One;
if (delta < BigInteger.One)
{
delta = BigInteger.One;
}
if (delta > maxKAllowed)
{
delta = maxKAllowed;
}
bool loggedAdditive = false;
while (current <= maxKAllowed)
{
if (!loggedAdditive)
{
loggedAdditive = true;
}
BigInteger startRange = current;
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
startRange,
startRange,
out bool processedRange,
out BigInteger foundRange,
ref startRange);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
BigInteger advanced = current + delta;
if (advanced <= current)
{
advanced = current + BigInteger.One;
}
if (advanced > maxKAllowed)
{
break;
}
current = advanced;
}
#endif
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsBigPlanRange(
ulong prime,
in BigInteger step,
in BigInteger allowedMax,
in BigInteger startK,
in BigInteger endK,
out bool processedAll,
out BigInteger foundDivisor,
ref BigInteger currentK)
{
processedAll = true;
foundDivisor = BigInteger.Zero;
if (startK < BigInteger.One || endK < startK)
{
return false;
}
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
if (startK > maxKAllowed)
{
return false;
}
BigInteger effectiveEnd = endK > maxKAllowed ? maxKAllowed : endK;
ushort primeDecimalMask = DivisorGenerator.GetDecimalMask((prime & 3UL) == 3UL);
BigInteger divisor = (step * startK) + BigInteger.One;
if (divisor > allowedMax)
{
return false;
}
BigInteger iterations = (effectiveEnd - startK) + BigInteger.One;
currentK = startK;
var rem10 = new BigIntegerCycleRemainderStepper(10);
var rem8 = new BigIntegerCycleRemainderStepper(8);
var rem3 = new BigIntegerCycleRemainderStepper(3);
var rem7 = new BigIntegerCycleRemainderStepper(7);
var rem11 = new BigIntegerCycleRemainderStepper(11);
var rem13 = new BigIntegerCycleRemainderStepper(13);
var rem17 = new BigIntegerCycleRemainderStepper(17);
var rem19 = new BigIntegerCycleRemainderStepper(19);
byte remainder10 = rem10.Initialize(divisor);
BigInteger primeBig = (BigInteger)prime;
while (iterations.Sign > 0)
{
if (remainder10 == 1 || remainder10 == 7)
{
if (rem3.NextIsNotDivisible(divisor) &&
rem7.NextIsNotDivisible(divisor) &&
rem11.NextIsNotDivisible(divisor) &&
rem13.NextIsNotDivisible(divisor) &&
rem17.NextIsNotDivisible(divisor) &&
rem19.NextIsNotDivisible(divisor))
{
byte remainder8 = rem8.ComputeNext(divisor);
if (remainder8 == 1 || remainder8 == 7)
{
if (((primeDecimalMask >> remainder10) & 1) != 0)
{
ContinueOuter:
RecordState(currentK);
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
// if (powResult.IsOne && !IsMersenneValue(prime, divisor))
if (powResult.IsOne)
{
foundDivisor = divisor;
return true;
}
}
}
}
}
iterations--;
if (iterations.IsZero)
{
break;
}
currentK++;
divisor += step;
remainder10 = rem10.ComputeNext(divisor);
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool TryExecutePlannedScan64(
ByDivisorClassModel model,
ulong prime,
ulong allowedMax,
ulong minK,
out bool processedAll,
out ulong foundDivisor)
{
processedAll = true;
foundDivisor = 0UL;
ulong step64 = prime << 1;
if (step64 == 0UL)
{
processedAll = false;
return false;
}
ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning;
ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax == ulong.MaxValue ? BigInteger.Zero : new BigInteger(allowedMax), tuning);
int suffix = (int)(prime & 1023UL);
_ = suffix;
// ByDivisorClassEntry entry = model.Entries[suffix];
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2}");
ulong effectiveAllowed = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
ulong maxKAllowed = effectiveAllowed == ulong.MaxValue ? ulong.MaxValue : ((effectiveAllowed - 1UL) / step64);
if (maxKAllowed == 0UL || maxKAllowed < minK)
{
processedAll = false;
return false;
}
BigInteger cheapChunk = new BigInteger(Math.Min(plan.CheapLimit, (double)maxKAllowed));
BigInteger cheapStart = BigInteger.Zero;
BigInteger cheapEnd = BigInteger.Zero;
if (TryBuildCheapRange(new BigInteger(minK), cheapChunk, new BigInteger(maxKAllowed), out cheapStart, out cheapEnd))
{
if (cheapStart <= ulong.MaxValue && cheapEnd <= ulong.MaxValue)
{
ulong cheapStart64 = (ulong)cheapStart;
ulong cheapEnd64 = (ulong)cheapEnd;
ulong currentK = cheapStart64;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
cheapStart64,
cheapEnd64,
ref currentK,
out bool processedRange,
out ulong foundRange);
if (composite)
{
RecordCheapState(currentK);
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
RecordCheapState(cheapEnd);
}
}
double nextK = Math.Max(plan.Start, (cheapEnd > BigInteger.Zero ? (double)(cheapEnd + BigInteger.One) : minK));
if (nextK < minK)
{
nextK = minK;
}
double target = plan.Target <= 0d ? nextK : plan.Target;
if (target < nextK)
{
target = nextK + 1d;
}
ulong current = (ulong)Math.Ceiling(nextK);
#if DivisorSet_Predictive
bool loggedProgressive = false;
while (current <= maxKAllowed)
{
if (!loggedProgressive)
{
Console.WriteLine($"[by-divisor-progressive] p={prime} startK={current} target={target:F0} rho={(current < target ? plan.Rho1 : plan.Rho2):F2} maxK={maxKAllowed}");
loggedProgressive = true;
}
ulong currentK = current;
bool compositeSingle = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
currentK,
currentK,
ref currentK,
out bool processedSingle,
out ulong foundSingle);
if (compositeSingle)
{
foundDivisor = foundSingle;
return true;
}
if (!processedSingle)
{
processedAll = false;
return false;
}
double multiplier = current < target ? plan.Rho1 : plan.Rho2;
double advanced = Math.Ceiling(current * multiplier);
if (advanced <= current)
{
advanced = current + 1d;
}
if (advanced > maxKAllowed)
{
break;
}
current = (ulong)advanced;
}
#elif DivisorSet_Percentile
ulong k10 = plan.K10 > 0d ? (ulong)Math.Ceiling(plan.K10) : current;
ulong k25 = plan.K25 > 0d ? (ulong)Math.Ceiling(plan.K25) : k10 + 1UL;
ulong k50 = plan.K50 > 0d ? (ulong)Math.Ceiling(plan.K50) : k25 + 1UL;
ulong k75 = plan.K75 > 0d ? (ulong)Math.Ceiling(plan.K75) : k50 + 1UL;
ReadOnlySpan<ulong> stops = stackalloc ulong[3]
{
k25 > 0UL ? k25 - 1UL : 0UL,
k50 > 0UL ? k50 - 1UL : 0UL,
k75 > 0UL ? k75 - 1UL : 0UL,
};
ulong rangeStart = current > k10 ? current : k10;
for (int i = 0; i < stops.Length; i++)
{
ulong stop = stops[i];
if (stop < rangeStart)
{
continue;
}
ulong currentK = rangeStart;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
rangeStart,
stop,
ref currentK,
out bool processedRange,
out ulong foundRange);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
rangeStart = stop + 1UL;
if (rangeStart > maxKAllowed)
{
return false;
}
}
ulong geoStart = rangeStart < k75 ? k75 : rangeStart;
while (geoStart <= maxKAllowed)
{
ulong currentK = geoStart;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
currentK,
currentK,
ref currentK,
out bool processedSingle,
out ulong foundSingle);
if (composite)
{
foundDivisor = foundSingle;
return true;
}
if (!processedSingle)
{
processedAll = false;
return false;
}
double advanced = Math.Ceiling(geoStart * plan.Rho2);
if (advanced <= geoStart)
{
advanced = geoStart + 1d;
}
if (advanced > maxKAllowed)
{
break;
}
geoStart = (ulong)advanced;
}
#else
ulong delta = plan.DeltaK > 0d ? (ulong)Math.Ceiling(plan.DeltaK) : 1UL;
if (delta == 0UL)
{
delta = 1UL;
}
if (delta > maxKAllowed)
{
delta = maxKAllowed;
}
while (current <= maxKAllowed)
{
ulong currentK = current;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
currentK,
currentK,
ref currentK,
out bool processedSingle,
out ulong foundSingle);
if (composite)
{
foundDivisor = foundSingle;
return true;
}
if (!processedSingle)
{
processedAll = false;
return false;
}
ulong advanced = current + delta;
if (advanced <= current || advanced == 0UL)
{
advanced = current + 1UL;
}
if (advanced > maxKAllowed)
{
break;
}
current = advanced;
}
#endif
return false;
}
#endif
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors64Bit(
ulong prime,
ushort primeDecimalMask,
ulong allowedMax,
ulong minK,
out bool processedAll,
out ulong foundDivisor)
{
foundDivisor = 0UL;
#if DivisorSet_Pow2Groups
ulong step64 = prime << 1;
if (step64 == 0UL)
{
processedAll = true;
return false;
}
processedAll = true;
BigInteger stepBig = step64;
BigInteger maxKAllowed = allowedMax == 0UL ? BigInteger.Zero : (((BigInteger)allowedMax - BigInteger.One) / stepBig);
TryClampMaxKForMersenneTail(prime, stepBig, ref maxKAllowed);
ReadOnlySpan<int> specialPercents = SpecialPercentPromille;
ReadOnlySpan<(int Start, int End)> groups = PercentGroupsPromille;
// Segment 1: ulong range
ulong max64K = maxKAllowed <= ulong.MaxValue ? (ulong)maxKAllowed : ulong.MaxValue;
if (minK <= max64K)
{
ulong max64 = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
bool specialFound = ProcessPow2Phase(
prime,
max64,
step64,
_specialResumeK > BigInteger.Zero ? _specialResumeK : minK,
minK,
Pow2Phase.Special,
_emptyPow2Groups,
specialPercents,
ref processedAll,
out foundDivisor);
if (specialFound)
{
return true;
}
bool groupFound = ProcessPow2Phase(
prime,
max64,
step64,
_groupsResumeK > BigInteger.Zero ? _groupsResumeK : minK,
minK,
Pow2Phase.Groups,
groups,
ReadOnlySpan<int>.Empty,
ref processedAll,
out foundDivisor);
if (groupFound)
{
return true;
}
}
// Segment 2: UInt128 range
BigInteger start128 = BigInteger.Max(minK, BigIntegerNumbers.UlongMaxValuePlusOne);
if (maxKAllowed > BigIntegerNumbers.UlongMaxValue && start128 <= maxKAllowed)
{
BigInteger end128 = BigInteger.Min(maxKAllowed, BigIntegerNumbers.UInt128MaxValue);
bool specialFound128 = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
stepBig,
_specialResumeK > start128 ? _specialResumeK : start128,
minK,
start128,
end128,
Pow2Phase.Special,
_emptyPow2Groups,
specialPercents,
ref processedAll,
out BigInteger foundBig);
if (specialFound128)
{
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
return true;
}
bool groupFound128 = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
stepBig,
_groupsResumeK > start128 ? _groupsResumeK : start128,
minK,
start128,
end128,
Pow2Phase.Groups,
groups,
ReadOnlySpan<int>.Empty,
ref processedAll,
out foundBig);
if (groupFound128)
{
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
return true;
}
}
// Segment 3: BigInteger range beyond UInt128
BigInteger startBig = BigInteger.Max(minK, BigIntegerNumbers.UInt128MaxValuePlusOne);
if (maxKAllowed > BigIntegerNumbers.UInt128MaxValue && startBig <= maxKAllowed)
{
bool specialFoundBig = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
stepBig,
_specialResumeK > startBig ? _specialResumeK : startBig,
minK,
startBig,
maxKAllowed,
Pow2Phase.Special,
_emptyPow2Groups,
specialPercents,
ref processedAll,
out BigInteger foundBig);
if (specialFoundBig)
{
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
return true;
}
bool groupFoundBig = ProcessPow2PhaseBig(
prime,
primeDecimalMask,
(BigInteger)allowedMax == BigInteger.Zero ? BigInteger.Zero : (BigInteger)allowedMax,
stepBig,
_groupsResumeK > startBig ? _groupsResumeK : startBig,
minK,
startBig,
maxKAllowed,
Pow2Phase.Groups,
groups,
ReadOnlySpan<int>.Empty,
ref processedAll,
out foundBig);
if (groupFoundBig)
{
foundDivisor = foundBig > ulong.MaxValue ? ulong.MaxValue : (ulong)foundBig;
return true;
}
}
return false;
#else
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
// if (allowedMax < 3UL)
// {
// return false;
// }
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
ref GpuUInt128 step = ref workSet.Step;
step.High = 0UL;
step.Low = prime;
step.ShiftLeft(1);
ref GpuUInt128 limit = ref workSet.Limit;
limit.High = 0UL;
limit.Low = allowedMax;
ref GpuUInt128 divisor = ref workSet.Divisor;
divisor.High = step.High;
divisor.Low = step.Low;
divisor.Add(1UL);
if (minK > 1UL)
{
GpuUInt128 offset = new(minK - 1UL);
divisor = step * offset;
divisor.Add(1UL);
}
if (divisor.CompareTo(limit) > 0)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
// Intentionally recomputes factorizations without a per-thread cache.
// The previous factor cache recorded virtually no hits and only slowed down the scan.
ulong currentK = minK,
// We're reusing candidate variable as stepLow to limit registry pressure
candidate = step.Low,
// We're reusing computedCycle variable as lowerEnd to limit registry pressure
computedCycle,
// We're reusing divisorCycle variable as maxK to limit registry pressure
divisorCycle;
// We're reusing computed variable as processedTop to limit registry pressure
bool computed,
// We're reusing primeOrderFailed variable as processedBottom to limit registry pressure
primeOrderFailed,
composite;
if (step.High == 0UL && limit.High == 0UL)
{
divisorCycle = allowedMax > 0UL ? (allowedMax - 1UL) / candidate : 0UL;
if (divisorCycle == 0UL)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
computed = true;
primeOrderFailed = true;
composite = false;
if (minK <= divisorCycle)
{
composite = CheckDivisors64Range(
prime,
candidate,
allowedMax,
minK,
divisorCycle,
ref currentK,
out computed,
out foundDivisor);
if (composite)
{
processedAll = true;
return true;
}
}
computedCycle = minK > 1UL ? minK - 1UL : 0UL;
if (computedCycle >= 1UL && minK == 1UL)
{
computedCycle = Math.Min(computedCycle, divisorCycle);
currentK = 1UL;
composite = CheckDivisors64Range(
prime,
candidate,
allowedMax,
1UL,
computedCycle,
ref currentK,
out primeOrderFailed,
out foundDivisor);
}
processedAll = computed && primeOrderFailed;
return composite;
}
#if DEVICE_GPU || DEVICE_HYBRID
PrimeOrderCalculatorAccelerator gpu = Accelerator;
#endif
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
while (divisor.CompareTo(limit) <= 0)
{
if (residueStepper.IsAdmissible())
{
candidate = divisor.Low;
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
#if DEVICE_GPU
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
candidate,
gpu,
prime,
divisorData,
out computedCycle,
out primeOrderFailed);
#elif DEVICE_HYBRID
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
candidate,
gpu,
prime,
divisorData,
out computedCycle,
out primeOrderFailed);
#else
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
candidate,
prime,
divisorData,
out computedCycle,
out primeOrderFailed);
#endif
if (computed && computedCycle != 0UL)
{
divisorCycle = computedCycle;
}
else
{
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
#if DEVICE_GPU
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
candidate,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#elif DEVICE_HYBRID
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
candidate,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#else
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
candidate,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#endif
}
ContinueOuter:
RecordState(currentK);
// if (divisorCycle == prime && !IsMersenneValue(prime, candidate))
if (divisorCycle == prime)
{
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
foundDivisor = candidate;
processedAll = true;
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
divisor.Add(step);
currentK++;
residueStepper.Advance();
}
processedAll = true;
foundDivisor = 0UL;
return false;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsPowerOfTwo(ulong value) => value != 0 && (value & (value - 1)) == 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong PowModUlong(ulong exponent, ulong modulus)
{
ulong result = 1 % modulus;
ulong baseVal = 2 % modulus;
ulong exp = exponent;
while (exp > 0)
{
if ((exp & 1UL) != 0)
{
result = (ulong)(((UInt128)result * baseVal) % modulus);
}
baseVal = (ulong)(((UInt128)baseVal * baseVal) % modulus);
exp >>= 1;
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetPow2PowModCached(ulong divisor, out ulong powMod)
{
#if DETAILED_LOG
_pow2CacheLookups++;
#endif
_pow2DivisorPowModCache ??= new Dictionary<ulong, ulong>(64);
if (_pow2DivisorPowModCache.TryGetValue(divisor, out powMod))
{
#if DETAILED_LOG
_pow2CacheHits++;
#endif
return true;
}
#if DETAILED_LOG
_pow2CacheMisses++;
#endif
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StorePow2PowModCached(ulong divisor, ulong powMod)
{
// EvenPerfectBitScanner's logic forces the divisor to be initialized here. We first get, then store.
_pow2DivisorPowModCache![divisor] = powMod;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsSequentialPow2Cache64(
ulong prime,
ushort primeDecimalMask,
ulong allowedMax,
ulong minK,
out bool processedAll,
out ulong foundDivisor)
{
processedAll = true;
foundDivisor = 0UL;
ulong step = prime << 1;
if (step == 0UL)
{
return false;
}
ulong effectiveMax = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
ulong maxKAllowed = (effectiveMax - 1UL) / step;
if (maxKAllowed < 1UL || minK > maxKAllowed)
{
return false;
}
UInt128 step128 = step;
UInt128 divisor128 = (step128 * minK) + UInt128.One;
if (divisor128 > effectiveMax)
{
return false;
}
ulong divisor = (ulong)divisor128;
ulong currentK = minK;
var residueStepper = new MersenneDivisorResidueStepper(prime, new GpuUInt128(step), new GpuUInt128(divisor));
while (divisor <= effectiveMax)
{
if (residueStepper.IsAdmissible())
{
// Stopwatch sw = Stopwatch.StartNew();
ContinueOuter:
RecordState(currentK);
ulong powMod;
bool isPow2 = IsPowerOfTwo(currentK);
#if DETAILED_LOG
if (isPow2)
{
_pow2CacheEligible++;
}
#endif
if (!isPow2 || !TryGetPow2PowModCached(divisor, out powMod))
{
powMod = PowModUlong(prime, divisor);
if (isPow2)
{
StorePow2PowModCached(divisor, powMod);
}
}
if (powMod == 1UL)
{
// sw.Stop();
foundDivisor = divisor;
return true;
}
// sw.Stop();
// Console.WriteLine($"[sequential-pow2] p={prime} divisor={divisor} powmod={powMod} isPow2={isPow2} pow2Eligible={_pow2CacheEligible} lookups={_pow2CacheLookups} cacheHits={_pow2CacheHits} cacheMisses={_pow2CacheMisses} elapsed={sw.Elapsed}");
}
if (currentK == maxKAllowed)
{
break;
}
divisor += step;
currentK++;
residueStepper.Advance();
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsSequentialPow2CacheBig(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger currentK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
if (allowedMax.IsZero)
{
processedAll = false;
return false;
}
processedAll = true;
BigInteger divisor = (step * currentK) + BigInteger.One;
if (divisor > allowedMax)
{
return false;
}
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
if (currentK > maxKAllowed)
{
return false;
}
BigInteger remainingIterationsBig = maxKAllowed - currentK + BigInteger.One;
var rem10 = new BigIntegerCycleRemainderStepper(10);
var rem8 = new BigIntegerCycleRemainderStepper(8);
var rem3 = new BigIntegerCycleRemainderStepper(3);
var rem7 = new BigIntegerCycleRemainderStepper(7);
var rem11 = new BigIntegerCycleRemainderStepper(11);
var rem13 = new BigIntegerCycleRemainderStepper(13);
var rem17 = new BigIntegerCycleRemainderStepper(17);
var rem19 = new BigIntegerCycleRemainderStepper(19);
byte remainder10 = rem10.Initialize(divisor);
BigInteger primeBig = (BigInteger)prime;
while (remainingIterationsBig.Sign > 0)
{
bool passesSmallModuli = remainder10 == 1 || remainder10 == 7;
if (passesSmallModuli && ((primeDecimalMask >> remainder10) & 1) != 0)
{
if (rem3.NextIsNotDivisible(divisor) &&
rem7.NextIsNotDivisible(divisor) &&
rem11.NextIsNotDivisible(divisor) &&
rem13.NextIsNotDivisible(divisor) &&
rem17.NextIsNotDivisible(divisor) &&
rem19.NextIsNotDivisible(divisor))
{
byte remainder8 = rem8.ComputeNext(divisor);
if (remainder8 == 1 || remainder8 == 7)
{
ContinueOuter:
RecordState(currentK);
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
if (powResult.IsOne)
{
foundDivisor = divisor;
processedAll = true;
return true;
}
}
}
}
currentK++;
divisor += step;
remainder10 = rem10.ComputeNext(divisor);
remainingIterationsBig--;
if (remainingIterationsBig.IsZero)
{
break;
}
}
processedAll = true;
foundDivisor = BigInteger.Zero;
return false;
}
#if DivisorSet_Pow2Groups
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool ProcessPow2Phase(
ulong prime,
ulong max64,
ulong step64,
BigInteger resumeK,
ulong minK,
Pow2Phase phase,
ReadOnlySpan<(int Start, int End)> ranges,
ReadOnlySpan<int> singles,
ref bool processedAll,
out ulong foundDivisor)
{
foundDivisor = 0UL;
int specialRangePromille = EnvironmentConfiguration.ByDivisorSpecialRange;
if (specialRangePromille < 0)
{
specialRangePromille = 0;
}
specialRangePromille *= 100; // convert percent to promille-of-percentScale (10_000 = 100%)
EnsurePreparedSpecials(specialRangePromille);
if (resumeK > ulong.MaxValue)
{
processedAll = false;
return false;
}
BigInteger effectiveResume = resumeK < minK ? minK : resumeK;
BigInteger firstDivisorBig = (((BigInteger)prime) << 1) * effectiveResume + BigInteger.One;
if (firstDivisorBig > max64)
{
processedAll = false;
return false;
}
ulong firstDivisor = (ulong)firstDivisorBig;
int startBit = BitOperations.Log2(firstDivisor);
int maxBit = BitOperations.Log2(max64);
ulong currentK = (ulong)effectiveResume;
_currentPow2Phase = phase;
FlushPow2State(effectiveResume, phase);
ReadOnlySpan<int> effectiveSingles = singles;
ReadOnlySpan<(int Start, int End)> effectiveRanges = ranges;
if (phase == Pow2Phase.Special)
{
effectiveSingles = _preparedSpecialSingles;
effectiveRanges = _preparedSpecialRanges;
}
try
{
for (int bit = startBit; bit <= maxBit; bit++)
{
ulong lower = 1UL << bit;
ulong upperExclusive = bit == 63 ? ulong.MaxValue : lower << 1;
if (lower > max64)
{
break;
}
BigInteger lowerBig = lower;
for (int i = 0; i < effectiveSingles.Length; i++)
{
int startProm = effectiveSingles[i];
int endProm = effectiveSingles[i];
if (specialRangePromille > 0)
{
startProm = Math.Max(0, effectiveSingles[i] - specialRangePromille);
endProm = Math.Min(PercentScale, effectiveSingles[i] + specialRangePromille);
}
if (specialRangePromille == 0)
{
int adjustedEndProm = Math.Min(PercentScale, startProm + 1);
BigInteger scaledStart = (PercentScale + startProm) * lowerBig;
BigInteger scaledEnd = (PercentScale + adjustedEndProm) * lowerBig;
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
BigInteger qMax = scaledEnd / PercentScale;
if ((qMin & 1) == 0)
{
qMin += 1;
}
if ((qMax & 1) == 0)
{
qMax -= 1;
}
if (qMin < 3 || qMin > qMax)
{
continue;
}
if (qMin < lowerBig)
{
qMin = lowerBig | 1;
}
if (qMax >= upperExclusive)
{
qMax = upperExclusive - 1;
if ((qMax & 1) == 0)
{
qMax -= 1;
}
}
if (qMin > qMax || qMin > max64)
{
continue;
}
if (qMax > max64)
{
qMax = max64;
if ((qMax & 1) == 0)
{
qMax -= 1;
}
}
BigInteger stepBig = step64;
BigInteger kMinBig = (qMin - 1 + (stepBig - 1)) / stepBig;
BigInteger kMaxBig = (qMax - 1) / stepBig;
if (kMinBig < minK || kMinBig < effectiveResume)
{
kMinBig = BigInteger.Max(BigInteger.Max(minK, effectiveResume), kMinBig);
}
if (kMinBig > kMaxBig)
{
continue;
}
if (kMaxBig > ulong.MaxValue)
{
kMaxBig = ulong.MaxValue;
}
ulong kStart = (ulong)kMinBig;
ulong kEnd = (ulong)kMaxBig;
bool composite = CheckDivisors64Range(
prime,
step64,
max64,
kStart,
kEnd,
ref currentK,
out bool processedRange,
out foundDivisor);
if (composite)
{
RecordPow2Hit(phase);
FlushPow2State(currentK, phase);
_currentPow2Phase = Pow2Phase.None;
return true;
}
if (!processedRange)
{
processedAll = false;
}
}
else
{
BigInteger scaledStart = (PercentScale + startProm) * lowerBig;
BigInteger scaledEnd = (PercentScale + endProm) * lowerBig;
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
BigInteger qMax = scaledEnd / PercentScale;
if ((qMin & 1) == 0)
{
qMin += 1;
}
if ((qMax & 1) == 0)
{
qMax -= 1;
}
if (qMin < 3 || qMin > qMax)
{
continue;
}
if (qMin < lowerBig)
{
qMin = lowerBig | 1;
}
if (qMax >= upperExclusive)
{
qMax = upperExclusive - 1;
if ((qMax & 1) == 0)
{
qMax -= 1;
}
}
if (qMin > qMax || qMin > max64)
{
continue;
}
if (qMax > max64)
{
qMax = max64;
if ((qMax & 1) == 0)
{
qMax -= 1;
}
}
BigInteger stepBig = step64;
BigInteger kMinBig = (qMin - 1 + (stepBig - 1)) / stepBig;
BigInteger kMaxBig = (qMax - 1) / stepBig;
if (kMinBig < minK || kMinBig < effectiveResume)
{
kMinBig = BigInteger.Max(minK, effectiveResume);
}
if (kMinBig > kMaxBig)
{
continue;
}
if (kMaxBig > ulong.MaxValue)
{
kMaxBig = ulong.MaxValue;
}
ulong kStart = (ulong)kMinBig;
ulong kEnd = (ulong)kMaxBig;
bool composite = CheckDivisors64Range(
prime,
step64,
max64,
kStart,
kEnd,
ref currentK,
out bool processedRange,
out foundDivisor);
if (composite)
{
RecordPow2Hit(phase);
FlushPow2State(currentK, phase);
_currentPow2Phase = Pow2Phase.None;
return true;
}
if (!processedRange)
{
processedAll = false;
}
}
}
for (int i = 0; i < effectiveRanges.Length; i++)
{
int start = effectiveRanges[i].Start;
int end = effectiveRanges[i].End;
BigInteger scaledStart = (PercentScale + start) * lowerBig;
BigInteger scaledEnd = (PercentScale + end) * lowerBig;
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
BigInteger qMax = scaledEnd / PercentScale;
if ((qMin & 1) == 0)
{
qMin += 1;
}
if ((qMax & 1) == 0)
{
qMax -= 1;
}
if (qMin < 3 || qMin > qMax)
{
continue;
}
if (qMin < lowerBig)
{
qMin = lowerBig | 1;
}
if (qMax >= upperExclusive)
{
qMax = upperExclusive - 1;
if ((qMax & 1) == 0)
{
qMax -= 1;
}
}
if (qMin > qMax || qMin > max64)
{
continue;
}
if (qMax > max64)
{
qMax = max64;
if ((qMax & 1) == 0)
{
qMax -= 1;
}
}
BigInteger stepBig = step64;
BigInteger kMinBig = (qMin - 1 + (stepBig - 1)) / stepBig;
BigInteger kMaxBig = (qMax - 1) / stepBig;
BigInteger kStartBig = kMinBig - BigInteger.One;
if (kStartBig < BigInteger.One)
{
kStartBig = kMinBig;
}
if (kStartBig < minK || kStartBig < effectiveResume)
{
kStartBig = BigInteger.Max(BigInteger.Max(minK, effectiveResume), kMinBig);
}
if (kStartBig > kMaxBig)
{
continue;
}
if (kMaxBig > ulong.MaxValue)
{
kMaxBig = ulong.MaxValue;
}
ulong kStart = (ulong)kStartBig;
ulong kEnd = (ulong)kMaxBig;
bool composite = CheckDivisors64Range(
prime,
step64,
max64,
kStart,
kEnd,
ref currentK,
out bool processedRange,
out foundDivisor);
if (composite)
{
RecordPow2Hit(phase);
FlushPow2State(currentK, phase);
_currentPow2Phase = Pow2Phase.None;
return true;
}
if (!processedRange)
{
processedAll = false;
}
}
}
if (currentK >= (ulong)effectiveResume)
{
FlushPow2State(currentK, phase);
}
return false;
}
finally
{
if (currentK >= (ulong)effectiveResume)
{
FlushPow2State(currentK, phase);
}
_currentPow2Phase = Pow2Phase.None;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsurePreparedSpecials(int specialRangePromille)
{
if (_preparedSpecialsInitialized && specialRangePromille == _preparedSpecialRangePromille)
{
return;
}
_preparedSpecialRangePromille = specialRangePromille;
if (specialRangePromille == 0)
{
_preparedSpecialSingles = SpecialPercentPromille;
_preparedSpecialRanges = Array.Empty<(int Start, int End)>();
_preparedSpecialsInitialized = true;
return;
}
int[] specials = SpecialPercentPromille;
if (specials.Length == 0)
{
_preparedSpecialSingles = Array.Empty<int>();
_preparedSpecialRanges = Array.Empty<(int Start, int End)>();
_preparedSpecialsInitialized = true;
return;
}
int[] sorted = new int[specials.Length];
Array.Copy(specials, sorted, specials.Length);
Array.Sort(sorted);
int range = specialRangePromille;
List<(int Start, int End)> merged = new(sorted.Length);
int currentStart = Math.Max(0, sorted[0] - range);
int currentEnd = Math.Min(PercentScale, sorted[0] + range);
for (int i = 1; i < sorted.Length; i++)
{
int start = Math.Max(0, sorted[i] - range);
int end = Math.Min(PercentScale, sorted[i] + range);
if (start <= currentEnd)
{
if (end > currentEnd)
{
currentEnd = end;
}
}
else
{
merged.Add((currentStart, currentEnd));
currentStart = start;
currentEnd = end;
}
}
merged.Add((currentStart, currentEnd));
_preparedSpecialRanges = merged.ToArray();
_preparedSpecialSingles = Array.Empty<int>();
_preparedSpecialsInitialized = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void FlushPow2State(BigInteger k, Pow2Phase phase)
{
if (phase == Pow2Phase.None || k < MinK)
{
return;
}
string targetPath = phase == Pow2Phase.Special ? _specialStateFilePath : _groupsStateFilePath;
ref int counter = ref (phase == Pow2Phase.Special ? ref _specialStateCounter : ref _groupsStateCounter);
ref BigInteger last = ref (phase == Pow2Phase.Special ? ref _specialLastSavedK : ref _groupsLastSavedK);
if (k <= last)
{
return;
}
if (!string.IsNullOrEmpty(targetPath))
{
string? directory = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
File.AppendAllText(targetPath, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
}
counter = 0;
last = k;
}
#endif
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors128Range(
ulong prime,
ushort primeDecimalMask,
UInt128 step,
BigInteger allowedMax,
UInt128 startK,
UInt128 endK,
out BigInteger foundDivisor,
out bool processedAll)
{
foundDivisor = BigInteger.Zero;
processedAll = true;
if (startK > endK)
{
return false;
}
UInt128 allowedMax128 = allowedMax <= BigIntegerNumbers.UInt128MaxValue ? (UInt128)allowedMax : UInt128.MaxValue;
UInt128 maxKAllowed = (allowedMax128 - UInt128.One) / step;
if (endK > maxKAllowed)
{
endK = maxKAllowed;
if (startK > endK)
{
return false;
}
}
UInt128 divisor = (step * startK) + UInt128.One;
processedAll = true;
for (UInt128 k = startK; k <= endK; k++)
{
ushort rem3 = (ushort)(divisor % 3);
ushort rem5 = (ushort)(divisor % 5);
ushort rem7 = (ushort)(divisor % 7);
ushort rem11 = (ushort)(divisor % 11);
ushort rem13 = (ushort)(divisor % 13);
ushort rem17 = (ushort)(divisor % 17);
ushort rem19 = (ushort)(divisor % 19);
if (rem3 != 0 && rem5 != 0 && rem7 != 0 && rem11 != 0 && rem13 != 0 && rem17 != 0 && rem19 != 0)
{
int rem10 = (int)(divisor % 10);
ushort rem8 = (ushort)(divisor % 8);
if ((rem8 == 1 || rem8 == 7) && ((primeDecimalMask >> rem10) & 1) != 0)
{
BigInteger divisorBig = (BigInteger)divisor;
RecordState((BigInteger)k);
BigInteger powResult = BigInteger.ModPow(2, prime, divisorBig);
// if (powResult.IsOne && !IsMersenneValue(prime, divisorBig))
if (powResult.IsOne)
{
foundDivisor = divisorBig;
return true;
}
}
}
if (k == endK)
{
break;
}
divisor += step;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsLarge(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger currentK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
if (allowedMax.IsZero)
{
processedAll = false;
return false;
}
processedAll = true;
BigInteger divisor = (step * currentK) + BigInteger.One;
if (divisor > allowedMax)
{
return false;
}
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
if (currentK > maxKAllowed)
{
return false;
}
BigInteger remainingIterationsBig = maxKAllowed - currentK + BigInteger.One;
if (step <= ulong.MaxValue)
{
UInt128 step128 = (UInt128)(ulong)step;
UInt128 maxKByStep = UInt128Numbers.MaxValueMinusOne / step128;
bool allowedFits128 = allowedMax <= BigIntegerNumbers.UInt128MaxValue;
if (allowedFits128)
{
UInt128 limitK = (((UInt128)allowedMax) - UInt128.One) / step128;
if (limitK < maxKByStep)
{
maxKByStep = limitK;
}
}
BigInteger maxKByStepBig = (BigInteger)maxKByStep;
if (maxKByStepBig >= currentK)
{
if (currentK > BigIntegerNumbers.UInt128MaxValue)
{
currentK = maxKByStepBig + BigInteger.One;
}
else
{
UInt128 startK = (UInt128)currentK;
bool composite128 = CheckDivisors128Range(
prime,
primeDecimalMask,
step128,
allowedMax,
startK,
maxKByStep,
out BigInteger found128,
out bool processed128);
if (composite128)
{
foundDivisor = found128;
return true;
}
if (!processed128)
{
processedAll = false;
}
currentK = maxKByStepBig + BigInteger.One;
divisor = (step * currentK) + BigInteger.One;
if (divisor > allowedMax)
{
return false;
}
}
}
}
var rem10 = new BigIntegerCycleRemainderStepper(10);
var rem8 = new BigIntegerCycleRemainderStepper(8);
var rem3 = new BigIntegerCycleRemainderStepper(3);
var rem7 = new BigIntegerCycleRemainderStepper(7);
var rem11 = new BigIntegerCycleRemainderStepper(11);
var rem13 = new BigIntegerCycleRemainderStepper(13);
var rem17 = new BigIntegerCycleRemainderStepper(17);
var rem19 = new BigIntegerCycleRemainderStepper(19);
byte remainder10 = rem10.Initialize(divisor);
BigInteger primeBig = (BigInteger)prime;
while (remainingIterationsBig.Sign > 0)
{
bool passesSmallModuli = remainder10 == 1 || remainder10 == 7;
if (passesSmallModuli && ((primeDecimalMask >> remainder10) & 1) != 0)
{
if (rem3.NextIsNotDivisible(divisor) &&
rem7.NextIsNotDivisible(divisor) &&
rem11.NextIsNotDivisible(divisor) &&
rem13.NextIsNotDivisible(divisor) &&
rem17.NextIsNotDivisible(divisor) &&
rem19.NextIsNotDivisible(divisor))
{
byte remainder8 = rem8.ComputeNext(divisor);
if (remainder8 == 1 || remainder8 == 7)
{
ContinueOuter:
RecordState(currentK);
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
// if (powResult.IsOne && !IsMersenneValue(prime, divisor))
if (powResult.IsOne)
{
foundDivisor = divisor;
processedAll = true;
return true;
}
}
}
}
currentK++;
divisor += step;
remainder10 = rem10.ComputeNext(divisor);
remainingIterationsBig--;
if (remainingIterationsBig.IsZero)
{
break;
}
}
processedAll = true;
foundDivisor = BigInteger.Zero;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors64Range(
ulong prime,
ulong step,
ulong allowedMax,
ulong startK,
ulong endK,
ref ulong currentK,
out bool processedAll,
out ulong foundDivisor)
{
if (startK < 1UL || endK < startK)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
ulong maxKAllowed = (allowedMax - 1UL) / step;
{
BigInteger maxKBig = maxKAllowed;
if (TryClampMaxKForMersenneTail(prime, (BigInteger)step, ref maxKBig))
{
maxKAllowed = maxKBig > ulong.MaxValue ? ulong.MaxValue : (ulong)maxKBig;
}
}
if (endK > maxKAllowed)
{
endK = maxKAllowed;
if (startK > endK)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
}
currentK = startK;
UInt128 step128 = step;
UInt128 allowedMax128 = allowedMax;
UInt128 startDivisor128 = (step128 * startK) + UInt128.One;
if (startDivisor128 > allowedMax128)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
UInt128 rangeLimit128 = (step128 * endK) + UInt128.One;
if (rangeLimit128 > allowedMax128)
{
rangeLimit128 = allowedMax128;
}
ulong startDivisor = (ulong)startDivisor128;
ulong rangeLimit = (ulong)rangeLimit128;
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
return CheckDivisors64(
prime,
step,
rangeLimit,
startDivisor,
ref residueStepper,
ref currentK,
out processedAll,
out foundDivisor);
}
#if DivisorSet_TopDown
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsTopDown(
ulong prime,
BigInteger allowedMax,
BigInteger minK,
out bool processedAll,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
processedAll = false;
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
ulong step = prime << 1;
ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (allowedMax > ulong.MaxValue ? ulong.MaxValue : (ulong)allowedMax);
if (allowedMax64 <= 1UL)
{
processedAll = true;
return false;
}
ulong maxKAllowed = (allowedMax64 - 1UL) / step;
{
BigInteger maxKBig = maxKAllowed;
if (TryClampMaxKForMersenneTail(prime, (BigInteger)step, ref maxKBig))
{
maxKAllowed = maxKBig > ulong.MaxValue ? ulong.MaxValue : (ulong)maxKBig;
}
}
if (maxKAllowed < 1UL)
{
processedAll = true;
return false;
}
if (normalizedMinK > maxKAllowed)
{
processedAll = true;
return false;
}
ulong minK64 = normalizedMinK > ulong.MaxValue ? ulong.MaxValue : (ulong)normalizedMinK;
if (minK64 < 1UL)
{
minK64 = 1UL;
}
ulong cursor = _topDownCursor > ulong.MaxValue ? ulong.MaxValue : (ulong)_topDownCursor;
ulong currentK = maxKAllowed;
if (cursor > 0 && cursor <= maxKAllowed)
{
currentK = maxKAllowed - cursor;
}
if (currentK < minK64)
{
processedAll = true;
return false;
}
UInt128 step128 = step;
UInt128 divisor = (step128 * currentK) + UInt128.One;
var residueStepper = new MersenneDivisorResidueStepperDescending(prime, new GpuUInt128(step), new GpuUInt128(divisor));
while (currentK >= minK64)
{
if (divisor > allowedMax64)
{
processedAll = true;
return false;
}
if (residueStepper.IsAdmissible())
{
ulong divisor64 = (ulong)divisor;
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
#if DEVICE_GPU
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
divisor64,
Accelerator,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed);
#elif DEVICE_HYBRID
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
divisor64,
Accelerator,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed);
#else
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
divisor64,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed);
#endif
ulong divisorCycle;
if (!computed || computedCycle == 0UL)
{
#if DEVICE_GPU
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
divisor64,
Accelerator,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#elif DEVICE_HYBRID
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
divisor64,
Accelerator,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#else
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
divisor64,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#endif
}
else
{
divisorCycle = computedCycle;
}
RecordState(cursor);
if (divisorCycle == prime && !IsMersenneValue(prime, divisor64))
{
foundDivisor = divisor64;
return true;
}
}
cursor++;
if (cursor > maxKAllowed)
{
break;
}
if (currentK == minK64)
{
break;
}
currentK--;
divisor -= step128;
residueStepper.Retreat();
}
processedAll = true;
return false;
}
#endif
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors64(
ulong prime,
ulong step,
ulong limit,
ulong divisor,
ref MersenneDivisorResidueStepper residueStepper,
ref ulong currentK,
out bool processedAll,
out ulong foundDivisor)
{
#if DEVICE_GPU || DEVICE_HYBRID
PrimeOrderCalculatorAccelerator gpu = Accelerator;
#endif
processedAll = true;
foundDivisor = 0UL;
ulong computedCycle;
bool primeOrderFailed;
ulong divisorCycle;
bool computed;
if (step > limit)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
if (divisor > limit)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
ulong maxKAllowed = ((limit - 1UL) / step);
ulong targetK = ((divisor - 1UL) / step);
if (targetK > maxKAllowed)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
ulong remainingIterations = (maxKAllowed - targetK) + 1UL;
while (remainingIterations > 0UL)
{
if (residueStepper.IsAdmissible())
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
#if DEVICE_GPU
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
divisor,
gpu,
prime,
divisorData,
out computedCycle,
out primeOrderFailed);
#elif DEVICE_HYBRID
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
divisor,
gpu,
prime,
divisorData,
out computedCycle,
out primeOrderFailed);
#else
computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
divisor,
prime,
divisorData,
out computedCycle,
out primeOrderFailed);
#endif
if (!computed || computedCycle == 0UL)
{
#if DEVICE_GPU
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
divisor,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#elif DEVICE_HYBRID
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
divisor,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#else
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#endif
}
else
{
divisorCycle = computedCycle;
}
ContinueOuter:
RecordState(currentK);
if (divisorCycle == prime && !IsMersenneValue(prime, divisor))
{
foundDivisor = divisor;
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
remainingIterations--;
if (remainingIterations == 0UL)
{
return false;
}
divisor += step;
currentK++;
residueStepper.Advance();
}
processedAll = true;
foundDivisor = 0UL;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private void RecordState(BigInteger k)
{
#if DivisorSet_TopDown
if (_currentPrime != 0UL)
{
if (k > _lastSavedK)
{
int next = _stateCounter + 1;
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
{
_kRepository.Upsert(_currentPrime, k);
_stateCounter = 0;
_lastSavedK = k;
_topDownCursor = k;
}
else
{
_stateCounter = next;
}
_topDownCursor = k;
}
}
return;
#endif
#if DivisorSet_Pow2Groups
if (_currentPow2Phase == Pow2Phase.Special)
{
RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK);
return;
}
if (_currentPow2Phase == Pow2Phase.Groups)
{
RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK);
return;
}
#endif
if (_currentPrime != 0UL)
{
if (k > _lastSavedK)
{
int next = _stateCounter + 1;
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
{
_kRepository.Upsert(_currentPrime, k);
_lastSavedK = k;
_stateCounter = 0;
}
else
{
_stateCounter = next;
}
}
}
}
#if DivisorSet_Pow2Groups
private void RecordState(BigInteger k, Pow2Phase phase)
{
switch (phase)
{
case Pow2Phase.Special:
RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK);
break;
case Pow2Phase.Groups:
RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK);
break;
default:
RecordState(k);
break;
}
}
private void RecordStatePow2(BigInteger k, KStateRepository? repo, ref int counter, ref BigInteger lastSavedK)
{
if (_currentPrime == 0UL || repo is null)
{
return;
}
if (k <= lastSavedK)
{
return;
}
int next = counter + 1;
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
{
repo.Upsert(_currentPrime, k);
counter = 0;
lastSavedK = k;
}
else
{
counter = next;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void RecordPow2Hit(Pow2Phase phase)
{
long special = Interlocked.Read(ref _specialHits);
long groups = Interlocked.Read(ref _groupHits);
if (phase == Pow2Phase.Special)
{
special = Interlocked.Increment(ref _specialHits);
}
else if (phase == Pow2Phase.Groups)
{
groups = Interlocked.Increment(ref _groupHits);
}
Console.WriteLine($"By-divisor pow2groups hits: specials={special}, groups={groups}");
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static int GetBitLength(BigInteger value)
{
if (value.IsZero)
{
return 0;
}
byte[] bytes = value.ToByteArray();
int lastIndex = bytes.Length - 1;
return (lastIndex * 8) + BitOperations.Log2(bytes[lastIndex]) + 1;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool ProcessPow2PhaseBig(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger step,
BigInteger resumeK,
BigInteger minK,
BigInteger segmentStartK,
BigInteger segmentEndK,
Pow2Phase phase,
ReadOnlySpan<(int Start, int End)> ranges,
ReadOnlySpan<int> singles,
ref bool processedAll,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
if (allowedMax.IsZero || segmentStartK > segmentEndK)
{
return false;
}
int specialRangePromille = EnvironmentConfiguration.ByDivisorSpecialRange;
if (specialRangePromille < 0)
{
specialRangePromille = 0;
}
specialRangePromille *= 100; // convert percent to promille-of-percentScale (10_000 = 100%)
EnsurePreparedSpecials(specialRangePromille);
BigInteger effectiveStart = segmentStartK;
if (resumeK > effectiveStart)
{
effectiveStart = resumeK;
}
if (effectiveStart < minK)
{
effectiveStart = minK;
}
if (effectiveStart > segmentEndK)
{
return false;
}
BigInteger firstDivisor = (step * effectiveStart) + BigInteger.One;
if (firstDivisor > allowedMax)
{
processedAll = false;
return false;
}
int startBit = GetBitLength(firstDivisor) - 1;
int maxBit = GetBitLength(allowedMax) - 1;
_currentPow2Phase = phase;
FlushPow2State(effectiveStart, phase);
ReadOnlySpan<int> effectiveSingles = singles;
ReadOnlySpan<(int Start, int End)> effectiveRanges = ranges;
if (phase == Pow2Phase.Special)
{
effectiveSingles = _preparedSpecialSingles;
effectiveRanges = _preparedSpecialRanges;
}
try
{
for (int bit = startBit; bit <= maxBit; bit++)
{
BigInteger lower = BigInteger.One << bit;
BigInteger upperExclusive = BigInteger.One << (bit + 1);
if (lower > allowedMax)
{
break;
}
for (int i = 0; i < effectiveSingles.Length; i++)
{
int startProm = effectiveSingles[i];
int endProm = effectiveSingles[i];
if (specialRangePromille > 0)
{
startProm = Math.Max(0, effectiveSingles[i] - specialRangePromille);
endProm = Math.Min(PercentScale, effectiveSingles[i] + specialRangePromille);
}
BigInteger scaledStart = (PercentScale + startProm) * lower;
BigInteger scaledEnd = (PercentScale + (specialRangePromille == 0 ? Math.Min(PercentScale, startProm + 1) : endProm)) * lower;
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
BigInteger qMax = scaledEnd / PercentScale;
if (qMin.IsEven)
{
qMin += BigInteger.One;
}
if (qMax.IsEven)
{
qMax -= BigInteger.One;
}
if (qMin < 3 || qMin > qMax)
{
continue;
}
if (qMin < lower)
{
qMin = lower | BigInteger.One;
}
if (qMax >= upperExclusive)
{
qMax = upperExclusive - BigInteger.One;
if ((qMax & BigInteger.One) == BigInteger.Zero)
{
qMax -= BigInteger.One;
}
}
if (qMin > qMax || qMin > allowedMax)
{
continue;
}
if (qMax > allowedMax)
{
qMax = allowedMax;
if ((qMax & BigInteger.One) == BigInteger.Zero)
{
qMax -= BigInteger.One;
}
}
BigInteger kMin = (qMin - BigInteger.One + (step - BigInteger.One)) / step;
BigInteger kMax = (qMax - BigInteger.One) / step;
if (kMin < minK || kMin < effectiveStart)
{
kMin = BigInteger.Max(BigInteger.Max(minK, effectiveStart), kMin);
}
if (kMax > segmentEndK)
{
kMax = segmentEndK;
}
if (kMin > kMax)
{
continue;
}
BigInteger currentK = kMin;
bool composite = CheckDivisorsBigRangePow2(
prime,
primeDecimalMask,
step,
allowedMax,
kMin,
kMax,
phase,
ref currentK,
out bool processedRange,
out BigInteger foundBig);
if (composite)
{
RecordPow2Hit(phase);
FlushPow2State(currentK, phase);
_currentPow2Phase = Pow2Phase.None;
foundDivisor = foundBig;
return true;
}
if (!processedRange)
{
processedAll = false;
}
}
for (int i = 0; i < effectiveRanges.Length; i++)
{
int startProm = effectiveRanges[i].Start;
int endProm = effectiveRanges[i].End;
if (specialRangePromille > 0 && phase == Pow2Phase.Special)
{
startProm = Math.Max(0, startProm - specialRangePromille);
endProm = Math.Min(PercentScale, endProm + specialRangePromille);
}
BigInteger scaledStart = (PercentScale + startProm) * lower;
BigInteger scaledEnd = (PercentScale + endProm) * lower;
BigInteger qMin = (scaledStart + (PercentScale - 1)) / PercentScale;
BigInteger qMax = scaledEnd / PercentScale;
if ((qMin & BigInteger.One) == BigInteger.Zero)
{
qMin += BigInteger.One;
}
if ((qMax & BigInteger.One) == BigInteger.Zero)
{
qMax -= BigInteger.One;
}
if (qMin < 3 || qMin > qMax)
{
continue;
}
if (qMin < lower)
{
qMin = lower | BigInteger.One;
}
if (qMax >= upperExclusive)
{
qMax = upperExclusive - BigInteger.One;
if ((qMax & BigInteger.One) == BigInteger.Zero)
{
qMax -= BigInteger.One;
}
}
if (qMin > qMax || qMin > allowedMax)
{
continue;
}
if (qMax > allowedMax)
{
qMax = allowedMax;
if ((qMax & BigInteger.One) == BigInteger.Zero)
{
qMax -= BigInteger.One;
}
}
BigInteger kMin = (qMin - BigInteger.One + (step - BigInteger.One)) / step;
BigInteger kMax = (qMax - BigInteger.One) / step;
if (kMin < minK || kMin < effectiveStart)
{
kMin = BigInteger.Max(BigInteger.Max(minK, effectiveStart), kMin);
}
if (kMax > segmentEndK)
{
kMax = segmentEndK;
}
if (kMin > kMax)
{
continue;
}
BigInteger currentK = kMin;
bool composite = CheckDivisorsBigRangePow2(
prime,
primeDecimalMask,
step,
allowedMax,
kMin,
kMax,
phase,
ref currentK,
out bool processedRange,
out BigInteger foundBig);
if (composite)
{
RecordPow2Hit(phase);
FlushPow2State(currentK, phase);
_currentPow2Phase = Pow2Phase.None;
foundDivisor = foundBig;
return true;
}
if (!processedRange)
{
processedAll = false;
}
}
}
}
finally
{
_currentPow2Phase = Pow2Phase.None;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsBigRangePow2(
ulong prime,
ushort primeDecimalMask,
BigInteger step,
BigInteger allowedMax,
BigInteger startK,
BigInteger endK,
Pow2Phase phase,
ref BigInteger currentK,
out bool processedAll,
out BigInteger foundDivisor)
{
if (startK < BigInteger.One || endK < startK)
{
processedAll = true;
foundDivisor = BigInteger.Zero;
return false;
}
currentK = startK;
BigInteger divisor = (step * currentK) + BigInteger.One;
if (divisor > allowedMax && !allowedMax.IsZero)
{
processedAll = false;
foundDivisor = BigInteger.Zero;
return false;
}
BigInteger iterations = (endK - currentK) + BigInteger.One;
var rem10 = new BigIntegerCycleRemainderStepper(10);
var rem8 = new BigIntegerCycleRemainderStepper(8);
var rem5 = new BigIntegerCycleRemainderStepper(5);
var rem3 = new BigIntegerCycleRemainderStepper(3);
var rem7 = new BigIntegerCycleRemainderStepper(7);
var rem11 = new BigIntegerCycleRemainderStepper(11);
var rem13 = new BigIntegerCycleRemainderStepper(13);
var rem17 = new BigIntegerCycleRemainderStepper(17);
var rem19 = new BigIntegerCycleRemainderStepper(19);
byte remainder10 = (byte)rem10.Initialize(divisor);
byte remainder8 = (byte)rem8.Initialize(divisor);
byte remainder5 = (byte)rem5.Initialize(divisor);
byte remainder3 = (byte)rem3.Initialize(divisor);
byte remainder7 = (byte)rem7.Initialize(divisor);
byte remainder11 = (byte)rem11.Initialize(divisor);
byte remainder13 = (byte)rem13.Initialize(divisor);
byte remainder17 = (byte)rem17.Initialize(divisor);
byte remainder19 = (byte)rem19.Initialize(divisor);
while (iterations > BigInteger.Zero)
{
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((primeDecimalMask >> remainder10) & 1) != 0)
{
RecordState(currentK, phase);
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
if (powResult.IsOne && !IsMersenneValue(prime, divisor))
{
foundDivisor = divisor;
processedAll = true;
return true;
}
}
currentK += BigInteger.One;
divisor += step;
remainder10 = (byte)rem10.ComputeNext(divisor);
remainder8 = (byte)rem8.ComputeNext(divisor);
remainder5 = (byte)rem5.ComputeNext(divisor);
remainder3 = (byte)rem3.ComputeNext(divisor);
remainder7 = (byte)rem7.ComputeNext(divisor);
remainder11 = (byte)rem11.ComputeNext(divisor);
remainder13 = (byte)rem13.ComputeNext(divisor);
remainder17 = (byte)rem17.ComputeNext(divisor);
remainder19 = (byte)rem19.ComputeNext(divisor);
iterations--;
}
processedAll = true;
foundDivisor = BigInteger.Zero;
return false;
}
#endif
#if DivisorSet_BitContradiction
private static readonly BigInteger Five = (BigInteger)5;
private static int _consolePrintOut;
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsBitContradiction(
ulong prime,
ushort primeDecimalMask,
BigInteger allowedMax,
BigInteger minK,
BigInteger step,
out bool processedAll,
out BigInteger foundDivisor)
{
#if DEVICE_GPU
return CheckDivisorsBitContradictionGpu(prime, primeDecimalMask, allowedMax, minK, step, out processedAll, out foundDivisor);
#else
processedAll = true;
foundDivisor = BigInteger.Zero;
BigInteger currentK = minK;
bool hasLimit = !allowedMax.IsZero;
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
int stepMod8 = (int)(step % 8);
int kRem8 = (int)(currentK % 8);
BigInteger divisor = (step * currentK) + BigInteger.One;
_qBits = new int[(int)prime];
Stopwatch sw = new Stopwatch();
#if WITH_PRUNE_STATS
var slowCalculationThreshold = _slowCalculationThreshold;
#endif
// sw.Start();
while (true)
{
int remainder8 = (stepMod8 * kRem8 + 1) & 7;
#if DETAILED_LOG
if (prime == 138000001UL && divisor == 1209708008767)
Console.WriteLine("[HIT] enumerated target q before remainder8 check");
#endif
if (remainder8 != 1 && remainder8 != 7)
{
goto MOVE_NEXT;
}
// if (
// divisor <= ulong.MaxValue &&
// )
// {
// goto MOVE_NEXT;
// }
bool divides;
sw.Reset();
sw.Start();
bool decided = TryExactBitContradictionCheck(prime, divisor, currentK, out divides);
sw.Stop();
if (!decided)
{
throw new InvalidOperationException($"BitContradiction check did not decide for p={prime}, divisor={divisor} (k={currentK}).");
}
if (divides)
{
foundDivisor = divisor;
return true;
}
else
{
#if WITH_PRUNE_STATS
if (sw.Elapsed >= slowCalculationThreshold)
{
Console.WriteLine($"SLOW CALCULATIONS: Prime {prime} ({sw.Elapsed}: {divisor})");
_consolePrintOut = 0;
}
#else
var printToConsole = (_consolePrintOut++) == PerfectNumberConstants.ConsoleInterval;
if (printToConsole)
{
// sw.Stop();
Console.WriteLine($"Prime {prime} ({sw.Elapsed}: {divisor})");
_consolePrintOut = 0;
// sw.Reset();
// sw.Start();
}
#endif
}
#if DETAILED_LOG
if (prime == 138000001UL && divisor == 1209708008767)
{
Console.WriteLine("[HIT] processed target q. Stopping.");
Environment.Exit(1);
}
#endif
RecordState(currentK);
MOVE_NEXT:
ContinueOuter:
if (hasLimit && currentK >= maxK)
{
RecordState(currentK);
break;
}
currentK += BigInteger.One;
divisor += step;
kRem8 = (kRem8 + 1) & 7;
}
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);
#endif
}
[ThreadStatic]
private static int[]? _qBits;
[ThreadStatic]
private static int[]? _pow2Offsets;
[ThreadStatic]
private static int _pow2OffsetCount;
[ThreadStatic]
private static BigInteger _pow2K;
[ThreadStatic]
private static bool _hasPow2K;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, BigInteger currentK, out bool divides)
{
divides = false;
// const ulong DebugTopDownDivisor = 1209708008767UL;
// const ulong DebugTopDownDivisor = 1007991562955719UL;
// Divisors generated by EvenPerfectBitScanner are of the form 2kp+1 and are never even. No need to check these.
// if (prime == 0 || divisor <= BigInteger.One)
// {
// Console.WriteLine($"[bitcontradiction] invalid inputs {prime} / {divisor}");
// return false;
// }
// divisor = DebugTopDownDivisor;
int qBitLength = GetBitLengthPortable(divisor);
if (qBitLength > (int)prime)
{
divides = false;
return true;
}
// Divisors generated by EvenPerfectBitScanner are of the form 2kp+1 and are never even. No need to check it.
// 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;
}
// if (divisor <= ulong.MaxValue)
// {
// // ulong exponent64 = effectiveExponent > 0UL ? effectiveExponent : prime;
// // ContradictionReason reason64;
// // bool decided64 = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromDivisor((ulong)divisor, exponent64, out divides, out reason64);
// // if (decided64 && divides)
// // {
// // bool structuralOk = ((divisor - BigInteger.One) % (((BigInteger)prime) << 1)) == BigInteger.Zero;
// // bool dividesExact = structuralOk && BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
// divides = BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
// return true;
// // if (!dividesExact)
// // {
// // divides = false;
// // reason64 = ContradictionReason.ParityUnreachable;
// // }
// // }
// // return decided64;
// }
// Console.WriteLine($"Switching to bit-contradiction mode for {prime}");
// Iterative column-wise propagation without storing all columns; we only keep assigned bits of 'a'.
int[] oneOffsets = _qBits!;
// Span<int> oneOffsets = stackalloc int[qBitLength];
int oneCount = 0;
bool isPowerOfTwo = currentK.IsPowerOfTwo;
bool usedPow2Offsets = false;
int[]? pow2Offsets = _pow2Offsets;
int pow2OffsetCount = _pow2OffsetCount;
BigInteger pow2K = _pow2K;
bool hasPow2K = _hasPow2K;
if (isPowerOfTwo && hasPow2K && pow2Offsets != null && pow2OffsetCount > 0 && currentK == (pow2K << 1))
{
int needed = pow2OffsetCount + 1;
if (needed <= oneOffsets.Length)
{
oneOffsets[0] = 0;
for (int i = 0; i < pow2OffsetCount; i++)
{
oneOffsets[i + 1] = pow2Offsets[i] + 1;
}
oneCount = needed;
usedPow2Offsets = true;
}
}
if (!usedPow2Offsets)
{
for (int i = 0; i < qBitLength; i++)
{
if (((divisor >> i) & BigInteger.One) == BigInteger.One)
{
oneOffsets[oneCount++] = i;
}
}
}
if (isPowerOfTwo)
{
int[]? pow2OffsetsTarget = _pow2Offsets;
if (pow2OffsetsTarget == null || pow2OffsetsTarget.Length < oneCount)
{
pow2OffsetsTarget = new int[oneCount];
_pow2Offsets = pow2OffsetsTarget;
}
Array.Copy(oneOffsets, pow2OffsetsTarget, oneCount);
_pow2OffsetCount = oneCount;
_pow2K = currentK;
_hasPow2K = true;
}
if (oneCount == 0)
{
divides = false;
return true;
}
ReadOnlySpan<int> oneOffsetsSlice = oneOffsets[..oneCount];
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(true);
#if DETAILED_LOG
bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, divisor, currentK, isPowerOfTwo, out divides, out var reason);
#else
bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, divisor, currentK, isPowerOfTwo, out divides);
#endif
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(false);
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)
{
throw new InvalidOperationException($"We reported divisor q={divisor} as dividing {prime} but it doesn't divide it");
divides = false;
#if DETAILED_LOG
reason = ContradictionReason.ParityUnreachable;
#endif
}
}
if (decided && !divides)
{
#if DETAILED_LOG
if (reason == ContradictionReason.ParityUnreachable)
{
var failure = BitContradictionSolverWithQMultiplier.LastTopDownFailure;
if (failure.HasValue)
{
var info = failure.Value;
Console.WriteLine($"[bit-contradiction] top-down prune failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] unknown={info.Unknown}");
}
var bottomUp = BitContradictionSolverWithQMultiplier.LastBottomUpFailure;
if (bottomUp.HasValue)
{
var info = bottomUp.Value;
Console.WriteLine($"[bit-contradiction] bottom-up failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] forced={info.Forced} unknown={info.Unknown}");
}
}
#endif
// _bitContradictionStateRepository?.Upsert(prime, "excluded");
}
#if DETAILED_LOG
Console.WriteLine($"[bitcontradiction] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides} Reason={reason}");
#endif
return decided;
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void MarkPow2MinusOneChecked()
{
_pow2MinusOneChecked = true;
if (_currentPrime != 0UL)
{
_pow2MinusOneRepository?.MarkChecked(_currentPrime);
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool TryFindPowerOfTwoMinusOneDivisor(ulong prime, BigInteger allowedMax, out BigInteger divisor)
{
divisor = BigInteger.Zero;
if (_pow2MinusOneChecked)
{
return false;
}
if (!_checkedPow2MinusOne.TryAdd(prime, 0))
{
return false;
}
BigInteger limit = allowedMax.IsZero ? new BigInteger(ulong.MaxValue) : allowedMax;
if (limit <= ulong.MaxValue)
{
ulong limit64 = (ulong)limit;
for (int x = 2; x < 64; x++)
{
if ((ulong)x == prime)
{
continue;
}
ulong d = (1UL << x) - 1UL;
if (d > limit64)
{
break;
}
if (BigInteger.ModPow(2, prime, d) == BigInteger.One)
{
divisor = d;
return true;
}
}
}
BigInteger pow = BigIntegerNumbers.OneShiftedLeft63;
for (int x = 63; ; x++)
{
pow <<= 1;
BigInteger dBig = pow - BigInteger.One;
if (dBig > limit)
{
break;
}
if ((ulong)x == prime)
{
continue;
}
if (BigInteger.ModPow(2, prime, dBig) == BigInteger.One)
{
divisor = dBig;
_checkedPow2MinusOne[prime] = 1;
MarkPow2MinusOneChecked();
return true;
}
}
_checkedPow2MinusOne[prime] = 1;
_currentPrime = prime;
MarkPow2MinusOneChecked();
return false;
}
}