Revert "Merge pull request #24 from mlemanczyk/codex/modify-evenperfectbitscanner-for-residue-mode"

This reverts commit 2d024cc708, reversing
changes made to b1c29ad16e.
This commit is contained in:
Marek Lemańczyk committed 2025-09-21 02:25:18 +02:00
1 parent deb25796a8
commit 8a6fc0c6a5
7 files changed
+48 -334

No files matched your search

+2 -91
View File
@@ -165,95 +165,6 @@ public class ProgramTests
} }
} }
[Fact]
public void Divisor_mode_sets_detailedCheck_when_divisor_is_found()
{
var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!;
var divisorField = typeof(Program).GetField("_divisor", BindingFlags.NonPublic | BindingFlags.Static)!;
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
useDivisorField.SetValue(null, true);
divisorField.SetValue(null, UInt128.Zero);
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, new (ulong, uint)[] { (23UL, 11U) });
forceCpuProp!.SetValue(null, true);
try
{
Program.IsEvenPerfectCandidate(11UL, 32UL, out bool searched, out bool detailedCheck).Should().BeFalse();
searched.Should().BeTrue();
detailedCheck.Should().BeTrue();
}
finally
{
useDivisorField.SetValue(null, false);
testerField.SetValue(null, null);
candidatesField.SetValue(null, null);
primeField.SetValue(null, null);
residueField.SetValue(null, null);
forceCpuProp!.SetValue(null, false);
}
}
[Fact]
public void Residue_mode_sets_detailedCheck_based_on_exhaustion()
{
var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!;
var residueModeField = typeof(Program).GetField("_useResidueMode", BindingFlags.NonPublic | BindingFlags.Static)!;
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
const ulong exponent = 7UL;
useDivisorField.SetValue(null, false);
residueModeField.SetValue(null, true);
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try
{
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useGpuScan: false,
useGpuOrder: false,
useResidue: true,
maxK: 0UL,
residueDivisorSets: 0UL), trackAllValues: true));
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
searched.Should().BeTrue();
detailedCheck.Should().BeFalse();
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useGpuScan: false,
useGpuOrder: false,
useResidue: true,
maxK: 4UL,
residueDivisorSets: 2UL), trackAllValues: true));
Program.IsEvenPerfectCandidate(exponent, 0UL, out searched, out detailedCheck).Should().BeTrue();
searched.Should().BeTrue();
detailedCheck.Should().BeTrue();
}
finally
{
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
residueField.SetValue(null, null);
residueModeField.SetValue(null, false);
forceCpuProp!.SetValue(null, false);
useDivisorField.SetValue(null, false);
}
}
[Fact] [Fact]
public void Divisor_mode_accepts_large_search_limit() public void Divisor_mode_accepts_large_search_limit()
{ {
@@ -335,7 +246,7 @@ public class ProgramTests
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
useDivisorField.SetValue(null, false); useDivisorField.SetValue(null, false);
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL, residueDivisorSets: 1UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false); forceCpuProp!.SetValue(null, false);
@@ -344,7 +255,7 @@ public class ProgramTests
{ {
Program.IsEvenPerfectCandidate(29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); Program.IsEvenPerfectCandidate(29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
searched.Should().BeTrue(); searched.Should().BeTrue();
detailed.Should().BeTrue(); detailed.Should().BeFalse();
Program.IsEvenPerfectCandidate(31UL, 0UL, out searched, out detailed).Should().BeTrue(); Program.IsEvenPerfectCandidate(31UL, 0UL, out searched, out detailed).Should().BeTrue();
searched.Should().BeTrue(); searched.Should().BeTrue();
+4 -27
View File
@@ -460,8 +460,7 @@ internal static class Program
useGpuScan: mersenneOnGpu, useGpuScan: mersenneOnGpu,
useGpuOrder: orderOnGpu, useGpuOrder: orderOnGpu,
useResidue: useResidue, useResidue: useResidue,
maxK: residueKMax, maxK: residueKMax);
residueDivisorSets: divisorCyclesSearchLimit128);
if (!useLucas) if (!useLucas)
{ {
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL); tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
@@ -767,7 +766,7 @@ internal static class Program
Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)"); 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-batch-size=<value> batch size for cycles generation (default 512)");
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation"); Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor or residue (UInt128 supported; residue path honors full range)"); Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --use-order test primality via q order");
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
@@ -1127,30 +1126,8 @@ internal static class Program
return _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out detailedCheck); return _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out detailedCheck);
} }
bool mersennePrime = MersenneTesters.Value!.IsMersennePrime(p, out bool residueExhausted); detailedCheck = MersenneTesters.Value!.IsMersennePrime(p);
detailedCheck = residueExhausted; return detailedCheck;
return mersennePrime;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static UInt128 ResolveResidueMaxK(
bool useResidue,
bool residueMaxExplicit,
bool divisorLimitExplicit,
UInt128 residueMaxK,
UInt128 divisorLimit)
{
if (!useResidue)
{
return residueMaxK;
}
if (residueMaxExplicit || !divisorLimitExplicit)
{
return residueMaxK;
}
return divisorLimit;
} }
// Use ModResidueTracker with a small set of primes to pre-filter composite p. // Use ModResidueTracker with a small set of primes to pre-filter composite p.
@@ -69,18 +69,8 @@ public class MersenneNumberResidueCpuTesterTests
bool expectedPrime) bool expectedPrime)
{ {
bool isPrime = true; bool isPrime = true;
bool exhausted = false; tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
tester.Scan(
exponent,
(UInt128)exponent << 1,
LastDigitIsSeven(exponent),
(UInt128)perSetLimit,
(UInt128)setCount,
(UInt128)overallLimit,
ref isPrime,
ref exhausted);
isPrime.Should().Be(expectedPrime); isPrime.Should().Be(expectedPrime);
exhausted.Should().BeTrue();
} }
} }
@@ -288,18 +288,8 @@ public class MersenneNumberResidueGpuTesterTests
bool expectedPrime) bool expectedPrime)
{ {
bool isPrime = true; bool isPrime = true;
bool exhausted = false; tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
tester.Scan(
exponent,
(UInt128)exponent << 1,
LastDigitIsSeven(exponent),
(UInt128)perSetLimit,
(UInt128)setCount,
(UInt128)overallLimit,
ref isPrime,
ref exhausted);
isPrime.Should().Be(expectedPrime); isPrime.Should().Be(expectedPrime);
exhausted.Should().BeTrue();
} }
} }
@@ -5,21 +5,8 @@ public class MersenneNumberResidueCpuTester
private ModResidueTracker? _mersenneResidueTracker; private ModResidueTracker? _mersenneResidueTracker;
// CPU residue variant using tracker + unrolled residue updates (no method calls in the loop). // CPU residue variant using tracker + unrolled residue updates (no method calls in the loop).
public void Scan( public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
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;
}
// Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors // Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors
_mersenneResidueTracker ??= new(ResidueModel.Mersenne); _mersenneResidueTracker ??= new(ResidueModel.Mersenne);
@@ -27,6 +14,12 @@ public class MersenneNumberResidueCpuTester
// and avoid per-q powmods when possible when scanning lanes. // and avoid per-q powmods when possible when scanning lanes.
_mersenneResidueTracker.BeginMerge(exponent); _mersenneResidueTracker.BeginMerge(exponent);
// Start at k = 1: q0 = 2*p*1 + 1
UInt128 k = UInt128.One;
UInt128 q = twoP + UInt128.One;
// Current residues
q.Mod10_8_5_3(out ulong r10, out ulong r8, out ulong r5, out ulong r3);
// Step residues for q += 2*p
ulong step10 = (ulong)(twoP % 10UL) % 10UL; ulong step10 = (ulong)(twoP % 10UL) % 10UL;
ulong step8 = (ulong)(twoP % 8UL); ulong step8 = (ulong)(twoP % 8UL);
ulong step3 = (ulong)(twoP % 3UL); ulong step3 = (ulong)(twoP % 3UL);
@@ -50,39 +43,18 @@ public class MersenneNumberResidueCpuTester
bool localIsPrime, isDivider; bool localIsPrime, isDivider;
maxK++;
ModResidueTracker tracker = _mersenneResidueTracker!; ModResidueTracker tracker = _mersenneResidueTracker!;
UInt128 limitInclusive = overallLimit + UInt128.One; while (k < maxK && (localIsPrime = Volatile.Read(ref isPrime)))
for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{ {
UInt128 setOffset = perSetLimit * setIndex; remaining = maxK - k;
UInt128 k = setOffset + UInt128.One;
if (k >= limitInclusive)
{
break;
}
UInt128 setLimitExclusive = setOffset + perSetLimit + UInt128.One;
if (setLimitExclusive > limitInclusive)
{
setLimitExclusive = limitInclusive;
}
UInt128 q = twoP * k + UInt128.One;
q.Mod10_8_5_3(out ulong r10, out ulong r8, out ulong r5, out ulong r3);
while (k < setLimitExclusive && (localIsPrime = Volatile.Read(ref isPrime)))
{
remaining = setLimitExclusive - k;
if (remaining == UInt128.Zero) if (remaining == UInt128.Zero)
{ {
break; break;
} }
lanes = remaining > 4UL ? 4 : (int)remaining; lanes = remaining > 4UL ? 4 : (int)remaining;
if (lanes <= 0)
{
break;
}
// Compute lane residues incrementally (avoid multiplications, minimize reductions) // Compute lane residues incrementally (avoid multiplications, minimize reductions)
// r10 // r10
@@ -113,6 +85,7 @@ public class MersenneNumberResidueCpuTester
if (lanes >= 1 && if (lanes >= 1 &&
(((allowMask >> (int)r10_0) & 1) != 0 && (r8_0 == 1UL || r8_0 == 7UL) && r3_0 != 0UL && r5_0 != 0UL)) (((allowMask >> (int)r10_0) & 1) != 0 && (r8_0 == 1UL || r8_0 == 7UL) && r3_0 != 0UL && r5_0 != 0UL))
{ {
// cycle-based quick check for small q
if (localIsPrime && tracker.MergeOrAppend(exponent, q, out isDivider) && isDivider && if (localIsPrime && tracker.MergeOrAppend(exponent, q, out isDivider) && isDivider &&
(q <= ulong.MaxValue ? ((ulong)q).IsPrimeCandidate() : q.IsPrimeCandidate())) (q <= ulong.MaxValue ? ((ulong)q).IsPrimeCandidate() : q.IsPrimeCandidate()))
{ {
@@ -162,12 +135,10 @@ public class MersenneNumberResidueCpuTester
if (!localIsPrime) if (!localIsPrime)
{ {
Volatile.Write(ref isPrime, false); Volatile.Write(ref isPrime, false);
divisorsExhausted = true;
return; return;
} }
else if (!Volatile.Read(ref isPrime)) else if (!Volatile.Read(ref isPrime))
{ {
divisorsExhausted = true;
return; return;
} }
@@ -191,9 +162,8 @@ public class MersenneNumberResidueCpuTester
r3 += step3; if (r3 >= 3UL) r3 -= 3UL; r3 += step3; if (r3 >= 3UL) r3 -= 3UL;
r5 += step5; if (r5 >= 10UL) r5 -= 10UL; if (r5 >= 5UL) r5 -= 5UL; r5 += step5; if (r5 >= 10UL) r5 -= 10UL; if (r5 >= 5UL) r5 -= 5UL;
} }
}
divisorsExhausted = true; return;
}
}
} }
} }
@@ -12,29 +12,15 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// via GpuKernelPool to avoid host round-trips. // via GpuKernelPool to avoid host round-trips.
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
public void Scan( public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
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 gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream;
// Ensure device has small cycles and primes tables for in-kernel lookup // Ensure device has small cycles and primes tables for in-kernel lookup
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator);
int batchSize = GpuConstants.ScanBatchSize; int batchSize = GpuConstants.ScanBatchSize;
UInt128 kStart = 1UL;
byte last = lastIsSeven ? (byte)1 : (byte)0; byte last = lastIsSeven ? (byte)1 : (byte)0;
var kernel = gpuLease.Pow2ModKernel; var kernel = gpuLease.Pow2ModKernel;
ulong step10 = ((exponent % 10UL) << 1) % 10UL; ulong step10 = ((exponent % 10UL) << 1) % 10UL;
@@ -52,59 +38,41 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// testers such as the order kernels. // testers such as the order kernels.
orderBuffer.MemSetToZero(); orderBuffer.MemSetToZero();
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize); ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
UInt128 batchSize128 = (UInt128)batchSize; UInt128 remaining;
UInt128 limitInclusive = overallLimit == UInt128.MaxValue int currentSize;
? overallLimit int i;
: checked(overallLimit + UInt128.One); UInt128 batchSize128 = (UInt128)batchSize, q;
Span<ulong> orders = orderArray.AsSpan(0, batchSize); Span<ulong> orders = orderArray.AsSpan(0, batchSize);
try try
{ {
for (UInt128 setIndex = UInt128.Zero; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++) while (kStart < maxK && Volatile.Read(ref isPrime))
{ {
UInt128 setOffset = checked(perSetLimit * setIndex); remaining = maxK - kStart;
UInt128 setStart = checked(setOffset + UInt128.One); if (remaining > batchSize128)
if (setStart >= limitInclusive)
{ {
break; currentSize = batchSize;
} }
else
UInt128 setLimitExclusive = checked(setOffset + perSetLimit);
setLimitExclusive = setLimitExclusive >= limitInclusive
? limitInclusive
: checked(setLimitExclusive + UInt128.One);
if (setLimitExclusive > limitInclusive)
{ {
setLimitExclusive = limitInclusive; currentSize = (int)remaining;
}
UInt128 kStart = setStart;
while (kStart < setLimitExclusive && Volatile.Read(ref isPrime))
{
UInt128 remaining = checked(setLimitExclusive - kStart);
int currentSize = remaining > batchSize128 ? batchSize : (int)remaining;
if (currentSize <= 0)
{
break;
}
orders = orderArray.AsSpan(0, currentSize); orders = orderArray.AsSpan(0, currentSize);
}
UInt128 q = checked(twoP * kStart); q = twoP * kStart + UInt128.One;
q = checked(q + UInt128.One);
q.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); q.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var ra = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); var ra = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, 0UL, kernel(currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, 0UL,
ra, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, primeViews.LastOnePow2, primeViews.LastSevenPow2); ra, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, primeViews.LastOnePow2, primeViews.LastSevenPow2);
stream.Synchronize(); accelerator.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize); orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
stream.Synchronize();
if (!Volatile.Read(ref isPrime)) if (!Volatile.Read(ref isPrime))
{ {
break; break;
} }
for (int i = 0; i < currentSize; i++) for (i = 0; i < currentSize; i++)
{ {
if (orders[i] != 0UL) if (orders[i] != 0UL)
{ {
@@ -113,18 +81,14 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
} }
} }
kStart = checked(kStart + (UInt128)currentSize); kStart += batchSize128;
}
} }
} }
finally finally
{ {
ArrayPool<ulong>.Shared.Return(orderArray); ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose(); orderBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
} }
divisorsExhausted = true;
} }
} }
+8 -96
View File
@@ -25,13 +25,11 @@ public sealed class MersenneNumberTester(
bool useGpuScan = true, bool useGpuScan = true,
bool useGpuOrder = true, bool useGpuOrder = true,
bool useResidue = true, bool useResidue = true,
UInt128? maxK = null, ulong maxK = 5_000_000UL)
UInt128? residueDivisorSets = null)
{ {
private readonly bool _useResidue = useResidue; private readonly bool _useResidue = useResidue;
private readonly bool _useIncremental = useIncremental && !useResidue; private readonly bool _useIncremental = useIncremental && !useResidue;
private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL; private readonly ulong _maxK = maxK;
private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useModuloWorkaround = useModuloWorkaround; private readonly bool _useModuloWorkaround = useModuloWorkaround;
private readonly bool _useGpuLucas = useGpuLucas; private readonly bool _useGpuLucas = useGpuLucas;
@@ -50,23 +48,6 @@ public sealed class MersenneNumberTester(
// TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
// Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers. // Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static UInt128 DivideRoundUp(UInt128 value, UInt128 divisor)
{
if (divisor == UInt128.Zero)
{
return UInt128.Zero;
}
UInt128 result = value / divisor;
if (result * divisor == value)
{
return result;
}
return result + UInt128.One;
}
public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL) public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL)
{ {
bool cacheEnabled = useOrderCache; bool cacheEnabled = useOrderCache;
@@ -218,30 +199,23 @@ public sealed class MersenneNumberTester(
} }
} }
public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _); public bool IsMersennePrime(ulong exponent)
public bool IsMersennePrime(ulong exponent, out bool divisorsExhausted)
{ {
divisorsExhausted = false;
// Safe early rejections using known orders for tiny primes: // Safe early rejections using known orders for tiny primes:
// 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime. // 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime.
if ((exponent % 3UL) == 0UL && exponent != 3UL) if ((exponent % 3UL) == 0UL && exponent != 3UL)
{ {
divisorsExhausted = true;
return false; return false;
} }
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne()) if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
{ {
divisorsExhausted = true;
return false; return false;
} }
// Skip even exponents: for even p > 2, M_p is composite; here p is large. // Skip even exponents: for even p > 2, M_p is composite; here p is large.
if ((exponent & 1UL) == 0UL) if ((exponent & 1UL) == 0UL)
{ {
divisorsExhausted = true;
return false; return false;
} }
@@ -254,79 +228,21 @@ public sealed class MersenneNumberTester(
// In residue mode the residue scan is the final primality check. // In residue mode the residue scan is the final primality check.
if (_useResidue) if (_useResidue)
{ {
if (_maxK < maxK) if ((UInt128)_maxK < maxK)
{ {
maxK = _maxK; maxK = _maxK;
} }
if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero)
{
return prePrime;
}
UInt128 totalLimit = _maxK;
if (totalLimit > maxK)
{
totalLimit = maxK;
}
if (totalLimit == UInt128.Zero)
{
return true;
}
UInt128 requestedSets = _residueDivisorSetCount;
if (_useGpuScan) if (_useGpuScan)
{ {
UInt128 maxUsefulSets = DivideRoundUp(totalLimit, (UInt128)GpuConstants.ScanBatchSize); _residueGpuTester!.Scan(exponent, twoP, lastIsSeven, maxK, ref prePrime);
if (maxUsefulSets == UInt128.Zero)
{
maxUsefulSets = UInt128.One;
}
if (requestedSets == UInt128.Zero || requestedSets > maxUsefulSets)
{
requestedSets = maxUsefulSets;
}
}
if (requestedSets == UInt128.Zero)
{
requestedSets = UInt128.One;
}
UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets);
if (perSetLimit == UInt128.Zero)
{
perSetLimit = UInt128.One;
}
UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit);
UInt128 effectiveSets = requestedSets;
if (setsNeeded < effectiveSets)
{
effectiveSets = setsNeeded;
}
bool scanCompleted = false;
if (_useGpuScan)
{
_residueGpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
} }
else else
{ {
_residueCpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted); _residueCpuTester!.Scan(exponent, twoP, lastIsSeven, maxK, ref prePrime);
} }
if (!prePrime) return prePrime;
{
divisorsExhausted = true;
return false;
}
divisorsExhausted = scanCompleted;
return true;
} }
if (!_useIncremental) if (!_useIncremental)
@@ -347,15 +263,12 @@ public sealed class MersenneNumberTester(
if (!prePrime) if (!prePrime)
{ {
divisorsExhausted = true;
return false; return false;
} }
bool lucasPrime = _useGpuLucas return _useGpuLucas
? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true) ? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true)
: _lucasLehmerCpuTester.IsPrime(exponent); : _lucasLehmerCpuTester.IsPrime(exponent);
divisorsExhausted = true;
return lucasPrime;
} }
bool isPrime = true; bool isPrime = true;
@@ -383,7 +296,6 @@ public sealed class MersenneNumberTester(
} }
} }
divisorsExhausted = true;
return isPrime; return isPrime;
} }