Initial commit.
This commit is contained in:
1 parent
e5b0fea18c
commit
9f35f6c558
94 files changed
+12857
No files matched your search
@@ -0,0 +1,69 @@
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
public class GpuContextPoolTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void RentPreferred_cpu_returns_cpu_accelerator()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
|
||||
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void RentPreferred_reuses_cpu_context()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
Context firstContext;
|
||||
Accelerator firstAccelerator;
|
||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||
{
|
||||
firstContext = lease.Context;
|
||||
firstAccelerator = lease.Accelerator;
|
||||
}
|
||||
|
||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||
{
|
||||
lease.Context.Should().BeSameAs(firstContext);
|
||||
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||
}
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_pooled_contexts()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
Context firstContext;
|
||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||
{
|
||||
firstContext = lease.Context;
|
||||
}
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||
{
|
||||
lease.Context.Should().NotBeSameAs(firstContext);
|
||||
}
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[CollectionDefinition("GpuNtt", DisableParallelization = true)]
|
||||
public class GpuNttCollection
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Diagnostics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
[Trait("Category", "Fast")]
|
||||
public class GpuPrimeLimiterTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Acquire_blocks_when_limit_reached()
|
||||
{
|
||||
// Ensure limit = 1 so second acquire must wait.
|
||||
GpuPrimeWorkLimiter.SetLimit(1);
|
||||
|
||||
var startedSecond = new TaskCompletionSource<bool>();
|
||||
var sw = new Stopwatch();
|
||||
|
||||
using var first = GpuPrimeWorkLimiter.Acquire();
|
||||
sw.Start();
|
||||
|
||||
var secondTask = Task.Run(() =>
|
||||
{
|
||||
using var second = GpuPrimeWorkLimiter.Acquire();
|
||||
startedSecond.TrySetResult(true);
|
||||
});
|
||||
|
||||
// Give secondTask a moment to attempt acquire (it should block).
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
// Release first after short delay, allowing second to continue.
|
||||
// (Dispose at scope end, but we want to end now.)
|
||||
first.Dispose();
|
||||
|
||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||
sw.Stop();
|
||||
|
||||
// Should be > ~50ms because second had to wait for first to release.
|
||||
sw.ElapsedMilliseconds.Should().BeGreaterThanOrEqualTo(50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class GpuUInt128Tests
|
||||
{
|
||||
[Fact]
|
||||
public void Mul_matches_UInt128()
|
||||
{
|
||||
var random = new Random(0);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var high1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var high2 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low2 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var a = new GpuUInt128(high1, low1);
|
||||
var b = new GpuUInt128(high2, low2);
|
||||
var expected = (UInt128)new GpuUInt128(high1, low1) * (UInt128)new GpuUInt128(high2, low2);
|
||||
a = a.Mul(b);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MulMod_matches_UInt128()
|
||||
{
|
||||
var random = new Random(1);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var high1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var high2 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low2 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(high1, low1) % (UInt128)modulus);
|
||||
var b = new GpuUInt128((UInt128)new GpuUInt128(high2, low2) % (UInt128)modulus);
|
||||
var expected = MulModReference((UInt128)a, (UInt128)b, (UInt128)modulus);
|
||||
a = a.MulMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MulMod_with_ulong_matches_UInt128()
|
||||
{
|
||||
var random = new Random(5);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var high1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(high1, low1) % (UInt128)modulus);
|
||||
ulong bRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
UInt128 bBig = (UInt128)bRaw % (UInt128)modulus;
|
||||
ulong b = (ulong)bBig;
|
||||
var expected = MulModReference((UInt128)a, bBig, (UInt128)modulus);
|
||||
a = a.MulMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MulMod_with_ulong_modulus_matches_UInt128()
|
||||
{
|
||||
var random = new Random(11);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
ulong modulus = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
ulong aRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong bRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong aVal = aRaw % modulus;
|
||||
ulong bVal = bRaw % modulus;
|
||||
var a = new GpuUInt128(aVal);
|
||||
var b = new GpuUInt128(bVal);
|
||||
UInt128 expected = ((UInt128)aVal * bVal) % modulus;
|
||||
a = a.MulMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MulModBigInteger_matches_UInt128()
|
||||
{
|
||||
var random = new Random(13);
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var high1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low1 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var high2 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var low2 = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(high1, low1) % (UInt128)modulus);
|
||||
var b = new GpuUInt128((UInt128)new GpuUInt128(high2, low2) % (UInt128)modulus);
|
||||
var expected = MulModReference((UInt128)a, (UInt128)b, (UInt128)modulus);
|
||||
var result = new GpuUInt128(a.High, a.Low);
|
||||
result = result.MulModBigInteger(b, modulus);
|
||||
((UInt128)result).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SquareMod_matches_UInt128()
|
||||
{
|
||||
var random = new Random(8);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var valueHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var valueLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(valueHigh, valueLow) % (UInt128)modulus);
|
||||
var expected = MulModReference((UInt128)a, (UInt128)a, (UInt128)modulus);
|
||||
a = a.SquareMod(modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModPow_matches_BigInteger()
|
||||
{
|
||||
var random = new Random(2);
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var baseHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var baseLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var expHigh = 0UL;
|
||||
var expLow = (ulong)random.Next(1, int.MaxValue);
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var value = new GpuUInt128((UInt128)new GpuUInt128(baseHigh, baseLow) % (UInt128)modulus);
|
||||
var exponent = new GpuUInt128(expHigh, expLow);
|
||||
|
||||
var expected = (UInt128)System.Numerics.BigInteger.ModPow((System.Numerics.BigInteger)(UInt128)value, (System.Numerics.BigInteger)(UInt128)exponent, (System.Numerics.BigInteger)(UInt128)modulus);
|
||||
value = value.ModPow(exponent, modulus);
|
||||
((UInt128)value).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModPow_with_ulong_matches_BigInteger()
|
||||
{
|
||||
var random = new Random(9);
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var baseHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var baseLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong exponent = (ulong)random.Next(1, int.MaxValue);
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var value = new GpuUInt128((UInt128)new GpuUInt128(baseHigh, baseLow) % (UInt128)modulus);
|
||||
|
||||
var expected = (UInt128)System.Numerics.BigInteger.ModPow((System.Numerics.BigInteger)(UInt128)value, exponent, (System.Numerics.BigInteger)(UInt128)modulus);
|
||||
value = value.ModPow(exponent, modulus);
|
||||
((UInt128)value).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubMod_matches_UInt128()
|
||||
{
|
||||
var random = new Random(3);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var valueHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var valueLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var subHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var subLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(valueHigh, valueLow) % (UInt128)modulus);
|
||||
var b = new GpuUInt128((UInt128)new GpuUInt128(subHigh, subLow) % (UInt128)modulus);
|
||||
UInt128 expected = ((UInt128)a + (UInt128)modulus - (UInt128)b) % (UInt128)modulus;
|
||||
a = a.SubMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubMod_with_ulong_matches_UInt128()
|
||||
{
|
||||
var random = new Random(6);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var valueHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var valueLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(valueHigh, valueLow) % (UInt128)modulus);
|
||||
ulong subRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
UInt128 bBig = (UInt128)subRaw % (UInt128)modulus;
|
||||
ulong b = (ulong)bBig;
|
||||
UInt128 expected = ((UInt128)a + (UInt128)modulus - bBig) % (UInt128)modulus;
|
||||
a = a.SubMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMod_matches_UInt128()
|
||||
{
|
||||
var random = new Random(4);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var valueHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var valueLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var addHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var addLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var a = new GpuUInt128((UInt128)new GpuUInt128(valueHigh, valueLow) % (UInt128)modulus);
|
||||
var b = new GpuUInt128((UInt128)new GpuUInt128(addHigh, addLow) % (UInt128)modulus);
|
||||
UInt128 expected = ((UInt128)a + (UInt128)b) % (UInt128)modulus;
|
||||
a = a.AddMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMod_with_ulong_modulus_matches_UInt128()
|
||||
{
|
||||
var random = new Random(12);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
ulong modulus = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
ulong aRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong bRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong aVal = aRaw % modulus;
|
||||
ulong bVal = bRaw % modulus;
|
||||
var a = new GpuUInt128(aVal);
|
||||
var b = new GpuUInt128(bVal);
|
||||
UInt128 expected = ((UInt128)aVal + bVal) % modulus;
|
||||
a = a.AddMod(b, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMod_with_ulong_operand_and_modulus_matches_UInt128()
|
||||
{
|
||||
var random = new Random(13);
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
ulong modulus = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
ulong aRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong bRaw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
ulong aVal = aRaw % modulus;
|
||||
ulong bVal = bRaw % modulus;
|
||||
var a = new GpuUInt128(aVal);
|
||||
UInt128 expected = ((UInt128)aVal + bVal) % modulus;
|
||||
a = a.AddMod(bVal, modulus);
|
||||
((UInt128)a).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModInv_matches_BigInteger()
|
||||
{
|
||||
var random = new Random(7);
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var modHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var modLow = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
var modulus = new GpuUInt128(modHigh, modLow);
|
||||
var valueHigh = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var valueLow = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var value = new GpuUInt128((UInt128)new GpuUInt128(valueHigh, valueLow) % (UInt128)modulus);
|
||||
if (value.IsZero)
|
||||
{
|
||||
value = value.Add(1UL);
|
||||
}
|
||||
|
||||
var expected = (UInt128)System.Numerics.BigInteger.ModPow((System.Numerics.BigInteger)(UInt128)value, (System.Numerics.BigInteger)(UInt128)modulus - 2, (System.Numerics.BigInteger)(UInt128)modulus);
|
||||
value = value.ModInv(modulus);
|
||||
((UInt128)value).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModInv_with_ulong_modulus_matches_BigInteger()
|
||||
{
|
||||
var random = new Random(8);
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
ulong modulus = ((ulong)random.Next() << 32) | (uint)random.Next() | 1UL;
|
||||
ulong raw = ((ulong)random.Next() << 32) | (uint)random.Next();
|
||||
var value = new GpuUInt128((UInt128)(raw % modulus));
|
||||
if (value.IsZero)
|
||||
{
|
||||
value = value.Add(1UL);
|
||||
}
|
||||
|
||||
var expected = (UInt128)System.Numerics.BigInteger.ModPow((System.Numerics.BigInteger)(UInt128)value, (System.Numerics.BigInteger)modulus - 2, (System.Numerics.BigInteger)modulus);
|
||||
value = value.ModInv(modulus);
|
||||
((UInt128)value).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
private static UInt128 MulModReference(UInt128 a, UInt128 b, UInt128 modulus)
|
||||
{
|
||||
UInt128 result = 0;
|
||||
UInt128 x = a;
|
||||
UInt128 y = b;
|
||||
while (y != 0)
|
||||
{
|
||||
if ((y & 1) != 0)
|
||||
{
|
||||
result += x;
|
||||
if (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
x <<= 1;
|
||||
if (x >= modulus)
|
||||
{
|
||||
x -= modulus;
|
||||
}
|
||||
|
||||
y >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Trait("Category", "Slow")]
|
||||
public class LucasLehmerPrimeTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(5UL, true)]
|
||||
[InlineData(11UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
|
||||
{
|
||||
new MersenneNumberLucasLehmerGpuTester().IsMersennePrime(p).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
|
||||
{
|
||||
// Use a modest exponent that still exercises the NTT path while keeping
|
||||
// runtime short enough for the unit test environment.
|
||||
const ulong exponent = 128UL;
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(132UL)]
|
||||
[InlineData(1000UL)]
|
||||
[Trait("Category", "Slow")]
|
||||
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(5UL, true)]
|
||||
[InlineData(11UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
|
||||
{
|
||||
new MersenneNumberLucasLehmerCpuTester().IsPrime(p).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
public class NttBackendConfigTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardInverseGpu_reference_backend_matches_cpu_on_small_input()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(gpu);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(cpu);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
gpu.ToArray().Should().Equal(cpu.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardInverseGpu_staged_backend_matches_cpu_on_small_input()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(gpu);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(cpu);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
gpu.ToArray().Should().Equal(cpu.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using System.Numerics;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
// GPU-related tests are organized per-backend so both reference and staged
|
||||
// implementations are exercised under the fast test suite.
|
||||
[Collection("GpuNtt")]
|
||||
public class NttGpuMathTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_reference_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_staged_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_reference_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_staged_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
using var gpu = GpuContextPool.Rent();
|
||||
var accelerator = gpu.Accelerator;
|
||||
|
||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
||||
var cacheType = cache.GetType();
|
||||
|
||||
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
||||
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
||||
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
||||
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
||||
|
||||
int count = 7; // length 8 -> 7 twiddles
|
||||
var forward = new GpuUInt128[count];
|
||||
var inverse = new GpuUInt128[count];
|
||||
twiddlesMont.CopyToCPU(forward);
|
||||
twiddlesInvMont.CopyToCPU(inverse);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
||||
prod.Should().Be(r2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_reference_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_staged_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_reference_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_staged_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void BitReverseGpu_matches_cpu()
|
||||
{
|
||||
GpuUInt128[] cpu = new GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[i] = new GpuUInt128(0UL, i);
|
||||
}
|
||||
|
||||
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
||||
NttGpuMath.BitReverse(cpu);
|
||||
NttGpuMath.BitReverseGpu(gpu);
|
||||
gpu.Should().Equal(cpu);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_ntt_caches()
|
||||
{
|
||||
// Ensure the cache starts empty.
|
||||
NttGpuMath.ClearCaches();
|
||||
|
||||
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
||||
values[0] = new GpuUInt128(0UL, 1UL);
|
||||
values[1] = new GpuUInt128(0UL, 2UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
using (var gpu = GpuContextPool.Rent())
|
||||
{
|
||||
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
||||
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
||||
buffer.Dispose();
|
||||
}
|
||||
|
||||
// Verify that the square cache now holds entries for the accelerator.
|
||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var cacheObj = field.GetValue(null)!;
|
||||
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().BeGreaterThan(0);
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using FluentAssertions;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
[Trait("Category", "Fast")]
|
||||
public class NttMontgomeryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
||||
{
|
||||
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
||||
cpu.CopyTo(gpu);
|
||||
for (int i = cpu.Length; i < gpu.Length; i++)
|
||||
{
|
||||
gpu[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
|
||||
// CPU reference
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
|
||||
// Device execution
|
||||
using var context = GpuContextPool.Rent();
|
||||
var accelerator = context.Accelerator;
|
||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
|
||||
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
|
||||
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
public class NttReductionModeTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(ModReductionMode.Auto)]
|
||||
[InlineData(ModReductionMode.GpuUInt128)]
|
||||
public void ForwardInverse_small64bit_modulus_reference_backend_works_with_mode(ModReductionMode mode)
|
||||
{
|
||||
var originalBackend = NttGpuMath.GpuTransformBackend;
|
||||
var originalMode = NttGpuMath.ReductionMode;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
NttGpuMath.ReductionMode = mode;
|
||||
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(cpu);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(gpu);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
|
||||
gpu.ToArray().Should().Equal(cpu.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = originalBackend;
|
||||
NttGpuMath.ReductionMode = originalMode;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(ModReductionMode.Auto)]
|
||||
[InlineData(ModReductionMode.GpuUInt128)]
|
||||
public void ForwardInverse_small64bit_modulus_staged_backend_works_with_mode(ModReductionMode mode)
|
||||
{
|
||||
var originalBackend = NttGpuMath.GpuTransformBackend;
|
||||
var originalMode = NttGpuMath.ReductionMode;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
NttGpuMath.ReductionMode = mode;
|
||||
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(cpu);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(gpu);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
|
||||
gpu.ToArray().Should().Equal(cpu.ToArray());
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = originalBackend;
|
||||
NttGpuMath.ReductionMode = originalMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user