Add divisor-mode detailed check coverage for factors

This commit is contained in:
Marek Lemańczyk committed 2025-09-17 19:02:21 +02:00
1 parent b1c29ad16e
commit 1b28584168
7 files changed
+286 -45

No files matched your search

+91 -2
View File
@@ -142,6 +142,95 @@ 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()
{ {
@@ -223,7 +312,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), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL, residueDivisorSets: 1UL), 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);
@@ -232,7 +321,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().BeFalse(); detailed.Should().BeTrue();
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();
+6 -4
View File
@@ -447,7 +447,8 @@ internal static class Program
useGpuScan: mersenneOnGpu, useGpuScan: mersenneOnGpu,
useGpuOrder: orderOnGpu, useGpuOrder: orderOnGpu,
useResidue: useResidue, useResidue: useResidue,
maxK: residueKMax); maxK: residueKMax,
residueDivisorSets: divisorCyclesSearchLimit);
if (!useLucas) if (!useLucas)
{ {
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL); tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
@@ -731,7 +732,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"); Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor or residue");
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");
@@ -1083,8 +1084,9 @@ internal static class Program
return _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out detailedCheck); return _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out detailedCheck);
} }
detailedCheck = MersenneTesters.Value!.IsMersennePrime(p); bool mersennePrime = MersenneTesters.Value!.IsMersennePrime(p, out bool residueExhausted);
return detailedCheck; detailedCheck = residueExhausted;
return mersennePrime;
} }
// 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.
@@ -24,8 +24,18 @@ public class MersenneNumberResidueCpuTesterTests
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{ {
bool isPrime = true; bool isPrime = true;
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); bool exhausted = false;
tester.Scan(
exponent,
(UInt128)exponent << 1,
LastDigitIsSeven(exponent),
(UInt128)maxK,
1UL,
(UInt128)maxK,
ref isPrime,
ref exhausted);
isPrime.Should().Be(expectedPrime); isPrime.Should().Be(expectedPrime);
exhausted.Should().BeTrue();
} }
} }
@@ -27,8 +27,18 @@ public class MersenneNumberResidueGpuTesterTests
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{ {
bool isPrime = true; bool isPrime = true;
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); bool exhausted = false;
tester.Scan(
exponent,
(UInt128)exponent << 1,
LastDigitIsSeven(exponent),
(UInt128)maxK,
1UL,
(UInt128)maxK,
ref isPrime,
ref exhausted);
isPrime.Should().Be(expectedPrime); isPrime.Should().Be(expectedPrime);
exhausted.Should().BeTrue();
} }
} }
@@ -5,8 +5,21 @@ 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(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) 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;
}
// 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);
@@ -14,12 +27,6 @@ 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);
@@ -43,18 +50,39 @@ public class MersenneNumberResidueCpuTester
bool localIsPrime, isDivider; bool localIsPrime, isDivider;
maxK++;
ModResidueTracker tracker = _mersenneResidueTracker!; ModResidueTracker tracker = _mersenneResidueTracker!;
while (k < maxK && (localIsPrime = Volatile.Read(ref isPrime))) UInt128 limitInclusive = overallLimit + UInt128.One;
for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{ {
remaining = maxK - k; UInt128 setOffset = perSetLimit * (UInt128)setIndex;
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
@@ -85,7 +113,6 @@ 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()))
{ {
@@ -135,10 +162,12 @@ 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;
} }
@@ -162,8 +191,9 @@ 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;
} }
}
return; divisorsExhausted = true;
}
}
} }
} }
@@ -12,15 +12,27 @@ 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(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) 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;
}
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
// 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;
@@ -31,27 +43,38 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize); ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
UInt128 remaining; UInt128 batchSize128 = (UInt128)batchSize;
int currentSize; UInt128 limitInclusive = overallLimit + UInt128.One;
int i;
UInt128 batchSize128 = (UInt128)batchSize, q;
Span<ulong> orders = orderArray.AsSpan(0, batchSize); Span<ulong> orders = orderArray.AsSpan(0, batchSize);
try try
{ {
while (kStart < maxK && Volatile.Read(ref isPrime)) for (ulong setIndex = 0; setIndex < setCount && Volatile.Read(ref isPrime); setIndex++)
{ {
remaining = maxK - kStart; UInt128 setOffset = perSetLimit * (UInt128)setIndex;
if (remaining > batchSize128) UInt128 setStart = setOffset + UInt128.One;
if (setStart >= limitInclusive)
{ {
currentSize = batchSize; break;
}
else
{
currentSize = (int)remaining;
orders = orderArray.AsSpan(0, currentSize);
} }
q = twoP * kStart + UInt128.One; UInt128 setLimitExclusive = setOffset + perSetLimit + UInt128.One;
if (setLimitExclusive > limitInclusive)
{
setLimitExclusive = limitInclusive;
}
UInt128 kStart = setStart;
while (kStart < setLimitExclusive && Volatile.Read(ref isPrime))
{
UInt128 remaining = setLimitExclusive - kStart;
int currentSize = remaining > batchSize128 ? batchSize : (int)remaining;
if (currentSize <= 0)
{
break;
}
orders = orderArray.AsSpan(0, currentSize);
UInt128 q = twoP * kStart + 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);
@@ -65,7 +88,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
break; break;
} }
for (i = 0; i < currentSize; i++) for (int i = 0; i < currentSize; i++)
{ {
if (orders[i] != 0UL) if (orders[i] != 0UL)
{ {
@@ -77,11 +100,14 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
kStart += batchSize128; kStart += batchSize128;
} }
} }
}
finally finally
{ {
ArrayPool<ulong>.Shared.Return(orderArray); ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose(); orderBuffer.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
} }
divisorsExhausted = true;
} }
} }
+80 -6
View File
@@ -25,11 +25,13 @@ public sealed class MersenneNumberTester(
bool useGpuScan = true, bool useGpuScan = true,
bool useGpuOrder = true, bool useGpuOrder = true,
bool useResidue = true, bool useResidue = true,
ulong maxK = 5_000_000UL) ulong maxK = 5_000_000UL,
ulong residueDivisorSets = PerfectNumberConstants.ExtraDivisorCycleSearchLimit)
{ {
private readonly bool _useResidue = useResidue; private readonly bool _useResidue = useResidue;
private readonly bool _useIncremental = useIncremental && !useResidue; private readonly bool _useIncremental = useIncremental && !useResidue;
private readonly ulong _maxK = maxK; private readonly ulong _maxK = maxK;
private readonly ulong _residueDivisorSetCount = residueDivisorSets;
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;
@@ -48,6 +50,23 @@ 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;
@@ -191,23 +210,30 @@ public sealed class MersenneNumberTester(
ArrayPool<UInt128>.Shared.Return(qs); ArrayPool<UInt128>.Shared.Return(qs);
} }
public bool IsMersennePrime(ulong exponent) public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _);
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;
} }
@@ -225,16 +251,60 @@ public sealed class MersenneNumberTester(
maxK = _maxK; maxK = _maxK;
} }
if (_maxK == 0UL || _residueDivisorSetCount == 0UL)
{
return prePrime;
}
UInt128 totalLimit = (UInt128)_maxK;
if (totalLimit > maxK)
{
totalLimit = maxK;
}
if (totalLimit == UInt128.Zero)
{
return true;
}
ulong requestedSets = _residueDivisorSetCount;
if (requestedSets == 0UL)
{
requestedSets = 1UL;
}
UInt128 perSetLimit = DivideRoundUp(totalLimit, (UInt128)requestedSets);
if (perSetLimit == UInt128.Zero)
{
perSetLimit = UInt128.One;
}
UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit);
ulong effectiveSets = requestedSets;
if (setsNeeded < (UInt128)effectiveSets)
{
effectiveSets = (ulong)setsNeeded;
}
bool scanCompleted = false;
if (_useGpuScan) if (_useGpuScan)
{ {
_residueGpuTester!.Scan(exponent, twoP, lastIsSeven, maxK, ref prePrime); _residueGpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
} }
else else
{ {
_residueCpuTester!.Scan(exponent, twoP, lastIsSeven, maxK, ref prePrime); _residueCpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted);
} }
return prePrime; if (!prePrime)
{
divisorsExhausted = true;
return false;
}
divisorsExhausted = scanCompleted;
return true;
} }
if (!_useIncremental) if (!_useIncremental)
@@ -255,12 +325,15 @@ public sealed class MersenneNumberTester(
if (!prePrime) if (!prePrime)
{ {
divisorsExhausted = true;
return false; return false;
} }
return _useGpuLucas bool lucasPrime = _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;
@@ -288,6 +361,7 @@ public sealed class MersenneNumberTester(
} }
} }
divisorsExhausted = true;
return isPrime; return isPrime;
} }