Initial commit.
This commit is contained in:
1 parent
e5b0fea18c
commit
9f35f6c558
94 files changed
+12857
No files matched your search
@@ -0,0 +1,12 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class AlphaCacheTests
|
||||
{
|
||||
[Fact(Skip = "TODO: implement test")]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class AlphaCalculationsTests
|
||||
{
|
||||
[Fact(Skip = "TODO: implement test")]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class EulerPrimeTesterTests
|
||||
{
|
||||
private readonly EulerPrimeTester _tester = new(new PrimeTester());
|
||||
|
||||
[Theory]
|
||||
[InlineData(13UL, true)]
|
||||
[InlineData(29UL, true)]
|
||||
[InlineData(3UL, false)]
|
||||
[InlineData(21UL, false)]
|
||||
[InlineData(1UL, false)]
|
||||
public void IsEulerPrime_returns_expected(ulong n, bool expected)
|
||||
{
|
||||
_tester.IsEulerPrime(n).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuKernelPoolTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||
{
|
||||
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
var accelerator = lease.Accelerator;
|
||||
|
||||
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
|
||||
second.Equals(first).Should().BeTrue();
|
||||
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Run_provides_accelerator_and_stream()
|
||||
{
|
||||
bool executed = false;
|
||||
|
||||
GpuKernelPool.Run((acc, stream) =>
|
||||
{
|
||||
acc.Should().NotBeNull();
|
||||
stream.Should().NotBeNull();
|
||||
executed = true;
|
||||
});
|
||||
|
||||
executed.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Diagnostics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuWorkLimiterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public async Task Acquire_blocks_when_limit_reached()
|
||||
{
|
||||
GpuWorkLimiter.SetLimit(1);
|
||||
|
||||
var startedSecond = new TaskCompletionSource<bool>();
|
||||
var sw = new Stopwatch();
|
||||
|
||||
using var first = GpuWorkLimiter.Acquire();
|
||||
sw.Start();
|
||||
|
||||
var secondTask = Task.Run(() =>
|
||||
{
|
||||
using var second = GpuWorkLimiter.Acquire();
|
||||
startedSecond.TrySetResult(true);
|
||||
});
|
||||
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
first.Dispose();
|
||||
|
||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||
sw.Stop();
|
||||
|
||||
sw.ElapsedMilliseconds.Should().BeGreaterThanOrEqualTo(50);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class InputParserTests
|
||||
{
|
||||
[Fact(Skip = "TODO: implement test")]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class KRangeFinderTests
|
||||
{
|
||||
[Fact(Skip = "TODO: implement test")]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneDivisorCyclesTests
|
||||
{
|
||||
public static IEnumerable<object[]> Divisors() =>
|
||||
new[]
|
||||
{
|
||||
new object[] {3UL, 2UL},
|
||||
new object[] {7UL, 3UL},
|
||||
new object[] {11UL, 10UL},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(Divisors))]
|
||||
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
||||
{
|
||||
MersenneDivisorCycles.CalculateCycleLength(divisor).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberIncrementalCpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[InlineData(GpuKernelType.Incremental)]
|
||||
[InlineData(GpuKernelType.Pow2Mod)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||
{
|
||||
var tester = new MersenneNumberIncrementalCpuTester(type);
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberIncrementalGpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[InlineData(GpuKernelType.Pow2Mod, false)]
|
||||
[InlineData(GpuKernelType.Incremental, false)]
|
||||
[InlineData(GpuKernelType.Pow2Mod, true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type, bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberIncrementalGpuTester(type, useGpuOrder);
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberIncrementalGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberOrderCpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[InlineData(GpuKernelType.Incremental)]
|
||||
[InlineData(GpuKernelType.Pow2Mod)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||
{
|
||||
var tester = new MersenneNumberOrderCpuTester(type);
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberOrderGpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[InlineData(GpuKernelType.Pow2Mod, false)]
|
||||
[InlineData(GpuKernelType.Incremental, false)]
|
||||
[InlineData(GpuKernelType.Pow2Mod, true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type, bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberOrderGpuTester(type, useGpuOrder);
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberOrderGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberResidueCpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents()
|
||||
{
|
||||
var tester = new MersenneNumberResidueCpuTester();
|
||||
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberResidueGpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
||||
|
||||
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, LastDigitIsSeven(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
[Trait("Category", "Fast")]
|
||||
public class MersenneNumberTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(136_000_002UL)]
|
||||
[InlineData(136_000_005UL)]
|
||||
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTester();
|
||||
tester.IsMersennePrime(p).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(125UL)]
|
||||
public void WarmUpOrders_populates_cache_without_affecting_results(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
|
||||
tester.WarmUpOrders(p, 1_000UL);
|
||||
tester.IsMersennePrime(p).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(125UL, true)]
|
||||
[InlineData(127UL, false)]
|
||||
public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected)
|
||||
{
|
||||
p.SharesFactorWithExponentMinusOne().Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class MersenneResidueModeTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
|
||||
{
|
||||
var tester = new MersenneNumberTester(useResidue: true, useIncremental: false);
|
||||
tester.IsMersennePrime(136_000_002UL).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersennePrimeFactorTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(5UL, 31UL, true)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(11UL, 89UL, true)]
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[InlineData(7UL, 17UL, false)]
|
||||
[InlineData(11UL, 331UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeFactor_identifies_divisibility(ulong p, ulong q, bool expected)
|
||||
{
|
||||
MersennePrimeFactorTester.IsPrimeFactor(p, q, CancellationToken.None).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Slow")]
|
||||
public void GetOrderOf2ModPrime_handles_large_divisors()
|
||||
{
|
||||
UInt128 q = UInt128.Parse("618970019642690137449562111");
|
||||
MersennePrimeFactorTester.GetOrderOf2ModPrime(q, CancellationToken.None)
|
||||
.Should().Be((UInt128)89);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneResidueStepperTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Step_advances_residue()
|
||||
{
|
||||
var stepper = new MersenneResidueStepper(13, 4, 2);
|
||||
|
||||
stepper.State().Should().Be((4UL, 2UL));
|
||||
|
||||
stepper.Step(3).Should().Be((7UL, 10UL));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void To_moves_directly_to_target_exponent()
|
||||
{
|
||||
var stepper = new MersenneResidueStepper(13, 4, 2);
|
||||
|
||||
stepper.To(10).Should().Be((10UL, 9UL));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Constructor_rejects_zero_modulus()
|
||||
{
|
||||
var act = () => new MersenneResidueStepper(0, 0, 0);
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class ModResidueTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsDivisible_advances_residue_for_identity()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.IsDivisible(5, 2).Should().BeFalse();
|
||||
tracker.IsDivisible(6, 2).Should().BeTrue();
|
||||
tracker.IsDivisible(7, 2).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(5UL, 31UL, true)]
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(11UL, 31UL, false)]
|
||||
public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
|
||||
|
||||
tracker.IsDivisible(p, divisor).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeOrAppend_tracks_existing_and_new_divisors()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppend(10, 3, out bool d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppend(10, 5, out bool d5).Should().BeTrue();
|
||||
d5.Should().BeTrue();
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppend(10, 4, out bool d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
|
||||
tracker.BeginMerge(11);
|
||||
tracker.MergeOrAppend(11, 3, out d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppend(11, 4, out d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
tracker.MergeOrAppend(11, 5, out d5).Should().BeTrue();
|
||||
d5.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDivisible_requires_non_decreasing_numbers()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
tracker.IsDivisible(5, 2);
|
||||
|
||||
Action act = () => tracker.IsDivisible(4, 2);
|
||||
act.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class ModuloAutomatonTests
|
||||
{
|
||||
[Fact]
|
||||
public void Ending7Automaton_mod9_hits_every_9th()
|
||||
{
|
||||
// Start below 7; sequence: 7, 17, 27, 37, ...
|
||||
var auto = new Ending7Automaton(1, 9);
|
||||
int hits = 0;
|
||||
ulong firstHit = 0;
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
ulong n = auto.Current();
|
||||
bool div9 = auto.DivisibleBy(9);
|
||||
if (div9)
|
||||
{
|
||||
hits++;
|
||||
if (firstHit == 0)
|
||||
{
|
||||
firstHit = n;
|
||||
}
|
||||
}
|
||||
|
||||
auto.Next();
|
||||
}
|
||||
|
||||
// Known CRT class: n ≡ 27 (mod 90) when n ends with 7 and n ≡ 0 (mod 9)
|
||||
firstHit.Should().Be(27);
|
||||
hits.Should().BeGreaterThan(1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(13UL, 2000)]
|
||||
[InlineData(17UL, 2000)]
|
||||
public void MersenneResidueAutomaton_matches_direct_mods(ulong p, int count)
|
||||
{
|
||||
// Compare residue progression against direct modular computations
|
||||
var auto = new MersenneResidueAutomaton(p);
|
||||
var twoP = (System.UInt128)p << 1;
|
||||
var q = twoP + 1UL;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong r10 = Mod10(q);
|
||||
ulong r8 = (ulong)q & 7UL;
|
||||
ulong r3 = Mod3(q);
|
||||
ulong r5 = Mod5(q);
|
||||
|
||||
auto.Mod10R.Should().Be(r10);
|
||||
auto.Mod8R.Should().Be(r8);
|
||||
auto.Mod3R.Should().Be(r3);
|
||||
auto.Mod5R.Should().Be(r5);
|
||||
|
||||
auto.Next();
|
||||
q += twoP;
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong Mod10(System.UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong highRem = Mod10(high);
|
||||
ulong lowRem = Mod10(low);
|
||||
ulong combined = lowRem + highRem * 6UL;
|
||||
return Mod10(combined);
|
||||
}
|
||||
|
||||
private static ulong Mod10(ulong value) => value % 10UL;
|
||||
|
||||
private static ulong Mod3(System.UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong rem = (low % 3UL) + (high % 3UL);
|
||||
return rem >= 3UL ? rem - 3UL : rem;
|
||||
}
|
||||
|
||||
private static ulong Mod5(System.UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong rem = (low % 5UL) + (high % 5UL);
|
||||
return rem >= 5UL ? rem - 5UL : rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../PerfectNumbers.Core/PerfectNumbers.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PeterO.Numbers;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class PowerCacheTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Get_returns_big_integer_power()
|
||||
{
|
||||
PowerCache.Get(2UL, 5).Should().Be(new BigInteger(32));
|
||||
PowerCache.Get(2UL, 10).Should().Be(new BigInteger(1024));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Get_returns_einteger_power()
|
||||
{
|
||||
EInteger baseVal = EInteger.FromInt32(3);
|
||||
PowerCache.Get(baseVal, 4).Should().Be(EInteger.FromInt32(81));
|
||||
PowerCache.Get(baseVal, 2).Should().Be(EInteger.FromInt32(9));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using FluentAssertions;
|
||||
using PeterO.Numbers;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class PrimeCacheTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void GetEulerPrimes_returns_primes_congruent_to_one_mod_four()
|
||||
{
|
||||
var cache = new PrimeCache();
|
||||
EInteger[] result = cache.GetEulerPrimes(EInteger.FromInt32(2), EInteger.FromInt32(20));
|
||||
|
||||
result.Should().Equal(
|
||||
EInteger.FromInt32(5),
|
||||
EInteger.FromInt32(13),
|
||||
EInteger.FromInt32(17));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void EnumeratePrimes_returns_sequence_starting_from_value()
|
||||
{
|
||||
var cache = new PrimeCache();
|
||||
EInteger[] result = cache.EnumeratePrimes(EInteger.FromInt32(10))
|
||||
.Take(5)
|
||||
.ToArray();
|
||||
|
||||
result.Should().Equal(
|
||||
EInteger.FromInt32(11),
|
||||
EInteger.FromInt32(13),
|
||||
EInteger.FromInt32(17),
|
||||
EInteger.FromInt32(19),
|
||||
EInteger.FromInt32(23));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class PrimeTesterGcdTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(81UL, true)]
|
||||
[InlineData(101UL, false)]
|
||||
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
|
||||
{
|
||||
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SharesFactorWithMaxExponentBatch_filters_values()
|
||||
{
|
||||
ulong[] values = [81UL, 101UL];
|
||||
Span<byte> results = stackalloc byte[values.Length];
|
||||
PrimeTester.SharesFactorWithMaxExponentBatch(values, results);
|
||||
results.ToArray().Should().Equal(new byte[] { 1, 0 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class PrimeTesterIsPrimeTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(1UL, false)]
|
||||
[InlineData(2UL, true)]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(4UL, false)]
|
||||
[InlineData(7UL, true)]
|
||||
[InlineData(97UL, true)]
|
||||
[InlineData(121UL, false)]
|
||||
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
|
||||
tester.IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
GpuContextPool.ForceCpu = true;
|
||||
|
||||
tester.IsPrimeGpu(11UL, CancellationToken.None).Should().BeTrue();
|
||||
|
||||
tester.IsPrimeGpu(12UL, CancellationToken.None).Should().BeFalse();
|
||||
|
||||
GpuContextPool.ForceCpu = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class PrimesGeneratorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Small_primes_and_squares_match()
|
||||
{
|
||||
var primes = PrimesGenerator.SmallPrimes;
|
||||
var squares = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
primes[..5].Should().Equal(new ulong[] { 2UL, 3UL, 5UL, 7UL, 11UL });
|
||||
|
||||
squares[..5].Should().Equal(new ulong[] { 4UL, 9UL, 25UL, 49UL, 121UL });
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
squares[i].Should().Be(primes[i] * primes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PeterO.Numbers;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class RationalHelperTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void FromDoubleRounded_ReducesFraction()
|
||||
{
|
||||
ERational result = RationalHelper.FromDoubleRounded(0.75);
|
||||
|
||||
result.Numerator.ToInt32Checked().Should().Be(3);
|
||||
result.Denominator.ToInt32Checked().Should().Be(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ToEInteger_ReturnsMatchingValue()
|
||||
{
|
||||
BigInteger value = BigInteger.Parse("12345678901234567890");
|
||||
|
||||
EInteger einteger = RationalHelper.ToEInteger(value);
|
||||
|
||||
einteger.ToString().Should().Be(value.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void FloorToUInt64_ReturnsFloor()
|
||||
{
|
||||
ERational value = ERational.Create(5, 2);
|
||||
|
||||
ulong result = RationalHelper.FloorToUInt64(value);
|
||||
|
||||
result.Should().Be(2UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void CeilingToUInt64_RoundsUpWhenFractional()
|
||||
{
|
||||
ERational fractional = ERational.Create(5, 2);
|
||||
ERational integral = ERational.Create(6, 2);
|
||||
|
||||
RationalHelper.CeilingToUInt64(fractional).Should().Be(3UL);
|
||||
RationalHelper.CeilingToUInt64(integral).Should().Be(3UL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class RleBlacklistTests
|
||||
{
|
||||
[Fact(Skip = "TODO: implement test")]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class StringBuilderPoolTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Rent_ReturnsClearedBuilder()
|
||||
{
|
||||
var sb = StringBuilderPool.Rent();
|
||||
|
||||
sb.Length.Should().Be(0);
|
||||
|
||||
StringBuilderPool.Return(sb);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Return_AllowsReuseOfBuilder()
|
||||
{
|
||||
var sb = StringBuilderPool.Rent();
|
||||
sb.Append("data");
|
||||
|
||||
StringBuilderPool.Return(sb);
|
||||
|
||||
var reused = StringBuilderPool.Rent();
|
||||
|
||||
reused.Should().BeSameAs(sb);
|
||||
reused.Length.Should().Be(0);
|
||||
|
||||
StringBuilderPool.Return(reused);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class TextFileWriterTests
|
||||
{
|
||||
[Fact(Skip = "TODO: implement test")]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Placeholder()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class UInt128ExtensionsTests
|
||||
{
|
||||
public static IEnumerable<object[]> BinaryGcdCases()
|
||||
{
|
||||
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
|
||||
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
|
||||
|
||||
UInt128 big1 = (UInt128.One << 96) + 12345;
|
||||
UInt128 big2 = (UInt128.One << 80) + 67890;
|
||||
yield return new object[]
|
||||
{
|
||||
big1,
|
||||
big2,
|
||||
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
|
||||
};
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(BinaryGcdCases))]
|
||||
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
|
||||
{
|
||||
a.BinaryGcd(b).Should().Be(expected);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> TrailingZeroCases()
|
||||
{
|
||||
yield return new object[] { UInt128.Zero, 128 };
|
||||
yield return new object[] { (UInt128)1, 0 };
|
||||
yield return new object[] { (UInt128)8, 3 };
|
||||
yield return new object[] { UInt128.One << 80, 80 };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(TrailingZeroCases))]
|
||||
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
|
||||
{
|
||||
value.CountTrailingZeros().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
public void CalculateOrder_matches_naive(ulong q)
|
||||
{
|
||||
UInt128 q128 = q;
|
||||
ulong expected = NaiveOrder(q);
|
||||
q128.CalculateOrder().Should().Be(expected);
|
||||
}
|
||||
|
||||
private static ulong NaiveOrder(ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % q;
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value << 1) % q;
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
public void IsPrimeCandidate_checks_small(ulong n, bool expected)
|
||||
{
|
||||
((UInt128)n).IsPrimeCandidate().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeCandidate_detects_large_composite()
|
||||
{
|
||||
UInt128 n = (UInt128.One << 64) + 1;
|
||||
n.IsPrimeCandidate().Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod10_8_5_3_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345678901234567890UL;
|
||||
|
||||
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
|
||||
ulong expected10 = (ulong)(value % 10);
|
||||
ulong expected8 = (ulong)(value % 8);
|
||||
ulong expected5 = (ulong)(value % 5);
|
||||
ulong expected3 = (ulong)(value % 3);
|
||||
|
||||
mod10.Should().Be(expected10);
|
||||
mod8.Should().Be(expected8);
|
||||
mod5.Should().Be(expected5);
|
||||
mod3.Should().Be(expected3);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, 10UL, 17UL)]
|
||||
[InlineData(123456789UL, 3UL, 97UL)]
|
||||
public void ModPow_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
|
||||
{
|
||||
UInt128 v = value;
|
||||
UInt128 e = exponent;
|
||||
UInt128 m = modulus;
|
||||
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus);
|
||||
|
||||
v.ModPow(e, m).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ModPow_handles_large_values()
|
||||
{
|
||||
UInt128 v = (UInt128.One << 96) + 12345;
|
||||
UInt128 e = 17;
|
||||
UInt128 m = 97;
|
||||
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m);
|
||||
|
||||
v.ModPow(e, m).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(123456789UL)]
|
||||
public void Mod10_matches_operator(ulong low)
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + low;
|
||||
value.Mod10().Should().Be((ulong)(value % 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod8_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod8().Should().Be((ulong)(value % 8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod3_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod3().Should().Be((ulong)(value % 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod5_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod5().Should().Be((ulong)(value % 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mul64_matches_expected_product()
|
||||
{
|
||||
UInt128 a = (UInt128.One << 80) + 123456789;
|
||||
UInt128 b = (UInt128.One << 64) + 987654321;
|
||||
|
||||
UInt128 expected = (UInt128)((UInt128)(ulong)a * b);
|
||||
|
||||
a.Mul64(b).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(123UL, 456UL, 789UL)]
|
||||
[InlineData(123456789UL, 987654321UL, 1009UL)]
|
||||
public void MulMod_matches_BigInteger(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
UInt128 ua = a % modulus;
|
||||
UInt128 ub = b % modulus;
|
||||
UInt128 mod = modulus;
|
||||
|
||||
UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus);
|
||||
|
||||
ua.MulMod(ub, mod).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void MulMod_handles_large_inputs()
|
||||
{
|
||||
UInt128 a = (UInt128.One << 80) + 12345;
|
||||
UInt128 b = (UInt128.One << 96) + 67890;
|
||||
UInt128 mod = (UInt128.One << 100) - 93;
|
||||
|
||||
UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod);
|
||||
|
||||
a.MulMod(b, mod).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class ULongExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(9UL)]
|
||||
[InlineData(10UL)]
|
||||
[InlineData(1234567890UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
public void Mod10_returns_same_as_operator(ulong value)
|
||||
{
|
||||
ulong expected = value % 10UL;
|
||||
|
||||
ULongExtensions.Mod10(value).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
|
||||
{
|
||||
candidate.IsPrimeCandidate().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
[InlineData(11UL)]
|
||||
public void CalculateOrder_matches_naive_algorithm(ulong q)
|
||||
{
|
||||
ulong expected = NaiveOrder(q);
|
||||
|
||||
q.CalculateOrder().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, 10UL, 17UL)]
|
||||
[InlineData(123456789UL, 3UL, 97UL)]
|
||||
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
|
||||
{
|
||||
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
|
||||
|
||||
value.ModPow64(exponent, modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(10UL, 17UL)]
|
||||
[InlineData(123UL, 97UL)]
|
||||
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
exponent.PowMod((UInt128)modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(5UL, 7UL, 3UL, 4UL)]
|
||||
[InlineData(10UL, 7UL, 3UL, 2UL)]
|
||||
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
|
||||
{
|
||||
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(15UL, true)]
|
||||
[InlineData(7UL, false)]
|
||||
[InlineData(9UL, false)]
|
||||
public void SharesFactorWithExponentMinusOne_detects_relationship(ulong exponent, bool expected)
|
||||
{
|
||||
exponent.SharesFactorWithExponentMinusOne().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(1234567890123456789UL, 3UL)]
|
||||
[InlineData(ulong.MaxValue, 7UL)]
|
||||
public void FastDiv64_matches_division(ulong value, ulong divisor)
|
||||
{
|
||||
ulong mul = (ulong)(((UInt128)1 << 64) / divisor);
|
||||
|
||||
value.FastDiv64(divisor, mul).Should().Be(value / divisor);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 1UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void MulHigh_matches_high_bits(ulong x, ulong y)
|
||||
{
|
||||
ulong expected = (ulong)(((UInt128)x * y) >> 64);
|
||||
|
||||
x.MulHigh(y).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 2UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void Mul64_matches_UInt128_product(ulong a, ulong b)
|
||||
{
|
||||
UInt128 expected = (UInt128)a * b;
|
||||
|
||||
a.Mul64(b).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(123456789UL, 987654321UL, 97UL)]
|
||||
[InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)]
|
||||
public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus);
|
||||
|
||||
a.MulMod64(b, modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(100UL, 7UL, 3UL)]
|
||||
[InlineData(123UL, 9UL, 6UL)]
|
||||
public void PowModWithCycle_ulong_overload_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(100UL, 7UL, 3UL)]
|
||||
[InlineData(123UL, 9UL, 6UL)]
|
||||
public void PowModWithCycle_ulong_uint128_cycle_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(11UL, 13UL)]
|
||||
public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
exponent.PowModCrt((UInt128)modulus, cycles).Should().Be(expected);
|
||||
}
|
||||
|
||||
private static ulong NaiveOrder(ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % q;
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value << 1) % q;
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user