Merge branch 'main' of https://github.com/mlemanczyk/even-perfect-numbers-scanner
This commit is contained in:
commit
186895e174
9 files changed
+96
-706
No files matched your search
@@ -39,28 +39,6 @@ public class ProgramTests
|
|||||||
Program.TransformPAdd(7UL, ref remainder).Should().Be(11UL);
|
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]
|
[Fact]
|
||||||
public void TransformPBit_appends_one_bit_and_skips_to_candidate()
|
public void TransformPBit_appends_one_bit_and_skips_to_candidate()
|
||||||
{
|
{
|
||||||
@@ -284,8 +262,7 @@ public class ProgramTests
|
|||||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
useIncremental: true,
|
useIncremental: true,
|
||||||
useResidue: true,
|
useResidue: true,
|
||||||
maxK: 1_024UL,
|
maxK: 1_024UL), trackAllValues: true));
|
||||||
residueDivisorSets: 1_024UL), trackAllValues: true));
|
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
forceCpuProp!.SetValue(null, false);
|
forceCpuProp!.SetValue(null, false);
|
||||||
@@ -326,8 +303,7 @@ public class ProgramTests
|
|||||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
useIncremental: true,
|
useIncremental: true,
|
||||||
useResidue: true,
|
useResidue: true,
|
||||||
maxK: 5_000_000UL,
|
maxK: 5_000_000UL), trackAllValues: true));
|
||||||
residueDivisorSets: PerfectNumberConstants.ExtraDivisorCycleSearchLimit), trackAllValues: true));
|
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
forceCpuProp!.SetValue(null, false);
|
forceCpuProp!.SetValue(null, false);
|
||||||
@@ -365,8 +341,7 @@ public class ProgramTests
|
|||||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
useIncremental: true,
|
useIncremental: true,
|
||||||
useResidue: true,
|
useResidue: true,
|
||||||
maxK: 5_000_000UL,
|
maxK: 5_000_000UL), trackAllValues: true));
|
||||||
residueDivisorSets: PerfectNumberConstants.ExtraDivisorCycleSearchLimit), trackAllValues: true));
|
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
forceCpuProp!.SetValue(null, false);
|
forceCpuProp!.SetValue(null, false);
|
||||||
@@ -409,8 +384,7 @@ public class ProgramTests
|
|||||||
useGpuScan: false,
|
useGpuScan: false,
|
||||||
useGpuOrder: false,
|
useGpuOrder: false,
|
||||||
useResidue: true,
|
useResidue: true,
|
||||||
maxK: 1_024UL,
|
maxK: 1_024UL), trackAllValues: true));
|
||||||
residueDivisorSets: 1UL), trackAllValues: true));
|
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
||||||
forceCpuProp!.SetValue(null, true);
|
forceCpuProp!.SetValue(null, true);
|
||||||
@@ -452,8 +426,7 @@ public class ProgramTests
|
|||||||
useGpuScan: true,
|
useGpuScan: true,
|
||||||
useGpuOrder: configuration.UseGpuOrder,
|
useGpuOrder: configuration.UseGpuOrder,
|
||||||
useResidue: true,
|
useResidue: true,
|
||||||
maxK: (UInt128)configuration.ResidueMaxK,
|
maxK: configuration.ResidueMaxK), trackAllValues: true));
|
||||||
residueDivisorSets: (UInt128)configuration.ResidueSetCount), trackAllValues: true));
|
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
||||||
forceCpuProp!.SetValue(null, false);
|
forceCpuProp!.SetValue(null, false);
|
||||||
@@ -487,10 +460,10 @@ public class ProgramTests
|
|||||||
{
|
{
|
||||||
MersenneGpuCandidateConfig[] configs =
|
MersenneGpuCandidateConfig[] configs =
|
||||||
{
|
{
|
||||||
new(GpuKernelType.Incremental, true, 1_024UL, 1UL),
|
new(GpuKernelType.Incremental, true, 1_024UL),
|
||||||
new(GpuKernelType.Incremental, false, 2_048UL, 1UL),
|
new(GpuKernelType.Incremental, false, 2_048UL),
|
||||||
new(GpuKernelType.Pow2Mod, true, 4_096UL, 2UL),
|
new(GpuKernelType.Pow2Mod, true, 4_096UL),
|
||||||
new(GpuKernelType.Pow2Mod, false, 512UL, 2UL),
|
new(GpuKernelType.Pow2Mod, false, 512UL),
|
||||||
};
|
};
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -525,8 +498,7 @@ public class ProgramTests
|
|||||||
public readonly record struct MersenneGpuCandidateConfig(
|
public readonly record struct MersenneGpuCandidateConfig(
|
||||||
GpuKernelType KernelType,
|
GpuKernelType KernelType,
|
||||||
bool UseGpuOrder,
|
bool UseGpuOrder,
|
||||||
ulong ResidueMaxK,
|
ulong ResidueMaxK);
|
||||||
ulong ResidueSetCount);
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Composite_candidates_flagged_for_residue_output_skip()
|
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]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Main_displays_help_when_requested()
|
public void Main_displays_help_when_requested()
|
||||||
|
|||||||
@@ -74,15 +74,12 @@ internal static class Program
|
|||||||
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
||||||
bool orderOnGpu = true; // controls order computations device
|
bool orderOnGpu = true; // controls order computations device
|
||||||
int scanBatchSize = 2_097_152, sliceSize = 32;
|
int scanBatchSize = 2_097_152, sliceSize = 32;
|
||||||
UInt128 residueKMax = 5_000_000UL;
|
ulong residueKMax = 5_000_000UL;
|
||||||
bool residueKMaxExplicit = false;
|
string filterFile = string.Empty;
|
||||||
string filterFile = string.Empty;
|
string cyclesPath = DefaultCyclesPath;
|
||||||
string cyclesPath = DefaultCyclesPath;
|
int cyclesBatchSize = 512;
|
||||||
int cyclesBatchSize = 512;
|
bool continueCyclesGeneration = false;
|
||||||
bool continueCyclesGeneration = false;
|
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
||||||
UInt128 divisorCyclesSearchLimit128 = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
|
||||||
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
|
||||||
bool divisorCyclesLimitExplicit = false;
|
|
||||||
|
|
||||||
// NTT backend selection (GPU): reference vs staged
|
// NTT backend selection (GPU): reference vs staged
|
||||||
for (int i = 0; i < args.Length; i++)
|
for (int i = 0; i < args.Length; i++)
|
||||||
@@ -144,24 +141,21 @@ internal static class Program
|
|||||||
int eq = arg.IndexOf('=');
|
int eq = arg.IndexOf('=');
|
||||||
divisor = UInt128.Parse(arg.AsSpan(eq + 1));
|
divisor = UInt128.Parse(arg.AsSpan(eq + 1));
|
||||||
}
|
}
|
||||||
else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
int eq = arg.IndexOf('=');
|
int eq = arg.IndexOf('=');
|
||||||
if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var limit))
|
if (ulong.TryParse(arg.AsSpan(eq + 1), out var limit))
|
||||||
{
|
{
|
||||||
divisorCyclesSearchLimit128 = limit;
|
divisorCyclesSearchLimit = limit;
|
||||||
divisorCyclesSearchLimit = limit > (UInt128)ulong.MaxValue ? ulong.MaxValue : (ulong)limit;
|
}
|
||||||
divisorCyclesLimitExplicit = true;
|
}
|
||||||
}
|
else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
||||||
}
|
{
|
||||||
else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
int eq = arg.IndexOf('=');
|
||||||
{
|
if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax))
|
||||||
int eq = arg.IndexOf('=');
|
{
|
||||||
if (UInt128.TryParse(arg.AsSpan(eq + 1), NumberStyles.Integer, provider: null, out var kmax))
|
residueKMax = kmax;
|
||||||
{
|
}
|
||||||
residueKMax = kmax;
|
|
||||||
residueKMaxExplicit = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Replaces --lucas=cpu|gpu; controls device for Lucas and for
|
// Replaces --lucas=cpu|gpu; controls device for Lucas and for
|
||||||
// incremental/pow2mod scanning
|
// incremental/pow2mod scanning
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Xunit;
|
|||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||||
|
|
||||||
|
[Collection("GpuNtt")]
|
||||||
public class GpuContextPoolTests
|
public class GpuContextPoolTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -21,56 +21,10 @@ public class MersenneNumberResidueCpuTesterTests
|
|||||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
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)
|
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;
|
bool isPrime = true;
|
||||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||||
isPrime.Should().Be(expectedPrime);
|
isPrime.Should().Be(expectedPrime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using PerfectNumbers.Core.Cpu;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
@@ -26,270 +21,13 @@ public class MersenneNumberResidueGpuTesterTests
|
|||||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||||
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
||||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||||
RunCase(tester, 107UL, 1_001UL, expectedPrime: true);
|
|
||||||
RunCase(tester, 127UL, 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)
|
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;
|
bool isPrime = true;
|
||||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||||
isPrime.Should().Be(expectedPrime);
|
isPrime.Should().Be(expectedPrime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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<object[]> 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<ulong, (bool IsPrime, bool Exhausted)>();
|
|
||||||
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<ulong, (bool IsPrime, bool Exhausted)>();
|
|
||||||
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<ulong>(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<ulong> processed = new();
|
|
||||||
List<ulong> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -9,21 +9,23 @@ public class MersenneResidueModeTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
|
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();
|
tester.IsMersennePrime(136_000_002UL).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
|
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberTester(useResidue: true, useIncremental: true, maxK: 1_000UL);
|
var tester = new MersenneNumberTester(
|
||||||
tester.IsMersennePrime(31UL).Should().BeTrue();
|
useResidue: true,
|
||||||
}
|
useIncremental: true,
|
||||||
|
useGpuScan: false,
|
||||||
[Fact]
|
useGpuOrder: false,
|
||||||
public void IsMersennePrime_residue_mode_accepts_prime_exponent_when_scanning_multiple_batches()
|
maxK: 1_000UL);
|
||||||
{
|
|
||||||
var tester = new MersenneNumberTester(useResidue: true, useIncremental: true, maxK: 2_100_000UL);
|
|
||||||
tester.IsMersennePrime(31UL).Should().BeTrue();
|
tester.IsMersennePrime(31UL).Should().BeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ public enum GpuKernelType
|
|||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MersenneNumberTester(
|
public sealed class MersenneNumberTester(
|
||||||
UInt128 maxK,
|
|
||||||
bool useIncremental = true,
|
bool useIncremental = true,
|
||||||
bool useOrderCache = false,
|
bool useOrderCache = false,
|
||||||
GpuKernelType kernelType = GpuKernelType.Incremental,
|
GpuKernelType kernelType = GpuKernelType.Incremental,
|
||||||
@@ -25,11 +24,12 @@ public sealed class MersenneNumberTester(
|
|||||||
bool useGpuLucas = true,
|
bool useGpuLucas = true,
|
||||||
bool useGpuScan = true,
|
bool useGpuScan = true,
|
||||||
bool useGpuOrder = true,
|
bool useGpuOrder = true,
|
||||||
bool useResidue = true)
|
bool useResidue = true,
|
||||||
|
ulong maxK = 5_000_000UL)
|
||||||
{
|
{
|
||||||
private readonly bool _useResidue = useResidue;
|
private readonly bool _useResidue = useResidue;
|
||||||
private readonly bool _useIncremental = useIncremental && !useResidue;
|
private readonly bool _useIncremental = useIncremental && !useResidue;
|
||||||
private readonly UInt128 _maxK = maxK;
|
private readonly ulong _maxK = maxK;
|
||||||
private readonly GpuKernelType _kernelType = kernelType;
|
private readonly GpuKernelType _kernelType = kernelType;
|
||||||
private readonly bool _useModuloWorkaround = useModuloWorkaround;
|
private readonly bool _useModuloWorkaround = useModuloWorkaround;
|
||||||
private readonly bool _useGpuLucas = useGpuLucas;
|
private readonly bool _useGpuLucas = useGpuLucas;
|
||||||
|
|||||||
@@ -120,37 +120,59 @@ public static class UInt128Extensions
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
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;
|
UInt128 temp = value;
|
||||||
if (value == zero)
|
uint byteSum = 0U;
|
||||||
{
|
|
||||||
mod3 = 0UL;
|
|
||||||
mod5 = 0UL;
|
|
||||||
mod8 = 0UL;
|
|
||||||
mod10 = 0UL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UInt128 high = value >> 64;
|
do
|
||||||
ulong result = (ulong)value;
|
{
|
||||||
mod8 = result & 7UL;
|
byteSum += (uint)(byte)temp;
|
||||||
// 2^64 ≡ 1 (mod 3)
|
temp >>= 8;
|
||||||
ulong modRem = (result % 3UL) + ((ulong)high % 3UL);
|
}
|
||||||
mod3 = modRem >= 3UL ? modRem - 3UL : modRem;
|
while (temp != UInt128.Zero);
|
||||||
// 2^64 ≡ 1 (mod 5)
|
|
||||||
modRem = (result % 5UL) + ((ulong)high % 5UL);
|
|
||||||
mod5 = modRem >= 5UL ? modRem - 5UL : modRem;
|
|
||||||
|
|
||||||
while (high != zero)
|
mod8 = (ulong)value & 7UL;
|
||||||
{
|
|
||||||
// 2^64 ≡ 6 (mod 10)
|
|
||||||
result = (result + (ulong)high * 6UL) % 10UL;
|
|
||||||
high >>= 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
Reference in new issue
Block a user