diff --git a/PerfectNumbers.Core/Gpu/Kernels/DivisorKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/DivisorKernels.cs index b63cc7c..9c5d80e 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/DivisorKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/DivisorKernels.cs @@ -21,7 +21,7 @@ internal static class DivisorKernels ReadOnlyGpuUInt128 readOnlyMod = divisor; if (x == 0) { - baseVal = GpuUInt128.Pow2Mod(exponent, in readOnlyMod); // Uses the eight-bit windowed ladder measured fastest in GPU benchmarks. + baseVal = GpuUInt128.Pow2Mod(exponent, in readOnlyMod); result[0] = baseVal.High == 0UL && baseVal.Low == 1UL ? (byte)1 : (byte)0; return; } @@ -38,7 +38,7 @@ internal static class DivisorKernels GpuUInt128 pow = baseVal; pow.ModPow(exponent / ux, in readOnlyMod); - var part2 = GpuUInt128.Pow2Mod(exponent % ux, in readOnlyMod); // Windowed pow2 keeps the mixed-radix tail fast. + var part2 = GpuUInt128.Pow2Mod(exponent % ux, in readOnlyMod); GpuUInt128 product = pow; ReadOnlyGpuUInt128 part2ReadOnly = part2.AsReadOnly(); product.MulMod(in part2ReadOnly, in readOnlyMod); diff --git a/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs index fcd05b9..2d8ff70 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs @@ -96,7 +96,7 @@ internal static class IncrementalKernels } ulong phi64 = phi.Low; - if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One) // Windowed pow2 keeps the totient check fast. + if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One) { orders[index] = 0UL; return; @@ -192,7 +192,7 @@ internal static class IncrementalKernels } ulong phi64 = phi.Low; - if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One) // Windowed pow2 keeps the order probe aligned with benchmarks. + if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One) { return; } diff --git a/PerfectNumbers.Core/Gpu/Kernels/OrderKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/OrderKernels.cs index 6548505..0762154 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/OrderKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/OrderKernels.cs @@ -22,7 +22,7 @@ internal static class OrderKernels } ulong phi64 = phi.Low; - GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, in readOnlyQ); // Windowed pow2 keeps the cycle check aligned with benchmark wins. + GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, in readOnlyQ); if (pow != GpuUInt128.One) { orders[index] = 0UL; diff --git a/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs index 9a8a9c2..a683c73 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs @@ -77,7 +77,6 @@ internal static class Pow2ModKernels return; } } - // Pow2Minus1Mod already routes through the windowed pow2 helper benchmarked as fastest for large divisors. if (GpuUInt128.Pow2Minus1Mod(exponent, in readOnlyQ) != GpuUInt128.Zero) { orders[index] = 0UL; @@ -169,7 +168,7 @@ internal static class Pow2ModKernels return; } } - if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One) // Windowed pow2 keeps the order guard aligned with GPU benchmarks. + if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One) { return; } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs index 6da61b2..9734c2a 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs @@ -112,7 +112,7 @@ public class MersenneNumberLucasLehmerGpuTester var gpu = GpuContextPool.RentPreferred(preferCpu: false); var accelerator = gpu.Accelerator; - var kernel = GetBatchKernel(accelerator); // Batch kernel already relies on the Mersenne squaring reducer; windowed pow2 helpers do not apply here. + var kernel = GetBatchKernel(accelerator); var expBuffer = accelerator.Allocate1D(count); ulong[] expArray = ArrayPool.Shared.Rent(count); @@ -479,7 +479,6 @@ public class MersenneNumberLucasLehmerGpuTester { if ((exponent & 1UL) != 0UL) { - // The base varies per primitive-root candidate, so pow2-specific ladders do not apply. result = MulMod(result, value, modulus); } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index 50f4e1a..4a237f3 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -22,9 +22,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO { GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel, _ => gpuLease.IncrementalOrderKernel, - }; // Pow2ModOrderKernel already routes through the windowed pow2 helper measured fastest for large divisors. - // Both GPU order kernels already rely on the windowed pow2 helper, so this wrapper only selects the delegate - // and manages the scoped GPU resources. + }; var foundBuffer = accelerator.Allocate1D(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run. exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 9d75530..c07f9ff 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -985,8 +985,6 @@ public class MersenneDivisorCycles private static ulong CalculateCycleLengthFallback(ulong divisor) { // Otherwise, find order of 2 mod divisor using the shared unrolled-hex stepping sequence. - // Divisors <= 3 hit this fallback, so the lightweight CPU path already matches the windowed pow2 helper - // without paying for a GPU round-trip. return divisor.CalculateMersenneDivisorCycleLengthUnrolledHexCpu(); } diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index ce2cd5e..0a5f763 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -319,8 +319,6 @@ public sealed class MersenneNumberTester( private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView qs, ArrayView orders) { UInt128 q = qs[index]; - // The legacy kernel already relies on the adaptive PowMod helper, which switches to the windowed pow2 - // ladder for large divisors. UInt128 pow = exponent.PowMod(q); orders[index] = pow == 1UL ? exponent : 0UL; } diff --git a/PerfectNumbers.Core/MersenneResidueStepper.cs b/PerfectNumbers.Core/MersenneResidueStepper.cs index 0e5f209..b66c547 100644 --- a/PerfectNumbers.Core/MersenneResidueStepper.cs +++ b/PerfectNumbers.Core/MersenneResidueStepper.cs @@ -17,10 +17,15 @@ public class MersenneResidueStepper m = modulus; p = p0; - // Callers hand this stepper pre-reduced residues from the windowed pow2 pipeline; keep the modulo guard for tests - // while production inputs already arrive inside [0, m). - M_mod = M_mod_m_at_p0 % m; - pow2_mod = (M_mod + 1) % m; + // Inputs are already reduced in production; keep the old modulo guard commented for debugging reference. + // M_mod = M_mod_m_at_p0 % m; + // pow2_mod = (M_mod + 1) % m; + M_mod = M_mod_m_at_p0; + pow2_mod = M_mod + 1; + if (pow2_mod >= m) + { + pow2_mod -= m; + } } // TODO: Inline this accessor into callers so the hot residue path reads the backing fields directly