diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index 785f2b4..8b1a8fa 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -4,19 +4,47 @@ using System.Reflection; using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; +using EvenPerfectBitScanner; using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.Candidates.Transforms; using System.Numerics; +using System.Threading; +using System.Threading.Tasks; namespace EvenPerfectBitScanner.Tests; [Trait("Category", "Fast")] public class ProgramTests { + private static readonly FieldInfo CliArgumentsField = typeof(Program).GetField("_cliArguments", BindingFlags.NonPublic | BindingFlags.Static)!; + private static readonly FieldInfo DivisorField = typeof(Program).GetField("_divisor", BindingFlags.NonPublic | BindingFlags.Static)!; + private static readonly CliArguments DefaultCliArguments = CliArguments.Parse(Array.Empty()); + + public ProgramTests() + { + ResetCliArguments(); + } + + private static void ConfigureCliArguments(params string[] args) + { + SetCliArguments(CliArguments.Parse(args)); + } + + private static void SetCliArguments(CliArguments arguments) + { + CliArgumentsField.SetValue(null, arguments); + } + + private static void ResetCliArguments() + { + SetCliArguments(DefaultCliArguments); + DivisorField.SetValue(null, UInt128.Zero); + } + [Fact] public void CountOnes_returns_correct_count() { - (ulong)BitOperations.PopCount(0b101010UL).Should().Be(3UL); + BitOperations.PopCount(0b101010).Should().Be(3); } [Theory] @@ -70,19 +98,15 @@ public class ProgramTests [Fact] public void Divisor_mode_scans_dynamic_cycles_when_none_specified() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; - var divisorField = typeof(Program).GetField("_divisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=divisor"); var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, true); - divisorField.SetValue(null, UInt128.Zero); testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) }); forceCpuProp!.SetValue(null, true); @@ -94,31 +118,28 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); testerField.SetValue(null, null); candidatesField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } [Fact] public void Divisor_mode_sets_detailedCheck_based_on_exhaustion() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; - var divisorField = typeof(Program).GetField("_divisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=divisor"); + var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, true); - divisorField.SetValue(null, UInt128.Zero); testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); candidatesField.SetValue(null, Array.Empty<(ulong, uint)>()); forceCpuProp!.SetValue(null, true); @@ -130,30 +151,28 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); testerField.SetValue(null, null); candidatesField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } [Fact] public void Divisor_mode_accepts_large_search_limit() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; - var divisorField = typeof(Program).GetField("_divisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=divisor"); + DivisorField.SetValue(null, (UInt128)7); + var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, true); - divisorField.SetValue(null, (UInt128)7); testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, true); try @@ -164,29 +183,27 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); testerField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } [Fact] public void Divisor_mode_checks_divisibility_on_gpu() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; - var divisorField = typeof(Program).GetField("_divisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=divisor"); + DivisorField.SetValue(null, (UInt128)23); + var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, true); - divisorField.SetValue(null, (UInt128)23); testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); try @@ -201,28 +218,26 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); - divisorField.SetValue(null, UInt128.Zero); testerField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } [Fact] public void Residue_mode_checks_mersenne_primes_on_gpu() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); try @@ -237,30 +252,29 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } [Fact] public void Residue_mode_with_large_divisor_cycle_limits_accepts_known_primes() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useResidue: true, maxK: 1_024UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); try @@ -275,11 +289,11 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } @@ -287,21 +301,18 @@ public class ProgramTests [Trait("Category", "Fast")] public void Residue_mode_accepts_known_primes_in_parallel() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; - var gcdFilterField = typeof(Program).GetField("_useGcdFilter", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); - gcdFilterField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useResidue: true, maxK: 5_000_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); try @@ -316,30 +327,29 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } [Fact] public void Residue_mode_with_default_limits_accepts_large_known_primes() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useResidue: true, maxK: 5_000_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); try @@ -354,11 +364,11 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } @@ -366,15 +376,12 @@ public class ProgramTests [MemberData(nameof(KnownMersennePrimeExponents))] public void IsEvenPerfectCandidate_accepts_known_small_mersenne_primes(ulong exponent) { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; - var gcdFilterField = typeof(Program).GetField("_useGcdFilter", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); - gcdFilterField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useGpuScan: false, @@ -382,7 +389,7 @@ public class ProgramTests useResidue: true, maxK: 1_024UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); forceCpuProp!.SetValue(null, true); try @@ -392,12 +399,11 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); - gcdFilterField.SetValue(null, false); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } @@ -407,15 +413,12 @@ public class ProgramTests ulong exponent, MersenneGpuCandidateConfig configuration) { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; - var gcdFilterField = typeof(Program).GetField("_useGcdFilter", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); - gcdFilterField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: false, kernelType: configuration.KernelType, @@ -424,7 +427,7 @@ public class ProgramTests useResidue: true, maxK: configuration.ResidueMaxK), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); try @@ -434,12 +437,11 @@ public class ProgramTests } finally { - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); - gcdFilterField.SetValue(null, false); + CandidatesCalculator.ResetResidueTrackers(); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); GpuContextPool.DisposeAll(); } } @@ -499,17 +501,16 @@ public class ProgramTests [Fact] public void Composite_candidates_flagged_for_residue_output_skip() { - var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + ConfigureCliArguments("--mersenne=residue"); + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); - useDivisorField.SetValue(null, false); mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); - residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, true); try @@ -529,9 +530,10 @@ public class ProgramTests { mersenneField.SetValue(null, null); primeField.SetValue(null, null); - residueField.SetValue(null, null); + CandidatesCalculator.ResetResidueTrackers(); compositeField.SetValue(null, false); forceCpuProp!.SetValue(null, false); + ResetCliArguments(); } } diff --git a/EvenPerfectBitScanner.Tests/ResultsFileNameTests.cs b/EvenPerfectBitScanner.Tests/ResultsFileNameTests.cs index d0b9853..04784fc 100644 --- a/EvenPerfectBitScanner.Tests/ResultsFileNameTests.cs +++ b/EvenPerfectBitScanner.Tests/ResultsFileNameTests.cs @@ -1,8 +1,13 @@ -using System.Reflection; +using System.Collections.Generic; +using System.IO; +using System.Numerics; using FluentAssertions; +using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; using Xunit; +using EvenPerfectBitScanner.IO; + namespace EvenPerfectBitScanner.Tests; [Trait("Category", "Fast")] @@ -11,41 +16,26 @@ public class ResultsFileNameTests [Fact] public void BuildResultsFileName_contains_reduction_and_devices() { - var programType = typeof(global::EvenPerfectBitScanner.Program); - var method = programType.GetMethod( - "BuildResultsFileName", - BindingFlags.NonPublic | BindingFlags.Static, - binder: null, - types: new[] - { - typeof(bool), typeof(int), typeof(int), typeof(global::PerfectNumbers.Core.GpuKernelType), - typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(NttBackend), typeof(int), typeof(int), typeof(int), typeof(ulong), typeof(ModReductionMode), typeof(string), typeof(string), typeof(string) - }, - modifiers: null)!; - - string name = (string)method.Invoke(null, new object[] - { - true, // bitInc - 8, // threads - 1, // block - global::PerfectNumbers.Core.GpuKernelType.Incremental, - false, // useLucasFlag - false, // useDivisorFlag - false, // useByDivisorFlag - true, // mersenneOnGpu (unused inside name content, devices passed explicitly below) - false, // useOrder - false, // useModWorkaround - false, // useGcd - NttBackend.Staged, - 1, // gpuPrimeThreads - 32, // llSlice - 1024, // gpuScanBatch - 1000UL, // warmupLimit - ModReductionMode.Barrett128, - "gpu", // mersenneDevice - "gpu", // primesDevice - "gpu" // orderDevice - })!; + string name = CalculationResultsFile.BuildFileName( + bitInc: true, + threads: 8, + block: 1, + kernelType: GpuKernelType.Incremental, + useLucasFlag: false, + useDivisorFlag: false, + useByDivisorFlag: false, + mersenneOnGpu: true, + useOrder: false, + useGcd: false, + nttBackend: NttBackend.Staged, + gpuPrimeThreads: 1, + llSlice: 32, + gpuScanBatch: 1024, + warmupLimit: 1000UL, + reduction: ModReductionMode.Barrett128, + mersenneDevice: "gpu", + primesDevice: "gpu", + orderDevice: "gpu"); name.Should().Contain("ntt-staged"); name.Should().Contain("red-barrett128"); @@ -53,4 +43,63 @@ public class ResultsFileNameTests name.Should().Contain("primesdev-gpu"); name.Should().Contain("orderdev-gpu"); } + + [Fact] + public void LoadCandidatesWithinRange_skips_comments_and_applies_limit() + { + string path = Path.GetTempFileName(); + try + { + File.WriteAllLines(path, new[] + { + "# header", + "5 7", + string.Empty, + "11", + "17", + "19" + }); + + List candidates = CalculationResultsFile.LoadCandidatesWithinRange(path, (UInt128)13, maxPrimeConfigured: true, out int skipped); + + candidates.Should().Equal(5UL, 7UL, 11UL); + skipped.Should().Be(2); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void EnumerateCandidates_reads_valid_rows_and_skips_invalid_entries() + { + string path = Path.GetTempFileName(); + try + { + File.WriteAllLines(path, new[] + { + "p,searched,detailed,passed", + "5,true,true,true", + string.Empty, + "11,false,false,true", + "invalid", + "13,true,true,false" + }); + + List<(ulong P, bool Detailed, bool Passed)> rows = new(); + CalculationResultsFile.EnumerateCandidates(path, (p, detailed, passed) => rows.Add((p, detailed, passed))); + + rows.Should().Equal(new (ulong, bool, bool)[] + { + (5UL, true, true), + (11UL, false, true), + (13UL, true, false) + }); + } + finally + { + File.Delete(path); + } + } } diff --git a/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs b/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs index c5391b5..352d5d4 100644 --- a/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs +++ b/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using System.Threading; using Open.Numeric.Primes; +using PerfectNumbers.Core; using EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates; @@ -10,6 +11,7 @@ internal static class CandidatesCalculator private static readonly Optimized PrimeIterator = new(); private static long _state; private static PrimeTransformMode _transformMode; + private static ThreadLocal? _residueTrackers; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Configure(PrimeTransformMode transformMode) @@ -17,6 +19,68 @@ internal static class CandidatesCalculator _transformMode = transformMode; } + internal static void Initialize(ulong initialNumber) + { + SetResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, initialNumber: initialNumber, initialized: true), trackAllValues: true)); + } + + internal static void OverrideResidueTrackers(ThreadLocal trackers) + { + if (trackers is null) + { + throw new ArgumentNullException(nameof(trackers)); + } + + SetResidueTrackers(trackers); + } + + internal static void ResetResidueTrackers() + { + ThreadLocal? previous = _residueTrackers; + _residueTrackers = null; + previous?.Dispose(); + } + + private static void SetResidueTrackers(ThreadLocal trackers) + { + ThreadLocal? previous = _residueTrackers; + _residueTrackers = trackers; + previous?.Dispose(); + } + + private static ModResidueTracker AcquireResidueTracker() + { + var trackers = _residueTrackers ?? throw new InvalidOperationException("CandidatesCalculator.Initialize must be called before residue checks."); + return trackers.Value!; + } + + internal static bool IsCompositeByResidues(ulong p) + { + ModResidueTracker tracker = AcquireResidueTracker(); + // Use ModResidueTracker with a small set of primes to pre-filter composite p. + tracker.BeginMerge(p); + // TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of + // running MergeOrAppend for every candidate and missing the cycle-accelerated early exits. + // Use the small prime list from PerfectNumbers.Core to the extent of sqrt(p) + var primes = PrimesGenerator.SmallPrimes; + var primesPow2 = PrimesGenerator.SmallPrimesPow2; + int len = primes.Length; + for (int primeIndex = 0; primeIndex < len; primeIndex++) + { + if (primesPow2[primeIndex] > p) + { + break; + } + + if (tracker.MergeOrAppend(p, primes[primeIndex], out bool divisible) && divisible) + { + return true; + } + } + + return false; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void InitializeState(ulong startPrime, ulong remainder) { diff --git a/EvenPerfectBitScanner/IO/CalculationResultsFile.cs b/EvenPerfectBitScanner/IO/CalculationResultsFile.cs new file mode 100644 index 0000000..e716002 --- /dev/null +++ b/EvenPerfectBitScanner/IO/CalculationResultsFile.cs @@ -0,0 +1,197 @@ +using Open.Collections; +using PerfectNumbers.Core; +using PerfectNumbers.Core.Gpu; +using EvenPerfectBitScanner; + +namespace EvenPerfectBitScanner.IO; + +internal static class CalculationResultsFile +{ + internal static List LoadCandidatesWithinRange(string candidateFile, UInt128 maxPrimeLimit, bool maxPrimeConfigured, out int skippedByLimit) + { + List candidates = []; + skippedByLimit = 0; + using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader reader = new(readStream); + string? line; + ReadOnlySpan span; + while ((line = reader.ReadLine()) is not null) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + span = line.AsSpan().Trim(); + if (span.IsEmpty || span[0] == '#') + { + continue; + } + int index = 0; + while (index < span.Length) + { + if (!char.IsDigit(span[index])) + { + index++; + continue; + } + + int start = index; + index++; + while (index < span.Length && char.IsDigit(span[index])) + { + index++; + } + + if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed)) + { + if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit) + { + candidates.Add(parsed); + } + else + { + skippedByLimit++; + } + } + } + } + + return candidates; + } + + internal static void EnumerateCandidates(string resultsFileName, Action lineProcessorAction) + { + using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader reader = new(readStream); + string? line; + bool headerSkipped = false; + ReadOnlySpan span; + int first = 0; + int second = 0; + int third = 0; + ulong parsedP = 0UL; + ReadOnlySpan detailedSpan = default; + ReadOnlySpan passedAllTestsSpan = default; + bool detailed = false; + bool passedAllTests = false; + while ((line = reader.ReadLine()) != null) + { + if (!headerSkipped) + { + headerSkipped = true; + continue; + } + + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + span = line.AsSpan(); + first = span.IndexOf(','); + if (first < 0) + { + continue; + } + parsedP = Utf8CliParser.ParseUInt64(span[..first]); + span = span[(first + 1)..]; + second = span.IndexOf(','); + if (second < 0) + { + continue; + } + + span = span[(second + 1)..]; + third = span.IndexOf(','); + if (third < 0) + { + continue; + } + + detailedSpan = span[..third]; + passedAllTestsSpan = span[(third + 1)..]; + + if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests)) + { + lineProcessorAction(parsedP, detailed, passedAllTests); + } + } + } + + internal static string BuildFileName( + bool bitInc, + int threads, + int block, + GpuKernelType kernelType, + bool useLucasFlag, + bool useDivisorFlag, + bool useByDivisorFlag, + bool mersenneOnGpu, + bool useOrder, + bool useGcd, + NttBackend nttBackend, + int gpuPrimeThreads, + int llSlice, + int gpuScanBatch, + ulong warmupLimit, + ModReductionMode reduction, + string mersenneDevice, + string primesDevice, + string orderDevice) + { + string inc = bitInc ? "bit" : "add"; + string mers = useDivisorFlag + ? "divisor" + : (useLucasFlag + ? "lucas" + : (useByDivisorFlag + ? "bydivisor" + : (kernelType == GpuKernelType.Pow2Mod ? "pow2mod" : "incremental"))); + string ntt = nttBackend == NttBackend.Staged ? "staged" : "reference"; + string red = reduction switch { ModReductionMode.Mont64 => "mont64", ModReductionMode.Barrett128 => "barrett128", ModReductionMode.GpuUInt128 => "uint128", _ => "auto" }; + string order = useOrder ? "order-on" : "order-off"; + string gcd = useGcd ? "gcd-on" : "gcd-off"; + Span initialBuffer = stackalloc char[256]; + var builder = new PooledValueStringBuilder(initialBuffer); + try + { + builder.Append("even_perfect_bit_scan_inc-"); + builder.Append(inc); + builder.Append("_thr-"); + builder.Append(threads); + builder.Append("_blk-"); + builder.Append(block); + builder.Append("_mers-"); + builder.Append(mers); + builder.Append("_mersdev-"); + builder.Append(mersenneDevice); + builder.Append("_ntt-"); + builder.Append(ntt); + builder.Append("_red-"); + builder.Append(red); + builder.Append("_primesdev-"); + builder.Append(primesDevice); + builder.Append('_'); + builder.Append(order); + builder.Append("_orderdev-"); + builder.Append(orderDevice); + builder.Append('_'); + builder.Append(gcd); + builder.Append("_gputh-"); + builder.Append(gpuPrimeThreads); + builder.Append("_llslice-"); + builder.Append(llSlice); + builder.Append("_scanb-"); + builder.Append(gpuScanBatch); + builder.Append("_warm-"); + builder.Append(warmupLimit); + builder.Append(".csv"); + return builder.ToString(); + } + finally + { + builder.Dispose(); + } + } +} diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index fe848b5..a451ce3 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using EvenPerfectBitScanner.Candidates; +using EvenPerfectBitScanner.IO; using Open.Collections; using PerfectNumbers.Core; using PerfectNumbers.Core.Cpu; @@ -13,9 +14,9 @@ internal static class Program { private static ThreadLocal PrimeTesters = null!; private static ThreadLocal MersenneTesters = null!; - private static ThreadLocal PResidue = null!; // p mod d tracker (per-thread) private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP; private static MersenneNumberDivisorGpuTester? _divisorTester; + private static UInt128 _divisor; private static IMersenneNumberDivisorByDivisorTester? _byDivisorTester; private static Dictionary? _byDivisorPreviousResults; private static bool _limitReached; @@ -38,6 +39,7 @@ internal static class Program } _cliArguments = parsedArguments; + _divisor = UInt128.Zero; if (_cliArguments.ShowHelp) { @@ -177,7 +179,7 @@ internal static class Program blockSize = 1; } - PResidue = new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, initialNumber: currentP, initialized: true), trackAllValues: true); + CandidatesCalculator.Initialize(currentP); PrimeTesters = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); // Note: --primes-device controls default device for library kernels; p primality remains CPU here. // Initialize per-thread p residue tracker (Identity model) at currentP @@ -243,7 +245,7 @@ internal static class Program Console.WriteLine("Initialization..."); // Compose a results file name that encodes configuration (before opening file) - var builtName = BuildResultsFileName( + var builtName = CalculationResultsFile.BuildFileName( useBitTransform, threadCount, blockSize, @@ -287,7 +289,7 @@ internal static class Program if (File.Exists(resultsFileName)) { Console.WriteLine("Processing previous results..."); - LoadResultsFile(resultsFileName, (p, detailedCheck, passedAllTests) => + CalculationResultsFile.EnumerateCandidates(resultsFileName, (p, detailedCheck, passedAllTests) => { CalculationResultHandler.RegisterExistingResult(detailedCheck, passedAllTests); @@ -319,7 +321,7 @@ internal static class Program { Console.WriteLine("Loading filter..."); int skippedByLimit = 0; - byDivisorCandidates = LoadByDivisorCandidates(filterFile, maxPrimeLimit, maxPrimeConfigured, out skippedByLimit); + byDivisorCandidates = CalculationResultsFile.LoadCandidatesWithinRange(filterFile, maxPrimeLimit, maxPrimeConfigured, out skippedByLimit); if (maxPrimeConfigured) { if (skippedByLimit > 0) @@ -344,7 +346,7 @@ internal static class Program filterCount = 0; bool addedWithinLimit = false; int skippedByLimit = 0; - LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) => + CalculationResultsFile.EnumerateCandidates(filterFile, (p, detailedCheck, passedAllTests) => { if (!passedAllTests) { @@ -546,179 +548,6 @@ internal static class Program } } - // TODO: Move this to a new static class CalculationResultsFile under IO namespace / folder. Rename it to LoadCandidatesWithinRange. - private static List LoadByDivisorCandidates(string candidateFile, UInt128 maxPrimeLimit, bool maxPrimeConfigured, out int skippedByLimit) - { - List candidates = []; - skippedByLimit = 0; - using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new(readStream); - string? line; - ReadOnlySpan span; - while ((line = reader.ReadLine()) is not null) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - span = line.AsSpan().Trim(); - if (span.IsEmpty || span[0] == '#') - { - continue; - } - int index = 0; - while (index < span.Length) - { - if (!char.IsDigit(span[index])) - { - index++; - continue; - } - - int start = index; - index++; - while (index < span.Length && char.IsDigit(span[index])) - { - index++; - } - - if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed)) - { - if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit) - { - candidates.Add(parsed); - } - else - { - skippedByLimit++; - } - } - } - } - - return candidates; - } - - // TODO: Move this to a new static class CalculationResultsFile under IO namespace / folder. Rename it to EnumerateCandidates. - private static void LoadResultsFile(string resultsFileName, Action lineProcessorAction) - { - using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new(readStream); - string? line; - bool headerSkipped = false; - ReadOnlySpan span; - int first = 0; - int second = 0; - int third = 0; - ulong parsedP = 0UL; - ReadOnlySpan detailedSpan = default; - ReadOnlySpan passedAllTestsSpan = default; - bool detailed = false; - bool passedAllTests = false; - while ((line = reader.ReadLine()) != null) - { - if (!headerSkipped) - { - headerSkipped = true; - continue; - } - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - span = line.AsSpan(); - first = span.IndexOf(','); - if (first < 0) - { - continue; - } - parsedP = Utf8CliParser.ParseUInt64(span[..first]); - span = span[(first + 1)..]; - second = span.IndexOf(','); - if (second < 0) - { - continue; - } - - span = span[(second + 1)..]; - third = span.IndexOf(','); - if (third < 0) - { - continue; - } - - detailedSpan = span[..third]; - passedAllTestsSpan = span[(third + 1)..]; - - if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests)) - { - lineProcessorAction(parsedP, detailed, passedAllTests); - } - } - } - - // TODO: Move this to a new static class CalculationResultsFile under IO namespace / folder. Rename it to BuildFileName. - private static string BuildResultsFileName(bool bitInc, int threads, int block, GpuKernelType kernelType, bool useLucasFlag, bool useDivisorFlag, bool useByDivisorFlag, bool mersenneOnGpu, bool useOrder, bool useGcd, NttBackend nttBackend, int gpuPrimeThreads, int llSlice, int gpuScanBatch, ulong warmupLimit, ModReductionMode reduction, string mersenneDevice, string primesDevice, string orderDevice) - { - string inc = bitInc ? "bit" : "add"; - string mers = useDivisorFlag - ? "divisor" - : (useLucasFlag - ? "lucas" - : (useByDivisorFlag - ? "bydivisor" - : (kernelType == GpuKernelType.Pow2Mod ? "pow2mod" : "incremental"))); - string ntt = nttBackend == NttBackend.Staged ? "staged" : "reference"; - string red = reduction switch { ModReductionMode.Mont64 => "mont64", ModReductionMode.Barrett128 => "barrett128", ModReductionMode.GpuUInt128 => "uint128", _ => "auto" }; - string order = useOrder ? "order-on" : "order-off"; - string gcd = useGcd ? "gcd-on" : "gcd-off"; - Span initialBuffer = stackalloc char[256]; - var builder = new PooledValueStringBuilder(initialBuffer); - try - { - builder.Append("even_perfect_bit_scan_inc-"); - builder.Append(inc); - builder.Append("_thr-"); - builder.Append(threads); - builder.Append("_blk-"); - builder.Append(block); - builder.Append("_mers-"); - builder.Append(mers); - builder.Append("_mersdev-"); - builder.Append(mersenneDevice); - builder.Append("_ntt-"); - builder.Append(ntt); - builder.Append("_red-"); - builder.Append(red); - builder.Append("_primesdev-"); - builder.Append(primesDevice); - builder.Append('_'); - builder.Append(order); - builder.Append("_orderdev-"); - builder.Append(orderDevice); - builder.Append('_'); - builder.Append(gcd); - builder.Append("_gputh-"); - builder.Append(gpuPrimeThreads); - builder.Append("_llslice-"); - builder.Append(llSlice); - builder.Append("_scanb-"); - builder.Append(gpuScanBatch); - builder.Append("_warm-"); - builder.Append(warmupLimit); - builder.Append(".csv"); - return builder.ToString(); - } - finally - { - builder.Dispose(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void OnPrimeCandidateConfirmed() { @@ -784,7 +613,7 @@ internal static class Program } // Fast residue-based composite check for p using small primes - if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && IsCompositeByResidues(p)) + if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && CandidatesCalculator.IsCompositeByResidues(p)) { searchedMersenne = false; detailedCheck = false; @@ -807,6 +636,14 @@ internal static class Program } searchedMersenne = true; + if (_cliArguments.UseDivisor) + { + bool divisorsExhausted; + bool isPrime = _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out divisorsExhausted); + detailedCheck = divisorsExhausted; + return isPrime; + } + if (_cliArguments.UseByDivisor) { // Windowed pow2mod kernel handles by-divisor scans. @@ -817,33 +654,4 @@ internal static class Program return detailedCheck; } - // TODO: Move this together with the tracker to existing CandidatesCalculator class in the Candidates folder. - // Use ModResidueTracker with a small set of primes to pre-filter composite p. - private static bool IsCompositeByResidues(ulong p) - { - var tracker = PResidue.Value!; - // TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of - // running MergeOrAppend for every candidate and missing the cycle-accelerated early exits. - tracker.BeginMerge(p); - // Use the small prime list from PerfectNumbers.Core to the extent of sqrt(p) - var primes = PrimesGenerator.SmallPrimes; - var primesPow2 = PrimesGenerator.SmallPrimesPow2; - int len = primes.Length; - int primeIndex = 0; - bool divisible; - for (; primeIndex < len; primeIndex++) - { - if (primesPow2[primeIndex] > p) - { - break; - } - - if (tracker.MergeOrAppend(p, primes[primeIndex], out divisible) && divisible) - { - return true; - } - } - - return false; } -}