Ensure GPU residue copies are synchronized and cover high divisor sets

This commit is contained in:
Marek Lemańczyk committed 2025-09-20 22:49:37 +02:00
1 parent 633c94906a
commit 7449f146d9
7 files changed
+215 -2

No files matched your search

@@ -1,3 +1,5 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using Xunit;
@@ -41,4 +43,69 @@ public class GpuUInt128Pow2Minus1ModTests
results[i].Should().Be(single);
}
}
[Fact]
public void Pow2Minus1Mod_matches_bigint_for_large_mersenne_residue_candidates()
{
const ulong exponent = 107UL;
const int limit = 500_000;
UInt128 twoP = (UInt128)(2UL * exponent);
for (int k = 1; k <= limit; k++)
{
UInt128 q = checked(twoP * (UInt128)(ulong)k + UInt128.One);
GpuUInt128 gpuRemainder = GpuUInt128.Pow2Minus1Mod(exponent, (GpuUInt128)q);
BigInteger bigQ = ToBigInteger(q);
BigInteger bigPow = BigInteger.ModPow(new BigInteger(2), checked((int)exponent), bigQ);
BigInteger bigRemainder = (bigPow - BigInteger.One) % bigQ;
if (bigRemainder.Sign < 0)
{
bigRemainder += bigQ;
}
gpuRemainder.Should().Be(ToGpuUInt128(bigRemainder));
}
UInt128[] largeKs =
{
UInt128.Parse("1000000000000000"),
UInt128.Parse("1000000000000000000"),
UInt128.Parse("1000000000000000000000"),
UInt128.Parse("99999999998192000000000")
};
foreach (UInt128 kValue in largeKs)
{
UInt128 q = checked(twoP * kValue + UInt128.One);
GpuUInt128 gpuRemainder = GpuUInt128.Pow2Minus1Mod(exponent, (GpuUInt128)q);
BigInteger bigQ = ToBigInteger(q);
BigInteger bigPow = BigInteger.ModPow(new BigInteger(2), checked((int)exponent), bigQ);
BigInteger bigRemainder = (bigPow - BigInteger.One) % bigQ;
if (bigRemainder.Sign < 0)
{
bigRemainder += bigQ;
}
gpuRemainder.Should().Be(ToGpuUInt128(bigRemainder));
}
}
private static BigInteger ToBigInteger(UInt128 value)
{
Span<byte> buffer = stackalloc byte[16];
buffer.Clear();
Unsafe.WriteUnaligned(ref buffer[0], value);
return new BigInteger(buffer, isUnsigned: true, isBigEndian: false);
}
private static GpuUInt128 ToGpuUInt128(BigInteger value)
{
Span<byte> buffer = stackalloc byte[16];
buffer.Clear();
value.TryWriteBytes(buffer, out _, isUnsigned: true, isBigEndian: false);
UInt128 converted = Unsafe.ReadUnaligned<UInt128>(ref buffer[0]);
return (GpuUInt128)converted;
}
}
@@ -0,0 +1,52 @@
using System.Threading;
using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu;
[Trait("Category", "Fast")]
public class PrimeTesterGpuTests
{
[Fact]
public void IsPrimeGpu_accepts_known_primes()
{
GpuContextPool.ForceCpu = false;
try
{
var tester = new PrimeTester();
ulong[] primes = [5UL, 31UL, 61UL, 89UL, 107UL, 127UL];
foreach (ulong prime in primes)
{
tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
}
}
finally
{
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll();
}
}
[Fact]
public void IsPrimeGpu_rejects_composites()
{
GpuContextPool.ForceCpu = false;
try
{
var tester = new PrimeTester();
ulong[] composites = [21UL, 25UL, 27UL, 35UL, 55UL];
foreach (ulong composite in composites)
{
tester.IsPrimeGpu(composite, CancellationToken.None).Should().BeFalse();
}
}
finally
{
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll();
}
}
}
@@ -65,6 +65,17 @@ public class MersenneNumberResidueGpuTesterTests
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)]
@@ -19,6 +19,38 @@ public class MersenneNumberResidueIntegrationTests
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]
[MemberData(nameof(ResidueThreadCounts))]
[Trait("Category", "Fast")]