Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0466d98434 |
No files matched your search
@@ -8,15 +8,6 @@ EvenPerfectBitScanner.ResultsParser/bin/*
|
|||||||
EvenPerfectBitScanner.ResultsParser/obj/*
|
EvenPerfectBitScanner.ResultsParser/obj/*
|
||||||
EvenPerfectBitScanner.Tests/bin/*
|
EvenPerfectBitScanner.Tests/bin/*
|
||||||
EvenPerfectBitScanner.Tests/obj/*
|
EvenPerfectBitScanner.Tests/obj/*
|
||||||
BuildDivisorTransitions/bin/*
|
|
||||||
BuildDivisorTransitions/obj/*
|
|
||||||
DeviceDependentGenerator/bin/*
|
|
||||||
DeviceDependentGenerator/obj/*
|
|
||||||
DeviceDependentGenerator.Tests/bin/*
|
|
||||||
DeviceDependentGenerator.Tests/obj/*
|
|
||||||
PerfectNumbers.Core.Tests/bin/*
|
PerfectNumbers.Core.Tests/bin/*
|
||||||
PerfectNumbers.Core.Tests/obj/*
|
PerfectNumbers.Core.Tests/obj/*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vs/*
|
|
||||||
*.generated.cs
|
|
||||||
PerfectNumbers.Core/generated/*
|
|
||||||
Vendored
+2
-30
@@ -8,8 +8,7 @@
|
|||||||
"options": [
|
"options": [
|
||||||
"EvenPerfectBitScanner",
|
"EvenPerfectBitScanner",
|
||||||
"EvenPerfectBitScanner.ResultsParser",
|
"EvenPerfectBitScanner.ResultsParser",
|
||||||
"EvenPerfectBitScanner.Benchmarks",
|
"EvenPerfectBitScanner.Benchmarks"
|
||||||
"BuildDivisorTransitions"
|
|
||||||
],
|
],
|
||||||
"default": "EvenPerfectBitScanner"
|
"default": "EvenPerfectBitScanner"
|
||||||
}
|
}
|
||||||
@@ -69,33 +68,6 @@
|
|||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"justMyCode": false
|
"justMyCode": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Run BuildDivisorTransitions",
|
|
||||||
"type": "coreclr",
|
|
||||||
"request": "launch",
|
|
||||||
"noDebug": true,
|
|
||||||
"preLaunchTask": "Build BuildDivisorTransitions Release",
|
|
||||||
"program": "${workspaceFolder}/BuildDivisorTransitions/bin/Release/net8.0/BuildDivisorTransitions.dll",
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"stopAtEntry": false,
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"justMyCode": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Debug BuildDivisorTransitions",
|
|
||||||
"type": "coreclr",
|
|
||||||
"request": "launch",
|
|
||||||
"preLaunchTask": "Build BuildDivisorTransitions Debug",
|
|
||||||
"program": "${workspaceFolder}/BuildDivisorTransitions/bin/Debug/net8.0/BuildDivisorTransitions.dll",
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
"stopAtEntry": false,
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"internalConsoleOptions": "neverOpen",
|
|
||||||
"justMyCode": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Run EvenPerfectBitScanner.ResultsParser",
|
"name": "Run EvenPerfectBitScanner.ResultsParser",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
@@ -164,4 +136,4 @@
|
|||||||
"justMyCode": false
|
"justMyCode": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Vendored
+1
-4
@@ -2,7 +2,6 @@
|
|||||||
"dotnet.defaultSolution": "FlexiCoder.sln",
|
"dotnet.defaultSolution": "FlexiCoder.sln",
|
||||||
"chatgpt.openOnStartup": true,
|
"chatgpt.openOnStartup": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"bydivisor",
|
|
||||||
"CCCCCCCDUL",
|
"CCCCCCCDUL",
|
||||||
"cofactor",
|
"cofactor",
|
||||||
"ILGPU",
|
"ILGPU",
|
||||||
@@ -10,7 +9,5 @@
|
|||||||
"Mersenne",
|
"Mersenne",
|
||||||
"primality",
|
"primality",
|
||||||
"suff"
|
"suff"
|
||||||
],
|
]
|
||||||
"geminicodeassist.project": "evenperfectbitscanner",
|
|
||||||
"todo-tree.tree.showCountsInTree": true
|
|
||||||
}
|
}
|
||||||
Vendored
+3
-50
@@ -11,8 +11,7 @@
|
|||||||
"--configuration",
|
"--configuration",
|
||||||
"Debug",
|
"Debug",
|
||||||
"--framework",
|
"--framework",
|
||||||
"net8.0",
|
"net8.0"
|
||||||
"/p:DeviceDependentGenerator_CopyGeneratedToProjectDir=true"
|
|
||||||
],
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@@ -30,8 +29,7 @@
|
|||||||
"--configuration",
|
"--configuration",
|
||||||
"Release",
|
"Release",
|
||||||
"--framework",
|
"--framework",
|
||||||
"net8.0",
|
"net8.0"
|
||||||
"/p:DeviceDependentGenerator_CopyGeneratedToProjectDir=true"
|
|
||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
@@ -96,36 +94,6 @@
|
|||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Build BuildDivisorTransitions Debug",
|
|
||||||
"command": "dotnet",
|
|
||||||
"type": "process",
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"${workspaceFolder}/BuildDivisorTransitions/BuildDivisorTransitions.csproj",
|
|
||||||
"--configuration",
|
|
||||||
"Debug",
|
|
||||||
"--framework",
|
|
||||||
"net8.0"
|
|
||||||
],
|
|
||||||
"group": "build",
|
|
||||||
"problemMatcher": "$msCompile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Build BuildDivisorTransitions Release",
|
|
||||||
"command": "dotnet",
|
|
||||||
"type": "process",
|
|
||||||
"args": [
|
|
||||||
"build",
|
|
||||||
"${workspaceFolder}/BuildDivisorTransitions/BuildDivisorTransitions.csproj",
|
|
||||||
"--configuration",
|
|
||||||
"Release",
|
|
||||||
"--framework",
|
|
||||||
"net8.0"
|
|
||||||
],
|
|
||||||
"group": "build",
|
|
||||||
"problemMatcher": "$msCompile"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Run EvenPerfectBitScanner",
|
"label": "Run EvenPerfectBitScanner",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
@@ -156,21 +124,6 @@
|
|||||||
],
|
],
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Run BuildDivisorTransitions",
|
|
||||||
"type": "process",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"run",
|
|
||||||
"--project",
|
|
||||||
"${workspaceFolder}/BuildDivisorTransitions/BuildDivisorTransitions.csproj",
|
|
||||||
"--configuration",
|
|
||||||
"Release",
|
|
||||||
"--framework",
|
|
||||||
"net8.0"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Run Release (no debug)",
|
"label": "Run Release (no debug)",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
@@ -230,4 +183,4 @@
|
|||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace BuildDivisorTransitions;
|
|
||||||
|
|
||||||
internal static class Program
|
|
||||||
{
|
|
||||||
private const string OutputFileName = "NextKForecastTraning.csv";
|
|
||||||
|
|
||||||
private static int Main(string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length != 2)
|
|
||||||
{
|
|
||||||
PrintUsage();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
string candidatesPath = args[0];
|
|
||||||
string divisorsPath = args[1];
|
|
||||||
|
|
||||||
if (!File.Exists(candidatesPath))
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Candidates file not found: {candidatesPath}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(divisorsPath))
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Divisors file not found: {divisorsPath}");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<long> candidatePs = LoadCandidates(candidatesPath);
|
|
||||||
Dictionary<long, BigInteger> divisors = LoadDivisors(divisorsPath);
|
|
||||||
List<Transition> transitions = BuildTransitions(candidatePs, divisors);
|
|
||||||
|
|
||||||
WriteOutput(transitions);
|
|
||||||
|
|
||||||
Console.WriteLine($"Saved {transitions.Count} transitions to {OutputFileName}.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PrintUsage()
|
|
||||||
{
|
|
||||||
Console.WriteLine("Usage: BuildDivisorTransitions <candidatesCsv> <divisorsCsv>");
|
|
||||||
Console.WriteLine(" candidatesCsv: CSV with columns p,searchedMersenne,detailedCheck,passedAllTests");
|
|
||||||
Console.WriteLine(" divisorsCsv: CSV with columns p,divisor");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<long> LoadCandidates(string path)
|
|
||||||
{
|
|
||||||
var result = new List<long>();
|
|
||||||
var seen = new HashSet<long>();
|
|
||||||
|
|
||||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
|
|
||||||
|
|
||||||
while (!reader.EndOfStream)
|
|
||||||
{
|
|
||||||
string? line = reader.ReadLine();
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string trimmedLine = line.Trim();
|
|
||||||
if (trimmedLine.StartsWith("p", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] parts = trimmedLine.Split(',', StringSplitOptions.TrimEntries);
|
|
||||||
if (parts.Length == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!long.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out long p))
|
|
||||||
{
|
|
||||||
throw new FormatException($"Invalid p value '{parts[0]}' in {path}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seen.Add(p))
|
|
||||||
{
|
|
||||||
result.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<long, BigInteger> LoadDivisors(string path)
|
|
||||||
{
|
|
||||||
var result = new Dictionary<long, BigInteger>();
|
|
||||||
|
|
||||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
|
|
||||||
|
|
||||||
while (!reader.EndOfStream)
|
|
||||||
{
|
|
||||||
string? line = reader.ReadLine();
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string trimmedLine = line.Trim();
|
|
||||||
if (trimmedLine.StartsWith("p", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] parts = trimmedLine.Split(',', StringSplitOptions.TrimEntries);
|
|
||||||
if (parts.Length < 2)
|
|
||||||
{
|
|
||||||
throw new FormatException($"Line '{line}' in {path} does not contain both p and divisor columns.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!long.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out long p))
|
|
||||||
{
|
|
||||||
throw new FormatException($"Invalid p value '{parts[0]}' in {path}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BigInteger.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out BigInteger divisor))
|
|
||||||
{
|
|
||||||
throw new FormatException($"Invalid divisor value '{parts[1]}' for p={p} in {path}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsProperMersenneDivisor(p, divisor))
|
|
||||||
{
|
|
||||||
result[p] = divisor;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Skipping invalid divisor entry p={p}, divisor={divisor} (not a proper divisor of 2^{p}-1 or fails powmod check).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Transition> BuildTransitions(IReadOnlyList<long> candidatePs, IReadOnlyDictionary<long, BigInteger> divisors)
|
|
||||||
{
|
|
||||||
var transitions = new List<Transition>();
|
|
||||||
|
|
||||||
for (int i = 0; i < candidatePs.Count - 1; i++)
|
|
||||||
{
|
|
||||||
long currentP = candidatePs[i];
|
|
||||||
long nextP = candidatePs[i + 1];
|
|
||||||
|
|
||||||
if (!divisors.TryGetValue(currentP, out BigInteger currentDivisor))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!divisors.TryGetValue(nextP, out BigInteger nextDivisor))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger currentK = CalculateK(currentP, currentDivisor);
|
|
||||||
BigInteger nextK = CalculateK(nextP, nextDivisor);
|
|
||||||
bool isNextKHigher = nextK > currentK;
|
|
||||||
transitions.Add(new Transition(currentP, currentDivisor, LastDigit(currentP), currentK, LastMersenneDigit(currentP), isNextKHigher));
|
|
||||||
}
|
|
||||||
|
|
||||||
return transitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigInteger CalculateK(long p, BigInteger divisor)
|
|
||||||
{
|
|
||||||
BigInteger numerator = divisor - BigInteger.One;
|
|
||||||
BigInteger denominator = new BigInteger(p) << 1;
|
|
||||||
|
|
||||||
if (denominator <= BigInteger.Zero)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Computed 2p is not positive for p={p}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger remainder = numerator % denominator;
|
|
||||||
if (remainder != BigInteger.Zero)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Divisor {divisor} is not congruent to 1 mod 2p for p={p}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger k = numerator / denominator;
|
|
||||||
if (k < BigInteger.One)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Calculated k={k} is less than 1 for p={p}, divisor={divisor}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsProperMersenneDivisor(long p, in BigInteger divisor)
|
|
||||||
{
|
|
||||||
if (p <= 1 || divisor <= BigInteger.One)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitLength = GetBitLength(divisor);
|
|
||||||
if (bitLength > p)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitLength == p && IsPowerOfTwo(divisor + BigInteger.One))
|
|
||||||
{
|
|
||||||
// Divisor equals the Mersenne number itself.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BigInteger.ModPow(2, p, divisor) == BigInteger.One;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetBitLength(in BigInteger value)
|
|
||||||
{
|
|
||||||
if (value.IsZero)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bytes = value.ToByteArray(isUnsigned: true, isBigEndian: false);
|
|
||||||
byte msb = bytes[^1];
|
|
||||||
return ((bytes.Length - 1) * 8) + (BitOperations.Log2(msb) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsPowerOfTwo(in BigInteger value)
|
|
||||||
{
|
|
||||||
return value > BigInteger.One && (value & (value - BigInteger.One)) == BigInteger.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void WriteOutput(IReadOnlyCollection<Transition> transitions)
|
|
||||||
{
|
|
||||||
using var writer = new StreamWriter(OutputFileName, false, Encoding.UTF8);
|
|
||||||
writer.WriteLine("p,divisor,lastDigit,k,lastMersenneDigit,isNextKHigher");
|
|
||||||
|
|
||||||
foreach (Transition transition in transitions)
|
|
||||||
{
|
|
||||||
writer.WriteLine($"{transition.P.ToString(CultureInfo.InvariantCulture)},{FormatBigInteger(transition.Divisor)},{transition.LastDigit},{FormatBigInteger(transition.CurrentK)},{transition.LastMersenneDigit},{(transition.IsNextKHigher ? "1" : "0")}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int LastDigit(long p) => (int)(Math.Abs(p) % 10);
|
|
||||||
|
|
||||||
private static int LastMersenneDigit(long p)
|
|
||||||
{
|
|
||||||
long mod = p & 3;
|
|
||||||
return mod == 1 ? 1 : 7; // odd primes give p%4 == 1 or 3; 2^p mod 10 cycles -> 1 or 7 after subtracting 1
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatBigInteger(in BigInteger value) => value.ToString(CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
private sealed record Transition(long P, BigInteger Divisor, int LastDigit, BigInteger CurrentK, int LastMersenneDigit, bool IsNextKHigher);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<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" />
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\DeviceDependentGenerator\DeviceDependentGenerator.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
using System.Collections.Immutable;
|
|
||||||
using FluentAssertions;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace DeviceDependentGenerator.Tests;
|
|
||||||
|
|
||||||
public sealed class DeviceDependentGeneratorTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Generates_classes_per_enum_value_and_filters_device_blocks_with_suffix()
|
|
||||||
{
|
|
||||||
const string attributeAndEnum = """
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
|
|
||||||
public sealed class DeviceDependentTemplateAttribute : Attribute
|
|
||||||
{
|
|
||||||
public DeviceDependentTemplateAttribute(Type enumType) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = true)]
|
|
||||||
public sealed class EnumDependentTemplateAttribute : Attribute
|
|
||||||
{
|
|
||||||
public EnumDependentTemplateAttribute(Type enumType) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
|
|
||||||
public sealed class NameSuffixAttribute : Attribute
|
|
||||||
{
|
|
||||||
public NameSuffixAttribute(string suffix) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum CalculationDevice
|
|
||||||
{
|
|
||||||
Cpu,
|
|
||||||
Hybrid,
|
|
||||||
Gpu,
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
const string template = """
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
[DeviceDependentTemplate(typeof(CalculationDevice))]
|
|
||||||
[NameSuffix("Impl")]
|
|
||||||
public static class PrimeCalculatorTemplate
|
|
||||||
{
|
|
||||||
#if DEVICE_CPU
|
|
||||||
public static string Device => "CPU";
|
|
||||||
#elif DEVICE_HYBRID
|
|
||||||
public static string Device => "HYBRID";
|
|
||||||
#elif DEVICE_GPU
|
|
||||||
public static string Device => "GPU";
|
|
||||||
#else
|
|
||||||
#error Unknown device
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
CSharpCompilation compilation = CreateCompilation(attributeAndEnum, template);
|
|
||||||
|
|
||||||
var generator = new PerfectNumbers.Core.Generators.DeviceDependentGenerator();
|
|
||||||
var parseOptions = (CSharpParseOptions)compilation.SyntaxTrees.First().Options;
|
|
||||||
ISourceGenerator sourceGenerator = generator.AsSourceGenerator();
|
|
||||||
GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { sourceGenerator }, parseOptions: parseOptions);
|
|
||||||
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out ImmutableArray<Diagnostic> diagnostics);
|
|
||||||
|
|
||||||
diagnostics.Should().BeEmpty();
|
|
||||||
|
|
||||||
GeneratorDriverRunResult runResult = driver.GetRunResult();
|
|
||||||
runResult.Diagnostics.Should().BeEmpty();
|
|
||||||
|
|
||||||
ImmutableArray<GeneratedSourceResult> generated = runResult.Results.Single().GeneratedSources;
|
|
||||||
|
|
||||||
string cpu = generated.Single(s => s.HintName.EndsWith("PrimeCalculator.CpuImpl.generated.cs", StringComparison.Ordinal)).SourceText.ToString();
|
|
||||||
string hybrid = generated.Single(s => s.HintName.EndsWith("PrimeCalculator.HybridImpl.generated.cs", StringComparison.Ordinal)).SourceText.ToString();
|
|
||||||
string gpu = generated.Single(s => s.HintName.EndsWith("PrimeCalculator.GpuImpl.generated.cs", StringComparison.Ordinal)).SourceText.ToString();
|
|
||||||
|
|
||||||
cpu.Should().Contain("class PrimeCalculatorCpuImpl");
|
|
||||||
cpu.Should().Contain("=> \"CPU\"");
|
|
||||||
cpu.Should().NotContain("#if");
|
|
||||||
cpu.Should().NotContain("#endif");
|
|
||||||
|
|
||||||
hybrid.Should().Contain("class PrimeCalculatorHybridImpl");
|
|
||||||
hybrid.Should().Contain("=> \"HYBRID\"");
|
|
||||||
hybrid.Should().NotContain("#if");
|
|
||||||
hybrid.Should().NotContain("#endif");
|
|
||||||
|
|
||||||
gpu.Should().Contain("class PrimeCalculatorGpuImpl");
|
|
||||||
gpu.Should().Contain("=> \"GPU\"");
|
|
||||||
gpu.Should().NotContain("#if");
|
|
||||||
gpu.Should().NotContain("#endif");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CSharpCompilation CreateCompilation(params string[] sources)
|
|
||||||
{
|
|
||||||
var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp12);
|
|
||||||
var trees = sources.Select((text, i) => CSharpSyntaxTree.ParseText(text, parseOptions, path: "Test" + i + ".cs"));
|
|
||||||
|
|
||||||
var references = new[]
|
|
||||||
{
|
|
||||||
typeof(object),
|
|
||||||
typeof(Attribute),
|
|
||||||
typeof(Enumerable),
|
|
||||||
typeof(System.Runtime.GCSettings),
|
|
||||||
}
|
|
||||||
.Select(static t => MetadataReference.CreateFromFile(t.Assembly.Location))
|
|
||||||
.DistinctBy(static r => r.Display)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return CSharpCompilation.Create(
|
|
||||||
assemblyName: "DeviceDependentGenerator.Tests.Compilation",
|
|
||||||
syntaxTrees: trees,
|
|
||||||
references: references,
|
|
||||||
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,678 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Microsoft.CodeAnalysis.Diagnostics;
|
|
||||||
using Microsoft.CodeAnalysis.Text;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Generators;
|
|
||||||
|
|
||||||
[Generator(LanguageNames.CSharp)]
|
|
||||||
public sealed class DeviceDependentGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private const string DeviceDependentTemplateAttributeMetadataName = "PerfectNumbers.Core.DeviceDependentTemplateAttribute";
|
|
||||||
private const string EnumDependentTemplateAttributeMetadataName = "PerfectNumbers.Core.EnumDependentTemplateAttribute";
|
|
||||||
private const string NameSuffixAttributeMetadataName = "PerfectNumbers.Core.NameSuffixAttribute";
|
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor TemplateEnumTypeMustBeEnum = new(
|
|
||||||
id: "DDG001",
|
|
||||||
title: "Template attribute requires an enum type",
|
|
||||||
messageFormat: "Template attribute '{0}' requires an enum type, but '{1}' is not an enum.",
|
|
||||||
category: "DeviceDependentGenerator",
|
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
|
||||||
isEnabledByDefault: true);
|
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor TemplateMustBeTopLevel = new(
|
|
||||||
id: "DDG002",
|
|
||||||
title: "DeviceDependentTemplate must be top-level",
|
|
||||||
messageFormat: "DeviceDependentTemplate can only be used on a top-level class/struct (not nested types).",
|
|
||||||
category: "DeviceDependentGenerator",
|
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
|
||||||
isEnabledByDefault: true);
|
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor TemplateNotFoundInDeviceParse = new(
|
|
||||||
id: "DDG003",
|
|
||||||
title: "Template not found after preprocessing",
|
|
||||||
messageFormat: "Could not find template type '{0}' after preprocessing for combination '{1}'.",
|
|
||||||
category: "DeviceDependentGenerator",
|
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
|
||||||
isEnabledByDefault: true);
|
|
||||||
|
|
||||||
private static readonly DiagnosticDescriptor DuplicateSingleUseAttribute = new(
|
|
||||||
id: "DDG004",
|
|
||||||
title: "Template attribute used multiple times",
|
|
||||||
messageFormat: "Attribute '{0}' can be applied only once to '{1}'.",
|
|
||||||
category: "DeviceDependentGenerator",
|
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
|
||||||
isEnabledByDefault: true);
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
IncrementalValuesProvider<TemplateInfo> templates = context.SyntaxProvider.CreateSyntaxProvider(
|
|
||||||
predicate: static (node, _) => node is TypeDeclarationSyntax typeDecl && typeDecl.AttributeLists.Count > 0,
|
|
||||||
transform: static (syntaxContext, cancellationToken) => TryGetTemplateInfo(syntaxContext, cancellationToken))
|
|
||||||
.Where(static info => info is not null)!
|
|
||||||
.Select(static (info, _) => info!);
|
|
||||||
|
|
||||||
IncrementalValueProvider<GeneratorOptions> options = context.AnalyzerConfigOptionsProvider.Select(
|
|
||||||
static (provider, _) => new GeneratorOptions(provider.GlobalOptions));
|
|
||||||
|
|
||||||
IncrementalValueProvider<(Compilation Compilation, GeneratorOptions Options)> compilationAndOptions =
|
|
||||||
context.CompilationProvider.Combine(options).Select(static (pair, _) => (pair.Left, pair.Right));
|
|
||||||
|
|
||||||
context.RegisterSourceOutput(templates.Combine(compilationAndOptions), static (productionContext, pair) =>
|
|
||||||
{
|
|
||||||
GenerateForTemplate(productionContext, pair.Left, pair.Right.Compilation, pair.Right.Options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TemplateInfo? TryGetTemplateInfo(GeneratorSyntaxContext context, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
if (context.Node is not TypeDeclarationSyntax typeSyntax)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.SemanticModel.GetDeclaredSymbol(typeSyntax, cancellationToken) is not INamedTypeSymbol templateSymbol)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CollectTemplateInfo(templateSymbol, typeSyntax, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateForTemplate(SourceProductionContext context, TemplateInfo template, Compilation compilation, GeneratorOptions options)
|
|
||||||
{
|
|
||||||
foreach (Diagnostic diagnostic in template.Diagnostics)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!template.Valid || !template.HasAnyTemplateAttribute)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (template.DeclaringSyntaxReference?.GetSyntax(context.CancellationToken) is not TypeDeclarationSyntax templateSyntax)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsTopLevel(templateSyntax))
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(TemplateMustBeTopLevel, templateSyntax.Identifier.GetLocation()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string templateName = template.Name;
|
|
||||||
string baseName = templateName.EndsWith("Template", StringComparison.Ordinal)
|
|
||||||
? templateName.Substring(0, templateName.Length - "Template".Length)
|
|
||||||
: templateName;
|
|
||||||
|
|
||||||
SourceText fileText = templateSyntax.SyntaxTree.GetText(context.CancellationToken);
|
|
||||||
CSharpParseOptions baseParseOptions = (CSharpParseOptions)templateSyntax.SyntaxTree.Options;
|
|
||||||
string filePath = templateSyntax.SyntaxTree.FilePath;
|
|
||||||
|
|
||||||
foreach (TemplateVariant variant in template.ExpandVariants())
|
|
||||||
{
|
|
||||||
CSharpParseOptions parseOptions = WithDefines(baseParseOptions, variant.PreprocessorSymbols);
|
|
||||||
|
|
||||||
SyntaxTree deviceTree = CSharpSyntaxTree.ParseText(
|
|
||||||
fileText.ToString(),
|
|
||||||
parseOptions,
|
|
||||||
filePath,
|
|
||||||
encoding: Encoding.UTF8);
|
|
||||||
|
|
||||||
CompilationUnitSyntax deviceRoot = deviceTree.GetCompilationUnitRoot(context.CancellationToken);
|
|
||||||
TypeDeclarationSyntax? deviceTemplateSyntax = FindTemplateDeclaration(deviceRoot, templateName);
|
|
||||||
if (deviceTemplateSyntax is null)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(
|
|
||||||
TemplateNotFoundInDeviceParse,
|
|
||||||
templateSyntax.Identifier.GetLocation(),
|
|
||||||
templateName,
|
|
||||||
variant.CombinationDisplay));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReportDeviceSpecificTemplateErrors(context, deviceTree);
|
|
||||||
|
|
||||||
string generatedTypeName = baseName + variant.NameSuffix + template.NameSuffix;
|
|
||||||
TypeDeclarationSyntax generatedTypeSyntax = TransformTemplateType(deviceTemplateSyntax, generatedTypeName);
|
|
||||||
|
|
||||||
CompilationUnitSyntax generatedRoot = BuildGeneratedFile(
|
|
||||||
externs: deviceRoot.Externs,
|
|
||||||
usings: deviceRoot.Usings,
|
|
||||||
containingNamespace: template.ContainingNamespace,
|
|
||||||
typeDeclaration: generatedTypeSyntax);
|
|
||||||
|
|
||||||
generatedRoot = (CompilationUnitSyntax)new ConditionalDirectiveAndDisabledTextStripper().Visit(generatedRoot)!;
|
|
||||||
generatedRoot = generatedRoot.NormalizeWhitespace();
|
|
||||||
|
|
||||||
string generatedFileName = baseName + "." + variant.FileSuffix + template.NameSuffix + ".generated.cs";
|
|
||||||
string hintName = options.BuildHintName(template.ContainingNamespace, generatedFileName);
|
|
||||||
context.AddSource(
|
|
||||||
hintName,
|
|
||||||
SourceText.From("// <auto-generated/>\n#nullable enable\n" + generatedRoot.ToFullString(), Encoding.UTF8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReportDeviceSpecificTemplateErrors(SourceProductionContext context, SyntaxTree deviceTree)
|
|
||||||
{
|
|
||||||
foreach (Diagnostic diagnostic in deviceTree.GetDiagnostics(context.CancellationToken))
|
|
||||||
{
|
|
||||||
if (diagnostic.Severity == DiagnosticSeverity.Error)
|
|
||||||
{
|
|
||||||
context.ReportDiagnostic(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsTopLevel(TypeDeclarationSyntax typeDeclaration)
|
|
||||||
=> typeDeclaration.Parent is NamespaceDeclarationSyntax
|
|
||||||
|| typeDeclaration.Parent is FileScopedNamespaceDeclarationSyntax
|
|
||||||
|| typeDeclaration.Parent is CompilationUnitSyntax;
|
|
||||||
|
|
||||||
private static CSharpParseOptions WithDefines(CSharpParseOptions baseParseOptions, IEnumerable<string> defines)
|
|
||||||
=> baseParseOptions.WithPreprocessorSymbols(baseParseOptions.PreprocessorSymbolNames.Concat(defines).Distinct());
|
|
||||||
|
|
||||||
private static TypeDeclarationSyntax? FindTemplateDeclaration(CompilationUnitSyntax root, string templateName)
|
|
||||||
{
|
|
||||||
foreach (TypeDeclarationSyntax typeDecl in root.DescendantNodes().OfType<TypeDeclarationSyntax>())
|
|
||||||
{
|
|
||||||
if (string.Equals(typeDecl.Identifier.ValueText, templateName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return typeDecl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TypeDeclarationSyntax TransformTemplateType(TypeDeclarationSyntax templateType, string generatedTypeName)
|
|
||||||
{
|
|
||||||
TypeDeclarationSyntax withoutAttribute = RemoveTemplateAttributes(templateType);
|
|
||||||
TypeDeclarationSyntax renamed = (TypeDeclarationSyntax)new SelfIdentifierRewriter(templateType.Identifier.ValueText, generatedTypeName).Visit(withoutAttribute)!;
|
|
||||||
return renamed.WithIdentifier(SyntaxFactory.Identifier(generatedTypeName));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TypeDeclarationSyntax RemoveTemplateAttributes(TypeDeclarationSyntax typeDeclaration)
|
|
||||||
{
|
|
||||||
if (typeDeclaration.AttributeLists.Count == 0)
|
|
||||||
{
|
|
||||||
return typeDeclaration;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newLists = new List<AttributeListSyntax>(typeDeclaration.AttributeLists.Count);
|
|
||||||
foreach (AttributeListSyntax list in typeDeclaration.AttributeLists)
|
|
||||||
{
|
|
||||||
SeparatedSyntaxList<AttributeSyntax> attributes = list.Attributes;
|
|
||||||
var newAttributes = new List<AttributeSyntax>(attributes.Count);
|
|
||||||
foreach (AttributeSyntax attribute in attributes)
|
|
||||||
{
|
|
||||||
if (IsTemplateAttribute(attribute.Name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newAttributes.Add(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newAttributes.Count == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newLists.Add(list.WithAttributes(SyntaxFactory.SeparatedList(newAttributes)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeDeclaration.WithAttributeLists(SyntaxFactory.List(newLists));
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class SelfIdentifierRewriter : CSharpSyntaxRewriter
|
|
||||||
{
|
|
||||||
private readonly string templateName;
|
|
||||||
private readonly string generatedName;
|
|
||||||
|
|
||||||
public SelfIdentifierRewriter(string templateName, string generatedName)
|
|
||||||
{
|
|
||||||
this.templateName = templateName;
|
|
||||||
this.generatedName = generatedName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override SyntaxToken VisitToken(SyntaxToken token)
|
|
||||||
{
|
|
||||||
if (token.IsKind(SyntaxKind.IdentifierToken) && string.Equals(token.ValueText, templateName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return SyntaxFactory.Identifier(token.LeadingTrivia, generatedName, token.TrailingTrivia);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.VisitToken(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsTemplateAttribute(NameSyntax attributeName)
|
|
||||||
{
|
|
||||||
string text = attributeName.ToString();
|
|
||||||
return text.EndsWith("DeviceDependentTemplate", StringComparison.Ordinal)
|
|
||||||
|| text.EndsWith("DeviceDependentTemplateAttribute", StringComparison.Ordinal)
|
|
||||||
|| text.EndsWith("EnumDependentTemplate", StringComparison.Ordinal)
|
|
||||||
|| text.EndsWith("EnumDependentTemplateAttribute", StringComparison.Ordinal)
|
|
||||||
|| text.EndsWith("NameSuffix", StringComparison.Ordinal)
|
|
||||||
|| text.EndsWith("NameSuffixAttribute", StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CompilationUnitSyntax BuildGeneratedFile(
|
|
||||||
SyntaxList<ExternAliasDirectiveSyntax> externs,
|
|
||||||
SyntaxList<UsingDirectiveSyntax> usings,
|
|
||||||
string containingNamespace,
|
|
||||||
TypeDeclarationSyntax typeDeclaration)
|
|
||||||
{
|
|
||||||
MemberDeclarationSyntax member;
|
|
||||||
if (string.IsNullOrEmpty(containingNamespace))
|
|
||||||
{
|
|
||||||
member = typeDeclaration;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
member = SyntaxFactory
|
|
||||||
.FileScopedNamespaceDeclaration(SyntaxFactory.ParseName(containingNamespace))
|
|
||||||
.WithMembers(SyntaxFactory.SingletonList<MemberDeclarationSyntax>(typeDeclaration));
|
|
||||||
}
|
|
||||||
|
|
||||||
return SyntaxFactory
|
|
||||||
.CompilationUnit()
|
|
||||||
.WithExterns(externs)
|
|
||||||
.WithUsings(usings)
|
|
||||||
.WithMembers(SyntaxFactory.SingletonList(member));
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class TemplateInfo
|
|
||||||
{
|
|
||||||
private TemplateInfo(
|
|
||||||
INamedTypeSymbol templateType,
|
|
||||||
ImmutableArray<EnumTemplateInfo> enumAttributes,
|
|
||||||
DeviceTemplateInfo? deviceAttribute,
|
|
||||||
string nameSuffix,
|
|
||||||
ImmutableArray<Diagnostic> diagnostics)
|
|
||||||
{
|
|
||||||
TemplateType = templateType;
|
|
||||||
EnumAttributes = enumAttributes;
|
|
||||||
DeviceAttribute = deviceAttribute;
|
|
||||||
NameSuffix = nameSuffix;
|
|
||||||
Diagnostics = diagnostics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public INamedTypeSymbol TemplateType { get; }
|
|
||||||
public ImmutableArray<EnumTemplateInfo> EnumAttributes { get; }
|
|
||||||
public DeviceTemplateInfo? DeviceAttribute { get; }
|
|
||||||
public string NameSuffix { get; }
|
|
||||||
public ImmutableArray<Diagnostic> Diagnostics { get; }
|
|
||||||
|
|
||||||
public bool Valid => Diagnostics.IsDefaultOrEmpty;
|
|
||||||
public bool HasAnyTemplateAttribute => EnumAttributes.Length > 0 || DeviceAttribute is not null;
|
|
||||||
|
|
||||||
public string Name => TemplateType.Name;
|
|
||||||
|
|
||||||
public string ContainingNamespace => TemplateType.ContainingNamespace?.IsGlobalNamespace == true
|
|
||||||
? string.Empty
|
|
||||||
: TemplateType.ContainingNamespace.ToDisplayString();
|
|
||||||
|
|
||||||
public SyntaxReference? DeclaringSyntaxReference => TemplateType.DeclaringSyntaxReferences.FirstOrDefault();
|
|
||||||
|
|
||||||
public IEnumerable<TemplateVariant> ExpandVariants()
|
|
||||||
{
|
|
||||||
var combinations = ImmutableArray.Create(new TemplateVariantBuilder());
|
|
||||||
|
|
||||||
foreach (EnumTemplateInfo enumInfo in EnumAttributes)
|
|
||||||
{
|
|
||||||
combinations = ExpandEnum(combinations, enumInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasEnumAttributes = EnumAttributes.Length > 0;
|
|
||||||
|
|
||||||
if (DeviceAttribute is not null)
|
|
||||||
{
|
|
||||||
combinations = ExpandDevice(combinations, DeviceAttribute.Value, hasEnumAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (TemplateVariantBuilder builder in combinations)
|
|
||||||
{
|
|
||||||
yield return new TemplateVariant(builder.Symbols, builder.NameSuffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableArray<TemplateVariantBuilder> ExpandEnum(
|
|
||||||
ImmutableArray<TemplateVariantBuilder> current,
|
|
||||||
EnumTemplateInfo enumInfo)
|
|
||||||
{
|
|
||||||
var builder = ImmutableArray.CreateBuilder<TemplateVariantBuilder>(current.Length * enumInfo.Values.Length);
|
|
||||||
foreach (TemplateVariantBuilder existing in current)
|
|
||||||
{
|
|
||||||
foreach (string value in enumInfo.Values)
|
|
||||||
{
|
|
||||||
builder.Add(existing.Append(
|
|
||||||
symbol: $"{enumInfo.EnumType.Name}_{value}",
|
|
||||||
namePart: $"For{value}{enumInfo.EnumType.Name}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableArray<TemplateVariantBuilder> ExpandDevice(
|
|
||||||
ImmutableArray<TemplateVariantBuilder> current,
|
|
||||||
DeviceTemplateInfo deviceInfo,
|
|
||||||
bool prefixWithFor)
|
|
||||||
{
|
|
||||||
var builder = ImmutableArray.CreateBuilder<TemplateVariantBuilder>(current.Length * deviceInfo.Values.Length);
|
|
||||||
foreach (TemplateVariantBuilder existing in current)
|
|
||||||
{
|
|
||||||
foreach (string value in deviceInfo.Values)
|
|
||||||
{
|
|
||||||
string namePart = prefixWithFor ? $"For{value}" : value;
|
|
||||||
builder.Add(existing.Append(
|
|
||||||
symbol: "DEVICE_" + value.ToUpperInvariant(),
|
|
||||||
namePart: namePart));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TemplateInfo Create(
|
|
||||||
INamedTypeSymbol templateType,
|
|
||||||
ImmutableArray<EnumTemplateInfo> enumAttributes,
|
|
||||||
DeviceTemplateInfo? deviceAttribute,
|
|
||||||
string? nameSuffix,
|
|
||||||
ImmutableArray<Diagnostic> diagnostics)
|
|
||||||
=> new(
|
|
||||||
templateType,
|
|
||||||
enumAttributes,
|
|
||||||
deviceAttribute,
|
|
||||||
nameSuffix ?? string.Empty,
|
|
||||||
diagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct EnumTemplateInfo
|
|
||||||
{
|
|
||||||
public EnumTemplateInfo(INamedTypeSymbol enumType, ImmutableArray<string> values, Location attributeLocation)
|
|
||||||
{
|
|
||||||
EnumType = enumType;
|
|
||||||
Values = values;
|
|
||||||
AttributeLocation = attributeLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public INamedTypeSymbol EnumType { get; }
|
|
||||||
public ImmutableArray<string> Values { get; }
|
|
||||||
public Location AttributeLocation { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct DeviceTemplateInfo
|
|
||||||
{
|
|
||||||
public DeviceTemplateInfo(INamedTypeSymbol enumType, ImmutableArray<string> values, Location attributeLocation)
|
|
||||||
{
|
|
||||||
EnumType = enumType;
|
|
||||||
Values = values;
|
|
||||||
AttributeLocation = attributeLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public INamedTypeSymbol EnumType { get; }
|
|
||||||
public ImmutableArray<string> Values { get; }
|
|
||||||
public Location AttributeLocation { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct TemplateVariant
|
|
||||||
{
|
|
||||||
public TemplateVariant(ImmutableArray<string> preprocessorSymbols, string nameSuffix)
|
|
||||||
{
|
|
||||||
PreprocessorSymbols = preprocessorSymbols;
|
|
||||||
NameSuffix = nameSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableArray<string> PreprocessorSymbols { get; }
|
|
||||||
public string NameSuffix { get; }
|
|
||||||
public string FileSuffix => NameSuffix;
|
|
||||||
public string CombinationDisplay => string.Join(", ", PreprocessorSymbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class TemplateVariantBuilder
|
|
||||||
{
|
|
||||||
public TemplateVariantBuilder()
|
|
||||||
{
|
|
||||||
Symbols = ImmutableArray<string>.Empty;
|
|
||||||
NameSuffix = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TemplateVariantBuilder(ImmutableArray<string> symbols, string nameSuffix)
|
|
||||||
{
|
|
||||||
Symbols = symbols;
|
|
||||||
NameSuffix = nameSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableArray<string> Symbols { get; }
|
|
||||||
public string NameSuffix { get; }
|
|
||||||
|
|
||||||
public TemplateVariantBuilder Append(string symbol, string namePart)
|
|
||||||
{
|
|
||||||
return new TemplateVariantBuilder(Symbols.Add(symbol), NameSuffix + namePart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TemplateInfo? CollectTemplateInfo(INamedTypeSymbol templateSymbol, TypeDeclarationSyntax typeSyntax, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
ImmutableArray<AttributeData> attributes = templateSymbol.GetAttributes();
|
|
||||||
var enumAttributes = new List<EnumTemplateInfo>();
|
|
||||||
DeviceTemplateInfo? deviceAttribute = null;
|
|
||||||
string? nameSuffix = null;
|
|
||||||
var diagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
|
|
||||||
|
|
||||||
AttributeData? nameSuffixAttribute = null;
|
|
||||||
AttributeData? deviceTemplateAttribute = null;
|
|
||||||
foreach (AttributeData attribute in attributes)
|
|
||||||
{
|
|
||||||
string? metadataName = attribute.AttributeClass?.ToDisplayString();
|
|
||||||
if (metadataName is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(metadataName, EnumDependentTemplateAttributeMetadataName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
enumAttributes.AddRange(CreateEnumTemplateInfo(attribute, TemplateEnumTypeMustBeEnum, diagnostics));
|
|
||||||
}
|
|
||||||
else if (string.Equals(metadataName, DeviceDependentTemplateAttributeMetadataName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (deviceTemplateAttribute is not null)
|
|
||||||
{
|
|
||||||
diagnostics.Add(Diagnostic.Create(
|
|
||||||
DuplicateSingleUseAttribute,
|
|
||||||
attribute.ApplicationSyntaxReference?.GetSyntax(cancellationToken)?.GetLocation() ?? typeSyntax.GetLocation(),
|
|
||||||
DeviceDependentTemplateAttributeMetadataName,
|
|
||||||
templateSymbol.Name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceTemplateAttribute = attribute;
|
|
||||||
}
|
|
||||||
else if (string.Equals(metadataName, NameSuffixAttributeMetadataName, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (nameSuffixAttribute is not null)
|
|
||||||
{
|
|
||||||
diagnostics.Add(Diagnostic.Create(
|
|
||||||
DuplicateSingleUseAttribute,
|
|
||||||
attribute.ApplicationSyntaxReference?.GetSyntax(cancellationToken)?.GetLocation() ?? typeSyntax.GetLocation(),
|
|
||||||
NameSuffixAttributeMetadataName,
|
|
||||||
templateSymbol.Name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
nameSuffixAttribute = attribute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deviceTemplateAttribute is not null)
|
|
||||||
{
|
|
||||||
var deviceInfo = CreateEnumTemplateInfo(deviceTemplateAttribute, TemplateEnumTypeMustBeEnum, diagnostics).FirstOrDefault();
|
|
||||||
if (deviceInfo.EnumType is not null)
|
|
||||||
{
|
|
||||||
deviceAttribute = new DeviceTemplateInfo(deviceInfo.EnumType, deviceInfo.Values, deviceInfo.AttributeLocation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameSuffixAttribute is not null)
|
|
||||||
{
|
|
||||||
nameSuffix = ExtractSuffix(nameSuffixAttribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enumAttributes.Any() && deviceAttribute is null && nameSuffixAttribute is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImmutableArray<EnumTemplateInfo> orderedEnums = enumAttributes
|
|
||||||
.OrderBy(static e => e.AttributeLocation.SourceSpan.Start)
|
|
||||||
.ToImmutableArray();
|
|
||||||
|
|
||||||
return TemplateInfo.Create(
|
|
||||||
templateSymbol,
|
|
||||||
orderedEnums,
|
|
||||||
deviceAttribute,
|
|
||||||
nameSuffix,
|
|
||||||
diagnostics.ToImmutable());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<EnumTemplateInfo> CreateEnumTemplateInfo(
|
|
||||||
AttributeData attribute,
|
|
||||||
DiagnosticDescriptor diagnosticDescriptor,
|
|
||||||
ImmutableArray<Diagnostic>.Builder diagnostics)
|
|
||||||
{
|
|
||||||
if (attribute.ConstructorArguments.Length < 1)
|
|
||||||
{
|
|
||||||
return Array.Empty<EnumTemplateInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attribute.ConstructorArguments[0].Value is not INamedTypeSymbol enumTypeSymbol)
|
|
||||||
{
|
|
||||||
return Array.Empty<EnumTemplateInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enumTypeSymbol.TypeKind != TypeKind.Enum)
|
|
||||||
{
|
|
||||||
Location location = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? Location.None;
|
|
||||||
diagnostics.Add(Diagnostic.Create(diagnosticDescriptor, location, attribute.AttributeClass?.Name ?? "TemplateAttribute", enumTypeSymbol.ToDisplayString()));
|
|
||||||
return Array.Empty<EnumTemplateInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImmutableArray<string> enumValues = enumTypeSymbol
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IFieldSymbol>()
|
|
||||||
.Where(static f => !f.IsImplicitlyDeclared && f.HasConstantValue)
|
|
||||||
.Select(static f => f.Name)
|
|
||||||
.ToImmutableArray();
|
|
||||||
|
|
||||||
Location attributeLocation = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? Location.None;
|
|
||||||
return new[] { new EnumTemplateInfo(enumTypeSymbol, enumValues, attributeLocation) };
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? ExtractSuffix(AttributeData attributeData)
|
|
||||||
{
|
|
||||||
if (attributeData.ConstructorArguments.Length > 0)
|
|
||||||
{
|
|
||||||
return attributeData.ConstructorArguments[0].Value as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, TypedConstant> argument in attributeData.NamedArguments)
|
|
||||||
{
|
|
||||||
if (string.Equals(argument.Key, "suffix", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return argument.Value.Value as string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct GeneratorOptions
|
|
||||||
{
|
|
||||||
private const string GeneratedDirectoryName = "generated";
|
|
||||||
private const string GeneratedRootOptionName = "build_property.DeviceDependentGenerator_OutputRoot";
|
|
||||||
|
|
||||||
private readonly AnalyzerConfigOptions globalOptions;
|
|
||||||
|
|
||||||
public GeneratorOptions(AnalyzerConfigOptions globalOptions)
|
|
||||||
{
|
|
||||||
this.globalOptions = globalOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildHintName(string containingNamespace, string generatedFileName)
|
|
||||||
{
|
|
||||||
string root = GetGeneratedRoot();
|
|
||||||
string trimmedRoot = string.IsNullOrWhiteSpace(root)
|
|
||||||
? GeneratedDirectoryName
|
|
||||||
: root.Replace('\\', '/').Trim('/').Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(trimmedRoot))
|
|
||||||
{
|
|
||||||
trimmedRoot = GeneratedDirectoryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
string namespacePath = string.IsNullOrWhiteSpace(containingNamespace)
|
|
||||||
? string.Empty
|
|
||||||
: containingNamespace.Replace('.', '/').Trim('/');
|
|
||||||
|
|
||||||
if (namespacePath.Length == 0)
|
|
||||||
{
|
|
||||||
return trimmedRoot + "/" + generatedFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return trimmedRoot + "/" + namespacePath + "/" + generatedFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetGeneratedRoot()
|
|
||||||
{
|
|
||||||
if (globalOptions.TryGetValue(GeneratedRootOptionName, out string? configuredRoot)
|
|
||||||
&& !string.IsNullOrWhiteSpace(configuredRoot))
|
|
||||||
{
|
|
||||||
return configuredRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GeneratedDirectoryName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ConditionalDirectiveAndDisabledTextStripper : CSharpSyntaxRewriter
|
|
||||||
{
|
|
||||||
public override SyntaxToken VisitToken(SyntaxToken token)
|
|
||||||
=> token.WithLeadingTrivia(Filter(token.LeadingTrivia)).WithTrailingTrivia(Filter(token.TrailingTrivia));
|
|
||||||
|
|
||||||
private static SyntaxTriviaList Filter(SyntaxTriviaList list)
|
|
||||||
{
|
|
||||||
var builder = ImmutableArray.CreateBuilder<SyntaxTrivia>(list.Count);
|
|
||||||
foreach (SyntaxTrivia trivia in list)
|
|
||||||
{
|
|
||||||
SyntaxKind kind = trivia.Kind();
|
|
||||||
if (kind is SyntaxKind.DisabledTextTrivia
|
|
||||||
or SyntaxKind.IfDirectiveTrivia
|
|
||||||
or SyntaxKind.ElifDirectiveTrivia
|
|
||||||
or SyntaxKind.ElseDirectiveTrivia
|
|
||||||
or SyntaxKind.EndIfDirectiveTrivia)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Add(trivia);
|
|
||||||
}
|
|
||||||
|
|
||||||
return SyntaxFactory.TriviaList(builder.ToImmutable());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
|
||||||
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
|
||||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using ILGPU;
|
|
||||||
using Open.Numeric.Primes;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
|
||||||
|
|
||||||
[ShortRunJob(RuntimeMoniker.Net80)]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class AtomicBenchmarks
|
|
||||||
{
|
|
||||||
private ulong[] _candidates = [];
|
|
||||||
private int[] _candidatesInt = [];
|
|
||||||
|
|
||||||
[ParamsSource(nameof(GetBenchmarkCases))]
|
|
||||||
public PrimeBenchmarkCase BenchmarkCase { get; set; }
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void GlobalSetup()
|
|
||||||
{
|
|
||||||
_candidates = BenchmarkCase.Candidates;
|
|
||||||
_candidatesInt = [.. _candidates.Select(x => (int)x)];
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public int AtomicAddInt()
|
|
||||||
{
|
|
||||||
int[] values = _candidatesInt;
|
|
||||||
int primeCount;
|
|
||||||
int oldValue = 0;
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
primeCount = values[i];
|
|
||||||
oldValue ^= Atomic.Add(ref primeCount, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public int InterlockedIncInt()
|
|
||||||
{
|
|
||||||
int[] values = _candidatesInt;
|
|
||||||
int primeCount;
|
|
||||||
int oldValue = 0;
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
primeCount = values[i];
|
|
||||||
oldValue ^= Interlocked.Increment(ref primeCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public ulong AtomicAddUlong()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
ulong primeCount;
|
|
||||||
ulong oldValue = 0UL;
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
primeCount = values[i];
|
|
||||||
oldValue ^= Atomic.Add(ref primeCount, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public ulong InterlockedIncUlong()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
ulong primeCount;
|
|
||||||
ulong oldValue = 0UL;
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
primeCount = values[i];
|
|
||||||
oldValue ^= Interlocked.Increment(ref primeCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<PrimeBenchmarkCase> GetBenchmarkCases()
|
|
||||||
{
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≤100",
|
|
||||||
"Admissible odd values ≤ 100 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
100UL,
|
|
||||||
int.MaxValue);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≤4_000_000",
|
|
||||||
"Sampled admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥16_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 16,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
16_000_001UL,
|
|
||||||
16_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥32_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 32,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
32_000_001UL,
|
|
||||||
32_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥64_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 64,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
64_000_001UL,
|
|
||||||
64_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥100_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 100,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
100_000_001UL,
|
|
||||||
100_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥138_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 138,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
138_000_001UL,
|
|
||||||
138_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥276_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 276,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
276_000_001UL,
|
|
||||||
276_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥552_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 552,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
552_000_001UL,
|
|
||||||
552_500_000UL,
|
|
||||||
4_096);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly record struct PrimeBenchmarkCase(string Name, string Description, ulong[] Candidates)
|
|
||||||
{
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrimeBenchmarkCase FromRange(
|
|
||||||
string name,
|
|
||||||
string description,
|
|
||||||
ulong startInclusive,
|
|
||||||
ulong endInclusive,
|
|
||||||
int maxCount)
|
|
||||||
{
|
|
||||||
ulong[] values = CollectRange(startInclusive, endInclusive, maxCount);
|
|
||||||
return new PrimeBenchmarkCase(name, description, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong[] CollectRange(ulong startInclusive, ulong endInclusive, int maxCount)
|
|
||||||
{
|
|
||||||
maxCount = maxCount < 0 ? 0 : maxCount;
|
|
||||||
if (maxCount == 0)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
int estimatedLength = (int)Math.Min((ulong)maxCount, endInclusive - startInclusive + 1UL);
|
|
||||||
ulong[] buffer = new ulong[estimatedLength];
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for (ulong value = startInclusive; value <= endInclusive && count < maxCount; value++)
|
|
||||||
{
|
|
||||||
if (!IsAdmissibleMersenneDivisorCandidate(value))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == buffer.Length)
|
|
||||||
{
|
|
||||||
Array.Resize(ref buffer, buffer.Length * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[count++] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == buffer.Length)
|
|
||||||
{
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong[] result = new ulong[count];
|
|
||||||
Array.Copy(buffer, result, count);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsAdmissibleMersenneDivisorCandidate(ulong value)
|
|
||||||
{
|
|
||||||
if ((value & 1UL) == 0UL || value % 5UL == 0UL || value <= 1UL)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong qMinusOne = value - 1UL;
|
|
||||||
|
|
||||||
for (ulong k = 1; k <= 5; k++)
|
|
||||||
{
|
|
||||||
ulong denominator = k << 1;
|
|
||||||
if (qMinusOne % denominator != 0UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong p = qMinusOne / denominator;
|
|
||||||
if (p < 2UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Prime.Numbers.IsPrime(p))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,321 +0,0 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using ILGPU;
|
|
||||||
using Open.Numeric.Primes;
|
|
||||||
using PerfectNumbers.Core;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
|
||||||
|
|
||||||
[ShortRunJob(RuntimeMoniker.Net80)]
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
public class BTreeBenchmarks
|
|
||||||
{
|
|
||||||
private ulong[] _candidates = [];
|
|
||||||
private int[] _candidatesInt = [];
|
|
||||||
|
|
||||||
[ParamsSource(nameof(GetBenchmarkCases))]
|
|
||||||
public PrimeBenchmarkCase BenchmarkCase { get; set; }
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void GlobalSetup()
|
|
||||||
{
|
|
||||||
_candidates = BenchmarkCase.Candidates;
|
|
||||||
_candidatesInt = [.. _candidates.Select(x => (int)x)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds all candidate ints into the keyed B-tree (key = value), using tiny/inline and mid-size fast paths; count comes from the benchmark case or defaults to 4,096 when unspecified.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public BTree<int, int> BTreeAddInt()
|
|
||||||
{
|
|
||||||
int[] values = _candidatesInt;
|
|
||||||
int prime;
|
|
||||||
BTree<int, int> result = new();
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
prime = values[i];
|
|
||||||
result.Add(prime, prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds all candidate ints into a dictionary keyed by their hash code (baseline); count comes from the benchmark case or defaults to 4,096 when unspecified.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public Dictionary<int, int> DictionaryAddInt()
|
|
||||||
{
|
|
||||||
int[] values = _candidatesInt;
|
|
||||||
int prime;
|
|
||||||
Dictionary<int, int> result = [];
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
prime = values[i];
|
|
||||||
result.Add(prime.GetHashCode(), prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds all candidate ulongs into the keyed B-tree (key = value), using tiny/inline and mid-size fast paths; count comes from the benchmark case or defaults to 4,096 when unspecified.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public BTree<ulong, ulong> BTreeAddUlong()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
ulong prime;
|
|
||||||
BTree<ulong, ulong> result = new();
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
prime = values[i];
|
|
||||||
result.Add(prime, prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds all candidate ulongs into a dictionary keyed by their hash code; count comes from the benchmark case or defaults to 4,096 when unspecified.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public Dictionary<int, ulong> DictionaryAddUlong()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
ulong prime;
|
|
||||||
Dictionary<int, ulong> result = [];
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
prime = values[i];
|
|
||||||
result.Add(prime.GetHashCode(), prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<PrimeBenchmarkCase> GetBenchmarkCases()
|
|
||||||
{
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≤100",
|
|
||||||
"Admissible odd values ≤ 100 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
100UL,
|
|
||||||
int.MaxValue);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"4x ≤4_000_000",
|
|
||||||
"4x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
4);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"12x ≤4_000_000",
|
|
||||||
"12x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
12);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"16x ≤4_000_000",
|
|
||||||
"16x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
16);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"32x ≤4_000_000",
|
|
||||||
"16x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
32);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"64x ≤4_000_000",
|
|
||||||
"16x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
64);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"128x ≤4_000_000",
|
|
||||||
"128x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
128);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"256x ≤4_000_000",
|
|
||||||
"256x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
256);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"512x ≤4_000_000",
|
|
||||||
"512x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
512);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"1024x ≤4_000_000",
|
|
||||||
"1024x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
1024);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"2048x ≤4_000_000",
|
|
||||||
"2048x sample admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
2048);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≤4_000_000",
|
|
||||||
"Sampled admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
3UL,
|
|
||||||
4_000_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥16_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 16,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
16_000_001UL,
|
|
||||||
16_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥32_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 32,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
32_000_001UL,
|
|
||||||
32_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥64_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 64,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
64_000_001UL,
|
|
||||||
64_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥100_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 100,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
100_000_001UL,
|
|
||||||
100_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥138_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 138,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
138_000_001UL,
|
|
||||||
138_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥276_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 276,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
276_000_001UL,
|
|
||||||
276_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥552_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 552,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
552_000_001UL,
|
|
||||||
552_500_000UL,
|
|
||||||
4_096);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly record struct PrimeBenchmarkCase(string Name, string Description, ulong[] Candidates)
|
|
||||||
{
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrimeBenchmarkCase FromRange(
|
|
||||||
string name,
|
|
||||||
string description,
|
|
||||||
ulong startInclusive,
|
|
||||||
ulong endInclusive,
|
|
||||||
int maxCount)
|
|
||||||
{
|
|
||||||
ulong[] values = CollectRange(startInclusive, endInclusive, maxCount);
|
|
||||||
return new PrimeBenchmarkCase(name, description, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong[] CollectRange(ulong startInclusive, ulong endInclusive, int maxCount)
|
|
||||||
{
|
|
||||||
maxCount = maxCount < 0 ? 0 : maxCount;
|
|
||||||
if (maxCount == 0)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
int estimatedLength = (int)Math.Min((ulong)maxCount, endInclusive - startInclusive + 1UL);
|
|
||||||
ulong[] buffer = new ulong[estimatedLength];
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for (ulong value = startInclusive; value <= endInclusive && count < maxCount; value++)
|
|
||||||
{
|
|
||||||
if (!IsAdmissibleMersenneDivisorCandidate(value))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == buffer.Length)
|
|
||||||
{
|
|
||||||
Array.Resize(ref buffer, buffer.Length * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[count++] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == buffer.Length)
|
|
||||||
{
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong[] result = new ulong[count];
|
|
||||||
Array.Copy(buffer, result, count);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsAdmissibleMersenneDivisorCandidate(ulong value)
|
|
||||||
{
|
|
||||||
if ((value & 1UL) == 0UL || value % 5UL == 0UL || value <= 1UL)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong qMinusOne = value - 1UL;
|
|
||||||
|
|
||||||
for (ulong k = 1; k <= 5; k++)
|
|
||||||
{
|
|
||||||
ulong denominator = k << 1;
|
|
||||||
if (qMinusOne % denominator != 0UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong p = qMinusOne / denominator;
|
|
||||||
if (p < 2UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Prime.Numbers.IsPrime(p))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class GpuUInt128Montgomery64Benchmarks
|
|||||||
[Benchmark(Baseline = true)]
|
[Benchmark(Baseline = true)]
|
||||||
public ulong ExtensionMontgomeryMultiply()
|
public ulong ExtensionMontgomeryMultiply()
|
||||||
{
|
{
|
||||||
return Input.Left.MontgomeryMultiplyCpu(Input.Right, Input.Modulus, Input.NPrime);
|
return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
public enum ModNumericType
|
public enum ModNumericType
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,6 @@ using BenchmarkDotNet.Attributes;
|
|||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
@@ -10,46 +9,43 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
|||||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||||
public class Pow2MontgomeryModBenchmarks
|
public class Pow2MontgomeryModBenchmarks
|
||||||
{
|
{
|
||||||
private const int SampleCount = 256;
|
private const int SampleCount = 256;
|
||||||
|
|
||||||
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||||
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||||
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
||||||
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
||||||
private readonly ulong[] _smallCycles = new ulong[SampleCount];
|
private readonly ulong[] _smallCycles = new ulong[SampleCount];
|
||||||
private readonly ulong[] _largeCycles = new ulong[SampleCount];
|
private readonly ulong[] _largeCycles = new ulong[SampleCount];
|
||||||
|
|
||||||
private readonly Random _random = new(13);
|
private readonly Random _random = new(13);
|
||||||
|
|
||||||
public enum InputScale
|
public enum InputScale
|
||||||
{
|
{
|
||||||
Small,
|
Small,
|
||||||
Large
|
Large
|
||||||
}
|
}
|
||||||
|
|
||||||
[ParamsAllValues]
|
[ParamsAllValues]
|
||||||
public InputScale Scale { get; set; }
|
public InputScale Scale { get; set; }
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
for (int i = 0; i < SampleCount; i++)
|
||||||
for (int i = 0; i < SampleCount; i++)
|
{
|
||||||
{
|
ulong smallModulus = NextSmallOddModulus();
|
||||||
ulong smallModulus = NextSmallOddModulus();
|
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
_smallExponents[i] = NextSmallExponent();
|
||||||
_smallExponents[i] = NextSmallExponent();
|
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||||
_smallCycles[i] = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
|
||||||
|
|
||||||
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
|
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
|
||||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||||
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
||||||
_largeExponents[i] = NextLargeExponent();
|
_largeExponents[i] = NextLargeExponent();
|
||||||
_largeCycles[i] = largeCycle;
|
_largeCycles[i] = largeCycle;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||||
@@ -62,466 +58,461 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertToStandardCpu(divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
||||||
///
|
///
|
||||||
/// This benchmark failed running, hanging up early before reaching workload testing.
|
/// This benchmark failed running, hanging up early before reaching workload testing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark]
|
// [Benchmark]
|
||||||
public ulong BaselineGpu()
|
public ulong BaselineGpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
for (int i = 0; i < SampleCount; i++)
|
||||||
for (int i = 0; i < SampleCount; i++)
|
{
|
||||||
{
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(gpu, divisors[i]);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
return checksum;
|
||||||
return checksum;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycleCpu()
|
public ulong BaselineWithKnownCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycleGpu()
|
public ulong BaselineWithKnownCycleGpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
for (int i = 0; i < SampleCount; i++)
|
||||||
for (int i = 0; i < SampleCount; i++)
|
{
|
||||||
{
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycles[i], divisors[i]);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
return checksum;
|
||||||
return checksum;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×).
|
/// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong LeftToRight()
|
public ulong LeftToRight()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
|
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×).
|
/// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong Batched4()
|
public ulong Batched4()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
|
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×).
|
/// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong SlidingWindow()
|
public ulong SlidingWindow()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
|
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
|
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BinaryMod()
|
public ulong BinaryMod()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
|
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
|
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong PrecomputedTableMod()
|
public ulong PrecomputedTableMod()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
|
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Relies on <see cref="BigInteger.ModPow"/>; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×).
|
/// Relies on <see cref="BigInteger.ModPow"/>; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BigIntegerMod()
|
public ulong BigIntegerMod()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
|
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||||
{
|
{
|
||||||
if (Scale == InputScale.Small)
|
if (Scale == InputScale.Small)
|
||||||
{
|
{
|
||||||
exponents = _smallExponents;
|
exponents = _smallExponents;
|
||||||
divisors = _smallDivisors;
|
divisors = _smallDivisors;
|
||||||
cycles = _smallCycles;
|
cycles = _smallCycles;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
exponents = _largeExponents;
|
exponents = _largeExponents;
|
||||||
divisors = _largeDivisors;
|
divisors = _largeDivisors;
|
||||||
cycles = _largeCycles;
|
cycles = _largeCycles;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
|
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
|
||||||
|
|
||||||
private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue);
|
private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue);
|
||||||
|
|
||||||
private ulong NextSmallOddModulus()
|
private ulong NextSmallOddModulus()
|
||||||
{
|
{
|
||||||
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
|
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
|
||||||
return value | 1UL;
|
return value | 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
|
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
|
||||||
{
|
{
|
||||||
int bitLength = _random.Next(48, 64);
|
int bitLength = _random.Next(48, 64);
|
||||||
ulong modulus = (1UL << bitLength) - 1UL;
|
ulong modulus = (1UL << bitLength) - 1UL;
|
||||||
return (modulus, (ulong)bitLength);
|
return (modulus, (ulong)bitLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||||
{
|
{
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
|
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||||
|
|
||||||
return new MontgomeryDivisorData(
|
return new MontgomeryDivisorData(
|
||||||
modulus,
|
modulus,
|
||||||
nPrime,
|
nPrime,
|
||||||
montgomeryOne,
|
montgomeryOne,
|
||||||
montgomeryTwo,
|
montgomeryTwo,
|
||||||
montgomeryTwoSquared);
|
montgomeryTwoSquared);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||||
|
|
||||||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||||
{
|
{
|
||||||
ulong inv = modulus;
|
ulong inv = modulus;
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
return unchecked(0UL - inv);
|
return unchecked(0UL - inv);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
|
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong baseVal = divisor.MontgomeryTwo;
|
ulong baseVal = divisor.MontgomeryTwo;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
|
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
{
|
{
|
||||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||||
|
|
||||||
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
|
|
||||||
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
result = result.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong baseVal = divisor.MontgomeryTwo;
|
ulong baseVal = divisor.MontgomeryTwo;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
ulong remainingExponent = exponent;
|
ulong remainingExponent = exponent;
|
||||||
|
|
||||||
while (remainingExponent != 0UL)
|
while (remainingExponent != 0UL)
|
||||||
{
|
{
|
||||||
ulong nibble = remainingExponent & 0xFUL;
|
ulong nibble = remainingExponent & 0xFUL;
|
||||||
|
|
||||||
ulong base0 = baseVal;
|
ulong base0 = baseVal;
|
||||||
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
|
||||||
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
|
||||||
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
|
||||||
|
|
||||||
if ((nibble & 1UL) != 0UL)
|
if ((nibble & 1UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
result = result.MontgomeryMultiply(base0, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((nibble & 2UL) != 0UL)
|
if ((nibble & 2UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
result = result.MontgomeryMultiply(base1, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((nibble & 4UL) != 0UL)
|
if ((nibble & 4UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
result = result.MontgomeryMultiply(base2, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((nibble & 8UL) != 0UL)
|
if ((nibble & 8UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
result = result.MontgomeryMultiply(base3, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime);
|
||||||
remainingExponent >>= 4;
|
remainingExponent >>= 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Update it with the latest implementations
|
// TODO: Update it with the latest implementations
|
||||||
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
|
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong baseVal = divisor.MontgomeryTwo;
|
ulong baseVal = divisor.MontgomeryTwo;
|
||||||
|
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
{
|
{
|
||||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int WindowSize = 5;
|
const int WindowSize = 5;
|
||||||
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
||||||
oddPowers[0] = baseVal;
|
oddPowers[0] = baseVal;
|
||||||
ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
ulong baseSquared = baseVal.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||||
for (int i = 1; i < oddPowers.Length; i++)
|
for (int i = 1; i < oddPowers.Length; i++)
|
||||||
{
|
{
|
||||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
|
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||||
|
|
||||||
while (bitIndex >= 0)
|
while (bitIndex >= 0)
|
||||||
{
|
{
|
||||||
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
bitIndex--;
|
bitIndex--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
|
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
|
||||||
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
|
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
|
||||||
|
|
||||||
while ((windowValue & 1UL) == 0UL)
|
while ((windowValue & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
windowValue >>= 1;
|
windowValue >>= 1;
|
||||||
windowStart++;
|
windowStart++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int squares = bitIndex - windowStart + 1;
|
int squares = bitIndex - windowStart + 1;
|
||||||
for (int i = 0; i < squares; i++)
|
for (int i = 0; i < squares; i++)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||||
bitIndex = windowStart - 1;
|
bitIndex = windowStart - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Update it with the latest implementations, if it exists in production code. Otherwise just remove TODO
|
// TODO: Update it with the latest implementations, if it exists in production code. Otherwise just remove TODO
|
||||||
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
|
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
|
||||||
{
|
{
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong result = 1UL % modulus;
|
ulong result = 1UL % modulus;
|
||||||
ulong baseVal = 2UL % modulus;
|
ulong baseVal = 2UL % modulus;
|
||||||
ulong remainingExponent = exponent;
|
ulong remainingExponent = exponent;
|
||||||
|
|
||||||
while (remainingExponent > 0UL)
|
while (remainingExponent > 0UL)
|
||||||
{
|
{
|
||||||
if ((remainingExponent & 1UL) != 0UL)
|
if ((remainingExponent & 1UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = MultiplyMod(result, baseVal, modulus);
|
result = MultiplyMod(result, baseVal, modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingExponent >>= 1;
|
remainingExponent >>= 1;
|
||||||
if (remainingExponent == 0UL)
|
if (remainingExponent == 0UL)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVal = MultiplyMod(baseVal, baseVal, modulus);
|
baseVal = MultiplyMod(baseVal, baseVal, modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
|
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
|
||||||
{
|
{
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
{
|
{
|
||||||
return 1UL % modulus;
|
return 1UL % modulus;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<ulong> powers = stackalloc ulong[64];
|
Span<ulong> powers = stackalloc ulong[64];
|
||||||
powers[0] = 2UL % modulus;
|
powers[0] = 2UL % modulus;
|
||||||
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
|
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||||
|
|
||||||
for (int index = 1; index <= highestBit; index++)
|
for (int index = 1; index <= highestBit; index++)
|
||||||
{
|
{
|
||||||
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
|
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong result = 1UL % modulus;
|
ulong result = 1UL % modulus;
|
||||||
ulong remainingExponent = exponent;
|
ulong remainingExponent = exponent;
|
||||||
int bitIndex = 0;
|
int bitIndex = 0;
|
||||||
|
|
||||||
while (remainingExponent > 0UL)
|
while (remainingExponent > 0UL)
|
||||||
{
|
{
|
||||||
if ((remainingExponent & 1UL) != 0UL)
|
if ((remainingExponent & 1UL) != 0UL)
|
||||||
{
|
{
|
||||||
result = MultiplyMod(result, powers[bitIndex], modulus);
|
result = MultiplyMod(result, powers[bitIndex], modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingExponent >>= 1;
|
remainingExponent >>= 1;
|
||||||
bitIndex++;
|
bitIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
|
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
|
||||||
{
|
{
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
BigInteger mod = modulus;
|
BigInteger mod = modulus;
|
||||||
BigInteger result = BigInteger.ModPow(2, exponent, mod);
|
BigInteger result = BigInteger.ModPow(2, exponent, mod);
|
||||||
return (ulong)result;
|
return (ulong)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
|
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
|
||||||
{
|
{
|
||||||
UInt128 product = (UInt128)x * y;
|
UInt128 product = (UInt128)x * y;
|
||||||
return (ulong)(product % modulus);
|
return (ulong)(product % modulus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using System;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
@@ -61,13 +61,12 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
|||||||
};
|
};
|
||||||
|
|
||||||
var cases = new Pow2MontgomeryModWindowedBenchmarkCase[seeds.Length];
|
var cases = new Pow2MontgomeryModWindowedBenchmarkCase[seeds.Length];
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
for (int i = 0; i < seeds.Length; i++)
|
for (int i = 0; i < seeds.Length; i++)
|
||||||
{
|
{
|
||||||
Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i];
|
Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i];
|
||||||
ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL;
|
ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL;
|
||||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||||
ulong cycleLength = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, modulus, divisor);
|
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(modulus, divisor);
|
||||||
ulong reducedExponent = seed.Exponent % cycleLength;
|
ulong reducedExponent = seed.Exponent % cycleLength;
|
||||||
cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase(
|
cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase(
|
||||||
seed.Exponent,
|
seed.Exponent,
|
||||||
@@ -77,7 +76,6 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
|||||||
reducedExponent);
|
reducedExponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
return new Pow2MontgomeryModWindowedBenchmarkInputs(cases);
|
return new Pow2MontgomeryModWindowedBenchmarkInputs(cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,4 +92,3 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
|||||||
public ulong Multiplier { get; }
|
public ulong Multiplier { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+3
-3
@@ -32,7 +32,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
|||||||
for (int i = 0; i < cases.Length; i++)
|
for (int i = 0; i < cases.Length; i++)
|
||||||
{
|
{
|
||||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(current.Divisor);
|
checksum ^= current.Exponent.Pow2MontgomeryModWindowedCpu(current.Divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -50,7 +50,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
|||||||
for (int i = 0; i < cases.Length; i++)
|
for (int i = 0; i < cases.Length; i++)
|
||||||
{
|
{
|
||||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepMontgomeryCpu(current.Divisor);
|
checksum ^= current.Exponent.Pow2MontgomeryModWindowedCpu(current.Divisor, keepMontgomery: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -68,7 +68,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
|||||||
for (int i = 0; i < cases.Length; i++)
|
for (int i = 0; i < cases.Length; i++)
|
||||||
{
|
{
|
||||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||||
checksum ^= current.Exponent.Pow2MontgomeryModWithCycleConvertToStandardCpu(current.CycleLength, current.Divisor);
|
checksum ^= current.Exponent.Pow2MontgomeryModWithCycleCpu(current.CycleLength, current.Divisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
|
|||||||
+3
-8
@@ -1,8 +1,7 @@
|
|||||||
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using PerfectNumbers.Core.Gpu.Kernels;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
@@ -32,14 +31,12 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
|
|||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
|
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
for (int i = 0; i < cases.Length; i++)
|
for (int i = 0; i < cases.Length; i++)
|
||||||
{
|
{
|
||||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(gpu, current.Divisor);
|
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +49,12 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
|
|||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
|
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
for (int i = 0; i < cases.Length; i++)
|
for (int i = 0; i < cases.Length; i++)
|
||||||
{
|
{
|
||||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(gpu, current.Divisor);
|
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,415 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using PerfectNumbers.Core;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks
|
|
||||||
{
|
|
||||||
public class Pow2ProviderBigIntegerBenchmarks
|
|
||||||
{
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public BigInteger ByShifting()
|
|
||||||
{
|
|
||||||
BigInteger result = BigInteger.Zero;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = BigInteger.One << i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public BigInteger ByMapping()
|
|
||||||
{
|
|
||||||
BigInteger result = BigInteger.Zero;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = Pow2Provider.BigInteger(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Pow2MinusOneeProviderBigIntegerBenchmarks
|
|
||||||
{
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public BigInteger ByShifting()
|
|
||||||
{
|
|
||||||
BigInteger result = BigInteger.Zero;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = (BigInteger.One << i) - BigInteger.One;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public BigInteger ByMapping()
|
|
||||||
{
|
|
||||||
BigInteger result = BigInteger.Zero;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = Pow2Provider.BigIntegerMinusOne(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Pow2ProviderULongBenchmarks
|
|
||||||
{
|
|
||||||
public static class ULongNumbers
|
|
||||||
{
|
|
||||||
public const ulong TwoMinusOne = 2L - 1L;
|
|
||||||
public const ulong FourMinusOne = 4L - 1L;
|
|
||||||
public const ulong EightMinusOne = 8L - 1L;
|
|
||||||
public const ulong SixteenMinusOne = 16L - 1L;
|
|
||||||
public const ulong ThirtyTwoMinusOne = 32L - 1L;
|
|
||||||
public const ulong SixtyFourMinusOne = 64L - 1L;
|
|
||||||
public const ulong OneHundredTwentyEightMinusOne = 128L - 1L;
|
|
||||||
public const ulong TwoHundredFiftySixMinusOne = 256L - 1L;
|
|
||||||
public const ulong FiveHundredTwelveMinusOne = 512L - 1L;
|
|
||||||
public const ulong OneThousandTwentyFourMinusOne = 1_024L - 1L;
|
|
||||||
public const ulong TwoThousandFortyEightMinusOne = 2_048L - 1L;
|
|
||||||
public const ulong FourThousandNinetySixMinusOne = 4_096L - 1L;
|
|
||||||
public const ulong EightThousandOneHundredNinetyTwoMinusOne = 8_192L - 1L;
|
|
||||||
public const ulong SixteenThousandThreeHundredEightyFourMinusOne = 16_384L - 1L;
|
|
||||||
public const ulong ThirtyTwoThousandSevenHundredSixtyEightMinusOne = 32_768L - 1L;
|
|
||||||
public const ulong SixtyFiveThousandFiveHundredThirtySixMinusOne = 65_536L - 1L;
|
|
||||||
public const ulong OneHundredThirtyOneThousandSeventyTwoMinusOne = 131_072L - 1L;
|
|
||||||
public const ulong TwoHundredSixtyTwoThousandOneHundredFortyFourMinusOne = 262_144L - 1L;
|
|
||||||
public const ulong FiveHundredTwentyFourThousandTwoHundredEightyEightMinusOne = 524_288L - 1L;
|
|
||||||
public const ulong OneMillionFortyEightThousandFiveHundredSeventySixMinusOne = 1_048_576L - 1L;
|
|
||||||
public const ulong TwoMillionNinetySevenThousandOneHundredFiftyTwoMinusOne = 2_097_152L - 1L;
|
|
||||||
public const ulong FourMillionOneHundredNinetyFourThousandThreeHundredFourMinusOne = 4_194_304L - 1L;
|
|
||||||
public const ulong EightMillionThreeHundredEightyEightThousandSixHundredEightMinusOne = 8_388_608L - 1L;
|
|
||||||
public const ulong SixteenMillionSevenHundredSeventySevenThousandTwoHundredSixteenMinusOne = 16_777_216L - 1L;
|
|
||||||
public const ulong ThirtyThreeMillionFiveHundredFiftyFourThousandFourHundredThirtyTwoMinusOne = 33_554_432L - 1L;
|
|
||||||
public const ulong SixtySevenMillionOneHundredEightThousandEightHundredSixtyFourMinusOne = 67_108_864L - 1L;
|
|
||||||
public const ulong OneHundredThirtyFourMillionTwoHundredSeventeenThousandSevenHundredTwentyEightMinusOne = 134_217_728L - 1L;
|
|
||||||
public const ulong TwoHundredSixtyEightMillionFourHundredThirtyFiveThousandFourHundredFiftySixMinusOne = 268_435_456L - 1L;
|
|
||||||
public const ulong FiveHundredThirtySixMillionEightHundredSeventyThousandNineHundredTwelveMinusOne = 536_870_912L - 1L;
|
|
||||||
public const ulong OneBillionSeventyThreeMillionSevenHundredFortyOneThousandEightHundredTwentyFourMinusOne = 1_073_741_824L - 1L;
|
|
||||||
public const ulong TwoBillionOneHundredFortySevenMillionFourHundredEightyThreeThousandSixHundredFortyEightMinusOne = 2_147_483_648L - 1L;
|
|
||||||
public const ulong FourBillionTwoHundredNinetyFourMillionNineHundredSixtySevenThousandTwoHundredNinetySixMinusOne = 4_294_967_296L - 1L;
|
|
||||||
public const ulong EightBillionFiveHundredEightyNineMillionNineHundredThirtyFourThousandFiveHundredNinetyTwoMinusOne = 8_589_934_592L - 1L;
|
|
||||||
public const ulong SeventeenBillionOneHundredSeventyNineMillionEightHundredSixtyNineThousandOneHundredEightyFourMinusOne = 17_179_869_184L - 1L;
|
|
||||||
public const ulong ThirtyFourBillionThreeHundredFiftyNineMillionSevenHundredThirtyEightThousandThreeHundredSixtyEightMinusOne = 34_359_738_368L - 1L;
|
|
||||||
public const ulong SixtyEightBillionSevenHundredNineteenMillionFourHundredSeventySixThousandSevenHundredThirtySixMinusOne = 68_719_476_736L - 1L;
|
|
||||||
public const ulong OneHundredThirtySevenBillionFourHundredThirtyEightMillionNineHundredFiftyThreeThousandFourHundredSeventyTwoMinusOne = 137_438_953_472L - 1L;
|
|
||||||
public const ulong TwoHundredSeventyFourBillionEightHundredSeventySevenMillionNineHundredSixThousandNineHundredFortyFourMinusOne = 274_877_906_944L - 1L;
|
|
||||||
public const ulong FiveHundredFortyNineBillionSevenHundredFiftyFiveMillionEightHundredThirteenThousandEightHundredEightyEightMinusOne = 549_755_813_888L - 1L;
|
|
||||||
public const ulong OneTrillionNinetyNineBillionFiveHundredElevenMillionSixHundredTwentySevenThousandSevenHundredSeventySixMinusOne = 1_099_511_627_776L - 1L;
|
|
||||||
public const ulong TwoTrillionOneHundredNinetyNineBillionTwentyThreeMillionTwoHundredFiftyFiveThousandFiveHundredFiftyTwoMinusOne = 2_199_023_255_552L - 1L;
|
|
||||||
public const ulong FourTrillionThreeHundredNinetyEightBillionFortySixMillionFiveHundredElevenThousandOneHundredFourMinusOne = 4_398_046_511_104L - 1L;
|
|
||||||
public const ulong EightTrillionSevenHundredNinetySixBillionNinetyThreeMillionTwentyTwoThousandTwoHundredEightMinusOne = 8_796_093_022_208L - 1L;
|
|
||||||
public const ulong SeventeenTrillionFiveHundredNinetyTwoBillionOneHundredEightySixMillionFortyFourThousandFourHundredSixteenMinusOne = 17_592_186_044_416L - 1L;
|
|
||||||
public const ulong ThirtyFiveTrillionOneHundredEightyFourBillionThreeHundredSeventyTwoMillionEightyEightThousandEightHundredThirtyTwoMinusOne = 35_184_372_088_832L - 1L;
|
|
||||||
public const ulong SeventyTrillionThreeHundredSixtyEightBillionSevenHundredFortyFourMillionOneHundredSeventySevenThousandSixHundredSixtyFourMinusOne = 70_368_744_177_664L - 1L;
|
|
||||||
public const ulong OneHundredFortyTrillionSevenHundredThirtySevenBillionFourHundredEightyEightMillionThreeHundredFiftyFiveThousandThreeHundredTwentyEightMinusOne = 140_737_488_355_328L - 1L;
|
|
||||||
public const ulong TwoHundredEightyOneTrillionFourHundredSeventyFourBillionNineHundredSeventySixMillionSevenHundredTenThousandSixHundredFiftySixMinusOne = 281_474_976_710_656L - 1L;
|
|
||||||
public const ulong FiveHundredSixtyTwoTrillionNineHundredFortyNineBillionNineHundredFiftyThreeMillionFourHundredTwentyOneThousandThreeHundredTwelveMinusOne = 562_949_953_421_312L - 1L;
|
|
||||||
public const ulong OneQuadrillionOneHundredTwentyFiveTrillionEightHundredNinetyNineBillionNineHundredSixMillionEightHundredFortyTwoThousandSixHundredTwentyFourMinusOne = 1_125_899_906_842_624L - 1L;
|
|
||||||
public const ulong TwoQuadrillionTwoHundredFiftyOneTrillionSevenHundredNinetyNineBillionEightHundredThirteenMillionSixHundredEightyFiveThousandTwoHundredFortyEightMinusOne = 2_251_799_813_685_248L - 1L;
|
|
||||||
public const ulong FourQuadrillionFiveHundredThreeTrillionFiveHundredNinetyNineBillionSixHundredTwentySevenMillionThreeHundredSeventyThousandFourHundredNinetySixMinusOne = 4_503_599_627_370_496L - 1L;
|
|
||||||
public const ulong NineQuadrillionSevenTrillionOneHundredNinetyNineBillionTwoHundredFiftyFourMillionSevenHundredFortyThousandNineHundredNinetyTwoMinusOne = 9_007_199_254_740_992L - 1L;
|
|
||||||
public const ulong EighteenQuadrillionFourteenTrillionThreeHundredNinetyEightBillionFiveHundredEightMillionEightHundredEightyThousandOneHundredEightyFourMinusOne = 18_014_398_509_481_984L - 1L;
|
|
||||||
public const ulong ThirtySixQuadrillionTwentyEightTrillionSevenHundredNinetySevenBillionSeventeenMillionSevenHundredSixtyThousandThreeHundredSixtyEightMinusOne = 36_028_797_018_963_968L - 1L;
|
|
||||||
public const ulong SeventyTwoQuadrillionFiftySevenTrillionFiveHundredNinetyFourBillionThirtyFiveMillionFiveHundredTwentyThousandSevenHundredThirtySixMinusOne = 72_057_594_037_927_936L - 1L;
|
|
||||||
public const ulong OneHundredFortyFourQuadrillionOneHundredFifteenTrillionOneHundredEightyEightBillionSeventyOneMillionFortyOneThousandFourHundredSeventyTwoMinusOne = 144_115_188_075_855_872L - 1L;
|
|
||||||
public const ulong TwoHundredEightyEightQuadrillionTwoHundredThirtyTrillionThreeHundredSeventySixBillionOneHundredFortyTwoMillionEightyTwoThousandNineHundredFortyFourMinusOne = 288_230_376_151_711_744L - 1L;
|
|
||||||
public const ulong FiveHundredSeventySixQuadrillionFourHundredSixtyTrillionSevenHundredFiftyTwoBillionTwoHundredEightyFourMillionOneHundredSixtyFiveThousandEightHundredEightyEightMinusOne = 576_460_752_303_423_488L - 1L;
|
|
||||||
public const ulong OneQuintillionOneHundredFiftyTwoQuadrillionNineHundredTwentyOneTrillionFiveHundredFourBillionSixHundredSixtyEightMillionThreeHundredThirtyOneThousandSevenHundredSeventySixMinusOne = 1_152_921_504_606_846_976L - 1L;
|
|
||||||
public const ulong TwoQuintillionThreeHundredFiveQuadrillionEightHundredFortyThreeTrillionNineBillionThreeHundredThirtySixMillionSixHundredSixtyThreeThousandFiveHundredFiftyTwoMinusOne = 2_305_843_009_213_693_952L - 1L;
|
|
||||||
public const ulong FourQuintillionSixHundredElevenQuadrillionSixHundredEightySevenTrillionEightHundredSixteenBillionSixHundredSeventyThreeMillionTwoHundredTwentySevenThousandOneHundredFourMinusOne = 4_611_686_018_427_387_904L - 1L;
|
|
||||||
public const ulong NineQuintillionTwoHundredTwentyThreeQuadrillionThreeHundredSeventyTwoTrillionThirtySixBillionEightHundredFiftyFourMillionSevenHundredSeventyFiveThousandEightHundredEightMinusOne = 9_223_372_036_854_775_808UL - 1UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public static ulong ULongMinusOne(int i) => i switch
|
|
||||||
{
|
|
||||||
0 => 0UL,
|
|
||||||
1 => ULongNumbers.TwoMinusOne,
|
|
||||||
2 => ULongNumbers.FourMinusOne,
|
|
||||||
3 => ULongNumbers.EightMinusOne,
|
|
||||||
4 => ULongNumbers.SixteenMinusOne,
|
|
||||||
5 => ULongNumbers.ThirtyTwoMinusOne,
|
|
||||||
6 => ULongNumbers.SixtyFourMinusOne,
|
|
||||||
7 => ULongNumbers.OneHundredTwentyEightMinusOne,
|
|
||||||
8 => ULongNumbers.TwoHundredFiftySixMinusOne,
|
|
||||||
9 => ULongNumbers.FiveHundredTwelveMinusOne,
|
|
||||||
10 => ULongNumbers.OneThousandTwentyFourMinusOne,
|
|
||||||
11 => ULongNumbers.TwoThousandFortyEightMinusOne,
|
|
||||||
12 => ULongNumbers.FourThousandNinetySixMinusOne,
|
|
||||||
13 => ULongNumbers.EightThousandOneHundredNinetyTwoMinusOne,
|
|
||||||
14 => ULongNumbers.SixteenThousandThreeHundredEightyFourMinusOne,
|
|
||||||
15 => ULongNumbers.ThirtyTwoThousandSevenHundredSixtyEightMinusOne,
|
|
||||||
16 => ULongNumbers.SixtyFiveThousandFiveHundredThirtySixMinusOne,
|
|
||||||
17 => ULongNumbers.OneHundredThirtyOneThousandSeventyTwoMinusOne,
|
|
||||||
18 => ULongNumbers.TwoHundredSixtyTwoThousandOneHundredFortyFourMinusOne,
|
|
||||||
19 => ULongNumbers.FiveHundredTwentyFourThousandTwoHundredEightyEightMinusOne,
|
|
||||||
20 => ULongNumbers.OneMillionFortyEightThousandFiveHundredSeventySixMinusOne,
|
|
||||||
21 => ULongNumbers.TwoMillionNinetySevenThousandOneHundredFiftyTwoMinusOne,
|
|
||||||
22 => ULongNumbers.FourMillionOneHundredNinetyFourThousandThreeHundredFourMinusOne,
|
|
||||||
23 => ULongNumbers.EightMillionThreeHundredEightyEightThousandSixHundredEightMinusOne,
|
|
||||||
24 => ULongNumbers.SixteenMillionSevenHundredSeventySevenThousandTwoHundredSixteenMinusOne,
|
|
||||||
25 => ULongNumbers.ThirtyThreeMillionFiveHundredFiftyFourThousandFourHundredThirtyTwoMinusOne,
|
|
||||||
26 => ULongNumbers.SixtySevenMillionOneHundredEightThousandEightHundredSixtyFourMinusOne,
|
|
||||||
27 => ULongNumbers.OneHundredThirtyFourMillionTwoHundredSeventeenThousandSevenHundredTwentyEightMinusOne,
|
|
||||||
28 => ULongNumbers.TwoHundredSixtyEightMillionFourHundredThirtyFiveThousandFourHundredFiftySixMinusOne,
|
|
||||||
29 => ULongNumbers.FiveHundredThirtySixMillionEightHundredSeventyThousandNineHundredTwelveMinusOne,
|
|
||||||
30 => ULongNumbers.OneBillionSeventyThreeMillionSevenHundredFortyOneThousandEightHundredTwentyFourMinusOne,
|
|
||||||
31 => ULongNumbers.TwoBillionOneHundredFortySevenMillionFourHundredEightyThreeThousandSixHundredFortyEightMinusOne,
|
|
||||||
32 => ULongNumbers.FourBillionTwoHundredNinetyFourMillionNineHundredSixtySevenThousandTwoHundredNinetySixMinusOne,
|
|
||||||
33 => ULongNumbers.EightBillionFiveHundredEightyNineMillionNineHundredThirtyFourThousandFiveHundredNinetyTwoMinusOne,
|
|
||||||
34 => ULongNumbers.SeventeenBillionOneHundredSeventyNineMillionEightHundredSixtyNineThousandOneHundredEightyFourMinusOne,
|
|
||||||
35 => ULongNumbers.ThirtyFourBillionThreeHundredFiftyNineMillionSevenHundredThirtyEightThousandThreeHundredSixtyEightMinusOne,
|
|
||||||
36 => ULongNumbers.SixtyEightBillionSevenHundredNineteenMillionFourHundredSeventySixThousandSevenHundredThirtySixMinusOne,
|
|
||||||
37 => ULongNumbers.OneHundredThirtySevenBillionFourHundredThirtyEightMillionNineHundredFiftyThreeThousandFourHundredSeventyTwoMinusOne,
|
|
||||||
38 => ULongNumbers.TwoHundredSeventyFourBillionEightHundredSeventySevenMillionNineHundredSixThousandNineHundredFortyFourMinusOne,
|
|
||||||
39 => ULongNumbers.FiveHundredFortyNineBillionSevenHundredFiftyFiveMillionEightHundredThirteenThousandEightHundredEightyEightMinusOne,
|
|
||||||
40 => ULongNumbers.OneTrillionNinetyNineBillionFiveHundredElevenMillionSixHundredTwentySevenThousandSevenHundredSeventySixMinusOne,
|
|
||||||
41 => ULongNumbers.TwoTrillionOneHundredNinetyNineBillionTwentyThreeMillionTwoHundredFiftyFiveThousandFiveHundredFiftyTwoMinusOne,
|
|
||||||
42 => ULongNumbers.FourTrillionThreeHundredNinetyEightBillionFortySixMillionFiveHundredElevenThousandOneHundredFourMinusOne,
|
|
||||||
43 => ULongNumbers.EightTrillionSevenHundredNinetySixBillionNinetyThreeMillionTwentyTwoThousandTwoHundredEightMinusOne,
|
|
||||||
44 => ULongNumbers.SeventeenTrillionFiveHundredNinetyTwoBillionOneHundredEightySixMillionFortyFourThousandFourHundredSixteenMinusOne,
|
|
||||||
45 => ULongNumbers.ThirtyFiveTrillionOneHundredEightyFourBillionThreeHundredSeventyTwoMillionEightyEightThousandEightHundredThirtyTwoMinusOne,
|
|
||||||
46 => ULongNumbers.SeventyTrillionThreeHundredSixtyEightBillionSevenHundredFortyFourMillionOneHundredSeventySevenThousandSixHundredSixtyFourMinusOne,
|
|
||||||
47 => ULongNumbers.OneHundredFortyTrillionSevenHundredThirtySevenBillionFourHundredEightyEightMillionThreeHundredFiftyFiveThousandThreeHundredTwentyEightMinusOne,
|
|
||||||
48 => ULongNumbers.TwoHundredEightyOneTrillionFourHundredSeventyFourBillionNineHundredSeventySixMillionSevenHundredTenThousandSixHundredFiftySixMinusOne,
|
|
||||||
49 => ULongNumbers.FiveHundredSixtyTwoTrillionNineHundredFortyNineBillionNineHundredFiftyThreeMillionFourHundredTwentyOneThousandThreeHundredTwelveMinusOne,
|
|
||||||
50 => ULongNumbers.OneQuadrillionOneHundredTwentyFiveTrillionEightHundredNinetyNineBillionNineHundredSixMillionEightHundredFortyTwoThousandSixHundredTwentyFourMinusOne,
|
|
||||||
51 => ULongNumbers.TwoQuadrillionTwoHundredFiftyOneTrillionSevenHundredNinetyNineBillionEightHundredThirteenMillionSixHundredEightyFiveThousandTwoHundredFortyEightMinusOne,
|
|
||||||
52 => ULongNumbers.FourQuadrillionFiveHundredThreeTrillionFiveHundredNinetyNineBillionSixHundredTwentySevenMillionThreeHundredSeventyThousandFourHundredNinetySixMinusOne,
|
|
||||||
53 => ULongNumbers.NineQuadrillionSevenTrillionOneHundredNinetyNineBillionTwoHundredFiftyFourMillionSevenHundredFortyThousandNineHundredNinetyTwoMinusOne,
|
|
||||||
54 => ULongNumbers.EighteenQuadrillionFourteenTrillionThreeHundredNinetyEightBillionFiveHundredEightMillionEightHundredEightyThousandOneHundredEightyFourMinusOne,
|
|
||||||
55 => ULongNumbers.ThirtySixQuadrillionTwentyEightTrillionSevenHundredNinetySevenBillionSeventeenMillionSevenHundredSixtyThousandThreeHundredSixtyEightMinusOne,
|
|
||||||
56 => ULongNumbers.SeventyTwoQuadrillionFiftySevenTrillionFiveHundredNinetyFourBillionThirtyFiveMillionFiveHundredTwentyThousandSevenHundredThirtySixMinusOne,
|
|
||||||
57 => ULongNumbers.OneHundredFortyFourQuadrillionOneHundredFifteenTrillionOneHundredEightyEightBillionSeventyOneMillionFortyOneThousandFourHundredSeventyTwoMinusOne,
|
|
||||||
58 => ULongNumbers.TwoHundredEightyEightQuadrillionTwoHundredThirtyTrillionThreeHundredSeventySixBillionOneHundredFortyTwoMillionEightyTwoThousandNineHundredFortyFourMinusOne,
|
|
||||||
59 => ULongNumbers.FiveHundredSeventySixQuadrillionFourHundredSixtyTrillionSevenHundredFiftyTwoBillionTwoHundredEightyFourMillionOneHundredSixtyFiveThousandEightHundredEightyEightMinusOne,
|
|
||||||
60 => ULongNumbers.OneQuintillionOneHundredFiftyTwoQuadrillionNineHundredTwentyOneTrillionFiveHundredFourBillionSixHundredSixtyEightMillionThreeHundredThirtyOneThousandSevenHundredSeventySixMinusOne,
|
|
||||||
61 => ULongNumbers.TwoQuintillionThreeHundredFiveQuadrillionEightHundredFortyThreeTrillionNineBillionThreeHundredThirtySixMillionSixHundredSixtyThreeThousandFiveHundredFiftyTwoMinusOne,
|
|
||||||
62 => ULongNumbers.FourQuintillionSixHundredElevenQuadrillionSixHundredEightySevenTrillionEightHundredSixteenBillionSixHundredSeventyThreeMillionTwoHundredTwentySevenThousandOneHundredFourMinusOne,
|
|
||||||
63 => ULongNumbers.NineQuintillionTwoHundredTwentyThreeQuadrillionThreeHundredSeventyTwoTrillionThirtySixBillionEightHundredFiftyFourMillionSevenHundredSeventyFiveThousandEightHundredEightMinusOne,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(i)),
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly ulong[] BitMask64 = CreateBitMask64();
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong[] CreateBitMask64()
|
|
||||||
{
|
|
||||||
ulong[] masks = new ulong[64];
|
|
||||||
ulong value = 1UL;
|
|
||||||
for (int i = 0; i < 64; i++)
|
|
||||||
{
|
|
||||||
masks[i] = value;
|
|
||||||
value <<= 1;
|
|
||||||
}
|
|
||||||
return masks;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public ulong ByShifting()
|
|
||||||
{
|
|
||||||
ulong result = 0UL;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = (1UL << i) - 1UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public ulong ByMapping()
|
|
||||||
{
|
|
||||||
ulong result = 0UL;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = ULongMinusOne(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public ulong StaticArray()
|
|
||||||
{
|
|
||||||
ulong result = 0UL;
|
|
||||||
ulong[] bitMask64 = BitMask64;
|
|
||||||
for (int i = 0; i <= 63; i++)
|
|
||||||
{
|
|
||||||
result = bitMask64[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Pow2ProviderLongBenchmarks
|
|
||||||
{
|
|
||||||
public static class LongNumbers
|
|
||||||
{
|
|
||||||
public const long TwoMinusOne = 2L - 1L;
|
|
||||||
public const long FourMinusOne = 4L - 1L;
|
|
||||||
public const long EightMinusOne = 8L - 1L;
|
|
||||||
public const long SixteenMinusOne = 16L - 1L;
|
|
||||||
public const long ThirtyTwoMinusOne = 32L - 1L;
|
|
||||||
public const long SixtyFourMinusOne = 64L - 1L;
|
|
||||||
public const long OneHundredTwentyEightMinusOne = 128L - 1L;
|
|
||||||
public const long TwoHundredFiftySixMinusOne = 256L - 1L;
|
|
||||||
public const long FiveHundredTwelveMinusOne = 512L - 1L;
|
|
||||||
public const long OneThousandTwentyFourMinusOne = 1_024L - 1L;
|
|
||||||
public const long TwoThousandFortyEightMinusOne = 2_048L - 1L;
|
|
||||||
public const long FourThousandNinetySixMinusOne = 4_096L - 1L;
|
|
||||||
public const long EightThousandOneHundredNinetyTwoMinusOne = 8_192L - 1L;
|
|
||||||
public const long SixteenThousandThreeHundredEightyFourMinusOne = 16_384L - 1L;
|
|
||||||
public const long ThirtyTwoThousandSevenHundredSixtyEightMinusOne = 32_768L - 1L;
|
|
||||||
public const long SixtyFiveThousandFiveHundredThirtySixMinusOne = 65_536L - 1L;
|
|
||||||
public const long OneHundredThirtyOneThousandSeventyTwoMinusOne = 131_072L - 1L;
|
|
||||||
public const long TwoHundredSixtyTwoThousandOneHundredFortyFourMinusOne = 262_144L - 1L;
|
|
||||||
public const long FiveHundredTwentyFourThousandTwoHundredEightyEightMinusOne = 524_288L - 1L;
|
|
||||||
public const long OneMillionFortyEightThousandFiveHundredSeventySixMinusOne = 1_048_576L - 1L;
|
|
||||||
public const long TwoMillionNinetySevenThousandOneHundredFiftyTwoMinusOne = 2_097_152L - 1L;
|
|
||||||
public const long FourMillionOneHundredNinetyFourThousandThreeHundredFourMinusOne = 4_194_304L - 1L;
|
|
||||||
public const long EightMillionThreeHundredEightyEightThousandSixHundredEightMinusOne = 8_388_608L - 1L;
|
|
||||||
public const long SixteenMillionSevenHundredSeventySevenThousandTwoHundredSixteenMinusOne = 16_777_216L - 1L;
|
|
||||||
public const long ThirtyThreeMillionFiveHundredFiftyFourThousandFourHundredThirtyTwoMinusOne = 33_554_432L - 1L;
|
|
||||||
public const long SixtySevenMillionOneHundredEightThousandEightHundredSixtyFourMinusOne = 67_108_864L - 1L;
|
|
||||||
public const long OneHundredThirtyFourMillionTwoHundredSeventeenThousandSevenHundredTwentyEightMinusOne = 134_217_728L - 1L;
|
|
||||||
public const long TwoHundredSixtyEightMillionFourHundredThirtyFiveThousandFourHundredFiftySixMinusOne = 268_435_456L - 1L;
|
|
||||||
public const long FiveHundredThirtySixMillionEightHundredSeventyThousandNineHundredTwelveMinusOne = 536_870_912L - 1L;
|
|
||||||
public const long OneBillionSeventyThreeMillionSevenHundredFortyOneThousandEightHundredTwentyFourMinusOne = 1_073_741_824L - 1L;
|
|
||||||
public const long TwoBillionOneHundredFortySevenMillionFourHundredEightyThreeThousandSixHundredFortyEightMinusOne = 2_147_483_648L - 1L;
|
|
||||||
public const long FourBillionTwoHundredNinetyFourMillionNineHundredSixtySevenThousandTwoHundredNinetySixMinusOne = 4_294_967_296L - 1L;
|
|
||||||
public const long EightBillionFiveHundredEightyNineMillionNineHundredThirtyFourThousandFiveHundredNinetyTwoMinusOne = 8_589_934_592L - 1L;
|
|
||||||
public const long SeventeenBillionOneHundredSeventyNineMillionEightHundredSixtyNineThousandOneHundredEightyFourMinusOne = 17_179_869_184L - 1L;
|
|
||||||
public const long ThirtyFourBillionThreeHundredFiftyNineMillionSevenHundredThirtyEightThousandThreeHundredSixtyEightMinusOne = 34_359_738_368L - 1L;
|
|
||||||
public const long SixtyEightBillionSevenHundredNineteenMillionFourHundredSeventySixThousandSevenHundredThirtySixMinusOne = 68_719_476_736L - 1L;
|
|
||||||
public const long OneHundredThirtySevenBillionFourHundredThirtyEightMillionNineHundredFiftyThreeThousandFourHundredSeventyTwoMinusOne = 137_438_953_472L - 1L;
|
|
||||||
public const long TwoHundredSeventyFourBillionEightHundredSeventySevenMillionNineHundredSixThousandNineHundredFortyFourMinusOne = 274_877_906_944L - 1L;
|
|
||||||
public const long FiveHundredFortyNineBillionSevenHundredFiftyFiveMillionEightHundredThirteenThousandEightHundredEightyEightMinusOne = 549_755_813_888L - 1L;
|
|
||||||
public const long OneTrillionNinetyNineBillionFiveHundredElevenMillionSixHundredTwentySevenThousandSevenHundredSeventySixMinusOne = 1_099_511_627_776L - 1L;
|
|
||||||
public const long TwoTrillionOneHundredNinetyNineBillionTwentyThreeMillionTwoHundredFiftyFiveThousandFiveHundredFiftyTwoMinusOne = 2_199_023_255_552L - 1L;
|
|
||||||
public const long FourTrillionThreeHundredNinetyEightBillionFortySixMillionFiveHundredElevenThousandOneHundredFourMinusOne = 4_398_046_511_104L - 1L;
|
|
||||||
public const long EightTrillionSevenHundredNinetySixBillionNinetyThreeMillionTwentyTwoThousandTwoHundredEightMinusOne = 8_796_093_022_208L - 1L;
|
|
||||||
public const long SeventeenTrillionFiveHundredNinetyTwoBillionOneHundredEightySixMillionFortyFourThousandFourHundredSixteenMinusOne = 17_592_186_044_416L - 1L;
|
|
||||||
public const long ThirtyFiveTrillionOneHundredEightyFourBillionThreeHundredSeventyTwoMillionEightyEightThousandEightHundredThirtyTwoMinusOne = 35_184_372_088_832L - 1L;
|
|
||||||
public const long SeventyTrillionThreeHundredSixtyEightBillionSevenHundredFortyFourMillionOneHundredSeventySevenThousandSixHundredSixtyFourMinusOne = 70_368_744_177_664L - 1L;
|
|
||||||
public const long OneHundredFortyTrillionSevenHundredThirtySevenBillionFourHundredEightyEightMillionThreeHundredFiftyFiveThousandThreeHundredTwentyEightMinusOne = 140_737_488_355_328L - 1L;
|
|
||||||
public const long TwoHundredEightyOneTrillionFourHundredSeventyFourBillionNineHundredSeventySixMillionSevenHundredTenThousandSixHundredFiftySixMinusOne = 281_474_976_710_656L - 1L;
|
|
||||||
public const long FiveHundredSixtyTwoTrillionNineHundredFortyNineBillionNineHundredFiftyThreeMillionFourHundredTwentyOneThousandThreeHundredTwelveMinusOne = 562_949_953_421_312L - 1L;
|
|
||||||
public const long OneQuadrillionOneHundredTwentyFiveTrillionEightHundredNinetyNineBillionNineHundredSixMillionEightHundredFortyTwoThousandSixHundredTwentyFourMinusOne = 1_125_899_906_842_624L - 1L;
|
|
||||||
public const long TwoQuadrillionTwoHundredFiftyOneTrillionSevenHundredNinetyNineBillionEightHundredThirteenMillionSixHundredEightyFiveThousandTwoHundredFortyEightMinusOne = 2_251_799_813_685_248L - 1L;
|
|
||||||
public const long FourQuadrillionFiveHundredThreeTrillionFiveHundredNinetyNineBillionSixHundredTwentySevenMillionThreeHundredSeventyThousandFourHundredNinetySixMinusOne = 4_503_599_627_370_496L - 1L;
|
|
||||||
public const long NineQuadrillionSevenTrillionOneHundredNinetyNineBillionTwoHundredFiftyFourMillionSevenHundredFortyThousandNineHundredNinetyTwoMinusOne = 9_007_199_254_740_992L - 1L;
|
|
||||||
public const long EighteenQuadrillionFourteenTrillionThreeHundredNinetyEightBillionFiveHundredEightMillionEightHundredEightyThousandOneHundredEightyFourMinusOne = 18_014_398_509_481_984L - 1L;
|
|
||||||
public const long ThirtySixQuadrillionTwentyEightTrillionSevenHundredNinetySevenBillionSeventeenMillionSevenHundredSixtyThousandThreeHundredSixtyEightMinusOne = 36_028_797_018_963_968L - 1L;
|
|
||||||
public const long SeventyTwoQuadrillionFiftySevenTrillionFiveHundredNinetyFourBillionThirtyFiveMillionFiveHundredTwentyThousandSevenHundredThirtySixMinusOne = 72_057_594_037_927_936L - 1L;
|
|
||||||
public const long OneHundredFortyFourQuadrillionOneHundredFifteenTrillionOneHundredEightyEightBillionSeventyOneMillionFortyOneThousandFourHundredSeventyTwoMinusOne = 144_115_188_075_855_872L - 1L;
|
|
||||||
public const long TwoHundredEightyEightQuadrillionTwoHundredThirtyTrillionThreeHundredSeventySixBillionOneHundredFortyTwoMillionEightyTwoThousandNineHundredFortyFourMinusOne = 288_230_376_151_711_744L - 1L;
|
|
||||||
public const long FiveHundredSeventySixQuadrillionFourHundredSixtyTrillionSevenHundredFiftyTwoBillionTwoHundredEightyFourMillionOneHundredSixtyFiveThousandEightHundredEightyEightMinusOne = 576_460_752_303_423_488L - 1L;
|
|
||||||
public const long OneQuintillionOneHundredFiftyTwoQuadrillionNineHundredTwentyOneTrillionFiveHundredFourBillionSixHundredSixtyEightMillionThreeHundredThirtyOneThousandSevenHundredSeventySixMinusOne = 1_152_921_504_606_846_976L - 1L;
|
|
||||||
public const long TwoQuintillionThreeHundredFiveQuadrillionEightHundredFortyThreeTrillionNineBillionThreeHundredThirtySixMillionSixHundredSixtyThreeThousandFiveHundredFiftyTwoMinusOne = 2_305_843_009_213_693_952L - 1L;
|
|
||||||
public const long FourQuintillionSixHundredElevenQuadrillionSixHundredEightySevenTrillionEightHundredSixteenBillionSixHundredSeventyThreeMillionTwoHundredTwentySevenThousandOneHundredFourMinusOne = 4_611_686_018_427_387_904L - 1L;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public static long LongMinusOne(int i) => i switch
|
|
||||||
{
|
|
||||||
0 => 0L,
|
|
||||||
1 => LongNumbers.TwoMinusOne,
|
|
||||||
2 => LongNumbers.FourMinusOne,
|
|
||||||
3 => LongNumbers.EightMinusOne,
|
|
||||||
4 => LongNumbers.SixteenMinusOne,
|
|
||||||
5 => LongNumbers.ThirtyTwoMinusOne,
|
|
||||||
6 => LongNumbers.SixtyFourMinusOne,
|
|
||||||
7 => LongNumbers.OneHundredTwentyEightMinusOne,
|
|
||||||
8 => LongNumbers.TwoHundredFiftySixMinusOne,
|
|
||||||
9 => LongNumbers.FiveHundredTwelveMinusOne,
|
|
||||||
10 => LongNumbers.OneThousandTwentyFourMinusOne,
|
|
||||||
11 => LongNumbers.TwoThousandFortyEightMinusOne,
|
|
||||||
12 => LongNumbers.FourThousandNinetySixMinusOne,
|
|
||||||
13 => LongNumbers.EightThousandOneHundredNinetyTwoMinusOne,
|
|
||||||
14 => LongNumbers.SixteenThousandThreeHundredEightyFourMinusOne,
|
|
||||||
15 => LongNumbers.ThirtyTwoThousandSevenHundredSixtyEightMinusOne,
|
|
||||||
16 => LongNumbers.SixtyFiveThousandFiveHundredThirtySixMinusOne,
|
|
||||||
17 => LongNumbers.OneHundredThirtyOneThousandSeventyTwoMinusOne,
|
|
||||||
18 => LongNumbers.TwoHundredSixtyTwoThousandOneHundredFortyFourMinusOne,
|
|
||||||
19 => LongNumbers.FiveHundredTwentyFourThousandTwoHundredEightyEightMinusOne,
|
|
||||||
20 => LongNumbers.OneMillionFortyEightThousandFiveHundredSeventySixMinusOne,
|
|
||||||
21 => LongNumbers.TwoMillionNinetySevenThousandOneHundredFiftyTwoMinusOne,
|
|
||||||
22 => LongNumbers.FourMillionOneHundredNinetyFourThousandThreeHundredFourMinusOne,
|
|
||||||
23 => LongNumbers.EightMillionThreeHundredEightyEightThousandSixHundredEightMinusOne,
|
|
||||||
24 => LongNumbers.SixteenMillionSevenHundredSeventySevenThousandTwoHundredSixteenMinusOne,
|
|
||||||
25 => LongNumbers.ThirtyThreeMillionFiveHundredFiftyFourThousandFourHundredThirtyTwoMinusOne,
|
|
||||||
26 => LongNumbers.SixtySevenMillionOneHundredEightThousandEightHundredSixtyFourMinusOne,
|
|
||||||
27 => LongNumbers.OneHundredThirtyFourMillionTwoHundredSeventeenThousandSevenHundredTwentyEightMinusOne,
|
|
||||||
28 => LongNumbers.TwoHundredSixtyEightMillionFourHundredThirtyFiveThousandFourHundredFiftySixMinusOne,
|
|
||||||
29 => LongNumbers.FiveHundredThirtySixMillionEightHundredSeventyThousandNineHundredTwelveMinusOne,
|
|
||||||
30 => LongNumbers.OneBillionSeventyThreeMillionSevenHundredFortyOneThousandEightHundredTwentyFourMinusOne,
|
|
||||||
31 => LongNumbers.TwoBillionOneHundredFortySevenMillionFourHundredEightyThreeThousandSixHundredFortyEightMinusOne,
|
|
||||||
32 => LongNumbers.FourBillionTwoHundredNinetyFourMillionNineHundredSixtySevenThousandTwoHundredNinetySixMinusOne,
|
|
||||||
33 => LongNumbers.EightBillionFiveHundredEightyNineMillionNineHundredThirtyFourThousandFiveHundredNinetyTwoMinusOne,
|
|
||||||
34 => LongNumbers.SeventeenBillionOneHundredSeventyNineMillionEightHundredSixtyNineThousandOneHundredEightyFourMinusOne,
|
|
||||||
35 => LongNumbers.ThirtyFourBillionThreeHundredFiftyNineMillionSevenHundredThirtyEightThousandThreeHundredSixtyEightMinusOne,
|
|
||||||
36 => LongNumbers.SixtyEightBillionSevenHundredNineteenMillionFourHundredSeventySixThousandSevenHundredThirtySixMinusOne,
|
|
||||||
37 => LongNumbers.OneHundredThirtySevenBillionFourHundredThirtyEightMillionNineHundredFiftyThreeThousandFourHundredSeventyTwoMinusOne,
|
|
||||||
38 => LongNumbers.TwoHundredSeventyFourBillionEightHundredSeventySevenMillionNineHundredSixThousandNineHundredFortyFourMinusOne,
|
|
||||||
39 => LongNumbers.FiveHundredFortyNineBillionSevenHundredFiftyFiveMillionEightHundredThirteenThousandEightHundredEightyEightMinusOne,
|
|
||||||
40 => LongNumbers.OneTrillionNinetyNineBillionFiveHundredElevenMillionSixHundredTwentySevenThousandSevenHundredSeventySixMinusOne,
|
|
||||||
41 => LongNumbers.TwoTrillionOneHundredNinetyNineBillionTwentyThreeMillionTwoHundredFiftyFiveThousandFiveHundredFiftyTwoMinusOne,
|
|
||||||
42 => LongNumbers.FourTrillionThreeHundredNinetyEightBillionFortySixMillionFiveHundredElevenThousandOneHundredFourMinusOne,
|
|
||||||
43 => LongNumbers.EightTrillionSevenHundredNinetySixBillionNinetyThreeMillionTwentyTwoThousandTwoHundredEightMinusOne,
|
|
||||||
44 => LongNumbers.SeventeenTrillionFiveHundredNinetyTwoBillionOneHundredEightySixMillionFortyFourThousandFourHundredSixteenMinusOne,
|
|
||||||
45 => LongNumbers.ThirtyFiveTrillionOneHundredEightyFourBillionThreeHundredSeventyTwoMillionEightyEightThousandEightHundredThirtyTwoMinusOne,
|
|
||||||
46 => LongNumbers.SeventyTrillionThreeHundredSixtyEightBillionSevenHundredFortyFourMillionOneHundredSeventySevenThousandSixHundredSixtyFourMinusOne,
|
|
||||||
47 => LongNumbers.OneHundredFortyTrillionSevenHundredThirtySevenBillionFourHundredEightyEightMillionThreeHundredFiftyFiveThousandThreeHundredTwentyEightMinusOne,
|
|
||||||
48 => LongNumbers.TwoHundredEightyOneTrillionFourHundredSeventyFourBillionNineHundredSeventySixMillionSevenHundredTenThousandSixHundredFiftySixMinusOne,
|
|
||||||
49 => LongNumbers.FiveHundredSixtyTwoTrillionNineHundredFortyNineBillionNineHundredFiftyThreeMillionFourHundredTwentyOneThousandThreeHundredTwelveMinusOne,
|
|
||||||
50 => LongNumbers.OneQuadrillionOneHundredTwentyFiveTrillionEightHundredNinetyNineBillionNineHundredSixMillionEightHundredFortyTwoThousandSixHundredTwentyFourMinusOne,
|
|
||||||
51 => LongNumbers.TwoQuadrillionTwoHundredFiftyOneTrillionSevenHundredNinetyNineBillionEightHundredThirteenMillionSixHundredEightyFiveThousandTwoHundredFortyEightMinusOne,
|
|
||||||
52 => LongNumbers.FourQuadrillionFiveHundredThreeTrillionFiveHundredNinetyNineBillionSixHundredTwentySevenMillionThreeHundredSeventyThousandFourHundredNinetySixMinusOne,
|
|
||||||
53 => LongNumbers.NineQuadrillionSevenTrillionOneHundredNinetyNineBillionTwoHundredFiftyFourMillionSevenHundredFortyThousandNineHundredNinetyTwoMinusOne,
|
|
||||||
54 => LongNumbers.EighteenQuadrillionFourteenTrillionThreeHundredNinetyEightBillionFiveHundredEightMillionEightHundredEightyThousandOneHundredEightyFourMinusOne,
|
|
||||||
55 => LongNumbers.ThirtySixQuadrillionTwentyEightTrillionSevenHundredNinetySevenBillionSeventeenMillionSevenHundredSixtyThousandThreeHundredSixtyEightMinusOne,
|
|
||||||
56 => LongNumbers.SeventyTwoQuadrillionFiftySevenTrillionFiveHundredNinetyFourBillionThirtyFiveMillionFiveHundredTwentyThousandSevenHundredThirtySixMinusOne,
|
|
||||||
57 => LongNumbers.OneHundredFortyFourQuadrillionOneHundredFifteenTrillionOneHundredEightyEightBillionSeventyOneMillionFortyOneThousandFourHundredSeventyTwoMinusOne,
|
|
||||||
58 => LongNumbers.TwoHundredEightyEightQuadrillionTwoHundredThirtyTrillionThreeHundredSeventySixBillionOneHundredFortyTwoMillionEightyTwoThousandNineHundredFortyFourMinusOne,
|
|
||||||
59 => LongNumbers.FiveHundredSeventySixQuadrillionFourHundredSixtyTrillionSevenHundredFiftyTwoBillionTwoHundredEightyFourMillionOneHundredSixtyFiveThousandEightHundredEightyEightMinusOne,
|
|
||||||
60 => LongNumbers.OneQuintillionOneHundredFiftyTwoQuadrillionNineHundredTwentyOneTrillionFiveHundredFourBillionSixHundredSixtyEightMillionThreeHundredThirtyOneThousandSevenHundredSeventySixMinusOne,
|
|
||||||
61 => LongNumbers.TwoQuintillionThreeHundredFiveQuadrillionEightHundredFortyThreeTrillionNineBillionThreeHundredThirtySixMillionSixHundredSixtyThreeThousandFiveHundredFiftyTwoMinusOne,
|
|
||||||
62 => LongNumbers.FourQuintillionSixHundredElevenQuadrillionSixHundredEightySevenTrillionEightHundredSixteenBillionSixHundredSeventyThreeMillionTwoHundredTwentySevenThousandOneHundredFourMinusOne,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(i)),
|
|
||||||
};
|
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public long ByShifting()
|
|
||||||
{
|
|
||||||
long result = 0L;
|
|
||||||
for (int i = 0; i <= 62; i++)
|
|
||||||
{
|
|
||||||
result = (1L << i) - 1L;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public long ByMapping()
|
|
||||||
{
|
|
||||||
long result = 0L;
|
|
||||||
for (int i = 0; i <= 62; i++)
|
|
||||||
{
|
|
||||||
result = LongMinusOne(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using Open.Numeric.Primes;
|
using Open.Numeric.Primes;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
|||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class PrimeTesterBenchmarks
|
public class PrimeTesterBenchmarks
|
||||||
{
|
{
|
||||||
|
private HeuristicPrimeTester _cpuTester = null!;
|
||||||
|
private HeuristicPrimeTester _gpuTester = null!;
|
||||||
private ulong[] _candidates = [];
|
private ulong[] _candidates = [];
|
||||||
|
|
||||||
[ParamsSource(nameof(GetBenchmarkCases))]
|
[ParamsSource(nameof(GetBenchmarkCases))]
|
||||||
@@ -19,8 +22,8 @@ public class PrimeTesterBenchmarks
|
|||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
{
|
{
|
||||||
EnvironmentConfiguration.RollingAccelerators = 1;
|
_cpuTester = new HeuristicPrimeTester();
|
||||||
|
_gpuTester = new HeuristicPrimeTester();
|
||||||
_candidates = BenchmarkCase.Candidates;
|
_candidates = BenchmarkCase.Candidates;
|
||||||
|
|
||||||
if (_candidates.Length == 0)
|
if (_candidates.Length == 0)
|
||||||
@@ -28,116 +31,81 @@ public class PrimeTesterBenchmarks
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancellationToken cancellationToken = CancellationToken.None;
|
||||||
PrimeOrderCalculatorAccelerator.WarmUp();
|
_cpuTester.IsPrimeCpu(_candidates[0], cancellationToken);
|
||||||
// HeuristicGroupABPrimeTesterAccelerator.WarmUp();
|
_gpuTester.IsPrimeGpu(_candidates[0]);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
|
|
||||||
_ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]);
|
|
||||||
_ = HeuristicPrimeTester.IsPrimeGpu(gpu, _candidates[0]);
|
|
||||||
_ = Prime.Numbers.IsPrime(_candidates[0]);
|
_ = Prime.Numbers.IsPrime(_candidates[0]);
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Benchmark]
|
|
||||||
public int HeuristicCpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (HeuristicPrimeTester.IsPrimeCpu(values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Benchmark]
|
|
||||||
public int HeuristicGpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (HeuristicPrimeTester.IsPrimeGpu(gpu, values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
return primeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Benchmark]
|
|
||||||
public int HeuristicCombinedGpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Benchmark]
|
|
||||||
public int HeuristicCombinedCpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (HeuristicCombinedPrimeTester.IsPrimeCpu(values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Benchmark]
|
|
||||||
public int NonHeuristicGpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (PrimeTester.IsPrimeGpu(gpu, values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
return primeCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
[Benchmark(Baseline = true)]
|
||||||
|
public int HeuristicCpu()
|
||||||
|
{
|
||||||
|
HeuristicPrimeTester tester = _cpuTester;
|
||||||
|
ulong[] values = _candidates;
|
||||||
|
CancellationToken cancellationToken = CancellationToken.None;
|
||||||
|
int primeCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
if (tester.IsPrimeCpu(values[i], cancellationToken))
|
||||||
|
{
|
||||||
|
primeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int HeuristicGpu()
|
||||||
|
{
|
||||||
|
HeuristicPrimeTester tester = _gpuTester;
|
||||||
|
ulong[] values = _candidates;
|
||||||
|
CancellationToken cancellationToken = CancellationToken.None;
|
||||||
|
int primeCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
if (tester.IsPrimeGpu(values[i]))
|
||||||
|
{
|
||||||
|
primeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This benchmark is very slow. No point in comparing it.
|
||||||
|
// [Benchmark]
|
||||||
|
public int NonHeuristicGpu()
|
||||||
|
{
|
||||||
|
// PrimeTester tester = _gpuTester;
|
||||||
|
ulong[] values = _candidates;
|
||||||
|
CancellationToken cancellationToken = CancellationToken.None;
|
||||||
|
int primeCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
if (PrimeTester.Exclusive.IsPrimeGpu(values[i], cancellationToken))
|
||||||
|
{
|
||||||
|
primeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return primeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
public int NonHeuristicCpu()
|
public int NonHeuristicCpu()
|
||||||
{
|
{
|
||||||
|
// PrimeTester tester = _gpuTester;
|
||||||
ulong[] values = _candidates;
|
ulong[] values = _candidates;
|
||||||
|
CancellationToken cancellationToken = CancellationToken.None;
|
||||||
int primeCount = 0;
|
int primeCount = 0;
|
||||||
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
for (int i = 0; i < values.Length; i++)
|
||||||
{
|
{
|
||||||
if (PrimeTester.IsPrimeCpu(values[i]))
|
if (PrimeTester.IsPrimeCpu(values[i], cancellationToken))
|
||||||
{
|
{
|
||||||
primeCount++;
|
primeCount++;
|
||||||
}
|
}
|
||||||
@@ -147,40 +115,6 @@ public class PrimeTesterBenchmarks
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public int ByLastDigitCpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (PrimeTesterByLastDigit.IsPrimeCpu(values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public int SevenOrOthersCpu()
|
|
||||||
{
|
|
||||||
ulong[] values = _candidates;
|
|
||||||
int primeCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < values.Length; i++)
|
|
||||||
{
|
|
||||||
if (PrimeTesterSeverOrOthers.IsPrimeCpu(values[i]))
|
|
||||||
{
|
|
||||||
primeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return primeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Benchmark]
|
|
||||||
public int OpenNumericPrimes()
|
public int OpenNumericPrimes()
|
||||||
{
|
{
|
||||||
ulong[] values = _candidates;
|
ulong[] values = _candidates;
|
||||||
@@ -213,54 +147,12 @@ public class PrimeTesterBenchmarks
|
|||||||
4_000_000UL,
|
4_000_000UL,
|
||||||
4_096);
|
4_096);
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥16_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 16,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
16_000_001UL,
|
|
||||||
16_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥32_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 32,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
32_000_001UL,
|
|
||||||
32_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥64_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 64,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
64_000_001UL,
|
|
||||||
64_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥100_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 100,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
100_000_001UL,
|
|
||||||
100_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
yield return PrimeBenchmarkCase.FromRange(
|
||||||
"≥138_000_000",
|
"≥138_000_000",
|
||||||
"Sampled admissible odd values ≥ 138,000,000 (k ≤ 5, not divisible by 5)",
|
"Sampled admissible odd values ≥ 138,000,000 (k ≤ 5, not divisible by 5)",
|
||||||
138_000_001UL,
|
138_000_001UL,
|
||||||
138_500_000UL,
|
138_500_000UL,
|
||||||
4_096);
|
4_096);
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥276_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 276,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
276_000_001UL,
|
|
||||||
276_500_000UL,
|
|
||||||
4_096);
|
|
||||||
|
|
||||||
yield return PrimeBenchmarkCase.FromRange(
|
|
||||||
"≥552_000_000",
|
|
||||||
"Sampled admissible odd values ≥ 552,000,000 (k ≤ 5, not divisible by 5)",
|
|
||||||
552_000_001UL,
|
|
||||||
552_500_000UL,
|
|
||||||
4_096);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct PrimeBenchmarkCase(string Name, string Description, ulong[] Candidates)
|
public readonly record struct PrimeBenchmarkCase(string Name, string Description, ulong[] Candidates)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ internal static class Program
|
|||||||
"/help"
|
"/help"
|
||||||
};
|
};
|
||||||
|
|
||||||
private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
|
private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||||
|
|
||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ using Xunit;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
using EvenPerfectBitScanner;
|
||||||
using EvenPerfectBitScanner.Candidates;
|
using EvenPerfectBitScanner.Candidates;
|
||||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Tests;
|
namespace EvenPerfectBitScanner.Tests;
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ public class ProgramTests
|
|||||||
[InlineData(81UL, false)]
|
[InlineData(81UL, false)]
|
||||||
public void IsPrime_identifies_primes_correctly(ulong n, bool expected)
|
public void IsPrime_identifies_primes_correctly(ulong n, bool expected)
|
||||||
{
|
{
|
||||||
PrimeTester.IsPrimeCpu(n).Should().Be(expected);
|
new PrimeTester().IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -99,26 +102,27 @@ public class ProgramTests
|
|||||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
|
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
|
||||||
|
forceCpuProp!.SetValue(null, true);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 32UL, out _, out _).Should().BeFalse();
|
Program.IsEvenPerfectCandidate(11UL, 32UL, out _, out _).Should().BeFalse();
|
||||||
Program.IsEvenPerfectCandidate(gpu, 5UL, 32UL, out _, out _).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(5UL, 32UL, out _, out _).Should().BeTrue();
|
||||||
Program.IsEvenPerfectCandidate(gpu, 127UL, 32UL, out _, out _).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(127UL, 32UL, out _, out _).Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
testerField.SetValue(null, null);
|
testerField.SetValue(null, null);
|
||||||
candidatesField.SetValue(null, null);
|
candidatesField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,26 +135,27 @@ public class ProgramTests
|
|||||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
|
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||||
|
forceCpuProp!.SetValue(null, true);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailedCheck.Should().BeFalse();
|
detailedCheck.Should().BeFalse();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
testerField.SetValue(null, null);
|
testerField.SetValue(null, null);
|
||||||
candidatesField.SetValue(null, null);
|
candidatesField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,24 +168,25 @@ public class ProgramTests
|
|||||||
|
|
||||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, true);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, 3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse();
|
Program.IsEvenPerfectCandidate(3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
testerField.SetValue(null, null);
|
testerField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,28 +199,29 @@ public class ProgramTests
|
|||||||
|
|
||||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
|
|
||||||
Program.IsEvenPerfectCandidate(gpu, 13UL, 0UL, out searched, out detailed).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(13UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeFalse();
|
detailed.Should().BeFalse();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
testerField.SetValue(null, null);
|
testerField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,28 +233,29 @@ public class ProgramTests
|
|||||||
|
|
||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu( maxK: 1_000UL), trackAllValues: true));
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true));
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, 29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
Program.IsEvenPerfectCandidate(29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeFalse();
|
detailed.Should().BeFalse();
|
||||||
|
|
||||||
Program.IsEvenPerfectCandidate(gpu, 31UL, 0UL, out searched, out detailed).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(31UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,30 +267,32 @@ public class ProgramTests
|
|||||||
|
|
||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
|
useIncremental: true,
|
||||||
|
useResidue: true,
|
||||||
maxK: 1_024UL), trackAllValues: true));
|
maxK: 1_024UL), trackAllValues: true));
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ulong[] primes = [107UL, 127UL];
|
ulong[] primes = [107UL, 127UL];
|
||||||
foreach (ulong prime in primes)
|
foreach (ulong prime in primes)
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,30 +305,32 @@ public class ProgramTests
|
|||||||
|
|
||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
|
useIncremental: true,
|
||||||
|
useResidue: true,
|
||||||
maxK: 5_000_000UL), trackAllValues: true));
|
maxK: 5_000_000UL), trackAllValues: true));
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
||||||
Parallel.ForEach(primes, prime =>
|
Parallel.ForEach(primes, prime =>
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,30 +342,32 @@ public class ProgramTests
|
|||||||
|
|
||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
|
useIncremental: true,
|
||||||
|
useResidue: true,
|
||||||
maxK: 5_000_000UL), trackAllValues: true));
|
maxK: 5_000_000UL), trackAllValues: true));
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, 107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
|
|
||||||
Program.IsEvenPerfectCandidate(gpu, 127UL, 0UL, out searched, out detailed).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(127UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
detailed.Should().BeTrue();
|
detailed.Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,25 +380,29 @@ public class ProgramTests
|
|||||||
|
|
||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
|
useIncremental: true,
|
||||||
|
useGpuScan: false,
|
||||||
|
useGpuOrder: false,
|
||||||
|
useResidue: true,
|
||||||
maxK: 1_024UL), trackAllValues: true));
|
maxK: 1_024UL), trackAllValues: true));
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, true);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,38 +417,32 @@ public class ProgramTests
|
|||||||
|
|
||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() =>
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||||
{
|
useIncremental: false,
|
||||||
if (configuration.UseGpuOrder)
|
kernelType: configuration.KernelType,
|
||||||
{
|
useGpuScan: true,
|
||||||
return new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForGpuOrderDeviceForCpu(
|
useGpuOrder: configuration.UseGpuOrder,
|
||||||
|
useResidue: true,
|
||||||
kernelType: configuration.KernelType,
|
maxK: configuration.ResidueMaxK), trackAllValues: true));
|
||||||
maxK: configuration.ResidueMaxK);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
|
||||||
|
|
||||||
kernelType: configuration.KernelType,
|
|
||||||
maxK: configuration.ResidueMaxK);
|
|
||||||
}, trackAllValues: true));
|
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,32 +506,33 @@ public class ProgramTests
|
|||||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||||
|
|
||||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForIncrementalCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu( maxK: 1_000UL), trackAllValues: true));
|
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true));
|
||||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||||
|
forceCpuProp!.SetValue(null, true);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
compositeField.SetValue(null, false);
|
compositeField.SetValue(null, false);
|
||||||
|
|
||||||
Program.IsEvenPerfectCandidate(gpu, 9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
Program.IsEvenPerfectCandidate(9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||||
searched.Should().BeFalse();
|
searched.Should().BeFalse();
|
||||||
detailed.Should().BeFalse();
|
detailed.Should().BeFalse();
|
||||||
((bool)compositeField.GetValue(null)!).Should().BeTrue();
|
((bool)compositeField.GetValue(null)!).Should().BeTrue();
|
||||||
|
|
||||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out searched, out detailed);
|
Program.IsEvenPerfectCandidate(11UL, 0UL, out searched, out detailed);
|
||||||
searched.Should().BeTrue();
|
searched.Should().BeTrue();
|
||||||
((bool)compositeField.GetValue(null)!).Should().BeFalse();
|
((bool)compositeField.GetValue(null)!).Should().BeFalse();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
mersenneField.SetValue(null, null);
|
mersenneField.SetValue(null, null);
|
||||||
primeField.SetValue(null, null);
|
primeField.SetValue(null, null);
|
||||||
CandidatesCalculator.ResetResidueTrackers();
|
CandidatesCalculator.ResetResidueTrackers();
|
||||||
compositeField.SetValue(null, false);
|
compositeField.SetValue(null, false);
|
||||||
|
forceCpuProp!.SetValue(null, false);
|
||||||
ResetCliArguments();
|
ResetCliArguments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,9 +557,8 @@ public class ProgramTests
|
|||||||
|
|
||||||
string output = writer.ToString();
|
string output = writer.ToString();
|
||||||
output.Should().Contain("Usage:");
|
output.Should().Contain("Usage:");
|
||||||
output.Should().Contain("--mersenne-device=cpu|gpu|hybrid");
|
output.Should().Contain("--mersenne-device=cpu|gpu");
|
||||||
output.Should().Contain("--primes-device=cpu|gpu");
|
output.Should().Contain("--primes-device=cpu|gpu");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
using System.Buffers.Text;
|
using System.Buffers.Text;
|
||||||
using System.Globalization;
|
using System.IO;
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner;
|
namespace EvenPerfectBitScanner;
|
||||||
|
|
||||||
internal static class CalculationResultHandler
|
internal static class CalculationResultHandler
|
||||||
{
|
{
|
||||||
private const string ResultsHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
|
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||||
internal const int DefaultWriteBatchSize = 100;
|
internal const int DefaultWriteBatchSize = 100;
|
||||||
|
|
||||||
@@ -95,46 +95,19 @@ internal static class CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static void HandleResult(ulong currentP, BigInteger divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
||||||
{
|
|
||||||
Span<char> divisorBuffer = stackalloc char[256];
|
|
||||||
if (!divisor.TryFormat(divisorBuffer, out int divisorChars, provider: CultureInfo.InvariantCulture))
|
|
||||||
{
|
|
||||||
string divisorString = divisor.ToString(CultureInfo.InvariantCulture);
|
|
||||||
HandleResultInternal(currentP, divisorString.AsSpan(), searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleResultInternal(currentP, divisorBuffer[..divisorChars], searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
|
||||||
{
|
|
||||||
Span<char> divisorBuffer = stackalloc char[32];
|
|
||||||
if (!divisor.TryFormat(divisorBuffer, out int divisorChars, provider: CultureInfo.InvariantCulture))
|
|
||||||
{
|
|
||||||
string divisorString = divisor.ToString(CultureInfo.InvariantCulture);
|
|
||||||
HandleResultInternal(currentP, divisorString.AsSpan(), searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleResultInternal(currentP, divisorBuffer[..divisorChars], searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void HandleResultInternal(ulong currentP, ReadOnlySpan<char> divisorText, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
|
||||||
{
|
{
|
||||||
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
|
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
|
||||||
|
|
||||||
Span<byte> stackBuffer = stackalloc byte[256];
|
Span<byte> stackBuffer = stackalloc byte[128];
|
||||||
byte[]? rentedBuffer = null;
|
byte[]? rentedBuffer = null;
|
||||||
Span<byte> utf8Span = stackBuffer;
|
Span<byte> utf8Span = stackBuffer;
|
||||||
int byteCount = TryFormatResultLine(utf8Span, currentP, divisorText, searchedMersenne, detailedCheck, passedAllTests);
|
int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||||
if (byteCount < 0)
|
if (byteCount < 0)
|
||||||
{
|
{
|
||||||
int minimumSize = Math.Max((divisorText.Length + 5) * 4, 256);
|
rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
|
||||||
rentedBuffer = ThreadStaticPools.BytePool.Rent(minimumSize);
|
|
||||||
utf8Span = rentedBuffer;
|
utf8Span = rentedBuffer;
|
||||||
byteCount = TryFormatResultLine(utf8Span, currentP, divisorText, searchedMersenne, detailedCheck, passedAllTests);
|
byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||||
if (byteCount < 0)
|
if (byteCount < 0)
|
||||||
{
|
{
|
||||||
ThreadStaticPools.BytePool.Return(rentedBuffer);
|
ThreadStaticPools.BytePool.Return(rentedBuffer);
|
||||||
@@ -143,7 +116,7 @@ internal static class CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<byte> utf8Line = utf8Span[..byteCount];
|
ReadOnlySpan<byte> utf8Line = utf8Span[..byteCount];
|
||||||
Span<char> charBuffer = stackalloc char[256];
|
Span<char> charBuffer = stackalloc char[128];
|
||||||
int charCount = Utf8Encoding.GetChars(utf8Line, charBuffer);
|
int charCount = Utf8Encoding.GetChars(utf8Line, charBuffer);
|
||||||
ReadOnlySpan<char> recordSpan = charBuffer[..charCount];
|
ReadOnlySpan<char> recordSpan = charBuffer[..charCount];
|
||||||
|
|
||||||
@@ -258,7 +231,7 @@ internal static class CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ReadOnlySpan<char> divisorText, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||||
{
|
{
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
@@ -275,17 +248,6 @@ internal static class CalculationResultHandler
|
|||||||
|
|
||||||
destination[offset++] = (byte)',';
|
destination[offset++] = (byte)',';
|
||||||
|
|
||||||
int divisorByteCount = Utf8Encoding.GetByteCount(divisorText);
|
|
||||||
if (offset + divisorByteCount >= destination.Length)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
divisorByteCount = Utf8Encoding.GetBytes(divisorText, destination[offset..]);
|
|
||||||
offset += divisorByteCount;
|
|
||||||
|
|
||||||
destination[offset++] = (byte)',';
|
|
||||||
|
|
||||||
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
|
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Candidates;
|
namespace EvenPerfectBitScanner.Candidates;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
using Open.Numeric.Primes;
|
using Open.Numeric.Primes;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Candidates;
|
namespace EvenPerfectBitScanner.Candidates;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ internal static class CandidatesCalculator
|
|||||||
return trackers.Value!;
|
return trackers.Value!;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsCompositeByResiduesCpu(ulong p)
|
internal static bool IsCompositeByResidues(ulong p)
|
||||||
{
|
{
|
||||||
ModResidueTracker tracker = AcquireResidueTracker();
|
ModResidueTracker tracker = AcquireResidueTracker();
|
||||||
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
|
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
|
||||||
@@ -72,57 +72,7 @@ internal static class CandidatesCalculator
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tracker.MergeOrAppendCpu(p, primes[primeIndex], out bool divisible) && divisible)
|
if (tracker.MergeOrAppend(p, primes[primeIndex], out bool divisible) && divisible)
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool IsCompositeByResiduesGpu(PrimeOrderCalculatorAccelerator gpu, ulong p)
|
|
||||||
{
|
|
||||||
ModResidueTracker tracker = AcquireResidueTracker();
|
|
||||||
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
|
|
||||||
tracker.BeginMerge(p);
|
|
||||||
// TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of
|
|
||||||
// running MergeOrAppend for every candidate and missing the cycle-accelerated early exits.
|
|
||||||
// Use the small prime list from PerfectNumbers.Core to the extent of sqrt(p)
|
|
||||||
var primes = PrimesGenerator.SmallPrimes;
|
|
||||||
var primesPow2 = PrimesGenerator.SmallPrimesPow2;
|
|
||||||
int len = primes.Length;
|
|
||||||
for (int primeIndex = 0; primeIndex < len; primeIndex++)
|
|
||||||
{
|
|
||||||
if (primesPow2[primeIndex] > p)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tracker.MergeOrAppendGpu(gpu, p, primes[primeIndex], out bool divisible) && divisible)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool IsCompositeByResiduesHybrid(PrimeOrderCalculatorAccelerator gpu, ulong p)
|
|
||||||
{
|
|
||||||
ModResidueTracker tracker = AcquireResidueTracker();
|
|
||||||
tracker.BeginMerge(p);
|
|
||||||
var primes = PrimesGenerator.SmallPrimes;
|
|
||||||
var primesPow2 = PrimesGenerator.SmallPrimesPow2;
|
|
||||||
int len = primes.Length;
|
|
||||||
for (int primeIndex = 0; primeIndex < len; primeIndex++)
|
|
||||||
{
|
|
||||||
if (primesPow2[primeIndex] > p)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tracker.MergeOrAppendHybrid(gpu, p, primes[primeIndex], out bool divisible) && divisible)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -179,15 +129,18 @@ internal static class CandidatesCalculator
|
|||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static ulong AdvancePrime(ulong value, ref ulong remainder, ref bool limitReached) => _transformMode switch
|
internal static ulong AdvancePrime(ulong value, ref ulong remainder, ref bool limitReached)
|
||||||
{
|
{
|
||||||
PrimeTransformMode.Bit => CandidateBitTransform.Transform(value, ref remainder, ref limitReached),
|
return _transformMode switch
|
||||||
PrimeTransformMode.Add => CandidateAddTransform.Transform(value, ref remainder, ref limitReached),
|
{
|
||||||
_ => CandidateAddPrimesTransform.Transform(value, ref remainder, ref limitReached),
|
PrimeTransformMode.Bit => CandidateBitTransform.Transform(value, ref remainder, ref limitReached),
|
||||||
};
|
PrimeTransformMode.Add => CandidateAddTransform.Transform(value, ref remainder, ref limitReached),
|
||||||
|
_ => CandidateAddPrimesTransform.Transform(value, ref remainder, ref limitReached),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
internal static int ReserveBlock(ulong[] buffer, int blockSize, ref bool limitReached)
|
internal static int ReserveBlock(ulong[] buffer, int blockSize, ref bool limitReached)
|
||||||
{
|
{
|
||||||
Span<ulong> bufferSpan = new(buffer);
|
Span<ulong> bufferSpan = new(buffer);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using EvenPerfectBitScanner.Candidates;
|
||||||
using Open.Numeric.Primes;
|
using Open.Numeric.Primes;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using EvenPerfectBitScanner.Candidates;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
@@ -5,18 +6,6 @@ using PerfectNumbers.Core.Gpu;
|
|||||||
|
|
||||||
namespace EvenPerfectBitScanner;
|
namespace EvenPerfectBitScanner;
|
||||||
|
|
||||||
internal enum ByDivisorKIncrementMode
|
|
||||||
{
|
|
||||||
Sequential,
|
|
||||||
Pow2Groups,
|
|
||||||
Predictive,
|
|
||||||
Percentile,
|
|
||||||
Additive,
|
|
||||||
TopDown,
|
|
||||||
BitContradiction,
|
|
||||||
BitTree,
|
|
||||||
}
|
|
||||||
|
|
||||||
internal readonly struct CliArguments
|
internal readonly struct CliArguments
|
||||||
{
|
{
|
||||||
private const string DefaultCyclesPath = "divisor_cycles.bin";
|
private const string DefaultCyclesPath = "divisor_cycles.bin";
|
||||||
@@ -37,17 +26,14 @@ internal readonly struct CliArguments
|
|||||||
internal readonly bool UseResidue;
|
internal readonly bool UseResidue;
|
||||||
internal readonly bool UseDivisor;
|
internal readonly bool UseDivisor;
|
||||||
internal readonly bool UseByDivisor;
|
internal readonly bool UseByDivisor;
|
||||||
internal readonly ByDivisorKIncrementMode ByDivisorKIncrementMode;
|
|
||||||
internal readonly int ByDivisorSpecialRange;
|
|
||||||
internal readonly bool UseGcdFilter;
|
internal readonly bool UseGcdFilter;
|
||||||
internal readonly bool TestMode;
|
internal readonly bool TestMode;
|
||||||
internal readonly bool UseGpuCycles;
|
internal readonly bool UseGpuCycles;
|
||||||
internal readonly ComputationDevice MersenneDevice;
|
internal readonly bool UseMersenneOnGpu;
|
||||||
internal readonly ComputationDevice OrderDevice;
|
internal readonly bool UseOrderOnGpu;
|
||||||
internal readonly int ScanBatchSize;
|
internal readonly int ScanBatchSize;
|
||||||
internal readonly int SliceSize;
|
internal readonly int SliceSize;
|
||||||
internal readonly ulong MaxKLimit;
|
internal readonly ulong ResidueKMax;
|
||||||
internal readonly bool MaxKConfigured;
|
|
||||||
internal readonly string FilterFile;
|
internal readonly string FilterFile;
|
||||||
internal readonly bool ForcePrimeKernelsOnCpu;
|
internal readonly bool ForcePrimeKernelsOnCpu;
|
||||||
internal readonly ModReductionMode ModReductionMode;
|
internal readonly ModReductionMode ModReductionMode;
|
||||||
@@ -66,57 +52,50 @@ internal readonly struct CliArguments
|
|||||||
internal readonly double ZeroFractionHard;
|
internal readonly double ZeroFractionHard;
|
||||||
internal readonly double ZeroFractionConjecture;
|
internal readonly double ZeroFractionConjecture;
|
||||||
internal readonly int MaxZeroConjecture;
|
internal readonly int MaxZeroConjecture;
|
||||||
internal readonly BigInteger MinK;
|
|
||||||
internal readonly int GpuRatio;
|
|
||||||
|
|
||||||
private CliArguments(
|
private CliArguments(
|
||||||
ulong startPrime,
|
ulong startPrime,
|
||||||
bool startPrimeProvided,
|
bool startPrimeProvided,
|
||||||
UInt128 maxPrimeLimit,
|
UInt128 maxPrimeLimit,
|
||||||
bool maxPrimeConfigured,
|
bool maxPrimeConfigured,
|
||||||
int threadCount,
|
int threadCount,
|
||||||
int blockSize,
|
int blockSize,
|
||||||
int gpuPrimeThreads,
|
int gpuPrimeThreads,
|
||||||
int gpuPrimeBatch,
|
int gpuPrimeBatch,
|
||||||
GpuKernelType kernelType,
|
GpuKernelType kernelType,
|
||||||
bool useOrder,
|
bool useOrder,
|
||||||
bool showHelp,
|
bool showHelp,
|
||||||
bool useBitTransform,
|
bool useBitTransform,
|
||||||
bool useLucas,
|
bool useLucas,
|
||||||
bool useResidue,
|
bool useResidue,
|
||||||
bool useDivisor,
|
bool useDivisor,
|
||||||
bool useByDivisor,
|
bool useByDivisor,
|
||||||
bool useGcdFilter,
|
bool useGcdFilter,
|
||||||
ByDivisorKIncrementMode byDivisorKIncrementMode,
|
bool testMode,
|
||||||
int byDivisorSpecialRange,
|
bool useGpuCycles,
|
||||||
bool testMode,
|
bool useMersenneOnGpu,
|
||||||
bool useGpuCycles,
|
bool useOrderOnGpu,
|
||||||
ComputationDevice mersenneDevice,
|
int scanBatchSize,
|
||||||
ComputationDevice orderDevice,
|
int sliceSize,
|
||||||
int scanBatchSize,
|
ulong residueKMax,
|
||||||
int sliceSize,
|
string filterFile,
|
||||||
ulong maxKLimit,
|
bool forcePrimeKernelsOnCpu,
|
||||||
bool maxKConfigured,
|
ModReductionMode modReductionMode,
|
||||||
string filterFile,
|
NttBackend nttBackend,
|
||||||
bool forcePrimeKernelsOnCpu,
|
string? resultsDirectory,
|
||||||
ModReductionMode modReductionMode,
|
string? resultsPrefix,
|
||||||
NttBackend nttBackend,
|
int writeBatchSize,
|
||||||
string? resultsDirectory,
|
string cyclesPath,
|
||||||
string? resultsPrefix,
|
bool continueCyclesGeneration,
|
||||||
int writeBatchSize,
|
int cyclesBatchSize,
|
||||||
string cyclesPath,
|
ulong divisorCyclesSearchLimit,
|
||||||
bool continueCyclesGeneration,
|
ulong? orderWarmupLimitOverride,
|
||||||
int cyclesBatchSize,
|
string? rleBlacklistPath,
|
||||||
ulong divisorCyclesSearchLimit,
|
ulong rleHardMaxP,
|
||||||
ulong? orderWarmupLimitOverride,
|
bool rleOnlyLast7,
|
||||||
string? rleBlacklistPath,
|
double zeroFractionHard,
|
||||||
ulong rleHardMaxP,
|
double zeroFractionConjecture,
|
||||||
bool rleOnlyLast7,
|
int maxZeroConjecture)
|
||||||
double zeroFractionHard,
|
|
||||||
double zeroFractionConjecture,
|
|
||||||
int maxZeroConjecture,
|
|
||||||
BigInteger minK,
|
|
||||||
int gpuRatio)
|
|
||||||
{
|
{
|
||||||
StartPrime = startPrime;
|
StartPrime = startPrime;
|
||||||
StartPrimeProvided = startPrimeProvided;
|
StartPrimeProvided = startPrimeProvided;
|
||||||
@@ -134,17 +113,14 @@ internal readonly struct CliArguments
|
|||||||
UseResidue = useResidue;
|
UseResidue = useResidue;
|
||||||
UseDivisor = useDivisor;
|
UseDivisor = useDivisor;
|
||||||
UseByDivisor = useByDivisor;
|
UseByDivisor = useByDivisor;
|
||||||
ByDivisorKIncrementMode = byDivisorKIncrementMode;
|
|
||||||
ByDivisorSpecialRange = byDivisorSpecialRange;
|
|
||||||
UseGcdFilter = useGcdFilter;
|
UseGcdFilter = useGcdFilter;
|
||||||
TestMode = testMode;
|
TestMode = testMode;
|
||||||
UseGpuCycles = useGpuCycles;
|
UseGpuCycles = useGpuCycles;
|
||||||
MersenneDevice = mersenneDevice;
|
UseMersenneOnGpu = useMersenneOnGpu;
|
||||||
OrderDevice = orderDevice;
|
UseOrderOnGpu = useOrderOnGpu;
|
||||||
ScanBatchSize = scanBatchSize;
|
ScanBatchSize = scanBatchSize;
|
||||||
SliceSize = sliceSize;
|
SliceSize = sliceSize;
|
||||||
MaxKLimit = maxKLimit;
|
ResidueKMax = residueKMax;
|
||||||
MaxKConfigured = maxKConfigured;
|
|
||||||
FilterFile = filterFile;
|
FilterFile = filterFile;
|
||||||
ForcePrimeKernelsOnCpu = forcePrimeKernelsOnCpu;
|
ForcePrimeKernelsOnCpu = forcePrimeKernelsOnCpu;
|
||||||
ModReductionMode = modReductionMode;
|
ModReductionMode = modReductionMode;
|
||||||
@@ -163,8 +139,6 @@ internal readonly struct CliArguments
|
|||||||
ZeroFractionHard = zeroFractionHard;
|
ZeroFractionHard = zeroFractionHard;
|
||||||
ZeroFractionConjecture = zeroFractionConjecture;
|
ZeroFractionConjecture = zeroFractionConjecture;
|
||||||
MaxZeroConjecture = maxZeroConjecture;
|
MaxZeroConjecture = maxZeroConjecture;
|
||||||
MinK = minK;
|
|
||||||
GpuRatio = gpuRatio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool UseFilter => !TestMode && !string.IsNullOrEmpty(FilterFile);
|
internal bool UseFilter => !TestMode && !string.IsNullOrEmpty(FilterFile);
|
||||||
@@ -187,17 +161,14 @@ internal readonly struct CliArguments
|
|||||||
bool useResidue = false;
|
bool useResidue = false;
|
||||||
bool useDivisor = false;
|
bool useDivisor = false;
|
||||||
bool useByDivisor = false;
|
bool useByDivisor = false;
|
||||||
ByDivisorKIncrementMode byDivisorKIncrementMode = ByDivisorKIncrementMode.Sequential;
|
|
||||||
int byDivisorSpecialRange = 0;
|
|
||||||
bool useGcdFilter = false;
|
bool useGcdFilter = false;
|
||||||
bool testMode = false;
|
bool testMode = false;
|
||||||
bool useGpuCycles = true;
|
bool useGpuCycles = true;
|
||||||
ComputationDevice mersenneDevice = ComputationDevice.Gpu;
|
bool useMersenneOnGpu = true;
|
||||||
ComputationDevice orderDevice = ComputationDevice.Gpu;
|
bool useOrderOnGpu = true;
|
||||||
int scanBatchSize = 2_097_152;
|
int scanBatchSize = 2_097_152;
|
||||||
int sliceSize = 32;
|
int sliceSize = 32;
|
||||||
ulong maxKLimit = 5_000_000UL;
|
ulong residueKMax = 5_000_000UL;
|
||||||
bool maxKConfigured = false;
|
|
||||||
string filterFile = string.Empty;
|
string filterFile = string.Empty;
|
||||||
bool forcePrimeKernelsOnCpu = false;
|
bool forcePrimeKernelsOnCpu = false;
|
||||||
ModReductionMode modReductionMode = ModReductionMode.Auto;
|
ModReductionMode modReductionMode = ModReductionMode.Auto;
|
||||||
@@ -216,8 +187,6 @@ internal readonly struct CliArguments
|
|||||||
double zeroFractionHard = -1.0;
|
double zeroFractionHard = -1.0;
|
||||||
double zeroFractionConjecture = -1.0;
|
double zeroFractionConjecture = -1.0;
|
||||||
int maxZeroConjecture = -1;
|
int maxZeroConjecture = -1;
|
||||||
BigInteger minK = BigInteger.One;
|
|
||||||
int gpuRatio = PerfectNumberConstants.GpuRatio;
|
|
||||||
|
|
||||||
foreach (string argument in args)
|
foreach (string argument in args)
|
||||||
{
|
{
|
||||||
@@ -233,15 +202,14 @@ internal readonly struct CliArguments
|
|||||||
|
|
||||||
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> primeValue = argument.AsSpan("--prime=".Length);
|
startPrime = Utf8CliParser.ParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1));
|
||||||
startPrime = Utf8CliParser.ParseUInt64(primeValue);
|
|
||||||
startPrimeProvided = true;
|
startPrimeProvided = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> value = argument.AsSpan("--max-prime=".Length);
|
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||||
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
||||||
{
|
{
|
||||||
maxPrimeLimit = parsedMaxPrime;
|
maxPrimeLimit = parsedMaxPrime;
|
||||||
@@ -268,34 +236,15 @@ internal readonly struct CliArguments
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--gpu-ratio=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
gpuRatio = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-ratio=".Length)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--threads=".Length)));
|
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
useOrder = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--max-k=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
maxKLimit = Utf8CliParser.ParseUInt64(argument.AsSpan("--max-k=".Length));
|
|
||||||
maxKConfigured = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> value = argument.AsSpan("--mersenne=".Length);
|
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||||
if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
|
if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
kernelType = GpuKernelType.Pow2Mod;
|
kernelType = GpuKernelType.Pow2Mod;
|
||||||
@@ -330,48 +279,51 @@ internal readonly struct CliArguments
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--bydivisor-k-increment=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> value = argument.AsSpan("--bydivisor-k-increment=".Length);
|
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||||
if (value.Equals("pow2groups", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.Pow2Groups;
|
divisorCyclesSearchLimit = parsedLimit;
|
||||||
}
|
|
||||||
else if (value.Equals("predictive", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.Predictive;
|
|
||||||
}
|
|
||||||
else if (value.Equals("percentile", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.Percentile;
|
|
||||||
}
|
|
||||||
else if (value.Equals("additive", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.Additive;
|
|
||||||
}
|
|
||||||
else if (value.Equals("topdown", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.TopDown;
|
|
||||||
}
|
|
||||||
else if (value.Equals("bitcontradiction", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.BitContradiction;
|
|
||||||
}
|
|
||||||
else if (value.Equals("bittree", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.BitTree;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.Sequential;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--bydivisor-special-range=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
byDivisorSpecialRange = Utf8CliParser.ParseInt32(argument.AsSpan("--bydivisor-special-range=".Length));
|
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedResidueMax))
|
||||||
|
{
|
||||||
|
residueKMax = parsedResidueMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.Equals("--test", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
testMode = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
useOrder = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||||
|
{
|
||||||
|
orderWarmupLimitOverride = parsedLimit;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,71 +333,17 @@ internal readonly struct CliArguments
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.Equals("--test-mode", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
testMode = true;
|
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||||
continue;
|
nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
mersenneDevice = ParseDevice(argument.AsSpan("--mersenne-device=".Length));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--primes=", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
argument.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
argument.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ReadOnlySpan<char> value = argument.Contains('=') ? argument.AsSpan(argument.IndexOf('=') + 1) : argument.AsSpan();
|
|
||||||
forcePrimeKernelsOnCpu = value.Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
orderDevice = ParseDevice(argument.AsSpan("--order-device=".Length));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--gpu-prime-batch=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-prime-batch=".Length)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--gpu-prime-threads=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-prime-threads=".Length)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--ll-slice=".Length)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--gpu-scan-batch=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-scan-batch=".Length)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--block-size=".Length)));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> value = argument.AsSpan("--mod-reduction=".Length);
|
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||||
if (value.Equals("uint128", StringComparison.OrdinalIgnoreCase))
|
if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
|
||||||
modReductionMode = ModReductionMode.GpuUInt128;
|
|
||||||
}
|
|
||||||
else if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
modReductionMode = ModReductionMode.Mont64;
|
modReductionMode = ModReductionMode.Mont64;
|
||||||
}
|
}
|
||||||
@@ -453,6 +351,10 @@ internal readonly struct CliArguments
|
|||||||
{
|
{
|
||||||
modReductionMode = ModReductionMode.Barrett128;
|
modReductionMode = ModReductionMode.Barrett128;
|
||||||
}
|
}
|
||||||
|
else if (value.Equals("uint128", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
modReductionMode = ModReductionMode.GpuUInt128;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
modReductionMode = ModReductionMode.Auto;
|
modReductionMode = ModReductionMode.Auto;
|
||||||
@@ -461,116 +363,203 @@ internal readonly struct CliArguments
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> value = argument.AsSpan("--ntt=".Length);
|
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
orderWarmupLimitOverride = Utf8CliParser.ParseUInt64(argument.AsSpan("--order-warmup-limit=".Length));
|
useOrderOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
divisorCyclesSearchLimit = Utf8CliParser.ParseUInt64(argument.AsSpan("--divisor-cycles-limit=".Length));
|
rleBlacklistPath = argument[(argument.IndexOf('=') + 1)..];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--min-k=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
minK = BigInteger.Parse(argument.AsSpan("--min-k=".Length), NumberStyles.None, CultureInfo.InvariantCulture);
|
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedMax))
|
||||||
|
{
|
||||||
|
rleHardMaxP = parsedMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--rle-only-last7=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||||
|
rleOnlyLast7 = !value.Equals("false", StringComparison.OrdinalIgnoreCase) && !value.Equals("0", StringComparison.OrdinalIgnoreCase);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--zero-hard=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (Utf8CliParser.TryParseDouble(argument.AsSpan(argument.IndexOf('=') + 1), out double zeroFraction))
|
||||||
|
{
|
||||||
|
zeroFractionHard = zeroFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||||
|
int colon = value.IndexOf(':');
|
||||||
|
if (colon > 0)
|
||||||
|
{
|
||||||
|
if (Utf8CliParser.TryParseDouble(value[..colon], out double zeroFrac) &&
|
||||||
|
Utf8CliParser.TryParseInt32(value[(colon + 1)..], out int maxZero))
|
||||||
|
{
|
||||||
|
zeroFractionConjecture = zeroFrac;
|
||||||
|
maxZeroConjecture = maxZero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--primes=", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
argument.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
argument.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
resultsDirectory = argument[(argument.IndexOf('=') + 1)..];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
resultsPrefix = argument[(argument.IndexOf('=') + 1)..];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--gpu-prime-threads=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--gpu-prime-batch=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--gpu-scan-batch=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
filterFile = argument["--filter-p=".Length..];
|
filterFile = argument[(argument.IndexOf('=') + 1)..];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--write-batch-size=".Length)));
|
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
cyclesPath = argument["--divisor-cycles=".Length..];
|
cyclesPath = argument[(argument.IndexOf('=') + 1)..];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
useGpuCycles = !argument.AsSpan("--divisor-cycles-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
useGpuCycles = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase))
|
if (argument.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--divisor-cycles-batch=".Length)));
|
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument.Equals("--divisor-cycles-continue", StringComparison.OrdinalIgnoreCase))
|
if (argument.Equals("--divisor-cycles-continue", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continueCyclesGeneration = true;
|
continueCyclesGeneration = true;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CliArguments(
|
return new CliArguments(
|
||||||
startPrime,
|
startPrime,
|
||||||
startPrimeProvided,
|
startPrimeProvided,
|
||||||
maxPrimeLimit,
|
maxPrimeLimit,
|
||||||
maxPrimeConfigured,
|
maxPrimeConfigured,
|
||||||
threadCount,
|
threadCount,
|
||||||
blockSize,
|
blockSize,
|
||||||
gpuPrimeThreads,
|
gpuPrimeThreads,
|
||||||
gpuPrimeBatch,
|
gpuPrimeBatch,
|
||||||
kernelType,
|
kernelType,
|
||||||
useOrder,
|
useOrder,
|
||||||
showHelp,
|
showHelp,
|
||||||
useBitTransform,
|
useBitTransform,
|
||||||
useLucas,
|
useLucas,
|
||||||
useResidue,
|
useResidue,
|
||||||
useDivisor,
|
useDivisor,
|
||||||
useByDivisor,
|
useByDivisor,
|
||||||
useGcdFilter,
|
useGcdFilter,
|
||||||
byDivisorKIncrementMode,
|
testMode,
|
||||||
byDivisorSpecialRange,
|
useGpuCycles,
|
||||||
testMode,
|
useMersenneOnGpu,
|
||||||
useGpuCycles,
|
useOrderOnGpu,
|
||||||
mersenneDevice,
|
scanBatchSize,
|
||||||
orderDevice,
|
sliceSize,
|
||||||
scanBatchSize,
|
residueKMax,
|
||||||
sliceSize,
|
filterFile,
|
||||||
maxKLimit,
|
forcePrimeKernelsOnCpu,
|
||||||
maxKConfigured,
|
modReductionMode,
|
||||||
filterFile,
|
nttBackend,
|
||||||
forcePrimeKernelsOnCpu,
|
resultsDirectory,
|
||||||
modReductionMode,
|
resultsPrefix,
|
||||||
nttBackend,
|
writeBatchSize,
|
||||||
resultsDirectory,
|
cyclesPath,
|
||||||
resultsPrefix,
|
continueCyclesGeneration,
|
||||||
writeBatchSize,
|
cyclesBatchSize,
|
||||||
cyclesPath,
|
divisorCyclesSearchLimit,
|
||||||
continueCyclesGeneration,
|
orderWarmupLimitOverride,
|
||||||
cyclesBatchSize,
|
rleBlacklistPath,
|
||||||
divisorCyclesSearchLimit,
|
rleHardMaxP,
|
||||||
orderWarmupLimitOverride,
|
rleOnlyLast7,
|
||||||
rleBlacklistPath,
|
zeroFractionHard,
|
||||||
rleHardMaxP,
|
zeroFractionConjecture,
|
||||||
rleOnlyLast7,
|
maxZeroConjecture);
|
||||||
zeroFractionHard,
|
|
||||||
zeroFractionConjecture,
|
|
||||||
maxZeroConjecture,
|
|
||||||
minK,
|
|
||||||
gpuRatio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void PrintHelp()
|
internal static void PrintHelp()
|
||||||
@@ -578,25 +567,22 @@ internal readonly struct CliArguments
|
|||||||
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Options:");
|
Console.WriteLine("Options:");
|
||||||
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
|
||||||
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
||||||
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
|
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
|
||||||
Console.WriteLine(" --increment=bit|add exponent increment method (default add)");
|
Console.WriteLine(" --increment=bit|add exponent increment method");
|
||||||
Console.WriteLine(" --threads=<value> number of worker threads");
|
Console.WriteLine(" --threads=<value> number of worker threads");
|
||||||
|
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
||||||
Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method");
|
Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method");
|
||||||
Console.WriteLine(" --bydivisor-k-increment=sequential|pow2groups|predictive|percentile|additive|topdown|bitcontradiction|bittree k iteration strategy for --mersenne=bydivisor (default sequential)");
|
Console.WriteLine(" --residue-max-k=<value> max k for residue Mersenne test (q = 2*p*k + 1)");
|
||||||
Console.WriteLine(" --bydivisor-special-range=<n> widen pow2groups special values to +/- n percent (default 0 = points)");
|
Console.WriteLine(" --mersenne-device=cpu|gpu Device for Mersenne method (default gpu)");
|
||||||
Console.WriteLine(" --max-k=<value> max k for Mersenne tests (q = 2*p*k + 1), including --mersenne=bydivisor");
|
Console.WriteLine(" --primes-device=cpu|gpu Device for prime-scan kernels (default gpu)");
|
||||||
Console.WriteLine(" --mersenne-device=cpu|gpu|hybrid Device for Mersenne method (default gpu)");
|
Console.WriteLine(" --gpu-prime-batch=<n> Batch size for GPU primality sieve (default 262144)");
|
||||||
Console.WriteLine(" --primes-device=cpu|gpu device for prime-scan kernels (default gpu)");
|
Console.WriteLine(" --order-device=cpu|gpu Device for order computations (default gpu)");
|
||||||
Console.WriteLine($" --gpu-ratio=<n> ratio of GPU operations vs CPU in hybrid device mode (default {PerfectNumberConstants.GpuRatio})");
|
|
||||||
Console.WriteLine(" --gpu-prime-threads=<value> max concurrent GPU prime checks (default 1)");
|
|
||||||
Console.WriteLine(" --gpu-prime-batch=<n> batch size for GPU primality sieve (default 262144)");
|
|
||||||
Console.WriteLine(" --gpu-scan-batch=<value> GPU q-scan batch size (default 2_097_152)");
|
|
||||||
Console.WriteLine(" --order-device=cpu|gpu|hybrid Device for order computations (default gpu)");
|
|
||||||
Console.WriteLine(" --ll-slice=<value> Lucas–Lehmer iterations per slice (default 32)");
|
|
||||||
Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)");
|
|
||||||
Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)");
|
Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)");
|
||||||
|
Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)");
|
||||||
|
Console.WriteLine(" --gpu-prime-threads=<value> max concurrent GPU prime checks (default 1)");
|
||||||
|
Console.WriteLine(" --ll-slice=<value> Lucas–Lehmer iterations per slice (default 32)");
|
||||||
|
Console.WriteLine(" --gpu-scan-batch=<value> GPU q-scan batch size (default 2_097_152)");
|
||||||
Console.WriteLine(" --order-warmup-limit=<value> Warm-up order candidates (default 5_000_000)");
|
Console.WriteLine(" --order-warmup-limit=<value> Warm-up order candidates (default 5_000_000)");
|
||||||
Console.WriteLine(" --rle-blacklist=<path> enable RLE blacklist for p (hard filter up to --rle-hard-max)");
|
Console.WriteLine(" --rle-blacklist=<path> enable RLE blacklist for p (hard filter up to --rle-hard-max)");
|
||||||
Console.WriteLine(" --rle-hard-max=<p> apply RLE blacklist only for p <= this (default ulong.MaxValue = no limit)");
|
Console.WriteLine(" --rle-hard-max=<p> apply RLE blacklist only for p <= this (default ulong.MaxValue = no limit)");
|
||||||
@@ -614,22 +600,6 @@ internal readonly struct CliArguments
|
|||||||
Console.WriteLine(" --filter-p=<path> process only p from previous run results (required for --mersenne=bydivisor)");
|
Console.WriteLine(" --filter-p=<path> process only p from previous run results (required for --mersenne=bydivisor)");
|
||||||
Console.WriteLine(" --write-batch-size=<value> overwrite frequency of disk writes (default 100 lines)");
|
Console.WriteLine(" --write-batch-size=<value> overwrite frequency of disk writes (default 100 lines)");
|
||||||
Console.WriteLine(" --gcd-filter enable early sieve based on GCD");
|
Console.WriteLine(" --gcd-filter enable early sieve based on GCD");
|
||||||
Console.WriteLine(" --min-k=<value> starting k for --mersenne=bydivisor divisor scan (default 1)");
|
|
||||||
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ComputationDevice ParseDevice(ReadOnlySpan<char> value)
|
|
||||||
{
|
|
||||||
if (value.Equals("cpu", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ComputationDevice.Cpu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.Equals("hybrid", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ComputationDevice.Hybrid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ComputationDevice.Gpu;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -13,16 +13,7 @@
|
|||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.ML" Version="3.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.ML.FastTree" Version="3.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.ML.LightGbm" Version="3.0.1" />
|
|
||||||
<PackageReference Include="Open.Numeric" Version="3.0.1" />
|
<PackageReference Include="Open.Numeric" Version="3.0.1" />
|
||||||
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
|
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
|
||||||
<PackageReference Include="Plotly.NET" Version="3.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="MLModel1">
|
</Project>
|
||||||
<None Include="MLModel1.mlnet">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Open.Collections;
|
using Open.Collections;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
using EvenPerfectBitScanner;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.IO;
|
namespace EvenPerfectBitScanner.IO;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ internal static class CalculationResultsFile
|
|||||||
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
using StreamReader reader = new(readStream);
|
using StreamReader reader = new(readStream);
|
||||||
string? line;
|
string? line;
|
||||||
ReadOnlySpan<char> trimmed;
|
ReadOnlySpan<char> span;
|
||||||
while ((line = reader.ReadLine()) is not null)
|
while ((line = reader.ReadLine()) is not null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
@@ -21,67 +22,36 @@ internal static class CalculationResultsFile
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmed = line.AsSpan().Trim();
|
span = line.AsSpan().Trim();
|
||||||
if (trimmed.IsEmpty || trimmed[0] == '#')
|
if (span.IsEmpty || span[0] == '#')
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int length = trimmed.Length;
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
if (!maxPrimeConfigured)
|
while (index < span.Length)
|
||||||
{
|
{
|
||||||
while (index < length)
|
if (!char.IsDigit(span[index]))
|
||||||
{
|
{
|
||||||
char current = trimmed[index];
|
index++;
|
||||||
uint digit = (uint)(current - '0');
|
continue;
|
||||||
if (digit > 9U)
|
}
|
||||||
{
|
|
||||||
index++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = index++;
|
int start = index;
|
||||||
while (index < length && (uint)(trimmed[index] - '0') <= 9U)
|
index++;
|
||||||
{
|
while (index < span.Length && char.IsDigit(span[index]))
|
||||||
index++;
|
{
|
||||||
}
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed))
|
if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed))
|
||||||
|
{
|
||||||
|
if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit)
|
||||||
{
|
{
|
||||||
candidates.Add(parsed);
|
candidates.Add(parsed);
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UInt128 limit = maxPrimeLimit;
|
|
||||||
while (index < length)
|
|
||||||
{
|
|
||||||
char current = trimmed[index];
|
|
||||||
uint digit = (uint)(current - '0');
|
|
||||||
if (digit > 9U)
|
|
||||||
{
|
{
|
||||||
index++;
|
skippedByLimit++;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = index++;
|
|
||||||
while (index < length && (uint)(trimmed[index] - '0') <= 9U)
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed))
|
|
||||||
{
|
|
||||||
if ((UInt128)parsed <= limit)
|
|
||||||
{
|
|
||||||
candidates.Add(parsed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
skippedByLimit++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +70,6 @@ internal static class CalculationResultsFile
|
|||||||
int first = 0;
|
int first = 0;
|
||||||
int second = 0;
|
int second = 0;
|
||||||
int third = 0;
|
int third = 0;
|
||||||
int fourth = 0;
|
|
||||||
ulong parsedP = 0UL;
|
ulong parsedP = 0UL;
|
||||||
ReadOnlySpan<char> detailedSpan = default;
|
ReadOnlySpan<char> detailedSpan = default;
|
||||||
ReadOnlySpan<char> passedAllTestsSpan = default;
|
ReadOnlySpan<char> passedAllTestsSpan = default;
|
||||||
@@ -140,15 +109,8 @@ internal static class CalculationResultsFile
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
span = span[(third + 1)..];
|
detailedSpan = span[..third];
|
||||||
fourth = span.IndexOf(',');
|
passedAllTestsSpan = span[(third + 1)..];
|
||||||
if (fourth < 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
detailedSpan = span[..fourth];
|
|
||||||
passedAllTestsSpan = span[(fourth + 1)..];
|
|
||||||
|
|
||||||
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
|
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
// This file was auto-generated by ML.NET Model Builder.
|
|
||||||
using Microsoft.ML;
|
|
||||||
using Microsoft.ML.Data;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
namespace EvenPerfectBitScanner
|
|
||||||
{
|
|
||||||
public partial class MLModel1
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// model input class for MLModel1.
|
|
||||||
/// </summary>
|
|
||||||
#region model input class
|
|
||||||
public class ModelInput
|
|
||||||
{
|
|
||||||
[LoadColumn(0)]
|
|
||||||
[ColumnName(@"p")]
|
|
||||||
public float P { get; set; }
|
|
||||||
|
|
||||||
[LoadColumn(1)]
|
|
||||||
[ColumnName(@"divisor")]
|
|
||||||
public float Divisor { get; set; }
|
|
||||||
|
|
||||||
[LoadColumn(2)]
|
|
||||||
[ColumnName(@"lastDigit")]
|
|
||||||
public float LastDigit { get; set; }
|
|
||||||
|
|
||||||
[LoadColumn(3)]
|
|
||||||
[ColumnName(@"k")]
|
|
||||||
public float K { get; set; }
|
|
||||||
|
|
||||||
[LoadColumn(4)]
|
|
||||||
[ColumnName(@"lastMersenneDigit")]
|
|
||||||
public float LastMersenneDigit { get; set; }
|
|
||||||
|
|
||||||
[LoadColumn(5)]
|
|
||||||
[ColumnName(@"isNextKHigher")]
|
|
||||||
public float IsNextKHigher { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// model output class for MLModel1.
|
|
||||||
/// </summary>
|
|
||||||
#region model output class
|
|
||||||
public class ModelOutput
|
|
||||||
{
|
|
||||||
[ColumnName(@"p")]
|
|
||||||
public float P { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"divisor")]
|
|
||||||
public float Divisor { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"lastDigit")]
|
|
||||||
public float LastDigit { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"k")]
|
|
||||||
public float K { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"lastMersenneDigit")]
|
|
||||||
public float LastMersenneDigit { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"isNextKHigher")]
|
|
||||||
public float IsNextKHigher { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"Features")]
|
|
||||||
public float[] Features { get; set; }
|
|
||||||
|
|
||||||
[ColumnName(@"Score")]
|
|
||||||
public float Score { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private static string MLNetModelPath = Path.GetFullPath("MLModel1.mlnet");
|
|
||||||
|
|
||||||
public static readonly Lazy<PredictionEngine<ModelInput, ModelOutput>> PredictEngine = new Lazy<PredictionEngine<ModelInput, ModelOutput>>(() => CreatePredictEngine(), true);
|
|
||||||
|
|
||||||
|
|
||||||
private static PredictionEngine<ModelInput, ModelOutput> CreatePredictEngine()
|
|
||||||
{
|
|
||||||
var mlContext = new MLContext();
|
|
||||||
ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _);
|
|
||||||
return mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Use this method to predict on <see cref="ModelInput"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">model input.</param>
|
|
||||||
/// <returns><seealso cref=" ModelOutput"/></returns>
|
|
||||||
public static ModelOutput Predict(ModelInput input)
|
|
||||||
{
|
|
||||||
var predEngine = PredictEngine.Value;
|
|
||||||
return predEngine.Predict(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// This file was auto-generated by ML.NET Model Builder.
|
|
||||||
|
|
||||||
using Microsoft.ML.Data;
|
|
||||||
using Microsoft.ML;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Plotly.NET;
|
|
||||||
using Plotly.NET.LayoutObjects;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner
|
|
||||||
{
|
|
||||||
public partial class MLModel1
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Permutation feature importance (PFI) is a technique to determine the importance
|
|
||||||
/// of features in a trained machine learning model. PFI works by taking a labeled dataset,
|
|
||||||
/// choosing a feature, and permuting the values for that feature across all the examples,
|
|
||||||
/// so that each example now has a random value for the feature and the original values for all other features.
|
|
||||||
/// The evaluation metric (e.g. R-squared) is then calculated for this modified dataset,
|
|
||||||
/// and the change in the evaluation metric from the original dataset is computed.
|
|
||||||
/// The larger the change in the evaluation metric, the more important the feature is to the model.
|
|
||||||
///
|
|
||||||
/// PFI typically takes a long time to compute, as the evaluation metric is calculated
|
|
||||||
/// many times to determine the importance of each feature.
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mlContext">The common context for all ML.NET operations.</param>
|
|
||||||
/// <param name="trainData">IDataView used to evaluate the model.</param>
|
|
||||||
/// <param name="model">Model to evaluate.</param>
|
|
||||||
/// <param name="labelColumnName">Label column being predicted.</param>
|
|
||||||
/// <returns>A list of each feature and its importance.</returns>
|
|
||||||
public static List<Tuple<string, double>> CalculatePFI(MLContext mlContext, IDataView trainData, ITransformer model, string labelColumnName)
|
|
||||||
{
|
|
||||||
var preprocessedTrainData = model.Transform(trainData);
|
|
||||||
|
|
||||||
var permutationFeatureImportance =
|
|
||||||
mlContext.Regression
|
|
||||||
.PermutationFeatureImportance(
|
|
||||||
model,
|
|
||||||
preprocessedTrainData,
|
|
||||||
labelColumnName: labelColumnName);
|
|
||||||
|
|
||||||
var featureImportanceMetrics =
|
|
||||||
permutationFeatureImportance
|
|
||||||
.Select((kvp) => new { kvp.Key, kvp.Value.RSquared })
|
|
||||||
.OrderByDescending(myFeatures => Math.Abs(myFeatures.RSquared.Mean));
|
|
||||||
|
|
||||||
var featurePFI = new List<Tuple<string, double>>();
|
|
||||||
foreach (var feature in featureImportanceMetrics)
|
|
||||||
{
|
|
||||||
var pfiValue = Math.Abs(feature.RSquared.Mean);
|
|
||||||
featurePFI.Add(new Tuple<string, double>(feature.Key, pfiValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
return featurePFI;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// R Squared is a measure of variation between the values predicted by the model and the true values.
|
|
||||||
/// In a "perfect" model, there would be no variation between predictions and true values.
|
|
||||||
///
|
|
||||||
/// Here we will plot the predicted values vs the true values for the trained model. This RegressionChart.html
|
|
||||||
/// is then saved to the location specified by <param name="folderPath"></param>.
|
|
||||||
///
|
|
||||||
/// See more information on R Squared at https://en.wikipedia.org/wiki/Coefficient_of_determination.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trainData">IDataView used to train the model.</param>
|
|
||||||
/// <param name="model">Model used for predictions.</param>
|
|
||||||
/// <param name="labelColumnName">Name of the predicted label column.</param>
|
|
||||||
/// <param name="folderPath">Folder path to save the RegressionChart.html file into.</param>
|
|
||||||
public static void PlotRSquaredValues(IDataView trainData, ITransformer model, string labelColumnName, string folderPath)
|
|
||||||
{
|
|
||||||
// Number of rows to display in charts.
|
|
||||||
int numberOfRows = 1000;
|
|
||||||
// Use the model to make batch predictions on training data
|
|
||||||
var testResults = model.Transform(trainData);
|
|
||||||
|
|
||||||
// Get the actual values from the dataset
|
|
||||||
var trueValues = testResults.GetColumn<float>(labelColumnName).Take(numberOfRows); ;
|
|
||||||
|
|
||||||
// Get the predicted values from the test results
|
|
||||||
var predictedValues = testResults.GetColumn<float>("Score").Take(numberOfRows);
|
|
||||||
|
|
||||||
// Setup what the graph looks like
|
|
||||||
var title = Title.init(Text: "R-Squared Plot");
|
|
||||||
var layout = Layout.init<IConvertible>(Title: title, PlotBGColor: Plotly.NET.Color.fromString("#e5ecf6"));
|
|
||||||
var xAxis = LinearAxis.init<IConvertible, IConvertible, IConvertible, IConvertible, IConvertible, IConvertible>(
|
|
||||||
Title: Title.init("True Values"),
|
|
||||||
ZeroLineColor: Plotly.NET.Color.fromString("#ffff"),
|
|
||||||
GridColor: Plotly.NET.Color.fromString("#ffff"),
|
|
||||||
ZeroLineWidth: 2);
|
|
||||||
var yAxis = LinearAxis.init<IConvertible, IConvertible, IConvertible, IConvertible, IConvertible, IConvertible>(
|
|
||||||
Title: Title.init("Predicted Values"),
|
|
||||||
ZeroLineColor: Plotly.NET.Color.fromString("#ffff"),
|
|
||||||
GridColor: Plotly.NET.Color.fromString("#ffff"),
|
|
||||||
ZeroLineWidth: 2);
|
|
||||||
|
|
||||||
// We will plot the line that shows the perfect result. Setup that line here.
|
|
||||||
var maximumValue = Math.Max(trueValues.Max(), predictedValues.Max());
|
|
||||||
var perfectX = new[] { 0, maximumValue };
|
|
||||||
var perfectY = new[] { 0, maximumValue };
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create the scatterplot that shows the true values vs the predicted values
|
|
||||||
var trueAndPredictedValues = Chart2D.Chart.Scatter<float, float, string>(x: trueValues, y: predictedValues, mode: StyleParam.Mode.Markers)
|
|
||||||
.WithLayout(layout)
|
|
||||||
.WithXAxis(xAxis)
|
|
||||||
.WithYAxis(yAxis);
|
|
||||||
|
|
||||||
// Setup the line that shows what a perfect prediction would look like
|
|
||||||
var perfectLineGraph = Chart2D.Chart.Line<float, float, string>(x: perfectX, y: perfectY)
|
|
||||||
.WithLayout(layout)
|
|
||||||
.WithLine(Line.init(Width: 1.5));
|
|
||||||
|
|
||||||
var chartWithValuesAndIdealLine = Chart.Combine(new[] { trueAndPredictedValues, perfectLineGraph });
|
|
||||||
var chartFilePath = folderPath + "\\RegressionChart.html";
|
|
||||||
|
|
||||||
chartWithValuesAndIdealLine.SaveHtml(chartFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
{
|
|
||||||
"Scenario": "Regression",
|
|
||||||
"DataSource": {
|
|
||||||
"Version": 3,
|
|
||||||
"EscapeCharacter": "\"",
|
|
||||||
"ReadMultiLines": false,
|
|
||||||
"KeepDiacritics": false,
|
|
||||||
"KeepPunctuations": false,
|
|
||||||
"AllowQuoting": false,
|
|
||||||
"Type": "TabularFile",
|
|
||||||
"FilePath": "C:\\Users\\marek\\source\\repos\\even-perfect-numbers-scanner\\BuildDivisorTransitions\\bin\\Release\\net8.0\\NextKForecastTraning.csv",
|
|
||||||
"Delimiter": ",",
|
|
||||||
"DecimalMarker": ".",
|
|
||||||
"HasHeader": true,
|
|
||||||
"ColumnProperties": [
|
|
||||||
{
|
|
||||||
"ColumnName": "p",
|
|
||||||
"ColumnPurpose": "Feature",
|
|
||||||
"ColumnDataFormat": "Single",
|
|
||||||
"IsCategorical": false,
|
|
||||||
"Type": "Column",
|
|
||||||
"Version": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ColumnName": "divisor",
|
|
||||||
"ColumnPurpose": "Feature",
|
|
||||||
"ColumnDataFormat": "Single",
|
|
||||||
"IsCategorical": false,
|
|
||||||
"Type": "Column",
|
|
||||||
"Version": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ColumnName": "lastDigit",
|
|
||||||
"ColumnPurpose": "Feature",
|
|
||||||
"ColumnDataFormat": "Single",
|
|
||||||
"IsCategorical": true,
|
|
||||||
"Type": "Column",
|
|
||||||
"Version": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ColumnName": "k",
|
|
||||||
"ColumnPurpose": "Feature",
|
|
||||||
"ColumnDataFormat": "Single",
|
|
||||||
"IsCategorical": false,
|
|
||||||
"Type": "Column",
|
|
||||||
"Version": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ColumnName": "lastMersenneDigit",
|
|
||||||
"ColumnPurpose": "Feature",
|
|
||||||
"ColumnDataFormat": "Single",
|
|
||||||
"IsCategorical": true,
|
|
||||||
"Type": "Column",
|
|
||||||
"Version": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ColumnName": "isNextKHigher",
|
|
||||||
"ColumnPurpose": "Label",
|
|
||||||
"ColumnDataFormat": "Single",
|
|
||||||
"IsCategorical": true,
|
|
||||||
"Type": "Column",
|
|
||||||
"Version": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Environment": {
|
|
||||||
"Type": "LocalCPU",
|
|
||||||
"Version": 1
|
|
||||||
},
|
|
||||||
"RunHistory": {
|
|
||||||
"Version": 3,
|
|
||||||
"Type": "Result",
|
|
||||||
"Trials": [
|
|
||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"Type": "Trial",
|
|
||||||
"TrainerName": "FastTreeRegression",
|
|
||||||
"Score": 0.30006118613190746,
|
|
||||||
"RuntimeInSeconds": 1.489,
|
|
||||||
"Parameter": {
|
|
||||||
"_SCHEMA_": "e0 * e1 * e2",
|
|
||||||
"e0": {
|
|
||||||
"OutputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"e1": {
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"OutputColumnName": "Features"
|
|
||||||
},
|
|
||||||
"e2": {
|
|
||||||
"NumberOfLeaves": 4,
|
|
||||||
"MinimumExampleCountPerLeaf": 9,
|
|
||||||
"NumberOfTrees": 8,
|
|
||||||
"MaximumBinCountPerFeature": 317,
|
|
||||||
"FeatureFraction": 0.98434331463123226,
|
|
||||||
"LearningRate": 0.3857390791447961,
|
|
||||||
"LabelColumnName": "isNextKHigher",
|
|
||||||
"FeatureColumnName": "Features",
|
|
||||||
"DiskTranspose": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"Type": "Trial",
|
|
||||||
"TrainerName": "FastTreeRegression",
|
|
||||||
"Score": 0.299969238277351,
|
|
||||||
"RuntimeInSeconds": 1.496,
|
|
||||||
"Parameter": {
|
|
||||||
"_SCHEMA_": "e0 * e1 * e2",
|
|
||||||
"e0": {
|
|
||||||
"OutputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"e1": {
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"OutputColumnName": "Features"
|
|
||||||
},
|
|
||||||
"e2": {
|
|
||||||
"NumberOfLeaves": 4,
|
|
||||||
"MinimumExampleCountPerLeaf": 9,
|
|
||||||
"NumberOfTrees": 8,
|
|
||||||
"MaximumBinCountPerFeature": 317,
|
|
||||||
"FeatureFraction": 0.98444606904931908,
|
|
||||||
"LearningRate": 0.38567641892007121,
|
|
||||||
"LabelColumnName": "isNextKHigher",
|
|
||||||
"FeatureColumnName": "Features",
|
|
||||||
"DiskTranspose": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"Type": "Trial",
|
|
||||||
"TrainerName": "FastTreeRegression",
|
|
||||||
"Score": 0.29992424440705012,
|
|
||||||
"RuntimeInSeconds": 0.993,
|
|
||||||
"Parameter": {
|
|
||||||
"_SCHEMA_": "e0 * e1 * e2",
|
|
||||||
"e0": {
|
|
||||||
"OutputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"e1": {
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"OutputColumnName": "Features"
|
|
||||||
},
|
|
||||||
"e2": {
|
|
||||||
"NumberOfLeaves": 4,
|
|
||||||
"MinimumExampleCountPerLeaf": 9,
|
|
||||||
"NumberOfTrees": 8,
|
|
||||||
"MaximumBinCountPerFeature": 317,
|
|
||||||
"FeatureFraction": 0.98445819026817927,
|
|
||||||
"LearningRate": 0.38613186537816252,
|
|
||||||
"LabelColumnName": "isNextKHigher",
|
|
||||||
"FeatureColumnName": "Features",
|
|
||||||
"DiskTranspose": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"Type": "Trial",
|
|
||||||
"TrainerName": "FastTreeRegression",
|
|
||||||
"Score": 0.29992046748552242,
|
|
||||||
"RuntimeInSeconds": 1.389,
|
|
||||||
"Parameter": {
|
|
||||||
"_SCHEMA_": "e0 * e1 * e2",
|
|
||||||
"e0": {
|
|
||||||
"OutputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"e1": {
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"OutputColumnName": "Features"
|
|
||||||
},
|
|
||||||
"e2": {
|
|
||||||
"NumberOfLeaves": 4,
|
|
||||||
"MinimumExampleCountPerLeaf": 9,
|
|
||||||
"NumberOfTrees": 8,
|
|
||||||
"MaximumBinCountPerFeature": 317,
|
|
||||||
"FeatureFraction": 0.984395476164661,
|
|
||||||
"LearningRate": 0.3858463322510558,
|
|
||||||
"LabelColumnName": "isNextKHigher",
|
|
||||||
"FeatureColumnName": "Features",
|
|
||||||
"DiskTranspose": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Version": 1,
|
|
||||||
"Type": "Trial",
|
|
||||||
"TrainerName": "FastTreeRegression",
|
|
||||||
"Score": 0.29984566377505206,
|
|
||||||
"RuntimeInSeconds": 1.512,
|
|
||||||
"Parameter": {
|
|
||||||
"_SCHEMA_": "e0 * e1 * e2",
|
|
||||||
"e0": {
|
|
||||||
"OutputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"e1": {
|
|
||||||
"InputColumnNames": [
|
|
||||||
"p",
|
|
||||||
"divisor",
|
|
||||||
"lastDigit",
|
|
||||||
"k",
|
|
||||||
"lastMersenneDigit"
|
|
||||||
],
|
|
||||||
"OutputColumnName": "Features"
|
|
||||||
},
|
|
||||||
"e2": {
|
|
||||||
"NumberOfLeaves": 4,
|
|
||||||
"MinimumExampleCountPerLeaf": 8,
|
|
||||||
"NumberOfTrees": 8,
|
|
||||||
"MaximumBinCountPerFeature": 317,
|
|
||||||
"FeatureFraction": 0.98439134546037266,
|
|
||||||
"LearningRate": 0.38521733136295422,
|
|
||||||
"LabelColumnName": "isNextKHigher",
|
|
||||||
"FeatureColumnName": "Features",
|
|
||||||
"DiskTranspose": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Estimators": {
|
|
||||||
"e0": "ReplaceMissingValues",
|
|
||||||
"e1": "Concatenate",
|
|
||||||
"e2": "FastTreeRegression",
|
|
||||||
"e3": "FastForestRegression",
|
|
||||||
"e4": "LightGbmRegression",
|
|
||||||
"e5": "LbfgsPoissonRegressionRegression",
|
|
||||||
"e6": "SdcaRegression"
|
|
||||||
},
|
|
||||||
"Schema": "e0 * e1 * (e2 + e3 + e4 + e5 + e6)",
|
|
||||||
"MetricName": "RSquared",
|
|
||||||
"ModelFilePath": "C:\\Users\\marek\\source\\repos\\even-perfect-numbers-scanner\\EvenPerfectBitScanner\\MLModel1.mlnet"
|
|
||||||
},
|
|
||||||
"Type": "TrainingConfig",
|
|
||||||
"Version": 5,
|
|
||||||
"TrainingOption": {
|
|
||||||
"Version": 2,
|
|
||||||
"Subsampling": false,
|
|
||||||
"Type": "RegressionTrainingOption",
|
|
||||||
"TrainingTime": 3600,
|
|
||||||
"LabelColumn": "isNextKHigher",
|
|
||||||
"ValidationOption": {
|
|
||||||
"Version": 0,
|
|
||||||
"Type": "CrossValidationValidationOption",
|
|
||||||
"NumberOfFolds": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,100 +0,0 @@
|
|||||||
// This file was auto-generated by ML.NET Model Builder.
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.ML;
|
|
||||||
using Microsoft.ML.Data;
|
|
||||||
using Microsoft.ML.Trainers;
|
|
||||||
using Microsoft.ML.Trainers.FastTree;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner
|
|
||||||
{
|
|
||||||
public partial class MLModel1
|
|
||||||
{
|
|
||||||
public const string RetrainFilePath = @"C:\Users\marek\source\repos\even-perfect-numbers-scanner\BuildDivisorTransitions\bin\Release\net8.0\NextKForecastTraning.csv";
|
|
||||||
public const char RetrainSeparatorChar = ',';
|
|
||||||
public const bool RetrainHasHeader = true;
|
|
||||||
public const bool RetrainAllowQuoting = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Train a new model with the provided dataset.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputModelPath">File path for saving the model. Should be similar to "C:\YourPath\ModelName.mlnet"</param>
|
|
||||||
/// <param name="inputDataFilePath">Path to the data file for training.</param>
|
|
||||||
/// <param name="separatorChar">Separator character for delimited training file.</param>
|
|
||||||
/// <param name="hasHeader">Boolean if training file has a header.</param>
|
|
||||||
public static void Train(string outputModelPath, string inputDataFilePath = RetrainFilePath, char separatorChar = RetrainSeparatorChar, bool hasHeader = RetrainHasHeader, bool allowQuoting = RetrainAllowQuoting)
|
|
||||||
{
|
|
||||||
var mlContext = new MLContext();
|
|
||||||
|
|
||||||
var data = LoadIDataViewFromFile(mlContext, inputDataFilePath, separatorChar, hasHeader, allowQuoting);
|
|
||||||
var model = RetrainModel(mlContext, data);
|
|
||||||
SaveModel(mlContext, model, data, outputModelPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load an IDataView from a file path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mlContext">The common context for all ML.NET operations.</param>
|
|
||||||
/// <param name="inputDataFilePath">Path to the data file for training.</param>
|
|
||||||
/// <param name="separatorChar">Separator character for delimited training file.</param>
|
|
||||||
/// <param name="hasHeader">Boolean if training file has a header.</param>
|
|
||||||
/// <returns>IDataView with loaded training data.</returns>
|
|
||||||
public static IDataView LoadIDataViewFromFile(MLContext mlContext, string inputDataFilePath, char separatorChar, bool hasHeader, bool allowQuoting)
|
|
||||||
{
|
|
||||||
return mlContext.Data.LoadFromTextFile<ModelInput>(inputDataFilePath, separatorChar, hasHeader, allowQuoting: allowQuoting);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save a model at the specified path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mlContext">The common context for all ML.NET operations.</param>
|
|
||||||
/// <param name="model">Model to save.</param>
|
|
||||||
/// <param name="data">IDataView used to train the model.</param>
|
|
||||||
/// <param name="modelSavePath">File path for saving the model. Should be similar to "C:\YourPath\ModelName.mlnet.</param>
|
|
||||||
public static void SaveModel(MLContext mlContext, ITransformer model, IDataView data, string modelSavePath)
|
|
||||||
{
|
|
||||||
// Pull the data schema from the IDataView used for training the model
|
|
||||||
DataViewSchema dataViewSchema = data.Schema;
|
|
||||||
|
|
||||||
using (var fs = File.Create(modelSavePath))
|
|
||||||
{
|
|
||||||
mlContext.Model.Save(model, dataViewSchema, fs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrain model using the pipeline generated as part of the training process.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mlContext"></param>
|
|
||||||
/// <param name="trainData"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static ITransformer RetrainModel(MLContext mlContext, IDataView trainData)
|
|
||||||
{
|
|
||||||
var pipeline = BuildPipeline(mlContext);
|
|
||||||
var model = pipeline.Fit(trainData);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// build the pipeline that is used from model builder. Use this function to retrain model.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mlContext"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static IEstimator<ITransformer> BuildPipeline(MLContext mlContext)
|
|
||||||
{
|
|
||||||
// Data process configuration with pipeline data transformations
|
|
||||||
var pipeline = mlContext.Transforms.ReplaceMissingValues(new []{new InputOutputColumnPair(@"p", @"p"),new InputOutputColumnPair(@"divisor", @"divisor"),new InputOutputColumnPair(@"lastDigit", @"lastDigit"),new InputOutputColumnPair(@"k", @"k"),new InputOutputColumnPair(@"lastMersenneDigit", @"lastMersenneDigit")})
|
|
||||||
.Append(mlContext.Transforms.Concatenate(@"Features", new []{@"p",@"divisor",@"lastDigit",@"k",@"lastMersenneDigit"}))
|
|
||||||
.Append(mlContext.Regression.Trainers.FastTree(new FastTreeRegressionTrainer.Options(){NumberOfLeaves=4,MinimumExampleCountPerLeaf=9,NumberOfTrees=8,MaximumBinCountPerFeature=317,FeatureFraction=0.9843433146312323,LearningRate=0.3857390791447961,LabelColumnName=@"isNextKHigher",FeatureColumnName=@"Features",DiskTranspose=false}));
|
|
||||||
|
|
||||||
return pipeline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Text;
|
using System.Buffers.Text;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -29,26 +30,11 @@ internal static class Utf8CliParser
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint ParseUInt32(ReadOnlySpan<char> value)
|
|
||||||
{
|
|
||||||
if (!TryParseUInt32(value, out uint result))
|
|
||||||
{
|
|
||||||
ThrowFormatException(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseUInt64(ReadOnlySpan<char> value, out ulong result)
|
public static bool TryParseUInt64(ReadOnlySpan<char> value, out ulong result)
|
||||||
{
|
{
|
||||||
return TryParse(value, static (ReadOnlySpan<byte> source, out ulong parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result);
|
return TryParse(value, static (ReadOnlySpan<byte> source, out ulong parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseUInt32(ReadOnlySpan<char> value, out uint result)
|
|
||||||
{
|
|
||||||
return TryParse(value, static (ReadOnlySpan<byte> source, out uint parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseInt32(ReadOnlySpan<char> value, out int result)
|
public static bool TryParseInt32(ReadOnlySpan<char> value, out int result)
|
||||||
{
|
{
|
||||||
return TryParse(value, static (ReadOnlySpan<byte> source, out int parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result);
|
return TryParse(value, static (ReadOnlySpan<byte> source, out int parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result);
|
||||||
|
|||||||
+27
-122
@@ -1,5 +1,4 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.0.31903.59
|
VisualStudioVersion = 17.0.31903.59
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
@@ -15,132 +14,38 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Resul
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Benchmarks", "EvenPerfectBitScanner.Benchmarks\EvenPerfectBitScanner.Benchmarks.csproj", "{83551FCC-41FA-4162-B541-AFCB5666A29F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Benchmarks", "EvenPerfectBitScanner.Benchmarks\EvenPerfectBitScanner.Benchmarks.csproj", "{83551FCC-41FA-4162-B541-AFCB5666A29F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildDivisorTransitions", "BuildDivisorTransitions\BuildDivisorTransitions.csproj", "{0A936837-5082-4C0B-AC3B-840AD2E11057}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceDependentGenerator", "DeviceDependentGenerator\DeviceDependentGenerator.csproj", "{E900CE3B-0DA4-4D7A-B825-851D6227ED67}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceDependentGenerator.Tests", "DeviceDependentGenerator.Tests\DeviceDependentGenerator.Tests.csproj", "{2C632996-9606-4CB5-B046-38AB91D6F6DC}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Debug|x86 = Debug|x86
|
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using FluentAssertions;
|
|
||||||
using PerfectNumbers.Core.ByDivisor;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
|
||||||
|
|
||||||
public class BitContradictionIntegrationTests
|
|
||||||
{
|
|
||||||
// 2^11 - 1 = 2047 = 23 * 89
|
|
||||||
[Fact]
|
|
||||||
public void BitContradictionAcceptsKnownDivisorWithDecision()
|
|
||||||
{
|
|
||||||
BigInteger divisor = 23;
|
|
||||||
ulong p = 11;
|
|
||||||
bool decided = Invoke(divisor, p, out bool divides);
|
|
||||||
decided.Should().BeTrue("small p should be decided exactly");
|
|
||||||
divides.Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BitContradictionRejectsEvenDivisorQuickly()
|
|
||||||
{
|
|
||||||
BigInteger divisor = 24;
|
|
||||||
ulong p = 11;
|
|
||||||
bool decided = Invoke(divisor, p, out bool divides);
|
|
||||||
decided.Should().BeTrue();
|
|
||||||
divides.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BitContradictionRejectsTooLongDivisor()
|
|
||||||
{
|
|
||||||
// q bit length >= p => impossible
|
|
||||||
BigInteger divisor = (BigInteger.One << 15) + 1;
|
|
||||||
ulong p = 11;
|
|
||||||
bool decided = Invoke(divisor, p, out bool divides);
|
|
||||||
decided.Should().BeTrue();
|
|
||||||
divides.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BitContradictionRejectsNonDivisorWithDecision()
|
|
||||||
{
|
|
||||||
// 2^11 - 1 is not divisible by 25
|
|
||||||
BigInteger divisor = 25;
|
|
||||||
ulong p = 11;
|
|
||||||
bool decided = Invoke(divisor, p, out bool divides);
|
|
||||||
decided.Should().BeTrue("small p should be decided exactly");
|
|
||||||
divides.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool Invoke(BigInteger divisor, ulong p, out bool divides)
|
|
||||||
{
|
|
||||||
int bitLen = GetBitLength(divisor);
|
|
||||||
Span<int> offsets = stackalloc int[bitLen];
|
|
||||||
int count = 0;
|
|
||||||
for (int i = 0; i < bitLen; i++)
|
|
||||||
{
|
|
||||||
if (((divisor >> i) & BigInteger.One) == BigInteger.One)
|
|
||||||
{
|
|
||||||
offsets[count++] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(offsets[..count], p, out divides);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetBitLength(BigInteger value)
|
|
||||||
{
|
|
||||||
if (value.IsZero)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)Math.Floor(BigInteger.Log(value, 2.0)) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using PerfectNumbers.Core.ByDivisor;
|
|
||||||
using Xunit;
|
|
||||||
using static PerfectNumbers.Core.ByDivisor.BitContradictionSolverWithAMultiplier;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
|
||||||
|
|
||||||
public class BitContradictionSolverTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void ComputeColumnBounds_TracksForcedAndPossibleOnes()
|
|
||||||
{
|
|
||||||
bool[] a = [true, false];
|
|
||||||
bool[] q = [true, true];
|
|
||||||
|
|
||||||
ColumnBounds bounds = ComputeColumnBounds(a, q, 1);
|
|
||||||
|
|
||||||
bounds.ForcedOnes.Should().Be(1);
|
|
||||||
bounds.PossibleOnes.Should().Be(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TryPropagateCarry_FailsWhenParityUnreachable()
|
|
||||||
{
|
|
||||||
CarryRange carry = CarryRange.Single(0);
|
|
||||||
bool success = TryPropagateCarry(
|
|
||||||
currentCarry: ref carry,
|
|
||||||
forcedOnes: 0,
|
|
||||||
possibleOnes: 0,
|
|
||||||
requiredResultBit: 1);
|
|
||||||
|
|
||||||
success.Should().BeFalse();
|
|
||||||
carry.Min.Should().Be(0);
|
|
||||||
carry.Max.Should().Be(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TryPropagateCarry_ComputesNextCarryRange()
|
|
||||||
{
|
|
||||||
CarryRange carry = new(0, 1);
|
|
||||||
bool success = TryPropagateCarry(
|
|
||||||
currentCarry: ref carry,
|
|
||||||
forcedOnes: 1,
|
|
||||||
possibleOnes: 3,
|
|
||||||
requiredResultBit: 1);
|
|
||||||
|
|
||||||
success.Should().BeTrue();
|
|
||||||
carry.Min.Should().Be(0);
|
|
||||||
carry.Max.Should().Be(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using FluentAssertions;
|
|
||||||
using PerfectNumbers.Core.Cpu;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
|
||||||
|
|
||||||
public class BitTreeSolverTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void BitTree_FindsKnownDivisor()
|
|
||||||
{
|
|
||||||
// 2^11-1 = 2047 = 23 * 89, so k=1 yields divisor 23.
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTesterWithForBitTreeDivisorSetForCpuOrder
|
|
||||||
{
|
|
||||||
DivisorLimit = new BigInteger(1000),
|
|
||||||
MinK = BigInteger.One,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool isPrime = tester.IsPrime(11, out bool exhausted, out BigInteger divisor);
|
|
||||||
|
|
||||||
isPrime.Should().BeFalse();
|
|
||||||
exhausted.Should().BeTrue();
|
|
||||||
divisor.Should().Be(23);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BitTree_ReportsPrimeWhenNoDivisorFound()
|
|
||||||
{
|
|
||||||
// 2^5-1 = 31 is prime.
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTesterWithForBitTreeDivisorSetForCpuOrder
|
|
||||||
{
|
|
||||||
DivisorLimit = new BigInteger(10_000),
|
|
||||||
MinK = BigInteger.One,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool isPrime = tester.IsPrime(5, out bool exhausted, out BigInteger divisor);
|
|
||||||
|
|
||||||
isPrime.Should().BeTrue();
|
|
||||||
exhausted.Should().BeTrue();
|
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using FluentAssertions;
|
|
||||||
using PerfectNumbers.Core.ByDivisor;
|
|
||||||
using Xunit;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
|
||||||
|
|
||||||
public sealed class ByDivisorClassModelTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void LoadOrBuild_builds_model_from_results_file()
|
|
||||||
{
|
|
||||||
string tempPath = Path.GetTempFileName();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.WriteAllLines(
|
|
||||||
tempPath,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"p,divisor,searchedMersenne,detailedCheck,passedAllTests",
|
|
||||||
"5,31,true,true,false",
|
|
||||||
"5,0,true,true,true",
|
|
||||||
"13,8191,true,true,false",
|
|
||||||
});
|
|
||||||
|
|
||||||
string modelPath = tempPath + ".model";
|
|
||||||
ByDivisorScanTuning tuning = new()
|
|
||||||
{
|
|
||||||
CheapLimit = 32,
|
|
||||||
};
|
|
||||||
|
|
||||||
ByDivisorClassModel model = ByDivisorClassModelLoader.LoadOrBuild(modelPath, tempPath, tuning, tuning.CheapLimit);
|
|
||||||
|
|
||||||
ByDivisorClassEntry classFive = model.Entries[5];
|
|
||||||
classFive.K50.Should().Be(3d);
|
|
||||||
classFive.K75.Should().Be(3d);
|
|
||||||
classFive.GapProbability.Should().BeApproximately(0.5d, 1e-12);
|
|
||||||
classFive.SampleCount.Should().Be(2);
|
|
||||||
classFive.GapCount.Should().Be(1);
|
|
||||||
|
|
||||||
ByDivisorClassEntry classThirteen = model.Entries[13];
|
|
||||||
classThirteen.K50.Should().Be(315d);
|
|
||||||
classThirteen.K75.Should().Be(315d);
|
|
||||||
classThirteen.GapProbability.Should().Be(0d);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
TryDelete(tempPath);
|
|
||||||
TryDelete(tempPath + ".model");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BuildPlan_uses_aggressive_tuning_for_hard_class()
|
|
||||||
{
|
|
||||||
var model = new ByDivisorClassModel
|
|
||||||
{
|
|
||||||
CheapLimit = 50,
|
|
||||||
};
|
|
||||||
model.Entries[0] = new ByDivisorClassEntry
|
|
||||||
{
|
|
||||||
K50 = 1000d,
|
|
||||||
K75 = 2000d,
|
|
||||||
GapProbability = 0.8d,
|
|
||||||
Stability = "escaping",
|
|
||||||
};
|
|
||||||
|
|
||||||
ByDivisorScanTuning tuning = new()
|
|
||||||
{
|
|
||||||
CheapLimit = 50,
|
|
||||||
Gamma50 = 0.8d,
|
|
||||||
Gamma75 = 1.2d,
|
|
||||||
AggressiveGamma50 = 1.0d,
|
|
||||||
AggressiveGamma75 = 1.4d,
|
|
||||||
Rho1 = 1.35d,
|
|
||||||
Rho2 = 1.6d,
|
|
||||||
AggressiveRho2 = 1.8d,
|
|
||||||
HardGapThreshold = 0.75d,
|
|
||||||
};
|
|
||||||
|
|
||||||
ByDivisorScanPlan plan = model.BuildPlan(2UL, BigInteger.Zero, tuning);
|
|
||||||
|
|
||||||
plan.CheapLimit.Should().Be(50d);
|
|
||||||
plan.Start.Should().Be(1000d);
|
|
||||||
plan.Target.Should().Be(2800d);
|
|
||||||
plan.Rho1.Should().Be(1.35d);
|
|
||||||
plan.Rho2.Should().Be(1.8d);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BuildPlan_exposes_percentiles_and_additive_delta()
|
|
||||||
{
|
|
||||||
var model = new ByDivisorClassModel
|
|
||||||
{
|
|
||||||
CheapLimit = 10_000,
|
|
||||||
};
|
|
||||||
var entries = new ByDivisorClassEntry[1024];
|
|
||||||
entries[0] = new ByDivisorClassEntry
|
|
||||||
{
|
|
||||||
K10 = 10d,
|
|
||||||
K25 = 20d,
|
|
||||||
K50 = 30d,
|
|
||||||
K75 = 40d,
|
|
||||||
GapProbability = 0.1d,
|
|
||||||
Stability = "stable",
|
|
||||||
};
|
|
||||||
model.Entries = entries;
|
|
||||||
|
|
||||||
ByDivisorScanTuning tuning = ByDivisorScanTuning.Default;
|
|
||||||
|
|
||||||
ByDivisorScanPlan plan = model.BuildPlan(0UL, BigInteger.Zero, tuning);
|
|
||||||
|
|
||||||
plan.K10.Should().Be(10d);
|
|
||||||
plan.K25.Should().Be(20d);
|
|
||||||
plan.K50.Should().Be(30d);
|
|
||||||
plan.K75.Should().Be(40d);
|
|
||||||
plan.Start.Should().Be(24d); // ceil(0.8 * 30)
|
|
||||||
plan.Target.Should().Be(48d); // ceil(1.2 * 40)
|
|
||||||
plan.DeltaK.Should().Be(4d); // ceil(24 * 0.15)
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BuildPlan_clamps_all_ranges_to_allowed_max()
|
|
||||||
{
|
|
||||||
var model = new ByDivisorClassModel
|
|
||||||
{
|
|
||||||
CheapLimit = 1000d,
|
|
||||||
};
|
|
||||||
var entries = new ByDivisorClassEntry[1024];
|
|
||||||
entries[0] = new ByDivisorClassEntry
|
|
||||||
{
|
|
||||||
K10 = 50d,
|
|
||||||
K25 = 60d,
|
|
||||||
K50 = 70d,
|
|
||||||
K75 = 80d,
|
|
||||||
GapProbability = 0.1d,
|
|
||||||
};
|
|
||||||
model.Entries = entries;
|
|
||||||
|
|
||||||
ByDivisorScanTuning tuning = ByDivisorScanTuning.Default;
|
|
||||||
// allowedMax = 41 => maxKAllowed = (41-1)/(2*5) = 4
|
|
||||||
ByDivisorScanPlan plan = model.BuildPlan(5UL, new BigInteger(41), tuning);
|
|
||||||
|
|
||||||
plan.CheapLimit.Should().Be(4d);
|
|
||||||
plan.K10.Should().Be(4d);
|
|
||||||
plan.K25.Should().Be(4d);
|
|
||||||
plan.K50.Should().Be(4d);
|
|
||||||
plan.K75.Should().Be(4d);
|
|
||||||
plan.Start.Should().Be(4d);
|
|
||||||
plan.Target.Should().Be(4d);
|
|
||||||
plan.DeltaK.Should().Be(4d);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void LoadOrBuild_rebuilds_when_results_newer_than_model()
|
|
||||||
{
|
|
||||||
string tempResults = Path.GetTempFileName();
|
|
||||||
string tempModel = tempResults + ".model";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.WriteAllLines(
|
|
||||||
tempResults,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
"p,divisor,searchedMersenne,detailedCheck,passedAllTests",
|
|
||||||
"5,31,true,true,false",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Write a stale model
|
|
||||||
var staleModel = new ByDivisorClassModel
|
|
||||||
{
|
|
||||||
CheapLimit = 10,
|
|
||||||
};
|
|
||||||
File.WriteAllText(tempModel, JsonSerializer.Serialize(staleModel));
|
|
||||||
File.SetLastWriteTimeUtc(tempModel, DateTime.UtcNow.AddMinutes(-10));
|
|
||||||
File.SetLastWriteTimeUtc(tempResults, DateTime.UtcNow);
|
|
||||||
|
|
||||||
ByDivisorScanTuning tuning = new()
|
|
||||||
{
|
|
||||||
CheapLimit = 32,
|
|
||||||
};
|
|
||||||
|
|
||||||
ByDivisorClassModel model = ByDivisorClassModelLoader.LoadOrBuild(tempModel, tempResults, tuning, tuning.CheapLimit);
|
|
||||||
|
|
||||||
model.Entries[5].K50.Should().Be(3d);
|
|
||||||
File.GetLastWriteTimeUtc(tempModel).Should().BeAfter(File.GetLastWriteTimeUtc(tempResults).AddSeconds(-2));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
TryDelete(tempResults);
|
|
||||||
TryDelete(tempModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void TryDelete(string path)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
File.Delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignore cleanup failure in tests
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||||
@@ -5,88 +9,88 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
|||||||
[Collection("GpuNtt")]
|
[Collection("GpuNtt")]
|
||||||
public class GpuContextPoolTests
|
public class GpuContextPoolTests
|
||||||
{
|
{
|
||||||
// [Fact]
|
[Fact]
|
||||||
// [Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
// public void RentPreferred_cpu_returns_cpu_accelerator()
|
public void RentPreferred_cpu_returns_cpu_accelerator()
|
||||||
// {
|
{
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// [Fact]
|
[Fact]
|
||||||
// [Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
// public void RentPreferred_reuses_cpu_context()
|
public void RentPreferred_reuses_cpu_context()
|
||||||
// {
|
{
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
// Context firstContext;
|
Context firstContext;
|
||||||
// Accelerator firstAccelerator;
|
Accelerator firstAccelerator;
|
||||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// firstContext = lease.Context;
|
firstContext = lease.Context;
|
||||||
// firstAccelerator = lease.Accelerator;
|
firstAccelerator = lease.Accelerator;
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// lease = GpuContextPool.RentPreferred(preferCpu: true);
|
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// lease.Context.Should().BeSameAs(firstContext);
|
lease.Context.Should().BeSameAs(firstContext);
|
||||||
// lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// [Fact]
|
[Fact]
|
||||||
// [Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
// public void DisposeAll_clears_pooled_contexts()
|
public void DisposeAll_clears_pooled_contexts()
|
||||||
// {
|
{
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
// Context firstContext;
|
Context firstContext;
|
||||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// firstContext = lease.Context;
|
firstContext = lease.Context;
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
// lease = GpuContextPool.RentPreferred(preferCpu: true);
|
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// lease.Context.Should().NotBeSameAs(firstContext);
|
lease.Context.Should().NotBeSameAs(firstContext);
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
|
|
||||||
// GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using FluentAssertions;
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using PerfectNumbers.Core.Gpu.Kernels;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||||
@@ -115,10 +114,14 @@ public class GpuKernelCompilationTests
|
|||||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
|
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
|
||||||
|
|
||||||
|
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
|
||||||
|
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||||
|
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
|
||||||
|
|
||||||
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
||||||
DivisorCycleKernels.GpuDivisorCycleKernel;
|
DivisorCycleKernels.GpuDivisorCycleKernel;
|
||||||
|
|
||||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||||
PrimeTesterKernels.SmallPrimeSieveKernel;
|
PrimeTesterKernels.SmallPrimeSieveKernel;
|
||||||
|
|
||||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
||||||
@@ -164,23 +167,23 @@ public class GpuKernelCompilationTests
|
|||||||
return new object[] { name, loader };
|
return new object[] { name, loader };
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Theory]
|
[Theory]
|
||||||
// [Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
// [MemberData(nameof(KernelLoaders))]
|
[MemberData(nameof(KernelLoaders))]
|
||||||
// public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||||
// {
|
{
|
||||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// var accelerator = lease.Accelerator;
|
var accelerator = lease.Accelerator;
|
||||||
// Action action = () => compile(accelerator);
|
Action action = () => compile(accelerator);
|
||||||
// action.Should().NotThrow($"kernel {name} should compile");
|
action.Should().NotThrow($"kernel {name} should compile");
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
@@ -322,67 +325,56 @@ public class GpuKernelCompilationTests
|
|||||||
|
|
||||||
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
|
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderKernels.Pow2ModKernelWide);
|
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
|
||||||
_ = kernel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
|
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(MersenneDivisorCyclesKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
|
||||||
_ = kernel;
|
_ = kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSmallPrimeKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
||||||
_ = kernel;
|
_ = kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
|
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSharesFactorKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
|
||||||
_ = kernel;
|
_ = kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KernelType _kernelType = KernelType.Pow2ModKernelScan | KernelType.OrderKernelScan | KernelType.IncrementalKernelScan | KernelType.IncrementalOrderKernelScan | KernelType.Pow2ModOrderKernelScan;
|
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
||||||
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
{
|
||||||
{
|
_ = accelerator;
|
||||||
|
var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = lease.OrderKernel;
|
||||||
|
_ = lease.IncrementalKernel;
|
||||||
|
_ = lease.Pow2ModKernel;
|
||||||
|
_ = lease.IncrementalOrderKernel;
|
||||||
|
_ = lease.Pow2ModOrderKernel;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
GpuPrimeWorkLimiter.Acquire();
|
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
int acceleratorIndex = AcceleratorPool.Shared.Rent();
|
try
|
||||||
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
{
|
||||||
try
|
_ = orderLease.OrderKernel;
|
||||||
{
|
_ = orderLease.IncrementalKernel;
|
||||||
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
|
_ = orderLease.Pow2ModKernel;
|
||||||
_ = kernels.Order;
|
_ = orderLease.IncrementalOrderKernel;
|
||||||
_ = kernels.Incremental;
|
_ = orderLease.Pow2ModOrderKernel;
|
||||||
_ = kernels.Pow2Mod;
|
}
|
||||||
_ = kernels.IncrementalOrder;
|
finally
|
||||||
_ = kernels.Pow2ModOrder;
|
{
|
||||||
}
|
orderLease.Dispose();
|
||||||
finally
|
}
|
||||||
{
|
}
|
||||||
stream.Synchronize();
|
|
||||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
|
||||||
// GpuPrimeWorkLimiter.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
|
||||||
GpuPrimeWorkLimiter.Acquire();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
|
|
||||||
_ = kernels.Order;
|
|
||||||
_ = kernels.Incremental;
|
|
||||||
_ = kernels.Pow2Mod;
|
|
||||||
_ = kernels.IncrementalOrder;
|
|
||||||
_ = kernels.Pow2ModOrder;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
stream.Synchronize();
|
|
||||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
|
||||||
// GpuPrimeWorkLimiter.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -18,33 +18,22 @@ public class GpuPrimeLimiterTests
|
|||||||
var startedSecond = new TaskCompletionSource<bool>();
|
var startedSecond = new TaskCompletionSource<bool>();
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
|
|
||||||
GpuPrimeWorkLimiter.Acquire();
|
using var first = GpuPrimeWorkLimiter.Acquire();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
var secondTask = Task.Run(() =>
|
var secondTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
GpuPrimeWorkLimiter.Acquire();
|
using var second = GpuPrimeWorkLimiter.Acquire();
|
||||||
try
|
startedSecond.TrySetResult(true);
|
||||||
{
|
|
||||||
startedSecond.TrySetResult(true);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
GpuPrimeWorkLimiter.Release();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
// Give secondTask a moment to attempt acquire (it should block).
|
||||||
{
|
await Task.Delay(50);
|
||||||
// Give secondTask a moment to attempt acquire (it should block).
|
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||||
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.)
|
||||||
finally
|
first.Dispose();
|
||||||
{
|
|
||||||
// Release first after the initial wait so the second task can proceed.
|
|
||||||
GpuPrimeWorkLimiter.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||||
@@ -8,91 +7,59 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
|||||||
[Trait("Category", "Slow")]
|
[Trait("Category", "Slow")]
|
||||||
public class LucasLehmerPrimeTesterTests
|
public class LucasLehmerPrimeTesterTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(3UL, true)]
|
[InlineData(3UL, true)]
|
||||||
[InlineData(5UL, true)]
|
[InlineData(5UL, true)]
|
||||||
[InlineData(11UL, false)]
|
[InlineData(11UL, false)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
|
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
|
||||||
{
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
new MersenneNumberLucasLehmerGpuTester().IsMersennePrime(p).Should().Be(expected);
|
||||||
try
|
}
|
||||||
{
|
|
||||||
new MersenneNumberLucasLehmerGpuTester().IsPrime(gpu, p, true).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
|
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
|
||||||
{
|
{
|
||||||
// Use a modest exponent that still exercises the NTT path while keeping
|
// Use a modest exponent that still exercises the NTT path while keeping
|
||||||
// runtime short enough for the unit test environment.
|
// runtime short enough for the unit test environment.
|
||||||
const ulong exponent = 128UL;
|
const ulong exponent = 128UL;
|
||||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||||
tester.WarmUpNttParameters(exponent);
|
tester.WarmUpNttParameters(exponent);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||||
try
|
}
|
||||||
{
|
|
||||||
tester.IsPrime(gpu, exponent, true).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsMersennePrimeBatch_matches_individual()
|
public void IsMersennePrimeBatch_matches_individual()
|
||||||
{
|
{
|
||||||
ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL };
|
ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL };
|
||||||
bool[] expected = new bool[] { true, true, true, false, true, true, true, false };
|
bool[] expected = new bool[] { true, true, true, false, true, true, true, false };
|
||||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||||
bool[] results = new bool[exponents.Length];
|
bool[] results = new bool[exponents.Length];
|
||||||
tester.IsMersennePrimeBatch(exponents, results);
|
tester.IsMersennePrimeBatch(exponents, results);
|
||||||
results.Should().Equal(expected);
|
results.Should().Equal(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(132UL)]
|
[InlineData(132UL)]
|
||||||
[InlineData(1000UL)]
|
[InlineData(1000UL)]
|
||||||
[Trait("Category", "Slow")]
|
[Trait("Category", "Slow")]
|
||||||
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
|
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||||
tester.WarmUpNttParameters(exponent);
|
tester.WarmUpNttParameters(exponent);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||||
try
|
}
|
||||||
{
|
|
||||||
tester.IsPrime(gpu, exponent, true).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(3UL, true)]
|
[InlineData(3UL, true)]
|
||||||
[InlineData(5UL, true)]
|
[InlineData(5UL, true)]
|
||||||
[InlineData(11UL, false)]
|
[InlineData(11UL, false)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
|
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
|
||||||
{
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
new MersenneNumberLucasLehmerCpuTester().IsPrime(p).Should().Be(expected);
|
||||||
try
|
}
|
||||||
{
|
|
||||||
new MersenneNumberLucasLehmerCpuTester().IsPrime(gpu, p).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,352 +13,367 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
|||||||
[Collection("GpuNtt")]
|
[Collection("GpuNtt")]
|
||||||
public class NttGpuMathTests
|
public class NttGpuMathTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ForwardGpu_reference_matches_cpu_forward()
|
public void ForwardGpu_reference_matches_cpu_forward()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||||
cpu.CopyTo(gpu);
|
cpu.CopyTo(gpu);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < cpu.Length; i++)
|
for (int i = 0; i < cpu.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ForwardGpu_staged_matches_cpu_forward()
|
public void ForwardGpu_staged_matches_cpu_forward()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||||
cpu.CopyTo(gpu);
|
cpu.CopyTo(gpu);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < cpu.Length; i++)
|
for (int i = 0; i < cpu.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void InverseGpu_reference_matches_cpu_inverse()
|
public void InverseGpu_reference_matches_cpu_inverse()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||||
input[0] = new GpuUInt128(0UL, 5UL);
|
input[0] = new GpuUInt128(0UL, 5UL);
|
||||||
input[1] = new GpuUInt128(0UL, 7UL);
|
input[1] = new GpuUInt128(0UL, 7UL);
|
||||||
input[2] = new GpuUInt128(0UL, 9UL);
|
input[2] = new GpuUInt128(0UL, 9UL);
|
||||||
input[3] = new GpuUInt128(0UL, 11UL);
|
input[3] = new GpuUInt128(0UL, 11UL);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||||
input.CopyTo(transformed);
|
input.CopyTo(transformed);
|
||||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||||
transformed.CopyTo(cpu);
|
transformed.CopyTo(cpu);
|
||||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||||
transformed.CopyTo(gpu);
|
transformed.CopyTo(gpu);
|
||||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < input.Length; i++)
|
for (int i = 0; i < input.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void InverseGpu_staged_matches_cpu_inverse()
|
public void InverseGpu_staged_matches_cpu_inverse()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||||
input[0] = new GpuUInt128(0UL, 5UL);
|
input[0] = new GpuUInt128(0UL, 5UL);
|
||||||
input[1] = new GpuUInt128(0UL, 7UL);
|
input[1] = new GpuUInt128(0UL, 7UL);
|
||||||
input[2] = new GpuUInt128(0UL, 9UL);
|
input[2] = new GpuUInt128(0UL, 9UL);
|
||||||
input[3] = new GpuUInt128(0UL, 11UL);
|
input[3] = new GpuUInt128(0UL, 11UL);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||||
input.CopyTo(transformed);
|
input.CopyTo(transformed);
|
||||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||||
transformed.CopyTo(cpu);
|
transformed.CopyTo(cpu);
|
||||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||||
transformed.CopyTo(gpu);
|
transformed.CopyTo(gpu);
|
||||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < input.Length; i++)
|
for (int i = 0; i < input.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
||||||
{
|
{
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
|
|
||||||
var accelerator = SharedGpuContext.CreateAccelerator();
|
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||||
var stream = accelerator.CreateStream();
|
try
|
||||||
|
{
|
||||||
|
var accelerator = gpu.Accelerator;
|
||||||
|
|
||||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
method.Should().NotBeNull();
|
method.Should().NotBeNull();
|
||||||
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
||||||
var cacheType = cache.GetType();
|
var cacheType = cache.GetType();
|
||||||
|
|
||||||
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
||||||
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
||||||
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
||||||
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
||||||
|
|
||||||
int count = 7; // length 8 -> 7 twiddles
|
int count = 7; // length 8 -> 7 twiddles
|
||||||
var forward = new GpuUInt128[count];
|
var forward = new GpuUInt128[count];
|
||||||
var inverse = new GpuUInt128[count];
|
var inverse = new GpuUInt128[count];
|
||||||
twiddlesMont.CopyToCPU(stream, forward);
|
twiddlesMont.CopyToCPU(forward);
|
||||||
twiddlesInvMont.CopyToCPU(stream, inverse);
|
twiddlesInvMont.CopyToCPU(inverse);
|
||||||
stream.Synchronize();
|
|
||||||
stream.Dispose();
|
|
||||||
accelerator.Dispose();
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
||||||
prod.Should().Be(r2);
|
prod.Should().Be(r2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
gpu.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ConvolveGpu_reference_matches_cpu_convolve()
|
public void ConvolveGpu_reference_matches_cpu_convolve()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||||
leftCpu.CopyTo(leftGpu);
|
leftCpu.CopyTo(leftGpu);
|
||||||
rightCpu.CopyTo(rightGpu);
|
rightCpu.CopyTo(rightGpu);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < leftCpu.Length; i++)
|
for (int i = 0; i < leftCpu.Length; i++)
|
||||||
{
|
{
|
||||||
leftGpu[i].Should().Be(leftCpu[i]);
|
leftGpu[i].Should().Be(leftCpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ConvolveGpu_staged_matches_cpu_convolve()
|
public void ConvolveGpu_staged_matches_cpu_convolve()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||||
leftCpu.CopyTo(leftGpu);
|
leftCpu.CopyTo(leftGpu);
|
||||||
rightCpu.CopyTo(rightGpu);
|
rightCpu.CopyTo(rightGpu);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < leftCpu.Length; i++)
|
for (int i = 0; i < leftCpu.Length; i++)
|
||||||
{
|
{
|
||||||
leftGpu[i].Should().Be(leftCpu[i]);
|
leftGpu[i].Should().Be(leftCpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void SquareGpu_reference_matches_cpu_square()
|
public void SquareGpu_reference_matches_cpu_square()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||||
cpu.CopyTo(gpu);
|
cpu.CopyTo(gpu);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < cpu.Length; i++)
|
for (int i = 0; i < cpu.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void SquareGpu_staged_matches_cpu_square()
|
public void SquareGpu_staged_matches_cpu_square()
|
||||||
{
|
{
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||||
cpu.CopyTo(gpu);
|
cpu.CopyTo(gpu);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||||
for (int i = 0; i < cpu.Length; i++)
|
for (int i = 0; i < cpu.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void BitReverseGpu_matches_cpu()
|
public void BitReverseGpu_matches_cpu()
|
||||||
{
|
{
|
||||||
GpuUInt128[] cpu = new GpuUInt128[8];
|
GpuUInt128[] cpu = new GpuUInt128[8];
|
||||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||||
{
|
{
|
||||||
cpu[i] = new GpuUInt128(0UL, i);
|
cpu[i] = new GpuUInt128(0UL, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
||||||
NttGpuMath.BitReverse(cpu);
|
NttGpuMath.BitReverse(cpu);
|
||||||
NttGpuMath.BitReverseGpu(gpu);
|
NttGpuMath.BitReverseGpu(gpu);
|
||||||
gpu.Should().Equal(cpu);
|
gpu.Should().Equal(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void DisposeAll_clears_ntt_caches()
|
public void DisposeAll_clears_ntt_caches()
|
||||||
{
|
{
|
||||||
// Ensure the cache starts empty.
|
// Ensure the cache starts empty.
|
||||||
NttGpuMath.ClearCaches();
|
NttGpuMath.ClearCaches();
|
||||||
|
|
||||||
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
||||||
values[0] = new GpuUInt128(0UL, 1UL);
|
values[0] = new GpuUInt128(0UL, 1UL);
|
||||||
values[1] = new GpuUInt128(0UL, 2UL);
|
values[1] = new GpuUInt128(0UL, 2UL);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
Accelerator accelerator = SharedGpuContext.CreateAccelerator();
|
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||||
var stream = accelerator.CreateStream();
|
try
|
||||||
var buffer = accelerator.Allocate1D<GpuUInt128>(values.Length);
|
{
|
||||||
buffer.View.CopyFromCPU(stream, ref values[0], values.Length);
|
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||||
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
|
try
|
||||||
stream.Synchronize();
|
{
|
||||||
stream.Dispose();
|
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
||||||
buffer.Dispose();
|
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
||||||
accelerator.Dispose();
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
buffer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
gpu.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the square cache now holds entries for the accelerator.
|
// Verify that the square cache now holds entries for the accelerator.
|
||||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var cacheObj = field.GetValue(null)!;
|
var cacheObj = field.GetValue(null)!;
|
||||||
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||||
count.Should().BeGreaterThan(0);
|
count.Should().BeGreaterThan(0);
|
||||||
|
|
||||||
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
GpuContextPool.DisposeAll();
|
||||||
count.Should().Be(0);
|
|
||||||
}
|
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||||
|
count.Should().Be(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,83 +9,86 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class NttMontgomeryTests
|
public class NttMontgomeryTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
||||||
{
|
{
|
||||||
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
|
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
||||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||||
{
|
{
|
||||||
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||||
}
|
}
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
||||||
cpu.CopyTo(gpu);
|
cpu.CopyTo(gpu);
|
||||||
for (int i = cpu.Length; i < gpu.Length; i++)
|
for (int i = cpu.Length; i < gpu.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i] = new GpuUInt128(0UL, 0UL);
|
gpu[i] = new GpuUInt128(0UL, 0UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPU reference
|
// CPU reference
|
||||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||||
|
|
||||||
// Device execution
|
// Device execution
|
||||||
var accelerator = SharedGpuContext.CreateAccelerator();
|
GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
|
||||||
var stream = accelerator.CreateStream();
|
try
|
||||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
{
|
||||||
buffer.View.CopyFromCPU(stream, ref gpu[0], gpu.Length);
|
var accelerator = context.Accelerator;
|
||||||
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
|
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||||
buffer.View.CopyToCPU(stream, ref gpu[0], gpu.Length);
|
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
||||||
stream.Synchronize();
|
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
|
||||||
stream.Dispose();
|
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
|
||||||
accelerator.Dispose();
|
|
||||||
|
|
||||||
for (int i = 0; i < cpu.Length; i++)
|
for (int i = 0; i < cpu.Length; i++)
|
||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
context.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
[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);
|
||||||
|
|
||||||
[Fact]
|
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||||
[Trait("Category", "Fast")]
|
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||||
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
|
{
|
||||||
{
|
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
}
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
|
||||||
|
|
||||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
||||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
input.CopyTo(transformed);
|
||||||
{
|
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||||
input.CopyTo(transformed);
|
transformed.CopyTo(cpu);
|
||||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||||
|
|
||||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
var original = NttGpuMath.GpuTransformBackend;
|
||||||
transformed.CopyTo(cpu);
|
try
|
||||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
{
|
||||||
|
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||||
var original = NttGpuMath.GpuTransformBackend;
|
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||||
try
|
transformed.CopyTo(gpu);
|
||||||
{
|
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
for (int i = 0; i < input.Length; i++)
|
||||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
{
|
||||||
transformed.CopyTo(gpu);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
}
|
||||||
for (int i = 0; i < input.Length; i++)
|
}
|
||||||
{
|
finally
|
||||||
gpu[i].Should().Be(cpu[i]);
|
{
|
||||||
}
|
NttGpuMath.GpuTransformBackend = original;
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
NttGpuMath.GpuTransformBackend = original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,410 +2,374 @@ using System.Numerics;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class PrimeOrderGpuHeuristicsTests
|
public class PrimeOrderGpuHeuristicsTests
|
||||||
{
|
{
|
||||||
public PrimeOrderGpuHeuristicsTests()
|
public PrimeOrderGpuHeuristicsTests()
|
||||||
{
|
{
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
|
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
|
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryPow2Mod_returns_overflow_for_marked_prime()
|
public void TryPow2Mod_returns_overflow_for_marked_prime()
|
||||||
{
|
{
|
||||||
const ulong prime = 97UL;
|
const ulong prime = 97UL;
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
try
|
||||||
try
|
{
|
||||||
{
|
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 1UL, prime, out ulong remainder, divisorData);
|
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
remainder.Should().Be(0UL);
|
remainder.Should().Be(0UL);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryPow2Mod_returns_success_for_supported_prime()
|
public void TryPow2Mod_returns_success_for_supported_prime()
|
||||||
{
|
{
|
||||||
const ulong exponent = 138_000_001UL;
|
const ulong exponent = 138_000_001UL;
|
||||||
const ulong prime = 138_000_001UL;
|
const ulong prime = 138_000_001UL;
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData);
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out ulong remainder, divisorData);
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
status.Should().Be(GpuPow2ModStatus.Success);
|
||||||
|
remainder.Should().Be(2UL);
|
||||||
|
}
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Success);
|
[Fact]
|
||||||
remainder.Should().Be(2UL);
|
public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability()
|
||||||
}
|
{
|
||||||
|
const ulong prime = 97UL;
|
||||||
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
|
||||||
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability()
|
remainder.Should().Be(0UL);
|
||||||
{
|
}
|
||||||
const ulong prime = 97UL;
|
finally
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
|
{
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||||
try
|
}
|
||||||
{
|
}
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 1UL, prime, out ulong remainder, divisorData);
|
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
[Fact]
|
||||||
remainder.Should().Be(0UL);
|
public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability()
|
||||||
}
|
{
|
||||||
finally
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
|
||||||
{
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
try
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
{
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
const ulong prime = 193UL;
|
||||||
}
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
}
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData);
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability()
|
remainder.Should().Be(0UL);
|
||||||
{
|
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const ulong prime = 193UL;
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 16UL, prime, out ulong remainder, divisorData);
|
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData);
|
||||||
remainder.Should().Be(0UL);
|
followUp.Should().Be(GpuPow2ModStatus.Success);
|
||||||
|
followUpRemainder.Should().Be(16UL);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 4UL, prime, out ulong followUpRemainder, divisorData);
|
[Fact]
|
||||||
followUp.Should().Be(GpuPow2ModStatus.Success);
|
public void TryPow2ModBatch_returns_overflow_for_marked_prime()
|
||||||
followUpRemainder.Should().Be(16UL);
|
{
|
||||||
}
|
const ulong prime = 193UL;
|
||||||
finally
|
try
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
ulong[] remainders = new ulong[2];
|
||||||
public void TryPow2ModBatch_returns_overflow_for_marked_prime()
|
ulong[] exponents = { 1UL, 2UL };
|
||||||
{
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
const ulong prime = 193UL;
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
|
||||||
|
|
||||||
ulong[] remainders = new ulong[2];
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
ulong[] exponents = { 1UL, 2UL };
|
remainders[0].Should().Be(0UL);
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
remainders[1].Should().Be(0UL);
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
[Fact]
|
||||||
remainders[0].Should().Be(0UL);
|
public void TryPow2ModBatch_returns_success_for_supported_prime()
|
||||||
remainders[1].Should().Be(0UL);
|
{
|
||||||
}
|
ulong[] remainders = { 123UL, 456UL };
|
||||||
finally
|
ulong[] exponents = { 138_000_001UL, 138_000_011UL };
|
||||||
{
|
const ulong prime = 138_000_001UL;
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime);
|
||||||
public void TryPow2ModBatch_returns_success_for_supported_prime()
|
ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime);
|
||||||
{
|
|
||||||
ulong[] remainders = { 123UL, 456UL };
|
|
||||||
ulong[] exponents = { 138_000_001UL, 138_000_011UL };
|
|
||||||
const ulong prime = 138_000_001UL;
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
status.Should().Be(GpuPow2ModStatus.Success);
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
expectedFirst.Should().Be(2UL);
|
||||||
|
expectedSecond.Should().Be(2048UL);
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
remainders[0].Should().Be(expectedFirst);
|
||||||
|
remainders[1].Should().Be(expectedSecond);
|
||||||
|
}
|
||||||
|
|
||||||
ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime);
|
[Fact]
|
||||||
ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime);
|
public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability()
|
||||||
|
{
|
||||||
|
const ulong prime = 97UL;
|
||||||
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
|
||||||
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ulong[] exponents = { 1UL, 2UL };
|
||||||
|
ulong[] remainders = { 5UL, 6UL };
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Success);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
expectedFirst.Should().Be(2UL);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||||
expectedSecond.Should().Be(2048UL);
|
|
||||||
remainders[0].Should().Be(expectedFirst);
|
|
||||||
remainders[1].Should().Be(expectedSecond);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability()
|
remainders[0].Should().Be(0UL);
|
||||||
{
|
remainders[1].Should().Be(0UL);
|
||||||
const ulong prime = 97UL;
|
}
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
|
finally
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
{
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
try
|
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||||
{
|
}
|
||||||
ulong[] exponents = { 1UL, 2UL };
|
}
|
||||||
ulong[] remainders = { 5UL, 6UL };
|
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
[Fact]
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability()
|
||||||
|
{
|
||||||
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
|
||||||
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ulong[] exponents = { 2UL, 16UL };
|
||||||
|
ulong[] remainders = { 9UL, 11UL };
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
const ulong prime = 193UL;
|
||||||
remainders[0].Should().Be(0UL);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
remainders[1].Should().Be(0UL);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability()
|
remainders[0].Should().Be(0UL);
|
||||||
{
|
remainders[1].Should().Be(0UL);
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
|
}
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
finally
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
{
|
||||||
try
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
{
|
}
|
||||||
ulong[] exponents = { 2UL, 16UL };
|
}
|
||||||
ulong[] remainders = { 9UL, 11UL };
|
|
||||||
|
|
||||||
const ulong prime = 193UL;
|
[Fact]
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
public void TryPow2ModWide_returns_overflow_for_marked_prime()
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
{
|
||||||
|
UInt128 prime = ((UInt128)1 << 80) + 7;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
|
||||||
remainders[0].Should().Be(0UL);
|
|
||||||
remainders[1].Should().Be(0UL);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
public void TryPow2ModWide_returns_overflow_for_marked_prime()
|
remainder.Should().Be(UInt128.Zero);
|
||||||
{
|
}
|
||||||
UInt128 prime = ((UInt128)1 << 80) + 7;
|
finally
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
{
|
||||||
try
|
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||||
{
|
}
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
|
}
|
||||||
|
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder);
|
[Fact]
|
||||||
|
public void TryPow2ModWide_returns_success_for_supported_prime()
|
||||||
|
{
|
||||||
|
UInt128 prime = ((UInt128)1 << 96) + 11;
|
||||||
|
UInt128 exponent = ((UInt128)1 << 64) + 3;
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
|
||||||
remainder.Should().Be(UInt128.Zero);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Success);
|
||||||
public void TryPow2ModWide_returns_success_for_supported_prime()
|
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
|
||||||
{
|
remainder.Should().Be(expected);
|
||||||
UInt128 prime = ((UInt128)1 << 96) + 11;
|
}
|
||||||
UInt128 exponent = ((UInt128)1 << 64) + 3;
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
[Theory]
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder);
|
[InlineData("193", "31")]
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
[InlineData("193", "255")]
|
||||||
|
[InlineData("79228162514264337593543950347", "36893488147419103251")]
|
||||||
|
public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText)
|
||||||
|
{
|
||||||
|
UInt128 modulus = UInt128.Parse(modulusText);
|
||||||
|
UInt128 exponent = UInt128.Parse(exponentText);
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Success);
|
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
|
method.Should().NotBeNull();
|
||||||
remainder.Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
|
||||||
[InlineData("193", "31")]
|
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
|
||||||
[InlineData("193", "255")]
|
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
|
||||||
[InlineData("79228162514264337593543950347", "36893488147419103251")]
|
|
||||||
public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText)
|
|
||||||
{
|
|
||||||
UInt128 modulus = UInt128.Parse(modulusText);
|
|
||||||
UInt128 exponent = UInt128.Parse(exponentText);
|
|
||||||
|
|
||||||
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
|
((UInt128)gpuResult).Should().Be(expected);
|
||||||
method.Should().NotBeNull();
|
}
|
||||||
|
|
||||||
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
|
[Fact]
|
||||||
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
|
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
|
||||||
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
|
{
|
||||||
|
UInt128 prime = ((UInt128)1 << 80) + 9;
|
||||||
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
|
||||||
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
|
||||||
|
|
||||||
((UInt128)gpuResult).Should().Be(expected);
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
}
|
remainder.Should().Be(UInt128.Zero);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
|
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
|
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
|
||||||
{
|
{
|
||||||
UInt128 prime = ((UInt128)1 << 80) + 9;
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
try
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
{
|
||||||
try
|
UInt128 exponent = UInt128.One << 8;
|
||||||
{
|
UInt128 prime = ((UInt128)1 << 70) + 3;
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder);
|
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
|
||||||
remainder.Should().Be(UInt128.Zero);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
|
remainder.Should().Be(UInt128.Zero);
|
||||||
{
|
}
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
|
finally
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
try
|
}
|
||||||
{
|
}
|
||||||
UInt128 exponent = UInt128.One << 8;
|
|
||||||
UInt128 prime = ((UInt128)1 << 70) + 3;
|
|
||||||
|
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder);
|
[Fact]
|
||||||
|
public void TryPow2ModBatchWide_returns_success_for_supported_prime()
|
||||||
|
{
|
||||||
|
UInt128 prime = ((UInt128)1 << 90) + 19;
|
||||||
|
UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 };
|
||||||
|
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||||
remainder.Should().Be(UInt128.Zero);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
status.Should().Be(GpuPow2ModStatus.Success);
|
||||||
public void TryPow2ModBatchWide_returns_success_for_supported_prime()
|
for (int i = 0; i < exponents.Length; i++)
|
||||||
{
|
{
|
||||||
UInt128 prime = ((UInt128)1 << 90) + 19;
|
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
|
||||||
UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 };
|
remainders[i].Should().Be(expected);
|
||||||
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
[Fact]
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
|
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
{
|
||||||
|
UInt128 prime = ((UInt128)1 << 88) + 13;
|
||||||
|
UInt128[] exponents = { UInt128.One, (UInt128)5 };
|
||||||
|
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
||||||
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
|
||||||
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Success);
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
remainders[0].Should().Be(UInt128.Zero);
|
||||||
{
|
remainders[1].Should().Be(UInt128.Zero);
|
||||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
|
}
|
||||||
remainders[i].Should().Be(expected);
|
finally
|
||||||
}
|
{
|
||||||
}
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
|
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
|
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
|
||||||
{
|
{
|
||||||
UInt128 prime = ((UInt128)1 << 88) + 13;
|
UInt128 prime = ((UInt128)1 << 72) + 23;
|
||||||
UInt128[] exponents = { UInt128.One, (UInt128)5 };
|
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
|
||||||
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
|
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
try
|
||||||
try
|
{
|
||||||
{
|
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
|
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||||
remainders[0].Should().Be(UInt128.Zero);
|
remainders[0].Should().Be(UInt128.Zero);
|
||||||
remainders[1].Should().Be(UInt128.Zero);
|
remainders[1].Should().Be(UInt128.Zero);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
}
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
// This will never happen in production code because the caller ensures the remainder span is large enough.
|
||||||
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
|
// [Fact]
|
||||||
{
|
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||||
UInt128 prime = ((UInt128)1 << 72) + 23;
|
// {
|
||||||
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
|
// ulong[] remainders = new ulong[1];
|
||||||
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
// ulong[] exponents = { 1UL, 2UL };
|
||||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
|
|
||||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
|
|
||||||
|
|
||||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
// Action act = () =>
|
||||||
remainders[0].Should().Be(UInt128.Zero);
|
// {
|
||||||
remainders[1].Should().Be(UInt128.Zero);
|
// const ulong prime = 5UL;
|
||||||
}
|
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
finally
|
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||||
{
|
// };
|
||||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will never happen in production code because the caller ensures the remainder span is large enough.
|
// act.Should().Throw<ArgumentException>();
|
||||||
// [Fact]
|
// }
|
||||||
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
|
||||||
// {
|
|
||||||
// ulong[] remainders = new ulong[1];
|
|
||||||
// ulong[] exponents = { 1UL, 2UL };
|
|
||||||
|
|
||||||
// Action act = () =>
|
[Fact]
|
||||||
// {
|
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
|
||||||
// const ulong prime = 5UL;
|
{
|
||||||
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
const ulong prime = 7UL;
|
||||||
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
try
|
||||||
// };
|
{
|
||||||
|
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||||
|
|
||||||
// act.Should().Throw<ArgumentException>();
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
// }
|
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
[Fact]
|
result.Should().Be(3UL);
|
||||||
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
|
}
|
||||||
{
|
finally
|
||||||
const ulong prime = 7UL;
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||||
try
|
}
|
||||||
{
|
}
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
|
||||||
ulong result = PrimeOrderCalculatorGpu.Calculate(gpu, prime, null, divisorData, PrimeOrderCalculatorConfig.HeuristicDefault);
|
|
||||||
|
|
||||||
result.Should().Be(3UL);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||||
@@ -7,39 +7,44 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class PrimeTesterGpuTests
|
public class PrimeTesterGpuTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsPrimeGpu_accepts_known_primes()
|
public void IsPrimeGpu_accepts_known_primes()
|
||||||
{
|
{
|
||||||
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
|
GpuContextPool.ForceCpu = false;
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
try
|
||||||
try
|
{
|
||||||
{
|
var tester = new PrimeTester();
|
||||||
foreach (ulong prime in primes)
|
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
|
||||||
{
|
foreach (ulong prime in primes)
|
||||||
PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue();
|
{
|
||||||
}
|
tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
finally
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
{
|
||||||
}
|
GpuContextPool.ForceCpu = false;
|
||||||
}
|
GpuContextPool.DisposeAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsPrimeGpu_rejects_composites()
|
||||||
|
{
|
||||||
|
GpuContextPool.ForceCpu = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tester = new PrimeTester();
|
||||||
|
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
|
||||||
|
foreach (ulong composite in composites)
|
||||||
|
{
|
||||||
|
tester.IsPrimeGpu(composite, CancellationToken.None).Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GpuContextPool.ForceCpu = false;
|
||||||
|
GpuContextPool.DisposeAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IsPrimeGpu_rejects_composites()
|
|
||||||
{
|
|
||||||
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (ulong composite in composites)
|
|
||||||
{
|
|
||||||
PrimeTester.IsPrimeGpu(gpu, composite).Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using PerfectNumbers.Core.Gpu.Kernels;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ public class ULongExtensionsGpuTests
|
|||||||
public void Pow2ModWindowedGpu_matches_montgomery_cpu_for_known_values(ulong exponent, ulong modulus)
|
public void Pow2ModWindowedGpu_matches_montgomery_cpu_for_known_values(ulong exponent, ulong modulus)
|
||||||
{
|
{
|
||||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||||
ulong expected = exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisor);
|
ulong expected = exponent.Pow2MontgomeryModWindowedCpu(divisor, keepMontgomery: false);
|
||||||
|
|
||||||
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ public class ULongExtensionsGpuTests
|
|||||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||||
foreach (ulong exponent in exponents)
|
foreach (ulong exponent in exponents)
|
||||||
{
|
{
|
||||||
ulong expected = exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisor);
|
ulong expected = exponent.Pow2MontgomeryModWindowedCpu(divisor, keepMontgomery: false);
|
||||||
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,26 +7,26 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
[Collection("GpuNtt")]
|
[Collection("GpuNtt")]
|
||||||
public class GpuKernelPoolTests
|
public class GpuKernelPoolTests
|
||||||
{
|
{
|
||||||
// [Fact]
|
[Fact]
|
||||||
// [Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
// public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||||
// {
|
{
|
||||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// var accelerator = lease.Accelerator;
|
var accelerator = lease.Accelerator;
|
||||||
|
|
||||||
// var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||||
// var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||||
|
|
||||||
// second.Equals(first).Should().BeTrue();
|
second.Equals(first).Should().BeTrue();
|
||||||
// second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||||
// }
|
}
|
||||||
// finally
|
finally
|
||||||
// {
|
{
|
||||||
// lease.Dispose();
|
lease.Dispose();
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
|
|||||||
@@ -17,31 +17,19 @@ public class GpuWorkLimiterTests
|
|||||||
var startedSecond = new TaskCompletionSource<bool>();
|
var startedSecond = new TaskCompletionSource<bool>();
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
|
|
||||||
var first = GpuWorkLimiter.Acquire();
|
using var first = GpuWorkLimiter.Acquire();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
var secondTask = Task.Run(() =>
|
var secondTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
var second = GpuWorkLimiter.Acquire();
|
using var second = GpuWorkLimiter.Acquire();
|
||||||
try
|
startedSecond.TrySetResult(true);
|
||||||
{
|
|
||||||
startedSecond.TrySetResult(true);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
second.Dispose();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
await Task.Delay(50);
|
||||||
{
|
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||||
await Task.Delay(50);
|
|
||||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
first.Dispose();
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
first.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|||||||
@@ -1,232 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
|
||||||
|
|
||||||
public class HeuristicCombinedPrimeTesterTests
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData((byte)1)]
|
|
||||||
[InlineData((byte)7)]
|
|
||||||
[InlineData((byte)9)]
|
|
||||||
public void Enumerator_uses_one_a_one_b_schedule_by_default(byte lastDigit)
|
|
||||||
{
|
|
||||||
var enumerator = HeuristicCombinedPrimeTester.CreateHeuristicDivisorEnumerator(500UL * 500UL, lastDigit);
|
|
||||||
var results = new List<HeuristicCombinedPrimeTester.HeuristicDivisorCandidate>(32);
|
|
||||||
|
|
||||||
while (results.Count < 20 && enumerator.TryGetNext(out var candidate))
|
|
||||||
{
|
|
||||||
results.Add(candidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int segmentsToCheck = 6;
|
|
||||||
results.Count.Should().BeGreaterThanOrEqualTo(4 + segmentsToCheck * 2);
|
|
||||||
results[0].Value.Should().Be(3UL);
|
|
||||||
results[0].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
|
||||||
results[1].Value.Should().Be(7UL);
|
|
||||||
results[1].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
|
||||||
results[2].Value.Should().Be(11UL);
|
|
||||||
results[2].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
|
||||||
results[3].Value.Should().Be(13UL);
|
|
||||||
results[3].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
|
||||||
|
|
||||||
int index = 4;
|
|
||||||
|
|
||||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
|
||||||
{
|
|
||||||
HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupACandidate = results[index++];
|
|
||||||
groupACandidate.Value.Should().BeGreaterThan(13UL);
|
|
||||||
(groupACandidate.Value % 10UL).Should().Be(3UL);
|
|
||||||
groupACandidate.Group.Should().NotBe(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB);
|
|
||||||
|
|
||||||
HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupBCandidate = results[index++];
|
|
||||||
groupBCandidate.Value.Should().BeGreaterThan(13UL);
|
|
||||||
(groupBCandidate.Value % 10UL).Should().NotBe(3UL);
|
|
||||||
groupBCandidate.Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData((byte)1)]
|
|
||||||
[InlineData((byte)7)]
|
|
||||||
[InlineData((byte)9)]
|
|
||||||
public void One_a_one_b_pattern_interleaves_group_a_and_group_b_values(byte lastDigit)
|
|
||||||
{
|
|
||||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
|
||||||
lastDigit,
|
|
||||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.OneAOneB);
|
|
||||||
|
|
||||||
combined.Length.Should().BeGreaterThan(18);
|
|
||||||
combined[0].Should().Be(3UL);
|
|
||||||
combined[1].Should().Be(7UL);
|
|
||||||
combined[2].Should().Be(11UL);
|
|
||||||
combined[3].Should().Be(13UL);
|
|
||||||
|
|
||||||
int index = 4;
|
|
||||||
const int segmentsToCheck = 6;
|
|
||||||
|
|
||||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
|
||||||
{
|
|
||||||
ulong groupAValue = combined[index++];
|
|
||||||
groupAValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupAValue % 10UL).Should().Be(3UL);
|
|
||||||
|
|
||||||
ulong groupBValue = combined[index++];
|
|
||||||
groupBValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData((byte)1)]
|
|
||||||
[InlineData((byte)7)]
|
|
||||||
[InlineData((byte)9)]
|
|
||||||
public void Two_a_one_b_pattern_interleaves_two_group_a_values_before_group_b(byte lastDigit)
|
|
||||||
{
|
|
||||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
|
||||||
lastDigit,
|
|
||||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.TwoAOneB);
|
|
||||||
|
|
||||||
combined.Length.Should().BeGreaterThan(20);
|
|
||||||
combined[0].Should().Be(3UL);
|
|
||||||
combined[1].Should().Be(7UL);
|
|
||||||
combined[2].Should().Be(11UL);
|
|
||||||
combined[3].Should().Be(13UL);
|
|
||||||
|
|
||||||
int index = 4;
|
|
||||||
const int segmentsToCheck = 5;
|
|
||||||
|
|
||||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
ulong groupAValue = combined[index++];
|
|
||||||
groupAValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupAValue % 10UL).Should().Be(3UL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong groupBValue = combined[index++];
|
|
||||||
groupBValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData((byte)1)]
|
|
||||||
[InlineData((byte)7)]
|
|
||||||
[InlineData((byte)9)]
|
|
||||||
public void Three_a_two_b_pattern_interleaves_three_group_a_values_before_two_group_b_values(byte lastDigit)
|
|
||||||
{
|
|
||||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
|
||||||
lastDigit,
|
|
||||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeATwoB);
|
|
||||||
|
|
||||||
combined.Length.Should().BeGreaterThan(28);
|
|
||||||
combined[0].Should().Be(3UL);
|
|
||||||
combined[1].Should().Be(7UL);
|
|
||||||
combined[2].Should().Be(11UL);
|
|
||||||
combined[3].Should().Be(13UL);
|
|
||||||
|
|
||||||
int index = 4;
|
|
||||||
const int segmentsToCheck = 4;
|
|
||||||
|
|
||||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
ulong groupAValue = combined[index++];
|
|
||||||
groupAValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupAValue % 10UL).Should().Be(3UL);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
ulong groupBValue = combined[index++];
|
|
||||||
groupBValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData((byte)1)]
|
|
||||||
[InlineData((byte)7)]
|
|
||||||
[InlineData((byte)9)]
|
|
||||||
public void Three_a_one_b_pattern_interleaves_three_group_a_values_before_group_b(byte lastDigit)
|
|
||||||
{
|
|
||||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
|
||||||
lastDigit,
|
|
||||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeAOneB);
|
|
||||||
|
|
||||||
combined.Length.Should().BeGreaterThan(24);
|
|
||||||
combined[0].Should().Be(3UL);
|
|
||||||
combined[1].Should().Be(7UL);
|
|
||||||
combined[2].Should().Be(11UL);
|
|
||||||
combined[3].Should().Be(13UL);
|
|
||||||
|
|
||||||
int index = 4;
|
|
||||||
const int segmentsToCheck = 4;
|
|
||||||
|
|
||||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
ulong groupAValue = combined[index++];
|
|
||||||
groupAValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupAValue % 10UL).Should().Be(3UL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong groupBValue = combined[index++];
|
|
||||||
groupBValue.Should().BeGreaterThan(13UL);
|
|
||||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData(0UL)]
|
|
||||||
[InlineData(1UL)]
|
|
||||||
[InlineData(2UL)]
|
|
||||||
[InlineData(3UL)]
|
|
||||||
[InlineData(4UL)]
|
|
||||||
[InlineData(5UL)]
|
|
||||||
[InlineData(8UL)]
|
|
||||||
[InlineData(9UL)]
|
|
||||||
[InlineData(15UL)]
|
|
||||||
[InlineData(16UL)]
|
|
||||||
[InlineData(17UL)]
|
|
||||||
[InlineData(24UL)]
|
|
||||||
[InlineData(25UL)]
|
|
||||||
[InlineData(26UL)]
|
|
||||||
[InlineData(255UL)]
|
|
||||||
[InlineData(256UL)]
|
|
||||||
[InlineData(257UL)]
|
|
||||||
[InlineData(1_000UL)]
|
|
||||||
[InlineData(1_048_575UL)]
|
|
||||||
[InlineData(1_048_576UL)]
|
|
||||||
[InlineData(1_048_577UL)]
|
|
||||||
[InlineData(4_000_000UL)]
|
|
||||||
[InlineData(4_000_001UL)]
|
|
||||||
[InlineData(4_000_004UL)]
|
|
||||||
[InlineData(65_535_999UL)]
|
|
||||||
[InlineData(65_536_000UL)]
|
|
||||||
[InlineData(65_536_001UL)]
|
|
||||||
[InlineData(4_294_967_295UL)]
|
|
||||||
[InlineData(4_294_967_296UL)]
|
|
||||||
[InlineData(4_294_967_297UL)]
|
|
||||||
[InlineData(18_446_744_073_709_551_615UL)]
|
|
||||||
[InlineData(18_446_744_073_709_551_614UL)]
|
|
||||||
[InlineData(18_446_744_073_709_551_613UL)]
|
|
||||||
public void ComputeHeuristicDivisorSquareLimit_returns_input_value(ulong value)
|
|
||||||
{
|
|
||||||
ulong limit = HeuristicCombinedPrimeTester.ComputeHeuristicDivisorSquareLimit(value);
|
|
||||||
|
|
||||||
limit.Should().Be(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +1,107 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class MersenneDivisorCyclesGenerateGpuTests
|
public class MersenneDivisorCyclesGenerateGpuTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void GenerateGpu_produces_expected_cycles_for_small_range()
|
public void GenerateGpu_produces_expected_cycles_for_small_range()
|
||||||
{
|
{
|
||||||
string path = Path.GetTempFileName();
|
string path = Path.GetTempFileName();
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
try
|
||||||
try
|
{
|
||||||
{
|
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
||||||
MersenneDivisorCyclesGpu.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
using var reader = new BinaryReader(stream);
|
||||||
using var reader = new BinaryReader(stream);
|
while (stream.Position < stream.Length)
|
||||||
while (stream.Position < stream.Length)
|
{
|
||||||
{
|
ulong d = reader.ReadUInt64();
|
||||||
ulong d = reader.ReadUInt64();
|
ulong c = reader.ReadUInt64();
|
||||||
ulong c = reader.ReadUInt64();
|
pairs.Add((d, c));
|
||||||
pairs.Add((d, c));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var expected = new List<ulong>();
|
var expected = new List<ulong>();
|
||||||
for (ulong d = 3; d <= 50; d++)
|
for (ulong d = 3; d <= 50; d++)
|
||||||
{
|
{
|
||||||
if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL)
|
if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
expected.Add(d);
|
expected.Add(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs.Should().HaveCount(expected.Count);
|
pairs.Should().HaveCount(expected.Count);
|
||||||
foreach (var pair in pairs)
|
foreach (var pair in pairs)
|
||||||
{
|
{
|
||||||
expected.Should().Contain(pair.divisor);
|
expected.Should().Contain(pair.divisor);
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||||
pair.cycle.Should().Be(MersenneDivisorCyclesCpu.CalculateCycleLength(pair.divisor, divisorData));
|
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
if (File.Exists(path))
|
||||||
if (File.Exists(path))
|
{
|
||||||
{
|
File.Delete(path);
|
||||||
File.Delete(path);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Generate_produces_unique_cycles_for_expected_divisors()
|
public void Generate_produces_unique_cycles_for_expected_divisors()
|
||||||
{
|
{
|
||||||
string path = Path.GetTempFileName();
|
string path = Path.GetTempFileName();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MersenneDivisorCyclesCpu.Generate(path, maxDivisor: 50UL, threads: 4);
|
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||||
|
|
||||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||||
using var reader = new BinaryReader(stream);
|
using var reader = new BinaryReader(stream);
|
||||||
while (stream.Position < stream.Length)
|
while (stream.Position < stream.Length)
|
||||||
{
|
{
|
||||||
ulong divisor = reader.ReadUInt64();
|
ulong divisor = reader.ReadUInt64();
|
||||||
ulong cycle = reader.ReadUInt64();
|
ulong cycle = reader.ReadUInt64();
|
||||||
pairs.Add((divisor, cycle));
|
pairs.Add((divisor, cycle));
|
||||||
}
|
}
|
||||||
|
|
||||||
var expected = new List<ulong>();
|
var expected = new List<ulong>();
|
||||||
for (ulong divisor = 3; divisor <= 50; divisor++)
|
for (ulong divisor = 3; divisor <= 50; divisor++)
|
||||||
{
|
{
|
||||||
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
|
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
expected.Add(divisor);
|
expected.Add(divisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs.Should().HaveCount(expected.Count);
|
pairs.Should().HaveCount(expected.Count);
|
||||||
|
|
||||||
HashSet<ulong> uniqueDivisors = new();
|
HashSet<ulong> uniqueDivisors = new();
|
||||||
foreach (var pair in pairs)
|
foreach (var pair in pairs)
|
||||||
{
|
{
|
||||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||||
pair.cycle.Should().Be(MersenneDivisorCyclesCpu.CalculateCycleLength(pair.divisor, divisorData));
|
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||||
|
|
||||||
var (_, completeCount) = MersenneDivisorCyclesCpu.FindLast(path);
|
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
|
||||||
completeCount.Should().Be(expected.Count);
|
completeCount.Should().Be(expected.Count);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,26 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class MersenneDivisorCyclesTests
|
public class MersenneDivisorCyclesTests
|
||||||
{
|
{
|
||||||
public static IEnumerable<object[]> Divisors() =>
|
public static IEnumerable<object[]> Divisors() =>
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new object[] {3UL, 2UL},
|
new object[] {3UL, 2UL},
|
||||||
new object[] {7UL, 3UL},
|
new object[] {7UL, 3UL},
|
||||||
new object[] {11UL, 10UL},
|
new object[] {11UL, 10UL},
|
||||||
};
|
};
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[MemberData(nameof(Divisors))]
|
[MemberData(nameof(Divisors))]
|
||||||
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
||||||
{
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||||
try
|
MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
|
||||||
{
|
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
}
|
||||||
MersenneDivisorCyclesCpu.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
|
|
||||||
MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Numerics;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
@@ -13,7 +11,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
{
|
{
|
||||||
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
|
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
|
||||||
var tester = new FakeTester();
|
var tester = new FakeTester();
|
||||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, BigInteger Divisor)>();
|
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>();
|
||||||
int compositeMarks = 0;
|
int compositeMarks = 0;
|
||||||
int clearedMarks = 0;
|
int clearedMarks = 0;
|
||||||
|
|
||||||
@@ -24,7 +22,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
startPrime: 0UL,
|
startPrime: 0UL,
|
||||||
markComposite: () => compositeMarks++,
|
markComposite: () => compositeMarks++,
|
||||||
clearComposite: () => clearedMarks++,
|
clearComposite: () => clearedMarks++,
|
||||||
printResult: (prime, searched, detailed, passed, divisor) => results.Add((prime, searched, detailed, passed, divisor)),
|
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
||||||
threadCount: 1,
|
threadCount: 1,
|
||||||
primesPerTask: 2);
|
primesPerTask: 2);
|
||||||
|
|
||||||
@@ -32,12 +30,12 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
compositeMarks.Should().Be(2);
|
compositeMarks.Should().Be(2);
|
||||||
clearedMarks.Should().Be(2);
|
clearedMarks.Should().Be(2);
|
||||||
results.Should().Equal(
|
results.Should().Equal(
|
||||||
new (ulong Prime, bool Searched, bool Detailed, bool Passed, BigInteger Divisor)[]
|
new[]
|
||||||
{
|
{
|
||||||
(5UL, true, true, true, BigInteger.Zero),
|
(5UL, true, true, true),
|
||||||
(7UL, true, true, false, new BigInteger(7)),
|
(7UL, true, true, false),
|
||||||
(11UL, true, true, false, new BigInteger(11)),
|
(11UL, true, true, false),
|
||||||
(13UL, true, true, true, BigInteger.Zero),
|
(13UL, true, true, true),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,47 +45,31 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
|
|
||||||
public int BatchSize { get; set; }
|
public int BatchSize { get; set; }
|
||||||
|
|
||||||
public BigInteger MinK { get; set; }
|
|
||||||
|
|
||||||
public string? StateFilePath { get; set; }
|
|
||||||
|
|
||||||
public void ResetStateTracking()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ulong DivisorLimit => ConfiguredMaxPrime + 100UL;
|
public ulong DivisorLimit => ConfiguredMaxPrime + 100UL;
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
ConfiguredMaxPrime = maxPrime;
|
||||||
if (prime % 7UL == 0UL)
|
|
||||||
{
|
|
||||||
divisor = 7UL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prime % 11UL == 0UL)
|
|
||||||
{
|
|
||||||
divisor = 11UL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
divisor = 0UL;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
|
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||||
|
{
|
||||||
|
return DivisorLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||||
|
{
|
||||||
|
divisorsExhausted = true;
|
||||||
|
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||||
{
|
{
|
||||||
return new DummySession();
|
return new DummySession();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||||
{
|
{
|
||||||
ConfiguredMaxPrime = maxPrime;
|
|
||||||
for (int i = 0; i < primes.Length; i++)
|
for (int i = 0; i < primes.Length; i++)
|
||||||
{
|
{
|
||||||
allowedMaxValues[i] = 100UL;
|
allowedMaxValues[i] = 100UL;
|
||||||
@@ -96,12 +78,12 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
|
|
||||||
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
{
|
{
|
||||||
public bool CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes)
|
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Return()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,138 +1,83 @@
|
|||||||
using System.Numerics;
|
|
||||||
using System.Reflection;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class MersenneNumberDivisorCpuTesterTests
|
public class MersenneNumberDivisorCpuTesterTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(11UL);
|
||||||
|
|
||||||
ulong[] primes = [5UL, 7UL, 11UL];
|
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
|
||||||
ulong[] allowedMax = new ulong[primes.Length];
|
divisorsExhausted.Should().BeTrue();
|
||||||
tester.PrepareCandidates(maxPrime: 11UL, primes, allowedMax);
|
|
||||||
|
|
||||||
tester.ResumeFromState(Path.Combine(Path.GetTempPath(), "bydivisor-cpu-tests.bin"), BigInteger.Zero, BigInteger.One);
|
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
|
||||||
tester.ResetStateTracking();
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
|
||||||
tester.IsPrime(5UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
|
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
}
|
||||||
|
|
||||||
tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue();
|
[Fact]
|
||||||
divisorsExhausted.Should().BeTrue();
|
[Trait("Category", "Fast")]
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
public void ByDivisor_session_checks_divisors_across_primes()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(13UL);
|
||||||
|
|
||||||
tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse();
|
using var session = tester.CreateDivisorSession();
|
||||||
divisorsExhausted.Should().BeTrue();
|
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
byte[] hits = new byte[primes.Length];
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||||
[Trait("Category", "Fast")]
|
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||||
public void ByDivisor_session_checks_divisors_across_primes()
|
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||||
{
|
|
||||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
|
||||||
|
|
||||||
ulong[] primes = [5UL, 7UL, 11UL, 13UL];
|
Array.Fill(hits, (byte)0);
|
||||||
|
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||||
|
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||||
|
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||||
|
}
|
||||||
|
|
||||||
ulong cycle23 = MersenneDivisorCyclesCpu.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
[Fact]
|
||||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes).Should().BeTrue();
|
[Trait("Category", "Fast")]
|
||||||
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
ulong[] primesWithoutHit = [5UL, 7UL, 13UL];
|
using var session = tester.CreateDivisorSession();
|
||||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primesWithoutHit).Should().BeFalse();
|
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||||
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
ulong cycle31 = MersenneDivisorCyclesCpu.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes).Should().BeTrue();
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||||
|
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||||
|
|
||||||
ulong[] primesBeforeHit = [7UL, 11UL, 13UL];
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primesBeforeHit).Should().BeFalse();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||||
{
|
{
|
||||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(61UL);
|
||||||
|
|
||||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
using var session = tester.CreateDivisorSession();
|
||||||
|
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||||
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||||
ulong cycle = MersenneDivisorCyclesCpu.CalculateCycleLength(7UL, divisorData);
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||||
session.CheckDivisor(7UL, divisorData, cycle, exponents).Should().BeTrue();
|
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||||
|
|
||||||
ulong[] nonDivisibleExponents = [5UL, 11UL];
|
|
||||||
session.CheckDivisor(7UL, divisorData, cycle, nonDivisibleExponents).Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
|
||||||
{
|
|
||||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
|
||||||
|
|
||||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
|
||||||
ulong cycle = MersenneDivisorCyclesCpu.CalculateCycleLength(11UL, divisorData);
|
|
||||||
session.CheckDivisor(11UL, divisorData, cycle, exponents).Should().BeTrue();
|
|
||||||
|
|
||||||
ulong[] nonDivisibleExponents = [5UL, 17UL];
|
|
||||||
session.CheckDivisor(11UL, divisorData, cycle, nonDivisibleExponents).Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void ByDivisor_does_not_mark_mersenne_prime_as_composite_via_self_divisor()
|
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder();
|
|
||||||
|
|
||||||
tester.DivisorLimit = (BigInteger.One << 30) - BigInteger.One;
|
|
||||||
BigInteger kSelf = ((BigInteger.One << 31) - 2) / 62; // (2^p - 2) / (2p) for p=31.
|
|
||||||
|
|
||||||
tester.ResumeFromState(Path.Combine(Path.GetTempPath(), "bydivisor-selfdivisor.bin"), BigInteger.Zero, kSelf);
|
|
||||||
tester.ResetStateTracking();
|
|
||||||
|
|
||||||
tester.IsPrime(31UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
|
|
||||||
divisorsExhausted.Should().BeTrue();
|
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData(127UL, 127, true)]
|
|
||||||
[InlineData(127UL, 89, false)]
|
|
||||||
[InlineData(89UL, 61, false)]
|
|
||||||
public void IsMersenneValue_flags_only_matching_exponent_as_trivial(ulong prime, int exponent, bool expected)
|
|
||||||
{
|
|
||||||
BigInteger divisor = (BigInteger.One << exponent) - BigInteger.One;
|
|
||||||
InvokeIsMersenneValue(prime, divisor).Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void IsMersenneValue_ignores_mersenne_shaped_divisors_above_prime()
|
|
||||||
{
|
|
||||||
ulong prime = 89UL;
|
|
||||||
BigInteger divisor = (BigInteger.One << 101) - BigInteger.One;
|
|
||||||
InvokeIsMersenneValue(prime, divisor).Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool InvokeIsMersenneValue(ulong prime, BigInteger divisor)
|
|
||||||
{
|
|
||||||
MethodInfo? method = typeof(MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder)
|
|
||||||
.GetMethod("IsMersenneValue", BindingFlags.NonPublic | BindingFlags.Static);
|
|
||||||
|
|
||||||
method.Should().NotBeNull("IsMersenneValue should exist as a private static helper on the tester type.");
|
|
||||||
return (bool)method!.Invoke(null, new object[] { prime, divisor })!;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,402 +1,310 @@
|
|||||||
using System.Numerics;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class MersenneNumberDivisorGpuTesterTests
|
public class MersenneNumberDivisorGpuTesterTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(33UL, 7UL, true)]
|
[InlineData(33UL, 7UL, true)]
|
||||||
[InlineData(35UL, 31UL, true)]
|
[InlineData(35UL, 31UL, true)]
|
||||||
[InlineData(37UL, 223UL, true)]
|
[InlineData(37UL, 223UL, true)]
|
||||||
[InlineData(33UL, 13UL, false)]
|
[InlineData(33UL, 13UL, false)]
|
||||||
[InlineData(35UL, 73UL, false)]
|
[InlineData(35UL, 73UL, false)]
|
||||||
[InlineData(37UL, 227UL, false)]
|
[InlineData(37UL, 227UL, false)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorGpuTester();
|
var tester = new MersenneNumberDivisorGpuTester();
|
||||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||||
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
|
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsDivisible_handles_large_divisors()
|
public void IsDivisible_handles_large_divisors()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorGpuTester();
|
var tester = new MersenneNumberDivisorGpuTester();
|
||||||
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
|
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
|
||||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||||
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
|
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
|
||||||
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
|
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
|
||||||
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
|
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted()
|
public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorGpuTester();
|
var tester = new MersenneNumberDivisorGpuTester();
|
||||||
typeof(MersenneNumberDivisorGpuTester)
|
typeof(MersenneNumberDivisorGpuTester)
|
||||||
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||||
.SetValue(null, Array.Empty<(ulong, uint)>());
|
.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||||
try
|
divisorsExhausted.Should().BeFalse();
|
||||||
{
|
}
|
||||||
tester.IsPrime(gpu, 31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
|
||||||
divisorsExhausted.Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
|
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorGpuTester();
|
var tester = new MersenneNumberDivisorGpuTester();
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||||
try
|
divisorsExhausted.Should().BeTrue();
|
||||||
{
|
}
|
||||||
tester.IsPrime(gpu, 35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
|
||||||
divisorsExhausted.Should().BeTrue();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsPrime_accepts_large_search_limits()
|
public void IsPrime_accepts_large_search_limits()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorGpuTester();
|
var tester = new MersenneNumberDivisorGpuTester();
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||||
try
|
exhausted.Should().BeTrue();
|
||||||
{
|
}
|
||||||
tester.IsPrime(gpu, 35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
|
||||||
exhausted.Should().BeTrue();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureByDivisorTester(MersenneNumberDivisorByDivisorGpuTester tester, ulong maxPrime)
|
[Fact]
|
||||||
{
|
[Trait("Category", "Fast")]
|
||||||
tester.PrepareCandidates(maxPrime, ReadOnlySpan<ulong>.Empty, Span<ulong>.Empty);
|
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||||
tester.ResumeFromState(Path.Combine(Path.GetTempPath(), "bydivisor-gpu-tests.bin"), BigInteger.Zero, BigInteger.One);
|
{
|
||||||
tester.ResetStateTracking();
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
}
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
[Fact]
|
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
|
||||||
[Trait("Category", "Fast")]
|
exhausted.Should().BeTrue();
|
||||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
|
||||||
|
|
||||||
tester.IsPrime(31UL, out bool exhausted, out BigInteger divisor).Should().BeTrue();
|
tester.IsPrime(37UL, out exhausted).Should().BeFalse();
|
||||||
exhausted.Should().BeTrue();
|
exhausted.Should().BeTrue();
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
|
||||||
|
|
||||||
tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse();
|
tester.IsPrime(43UL, out exhausted).Should().BeFalse();
|
||||||
exhausted.Should().BeTrue();
|
exhausted.Should().BeTrue();
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
}
|
||||||
|
|
||||||
tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse();
|
[Fact]
|
||||||
exhausted.Should().BeTrue();
|
[Trait("Category", "Fast")]
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
public void ByDivisor_tester_defaults_to_zero_limit_before_configuration()
|
||||||
}
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
|
|
||||||
[Fact]
|
tester.DivisorLimit.Should().Be(0UL);
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void ByDivisor_tester_defaults_to_zero_limit_before_prepare_candidates()
|
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
tester.DivisorLimit.Should().Be(0UL);
|
|
||||||
|
|
||||||
ulong[] primes = [31UL, 37UL];
|
ulong[] primes = { 31UL, 37UL };
|
||||||
ulong[] allowed = new ulong[primes.Length];
|
ulong[] allowed = new ulong[primes.Length];
|
||||||
tester.PrepareCandidates(maxPrime: 37UL, primes, allowed);
|
|
||||||
|
|
||||||
tester.DivisorLimit.Should().BeGreaterThan(0UL);
|
tester.PrepareCandidates(primes, allowed);
|
||||||
allowed.Should().OnlyContain(value => value > 0UL);
|
allowed.Should().OnlyContain(value => value == 0UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_tester_respects_filter_based_limit()
|
public void ByDivisor_tester_respects_filter_based_limit()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 41UL);
|
tester.ConfigureFromMaxPrime(41UL);
|
||||||
|
|
||||||
tester.IsPrime(31UL, out _, out BigInteger divisor).Should().BeTrue();
|
tester.IsPrime(31UL, out _).Should().BeTrue();
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
|
||||||
|
exhausted.Should().BeTrue();
|
||||||
|
|
||||||
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
|
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||||
exhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
}
|
||||||
|
|
||||||
tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse();
|
|
||||||
divisorsExhausted.Should().BeTrue();
|
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(71UL, false)]
|
[InlineData(71UL, false)]
|
||||||
[InlineData(89UL, true)]
|
[InlineData(89UL, true)]
|
||||||
[InlineData(127UL, true)]
|
[InlineData(127UL, true)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
|
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
ConfigureByDivisorTester(tester, maxPrime: exponent);
|
tester.ConfigureFromMaxPrime(exponent);
|
||||||
|
|
||||||
tester.IsPrime(exponent, out bool exhausted, out BigInteger divisor).Should().Be(expectedPrime);
|
tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime);
|
||||||
|
exhausted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
[Trait("Category", "Fast")]
|
||||||
|
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||||
|
{
|
||||||
|
GpuBatchSize = 8,
|
||||||
|
};
|
||||||
|
|
||||||
exhausted.Should().BeTrue();
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
if (expectedPrime)
|
|
||||||
{
|
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||||
[Trait("Category", "Fast")]
|
divisorsExhausted.Should().BeTrue();
|
||||||
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
|
}
|
||||||
{
|
|
||||||
int originalBatchSize = EnvironmentConfiguration.GpuBatchSize;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
EnvironmentConfiguration.GpuBatchSize = 8;
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
|
||||||
|
|
||||||
tester.IsPrime(41UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeFalse();
|
[Fact]
|
||||||
divisorsExhausted.Should().BeTrue();
|
[Trait("Category", "Fast")]
|
||||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
public void ByDivisor_session_computes_cycle_when_zero_is_provided()
|
||||||
}
|
{
|
||||||
finally
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
{
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
EnvironmentConfiguration.GpuBatchSize = originalBatchSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
using var session = tester.CreateDivisorSession();
|
||||||
[Trait("Category", "Fast")]
|
ulong[] primes = { 31UL, 37UL, 41UL };
|
||||||
public void ByDivisor_session_computes_cycle_when_zero_is_provided()
|
byte[] hits = new byte[primes.Length];
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes, hits);
|
||||||
try
|
|
||||||
{
|
|
||||||
var session = tester.CreateDivisorSession(gpu);
|
|
||||||
|
|
||||||
ulong[] primes = [31UL, 37UL, 41UL];
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes).Should().BeTrue();
|
}
|
||||||
|
|
||||||
ulong[] primesWithoutHit = [31UL, 41UL];
|
[Fact]
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primesWithoutHit).Should().BeFalse();
|
[Trait("Category", "Fast")]
|
||||||
}
|
public void ByDivisor_session_checks_divisors_across_primes()
|
||||||
finally
|
{
|
||||||
{
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
using var session = tester.CreateDivisorSession();
|
||||||
[Trait("Category", "Fast")]
|
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||||
public void ByDivisor_session_checks_divisors_across_primes()
|
byte[] hits = new byte[primes.Length];
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||||
try
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||||
{
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||||
var session = tester.CreateDivisorSession(gpu);
|
|
||||||
|
|
||||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL];
|
Array.Fill(hits, (byte)0);
|
||||||
|
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||||
|
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
|
||||||
|
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||||
|
}
|
||||||
|
|
||||||
ulong cycle223 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
[Fact]
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
[Trait("Category", "Fast")]
|
||||||
|
public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||||
|
{
|
||||||
|
GpuBatchSize = 2,
|
||||||
|
};
|
||||||
|
|
||||||
ulong[] primesBeforeHit = [31UL];
|
tester.ConfigureFromMaxPrime(47UL);
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primesBeforeHit).Should().BeFalse();
|
|
||||||
|
|
||||||
ulong cycle13367 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
using var session = tester.CreateDivisorSession();
|
||||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes).Should().BeTrue();
|
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||||
|
byte[] hits = new byte[primes.Length];
|
||||||
|
|
||||||
ulong[] primesWithoutMatchingExponent = [31UL, 37UL, 43UL];
|
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primesWithoutMatchingExponent).Should().BeFalse();
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches()
|
|
||||||
{
|
|
||||||
int originalBatchSize = EnvironmentConfiguration.GpuBatchSize;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
EnvironmentConfiguration.GpuBatchSize = 2;
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 47UL);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
[Fact]
|
||||||
try
|
[Trait("Category", "Fast")]
|
||||||
{
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||||
var session = tester.CreateDivisorSession(gpu);
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||||
|
{
|
||||||
|
GpuBatchSize = 5,
|
||||||
|
};
|
||||||
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
using var session = tester.CreateDivisorSession();
|
||||||
|
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||||
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
ulong cycle223 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||||
|
|
||||||
ulong[] primesBeforeHit = [31UL];
|
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primesBeforeHit).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
EnvironmentConfiguration.GpuBatchSize = originalBatchSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
[Fact]
|
||||||
try
|
[Trait("Category", "Fast")]
|
||||||
{
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||||
var session = tester.CreateDivisorSession(gpu);
|
{
|
||||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||||
|
{
|
||||||
|
GpuBatchSize = 5,
|
||||||
|
};
|
||||||
|
tester.ConfigureFromMaxPrime(61UL);
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
using var session = tester.CreateDivisorSession();
|
||||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 7UL, divisorData);
|
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||||
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
session.CheckDivisor(7UL, divisorData, cycle, exponents).Should().BeTrue();
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||||
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||||
|
|
||||||
ulong[] nonDivisibleExponents = [5UL, 11UL];
|
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||||
session.CheckDivisor(7UL, divisorData, cycle, nonDivisibleExponents).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 61UL);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
[Fact]
|
||||||
try
|
[Trait("Category", "Fast")]
|
||||||
{
|
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
||||||
var session = tester.CreateDivisorSession(gpu);
|
{
|
||||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
var cycles = MersenneDivisorCycles.Shared;
|
||||||
|
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
|
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 11UL, divisorData);
|
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||||
|
|
||||||
session.CheckDivisor(11UL, divisorData, cycle, exponents).Should().BeTrue();
|
try
|
||||||
|
{
|
||||||
|
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
||||||
|
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||||
|
|
||||||
ulong[] nonDivisibleExponents = [5UL, 17UL];
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||||
session.CheckDivisor(11UL, divisorData, cycle, nonDivisibleExponents).Should().BeFalse();
|
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
tableField.SetValue(cycles, patchedTable);
|
||||||
[Trait("Category", "Fast")]
|
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||||
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||||
{
|
|
||||||
var cycles = MersenneDivisorCyclesGpu.Shared;
|
|
||||||
var tableField = typeof(MersenneDivisorCyclesGpu).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
|
||||||
var smallCyclesField = typeof(MersenneDivisorCyclesGpu).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
|
||||||
|
|
||||||
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
tester.ConfigureFromMaxPrime(19UL);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
||||||
try
|
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||||
{
|
.SetValue(tester, 200UL);
|
||||||
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
|
||||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
|
||||||
patchedSmall[191] = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 191UL, divisorData);
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tableField.SetValue(cycles, originalTable);
|
||||||
|
smallCyclesField.SetValue(cycles, originalSmall);
|
||||||
|
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||||
|
GpuContextPool.DisposeAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tableField.SetValue(cycles, patchedTable);
|
[Fact]
|
||||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
[Trait("Category", "Fast")]
|
||||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorCpuTester
|
||||||
|
{
|
||||||
|
BatchSize = 2,
|
||||||
|
};
|
||||||
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
tester.ConfigureFromMaxPrime(47UL);
|
||||||
ConfigureByDivisorTester(tester, maxPrime: 19UL);
|
|
||||||
|
|
||||||
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
using var session = tester.CreateDivisorSession();
|
||||||
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||||
.SetValue(tester, 200UL);
|
byte[] hits = new byte[primes.Length];
|
||||||
|
|
||||||
tester.IsPrime(19UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
|
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||||
divisorsExhausted.Should().BeTrue();
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||||
divisor.Should().Be(BigInteger.Zero);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
tableField.SetValue(cycles, originalTable);
|
|
||||||
smallCyclesField.SetValue(cycles, originalSmall);
|
|
||||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
public void ByDivisor_cpu_session_checks_divisors_across_primes()
|
|
||||||
{
|
|
||||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
|
||||||
|
|
||||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
|
||||||
ulong cycle223 = MersenneDivisorCyclesCpu.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
|
||||||
|
|
||||||
ulong[] primesBeforeHit = [31UL];
|
|
||||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primesBeforeHit).Should().BeFalse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
|
|
||||||
@@ -8,38 +8,30 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class MersenneNumberIncrementalCpuTesterTests
|
public class MersenneNumberIncrementalCpuTesterTests
|
||||||
{
|
{
|
||||||
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(GpuKernelType.Incremental)]
|
[InlineData(GpuKernelType.Incremental)]
|
||||||
[InlineData(GpuKernelType.Pow2Mod)]
|
[InlineData(GpuKernelType.Pow2Mod)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberIncrementalCpuTester(type);
|
var tester = new MersenneNumberIncrementalCpuTester(type);
|
||||||
|
|
||||||
if (type == GpuKernelType.Pow2Mod)
|
if (type == GpuKernelType.Pow2Mod)
|
||||||
{
|
{
|
||||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||||
{
|
{
|
||||||
bool isPrime = true;
|
bool isPrime = true;
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||||
try
|
isPrime.Should().Be(expectedPrime);
|
||||||
{
|
}
|
||||||
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
|
||||||
isPrime.Should().Be(expectedPrime);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
|
|
||||||
@@ -8,39 +8,31 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class MersenneNumberOrderCpuTesterTests
|
public class MersenneNumberOrderCpuTesterTests
|
||||||
{
|
{
|
||||||
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(GpuKernelType.Incremental)]
|
[InlineData(GpuKernelType.Incremental)]
|
||||||
[InlineData(GpuKernelType.Pow2Mod)]
|
[InlineData(GpuKernelType.Pow2Mod)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberOrderCpuTester(type);
|
var tester = new MersenneNumberOrderCpuTester(type);
|
||||||
|
|
||||||
if (type == GpuKernelType.Pow2Mod)
|
if (type == GpuKernelType.Pow2Mod)
|
||||||
{
|
{
|
||||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||||
{
|
{
|
||||||
bool isPrime = true;
|
bool isPrime = true;
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||||
try
|
isPrime.Should().Be(expectedPrime);
|
||||||
{
|
}
|
||||||
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
|
||||||
isPrime.Should().Be(expectedPrime);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
@@ -45,15 +46,16 @@ public class MersenneNumberOrderGpuTesterTests
|
|||||||
writer.Write((ulong)uint.MaxValue);
|
writer.Write((ulong)uint.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
var cycles = MersenneDivisorCyclesGpu.Shared;
|
var cycles = MersenneDivisorCycles.Shared;
|
||||||
var tableField = typeof(MersenneDivisorCyclesGpu).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
var smallCyclesField = typeof(MersenneDivisorCyclesGpu).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
|
|
||||||
var originalTable = ((List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!).Select(x => x).ToList();
|
var originalTable = ((List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!).Select(x => x).ToList();
|
||||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||||
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
|
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
|
||||||
|
|
||||||
cycles.LoadFrom(tempPath);
|
cycles.LoadFrom(tempPath);
|
||||||
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -73,6 +75,7 @@ public class MersenneNumberOrderGpuTesterTests
|
|||||||
{
|
{
|
||||||
tableField.SetValue(cycles, originalTable);
|
tableField.SetValue(cycles, originalTable);
|
||||||
smallCyclesField.SetValue(cycles, backupSmall);
|
smallCyclesField.SetValue(cycles, backupSmall);
|
||||||
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -88,3 +91,4 @@ public class MersenneNumberOrderGpuTesterTests
|
|||||||
isPrime.Should().Be(expectedPrime);
|
isPrime.Should().Be(expectedPrime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
@@ -7,25 +8,24 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class MersenneNumberResidueCpuTesterTests
|
public class MersenneNumberResidueCpuTesterTests
|
||||||
{
|
{
|
||||||
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Scan_handles_various_prime_exponents()
|
public void Scan_handles_various_prime_exponents()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberResidueCpuTester();
|
var tester = new MersenneNumberResidueCpuTester();
|
||||||
|
|
||||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||||
{
|
{
|
||||||
bool isPrime = true;
|
bool isPrime = true;
|
||||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||||
|
isPrime.Should().Be(expectedPrime);
|
||||||
isPrime.Should().Be(expectedPrime);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using UInt128 = System.UInt128;
|
using UInt128 = System.UInt128;
|
||||||
|
|
||||||
@@ -8,36 +8,27 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class MersenneNumberResidueGpuTesterTests
|
public class MersenneNumberResidueGpuTesterTests
|
||||||
{
|
{
|
||||||
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
||||||
|
|
||||||
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
||||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||||
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
||||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||||
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||||
{
|
{
|
||||||
bool isPrime = true;
|
bool isPrime = true;
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||||
try
|
isPrime.Should().Be(expectedPrime);
|
||||||
{
|
}
|
||||||
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
|
||||||
|
|
||||||
isPrime.Should().Be(expectedPrime);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
@@ -8,81 +7,30 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class MersenneNumberTesterTests
|
public class MersenneNumberTesterTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(136_000_002UL)]
|
[InlineData(136_000_002UL)]
|
||||||
[InlineData(136_000_005UL)]
|
[InlineData(136_000_005UL)]
|
||||||
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
|
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberTesterForIncrementalCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu();
|
var tester = new MersenneNumberTester();
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsMersennePrime(p).Should().BeFalse();
|
||||||
try
|
}
|
||||||
{
|
|
||||||
tester.IsMersennePrime(gpu, p).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(125UL)]
|
[InlineData(125UL)]
|
||||||
public void WarmUpOrdersCpu_populates_cache_without_affecting_results(ulong p)
|
public void WarmUpOrders_populates_cache_without_affecting_results(ulong p)
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberTesterForIncrementalCalculationMethodForEnabledCacheStatusForGpuOrderDeviceForCpu();
|
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
|
||||||
tester.WarmUpOrders(p, 1_000UL);
|
tester.WarmUpOrders(p, 1_000UL);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsMersennePrime(p).Should().BeFalse();
|
||||||
try
|
}
|
||||||
{
|
|
||||||
tester.IsMersennePrime(gpu, p).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(125UL)]
|
[InlineData(125UL, true)]
|
||||||
public void WarmUpOrdersGpu_populates_cache_without_affecting_results(ulong p)
|
[InlineData(127UL, false)]
|
||||||
{
|
public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected)
|
||||||
var tester = new MersenneNumberTesterForIncrementalCalculationMethodForEnabledCacheStatusForGpuOrderDeviceForGpu();
|
{
|
||||||
tester.WarmUpOrders(p, 1_000UL);
|
p.SharesFactorWithExponentMinusOne().Should().Be(expected);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
}
|
||||||
try
|
|
||||||
{
|
|
||||||
tester.IsMersennePrime(gpu, p).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(125UL, true)]
|
|
||||||
[InlineData(127UL, false)]
|
|
||||||
public void SharesFactorWithExponentMinusOneCpu_detects_common_factors(ulong p, bool expected)
|
|
||||||
{
|
|
||||||
p.SharesFactorWithExponentMinusOneCpu().Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(125UL, true)]
|
|
||||||
[InlineData(127UL, false)]
|
|
||||||
public void SharesFactorWithExponentMinusOneGpu_detects_common_factors(ulong p, bool expected)
|
|
||||||
{
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
p.SharesFactorWithExponentMinusOneGpu(gpu).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
@@ -7,37 +6,27 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class MersenneResidueModeTests
|
public class MersenneResidueModeTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
|
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu();
|
var tester = new MersenneNumberTester(
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
useResidue: true,
|
||||||
try
|
useIncremental: false,
|
||||||
{
|
useGpuScan: false,
|
||||||
tester.IsMersennePrime(gpu, 136_000_002UL).Should().BeFalse();
|
useGpuOrder: false);
|
||||||
}
|
tester.IsMersennePrime(136_000_002UL).Should().BeFalse();
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
|
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
var tester = new MersenneNumberTester(
|
||||||
maxK: 1_000UL);
|
useResidue: true,
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
useIncremental: true,
|
||||||
try
|
useGpuScan: false,
|
||||||
{
|
useGpuOrder: false,
|
||||||
tester.IsMersennePrime(gpu, 31UL).Should().BeTrue();
|
maxK: 1_000UL);
|
||||||
}
|
tester.IsMersennePrime(31UL).Should().BeTrue();
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
@@ -7,150 +6,61 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class ModResidueTrackerTests
|
public class ModResidueTrackerTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsDivisibleCpu_advances_residue_for_identity()
|
public void IsDivisible_advances_residue_for_identity()
|
||||||
{
|
{
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||||
|
|
||||||
tracker.IsDivisibleCpu(5, 2).Should().BeFalse();
|
tracker.IsDivisible(5, 2).Should().BeFalse();
|
||||||
tracker.IsDivisibleCpu(6, 2).Should().BeTrue();
|
tracker.IsDivisible(6, 2).Should().BeTrue();
|
||||||
tracker.IsDivisibleCpu(7, 2).Should().BeFalse();
|
tracker.IsDivisible(7, 2).Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public void IsDivisibleGpu_advances_residue_for_identity()
|
[InlineData(3UL, 7UL, true)]
|
||||||
{
|
[InlineData(5UL, 31UL, true)]
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
[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);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tracker.IsDivisible(p, divisor).Should().Be(expected);
|
||||||
try
|
}
|
||||||
{
|
|
||||||
tracker.IsDivisibleGpu(gpu, 5, 2).Should().BeFalse();
|
|
||||||
tracker.IsDivisibleGpu(gpu, 6, 2).Should().BeTrue();
|
|
||||||
tracker.IsDivisibleGpu(gpu, 7, 2).Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[InlineData(3UL, 7UL, true)]
|
public void MergeOrAppend_tracks_existing_and_new_divisors()
|
||||||
[InlineData(5UL, 31UL, true)]
|
{
|
||||||
[InlineData(5UL, 7UL, false)]
|
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||||
[InlineData(11UL, 23UL, true)]
|
|
||||||
[InlineData(11UL, 31UL, false)]
|
|
||||||
public void IsDivisibleCpu_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
|
||||||
{
|
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
|
|
||||||
tracker.IsDivisibleCpu(p, divisor).Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
tracker.BeginMerge(10);
|
||||||
[InlineData(3UL, 7UL, true)]
|
tracker.MergeOrAppend(10, 3, out bool d3).Should().BeTrue();
|
||||||
[InlineData(5UL, 31UL, true)]
|
d3.Should().BeFalse();
|
||||||
[InlineData(5UL, 7UL, false)]
|
tracker.MergeOrAppend(10, 5, out bool d5).Should().BeTrue();
|
||||||
[InlineData(11UL, 23UL, true)]
|
d5.Should().BeTrue();
|
||||||
[InlineData(11UL, 31UL, false)]
|
|
||||||
public void IsDivisibleGpu_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
|
||||||
{
|
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
|
|
||||||
try
|
tracker.BeginMerge(10);
|
||||||
{
|
tracker.MergeOrAppend(10, 4, out bool d4).Should().BeTrue();
|
||||||
tracker.IsDivisibleGpu(gpu, p, divisor).Should().Be(expected);
|
d4.Should().BeFalse();
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
tracker.BeginMerge(11);
|
||||||
public void MergeOrAppendCpu_tracks_existing_and_new_divisors()
|
tracker.MergeOrAppend(11, 3, out d3).Should().BeTrue();
|
||||||
{
|
d3.Should().BeFalse();
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
tracker.MergeOrAppend(11, 4, out d4).Should().BeTrue();
|
||||||
|
d4.Should().BeFalse();
|
||||||
|
tracker.MergeOrAppend(11, 5, out d5).Should().BeTrue();
|
||||||
|
d5.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
tracker.BeginMerge(10);
|
[Fact]
|
||||||
tracker.MergeOrAppendCpu(10, 3, out bool d3).Should().BeTrue();
|
public void IsDivisible_requires_non_decreasing_numbers()
|
||||||
d3.Should().BeFalse();
|
{
|
||||||
tracker.MergeOrAppendCpu(10, 5, out bool d5).Should().BeTrue();
|
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||||
d5.Should().BeTrue();
|
tracker.IsDivisible(5, 2);
|
||||||
|
|
||||||
tracker.BeginMerge(10);
|
Action act = () => tracker.IsDivisible(4, 2);
|
||||||
tracker.MergeOrAppendCpu(10, 4, out bool d4).Should().BeTrue();
|
act.Should().Throw<InvalidOperationException>();
|
||||||
d4.Should().BeFalse();
|
}
|
||||||
|
|
||||||
tracker.BeginMerge(11);
|
|
||||||
tracker.MergeOrAppendCpu(11, 3, out d3).Should().BeTrue();
|
|
||||||
d3.Should().BeFalse();
|
|
||||||
tracker.MergeOrAppendCpu(11, 4, out d4).Should().BeTrue();
|
|
||||||
d4.Should().BeFalse();
|
|
||||||
tracker.MergeOrAppendCpu(11, 5, out d5).Should().BeTrue();
|
|
||||||
d5.Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MergeOrAppendGpu_tracks_existing_and_new_divisors()
|
|
||||||
{
|
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tracker.BeginMerge(10);
|
|
||||||
tracker.MergeOrAppendGpu(gpu, 10, 3, out bool d3).Should().BeTrue();
|
|
||||||
d3.Should().BeFalse();
|
|
||||||
tracker.MergeOrAppendGpu(gpu, 10, 5, out bool d5).Should().BeTrue();
|
|
||||||
d5.Should().BeTrue();
|
|
||||||
|
|
||||||
tracker.BeginMerge(10);
|
|
||||||
tracker.MergeOrAppendGpu(gpu, 10, 4, out bool d4).Should().BeTrue();
|
|
||||||
d4.Should().BeFalse();
|
|
||||||
|
|
||||||
tracker.BeginMerge(11);
|
|
||||||
tracker.MergeOrAppendGpu(gpu, 11, 3, out d3).Should().BeTrue();
|
|
||||||
d3.Should().BeFalse();
|
|
||||||
tracker.MergeOrAppendGpu(gpu, 11, 4, out d4).Should().BeTrue();
|
|
||||||
d4.Should().BeFalse();
|
|
||||||
tracker.MergeOrAppendGpu(gpu, 11, 5, out d5).Should().BeTrue();
|
|
||||||
d5.Should().BeFalse();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IsDivisibleCpu_requires_non_decreasing_numbers()
|
|
||||||
{
|
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
|
||||||
|
|
||||||
tracker.IsDivisibleCpu(5, 2);
|
|
||||||
|
|
||||||
Action act = () => tracker.IsDivisibleCpu(4, 2);
|
|
||||||
act.Should().Throw<InvalidOperationException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IsDivisibleGpu_requires_non_decreasing_numbers()
|
|
||||||
{
|
|
||||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
tracker.IsDivisibleGpu(gpu, 5, 2);
|
|
||||||
|
|
||||||
Action act = () => tracker.IsDivisibleGpu(gpu, 4, 2);
|
|
||||||
act.Should().Throw<InvalidOperationException>();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
|
||||||
|
|
||||||
public class PollingSemaphoreTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void TryEnter_AllowsConfiguredConcurrency()
|
|
||||||
{
|
|
||||||
var semaphore = new PollingSemaphore(2, TimeSpan.Zero);
|
|
||||||
|
|
||||||
semaphore.TryEnter().Should().BeTrue();
|
|
||||||
semaphore.TryEnter().Should().BeTrue();
|
|
||||||
semaphore.TryEnter().Should().BeFalse();
|
|
||||||
|
|
||||||
semaphore.Release();
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public async Task Wait_BlocksUntilRelease()
|
|
||||||
{
|
|
||||||
var semaphore = new PollingSemaphore(1, TimeSpan.FromMilliseconds(1));
|
|
||||||
|
|
||||||
semaphore.TryEnter().Should().BeTrue();
|
|
||||||
|
|
||||||
Task<bool> waiter = Task.Run(() => semaphore.Wait(TimeSpan.FromMilliseconds(200)));
|
|
||||||
|
|
||||||
await Task.Delay(20);
|
|
||||||
semaphore.Release();
|
|
||||||
|
|
||||||
bool entered = await waiter;
|
|
||||||
entered.Should().BeTrue();
|
|
||||||
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Wait_ReturnsFalseWhenTimeoutHits()
|
|
||||||
{
|
|
||||||
var semaphore = new PollingSemaphore(1, TimeSpan.FromMilliseconds(2));
|
|
||||||
|
|
||||||
semaphore.TryEnter().Should().BeTrue();
|
|
||||||
|
|
||||||
bool acquired = semaphore.Wait(TimeSpan.FromMilliseconds(30));
|
|
||||||
|
|
||||||
acquired.Should().BeFalse();
|
|
||||||
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,228 +1,198 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class PrimeOrderCalculatorTests
|
public class PrimeOrderCalculatorTests
|
||||||
{
|
{
|
||||||
public static IEnumerable<object[]> SmallPrimes()
|
public static IEnumerable<object[]> SmallPrimes()
|
||||||
{
|
{
|
||||||
yield return new object[] { 3UL };
|
yield return new object[] { 3UL };
|
||||||
yield return new object[] { 5UL };
|
yield return new object[] { 5UL };
|
||||||
yield return new object[] { 7UL };
|
yield return new object[] { 7UL };
|
||||||
yield return new object[] { 11UL };
|
yield return new object[] { 11UL };
|
||||||
yield return new object[] { 13UL };
|
yield return new object[] { 13UL };
|
||||||
yield return new object[] { 17UL };
|
yield return new object[] { 17UL };
|
||||||
yield return new object[] { 19UL };
|
yield return new object[] { 19UL };
|
||||||
yield return new object[] { 31UL };
|
yield return new object[] { 31UL };
|
||||||
yield return new object[] { 61UL };
|
yield return new object[] { 61UL };
|
||||||
yield return new object[] { 89UL };
|
yield return new object[] { 89UL };
|
||||||
yield return new object[] { 127UL };
|
yield return new object[] { 127UL };
|
||||||
yield return new object[] { 521UL };
|
yield return new object[] { 521UL };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object[]> PrimesWithPrevious()
|
public static IEnumerable<object[]> PrimesWithPrevious()
|
||||||
{
|
{
|
||||||
yield return new object[] { 5UL, 3UL };
|
yield return new object[] { 5UL, 3UL };
|
||||||
yield return new object[] { 7UL, 5UL };
|
yield return new object[] { 7UL, 5UL };
|
||||||
yield return new object[] { 11UL, 7UL };
|
yield return new object[] { 11UL, 7UL };
|
||||||
yield return new object[] { 13UL, 11UL };
|
yield return new object[] { 13UL, 11UL };
|
||||||
yield return new object[] { 17UL, 13UL };
|
yield return new object[] { 17UL, 13UL };
|
||||||
yield return new object[] { 19UL, 17UL };
|
yield return new object[] { 19UL, 17UL };
|
||||||
yield return new object[] { 31UL, 29UL };
|
yield return new object[] { 31UL, 29UL };
|
||||||
yield return new object[] { 61UL, 59UL };
|
yield return new object[] { 61UL, 59UL };
|
||||||
yield return new object[] { 89UL, 83UL };
|
yield return new object[] { 89UL, 83UL };
|
||||||
yield return new object[] { 127UL, 113UL };
|
yield return new object[] { 127UL, 113UL };
|
||||||
yield return new object[] { 521UL, 509UL };
|
yield return new object[] { 521UL, 509UL };
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[MemberData(nameof(SmallPrimes))]
|
[MemberData(nameof(SmallPrimes))]
|
||||||
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
|
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
|
||||||
{
|
{
|
||||||
ulong expected = ComputeOrderByDoubling(prime);
|
ulong expected = ComputeOrderByDoubling(prime);
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
|
|
||||||
ulong result = PrimeOrderCalculatorGpu.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
gpu,
|
prime,
|
||||||
prime,
|
previousOrder: null,
|
||||||
previousOrder: null,
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
result.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
[Theory]
|
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||||
[Trait("Category", "Fast")]
|
prime,
|
||||||
[MemberData(nameof(PrimesWithPrevious))]
|
previousOrder,
|
||||||
public void Calculate_heuristic_mode_matches_strict_mode_even_with_previous_order(ulong prime, ulong previousPrime)
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
{
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
ulong strict = PrimeOrderCalculator.Calculate(
|
||||||
ulong heuristic = PrimeOrderCalculatorGpu.Calculate(
|
prime,
|
||||||
gpu,
|
previousOrder: null,
|
||||||
prime,
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
previousOrder,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
|
||||||
|
|
||||||
ulong strict = PrimeOrderCalculatorGpu.Calculate(
|
heuristic.Should().Be(strict);
|
||||||
gpu,
|
}
|
||||||
prime,
|
|
||||||
previousOrder: null,
|
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
|
||||||
PrimeOrderCalculatorConfig.StrictDefault);
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
[Theory]
|
||||||
|
[Trait("Category", "Fast")]
|
||||||
|
[InlineData(2UL, 1UL)]
|
||||||
|
[InlineData(3UL, 2UL)]
|
||||||
|
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||||
|
{
|
||||||
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
|
prime,
|
||||||
|
previousOrder: null,
|
||||||
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
heuristic.Should().Be(strict);
|
result.Should().Be(expectedOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(2UL, 1UL)]
|
public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored()
|
||||||
[InlineData(3UL, 2UL)]
|
{
|
||||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
var config = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||||
{
|
smallFactorLimit: 1,
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
pollardRhoMilliseconds: 0,
|
||||||
ulong result = PrimeOrderCalculatorGpu.Calculate(
|
maxPowChecks: 8,
|
||||||
gpu,
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
prime,
|
|
||||||
previousOrder: null,
|
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
|
||||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
result.Should().Be(expectedOrder);
|
prime: 13UL,
|
||||||
}
|
previousOrder: null,
|
||||||
|
MontgomeryDivisorData.FromModulus(13UL),
|
||||||
|
config,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
[Fact]
|
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||||
[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);
|
[Fact]
|
||||||
ulong result = PrimeOrderCalculatorGpu.Calculate(
|
[Trait("Category", "Fast")]
|
||||||
gpu,
|
public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode()
|
||||||
prime: 13UL,
|
{
|
||||||
previousOrder: null,
|
var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||||
MontgomeryDivisorData.FromModulus(13UL),
|
smallFactorLimit: 2,
|
||||||
config);
|
pollardRhoMilliseconds: 0,
|
||||||
|
maxPowChecks: 1,
|
||||||
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||||
result.Should().Be(ComputeOrderByDoubling(13UL));
|
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||||
}
|
prime: 239UL,
|
||||||
|
previousOrder: null,
|
||||||
|
divisorData,
|
||||||
|
heuristicConfig,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
[Fact]
|
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||||
[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);
|
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||||
try
|
smallFactorLimit: 2,
|
||||||
{
|
pollardRhoMilliseconds: 0,
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
maxPowChecks: 1,
|
||||||
ulong heuristic = PrimeOrderCalculatorGpu.Calculate(
|
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||||
gpu,
|
|
||||||
prime: 239UL,
|
|
||||||
previousOrder: null,
|
|
||||||
divisorData,
|
|
||||||
heuristicConfig);
|
|
||||||
|
|
||||||
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
ulong strict = PrimeOrderCalculator.Calculate(
|
||||||
|
prime: 239UL,
|
||||||
|
previousOrder: null,
|
||||||
|
divisorData,
|
||||||
|
strictConfig,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
var strictConfig = new PrimeOrderCalculatorConfig(
|
strict.Should().Be(heuristic);
|
||||||
smallFactorLimit: 2,
|
}
|
||||||
pollardRhoMilliseconds: 0,
|
|
||||||
maxPowChecks: 1,
|
|
||||||
strictMode: true);
|
|
||||||
|
|
||||||
ulong strict = PrimeOrderCalculatorGpu.Calculate(
|
[Fact]
|
||||||
gpu,
|
[Trait("Category", "Fast")]
|
||||||
prime: 239UL,
|
public void Calculate_handles_128_bit_prime()
|
||||||
previousOrder: null,
|
{
|
||||||
divisorData,
|
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||||
strictConfig);
|
|
||||||
|
|
||||||
strict.Should().Be(heuristic);
|
UInt128 result = PrimeOrderCalculator.Calculate(
|
||||||
}
|
prime,
|
||||||
finally
|
previousOrder: null,
|
||||||
{
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
result.Should().Be((UInt128)1229782938247309446UL);
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
public void Calculate_handles_128_bit_prime()
|
|
||||||
{
|
|
||||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
[Fact]
|
||||||
UInt128 result = PrimeOrderCalculatorGpu.Calculate(
|
[Trait("Category", "Fast")]
|
||||||
gpu,
|
public void GetCycle_uses_wide_prime_heuristic()
|
||||||
prime,
|
{
|
||||||
previousOrder: null,
|
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
MersenneDivisorCycles.GetCycle(prime).Should().Be((UInt128)1229782938247309446UL);
|
||||||
|
}
|
||||||
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
private static ulong ComputeOrderByDoubling(ulong prime)
|
||||||
result.Should().Be((UInt128)1229782938247309446UL);
|
{
|
||||||
}
|
if (prime == 2UL)
|
||||||
|
{
|
||||||
|
return 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
if (prime == 3UL)
|
||||||
[Trait("Category", "Fast")]
|
{
|
||||||
public void GetCycle_uses_wide_prime_heuristic()
|
return 2UL;
|
||||||
{
|
}
|
||||||
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)
|
ulong order = 1UL;
|
||||||
{
|
ulong value = 2UL % prime;
|
||||||
if (prime == 2UL)
|
|
||||||
{
|
|
||||||
return 1UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prime == 3UL)
|
while (value != 1UL)
|
||||||
{
|
{
|
||||||
return 2UL;
|
value = (value * 2UL) % prime;
|
||||||
}
|
order++;
|
||||||
|
}
|
||||||
|
|
||||||
ulong order = 1UL;
|
return order;
|
||||||
ulong value = 2UL % prime;
|
}
|
||||||
|
|
||||||
while (value != 1UL)
|
|
||||||
{
|
|
||||||
value = (value * 2UL) % prime;
|
|
||||||
order++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
@@ -7,28 +6,20 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public class PrimeTesterGcdTests
|
public class PrimeTesterGcdTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(81UL, true)]
|
[InlineData(81UL, true)]
|
||||||
[InlineData(101UL, false)]
|
[InlineData(101UL, false)]
|
||||||
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
|
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
|
||||||
{
|
{
|
||||||
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
|
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SharesFactorWithMaxExponentBatch_filters_values()
|
public void SharesFactorWithMaxExponentBatch_filters_values()
|
||||||
{
|
{
|
||||||
ulong[] values = [81UL, 101UL];
|
ulong[] values = [81UL, 101UL];
|
||||||
Span<byte> results = stackalloc byte[values.Length];
|
Span<byte> results = stackalloc byte[values.Length];
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
PrimeTester.SharesFactorWithMaxExponentBatch(values, results);
|
||||||
try
|
results.ToArray().Should().Equal(new byte[] { 1, 0 });
|
||||||
{
|
}
|
||||||
PrimeTester.SharesFactorWithMaxExponentBatch(gpu, values, results);
|
|
||||||
results.ToArray().Should().Equal(new byte[] { 1, 0 });
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,98 +1,70 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class PrimeTesterIsPrimeTests
|
public class PrimeTesterIsPrimeTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
// EvenPerfectBitScanner never routes the tiny values anymore; keep coverage focused on the admissible range handled by production scans so the guards in PrimeTester stay commented out without breaking validation.
|
// EvenPerfectBitScanner never routes the tiny values anymore; keep coverage focused on the admissible range handled by production scans so the guards in PrimeTester stay commented out without breaking validation.
|
||||||
[InlineData(7UL, true)]
|
[InlineData(7UL, true)]
|
||||||
[InlineData(9UL, false)]
|
[InlineData(9UL, false)]
|
||||||
[InlineData(11UL, true)]
|
[InlineData(11UL, true)]
|
||||||
[InlineData(13UL, true)]
|
[InlineData(13UL, true)]
|
||||||
[InlineData(21UL, false)]
|
[InlineData(21UL, false)]
|
||||||
[InlineData(97UL, true)]
|
[InlineData(97UL, true)]
|
||||||
[InlineData(133UL, false)]
|
[InlineData(133UL, false)]
|
||||||
[InlineData(137UL, true)]
|
[InlineData(137UL, true)]
|
||||||
[InlineData(143UL, false)]
|
[InlineData(341UL, false)]
|
||||||
[InlineData(199UL, true)]
|
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
||||||
[InlineData(209UL, false)]
|
{
|
||||||
[InlineData(403UL, false)]
|
var tester = new PrimeTester();
|
||||||
[InlineData(341UL, false)]
|
|
||||||
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
|
||||||
{
|
|
||||||
var tester = new PrimeTester();
|
|
||||||
|
|
||||||
PrimeTester.IsPrimeCpu(n).Should().Be(expected);
|
tester.IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(139UL, true)]
|
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
||||||
[InlineData(189UL, false)]
|
{
|
||||||
[InlineData(193UL, true)]
|
var tester = new PrimeTester();
|
||||||
[InlineData(279UL, false)]
|
GpuContextPool.ForceCpu = true;
|
||||||
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
|
|
||||||
{
|
|
||||||
var tester = new PrimeTester();
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
tester.IsPrimeGpu(137UL, CancellationToken.None).Should().BeTrue();
|
||||||
try
|
|
||||||
{
|
|
||||||
PrimeTester.IsPrimeGpu(gpu, n).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
tester.IsPrimeGpu(341UL, CancellationToken.None).Should().BeFalse();
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
|
||||||
{
|
|
||||||
var tester = new PrimeTester();
|
|
||||||
PrimeTester.IsPrimeCpu(137UL).Should().BeTrue();
|
|
||||||
PrimeTester.IsPrimeCpu(341UL).Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
GpuContextPool.ForceCpu = false;
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
|
|
||||||
{
|
|
||||||
GpuPrimeWorkLimiter.SetLimit(64);
|
|
||||||
PrimeTester.GpuBatchSize = 256_000;
|
|
||||||
|
|
||||||
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
[Fact]
|
||||||
var results = new bool[primes.Length];
|
[Trait("Category", "Fast")]
|
||||||
|
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
|
||||||
|
{
|
||||||
|
GpuContextPool.ForceCpu = false;
|
||||||
|
GpuPrimeWorkLimiter.SetLimit(64);
|
||||||
|
PrimeTester.GpuBatchSize = 256_000;
|
||||||
|
|
||||||
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
var results = new bool[primes.Length];
|
||||||
try
|
|
||||||
{
|
|
||||||
Parallel.For(0, primes.Length, i =>
|
|
||||||
{
|
|
||||||
results[i] = PrimeTester.IsPrimeGpu(gpu, primes[i]);
|
|
||||||
});
|
|
||||||
|
|
||||||
results.Should().AllBeEquivalentTo(true);
|
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||||
|
Parallel.For(0, primes.Length, i =>
|
||||||
|
{
|
||||||
|
results[i] = testers.Value!.IsPrimeGpu(primes[i], CancellationToken.None);
|
||||||
|
});
|
||||||
|
|
||||||
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
|
results.Should().AllBeEquivalentTo(true);
|
||||||
{
|
|
||||||
foreach (ulong prime in primes)
|
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
|
||||||
{
|
{
|
||||||
PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue();
|
foreach (ulong prime in primes)
|
||||||
}
|
{
|
||||||
});
|
testers.Value!.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
|
||||||
}
|
}
|
||||||
finally
|
});
|
||||||
{
|
}
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,39 +32,14 @@ public class PrimesGeneratorTests
|
|||||||
var lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
var lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||||
var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
||||||
var lastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
var lastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
||||||
var lastThree = DivisorGenerator.SmallPrimesLastThree;
|
|
||||||
var lastThreePow2 = DivisorGenerator.SmallPrimesPow2LastThree;
|
|
||||||
var lastNine = DivisorGenerator.SmallPrimesLastNine;
|
|
||||||
var lastNinePow2 = DivisorGenerator.SmallPrimesPow2LastNine;
|
|
||||||
var lastOneWithoutThree = DivisorGenerator.SmallPrimesLastOneWithoutLastThree;
|
|
||||||
var lastOneWithoutThreePow2 = DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree;
|
|
||||||
var lastSevenWithoutThree = DivisorGenerator.SmallPrimesLastSevenWithoutLastThree;
|
|
||||||
var lastSevenWithoutThreePow2 = DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree;
|
|
||||||
var lastNineWithoutThree = DivisorGenerator.SmallPrimesLastNineWithoutLastThree;
|
|
||||||
var lastNineWithoutThreePow2 = DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree;
|
|
||||||
|
|
||||||
lastOne.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
lastOne.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||||
lastSeven.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
lastSeven.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||||
lastThree.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
|
||||||
lastNine.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
|
||||||
lastOneWithoutThree.Length.Should().BeGreaterThan(0);
|
|
||||||
lastSevenWithoutThree.Length.Should().BeGreaterThan(0);
|
|
||||||
lastNineWithoutThree.Length.Should().BeGreaterThan(0);
|
|
||||||
lastOneWithoutThree.Length.Should().BeLessThan(lastOne.Length);
|
|
||||||
lastSevenWithoutThree.Length.Should().BeLessThan(lastSeven.Length);
|
|
||||||
lastNineWithoutThree.Length.Should().BeLessThan(lastNine.Length);
|
|
||||||
lastOnePow2.Length.Should().Be(lastOne.Length);
|
lastOnePow2.Length.Should().Be(lastOne.Length);
|
||||||
lastSevenPow2.Length.Should().Be(lastSeven.Length);
|
lastSevenPow2.Length.Should().Be(lastSeven.Length);
|
||||||
lastThreePow2.Length.Should().Be(lastThree.Length);
|
|
||||||
lastNinePow2.Length.Should().Be(lastNine.Length);
|
|
||||||
lastOneWithoutThreePow2.Length.Should().Be(lastOneWithoutThree.Length);
|
|
||||||
lastSevenWithoutThreePow2.Length.Should().Be(lastSevenWithoutThree.Length);
|
|
||||||
lastNineWithoutThreePow2.Length.Should().Be(lastNineWithoutThree.Length);
|
|
||||||
|
|
||||||
lastOne[..5].Should().Equal([3U, 7U, 11U, 13U, 19U]);
|
lastOne[..5].Should().Equal([3U, 7U, 11U, 13U, 19U]);
|
||||||
lastSeven[..5].Should().Equal([3U, 7U, 11U, 13U, 17U]);
|
lastSeven[..5].Should().Equal([3U, 7U, 11U, 13U, 17U]);
|
||||||
lastThree[..6].Should().Equal([3U, 7U, 11U, 13U, 17U, 19U]);
|
|
||||||
lastNine[..6].Should().Equal([3U, 7U, 11U, 13U, 17U, 19U]);
|
|
||||||
|
|
||||||
for (int i = 0; i < lastOne.Length; i++)
|
for (int i = 0; i < lastOne.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -83,64 +58,6 @@ public class PrimesGeneratorTests
|
|||||||
.Should().BeTrue();
|
.Should().BeTrue();
|
||||||
lastSevenPow2[i].Should().Be(prime * (ulong)prime);
|
lastSevenPow2[i].Should().Be(prime * (ulong)prime);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < lastThree.Length; i++)
|
|
||||||
{
|
|
||||||
uint prime = lastThree[i];
|
|
||||||
uint mod10 = prime % 10U;
|
|
||||||
(mod10 == 3U || mod10 == 7U || prime == 11U || prime == 19U)
|
|
||||||
.Should().BeTrue();
|
|
||||||
lastThreePow2[i].Should().Be(prime * (ulong)prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
lastOneWithoutThree.Should().NotBeEmpty();
|
|
||||||
lastSevenWithoutThree.Should().NotBeEmpty();
|
|
||||||
lastNineWithoutThree.Should().NotBeEmpty();
|
|
||||||
|
|
||||||
for (int i = 0; i < lastOneWithoutThree.Length; i++)
|
|
||||||
{
|
|
||||||
uint prime = lastOneWithoutThree[i];
|
|
||||||
(prime % 10U != 3U).Should().BeTrue();
|
|
||||||
if (i > 0)
|
|
||||||
{
|
|
||||||
prime.Should().BeGreaterThan(lastOneWithoutThree[i - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastOneWithoutThreePow2[i].Should().Be(prime * (ulong)prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < lastSevenWithoutThree.Length; i++)
|
|
||||||
{
|
|
||||||
uint prime = lastSevenWithoutThree[i];
|
|
||||||
(prime % 10U != 3U).Should().BeTrue();
|
|
||||||
if (i > 0)
|
|
||||||
{
|
|
||||||
prime.Should().BeGreaterThan(lastSevenWithoutThree[i - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSevenWithoutThreePow2[i].Should().Be(prime * (ulong)prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < lastNineWithoutThree.Length; i++)
|
|
||||||
{
|
|
||||||
uint prime = lastNineWithoutThree[i];
|
|
||||||
(prime % 10U != 3U).Should().BeTrue();
|
|
||||||
if (i > 0)
|
|
||||||
{
|
|
||||||
prime.Should().BeGreaterThan(lastNineWithoutThree[i - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastNineWithoutThreePow2[i].Should().Be(prime * (ulong)prime);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < lastNine.Length; i++)
|
|
||||||
{
|
|
||||||
uint prime = lastNine[i];
|
|
||||||
uint mod10 = prime % 10U;
|
|
||||||
(mod10 == 3U || mod10 == 7U || mod10 == 9U || prime == 11U)
|
|
||||||
.Should().BeTrue();
|
|
||||||
lastNinePow2[i].Should().Be(prime * (ulong)prime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,305 +1,284 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class UInt128ExtensionsTests
|
public class UInt128ExtensionsTests
|
||||||
{
|
{
|
||||||
public static IEnumerable<object[]> BinaryGcdCases()
|
public static IEnumerable<object[]> BinaryGcdCases()
|
||||||
{
|
{
|
||||||
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
|
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
|
||||||
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
|
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
|
||||||
|
|
||||||
UInt128 big1 = (UInt128.One << 96) + 12345;
|
UInt128 big1 = (UInt128.One << 96) + 12345;
|
||||||
UInt128 big2 = (UInt128.One << 80) + 67890;
|
UInt128 big2 = (UInt128.One << 80) + 67890;
|
||||||
yield return new object[]
|
yield return new object[]
|
||||||
{
|
{
|
||||||
big1,
|
big1,
|
||||||
big2,
|
big2,
|
||||||
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
|
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[MemberData(nameof(BinaryGcdCases))]
|
[MemberData(nameof(BinaryGcdCases))]
|
||||||
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
|
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
|
||||||
{
|
{
|
||||||
a.BinaryGcd(b).Should().Be(expected);
|
a.BinaryGcd(b).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<object[]> TrailingZeroCases()
|
public static IEnumerable<object[]> TrailingZeroCases()
|
||||||
{
|
{
|
||||||
yield return new object[] { UInt128.Zero, 128 };
|
yield return new object[] { UInt128.Zero, 128 };
|
||||||
yield return new object[] { (UInt128)1, 0 };
|
yield return new object[] { (UInt128)1, 0 };
|
||||||
yield return new object[] { (UInt128)8, 3 };
|
yield return new object[] { (UInt128)8, 3 };
|
||||||
yield return new object[] { UInt128.One << 80, 80 };
|
yield return new object[] { UInt128.One << 80, 80 };
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[MemberData(nameof(TrailingZeroCases))]
|
[MemberData(nameof(TrailingZeroCases))]
|
||||||
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
|
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
|
||||||
{
|
{
|
||||||
value.CountTrailingZeros().Should().Be(expected);
|
value.CountTrailingZeros().Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(3UL)]
|
[InlineData(3UL)]
|
||||||
[InlineData(5UL)]
|
[InlineData(5UL)]
|
||||||
[InlineData(7UL)]
|
[InlineData(7UL)]
|
||||||
public void CalculateOrderCpu_matches_naive(ulong q)
|
public void CalculateOrder_matches_naive(ulong q)
|
||||||
{
|
{
|
||||||
UInt128 q128 = q;
|
UInt128 q128 = q;
|
||||||
ulong expected = NaiveOrder(q);
|
ulong expected = NaiveOrder(q);
|
||||||
q128.CalculateOrderCpu().Should().Be(expected);
|
q128.CalculateOrder().Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
private static ulong NaiveOrder(ulong q)
|
||||||
[Trait("Category", "Fast")]
|
{
|
||||||
[InlineData(3UL)]
|
if (q <= 2UL)
|
||||||
[InlineData(5UL)]
|
{
|
||||||
[InlineData(7UL)]
|
return 0UL;
|
||||||
public void CalculateOrderGpu_matches_naive(ulong q)
|
}
|
||||||
{
|
|
||||||
UInt128 q128 = q;
|
|
||||||
ulong expected = NaiveOrder(q);
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
q128.CalculateOrderGpu(gpu).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong NaiveOrder(ulong q)
|
ulong order = 1UL;
|
||||||
{
|
ulong value = 2UL % q;
|
||||||
if (q <= 2UL)
|
while (value != 1UL)
|
||||||
{
|
{
|
||||||
return 0UL;
|
value = (value << 1) % q;
|
||||||
}
|
order++;
|
||||||
|
}
|
||||||
|
|
||||||
ulong order = 1UL;
|
return order;
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(2UL, true)]
|
public void IsPrimeCandidate_detects_large_composite()
|
||||||
[InlineData(9UL, false)]
|
{
|
||||||
[InlineData(97UL, true)]
|
UInt128 n = (UInt128.One << 64) + 1;
|
||||||
public void IsPrimeCandidate_checks_small(ulong n, bool expected)
|
n.IsPrimeCandidate().Should().BeFalse();
|
||||||
{
|
}
|
||||||
((UInt128)n).IsPrimeCandidate().Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsPrimeCandidate_detects_large_composite()
|
public void Mod10_8_5_3_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 n = (UInt128.One << 64) + 1;
|
UInt128 value = (UInt128.One << 100) + 12345678901234567890UL;
|
||||||
n.IsPrimeCandidate().Should().BeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||||
[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);
|
||||||
|
|
||||||
ulong expected10 = (ulong)(value % 10);
|
mod10.Should().Be(expected10);
|
||||||
ulong expected8 = (ulong)(value % 8);
|
mod8.Should().Be(expected8);
|
||||||
ulong expected5 = (ulong)(value % 5);
|
mod5.Should().Be(expected5);
|
||||||
ulong expected3 = (ulong)(value % 3);
|
mod3.Should().Be(expected3);
|
||||||
|
}
|
||||||
|
|
||||||
mod10.Should().Be(expected10);
|
[Theory]
|
||||||
mod8.Should().Be(expected8);
|
[Trait("Category", "Fast")]
|
||||||
mod5.Should().Be(expected5);
|
[InlineData(2UL, 0UL)]
|
||||||
mod3.Should().Be(expected3);
|
[InlineData(2UL, 1UL)]
|
||||||
}
|
[InlineData(2UL, 63UL)]
|
||||||
|
[InlineData(2UL, 127UL)]
|
||||||
|
[InlineData(3UL, 5UL)]
|
||||||
|
public void Pow_matches_BigInteger(ulong value, ulong exponent)
|
||||||
|
{
|
||||||
|
UInt128 v = value;
|
||||||
|
UInt128 expected = (UInt128)BigPow(value, exponent);
|
||||||
|
|
||||||
[Theory]
|
v.Pow(exponent).Should().Be(expected);
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
[InlineData(2UL, 0UL)]
|
|
||||||
[InlineData(2UL, 1UL)]
|
|
||||||
[InlineData(2UL, 63UL)]
|
|
||||||
[InlineData(2UL, 127UL)]
|
|
||||||
[InlineData(3UL, 5UL)]
|
|
||||||
public void Pow_matches_BigInteger(ulong value, ulong exponent)
|
|
||||||
{
|
|
||||||
UInt128 v = value;
|
|
||||||
UInt128 expected = (UInt128)BigPow(value, exponent);
|
|
||||||
|
|
||||||
v.Pow(exponent).Should().Be(expected);
|
private static BigInteger BigPow(ulong value, ulong exponent)
|
||||||
}
|
{
|
||||||
|
BigInteger result = BigInteger.One;
|
||||||
|
BigInteger baseValue = value;
|
||||||
|
|
||||||
private static BigInteger BigPow(ulong value, ulong exponent)
|
while (exponent != 0UL)
|
||||||
{
|
{
|
||||||
BigInteger result = BigInteger.One;
|
if ((exponent & 1UL) != 0UL)
|
||||||
BigInteger baseValue = value;
|
{
|
||||||
|
result *= baseValue;
|
||||||
|
}
|
||||||
|
|
||||||
while (exponent != 0UL)
|
baseValue *= baseValue;
|
||||||
{
|
exponent >>= 1;
|
||||||
if ((exponent & 1UL) != 0UL)
|
}
|
||||||
{
|
|
||||||
result *= baseValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
baseValue *= baseValue;
|
return result;
|
||||||
exponent >>= 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
[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;
|
||||||
|
|
||||||
[Theory]
|
UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus);
|
||||||
[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);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
[Fact]
|
UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m);
|
||||||
[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);
|
||||||
|
}
|
||||||
|
|
||||||
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));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(0UL)]
|
public void Mod8_matches_operator()
|
||||||
[InlineData(1UL)]
|
{
|
||||||
[InlineData(123456789UL)]
|
UInt128 value = (UInt128.One << 100) + 12345;
|
||||||
public void Mod10_matches_operator(ulong low)
|
value.Mod8().Should().Be((ulong)(value % 8));
|
||||||
{
|
}
|
||||||
UInt128 value = (UInt128.One << 100) + low;
|
|
||||||
value.Mod10().Should().Be((ulong)(value % 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod8_matches_operator()
|
public void Mod3_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 100) + 12345;
|
UInt128 value = (UInt128.One << 100) + 12345;
|
||||||
value.Mod8().Should().Be((ulong)(value % 8));
|
value.Mod3().Should().Be((ulong)(value % 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod3_matches_operator()
|
public void Mod6_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 100) + 12345;
|
UInt128 value = (UInt128.One << 100) + 9876543210UL;
|
||||||
value.Mod3().Should().Be((ulong)(value % 3));
|
value.Mod6().Should().Be((ulong)(value % 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod6_matches_operator()
|
public void Mod5_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 100) + 9876543210UL;
|
UInt128 value = (UInt128.One << 100) + 12345;
|
||||||
value.Mod6().Should().Be((ulong)(value % 6));
|
value.Mod5().Should().Be((ulong)(value % 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod5_matches_operator()
|
public void Mod7_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 100) + 12345;
|
UInt128 value = (UInt128.One << 100) + 9876543210987654321UL;
|
||||||
value.Mod5().Should().Be((ulong)(value % 5));
|
value.Mod7().Should().Be((ulong)(value % 7));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod7_matches_operator()
|
public void Mod11_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 100) + 9876543210987654321UL;
|
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
||||||
value.Mod7().Should().Be((ulong)(value % 7));
|
value.Mod11().Should().Be((ulong)(value % 11));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod11_matches_operator()
|
public void Mod128_matches_operator()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
||||||
value.Mod11().Should().Be((ulong)(value % 11));
|
value.Mod128().Should().Be((ulong)(value % 128));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void Mod128_matches_operator()
|
public void Mul64_matches_expected_product()
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
UInt128 a = (UInt128.One << 80) + 123456789;
|
||||||
value.Mod128().Should().Be((ulong)(value % 128));
|
UInt128 b = (UInt128.One << 64) + 987654321;
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
UInt128 expected = (UInt128)((UInt128)(ulong)a * b);
|
||||||
[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);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
[Theory]
|
UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus);
|
||||||
[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);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
[Fact]
|
UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod);
|
||||||
[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);
|
||||||
|
}
|
||||||
a.MulMod(b, mod).Should().Be(expected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,282 +1,225 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|
||||||
public class ULongExtensionsTests
|
public class ULongExtensionsTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(0UL)]
|
[InlineData(0UL)]
|
||||||
[InlineData(1UL)]
|
[InlineData(1UL)]
|
||||||
[InlineData(9UL)]
|
[InlineData(9UL)]
|
||||||
[InlineData(10UL)]
|
[InlineData(10UL)]
|
||||||
[InlineData(1234567890UL)]
|
[InlineData(1234567890UL)]
|
||||||
[InlineData(ulong.MaxValue)]
|
[InlineData(ulong.MaxValue)]
|
||||||
public void Mod10_returns_same_as_operator(ulong value)
|
public void Mod10_returns_same_as_operator(ulong value)
|
||||||
{
|
{
|
||||||
byte expected = (byte)(value % 10UL);
|
ulong expected = value % 10UL;
|
||||||
|
|
||||||
ULongExtensions.Mod10(value).Should().Be(expected);
|
ULongExtensions.Mod10(value).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(0UL)]
|
[InlineData(0UL)]
|
||||||
[InlineData(ulong.MaxValue)]
|
[InlineData(ulong.MaxValue)]
|
||||||
[InlineData(1234567890123456789UL)]
|
[InlineData(1234567890123456789UL)]
|
||||||
public void Mod128_matches_operator(ulong value)
|
public void Mod128_matches_operator(ulong value)
|
||||||
{
|
{
|
||||||
value.Mod128().Should().Be(value % 128UL);
|
value.Mod128().Should().Be(value % 128UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(1UL)]
|
[InlineData(1UL)]
|
||||||
[InlineData(123456789UL)]
|
[InlineData(123456789UL)]
|
||||||
[InlineData(ulong.MaxValue)]
|
[InlineData(ulong.MaxValue)]
|
||||||
public void Mod10_8_5_3Steps_matches_manual_formula(ulong value)
|
public void Mod10_8_5_3Steps_matches_manual_formula(ulong value)
|
||||||
{
|
{
|
||||||
value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
|
value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
|
||||||
|
|
||||||
ulong expected10 = ((value % 10UL) << 1) % 10UL;
|
ulong expected10 = ((value % 10UL) << 1) % 10UL;
|
||||||
ulong expected8 = ((value & 7UL) << 1) & 7UL;
|
ulong expected8 = ((value & 7UL) << 1) & 7UL;
|
||||||
ulong expected5 = ((value % 5UL) << 1) % 5UL;
|
ulong expected5 = ((value % 5UL) << 1) % 5UL;
|
||||||
ulong expected3 = ((value % 3UL) << 1) % 3UL;
|
ulong expected3 = ((value % 3UL) << 1) % 3UL;
|
||||||
|
|
||||||
step10.Should().Be(expected10);
|
step10.Should().Be(expected10);
|
||||||
step8.Should().Be(expected8);
|
step8.Should().Be(expected8);
|
||||||
step5.Should().Be(expected5);
|
step5.Should().Be(expected5);
|
||||||
step3.Should().Be(expected3);
|
step3.Should().Be(expected3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(2UL, true)]
|
[InlineData(2UL, true)]
|
||||||
[InlineData(9UL, false)]
|
[InlineData(9UL, false)]
|
||||||
[InlineData(97UL, true)]
|
[InlineData(97UL, true)]
|
||||||
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
|
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
|
||||||
{
|
{
|
||||||
candidate.IsPrimeCandidate().Should().Be(expected);
|
candidate.IsPrimeCandidate().Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(3UL)]
|
[InlineData(3UL)]
|
||||||
[InlineData(5UL)]
|
[InlineData(5UL)]
|
||||||
[InlineData(7UL)]
|
[InlineData(7UL)]
|
||||||
[InlineData(11UL)]
|
[InlineData(11UL)]
|
||||||
public void CalculateOrderCpu_matches_naive_algorithm(ulong q)
|
public void CalculateOrder_matches_naive_algorithm(ulong q)
|
||||||
{
|
{
|
||||||
ulong expected = NaiveOrder(q);
|
ulong expected = NaiveOrder(q);
|
||||||
|
|
||||||
q.CalculateOrderCpu().Should().Be(expected);
|
q.CalculateOrder().Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(3UL)]
|
[InlineData(2UL, 10UL, 17UL)]
|
||||||
[InlineData(5UL)]
|
[InlineData(123456789UL, 3UL, 97UL)]
|
||||||
[InlineData(7UL)]
|
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
|
||||||
[InlineData(11UL)]
|
{
|
||||||
public void CalculateOrderGpu_matches_naive_algorithm(ulong q)
|
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
|
||||||
{
|
|
||||||
ulong expected = NaiveOrder(q);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
value.ModPow64(exponent, modulus).Should().Be(expected);
|
||||||
try
|
}
|
||||||
{
|
|
||||||
q.CalculateOrderGpu(gpu).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(2UL, 10UL, 17UL)]
|
[InlineData(10UL, 17UL)]
|
||||||
[InlineData(123456789UL, 3UL, 97UL)]
|
[InlineData(123UL, 97UL)]
|
||||||
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
|
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
|
||||||
{
|
{
|
||||||
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
|
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||||
|
|
||||||
value.ModPow64(exponent, modulus).Should().Be(expected);
|
exponent.PowMod((UInt128)modulus).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(10UL, 17UL)]
|
[InlineData(5UL, 7UL, 3UL, 4UL)]
|
||||||
[InlineData(123UL, 97UL)]
|
[InlineData(10UL, 7UL, 3UL, 2UL)]
|
||||||
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
|
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
|
||||||
{
|
{
|
||||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
|
||||||
|
}
|
||||||
|
|
||||||
exponent.PowMod((UInt128)modulus).Should().Be(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]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(5UL, 7UL, 3UL, 4UL)]
|
[InlineData(1234567890123456789UL, 3UL)]
|
||||||
[InlineData(10UL, 7UL, 3UL, 2UL)]
|
[InlineData(ulong.MaxValue, 7UL)]
|
||||||
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
|
public void FastDiv64_matches_division(ulong value, ulong divisor)
|
||||||
{
|
{
|
||||||
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
|
ulong mul = (ulong)(((UInt128)1 << 64) / divisor);
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
value.FastDiv64(divisor, mul).Should().Be(value / divisor);
|
||||||
[Trait("Category", "Fast")]
|
}
|
||||||
[InlineData(15UL, true)]
|
|
||||||
[InlineData(7UL, false)]
|
|
||||||
[InlineData(9UL, false)]
|
|
||||||
public void SharesFactorWithExponentMinusOneCpu_detects_relationship(ulong exponent, bool expected)
|
|
||||||
{
|
|
||||||
exponent.SharesFactorWithExponentMinusOneCpu().Should().Be(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(15UL, true)]
|
[InlineData(0UL, 0UL)]
|
||||||
[InlineData(7UL, false)]
|
[InlineData(ulong.MaxValue, 1UL)]
|
||||||
[InlineData(9UL, false)]
|
[InlineData(123456789UL, 987654321UL)]
|
||||||
public void SharesFactorWithExponentMinusOneGpu_detects_relationship(ulong exponent, bool expected)
|
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
|
||||||
{
|
{
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
ulong expected = (ulong)(((UInt128)x * y) >> 64);
|
||||||
try
|
|
||||||
{
|
|
||||||
exponent.SharesFactorWithExponentMinusOneGpu(gpu).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
x.MulHighCpu(y).Should().Be(expected);
|
||||||
[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 MulHighGpu_matches_cpu_baseline(ulong x, ulong y)
|
||||||
|
{
|
||||||
|
x.MulHighGpu(y).Should().Be(x.MulHighCpu(y));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(0UL, 0UL)]
|
[InlineData(0UL, 0UL)]
|
||||||
[InlineData(ulong.MaxValue, 1UL)]
|
[InlineData(ulong.MaxValue, 2UL)]
|
||||||
[InlineData(123456789UL, 987654321UL)]
|
[InlineData(123456789UL, 987654321UL)]
|
||||||
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
|
public void Mul64_matches_UInt128_product(ulong a, ulong b)
|
||||||
{
|
{
|
||||||
ulong expected = (ulong)(((UInt128)x * y) >> 64);
|
UInt128 expected = (UInt128)a * b;
|
||||||
|
|
||||||
x.MulHighCpu(y).Should().Be(expected);
|
a.Mul64(b).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
[InlineData(0UL, 0UL)]
|
[InlineData(123456789UL, 987654321UL, 97UL)]
|
||||||
[InlineData(ulong.MaxValue, 1UL)]
|
[InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)]
|
||||||
[InlineData(123456789UL, 987654321UL)]
|
public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus)
|
||||||
public void MulHighGpu_matches_cpu_baseline(ulong x, ulong y)
|
{
|
||||||
{
|
ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus);
|
||||||
x.MulHighGpu(y).Should().Be(x.MulHighCpu(y));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
a.MulMod64(b, modulus).Should().Be(expected);
|
||||||
[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(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);
|
||||||
|
|
||||||
[Theory]
|
exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected);
|
||||||
[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_uint128_cycle_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
|
||||||
|
{
|
||||||
|
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||||
|
|
||||||
[Theory]
|
exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected);
|
||||||
[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(11UL, 13UL)]
|
||||||
|
public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus)
|
||||||
|
{
|
||||||
|
MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared;
|
||||||
|
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||||
|
|
||||||
[Theory]
|
exponent.PowModCrt((UInt128)modulus, cycles).Should().Be(expected);
|
||||||
[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);
|
private static ulong NaiveOrder(ulong q)
|
||||||
}
|
{
|
||||||
|
if (q <= 2UL)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
ulong order = 1UL;
|
||||||
[Trait("Category", "Fast")]
|
ulong value = 2UL % q;
|
||||||
[InlineData(11UL, 13UL)]
|
while (value != 1UL)
|
||||||
public void PowModCrtCpu_matches_BigInteger(ulong exponent, ulong modulus)
|
{
|
||||||
{
|
value = (value << 1) % q;
|
||||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
order++;
|
||||||
|
}
|
||||||
|
|
||||||
exponent.PowModCrtCpu((UInt128)modulus).Should().Be(expected);
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
[InlineData(11UL, 13UL)]
|
|
||||||
public void PowModCrtGpu_matches_BigInteger(ulong exponent, ulong modulus)
|
|
||||||
{
|
|
||||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
|
||||||
|
|
||||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
exponent.PowModCrtGpu(gpu, (UInt128)modulus).Should().Be(expected);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
public struct AscendingDivisorRemainderStepper
|
|
||||||
{
|
|
||||||
private readonly ulong _dividend;
|
|
||||||
private ulong _maxDivisor;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
|
||||||
public AscendingDivisorRemainderStepper(ulong value)
|
|
||||||
{
|
|
||||||
_dividend = value;
|
|
||||||
ulong root = (ulong)Math.Sqrt(value);
|
|
||||||
if ((UInt128)root * root < value)
|
|
||||||
{
|
|
||||||
root++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_maxDivisor = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public readonly bool ShouldContinue(ulong divisor) => divisor <= _maxDivisor;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public bool Divides(ulong divisor)
|
|
||||||
{
|
|
||||||
ulong remainder = _dividend;
|
|
||||||
ulong quotient = remainder / divisor;
|
|
||||||
remainder -= quotient * divisor;
|
|
||||||
if (quotient < _maxDivisor)
|
|
||||||
{
|
|
||||||
_maxDivisor = quotient;
|
|
||||||
}
|
|
||||||
|
|
||||||
return remainder == 0UL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
[method: MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public sealed class BTree<TKey, TValue>()
|
|
||||||
{
|
|
||||||
private const int HashByteCount = sizeof(int);
|
|
||||||
private const int DefaultCapacity = 64;
|
|
||||||
private const int SmallBufferCapacity = 4;
|
|
||||||
private const int RootInlineThreshold = 2048;
|
|
||||||
private const int RootInlineInitialCapacity = 32;
|
|
||||||
private const int ShallowTreeThreshold = 1024;
|
|
||||||
private const int ShallowHashByteCount = 1;
|
|
||||||
private const int LeafInitialCapacity = 8;
|
|
||||||
private static readonly EqualityComparer<TKey> _defaultComparer = EqualityComparer<TKey>.Default;
|
|
||||||
|
|
||||||
private readonly TKey[] _smallKeys = new TKey[SmallBufferCapacity];
|
|
||||||
private readonly TValue[] _smallValues = new TValue[SmallBufferCapacity];
|
|
||||||
private int _smallCount;
|
|
||||||
|
|
||||||
private Node[] _nodes = [];
|
|
||||||
private int _nodeCount;
|
|
||||||
|
|
||||||
public int Count;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public void Add(TKey key, TValue value)
|
|
||||||
{
|
|
||||||
TKey[] smallKeys = _smallKeys;
|
|
||||||
TValue[] smallValues = _smallValues;
|
|
||||||
int smallCount = _smallCount;
|
|
||||||
if (smallCount < SmallBufferCapacity)
|
|
||||||
{
|
|
||||||
smallKeys[smallCount] = key;
|
|
||||||
smallValues[smallCount] = value;
|
|
||||||
_smallCount = smallCount + 1;
|
|
||||||
Count++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_nodeCount == 0)
|
|
||||||
{
|
|
||||||
EnsureRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
Node[] nodes = _nodes;
|
|
||||||
if (smallCount > 0)
|
|
||||||
{
|
|
||||||
ref Node rootNode = ref nodes[0];
|
|
||||||
for (int i = 0; i < smallCount; i++)
|
|
||||||
{
|
|
||||||
rootNode.AddKeyValue(smallKeys[i], smallValues[i], RootInlineInitialCapacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
_smallCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Count < RootInlineThreshold)
|
|
||||||
{
|
|
||||||
ref Node root = ref nodes[0];
|
|
||||||
root.AddKeyValue(key, value, RootInlineInitialCapacity);
|
|
||||||
Count++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
InsertIntoTree(GetHash(key), key, value, Count);
|
|
||||||
Count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public int CopyTo(Span<TValue> destination)
|
|
||||||
{
|
|
||||||
ArgumentOutOfRangeException.ThrowIfLessThan(destination.Length, Count);
|
|
||||||
|
|
||||||
var written = 0;
|
|
||||||
|
|
||||||
int smallCount = _smallCount;
|
|
||||||
TValue[] smallValues = _smallValues;
|
|
||||||
for (int i = 0; i < smallCount; i++)
|
|
||||||
{
|
|
||||||
destination[written++] = smallValues[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int nodeCount = _nodeCount;
|
|
||||||
if (nodeCount == 0)
|
|
||||||
{
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
|
|
||||||
FixedCapacityStack<int> stack = new(nodeCount);
|
|
||||||
stack.Push(0);
|
|
||||||
|
|
||||||
Node[]? nodes = _nodes;
|
|
||||||
while (stack.Count > 0)
|
|
||||||
{
|
|
||||||
int index = stack.Pop();
|
|
||||||
ref readonly Node node = ref nodes[index];
|
|
||||||
written += node.CopyValues(destination[written..]);
|
|
||||||
|
|
||||||
for (int childIndex = 0; childIndex < Node.ChildCount; childIndex++)
|
|
||||||
{
|
|
||||||
index = node.GetChild(childIndex);
|
|
||||||
if (index != -1)
|
|
||||||
{
|
|
||||||
stack.Push(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private static uint GetHash(TKey key)
|
|
||||||
{
|
|
||||||
return key is not null
|
|
||||||
? unchecked((uint)_defaultComparer.GetHashCode(key))
|
|
||||||
: 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
||||||
private void EnsureRoot()
|
|
||||||
{
|
|
||||||
_nodes = new Node[DefaultCapacity];
|
|
||||||
_nodes[0] = new Node();
|
|
||||||
_nodeCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private void InsertIntoTree(uint hash, TKey key, TValue value, int currentCount)
|
|
||||||
{
|
|
||||||
Node[]? nodes = _nodes;
|
|
||||||
int currentIndex = 0;
|
|
||||||
int depth = currentCount < ShallowTreeThreshold ? ShallowHashByteCount : HashByteCount;
|
|
||||||
|
|
||||||
int nodeCount = _nodeCount;
|
|
||||||
for (int i = 0; i < depth; i++)
|
|
||||||
{
|
|
||||||
ref Node node = ref nodes[currentIndex];
|
|
||||||
int bucket = (byte)(hash >> (i << 3)) >> 5;
|
|
||||||
int nextIndex = node.GetChild(bucket);
|
|
||||||
if (nextIndex == -1)
|
|
||||||
{
|
|
||||||
nextIndex = nodeCount;
|
|
||||||
nodes = EnsureNodeCapacity(nodes, nextIndex, nodeCount);
|
|
||||||
node = ref nodes[currentIndex];
|
|
||||||
nodes[nextIndex] = new Node();
|
|
||||||
node.SetChild(bucket, nextIndex);
|
|
||||||
nodeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentIndex = nextIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
_nodes = nodes;
|
|
||||||
_nodeCount = nodeCount;
|
|
||||||
ref Node leaf = ref nodes[currentIndex];
|
|
||||||
leaf.AddKeyValue(key, value, LeafInitialCapacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private static Node[] EnsureNodeCapacity(in Node[] nodes, int requiredIndex, int liveNodeCount)
|
|
||||||
{
|
|
||||||
int currentLength = nodes.Length;
|
|
||||||
if (requiredIndex < currentLength)
|
|
||||||
{
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
int targetLength = currentLength == 0 ? DefaultCapacity : currentLength;
|
|
||||||
while (targetLength <= requiredIndex)
|
|
||||||
{
|
|
||||||
targetLength <<= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node[] newNodes = new Node[targetLength];
|
|
||||||
if (liveNodeCount > currentLength)
|
|
||||||
{
|
|
||||||
liveNodeCount = currentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.Copy(nodes, newNodes, liveNodeCount);
|
|
||||||
return newNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct Node
|
|
||||||
{
|
|
||||||
public const int ChildCount = 8;
|
|
||||||
|
|
||||||
private TKey[]? _keys;
|
|
||||||
private TValue[]? _values;
|
|
||||||
private int _entryCount;
|
|
||||||
private int[]? _children;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public readonly int GetChild(int slot)
|
|
||||||
{
|
|
||||||
int[]? children = _children;
|
|
||||||
return children == null ? -1 : children[slot];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public void SetChild(int slot, int index)
|
|
||||||
{
|
|
||||||
int[]? children = _children;
|
|
||||||
if (children == null)
|
|
||||||
{
|
|
||||||
children = new int[ChildCount];
|
|
||||||
for (int i = 0; i < ChildCount; i++)
|
|
||||||
{
|
|
||||||
children[i] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_children = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
children[slot] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public void AddKeyValue(TKey key, TValue value, int desiredInitialCapacity)
|
|
||||||
{
|
|
||||||
TKey[]? keys = _keys;
|
|
||||||
TValue[]? values = _values;
|
|
||||||
int entryCount = _entryCount;
|
|
||||||
|
|
||||||
int newLength;
|
|
||||||
if (keys == null)
|
|
||||||
{
|
|
||||||
newLength = desiredInitialCapacity < 4 ? 4 : desiredInitialCapacity;
|
|
||||||
keys = new TKey[newLength];
|
|
||||||
values = new TValue[newLength];
|
|
||||||
_keys = keys;
|
|
||||||
_values = values;
|
|
||||||
}
|
|
||||||
else if (entryCount == keys.Length)
|
|
||||||
{
|
|
||||||
newLength = keys.Length << 1;
|
|
||||||
TKey[] newKeys = new TKey[newLength];
|
|
||||||
TValue[] newValues = new TValue[newLength];
|
|
||||||
new Span<TKey>(keys, 0, entryCount).CopyTo(newKeys);
|
|
||||||
new Span<TValue>(values, 0, entryCount).CopyTo(newValues);
|
|
||||||
keys = newKeys;
|
|
||||||
values = newValues;
|
|
||||||
_keys = newKeys;
|
|
||||||
_values = newValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
keys[entryCount] = key;
|
|
||||||
values![entryCount] = value;
|
|
||||||
_entryCount = entryCount + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public readonly int CopyValues(Span<TValue> destination)
|
|
||||||
{
|
|
||||||
TValue[]? values = _values;
|
|
||||||
if (values == null)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int entryCount = _entryCount;
|
|
||||||
new Span<TValue>(values, 0, entryCount).CopyTo(destination);
|
|
||||||
return entryCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
public struct BidirectionalCycleRemainderStepper(ulong modulus)
|
|
||||||
{
|
|
||||||
private readonly ulong _modulus = modulus;
|
|
||||||
private ulong _previous = 0UL;
|
|
||||||
private ulong _remainder = 0UL;
|
|
||||||
|
|
||||||
public readonly ulong Modulus
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _modulus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public void Initialize(ulong value)
|
|
||||||
{
|
|
||||||
_remainder = value.ReduceCycleRemainder(_modulus);
|
|
||||||
_previous = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public ulong Step(ulong value)
|
|
||||||
{
|
|
||||||
long delta = unchecked((long)value - (long)_previous);
|
|
||||||
_previous = value;
|
|
||||||
if (delta == 0)
|
|
||||||
{
|
|
||||||
return _remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta > 0)
|
|
||||||
{
|
|
||||||
ulong step = ((ulong)delta).ReduceCycleRemainder(_modulus);
|
|
||||||
ulong sum = _remainder + step;
|
|
||||||
if (sum >= _modulus)
|
|
||||||
{
|
|
||||||
sum -= _modulus;
|
|
||||||
}
|
|
||||||
|
|
||||||
_remainder = sum;
|
|
||||||
return _remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong backward = ((ulong)-delta).ReduceCycleRemainder(_modulus);
|
|
||||||
if (backward > _remainder)
|
|
||||||
{
|
|
||||||
ulong diff = backward - _remainder;
|
|
||||||
ulong wrap = diff.ReduceCycleRemainder(_modulus);
|
|
||||||
_remainder = wrap == 0 ? 0UL : _modulus - wrap;
|
|
||||||
return _remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
_remainder -= backward;
|
|
||||||
return _remainder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maintains rolling remainders of an increasing sequence modulo a BigInteger cycle length.
|
|
||||||
/// Mirrors <see cref="CycleRemainderStepper"/> for values that exceed 64 bits.
|
|
||||||
/// </summary>
|
|
||||||
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public struct BigIntegerCycleRemainderStepper(byte cycleLength)
|
|
||||||
{
|
|
||||||
private readonly BigInteger _cycleLength = (BigInteger)cycleLength;
|
|
||||||
private BigInteger _previousValue = BigInteger.Zero;
|
|
||||||
private BigInteger _currentRemainder = 0;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public byte Initialize(BigInteger value)
|
|
||||||
{
|
|
||||||
_previousValue = value;
|
|
||||||
_currentRemainder = value % _cycleLength;
|
|
||||||
return (byte)_currentRemainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public bool NextIsNotDivisible(BigInteger value)
|
|
||||||
{
|
|
||||||
BigInteger delta = value - _previousValue;
|
|
||||||
_previousValue = value;
|
|
||||||
delta = (_currentRemainder + delta) % _cycleLength;
|
|
||||||
_currentRemainder = delta;
|
|
||||||
return !delta.IsZero;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
public byte ComputeNext(BigInteger value)
|
|
||||||
{
|
|
||||||
BigInteger delta = value - _previousValue;
|
|
||||||
_previousValue = value;
|
|
||||||
delta = (_currentRemainder + delta) % _cycleLength;
|
|
||||||
_currentRemainder = delta;
|
|
||||||
|
|
||||||
return (byte)delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks successive residues of <c>baseValue^exponent mod modulus</c> for ascending exponents using <see cref="BigInteger"/> inputs.
|
|
||||||
/// This avoids recomputing full modular exponentiation when stepping forward by a known delta.
|
|
||||||
/// </summary>
|
|
||||||
public struct BigIntegerExponentStepper(BigInteger modulus, BigInteger baseValue)
|
|
||||||
{
|
|
||||||
private readonly BigInteger _modulus = modulus;
|
|
||||||
private readonly BigInteger _baseValue = baseValue % modulus;
|
|
||||||
private BigInteger PreviousExponent = BigInteger.Zero;
|
|
||||||
private BigInteger CurrentResidue = BigInteger.One % modulus;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public BigInteger Initialize(BigInteger exponent)
|
|
||||||
{
|
|
||||||
CurrentResidue = BigInteger.ModPow(_baseValue, exponent, _modulus);
|
|
||||||
PreviousExponent = exponent;
|
|
||||||
return CurrentResidue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool InitializeIsUnity(BigInteger exponent)
|
|
||||||
{
|
|
||||||
Initialize(exponent);
|
|
||||||
return CurrentResidue.IsOne;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public BigInteger ComputeNext(BigInteger exponent)
|
|
||||||
{
|
|
||||||
if (exponent == PreviousExponent)
|
|
||||||
{
|
|
||||||
return CurrentResidue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger delta = exponent - PreviousExponent;
|
|
||||||
BigInteger deltaResidue = BigInteger.ModPow(_baseValue, delta, _modulus);
|
|
||||||
CurrentResidue = (CurrentResidue * deltaResidue) % _modulus;
|
|
||||||
PreviousExponent = exponent;
|
|
||||||
return CurrentResidue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool ComputeNextIsUnity(BigInteger exponent)
|
|
||||||
{
|
|
||||||
ComputeNext(exponent);
|
|
||||||
return CurrentResidue.IsOne;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
PreviousExponent = BigInteger.Zero;
|
|
||||||
CurrentResidue = BigInteger.One % _modulus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BigInteger helpers including cycle-remainder reduction using the subtract ladder that benchmarked best.
|
|
||||||
/// Shared stats/warm set stay here so ulong reductions can reuse them through ULongExtensions.
|
|
||||||
/// </summary>
|
|
||||||
public static partial class BigIntegerExtensions
|
|
||||||
{
|
|
||||||
// private const int UnrolledSubtractCount = 6;
|
|
||||||
// private static readonly HashSet<ulong> WarmModuli;
|
|
||||||
// private static long _hits;
|
|
||||||
// private static long _misses;
|
|
||||||
|
|
||||||
// static BigIntegerExtensions()
|
|
||||||
// {
|
|
||||||
// ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
|
||||||
// WarmModuli = new HashSet<ulong>(snapshot.Length);
|
|
||||||
// for (int i = 0; i < snapshot.Length; i++)
|
|
||||||
// {
|
|
||||||
// ulong cycle = snapshot[i];
|
|
||||||
// if (cycle > 1UL)
|
|
||||||
// {
|
|
||||||
// WarmModuli.Add(cycle);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public static void DumpCycleRemainderStats()
|
|
||||||
// {
|
|
||||||
// long hits = Interlocked.Read(ref _hits);
|
|
||||||
// long misses = Interlocked.Read(ref _misses);
|
|
||||||
// Console.WriteLine($"CycleRemainderReducerCache: hits={hits}, misses={misses}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static BigInteger ReduceCycleRemainder(this BigInteger value, BigInteger modulus)
|
|
||||||
{
|
|
||||||
// Track(modulus);
|
|
||||||
// DumpCycleRemainderStats();
|
|
||||||
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value -= modulus;
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value -= modulus;
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value -= modulus;
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value -= modulus;
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value -= modulus;
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value -= modulus;
|
|
||||||
if (value < modulus)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value % modulus;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
// internal static void Track(in BigInteger modulus)
|
|
||||||
// {
|
|
||||||
// if (modulus <= ulong.MaxValue)
|
|
||||||
// {
|
|
||||||
// Track((ulong)modulus);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// Interlocked.Increment(ref _misses);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
// internal static void Track(ulong modulus)
|
|
||||||
// {
|
|
||||||
// if (WarmModuli.Contains(modulus))
|
|
||||||
// {
|
|
||||||
// Interlocked.Increment(ref _hits);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// Interlocked.Increment(ref _misses);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core
|
|
||||||
{
|
|
||||||
public static class BigIntegerNumbers
|
|
||||||
{
|
|
||||||
public static readonly BigInteger OneShiftedLeft63 = BigInteger.One << 63;
|
|
||||||
public static readonly BigInteger OneShiftedLeft256MinusOne = (BigInteger.One << 256) - BigInteger.One;
|
|
||||||
public static readonly BigInteger UlongMaxValue = (BigInteger)ulong.MaxValue;
|
|
||||||
public static readonly BigInteger UlongMaxValuePlusOne = UlongMaxValue + BigInteger.One;
|
|
||||||
public static readonly BigInteger UInt128MaxValue = (BigInteger)UInt128.MaxValue;
|
|
||||||
public static readonly BigInteger UInt128MaxValuePlusOne = UInt128MaxValue + BigInteger.One;
|
|
||||||
|
|
||||||
public static readonly BigInteger Two = 2UL;
|
|
||||||
public static readonly BigInteger Pow2Exact2 = 4UL;
|
|
||||||
public static readonly BigInteger Pow2Exact3 = 8UL;
|
|
||||||
public static readonly BigInteger Pow2Exact4 = 16UL;
|
|
||||||
public static readonly BigInteger Pow2Exact5 = 32UL;
|
|
||||||
public static readonly BigInteger Pow2Exact6 = 64UL;
|
|
||||||
public static readonly BigInteger Pow2Exact7 = 128UL;
|
|
||||||
public static readonly BigInteger Pow2Exact8 = 256UL;
|
|
||||||
public static readonly BigInteger Pow2Exact9 = 512UL;
|
|
||||||
public static readonly BigInteger Pow2Exact10 = 1_024UL;
|
|
||||||
public static readonly BigInteger Pow2Exact11 = 2_048UL;
|
|
||||||
public static readonly BigInteger Pow2Exact12 = 4_096UL;
|
|
||||||
public static readonly BigInteger Pow2Exact13 = 8_192UL;
|
|
||||||
public static readonly BigInteger Pow2Exact14 = 16_384UL;
|
|
||||||
public static readonly BigInteger Pow2Exact15 = 32_768UL;
|
|
||||||
public static readonly BigInteger Pow2Exact16 = 65_536UL;
|
|
||||||
public static readonly BigInteger Pow2Exact17 = 131_072UL;
|
|
||||||
public static readonly BigInteger Pow2Exact18 = 262_144UL;
|
|
||||||
public static readonly BigInteger Pow2Exact19 = 524_288UL;
|
|
||||||
public static readonly BigInteger Pow2Exact20 = 1_048_576UL;
|
|
||||||
public static readonly BigInteger Pow2Exact21 = 2_097_152UL;
|
|
||||||
public static readonly BigInteger Pow2Exact22 = 4_194_304UL;
|
|
||||||
public static readonly BigInteger Pow2Exact23 = 8_388_608UL;
|
|
||||||
public static readonly BigInteger Pow2Exact24 = 16_777_216UL;
|
|
||||||
public static readonly BigInteger Pow2Exact25 = 33_554_432UL;
|
|
||||||
public static readonly BigInteger Pow2Exact26 = 67_108_864UL;
|
|
||||||
public static readonly BigInteger Pow2Exact27 = 134_217_728UL;
|
|
||||||
public static readonly BigInteger Pow2Exact28 = 268_435_456UL;
|
|
||||||
public static readonly BigInteger Pow2Exact29 = 536_870_912UL;
|
|
||||||
public static readonly BigInteger Pow2Exact30 = 1_073_741_824UL;
|
|
||||||
public static readonly BigInteger Pow2Exact31 = 2_147_483_648UL;
|
|
||||||
public static readonly BigInteger Pow2Exact32 = 4_294_967_296UL;
|
|
||||||
public static readonly BigInteger Pow2Exact33 = 8_589_934_592UL;
|
|
||||||
public static readonly BigInteger Pow2Exact34 = 17_179_869_184UL;
|
|
||||||
public static readonly BigInteger Pow2Exact35 = 34_359_738_368UL;
|
|
||||||
public static readonly BigInteger Pow2Exact36 = 68_719_476_736UL;
|
|
||||||
public static readonly BigInteger Pow2Exact37 = 137_438_953_472UL;
|
|
||||||
public static readonly BigInteger Pow2Exact38 = 274_877_906_944UL;
|
|
||||||
public static readonly BigInteger Pow2Exact39 = 549_755_813_888UL;
|
|
||||||
public static readonly BigInteger Pow2Exact40 = 1_099_511_627_776UL;
|
|
||||||
public static readonly BigInteger Pow2Exact41 = 2_199_023_255_552UL;
|
|
||||||
public static readonly BigInteger Pow2Exact42 = 4_398_046_511_104UL;
|
|
||||||
public static readonly BigInteger Pow2Exact43 = 8_796_093_022_208UL;
|
|
||||||
public static readonly BigInteger Pow2Exact44 = 17_592_186_044_416UL;
|
|
||||||
public static readonly BigInteger Pow2Exact45 = 35_184_372_088_832UL;
|
|
||||||
public static readonly BigInteger Pow2Exact46 = 70_368_744_177_664UL;
|
|
||||||
public static readonly BigInteger Pow2Exact47 = 140_737_488_355_328UL;
|
|
||||||
public static readonly BigInteger Pow2Exact48 = 281_474_976_710_656UL;
|
|
||||||
public static readonly BigInteger Pow2Exact49 = 562_949_953_421_312UL;
|
|
||||||
public static readonly BigInteger Pow2Exact50 = 1_125_899_906_842_624UL;
|
|
||||||
public static readonly BigInteger Pow2Exact51 = 2_251_799_813_685_248UL;
|
|
||||||
public static readonly BigInteger Pow2Exact52 = 4_503_599_627_370_496UL;
|
|
||||||
public static readonly BigInteger Pow2Exact53 = 9_007_199_254_740_992UL;
|
|
||||||
public static readonly BigInteger Pow2Exact54 = 18_014_398_509_481_984UL;
|
|
||||||
public static readonly BigInteger Pow2Exact55 = 36_028_797_018_963_968UL;
|
|
||||||
public static readonly BigInteger Pow2Exact56 = 72_057_594_037_927_936UL;
|
|
||||||
public static readonly BigInteger Pow2Exact57 = 144_115_188_075_855_872UL;
|
|
||||||
public static readonly BigInteger Pow2Exact58 = 288_230_376_151_711_744UL;
|
|
||||||
public static readonly BigInteger Pow2Exact59 = 576_460_752_303_423_488UL;
|
|
||||||
public static readonly BigInteger Pow2Exact60 = 1_152_921_504_606_846_976UL;
|
|
||||||
public static readonly BigInteger Pow2Exact61 = 2_305_843_009_213_693_952UL;
|
|
||||||
public static readonly BigInteger Pow2Exact62 = 4_611_686_018_427_387_904UL;
|
|
||||||
public static readonly BigInteger Pow2Exact63 = 9_223_372_036_854_775_808UL;
|
|
||||||
|
|
||||||
public static readonly BigInteger Pow2MinusOne1 = 2UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne2 = 4UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne3 = 8UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne4 = 16UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne5 = 32UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne6 = 64UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne7 = 128UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne8 = 256UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne9 = 512UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne10 = 1_024UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne11 = 2_048UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne12 = 4_096UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne13 = 8_192UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne14 = 16_384UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne15 = 32_768UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne16 = 65_536UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne17 = 131_072UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne18 = 262_144UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne19 = 524_288UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne20 = 1_048_576UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne21 = 2_097_152UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne22 = 4_194_304UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne23 = 8_388_608UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne24 = 16_777_216UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne25 = 33_554_432UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne26 = 67_108_864UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne27 = 134_217_728UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne28 = 268_435_456UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne29 = 536_870_912UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne30 = 1_073_741_824UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne31 = 2_147_483_648UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne32 = 4_294_967_296UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne33 = 8_589_934_592UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne34 = 17_179_869_184UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne35 = 34_359_738_368UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne36 = 68_719_476_736UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne37 = 137_438_953_472UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne38 = 274_877_906_944UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne39 = 549_755_813_888UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne40 = 1_099_511_627_776UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne41 = 2_199_023_255_552UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne42 = 4_398_046_511_104UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne43 = 8_796_093_022_208UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne44 = 17_592_186_044_416UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne45 = 35_184_372_088_832UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne46 = 70_368_744_177_664UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne47 = 140_737_488_355_328UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne48 = 281_474_976_710_656UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne49 = 562_949_953_421_312UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne50 = 1_125_899_906_842_624UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne51 = 2_251_799_813_685_248UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne52 = 4_503_599_627_370_496UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne53 = 9_007_199_254_740_992UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne54 = 18_014_398_509_481_984UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne55 = 36_028_797_018_963_968UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne56 = 72_057_594_037_927_936UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne57 = 144_115_188_075_855_872UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne58 = 288_230_376_151_711_744UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne59 = 576_460_752_303_423_488UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne60 = 1_152_921_504_606_846_976UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne61 = 2_305_843_009_213_693_952UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne62 = 4_611_686_018_427_387_904UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne63 = 9_223_372_036_854_775_808UL - 1UL;
|
|
||||||
public static readonly BigInteger Pow2MinusOne1024 = (BigInteger.One << 1024) - BigInteger.One;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,977 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.ByDivisor;
|
|
||||||
|
|
||||||
public readonly struct ColumnBounds(int forcedOnes, int possibleOnes)
|
|
||||||
{
|
|
||||||
public readonly int ForcedOnes = forcedOnes;
|
|
||||||
public readonly int PossibleOnes = possibleOnes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct CarryRange(long min, long max)
|
|
||||||
{
|
|
||||||
public readonly long Min = min;
|
|
||||||
public readonly long Max = max;
|
|
||||||
|
|
||||||
public static CarryRange Single(long value) => new(value, value);
|
|
||||||
public static readonly CarryRange Zero = Single(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ContradictionReason
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
ParityUnreachable,
|
|
||||||
TruncatedLength,
|
|
||||||
TailNotZero,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper utilities for the BitContradiction divisor search. These methods implement the
|
|
||||||
/// interval-based carry propagation described in the BitContradictions plan document.
|
|
||||||
/// The solver itself will build on top of these primitives.
|
|
||||||
/// </summary>
|
|
||||||
internal static class BitContradictionSolverWithQMultiplier
|
|
||||||
{
|
|
||||||
private const int PingPongBlockColumns = 64;
|
|
||||||
private const int TailCarryBatchColumns = 1024;
|
|
||||||
private const int TailCarryBatchRepeats = 1;
|
|
||||||
private const int TopDownCacheCapacity = 16;
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
public readonly struct TopDownPruneFailure(int column, long carryMin, long carryMax, int unknown)
|
|
||||||
{
|
|
||||||
public readonly int Column = column;
|
|
||||||
public readonly long CarryMin = carryMin;
|
|
||||||
public readonly long CarryMax = carryMax;
|
|
||||||
public readonly int Unknown = unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct TopDownCacheValue(bool success, TopDownPruneFailure? failure)
|
|
||||||
{
|
|
||||||
public readonly bool Success = success;
|
|
||||||
public readonly TopDownPruneFailure? Failure = failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal readonly struct BottomUpFailure(int column, long carryMin, long carryMax, int forced, int unknown)
|
|
||||||
{
|
|
||||||
public readonly int Column = column;
|
|
||||||
public readonly long CarryMin = carryMin;
|
|
||||||
public readonly long CarryMax = carryMax;
|
|
||||||
public readonly int Forced = forced;
|
|
||||||
public readonly int Unknown = unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ThreadStatic]
|
|
||||||
private static TopDownPruneFailure? _lastTopDownFailure;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static BottomUpFailure? _lastBottomUpFailure;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static Dictionary<TopDownCacheKey, TopDownCacheValue>? _topDownCache;
|
|
||||||
|
|
||||||
internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure;
|
|
||||||
internal static BottomUpFailure? LastBottomUpFailure => _lastBottomUpFailure;
|
|
||||||
#else
|
|
||||||
[ThreadStatic]
|
|
||||||
private static Dictionary<TopDownCacheKey, bool>? _topDownCache;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[ThreadStatic]
|
|
||||||
private static List<TopDownCacheKey>? _topDownCacheOrder;
|
|
||||||
|
|
||||||
private readonly struct TopDownCacheKey(long hash, int length, int maxAllowedA, int pLong)
|
|
||||||
{
|
|
||||||
public readonly long Hash = hash;
|
|
||||||
public readonly int Length = length;
|
|
||||||
public readonly int MaxAllowedA = maxAllowedA;
|
|
||||||
public readonly int PLong = pLong;
|
|
||||||
|
|
||||||
public bool Equals(TopDownCacheKey other) => Hash == other.Hash && Length == other.Length && MaxAllowedA == other.MaxAllowedA && PLong == other.PLong;
|
|
||||||
public override bool Equals(object? obj) => obj is TopDownCacheKey other && Equals(other);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(Hash, Length, MaxAllowedA, PLong);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long ComputeQOffsetsHash(ReadOnlySpan<int> qOneOffsets)
|
|
||||||
{
|
|
||||||
const long fnvOffset = unchecked((long)1469598103934665603);
|
|
||||||
const long fnvPrime = 1099511628211;
|
|
||||||
long hash = fnvOffset;
|
|
||||||
for (int i = 0; i < qOneOffsets.Length; i++)
|
|
||||||
{
|
|
||||||
hash ^= qOneOffsets[i];
|
|
||||||
hash *= fnvPrime;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
private static bool TryGetTopDownCache(in TopDownCacheKey key, out TopDownCacheValue value)
|
|
||||||
#else
|
|
||||||
private static bool TryGetTopDownCache(in TopDownCacheKey key, out bool value)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
var cache = _topDownCache;
|
|
||||||
if (cache == null)
|
|
||||||
{
|
|
||||||
cache = [];
|
|
||||||
_topDownCache = cache;
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache.TryGetValue(key, out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
private static void StoreTopDownCache(in TopDownCacheKey key, in TopDownCacheValue value)
|
|
||||||
#else
|
|
||||||
private static void StoreTopDownCache(in TopDownCacheKey key, bool value)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
// _topDownCache is always non-null here on EvenPerfectBitScanner execution path.
|
|
||||||
var topDownCache = _topDownCache!;
|
|
||||||
if (topDownCache.ContainsKey(key))
|
|
||||||
{
|
|
||||||
topDownCache[key] = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TopDownCacheKey>? topDownCacheOrder = _topDownCacheOrder;
|
|
||||||
if (topDownCacheOrder == null)
|
|
||||||
{
|
|
||||||
topDownCacheOrder = new List<TopDownCacheKey>(TopDownCacheCapacity + 1);
|
|
||||||
_topDownCacheOrder = topDownCacheOrder;
|
|
||||||
}
|
|
||||||
else if (topDownCacheOrder.Count >= TopDownCacheCapacity)
|
|
||||||
{
|
|
||||||
var evict = topDownCacheOrder[0];
|
|
||||||
topDownCacheOrder.RemoveAt(0);
|
|
||||||
topDownCache.Remove(evict);
|
|
||||||
}
|
|
||||||
|
|
||||||
topDownCache[key] = value;
|
|
||||||
topDownCacheOrder.Add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static ColumnBounds ComputeColumnBounds(
|
|
||||||
in ReadOnlySpan<bool> multiplicand,
|
|
||||||
in ReadOnlySpan<bool> multiplier,
|
|
||||||
int columnIndex)
|
|
||||||
{
|
|
||||||
int forced = 0;
|
|
||||||
int possible = 0;
|
|
||||||
|
|
||||||
int multiplicandLength = multiplicand.Length;
|
|
||||||
int multiplierLength = multiplier.Length;
|
|
||||||
for (int i = 0; i <= columnIndex; i++)
|
|
||||||
{
|
|
||||||
bool a = i < multiplicandLength && multiplicand[i];
|
|
||||||
bool q = columnIndex - i < multiplierLength && multiplier[columnIndex - i];
|
|
||||||
if (!a && !q)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a && q)
|
|
||||||
{
|
|
||||||
forced++;
|
|
||||||
}
|
|
||||||
|
|
||||||
possible++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ColumnBounds(forced, possible);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
internal static bool TryPropagateCarry(
|
|
||||||
CarryRange currentCarry,
|
|
||||||
long forcedOnes,
|
|
||||||
long possibleOnes,
|
|
||||||
int requiredResultBit,
|
|
||||||
out CarryRange nextCarry,
|
|
||||||
out ContradictionReason reason)
|
|
||||||
#else
|
|
||||||
internal static bool TryPropagateCarry(
|
|
||||||
CarryRange currentCarry,
|
|
||||||
int forcedOnes,
|
|
||||||
int possibleOnes,
|
|
||||||
int requiredResultBit,
|
|
||||||
out CarryRange nextCarry)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
long minSum = currentCarry.Min + forcedOnes;
|
|
||||||
long maxSum = currentCarry.Max + possibleOnes;
|
|
||||||
int parity = requiredResultBit & 1;
|
|
||||||
|
|
||||||
minSum = AlignUpToParity(minSum, parity);
|
|
||||||
maxSum = AlignDownToParity(maxSum, parity);
|
|
||||||
|
|
||||||
if (minSum > maxSum)
|
|
||||||
{
|
|
||||||
nextCarry = default;
|
|
||||||
#if DETAILED_LOG
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextCarry = new CarryRange((minSum - requiredResultBit) >> 1, (maxSum - requiredResultBit) >> 1);
|
|
||||||
#if DETAILED_LOG
|
|
||||||
reason = ContradictionReason.None;
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Full column-wise propagation for a given set of q one-bit offsets (LSB=0) and exponent p.
|
|
||||||
/// Returns true if the solver could decide locally (divides or contradiction).
|
|
||||||
/// </summary>
|
|
||||||
internal static bool TryCheckDivisibilityFromOneOffsets(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
ulong p,
|
|
||||||
out bool divides,
|
|
||||||
out ContradictionReason reason)
|
|
||||||
{
|
|
||||||
divides = false;
|
|
||||||
#if DETAILED_LOG
|
|
||||||
_lastTopDownFailure = null;
|
|
||||||
_lastBottomUpFailure = null;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// These conditions will never be true on EvenPerfectBitScanner execution path.
|
|
||||||
// if (p == 0 || qOneOffsets.Length == 0)
|
|
||||||
// {
|
|
||||||
// reason = ContradictionReason.ParityUnreachable;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// While the scanner supports p >= 31, it's a tiny subset of all checked exponents. No need to support it.
|
|
||||||
// Exact small-case evaluation to short-circuit both success and failure deterministically.
|
|
||||||
// int highestBit = qOneOffsets[^1];
|
|
||||||
// if (p <= 64 && highestBit < 63)
|
|
||||||
// {
|
|
||||||
// ulong q = 0;
|
|
||||||
// foreach (int bit in qOneOffsets)
|
|
||||||
// {
|
|
||||||
// q |= 1UL << bit;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ulong mersenne = (1UL << (int)p) - 1UL;
|
|
||||||
// divides = q != 0 && (mersenne % q == 0);
|
|
||||||
// reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int qOneOffsetsLength = qOneOffsets.Length;
|
|
||||||
int maxOffset = qOneOffsets[qOneOffsetsLength - 1];
|
|
||||||
if ((ulong)(maxOffset + 1) >= p)
|
|
||||||
{
|
|
||||||
reason = ContradictionReason.TruncatedLength;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// q from EvenPerfectBitScanner will always have the correct 2kp+1 form.
|
|
||||||
// if (qOneOffsets[0] != 0)
|
|
||||||
// {
|
|
||||||
// reason = ContradictionReason.ParityUnreachable;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int windowSize = maxOffset + 1;
|
|
||||||
var segmentState0 = new sbyte[windowSize];
|
|
||||||
var segmentState1 = new sbyte[windowSize];
|
|
||||||
long segmentBase = 0;
|
|
||||||
int pLong = (int)p;
|
|
||||||
int maxAllowedA = pLong - (maxOffset + 1);
|
|
||||||
if (maxAllowedA < 0)
|
|
||||||
{
|
|
||||||
reason = ContradictionReason.TruncatedLength;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
long qHash = ComputeQOffsetsHash(qOneOffsets);
|
|
||||||
var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong);
|
|
||||||
if (TryGetTopDownCache(in cacheKey, out var cached))
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
_lastTopDownFailure = cached.Failure;
|
|
||||||
if (!cached.Success)
|
|
||||||
#else
|
|
||||||
if (!cached)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
#if DETAILED_LOG
|
|
||||||
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
|
|
||||||
#else
|
|
||||||
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
StoreTopDownCache(in cacheKey, new TopDownCacheValue(false, _lastTopDownFailure));
|
|
||||||
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
|
|
||||||
#else
|
|
||||||
StoreTopDownCache(in cacheKey, false);
|
|
||||||
#endif
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
StoreTopDownCache(in cacheKey, new TopDownCacheValue(true, null));
|
|
||||||
#else
|
|
||||||
StoreTopDownCache(in cacheKey, true);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int lowColumn = 0;
|
|
||||||
int highColumn = pLong - 1;
|
|
||||||
CarryRange carryLow = CarryRange.Zero;
|
|
||||||
CarryRange carryHigh = CarryRange.Zero;
|
|
||||||
int bottomWindowStart = 0;
|
|
||||||
int bottomWindowEnd = 0;
|
|
||||||
int topWindowStart = qOneOffsetsLength;
|
|
||||||
int topWindowEnd = qOneOffsetsLength;
|
|
||||||
|
|
||||||
bool processTop = true;
|
|
||||||
|
|
||||||
while (lowColumn <= highColumn)
|
|
||||||
{
|
|
||||||
int remaining = (int)(highColumn - lowColumn + 1);
|
|
||||||
int blockSize = remaining < PingPongBlockColumns ? remaining : PingPongBlockColumns;
|
|
||||||
|
|
||||||
if (processTop)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh, out _lastTopDownFailure))
|
|
||||||
#else
|
|
||||||
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
highColumn -= blockSize;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!TryProcessBottomUpBlock(qOneOffsets, maxAllowedA, lowColumn, blockSize, ref carryLow, ref segmentBase, ref bottomWindowStart, ref bottomWindowEnd, segmentState0, segmentState1, out reason))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
lowColumn += blockSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
processTop = !processTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
long carryMin = carryLow.Min > carryHigh.Min ? carryLow.Min : carryHigh.Min;
|
|
||||||
long carryMax = carryLow.Max < carryHigh.Max ? carryLow.Max : carryHigh.Max;
|
|
||||||
if (carryMin > carryMax)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
Console.WriteLine($"[bit-contradiction] parity unreachable after ping-pong carry merge (low=[{carryLow.Min},{carryLow.Max}] high=[{carryHigh.Min},{carryHigh.Max}])");
|
|
||||||
#endif
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
divides = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
divides = true;
|
|
||||||
reason = ContradictionReason.None;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool TryCheckDivisibilityFromOneOffsets(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
ulong p,
|
|
||||||
out bool divides) => TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _);
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private static bool TryAdvanceZeroColumns(ref CarryRange carry, int columnCount)
|
|
||||||
{
|
|
||||||
long carryMin = carry.Min;
|
|
||||||
long carryMax = carry.Max;
|
|
||||||
while (columnCount > 0)
|
|
||||||
{
|
|
||||||
int step = columnCount > 30 ? 30 : columnCount;
|
|
||||||
int add = (1 << step) - 1;
|
|
||||||
long nextMin = (carryMin + add) >> step;
|
|
||||||
long nextMax = carryMax >> step;
|
|
||||||
if (nextMin > nextMax)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
carryMin = nextMin;
|
|
||||||
carryMax = nextMax;
|
|
||||||
columnCount -= step;
|
|
||||||
}
|
|
||||||
|
|
||||||
carry = new CarryRange(carryMin, carryMax);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryProcessBottomUpBlock(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
int maxAllowedA,
|
|
||||||
int startColumn,
|
|
||||||
int columnCount,
|
|
||||||
ref CarryRange carry,
|
|
||||||
ref long segmentBase,
|
|
||||||
ref int windowStartIdx,
|
|
||||||
ref int windowEndIdx,
|
|
||||||
sbyte[] segmentState0,
|
|
||||||
sbyte[] segmentState1,
|
|
||||||
out ContradictionReason reason)
|
|
||||||
{
|
|
||||||
long endColumn = startColumn + columnCount;
|
|
||||||
int qOneOffsetsLength = qOneOffsets.Length;
|
|
||||||
int windowSize = segmentState0.Length;
|
|
||||||
for (int column = startColumn; column < endColumn; column++)
|
|
||||||
{
|
|
||||||
int forced = 0;
|
|
||||||
int unknown = 0;
|
|
||||||
int chosenIndex = -1;
|
|
||||||
int newBase = (column / windowSize) * windowSize;
|
|
||||||
if (newBase != segmentBase)
|
|
||||||
{
|
|
||||||
if (newBase == segmentBase + windowSize)
|
|
||||||
{
|
|
||||||
(segmentState0, segmentState1) = (segmentState1, segmentState0);
|
|
||||||
Array.Clear(segmentState0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Array.Clear(segmentState0);
|
|
||||||
Array.Clear(segmentState1);
|
|
||||||
}
|
|
||||||
|
|
||||||
segmentBase = newBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (windowEndIdx < qOneOffsetsLength && qOneOffsets[windowEndIdx] <= column)
|
|
||||||
{
|
|
||||||
windowEndIdx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lowerBound = column - maxAllowedA;
|
|
||||||
while (windowStartIdx < windowEndIdx && qOneOffsets[windowStartIdx] < lowerBound)
|
|
||||||
{
|
|
||||||
windowStartIdx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int offsetIndex = windowStartIdx; offsetIndex < windowEndIdx; offsetIndex++)
|
|
||||||
{
|
|
||||||
int offset = qOneOffsets[offsetIndex];
|
|
||||||
int aIndex = column - offset;
|
|
||||||
if (aIndex < 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aIndex > maxAllowedA)
|
|
||||||
{
|
|
||||||
continue; // a must be zero here
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aIndex >= segmentBase)
|
|
||||||
{
|
|
||||||
int slot = (int)(aIndex - segmentBase);
|
|
||||||
sbyte state = segmentState0[slot];
|
|
||||||
if (state == 1)
|
|
||||||
{
|
|
||||||
forced++;
|
|
||||||
}
|
|
||||||
else if (state == 0)
|
|
||||||
{
|
|
||||||
unknown++;
|
|
||||||
if (chosenIndex == -1L)
|
|
||||||
{
|
|
||||||
chosenIndex = aIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (aIndex >= segmentBase - windowSize)
|
|
||||||
{
|
|
||||||
int slot = (int)(aIndex - (segmentBase - windowSize));
|
|
||||||
sbyte state = segmentState1[slot];
|
|
||||||
if (state == 1)
|
|
||||||
{
|
|
||||||
forced++;
|
|
||||||
}
|
|
||||||
else if (state == 0)
|
|
||||||
{
|
|
||||||
unknown++;
|
|
||||||
if (chosenIndex == -1L)
|
|
||||||
{
|
|
||||||
chosenIndex = aIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unknown++;
|
|
||||||
if (chosenIndex == -1L)
|
|
||||||
{
|
|
||||||
chosenIndex = aIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long minSum = carry.Min;
|
|
||||||
long maxSum = carry.Max;
|
|
||||||
if (unknown == 1 && ((minSum ^ maxSum) & 1) == 0)
|
|
||||||
{
|
|
||||||
int requiredUnknownParity = 1 ^ ((int)minSum & 1) ^ ((int)forced & 1);
|
|
||||||
if (requiredUnknownParity != 0)
|
|
||||||
{
|
|
||||||
if (chosenIndex >= 0)
|
|
||||||
{
|
|
||||||
if (chosenIndex >= segmentBase)
|
|
||||||
{
|
|
||||||
int slot = (int)(chosenIndex - segmentBase);
|
|
||||||
segmentState0[slot] = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int slot = (int)(chosenIndex - (segmentBase - windowSize));
|
|
||||||
segmentState1[slot] = 1;
|
|
||||||
}
|
|
||||||
forced++;
|
|
||||||
unknown = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unknown = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unknown == 0)
|
|
||||||
{
|
|
||||||
int requiredCarryParity = 1 ^ ((int)forced & 1);
|
|
||||||
long carryMin = AlignUpToParity(carry.Min, requiredCarryParity);
|
|
||||||
long carryMax = AlignDownToParity(carry.Max, requiredCarryParity);
|
|
||||||
if (carryMin > carryMax)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carryMin, carryMax, forced, unknown);
|
|
||||||
#endif
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
carry = new CarryRange(carryMin, carryMax);
|
|
||||||
minSum = carry.Min;
|
|
||||||
maxSum = carry.Max;
|
|
||||||
}
|
|
||||||
|
|
||||||
minSum += forced;
|
|
||||||
maxSum += forced + unknown;
|
|
||||||
const int requiredParity = 1;
|
|
||||||
|
|
||||||
if (minSum == maxSum)
|
|
||||||
{
|
|
||||||
if ((minSum & 1) != requiredParity)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
|
||||||
#endif
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long nextCarry = (minSum - requiredParity) >> 1;
|
|
||||||
carry = CarryRange.Single(nextCarry);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((minSum & 1) != requiredParity && unknown == 0)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
|
||||||
#endif
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
if (!TryPropagateCarry(
|
|
||||||
carry,
|
|
||||||
forced,
|
|
||||||
forced + unknown,
|
|
||||||
requiredParity,
|
|
||||||
out var next,
|
|
||||||
out var propagateReason))
|
|
||||||
#else
|
|
||||||
if (!TryPropagateCarry(
|
|
||||||
carry,
|
|
||||||
forced,
|
|
||||||
forced + unknown,
|
|
||||||
requiredParity,
|
|
||||||
out var next))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
|
||||||
reason = propagateReason;
|
|
||||||
#else
|
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
carry = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
reason = ContradictionReason.None;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
private static bool TryProcessTopDownBlock(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
int qOneOffsetsLength,
|
|
||||||
int maxAllowedA,
|
|
||||||
int startHighColumn,
|
|
||||||
int columnCount,
|
|
||||||
CarryRange carryOut,
|
|
||||||
ref int windowStart,
|
|
||||||
ref int windowEnd,
|
|
||||||
out CarryRange nextCarryOut,
|
|
||||||
out TopDownPruneFailure? failure)
|
|
||||||
#else
|
|
||||||
private static bool TryProcessTopDownBlock(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
int qOneOffsetsLength,
|
|
||||||
int maxAllowedA,
|
|
||||||
int startHighColumn,
|
|
||||||
int columnCount,
|
|
||||||
CarryRange carryOut,
|
|
||||||
ref int windowStart,
|
|
||||||
ref int windowEnd,
|
|
||||||
out CarryRange nextCarryOut)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = null;
|
|
||||||
#endif
|
|
||||||
long carryOutMin = carryOut.Min,
|
|
||||||
carryOutMax = carryOut.Max;
|
|
||||||
int value;
|
|
||||||
int endColumn = startHighColumn - columnCount + 1;
|
|
||||||
|
|
||||||
int column = startHighColumn;
|
|
||||||
while (column >= endColumn)
|
|
||||||
{
|
|
||||||
// value is used as high column index
|
|
||||||
value = column;
|
|
||||||
while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value)
|
|
||||||
{
|
|
||||||
windowEnd--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is used as low column index
|
|
||||||
value = column - maxAllowedA;
|
|
||||||
if (value < 0)
|
|
||||||
{
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value)
|
|
||||||
{
|
|
||||||
windowStart--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowStart > windowEnd)
|
|
||||||
{
|
|
||||||
windowStart = windowEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is unknown bits count here
|
|
||||||
value = windowEnd - windowStart;
|
|
||||||
if (value == 0)
|
|
||||||
{
|
|
||||||
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
|
||||||
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
|
||||||
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
|
||||||
if (chunkEnd < endColumn)
|
|
||||||
{
|
|
||||||
chunkEnd = endColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
int steps = (int)(column - chunkEnd + 1);
|
|
||||||
if (steps > 1)
|
|
||||||
{
|
|
||||||
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
|
||||||
#endif
|
|
||||||
nextCarryOut = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
column = chunkEnd - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value == 1)
|
|
||||||
{
|
|
||||||
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
|
||||||
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
|
||||||
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
|
||||||
if (chunkEnd < endColumn)
|
|
||||||
{
|
|
||||||
chunkEnd = endColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
int steps = (int)(column - chunkEnd + 1);
|
|
||||||
if (steps > 1)
|
|
||||||
{
|
|
||||||
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
|
||||||
#endif
|
|
||||||
nextCarryOut = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
column = chunkEnd - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// carryOutMin now becomes nextCarryMin to limit registry pressure
|
|
||||||
carryOutMin = (carryOutMin << 1) + 1 - value;
|
|
||||||
// carryOutMax now becomes nextCarryMax to limit registry pressure
|
|
||||||
carryOutMax = (carryOutMax << 1) + 1;
|
|
||||||
if (carryOutMin < 0)
|
|
||||||
{
|
|
||||||
carryOutMin = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (carryOutMax < 0)
|
|
||||||
{
|
|
||||||
carryOutMax = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (carryOutMin > carryOutMax)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
|
||||||
#endif
|
|
||||||
nextCarryOut = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
column--;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextCarryOut = new CarryRange(carryOutMin, carryOutMax);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long AlignUpToParity(long value, int parity) => (value & 1L) == parity ? value : value + 1L;
|
|
||||||
private static long AlignDownToParity(long value, int parity) => (value & 1L) == parity ? value : value - 1L;
|
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
private static bool TryTopDownCarryPrune(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
int qOneOffsetsLength,
|
|
||||||
int pLong,
|
|
||||||
int maxAllowedA,
|
|
||||||
out TopDownPruneFailure? failure)
|
|
||||||
#else
|
|
||||||
private static bool TryTopDownCarryPrune(
|
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
|
||||||
int qOneOffsetsLength,
|
|
||||||
int pLong,
|
|
||||||
int maxAllowedA)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = null;
|
|
||||||
#endif
|
|
||||||
long carryOutMin = 0,
|
|
||||||
carryOutMax = 0;
|
|
||||||
int value;
|
|
||||||
|
|
||||||
int windowStart = qOneOffsetsLength;
|
|
||||||
int windowEnd = qOneOffsetsLength;
|
|
||||||
|
|
||||||
int column = pLong - 1;
|
|
||||||
while (column >= 0)
|
|
||||||
{
|
|
||||||
// value is used as high column index
|
|
||||||
value = column;
|
|
||||||
while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value)
|
|
||||||
{
|
|
||||||
windowEnd--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is used as low column index
|
|
||||||
value = column - maxAllowedA;
|
|
||||||
if (value < 0)
|
|
||||||
{
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value)
|
|
||||||
{
|
|
||||||
windowStart--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowStart > windowEnd)
|
|
||||||
{
|
|
||||||
windowStart = windowEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is used as unknown bits count here
|
|
||||||
value = windowEnd - windowStart;
|
|
||||||
if (value == 0)
|
|
||||||
{
|
|
||||||
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
|
||||||
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
|
||||||
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
|
||||||
if (chunkEnd < 0)
|
|
||||||
{
|
|
||||||
chunkEnd = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int steps = (int)(column - chunkEnd + 1);
|
|
||||||
if (steps > 1)
|
|
||||||
{
|
|
||||||
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
column = chunkEnd - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value == 1)
|
|
||||||
{
|
|
||||||
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
|
||||||
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
|
||||||
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
|
||||||
if (chunkEnd < 0)
|
|
||||||
{
|
|
||||||
chunkEnd = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int steps = (int)(column - chunkEnd + 1);
|
|
||||||
if (steps > 1)
|
|
||||||
{
|
|
||||||
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
column = chunkEnd - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// carryOutMin now becomes nextCarryMin to limit registry pressure
|
|
||||||
carryOutMin = (carryOutMin << 1) + 1 - value;
|
|
||||||
// carryOutMax now becomes nextCarryMax to limit registry pressure
|
|
||||||
carryOutMax = (carryOutMax << 1) + 1;
|
|
||||||
if (carryOutMin < 0)
|
|
||||||
{
|
|
||||||
carryOutMin = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (carryOutMax < 0)
|
|
||||||
{
|
|
||||||
carryOutMax = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (carryOutMin > carryOutMax)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
column--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (carryOutMin > 0)
|
|
||||||
{
|
|
||||||
#if DETAILED_LOG
|
|
||||||
failure = new TopDownPruneFailure(0, carryOutMin, carryOutMax, 0);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool AdvanceTopDownUnknown1(ref long carryOutMin, ref long carryOutMax, int steps)
|
|
||||||
{
|
|
||||||
while (steps > 0)
|
|
||||||
{
|
|
||||||
int step = steps > 30 ? 30 : steps;
|
|
||||||
int pow = 1 << step;
|
|
||||||
|
|
||||||
if (carryOutMin > (long.MaxValue >> step) || carryOutMax > ((long.MaxValue - (pow - 1)) >> step))
|
|
||||||
{
|
|
||||||
carryOutMin = 0;
|
|
||||||
carryOutMax = int.MaxValue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
carryOutMin <<= step;
|
|
||||||
carryOutMax = (carryOutMax << step) + (pow - 1);
|
|
||||||
steps -= step;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool AdvanceTopDownUnknown0(ref long carryOutMin, ref long carryOutMax, int steps)
|
|
||||||
{
|
|
||||||
while (steps > 0)
|
|
||||||
{
|
|
||||||
int step = steps > 30 ? 30 : steps;
|
|
||||||
int add = (1 << step) - 1;
|
|
||||||
if (carryOutMin > ((int.MaxValue - add) >> step) || carryOutMax > ((int.MaxValue - add) >> step))
|
|
||||||
{
|
|
||||||
carryOutMin = 0;
|
|
||||||
carryOutMax = int.MaxValue;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
carryOutMin = (carryOutMin << step) + add;
|
|
||||||
carryOutMax = (carryOutMax << step) + add;
|
|
||||||
steps -= step;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loaded 100 of 240 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user