Files
even-perfect-numbers-scanner/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs

229 lines
5.7 KiB
C#

using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class PrimeOrderCalculatorTests
{
public static IEnumerable<object[]> SmallPrimes()
{
yield return new object[] { 3UL };
yield return new object[] { 5UL };
yield return new object[] { 7UL };
yield return new object[] { 11UL };
yield return new object[] { 13UL };
yield return new object[] { 17UL };
yield return new object[] { 19UL };
yield return new object[] { 31UL };
yield return new object[] { 61UL };
yield return new object[] { 89UL };
yield return new object[] { 127UL };
yield return new object[] { 521UL };
}
public static IEnumerable<object[]> PrimesWithPrevious()
{
yield return new object[] { 5UL, 3UL };
yield return new object[] { 7UL, 5UL };
yield return new object[] { 11UL, 7UL };
yield return new object[] { 13UL, 11UL };
yield return new object[] { 17UL, 13UL };
yield return new object[] { 19UL, 17UL };
yield return new object[] { 31UL, 29UL };
yield return new object[] { 61UL, 59UL };
yield return new object[] { 89UL, 83UL };
yield return new object[] { 127UL, 113UL };
yield return new object[] { 521UL, 509UL };
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(SmallPrimes))]
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
{
ulong expected = ComputeOrderByDoubling(prime);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong result = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime,
previousOrder: null,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculatorConfig.HeuristicDefault);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(PrimesWithPrevious))]
public void Calculate_heuristic_mode_matches_strict_mode_even_with_previous_order(ulong prime, ulong previousPrime)
{
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong heuristic = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime,
previousOrder,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculatorConfig.HeuristicDefault);
ulong strict = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime,
previousOrder: null,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculatorConfig.StrictDefault);
PrimeOrderCalculatorAccelerator.Return(gpu);
heuristic.Should().Be(strict);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 1UL)]
[InlineData(3UL, 2UL)]
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong result = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime,
previousOrder: null,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculatorConfig.HeuristicDefault);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be(expectedOrder);
}
[Fact]
[Trait("Category", "Fast")]
public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored()
{
var config = new PrimeOrderCalculatorConfig(
smallFactorLimit: 1,
pollardRhoMilliseconds: 0,
maxPowChecks: 8,
strictMode: false);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong result = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime: 13UL,
previousOrder: null,
MontgomeryDivisorData.FromModulus(13UL),
config);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be(ComputeOrderByDoubling(13UL));
}
[Fact]
[Trait("Category", "Fast")]
public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode()
{
var heuristicConfig = new PrimeOrderCalculatorConfig(
smallFactorLimit: 2,
pollardRhoMilliseconds: 0,
maxPowChecks: 1,
strictMode: false);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
ulong heuristic = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime: 239UL,
previousOrder: null,
divisorData,
heuristicConfig);
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
var strictConfig = new PrimeOrderCalculatorConfig(
smallFactorLimit: 2,
pollardRhoMilliseconds: 0,
maxPowChecks: 1,
strictMode: true);
ulong strict = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime: 239UL,
previousOrder: null,
divisorData,
strictConfig);
strict.Should().Be(heuristic);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void Calculate_handles_128_bit_prime()
{
UInt128 prime = UInt128.Parse("18446744073709641691");
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
UInt128 result = PrimeOrderCalculatorGpu.Calculate(
gpu,
prime,
previousOrder: null,
PrimeOrderCalculatorConfig.HeuristicDefault);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be((UInt128)1229782938247309446UL);
}
[Fact]
[Trait("Category", "Fast")]
public void GetCycle_uses_wide_prime_heuristic()
{
UInt128 prime = UInt128.Parse("18446744073709641691");
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
var mersenneDivisorCycles = MersenneDivisorCyclesGpu.GetCycle(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
private static ulong ComputeOrderByDoubling(ulong prime)
{
if (prime == 2UL)
{
return 1UL;
}
if (prime == 3UL)
{
return 2UL;
}
ulong order = 1UL;
ulong value = 2UL % prime;
while (value != 1UL)
{
value = (value * 2UL) % prime;
order++;
}
return order;
}
}