Predictive scanning implemented for bydivisor method.

This commit is contained in:
mlemanczyk committed 2026-01-03 20:01:22 +01:00
1 parent ef8034b258
commit a6d408f2aa
10 files changed
+1562 -40

No files matched your search

@@ -0,0 +1,148 @@
using System;
using System.IO;
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 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,4 +1,5 @@
using System.Numerics;
using System.Reflection;
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder = PerfectNumbers.Core.Cpu.MersenneNumberDivisorByDivisorCpuTesterWithForOneByOneDivisorSetForCpuOrder;
@@ -99,9 +100,38 @@ public class MersenneNumberDivisorCpuTesterTests
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);
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 })!;
}
}