Compare commits
338
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a44dbb5171 | ||
|
|
3ca99460bb | ||
|
|
b9b8422fb9 | ||
|
|
f755b30153 | ||
|
|
67291f76a2 | ||
|
|
5eee2cd4a0 | ||
|
|
f864e73f67 | ||
|
|
38b83a3d55 | ||
|
|
c6be5ee679 | ||
|
|
80e58fc55b | ||
|
|
b2a6a1c332 | ||
|
|
b014fdb7f1 | ||
|
|
9886d8bbb2 | ||
|
|
1719b3350d | ||
|
|
dfd3f71f5d | ||
|
|
cc8bb98e2c | ||
|
|
04545c93b4 | ||
|
|
edecffe10b | ||
|
|
13e1c7f34e | ||
|
|
c63ff5df32 | ||
|
|
302bfba40c | ||
|
|
c11a886197 | ||
|
|
b9f2b08c78 | ||
|
|
d9816b48e2 | ||
|
|
3212297911 | ||
|
|
e53f0b7a95 | ||
|
|
c537e915d1 | ||
|
|
afed716105 | ||
|
|
c8aa842eeb | ||
|
|
b934112074 | ||
|
|
4f12375427 | ||
|
|
778a36d2eb | ||
|
|
1a97cdf754 | ||
|
|
41e421e1eb | ||
|
|
e97ea151e1 | ||
|
|
09945544fc | ||
|
|
047e3d6233 | ||
|
|
e738ee29e0 | ||
|
|
12c477b1a2 | ||
|
|
d0dbb8f92a | ||
|
|
c6dea4afcc | ||
|
|
bf93bcff1a | ||
|
|
dc7a499f82 | ||
|
|
2b803abcac | ||
|
|
458e6d14a0 | ||
|
|
d88ad25476 | ||
|
|
45e3455ff3 | ||
|
|
6a924f4041 | ||
|
|
27f65baf3d | ||
|
|
a6d408f2aa | ||
|
|
ef8034b258 | ||
|
|
6b80e31e6c | ||
|
|
3f59cdc14b | ||
|
|
ce3079a48e | ||
|
|
f3ed5b04a1 | ||
|
|
0d11199ef2 | ||
|
|
bfb3bcb1e3 | ||
|
|
be92ef1ae3 | ||
|
|
7e00a73174 | ||
|
|
e02264d3f1 | ||
|
|
48ff886fb2 | ||
|
|
9a6a1968b7 | ||
|
|
87a66e4eca | ||
|
|
9391954b3e | ||
|
|
759614de54 | ||
|
|
3342ead2d4 | ||
|
|
75a2017385 | ||
|
|
6603f65993 | ||
|
|
6678f24887 | ||
|
|
2bf9ff233c | ||
|
|
48df3df290 | ||
|
|
9147124ddf | ||
|
|
c5c42b0c19 | ||
|
|
c9a421a1b4 | ||
|
|
d32eea0212 | ||
|
|
9ff0431f59 | ||
|
|
a21ab3134e | ||
|
|
b53d00a8cc | ||
|
|
74837a407e | ||
|
|
31ecf24522 | ||
|
|
870819dec6 | ||
|
|
4edad10be8 | ||
|
|
ed299edf56 | ||
|
|
2236c3ec35 | ||
|
|
8ea975f2ed | ||
|
|
af7c9491e4 | ||
|
|
0b226c76e8 | ||
|
|
9faa397364 | ||
|
|
23748ad3d2 | ||
|
|
189013b849 | ||
|
|
2abc6aae49 | ||
|
|
3c80221105 | ||
|
|
b7773adad8 | ||
|
|
6d427c6906 | ||
|
|
566cbc3599 | ||
|
|
5523469e8a | ||
|
|
046cfe7d26 | ||
|
|
97fd041ec0 | ||
|
|
2c18818ac8 | ||
|
|
048d1576ef | ||
|
|
dce598c857 | ||
|
|
a8a41c6132 | ||
|
|
7c3ed34df3 | ||
|
|
695e164f51 | ||
|
|
974b1da873 | ||
|
|
e0daccdc1a | ||
|
|
bca059c4f2 | ||
|
|
e8cca74f68 | ||
|
|
1b3882addc | ||
|
|
0fb5a5fa10 | ||
|
|
079b2d5b72 | ||
|
|
96bb6a4340 | ||
|
|
a185149eba | ||
|
|
9fcd21b734 | ||
|
|
cbf3dfcf2e | ||
|
|
ec080ade7f | ||
|
|
8fbe248a49 | ||
|
|
39a66fdef1 | ||
|
|
fd58079c5a | ||
|
|
964bc8b734 | ||
|
|
d9bff88055 | ||
|
|
2607c6e2d4 | ||
|
|
e8e67ebf83 | ||
|
|
ca3785c20d | ||
|
|
8b0d09cc9c | ||
|
|
8a5a8a7bb5 | ||
|
|
382f0c01a2 | ||
|
|
3e591586fb | ||
|
|
d066c780ea | ||
|
|
15bcde1776 | ||
|
|
471f9ac6cf | ||
|
|
8afd761d2a | ||
|
|
477d2675ef | ||
|
|
10f0fa272c | ||
|
|
45c477de5a | ||
|
|
49e47b161d | ||
|
|
43376745f5 | ||
|
|
0ec48f1f0b | ||
|
|
51284be4fb | ||
|
|
8f0cc17d90 | ||
|
|
60ccb3db74 | ||
|
|
260aa19e44 | ||
|
|
547d43de84 | ||
|
|
131b3265bc | ||
|
|
5df2cc7f9b | ||
|
|
05d3061c47 | ||
|
|
7a242b3bfa | ||
|
|
72890bf343 | ||
|
|
7518ba396e | ||
|
|
f7dbc017a9 | ||
|
|
475507ce0c | ||
|
|
5c91d79927 | ||
|
|
6e68252279 | ||
|
|
f1109330df | ||
|
|
f76cf05e10 | ||
|
|
d69ad9c64a | ||
|
|
6887111128 | ||
|
|
fbf074ade0 | ||
|
|
3f9cd53efc | ||
|
|
a4c52e7282 | ||
|
|
9af8244040 | ||
|
|
788e11588e | ||
|
|
c44d188720 | ||
|
|
3cde986fa0 | ||
|
|
573781f086 | ||
|
|
374bf5fe53 | ||
|
|
946798affe | ||
|
|
86f20ed083 | ||
|
|
cf5bd6bc1a | ||
|
|
928e847f1f | ||
|
|
b3a325521a | ||
|
|
efb4933935 | ||
|
|
19e549adc5 | ||
|
|
bce11e0842 | ||
|
|
86192a71b0 | ||
|
|
85a90c70bf | ||
|
|
f43c67c86f | ||
|
|
941733905b | ||
|
|
353a121d03 | ||
|
|
c3254e3fda | ||
|
|
67c1d9309a | ||
|
|
fbfc682899 | ||
|
|
6a1198c417 | ||
|
|
c311b27dcc | ||
|
|
a296e62d4d | ||
|
|
13498dc460 | ||
|
|
5b48a89448 | ||
|
|
bacb2a1d18 | ||
|
|
1e9bd49145 | ||
|
|
47521e327a | ||
|
|
e8b4c1fc83 | ||
|
|
3e756b262e | ||
|
|
e9787f2991 | ||
|
|
91894637af | ||
|
|
b9ca95bf8c | ||
|
|
57a50e2a37 | ||
|
|
070e2a14fd | ||
|
|
07efd5d2a1 | ||
|
|
79021108f5 | ||
|
|
6d1ff1372e | ||
|
|
7b09c1fd3b | ||
|
|
497b1fcf89 | ||
|
|
df04ba601c | ||
|
|
9939c5d562 | ||
|
|
54fdff0a4e | ||
|
|
5cac51479e | ||
|
|
02d16bf05c | ||
|
|
a5696ca17f | ||
|
|
56be12ab95 | ||
|
|
bc51962f9d | ||
|
|
801b1be3e3 | ||
|
|
66ca969929 | ||
|
|
bf7c4bab39 | ||
|
|
342eff2726 | ||
|
|
2fc74cf98d | ||
|
|
f630bfb598 | ||
|
|
183b147135 | ||
|
|
c413ee1162 | ||
|
|
a84445ef2a | ||
|
|
cd7636ddbe | ||
|
|
0540411cad | ||
|
|
6cb63b6e73 | ||
|
|
42447c4e43 | ||
|
|
be435b7760 | ||
|
|
0d2a88c45f | ||
|
|
1552e10ff0 | ||
|
|
c53aa517f7 | ||
|
|
43863d03fb | ||
|
|
5bc65464d0 | ||
|
|
735bfa1557 | ||
|
|
fdc2da7dae | ||
|
|
cf5f78c2ef | ||
|
|
0de9871529 | ||
|
|
45dfa3b724 | ||
|
|
fa89958736 | ||
|
|
2726c369f6 | ||
|
|
d0b1639a5d | ||
|
|
a338bb0d0b | ||
|
|
8e779c5c9e | ||
|
|
da3093a9ba | ||
|
|
0410fd2c91 | ||
|
|
f4e365194d | ||
|
|
7c035239b4 | ||
|
|
119690086a | ||
|
|
9537addf38 | ||
|
|
c004e140db | ||
|
|
de2a78b27b | ||
|
|
64d615c402 | ||
|
|
5a17fa64d5 | ||
|
|
dbde6337ea | ||
|
|
4c4d4243fb | ||
|
|
507daaddb9 | ||
|
|
1cbe7bf869 | ||
|
|
00d4e9bde2 | ||
|
|
0e7bbae132 | ||
|
|
b33a3c8e75 | ||
|
|
07be671726 | ||
|
|
76dfe108a9 | ||
|
|
5339c3c304 | ||
|
|
ab6a416de2 | ||
|
|
2281d9535e | ||
|
|
936ce239db | ||
|
|
22f20bfe98 | ||
|
|
b7c34f4960 | ||
|
|
469c8057d3 | ||
|
|
8c662c5c85 | ||
|
|
567b92792c | ||
|
|
6864b316d5 | ||
|
|
e14b77e504 | ||
|
|
30af16a4f6 | ||
|
|
fc5700f82c | ||
|
|
f9504a6a19 | ||
|
|
5ac05489c1 | ||
|
|
f7a0627ce5 | ||
|
|
5536dc4865 | ||
|
|
c61df1c33b | ||
|
|
2b0fc5be13 | ||
|
|
a4ac82e74d | ||
|
|
23fa56b356 | ||
|
|
08499fdda1 | ||
|
|
6395b06b19 | ||
|
|
83a6c6c558 | ||
|
|
ca53ddc798 | ||
|
|
aab785650d | ||
|
|
13b59abb1c | ||
|
|
b753654ebb | ||
|
|
b0158a844a | ||
|
|
4ab77ba170 | ||
|
|
47eee81d3c | ||
|
|
b2865b54ce | ||
|
|
4c79c4087e | ||
|
|
3147728d5b | ||
|
|
880b1cd962 | ||
|
|
5d14efb739 | ||
|
|
f49963c4f6 | ||
|
|
5a9e103bd5 | ||
|
|
eeeff42be6 | ||
|
|
b54f4b6bd6 | ||
|
|
b2cb17a96f | ||
|
|
f993561cd5 | ||
|
|
e8d163f595 | ||
|
|
465b40bf35 | ||
|
|
4c445f2a42 | ||
|
|
41bff698e8 | ||
|
|
0850227881 | ||
|
|
c693a05708 | ||
|
|
b83bddf49a | ||
|
|
e5f5cd563e | ||
|
|
3304335072 | ||
|
|
ab045a62c4 | ||
|
|
eaee599f25 | ||
|
|
4db06cfe80 | ||
|
|
bead40713c | ||
|
|
ca8ab2bcf8 | ||
|
|
d149700311 | ||
|
|
1792349bad | ||
|
|
2f461ee001 | ||
|
|
93aed56f23 | ||
|
|
ff9774925e | ||
|
|
1f9421430b | ||
|
|
c53966e481 | ||
|
|
3d43d52f2e | ||
|
|
fa4bb0bcc1 | ||
|
|
c4c783610b | ||
|
|
29f58e3059 | ||
|
|
29af0cb0ce | ||
|
|
3edf2cd575 | ||
|
|
a89ed7576e | ||
|
|
86990ae0f7 | ||
|
|
de7ddd8db3 | ||
|
|
139899fa5e | ||
|
|
c39a99a4fd | ||
|
|
6dd5cea79f | ||
|
|
bd0727131f | ||
|
|
968dd89d0d | ||
|
|
94a4ed8328 | ||
|
|
d381abe0cc | ||
|
|
50d66a5186 |
No files matched your search
@@ -8,6 +8,15 @@ EvenPerfectBitScanner.ResultsParser/bin/*
|
||||
EvenPerfectBitScanner.ResultsParser/obj/*
|
||||
EvenPerfectBitScanner.Tests/bin/*
|
||||
EvenPerfectBitScanner.Tests/obj/*
|
||||
BuildDivisorTransitions/bin/*
|
||||
BuildDivisorTransitions/obj/*
|
||||
DeviceDependentGenerator/bin/*
|
||||
DeviceDependentGenerator/obj/*
|
||||
DeviceDependentGenerator.Tests/bin/*
|
||||
DeviceDependentGenerator.Tests/obj/*
|
||||
PerfectNumbers.Core.Tests/bin/*
|
||||
PerfectNumbers.Core.Tests/obj/*
|
||||
.DS_Store
|
||||
.vs/*
|
||||
*.generated.cs
|
||||
PerfectNumbers.Core/generated/*
|
||||
Vendored
+30
-2
@@ -8,7 +8,8 @@
|
||||
"options": [
|
||||
"EvenPerfectBitScanner",
|
||||
"EvenPerfectBitScanner.ResultsParser",
|
||||
"EvenPerfectBitScanner.Benchmarks"
|
||||
"EvenPerfectBitScanner.Benchmarks",
|
||||
"BuildDivisorTransitions"
|
||||
],
|
||||
"default": "EvenPerfectBitScanner"
|
||||
}
|
||||
@@ -68,6 +69,33 @@
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"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",
|
||||
"type": "coreclr",
|
||||
@@ -136,4 +164,4 @@
|
||||
"justMyCode": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+4
-1
@@ -2,6 +2,7 @@
|
||||
"dotnet.defaultSolution": "FlexiCoder.sln",
|
||||
"chatgpt.openOnStartup": true,
|
||||
"cSpell.words": [
|
||||
"bydivisor",
|
||||
"CCCCCCCDUL",
|
||||
"cofactor",
|
||||
"ILGPU",
|
||||
@@ -9,5 +10,7 @@
|
||||
"Mersenne",
|
||||
"primality",
|
||||
"suff"
|
||||
]
|
||||
],
|
||||
"geminicodeassist.project": "evenperfectbitscanner",
|
||||
"todo-tree.tree.showCountsInTree": true
|
||||
}
|
||||
Vendored
+50
-3
@@ -11,7 +11,8 @@
|
||||
"--configuration",
|
||||
"Debug",
|
||||
"--framework",
|
||||
"net8.0"
|
||||
"net8.0",
|
||||
"/p:DeviceDependentGenerator_CopyGeneratedToProjectDir=true"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
@@ -29,7 +30,8 @@
|
||||
"--configuration",
|
||||
"Release",
|
||||
"--framework",
|
||||
"net8.0"
|
||||
"net8.0",
|
||||
"/p:DeviceDependentGenerator_CopyGeneratedToProjectDir=true"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
@@ -94,6 +96,36 @@
|
||||
"group": "build",
|
||||
"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",
|
||||
"type": "process",
|
||||
@@ -124,6 +156,21 @@
|
||||
],
|
||||
"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)",
|
||||
"type": "process",
|
||||
@@ -183,4 +230,4 @@
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,257 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,678 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<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>
|
||||
@@ -0,0 +1,238 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
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,4 +1,3 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class GpuUInt128Montgomery64Benchmarks
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ExtensionMontgomeryMultiply()
|
||||
{
|
||||
return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime);
|
||||
return Input.Left.MontgomeryMultiplyCpu(Input.Right, Input.Modulus, Input.NPrime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
public enum ModNumericType
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,6 +2,7 @@ using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -9,43 +10,46 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
public class Pow2MontgomeryModBenchmarks
|
||||
{
|
||||
private const int SampleCount = 256;
|
||||
private const int SampleCount = 256;
|
||||
|
||||
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _smallCycles = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeCycles = new ulong[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _smallCycles = 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
|
||||
{
|
||||
Small,
|
||||
Large
|
||||
}
|
||||
public enum InputScale
|
||||
{
|
||||
Small,
|
||||
Large
|
||||
}
|
||||
|
||||
[ParamsAllValues]
|
||||
public InputScale Scale { get; set; }
|
||||
[ParamsAllValues]
|
||||
public InputScale Scale { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
|
||||
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
||||
_largeExponents[i] = NextLargeExponent();
|
||||
_largeCycles[i] = largeCycle;
|
||||
}
|
||||
}
|
||||
_largeExponents[i] = NextLargeExponent();
|
||||
_largeCycles[i] = largeCycle;
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||
@@ -58,461 +62,466 @@ public class Pow2MontgomeryModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertToStandardCpu(divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
// [Benchmark]
|
||||
public ulong BaselineGpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// </summary>
|
||||
// [Benchmark]
|
||||
public ulong BaselineGpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(gpu, divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BaselineWithKnownCycleCpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
/// <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).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BaselineWithKnownCycleCpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BaselineWithKnownCycleGpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
/// <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).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BaselineWithKnownCycleGpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong LeftToRight()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong LeftToRight()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong Batched4()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong Batched4()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong SlidingWindow()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong SlidingWindow()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BinaryMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BinaryMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong PrecomputedTableMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// <summary>
|
||||
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong PrecomputedTableMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BigIntegerMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
/// <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×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BigIntegerMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||
{
|
||||
if (Scale == InputScale.Small)
|
||||
{
|
||||
exponents = _smallExponents;
|
||||
divisors = _smallDivisors;
|
||||
cycles = _smallCycles;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponents = _largeExponents;
|
||||
divisors = _largeDivisors;
|
||||
cycles = _largeCycles;
|
||||
}
|
||||
}
|
||||
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||
{
|
||||
if (Scale == InputScale.Small)
|
||||
{
|
||||
exponents = _smallExponents;
|
||||
divisors = _smallDivisors;
|
||||
cycles = _smallCycles;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponents = _largeExponents;
|
||||
divisors = _largeDivisors;
|
||||
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()
|
||||
{
|
||||
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
|
||||
return value | 1UL;
|
||||
}
|
||||
private ulong NextSmallOddModulus()
|
||||
{
|
||||
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
|
||||
return value | 1UL;
|
||||
}
|
||||
|
||||
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
|
||||
{
|
||||
int bitLength = _random.Next(48, 64);
|
||||
ulong modulus = (1UL << bitLength) - 1UL;
|
||||
return (modulus, (ulong)bitLength);
|
||||
}
|
||||
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
|
||||
{
|
||||
int bitLength = _random.Next(48, 64);
|
||||
ulong modulus = (1UL << bitLength) - 1UL;
|
||||
return (modulus, (ulong)bitLength);
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
|
||||
}
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
private static ulong ComputeMontgomeryNPrime(ulong 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);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
|
||||
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong remainingExponent = exponent;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong remainingExponent = exponent;
|
||||
|
||||
while (remainingExponent != 0UL)
|
||||
{
|
||||
ulong nibble = remainingExponent & 0xFUL;
|
||||
while (remainingExponent != 0UL)
|
||||
{
|
||||
ulong nibble = remainingExponent & 0xFUL;
|
||||
|
||||
ulong base0 = baseVal;
|
||||
ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
ulong base0 = baseVal;
|
||||
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
||||
|
||||
if ((nibble & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
}
|
||||
if ((nibble & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 2UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
}
|
||||
if ((nibble & 2UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 4UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
}
|
||||
if ((nibble & 4UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 8UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
}
|
||||
if ((nibble & 8UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
||||
}
|
||||
|
||||
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
remainingExponent >>= 4;
|
||||
}
|
||||
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
||||
remainingExponent >>= 4;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
const int WindowSize = 5;
|
||||
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
||||
oddPowers[0] = baseVal;
|
||||
ulong baseSquared = baseVal.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime);
|
||||
}
|
||||
const int WindowSize = 5;
|
||||
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
||||
oddPowers[0] = baseVal;
|
||||
ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
|
||||
}
|
||||
|
||||
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
while (bitIndex >= 0)
|
||||
{
|
||||
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
bitIndex--;
|
||||
continue;
|
||||
}
|
||||
while (bitIndex >= 0)
|
||||
{
|
||||
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
bitIndex--;
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
|
||||
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
|
||||
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
|
||||
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
|
||||
|
||||
while ((windowValue & 1UL) == 0UL)
|
||||
{
|
||||
windowValue >>= 1;
|
||||
windowStart++;
|
||||
}
|
||||
while ((windowValue & 1UL) == 0UL)
|
||||
{
|
||||
windowValue >>= 1;
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int squares = bitIndex - windowStart + 1;
|
||||
for (int i = 0; i < squares; i++)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
}
|
||||
int squares = bitIndex - windowStart + 1;
|
||||
for (int i = 0; i < squares; i++)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
bitIndex = windowStart - 1;
|
||||
}
|
||||
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
bitIndex = windowStart - 1;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = 1UL % modulus;
|
||||
ulong baseVal = 2UL % modulus;
|
||||
ulong remainingExponent = exponent;
|
||||
ulong result = 1UL % modulus;
|
||||
ulong baseVal = 2UL % modulus;
|
||||
ulong remainingExponent = exponent;
|
||||
|
||||
while (remainingExponent > 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MultiplyMod(result, baseVal, modulus);
|
||||
}
|
||||
while (remainingExponent > 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MultiplyMod(result, baseVal, modulus);
|
||||
}
|
||||
|
||||
remainingExponent >>= 1;
|
||||
if (remainingExponent == 0UL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
remainingExponent >>= 1;
|
||||
if (remainingExponent == 0UL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
baseVal = MultiplyMod(baseVal, baseVal, modulus);
|
||||
}
|
||||
baseVal = MultiplyMod(baseVal, baseVal, modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return 1UL % modulus;
|
||||
}
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return 1UL % modulus;
|
||||
}
|
||||
|
||||
Span<ulong> powers = stackalloc ulong[64];
|
||||
powers[0] = 2UL % modulus;
|
||||
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
Span<ulong> powers = stackalloc ulong[64];
|
||||
powers[0] = 2UL % modulus;
|
||||
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
for (int index = 1; index <= highestBit; index++)
|
||||
{
|
||||
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
|
||||
}
|
||||
for (int index = 1; index <= highestBit; index++)
|
||||
{
|
||||
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
|
||||
}
|
||||
|
||||
ulong result = 1UL % modulus;
|
||||
ulong remainingExponent = exponent;
|
||||
int bitIndex = 0;
|
||||
ulong result = 1UL % modulus;
|
||||
ulong remainingExponent = exponent;
|
||||
int bitIndex = 0;
|
||||
|
||||
while (remainingExponent > 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MultiplyMod(result, powers[bitIndex], modulus);
|
||||
}
|
||||
while (remainingExponent > 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MultiplyMod(result, powers[bitIndex], modulus);
|
||||
}
|
||||
|
||||
remainingExponent >>= 1;
|
||||
bitIndex++;
|
||||
}
|
||||
remainingExponent >>= 1;
|
||||
bitIndex++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
BigInteger mod = modulus;
|
||||
BigInteger result = BigInteger.ModPow(2, exponent, mod);
|
||||
return (ulong)result;
|
||||
}
|
||||
BigInteger mod = modulus;
|
||||
BigInteger result = BigInteger.ModPow(2, exponent, mod);
|
||||
return (ulong)result;
|
||||
}
|
||||
|
||||
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
|
||||
{
|
||||
UInt128 product = (UInt128)x * y;
|
||||
return (ulong)(product % modulus);
|
||||
}
|
||||
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
|
||||
{
|
||||
UInt128 product = (UInt128)x * y;
|
||||
return (ulong)(product % modulus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -61,12 +61,13 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
||||
};
|
||||
|
||||
var cases = new Pow2MontgomeryModWindowedBenchmarkCase[seeds.Length];
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < seeds.Length; i++)
|
||||
{
|
||||
Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i];
|
||||
ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL;
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(modulus, divisor);
|
||||
ulong cycleLength = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, modulus, divisor);
|
||||
ulong reducedExponent = seed.Exponent % cycleLength;
|
||||
cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase(
|
||||
seed.Exponent,
|
||||
@@ -76,6 +77,7 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
||||
reducedExponent);
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return new Pow2MontgomeryModWindowedBenchmarkInputs(cases);
|
||||
}
|
||||
|
||||
@@ -92,3 +94,4 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
||||
public ulong Multiplier { get; }
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -32,7 +32,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedCpu(current.Divisor, keepMontgomery: false);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(current.Divisor);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -50,7 +50,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedCpu(current.Divisor, keepMontgomery: true);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepMontgomeryCpu(current.Divisor);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -68,7 +68,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWithCycleCpu(current.CycleLength, current.Divisor);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWithCycleConvertToStandardCpu(current.CycleLength, current.Divisor);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
|
||||
+8
-3
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using PerfectNumbers.Core.Gpu.Kernels;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -31,12 +32,14 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
|
||||
ulong checksum = 0UL;
|
||||
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: false);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(gpu, current.Divisor);
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
@@ -49,12 +52,14 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
|
||||
ulong checksum = 0UL;
|
||||
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: true);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(gpu, current.Divisor);
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
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,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using Open.Numeric.Primes;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -12,8 +11,6 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
||||
[MemoryDiagnoser]
|
||||
public class PrimeTesterBenchmarks
|
||||
{
|
||||
private HeuristicPrimeTester _cpuTester = null!;
|
||||
private HeuristicPrimeTester _gpuTester = null!;
|
||||
private ulong[] _candidates = [];
|
||||
|
||||
[ParamsSource(nameof(GetBenchmarkCases))]
|
||||
@@ -22,8 +19,8 @@ public class PrimeTesterBenchmarks
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_cpuTester = new HeuristicPrimeTester();
|
||||
_gpuTester = new HeuristicPrimeTester();
|
||||
EnvironmentConfiguration.RollingAccelerators = 1;
|
||||
|
||||
_candidates = BenchmarkCase.Candidates;
|
||||
|
||||
if (_candidates.Length == 0)
|
||||
@@ -31,81 +28,116 @@ public class PrimeTesterBenchmarks
|
||||
return;
|
||||
}
|
||||
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
_cpuTester.IsPrimeCpu(_candidates[0], cancellationToken);
|
||||
_gpuTester.IsPrimeGpu(_candidates[0]);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.WarmUp();
|
||||
// HeuristicGroupABPrimeTesterAccelerator.WarmUp();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
_ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]);
|
||||
_ = HeuristicPrimeTester.IsPrimeGpu(gpu, _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)]
|
||||
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()
|
||||
{
|
||||
// PrimeTester tester = _gpuTester;
|
||||
ulong[] values = _candidates;
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (PrimeTester.IsPrimeCpu(values[i], cancellationToken))
|
||||
if (PrimeTester.IsPrimeCpu(values[i]))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
@@ -115,6 +147,40 @@ public class PrimeTesterBenchmarks
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
ulong[] values = _candidates;
|
||||
@@ -147,12 +213,54 @@ public class PrimeTesterBenchmarks
|
||||
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)
|
||||
|
||||
@@ -19,7 +19,7 @@ internal static class Program
|
||||
"/help"
|
||||
};
|
||||
|
||||
private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
|
||||
@@ -3,13 +3,10 @@ using Xunit;
|
||||
using System.Reflection;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
using EvenPerfectBitScanner;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace EvenPerfectBitScanner.Tests;
|
||||
|
||||
@@ -55,7 +52,7 @@ public class ProgramTests
|
||||
[InlineData(81UL, false)]
|
||||
public void IsPrime_identifies_primes_correctly(ulong n, bool expected)
|
||||
{
|
||||
new PrimeTester().IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||
PrimeTester.IsPrimeCpu(n).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -102,27 +99,26 @@ public class ProgramTests
|
||||
var testerField = typeof(Program).GetField("_divisorTester", 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 forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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) });
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(11UL, 32UL, out _, out _).Should().BeFalse();
|
||||
Program.IsEvenPerfectCandidate(5UL, 32UL, out _, out _).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(127UL, 32UL, out _, out _).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 32UL, out _, out _).Should().BeFalse();
|
||||
Program.IsEvenPerfectCandidate(gpu, 5UL, 32UL, out _, out _).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 127UL, 32UL, out _, out _).Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
testerField.SetValue(null, null);
|
||||
candidatesField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -135,27 +131,26 @@ public class ProgramTests
|
||||
var testerField = typeof(Program).GetField("_divisorTester", 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 forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailedCheck.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
testerField.SetValue(null, null);
|
||||
candidatesField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -168,25 +163,24 @@ public class ProgramTests
|
||||
|
||||
var testerField = typeof(Program).GetField("_divisorTester", 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());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
Program.IsEvenPerfectCandidate(gpu, 3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
testerField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -199,29 +193,28 @@ public class ProgramTests
|
||||
|
||||
var testerField = typeof(Program).GetField("_divisorTester", 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());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
|
||||
Program.IsEvenPerfectCandidate(13UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 13UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
testerField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -233,29 +226,28 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true));
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu( maxK: 1_000UL), 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));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
Program.IsEvenPerfectCandidate(gpu, 29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeFalse();
|
||||
|
||||
Program.IsEvenPerfectCandidate(31UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 31UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -267,32 +259,30 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
useResidue: true,
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
||||
|
||||
maxK: 1_024UL), 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));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
ulong[] primes = [107UL, 127UL];
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -305,32 +295,30 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
useResidue: true,
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
||||
|
||||
maxK: 5_000_000UL), 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));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
||||
Parallel.ForEach(primes, prime =>
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -342,32 +330,30 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
useResidue: true,
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
||||
|
||||
maxK: 5_000_000UL), 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));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
|
||||
Program.IsEvenPerfectCandidate(127UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, 127UL, 0UL, out searched, out detailed).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
detailed.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -380,29 +366,25 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
useGpuScan: false,
|
||||
useGpuOrder: false,
|
||||
useResidue: true,
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
||||
|
||||
maxK: 1_024UL), 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));
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -417,32 +399,38 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: false,
|
||||
kernelType: configuration.KernelType,
|
||||
useGpuScan: true,
|
||||
useGpuOrder: configuration.UseGpuOrder,
|
||||
useResidue: true,
|
||||
maxK: configuration.ResidueMaxK), trackAllValues: true));
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() =>
|
||||
{
|
||||
if (configuration.UseGpuOrder)
|
||||
{
|
||||
return new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForGpuOrderDeviceForCpu(
|
||||
|
||||
kernelType: configuration.KernelType,
|
||||
maxK: configuration.ResidueMaxK);
|
||||
}
|
||||
|
||||
return new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
||||
|
||||
kernelType: configuration.KernelType,
|
||||
maxK: configuration.ResidueMaxK);
|
||||
}, 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));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||
Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
|
||||
searched.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,33 +494,32 @@ public class ProgramTests
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", 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 forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true));
|
||||
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForIncrementalCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu( maxK: 1_000UL), 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));
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
compositeField.SetValue(null, false);
|
||||
|
||||
Program.IsEvenPerfectCandidate(9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
Program.IsEvenPerfectCandidate(gpu, 9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
|
||||
searched.Should().BeFalse();
|
||||
detailed.Should().BeFalse();
|
||||
((bool)compositeField.GetValue(null)!).Should().BeTrue();
|
||||
|
||||
Program.IsEvenPerfectCandidate(11UL, 0UL, out searched, out detailed);
|
||||
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out searched, out detailed);
|
||||
searched.Should().BeTrue();
|
||||
((bool)compositeField.GetValue(null)!).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
compositeField.SetValue(null, false);
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -557,8 +544,9 @@ public class ProgramTests
|
||||
|
||||
string output = writer.ToString();
|
||||
output.Should().Contain("Usage:");
|
||||
output.Should().Contain("--mersenne-device=cpu|gpu");
|
||||
output.Should().Contain("--mersenne-device=cpu|gpu|hybrid");
|
||||
output.Should().Contain("--primes-device=cpu|gpu");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Buffers.Text;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner;
|
||||
|
||||
internal static class CalculationResultHandler
|
||||
{
|
||||
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string ResultsHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||
internal const int DefaultWriteBatchSize = 100;
|
||||
|
||||
@@ -95,19 +95,46 @@ internal static class CalculationResultHandler
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
||||
internal static void HandleResult(ulong currentP, BigInteger divisor, 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.
|
||||
|
||||
Span<byte> stackBuffer = stackalloc byte[128];
|
||||
Span<byte> stackBuffer = stackalloc byte[256];
|
||||
byte[]? rentedBuffer = null;
|
||||
Span<byte> utf8Span = stackBuffer;
|
||||
int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||
int byteCount = TryFormatResultLine(utf8Span, currentP, divisorText, searchedMersenne, detailedCheck, passedAllTests);
|
||||
if (byteCount < 0)
|
||||
{
|
||||
rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
|
||||
int minimumSize = Math.Max((divisorText.Length + 5) * 4, 256);
|
||||
rentedBuffer = ThreadStaticPools.BytePool.Rent(minimumSize);
|
||||
utf8Span = rentedBuffer;
|
||||
byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||
byteCount = TryFormatResultLine(utf8Span, currentP, divisorText, searchedMersenne, detailedCheck, passedAllTests);
|
||||
if (byteCount < 0)
|
||||
{
|
||||
ThreadStaticPools.BytePool.Return(rentedBuffer);
|
||||
@@ -116,7 +143,7 @@ internal static class CalculationResultHandler
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> utf8Line = utf8Span[..byteCount];
|
||||
Span<char> charBuffer = stackalloc char[128];
|
||||
Span<char> charBuffer = stackalloc char[256];
|
||||
int charCount = Utf8Encoding.GetChars(utf8Line, charBuffer);
|
||||
ReadOnlySpan<char> recordSpan = charBuffer[..charCount];
|
||||
|
||||
@@ -231,7 +258,7 @@ internal static class CalculationResultHandler
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ReadOnlySpan<char> divisorText, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
@@ -248,6 +275,17 @@ internal static class CalculationResultHandler
|
||||
|
||||
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))
|
||||
{
|
||||
return -1;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Open.Numeric.Primes;
|
||||
using PerfectNumbers.Core;
|
||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates;
|
||||
|
||||
@@ -54,7 +54,7 @@ internal static class CandidatesCalculator
|
||||
return trackers.Value!;
|
||||
}
|
||||
|
||||
internal static bool IsCompositeByResidues(ulong p)
|
||||
internal static bool IsCompositeByResiduesCpu(ulong p)
|
||||
{
|
||||
ModResidueTracker tracker = AcquireResidueTracker();
|
||||
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
|
||||
@@ -72,7 +72,57 @@ internal static class CandidatesCalculator
|
||||
break;
|
||||
}
|
||||
|
||||
if (tracker.MergeOrAppend(p, primes[primeIndex], out bool divisible) && divisible)
|
||||
if (tracker.MergeOrAppendCpu(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;
|
||||
}
|
||||
@@ -129,18 +179,15 @@ internal static class CandidatesCalculator
|
||||
return candidates;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong AdvancePrime(ulong value, ref ulong remainder, ref bool limitReached)
|
||||
{
|
||||
return _transformMode switch
|
||||
{
|
||||
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),
|
||||
};
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static ulong AdvancePrime(ulong value, ref ulong remainder, ref bool limitReached) => _transformMode switch
|
||||
{
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
using Open.Numeric.Primes;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
@@ -6,6 +5,18 @@ using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner;
|
||||
|
||||
internal enum ByDivisorKIncrementMode
|
||||
{
|
||||
Sequential,
|
||||
Pow2Groups,
|
||||
Predictive,
|
||||
Percentile,
|
||||
Additive,
|
||||
TopDown,
|
||||
BitContradiction,
|
||||
BitTree,
|
||||
}
|
||||
|
||||
internal readonly struct CliArguments
|
||||
{
|
||||
private const string DefaultCyclesPath = "divisor_cycles.bin";
|
||||
@@ -26,14 +37,17 @@ internal readonly struct CliArguments
|
||||
internal readonly bool UseResidue;
|
||||
internal readonly bool UseDivisor;
|
||||
internal readonly bool UseByDivisor;
|
||||
internal readonly ByDivisorKIncrementMode ByDivisorKIncrementMode;
|
||||
internal readonly int ByDivisorSpecialRange;
|
||||
internal readonly bool UseGcdFilter;
|
||||
internal readonly bool TestMode;
|
||||
internal readonly bool UseGpuCycles;
|
||||
internal readonly bool UseMersenneOnGpu;
|
||||
internal readonly bool UseOrderOnGpu;
|
||||
internal readonly ComputationDevice MersenneDevice;
|
||||
internal readonly ComputationDevice OrderDevice;
|
||||
internal readonly int ScanBatchSize;
|
||||
internal readonly int SliceSize;
|
||||
internal readonly ulong ResidueKMax;
|
||||
internal readonly ulong MaxKLimit;
|
||||
internal readonly bool MaxKConfigured;
|
||||
internal readonly string FilterFile;
|
||||
internal readonly bool ForcePrimeKernelsOnCpu;
|
||||
internal readonly ModReductionMode ModReductionMode;
|
||||
@@ -52,50 +66,57 @@ internal readonly struct CliArguments
|
||||
internal readonly double ZeroFractionHard;
|
||||
internal readonly double ZeroFractionConjecture;
|
||||
internal readonly int MaxZeroConjecture;
|
||||
internal readonly BigInteger MinK;
|
||||
internal readonly int GpuRatio;
|
||||
|
||||
private CliArguments(
|
||||
ulong startPrime,
|
||||
bool startPrimeProvided,
|
||||
UInt128 maxPrimeLimit,
|
||||
bool maxPrimeConfigured,
|
||||
int threadCount,
|
||||
int blockSize,
|
||||
int gpuPrimeThreads,
|
||||
int gpuPrimeBatch,
|
||||
GpuKernelType kernelType,
|
||||
bool useOrder,
|
||||
bool showHelp,
|
||||
bool useBitTransform,
|
||||
bool useLucas,
|
||||
bool useResidue,
|
||||
bool useDivisor,
|
||||
bool useByDivisor,
|
||||
bool useGcdFilter,
|
||||
bool testMode,
|
||||
bool useGpuCycles,
|
||||
bool useMersenneOnGpu,
|
||||
bool useOrderOnGpu,
|
||||
int scanBatchSize,
|
||||
int sliceSize,
|
||||
ulong residueKMax,
|
||||
string filterFile,
|
||||
bool forcePrimeKernelsOnCpu,
|
||||
ModReductionMode modReductionMode,
|
||||
NttBackend nttBackend,
|
||||
string? resultsDirectory,
|
||||
string? resultsPrefix,
|
||||
int writeBatchSize,
|
||||
string cyclesPath,
|
||||
bool continueCyclesGeneration,
|
||||
int cyclesBatchSize,
|
||||
ulong divisorCyclesSearchLimit,
|
||||
ulong? orderWarmupLimitOverride,
|
||||
string? rleBlacklistPath,
|
||||
ulong rleHardMaxP,
|
||||
bool rleOnlyLast7,
|
||||
double zeroFractionHard,
|
||||
double zeroFractionConjecture,
|
||||
int maxZeroConjecture)
|
||||
ulong startPrime,
|
||||
bool startPrimeProvided,
|
||||
UInt128 maxPrimeLimit,
|
||||
bool maxPrimeConfigured,
|
||||
int threadCount,
|
||||
int blockSize,
|
||||
int gpuPrimeThreads,
|
||||
int gpuPrimeBatch,
|
||||
GpuKernelType kernelType,
|
||||
bool useOrder,
|
||||
bool showHelp,
|
||||
bool useBitTransform,
|
||||
bool useLucas,
|
||||
bool useResidue,
|
||||
bool useDivisor,
|
||||
bool useByDivisor,
|
||||
bool useGcdFilter,
|
||||
ByDivisorKIncrementMode byDivisorKIncrementMode,
|
||||
int byDivisorSpecialRange,
|
||||
bool testMode,
|
||||
bool useGpuCycles,
|
||||
ComputationDevice mersenneDevice,
|
||||
ComputationDevice orderDevice,
|
||||
int scanBatchSize,
|
||||
int sliceSize,
|
||||
ulong maxKLimit,
|
||||
bool maxKConfigured,
|
||||
string filterFile,
|
||||
bool forcePrimeKernelsOnCpu,
|
||||
ModReductionMode modReductionMode,
|
||||
NttBackend nttBackend,
|
||||
string? resultsDirectory,
|
||||
string? resultsPrefix,
|
||||
int writeBatchSize,
|
||||
string cyclesPath,
|
||||
bool continueCyclesGeneration,
|
||||
int cyclesBatchSize,
|
||||
ulong divisorCyclesSearchLimit,
|
||||
ulong? orderWarmupLimitOverride,
|
||||
string? rleBlacklistPath,
|
||||
ulong rleHardMaxP,
|
||||
bool rleOnlyLast7,
|
||||
double zeroFractionHard,
|
||||
double zeroFractionConjecture,
|
||||
int maxZeroConjecture,
|
||||
BigInteger minK,
|
||||
int gpuRatio)
|
||||
{
|
||||
StartPrime = startPrime;
|
||||
StartPrimeProvided = startPrimeProvided;
|
||||
@@ -113,14 +134,17 @@ internal readonly struct CliArguments
|
||||
UseResidue = useResidue;
|
||||
UseDivisor = useDivisor;
|
||||
UseByDivisor = useByDivisor;
|
||||
ByDivisorKIncrementMode = byDivisorKIncrementMode;
|
||||
ByDivisorSpecialRange = byDivisorSpecialRange;
|
||||
UseGcdFilter = useGcdFilter;
|
||||
TestMode = testMode;
|
||||
UseGpuCycles = useGpuCycles;
|
||||
UseMersenneOnGpu = useMersenneOnGpu;
|
||||
UseOrderOnGpu = useOrderOnGpu;
|
||||
MersenneDevice = mersenneDevice;
|
||||
OrderDevice = orderDevice;
|
||||
ScanBatchSize = scanBatchSize;
|
||||
SliceSize = sliceSize;
|
||||
ResidueKMax = residueKMax;
|
||||
MaxKLimit = maxKLimit;
|
||||
MaxKConfigured = maxKConfigured;
|
||||
FilterFile = filterFile;
|
||||
ForcePrimeKernelsOnCpu = forcePrimeKernelsOnCpu;
|
||||
ModReductionMode = modReductionMode;
|
||||
@@ -139,6 +163,8 @@ internal readonly struct CliArguments
|
||||
ZeroFractionHard = zeroFractionHard;
|
||||
ZeroFractionConjecture = zeroFractionConjecture;
|
||||
MaxZeroConjecture = maxZeroConjecture;
|
||||
MinK = minK;
|
||||
GpuRatio = gpuRatio;
|
||||
}
|
||||
|
||||
internal bool UseFilter => !TestMode && !string.IsNullOrEmpty(FilterFile);
|
||||
@@ -161,14 +187,17 @@ internal readonly struct CliArguments
|
||||
bool useResidue = false;
|
||||
bool useDivisor = false;
|
||||
bool useByDivisor = false;
|
||||
ByDivisorKIncrementMode byDivisorKIncrementMode = ByDivisorKIncrementMode.Sequential;
|
||||
int byDivisorSpecialRange = 0;
|
||||
bool useGcdFilter = false;
|
||||
bool testMode = false;
|
||||
bool useGpuCycles = true;
|
||||
bool useMersenneOnGpu = true;
|
||||
bool useOrderOnGpu = true;
|
||||
ComputationDevice mersenneDevice = ComputationDevice.Gpu;
|
||||
ComputationDevice orderDevice = ComputationDevice.Gpu;
|
||||
int scanBatchSize = 2_097_152;
|
||||
int sliceSize = 32;
|
||||
ulong residueKMax = 5_000_000UL;
|
||||
ulong maxKLimit = 5_000_000UL;
|
||||
bool maxKConfigured = false;
|
||||
string filterFile = string.Empty;
|
||||
bool forcePrimeKernelsOnCpu = false;
|
||||
ModReductionMode modReductionMode = ModReductionMode.Auto;
|
||||
@@ -187,6 +216,8 @@ internal readonly struct CliArguments
|
||||
double zeroFractionHard = -1.0;
|
||||
double zeroFractionConjecture = -1.0;
|
||||
int maxZeroConjecture = -1;
|
||||
BigInteger minK = BigInteger.One;
|
||||
int gpuRatio = PerfectNumberConstants.GpuRatio;
|
||||
|
||||
foreach (string argument in args)
|
||||
{
|
||||
@@ -202,14 +233,15 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
startPrime = Utf8CliParser.ParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1));
|
||||
ReadOnlySpan<char> primeValue = argument.AsSpan("--prime=".Length);
|
||||
startPrime = Utf8CliParser.ParseUInt64(primeValue);
|
||||
startPrimeProvided = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--max-prime=".Length);
|
||||
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
||||
{
|
||||
maxPrimeLimit = parsedMaxPrime;
|
||||
@@ -236,15 +268,34 @@ internal readonly struct CliArguments
|
||||
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))
|
||||
{
|
||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--threads=".Length)));
|
||||
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;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--mersenne=".Length);
|
||||
if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
kernelType = GpuKernelType.Pow2Mod;
|
||||
@@ -279,51 +330,48 @@ internal readonly struct CliArguments
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--bydivisor-k-increment=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--bydivisor-k-increment=".Length);
|
||||
if (value.Equals("pow2groups", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
divisorCyclesSearchLimit = parsedLimit;
|
||||
byDivisorKIncrementMode = ByDivisorKIncrementMode.Pow2Groups;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--bydivisor-special-range=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
byDivisorSpecialRange = Utf8CliParser.ParseInt32(argument.AsSpan("--bydivisor-special-range=".Length));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -333,17 +381,71 @@ internal readonly struct CliArguments
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.Equals("--test-mode", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
|
||||
testMode = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--mod-reduction=".Length);
|
||||
if (value.Equals("uint128", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.GpuUInt128;
|
||||
}
|
||||
else if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.Mont64;
|
||||
}
|
||||
@@ -351,10 +453,6 @@ internal readonly struct CliArguments
|
||||
{
|
||||
modReductionMode = ModReductionMode.Barrett128;
|
||||
}
|
||||
else if (value.Equals("uint128", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.GpuUInt128;
|
||||
}
|
||||
else
|
||||
{
|
||||
modReductionMode = ModReductionMode.Auto;
|
||||
@@ -363,203 +461,116 @@ internal readonly struct CliArguments
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--ntt=".Length);
|
||||
nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useOrderOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
orderWarmupLimitOverride = Utf8CliParser.ParseUInt64(argument.AsSpan("--order-warmup-limit=".Length));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rleBlacklistPath = argument[(argument.IndexOf('=') + 1)..];
|
||||
divisorCyclesSearchLimit = Utf8CliParser.ParseUInt64(argument.AsSpan("--divisor-cycles-limit=".Length));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--min-k=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
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)));
|
||||
minK = BigInteger.Parse(argument.AsSpan("--min-k=".Length), NumberStyles.None, CultureInfo.InvariantCulture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filterFile = argument[(argument.IndexOf('=') + 1)..];
|
||||
filterFile = argument["--filter-p=".Length..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--write-batch-size=".Length)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesPath = argument[(argument.IndexOf('=') + 1)..];
|
||||
cyclesPath = argument["--divisor-cycles=".Length..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useGpuCycles = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
useGpuCycles = !argument.AsSpan("--divisor-cycles-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--divisor-cycles-batch=".Length)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--divisor-cycles-continue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continueCyclesGeneration = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return new CliArguments(
|
||||
startPrime,
|
||||
startPrimeProvided,
|
||||
maxPrimeLimit,
|
||||
maxPrimeConfigured,
|
||||
threadCount,
|
||||
blockSize,
|
||||
gpuPrimeThreads,
|
||||
gpuPrimeBatch,
|
||||
kernelType,
|
||||
useOrder,
|
||||
showHelp,
|
||||
useBitTransform,
|
||||
useLucas,
|
||||
useResidue,
|
||||
useDivisor,
|
||||
useByDivisor,
|
||||
useGcdFilter,
|
||||
testMode,
|
||||
useGpuCycles,
|
||||
useMersenneOnGpu,
|
||||
useOrderOnGpu,
|
||||
scanBatchSize,
|
||||
sliceSize,
|
||||
residueKMax,
|
||||
filterFile,
|
||||
forcePrimeKernelsOnCpu,
|
||||
modReductionMode,
|
||||
nttBackend,
|
||||
resultsDirectory,
|
||||
resultsPrefix,
|
||||
writeBatchSize,
|
||||
cyclesPath,
|
||||
continueCyclesGeneration,
|
||||
cyclesBatchSize,
|
||||
divisorCyclesSearchLimit,
|
||||
orderWarmupLimitOverride,
|
||||
rleBlacklistPath,
|
||||
rleHardMaxP,
|
||||
rleOnlyLast7,
|
||||
zeroFractionHard,
|
||||
zeroFractionConjecture,
|
||||
maxZeroConjecture);
|
||||
startPrime,
|
||||
startPrimeProvided,
|
||||
maxPrimeLimit,
|
||||
maxPrimeConfigured,
|
||||
threadCount,
|
||||
blockSize,
|
||||
gpuPrimeThreads,
|
||||
gpuPrimeBatch,
|
||||
kernelType,
|
||||
useOrder,
|
||||
showHelp,
|
||||
useBitTransform,
|
||||
useLucas,
|
||||
useResidue,
|
||||
useDivisor,
|
||||
useByDivisor,
|
||||
useGcdFilter,
|
||||
byDivisorKIncrementMode,
|
||||
byDivisorSpecialRange,
|
||||
testMode,
|
||||
useGpuCycles,
|
||||
mersenneDevice,
|
||||
orderDevice,
|
||||
scanBatchSize,
|
||||
sliceSize,
|
||||
maxKLimit,
|
||||
maxKConfigured,
|
||||
filterFile,
|
||||
forcePrimeKernelsOnCpu,
|
||||
modReductionMode,
|
||||
nttBackend,
|
||||
resultsDirectory,
|
||||
resultsPrefix,
|
||||
writeBatchSize,
|
||||
cyclesPath,
|
||||
continueCyclesGeneration,
|
||||
cyclesBatchSize,
|
||||
divisorCyclesSearchLimit,
|
||||
orderWarmupLimitOverride,
|
||||
rleBlacklistPath,
|
||||
rleHardMaxP,
|
||||
rleOnlyLast7,
|
||||
zeroFractionHard,
|
||||
zeroFractionConjecture,
|
||||
maxZeroConjecture,
|
||||
minK,
|
||||
gpuRatio);
|
||||
}
|
||||
|
||||
internal static void PrintHelp()
|
||||
@@ -567,22 +578,25 @@ internal readonly struct CliArguments
|
||||
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Options:");
|
||||
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
||||
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
||||
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
|
||||
Console.WriteLine(" --increment=bit|add exponent increment method");
|
||||
Console.WriteLine(" --increment=bit|add exponent increment method (default add)");
|
||||
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(" --residue-max-k=<value> max k for residue Mersenne test (q = 2*p*k + 1)");
|
||||
Console.WriteLine(" --mersenne-device=cpu|gpu Device for Mersenne method (default gpu)");
|
||||
Console.WriteLine(" --primes-device=cpu|gpu Device for prime-scan kernels (default gpu)");
|
||||
Console.WriteLine(" --gpu-prime-batch=<n> Batch size for GPU primality sieve (default 262144)");
|
||||
Console.WriteLine(" --order-device=cpu|gpu Device for order computations (default gpu)");
|
||||
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(" --bydivisor-k-increment=sequential|pow2groups|predictive|percentile|additive|topdown|bitcontradiction|bittree k iteration strategy for --mersenne=bydivisor (default sequential)");
|
||||
Console.WriteLine(" --bydivisor-special-range=<n> widen pow2groups special values to +/- n percent (default 0 = points)");
|
||||
Console.WriteLine(" --max-k=<value> max k for Mersenne tests (q = 2*p*k + 1), including --mersenne=bydivisor");
|
||||
Console.WriteLine(" --mersenne-device=cpu|gpu|hybrid Device for Mersenne method (default gpu)");
|
||||
Console.WriteLine(" --primes-device=cpu|gpu device for prime-scan kernels (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(" --ll-slice=<value> Lucas–Lehmer iterations per slice (default 32)");
|
||||
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(" --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-hard-max=<p> apply RLE blacklist only for p <= this (default ulong.MaxValue = no limit)");
|
||||
@@ -600,6 +614,22 @@ internal readonly struct CliArguments
|
||||
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(" --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");
|
||||
}
|
||||
|
||||
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,7 +13,16 @@
|
||||
</AssemblyAttribute>
|
||||
</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.Primes" Version="4.0.4" />
|
||||
<PackageReference Include="Plotly.NET" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<ItemGroup Label="MLModel1">
|
||||
<None Include="MLModel1.mlnet">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,7 +1,6 @@
|
||||
using Open.Collections;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using EvenPerfectBitScanner;
|
||||
|
||||
namespace EvenPerfectBitScanner.IO;
|
||||
|
||||
@@ -14,7 +13,7 @@ internal static class CalculationResultsFile
|
||||
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using StreamReader reader = new(readStream);
|
||||
string? line;
|
||||
ReadOnlySpan<char> span;
|
||||
ReadOnlySpan<char> trimmed;
|
||||
while ((line = reader.ReadLine()) is not null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
@@ -22,36 +21,67 @@ internal static class CalculationResultsFile
|
||||
continue;
|
||||
}
|
||||
|
||||
span = line.AsSpan().Trim();
|
||||
if (span.IsEmpty || span[0] == '#')
|
||||
trimmed = line.AsSpan().Trim();
|
||||
if (trimmed.IsEmpty || trimmed[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int length = trimmed.Length;
|
||||
int index = 0;
|
||||
while (index < span.Length)
|
||||
if (!maxPrimeConfigured)
|
||||
{
|
||||
if (!char.IsDigit(span[index]))
|
||||
while (index < length)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
char current = trimmed[index];
|
||||
uint digit = (uint)(current - '0');
|
||||
if (digit > 9U)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = index;
|
||||
index++;
|
||||
while (index < span.Length && char.IsDigit(span[index]))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
int start = index++;
|
||||
while (index < length && (uint)(trimmed[index] - '0') <= 9U)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed))
|
||||
{
|
||||
if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit)
|
||||
if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed))
|
||||
{
|
||||
candidates.Add(parsed);
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt128 limit = maxPrimeLimit;
|
||||
while (index < length)
|
||||
{
|
||||
char current = trimmed[index];
|
||||
uint digit = (uint)(current - '0');
|
||||
if (digit > 9U)
|
||||
{
|
||||
skippedByLimit++;
|
||||
index++;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +100,7 @@ internal static class CalculationResultsFile
|
||||
int first = 0;
|
||||
int second = 0;
|
||||
int third = 0;
|
||||
int fourth = 0;
|
||||
ulong parsedP = 0UL;
|
||||
ReadOnlySpan<char> detailedSpan = default;
|
||||
ReadOnlySpan<char> passedAllTestsSpan = default;
|
||||
@@ -109,8 +140,15 @@ internal static class CalculationResultsFile
|
||||
continue;
|
||||
}
|
||||
|
||||
detailedSpan = span[..third];
|
||||
passedAllTestsSpan = span[(third + 1)..];
|
||||
span = span[(third + 1)..];
|
||||
fourth = span.IndexOf(',');
|
||||
if (fourth < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
detailedSpan = span[..fourth];
|
||||
passedAllTestsSpan = span[(fourth + 1)..];
|
||||
|
||||
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
{
|
||||
"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.
@@ -0,0 +1,100 @@
|
||||
// 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,4 +1,3 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Text;
|
||||
@@ -30,11 +29,26 @@ internal static class Utf8CliParser
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return TryParse(value, static (ReadOnlySpan<byte> source, out int parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result);
|
||||
|
||||
+99
-4
@@ -1,4 +1,5 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
@@ -14,38 +15,132 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Resul
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Benchmarks", "EvenPerfectBitScanner.Benchmarks\EvenPerfectBitScanner.Benchmarks.csproj", "{83551FCC-41FA-4162-B541-AFCB5666A29F}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,78 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
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,7 +1,3 @@
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -9,88 +5,88 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuContextPoolTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void RentPreferred_cpu_returns_cpu_accelerator()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void RentPreferred_cpu_returns_cpu_accelerator()
|
||||
// {
|
||||
// GpuContextPool.DisposeAll();
|
||||
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
// GpuContextPool.DisposeAll();
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void RentPreferred_reuses_cpu_context()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void RentPreferred_reuses_cpu_context()
|
||||
// {
|
||||
// GpuContextPool.DisposeAll();
|
||||
|
||||
Context firstContext;
|
||||
Accelerator firstAccelerator;
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
firstContext = lease.Context;
|
||||
firstAccelerator = lease.Accelerator;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
// Context firstContext;
|
||||
// Accelerator firstAccelerator;
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// firstContext = lease.Context;
|
||||
// firstAccelerator = lease.Accelerator;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
|
||||
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
lease.Context.Should().BeSameAs(firstContext);
|
||||
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
// lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// lease.Context.Should().BeSameAs(firstContext);
|
||||
// lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
// GpuContextPool.DisposeAll();
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_pooled_contexts()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void DisposeAll_clears_pooled_contexts()
|
||||
// {
|
||||
// GpuContextPool.DisposeAll();
|
||||
|
||||
Context firstContext;
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
firstContext = lease.Context;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
// Context firstContext;
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// firstContext = lease.Context;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
// GpuContextPool.DisposeAll();
|
||||
|
||||
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
lease.Context.Should().NotBeSameAs(firstContext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
// lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// lease.Context.Should().NotBeSameAs(firstContext);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
// GpuContextPool.DisposeAll();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using PerfectNumbers.Core.Gpu.Kernels;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -114,14 +115,10 @@ public class GpuKernelCompilationTests
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? 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 =
|
||||
DivisorCycleKernels.GpuDivisorCycleKernel;
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||
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 =
|
||||
PrimeTesterKernels.SmallPrimeSieveKernel;
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
||||
@@ -167,23 +164,23 @@ public class GpuKernelCompilationTests
|
||||
return new object[] { name, loader };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(KernelLoaders))]
|
||||
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||
{
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
var accelerator = lease.Accelerator;
|
||||
Action action = () => compile(accelerator);
|
||||
action.Should().NotThrow($"kernel {name} should compile");
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
}
|
||||
// [Theory]
|
||||
// [Trait("Category", "Fast")]
|
||||
// [MemberData(nameof(KernelLoaders))]
|
||||
// public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||
// {
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// var accelerator = lease.Accelerator;
|
||||
// Action action = () => compile(accelerator);
|
||||
// action.Should().NotThrow($"kernel {name} should compile");
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
// }
|
||||
|
||||
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
||||
{
|
||||
@@ -325,56 +322,67 @@ public class GpuKernelCompilationTests
|
||||
|
||||
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderKernels.Pow2ModKernelWide);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(MersenneDivisorCyclesKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSmallPrimeKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSharesFactorKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
private static KernelType _kernelType = KernelType.Pow2ModKernelScan | KernelType.OrderKernelScan | KernelType.IncrementalKernelScan | KernelType.IncrementalOrderKernelScan | KernelType.Pow2ModOrderKernelScan;
|
||||
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
||||
{
|
||||
|
||||
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
_ = orderLease.OrderKernel;
|
||||
_ = orderLease.IncrementalKernel;
|
||||
_ = orderLease.Pow2ModKernel;
|
||||
_ = orderLease.IncrementalOrderKernel;
|
||||
_ = orderLease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderLease.Dispose();
|
||||
}
|
||||
}
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
int acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
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();
|
||||
}
|
||||
|
||||
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,22 +18,33 @@ public class GpuPrimeLimiterTests
|
||||
var startedSecond = new TaskCompletionSource<bool>();
|
||||
var sw = new Stopwatch();
|
||||
|
||||
using var first = GpuPrimeWorkLimiter.Acquire();
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
sw.Start();
|
||||
|
||||
var secondTask = Task.Run(() =>
|
||||
{
|
||||
using var second = GpuPrimeWorkLimiter.Acquire();
|
||||
startedSecond.TrySetResult(true);
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
try
|
||||
{
|
||||
startedSecond.TrySetResult(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
});
|
||||
|
||||
// Give secondTask a moment to attempt acquire (it should block).
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
// Release first after short delay, allowing second to continue.
|
||||
// (Dispose at scope end, but we want to end now.)
|
||||
first.Dispose();
|
||||
try
|
||||
{
|
||||
// Give secondTask a moment to attempt acquire (it should block).
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release first after the initial wait so the second task can proceed.
|
||||
GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
|
||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||
sw.Stop();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -7,59 +8,91 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Trait("Category", "Slow")]
|
||||
public class LucasLehmerPrimeTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(5UL, true)]
|
||||
[InlineData(11UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
|
||||
{
|
||||
new MersenneNumberLucasLehmerGpuTester().IsMersennePrime(p).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(5UL, true)]
|
||||
[InlineData(11UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
new MersenneNumberLucasLehmerGpuTester().IsPrime(gpu, p, true).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
|
||||
{
|
||||
// Use a modest exponent that still exercises the NTT path while keeping
|
||||
// runtime short enough for the unit test environment.
|
||||
const ulong exponent = 128UL;
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
|
||||
{
|
||||
// Use a modest exponent that still exercises the NTT path while keeping
|
||||
// runtime short enough for the unit test environment.
|
||||
const ulong exponent = 128UL;
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsPrime(gpu, exponent, true).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrimeBatch_matches_individual()
|
||||
{
|
||||
ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL };
|
||||
bool[] expected = new bool[] { true, true, true, false, true, true, true, false };
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
bool[] results = new bool[exponents.Length];
|
||||
tester.IsMersennePrimeBatch(exponents, results);
|
||||
results.Should().Equal(expected);
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrimeBatch_matches_individual()
|
||||
{
|
||||
ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL };
|
||||
bool[] expected = new bool[] { true, true, true, false, true, true, true, false };
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
bool[] results = new bool[exponents.Length];
|
||||
tester.IsMersennePrimeBatch(exponents, results);
|
||||
results.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(132UL)]
|
||||
[InlineData(1000UL)]
|
||||
[Trait("Category", "Slow")]
|
||||
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(132UL)]
|
||||
[InlineData(1000UL)]
|
||||
[Trait("Category", "Slow")]
|
||||
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsPrime(gpu, exponent, true).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(5UL, true)]
|
||||
[InlineData(11UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
|
||||
{
|
||||
new MersenneNumberLucasLehmerCpuTester().IsPrime(p).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(3UL, true)]
|
||||
[InlineData(5UL, true)]
|
||||
[InlineData(11UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
new MersenneNumberLucasLehmerCpuTester().IsPrime(gpu, p).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,367 +13,352 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Collection("GpuNtt")]
|
||||
public class NttGpuMathTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_reference_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_reference_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_staged_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_staged_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_reference_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_reference_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_staged_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_staged_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||
try
|
||||
{
|
||||
var accelerator = gpu.Accelerator;
|
||||
var accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var stream = accelerator.CreateStream();
|
||||
|
||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
||||
var cacheType = cache.GetType();
|
||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
||||
var cacheType = cache.GetType();
|
||||
|
||||
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
||||
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
||||
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
||||
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
||||
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
||||
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
||||
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
||||
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
||||
|
||||
int count = 7; // length 8 -> 7 twiddles
|
||||
var forward = new GpuUInt128[count];
|
||||
var inverse = new GpuUInt128[count];
|
||||
twiddlesMont.CopyToCPU(forward);
|
||||
twiddlesInvMont.CopyToCPU(inverse);
|
||||
int count = 7; // length 8 -> 7 twiddles
|
||||
var forward = new GpuUInt128[count];
|
||||
var inverse = new GpuUInt128[count];
|
||||
twiddlesMont.CopyToCPU(stream, forward);
|
||||
twiddlesInvMont.CopyToCPU(stream, inverse);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
accelerator.Dispose();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
||||
prod.Should().Be(r2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
||||
prod.Should().Be(r2);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_reference_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_reference_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_staged_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_staged_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_reference_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_reference_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_staged_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_staged_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void BitReverseGpu_matches_cpu()
|
||||
{
|
||||
GpuUInt128[] cpu = new GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[i] = new GpuUInt128(0UL, i);
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void BitReverseGpu_matches_cpu()
|
||||
{
|
||||
GpuUInt128[] cpu = new GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[i] = new GpuUInt128(0UL, i);
|
||||
}
|
||||
|
||||
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
||||
NttGpuMath.BitReverse(cpu);
|
||||
NttGpuMath.BitReverseGpu(gpu);
|
||||
gpu.Should().Equal(cpu);
|
||||
}
|
||||
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
||||
NttGpuMath.BitReverse(cpu);
|
||||
NttGpuMath.BitReverseGpu(gpu);
|
||||
gpu.Should().Equal(cpu);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_ntt_caches()
|
||||
{
|
||||
// Ensure the cache starts empty.
|
||||
NttGpuMath.ClearCaches();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_ntt_caches()
|
||||
{
|
||||
// Ensure the cache starts empty.
|
||||
NttGpuMath.ClearCaches();
|
||||
|
||||
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
||||
values[0] = new GpuUInt128(0UL, 1UL);
|
||||
values[1] = new GpuUInt128(0UL, 2UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||
try
|
||||
{
|
||||
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||
try
|
||||
{
|
||||
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
||||
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
||||
values[0] = new GpuUInt128(0UL, 1UL);
|
||||
values[1] = new GpuUInt128(0UL, 2UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Accelerator accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var stream = accelerator.CreateStream();
|
||||
var buffer = accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||
buffer.View.CopyFromCPU(stream, ref values[0], values.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
buffer.Dispose();
|
||||
accelerator.Dispose();
|
||||
|
||||
// Verify that the square cache now holds entries for the accelerator.
|
||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var cacheObj = field.GetValue(null)!;
|
||||
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().BeGreaterThan(0);
|
||||
// Verify that the square cache now holds entries for the accelerator.
|
||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var cacheObj = field.GetValue(null)!;
|
||||
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().BeGreaterThan(0);
|
||||
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().Be(0);
|
||||
}
|
||||
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,86 +9,83 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Trait("Category", "Fast")]
|
||||
public class NttMontgomeryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
||||
{
|
||||
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
||||
{
|
||||
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
||||
cpu.CopyTo(gpu);
|
||||
for (int i = cpu.Length; i < gpu.Length; i++)
|
||||
{
|
||||
gpu[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
||||
cpu.CopyTo(gpu);
|
||||
for (int i = cpu.Length; i < gpu.Length; i++)
|
||||
{
|
||||
gpu[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
|
||||
// CPU reference
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
// CPU reference
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
|
||||
// Device execution
|
||||
GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
|
||||
try
|
||||
{
|
||||
var accelerator = context.Accelerator;
|
||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
|
||||
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
|
||||
// Device execution
|
||||
var accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var stream = accelerator.CreateStream();
|
||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||
buffer.View.CopyFromCPU(stream, ref gpu[0], gpu.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
|
||||
buffer.View.CopyToCPU(stream, ref gpu[0], gpu.Length);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
accelerator.Dispose();
|
||||
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
}
|
||||
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,374 +2,410 @@ using System.Numerics;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Trait("Category", "Fast")]
|
||||
public class PrimeOrderGpuHeuristicsTests
|
||||
{
|
||||
public PrimeOrderGpuHeuristicsTests()
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
public PrimeOrderGpuHeuristicsTests()
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_overflow_for_marked_prime()
|
||||
{
|
||||
const ulong prime = 97UL;
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_overflow_for_marked_prime()
|
||||
{
|
||||
const ulong prime = 97UL;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_success_for_supported_prime()
|
||||
{
|
||||
const ulong exponent = 138_000_001UL;
|
||||
const ulong prime = 138_000_001UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData);
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_success_for_supported_prime()
|
||||
{
|
||||
const ulong exponent = 138_000_001UL;
|
||||
const ulong prime = 138_000_001UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
remainder.Should().Be(2UL);
|
||||
}
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
[Fact]
|
||||
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);
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
remainder.Should().Be(2UL);
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability()
|
||||
{
|
||||
const ulong prime = 97UL;
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability()
|
||||
{
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability()
|
||||
{
|
||||
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);
|
||||
|
||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData);
|
||||
followUp.Should().Be(GpuPow2ModStatus.Success);
|
||||
followUpRemainder.Should().Be(16UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
}
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_returns_overflow_for_marked_prime()
|
||||
{
|
||||
const ulong prime = 193UL;
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, 4UL, prime, out ulong followUpRemainder, divisorData);
|
||||
followUp.Should().Be(GpuPow2ModStatus.Success);
|
||||
followUpRemainder.Should().Be(16UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
}
|
||||
|
||||
ulong[] remainders = new ulong[2];
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_returns_overflow_for_marked_prime()
|
||||
{
|
||||
const ulong prime = 193UL;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
remainders[1].Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
ulong[] remainders = new ulong[2];
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_returns_success_for_supported_prime()
|
||||
{
|
||||
ulong[] remainders = { 123UL, 456UL };
|
||||
ulong[] exponents = { 138_000_001UL, 138_000_011UL };
|
||||
const ulong prime = 138_000_001UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
remainders[1].Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
|
||||
ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime);
|
||||
ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime);
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_returns_success_for_supported_prime()
|
||||
{
|
||||
ulong[] remainders = { 123UL, 456UL };
|
||||
ulong[] exponents = { 138_000_001UL, 138_000_011UL };
|
||||
const ulong prime = 138_000_001UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
expectedFirst.Should().Be(2UL);
|
||||
expectedSecond.Should().Be(2048UL);
|
||||
remainders[0].Should().Be(expectedFirst);
|
||||
remainders[1].Should().Be(expectedSecond);
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
[Fact]
|
||||
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 };
|
||||
ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime);
|
||||
ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime);
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
expectedFirst.Should().Be(2UL);
|
||||
expectedSecond.Should().Be(2048UL);
|
||||
remainders[0].Should().Be(expectedFirst);
|
||||
remainders[1].Should().Be(expectedSecond);
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
remainders[1].Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability()
|
||||
{
|
||||
const ulong prime = 97UL;
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
ulong[] remainders = { 5UL, 6UL };
|
||||
|
||||
[Fact]
|
||||
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 };
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
||||
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
remainders[1].Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
remainders[1].Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability()
|
||||
{
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 2UL, 16UL };
|
||||
ulong[] remainders = { 9UL, 11UL };
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_overflow_for_marked_prime()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 80) + 7;
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders, divisorData);
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
remainders[1].Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_overflow_for_marked_prime()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 80) + 7;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_success_for_supported_prime()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 96) + 11;
|
||||
UInt128 exponent = ((UInt128)1 << 64) + 3;
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder);
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
|
||||
remainder.Should().Be(expected);
|
||||
}
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_success_for_supported_prime()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 96) + 11;
|
||||
UInt128 exponent = ((UInt128)1 << 64) + 3;
|
||||
|
||||
[Theory]
|
||||
[InlineData("193", "31")]
|
||||
[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);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
|
||||
remainder.Should().Be(expected);
|
||||
}
|
||||
|
||||
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
|
||||
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
|
||||
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
|
||||
[Theory]
|
||||
[InlineData("193", "31")]
|
||||
[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);
|
||||
|
||||
((UInt128)gpuResult).Should().Be(expected);
|
||||
}
|
||||
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
|
||||
{
|
||||
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);
|
||||
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
|
||||
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
|
||||
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
((UInt128)gpuResult).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
|
||||
{
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
UInt128 exponent = UInt128.One << 8;
|
||||
UInt128 prime = ((UInt128)1 << 70) + 3;
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 80) + 9;
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder);
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
|
||||
{
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
UInt128 exponent = UInt128.One << 8;
|
||||
UInt128 prime = ((UInt128)1 << 70) + 3;
|
||||
|
||||
[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 };
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder);
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
for (int i = 0; i < exponents.Length; i++)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
|
||||
remainders[i].Should().Be(expected);
|
||||
}
|
||||
}
|
||||
[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 };
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
|
||||
{
|
||||
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);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(UInt128.Zero);
|
||||
remainders[1].Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
for (int i = 0; i < exponents.Length; i++)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
|
||||
remainders[i].Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 72) + 23;
|
||||
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
|
||||
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||
[Fact]
|
||||
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
|
||||
{
|
||||
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);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(UInt128.Zero);
|
||||
remainders[1].Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
}
|
||||
}
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(UInt128.Zero);
|
||||
remainders[1].Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
// This will never happen in production code because the caller ensures the remainder span is large enough.
|
||||
// [Fact]
|
||||
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||
// {
|
||||
// ulong[] remainders = new ulong[1];
|
||||
// ulong[] exponents = { 1UL, 2UL };
|
||||
[Fact]
|
||||
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
|
||||
{
|
||||
UInt128 prime = ((UInt128)1 << 72) + 23;
|
||||
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
|
||||
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
|
||||
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
|
||||
|
||||
// Action act = () =>
|
||||
// {
|
||||
// const ulong prime = 5UL;
|
||||
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
// };
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(UInt128.Zero);
|
||||
remainders[1].Should().Be(UInt128.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
// act.Should().Throw<ArgumentException>();
|
||||
// }
|
||||
// This will never happen in production code because the caller ensures the remainder span is large enough.
|
||||
// [Fact]
|
||||
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||
// {
|
||||
// ulong[] remainders = new ulong[1];
|
||||
// ulong[] exponents = { 1UL, 2UL };
|
||||
|
||||
[Fact]
|
||||
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
|
||||
{
|
||||
const ulong prime = 7UL;
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
// Action act = () =>
|
||||
// {
|
||||
// const ulong prime = 5UL;
|
||||
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
// };
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
// act.Should().Throw<ArgumentException>();
|
||||
// }
|
||||
|
||||
result.Should().Be(3UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
|
||||
{
|
||||
const ulong prime = 7UL;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
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 PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -7,44 +7,39 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Trait("Category", "Fast")]
|
||||
public class PrimeTesterGpuTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsPrimeGpu_accepts_known_primes()
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
try
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsPrimeGpu_rejects_composites()
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
try
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
ulong[] composites = [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_accepts_known_primes()
|
||||
{
|
||||
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[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 PerfectNumbers.Core;
|
||||
using Xunit;
|
||||
using PerfectNumbers.Core.Gpu.Kernels;
|
||||
|
||||
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)
|
||||
{
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedCpu(divisor, keepMontgomery: false);
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisor);
|
||||
|
||||
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class ULongExtensionsGpuTests
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||
foreach (ulong exponent in exponents)
|
||||
{
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedCpu(divisor, keepMontgomery: false);
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisor);
|
||||
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,26 @@ namespace PerfectNumbers.Core.Tests;
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuKernelPoolTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||
{
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
var accelerator = lease.Accelerator;
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||
// {
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// var accelerator = lease.Accelerator;
|
||||
|
||||
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
// var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
// var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
|
||||
second.Equals(first).Should().BeTrue();
|
||||
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
}
|
||||
// second.Equals(first).Should().BeTrue();
|
||||
// second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
|
||||
@@ -17,19 +17,31 @@ public class GpuWorkLimiterTests
|
||||
var startedSecond = new TaskCompletionSource<bool>();
|
||||
var sw = new Stopwatch();
|
||||
|
||||
using var first = GpuWorkLimiter.Acquire();
|
||||
var first = GpuWorkLimiter.Acquire();
|
||||
sw.Start();
|
||||
|
||||
var secondTask = Task.Run(() =>
|
||||
{
|
||||
using var second = GpuWorkLimiter.Acquire();
|
||||
startedSecond.TrySetResult(true);
|
||||
var second = GpuWorkLimiter.Acquire();
|
||||
try
|
||||
{
|
||||
startedSecond.TrySetResult(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
second.Dispose();
|
||||
}
|
||||
});
|
||||
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
first.Dispose();
|
||||
try
|
||||
{
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
first.Dispose();
|
||||
}
|
||||
|
||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||
sw.Stop();
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
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,107 +1,110 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneDivisorCyclesGenerateGpuTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void GenerateGpu_produces_expected_cycles_for_small_range()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
ulong d = reader.ReadUInt64();
|
||||
ulong c = reader.ReadUInt64();
|
||||
pairs.Add((d, c));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void GenerateGpu_produces_expected_cycles_for_small_range()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
MersenneDivisorCyclesGpu.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
ulong d = reader.ReadUInt64();
|
||||
ulong c = reader.ReadUInt64();
|
||||
pairs.Add((d, c));
|
||||
}
|
||||
|
||||
var expected = new List<ulong>();
|
||||
for (ulong d = 3; d <= 50; d++)
|
||||
{
|
||||
if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var expected = new List<ulong>();
|
||||
for (ulong d = 3; d <= 50; d++)
|
||||
{
|
||||
if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
expected.Add(d);
|
||||
}
|
||||
expected.Add(d);
|
||||
}
|
||||
|
||||
pairs.Should().HaveCount(expected.Count);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
expected.Should().Contain(pair.divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
pairs.Should().HaveCount(expected.Count);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
expected.Should().Contain(pair.divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCyclesCpu.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Generate_produces_unique_cycles_for_expected_divisors()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Generate_produces_unique_cycles_for_expected_divisors()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
MersenneDivisorCyclesCpu.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||
|
||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
ulong divisor = reader.ReadUInt64();
|
||||
ulong cycle = reader.ReadUInt64();
|
||||
pairs.Add((divisor, cycle));
|
||||
}
|
||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
ulong divisor = reader.ReadUInt64();
|
||||
ulong cycle = reader.ReadUInt64();
|
||||
pairs.Add((divisor, cycle));
|
||||
}
|
||||
|
||||
var expected = new List<ulong>();
|
||||
for (ulong divisor = 3; divisor <= 50; divisor++)
|
||||
{
|
||||
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var expected = new List<ulong>();
|
||||
for (ulong divisor = 3; divisor <= 50; divisor++)
|
||||
{
|
||||
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
expected.Add(divisor);
|
||||
}
|
||||
expected.Add(divisor);
|
||||
}
|
||||
|
||||
pairs.Should().HaveCount(expected.Count);
|
||||
pairs.Should().HaveCount(expected.Count);
|
||||
|
||||
HashSet<ulong> uniqueDivisors = new();
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
HashSet<ulong> uniqueDivisors = new();
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCyclesCpu.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
|
||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||
|
||||
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
|
||||
completeCount.Should().Be(expected.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
var (_, completeCount) = MersenneDivisorCyclesCpu.FindLast(path);
|
||||
completeCount.Should().Be(expected.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,35 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneDivisorCyclesTests
|
||||
{
|
||||
public static IEnumerable<object[]> Divisors() =>
|
||||
new[]
|
||||
{
|
||||
new object[] {3UL, 2UL},
|
||||
new object[] {7UL, 3UL},
|
||||
new object[] {11UL, 10UL},
|
||||
};
|
||||
public static IEnumerable<object[]> Divisors() =>
|
||||
new[]
|
||||
{
|
||||
new object[] {3UL, 2UL},
|
||||
new object[] {7UL, 3UL},
|
||||
new object[] {11UL, 10UL},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(Divisors))]
|
||||
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(Divisors))]
|
||||
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
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,4 +1,6 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
@@ -11,7 +13,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
{
|
||||
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
|
||||
var tester = new FakeTester();
|
||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>();
|
||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, BigInteger Divisor)>();
|
||||
int compositeMarks = 0;
|
||||
int clearedMarks = 0;
|
||||
|
||||
@@ -22,7 +24,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
startPrime: 0UL,
|
||||
markComposite: () => compositeMarks++,
|
||||
clearComposite: () => clearedMarks++,
|
||||
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
||||
printResult: (prime, searched, detailed, passed, divisor) => results.Add((prime, searched, detailed, passed, divisor)),
|
||||
threadCount: 1,
|
||||
primesPerTask: 2);
|
||||
|
||||
@@ -30,12 +32,12 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
compositeMarks.Should().Be(2);
|
||||
clearedMarks.Should().Be(2);
|
||||
results.Should().Equal(
|
||||
new[]
|
||||
new (ulong Prime, bool Searched, bool Detailed, bool Passed, BigInteger Divisor)[]
|
||||
{
|
||||
(5UL, true, true, true),
|
||||
(7UL, true, true, false),
|
||||
(11UL, true, true, false),
|
||||
(13UL, true, true, true),
|
||||
(5UL, true, true, true, BigInteger.Zero),
|
||||
(7UL, true, true, false, new BigInteger(7)),
|
||||
(11UL, true, true, false, new BigInteger(11)),
|
||||
(13UL, true, true, true, BigInteger.Zero),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,31 +47,47 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
|
||||
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 void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
ConfiguredMaxPrime = maxPrime;
|
||||
}
|
||||
|
||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||
{
|
||||
return DivisorLimit;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||
if (prime % 7UL == 0UL)
|
||||
{
|
||||
divisor = 7UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prime % 11UL == 0UL)
|
||||
{
|
||||
divisor = 11UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor = 0UL;
|
||||
return true;
|
||||
}
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
return new DummySession();
|
||||
}
|
||||
|
||||
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
ConfiguredMaxPrime = maxPrime;
|
||||
for (int i = 0; i < primes.Length; i++)
|
||||
{
|
||||
allowedMaxValues[i] = 100UL;
|
||||
@@ -78,12 +96,12 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
|
||||
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
public bool CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Return()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,138 @@
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberDivisorCpuTesterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(11UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder();
|
||||
|
||||
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
ulong[] primes = [5UL, 7UL, 11UL];
|
||||
ulong[] allowedMax = new ulong[primes.Length];
|
||||
tester.PrepareCandidates(maxPrime: 11UL, primes, allowedMax);
|
||||
|
||||
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
tester.ResumeFromState(Path.Combine(Path.GetTempPath(), "bydivisor-cpu-tests.bin"), BigInteger.Zero, BigInteger.One);
|
||||
tester.ResetStateTracking();
|
||||
|
||||
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
tester.IsPrime(5UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().Be(BigInteger.Zero);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().Be(BigInteger.Zero);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
||||
}
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
{
|
||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
||||
|
||||
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[] primes = [5UL, 7UL, 11UL, 13UL];
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
ulong cycle23 = MersenneDivisorCyclesCpu.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes).Should().BeTrue();
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
ulong[] primesWithoutHit = [5UL, 7UL, 13UL];
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primesWithoutHit).Should().BeFalse();
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
ulong cycle31 = MersenneDivisorCyclesCpu.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes).Should().BeTrue();
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
ulong[] primesBeforeHit = [7UL, 11UL, 13UL];
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primesBeforeHit).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCyclesCpu.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents).Should().BeTrue();
|
||||
|
||||
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,310 +1,402 @@
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberDivisorGpuTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(33UL, 7UL, true)]
|
||||
[InlineData(35UL, 31UL, true)]
|
||||
[InlineData(37UL, 223UL, true)]
|
||||
[InlineData(33UL, 13UL, false)]
|
||||
[InlineData(35UL, 73UL, false)]
|
||||
[InlineData(37UL, 227UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(33UL, 7UL, true)]
|
||||
[InlineData(35UL, 31UL, true)]
|
||||
[InlineData(37UL, 223UL, true)]
|
||||
[InlineData(33UL, 13UL, false)]
|
||||
[InlineData(35UL, 73UL, false)]
|
||||
[InlineData(37UL, 227UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsDivisible_handles_large_divisors()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
|
||||
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsDivisible_handles_large_divisors()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
|
||||
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
typeof(MersenneNumberDivisorGpuTester)
|
||||
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||
.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
typeof(MersenneNumberDivisorGpuTester)
|
||||
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||
.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
|
||||
tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeFalse();
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsPrime(gpu, 31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsPrime(gpu, 35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrime_accepts_large_search_limits()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrime_accepts_large_search_limits()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsPrime(gpu, 35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
private static void ConfigureByDivisorTester(MersenneNumberDivisorByDivisorGpuTester tester, ulong maxPrime)
|
||||
{
|
||||
tester.PrepareCandidates(maxPrime, ReadOnlySpan<ulong>.Empty, Span<ulong>.Empty);
|
||||
tester.ResumeFromState(Path.Combine(Path.GetTempPath(), "bydivisor-gpu-tests.bin"), BigInteger.Zero, BigInteger.One);
|
||||
tester.ResetStateTracking();
|
||||
}
|
||||
|
||||
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
|
||||
exhausted.Should().BeTrue();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
||||
|
||||
tester.IsPrime(37UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
tester.IsPrime(31UL, out bool exhausted, out BigInteger divisor).Should().BeTrue();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().Be(BigInteger.Zero);
|
||||
|
||||
tester.IsPrime(43UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_defaults_to_zero_limit_before_configuration()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
||||
}
|
||||
|
||||
tester.DivisorLimit.Should().Be(0UL);
|
||||
[Fact]
|
||||
[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[] allowed = new ulong[primes.Length];
|
||||
ulong[] primes = [31UL, 37UL];
|
||||
ulong[] allowed = new ulong[primes.Length];
|
||||
tester.PrepareCandidates(maxPrime: 37UL, primes, allowed);
|
||||
|
||||
tester.PrepareCandidates(primes, allowed);
|
||||
allowed.Should().OnlyContain(value => value == 0UL);
|
||||
}
|
||||
tester.DivisorLimit.Should().BeGreaterThan(0UL);
|
||||
allowed.Should().OnlyContain(value => value > 0UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_respects_filter_based_limit()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(41UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_respects_filter_based_limit()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 41UL);
|
||||
|
||||
tester.IsPrime(31UL, out _).Should().BeTrue();
|
||||
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
tester.IsPrime(31UL, out _, out BigInteger divisor).Should().BeTrue();
|
||||
divisor.Should().Be(BigInteger.Zero);
|
||||
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
|
||||
exhausted.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]
|
||||
[InlineData(71UL, false)]
|
||||
[InlineData(89UL, true)]
|
||||
[InlineData(127UL, true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(exponent);
|
||||
[Theory]
|
||||
[InlineData(71UL, false)]
|
||||
[InlineData(89UL, true)]
|
||||
[InlineData(127UL, true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: exponent);
|
||||
|
||||
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,
|
||||
};
|
||||
tester.IsPrime(exponent, out bool exhausted, out BigInteger divisor).Should().Be(expectedPrime);
|
||||
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
exhausted.Should().BeTrue();
|
||||
if (expectedPrime)
|
||||
{
|
||||
divisor.Should().Be(BigInteger.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
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);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_computes_cycle_when_zero_is_provided()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(BigInteger.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EnvironmentConfiguration.GpuBatchSize = originalBatchSize;
|
||||
}
|
||||
}
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_computes_cycle_when_zero_is_provided()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
||||
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes, hits);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
|
||||
}
|
||||
ulong[] primes = [31UL, 37UL, 41UL];
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes).Should().BeTrue();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
ulong[] primesWithoutHit = [31UL, 41UL];
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primesWithoutHit).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
|
||||
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[] primes = [31UL, 37UL, 41UL, 43UL];
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 2,
|
||||
};
|
||||
ulong cycle223 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
||||
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
ulong[] primesBeforeHit = [31UL];
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primesBeforeHit).Should().BeFalse();
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
ulong cycle13367 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes).Should().BeTrue();
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
ulong[] primesWithoutMatchingExponent = [31UL, 37UL, 43UL];
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primesWithoutMatchingExponent).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
[Fact]
|
||||
[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);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 5,
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
ulong cycle223 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
||||
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
ulong[] primesBeforeHit = [31UL];
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primesBeforeHit).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EnvironmentConfiguration.GpuBatchSize = originalBatchSize;
|
||||
}
|
||||
}
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 43UL);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 5,
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
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);
|
||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 7UL, divisorData);
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
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();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 61UL);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
||||
{
|
||||
var cycles = MersenneDivisorCycles.Shared;
|
||||
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
||||
|
||||
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 11UL, divisorData);
|
||||
|
||||
try
|
||||
{
|
||||
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents).Should().BeTrue();
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData);
|
||||
ulong[] nonDivisibleExponents = [5UL, 17UL];
|
||||
session.CheckDivisor(11UL, divisorData, cycle, nonDivisibleExponents).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
tableField.SetValue(cycles, patchedTable);
|
||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
||||
{
|
||||
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 tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(19UL);
|
||||
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
||||
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||
.SetValue(tester, 200UL);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||
|
||||
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, originalSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||
patchedSmall[191] = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 191UL, divisorData);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester
|
||||
{
|
||||
BatchSize = 2,
|
||||
};
|
||||
tableField.SetValue(cycles, patchedTable);
|
||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
ConfigureByDivisorTester(tester, maxPrime: 19UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
||||
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||
.SetValue(tester, 200UL);
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
tester.IsPrime(19UL, out bool divisorsExhausted, out BigInteger divisor).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().Be(BigInteger.Zero);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, originalSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
[Fact]
|
||||
[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 PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -8,30 +8,38 @@ namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
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]
|
||||
[InlineData(GpuKernelType.Incremental)]
|
||||
[InlineData(GpuKernelType.Pow2Mod)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||
{
|
||||
var tester = new MersenneNumberIncrementalCpuTester(type);
|
||||
[Theory]
|
||||
[InlineData(GpuKernelType.Incremental)]
|
||||
[InlineData(GpuKernelType.Pow2Mod)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||
{
|
||||
var tester = new MersenneNumberIncrementalCpuTester(type);
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||
}
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
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 PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -8,31 +8,39 @@ namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
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]
|
||||
[InlineData(GpuKernelType.Incremental)]
|
||||
[InlineData(GpuKernelType.Pow2Mod)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||
{
|
||||
var tester = new MersenneNumberOrderCpuTester(type);
|
||||
[Theory]
|
||||
[InlineData(GpuKernelType.Incremental)]
|
||||
[InlineData(GpuKernelType.Pow2Mod)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(GpuKernelType type)
|
||||
{
|
||||
var tester = new MersenneNumberOrderCpuTester(type);
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
}
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
@@ -46,16 +45,15 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
writer.Write((ulong)uint.MaxValue);
|
||||
}
|
||||
|
||||
var cycles = MersenneDivisorCycles.Shared;
|
||||
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
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)!).Select(x => x).ToList();
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
|
||||
|
||||
cycles.LoadFrom(tempPath);
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -75,7 +73,6 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
{
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, backupSmall);
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -91,4 +88,3 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
@@ -8,24 +7,25 @@ namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
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]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents()
|
||||
{
|
||||
var tester = new MersenneNumberResidueCpuTester();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents()
|
||||
{
|
||||
var tester = new MersenneNumberResidueCpuTester();
|
||||
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
RunCase(tester, 23UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 36UL, expectedPrime: false);
|
||||
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -8,27 +8,36 @@ namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
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]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
||||
|
||||
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
||||
}
|
||||
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
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 PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
@@ -7,30 +8,81 @@ namespace PerfectNumbers.Core.Tests;
|
||||
[Trait("Category", "Fast")]
|
||||
public class MersenneNumberTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(136_000_002UL)]
|
||||
[InlineData(136_000_005UL)]
|
||||
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTester();
|
||||
tester.IsMersennePrime(p).Should().BeFalse();
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(136_000_002UL)]
|
||||
[InlineData(136_000_005UL)]
|
||||
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTesterForIncrementalCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsMersennePrime(gpu, p).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(125UL)]
|
||||
public void WarmUpOrders_populates_cache_without_affecting_results(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
|
||||
tester.WarmUpOrders(p, 1_000UL);
|
||||
tester.IsMersennePrime(p).Should().BeFalse();
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(125UL)]
|
||||
public void WarmUpOrdersCpu_populates_cache_without_affecting_results(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTesterForIncrementalCalculationMethodForEnabledCacheStatusForGpuOrderDeviceForCpu();
|
||||
tester.WarmUpOrders(p, 1_000UL);
|
||||
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 SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected)
|
||||
{
|
||||
p.SharesFactorWithExponentMinusOne().Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(125UL)]
|
||||
public void WarmUpOrdersGpu_populates_cache_without_affecting_results(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTesterForIncrementalCalculationMethodForEnabledCacheStatusForGpuOrderDeviceForGpu();
|
||||
tester.WarmUpOrders(p, 1_000UL);
|
||||
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,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
@@ -6,27 +7,37 @@ namespace PerfectNumbers.Core.Tests;
|
||||
[Trait("Category", "Fast")]
|
||||
public class MersenneResidueModeTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
|
||||
{
|
||||
var tester = new MersenneNumberTester(
|
||||
useResidue: true,
|
||||
useIncremental: false,
|
||||
useGpuScan: false,
|
||||
useGpuOrder: false);
|
||||
tester.IsMersennePrime(136_000_002UL).Should().BeFalse();
|
||||
}
|
||||
[Fact]
|
||||
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
|
||||
{
|
||||
var tester = new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsMersennePrime(gpu, 136_000_002UL).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
|
||||
{
|
||||
var tester = new MersenneNumberTester(
|
||||
useResidue: true,
|
||||
useIncremental: true,
|
||||
useGpuScan: false,
|
||||
useGpuOrder: false,
|
||||
maxK: 1_000UL);
|
||||
tester.IsMersennePrime(31UL).Should().BeTrue();
|
||||
}
|
||||
[Fact]
|
||||
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
|
||||
{
|
||||
var tester = new MersenneNumberTesterForResidueCalculationMethodForDisabledCacheStatusForCpuOrderDeviceForCpu(
|
||||
maxK: 1_000UL);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsMersennePrime(gpu, 31UL).Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
@@ -6,61 +7,150 @@ namespace PerfectNumbers.Core.Tests;
|
||||
[Trait("Category", "Fast")]
|
||||
public class ModResidueTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsDivisible_advances_residue_for_identity()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
[Fact]
|
||||
public void IsDivisibleCpu_advances_residue_for_identity()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.IsDivisible(5, 2).Should().BeFalse();
|
||||
tracker.IsDivisible(6, 2).Should().BeTrue();
|
||||
tracker.IsDivisible(7, 2).Should().BeFalse();
|
||||
}
|
||||
tracker.IsDivisibleCpu(5, 2).Should().BeFalse();
|
||||
tracker.IsDivisibleCpu(6, 2).Should().BeTrue();
|
||||
tracker.IsDivisibleCpu(7, 2).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(5UL, 31UL, true)]
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(11UL, 31UL, false)]
|
||||
public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
|
||||
[Fact]
|
||||
public void IsDivisibleGpu_advances_residue_for_identity()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.IsDivisible(p, divisor).Should().Be(expected);
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeOrAppend_tracks_existing_and_new_divisors()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(5UL, 31UL, true)]
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[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);
|
||||
}
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppend(10, 3, out bool d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppend(10, 5, out bool d5).Should().BeTrue();
|
||||
d5.Should().BeTrue();
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(5UL, 31UL, true)]
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[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);
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppend(10, 4, out bool d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
try
|
||||
{
|
||||
tracker.IsDivisibleGpu(gpu, p, divisor).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
tracker.BeginMerge(11);
|
||||
tracker.MergeOrAppend(11, 3, out d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppend(11, 4, out d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
tracker.MergeOrAppend(11, 5, out d5).Should().BeTrue();
|
||||
d5.Should().BeFalse();
|
||||
}
|
||||
[Fact]
|
||||
public void MergeOrAppendCpu_tracks_existing_and_new_divisors()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
[Fact]
|
||||
public void IsDivisible_requires_non_decreasing_numbers()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
tracker.IsDivisible(5, 2);
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppendCpu(10, 3, out bool d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppendCpu(10, 5, out bool d5).Should().BeTrue();
|
||||
d5.Should().BeTrue();
|
||||
|
||||
Action act = () => tracker.IsDivisible(4, 2);
|
||||
act.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppendCpu(10, 4, out bool d4).Should().BeTrue();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
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,198 +1,228 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class PrimeOrderCalculatorTests
|
||||
{
|
||||
public static IEnumerable<object[]> SmallPrimes()
|
||||
{
|
||||
yield return new object[] { 3UL };
|
||||
yield return new object[] { 5UL };
|
||||
yield return new object[] { 7UL };
|
||||
yield return new object[] { 11UL };
|
||||
yield return new object[] { 13UL };
|
||||
yield return new object[] { 17UL };
|
||||
yield return new object[] { 19UL };
|
||||
yield return new object[] { 31UL };
|
||||
yield return new object[] { 61UL };
|
||||
yield return new object[] { 89UL };
|
||||
yield return new object[] { 127UL };
|
||||
yield return new object[] { 521UL };
|
||||
}
|
||||
public static IEnumerable<object[]> SmallPrimes()
|
||||
{
|
||||
yield return new object[] { 3UL };
|
||||
yield return new object[] { 5UL };
|
||||
yield return new object[] { 7UL };
|
||||
yield return new object[] { 11UL };
|
||||
yield return new object[] { 13UL };
|
||||
yield return new object[] { 17UL };
|
||||
yield return new object[] { 19UL };
|
||||
yield return new object[] { 31UL };
|
||||
yield return new object[] { 61UL };
|
||||
yield return new object[] { 89UL };
|
||||
yield return new object[] { 127UL };
|
||||
yield return new object[] { 521UL };
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> PrimesWithPrevious()
|
||||
{
|
||||
yield return new object[] { 5UL, 3UL };
|
||||
yield return new object[] { 7UL, 5UL };
|
||||
yield return new object[] { 11UL, 7UL };
|
||||
yield return new object[] { 13UL, 11UL };
|
||||
yield return new object[] { 17UL, 13UL };
|
||||
yield return new object[] { 19UL, 17UL };
|
||||
yield return new object[] { 31UL, 29UL };
|
||||
yield return new object[] { 61UL, 59UL };
|
||||
yield return new object[] { 89UL, 83UL };
|
||||
yield return new object[] { 127UL, 113UL };
|
||||
yield return new object[] { 521UL, 509UL };
|
||||
}
|
||||
public static IEnumerable<object[]> PrimesWithPrevious()
|
||||
{
|
||||
yield return new object[] { 5UL, 3UL };
|
||||
yield return new object[] { 7UL, 5UL };
|
||||
yield return new object[] { 11UL, 7UL };
|
||||
yield return new object[] { 13UL, 11UL };
|
||||
yield return new object[] { 17UL, 13UL };
|
||||
yield return new object[] { 19UL, 17UL };
|
||||
yield return new object[] { 31UL, 29UL };
|
||||
yield return new object[] { 61UL, 59UL };
|
||||
yield return new object[] { 89UL, 83UL };
|
||||
yield return new object[] { 127UL, 113UL };
|
||||
yield return new object[] { 521UL, 509UL };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(SmallPrimes))]
|
||||
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
|
||||
{
|
||||
ulong expected = ComputeOrderByDoubling(prime);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(SmallPrimes))]
|
||||
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
|
||||
{
|
||||
ulong expected = ComputeOrderByDoubling(prime);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
ulong result = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
[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);
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
[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);
|
||||
|
||||
ulong strict = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
ulong heuristic = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
|
||||
heuristic.Should().Be(strict);
|
||||
}
|
||||
ulong strict = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculatorConfig.StrictDefault);
|
||||
|
||||
[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);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
result.Should().Be(expectedOrder);
|
||||
}
|
||||
heuristic.Should().Be(strict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored()
|
||||
{
|
||||
var config = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||
smallFactorLimit: 1,
|
||||
pollardRhoMilliseconds: 0,
|
||||
maxPowChecks: 8,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, 1UL)]
|
||||
[InlineData(3UL, 2UL)]
|
||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
ulong result = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
prime: 13UL,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(13UL),
|
||||
config,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
result.Should().Be(expectedOrder);
|
||||
}
|
||||
|
||||
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored()
|
||||
{
|
||||
var config = new PrimeOrderCalculatorConfig(
|
||||
smallFactorLimit: 1,
|
||||
pollardRhoMilliseconds: 0,
|
||||
maxPowChecks: 8,
|
||||
strictMode: false);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode()
|
||||
{
|
||||
var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||
smallFactorLimit: 2,
|
||||
pollardRhoMilliseconds: 0,
|
||||
maxPowChecks: 1,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
ulong result = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime: 13UL,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(13UL),
|
||||
config);
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
heuristicConfig,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||
}
|
||||
|
||||
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode()
|
||||
{
|
||||
var heuristicConfig = new PrimeOrderCalculatorConfig(
|
||||
smallFactorLimit: 2,
|
||||
pollardRhoMilliseconds: 0,
|
||||
maxPowChecks: 1,
|
||||
strictMode: false);
|
||||
|
||||
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||
smallFactorLimit: 2,
|
||||
pollardRhoMilliseconds: 0,
|
||||
maxPowChecks: 1,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||
ulong heuristic = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
heuristicConfig);
|
||||
|
||||
ulong strict = PrimeOrderCalculator.Calculate(
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
strictConfig,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||
|
||||
strict.Should().Be(heuristic);
|
||||
}
|
||||
var strictConfig = new PrimeOrderCalculatorConfig(
|
||||
smallFactorLimit: 2,
|
||||
pollardRhoMilliseconds: 0,
|
||||
maxPowChecks: 1,
|
||||
strictMode: true);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Calculate_handles_128_bit_prime()
|
||||
{
|
||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||
ulong strict = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
strictConfig);
|
||||
|
||||
UInt128 result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
strict.Should().Be(heuristic);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
result.Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Calculate_handles_128_bit_prime()
|
||||
{
|
||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void GetCycle_uses_wide_prime_heuristic()
|
||||
{
|
||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||
MersenneDivisorCycles.GetCycle(prime).Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
UInt128 result = PrimeOrderCalculatorGpu.Calculate(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
|
||||
private static ulong ComputeOrderByDoubling(ulong prime)
|
||||
{
|
||||
if (prime == 2UL)
|
||||
{
|
||||
return 1UL;
|
||||
}
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
result.Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
|
||||
if (prime == 3UL)
|
||||
{
|
||||
return 2UL;
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void GetCycle_uses_wide_prime_heuristic()
|
||||
{
|
||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var mersenneDivisorCycles = MersenneDivisorCyclesGpu.GetCycle(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % prime;
|
||||
private static ulong ComputeOrderByDoubling(ulong prime)
|
||||
{
|
||||
if (prime == 2UL)
|
||||
{
|
||||
return 1UL;
|
||||
}
|
||||
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value * 2UL) % prime;
|
||||
order++;
|
||||
}
|
||||
if (prime == 3UL)
|
||||
{
|
||||
return 2UL;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % prime;
|
||||
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value * 2UL) % prime;
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
@@ -6,20 +7,28 @@ namespace PerfectNumbers.Core.Tests;
|
||||
[Trait("Category", "Fast")]
|
||||
public class PrimeTesterGcdTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(81UL, true)]
|
||||
[InlineData(101UL, false)]
|
||||
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
|
||||
{
|
||||
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData(81UL, true)]
|
||||
[InlineData(101UL, false)]
|
||||
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
|
||||
{
|
||||
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SharesFactorWithMaxExponentBatch_filters_values()
|
||||
{
|
||||
ulong[] values = [81UL, 101UL];
|
||||
Span<byte> results = stackalloc byte[values.Length];
|
||||
PrimeTester.SharesFactorWithMaxExponentBatch(values, results);
|
||||
results.ToArray().Should().Equal(new byte[] { 1, 0 });
|
||||
}
|
||||
[Fact]
|
||||
public void SharesFactorWithMaxExponentBatch_filters_values()
|
||||
{
|
||||
ulong[] values = [81UL, 101UL];
|
||||
Span<byte> results = stackalloc byte[values.Length];
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
PrimeTester.SharesFactorWithMaxExponentBatch(gpu, values, results);
|
||||
results.ToArray().Should().Equal(new byte[] { 1, 0 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,98 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class PrimeTesterIsPrimeTests
|
||||
{
|
||||
[Theory]
|
||||
[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.
|
||||
[InlineData(7UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(11UL, true)]
|
||||
[InlineData(13UL, true)]
|
||||
[InlineData(21UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
[InlineData(133UL, false)]
|
||||
[InlineData(137UL, true)]
|
||||
[InlineData(341UL, false)]
|
||||
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
[Theory]
|
||||
[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.
|
||||
[InlineData(7UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(11UL, true)]
|
||||
[InlineData(13UL, true)]
|
||||
[InlineData(21UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
[InlineData(133UL, false)]
|
||||
[InlineData(137UL, true)]
|
||||
[InlineData(143UL, false)]
|
||||
[InlineData(199UL, true)]
|
||||
[InlineData(209UL, false)]
|
||||
[InlineData(403UL, false)]
|
||||
[InlineData(341UL, false)]
|
||||
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
|
||||
tester.IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||
}
|
||||
PrimeTester.IsPrimeCpu(n).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
GpuContextPool.ForceCpu = true;
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(139UL, true)]
|
||||
[InlineData(189UL, false)]
|
||||
[InlineData(193UL, true)]
|
||||
[InlineData(279UL, false)]
|
||||
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
|
||||
tester.IsPrimeGpu(137UL, CancellationToken.None).Should().BeTrue();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
PrimeTester.IsPrimeGpu(gpu, n).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
tester.IsPrimeGpu(341UL, CancellationToken.None).Should().BeFalse();
|
||||
[Fact]
|
||||
[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();
|
||||
}
|
||||
|
||||
GpuContextPool.ForceCpu = false;
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
|
||||
{
|
||||
GpuPrimeWorkLimiter.SetLimit(64);
|
||||
PrimeTester.GpuBatchSize = 256_000;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
GpuPrimeWorkLimiter.SetLimit(64);
|
||||
PrimeTester.GpuBatchSize = 256_000;
|
||||
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
||||
var results = new bool[primes.Length];
|
||||
|
||||
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
|
||||
var results = new bool[primes.Length];
|
||||
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
Parallel.For(0, primes.Length, i =>
|
||||
{
|
||||
results[i] = PrimeTester.IsPrimeGpu(gpu, primes[i]);
|
||||
});
|
||||
|
||||
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||
Parallel.For(0, primes.Length, i =>
|
||||
{
|
||||
results[i] = testers.Value!.IsPrimeGpu(primes[i], CancellationToken.None);
|
||||
});
|
||||
results.Should().AllBeEquivalentTo(true);
|
||||
|
||||
results.Should().AllBeEquivalentTo(true);
|
||||
|
||||
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
|
||||
{
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
testers.Value!.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
|
||||
}
|
||||
});
|
||||
}
|
||||
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
|
||||
{
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue();
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,14 +32,39 @@ public class PrimesGeneratorTests
|
||||
var lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||
var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
||||
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);
|
||||
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);
|
||||
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]);
|
||||
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++)
|
||||
{
|
||||
@@ -58,6 +83,64 @@ public class PrimesGeneratorTests
|
||||
.Should().BeTrue();
|
||||
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,284 +1,305 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class UInt128ExtensionsTests
|
||||
{
|
||||
public static IEnumerable<object[]> BinaryGcdCases()
|
||||
{
|
||||
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
|
||||
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
|
||||
public static IEnumerable<object[]> BinaryGcdCases()
|
||||
{
|
||||
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
|
||||
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
|
||||
|
||||
UInt128 big1 = (UInt128.One << 96) + 12345;
|
||||
UInt128 big2 = (UInt128.One << 80) + 67890;
|
||||
yield return new object[]
|
||||
{
|
||||
big1,
|
||||
big2,
|
||||
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
|
||||
};
|
||||
}
|
||||
UInt128 big1 = (UInt128.One << 96) + 12345;
|
||||
UInt128 big2 = (UInt128.One << 80) + 67890;
|
||||
yield return new object[]
|
||||
{
|
||||
big1,
|
||||
big2,
|
||||
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
|
||||
};
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(BinaryGcdCases))]
|
||||
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
|
||||
{
|
||||
a.BinaryGcd(b).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(BinaryGcdCases))]
|
||||
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
|
||||
{
|
||||
a.BinaryGcd(b).Should().Be(expected);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> TrailingZeroCases()
|
||||
{
|
||||
yield return new object[] { UInt128.Zero, 128 };
|
||||
yield return new object[] { (UInt128)1, 0 };
|
||||
yield return new object[] { (UInt128)8, 3 };
|
||||
yield return new object[] { UInt128.One << 80, 80 };
|
||||
}
|
||||
public static IEnumerable<object[]> TrailingZeroCases()
|
||||
{
|
||||
yield return new object[] { UInt128.Zero, 128 };
|
||||
yield return new object[] { (UInt128)1, 0 };
|
||||
yield return new object[] { (UInt128)8, 3 };
|
||||
yield return new object[] { UInt128.One << 80, 80 };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(TrailingZeroCases))]
|
||||
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
|
||||
{
|
||||
value.CountTrailingZeros().Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(TrailingZeroCases))]
|
||||
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
|
||||
{
|
||||
value.CountTrailingZeros().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
public void CalculateOrder_matches_naive(ulong q)
|
||||
{
|
||||
UInt128 q128 = q;
|
||||
ulong expected = NaiveOrder(q);
|
||||
q128.CalculateOrder().Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
public void CalculateOrderCpu_matches_naive(ulong q)
|
||||
{
|
||||
UInt128 q128 = q;
|
||||
ulong expected = NaiveOrder(q);
|
||||
q128.CalculateOrderCpu().Should().Be(expected);
|
||||
}
|
||||
|
||||
private static ulong NaiveOrder(ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % q;
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value << 1) % q;
|
||||
order++;
|
||||
}
|
||||
private static ulong NaiveOrder(ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % q;
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value << 1) % q;
|
||||
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);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeCandidate_detects_large_composite()
|
||||
{
|
||||
UInt128 n = (UInt128.One << 64) + 1;
|
||||
n.IsPrimeCandidate().Should().BeFalse();
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
public void IsPrimeCandidate_checks_small(ulong n, bool expected)
|
||||
{
|
||||
((UInt128)n).IsPrimeCandidate().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod10_8_5_3_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345678901234567890UL;
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeCandidate_detects_large_composite()
|
||||
{
|
||||
UInt128 n = (UInt128.One << 64) + 1;
|
||||
n.IsPrimeCandidate().Should().BeFalse();
|
||||
}
|
||||
|
||||
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod10_8_5_3_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345678901234567890UL;
|
||||
|
||||
ulong expected10 = (ulong)(value % 10);
|
||||
ulong expected8 = (ulong)(value % 8);
|
||||
ulong expected5 = (ulong)(value % 5);
|
||||
ulong expected3 = (ulong)(value % 3);
|
||||
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
|
||||
mod10.Should().Be(expected10);
|
||||
mod8.Should().Be(expected8);
|
||||
mod5.Should().Be(expected5);
|
||||
mod3.Should().Be(expected3);
|
||||
}
|
||||
ulong expected10 = (ulong)(value % 10);
|
||||
ulong expected8 = (ulong)(value % 8);
|
||||
ulong expected5 = (ulong)(value % 5);
|
||||
ulong expected3 = (ulong)(value % 3);
|
||||
|
||||
[Theory]
|
||||
[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);
|
||||
mod10.Should().Be(expected10);
|
||||
mod8.Should().Be(expected8);
|
||||
mod5.Should().Be(expected5);
|
||||
mod3.Should().Be(expected3);
|
||||
}
|
||||
|
||||
v.Pow(exponent).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[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);
|
||||
|
||||
private static BigInteger BigPow(ulong value, ulong exponent)
|
||||
{
|
||||
BigInteger result = BigInteger.One;
|
||||
BigInteger baseValue = value;
|
||||
v.Pow(exponent).Should().Be(expected);
|
||||
}
|
||||
|
||||
while (exponent != 0UL)
|
||||
{
|
||||
if ((exponent & 1UL) != 0UL)
|
||||
{
|
||||
result *= baseValue;
|
||||
}
|
||||
private static BigInteger BigPow(ulong value, ulong exponent)
|
||||
{
|
||||
BigInteger result = BigInteger.One;
|
||||
BigInteger baseValue = value;
|
||||
|
||||
baseValue *= baseValue;
|
||||
exponent >>= 1;
|
||||
}
|
||||
while (exponent != 0UL)
|
||||
{
|
||||
if ((exponent & 1UL) != 0UL)
|
||||
{
|
||||
result *= baseValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
baseValue *= baseValue;
|
||||
exponent >>= 1;
|
||||
}
|
||||
|
||||
[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;
|
||||
return result;
|
||||
}
|
||||
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus);
|
||||
[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;
|
||||
|
||||
v.ModPow(e, m).Should().Be(expected);
|
||||
}
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ModPow_handles_large_values()
|
||||
{
|
||||
UInt128 v = (UInt128.One << 96) + 12345;
|
||||
UInt128 e = 17;
|
||||
UInt128 m = 97;
|
||||
v.ModPow(e, m).Should().Be(expected);
|
||||
}
|
||||
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ModPow_handles_large_values()
|
||||
{
|
||||
UInt128 v = (UInt128.One << 96) + 12345;
|
||||
UInt128 e = 17;
|
||||
UInt128 m = 97;
|
||||
|
||||
v.ModPow(e, m).Should().Be(expected);
|
||||
}
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m);
|
||||
|
||||
[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));
|
||||
}
|
||||
v.ModPow(e, m).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod8_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod8().Should().Be((ulong)(value % 8));
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(123456789UL)]
|
||||
public void Mod10_matches_operator(ulong low)
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + low;
|
||||
value.Mod10().Should().Be((ulong)(value % 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod3_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod3().Should().Be((ulong)(value % 3));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod8_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod8().Should().Be((ulong)(value % 8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod6_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 9876543210UL;
|
||||
value.Mod6().Should().Be((ulong)(value % 6));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod3_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod3().Should().Be((ulong)(value % 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod5_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod5().Should().Be((ulong)(value % 5));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod6_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 9876543210UL;
|
||||
value.Mod6().Should().Be((ulong)(value % 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod7_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 9876543210987654321UL;
|
||||
value.Mod7().Should().Be((ulong)(value % 7));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod5_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 12345;
|
||||
value.Mod5().Should().Be((ulong)(value % 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod11_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
||||
value.Mod11().Should().Be((ulong)(value % 11));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod7_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 100) + 9876543210987654321UL;
|
||||
value.Mod7().Should().Be((ulong)(value % 7));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod128_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
||||
value.Mod128().Should().Be((ulong)(value % 128));
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod11_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
||||
value.Mod11().Should().Be((ulong)(value % 11));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mul64_matches_expected_product()
|
||||
{
|
||||
UInt128 a = (UInt128.One << 80) + 123456789;
|
||||
UInt128 b = (UInt128.One << 64) + 987654321;
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mod128_matches_operator()
|
||||
{
|
||||
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
|
||||
value.Mod128().Should().Be((ulong)(value % 128));
|
||||
}
|
||||
|
||||
UInt128 expected = (UInt128)((UInt128)(ulong)a * b);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Mul64_matches_expected_product()
|
||||
{
|
||||
UInt128 a = (UInt128.One << 80) + 123456789;
|
||||
UInt128 b = (UInt128.One << 64) + 987654321;
|
||||
|
||||
a.Mul64(b).Should().Be(expected);
|
||||
}
|
||||
UInt128 expected = (UInt128)((UInt128)(ulong)a * b);
|
||||
|
||||
[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;
|
||||
a.Mul64(b).Should().Be(expected);
|
||||
}
|
||||
|
||||
UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus);
|
||||
[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;
|
||||
|
||||
ua.MulMod(ub, mod).Should().Be(expected);
|
||||
}
|
||||
UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus);
|
||||
|
||||
[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;
|
||||
ua.MulMod(ub, mod).Should().Be(expected);
|
||||
}
|
||||
|
||||
UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod);
|
||||
[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;
|
||||
|
||||
a.MulMod(b, mod).Should().Be(expected);
|
||||
}
|
||||
UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod);
|
||||
|
||||
a.MulMod(b, mod).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,225 +1,282 @@
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class ULongExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(9UL)]
|
||||
[InlineData(10UL)]
|
||||
[InlineData(1234567890UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
public void Mod10_returns_same_as_operator(ulong value)
|
||||
{
|
||||
ulong expected = value % 10UL;
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(9UL)]
|
||||
[InlineData(10UL)]
|
||||
[InlineData(1234567890UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
public void Mod10_returns_same_as_operator(ulong value)
|
||||
{
|
||||
byte expected = (byte)(value % 10UL);
|
||||
|
||||
ULongExtensions.Mod10(value).Should().Be(expected);
|
||||
}
|
||||
ULongExtensions.Mod10(value).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
[InlineData(1234567890123456789UL)]
|
||||
public void Mod128_matches_operator(ulong value)
|
||||
{
|
||||
value.Mod128().Should().Be(value % 128UL);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
[InlineData(1234567890123456789UL)]
|
||||
public void Mod128_matches_operator(ulong value)
|
||||
{
|
||||
value.Mod128().Should().Be(value % 128UL);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(123456789UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
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);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(123456789UL)]
|
||||
[InlineData(ulong.MaxValue)]
|
||||
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);
|
||||
|
||||
ulong expected10 = ((value % 10UL) << 1) % 10UL;
|
||||
ulong expected8 = ((value & 7UL) << 1) & 7UL;
|
||||
ulong expected5 = ((value % 5UL) << 1) % 5UL;
|
||||
ulong expected3 = ((value % 3UL) << 1) % 3UL;
|
||||
ulong expected10 = ((value % 10UL) << 1) % 10UL;
|
||||
ulong expected8 = ((value & 7UL) << 1) & 7UL;
|
||||
ulong expected5 = ((value % 5UL) << 1) % 5UL;
|
||||
ulong expected3 = ((value % 3UL) << 1) % 3UL;
|
||||
|
||||
step10.Should().Be(expected10);
|
||||
step8.Should().Be(expected8);
|
||||
step5.Should().Be(expected5);
|
||||
step3.Should().Be(expected3);
|
||||
}
|
||||
step10.Should().Be(expected10);
|
||||
step8.Should().Be(expected8);
|
||||
step5.Should().Be(expected5);
|
||||
step3.Should().Be(expected3);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
|
||||
{
|
||||
candidate.IsPrimeCandidate().Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, true)]
|
||||
[InlineData(9UL, false)]
|
||||
[InlineData(97UL, true)]
|
||||
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
|
||||
{
|
||||
candidate.IsPrimeCandidate().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
[InlineData(11UL)]
|
||||
public void CalculateOrder_matches_naive_algorithm(ulong q)
|
||||
{
|
||||
ulong expected = NaiveOrder(q);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
[InlineData(11UL)]
|
||||
public void CalculateOrderCpu_matches_naive_algorithm(ulong q)
|
||||
{
|
||||
ulong expected = NaiveOrder(q);
|
||||
|
||||
q.CalculateOrder().Should().Be(expected);
|
||||
}
|
||||
q.CalculateOrderCpu().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, 10UL, 17UL)]
|
||||
[InlineData(123456789UL, 3UL, 97UL)]
|
||||
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
|
||||
{
|
||||
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
[InlineData(11UL)]
|
||||
public void CalculateOrderGpu_matches_naive_algorithm(ulong q)
|
||||
{
|
||||
ulong expected = NaiveOrder(q);
|
||||
|
||||
value.ModPow64(exponent, modulus).Should().Be(expected);
|
||||
}
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
q.CalculateOrderGpu(gpu).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(10UL, 17UL)]
|
||||
[InlineData(123UL, 97UL)]
|
||||
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(2UL, 10UL, 17UL)]
|
||||
[InlineData(123456789UL, 3UL, 97UL)]
|
||||
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
|
||||
{
|
||||
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
|
||||
|
||||
exponent.PowMod((UInt128)modulus).Should().Be(expected);
|
||||
}
|
||||
value.ModPow64(exponent, modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(5UL, 7UL, 3UL, 4UL)]
|
||||
[InlineData(10UL, 7UL, 3UL, 2UL)]
|
||||
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
|
||||
{
|
||||
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(10UL, 17UL)]
|
||||
[InlineData(123UL, 97UL)]
|
||||
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
[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);
|
||||
}
|
||||
exponent.PowMod((UInt128)modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(1234567890123456789UL, 3UL)]
|
||||
[InlineData(ulong.MaxValue, 7UL)]
|
||||
public void FastDiv64_matches_division(ulong value, ulong divisor)
|
||||
{
|
||||
ulong mul = (ulong)(((UInt128)1 << 64) / divisor);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(5UL, 7UL, 3UL, 4UL)]
|
||||
[InlineData(10UL, 7UL, 3UL, 2UL)]
|
||||
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
|
||||
{
|
||||
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
|
||||
}
|
||||
|
||||
value.FastDiv64(divisor, mul).Should().Be(value / divisor);
|
||||
}
|
||||
[Theory]
|
||||
[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]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 1UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
|
||||
{
|
||||
ulong expected = (ulong)(((UInt128)x * y) >> 64);
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(15UL, true)]
|
||||
[InlineData(7UL, false)]
|
||||
[InlineData(9UL, false)]
|
||||
public void SharesFactorWithExponentMinusOneGpu_detects_relationship(ulong exponent, bool expected)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
exponent.SharesFactorWithExponentMinusOneGpu(gpu).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
x.MulHighCpu(y).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(1234567890123456789UL, 3UL)]
|
||||
[InlineData(ulong.MaxValue, 7UL)]
|
||||
public void FastDiv64_matches_division(ulong value, ulong divisor)
|
||||
{
|
||||
ulong mul = (ulong)(((UInt128)1 << 64) / divisor);
|
||||
|
||||
[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));
|
||||
}
|
||||
value.FastDiv64(divisor, mul).Should().Be(value / divisor);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 2UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void Mul64_matches_UInt128_product(ulong a, ulong b)
|
||||
{
|
||||
UInt128 expected = (UInt128)a * b;
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 1UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
|
||||
{
|
||||
ulong expected = (ulong)(((UInt128)x * y) >> 64);
|
||||
|
||||
a.Mul64(b).Should().Be(expected);
|
||||
}
|
||||
x.MulHighCpu(y).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(123456789UL, 987654321UL, 97UL)]
|
||||
[InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)]
|
||||
public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus);
|
||||
[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));
|
||||
}
|
||||
|
||||
a.MulMod64(b, modulus).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 2UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void Mul64_matches_UInt128_product(ulong a, ulong b)
|
||||
{
|
||||
UInt128 expected = (UInt128)a * b;
|
||||
|
||||
[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);
|
||||
a.Mul64(b).Should().Be(expected);
|
||||
}
|
||||
|
||||
exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected);
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(123456789UL, 987654321UL, 97UL)]
|
||||
[InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)]
|
||||
public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus);
|
||||
|
||||
[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);
|
||||
a.MulMod64(b, modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).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]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(11UL, 13UL)]
|
||||
public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected);
|
||||
}
|
||||
|
||||
exponent.PowModCrt((UInt128)modulus, cycles).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);
|
||||
|
||||
private static ulong NaiveOrder(ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected);
|
||||
}
|
||||
|
||||
ulong order = 1UL;
|
||||
ulong value = 2UL % q;
|
||||
while (value != 1UL)
|
||||
{
|
||||
value = (value << 1) % q;
|
||||
order++;
|
||||
}
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(11UL, 13UL)]
|
||||
public void PowModCrtCpu_matches_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
return order;
|
||||
}
|
||||
exponent.PowModCrtCpu((UInt128)modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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
@@ -0,0 +1,977 @@
|
||||
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