From 37585244500061ff517ab2f3aa688038dedc4051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 26 Oct 2025 18:59:07 +0100 Subject: [PATCH] Extract divisor validator and rename legacy CPU prime helper --- .../PrimeTesterBenchmarks.cs | 2 +- PerfectNumbers.Core/DivisorGenerator.cs | 38 +++++++++++++++++++ ...MersenneNumberDivisorByDivisorGpuTester.cs | 34 +---------------- PerfectNumbers.Core/MersenneDivisorCycles.cs | 6 +-- .../MersennePrimeFactorTester.cs | 4 +- .../PrimeOrderCalculator.Cpu.cs | 26 ++++++------- .../PrimeOrderCalculator.Shared.cs | 2 +- PerfectNumbers.Core/PrimeTester.cs | 4 +- docs/HeuristicIsPrimePlan.md | 8 ++-- docs/HeuristicIsPrimeSpecification.md | 2 +- todo_list.txt | 2 +- 11 files changed, 67 insertions(+), 61 deletions(-) create mode 100644 PerfectNumbers.Core/DivisorGenerator.cs diff --git a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs index e44c7b1..04fb367 100644 --- a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs @@ -105,7 +105,7 @@ public class PrimeTesterBenchmarks for (int i = 0; i < values.Length; i++) { - if (PrimeTester.IsPrimeCp(values[i], cancellationToken)) + if (PrimeTester.IsPrimeCpu(values[i], cancellationToken)) { primeCount++; } diff --git a/PerfectNumbers.Core/DivisorGenerator.cs b/PerfectNumbers.Core/DivisorGenerator.cs new file mode 100644 index 0000000..0001267 --- /dev/null +++ b/PerfectNumbers.Core/DivisorGenerator.cs @@ -0,0 +1,38 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core; + +internal static class DivisorGenerator +{ + private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9); + private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsValidDivisor( + byte remainder10, + byte remainder8, + byte remainder3, + byte remainder5, + byte remainder7, + byte remainder11, + LastDigit lastDigit) + { + ushort decimalMask = lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise; + if (((decimalMask >> remainder10) & 1) == 0) + { + return false; + } + + if (remainder8 != 1 && remainder8 != 7) + { + return false; + } + + if (remainder3 == 0 || remainder5 == 0 || remainder7 == 0 || remainder11 == 0) + { + return false; + } + + return true; + } +} diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index e1b1fe5..8d83ee2 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -263,7 +263,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv { ulong candidate = (ulong)nextDivisor128; - if (CandidatePassesHeuristics(localRemainder10, localRemainder8, localRemainder3, localRemainder5, localRemainder7, localRemainder11, lastDigit)) + if (DivisorGenerator.IsValidDivisor(localRemainder10, localRemainder8, localRemainder3, localRemainder5, localRemainder7, localRemainder11, lastDigit)) { filteredStorage[filteredCount++] = candidate; } @@ -378,38 +378,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv return computedCycle; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool CandidatePassesHeuristics( - byte remainder10, - byte remainder8, - byte remainder3, - byte remainder5, - byte remainder7, - byte remainder11, - LastDigit lastDigit) - { - const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9); - const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9); - - ushort decimalMask = lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise; - if (((decimalMask >> remainder10) & 1) == 0) - { - return false; - } - - if (remainder8 != 1 && remainder8 != 7) - { - return false; - } - - if (remainder3 == 0 || remainder5 == 0 || remainder7 == 0 || remainder11 == 0) - { - return false; - } - - return true; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte AddMod(byte value, byte delta, byte modulus) { diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index ea03d66..516c512 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -394,9 +394,9 @@ public class MersenneDivisorCycles return true; } - // TODO: Benchmark PrimeTester.IsPrimeCp against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites. + // TODO: Benchmark PrimeTester.IsPrimeCpu against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites. // if (HeuristicPrimeTester.Exclusive.IsPrimeCpu(remaining, CancellationToken.None)) - if (PrimeTester.IsPrimeCp(remaining, CancellationToken.None)) + if (PrimeTester.IsPrimeCpu(remaining, CancellationToken.None)) { AddFactor(counts, remaining); return true; @@ -929,7 +929,7 @@ public class MersenneDivisorCycles } // if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None)) - if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeCp(divisor, CancellationToken.None)) + if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeCpu(divisor, CancellationToken.None)) { ulong computedOrder = PrimeOrderCalculator.Calculate( divisor, diff --git a/PerfectNumbers.Core/MersennePrimeFactorTester.cs b/PerfectNumbers.Core/MersennePrimeFactorTester.cs index d8a63cc..0bf2818 100644 --- a/PerfectNumbers.Core/MersennePrimeFactorTester.cs +++ b/PerfectNumbers.Core/MersennePrimeFactorTester.cs @@ -26,7 +26,7 @@ public static class MersennePrimeFactorTester } // if (!HeuristicPrimeTester.Exclusive.IsPrimeCpu(q, ct)) - if (!PrimeTester.IsPrimeCp(q, ct)) + if (!PrimeTester.IsPrimeCpu(q, ct)) { return false; } @@ -235,7 +235,7 @@ public static class MersennePrimeFactorTester } // if (HeuristicPrimeTester.Exclusive.IsPrimeCpu(m, ct)) - if (PrimeTester.IsPrimeCp(m, ct)) + if (PrimeTester.IsPrimeCpu(m, ct)) { dict[m] = 1; continue; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index c85e6bf..00981e2 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -516,7 +516,7 @@ internal static partial class PrimeOrderCalculator return false; } - // if (!PrimeTester.IsPrimeCp(orderFactors.Cofactor, CancellationToken.None)) + // if (!PrimeTester.IsPrimeCpu(orderFactors.Cofactor, CancellationToken.None)) if (!orderFactors.CofactorIsPrime) { return false; @@ -1123,8 +1123,8 @@ internal static partial class PrimeOrderCalculator // } // bool isPrime = primeTester.HeuristicIsPrimeGpu(composite); - // bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None); - bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None); + // bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None); + bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); if (isPrime) @@ -1186,8 +1186,8 @@ internal static partial class PrimeOrderCalculator if (!entry.HasKnownPrimality) { // bool isPrime = primeTester.HeuristicIsPrimeGpu(composite); - // bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None); - bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None); + // bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None); + bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); entry = entry.WithPrimality(isPrime); @@ -1219,8 +1219,8 @@ internal static partial class PrimeOrderCalculator else { // cofactorIsPrime = primeTester.HeuristicIsPrimeGpu(cofactor); - // cofactorIsPrime = PrimeTester.IsPrimeCp(cofactor, CancellationToken.None); - cofactorIsPrime = PrimeTester.IsPrimeCp(cofactor, CancellationToken.None); + // cofactorIsPrime = PrimeTester.IsPrimeCpu(cofactor, CancellationToken.None); + cofactorIsPrime = PrimeTester.IsPrimeCpu(cofactor, CancellationToken.None); // cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor); } @@ -1752,8 +1752,8 @@ internal static partial class PrimeOrderCalculator PrimeTester primeTester = PrimeTester.Exclusive; // bool isPrime = primeTester.HeuristicIsPrimeGpu(value); - // bool isPrime = PrimeTester.IsPrimeCp(value, CancellationToken.None); - bool isPrime = PrimeTester.IsPrimeCp(value, CancellationToken.None); + // bool isPrime = PrimeTester.IsPrimeCpu(value, CancellationToken.None); + bool isPrime = PrimeTester.IsPrimeCpu(value, CancellationToken.None); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value); if (!knownComposite && isPrime) { @@ -1765,8 +1765,8 @@ internal static partial class PrimeOrderCalculator ulong quotient = value / factor; // isPrime = primeTester.HeuristicIsPrimeGpu(factor); - // isPrime = PrimeTester.IsPrimeCp(factor, CancellationToken.None); - isPrime = PrimeTester.IsPrimeCp(factor, CancellationToken.None); + // isPrime = PrimeTester.IsPrimeCpu(factor, CancellationToken.None); + isPrime = PrimeTester.IsPrimeCpu(factor, CancellationToken.None); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor); if (isPrime) { @@ -1797,8 +1797,8 @@ internal static partial class PrimeOrderCalculator else { // isPrime = primeTester.HeuristicIsPrimeGpu(quotient); - // isPrime = PrimeTester.IsPrimeCp(quotient, CancellationToken.None); - isPrime = PrimeTester.IsPrimeCp(quotient, CancellationToken.None); + // isPrime = PrimeTester.IsPrimeCpu(quotient, CancellationToken.None); + isPrime = PrimeTester.IsPrimeCpu(quotient, CancellationToken.None); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient); } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs index ee4b914..24ba684 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs @@ -956,7 +956,7 @@ internal static partial class PrimeOrderCalculator if (value <= ulong.MaxValue) { // return HeuristicPrimeTester.Exclusive.IsPrimeCpu((ulong)value, CancellationToken.None); - return PrimeTester.IsPrimeCp((ulong)value, CancellationToken.None); + return PrimeTester.IsPrimeCpu((ulong)value, CancellationToken.None); } if ((value & UInt128.One) == UInt128.Zero) diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 31933a0..dd15c4e 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -93,10 +93,10 @@ public sealed class PrimeTester bool gpuReportedPrime = !forceCpu && !belowGpuRange && outFlags[0] != 0; bool requiresCpuFallback = forceCpu || belowGpuRange || !gpuReportedPrime; - return requiresCpuFallback ? IsPrimeCp(n, ct) : true; + return requiresCpuFallback ? IsPrimeCpu(n, ct) : true; } - public static bool IsPrimeCp(ulong n, CancellationToken ct) + public static bool IsPrimeCpu(ulong n, CancellationToken ct) { // Preserve the legacy entry point for callers that bypass the thread-local caches. return Exclusive.IsPrime(n, ct); diff --git a/docs/HeuristicIsPrimePlan.md b/docs/HeuristicIsPrimePlan.md index ed33f39..fd6671e 100644 --- a/docs/HeuristicIsPrimePlan.md +++ b/docs/HeuristicIsPrimePlan.md @@ -64,12 +64,12 @@ public bool IsPrimeGpu(ulong n, CancellationToken ct) bool gpuReportedPrime = !forceCpu && !belowGpuRange && outFlags[0] != 0; bool requiresCpuFallback = forceCpu || belowGpuRange || !gpuReportedPrime; - return requiresCpuFallback ? IsPrimeCp(n, ct) : true; + return requiresCpuFallback ? IsPrimeCpu(n, ct) : true; } ``` ```csharp -internal static bool IsPrimeCp(ulong n, CancellationToken ct) +internal static bool IsPrimeCpu(ulong n, CancellationToken ct) { bool isTwo = n == 2UL; bool isOdd = (n & 1UL) != 0UL; @@ -113,7 +113,7 @@ internal static bool IsPrimeCp(ulong n, CancellationToken ct) The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels, residue priorities) and connects them to both CPU and GPU execution, including every by-divisor integration point. Items already completed are marked with `[done]`. -1. [done] **Snapshot current implementations** – preserve `IsPrimeGpu` and `IsPrimeCp` as references for regression comparisons and migration checkpoints (see Section 2). +1. [done] **Snapshot current implementations** – preserve `IsPrimeGpu` and `IsPrimeCpu` as references for regression comparisons and migration checkpoints (see Section 2). 2. [done] **Introduce placeholder heuristic entry points** – add `IsPrimeCpu` and `IsPrimeGpu` by copying the current internal and GPU methods so subsequent commits can iterate without disrupting legacy behavior. 3. [done] **Design shared heuristic scaffolding** * [done] Added `HeuristicPrimeSieves` to precompute 4M-entry Group A/B divisor tables so heuristic prime checks reuse cached sequences without regenerating wheel steps at runtime; the enumerator remains available for residue-driven consumers. @@ -140,7 +140,7 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels, * [done] Streamed GPU batches from the same enumerator and Montgomery preparation data, removing the filtered-divisor scratch arrays while continuing to feed the existing kernel and hit bookkeeping. 9. [done] **Route legacy prime checks through heuristics** * [done] Introduce `HeuristicPrimeTester` so heuristic CPU/GPU checks live alongside the legacy `PrimeTester` implementations while callers opt in explicitly. - * [done] Maintain legacy paths for benchmarking via `PrimeTester.IsPrimeCp`, `PrimeTester.Exclusive.IsPrimeGpu`, and the explicit `HeuristicPrimeTester` entry points so benchmark harnesses can compare behaviours without runtime switches. + * [done] Maintain legacy paths for benchmarking via `PrimeTester.IsPrimeCpu`, `PrimeTester.Exclusive.IsPrimeGpu`, and the explicit `HeuristicPrimeTester` entry points so benchmark harnesses can compare behaviours without runtime switches. 10. **Validation and benchmarking** * Create targeted unit tests covering boundary cases for Group A/B transitions, wheel enumerations, residue deltas, and the stop-on-hit behaviour. * Expand GPU test harnesses to assert equivalence between CPU/GPU heuristics on sampled composite and prime inputs and to verify the residue-stream contract with by-divisor sessions. diff --git a/docs/HeuristicIsPrimeSpecification.md b/docs/HeuristicIsPrimeSpecification.md index 2d02573..6d3d374 100644 --- a/docs/HeuristicIsPrimeSpecification.md +++ b/docs/HeuristicIsPrimeSpecification.md @@ -62,7 +62,7 @@ No other endings are allowed or reordered. ## Default Prime Tester Entry Points * `PrimeTester.IsPrime` and `PrimeTester.IsPrimeGpu` continue to run the legacy trial-division and GPU sieve paths. Use `HeuristicPrimeTester` when the heuristic CPU/GPU flow is required. -* `HeuristicPrimeTester` always executes the heuristic CPU/GPU implementations; opt into the legacy paths by calling `PrimeTester.IsPrimeCp` or `PrimeTester.Exclusive.IsPrimeGpu` directly. +* `HeuristicPrimeTester` always executes the heuristic CPU/GPU implementations; opt into the legacy paths by calling `PrimeTester.IsPrimeCpu` or `PrimeTester.Exclusive.IsPrimeGpu` directly. * Benchmark harnesses can compare behaviours by calling the legacy helpers on `PrimeTester` alongside the heuristic equivalents on `HeuristicPrimeTester`. ## Integration with the By-Divisor Pipeline diff --git a/todo_list.txt b/todo_list.txt index c65c9e4..72016e5 100644 --- a/todo_list.txt +++ b/todo_list.txt @@ -18,7 +18,7 @@ Divisor Cycle Cache & Generation - PerfectNumbers.Core/UInt128Extensions.cs:372: If the cache lacks this cycle, immediately schedule the configured device (GPU by default) to compute it on the fly and skip inserting it into the cache so wide-order factoring can still leverage cycle stepping without breaching the memory cap or introducing extra synchronization. Primality & Order Screening -- PerfectNumbers.Core/MersenneDivisorCycles.cs:477: Benchmark PrimeTester.IsPrimeCp against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites. +- PerfectNumbers.Core/MersenneDivisorCycles.cs:477: Benchmark PrimeTester.IsPrimeCpu against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites. - PerfectNumbers.Core/ULongExtensions.cs:119: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand GPU computation (respecting the configured device) without promoting the result into the cache so the order calculator still benefits from cycle stepping while keeping the single-block memory plan intact. - PerfectNumbers.Core/ULongExtensions.cs:132: Replace this `%` driven factor peeling with the divisor-cycle aware factoring helper so large orders reuse the cached remainders highlighted in the latest divisor-cycle benchmarks instead of recomputing slow modulo checks. - PerfectNumbers.Core/ULongExtensions.cs:279: Swap this modulo check for the shared small-prime cycle filter once the divisor-cycle cache is mandatory, matching the PrimeTester improvements noted in the CPU sieve benchmarks.