Files
even-perfect-numbers-scanner/docs/HeuristicIsPrimePlan.md
T

14 KiB
Raw Blame History

EvenPerfectBitScanner --mersenne=bydivisor Flow and Heuristic Prime Tester Plan

1. Execution Flow in --mersenne=bydivisor Mode

The following map captures every branch that executes when the scanner runs with --mersenne=bydivisor, highlighting interactions with prime testing, divisor heuristics, residue reuse, and factor verification.

  1. CLI processing and guards

    • CliArguments.Parse fills _cliArguments, then Main validates thread count, help flag, and shared GPU settings.
    • useByDivisor is set from CLI. If enabled without --filter-p (and not in test mode), the scanner prints "--mersenne=bydivisor requires --filter-p=." and exits early.【F:EvenPerfectBitScanner/Program.cs†L32-L132】
  2. Global configuration

    • GPU settings (GpuPrimeWorkLimiter, PrimeTester.GpuBatchSize, transforms) are configured for both CPU and GPU runs.
    • By-divisor specific state records _byDivisorStartPrime, _byDivisorTester, and _byDivisorPreviousResults for later reuse.【F:EvenPerfectBitScanner/Program.cs†L134-L232】
  3. Tester initialization

    • When useByDivisor is active, _byDivisorTester is instantiated as GPU or CPU implementation, with BatchSize set from --scan-batch. PrimeTester/MersenneNumberTester pools are not created for this mode because the flow exits before the generic candidate loop.【F:EvenPerfectBitScanner/Program.cs†L200-L232】【F:EvenPerfectBitScanner/Program.cs†L400-L460】
  4. Results bootstrap

    • The scanner builds the results filename, initializes CalculationResultHandler, and loads previous results. Previous entries populate _byDivisorPreviousResults so the by-divisor runner can skip already processed primes.【F:EvenPerfectBitScanner/Program.cs†L232-L318】
  5. Candidate acquisition

    • byDivisorCandidates is populated either from deterministic test data (test mode) or by reloading the filter file with CalculationResultsFile.LoadCandidatesWithinRange. Limits (--max-prime) and skipped candidates are reported to the console.【F:EvenPerfectBitScanner/Program.cs†L318-L362】
    • In contrast to other modes, generic residue filters and candidate queues are bypassed; only the provided prime list is considered.【F:EvenPerfectBitScanner/Program.cs†L318-L362】
  6. Previous-result pruning

    • MersenneNumberDivisorByDivisorTester.Run sorts the input primes, removes entries already present in _byDivisorPreviousResults, applies --start-prime, and converts the remaining list to spans for batch preparation.【F:PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs†L11-L138】
  7. Tester configuration per maximum prime

    • IMersenneNumberDivisorByDivisorTester.ConfigureFromMaxPrime and PrepareCandidates pre-compute divisor limits for every prime. All primes with allowedMax >= 3 are retained (current implementations never filter any out in production because limits are huge).【F:PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs†L140-L201】
  8. Residue-sieved divisor scanning (CPU)

    • MersenneNumberDivisorByDivisorCpuTester.CheckDivisors initialises Montgomery stepping data (GpuUInt128), computes rolling residues for mod 10/8/5/3/7/11, and advances divisors by 2p while applying decimal masks tied to the exponent’s last digit.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L63-L213】
    • When a candidate passes residue filters, the CPU session obtains cycle lengths from DivisorCycleCache or MersenneDivisorCycles, validates them via Montgomery exponentiation, and reports hits while updating _lastStatusDivisor.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L213-L318】
  9. Residue-sieved divisor scanning (GPU)

    • MersenneNumberDivisorByDivisorGpuTester.CheckDivisors reuses the same wheel offsets and residue tests but performs them inside DivisorByDivisorKernels.CheckKernel, writing hits to pooled GPU buffers. CPU fallbacks honour the same residue, decimal mask, and cycle rules when GPU context is unavailable.【F:PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs†L1-L240】【F:PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs†L240-L520】
  10. Prime decision path

    • By-divisor sessions internally reuse divisor remainders across primes, but they do not call PrimeTester.IsPrime*. The primality decision depends entirely on divisor scans up to the limit configured for each prime candidate.
    • If IsPrime returns false, the result file records (detailedCheck=true, passedAllTests=false). When true, divisorsExhausted reports whether the scan reached the configured divisor bound (driving the DetailedCheck flag).【F:PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs†L203-L272】
  11. Shutdown

    • After MersenneNumberDivisorByDivisorTester.Run completes, the main loop flushes results and exits before any of the generic candidate enumeration logic runs.【F:EvenPerfectBitScanner/Program.cs†L400-L460】

2. Baseline Prime Tester Methods

The heuristics will replace the internal logic of the current GPU-assisted and CPU-only paths. The snapshots below capture the existing implementations before modification (copied verbatim for reference).

public bool IsPrimeGpu(ulong n, CancellationToken ct)
{
    bool forceCpu = GpuContextPool.ForceCpu;
    Span<ulong> one = stackalloc ulong[1];
    Span<byte> outFlags = stackalloc byte[1];
    one[0] = n;
    outFlags[0] = 0;

    if (!forceCpu)
    {
        IsPrimeBatchGpu(one, outFlags);
    }

    bool belowGpuRange = n < 31UL;
    bool gpuReportedPrime = !forceCpu && !belowGpuRange && outFlags[0] != 0;
    bool requiresCpuFallback = forceCpu || belowGpuRange || !gpuReportedPrime;

    return requiresCpuFallback ? IsPrimeCpu(n, ct) : true;
}
internal static bool IsPrimeCpu(ulong n, CancellationToken ct)
{
    bool isTwo = n == 2UL;
    bool isOdd = (n & 1UL) != 0UL;
    ulong mod5 = n % 5UL;
    bool divisibleByFive = n > 5UL && mod5 == 0UL;

    bool result = n >= 2UL && (isTwo || isOdd) && !divisibleByFive;
    bool requiresTrialDivision = result && n >= 7UL && !isTwo;

    if (requiresTrialDivision)
    {
        bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n);
        result &= !sharesMaxExponentFactor;

        if (result)
        {
            var smallPrimeDivisorsLength = PrimesGenerator.SmallPrimes.Length;
            uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes;
            ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2;
            for (int i = 0; i < smallPrimeDivisorsLength; i++)
            {
                if (smallPrimeDivisorsMul[i] > n)
                {
                    break;
                }

                if (n % smallPrimeDivisors[i] == 0)
                {
                    result = false;
                    break;
                }
            }
        }
    }

    return result;
}

3. Implementation Plan for HeuristicPrimeTester

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 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.
    • [done] Surface helpers that accept the precomputed R = ⌊√n⌋ and nMod10 values from callers via the new overload of IsPrimeCpu; HeuristicPrimeTester still computes them locally until call sites forward the parameters.
    • [done] Simplified the heuristic execution path so it runs without cancellation tokens or callback plumbing while keeping the shared enumerator available to other components.
  4. Share residue and wheel state with by-divisor sessions
    • [done] Extracted the rolling-modulus helpers (mod 10/8/5/3/7/11) and decimal masks from MersenneNumberDivisorByDivisorCpuTester.CheckDivisors into the shared MersenneDivisorResidueStepper so heuristic prime tests and by-divisor sessions consume the same filters.
    • [done] Audited how MersenneCpuDivisorScanSession advances residues via ExponentRemainderStepperCpu/CycleRemainderStepper and updated the GPU by-divisor path to reuse MersenneDivisorResidueStepper, keeping CPU/GPU residue stepping aligned.
    • [done] Publish wheel iterators that can stream candidates directly into DivisorCycleCache, GPU hit buffers, or CPU Montgomery checks without recomputing offsets by exposing HeuristicPrimeTester.CreateHeuristicDivisorEnumerator and the reusable HeuristicGroupBSequenceState buffer.
  5. Expose cycle-preparation helpers
    • [done] Extend the heuristic scaffolding so it returns MontgomeryDivisorData descriptors and cycle-length hints for each candidate divisor via HeuristicDivisorPreparation and PrepareHeuristicDivisor.
    • Wire these outputs into the existing cycle calculators to avoid duplicate cache lookups inside the CPU/GPU divisor scans.
  6. [done] Implement IsPrimeCpu
    • Baseline Group A/B enumeration now executes inside IsPrimeCpu via the cached HeuristicPrimeSieves tables, computing ⌊√n⌋ locally and short-circuiting on the first divisor.
    • [done] The CPU heuristic now operates as a pure trial-division sweep without maintaining per-call summaries, relying on PrepareHeuristicDivisor only when downstream consumers request Montgomery data.
    • [done] Introduced the temporary UseHeuristicGroupBTrialDivision gate so current builds execute Group A locally and then fall back to Open.Numeric.Primes.Prime.Numbers.IsPrime, keeping the full Group B implementation available for reactivation.
  7. [done] Implement IsPrimeGpu
    • [done] Mirror CPU structure while batching divisor checks on the accelerator via HeuristicTrialDivisionGpu, drawing divisors from HeuristicPrimeSieves, reusing caller-provided sqrtLimit/nMod10 inputs, and falling back to CPU when GPUs are disabled.
    • [done] Added PrimeTesterKernels.HeuristicTrialDivisionKernel so GPU batches respect Group A/B ordering while emitting hit flags for early exits.
    • [done] Kept GPU divisibility in standard modular arithmetic while reserving Montgomery transforms for CPU confirmation logic.
    • [done] When the temporary Group B gate is disabled, delegate GPU calls to the CPU fallback so Group A coverage remains in place while the accelerator path stays available for future re-enablement.
  8. [done] Refactor by-divisor CPU/GPU testers to reuse heuristics
    • [done] Drove MersenneNumberDivisorByDivisorCpuTester.CheckDivisors from HeuristicPrimeTester.CreateMersenneDivisorEnumerator, eliminating the bespoke residue loop while preserving the existing status counters.
    • [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.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.
    • Benchmark against historical data (hundreds of thousands of composites) to confirm that heuristic ordering improves early divisor detection and that residue sharing reduces duplicate computation.
    • [done] Build BenchmarkDotNet comparisons for HeuristicPrimeTester.IsPrimeGpu, HeuristicPrimeTester.IsPrimeCpu, and Open.Numeric.Primes.Prime.Numbers.IsPrime across representative ranges (≤100, ≤4,000,000, and ≥138,000,000) before routing heuristic code into production. Implemented in EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs to exercise both CPU and GPU heuristic paths alongside the external library.
  11. Documentation and rollout
    • Update README/docs to explain heuristic prerequisites (odd, not divisible by 5) and how configuration toggles affect execution across prime testing and by-divisor scans.
    • Provide migration guidance for operators toggling between legacy and heuristic modes and describe how telemetry/metrics change after integration.
    • Publish benchmark results in the heuristic specification once measurements are available so the documented guidance reflects the observed CPU/GPU/external comparisons.

4. Remaining Work Summary

  • Complete the remaining rollout tasks in steps 10–11, including legacy-call-site integration, validation, and documentation updates.
  • Develop comprehensive validation suites (unit tests and performance benchmarks) to compare heuristic behaviour with current implementations across both prime testing and by-divisor divisor scans.
  • Document configuration switches, runtime metrics, and operational playbooks once the heuristics are in place.