DivisorSet.Percentile, Additive and TopDown modes implemented. State files ported to LMDB => FASTER to avoid millions of files in folders.

This commit is contained in:
mlemanczyk committed 2026-01-04 12:17:27 +01:00
1 parent 27f65baf3d
commit 6a924f4041
18 files changed
+2619 -367

No files matched your search

@@ -11,6 +11,8 @@ namespace PerfectNumbers.Core.ByDivisor;
public sealed class ByDivisorClassEntry
{
public double K10 { get; set; }
public double K25 { get; set; }
public double K50 { get; set; }
public double K75 { get; set; }
public double GapProbability { get; set; }
@@ -26,6 +28,11 @@ public sealed class ByDivisorScanPlan
public double Target { get; init; }
public double Rho1 { get; init; }
public double Rho2 { get; init; }
public double K10 { get; init; }
public double K25 { get; init; }
public double K50 { get; init; }
public double K75 { get; init; }
public double DeltaK { get; init; }
}
public sealed class ByDivisorScanTuning
@@ -39,6 +46,7 @@ public sealed class ByDivisorScanTuning
public double Rho2 { get; init; } = 1.6d;
public double AggressiveRho2 { get; init; } = 1.8d;
public double HardGapThreshold { get; init; } = 0.75d;
public double DeltaFraction { get; init; } = 0.15d;
[JsonIgnore]
public static ByDivisorScanTuning Default => new();
@@ -47,10 +55,10 @@ public sealed class ByDivisorScanTuning
public sealed class ByDivisorClassModel
{
private const int ClassCount = 1024;
public int Version { get; set; } = 1;
public int Version { get; set; } = 2;
public double CheapLimit { get; set; } = 10_000d;
public ByDivisorClassEntry[] Entries { get; set; } = Enumerable.Range(0, ClassCount)
.Select(_ => new ByDivisorClassEntry { K50 = 1d, K75 = 1d })
.Select(_ => new ByDivisorClassEntry { K10 = 1d, K25 = 1d, K50 = 1d, K75 = 1d })
.ToArray();
public ByDivisorScanPlan BuildPlan(ulong prime, BigInteger allowedMax, in ByDivisorScanTuning tuning)
@@ -58,6 +66,8 @@ public sealed class ByDivisorClassModel
int suffix = (int)(prime & 1023UL);
ByDivisorClassEntry entry = Entries[suffix];
double k10 = entry.K10 > 1d ? entry.K10 : 1d;
double k25 = entry.K25 > 1d ? entry.K25 : Math.Max(k10, 1d);
double k50 = entry.K50 > 1d ? entry.K50 : 1d;
double k75 = entry.K75 > 1d ? entry.K75 : Math.Max(k50, 1d);
bool isHard = entry.GapProbability >= tuning.HardGapThreshold || string.Equals(entry.Stability, "escaping", StringComparison.OrdinalIgnoreCase);
@@ -69,6 +79,7 @@ public sealed class ByDivisorClassModel
double cheap = tuning.CheapLimit > 1d ? tuning.CheapLimit : CheapLimit;
double rho2 = isHard ? tuning.AggressiveRho2 : tuning.Rho2;
double deltaK = Math.Max(1d, Math.Ceiling(start * Math.Max(0d, tuning.DeltaFraction)));
double maxKAllowed = ComputeMaxKAllowed(prime, allowedMax);
if (maxKAllowed > 0d)
@@ -76,6 +87,11 @@ public sealed class ByDivisorClassModel
cheap = Math.Min(cheap, maxKAllowed);
start = Math.Min(start, maxKAllowed);
target = Math.Min(target, maxKAllowed);
k10 = Math.Min(k10, maxKAllowed);
k25 = Math.Min(k25, maxKAllowed);
k50 = Math.Min(k50, maxKAllowed);
k75 = Math.Min(k75, maxKAllowed);
deltaK = Math.Min(deltaK, maxKAllowed);
}
return new ByDivisorScanPlan
@@ -85,6 +101,11 @@ public sealed class ByDivisorClassModel
Target = target,
Rho1 = tuning.Rho1,
Rho2 = rho2,
K10 = k10,
K25 = k25,
K50 = k50,
K75 = k75,
DeltaK = deltaK,
};
}
@@ -149,6 +170,7 @@ public static class ByDivisorClassModelLoader
private static void Save(string modelPath, ByDivisorClassModel model)
{
string tempPath = modelPath + ".tmp";
string? directory = Path.GetDirectoryName(modelPath);
if (!string.IsNullOrEmpty(directory))
{
@@ -160,10 +182,12 @@ public static class ByDivisorClassModelLoader
WriteIndented = true,
};
string json = JsonSerializer.Serialize(model, options);
string tempPath = modelPath + ".tmp";
File.WriteAllText(tempPath, json);
File.Copy(tempPath, modelPath, overwrite: true);
File.Delete(tempPath);
lock (IoLock)
{
File.WriteAllText(tempPath, json);
File.Copy(tempPath, modelPath, overwrite: true);
File.Delete(tempPath);
}
}
private static ByDivisorClassModel BuildFromResults(string resultsPath, double cheapLimit)
@@ -251,11 +275,15 @@ public static class ByDivisorClassModelLoader
for (int i = 0; i < 1024; i++)
{
List<double>? classSamples = samples[i];
double k10 = 1d;
double k25 = 1d;
double k50 = 1d;
double k75 = 1d;
if (classSamples is not null && classSamples.Count > 0)
{
classSamples.Sort();
k10 = ComputePercentile(classSamples, 0.10d);
k25 = ComputePercentile(classSamples, 0.25d);
k50 = ComputePercentile(classSamples, 0.5d);
k75 = ComputePercentile(classSamples, 0.75d);
}
@@ -266,6 +294,8 @@ public static class ByDivisorClassModelLoader
model.Entries[i] = new ByDivisorClassEntry
{
K10 = k10,
K25 = k25,
K50 = k50,
K75 = k75,
GapProbability = gapProb,
@@ -341,4 +371,6 @@ public static class ByDivisorClassModelLoader
double fraction = index - lower;
return values[lower] + ((values[upper] - values[lower]) * fraction);
}
private static readonly object IoLock = new();
}
@@ -7,14 +7,18 @@ using System.Collections.Concurrent;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using PerfectNumbers.Core.ByDivisor;
using PerfectNumbers.Core.Persistence;
namespace PerfectNumbers.Core.Cpu;
public enum DivisorSet
{
OneByOne,
Sequential,
Pow2Groups,
Predictive,
Percentile,
Additive,
TopDown,
}
[EnumDependentTemplate(typeof(DivisorSet))]
@@ -36,10 +40,18 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
private int _stateCounter;
private BigInteger _lastSavedK;
private bool _pow2MinusOneChecked;
private string _pow2MinusOneStateFilePath = string.Empty;
private Pow2Minus1StateRepository? _pow2MinusOneRepository;
private KStateRepository _kRepository = null!;
private KStateRepository? _cheapRepository;
private BigInteger _cheapLastSavedK;
private BigInteger _cheapResumeK;
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
private ulong _currentPrime;
#if DivisorSet_TopDown
private BigInteger _topDownCursor;
#endif
#if DivisorSet_Pow2Groups
private const int PercentScale = DivisorByDivisorConfig.PercentScale;
@@ -68,6 +80,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
private int _preparedSpecialRangePromille;
private int[] _preparedSpecialSingles = Array.Empty<int>();
private (int Start, int End)[] _preparedSpecialRanges = Array.Empty<(int Start, int End)>();
private KStateRepository? _specialRepository;
private KStateRepository? _groupsRepository;
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -197,11 +211,59 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private bool TryBuildCheapRange(in BigInteger minK, in BigInteger chunkSize, in BigInteger maxKAllowed, out BigInteger start, out BigInteger end)
{
start = minK > BigInteger.One ? minK : BigInteger.One;
if (_cheapResumeK > start)
{
start = _cheapResumeK;
}
if (start > maxKAllowed)
{
end = BigInteger.Zero;
return false;
}
BigInteger span = chunkSize > BigInteger.One ? chunkSize - BigInteger.One : BigInteger.Zero;
end = start + span;
if (end > maxKAllowed)
{
end = maxKAllowed;
}
return end >= start;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void RecordCheapState(in BigInteger k)
{
if (_currentPrime == 0UL || _cheapRepository is null)
{
return;
}
if (k <= _cheapLastSavedK)
{
return;
}
_cheapRepository.Upsert(_currentPrime, k);
_cheapLastSavedK = k;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ResetStateTracking()
{
_stateCounter = 0;
_pow2MinusOneChecked = false;
_currentPrime = 0UL;
_cheapLastSavedK = BigInteger.Zero;
_cheapResumeK = BigInteger.One;
#if DivisorSet_TopDown
_topDownCursor = BigInteger.Zero;
#endif
#if DivisorSet_Pow2Groups
_specialStateCounter = 0;
_groupsStateCounter = 0;
@@ -217,21 +279,49 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
_lastSavedK = lastSavedK;
MinK = minK;
_stateCounter = 0;
_pow2MinusOneStateFilePath = stateFile + ".pow2minus1";
_pow2MinusOneChecked = File.Exists(_pow2MinusOneStateFilePath);
_kRepository = EnvironmentConfiguration.ByDivisorKStateRepository ?? throw new InvalidOperationException("By-divisor k-state repository not initialized.");
_pow2MinusOneRepository = EnvironmentConfiguration.ByDivisorPow2Minus1Repository;
_cheapRepository = EnvironmentConfiguration.ByDivisorCheapKStateRepository;
_cheapLastSavedK = BigInteger.Zero;
_cheapResumeK = MinK;
string stateName = Path.GetFileNameWithoutExtension(stateFile);
if (ulong.TryParse(stateName, NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime))
{
_currentPrime = parsedPrime;
if (_kRepository.TryGet(parsedPrime, out BigInteger storedK) && storedK > BigInteger.Zero)
{
_lastSavedK = storedK;
}
_pow2MinusOneChecked = _pow2MinusOneRepository?.IsChecked(parsedPrime) ?? false;
if (_cheapRepository is not null && _cheapRepository.TryGet(parsedPrime, out BigInteger cheapStored) && cheapStored > BigInteger.Zero)
{
_cheapLastSavedK = cheapStored;
_cheapResumeK = cheapStored + BigInteger.One;
}
}
#if DivisorSet_TopDown
_topDownCursor = _lastSavedK;
#endif
#if DivisorSet_Pow2Groups
_specialStateFilePath = stateFile + ".special";
_groupsStateFilePath = stateFile + ".groups";
_specialRepository = EnvironmentConfiguration.ByDivisorSpecialStateRepository;
_groupsRepository = EnvironmentConfiguration.ByDivisorGroupsStateRepository;
_specialStateCounter = 0;
_groupsStateCounter = 0;
if (!MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(_specialStateFilePath, out _specialLastSavedK))
_specialLastSavedK = lastSavedK;
_groupsLastSavedK = lastSavedK;
if (_currentPrime != 0UL)
{
_specialLastSavedK = lastSavedK;
}
if (_specialRepository is not null && _specialRepository.TryGet(_currentPrime, out BigInteger storedSpecial) && storedSpecial > BigInteger.Zero)
{
_specialLastSavedK = storedSpecial;
}
if (!MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(_groupsStateFilePath, out _groupsLastSavedK))
{
_groupsLastSavedK = lastSavedK;
if (_groupsRepository is not null && _groupsRepository.TryGet(_currentPrime, out BigInteger storedGroups) && storedGroups > BigInteger.Zero)
{
_groupsLastSavedK = storedGroups;
}
}
_specialResumeK = _specialLastSavedK + BigInteger.One;
@@ -244,6 +334,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{
_currentPrime = prime;
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
if (TryFindPowerOfTwoMinusOneDivisor(prime, allowedMax, out divisor))
@@ -341,7 +432,16 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={(prime & 1023UL)} using pow2-groups plan; model skipped.");
return compositePow2;
#else
#if DivisorSet_Predictive
#if DivisorSet_TopDown
bool compositeTopDown = CheckDivisorsTopDown(
prime,
allowedMax,
normalizedMinK,
out processedAll,
out foundDivisor);
return compositeTopDown;
#endif
#if DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive
ByDivisorClassModel? classifier = EnvironmentConfiguration.ByDivisorClassModel;
if (classifier is not null)
{
@@ -591,7 +691,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
}
#endif
#if !DivisorSet_Pow2Groups && DivisorSet_Predictive
#if !DivisorSet_Pow2Groups && (DivisorSet_Predictive || DivisorSet_Percentile || DivisorSet_Additive)
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool TryExecutePlannedScan(
ByDivisorClassModel model,
@@ -675,24 +775,28 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning;
ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax, tuning);
int suffix = (int)(prime & 1023UL);
_ = suffix;
// ByDivisorClassEntry entry = model.Entries[suffix];
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2} maxK={maxKAllowed}");
BigInteger cheapLimit = BigInteger.Min((BigInteger)plan.CheapLimit, maxKAllowed);
if (cheapLimit >= minK)
BigInteger cheapChunk = BigInteger.Min((BigInteger)plan.CheapLimit, maxKAllowed);
BigInteger cheapStart = BigInteger.Zero;
BigInteger cheapEnd = BigInteger.Zero;
if (TryBuildCheapRange(minK, cheapChunk, maxKAllowed, out cheapStart, out cheapEnd))
{
BigInteger currentK = minK;
BigInteger currentK = cheapStart;
bool compositeCheap = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
minK,
cheapLimit,
cheapStart,
cheapEnd,
out bool processedRange,
out BigInteger foundRange,
ref currentK);
if (compositeCheap)
{
RecordCheapState(currentK);
foundDivisor = foundRange;
return true;
}
@@ -702,6 +806,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
processedAll = false;
return false;
}
RecordCheapState(cheapEnd);
}
BigInteger startPlan = plan.Start <= 0d ? minK : new BigInteger(Math.Ceiling(plan.Start));
@@ -716,12 +822,13 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
target = startPlan + BigInteger.One;
}
BigInteger current = cheapLimit + BigInteger.One;
BigInteger current = (cheapEnd > BigInteger.Zero ? cheapEnd + BigInteger.One : minK);
if (current < startPlan)
{
current = startPlan;
}
#if DivisorSet_Predictive
bool loggedProgressive = false;
while (current <= maxKAllowed)
{
@@ -768,6 +875,163 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
current = advanced;
}
#elif DivisorSet_Percentile
BigInteger k10 = plan.K10 > 0d ? new BigInteger(Math.Ceiling(plan.K10)) : current;
BigInteger k25 = plan.K25 > 0d ? new BigInteger(Math.Ceiling(plan.K25)) : k10 + BigInteger.One;
BigInteger k50 = plan.K50 > 0d ? new BigInteger(Math.Ceiling(plan.K50)) : k25 + BigInteger.One;
BigInteger k75 = plan.K75 > 0d ? new BigInteger(Math.Ceiling(plan.K75)) : k50 + BigInteger.One;
BigInteger[] stops = new BigInteger[]
{
k25 - BigInteger.One,
k50 - BigInteger.One,
k75 - BigInteger.One,
};
BigInteger startRangeK = current > k10 ? current : k10;
for (int i = 0; i < stops.Length; i++)
{
BigInteger endRange = stops[i];
if (endRange < startRangeK)
{
continue;
}
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
startRangeK,
endRange,
out bool processedRange,
out BigInteger foundRange,
ref startRangeK);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
startRangeK = endRange + BigInteger.One;
if (startRangeK > maxKAllowed)
{
return false;
}
}
// After percentile sweeps, escalate geometrically.
BigInteger startGeo = startRangeK;
if (startGeo < k75)
{
startGeo = k75;
}
bool loggedPercentile = false;
while (startGeo <= maxKAllowed)
{
if (!loggedPercentile)
{
loggedPercentile = true;
}
BigInteger scanStart = startGeo;
BigInteger scanEnd = scanStart;
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
scanStart,
scanEnd,
out bool processedRange,
out BigInteger foundRange,
ref scanStart);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
BigInteger advanced = new BigInteger(Math.Ceiling((double)startGeo * plan.Rho2));
if (advanced <= startGeo)
{
advanced = startGeo + BigInteger.One;
}
if (advanced > maxKAllowed)
{
break;
}
startGeo = advanced;
}
#else
BigInteger delta = plan.DeltaK > 0d ? new BigInteger(Math.Ceiling(plan.DeltaK)) : BigInteger.One;
if (delta < BigInteger.One)
{
delta = BigInteger.One;
}
if (delta > maxKAllowed)
{
delta = maxKAllowed;
}
bool loggedAdditive = false;
while (current <= maxKAllowed)
{
if (!loggedAdditive)
{
loggedAdditive = true;
}
BigInteger startRange = current;
bool composite = CheckDivisorsBigPlanRange(
prime,
step,
allowedMax,
startRange,
startRange,
out bool processedRange,
out BigInteger foundRange,
ref startRange);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
BigInteger advanced = current + delta;
if (advanced <= current)
{
advanced = current + BigInteger.One;
}
if (advanced > maxKAllowed)
{
break;
}
current = advanced;
}
#endif
return false;
}
@@ -885,6 +1149,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
ByDivisorScanTuning tuning = EnvironmentConfiguration.ByDivisorScanTuning;
ByDivisorScanPlan plan = model.BuildPlan(prime, allowedMax == ulong.MaxValue ? BigInteger.Zero : new BigInteger(allowedMax), tuning);
int suffix = (int)(prime & 1023UL);
_ = suffix;
// ByDivisorClassEntry entry = model.Entries[suffix];
// Console.WriteLine($"[by-divisor-plan] p={prime} suffix={suffix} k50={entry.K50:F2} k75={entry.K75:F2} gap={entry.GapProbability:F3} cheap<= {plan.CheapLimit:F0} start={plan.Start:F0} target={plan.Target:F0} rho1={plan.Rho1:F2} rho2={plan.Rho2:F2}");
@@ -896,33 +1161,43 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return false;
}
ulong cheapLimit = (ulong)Math.Min(plan.CheapLimit, maxKAllowed);
if (cheapLimit >= minK)
BigInteger cheapChunk = new BigInteger(Math.Min(plan.CheapLimit, (double)maxKAllowed));
BigInteger cheapStart = BigInteger.Zero;
BigInteger cheapEnd = BigInteger.Zero;
if (TryBuildCheapRange(new BigInteger(minK), cheapChunk, new BigInteger(maxKAllowed), out cheapStart, out cheapEnd))
{
ulong currentK = minK;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
minK,
cheapLimit,
ref currentK,
out bool processedRange,
out ulong foundRange);
if (composite)
if (cheapStart <= ulong.MaxValue && cheapEnd <= ulong.MaxValue)
{
foundDivisor = foundRange;
return true;
}
ulong cheapStart64 = (ulong)cheapStart;
ulong cheapEnd64 = (ulong)cheapEnd;
ulong currentK = cheapStart64;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
cheapStart64,
cheapEnd64,
ref currentK,
out bool processedRange,
out ulong foundRange);
if (composite)
{
RecordCheapState(currentK);
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
if (!processedRange)
{
processedAll = false;
return false;
}
RecordCheapState(cheapEnd);
}
}
double nextK = Math.Max(plan.Start, cheapLimit + 1d);
double nextK = Math.Max(plan.Start, (cheapEnd > BigInteger.Zero ? (double)(cheapEnd + BigInteger.One) : minK));
if (nextK < minK)
{
nextK = minK;
@@ -935,6 +1210,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
}
ulong current = (ulong)Math.Ceiling(nextK);
#if DivisorSet_Predictive
bool loggedProgressive = false;
while (current <= maxKAllowed)
{
@@ -980,10 +1256,149 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
current = (ulong)advanced;
}
#elif DivisorSet_Percentile
ulong k10 = plan.K10 > 0d ? (ulong)Math.Ceiling(plan.K10) : current;
ulong k25 = plan.K25 > 0d ? (ulong)Math.Ceiling(plan.K25) : k10 + 1UL;
ulong k50 = plan.K50 > 0d ? (ulong)Math.Ceiling(plan.K50) : k25 + 1UL;
ulong k75 = plan.K75 > 0d ? (ulong)Math.Ceiling(plan.K75) : k50 + 1UL;
ReadOnlySpan<ulong> stops = stackalloc ulong[3]
{
k25 > 0UL ? k25 - 1UL : 0UL,
k50 > 0UL ? k50 - 1UL : 0UL,
k75 > 0UL ? k75 - 1UL : 0UL,
};
ulong rangeStart = current > k10 ? current : k10;
for (int i = 0; i < stops.Length; i++)
{
ulong stop = stops[i];
if (stop < rangeStart)
{
continue;
}
ulong currentK = rangeStart;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
rangeStart,
stop,
ref currentK,
out bool processedRange,
out ulong foundRange);
if (composite)
{
foundDivisor = foundRange;
return true;
}
if (!processedRange)
{
processedAll = false;
return false;
}
rangeStart = stop + 1UL;
if (rangeStart > maxKAllowed)
{
return false;
}
}
ulong geoStart = rangeStart < k75 ? k75 : rangeStart;
while (geoStart <= maxKAllowed)
{
ulong currentK = geoStart;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
currentK,
currentK,
ref currentK,
out bool processedSingle,
out ulong foundSingle);
if (composite)
{
foundDivisor = foundSingle;
return true;
}
if (!processedSingle)
{
processedAll = false;
return false;
}
double advanced = Math.Ceiling(geoStart * plan.Rho2);
if (advanced <= geoStart)
{
advanced = geoStart + 1d;
}
if (advanced > maxKAllowed)
{
break;
}
geoStart = (ulong)advanced;
}
#else
ulong delta = plan.DeltaK > 0d ? (ulong)Math.Ceiling(plan.DeltaK) : 1UL;
if (delta == 0UL)
{
delta = 1UL;
}
if (delta > maxKAllowed)
{
delta = maxKAllowed;
}
while (current <= maxKAllowed)
{
ulong currentK = current;
bool composite = CheckDivisors64Range(
prime,
step64,
effectiveAllowed,
currentK,
currentK,
ref currentK,
out bool processedSingle,
out ulong foundSingle);
if (composite)
{
foundDivisor = foundSingle;
return true;
}
if (!processedSingle)
{
processedAll = false;
return false;
}
ulong advanced = current + delta;
if (advanced <= current || advanced == 0UL)
{
advanced = current + 1UL;
}
if (advanced > maxKAllowed)
{
break;
}
current = advanced;
}
#endif
return false;
}
#endif
#endif
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors64Bit(
@@ -1809,13 +2224,17 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return;
}
string? directory = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(directory))
if (!string.IsNullOrEmpty(targetPath))
{
Directory.CreateDirectory(directory);
string? directory = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
File.AppendAllText(targetPath, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
}
File.AppendAllText(targetPath, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
counter = 0;
last = k;
}
@@ -2103,6 +2522,162 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out foundDivisor);
}
#if DivisorSet_TopDown
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisorsTopDown(
ulong prime,
BigInteger allowedMax,
BigInteger minK,
out bool processedAll,
out BigInteger foundDivisor)
{
foundDivisor = BigInteger.Zero;
processedAll = false;
BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One;
ulong step = prime << 1;
ulong allowedMax64 = allowedMax.IsZero ? ulong.MaxValue : (allowedMax > ulong.MaxValue ? ulong.MaxValue : (ulong)allowedMax);
if (allowedMax64 <= 1UL)
{
processedAll = true;
return false;
}
ulong maxKAllowed = (allowedMax64 - 1UL) / step;
{
BigInteger maxKBig = maxKAllowed;
if (TryClampMaxKForMersenneTail(prime, (BigInteger)step, ref maxKBig))
{
maxKAllowed = maxKBig > ulong.MaxValue ? ulong.MaxValue : (ulong)maxKBig;
}
}
if (maxKAllowed < 1UL)
{
processedAll = true;
return false;
}
if (normalizedMinK > maxKAllowed)
{
processedAll = true;
return false;
}
ulong minK64 = normalizedMinK > ulong.MaxValue ? ulong.MaxValue : (ulong)normalizedMinK;
if (minK64 < 1UL)
{
minK64 = 1UL;
}
ulong cursor = _topDownCursor > ulong.MaxValue ? ulong.MaxValue : (ulong)_topDownCursor;
ulong currentK = maxKAllowed;
if (cursor > 0 && cursor <= maxKAllowed)
{
currentK = maxKAllowed - cursor;
}
if (currentK < minK64)
{
processedAll = true;
return false;
}
UInt128 step128 = step;
UInt128 divisor = (step128 * currentK) + UInt128.One;
var residueStepper = new MersenneDivisorResidueStepperDescending(prime, new GpuUInt128(step), new GpuUInt128(divisor));
while (currentK >= minK64)
{
if (divisor > allowedMax64)
{
processedAll = true;
return false;
}
if (residueStepper.IsAdmissible())
{
ulong divisor64 = (ulong)divisor;
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
#if DEVICE_GPU
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
divisor64,
Accelerator,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed);
#elif DEVICE_HYBRID
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
divisor64,
Accelerator,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed);
#else
bool computed = MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
divisor64,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed);
#endif
ulong divisorCycle;
if (!computed || computedCycle == 0UL)
{
#if DEVICE_GPU
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
divisor64,
Accelerator,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#elif DEVICE_HYBRID
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
divisor64,
Accelerator,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#else
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
divisor64,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
#endif
}
else
{
divisorCycle = computedCycle;
}
RecordState(cursor);
if (divisorCycle == prime && !IsMersenneValue(prime, divisor64))
{
foundDivisor = divisor64;
return true;
}
}
cursor++;
if (cursor > maxKAllowed)
{
break;
}
if (currentK == minK64)
{
break;
}
currentK--;
divisor -= step128;
residueStepper.Retreat();
}
processedAll = true;
return false;
}
#endif
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors64(
ulong prime,
@@ -2234,28 +2809,76 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private void RecordState(BigInteger k)
{
#if DivisorSet_TopDown
if (_currentPrime != 0UL)
{
if (k > _lastSavedK)
{
int next = _stateCounter + 1;
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
{
_kRepository.Upsert(_currentPrime, k);
_stateCounter = 0;
_lastSavedK = k;
_topDownCursor = k;
}
else
{
_stateCounter = next;
}
_topDownCursor = k;
}
}
return;
#endif
#if DivisorSet_Pow2Groups
if (_currentPow2Phase == Pow2Phase.Special)
{
RecordStateInternal(k, _specialStateFilePath, ref _specialStateCounter, ref _specialLastSavedK, _currentPow2Phase);
RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK);
return;
}
if (_currentPow2Phase == Pow2Phase.Groups)
{
RecordStateInternal(k, _groupsStateFilePath, ref _groupsStateCounter, ref _groupsLastSavedK, _currentPow2Phase);
RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK);
return;
}
#endif
RecordStateInternal(k, StateFilePath, ref _stateCounter, ref _lastSavedK, default);
if (_currentPrime != 0UL)
{
if (k > _lastSavedK)
{
_kRepository.Upsert(_currentPrime, k);
_lastSavedK = k;
}
_stateCounter = 0;
}
}
#if DivisorSet_Pow2Groups
private void RecordStateInternal(BigInteger k, string path, ref int counter, ref BigInteger lastSavedK, Pow2Phase phase)
#else
private void RecordStateInternal(BigInteger k, string path, ref int counter, ref BigInteger lastSavedK, object _)
#endif
private void RecordState(BigInteger k, Pow2Phase phase)
{
switch (phase)
{
case Pow2Phase.Special:
RecordStatePow2(k, _specialRepository, ref _specialStateCounter, ref _specialLastSavedK);
break;
case Pow2Phase.Groups:
RecordStatePow2(k, _groupsRepository, ref _groupsStateCounter, ref _groupsLastSavedK);
break;
default:
RecordState(k);
break;
}
}
private void RecordStatePow2(BigInteger k, KStateRepository? repo, ref int counter, ref BigInteger lastSavedK)
{
if (_currentPrime == 0UL || repo is null)
{
return;
}
if (k <= lastSavedK)
{
return;
@@ -2264,13 +2887,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
int next = counter + 1;
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
{
string? directory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
repo.Upsert(_currentPrime, k);
counter = 0;
lastSavedK = k;
}
@@ -2280,24 +2897,6 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
}
}
#if DivisorSet_Pow2Groups
// Overload used from CheckDivisors64Range through RecordState with current phase routed automatically.
private void RecordState(BigInteger k, Pow2Phase phase)
{
switch (phase)
{
case Pow2Phase.Special:
RecordStateInternal(k, _specialStateFilePath, ref _specialStateCounter, ref _specialLastSavedK, phase);
break;
case Pow2Phase.Groups:
RecordStateInternal(k, _groupsStateFilePath, ref _groupsStateCounter, ref _groupsLastSavedK, phase);
break;
default:
RecordStateInternal(k, StateFilePath, ref _stateCounter, ref _lastSavedK, phase);
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void RecordPow2Hit(Pow2Phase phase)
{
@@ -2714,22 +3313,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
private void MarkPow2MinusOneChecked()
{
_pow2MinusOneChecked = true;
if (!string.IsNullOrEmpty(_pow2MinusOneStateFilePath))
if (_currentPrime != 0UL)
{
try
{
string? directory = Path.GetDirectoryName(_pow2MinusOneStateFilePath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(_pow2MinusOneStateFilePath, "checked");
}
catch
{
// Best-effort persistence; ignore IO errors.
}
_pow2MinusOneRepository?.MarkChecked(_currentPrime);
}
}
@@ -2797,6 +3383,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
}
_checkedPow2MinusOne[prime] = 1;
_currentPrime = prime;
MarkPow2MinusOneChecked();
return false;
}
@@ -15,10 +15,18 @@ using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTeste
using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForCpuOrder;
using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForHybridOrder;
using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForGpuOrder;
#elif DivisorSet_Percentile
using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForCpuOrder;
using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForHybridOrder;
using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForGpuOrder;
#elif DivisorSet_Additive
using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder;
using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder;
using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder;
#else
using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder;
using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForHybridOrder;
using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForGpuOrder;
using TesterCpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder;
using TesterHybrid = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForHybridOrder;
using TesterGpu = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForGpuOrder;
#endif
namespace PerfectNumbers.Core.Cpu;
@@ -71,10 +79,14 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate
string extension = ".bin";
#if DivisorSet_Predictive
extension = ".predictive";
#elif DivisorSet_Percentile
extension = ".percentile";
#elif DivisorSet_Additive
extension = ".additive";
#elif DivisorSet_TopDown
extension = ".topdown";
#endif
string stateFile = Path.Combine(
PerfectNumberConstants.ByDivisorStateDirectory,
prime.ToString(CultureInfo.InvariantCulture) + extension);
string stateFile = prime.ToString(CultureInfo.InvariantCulture) + extension;
ConfigureForPrime(stateFile);
@@ -82,11 +94,6 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate
if (!isPrime)
{
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
{
File.Delete(stateFile);
}
_markComposite();
_printResult(prime, true, true, false, divisor);
Console.WriteLine($"Finished processing {prime}");
@@ -106,25 +113,39 @@ internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithTemplate
private void ConfigureForPrime(string stateFile)
{
#if DivisorSet_TopDown
BigInteger resumeK = EnvironmentConfiguration.MinK;
if (File.Exists(stateFile))
BigInteger lastK = BigInteger.Zero;
if (EnvironmentConfiguration.ByDivisorKStateRepository is not null)
{
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) &&
EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) &&
storedK > BigInteger.Zero)
{
resumeK = lastK + BigInteger.One;
_tester.ResumeFromState(stateFile, lastK, resumeK);
lastK = storedK;
}
else
{
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
}
}
else
{
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
}
_tester.ResumeFromState(stateFile, lastK, resumeK);
_tester.ResetStateTracking();
return;
#else
BigInteger resumeK = EnvironmentConfiguration.MinK;
BigInteger lastK = BigInteger.Zero;
if (EnvironmentConfiguration.ByDivisorKStateRepository is not null)
{
if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) &&
EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) &&
storedK > BigInteger.Zero)
{
lastK = storedK;
resumeK = storedK + BigInteger.One;
}
}
_tester.ResumeFromState(stateFile, lastK, resumeK);
_tester.ResetStateTracking();
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -22,7 +22,13 @@ public static class EnvironmentConfiguration
public static int ByDivisorSpecialRange;
public static ByDivisor.ByDivisorClassModel? ByDivisorClassModel;
public static ByDivisor.ByDivisorScanTuning ByDivisorScanTuning = ByDivisor.ByDivisorScanTuning.Default;
public static double ByDivisorCheapKLimit = 100_000d;
public static double ByDivisorCheapKLimit = 10_000d;
public static Persistence.ByDivisorClassModelRepository? ByDivisorClassModelRepository;
public static Persistence.KStateRepository? ByDivisorKStateRepository;
public static Persistence.KStateRepository? ByDivisorCheapKStateRepository;
public static Persistence.KStateRepository? ByDivisorSpecialStateRepository;
public static Persistence.KStateRepository? ByDivisorGroupsStateRepository;
public static Persistence.Pow2Minus1StateRepository? ByDivisorPow2Minus1Repository;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Initialize()
@@ -1,4 +1,5 @@
using System.Globalization;
using System.IO;
using System.Numerics;
namespace PerfectNumbers.Core.Gpu;
@@ -34,9 +35,7 @@ internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession
try
{
Console.WriteLine($"Processing {prime}");
string stateFile = Path.Combine(
PerfectNumberConstants.ByDivisorStateDirectory,
prime.ToString(CultureInfo.InvariantCulture) + ".bin");
string stateFile = prime.ToString(CultureInfo.InvariantCulture) + ".bin";
ConfigureForPrime(stateFile);
@@ -44,11 +43,6 @@ internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession
if (!isPrime)
{
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
{
File.Delete(stateFile);
}
_markComposite();
_printResult(prime, true, true, false, divisor);
Console.WriteLine($"Finished processing {prime}");
@@ -68,25 +62,39 @@ internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession
private void ConfigureForPrime(string stateFile)
{
#if DivisorSet_TopDown
BigInteger resumeK = EnvironmentConfiguration.MinK;
if (File.Exists(stateFile))
BigInteger lastK = BigInteger.Zero;
if (EnvironmentConfiguration.ByDivisorKStateRepository is not null)
{
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) &&
EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) &&
storedK > BigInteger.Zero)
{
resumeK = lastK + BigInteger.One;
_tester.ResumeFromState(stateFile, lastK, resumeK);
lastK = storedK;
}
else
{
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
}
}
else
{
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
}
_tester.ResumeFromState(stateFile, lastK, resumeK);
_tester.ResetStateTracking();
return;
#else
BigInteger resumeK = EnvironmentConfiguration.MinK;
BigInteger lastK = BigInteger.Zero;
if (EnvironmentConfiguration.ByDivisorKStateRepository is not null)
{
if (ulong.TryParse(Path.GetFileNameWithoutExtension(stateFile), NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsedPrime) &&
EnvironmentConfiguration.ByDivisorKStateRepository.TryGet(parsedPrime, out BigInteger storedK) &&
storedK > BigInteger.Zero)
{
lastK = storedK;
resumeK = storedK + BigInteger.One;
}
}
_tester.ResumeFromState(stateFile, lastK, resumeK);
_tester.ResetStateTracking();
#endif
}
public void Dispose()
@@ -87,3 +87,88 @@ internal struct MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, i
return (byte)sum;
}
}
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]
internal struct MersenneDivisorResidueStepperDescending(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
{
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6);
private readonly byte step8 = (byte)(step.Low % 8UL);
private readonly byte step5 = ComputeModulo(step, 5, multiplier: 1);
private readonly byte step3 = ComputeModulo(step, 3, multiplier: 1);
private readonly byte step7 = ComputeModulo(step, 7, multiplier: 2);
private readonly byte step11 = ComputeModulo(step, 11, multiplier: 5);
private readonly byte step13 = ComputeModulo(step, 13, multiplier: 3);
private readonly byte step17 = ComputeModulo(step, 17, multiplier: 1);
private readonly byte step19 = ComputeModulo(step, 19, multiplier: 17);
private byte Remainder10 = ComputeModulo(firstDivisor, 10, multiplier: 6);
private byte Remainder8 = (byte)(firstDivisor.Low % 8UL);
private byte Remainder5 = ComputeModulo(firstDivisor, 5, multiplier: 1);
private byte Remainder3 = ComputeModulo(firstDivisor, 3, multiplier: 1);
private byte Remainder7 = ComputeModulo(firstDivisor, 7, multiplier: 2);
private byte Remainder11 = ComputeModulo(firstDivisor, 11, multiplier: 5);
private byte Remainder13 = ComputeModulo(firstDivisor, 13, multiplier: 3);
private byte Remainder17 = ComputeModulo(firstDivisor, 17, multiplier: 1);
private byte Remainder19 = ComputeModulo(firstDivisor, 19, multiplier: 17);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsAdmissible()
{
if (((decimalMask >> Remainder10) & 1) == 0)
{
return false;
}
if (Remainder8 != 1 && Remainder8 != 7)
{
return false;
}
if (Remainder3 == 0 || Remainder5 == 0 || Remainder7 == 0 || Remainder11 == 0 || Remainder13 == 0 || Remainder17 == 0 || Remainder19 == 0)
{
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Retreat()
{
Remainder10 = SubMod(Remainder10, step10, 10);
Remainder8 = SubMod(Remainder8, step8, 8);
Remainder5 = SubMod(Remainder5, step5, 5);
Remainder3 = SubMod(Remainder3, step3, 3);
Remainder7 = SubMod(Remainder7, step7, 7);
Remainder11 = SubMod(Remainder11, step11, 11);
Remainder13 = SubMod(Remainder13, step13, 13);
Remainder17 = SubMod(Remainder17, step17, 17);
Remainder19 = SubMod(Remainder19, step19, 19);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ComputeModulo(in GpuUInt128 value, byte modulus, byte multiplier)
{
ulong high = value.High % modulus;
ulong low = value.Low % modulus;
int result = (int)((high * multiplier) + low);
result %= modulus;
return (byte)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte SubMod(byte value, byte delta, byte modulus)
{
int diff = value - delta;
if (diff < 0)
{
diff += modulus;
}
return (byte)diff;
}
}
@@ -6,12 +6,18 @@ using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using PerfectNumbers.Core.Cpu;
using System.Runtime.CompilerServices;
using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForHybridOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForGpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForCpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForHybridOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForSequentialDivisorSetForGpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithPredictiveCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForCpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithPredictiveHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForHybridOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithPredictiveGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPredictiveDivisorSetForGpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForCpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForHybridOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForPercentileDivisorSetForGpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForCpuOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForHybridOrder;
using MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForAdditiveDivisorSetForGpuOrder;
namespace PerfectNumbers.Core;
@@ -27,7 +33,7 @@ public static class MersenneNumberDivisorByDivisorTester
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
@@ -79,6 +85,68 @@ public static class MersenneNumberDivisorByDivisorTester
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunPredictiveCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
ref MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
previousResults,
startPrime,
markComposite,
clearComposite,
printResult,
threadCount,
primesPerTask,
static (
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder prototype,
int workerCount,
int partitionSize,
Action mc,
Action cc,
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunPercentileCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
ref MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
previousResults,
startPrime,
markComposite,
clearComposite,
printResult,
threadCount,
primesPerTask,
static (
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder prototype,
int workerCount,
int partitionSize,
Action mc,
Action cc,
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunAdditiveCpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
@@ -141,6 +209,68 @@ public static class MersenneNumberDivisorByDivisorTester
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunPredictiveHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
ref MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
previousResults,
startPrime,
markComposite,
clearComposite,
printResult,
threadCount,
primesPerTask,
static (
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder prototype,
int workerCount,
int partitionSize,
Action mc,
Action cc,
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunPercentileHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
ref MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
previousResults,
startPrime,
markComposite,
clearComposite,
printResult,
threadCount,
primesPerTask,
static (
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder prototype,
int workerCount,
int partitionSize,
Action mc,
Action cc,
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunAdditiveHybridOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
@@ -203,6 +333,68 @@ public static class MersenneNumberDivisorByDivisorTester
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunPredictiveGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
ref MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
previousResults,
startPrime,
markComposite,
clearComposite,
printResult,
threadCount,
primesPerTask,
static (
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder prototype,
int workerCount,
int partitionSize,
Action mc,
Action cc,
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunPercentileGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
ref MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult,
int threadCount,
int primesPerTask)
=> RunStructTester(
candidates,
ref tester,
previousResults,
startPrime,
markComposite,
clearComposite,
printResult,
threadCount,
primesPerTask,
static (
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder prototype,
int workerCount,
int partitionSize,
Action mc,
Action cc,
Action<ulong, bool, bool, bool, BigInteger> pr) =>
RunAdditiveGpuOrderFiltered(filteredPrimes, prototype, workerCount, partitionSize, mc, cc, pr));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Run(
List<ulong> candidates,
@@ -1093,7 +1285,7 @@ public static class MersenneNumberDivisorByDivisorTester
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
@@ -1115,7 +1307,7 @@ public static class MersenneNumberDivisorByDivisorTester
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
@@ -1230,6 +1422,162 @@ public static class MersenneNumberDivisorByDivisorTester
startGate.Dispose();
}
private static void RunPercentileCpuOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder prototype,
int workerCount,
int partitionSize,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
MersenneNumberDivisorByDivisorCpuTesterWithPercentileCpuOrder prototypeCopy = prototype;
int totalCount = filteredPrimes.Count;
if (partitionSize > totalCount)
{
partitionSize = totalCount;
}
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
}
session.Dispose();
return;
}
TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
int nextIndex = 0;
Task[] tasks = new Task[workerCount];
var startGate = new ManualResetEventSlim(initialState: false);
for (int taskIndex = 0; taskIndex < workerCount; taskIndex++)
{
int workerIndex = taskIndex;
tasks[taskIndex] = Task.Factory.StartNew(
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
while (true)
{
int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize;
if (start >= totalCount)
{
break;
}
int end = start + partitionSize;
if (end > totalCount)
{
end = totalCount;
}
for (int index = start; index < end; index++)
{
session.ProcessPrime(filteredPrimes[index]);
}
}
session.Dispose();
Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
}
private static void RunAdditiveCpuOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder prototype,
int workerCount,
int partitionSize,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
MersenneNumberDivisorByDivisorCpuTesterWithAdditiveCpuOrder prototypeCopy = prototype;
int totalCount = filteredPrimes.Count;
if (partitionSize > totalCount)
{
partitionSize = totalCount;
}
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
}
session.Dispose();
return;
}
TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
int nextIndex = 0;
Task[] tasks = new Task[workerCount];
var startGate = new ManualResetEventSlim(initialState: false);
for (int taskIndex = 0; taskIndex < workerCount; taskIndex++)
{
int workerIndex = taskIndex;
tasks[taskIndex] = Task.Factory.StartNew(
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForCpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
while (true)
{
int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize;
if (start >= totalCount)
{
break;
}
int end = start + partitionSize;
if (end > totalCount)
{
end = totalCount;
}
for (int index = start; index < end; index++)
{
session.ProcessPrime(filteredPrimes[index]);
}
}
session.Dispose();
Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
}
private static void RunHybridOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder prototype,
@@ -1256,7 +1604,7 @@ public static class MersenneNumberDivisorByDivisorTester
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
GpuPrimeWorkLimiter.Acquire();
foreach (ulong prime in filteredPrimes)
@@ -1282,7 +1630,7 @@ public static class MersenneNumberDivisorByDivisorTester
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
@@ -1399,6 +1747,188 @@ public static class MersenneNumberDivisorByDivisorTester
startGate.Dispose();
}
private static void RunPercentileHybridOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder prototype,
int workerCount,
int partitionSize,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
MersenneNumberDivisorByDivisorCpuTesterWithPercentileHybridOrder prototypeCopy = prototype;
int totalCount = filteredPrimes.Count;
if (partitionSize > totalCount)
{
partitionSize = totalCount;
}
int effectiveWorkerCount = workerCount;
int gpuLimit = GpuPrimeWorkLimiter.CurrentLimit;
if (effectiveWorkerCount > gpuLimit)
{
effectiveWorkerCount = gpuLimit;
}
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
GpuPrimeWorkLimiter.Acquire();
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
}
GpuPrimeWorkLimiter.Release();
session.Dispose();
return;
}
TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
int nextIndex = 0;
Task[] tasks = new Task[effectiveWorkerCount];
var startGate = new ManualResetEventSlim(initialState: false);
for (int taskIndex = 0; taskIndex < effectiveWorkerCount; taskIndex++)
{
int workerIndex = taskIndex;
tasks[taskIndex] = Task.Factory.StartNew(
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
GpuPrimeWorkLimiter.Acquire();
while (true)
{
int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize;
if (start >= totalCount)
{
break;
}
int end = start + partitionSize;
if (end > totalCount)
{
end = totalCount;
}
for (int index = start; index < end; index++)
{
session.ProcessPrime(filteredPrimes[index]);
}
}
GpuPrimeWorkLimiter.Release();
session.Dispose();
Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
}
private static void RunAdditiveHybridOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder prototype,
int workerCount,
int partitionSize,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
MersenneNumberDivisorByDivisorCpuTesterWithAdditiveHybridOrder prototypeCopy = prototype;
int totalCount = filteredPrimes.Count;
if (partitionSize > totalCount)
{
partitionSize = totalCount;
}
int effectiveWorkerCount = workerCount;
int gpuLimit = GpuPrimeWorkLimiter.CurrentLimit;
if (effectiveWorkerCount > gpuLimit)
{
effectiveWorkerCount = gpuLimit;
}
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
GpuPrimeWorkLimiter.Acquire();
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
}
GpuPrimeWorkLimiter.Release();
session.Dispose();
return;
}
TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
int nextIndex = 0;
Task[] tasks = new Task[effectiveWorkerCount];
var startGate = new ManualResetEventSlim(initialState: false);
for (int taskIndex = 0; taskIndex < effectiveWorkerCount; taskIndex++)
{
int workerIndex = taskIndex;
tasks[taskIndex] = Task.Factory.StartNew(
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForHybridOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
GpuPrimeWorkLimiter.Acquire();
while (true)
{
int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize;
if (start >= totalCount)
{
break;
}
int end = start + partitionSize;
if (end > totalCount)
{
end = totalCount;
}
for (int index = start; index < end; index++)
{
session.ProcessPrime(filteredPrimes[index]);
}
}
GpuPrimeWorkLimiter.Release();
session.Dispose();
Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
}
private static void RunGpuOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder prototype,
@@ -1425,7 +1955,7 @@ public static class MersenneNumberDivisorByDivisorTester
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
GpuPrimeWorkLimiter.Acquire();
foreach (ulong prime in filteredPrimes)
@@ -1451,7 +1981,7 @@ public static class MersenneNumberDivisorByDivisorTester
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForOneByOneDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForSequentialDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
@@ -1568,6 +2098,162 @@ public static class MersenneNumberDivisorByDivisorTester
startGate.Dispose();
}
private static void RunPercentileGpuOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder prototype,
int workerCount,
int partitionSize,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
MersenneNumberDivisorByDivisorCpuTesterWithPercentileGpuOrder prototypeCopy = prototype;
int totalCount = filteredPrimes.Count;
if (partitionSize > totalCount)
{
partitionSize = totalCount;
}
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
}
session.Dispose();
return;
}
TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
int nextIndex = 0;
Task[] tasks = new Task[workerCount];
var startGate = new ManualResetEventSlim(initialState: false);
for (int taskIndex = 0; taskIndex < workerCount; taskIndex++)
{
int workerIndex = taskIndex;
tasks[taskIndex] = Task.Factory.StartNew(
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForPercentileDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
while (true)
{
int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize;
if (start >= totalCount)
{
break;
}
int end = start + partitionSize;
if (end > totalCount)
{
end = totalCount;
}
for (int index = start; index < end; index++)
{
session.ProcessPrime(filteredPrimes[index]);
}
}
session.Dispose();
Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
}
private static void RunAdditiveGpuOrderFiltered(
List<ulong> filteredPrimes,
in MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder prototype,
int workerCount,
int partitionSize,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
MersenneNumberDivisorByDivisorCpuTesterWithAdditiveGpuOrder prototypeCopy = prototype;
int totalCount = filteredPrimes.Count;
if (partitionSize > totalCount)
{
partitionSize = totalCount;
}
if (workerCount == 1)
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
foreach (ulong prime in filteredPrimes)
{
session.ProcessPrime(prime);
}
session.Dispose();
return;
}
TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
int nextIndex = 0;
Task[] tasks = new Task[workerCount];
var startGate = new ManualResetEventSlim(initialState: false);
for (int taskIndex = 0; taskIndex < workerCount; taskIndex++)
{
int workerIndex = taskIndex;
tasks[taskIndex] = Task.Factory.StartNew(
() =>
{
DeterministicRandomCpu.Initialize();
var session = new MersenneNumberDivisorByDivisorPrimeScanSessionWithForAdditiveDivisorSetForGpuOrder(prototypeCopy, markComposite, clearComposite, printResult);
startGate.Wait();
Console.WriteLine($"Task started for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
while (true)
{
int start = Interlocked.Add(ref nextIndex, partitionSize) - partitionSize;
if (start >= totalCount)
{
break;
}
int end = start + partitionSize;
if (end > totalCount)
{
end = totalCount;
}
for (int index = start; index < end; index++)
{
session.ProcessPrime(filteredPrimes[index]);
}
}
session.Dispose();
Console.WriteLine($"Task finished for worker {workerIndex.ToString(CultureInfo.InvariantCulture)}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
}
private static void RunGpuTesterFiltered(
List<ulong> filteredPrimes,
MersenneNumberDivisorByDivisorGpuTester prototype,
@@ -20,7 +20,7 @@ public static class PerfectNumberConstants
public const int PooledArrayThreshold = 64;
public const int Pow2WindowSize = 8;
public const int RollingAccelerators = 298;
public const int ThreadsByAccelerator = 1;
public const int ThreadsByAccelerator = 4;
public const int ByDivisorStateSaveInterval = 200_000;
public const string ByDivisorStateDirectory = "Checks";
public const int PollardRhoMilliseconds = 100;
@@ -19,6 +19,8 @@
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
<PackageReference Include="ILGPU" Version="1.5.3" />
<PackageReference Include="ILGPU.Algorithms" Version="1.5.3" />
<PackageReference Include="LightningDB" Version="0.21.0" />
<PackageReference Include="Microsoft.FASTER.Core" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,181 @@
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using FASTER.core;
using LightningDB;
using PerfectNumbers.Core.ByDivisor;
namespace PerfectNumbers.Core.Persistence;
/// <summary>
/// Persists the by-divisor class model using Microsoft.FASTER.Core with optional migration from LightningDB.
/// </summary>
public sealed class ByDivisorClassModelRepository : IDisposable
{
private const string ModelKey = "bydivisor_model";
private readonly FasterKV<string, string> _store;
private readonly IDevice _logDevice;
private readonly IDevice _objectLogDevice;
private static readonly Encoding Utf8 = Encoding.UTF8;
public string DatabasePath { get; }
public static ByDivisorClassModelRepository Open(
string directory,
string repositoryFileName = "bydivisor_model.faster",
string? lmdbPath = null,
string? lmdbDirectory = null,
bool deleteLmdbAfterMigration = true)
{
Directory.CreateDirectory(directory);
string dbPath = Path.Combine(directory, repositoryFileName);
var repo = new ByDivisorClassModelRepository(dbPath);
string lmdbSource = lmdbPath ?? Path.ChangeExtension(dbPath, ".lmdb");
if (!File.Exists(lmdbSource) && !Directory.Exists(lmdbSource) && !string.IsNullOrEmpty(lmdbDirectory))
{
lmdbSource = Path.Combine(lmdbDirectory, Path.GetFileName(Path.ChangeExtension(repositoryFileName, ".lmdb")));
}
repo.MigrateFromLmdb(lmdbSource, deleteLmdbAfterMigration);
return repo;
}
private ByDivisorClassModelRepository(string path)
{
DatabasePath = path;
_logDevice = Devices.CreateLogDevice(path + ".log");
_objectLogDevice = Devices.CreateLogDevice(path + ".obj.log");
_store = new FasterKV<string, string>(
1L << 16,
new LogSettings
{
LogDevice = _logDevice,
ObjectLogDevice = _objectLogDevice,
});
}
public void Save(ByDivisorClassModel model)
{
string json = JsonSerializer.Serialize(model, new JsonSerializerOptions { WriteIndented = true });
using var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
session.Upsert(ModelKey, json);
session.CompletePending(true);
}
public bool TryLoad(out ByDivisorClassModel? model)
{
using var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
if (session.Read(ModelKey, out string? json).Found && json is not null)
{
try
{
model = JsonSerializer.Deserialize<ByDivisorClassModel>(json);
return model is not null;
}
catch
{
// ignore deserialization errors
}
}
model = null;
return false;
}
public void Commit()
{
_store.Log.FlushAndEvict(wait: true);
}
public void Dispose()
{
_store.Dispose();
_logDevice.Dispose();
_objectLogDevice.Dispose();
}
private void MigrateFromLmdb(string lmdbPath, bool deleteAfter)
{
try
{
if (!LmdbExists(lmdbPath))
{
Console.WriteLine($"No LMDB by-divisor model found at '{lmdbPath}', skipping migration.");
return;
}
Console.WriteLine($"Migrating by-divisor model from LMDB at '{lmdbPath}' to FASTER...");
using var env = new LightningEnvironment(lmdbPath) { MaxDatabases = 2 };
env.Open();
using var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly);
var db = tx.OpenDatabase("bydivisor_model", new DatabaseConfiguration { Flags = DatabaseOpenFlags.None });
if (tx.TryGet(db, Utf8.GetBytes(ModelKey), out byte[]? jsonBytes) && jsonBytes is not null)
{
try
{
string json = Utf8.GetString(jsonBytes);
var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
session.Upsert(ModelKey, json);
session.CompletePending(true);
session.Dispose();
}
catch
{
// ignore migration issues
}
}
_store.Log.FlushAndEvict(wait: true);
Console.WriteLine("Migration of by-divisor model to FASTER completed.");
}
catch
{
// ignore migration failures
}
if (deleteAfter)
{
Console.WriteLine($"Starting cleanup of LMDB by-divisor model at '{lmdbPath}'...");
try
{
if (Directory.Exists(lmdbPath))
{
Directory.Delete(lmdbPath, recursive: true);
}
else if (File.Exists(lmdbPath))
{
File.Delete(lmdbPath);
}
}
catch
{
// ignore cleanup failures
}
}
}
private static bool LmdbExists(string path)
{
if (Directory.Exists(path) || File.Exists(path))
{
return true;
}
string candidate = Path.Combine(path, "data.mdb");
if (File.Exists(candidate))
{
return true;
}
string? directory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory))
{
string sibling = Path.Combine(directory, "data.mdb");
if (File.Exists(sibling))
{
return true;
}
}
return false;
}
}
@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Numerics;
using System.Text;
using FASTER.core;
using LightningDB;
namespace PerfectNumbers.Core.Persistence;
/// <summary>
/// Stores last-checked k values (BigInteger) per prime using Microsoft.FASTER.Core.
/// Supports one-time migration from an existing LightningDB (LMDB) store.
/// </summary>
public sealed class KStateRepository : IDisposable
{
private readonly FasterKV<string, string> _store;
private readonly IDevice _logDevice;
private readonly IDevice _objectLogDevice;
private static readonly Encoding Utf8 = Encoding.UTF8;
public string DatabasePath { get; }
public static KStateRepository Open(
string directory,
string repositoryFileName = "kstate.faster",
string? lmdbPath = null,
string? lmdbDirectory = null,
string lmdbDatabaseName = "kstate",
bool deleteLmdbAfterMigration = true)
{
Directory.CreateDirectory(directory);
string dbPath = Path.Combine(directory, repositoryFileName);
var repo = new KStateRepository(dbPath);
string lmdbSource = lmdbPath ?? Path.ChangeExtension(dbPath, ".lmdb");
if (!File.Exists(lmdbSource) && !Directory.Exists(lmdbSource) && !string.IsNullOrEmpty(lmdbDirectory))
{
lmdbSource = Path.Combine(lmdbDirectory, Path.GetFileName(Path.ChangeExtension(repositoryFileName, ".lmdb")));
}
repo.MigrateFromLmdb(lmdbSource, lmdbDatabaseName, deleteLmdbAfterMigration);
return repo;
}
private KStateRepository(string path)
{
DatabasePath = path;
_logDevice = Devices.CreateLogDevice(path + ".log");
_objectLogDevice = Devices.CreateLogDevice(path + ".obj.log");
_store = new FasterKV<string, string>(
1L << 20,
new LogSettings
{
LogDevice = _logDevice,
ObjectLogDevice = _objectLogDevice,
});
}
public void Upsert(ulong prime, BigInteger k)
{
using var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
string keyString = prime.ToString(CultureInfo.InvariantCulture);
string valueString = k.ToString(CultureInfo.InvariantCulture);
session.Upsert(keyString, valueString);
session.CompletePending(true);
}
public bool TryGet(ulong prime, out BigInteger value)
{
using var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
string keyString = prime.ToString(CultureInfo.InvariantCulture);
if (session.Read(keyString, out string? stored).Found &&
stored is not null &&
BigInteger.TryParse(stored, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
{
return true;
}
value = BigInteger.Zero;
return false;
}
public void SeedFromStates(IEnumerable<(ulong Prime, BigInteger K)> states)
{
foreach (var (prime, k) in states)
{
Upsert(prime, k);
}
}
public void Commit()
{
_store.Log.FlushAndEvict(wait: true);
}
public void Dispose()
{
_store.Dispose();
_logDevice.Dispose();
_objectLogDevice.Dispose();
}
private void MigrateFromLmdb(string lmdbPath, string databaseName, bool deleteAfter)
{
try
{
if (!LmdbExists(lmdbPath))
{
Console.WriteLine($"No LMDB k-state found at '{lmdbPath}', skipping migration.");
return;
}
Console.WriteLine($"Migrating k-state from LMDB at '{lmdbPath}' to FASTER...");
using var env = new LightningEnvironment(lmdbPath) { MaxDatabases = 2 };
env.Open();
using var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly);
var db = tx.OpenDatabase(databaseName, new DatabaseConfiguration { Flags = DatabaseOpenFlags.None });
using var cursor = tx.CreateCursor(db);
foreach (var entry in cursor.AsEnumerable())
{
string keyString = Utf8.GetString(entry.Item1.CopyToNewArray());
string valueString = Utf8.GetString(entry.Item2.CopyToNewArray());
if (ulong.TryParse(keyString, NumberStyles.None, CultureInfo.InvariantCulture, out ulong prime) &&
BigInteger.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out BigInteger k) &&
k > BigInteger.Zero)
{
Upsert(prime, k);
}
}
_store.Log.FlushAndEvict(wait: true);
Console.WriteLine("Migration of k-state to FASTER completed.");
}
catch
{
// Ignore migration failures; repo will simply start empty.
}
if (deleteAfter)
{
Console.WriteLine($"Starting cleanup of LMDB k-state at '{lmdbPath}'...");
try
{
if (Directory.Exists(lmdbPath))
{
Directory.Delete(lmdbPath, recursive: true);
}
else if (File.Exists(lmdbPath))
{
File.Delete(lmdbPath);
}
}
catch
{
// ignore cleanup failures
}
}
}
private static bool LmdbExists(string path)
{
if (Directory.Exists(path) || File.Exists(path))
{
return true;
}
string candidate = Path.Combine(path, "data.mdb");
if (File.Exists(candidate))
{
return true;
}
string? directory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory))
{
string sibling = Path.Combine(directory, "data.mdb");
if (File.Exists(sibling))
{
return true;
}
}
return false;
}
}
@@ -0,0 +1,165 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using FASTER.core;
using LightningDB;
namespace PerfectNumbers.Core.Persistence;
/// <summary>
/// Tracks primes for which the pow2-1 divisor check was completed, backed by Microsoft.FASTER.Core.
/// Supports one-time migration from LightningDB.
/// </summary>
public sealed class Pow2Minus1StateRepository : IDisposable
{
private readonly FasterKV<string, string> _store;
private readonly IDevice _logDevice;
private readonly IDevice _objectLogDevice;
private static readonly Encoding Utf8 = Encoding.UTF8;
public string DatabasePath { get; }
public static Pow2Minus1StateRepository Open(
string databasePath,
string lmdbPath = "pow2minus1.lmdb",
string? lmdbDirectory = null,
string lmdbDatabaseName = "pow2minus1",
bool deleteLmdbAfterMigration = true)
{
string directory = Path.GetDirectoryName(databasePath) ?? ".";
Directory.CreateDirectory(directory);
var repo = new Pow2Minus1StateRepository(databasePath);
if (!File.Exists(lmdbPath) && !Directory.Exists(lmdbPath) && !string.IsNullOrEmpty(lmdbDirectory))
{
lmdbPath = Path.Combine(lmdbDirectory, Path.GetFileName(lmdbPath));
}
repo.MigrateFromLmdb(lmdbPath, lmdbDatabaseName, deleteLmdbAfterMigration);
return repo;
}
private Pow2Minus1StateRepository(string path)
{
DatabasePath = path;
_logDevice = Devices.CreateLogDevice(path + ".log");
_objectLogDevice = Devices.CreateLogDevice(path + ".obj.log");
_store = new FasterKV<string, string>(
1L << 18,
new LogSettings
{
LogDevice = _logDevice,
ObjectLogDevice = _objectLogDevice,
});
}
public void MarkChecked(ulong prime)
{
using var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
string key = prime.ToString(CultureInfo.InvariantCulture);
session.Upsert(key, "checked");
session.CompletePending(true);
}
public bool IsChecked(ulong prime)
{
using var session = _store.For(new SimpleFunctions<string, string>()).NewSession<SimpleFunctions<string, string>>();
string key = prime.ToString(CultureInfo.InvariantCulture);
var status = session.Read(key, out _);
session.CompletePending(true);
return status.Found;
}
public void Commit()
{
_store.Log.FlushAndEvict(wait: true);
}
public void Dispose()
{
_store.Dispose();
_logDevice.Dispose();
_objectLogDevice.Dispose();
}
private void MigrateFromLmdb(string lmdbPath, string databaseName, bool deleteAfter)
{
try
{
if (!LmdbExists(lmdbPath))
{
Console.WriteLine($"No LMDB pow2minus1 state found at '{lmdbPath}', skipping migration.");
return;
}
Console.WriteLine($"Migrating pow2minus1 state from LMDB at '{lmdbPath}' to FASTER...");
using var env = new LightningEnvironment(lmdbPath) { MaxDatabases = 2 };
env.Open();
using var tx = env.BeginTransaction(TransactionBeginFlags.ReadOnly);
var db = tx.OpenDatabase(databaseName, new DatabaseConfiguration { Flags = DatabaseOpenFlags.None });
using var cursor = tx.CreateCursor(db);
foreach (var entry in cursor.AsEnumerable())
{
string key = Utf8.GetString(entry.Item1.CopyToNewArray());
if (ulong.TryParse(key, NumberStyles.None, CultureInfo.InvariantCulture, out ulong prime))
{
MarkChecked(prime);
}
}
_store.Log.FlushAndEvict(wait: true);
Console.WriteLine("Migration of pow2minus1 state to FASTER completed.");
}
catch
{
// ignore migration issues
}
if (deleteAfter)
{
Console.WriteLine($"Starting cleanup of LMDB pow2minus1 state at '{lmdbPath}'...");
try
{
if (Directory.Exists(lmdbPath))
{
Directory.Delete(lmdbPath, recursive: true);
}
else if (File.Exists(lmdbPath))
{
File.Delete(lmdbPath);
}
}
catch
{
// ignore cleanup failures
}
}
}
private static bool LmdbExists(string path)
{
if (Directory.Exists(path) || File.Exists(path))
{
return true;
}
string candidate = Path.Combine(path, "data.mdb");
if (File.Exists(candidate))
{
return true;
}
string? directory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory))
{
string sibling = Path.Combine(directory, "data.mdb");
if (File.Exists(sibling))
{
return true;
}
}
return false;
}
}