Add heuristic prime sieves for cached divisor sequences

This commit is contained in:
Marek Lemańczyk committed 2025-10-26 03:21:29 +01:00
1 parent 4ee6464130
commit 15c229665d
10 files changed
+1597 -364

No files matched your search

+158
View File
@@ -0,0 +1,158 @@
# 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=<path>." 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).
```csharp
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 ? IsPrimeInternal(n, ct) : true;
}
```
```csharp
internal static bool IsPrimeInternal(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 `PrimeTester.HeuristicIsPrime*`
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 `IsPrimeInternal` as references for regression comparisons and migration checkpoints (see Section 2).
2. [done] **Introduce placeholder heuristic entry points** – add `HeuristicIsPrimeCpu` and `HeuristicIsPrimeGpu` by copying the current internal and GPU methods so subsequent commits can iterate without disrupting legacy behavior.
3. [done] **Design shared heuristic scaffolding**
* [done] Introduced `HeuristicDivisorEnumerator` and stackalloc-backed Group B sequence states so CPU/GPU heuristics share the Group A/B wheel metadata and candidate ordering.
* [done] Surface helpers that accept the precomputed `R = ⌊√n⌋` and `nMod10` values from callers via the new overload of `HeuristicIsPrimeCpu`; PrimeTester 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 `PrimeTester.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 `HeuristicIsPrimeCpu`**
* Baseline Group A/B enumeration now executes inside `HeuristicIsPrimeCpu` via `HeuristicDivisorEnumerator`, 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 `HeuristicIsPrimeGpu`**
* [done] Mirror CPU structure while batching divisor checks on the accelerator via `HeuristicTrialDivisionGpu`, 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 `PrimeTester.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] Update `IsPrimeInternal` and `IsPrimeGpu` to delegate to the heuristic implementations when `EnableHeuristicPrimeTesting` is enabled, while allowing `PrimeTester(useInternal: true)` to force the legacy CPU/GPU paths.
* [done] Maintain legacy paths for benchmarking via `LegacyIsPrimeInternal`, `LegacyIsPrimeGpu`, and the global `EnableHeuristicPrimeTesting` switch so benchmark harnesses can compare behaviours.
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 `PrimeTester.IsPrimeGpu`, `PrimeTester.IsPrimeInternal`, 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.
+107
View File
@@ -0,0 +1,107 @@
# Heuristic Prime Tester Specification
This document describes the heuristic prime testing procedure implemented by `PrimeTester.HeuristicIsPrimeCpu` and `PrimeTester.HeuristicIsPrimeGpu`. The guidance consolidates the residue analysis and empirical statistics for odd inputs that are not divisible by five, based on the first 500,000 odd composite numbers after removing multiples of five. It also records the integration contracts required by the by-divisor pipeline so the heuristic covers every path that participates in divisor searches, residue sieves, and factorisation checks.
## Input Assumptions
* `n` is odd, `n > 2`, and `n` is not divisible by 5.
* The goal is to **prove compositeness** by finding a single proper divisor.
* Trial division always stops at `⌊√n⌋`, which the caller computes and supplies alongside the candidate.
## Candidate Partitioning
### Group A (special case)
```
A = {3, 7, 11, 13} ∪ { d : d ≡ 3 (mod 10), d ≤ ⌊√n⌋ }
```
Group A always executes first.
### Group B (remaining candidates)
Group B contains every other proper divisor `≤ ⌊√n⌋` that does **not** end with digit 3.
#### Allowed endings for Group B (after removing A)
The allowed endings depend on `n mod 10`:
* `n ≡ 1 (mod 10)` → `{1, 9}`
* `n ≡ 3 (mod 10)` → `{7, 9}`
* `n ≡ 7 (mod 10)` → `{1, 7}`
* `n ≡ 9 (mod 10)` → `{1, 7, 9}`
These sets are exhaustive. Do **not** add other endings.
## Wheel Strategy
* **Group A** uses the wheel based on 30 (residues coprime with 2 and 5: `{1, 7, 11, 13, 17, 19, 23, 29}`) combined with the `d ≡ 3 (mod 10)` filter. The constants `{3, 7, 11, 13}` are checked explicitly before wheel generation. The implementation uses wheel 30; a wheel of 210 may also be used for full uniformity, but 30 is lighter and already filtered to the `≡ 3 (mod 10)` residue.
* **Group B** uses a wheel based on 210 (coprime with 2, 3, 5, and 7). Only numbers whose final digit belongs to the allowed-ending set for the current `n mod 10` are retained.
## Testing Order
After Group A finishes, Group B enumerates candidates in strictly increasing order up to `⌊√n⌋`. Within that ascending order, endings are prioritised based on empirical hit rates **after excluding Group A**:
* `n ≡ 1 (mod 10)` → try endings `9` first, then `1`.
* `n ≡ 3 (mod 10)` → endings `9` then `7`.
* `n ≡ 7 (mod 10)` → endings `7` then `1`.
* `n ≡ 9 (mod 10)` → endings `9`, then `7`, then `1`.
No other endings are allowed or reordered.
## Algorithm Steps
1. Accept `R = ⌊√n⌋` and `nMod10` as parameters computed by the caller to avoid redundant square-root and modulo work and to enable reuse in other calculations.
2. **Group A constants**: test `{3, 7, 11, 13}` when they are `≤ R`. Any hit marks `n` as composite and terminates the search.
3. **Group A wheel**: generate candidates using the wheel-30 sequence, filter to `d ≡ 3 (mod 10)`, stop at `R`. Any hit marks `n` as composite and terminates the search.
4. **Group B**: read the precomputed `n mod 10` (made available by the caller configuration such as `--gpu-prime-batch`). Iterate candidates via wheel 210, maintaining ascending order and applying the ending-priority sequence above. Any hit marks `n` as composite and terminates the search.
5. **Decision**: if no divisors were found up to `R`, report `n` as “prime by trial”. This outcome is final—no further confirmation runs are performed.
> **Temporary status:** Current builds disable Group B via `UseHeuristicGroupBTrialDivision`. After exhausting Group A, `HeuristicIsPrime*` delegates to `Open.Numeric.Primes.Prime.Numbers.IsPrime`, preserving the Group B implementation for future use while ensuring production checks mirror the external library once Group A filters pass.
## Default Prime Tester Entry Points
* `PrimeTester.IsPrime` and `PrimeTester.IsPrimeGpu` now delegate to the heuristic CPU/GPU implementations by default.
* Set `PrimeTester.EnableHeuristicPrimeTesting = false` or instantiate `new PrimeTester(useInternal: true)` to force the legacy trial-division and GPU-sieve logic without removing the heuristic helpers.
* Benchmark harnesses can call `PrimeTester.LegacyIsPrimeInternal` (and `PrimeTester(useInternal: true).IsPrimeGpu`) to compare the legacy behaviour with the heuristic flow when collecting performance baselines.
## Integration with the By-Divisor Pipeline
The by-divisor executors (`MersenneNumberDivisorByDivisorCpuTester` and `MersenneNumberDivisorByDivisorGpuTester`) reuse prime heuristics to seed their divisor scans, residue sieves, and cycle computations. The heuristic implementations must therefore expose the following data and behaviours:
* **Residue filters** – The CPU scan maintains rolling residues modulo 10/8/5/3/7/11 to match the filters in `MersenneCpuDivisorScanSession.CheckDivisors`. `HeuristicIsPrime*` must either share the residue update helpers or produce identical stepping deltas so the divisor scanners can reuse the pre-filtered candidate stream without recomputing moduli. The shared `MersenneDivisorResidueStepper` now centralises those modulo calculations so both paths reuse the same state transitions, and the GPU by-divisor tester instantiates the same stepper before filtering candidates.
* **Stepper integration** – CPU sessions advance residues and unity checks through `ExponentRemainderStepperCpu` together with `CycleRemainderStepper`; GPU kernels mirror this contract via `ExponentRemainderStepperGpu`. The heuristic implementation must call into these steppers (or emit the same remainder deltas) so the shared stepping state remains aligned across CPU/GPU scans and factor validation.
* **Wheel alignment** – The wheel 30/210 sequences emitted by `PrimeTester.CreateHeuristicDivisorEnumerator` now drive both the CPU and GPU by-divisor testers, keeping the divisor cache and kernel inputs in lockstep without bespoke residue loops.
* **Divisor streaming API** – `PrimeTester.CreateHeuristicDivisorEnumerator` now feeds `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` and `MersenneNumberDivisorByDivisorGpuTester.CheckDivisors`, replacing bespoke residue loops while supplying candidates directly to Montgomery preparation and GPU kernels.
* **GPU batching** – `HeuristicIsPrimeGpu` streams Group A/B candidates through the same enumerator, packs them into batches controlled by `PrimeTester.HeuristicGpuDivisorBatchSize`, and launches `PrimeTesterKernels.HeuristicTrialDivisionKernel` to test each divisor with standard `%` arithmetic. When GPUs are disabled, the method delegates to the CPU heuristic so fallbacks reuse the same candidate ordering without additional plumbing.
* **Cycle preparation** – When the heuristic discovers a divisor candidate, it should populate `MontgomeryDivisorData` and expose the computed cycle length (or the need to recompute it) so that `DivisorCycleCache` and `MersenneDivisorCycles` do not duplicate work for the same `(prime, divisor)` pair during factor verification. `PrimeTester.PrepareHeuristicDivisor` together with `PrimeTester.ResolveHeuristicCycleLength` now provides this payload to both CPU and GPU by-divisor scans, and the trial-division summaries capture the data whenever a divisor is found.
* **Caching contract** – The by-divisor GPU implementation pools `DivisorScanSession` instances and reuses GPU buffers. The heuristic must accept externally allocated spans/buffers for divisor candidates, cycle lengths, and hits so the existing resource pools remain valid.
## Statistical Guidance
The following summaries cover the first 500,000 odd composite numbers after removing multiples of five (≈381,651 values). Among those, roughly 67,190 numbers had **no** divisor in Group A.
### Distribution of the smallest Group B divisor (when Group A contains a divisor)
* `n ≡ 1 (mod 10)` → ending `7`: **40.30%**, ending `9`: **38.38%**, ending `1`: **21.32%`. In these cases, `min(B) < A_max(n)` occurred **60.77%** of the time, while `min(B) > A_max(n)` occurred **39.23%** of the time.
* `n ≡ 3 (mod 10)` → ending `9`: **40.91%**, ending `1`: **38.92%**, ending `7`: **20.17%`. Here, `min(B) < A_max(n)` occurred **54.92%** of the time, while `min(B) > A_max(n)` occurred **45.08%** of the time.
* `n ≡ 7 (mod 10)` → ending `9`: **56.98%**, ending `7`: **21.52%**, ending `1`: **21.50%`. The relation `min(B) < A_max(n)` held **56.09%** of the time versus **43.91%** for `min(B) > A_max(n)`.
* `n ≡ 9 (mod 10)` → ending `9`: **49.64%**, ending `7`: **27.61%**, ending `1`: **22.75%`. In this class, `min(B) < A_max(n)` occurred **76.20%** of the time, with **23.80%** for `min(B) > A_max(n)`.
### Behaviour when no Group A divisor exists
* With full Group A processing, the minimum Group B divisor satisfied `min(B) < A_max(n)` about **99.67%** of the time, `min(B) = A_max(n)` essentially never, and `min(B) > A_max(n)` about **0.33%** of the time.
* The ratio `r = min(B) / A_max(n)` showed a median of ≈0.115, with quartiles `Q1 ≈ 0.044` and `Q3 ≈ 0.335`, confirming that the first Group B hit typically appears well before the tail of the Group A sweep.
* Minimum Group B divisors appeared **only** with the endings listed in the “Allowed endings” table above once Group A candidates were excluded.
### Heuristic cheat sheet
* Group A: test `3, 7, 11, 13` and every `d ≡ 3 (mod 10)` up to `⌊√n⌋` (wheel 30).
* Group B: test only the allowed endings determined by `n mod 10`, scanning in ascending order (wheel 210) and applying the per-class priority order: `n ≡ 1` → `9` then `1`; `n ≡ 3` → `9` then `7`; `n ≡ 7` → `7` then `1`; `n ≡ 9` → `9`, `7`, `1`.
* Because `min(B)` is overwhelmingly smaller than `A_max(n)` for the “no Group A divisor” cohort, always start Group B from its smallest candidates instead of resuming above `A_max(n)`.
## Benchmark Reporting
Benchmark results comparing `PrimeTester.IsPrimeGpu`, `PrimeTester.IsPrimeInternal`, and `Open.Numeric.Primes.Prime.Numbers.IsPrime` will be documented here once the dedicated benchmarks covering ≤100, ≤4,000,000, and ≥138,000,000 candidates are executed. The specification will be updated with the measured timings and analysis so operators can evaluate heuristic rollouts against legacy and external implementations. Benchmark harnesses are available via `EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs`, which runs the heuristic CPU and GPU paths alongside `Open.Numeric.Primes` for the three target ranges.