diff --git a/EvenPerfectBitScanner/CliArguments.cs b/EvenPerfectBitScanner/CliArguments.cs index 56dda07..50f5c90 100644 --- a/EvenPerfectBitScanner/CliArguments.cs +++ b/EvenPerfectBitScanner/CliArguments.cs @@ -5,6 +5,16 @@ using PerfectNumbers.Core.Gpu; namespace EvenPerfectBitScanner; + internal enum ByDivisorKIncrementMode + { + Sequential, + Pow2Groups, + Predictive, + Percentile, + Additive, + TopDown, +} + internal readonly struct CliArguments { private const string DefaultCyclesPath = "divisor_cycles.bin"; @@ -25,8 +35,7 @@ namespace EvenPerfectBitScanner; internal readonly bool UseResidue; internal readonly bool UseDivisor; internal readonly bool UseByDivisor; - internal readonly bool UseDivisorPow2Increment; - internal readonly bool UseDivisorPredictiveIncrement; + internal readonly ByDivisorKIncrementMode ByDivisorKIncrementMode; internal readonly int ByDivisorSpecialRange; internal readonly bool UseGcdFilter; internal readonly bool TestMode; @@ -76,8 +85,7 @@ namespace EvenPerfectBitScanner; bool useDivisor, bool useByDivisor, bool useGcdFilter, - bool useDivisorPow2Increment, - bool useDivisorPredictiveIncrement, + ByDivisorKIncrementMode byDivisorKIncrementMode, int byDivisorSpecialRange, bool testMode, bool useGpuCycles, @@ -124,8 +132,7 @@ namespace EvenPerfectBitScanner; UseResidue = useResidue; UseDivisor = useDivisor; UseByDivisor = useByDivisor; - UseDivisorPow2Increment = useDivisorPow2Increment; - UseDivisorPredictiveIncrement = useDivisorPredictiveIncrement; + ByDivisorKIncrementMode = byDivisorKIncrementMode; ByDivisorSpecialRange = byDivisorSpecialRange; UseGcdFilter = useGcdFilter; TestMode = testMode; @@ -178,8 +185,7 @@ namespace EvenPerfectBitScanner; bool useResidue = false; bool useDivisor = false; bool useByDivisor = false; - bool useDivisorPow2Increment = false; - bool useDivisorPredictiveIncrement = false; + ByDivisorKIncrementMode byDivisorKIncrementMode = ByDivisorKIncrementMode.Sequential; int byDivisorSpecialRange = 0; bool useGcdFilter = false; bool testMode = false; @@ -314,22 +320,31 @@ namespace EvenPerfectBitScanner; string value = argument["--bydivisor-k-increment=".Length..].Trim(); if (value.Equals("pow2groups", StringComparison.OrdinalIgnoreCase)) { - useDivisorPow2Increment = true; - useDivisorPredictiveIncrement = false; + byDivisorKIncrementMode = ByDivisorKIncrementMode.Pow2Groups; } else if (value.Equals("sequential", StringComparison.OrdinalIgnoreCase)) { - useDivisorPow2Increment = false; - useDivisorPredictiveIncrement = false; + byDivisorKIncrementMode = ByDivisorKIncrementMode.Sequential; } else if (value.Equals("predictive", StringComparison.OrdinalIgnoreCase)) { - useDivisorPow2Increment = false; - useDivisorPredictiveIncrement = true; + byDivisorKIncrementMode = ByDivisorKIncrementMode.Predictive; + } + else if (value.Equals("percentile", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.Percentile; + } + else if (value.Equals("additive", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.Additive; + } + else if (value.Equals("topdown", StringComparison.OrdinalIgnoreCase)) + { + byDivisorKIncrementMode = ByDivisorKIncrementMode.TopDown; } else { - Console.WriteLine("Invalid value for --bydivisor-k-increment. Use sequential, pow2groups, or predictive."); + Console.WriteLine("Invalid value for --bydivisor-k-increment. Use sequential, pow2groups, predictive, percentile, additive, or topdown."); return default; } @@ -652,8 +667,7 @@ namespace EvenPerfectBitScanner; useDivisor, useByDivisor, useGcdFilter, - useDivisorPow2Increment, - useDivisorPredictiveIncrement, + byDivisorKIncrementMode, byDivisorSpecialRange, testMode, useGpuCycles, @@ -696,7 +710,7 @@ namespace EvenPerfectBitScanner; Console.WriteLine(" --increment=bit|add exponent increment method (default add)"); Console.WriteLine(" --threads= number of worker threads"); Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method"); - Console.WriteLine(" --bydivisor-k-increment=sequential|pow2groups|predictive k iteration strategy for --mersenne=bydivisor (default sequential)"); + Console.WriteLine(" --bydivisor-k-increment=sequential|pow2groups|predictive|percentile|additive|topdown k iteration strategy for --mersenne=bydivisor (default sequential)"); Console.WriteLine(" --bydivisor-special-range= widen pow2groups special values to +/- n percent (default 0 = points)"); Console.WriteLine(" --max-k= max k for Mersenne tests (q = 2*p*k + 1), including --mersenne=bydivisor"); Console.WriteLine(" --mersenne-device=cpu|gpu|hybrid Device for Mersenne method (default gpu)"); diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index fc9559f..2ae57a7 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -10,15 +10,25 @@ using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.ByDivisor; using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Accelerators; -using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder; -using MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForHybridOrder; -using MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForGpuOrder; +using PerfectNumbers.Core.Persistence; +using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForHybridOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForGpuOrder; using ByDivisorCpuOrderPow2 = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPow2GroupsDivisorSetForCpuOrder; using ByDivisorHybridOrderPow2 = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPow2GroupsDivisorSetForHybridOrder; using ByDivisorGpuOrderPow2 = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPow2GroupsDivisorSetForGpuOrder; using ByDivisorCpuOrderPredictive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForCpuOrder; using ByDivisorHybridOrderPredictive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForHybridOrder; using ByDivisorGpuOrderPredictive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForGpuOrder; +using ByDivisorCpuOrderPercentile = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForCpuOrder; +using ByDivisorHybridOrderPercentile = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForHybridOrder; +using ByDivisorGpuOrderPercentile = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForGpuOrder; +using ByDivisorCpuOrderAdditive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder; +using ByDivisorHybridOrderAdditive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder; +using ByDivisorGpuOrderAdditive = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder; +using ByDivisorCpuOrderTopDown = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForTopDownDivisorSetForCpuOrder; +using ByDivisorHybridOrderTopDown = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForTopDownDivisorSetForHybridOrder; +using ByDivisorGpuOrderTopDown = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForTopDownDivisorSetForGpuOrder; using System.Globalization; namespace EvenPerfectBitScanner; @@ -39,8 +49,18 @@ private static ThreadLocal? MersenneTesters; private static ByDivisorCpuOrderPredictive _byDivisorCpuOrderTesterPredictive; private static ByDivisorHybridOrderPredictive _byDivisorHybridOrderTesterPredictive; private static ByDivisorGpuOrderPredictive _byDivisorGpuOrderTesterPredictive; + private static ByDivisorCpuOrderPercentile _byDivisorCpuOrderTesterPercentile; + private static ByDivisorHybridOrderPercentile _byDivisorHybridOrderTesterPercentile; + private static ByDivisorGpuOrderPercentile _byDivisorGpuOrderTesterPercentile; + private static ByDivisorCpuOrderAdditive _byDivisorCpuOrderTesterAdditive; + private static ByDivisorHybridOrderAdditive _byDivisorHybridOrderTesterAdditive; + private static ByDivisorGpuOrderAdditive _byDivisorGpuOrderTesterAdditive; + private static ByDivisorCpuOrderTopDown _byDivisorCpuOrderTesterTopDown; + private static ByDivisorHybridOrderTopDown _byDivisorHybridOrderTesterTopDown; + private static ByDivisorGpuOrderTopDown _byDivisorGpuOrderTesterTopDown; private static Dictionary? _byDivisorPreviousResults; private static string? _byDivisorModelPath; + private static bool _byDivisorModeUsesModel; private static bool _limitReached; private static ulong _byDivisorStartPrime; private static CliArguments _cliArguments; @@ -87,7 +107,7 @@ private static ThreadLocal? MersenneTesters; EnvironmentConfiguration.GpuBatchSize = Math.Max(1, _cliArguments.ScanBatchSize); EnvironmentConfiguration.GpuRatio = _cliArguments.GpuRatio; EnvironmentConfiguration.OrderDevice = _cliArguments.OrderDevice; - EnvironmentConfiguration.UsePow2GroupDivisors = _cliArguments.UseDivisorPow2Increment; + EnvironmentConfiguration.UsePow2GroupDivisors = _cliArguments.ByDivisorKIncrementMode == ByDivisorKIncrementMode.Pow2Groups; EnvironmentConfiguration.ByDivisorSpecialRange = _cliArguments.ByDivisorSpecialRange; EnvironmentConfiguration.RollingAccelerators = Math.Min(Math.Min(PerfectNumberConstants.RollingAccelerators, gpuPrimeThreads), threadCount); EnvironmentConfiguration.Initialize(); @@ -289,51 +309,77 @@ private static ThreadLocal? MersenneTesters; throw new NotSupportedException("--mersenne-device=gpu is not supported for --mersenne=bydivisor on this execution path."); } - bool usePow2Divisors = _cliArguments.UseDivisorPow2Increment; - bool usePredictiveDivisors = _cliArguments.UseDivisorPredictiveIncrement; + ByDivisorKIncrementMode kIncrementMode = _cliArguments.ByDivisorKIncrementMode; if (EnvironmentConfiguration.UseCpuOrder) { - if (usePow2Divisors) + switch (kIncrementMode) { - _byDivisorCpuOrderTesterPow2 = new ByDivisorCpuOrderPow2(); - } - else if (usePredictiveDivisors) - { - _byDivisorCpuOrderTesterPredictive = new ByDivisorCpuOrderPredictive(); - } - else - { - _byDivisorCpuOrderTester = new MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder(); + case ByDivisorKIncrementMode.Pow2Groups: + _byDivisorCpuOrderTesterPow2 = new ByDivisorCpuOrderPow2(); + break; + case ByDivisorKIncrementMode.Predictive: + _byDivisorCpuOrderTesterPredictive = new ByDivisorCpuOrderPredictive(); + break; + case ByDivisorKIncrementMode.Percentile: + _byDivisorCpuOrderTesterPercentile = new ByDivisorCpuOrderPercentile(); + break; + case ByDivisorKIncrementMode.Additive: + _byDivisorCpuOrderTesterAdditive = new ByDivisorCpuOrderAdditive(); + break; + case ByDivisorKIncrementMode.TopDown: + _byDivisorCpuOrderTesterTopDown = new ByDivisorCpuOrderTopDown(); + break; + default: + _byDivisorCpuOrderTester = new MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder(); + break; } } else if (EnvironmentConfiguration.UseHybridOrder) { - if (usePow2Divisors) + switch (kIncrementMode) { - _byDivisorHybridOrderTesterPow2 = new ByDivisorHybridOrderPow2(); - } - else if (usePredictiveDivisors) - { - _byDivisorHybridOrderTesterPredictive = new ByDivisorHybridOrderPredictive(); - } - else - { - _byDivisorHybridOrderTester = new MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder(); + case ByDivisorKIncrementMode.Pow2Groups: + _byDivisorHybridOrderTesterPow2 = new ByDivisorHybridOrderPow2(); + break; + case ByDivisorKIncrementMode.Predictive: + _byDivisorHybridOrderTesterPredictive = new ByDivisorHybridOrderPredictive(); + break; + case ByDivisorKIncrementMode.Percentile: + _byDivisorHybridOrderTesterPercentile = new ByDivisorHybridOrderPercentile(); + break; + case ByDivisorKIncrementMode.Additive: + _byDivisorHybridOrderTesterAdditive = new ByDivisorHybridOrderAdditive(); + break; + case ByDivisorKIncrementMode.TopDown: + _byDivisorHybridOrderTesterTopDown = new ByDivisorHybridOrderTopDown(); + break; + default: + _byDivisorHybridOrderTester = new MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder(); + break; } } else { - if (usePow2Divisors) + switch (kIncrementMode) { - _byDivisorGpuOrderTesterPow2 = new ByDivisorGpuOrderPow2(); - } - else if (usePredictiveDivisors) - { - _byDivisorGpuOrderTesterPredictive = new ByDivisorGpuOrderPredictive(); - } - else - { - _byDivisorGpuOrderTester = new MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder(); + case ByDivisorKIncrementMode.Pow2Groups: + _byDivisorGpuOrderTesterPow2 = new ByDivisorGpuOrderPow2(); + break; + case ByDivisorKIncrementMode.Predictive: + _byDivisorGpuOrderTesterPredictive = new ByDivisorGpuOrderPredictive(); + break; + case ByDivisorKIncrementMode.Percentile: + _byDivisorGpuOrderTesterPercentile = new ByDivisorGpuOrderPercentile(); + break; + case ByDivisorKIncrementMode.Additive: + _byDivisorGpuOrderTesterAdditive = new ByDivisorGpuOrderAdditive(); + break; + case ByDivisorKIncrementMode.TopDown: + _byDivisorGpuOrderTesterTopDown = new ByDivisorGpuOrderTopDown(); + break; + default: + _byDivisorGpuOrderTester = new MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder(); + break; } } } @@ -437,25 +483,79 @@ private static ThreadLocal? MersenneTesters; if (useByDivisor) { - _byDivisorModelPath = Path.Combine(PerfectNumberConstants.ByDivisorStateDirectory, "bydivisor_k10_model.json"); + ByDivisorKIncrementMode kIncrementMode = _cliArguments.ByDivisorKIncrementMode; + _byDivisorModeUsesModel = kIncrementMode is ByDivisorKIncrementMode.Predictive or ByDivisorKIncrementMode.Percentile or ByDivisorKIncrementMode.Additive; var byDivisorTuning = new ByDivisorScanTuning { CheapLimit = EnvironmentConfiguration.ByDivisorCheapKLimit, }; EnvironmentConfiguration.ByDivisorScanTuning = byDivisorTuning; - bool modelExists = File.Exists(_byDivisorModelPath); - bool resultsExist = File.Exists(resultsFileName); - bool needsBuild = !modelExists || (resultsExist && File.GetLastWriteTimeUtc(resultsFileName) > File.GetLastWriteTimeUtc(_byDivisorModelPath)); - Console.WriteLine(needsBuild - ? $"Building by-divisor class model from {resultsFileName}..." - : $"Loading by-divisor class model from {_byDivisorModelPath}..."); - EnvironmentConfiguration.ByDivisorClassModel = ByDivisorClassModelLoader.LoadOrBuild( - _byDivisorModelPath, - resultsFileName, - byDivisorTuning, - byDivisorTuning.CheapLimit, - rebuildWhenResultsNewer: true); - Console.WriteLine($"By-divisor class model ready (cheap<={byDivisorTuning.CheapLimit:F0})."); + + EnvironmentConfiguration.ByDivisorClassModel = null; + EnvironmentConfiguration.ByDivisorClassModelRepository = null; + if (_byDivisorModeUsesModel) + { + string modelRepoFile = "bydivisor_model.faster"; + var modelRepo = ByDivisorClassModelRepository.Open(PerfectNumberConstants.ByDivisorStateDirectory, modelRepoFile); + EnvironmentConfiguration.ByDivisorClassModelRepository = modelRepo; + if (!modelRepo.TryLoad(out var loadedModel) || loadedModel is null) + { + _byDivisorModelPath = Path.Combine(PerfectNumberConstants.ByDivisorStateDirectory, "bydivisor_k10_model.json"); + Console.WriteLine($"Building by-divisor class model from {resultsFileName}..."); + ByDivisorClassModel model = ByDivisorClassModelLoader.LoadOrBuild( + _byDivisorModelPath, + resultsFileName, + byDivisorTuning, + byDivisorTuning.CheapLimit, + rebuildWhenResultsNewer: true); + modelRepo.Save(model); + modelRepo.Commit(); + try + { + File.Delete(_byDivisorModelPath); + } + catch + { + // best-effort cleanup + } + EnvironmentConfiguration.ByDivisorClassModel = model; + Console.WriteLine($"By-divisor class model ready (cheap<={byDivisorTuning.CheapLimit:F0})."); + } + else + { + loadedModel.CheapLimit = byDivisorTuning.CheapLimit; + EnvironmentConfiguration.ByDivisorClassModel = loadedModel; + Console.WriteLine($"By-divisor class model loaded from LMDB (cheap<={byDivisorTuning.CheapLimit:F0})."); + } + } + + // Initialize LMDB repositories for by-divisor state + string checksDir = PerfectNumberConstants.ByDivisorStateDirectory; + string kRepoFile = kIncrementMode switch + { + ByDivisorKIncrementMode.Pow2Groups => "pow2groups.kstate.faster", + ByDivisorKIncrementMode.Predictive => "predictive.kstate.faster", + ByDivisorKIncrementMode.Percentile => "percentile.kstate.faster", + ByDivisorKIncrementMode.Additive => "additive.kstate.faster", + ByDivisorKIncrementMode.TopDown => "topdown.kstate.faster", + _ => "sequential.kstate.faster", + }; + string lmdbPath = Path.Combine(checksDir, Path.ChangeExtension(kRepoFile, ".lmdb")); + EnvironmentConfiguration.ByDivisorKStateRepository = KStateRepository.Open(checksDir, kRepoFile, lmdbPath, checksDir); + EnvironmentConfiguration.ByDivisorCheapKStateRepository = KStateRepository.Open(checksDir, "cheaplimit.kstate.faster", Path.Combine(checksDir, "cheaplimit.kstate.lmdb"), checksDir); + string pow2MinusLmdb = Path.Combine(checksDir, "pow2minus1.lmdb"); + EnvironmentConfiguration.ByDivisorPow2Minus1Repository ??= Pow2Minus1StateRepository.Open(Path.Combine(checksDir, "pow2minus1.faster"), pow2MinusLmdb, checksDir); + + if (kIncrementMode == ByDivisorKIncrementMode.Pow2Groups) + { + EnvironmentConfiguration.ByDivisorSpecialStateRepository = KStateRepository.Open(checksDir, "pow2groups.special.kstate.faster", Path.Combine(checksDir, "pow2groups.special.kstate.lmdb"), checksDir); + EnvironmentConfiguration.ByDivisorGroupsStateRepository = KStateRepository.Open(checksDir, "pow2groups.groups.kstate.faster", Path.Combine(checksDir, "pow2groups.groups.kstate.lmdb"), checksDir); + } + else + { + EnvironmentConfiguration.ByDivisorSpecialStateRepository = null; + EnvironmentConfiguration.ByDivisorGroupsStateRepository = null; + } } bool useFilter = !testMode && !string.IsNullOrEmpty(filterFile); @@ -573,138 +673,215 @@ private static ThreadLocal? MersenneTesters; if (useByDivisor) { int byDivisorPrimeRange = Math.Max(1, _cliArguments.BlockSize); - bool usePow2Divisors = _cliArguments.UseDivisorPow2Increment; - bool usePredictiveDivisors = _cliArguments.UseDivisorPredictiveIncrement; + ByDivisorKIncrementMode kIncrementMode = _cliArguments.ByDivisorKIncrementMode; if (EnvironmentConfiguration.UseCpuOrder) { - if (usePow2Divisors) + switch (kIncrementMode) { - Console.WriteLine("Using by-divisor pow2groups increment (CPU order)."); - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorCpuOrderTesterPow2, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); - } - else if (usePredictiveDivisors) - { - Console.WriteLine("Using by-divisor predictive increment (CPU order)."); - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorCpuOrderTesterPredictive, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); - } - else - { - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorCpuOrderTester, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); + case ByDivisorKIncrementMode.Pow2Groups: + Console.WriteLine("Using by-divisor pow2groups increment (CPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorCpuOrderTesterPow2, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Predictive: + Console.WriteLine("Using by-divisor predictive increment (CPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorCpuOrderTesterPredictive, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Percentile: + Console.WriteLine("Using by-divisor percentile increment (CPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorCpuOrderTesterPercentile, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Additive: + Console.WriteLine("Using by-divisor additive increment (CPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorCpuOrderTesterAdditive, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + default: + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorCpuOrderTester, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; } } else if (EnvironmentConfiguration.UseHybridOrder) { - if (usePow2Divisors) + switch (kIncrementMode) { - Console.WriteLine("Using by-divisor pow2groups increment (Hybrid order)."); - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorHybridOrderTesterPow2, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); - } - else if (usePredictiveDivisors) - { - Console.WriteLine("Using by-divisor predictive increment (Hybrid order)."); - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorHybridOrderTesterPredictive, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); - } - else - { - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorHybridOrderTester, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); + case ByDivisorKIncrementMode.Pow2Groups: + Console.WriteLine("Using by-divisor pow2groups increment (Hybrid order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorHybridOrderTesterPow2, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Predictive: + Console.WriteLine("Using by-divisor predictive increment (Hybrid order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorHybridOrderTesterPredictive, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Percentile: + Console.WriteLine("Using by-divisor percentile increment (Hybrid order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorHybridOrderTesterPercentile, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Additive: + Console.WriteLine("Using by-divisor additive increment (Hybrid order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorHybridOrderTesterAdditive, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + default: + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorHybridOrderTester, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; } } else { - if (usePow2Divisors) + switch (kIncrementMode) { - Console.WriteLine("Using by-divisor pow2groups increment (GPU order)."); - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorGpuOrderTesterPow2, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); - } - else if (usePredictiveDivisors) - { - Console.WriteLine("Using by-divisor predictive increment (GPU order)."); - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorGpuOrderTesterPredictive, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); - } - else - { - MersenneNumberDivisorByDivisorTester.Run( - byDivisorCandidates, - ref _byDivisorGpuOrderTester, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - HandleByDivisorResult, - threadCount, - byDivisorPrimeRange); + case ByDivisorKIncrementMode.Pow2Groups: + Console.WriteLine("Using by-divisor pow2groups increment (GPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorGpuOrderTesterPow2, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Predictive: + Console.WriteLine("Using by-divisor predictive increment (GPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorGpuOrderTesterPredictive, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Percentile: + Console.WriteLine("Using by-divisor percentile increment (GPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorGpuOrderTesterPercentile, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + case ByDivisorKIncrementMode.Additive: + Console.WriteLine("Using by-divisor additive increment (GPU order)."); + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorGpuOrderTesterAdditive, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; + default: + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + ref _byDivisorGpuOrderTester, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + HandleByDivisorResult, + threadCount, + byDivisorPrimeRange); + break; } } @@ -1092,25 +1269,43 @@ private static ThreadLocal? MersenneTesters; if (_cliArguments.UseByDivisor) { // Windowed pow2mod kernel handles by-divisor scans. - bool usePow2Divisors = _cliArguments.UseDivisorPow2Increment; + ByDivisorKIncrementMode mode = _cliArguments.ByDivisorKIncrementMode; if (EnvironmentConfiguration.UseCpuOrder) - { - return usePow2Divisors - ? _byDivisorCpuOrderTesterPow2.IsPrime(p, out detailedCheck, out _) - : _byDivisorCpuOrderTester.IsPrime(p, out detailedCheck, out _); - } + { + return mode switch + { + ByDivisorKIncrementMode.Pow2Groups => _byDivisorCpuOrderTesterPow2.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Predictive => _byDivisorCpuOrderTesterPredictive.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Percentile => _byDivisorCpuOrderTesterPercentile.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Additive => _byDivisorCpuOrderTesterAdditive.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.TopDown => _byDivisorCpuOrderTesterTopDown.IsPrime(p, out detailedCheck, out _), + _ => _byDivisorCpuOrderTester.IsPrime(p, out detailedCheck, out _), + }; + } - if (EnvironmentConfiguration.UseHybridOrder) - { - return usePow2Divisors - ? _byDivisorHybridOrderTesterPow2.IsPrime(p, out detailedCheck, out _) - : _byDivisorHybridOrderTester.IsPrime(p, out detailedCheck, out _); - } + if (EnvironmentConfiguration.UseHybridOrder) + { + return mode switch + { + ByDivisorKIncrementMode.Pow2Groups => _byDivisorHybridOrderTesterPow2.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Predictive => _byDivisorHybridOrderTesterPredictive.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Percentile => _byDivisorHybridOrderTesterPercentile.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Additive => _byDivisorHybridOrderTesterAdditive.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.TopDown => _byDivisorHybridOrderTesterTopDown.IsPrime(p, out detailedCheck, out _), + _ => _byDivisorHybridOrderTester.IsPrime(p, out detailedCheck, out _), + }; + } - return usePow2Divisors - ? _byDivisorGpuOrderTesterPow2.IsPrime(p, out detailedCheck, out _) - : _byDivisorGpuOrderTester.IsPrime(p, out detailedCheck, out _); - } + return mode switch + { + ByDivisorKIncrementMode.Pow2Groups => _byDivisorGpuOrderTesterPow2.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Predictive => _byDivisorGpuOrderTesterPredictive.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Percentile => _byDivisorGpuOrderTesterPercentile.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.Additive => _byDivisorGpuOrderTesterAdditive.IsPrime(p, out detailedCheck, out _), + ByDivisorKIncrementMode.TopDown => _byDivisorGpuOrderTesterTopDown.IsPrime(p, out detailedCheck, out _), + _ => _byDivisorGpuOrderTester.IsPrime(p, out detailedCheck, out _), + }; + } detailedCheck = IsMersennePrime(gpu, p); return detailedCheck; @@ -1119,7 +1314,7 @@ private static ThreadLocal? MersenneTesters; private static void HandleByDivisorResult(ulong candidate, bool searchedMersenne, bool detailedCheck, bool passedAllTests, BigInteger divisor) { CalculationResultHandler.HandleResult(candidate, divisor, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); - if (divisor.IsZero || _byDivisorModelPath is null || EnvironmentConfiguration.ByDivisorClassModel is null) + if (divisor.IsZero || !_byDivisorModeUsesModel || EnvironmentConfiguration.ByDivisorClassModel is null) { return; } @@ -1129,14 +1324,14 @@ private static ThreadLocal? MersenneTesters; private static void TryRefreshByDivisorModel() { - string? modelPath = _byDivisorModelPath; - if (modelPath is null) + if (!_byDivisorModeUsesModel) { return; } + var repository = EnvironmentConfiguration.ByDivisorClassModelRepository; string resultsPath = CalculationResultHandler.ResultsFileName; - if (string.IsNullOrEmpty(resultsPath)) + if (repository is null || string.IsNullOrEmpty(resultsPath)) { return; } @@ -1165,13 +1360,25 @@ private static ThreadLocal? MersenneTesters; { double cheapLimit = EnvironmentConfiguration.ByDivisorCheapKLimit; Console.WriteLine("Refreshing by-divisor class model from results..."); - ByDivisorClassModelLoader.RebuildAndSave(modelPath, resultsPath, cheapLimit); - EnvironmentConfiguration.ByDivisorClassModel = ByDivisorClassModelLoader.LoadOrBuild( - modelPath, + string tempModelPath = Path.Combine(PerfectNumberConstants.ByDivisorStateDirectory, "bydivisor_k10_model.json"); + ByDivisorClassModelLoader.RebuildAndSave(tempModelPath, resultsPath, cheapLimit); + ByDivisorClassModel refreshed = ByDivisorClassModelLoader.LoadOrBuild( + tempModelPath, resultsPath, EnvironmentConfiguration.ByDivisorScanTuning, cheapLimit, rebuildWhenResultsNewer: false); + repository.Save(refreshed); + repository.Commit(); + EnvironmentConfiguration.ByDivisorClassModel = refreshed; + try + { + File.Delete(tempModelPath); + } + catch + { + // ignore cleanup errors + } Console.WriteLine($"By-divisor class model refreshed (cheap<={cheapLimit:F0})."); } catch (Exception ex) diff --git a/PerfectNumbers.Core.Tests/ByDivisorClassModelTests.cs b/PerfectNumbers.Core.Tests/ByDivisorClassModelTests.cs index 6d1855c..ea2627d 100644 --- a/PerfectNumbers.Core.Tests/ByDivisorClassModelTests.cs +++ b/PerfectNumbers.Core.Tests/ByDivisorClassModelTests.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Numerics; using FluentAssertions; using PerfectNumbers.Core.ByDivisor; @@ -90,6 +91,70 @@ public sealed class ByDivisorClassModelTests plan.Rho2.Should().Be(1.8d); } + [Fact] + public void BuildPlan_exposes_percentiles_and_additive_delta() + { + var model = new ByDivisorClassModel + { + CheapLimit = 10_000, + }; + var entries = new ByDivisorClassEntry[1024]; + entries[0] = new ByDivisorClassEntry + { + K10 = 10d, + K25 = 20d, + K50 = 30d, + K75 = 40d, + GapProbability = 0.1d, + Stability = "stable", + }; + model.Entries = entries; + + ByDivisorScanTuning tuning = ByDivisorScanTuning.Default; + + ByDivisorScanPlan plan = model.BuildPlan(0UL, BigInteger.Zero, tuning); + + plan.K10.Should().Be(10d); + plan.K25.Should().Be(20d); + plan.K50.Should().Be(30d); + plan.K75.Should().Be(40d); + plan.Start.Should().Be(24d); // ceil(0.8 * 30) + plan.Target.Should().Be(48d); // ceil(1.2 * 40) + plan.DeltaK.Should().Be(4d); // ceil(24 * 0.15) + } + + [Fact] + public void BuildPlan_clamps_all_ranges_to_allowed_max() + { + var model = new ByDivisorClassModel + { + CheapLimit = 1000d, + }; + var entries = new ByDivisorClassEntry[1024]; + entries[0] = new ByDivisorClassEntry + { + K10 = 50d, + K25 = 60d, + K50 = 70d, + K75 = 80d, + GapProbability = 0.1d, + }; + model.Entries = entries; + + ByDivisorScanTuning tuning = ByDivisorScanTuning.Default; + // allowedMax = 41 => maxKAllowed = (41-1)/(2*5) = 4 + ByDivisorScanPlan plan = model.BuildPlan(5UL, new BigInteger(41), tuning); + + plan.CheapLimit.Should().Be(4d); + plan.K10.Should().Be(4d); + plan.K25.Should().Be(4d); + plan.K50.Should().Be(4d); + plan.K75.Should().Be(4d); + plan.Start.Should().Be(4d); + plan.Target.Should().Be(4d); + plan.DeltaK.Should().Be(4d); + } + [Fact] public void LoadOrBuild_rebuilds_when_results_newer_than_model() { diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs index c07a042..ee230e9 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs @@ -2,7 +2,7 @@ using System.Numerics; using System.Reflection; using FluentAssertions; using PerfectNumbers.Core.Cpu; -using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder; using Xunit; namespace PerfectNumbers.Core.Tests; diff --git a/PerfectNumbers.Core/ByDivisor/ByDivisorClassModel.cs b/PerfectNumbers.Core/ByDivisor/ByDivisorClassModel.cs index 49099bc..acf435a 100644 --- a/PerfectNumbers.Core/ByDivisor/ByDivisorClassModel.cs +++ b/PerfectNumbers.Core/ByDivisor/ByDivisorClassModel.cs @@ -11,6 +11,8 @@ namespace PerfectNumbers.Core.ByDivisor; public sealed class ByDivisorClassEntry { + public double K10 { get; set; } + public double K25 { get; set; } public double K50 { get; set; } public double K75 { get; set; } public double GapProbability { get; set; } @@ -26,6 +28,11 @@ public sealed class ByDivisorScanPlan public double Target { get; init; } public double Rho1 { get; init; } public double Rho2 { get; init; } + public double K10 { get; init; } + public double K25 { get; init; } + public double K50 { get; init; } + public double K75 { get; init; } + public double DeltaK { get; init; } } public sealed class ByDivisorScanTuning @@ -39,6 +46,7 @@ public sealed class ByDivisorScanTuning public double Rho2 { get; init; } = 1.6d; public double AggressiveRho2 { get; init; } = 1.8d; public double HardGapThreshold { get; init; } = 0.75d; + public double DeltaFraction { get; init; } = 0.15d; [JsonIgnore] public static ByDivisorScanTuning Default => new(); @@ -47,10 +55,10 @@ public sealed class ByDivisorScanTuning public sealed class ByDivisorClassModel { private const int ClassCount = 1024; - public int Version { get; set; } = 1; + public int Version { get; set; } = 2; public double CheapLimit { get; set; } = 10_000d; public ByDivisorClassEntry[] Entries { get; set; } = Enumerable.Range(0, ClassCount) - .Select(_ => new ByDivisorClassEntry { K50 = 1d, K75 = 1d }) + .Select(_ => new ByDivisorClassEntry { K10 = 1d, K25 = 1d, K50 = 1d, K75 = 1d }) .ToArray(); public ByDivisorScanPlan BuildPlan(ulong prime, BigInteger allowedMax, in ByDivisorScanTuning tuning) @@ -58,6 +66,8 @@ public sealed class ByDivisorClassModel int suffix = (int)(prime & 1023UL); ByDivisorClassEntry entry = Entries[suffix]; + double k10 = entry.K10 > 1d ? entry.K10 : 1d; + double k25 = entry.K25 > 1d ? entry.K25 : Math.Max(k10, 1d); double k50 = entry.K50 > 1d ? entry.K50 : 1d; double k75 = entry.K75 > 1d ? entry.K75 : Math.Max(k50, 1d); bool isHard = entry.GapProbability >= tuning.HardGapThreshold || string.Equals(entry.Stability, "escaping", StringComparison.OrdinalIgnoreCase); @@ -69,6 +79,7 @@ public sealed class ByDivisorClassModel double cheap = tuning.CheapLimit > 1d ? tuning.CheapLimit : CheapLimit; double rho2 = isHard ? tuning.AggressiveRho2 : tuning.Rho2; + double deltaK = Math.Max(1d, Math.Ceiling(start * Math.Max(0d, tuning.DeltaFraction))); double maxKAllowed = ComputeMaxKAllowed(prime, allowedMax); if (maxKAllowed > 0d) @@ -76,6 +87,11 @@ public sealed class ByDivisorClassModel cheap = Math.Min(cheap, maxKAllowed); start = Math.Min(start, maxKAllowed); target = Math.Min(target, maxKAllowed); + k10 = Math.Min(k10, maxKAllowed); + k25 = Math.Min(k25, maxKAllowed); + k50 = Math.Min(k50, maxKAllowed); + k75 = Math.Min(k75, maxKAllowed); + deltaK = Math.Min(deltaK, maxKAllowed); } return new ByDivisorScanPlan @@ -85,6 +101,11 @@ public sealed class ByDivisorClassModel Target = target, Rho1 = tuning.Rho1, Rho2 = rho2, + K10 = k10, + K25 = k25, + K50 = k50, + K75 = k75, + DeltaK = deltaK, }; } @@ -149,6 +170,7 @@ public static class ByDivisorClassModelLoader private static void Save(string modelPath, ByDivisorClassModel model) { + string tempPath = modelPath + ".tmp"; string? directory = Path.GetDirectoryName(modelPath); if (!string.IsNullOrEmpty(directory)) { @@ -160,10 +182,12 @@ public static class ByDivisorClassModelLoader WriteIndented = true, }; string json = JsonSerializer.Serialize(model, options); - string tempPath = modelPath + ".tmp"; - File.WriteAllText(tempPath, json); - File.Copy(tempPath, modelPath, overwrite: true); - File.Delete(tempPath); + lock (IoLock) + { + File.WriteAllText(tempPath, json); + File.Copy(tempPath, modelPath, overwrite: true); + File.Delete(tempPath); + } } private static ByDivisorClassModel BuildFromResults(string resultsPath, double cheapLimit) @@ -251,11 +275,15 @@ public static class ByDivisorClassModelLoader for (int i = 0; i < 1024; i++) { List? classSamples = samples[i]; + double k10 = 1d; + double k25 = 1d; double k50 = 1d; double k75 = 1d; if (classSamples is not null && classSamples.Count > 0) { classSamples.Sort(); + k10 = ComputePercentile(classSamples, 0.10d); + k25 = ComputePercentile(classSamples, 0.25d); k50 = ComputePercentile(classSamples, 0.5d); k75 = ComputePercentile(classSamples, 0.75d); } @@ -266,6 +294,8 @@ public static class ByDivisorClassModelLoader model.Entries[i] = new ByDivisorClassEntry { + K10 = k10, + K25 = k25, K50 = k50, K75 = k75, GapProbability = gapProb, @@ -341,4 +371,6 @@ public static class ByDivisorClassModelLoader double fraction = index - lower; return values[lower] + ((values[upper] - values[lower]) * fraction); } + + private static readonly object IoLock = new(); } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs index 54be908..d50afd7 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTesterWithTemplate.cs @@ -7,14 +7,18 @@ using System.Collections.Concurrent; using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.ByDivisor; +using PerfectNumbers.Core.Persistence; namespace PerfectNumbers.Core.Cpu; public enum DivisorSet { - OneByOne, + Sequential, Pow2Groups, Predictive, + Percentile, + Additive, + TopDown, } [EnumDependentTemplate(typeof(DivisorSet))] @@ -36,10 +40,18 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN private int _stateCounter; private BigInteger _lastSavedK; private bool _pow2MinusOneChecked; - private string _pow2MinusOneStateFilePath = string.Empty; + private Pow2Minus1StateRepository? _pow2MinusOneRepository; + private KStateRepository _kRepository = null!; + private KStateRepository? _cheapRepository; + private BigInteger _cheapLastSavedK; + private BigInteger _cheapResumeK; private GpuUInt128WorkSet _divisorScanGpuWorkSet; - private static readonly ConcurrentDictionary _checkedPow2MinusOne = new(); +private static readonly ConcurrentDictionary _checkedPow2MinusOne = new(); +private ulong _currentPrime; +#if DivisorSet_TopDown + private BigInteger _topDownCursor; +#endif #if DivisorSet_Pow2Groups private const int PercentScale = DivisorByDivisorConfig.PercentScale; @@ -68,6 +80,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN private int _preparedSpecialRangePromille; private int[] _preparedSpecialSingles = Array.Empty(); private (int Start, int End)[] _preparedSpecialRanges = Array.Empty<(int Start, int End)>(); + private KStateRepository? _specialRepository; + private KStateRepository? _groupsRepository; #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -197,11 +211,59 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private bool TryBuildCheapRange(in BigInteger minK, in BigInteger chunkSize, in BigInteger maxKAllowed, out BigInteger start, out BigInteger end) + { + start = minK > BigInteger.One ? minK : BigInteger.One; + if (_cheapResumeK > start) + { + start = _cheapResumeK; + } + + if (start > maxKAllowed) + { + end = BigInteger.Zero; + return false; + } + + BigInteger span = chunkSize > BigInteger.One ? chunkSize - BigInteger.One : BigInteger.Zero; + end = start + span; + if (end > maxKAllowed) + { + end = maxKAllowed; + } + + return end >= start; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void RecordCheapState(in BigInteger k) + { + if (_currentPrime == 0UL || _cheapRepository is null) + { + return; + } + + if (k <= _cheapLastSavedK) + { + return; + } + + _cheapRepository.Upsert(_currentPrime, k); + _cheapLastSavedK = k; + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void ResetStateTracking() { _stateCounter = 0; _pow2MinusOneChecked = false; + _currentPrime = 0UL; + _cheapLastSavedK = BigInteger.Zero; + _cheapResumeK = BigInteger.One; +#if DivisorSet_TopDown + _topDownCursor = BigInteger.Zero; +#endif #if DivisorSet_Pow2Groups _specialStateCounter = 0; _groupsStateCounter = 0; @@ -217,21 +279,49 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN _lastSavedK = lastSavedK; MinK = minK; _stateCounter = 0; - _pow2MinusOneStateFilePath = stateFile + ".pow2minus1"; - _pow2MinusOneChecked = File.Exists(_pow2MinusOneStateFilePath); + _kRepository = EnvironmentConfiguration.ByDivisorKStateRepository ?? throw new InvalidOperationException("By-divisor k-state repository not initialized."); + _pow2MinusOneRepository = EnvironmentConfiguration.ByDivisorPow2Minus1Repository; + _cheapRepository = EnvironmentConfiguration.ByDivisorCheapKStateRepository; + _cheapLastSavedK = BigInteger.Zero; + _cheapResumeK = MinK; + + string stateName = Path.GetFileNameWithoutExtension(stateFile); + if (ulong.TryParse(stateName, NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime)) + { + _currentPrime = parsedPrime; + if (_kRepository.TryGet(parsedPrime, out BigInteger storedK) && storedK > BigInteger.Zero) + { + _lastSavedK = storedK; + } + + _pow2MinusOneChecked = _pow2MinusOneRepository?.IsChecked(parsedPrime) ?? false; + if (_cheapRepository is not null && _cheapRepository.TryGet(parsedPrime, out BigInteger cheapStored) && cheapStored > BigInteger.Zero) + { + _cheapLastSavedK = cheapStored; + _cheapResumeK = cheapStored + BigInteger.One; + } + } +#if DivisorSet_TopDown + _topDownCursor = _lastSavedK; +#endif #if DivisorSet_Pow2Groups - _specialStateFilePath = stateFile + ".special"; - _groupsStateFilePath = stateFile + ".groups"; + _specialRepository = EnvironmentConfiguration.ByDivisorSpecialStateRepository; + _groupsRepository = EnvironmentConfiguration.ByDivisorGroupsStateRepository; _specialStateCounter = 0; _groupsStateCounter = 0; - if (!MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(_specialStateFilePath, out _specialLastSavedK)) + _specialLastSavedK = lastSavedK; + _groupsLastSavedK = lastSavedK; + if (_currentPrime != 0UL) { - _specialLastSavedK = lastSavedK; - } + if (_specialRepository is not null && _specialRepository.TryGet(_currentPrime, out BigInteger storedSpecial) && storedSpecial > BigInteger.Zero) + { + _specialLastSavedK = storedSpecial; + } - if (!MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(_groupsStateFilePath, out _groupsLastSavedK)) - { - _groupsLastSavedK = lastSavedK; + if (_groupsRepository is not null && _groupsRepository.TryGet(_currentPrime, out BigInteger storedGroups) && storedGroups > BigInteger.Zero) + { + _groupsLastSavedK = storedGroups; + } } _specialResumeK = _specialLastSavedK + BigInteger.One; @@ -244,6 +334,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN [MethodImpl(MethodImplOptions.AggressiveOptimization)] public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor) { + _currentPrime = prime; BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit); if (TryFindPowerOfTwoMinusOneDivisor(prime, allowedMax, out divisor)) @@ -341,7 +432,16 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN // Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} using pow2-groups plan; model skipped."); return compositePow2; #else -#if DivisorSet_Predictive +#if DivisorSet_TopDown + bool compositeTopDown = CheckDivisorsTopDown( + prime, + allowedMax, + normalizedMinK, + out processedAll, + out foundDivisor); + return compositeTopDown; +#endif +#if DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive ByDivisorClassModel? classifier = EnvironmentConfiguration.ByDivisorClassModel; if (classifier is not null) { @@ -591,7 +691,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN } #endif - #if !DivisorSet_Pow2Groups && DivisorSet_Predictive +#if !DivisorSet_Pow2Groups && (DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive) [MethodImpl(MethodImplOptions.AggressiveOptimization)] private bool TryExecutePlannedScan( ByDivisorClassModel model, @@ -675,24 +775,28 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning; ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax, tuning); int suffix = (int)(prime & 1023UL); + _ = suffix; // ByDivisorClassEntry entry = model.Entries[suffix]; // Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2} maxK={maxKAllowed}"); - BigInteger cheapLimit = BigInteger.Min((BigInteger)plan.CheapLimit, maxKAllowed); - if (cheapLimit >= minK) + BigInteger cheapChunk = BigInteger.Min((BigInteger)plan.CheapLimit, maxKAllowed); + BigInteger cheapStart = BigInteger.Zero; + BigInteger cheapEnd = BigInteger.Zero; + if (TryBuildCheapRange(minK, cheapChunk, maxKAllowed, out cheapStart, out cheapEnd)) { - BigInteger currentK = minK; + BigInteger currentK = cheapStart; bool compositeCheap = CheckDivisorsBigPlanRange( prime, step, allowedMax, - minK, - cheapLimit, + cheapStart, + cheapEnd, out bool processedRange, out BigInteger foundRange, ref currentK); if (compositeCheap) { + RecordCheapState(currentK); foundDivisor = foundRange; return true; } @@ -702,6 +806,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN processedAll = false; return false; } + + RecordCheapState(cheapEnd); } BigInteger startPlan = plan.Start <= 0d ? minK : new BigInteger(Math.Ceiling(plan.Start)); @@ -716,12 +822,13 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN target = startPlan + BigInteger.One; } - BigInteger current = cheapLimit + BigInteger.One; + BigInteger current = (cheapEnd > BigInteger.Zero ? cheapEnd + BigInteger.One : minK); if (current < startPlan) { current = startPlan; } +#if DivisorSet_Predictive bool loggedProgressive = false; while (current <= maxKAllowed) { @@ -768,6 +875,163 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN current = advanced; } +#elif DivisorSet_Percentile + BigInteger k10 = plan.K10 > 0d ? new BigInteger(Math.Ceiling(plan.K10)) : current; + BigInteger k25 = plan.K25 > 0d ? new BigInteger(Math.Ceiling(plan.K25)) : k10 + BigInteger.One; + BigInteger k50 = plan.K50 > 0d ? new BigInteger(Math.Ceiling(plan.K50)) : k25 + BigInteger.One; + BigInteger k75 = plan.K75 > 0d ? new BigInteger(Math.Ceiling(plan.K75)) : k50 + BigInteger.One; + + BigInteger[] stops = new BigInteger[] + { + k25 - BigInteger.One, + k50 - BigInteger.One, + k75 - BigInteger.One, + }; + + BigInteger startRangeK = current > k10 ? current : k10; + for (int i = 0; i < stops.Length; i++) + { + BigInteger endRange = stops[i]; + if (endRange < startRangeK) + { + continue; + } + + bool composite = CheckDivisorsBigPlanRange( + prime, + step, + allowedMax, + startRangeK, + endRange, + out bool processedRange, + out BigInteger foundRange, + ref startRangeK); + if (composite) + { + foundDivisor = foundRange; + return true; + } + + if (!processedRange) + { + processedAll = false; + return false; + } + + startRangeK = endRange + BigInteger.One; + if (startRangeK > maxKAllowed) + { + return false; + } + } + + // After percentile sweeps, escalate geometrically. + BigInteger startGeo = startRangeK; + if (startGeo < k75) + { + startGeo = k75; + } + + bool loggedPercentile = false; + while (startGeo <= maxKAllowed) + { + if (!loggedPercentile) + { + loggedPercentile = true; + } + + BigInteger scanStart = startGeo; + BigInteger scanEnd = scanStart; + bool composite = CheckDivisorsBigPlanRange( + prime, + step, + allowedMax, + scanStart, + scanEnd, + out bool processedRange, + out BigInteger foundRange, + ref scanStart); + if (composite) + { + foundDivisor = foundRange; + return true; + } + + if (!processedRange) + { + processedAll = false; + return false; + } + + BigInteger advanced = new BigInteger(Math.Ceiling((double)startGeo * plan.Rho2)); + if (advanced <= startGeo) + { + advanced = startGeo + BigInteger.One; + } + + if (advanced > maxKAllowed) + { + break; + } + + startGeo = advanced; + } +#else + BigInteger delta = plan.DeltaK > 0d ? new BigInteger(Math.Ceiling(plan.DeltaK)) : BigInteger.One; + if (delta < BigInteger.One) + { + delta = BigInteger.One; + } + + if (delta > maxKAllowed) + { + delta = maxKAllowed; + } + + bool loggedAdditive = false; + while (current <= maxKAllowed) + { + if (!loggedAdditive) + { + loggedAdditive = true; + } + + BigInteger startRange = current; + bool composite = CheckDivisorsBigPlanRange( + prime, + step, + allowedMax, + startRange, + startRange, + out bool processedRange, + out BigInteger foundRange, + ref startRange); + if (composite) + { + foundDivisor = foundRange; + return true; + } + + if (!processedRange) + { + processedAll = false; + return false; + } + + BigInteger advanced = current + delta; + if (advanced <= current) + { + advanced = current + BigInteger.One; + } + + if (advanced > maxKAllowed) + { + break; + } + + current = advanced; + } +#endif return false; } @@ -885,6 +1149,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning; ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax == ulong.MaxValue ? BigInteger.Zero : new BigInteger(allowedMax), tuning); int suffix = (int)(prime & 1023UL); + _ = suffix; // ByDivisorClassEntry entry = model.Entries[suffix]; // Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2}"); @@ -896,33 +1161,43 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN return false; } - ulong cheapLimit = (ulong)Math.Min(plan.CheapLimit, maxKAllowed); - if (cheapLimit >= minK) + BigInteger cheapChunk = new BigInteger(Math.Min(plan.CheapLimit, (double)maxKAllowed)); + BigInteger cheapStart = BigInteger.Zero; + BigInteger cheapEnd = BigInteger.Zero; + if (TryBuildCheapRange(new BigInteger(minK), cheapChunk, new BigInteger(maxKAllowed), out cheapStart, out cheapEnd)) { - ulong currentK = minK; - bool composite = CheckDivisors64Range( - prime, - step64, - effectiveAllowed, - minK, - cheapLimit, - ref currentK, - out bool processedRange, - out ulong foundRange); - if (composite) + if (cheapStart <= ulong.MaxValue && cheapEnd <= ulong.MaxValue) { - foundDivisor = foundRange; - return true; - } + ulong cheapStart64 = (ulong)cheapStart; + ulong cheapEnd64 = (ulong)cheapEnd; + ulong currentK = cheapStart64; + bool composite = CheckDivisors64Range( + prime, + step64, + effectiveAllowed, + cheapStart64, + cheapEnd64, + ref currentK, + out bool processedRange, + out ulong foundRange); + if (composite) + { + RecordCheapState(currentK); + foundDivisor = foundRange; + return true; + } - if (!processedRange) - { - processedAll = false; - return false; + if (!processedRange) + { + processedAll = false; + return false; + } + + RecordCheapState(cheapEnd); } } - double nextK = Math.Max(plan.Start, cheapLimit + 1d); + double nextK = Math.Max(plan.Start, (cheapEnd > BigInteger.Zero ? (double)(cheapEnd + BigInteger.One) : minK)); if (nextK < minK) { nextK = minK; @@ -935,6 +1210,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN } ulong current = (ulong)Math.Ceiling(nextK); +#if DivisorSet_Predictive bool loggedProgressive = false; while (current <= maxKAllowed) { @@ -980,10 +1256,149 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN current = (ulong)advanced; } +#elif DivisorSet_Percentile + ulong k10 = plan.K10 > 0d ? (ulong)Math.Ceiling(plan.K10) : current; + ulong k25 = plan.K25 > 0d ? (ulong)Math.Ceiling(plan.K25) : k10 + 1UL; + ulong k50 = plan.K50 > 0d ? (ulong)Math.Ceiling(plan.K50) : k25 + 1UL; + ulong k75 = plan.K75 > 0d ? (ulong)Math.Ceiling(plan.K75) : k50 + 1UL; + + ReadOnlySpan stops = stackalloc ulong[3] + { + k25 > 0UL ? k25 - 1UL : 0UL, + k50 > 0UL ? k50 - 1UL : 0UL, + k75 > 0UL ? k75 - 1UL : 0UL, + }; + + ulong rangeStart = current > k10 ? current : k10; + for (int i = 0; i < stops.Length; i++) + { + ulong stop = stops[i]; + if (stop < rangeStart) + { + continue; + } + + ulong currentK = rangeStart; + bool composite = CheckDivisors64Range( + prime, + step64, + effectiveAllowed, + rangeStart, + stop, + ref currentK, + out bool processedRange, + out ulong foundRange); + if (composite) + { + foundDivisor = foundRange; + return true; + } + + if (!processedRange) + { + processedAll = false; + return false; + } + + rangeStart = stop + 1UL; + if (rangeStart > maxKAllowed) + { + return false; + } + } + + ulong geoStart = rangeStart < k75 ? k75 : rangeStart; + while (geoStart <= maxKAllowed) + { + ulong currentK = geoStart; + bool composite = CheckDivisors64Range( + prime, + step64, + effectiveAllowed, + currentK, + currentK, + ref currentK, + out bool processedSingle, + out ulong foundSingle); + if (composite) + { + foundDivisor = foundSingle; + return true; + } + + if (!processedSingle) + { + processedAll = false; + return false; + } + + double advanced = Math.Ceiling(geoStart * plan.Rho2); + if (advanced <= geoStart) + { + advanced = geoStart + 1d; + } + + if (advanced > maxKAllowed) + { + break; + } + + geoStart = (ulong)advanced; + } +#else + ulong delta = plan.DeltaK > 0d ? (ulong)Math.Ceiling(plan.DeltaK) : 1UL; + if (delta == 0UL) + { + delta = 1UL; + } + + if (delta > maxKAllowed) + { + delta = maxKAllowed; + } + + while (current <= maxKAllowed) + { + ulong currentK = current; + bool composite = CheckDivisors64Range( + prime, + step64, + effectiveAllowed, + currentK, + currentK, + ref currentK, + out bool processedSingle, + out ulong foundSingle); + if (composite) + { + foundDivisor = foundSingle; + return true; + } + + if (!processedSingle) + { + processedAll = false; + return false; + } + + ulong advanced = current + delta; + if (advanced <= current || advanced == 0UL) + { + advanced = current + 1UL; + } + + if (advanced > maxKAllowed) + { + break; + } + + current = advanced; + } +#endif return false; } - #endif +#endif [MethodImpl(MethodImplOptions.AggressiveOptimization)] private bool CheckDivisors64Bit( @@ -1809,13 +2224,17 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN return; } - string? directory = Path.GetDirectoryName(targetPath); - if (!string.IsNullOrEmpty(directory)) + if (!string.IsNullOrEmpty(targetPath)) { - Directory.CreateDirectory(directory); + string? directory = Path.GetDirectoryName(targetPath); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + + File.AppendAllText(targetPath, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine); } - File.AppendAllText(targetPath, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine); counter = 0; last = k; } @@ -2103,6 +2522,162 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN out foundDivisor); } +#if DivisorSet_TopDown + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private bool CheckDivisorsTopDown( + ulong prime, + BigInteger allowedMax, + BigInteger minK, + out bool processedAll, + out BigInteger foundDivisor) + { + foundDivisor = BigInteger.Zero; + processedAll = false; + + BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One; + ulong step = prime << 1; + ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (allowedMax > ulong.MaxValue ? ulong.MaxValue : (ulong)allowedMax); + if (allowedMax64 <= 1UL) + { + processedAll = true; + return false; + } + + ulong maxKAllowed = (allowedMax64 - 1UL) / step; + { + BigInteger maxKBig = maxKAllowed; + if (TryClampMaxKForMersenneTail(prime, (BigInteger)step, ref maxKBig)) + { + maxKAllowed = maxKBig > ulong.MaxValue ? ulong.MaxValue : (ulong)maxKBig; + } + } + + if (maxKAllowed < 1UL) + { + processedAll = true; + return false; + } + + if (normalizedMinK > maxKAllowed) + { + processedAll = true; + return false; + } + + ulong minK64 = normalizedMinK > ulong.MaxValue ? ulong.MaxValue : (ulong)normalizedMinK; + if (minK64 < 1UL) + { + minK64 = 1UL; + } + + ulong cursor = _topDownCursor > ulong.MaxValue ? ulong.MaxValue : (ulong)_topDownCursor; + ulong currentK = maxKAllowed; + if (cursor > 0 && cursor <= maxKAllowed) + { + currentK = maxKAllowed - cursor; + } + + if (currentK < minK64) + { + processedAll = true; + return false; + } + + UInt128 step128 = step; + UInt128 divisor = (step128 * currentK) + UInt128.One; + var residueStepper = new MersenneDivisorResidueStepperDescending(prime, new GpuUInt128(step), new GpuUInt128(divisor)); + while (currentK >= minK64) + { + if (divisor > allowedMax64) + { + processedAll = true; + return false; + } + + if (residueStepper.IsAdmissible()) + { + ulong divisor64 = (ulong)divisor; + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64); +#if DEVICE_GPU + bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu( + divisor64, + Accelerator, + prime, + divisorData, + out ulong computedCycle, + out bool primeOrderFailed); +#elif DEVICE_HYBRID + bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid( + divisor64, + Accelerator, + prime, + divisorData, + out ulong computedCycle, + out bool primeOrderFailed); +#else + bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu( + divisor64, + prime, + divisorData, + out ulong computedCycle, + out bool primeOrderFailed); +#endif + ulong divisorCycle; + if (!computed || computedCycle == 0UL) + { +#if DEVICE_GPU + divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu( + divisor64, + Accelerator, + divisorData, + skipPrimeOrderHeuristic: primeOrderFailed); +#elif DEVICE_HYBRID + divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid( + divisor64, + Accelerator, + divisorData, + skipPrimeOrderHeuristic: primeOrderFailed); +#else + divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu( + divisor64, + divisorData, + skipPrimeOrderHeuristic: primeOrderFailed); +#endif + } + else + { + divisorCycle = computedCycle; + } + + RecordState(cursor); + if (divisorCycle == prime && !IsMersenneValue(prime, divisor64)) + { + foundDivisor = divisor64; + return true; + } + } + + cursor++; + if (cursor > maxKAllowed) + { + break; + } + + if (currentK == minK64) + { + break; + } + + currentK--; + divisor -= step128; + residueStepper.Retreat(); + } + + processedAll = true; + return false; + } +#endif + [MethodImpl(MethodImplOptions.AggressiveOptimization)] private bool CheckDivisors64( ulong prime, @@ -2234,28 +2809,76 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void RecordState(BigInteger k) { +#if DivisorSet_TopDown + if (_currentPrime != 0UL) + { + if (k > _lastSavedK) + { + int next = _stateCounter + 1; + if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval) + { + _kRepository.Upsert(_currentPrime, k); + _stateCounter = 0; + _lastSavedK = k; + _topDownCursor = k; + } + else + { + _stateCounter = next; + } + _topDownCursor = k; + } + } + return; +#endif #if DivisorSet_Pow2Groups if (_currentPow2Phase == Pow2Phase.Special) { - RecordStateInternal(k, _specialStateFilePath, ref _specialStateCounter, ref _specialLastSavedK, _currentPow2Phase); + RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK); return; } if (_currentPow2Phase == Pow2Phase.Groups) { - RecordStateInternal(k, _groupsStateFilePath, ref _groupsStateCounter, ref _groupsLastSavedK, _currentPow2Phase); + RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK); return; } #endif - RecordStateInternal(k, StateFilePath, ref _stateCounter, ref _lastSavedK, default); + if (_currentPrime != 0UL) + { + if (k > _lastSavedK) + { + _kRepository.Upsert(_currentPrime, k); + _lastSavedK = k; + } + _stateCounter = 0; + } } #if DivisorSet_Pow2Groups - private void RecordStateInternal(BigInteger k, string path, ref int counter, ref BigInteger lastSavedK, Pow2Phase phase) -#else - private void RecordStateInternal(BigInteger k, string path, ref int counter, ref BigInteger lastSavedK, object _) -#endif + private void RecordState(BigInteger k, Pow2Phase phase) { + switch (phase) + { + case Pow2Phase.Special: + RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK); + break; + case Pow2Phase.Groups: + RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK); + break; + default: + RecordState(k); + break; + } + } + + private void RecordStatePow2(BigInteger k, KStateRepository? repo, ref int counter, ref BigInteger lastSavedK) + { + if (_currentPrime == 0UL || repo is null) + { + return; + } + if (k <= lastSavedK) { return; @@ -2264,13 +2887,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN int next = counter + 1; if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval) { - string? directory = Path.GetDirectoryName(path); - if (!string.IsNullOrEmpty(directory)) - { - Directory.CreateDirectory(directory); - } - - File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine); + repo.Upsert(_currentPrime, k); counter = 0; lastSavedK = k; } @@ -2280,24 +2897,6 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN } } -#if DivisorSet_Pow2Groups - // Overload used from CheckDivisors64Range through RecordState with current phase routed automatically. - private void RecordState(BigInteger k, Pow2Phase phase) - { - switch (phase) - { - case Pow2Phase.Special: - RecordStateInternal(k, _specialStateFilePath, ref _specialStateCounter, ref _specialLastSavedK, phase); - break; - case Pow2Phase.Groups: - RecordStateInternal(k, _groupsStateFilePath, ref _groupsStateCounter, ref _groupsLastSavedK, phase); - break; - default: - RecordStateInternal(k, StateFilePath, ref _stateCounter, ref _lastSavedK, phase); - break; - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static void RecordPow2Hit(Pow2Phase phase) { @@ -2714,22 +3313,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN private void MarkPow2MinusOneChecked() { _pow2MinusOneChecked = true; - if (!string.IsNullOrEmpty(_pow2MinusOneStateFilePath)) + if (_currentPrime != 0UL) { - try - { - string? directory = Path.GetDirectoryName(_pow2MinusOneStateFilePath); - if (!string.IsNullOrEmpty(directory)) - { - Directory.CreateDirectory(directory); - } - - File.WriteAllText(_pow2MinusOneStateFilePath, "checked"); - } - catch - { - // Best-effort persistence; ignore IO errors. - } + _pow2MinusOneRepository?.MarkChecked(_currentPrime); } } @@ -2797,6 +3383,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN } _checkedPow2MinusOne[prime] = 1; + _currentPrime = prime; MarkPow2MinusOneChecked(); return false; } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs index 0083438..5050517 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate.cs @@ -15,10 +15,18 @@ using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTeste using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForCpuOrder; using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForHybridOrder; using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForGpuOrder; +#elif DivisorSet_Percentile +using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForCpuOrder; +using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForHybridOrder; +using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForGpuOrder; +#elif DivisorSet_Additive +using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder; +using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder; +using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder; #else -using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder; -using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForHybridOrder; -using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForGpuOrder; +using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder; +using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForHybridOrder; +using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForGpuOrder; #endif namespace PerfectNumbers.Core.Cpu; @@ -71,10 +79,14 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate string extension = ".bin"; #if DivisorSet_Predictive extension = ".predictive"; +#elif DivisorSet_Percentile + extension = ".percentile"; +#elif DivisorSet_Additive + extension = ".additive"; +#elif DivisorSet_TopDown + extension = ".topdown"; #endif - string stateFile = Path.Combine( - PerfectNumberConstants.ByDivisorStateDirectory, - prime.ToString(CultureInfo.InvariantCulture) + extension); + string stateFile = prime.ToString(CultureInfo.InvariantCulture) + extension; ConfigureForPrime(stateFile); @@ -82,11 +94,6 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate if (!isPrime) { - if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile)) - { - File.Delete(stateFile); - } - _markComposite(); _printResult(prime, true, true, false, divisor); Console.WriteLine($"Finished processing {prime}"); @@ -106,25 +113,39 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate private void ConfigureForPrime(string stateFile) { +#if DivisorSet_TopDown BigInteger resumeK = EnvironmentConfiguration.MinK; - if (File.Exists(stateFile)) + BigInteger lastK = BigInteger.Zero; + if (EnvironmentConfiguration.ByDivisorKStateRepository is not null) { - if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK)) + if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) && + EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) && + storedK > BigInteger.Zero) { - resumeK = lastK + BigInteger.One; - _tester.ResumeFromState(stateFile, lastK, resumeK); + lastK = storedK; } - else - { - _tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK); - } - } - else - { - _tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK); } + _tester.ResumeFromState(stateFile, lastK, resumeK); _tester.ResetStateTracking(); + return; +#else + BigInteger resumeK = EnvironmentConfiguration.MinK; + BigInteger lastK = BigInteger.Zero; + if (EnvironmentConfiguration.ByDivisorKStateRepository is not null) + { + if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) && + EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) && + storedK > BigInteger.Zero) + { + lastK = storedK; + resumeK = storedK + BigInteger.One; + } + } + + _tester.ResumeFromState(stateFile, lastK, resumeK); + _tester.ResetStateTracking(); +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/PerfectNumbers.Core/EnvironmentConfiguration.cs b/PerfectNumbers.Core/EnvironmentConfiguration.cs index 1be8f67..43c2115 100644 --- a/PerfectNumbers.Core/EnvironmentConfiguration.cs +++ b/PerfectNumbers.Core/EnvironmentConfiguration.cs @@ -22,7 +22,13 @@ public static class EnvironmentConfiguration public static int ByDivisorSpecialRange; public static ByDivisor.ByDivisorClassModel? ByDivisorClassModel; public static ByDivisor.ByDivisorScanTuning ByDivisorScanTuning = ByDivisor.ByDivisorScanTuning.Default; - public static double ByDivisorCheapKLimit = 100_000d; + public static double ByDivisorCheapKLimit = 10_000d; + public static Persistence.ByDivisorClassModelRepository? ByDivisorClassModelRepository; + public static Persistence.KStateRepository? ByDivisorKStateRepository; + public static Persistence.KStateRepository? ByDivisorCheapKStateRepository; + public static Persistence.KStateRepository? ByDivisorSpecialStateRepository; + public static Persistence.KStateRepository? ByDivisorGroupsStateRepository; + public static Persistence.Pow2Minus1StateRepository? ByDivisorPow2Minus1Repository; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Initialize() diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuPrimeScanSession.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuPrimeScanSession.cs index 27a3003..24446df 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuPrimeScanSession.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuPrimeScanSession.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.IO; using System.Numerics; namespace PerfectNumbers.Core.Gpu; @@ -34,9 +35,7 @@ internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession try { Console.WriteLine($"Processing {prime}"); - string stateFile = Path.Combine( - PerfectNumberConstants.ByDivisorStateDirectory, - prime.ToString(CultureInfo.InvariantCulture) + ".bin"); + string stateFile = prime.ToString(CultureInfo.InvariantCulture) + ".bin"; ConfigureForPrime(stateFile); @@ -44,11 +43,6 @@ internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession if (!isPrime) { - if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile)) - { - File.Delete(stateFile); - } - _markComposite(); _printResult(prime, true, true, false, divisor); Console.WriteLine($"Finished processing {prime}"); @@ -68,25 +62,39 @@ internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession private void ConfigureForPrime(string stateFile) { +#if DivisorSet_TopDown BigInteger resumeK = EnvironmentConfiguration.MinK; - if (File.Exists(stateFile)) + BigInteger lastK = BigInteger.Zero; + if (EnvironmentConfiguration.ByDivisorKStateRepository is not null) { - if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK)) + if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) && + EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) && + storedK > BigInteger.Zero) { - resumeK = lastK + BigInteger.One; - _tester.ResumeFromState(stateFile, lastK, resumeK); + lastK = storedK; } - else - { - _tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK); - } - } - else - { - _tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK); } + _tester.ResumeFromState(stateFile, lastK, resumeK); _tester.ResetStateTracking(); + return; +#else + BigInteger resumeK = EnvironmentConfiguration.MinK; + BigInteger lastK = BigInteger.Zero; + if (EnvironmentConfiguration.ByDivisorKStateRepository is not null) + { + if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) && + EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) && + storedK > BigInteger.Zero) + { + lastK = storedK; + resumeK = storedK + BigInteger.One; + } + } + + _tester.ResumeFromState(stateFile, lastK, resumeK); + _tester.ResetStateTracking(); +#endif } public void Dispose() diff --git a/PerfectNumbers.Core/MersenneDivisorResidueStepper.cs b/PerfectNumbers.Core/MersenneDivisorResidueStepper.cs index a7492a0..ab87efb 100644 --- a/PerfectNumbers.Core/MersenneDivisorResidueStepper.cs +++ b/PerfectNumbers.Core/MersenneDivisorResidueStepper.cs @@ -87,3 +87,88 @@ internal struct MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, i return (byte)sum; } } + +[method: MethodImpl(MethodImplOptions.AggressiveInlining)] +internal struct MersenneDivisorResidueStepperDescending(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor) +{ + private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9); + private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9); + + private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise; + private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6); + private readonly byte step8 = (byte)(step.Low % 8UL); + private readonly byte step5 = ComputeModulo(step, 5, multiplier: 1); + private readonly byte step3 = ComputeModulo(step, 3, multiplier: 1); + private readonly byte step7 = ComputeModulo(step, 7, multiplier: 2); + private readonly byte step11 = ComputeModulo(step, 11, multiplier: 5); + private readonly byte step13 = ComputeModulo(step, 13, multiplier: 3); + private readonly byte step17 = ComputeModulo(step, 17, multiplier: 1); + private readonly byte step19 = ComputeModulo(step, 19, multiplier: 17); + + private byte Remainder10 = ComputeModulo(firstDivisor, 10, multiplier: 6); + private byte Remainder8 = (byte)(firstDivisor.Low % 8UL); + private byte Remainder5 = ComputeModulo(firstDivisor, 5, multiplier: 1); + private byte Remainder3 = ComputeModulo(firstDivisor, 3, multiplier: 1); + private byte Remainder7 = ComputeModulo(firstDivisor, 7, multiplier: 2); + private byte Remainder11 = ComputeModulo(firstDivisor, 11, multiplier: 5); + private byte Remainder13 = ComputeModulo(firstDivisor, 13, multiplier: 3); + private byte Remainder17 = ComputeModulo(firstDivisor, 17, multiplier: 1); + private byte Remainder19 = ComputeModulo(firstDivisor, 19, multiplier: 17); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsAdmissible() + { + if (((decimalMask >> Remainder10) & 1) == 0) + { + return false; + } + + if (Remainder8 != 1 && Remainder8 != 7) + { + return false; + } + + if (Remainder3 == 0 || Remainder5 == 0 || Remainder7 == 0 || Remainder11 == 0 || Remainder13 == 0 || Remainder17 == 0 || Remainder19 == 0) + { + return false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Retreat() + { + Remainder10 = SubMod(Remainder10, step10, 10); + Remainder8 = SubMod(Remainder8, step8, 8); + Remainder5 = SubMod(Remainder5, step5, 5); + Remainder3 = SubMod(Remainder3, step3, 3); + Remainder7 = SubMod(Remainder7, step7, 7); + Remainder11 = SubMod(Remainder11, step11, 11); + Remainder13 = SubMod(Remainder13, step13, 13); + Remainder17 = SubMod(Remainder17, step17, 17); + Remainder19 = SubMod(Remainder19, step19, 19); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ComputeModulo(in GpuUInt128 value, byte modulus, byte multiplier) + { + ulong high = value.High % modulus; + ulong low = value.Low % modulus; + int result = (int)((high * multiplier) + low); + result %= modulus; + return (byte)result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte SubMod(byte value, byte delta, byte modulus) + { + int diff = value - delta; + if (diff < 0) + { + diff += modulus; + } + + return (byte)diff; + } +} diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index 4ea7e0f..2d6dd3d 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -6,12 +6,18 @@ using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Cpu; using System.Runtime.CompilerServices; -using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder; -using MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForHybridOrder; -using MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForGpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForHybridOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForGpuOrder; using MersenneNumberDivisorByDivisorCpuTesterWithPredictiveCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForCpuOrder; using MersenneNumberDivisorByDivisorCpuTesterWithPredictiveHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForHybridOrder; using MersenneNumberDivisorByDivisorCpuTesterWithPredictiveGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForGpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForCpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForHybridOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForGpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder; +using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder; namespace PerfectNumbers.Core; @@ -27,7 +33,7 @@ public static class MersenneNumberDivisorByDivisorTester Action clearComposite, Action printResult, int threadCount, - int primesPerTask) + int primesPerTask) => RunStructTester( candidates, ref tester, @@ -79,6 +85,68 @@ public static class MersenneNumberDivisorByDivisorTester Action pr) => RunPredictiveCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunPercentileCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunAdditiveCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Run( List candidates, @@ -141,6 +209,68 @@ public static class MersenneNumberDivisorByDivisorTester Action pr) => RunPredictiveHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunPercentileHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunAdditiveHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Run( List candidates, @@ -203,6 +333,68 @@ public static class MersenneNumberDivisorByDivisorTester Action pr) => RunPredictiveGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunPercentileGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Run( + List candidates, + ref MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder tester, + Dictionary? previousResults, + ulong startPrime, + Action markComposite, + Action clearComposite, + Action printResult, + int threadCount, + int primesPerTask) + => RunStructTester( + candidates, + ref tester, + previousResults, + startPrime, + markComposite, + clearComposite, + printResult, + threadCount, + primesPerTask, + static ( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder prototype, + int workerCount, + int partitionSize, + Action mc, + Action cc, + Action pr) => + RunAdditiveGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Run( List candidates, @@ -1093,7 +1285,7 @@ public static class MersenneNumberDivisorByDivisorTester if (workerCount == 1) { DeterministicRandomCpu.Initialize(); - var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); foreach (ulong prime in filteredPrimes) { session.ProcessPrime(prime); @@ -1115,7 +1307,7 @@ public static class MersenneNumberDivisorByDivisorTester () => { DeterministicRandomCpu.Initialize(); - var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); startGate.Wait(); Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); @@ -1230,6 +1422,162 @@ public static class MersenneNumberDivisorByDivisorTester startGate.Dispose(); } + private static void RunPercentileCpuOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount == 1) + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + foreach (ulong prime in filteredPrimes) + { + session.ProcessPrime(prime); + } + + session.Dispose(); + return; + } + + TaskScheduler scheduler = UnboundedTaskScheduler.Instance; + int nextIndex = 0; + Task[] tasks = new Task[workerCount]; + var startGate = new ManualResetEventSlim(initialState: false); + + for (int taskIndex = 0; taskIndex < workerCount; taskIndex++) + { + int workerIndex = taskIndex; + tasks[taskIndex] = Task.Factory.StartNew( + () => + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + startGate.Wait(); + Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + + while (true) + { + int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize; + if (start >= totalCount) + { + break; + } + + int end = start + partitionSize; + if (end > totalCount) + { + end = totalCount; + } + + for (int index = start; index < end; index++) + { + session.ProcessPrime(filteredPrimes[index]); + } + } + + session.Dispose(); + Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + }, + CancellationToken.None, + TaskCreationOptions.DenyChildAttach, + scheduler); + } + + startGate.Set(); + Task.WaitAll(tasks); + startGate.Dispose(); + } + + private static void RunAdditiveCpuOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount == 1) + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + foreach (ulong prime in filteredPrimes) + { + session.ProcessPrime(prime); + } + + session.Dispose(); + return; + } + + TaskScheduler scheduler = UnboundedTaskScheduler.Instance; + int nextIndex = 0; + Task[] tasks = new Task[workerCount]; + var startGate = new ManualResetEventSlim(initialState: false); + + for (int taskIndex = 0; taskIndex < workerCount; taskIndex++) + { + int workerIndex = taskIndex; + tasks[taskIndex] = Task.Factory.StartNew( + () => + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + startGate.Wait(); + Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + + while (true) + { + int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize; + if (start >= totalCount) + { + break; + } + + int end = start + partitionSize; + if (end > totalCount) + { + end = totalCount; + } + + for (int index = start; index < end; index++) + { + session.ProcessPrime(filteredPrimes[index]); + } + } + + session.Dispose(); + Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + }, + CancellationToken.None, + TaskCreationOptions.DenyChildAttach, + scheduler); + } + + startGate.Set(); + Task.WaitAll(tasks); + startGate.Dispose(); + } + private static void RunHybridOrderFiltered( List filteredPrimes, in MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder prototype, @@ -1256,7 +1604,7 @@ public static class MersenneNumberDivisorByDivisorTester if (workerCount == 1) { DeterministicRandomCpu.Initialize(); - var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); GpuPrimeWorkLimiter.Acquire(); foreach (ulong prime in filteredPrimes) @@ -1282,7 +1630,7 @@ public static class MersenneNumberDivisorByDivisorTester () => { DeterministicRandomCpu.Initialize(); - var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); startGate.Wait(); Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); @@ -1399,6 +1747,188 @@ public static class MersenneNumberDivisorByDivisorTester startGate.Dispose(); } + private static void RunPercentileHybridOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + int effectiveWorkerCount = workerCount; + int gpuLimit = GpuPrimeWorkLimiter.CurrentLimit; + if (effectiveWorkerCount > gpuLimit) + { + effectiveWorkerCount = gpuLimit; + } + + if (workerCount == 1) + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + + GpuPrimeWorkLimiter.Acquire(); + foreach (ulong prime in filteredPrimes) + { + session.ProcessPrime(prime); + } + + GpuPrimeWorkLimiter.Release(); + session.Dispose(); + + return; + } + + TaskScheduler scheduler = UnboundedTaskScheduler.Instance; + int nextIndex = 0; + Task[] tasks = new Task[effectiveWorkerCount]; + var startGate = new ManualResetEventSlim(initialState: false); + + for (int taskIndex = 0; taskIndex < effectiveWorkerCount; taskIndex++) + { + int workerIndex = taskIndex; + tasks[taskIndex] = Task.Factory.StartNew( + () => + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + startGate.Wait(); + Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + + GpuPrimeWorkLimiter.Acquire(); + while (true) + { + int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize; + if (start >= totalCount) + { + break; + } + + int end = start + partitionSize; + if (end > totalCount) + { + end = totalCount; + } + + for (int index = start; index < end; index++) + { + session.ProcessPrime(filteredPrimes[index]); + } + } + + GpuPrimeWorkLimiter.Release(); + session.Dispose(); + Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + }, + CancellationToken.None, + TaskCreationOptions.DenyChildAttach, + scheduler); + } + + startGate.Set(); + Task.WaitAll(tasks); + startGate.Dispose(); + } + + private static void RunAdditiveHybridOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + int effectiveWorkerCount = workerCount; + int gpuLimit = GpuPrimeWorkLimiter.CurrentLimit; + if (effectiveWorkerCount > gpuLimit) + { + effectiveWorkerCount = gpuLimit; + } + + if (workerCount == 1) + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + + GpuPrimeWorkLimiter.Acquire(); + foreach (ulong prime in filteredPrimes) + { + session.ProcessPrime(prime); + } + + GpuPrimeWorkLimiter.Release(); + session.Dispose(); + + return; + } + + TaskScheduler scheduler = UnboundedTaskScheduler.Instance; + int nextIndex = 0; + Task[] tasks = new Task[effectiveWorkerCount]; + var startGate = new ManualResetEventSlim(initialState: false); + + for (int taskIndex = 0; taskIndex < effectiveWorkerCount; taskIndex++) + { + int workerIndex = taskIndex; + tasks[taskIndex] = Task.Factory.StartNew( + () => + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult); + startGate.Wait(); + Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + + GpuPrimeWorkLimiter.Acquire(); + while (true) + { + int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize; + if (start >= totalCount) + { + break; + } + + int end = start + partitionSize; + if (end > totalCount) + { + end = totalCount; + } + + for (int index = start; index < end; index++) + { + session.ProcessPrime(filteredPrimes[index]); + } + } + + GpuPrimeWorkLimiter.Release(); + session.Dispose(); + Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + }, + CancellationToken.None, + TaskCreationOptions.DenyChildAttach, + scheduler); + } + + startGate.Set(); + Task.WaitAll(tasks); + startGate.Dispose(); + } + private static void RunGpuOrderFiltered( List filteredPrimes, in MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder prototype, @@ -1425,7 +1955,7 @@ public static class MersenneNumberDivisorByDivisorTester if (workerCount == 1) { DeterministicRandomCpu.Initialize(); - var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); GpuPrimeWorkLimiter.Acquire(); foreach (ulong prime in filteredPrimes) @@ -1451,7 +1981,7 @@ public static class MersenneNumberDivisorByDivisorTester () => { DeterministicRandomCpu.Initialize(); - var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); startGate.Wait(); Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); @@ -1568,6 +2098,162 @@ public static class MersenneNumberDivisorByDivisorTester startGate.Dispose(); } + private static void RunPercentileGpuOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount == 1) + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + foreach (ulong prime in filteredPrimes) + { + session.ProcessPrime(prime); + } + + session.Dispose(); + return; + } + + TaskScheduler scheduler = UnboundedTaskScheduler.Instance; + int nextIndex = 0; + Task[] tasks = new Task[workerCount]; + var startGate = new ManualResetEventSlim(initialState: false); + + for (int taskIndex = 0; taskIndex < workerCount; taskIndex++) + { + int workerIndex = taskIndex; + tasks[taskIndex] = Task.Factory.StartNew( + () => + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + startGate.Wait(); + Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + + while (true) + { + int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize; + if (start >= totalCount) + { + break; + } + + int end = start + partitionSize; + if (end > totalCount) + { + end = totalCount; + } + + for (int index = start; index < end; index++) + { + session.ProcessPrime(filteredPrimes[index]); + } + } + + session.Dispose(); + Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + }, + CancellationToken.None, + TaskCreationOptions.DenyChildAttach, + scheduler); + } + + startGate.Set(); + Task.WaitAll(tasks); + startGate.Dispose(); + } + + private static void RunAdditiveGpuOrderFiltered( + List filteredPrimes, + in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder prototype, + int workerCount, + int partitionSize, + Action markComposite, + Action clearComposite, + Action printResult) + { + MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder prototypeCopy = prototype; + int totalCount = filteredPrimes.Count; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + if (workerCount == 1) + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + foreach (ulong prime in filteredPrimes) + { + session.ProcessPrime(prime); + } + + session.Dispose(); + return; + } + + TaskScheduler scheduler = UnboundedTaskScheduler.Instance; + int nextIndex = 0; + Task[] tasks = new Task[workerCount]; + var startGate = new ManualResetEventSlim(initialState: false); + + for (int taskIndex = 0; taskIndex < workerCount; taskIndex++) + { + int workerIndex = taskIndex; + tasks[taskIndex] = Task.Factory.StartNew( + () => + { + DeterministicRandomCpu.Initialize(); + var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult); + startGate.Wait(); + Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + + while (true) + { + int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize; + if (start >= totalCount) + { + break; + } + + int end = start + partitionSize; + if (end > totalCount) + { + end = totalCount; + } + + for (int index = start; index < end; index++) + { + session.ProcessPrime(filteredPrimes[index]); + } + } + + session.Dispose(); + Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}"); + }, + CancellationToken.None, + TaskCreationOptions.DenyChildAttach, + scheduler); + } + + startGate.Set(); + Task.WaitAll(tasks); + startGate.Dispose(); + } + private static void RunGpuTesterFiltered( List filteredPrimes, MersenneNumberDivisorByDivisorGpuTester prototype, diff --git a/PerfectNumbers.Core/PerfectNumberConstants.cs b/PerfectNumbers.Core/PerfectNumberConstants.cs index cdcc2f7..86ceaf9 100644 --- a/PerfectNumbers.Core/PerfectNumberConstants.cs +++ b/PerfectNumbers.Core/PerfectNumberConstants.cs @@ -20,7 +20,7 @@ public static class PerfectNumberConstants public const int PooledArrayThreshold = 64; public const int Pow2WindowSize = 8; public const int RollingAccelerators = 298; - public const int ThreadsByAccelerator = 1; + public const int ThreadsByAccelerator = 4; public const int ByDivisorStateSaveInterval = 200_000; public const string ByDivisorStateDirectory = "Checks"; public const int PollardRhoMilliseconds = 100; diff --git a/PerfectNumbers.Core/PerfectNumbers.Core.csproj b/PerfectNumbers.Core/PerfectNumbers.Core.csproj index 02a8bf3..5374b96 100644 --- a/PerfectNumbers.Core/PerfectNumbers.Core.csproj +++ b/PerfectNumbers.Core/PerfectNumbers.Core.csproj @@ -19,6 +19,8 @@ + + diff --git a/PerfectNumbers.Core/Persistence/ByDivisorClassModelRepository.cs b/PerfectNumbers.Core/Persistence/ByDivisorClassModelRepository.cs new file mode 100644 index 0000000..c1d2954 --- /dev/null +++ b/PerfectNumbers.Core/Persistence/ByDivisorClassModelRepository.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; +using System.Text; +using System.Text.Json; +using FASTER.core; +using LightningDB; +using PerfectNumbers.Core.ByDivisor; + +namespace PerfectNumbers.Core.Persistence; + +/// +/// Persists the by-divisor class model using Microsoft.FASTER.Core with optional migration from LightningDB. +/// +public sealed class ByDivisorClassModelRepository : IDisposable +{ + private const string ModelKey = "bydivisor_model"; + private readonly FasterKV _store; + private readonly IDevice _logDevice; + private readonly IDevice _objectLogDevice; + private static readonly Encoding Utf8 = Encoding.UTF8; + + public string DatabasePath { get; } + + public static ByDivisorClassModelRepository Open( + string directory, + string repositoryFileName = "bydivisor_model.faster", + string? lmdbPath = null, + string? lmdbDirectory = null, + bool deleteLmdbAfterMigration = true) + { + Directory.CreateDirectory(directory); + string dbPath = Path.Combine(directory, repositoryFileName); + var repo = new ByDivisorClassModelRepository(dbPath); + string lmdbSource = lmdbPath ?? Path.ChangeExtension(dbPath, ".lmdb"); + if (!File.Exists(lmdbSource) && !Directory.Exists(lmdbSource) && !string.IsNullOrEmpty(lmdbDirectory)) + { + lmdbSource = Path.Combine(lmdbDirectory, Path.GetFileName(Path.ChangeExtension(repositoryFileName, ".lmdb"))); + } + repo.MigrateFromLmdb(lmdbSource, deleteLmdbAfterMigration); + return repo; + } + + private ByDivisorClassModelRepository(string path) + { + DatabasePath = path; + _logDevice = Devices.CreateLogDevice(path + ".log"); + _objectLogDevice = Devices.CreateLogDevice(path + ".obj.log"); + _store = new FasterKV( + 1L << 16, + new LogSettings + { + LogDevice = _logDevice, + ObjectLogDevice = _objectLogDevice, + }); + } + + public void Save(ByDivisorClassModel model) + { + string json = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true }); + using var session = _store.For(new SimpleFunctions()).NewSession>(); + session.Upsert(ModelKey, json); + session.CompletePending(true); + } + + public bool TryLoad(out ByDivisorClassModel? model) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + if (session.Read(ModelKey, out string? json).Found && json is not null) + { + try + { + model = JsonSerializer.Deserialize(json); + return model is not null; + } + catch + { + // ignore deserialization errors + } + } + + model = null; + return false; + } + + public void Commit() + { + _store.Log.FlushAndEvict(wait: true); + } + + public void Dispose() + { + _store.Dispose(); + _logDevice.Dispose(); + _objectLogDevice.Dispose(); + } + + private void MigrateFromLmdb(string lmdbPath, bool deleteAfter) + { + try + { + if (!LmdbExists(lmdbPath)) + { + Console.WriteLine($"No LMDB by-divisor model found at '{lmdbPath}', skipping migration."); + return; + } + + Console.WriteLine($"Migrating by-divisor model from LMDB at '{lmdbPath}' to FASTER..."); + using var env = new LightningEnvironment(lmdbPath) { MaxDatabases = 2 }; + env.Open(); + using var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly); + var db = tx.OpenDatabase("bydivisor_model", new DatabaseConfiguration { Flags = DatabaseOpenFlags.None }); + if (tx.TryGet(db, Utf8.GetBytes(ModelKey), out byte[]? jsonBytes) && jsonBytes is not null) + { + try + { + string json = Utf8.GetString(jsonBytes); + var session = _store.For(new SimpleFunctions()).NewSession>(); + session.Upsert(ModelKey, json); + session.CompletePending(true); + session.Dispose(); + } + catch + { + // ignore migration issues + } + } + _store.Log.FlushAndEvict(wait: true); + Console.WriteLine("Migration of by-divisor model to FASTER completed."); + } + catch + { + // ignore migration failures + } + + if (deleteAfter) + { + Console.WriteLine($"Starting cleanup of LMDB by-divisor model at '{lmdbPath}'..."); + try + { + if (Directory.Exists(lmdbPath)) + { + Directory.Delete(lmdbPath, recursive: true); + } + else if (File.Exists(lmdbPath)) + { + File.Delete(lmdbPath); + } + } + catch + { + // ignore cleanup failures + } + } + } + + private static bool LmdbExists(string path) + { + if (Directory.Exists(path) || File.Exists(path)) + { + return true; + } + + string candidate = Path.Combine(path, "data.mdb"); + if (File.Exists(candidate)) + { + return true; + } + + string? directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory)) + { + string sibling = Path.Combine(directory, "data.mdb"); + if (File.Exists(sibling)) + { + return true; + } + } + + return false; + } +} diff --git a/PerfectNumbers.Core/Persistence/KStateRepository.cs b/PerfectNumbers.Core/Persistence/KStateRepository.cs new file mode 100644 index 0000000..a4e64a5 --- /dev/null +++ b/PerfectNumbers.Core/Persistence/KStateRepository.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Numerics; +using System.Text; +using FASTER.core; +using LightningDB; + +namespace PerfectNumbers.Core.Persistence; + +/// +/// Stores last-checked k values (BigInteger) per prime using Microsoft.FASTER.Core. +/// Supports one-time migration from an existing LightningDB (LMDB) store. +/// +public sealed class KStateRepository : IDisposable +{ + private readonly FasterKV _store; + private readonly IDevice _logDevice; + private readonly IDevice _objectLogDevice; + private static readonly Encoding Utf8 = Encoding.UTF8; + + public string DatabasePath { get; } + + public static KStateRepository Open( + string directory, + string repositoryFileName = "kstate.faster", + string? lmdbPath = null, + string? lmdbDirectory = null, + string lmdbDatabaseName = "kstate", + bool deleteLmdbAfterMigration = true) + { + Directory.CreateDirectory(directory); + string dbPath = Path.Combine(directory, repositoryFileName); + var repo = new KStateRepository(dbPath); + + string lmdbSource = lmdbPath ?? Path.ChangeExtension(dbPath, ".lmdb"); + if (!File.Exists(lmdbSource) && !Directory.Exists(lmdbSource) && !string.IsNullOrEmpty(lmdbDirectory)) + { + lmdbSource = Path.Combine(lmdbDirectory, Path.GetFileName(Path.ChangeExtension(repositoryFileName, ".lmdb"))); + } + + repo.MigrateFromLmdb(lmdbSource, lmdbDatabaseName, deleteLmdbAfterMigration); + return repo; + } + + private KStateRepository(string path) + { + DatabasePath = path; + _logDevice = Devices.CreateLogDevice(path + ".log"); + _objectLogDevice = Devices.CreateLogDevice(path + ".obj.log"); + _store = new FasterKV( + 1L << 20, + new LogSettings + { + LogDevice = _logDevice, + ObjectLogDevice = _objectLogDevice, + }); + } + + public void Upsert(ulong prime, BigInteger k) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + string keyString = prime.ToString(CultureInfo.InvariantCulture); + string valueString = k.ToString(CultureInfo.InvariantCulture); + session.Upsert(keyString, valueString); + session.CompletePending(true); + } + + public bool TryGet(ulong prime, out BigInteger value) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + string keyString = prime.ToString(CultureInfo.InvariantCulture); + if (session.Read(keyString, out string? stored).Found && + stored is not null && + BigInteger.TryParse(stored, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) + { + return true; + } + + value = BigInteger.Zero; + return false; + } + + public void SeedFromStates(IEnumerable<(ulong Prime, BigInteger K)> states) + { + foreach (var (prime, k) in states) + { + Upsert(prime, k); + } + } + + public void Commit() + { + _store.Log.FlushAndEvict(wait: true); + } + + public void Dispose() + { + _store.Dispose(); + _logDevice.Dispose(); + _objectLogDevice.Dispose(); + } + + private void MigrateFromLmdb(string lmdbPath, string databaseName, bool deleteAfter) + { + try + { + if (!LmdbExists(lmdbPath)) + { + Console.WriteLine($"No LMDB k-state found at '{lmdbPath}', skipping migration."); + return; + } + + Console.WriteLine($"Migrating k-state from LMDB at '{lmdbPath}' to FASTER..."); + using var env = new LightningEnvironment(lmdbPath) { MaxDatabases = 2 }; + env.Open(); + using var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly); + var db = tx.OpenDatabase(databaseName, new DatabaseConfiguration { Flags = DatabaseOpenFlags.None }); + using var cursor = tx.CreateCursor(db); + foreach (var entry in cursor.AsEnumerable()) + { + string keyString = Utf8.GetString(entry.Item1.CopyToNewArray()); + string valueString = Utf8.GetString(entry.Item2.CopyToNewArray()); + if (ulong.TryParse(keyString, NumberStyles.None, CultureInfo.InvariantCulture, out ulong prime) && + BigInteger.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out BigInteger k) && + k > BigInteger.Zero) + { + Upsert(prime, k); + } + } + _store.Log.FlushAndEvict(wait: true); + Console.WriteLine("Migration of k-state to FASTER completed."); + } + catch + { + // Ignore migration failures; repo will simply start empty. + } + + if (deleteAfter) + { + Console.WriteLine($"Starting cleanup of LMDB k-state at '{lmdbPath}'..."); + try + { + if (Directory.Exists(lmdbPath)) + { + Directory.Delete(lmdbPath, recursive: true); + } + else if (File.Exists(lmdbPath)) + { + File.Delete(lmdbPath); + } + } + catch + { + // ignore cleanup failures + } + } + } + + private static bool LmdbExists(string path) + { + if (Directory.Exists(path) || File.Exists(path)) + { + return true; + } + + string candidate = Path.Combine(path, "data.mdb"); + if (File.Exists(candidate)) + { + return true; + } + + string? directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory)) + { + string sibling = Path.Combine(directory, "data.mdb"); + if (File.Exists(sibling)) + { + return true; + } + } + + return false; + } +} diff --git a/PerfectNumbers.Core/Persistence/Pow2Minus1StateRepository.cs b/PerfectNumbers.Core/Persistence/Pow2Minus1StateRepository.cs new file mode 100644 index 0000000..8badf79 --- /dev/null +++ b/PerfectNumbers.Core/Persistence/Pow2Minus1StateRepository.cs @@ -0,0 +1,165 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using FASTER.core; +using LightningDB; + +namespace PerfectNumbers.Core.Persistence; + +/// +/// Tracks primes for which the pow2-1 divisor check was completed, backed by Microsoft.FASTER.Core. +/// Supports one-time migration from LightningDB. +/// +public sealed class Pow2Minus1StateRepository : IDisposable +{ + private readonly FasterKV _store; + private readonly IDevice _logDevice; + private readonly IDevice _objectLogDevice; + private static readonly Encoding Utf8 = Encoding.UTF8; + + public string DatabasePath { get; } + + public static Pow2Minus1StateRepository Open( + string databasePath, + string lmdbPath = "pow2minus1.lmdb", + string? lmdbDirectory = null, + string lmdbDatabaseName = "pow2minus1", + bool deleteLmdbAfterMigration = true) + { + string directory = Path.GetDirectoryName(databasePath) ?? "."; + Directory.CreateDirectory(directory); + + var repo = new Pow2Minus1StateRepository(databasePath); + + if (!File.Exists(lmdbPath) && !Directory.Exists(lmdbPath) && !string.IsNullOrEmpty(lmdbDirectory)) + { + lmdbPath = Path.Combine(lmdbDirectory, Path.GetFileName(lmdbPath)); + } + + repo.MigrateFromLmdb(lmdbPath, lmdbDatabaseName, deleteLmdbAfterMigration); + return repo; + } + + private Pow2Minus1StateRepository(string path) + { + DatabasePath = path; + _logDevice = Devices.CreateLogDevice(path + ".log"); + _objectLogDevice = Devices.CreateLogDevice(path + ".obj.log"); + _store = new FasterKV( + 1L << 18, + new LogSettings + { + LogDevice = _logDevice, + ObjectLogDevice = _objectLogDevice, + }); + } + + public void MarkChecked(ulong prime) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + string key = prime.ToString(CultureInfo.InvariantCulture); + session.Upsert(key, "checked"); + session.CompletePending(true); + } + + public bool IsChecked(ulong prime) + { + using var session = _store.For(new SimpleFunctions()).NewSession>(); + string key = prime.ToString(CultureInfo.InvariantCulture); + var status = session.Read(key, out _); + session.CompletePending(true); + return status.Found; + } + + public void Commit() + { + _store.Log.FlushAndEvict(wait: true); + } + + public void Dispose() + { + _store.Dispose(); + _logDevice.Dispose(); + _objectLogDevice.Dispose(); + } + + private void MigrateFromLmdb(string lmdbPath, string databaseName, bool deleteAfter) + { + try + { + if (!LmdbExists(lmdbPath)) + { + Console.WriteLine($"No LMDB pow2minus1 state found at '{lmdbPath}', skipping migration."); + return; + } + + Console.WriteLine($"Migrating pow2minus1 state from LMDB at '{lmdbPath}' to FASTER..."); + using var env = new LightningEnvironment(lmdbPath) { MaxDatabases = 2 }; + env.Open(); + using var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly); + var db = tx.OpenDatabase(databaseName, new DatabaseConfiguration { Flags = DatabaseOpenFlags.None }); + using var cursor = tx.CreateCursor(db); + foreach (var entry in cursor.AsEnumerable()) + { + string key = Utf8.GetString(entry.Item1.CopyToNewArray()); + if (ulong.TryParse(key, NumberStyles.None, CultureInfo.InvariantCulture, out ulong prime)) + { + MarkChecked(prime); + } + } + _store.Log.FlushAndEvict(wait: true); + Console.WriteLine("Migration of pow2minus1 state to FASTER completed."); + } + catch + { + // ignore migration issues + } + + if (deleteAfter) + { + Console.WriteLine($"Starting cleanup of LMDB pow2minus1 state at '{lmdbPath}'..."); + try + { + if (Directory.Exists(lmdbPath)) + { + Directory.Delete(lmdbPath, recursive: true); + } + else if (File.Exists(lmdbPath)) + { + File.Delete(lmdbPath); + } + } + catch + { + // ignore cleanup failures + } + } + } + + private static bool LmdbExists(string path) + { + if (Directory.Exists(path) || File.Exists(path)) + { + return true; + } + + string candidate = Path.Combine(path, "data.mdb"); + if (File.Exists(candidate)) + { + return true; + } + + string? directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory)) + { + string sibling = Path.Combine(directory, "data.mdb"); + if (File.Exists(sibling)) + { + return true; + } + } + + return false; + } +} diff --git a/README.md b/README.md index c9d3a2f..ad2cad4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The source code relays on selected functionality from the following packages: * [FluentAssertions](https://www.nuget.org/packages/FluentAssertions) * [ILGPU](https://www.nuget.org/packages/ILGPU) * [ILGPU.Algorithms](https://www.nuget.org/packages/ILGPU.Algorithms) +* [LightningDB](https://www.nuget.org/packages/LightningDB) (LMDB-backed repositories for by-divisor state) * [Open.Numeric.Primes](https://www.nuget.org/packages/Open.Numeric.Primes) * [PeterO.Numbers](https://www.nuget.org/packages/PeterO.Numbers) * [xunit](https://www.nuget.org/packages/xunit) @@ -274,4 +275,4 @@ For the residue wheels, digit masks, and the heuristic order calculation that dr * **Process scope**: divisor cycles dataset, small primes. * **IO**: pooled `StringBuilder` + batched writes. ---- \ No newline at end of file +--- diff --git a/docs/ByDivisorPredictiveScanPlan.md b/docs/ByDivisorPredictiveScanPlan.md index 41cdb82..034ea6e 100644 --- a/docs/ByDivisorPredictiveScanPlan.md +++ b/docs/ByDivisorPredictiveScanPlan.md @@ -42,6 +42,12 @@ - [x] Skip trivial divisors and previously recorded divisors for p when scanning/rebuilding stats. - [x] Persist updated model after processing primes (additions from new divisors found during run). +5) Additional increment modes and state separation +- [x] Add percentile and additive increment modes alongside predictive/pow2/sequential. +- [x] Use mode-specific state file extensions (.predictive/.percentile/.additive) to avoid cross-mode interference. +- [x] Wire CLI flag `--bydivisor-k-increment=predictive|percentile|additive|pow2|sequential` (default predictive) and route Program.cs to the correct tester. +- [x] Extend model percentiles (k10/k25/k50/k75) and delta-k tuning for additive mode. + 4) Validation - [ ] Add targeted unit/functional tests (xUnit + FluentAssertions) for classifier defaults, model rebuild from synthetic results, and scan plan generation for easy vs hard classes. - [ ] Document how to run fast tests (Category=Fast) and keep runtime under 2 minutes.