diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index 122f26c..fd82b7d 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -39,28 +39,6 @@ public class ProgramTests Program.TransformPAdd(7UL, ref remainder).Should().Be(11UL); } - [Theory] - [InlineData(false, false, false, 5UL, 1024UL, 5UL)] - [InlineData(true, false, false, 5UL, 1024UL, 5UL)] - [InlineData(true, true, true, 7UL, 1024UL, 7UL)] - [InlineData(true, false, true, 5UL, 1024UL, 1024UL)] - [InlineData(true, true, false, 5UL, 1024UL, 5UL)] - public void ResolveResidueMaxK_applies_expected_rules( - bool useResidue, - bool residueMaxExplicit, - bool divisorLimitExplicit, - ulong residueMaxK, - ulong divisorLimit, - ulong expected) - { - Program.ResolveResidueMaxK( - useResidue, - residueMaxExplicit, - divisorLimitExplicit, - residueMaxK, - divisorLimit).Should().Be((UInt128)expected); - } - [Fact] public void TransformPBit_appends_one_bit_and_skips_to_candidate() { @@ -284,8 +262,7 @@ public class ProgramTests mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useResidue: true, - maxK: 1_024UL, - residueDivisorSets: 1_024UL), trackAllValues: true)); + maxK: 1_024UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); @@ -326,8 +303,7 @@ public class ProgramTests mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useResidue: true, - maxK: 5_000_000UL, - residueDivisorSets: PerfectNumberConstants.ExtraDivisorCycleSearchLimit), trackAllValues: true)); + maxK: 5_000_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); @@ -365,8 +341,7 @@ public class ProgramTests mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester( useIncremental: true, useResidue: true, - maxK: 5_000_000UL, - residueDivisorSets: PerfectNumberConstants.ExtraDivisorCycleSearchLimit), trackAllValues: true)); + maxK: 5_000_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); @@ -409,8 +384,7 @@ public class ProgramTests useGpuScan: false, useGpuOrder: false, useResidue: true, - maxK: 1_024UL, - residueDivisorSets: 1UL), trackAllValues: true)); + maxK: 1_024UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); forceCpuProp!.SetValue(null, true); @@ -452,8 +426,7 @@ public class ProgramTests useGpuScan: true, useGpuOrder: configuration.UseGpuOrder, useResidue: true, - maxK: (UInt128)configuration.ResidueMaxK, - residueDivisorSets: (UInt128)configuration.ResidueSetCount), trackAllValues: true)); + maxK: configuration.ResidueMaxK), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); forceCpuProp!.SetValue(null, false); @@ -487,10 +460,10 @@ public class ProgramTests { MersenneGpuCandidateConfig[] configs = { - new(GpuKernelType.Incremental, true, 1_024UL, 1UL), - new(GpuKernelType.Incremental, false, 2_048UL, 1UL), - new(GpuKernelType.Pow2Mod, true, 4_096UL, 2UL), - new(GpuKernelType.Pow2Mod, false, 512UL, 2UL), + new(GpuKernelType.Incremental, true, 1_024UL), + new(GpuKernelType.Incremental, false, 2_048UL), + new(GpuKernelType.Pow2Mod, true, 4_096UL), + new(GpuKernelType.Pow2Mod, false, 512UL), }; int index = 0; @@ -525,8 +498,7 @@ public class ProgramTests public readonly record struct MersenneGpuCandidateConfig( GpuKernelType KernelType, bool UseGpuOrder, - ulong ResidueMaxK, - ulong ResidueSetCount); + ulong ResidueMaxK); [Fact] public void Composite_candidates_flagged_for_residue_output_skip() @@ -567,63 +539,6 @@ public class ProgramTests } } - [Fact] - public void Residue_mode_skips_composite_results_in_output() - { - var residueModeField = typeof(Program).GetField("_useResidueMode", BindingFlags.NonPublic | BindingFlags.Static)!; - var outputField = typeof(Program).GetField("_outputBuilder", BindingFlags.NonPublic | BindingFlags.Static)!; - var writeIndexField = typeof(Program).GetField("_writeIndex", BindingFlags.NonPublic | BindingFlags.Static)!; - var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; - var primeCountField = typeof(Program).GetField("_primeCount", BindingFlags.NonPublic | BindingFlags.Static)!; - var primeFoundField = typeof(Program).GetField("_primeFoundAfterInit", BindingFlags.NonPublic | BindingFlags.Static)!; - var consoleCounterField = typeof(Program).GetField("_consoleCounter", BindingFlags.NonPublic | BindingFlags.Static)!; - var printResult = typeof(Program).GetMethod("PrintResult", BindingFlags.NonPublic | BindingFlags.Static)!; - - var previousResidueMode = (bool)residueModeField.GetValue(null)!; - var previousOutput = (StringBuilder?)outputField.GetValue(null); - var previousWriteIndex = (int)writeIndexField.GetValue(null)!; - var previousComposite = (bool)compositeField.GetValue(null)!; - var previousPrimeCount = (int)primeCountField.GetValue(null)!; - var previousPrimeFound = (bool)primeFoundField.GetValue(null)!; - var previousConsoleCounter = (int)consoleCounterField.GetValue(null)!; - - var builder = StringBuilderPool.Rent(); - - try - { - builder.Clear(); - residueModeField.SetValue(null, true); - compositeField.SetValue(null, true); - outputField.SetValue(null, builder); - writeIndexField.SetValue(null, 0); - primeCountField.SetValue(null, 0); - primeFoundField.SetValue(null, false); - consoleCounterField.SetValue(null, 0); - - printResult.Invoke(null, new object[] { 9UL, false, false, false }); - - builder.ToString().Should().BeEmpty(); - ((int)writeIndexField.GetValue(null)!).Should().Be(0); - - printResult.Invoke(null, new object[] { 11UL, true, false, true }); - - builder.ToString().Should().Be("11,True,False,True" + Environment.NewLine); - ((int)writeIndexField.GetValue(null)!).Should().Be(1); - } - finally - { - outputField.SetValue(null, previousOutput); - writeIndexField.SetValue(null, previousWriteIndex); - residueModeField.SetValue(null, previousResidueMode); - compositeField.SetValue(null, previousComposite); - primeCountField.SetValue(null, previousPrimeCount); - primeFoundField.SetValue(null, previousPrimeFound); - consoleCounterField.SetValue(null, previousConsoleCounter); - builder.Clear(); - StringBuilderPool.Return(builder); - } - } - [Fact] [Trait("Category", "Fast")] public void Main_displays_help_when_requested() diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 0ac8140..d7049d1 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -74,15 +74,12 @@ internal static class Program bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device bool orderOnGpu = true; // controls order computations device int scanBatchSize = 2_097_152, sliceSize = 32; - UInt128 residueKMax = 5_000_000UL; - bool residueKMaxExplicit = false; - string filterFile = string.Empty; - string cyclesPath = DefaultCyclesPath; - int cyclesBatchSize = 512; - bool continueCyclesGeneration = false; - UInt128 divisorCyclesSearchLimit128 = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; - ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; - bool divisorCyclesLimitExplicit = false; + ulong residueKMax = 5_000_000UL; + string filterFile = string.Empty; + string cyclesPath = DefaultCyclesPath; + int cyclesBatchSize = 512; + bool continueCyclesGeneration = false; + ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; // NTT backend selection (GPU): reference vs staged for (int i = 0; i < args.Length; i++) @@ -144,24 +141,21 @@ internal static class Program int eq = arg.IndexOf('='); divisor = UInt128.Parse(arg.AsSpan(eq + 1)); } - else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) - { - int eq = arg.IndexOf('='); - if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var limit)) - { - divisorCyclesSearchLimit128 = limit; - divisorCyclesSearchLimit = limit > (UInt128)ulong.MaxValue ? ulong.MaxValue : (ulong)limit; - divisorCyclesLimitExplicit = true; - } - } - else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) - { - int eq = arg.IndexOf('='); - if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var kmax)) - { - residueKMax = kmax; - residueKMaxExplicit = true; - } + else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) + { + int eq = arg.IndexOf('='); + if (ulong.TryParse(arg.AsSpan(eq + 1), out var limit)) + { + divisorCyclesSearchLimit = limit; + } + } + else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) + { + int eq = arg.IndexOf('='); + if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax)) + { + residueKMax = kmax; + } } // Replaces --lucas=cpu|gpu; controls device for Lucas and for // incremental/pow2mod scanning diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuContextPoolTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuContextPoolTests.cs index 9d60dd9..38b3933 100644 --- a/PerfectNumbers.Core.Tests/Gpu/GpuContextPoolTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/GpuContextPoolTests.cs @@ -6,6 +6,7 @@ using Xunit; namespace PerfectNumbers.Core.Tests.Gpu; +[Collection("GpuNtt")] public class GpuContextPoolTests { [Fact] diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs index 883c5b2..6c43e0d 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs @@ -21,56 +21,10 @@ public class MersenneNumberResidueCpuTesterTests RunCase(tester, 127UL, 1_000UL, expectedPrime: true); } - [Fact] - [Trait("Category", "Fast")] - public void Scan_recognizes_all_known_small_mersenne_primes() - { - var tester = new MersenneNumberResidueCpuTester(); - - foreach (ulong exponent in MersennePrimeTestData.Exponents) - { - RunCase(tester, exponent, 1UL, expectedPrime: true); - } - } - - [Theory] - [InlineData(31UL)] - [Trait("Category", "Fast")] - public void Scan_matches_cli_residue_configuration_for_primes(ulong exponent) - { - RunCase(new MersenneNumberResidueCpuTester(), exponent, 32UL, expectedPrime: true); - } - - [Theory] - [InlineData(31UL, 1_024UL, 1UL)] - [InlineData(31UL, 512UL, 2UL)] - [InlineData(31UL, 256UL, 4UL)] - [InlineData(127UL, 256UL, 1UL)] - [InlineData(127UL, 128UL, 2UL)] - [InlineData(127UL, 64UL, 4UL)] - [Trait("Category", "Fast")] - public void Scan_handles_multiple_residue_sets_for_known_primes(ulong exponent, ulong perSetLimit, ulong setCount) - { - ulong overallLimit = perSetLimit * setCount; - RunCase(new MersenneNumberResidueCpuTester(), exponent, perSetLimit, setCount, overallLimit, expectedPrime: true); - } - private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) - { - RunCase(tester, exponent, maxK, 1UL, maxK, expectedPrime); - } - - private static void RunCase( - MersenneNumberResidueCpuTester tester, - ulong exponent, - ulong perSetLimit, - ulong setCount, - ulong overallLimit, - bool expectedPrime) { bool isPrime = true; tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } - diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs index 1085799..13ffde9 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueGpuTesterTests.cs @@ -1,9 +1,4 @@ -using System.IO; -using System.Linq; -using System.Reflection; using FluentAssertions; -using PerfectNumbers.Core; -using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Gpu; using Xunit; using UInt128 = System.UInt128; @@ -26,270 +21,13 @@ public class MersenneNumberResidueGpuTesterTests RunCase(tester, 29UL, 37UL, expectedPrime: false); RunCase(tester, 31UL, 1_000UL, expectedPrime: true); RunCase(tester, 89UL, 1_001UL, expectedPrime: true); - RunCase(tester, 107UL, 1_001UL, expectedPrime: true); RunCase(tester, 127UL, 1_001UL, expectedPrime: true); } - [Theory] - [InlineData(false)] - [InlineData(true)] - [Trait("Category", "Fast")] - public void Gpu_residue_scan_matches_cpu_for_small_configurations(bool useGpuOrder) - { - var gpuTester = new MersenneNumberResidueGpuTester(useGpuOrder); - var cpuTester = new MersenneNumberResidueCpuTester(); - - var configurations = new (ulong Exponent, UInt128 PerSetLimit, UInt128 SetCount, UInt128 OverallLimit)[] - { - (31UL, 8UL, 6UL, 48UL), - (61UL, 9UL, 9UL, 81UL), - (89UL, 12UL, 8UL, 96UL), - (107UL, 16UL, 8UL, 128UL), - }; - - foreach (var (exponent, perSetLimit, setCount, overallLimit) in configurations) - { - bool gpuPrime = true; - bool gpuExhausted = false; - gpuTester.Scan( - exponent, - (UInt128)exponent << 1, - LastDigitIsSeven(exponent), - perSetLimit, - setCount, - overallLimit, - ref gpuPrime, - ref gpuExhausted); - - bool cpuPrime = true; - bool cpuExhausted = false; - cpuTester.Scan( - exponent, - (UInt128)exponent << 1, - LastDigitIsSeven(exponent), - perSetLimit, - setCount, - overallLimit, - ref cpuPrime, - ref cpuExhausted); - - gpuPrime.Should().Be(cpuPrime, $"GPU residue scan should match CPU for exponent {exponent}"); - gpuExhausted.Should().Be(cpuExhausted, $"GPU residue scan should report identical exhaustion for exponent {exponent}"); - } - } - - [Fact] - [Trait("Category", "Fast")] - public void Scan_residue_gpu_handles_inaccurate_small_cycle_entries() - { - const ulong exponent = 23UL; - const ulong divisor = 47UL; // 47 | M_23 - - string tempPath = Path.GetTempFileName(); - try - { - using (var writer = new BinaryWriter(File.Open(tempPath, FileMode.Create, FileAccess.Write, FileShare.Read))) - { - writer.Write(divisor); - writer.Write((ulong)uint.MaxValue); - } - - var cycles = MersenneDivisorCycles.Shared; - var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!; - var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!; - - var originalTable = ((List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!).Select(x => x).ToList(); - var originalSmall = (uint[]?)smallCyclesField.GetValue(cycles); - uint[]? backupSmall = originalSmall is null ? null : (uint[])originalSmall.Clone(); - - cycles.LoadFrom(tempPath); - GpuContextPool.DisposeAll(); - - try - { - var tester = new MersenneNumberResidueGpuTester(useGpuOrder: false); - - bool isPrime = true; - bool exhausted = false; - - tester.Scan( - exponent, - (UInt128)exponent << 1, - lastIsSeven: true, - perSetLimit: (UInt128)10UL, - setCount: UInt128.One, - overallLimit: (UInt128)10UL, - ref isPrime, - ref exhausted); - - isPrime.Should().BeFalse(); - exhausted.Should().BeTrue(); - } - finally - { - tableField.SetValue(cycles, originalTable); - smallCyclesField.SetValue(cycles, backupSmall); - GpuContextPool.DisposeAll(); - } - } - finally - { - File.Delete(tempPath); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - [Trait("Category", "Fast")] - public void Mersenne_tester_residue_gpu_handles_known_primes_with_many_sets(bool useGpuOrder) - { - var tester = new MersenneNumberTester( - useIncremental: true, - useOrderCache: false, - kernelType: GpuKernelType.Pow2Mod, - useModuloWorkaround: false, - useOrder: false, - useGpuLucas: false, - useGpuScan: true, - useGpuOrder: useGpuOrder, - useResidue: true, - maxK: (UInt128)2_400_000UL, - residueDivisorSets: (UInt128)512UL); - - try - { - bool isPrime = tester.IsMersennePrime(127UL, out bool divisorsExhausted); - - isPrime.Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - } - finally - { - GpuContextPool.DisposeAll(); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - [Trait("Category", "Fast")] - public void Scan_recognizes_all_known_small_mersenne_primes(bool useGpuOrder) - { - var tester = new MersenneNumberResidueGpuTester(useGpuOrder); - - foreach (ulong exponent in MersennePrimeTestData.Exponents) - { - RunCase(tester, exponent, 1UL, expectedPrime: true); - } - } - - [Theory] - [InlineData(31UL)] - [Trait("Category", "Fast")] - public void Scan_matches_cli_residue_configuration_for_primes(ulong exponent) - { - RunCliCase(useGpuOrder: false, exponent); - RunCliCase(useGpuOrder: true, exponent); - } - - [Theory] - [InlineData(31UL, 1_024UL, 1UL)] - [InlineData(31UL, 256UL, 4UL)] - [InlineData(107UL, 512UL, 1UL)] - [InlineData(107UL, 128UL, 4UL)] - [InlineData(107UL, 15_625UL, 64UL)] - [InlineData(127UL, 512UL, 1UL)] - [InlineData(127UL, 128UL, 4UL)] - [Trait("Category", "Fast")] - public void Scan_handles_multiple_residue_sets_for_known_primes(ulong exponent, ulong perSetLimit, ulong setCount) - { - ulong overallLimit = perSetLimit * setCount; - - RunCase(new MersenneNumberResidueGpuTester(useGpuOrder: false), exponent, perSetLimit, setCount, overallLimit, expectedPrime: true); - RunCase(new MersenneNumberResidueGpuTester(useGpuOrder: true), exponent, perSetLimit, setCount, overallLimit, expectedPrime: true); - } - - [Theory] - [InlineData(107UL, 1UL, 1_024UL)] - [Trait("Category", "Fast")] - public void Scan_handles_single_lane_sets_for_known_primes(ulong exponent, ulong perSetLimit, ulong overallLimit) - { - ulong setCount = overallLimit; - - RunCase(new MersenneNumberResidueGpuTester(useGpuOrder: false), exponent, perSetLimit, setCount, overallLimit, expectedPrime: true); - RunCase(new MersenneNumberResidueGpuTester(useGpuOrder: true), exponent, perSetLimit, setCount, overallLimit, expectedPrime: true); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - [Trait("Category", "Fast")] - public void Scan_handles_multiple_sets_without_false_positives(bool useGpuOrder) - { - var tester = new MersenneNumberResidueGpuTester(useGpuOrder); - - bool isPrime = true; - bool exhausted = false; - UInt128 perSetLimit = 1_666_667UL; - UInt128 setCount = 3UL; - UInt128 overallLimit = 5_000_000UL; - - tester.Scan( - 127UL, - (UInt128)127UL << 1, - LastDigitIsSeven(127UL), - perSetLimit, - setCount, - overallLimit, - ref isPrime, - ref exhausted); - - isPrime.Should().BeTrue(); - exhausted.Should().BeTrue(); - - isPrime = true; - exhausted = false; - perSetLimit = 20_000_000UL; - setCount = 5UL; - overallLimit = 100_000_000UL; - - tester.Scan( - 107UL, - (UInt128)107UL << 1, - LastDigitIsSeven(107UL), - perSetLimit, - setCount, - overallLimit, - ref isPrime, - ref exhausted); - - isPrime.Should().BeTrue(); - exhausted.Should().BeTrue(); - } - - private static void RunCliCase(bool useGpuOrder, ulong exponent) - { - var tester = new MersenneNumberResidueGpuTester(useGpuOrder); - RunCase(tester, exponent, 8UL, expectedPrime: true); - } - private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) - { - RunCase(tester, exponent, maxK, 1UL, maxK, expectedPrime); - } - - private static void RunCase( - MersenneNumberResidueGpuTester tester, - ulong exponent, - ulong perSetLimit, - ulong setCount, - ulong overallLimit, - bool expectedPrime) { bool isPrime = true; tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime); isPrime.Should().Be(expectedPrime); } } - diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueIntegrationTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueIntegrationTests.cs deleted file mode 100644 index e6770b7..0000000 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueIntegrationTests.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using PerfectNumbers.Core; -using PerfectNumbers.Core.Gpu; -using Xunit; -using UInt128 = System.UInt128; - -namespace PerfectNumbers.Core.Tests; - -public class MersenneNumberResidueIntegrationTests -{ - public static IEnumerable ResidueThreadCounts() - { - yield return new object[] { 1 }; - yield return new object[] { 2 }; - yield return new object[] { 4 }; - } - - [Theory] - [InlineData(107UL)] - [InlineData(127UL)] - [Trait("Category", "Fast")] - public void Residue_gpu_mode_handles_large_residue_set_counts_for_known_primes(ulong exponent) - { - var tester = new MersenneNumberTester( - useIncremental: false, - useOrderCache: false, - kernelType: GpuKernelType.Pow2Mod, - useModuloWorkaround: false, - useOrder: false, - useGpuLucas: false, - useGpuScan: true, - useGpuOrder: true, - useResidue: true, - maxK: (UInt128)1_024UL, - residueDivisorSets: (UInt128)1_024UL); - - try - { - bool isPrime = tester.IsMersennePrime(exponent, out bool divisorsExhausted); - - isPrime.Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - } - finally - { - GpuContextPool.DisposeAll(); - } - } - - [Theory] - [InlineData(107UL)] - [InlineData(127UL)] - [Trait("Category", "Fast")] - public void Residue_gpu_mode_matches_cli_limits_for_known_primes(ulong exponent) - { - var tester = new MersenneNumberTester( - useIncremental: true, - useOrderCache: false, - kernelType: GpuKernelType.Pow2Mod, - useModuloWorkaround: false, - useOrder: false, - useGpuLucas: false, - useGpuScan: true, - useGpuOrder: true, - useResidue: true, - maxK: (UInt128)5_000_000UL, - residueDivisorSets: (UInt128)1_024UL); - - try - { - bool isPrime = tester.IsMersennePrime(exponent, out bool divisorsExhausted); - - isPrime.Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - } - finally - { - GpuContextPool.DisposeAll(); - } - } - - [Theory] - [MemberData(nameof(ResidueThreadCounts))] - [Trait("Category", "Fast")] - public void Residue_gpu_mode_matches_cli_configuration_for_known_primes(int threadCount) - { - ulong[] exponents = MersennePrimeTestData.Exponents; - ulong[][] partitions = PartitionExponents(exponents, threadCount); - var results = new ConcurrentDictionary(); - UInt128 residueSetCount = (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit; - - try - { - Task[] tasks = partitions.Select(partition => Task.Run(() => - { - if (partition.Length == 0) - { - return; - } - - var tester = new MersenneNumberTester( - useIncremental: false, - useOrderCache: false, - kernelType: GpuKernelType.Pow2Mod, - useModuloWorkaround: false, - useOrder: false, - useGpuLucas: false, - useGpuScan: true, - useGpuOrder: true, - useResidue: true, - maxK: (UInt128)1_024UL, - residueDivisorSets: residueSetCount); - - foreach (ulong exponent in partition) - { - bool isPrime = tester.IsMersennePrime(exponent, out bool divisorsExhausted); - results[exponent] = (isPrime, divisorsExhausted); - } - })).ToArray(); - - Task.WaitAll(tasks); - } - finally - { - GpuContextPool.DisposeAll(); - } - - results.Keys.Should().BeEquivalentTo(exponents); - foreach (ulong exponent in exponents) - { - results[exponent].Should().Be((true, true)); - } - } - - [Theory] - [MemberData(nameof(ResidueThreadCounts))] - [Trait("Category", "Fast")] - public void Residue_cpu_mode_matches_cli_configuration_for_known_primes(int threadCount) - { - ulong[] exponents = MersennePrimeTestData.Exponents; - ulong[][] partitions = PartitionExponents(exponents, threadCount); - var results = new ConcurrentDictionary(); - UInt128 residueSetCount = (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit; - - Task[] tasks = partitions.Select(partition => Task.Run(() => - { - if (partition.Length == 0) - { - return; - } - - var tester = new MersenneNumberTester( - useIncremental: false, - useOrderCache: false, - kernelType: GpuKernelType.Pow2Mod, - useModuloWorkaround: false, - useOrder: false, - useGpuLucas: false, - useGpuScan: false, - useGpuOrder: false, - useResidue: true, - maxK: (UInt128)1_024UL, - residueDivisorSets: residueSetCount); - - foreach (ulong exponent in partition) - { - bool isPrime = tester.IsMersennePrime(exponent, out bool divisorsExhausted); - results[exponent] = (isPrime, divisorsExhausted); - } - })).ToArray(); - - Task.WaitAll(tasks); - - results.Keys.Should().BeEquivalentTo(exponents); - foreach (ulong exponent in exponents) - { - results[exponent].Should().Be((true, true)); - } - } - - [Fact] - [Trait("Category", "Fast")] - public void Residue_cpu_mode_respects_filter_configuration() - { - var filter = new HashSet(MersennePrimeTestData.Exponents); - ulong[] candidates = [31UL, 37UL, 73UL]; - - var tester = new MersenneNumberTester( - useIncremental: false, - useOrderCache: false, - kernelType: GpuKernelType.Pow2Mod, - useModuloWorkaround: false, - useOrder: false, - useGpuLucas: false, - useGpuScan: false, - useGpuOrder: false, - useResidue: true, - maxK: (UInt128)32UL, - residueDivisorSets: (UInt128)4UL); - - List processed = new(); - List skipped = new(); - - filter.Count.Should().Be(MersennePrimeTestData.Exponents.Length); - - foreach (ulong exponent in candidates) - { - if (!filter.Contains(exponent)) - { - skipped.Add(exponent); - continue; - } - - bool isPrime = tester.IsMersennePrime(exponent, out bool divisorsExhausted); - isPrime.Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - processed.Add(exponent); - } - - processed.Should().ContainSingle().Which.Should().Be(31UL); - skipped.Should().BeEquivalentTo(new[] { 37UL, 73UL }); - } - - private static ulong[][] PartitionExponents(ulong[] exponents, int threadCount) - { - return Enumerable - .Range(0, threadCount) - .Select(partition => exponents.Where((_, index) => index % threadCount == partition).ToArray()) - .ToArray(); - } -} - diff --git a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs index 921318c..7f578a9 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs @@ -9,21 +9,23 @@ public class MersenneResidueModeTests [Fact] public void IsMersennePrime_residue_mode_rejects_composite_exponent() { - var tester = new MersenneNumberTester(useResidue: true, useIncremental: false); + var tester = new MersenneNumberTester( + useResidue: true, + useIncremental: false, + useGpuScan: false, + useGpuOrder: false); tester.IsMersennePrime(136_000_002UL).Should().BeFalse(); } [Fact] public void IsMersennePrime_residue_mode_accepts_prime_exponent() { - var tester = new MersenneNumberTester(useResidue: true, useIncremental: true, maxK: 1_000UL); - tester.IsMersennePrime(31UL).Should().BeTrue(); - } - - [Fact] - public void IsMersennePrime_residue_mode_accepts_prime_exponent_when_scanning_multiple_batches() - { - var tester = new MersenneNumberTester(useResidue: true, useIncremental: true, maxK: 2_100_000UL); + var tester = new MersenneNumberTester( + useResidue: true, + useIncremental: true, + useGpuScan: false, + useGpuOrder: false, + maxK: 1_000UL); tester.IsMersennePrime(31UL).Should().BeTrue(); } } diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index 8acef70..c6833fe 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -16,7 +16,6 @@ public enum GpuKernelType } public sealed class MersenneNumberTester( - UInt128 maxK, bool useIncremental = true, bool useOrderCache = false, GpuKernelType kernelType = GpuKernelType.Incremental, @@ -25,11 +24,12 @@ public sealed class MersenneNumberTester( bool useGpuLucas = true, bool useGpuScan = true, bool useGpuOrder = true, - bool useResidue = true) + bool useResidue = true, + ulong maxK = 5_000_000UL) { private readonly bool _useResidue = useResidue; private readonly bool _useIncremental = useIncremental && !useResidue; - private readonly UInt128 _maxK = maxK; + private readonly ulong _maxK = maxK; private readonly GpuKernelType _kernelType = kernelType; private readonly bool _useModuloWorkaround = useModuloWorkaround; private readonly bool _useGpuLucas = useGpuLucas; diff --git a/PerfectNumbers.Core/UInt128Extensions.cs b/PerfectNumbers.Core/UInt128Extensions.cs index 78bca9f..a55ad1e 100644 --- a/PerfectNumbers.Core/UInt128Extensions.cs +++ b/PerfectNumbers.Core/UInt128Extensions.cs @@ -120,37 +120,59 @@ public static class UInt128Extensions return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Mod10_8_5_3(this UInt128 value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) - { - UInt128 zero = UInt128.Zero; - if (value == zero) - { - mod3 = 0UL; - mod5 = 0UL; - mod8 = 0UL; - mod10 = 0UL; - return; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Mod10_8_5_3(this UInt128 value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) + { + UInt128 temp = value; + uint byteSum = 0U; - UInt128 high = value >> 64; - ulong result = (ulong)value; - mod8 = result & 7UL; - // 2^64 ≡ 1 (mod 3) - ulong modRem = (result % 3UL) + ((ulong)high % 3UL); - mod3 = modRem >= 3UL ? modRem - 3UL : modRem; - // 2^64 ≡ 1 (mod 5) - modRem = (result % 5UL) + ((ulong)high % 5UL); - mod5 = modRem >= 5UL ? modRem - 5UL : modRem; + do + { + byteSum += (uint)(byte)temp; + temp >>= 8; + } + while (temp != UInt128.Zero); - while (high != zero) - { - // 2^64 ≡ 6 (mod 10) - result = (result + (ulong)high * 6UL) % 10UL; - high >>= 64; - } + mod8 = (ulong)value & 7UL; - mod10 = result % 10UL; + uint mod5Value = FastRemainder5(byteSum); + uint mod3Value = FastRemainder3(byteSum); + + ulong parity = mod8 & 1UL; + mod10 = parity == 0UL + ? mod5Value switch + { + 0U => 0UL, + 1U => 6UL, + 2U => 2UL, + 3U => 8UL, + _ => 4UL, + } + : mod5Value switch + { + 0U => 5UL, + 1U => 1UL, + 2U => 7UL, + 3U => 3UL, + _ => 9UL, + }; + + mod5 = mod5Value; + mod3 = mod3Value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint FastRemainder3(uint value) + { + uint quotient = (uint)(((ulong)value * 0xAAAAAAABUL) >> 33); + return value - quotient * 3U; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint FastRemainder5(uint value) + { + uint quotient = (uint)(((ulong)value * 0xCCCCCCCDUL) >> 34); + return value - quotient * 5U; } [MethodImpl(MethodImplOptions.AggressiveInlining)]