Initial commit.
This commit is contained in:
1 parent
e5b0fea18c
commit
9f35f6c558
94 files changed
+12857
No files matched your search
@@ -0,0 +1,9 @@
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public static class CpuConstants
|
||||
{
|
||||
// Allowed q mod 10 sets depending on last digit of M_p
|
||||
public const int LastSevenMask10 = (1 << 7) | (1 << 9);
|
||||
// For M_p ending with 1, restrict to {1,3,9}; 7 considered only for q == 7 (not possible for large p)
|
||||
public const int LastOneMask10 = (1 << 1) | (1 << 3) | (1 << 9);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
|
||||
{
|
||||
private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared;
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
UInt128 k, one = k = UInt128.One;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
// Use residue automaton always on CPU
|
||||
var auto = new MersenneResidueAutomaton(exponent);
|
||||
|
||||
bool reject, shouldCheck;
|
||||
UInt128 q = auto.CurrentQ(),
|
||||
divPow, halfPow, phi, qCycle;
|
||||
|
||||
ulong div, mod8, phi64;
|
||||
|
||||
while (k <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
qCycle = MersenneDivisorCycles.GetCycle(q);
|
||||
shouldCheck = lastIsSeven
|
||||
? (auto.Mod10R != 5UL)
|
||||
: (auto.Mod10R != 5UL);
|
||||
if (shouldCheck)
|
||||
{
|
||||
mod8 = auto.Mod8R;
|
||||
if (mod8 != 1UL && mod8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else if (auto.Mod3R == 0UL || auto.Mod5R == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCheck)
|
||||
{
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
phi = q - one;
|
||||
reject = false;
|
||||
if (phi <= ulong.MaxValue)
|
||||
{
|
||||
phi64 = (ulong)phi;
|
||||
if (phi64.PowModWithCycle(q, qCycle) == one)
|
||||
{
|
||||
halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one;
|
||||
if (halfPow.BinaryGcd(q) == one)
|
||||
{
|
||||
div = phi64.FastDiv64(exponent, divMul);
|
||||
divPow = div.PowModWithCycle(q, qCycle) - one;
|
||||
if (divPow.BinaryGcd(q) == one)
|
||||
{
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reject && q.IsPrimeCandidate())
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
k += 1UL;
|
||||
auto.Next();
|
||||
q = auto.CurrentQ();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using PeterO.Numbers;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
public class MersenneNumberLucasLehmerCpuTester
|
||||
{
|
||||
public bool IsPrime(ulong exponent)
|
||||
{
|
||||
// Early rejections aligned with incremental/order sieves, but safe for small p:
|
||||
// - If 3 | p and p != 3, then 7 | M_p -> composite.
|
||||
// - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast.
|
||||
// - If p is divisible by divisors specific to numbers ending with 1 or 7, reject fast.
|
||||
if (
|
||||
((exponent % 3UL) == 0UL && exponent != 3UL) ||
|
||||
((exponent % 5UL) == 0UL && exponent != 5UL) ||
|
||||
((exponent % 7UL) == 0UL && exponent != 7UL) ||
|
||||
((exponent % 11UL) == 0UL && exponent != 11UL)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two,
|
||||
m = (UInt128.One << (int)exponent) - UInt128.One;
|
||||
|
||||
UInt128 limit = exponent - UInt128Numbers.Two;
|
||||
for (UInt128 i = UInt128.Zero; i < limit; i++)
|
||||
{
|
||||
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(m)) - two;
|
||||
if (s < UInt128.Zero)
|
||||
{
|
||||
s += m;
|
||||
}
|
||||
}
|
||||
|
||||
return s == UInt128.Zero;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
|
||||
{
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
UInt128 k = 1UL;
|
||||
var auto = new MersenneResidueAutomaton(exponent);
|
||||
UInt128 qCycle,
|
||||
q = auto.CurrentQ();
|
||||
|
||||
while (k <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
qCycle = MersenneDivisorCycles.GetCycle(q);
|
||||
int allowMask2 = lastIsSeven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10;
|
||||
bool shouldCheck = ((allowMask2 >> (int)auto.Mod10R) & 1) != 0;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong mod8 = auto.Mod8R;
|
||||
if (mod8 != 1UL && mod8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else if (auto.Mod3R == 0UL || auto.Mod5R == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
if (shouldCheck)
|
||||
{
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
if (exponent.PowModWithCycle(q, qCycle) == 1UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt128 phi = q - 1UL;
|
||||
if (phi <= ulong.MaxValue)
|
||||
{
|
||||
ulong phi64 = (ulong)phi;
|
||||
if (phi64.PowModWithCycle(q, qCycle) == 1UL)
|
||||
{
|
||||
UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL;
|
||||
if (halfPow.BinaryGcd(q) == 1UL)
|
||||
{
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
||||
ulong div = phi64.FastDiv64(exponent, divMul);
|
||||
UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL;
|
||||
if (divPow.BinaryGcd(q) == 1UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k += 1UL;
|
||||
auto.Next();
|
||||
q = auto.CurrentQ();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public class MersenneNumberResidueCpuTester
|
||||
{
|
||||
private ModResidueTracker? _mersenneResidueTracker;
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors
|
||||
|
||||
_mersenneResidueTracker ??= new(ResidueModel.Mersenne);
|
||||
// For small divisors q <= 4,000,000 use cached cycle length to fast-path checks
|
||||
// and avoid per-q powmods when possible when scanning lanes.
|
||||
_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 step8 = (ulong)(twoP % 8UL);
|
||||
ulong step3 = (ulong)(twoP % 3UL);
|
||||
ulong step5 = (ulong)(twoP % 5UL);
|
||||
|
||||
// Allowed last-digit sets for q depending on last digit of M_p
|
||||
// lastIsSeven == true => allow {7,9}
|
||||
// lastIsSeven == false => allow {1,3,7,9}
|
||||
int allowMask = lastIsSeven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10;
|
||||
|
||||
// Predeclare temps to avoid redeclaration overhead in the loop
|
||||
UInt128 remaining;
|
||||
int i, lanes;
|
||||
// per-lane residues
|
||||
ulong r10_0, r10_1, r10_2, r10_3, r8_0, r8_1, r8_2, r8_3, r3_0, r3_1, r3_2, r3_3, r5_0, r5_1, r5_2, r5_3;
|
||||
// q lanes
|
||||
UInt128 twoP2 = twoP + twoP,
|
||||
twoP3 = twoP2 + twoP,
|
||||
twoP4 = twoP3 + twoP,
|
||||
qIncremental;
|
||||
|
||||
bool localIsPrime, isDivider;
|
||||
|
||||
maxK++;
|
||||
|
||||
ModResidueTracker tracker = _mersenneResidueTracker!;
|
||||
while (k < maxK && (localIsPrime = Volatile.Read(ref isPrime)))
|
||||
{
|
||||
remaining = maxK - k;
|
||||
if (remaining == UInt128.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lanes = remaining > 4UL ? 4 : (int)remaining;
|
||||
|
||||
// Compute lane residues incrementally (avoid multiplications, minimize reductions)
|
||||
// r10
|
||||
r10_0 = r10;
|
||||
r10_1 = r10_0 + step10; if (r10_1 >= 20UL) r10_1 -= 20UL; if (r10_1 >= 10UL) r10_1 -= 10UL;
|
||||
r10_2 = r10_1 + step10; if (r10_2 >= 20UL) r10_2 -= 20UL; if (r10_2 >= 10UL) r10_2 -= 10UL;
|
||||
r10_3 = r10_2 + step10; if (r10_3 >= 20UL) r10_3 -= 20UL; if (r10_3 >= 10UL) r10_3 -= 10UL;
|
||||
|
||||
// r8
|
||||
r8_0 = r8 & 7UL;
|
||||
r8_1 = (r8_0 + step8) & 7UL;
|
||||
r8_2 = (r8_1 + step8) & 7UL;
|
||||
r8_3 = (r8_2 + step8) & 7UL;
|
||||
|
||||
// r3 (pattern 0,1,2,0)
|
||||
r3_0 = r3; if (r3_0 >= 3UL) r3_0 -= 3UL;
|
||||
r3_1 = r3_0 + step3; if (r3_1 >= 3UL) r3_1 -= 3UL;
|
||||
r3_2 = r3_1 + step3; if (r3_2 >= 3UL) r3_2 -= 3UL;
|
||||
r3_3 = r3; if (r3_3 >= 3UL) r3_3 -= 3UL;
|
||||
|
||||
// r5 (pattern 0,1,2,3)
|
||||
r5_0 = r5; if (r5_0 >= 5UL) r5_0 -= 5UL;
|
||||
r5_1 = r5_0 + step5; if (r5_1 >= 10UL) r5_1 -= 10UL; if (r5_1 >= 5UL) r5_1 -= 5UL;
|
||||
r5_2 = r5_1 + step5; if (r5_2 >= 10UL) r5_2 -= 10UL; if (r5_2 >= 5UL) r5_2 -= 5UL;
|
||||
r5_3 = r5_2 + step5; if (r5_3 >= 10UL) r5_3 -= 10UL; if (r5_3 >= 5UL) r5_3 -= 5UL;
|
||||
|
||||
// lane 0
|
||||
if (lanes >= 1 &&
|
||||
(((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 &&
|
||||
(q <= ulong.MaxValue ? ((ulong)q).IsPrimeCandidate() : q.IsPrimeCandidate()))
|
||||
{
|
||||
localIsPrime = false;
|
||||
}
|
||||
}
|
||||
|
||||
// lane 1
|
||||
qIncremental = q + twoP;
|
||||
if (lanes >= 2 &&
|
||||
((allowMask >> (int)r10_1) & 1) != 0 && (r8_1 == 1UL || r8_1 == 7UL) && r3_1 != 0UL && r5_1 != 0UL &&
|
||||
localIsPrime)
|
||||
{
|
||||
if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider &&
|
||||
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
|
||||
{
|
||||
localIsPrime = false;
|
||||
}
|
||||
}
|
||||
|
||||
// lane 2
|
||||
qIncremental += twoP;
|
||||
if (lanes >= 3 &&
|
||||
((allowMask >> (int)r10_2) & 1) != 0 && (r8_2 == 1UL || r8_2 == 7UL) && r3_2 != 0UL && r5_2 != 0UL &&
|
||||
localIsPrime)
|
||||
{
|
||||
if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider &&
|
||||
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
|
||||
{
|
||||
localIsPrime = false;
|
||||
}
|
||||
}
|
||||
|
||||
// lane 3
|
||||
qIncremental += twoP;
|
||||
if (lanes >= 4 &&
|
||||
((allowMask >> (int)r10_3) & 1) != 0 && (r8_3 == 1UL || r8_3 == 7UL) && r3_3 != 0UL && r5_3 != 0UL &&
|
||||
localIsPrime)
|
||||
{
|
||||
if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider &&
|
||||
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
|
||||
{
|
||||
localIsPrime = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!localIsPrime)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
return;
|
||||
}
|
||||
else if (!Volatile.Read(ref isPrime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// advance base by processed lanes
|
||||
k += (UInt128)lanes;
|
||||
q += lanes switch
|
||||
{
|
||||
0 => 0UL,
|
||||
1 => twoP,
|
||||
2 => twoP2,
|
||||
3 => twoP3,
|
||||
4 => twoP4,
|
||||
_ => throw new ArgumentException("Unsupproted value", nameof(twoP))
|
||||
};
|
||||
|
||||
// advance residues by 'lanes' steps (consistent with base advance)
|
||||
for (i = 0; i < lanes; i++)
|
||||
{
|
||||
r10 += step10; if (r10 >= 20UL) r10 -= 20UL; if (r10 >= 10UL) r10 -= 10UL;
|
||||
r8 = (r8 + step8) & 7UL;
|
||||
r3 += step3; if (r3 >= 3UL) r3 -= 3UL;
|
||||
r5 += step5; if (r5 >= 10UL) r5 -= 10UL; if (r5 >= 5UL) r5 -= 5UL;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user