From 5ce0cf74fc0210c8ac760eaf3af1463dacef9589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 21 Sep 2025 11:11:51 +0200 Subject: [PATCH] Require filtered primes for by-divisor scanning --- EvenPerfectBitScanner/Program.cs | 61 ++++++++++++------- .../MersenneNumberDivisorGpuTesterTests.cs | 33 +++++++++- ...MersenneNumberDivisorByDivisorGpuTester.cs | 48 +++++++++++++-- 3 files changed, 111 insertions(+), 31 deletions(-) diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 5991356..85f5bab 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -374,13 +374,19 @@ internal static class Program { continueCyclesGeneration = true; } - } + } - if (showHelp) - { - PrintHelp(); - return; - } + if (useByDivisor && string.IsNullOrEmpty(filterFile)) + { + Console.WriteLine("--mersenne=bydivisor requires --filter-p=."); + return; + } + + if (showHelp) + { + PrintHelp(); + return; + } // Apply GPU prime sieve runtime configuration GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads); @@ -566,12 +572,12 @@ internal static class Program File.WriteAllText(ResultsFileName, $"p,searchedMersenne,detailedCheck,passedAllTests{Environment.NewLine}"); } - bool useFilter = !string.IsNullOrEmpty(filterFile); - HashSet filter = []; - ulong maxP = 0UL; - if (useFilter) - { - Console.WriteLine("Loading filter..."); + bool useFilter = !string.IsNullOrEmpty(filterFile); + HashSet filter = []; + ulong maxP = 0UL; + if (useFilter) + { + Console.WriteLine("Loading filter..."); ulong[] localFilter = new ulong[1024]; int count = 0; LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) => @@ -596,15 +602,26 @@ internal static class Program if (count > 0) { filter.AddRange(localFilter[..count]); - } - // Restore this if you want to use List instead of HashSet - // filter.Sort(); - } + } + // Restore this if you want to use List instead of HashSet + // filter.Sort(); + } - if (_primeCount >= 2) - { - _primeFoundAfterInit = true; - } + if (useByDivisor) + { + if (maxP <= 1UL) + { + Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor."); + return; + } + + _byDivisorTester!.ConfigureFromMaxPrime(maxP); + } + + if (_primeCount >= 2) + { + _primeFoundAfterInit = true; + } _outputBuilder = StringBuilderPool.Rent(); @@ -785,7 +802,7 @@ internal static class Program Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); // mod-automaton removed - Console.WriteLine(" --filter-p= process only p from previous run results"); + Console.WriteLine(" --filter-p= process only p from previous run results (required for --mersenne=bydivisor)"); Console.WriteLine(" --write-batch-size= overwrite frequency of disk writes (default 100 lines)"); Console.WriteLine(" --gcd-filter enable early sieve based on GCD"); Console.WriteLine(" --help, -help, --?, -?, /? show this help message"); @@ -1143,7 +1160,7 @@ internal static class Program searchedMersenne = true; if (_useByDivisorMode) { - return _byDivisorTester!.IsPrime(p, divisorCyclesSearchLimit, out detailedCheck); + return _byDivisorTester!.IsPrime(p, out detailedCheck); } if (_useDivisor) diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index d46219d..d50edf3 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using PerfectNumbers.Core.Gpu; +using System; using System.Numerics; using System.Reflection; using Xunit; @@ -66,14 +67,40 @@ public class MersenneNumberDivisorGpuTesterTests public void ByDivisor_tester_tracks_divisors_across_primes() { var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.IsPrime(5UL, 64UL, out bool exhausted).Should().BeTrue(); + tester.ConfigureFromMaxPrime(11UL); + + tester.IsPrime(5UL, out bool exhausted).Should().BeTrue(); exhausted.Should().BeTrue(); - tester.IsPrime(7UL, 96UL, out exhausted).Should().BeTrue(); + tester.IsPrime(7UL, out exhausted).Should().BeTrue(); exhausted.Should().BeTrue(); - tester.IsPrime(11UL, 128UL, out exhausted).Should().BeFalse(); + tester.IsPrime(11UL, out exhausted).Should().BeFalse(); exhausted.Should().BeTrue(); } + + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_tester_requires_configuration() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + Action act = () => tester.IsPrime(5UL, out _); + act.Should().Throw(); + } + + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_tester_respects_filter_based_limit() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(5UL); + + tester.IsPrime(5UL, out _).Should().BeTrue(); + tester.IsPrime(7UL, out bool exhausted).Should().BeTrue(); + exhausted.Should().BeTrue(); + + tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeTrue(); + divisorsExhausted.Should().BeTrue(); + } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 0cf2324..0be05a4 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -16,6 +16,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private readonly List _lastPrimes = new(); private readonly object _sync = new(); private ulong _divisorLimit; + private bool _isConfigured; private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView>> _updateKernelCache = new(); private readonly ConcurrentDictionary, ArrayView, ArrayView>> _initialKernelCache = new(); @@ -26,12 +27,32 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private Action, ArrayView, ArrayView> GetInitialKernel(Accelerator accelerator) => _initialKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView>(InitialKernel)); - public bool IsPrime(ulong prime, ulong divisorLimit, out bool divisorsExhausted) + public void ConfigureFromMaxPrime(ulong maxPrime) { lock (_sync) { - _divisorLimit = divisorLimit; - ulong allowedMax = ComputeAllowedMaxDivisor(prime, divisorLimit); + if (_divisors.Count > 0) + { + _divisors.Clear(); + _remainders.Clear(); + _lastPrimes.Clear(); + } + + _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); + _isConfigured = true; + } + } + + public bool IsPrime(ulong prime, out bool divisorsExhausted) + { + lock (_sync) + { + if (!_isConfigured) + { + throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + } + + ulong allowedMax = ComputeAllowedMaxDivisor(prime); if (allowedMax < 3UL) { divisorsExhausted = true; @@ -177,7 +198,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester return composite; } - private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit) + private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime) + { + if (maxPrime <= 1UL) + { + return 0UL; + } + + if (maxPrime - 1UL >= 64UL) + { + return ulong.MaxValue; + } + + return (1UL << (int)(maxPrime - 1UL)) - 1UL; + } + + private ulong ComputeAllowedMaxDivisor(ulong prime) { if (prime <= 1UL) { @@ -186,11 +222,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester if (prime - 1UL >= 64UL) { - return divisorLimit; + return _divisorLimit; } ulong maxByPrime = (1UL << (int)(prime - 1UL)) - 1UL; - return Math.Min(maxByPrime, divisorLimit); + return Math.Min(maxByPrime, _divisorLimit); } private static void UpdateKernel(Index1D index, ulong prime, ArrayView divisors, ArrayView remainders, ArrayView lastPrimes, ArrayView hits)