Merge pull request #31 from mlemanczyk/codex/modify-evenperfectbitscanner-for-uint128-divisors

Allow residue GPU scanning to cover UInt128 divisors
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-09-20 02:49:36 +02:00
commit ff2c516a0e
6 files changed
+62 -56

No files matched your search

+24 -18
View File
@@ -1,3 +1,4 @@
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
@@ -73,12 +74,13 @@ internal static class Program
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
bool orderOnGpu = true; // controls order computations device
int scanBatchSize = 2_097_152, sliceSize = 32;
ulong residueKMax = 5_000_000UL;
UInt128 residueKMax = 5_000_000UL;
string filterFile = string.Empty;
string cyclesPath = DefaultCyclesPath;
int cyclesBatchSize = 512;
bool continueCyclesGeneration = false;
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
UInt128 divisorCyclesSearchLimit128 = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
// NTT backend selection (GPU): reference vs staged
for (int i = 0; i < args.Length; i++)
@@ -140,19 +142,23 @@ internal static class Program
int eq = arg.IndexOf('=');
divisor = UInt128.Parse(arg.AsSpan(eq + 1));
}
else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
{
int eq = arg.IndexOf('=');
divisorCyclesSearchLimit = ulong.Parse(arg.AsSpan(eq + 1));
}
else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
{
int eq = arg.IndexOf('=');
if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax))
{
residueKMax = kmax;
}
}
else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
{
int eq = arg.IndexOf('=');
if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var limit))
{
divisorCyclesSearchLimit128 = limit;
divisorCyclesSearchLimit = limit > (UInt128)ulong.MaxValue ? ulong.MaxValue : (ulong)limit;
}
}
else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
{
int eq = arg.IndexOf('=');
if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var kmax))
{
residueKMax = kmax;
}
}
// Replaces --lucas=cpu|gpu; controls device for Lucas and for
// incremental/pow2mod scanning
else if (arg.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
@@ -448,8 +454,8 @@ internal static class Program
useGpuScan: mersenneOnGpu,
useGpuOrder: orderOnGpu,
useResidue: useResidue,
maxK: residueKMax,
residueDivisorSets: divisorCyclesSearchLimit);
maxK: residueKMax,
residueDivisorSets: divisorCyclesSearchLimit128);
if (!useLucas)
{
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
@@ -741,7 +747,7 @@ internal static class Program
Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)");
Console.WriteLine(" --divisor-cycles-batch-size=<value> batch size for cycles generation (default 512)");
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor or residue");
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor or residue (UInt128 supported; residue path honors full range)");
Console.WriteLine(" --use-order test primality via q order");
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
@@ -30,7 +30,7 @@ public class MersenneNumberResidueCpuTesterTests
(UInt128)exponent << 1,
LastDigitIsSeven(exponent),
(UInt128)maxK,
1UL,
UInt128.One,
(UInt128)maxK,
ref isPrime,
ref exhausted);
@@ -33,7 +33,7 @@ public class MersenneNumberResidueGpuTesterTests
(UInt128)exponent << 1,
LastDigitIsSeven(exponent),
(UInt128)maxK,
1UL,
UInt128.One,
(UInt128)maxK,
ref isPrime,
ref exhausted);
@@ -10,12 +10,12 @@ public class MersenneNumberResidueCpuTester
UInt128 twoP,
bool lastIsSeven,
UInt128 perSetLimit,
ulong setCount,
UInt128 setCount,
UInt128 overallLimit,
ref bool isPrime,
ref bool divisorsExhausted)
{
if (setCount == 0UL || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero)
if (setCount == UInt128.Zero || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero)
{
return;
}
@@ -52,9 +52,9 @@ public class MersenneNumberResidueCpuTester
ModResidueTracker tracker = _mersenneResidueTracker!;
UInt128 limitInclusive = overallLimit + UInt128.One;
for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{
UInt128 setOffset = perSetLimit * (UInt128)setIndex;
UInt128 setOffset = perSetLimit * setIndex;
UInt128 k = setOffset + UInt128.One;
if (k >= limitInclusive)
{
@@ -12,20 +12,20 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// via GpuKernelPool to avoid host round-trips.
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
public void Scan(
ulong exponent,
UInt128 twoP,
bool lastIsSeven,
UInt128 perSetLimit,
ulong setCount,
UInt128 overallLimit,
ref bool isPrime,
ref bool divisorsExhausted)
{
if (setCount == 0UL || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero)
{
return;
}
public void Scan(
ulong exponent,
UInt128 twoP,
bool lastIsSeven,
UInt128 perSetLimit,
UInt128 setCount,
UInt128 overallLimit,
ref bool isPrime,
ref bool divisorsExhausted)
{
if (setCount == UInt128.Zero || perSetLimit == UInt128.Zero || overallLimit == UInt128.Zero)
{
return;
}
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var accelerator = gpuLease.Accelerator;
@@ -48,9 +48,9 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
Span<ulong> orders = orderArray.AsSpan(0, batchSize);
try
{
for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{
UInt128 setOffset = perSetLimit * (UInt128)setIndex;
for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{
UInt128 setOffset = perSetLimit * setIndex;
UInt128 setStart = setOffset + UInt128.One;
if (setStart >= limitInclusive)
{
+15 -15
View File
@@ -25,13 +25,13 @@ public sealed class MersenneNumberTester(
bool useGpuScan = true,
bool useGpuOrder = true,
bool useResidue = true,
ulong maxK = 5_000_000UL,
ulong residueDivisorSets = PerfectNumberConstants.ExtraDivisorCycleSearchLimit)
UInt128? maxK = null,
UInt128? residueDivisorSets = null)
{
private readonly bool _useResidue = useResidue;
private readonly bool _useIncremental = useIncremental && !useResidue;
private readonly ulong _maxK = maxK;
private readonly ulong _residueDivisorSetCount = residueDivisorSets;
private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL;
private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useModuloWorkaround = useModuloWorkaround;
private readonly bool _useGpuLucas = useGpuLucas;
@@ -44,7 +44,7 @@ public sealed class MersenneNumberTester(
private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType);
private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new();
private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new();
private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null;
private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null;
private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null;
// TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
@@ -246,17 +246,17 @@ public sealed class MersenneNumberTester(
// In residue mode the residue scan is the final primality check.
if (_useResidue)
{
if ((UInt128)_maxK < maxK)
if (_maxK < maxK)
{
maxK = _maxK;
}
if (_maxK == 0UL || _residueDivisorSetCount == 0UL)
if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero)
{
return prePrime;
}
UInt128 totalLimit = (UInt128)_maxK;
UInt128 totalLimit = _maxK;
if (totalLimit > maxK)
{
totalLimit = maxK;
@@ -267,23 +267,23 @@ public sealed class MersenneNumberTester(
return true;
}
ulong requestedSets = _residueDivisorSetCount;
if (requestedSets == 0UL)
UInt128 requestedSets = _residueDivisorSetCount;
if (requestedSets == UInt128.Zero)
{
requestedSets = 1UL;
requestedSets = UInt128.One;
}
UInt128 perSetLimit = DivideRoundUp(totalLimit, (UInt128)requestedSets);
UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets);
if (perSetLimit == UInt128.Zero)
{
perSetLimit = UInt128.One;
}
UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit);
ulong effectiveSets = requestedSets;
if (setsNeeded < (UInt128)effectiveSets)
UInt128 effectiveSets = requestedSets;
if (setsNeeded < effectiveSets)
{
effectiveSets = (ulong)setsNeeded;
effectiveSets = setsNeeded;
}
bool scanCompleted = false;