From f993561cd5ec1fe7ed37736f9a27de3e59e0985f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Tue, 28 Oct 2025 23:43:15 +0100 Subject: [PATCH] Eagerly build combined divisors and restore two-A-one-B default --- .../HeuristicCombinedPrimeTesterTests.cs | 41 ++++++++- .../HeuristicCombinedPrimeTester.cs | 88 +++++++++++-------- 2 files changed, 91 insertions(+), 38 deletions(-) diff --git a/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs b/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs index a9e94a3..a03ccc5 100644 --- a/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs +++ b/PerfectNumbers.Core.Tests/HeuristicCombinedPrimeTesterTests.cs @@ -13,7 +13,7 @@ public class HeuristicCombinedPrimeTesterTests [InlineData((byte)1)] [InlineData((byte)7)] [InlineData((byte)9)] - public void Enumerator_uses_three_a_two_b_schedule_by_default(byte lastDigit) + public void Enumerator_uses_two_a_one_b_schedule_by_default(byte lastDigit) { Span groupBState = stackalloc HeuristicCombinedPrimeTester.HeuristicGroupBSequenceState[4]; @@ -41,7 +41,7 @@ public class HeuristicCombinedPrimeTesterTests for (int segment = 0; segment < segmentsToCheck; segment++) { - for (int i = 0; i < 3; i++) + for (int i = 0; i < 2; i++) { HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupACandidate = results[index++]; groupACandidate.Value.Should().BeGreaterThan(13UL); @@ -49,7 +49,7 @@ public class HeuristicCombinedPrimeTesterTests groupACandidate.Group.Should().NotBe(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB); } - for (int i = 0; i < 2; i++) + for (int i = 0; i < 1; i++) { HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupBCandidate = results[index++]; groupBCandidate.Value.Should().BeGreaterThan(13UL); @@ -59,6 +59,41 @@ public class HeuristicCombinedPrimeTesterTests } } + [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 combined = HeuristicCombinedPrimeTester.GetCombinedDivisors( + lastDigit, + HeuristicCombinedPrimeTester.CombinedDivisorPattern.TwoAOneB); + + combined.Length.Should().BeGreaterThan(20); + combined[0].Should().Be(3U); + combined[1].Should().Be(7U); + combined[2].Should().Be(11U); + combined[3].Should().Be(13U); + + int index = 4; + const int segmentsToCheck = 5; + + for (int segment = 0; segment < segmentsToCheck; segment++) + { + for (int i = 0; i < 2; i++) + { + uint groupAValue = combined[index++]; + groupAValue.Should().BeGreaterThan(13U); + (groupAValue % 10U).Should().Be(3U); + } + + uint groupBValue = combined[index++]; + groupBValue.Should().BeGreaterThan(13U); + (groupBValue % 10U).Should().NotBe(3U); + } + } + [Theory] [Trait("Category", "Fast")] [InlineData((byte)1)] diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index 2ab9447..240180f 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -1,6 +1,4 @@ using System; -using System.Buffers; -using System.Collections.Concurrent; using Open.Numeric.Primes; using System.Collections.Generic; using System.Numerics; @@ -18,31 +16,51 @@ public sealed class HeuristicCombinedPrimeTester static HeuristicCombinedPrimeTester() { HeuristicPrimeSieves.EnsureInitialized(); + + CombinedDivisorsEnding1TwoAOneB = BuildCombinedDivisors(1, CombinedDivisorPattern.TwoAOneB); + CombinedDivisorsEnding3TwoAOneB = BuildCombinedDivisors(3, CombinedDivisorPattern.TwoAOneB); + CombinedDivisorsEnding7TwoAOneB = BuildCombinedDivisors(7, CombinedDivisorPattern.TwoAOneB); + CombinedDivisorsEnding9TwoAOneB = BuildCombinedDivisors(9, CombinedDivisorPattern.TwoAOneB); + + CombinedDivisorsEnding1OneAOneB = BuildCombinedDivisors(1, CombinedDivisorPattern.OneAOneB); + CombinedDivisorsEnding3OneAOneB = BuildCombinedDivisors(3, CombinedDivisorPattern.OneAOneB); + CombinedDivisorsEnding7OneAOneB = BuildCombinedDivisors(7, CombinedDivisorPattern.OneAOneB); + CombinedDivisorsEnding9OneAOneB = BuildCombinedDivisors(9, CombinedDivisorPattern.OneAOneB); + + CombinedDivisorsEnding1ThreeAOneB = BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeAOneB); + CombinedDivisorsEnding3ThreeAOneB = BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeAOneB); + CombinedDivisorsEnding7ThreeAOneB = BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeAOneB); + CombinedDivisorsEnding9ThreeAOneB = BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeAOneB); + + CombinedDivisorsEnding1ThreeATwoB = BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeATwoB); + CombinedDivisorsEnding3ThreeATwoB = BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeATwoB); + CombinedDivisorsEnding7ThreeATwoB = BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeATwoB); + CombinedDivisorsEnding9ThreeATwoB = BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeATwoB); } private const ulong Wheel210 = 210UL; private static readonly bool UseHeuristicGroupBTrialDivision = false; // Temporary fallback gate for Group B. - private static readonly Lazy CombinedDivisorsEnding1TwoAOneB = new(() => BuildCombinedDivisors(1, CombinedDivisorPattern.TwoAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding3TwoAOneB = new(() => BuildCombinedDivisors(3, CombinedDivisorPattern.TwoAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding7TwoAOneB = new(() => BuildCombinedDivisors(7, CombinedDivisorPattern.TwoAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding9TwoAOneB = new(() => BuildCombinedDivisors(9, CombinedDivisorPattern.TwoAOneB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly uint[] CombinedDivisorsEnding1TwoAOneB; + private static readonly uint[] CombinedDivisorsEnding3TwoAOneB; + private static readonly uint[] CombinedDivisorsEnding7TwoAOneB; + private static readonly uint[] CombinedDivisorsEnding9TwoAOneB; - private static readonly Lazy CombinedDivisorsEnding1OneAOneB = new(() => BuildCombinedDivisors(1, CombinedDivisorPattern.OneAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding3OneAOneB = new(() => BuildCombinedDivisors(3, CombinedDivisorPattern.OneAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding7OneAOneB = new(() => BuildCombinedDivisors(7, CombinedDivisorPattern.OneAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding9OneAOneB = new(() => BuildCombinedDivisors(9, CombinedDivisorPattern.OneAOneB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly uint[] CombinedDivisorsEnding1OneAOneB; + private static readonly uint[] CombinedDivisorsEnding3OneAOneB; + private static readonly uint[] CombinedDivisorsEnding7OneAOneB; + private static readonly uint[] CombinedDivisorsEnding9OneAOneB; - private static readonly Lazy CombinedDivisorsEnding1ThreeAOneB = new(() => BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding3ThreeAOneB = new(() => BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding7ThreeAOneB = new(() => BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeAOneB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding9ThreeAOneB = new(() => BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeAOneB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly uint[] CombinedDivisorsEnding1ThreeAOneB; + private static readonly uint[] CombinedDivisorsEnding3ThreeAOneB; + private static readonly uint[] CombinedDivisorsEnding7ThreeAOneB; + private static readonly uint[] CombinedDivisorsEnding9ThreeAOneB; - private static readonly Lazy CombinedDivisorsEnding1ThreeATwoB = new(() => BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding3ThreeATwoB = new(() => BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding7ThreeATwoB = new(() => BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); - private static readonly Lazy CombinedDivisorsEnding9ThreeATwoB = new(() => BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeATwoB), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly uint[] CombinedDivisorsEnding1ThreeATwoB; + private static readonly uint[] CombinedDivisorsEnding3ThreeATwoB; + private static readonly uint[] CombinedDivisorsEnding7ThreeATwoB; + private static readonly uint[] CombinedDivisorsEnding9ThreeATwoB; private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); @@ -410,7 +428,7 @@ Cleanup: private static ReadOnlySpan GetCombinedDivisors(byte nMod10) { - return GetCombinedDivisors(nMod10, CombinedDivisorPattern.ThreeATwoB); + return GetCombinedDivisors(nMod10, CombinedDivisorPattern.TwoAOneB); } internal static ReadOnlySpan GetCombinedDivisors(byte nMod10, CombinedDivisorPattern pattern) @@ -419,34 +437,34 @@ Cleanup: { CombinedDivisorPattern.TwoAOneB => nMod10 switch { - 1 => CombinedDivisorsEnding1TwoAOneB.Value, - 3 => CombinedDivisorsEnding3TwoAOneB.Value, - 7 => CombinedDivisorsEnding7TwoAOneB.Value, - 9 => CombinedDivisorsEnding9TwoAOneB.Value, + 1 => CombinedDivisorsEnding1TwoAOneB, + 3 => CombinedDivisorsEnding3TwoAOneB, + 7 => CombinedDivisorsEnding7TwoAOneB, + 9 => CombinedDivisorsEnding9TwoAOneB, _ => ReadOnlySpan.Empty, }, CombinedDivisorPattern.OneAOneB => nMod10 switch { - 1 => CombinedDivisorsEnding1OneAOneB.Value, - 3 => CombinedDivisorsEnding3OneAOneB.Value, - 7 => CombinedDivisorsEnding7OneAOneB.Value, - 9 => CombinedDivisorsEnding9OneAOneB.Value, + 1 => CombinedDivisorsEnding1OneAOneB, + 3 => CombinedDivisorsEnding3OneAOneB, + 7 => CombinedDivisorsEnding7OneAOneB, + 9 => CombinedDivisorsEnding9OneAOneB, _ => ReadOnlySpan.Empty, }, CombinedDivisorPattern.ThreeAOneB => nMod10 switch { - 1 => CombinedDivisorsEnding1ThreeAOneB.Value, - 3 => CombinedDivisorsEnding3ThreeAOneB.Value, - 7 => CombinedDivisorsEnding7ThreeAOneB.Value, - 9 => CombinedDivisorsEnding9ThreeAOneB.Value, + 1 => CombinedDivisorsEnding1ThreeAOneB, + 3 => CombinedDivisorsEnding3ThreeAOneB, + 7 => CombinedDivisorsEnding7ThreeAOneB, + 9 => CombinedDivisorsEnding9ThreeAOneB, _ => ReadOnlySpan.Empty, }, CombinedDivisorPattern.ThreeATwoB => nMod10 switch { - 1 => CombinedDivisorsEnding1ThreeATwoB.Value, - 3 => CombinedDivisorsEnding3ThreeATwoB.Value, - 7 => CombinedDivisorsEnding7ThreeATwoB.Value, - 9 => CombinedDivisorsEnding9ThreeATwoB.Value, + 1 => CombinedDivisorsEnding1ThreeATwoB, + 3 => CombinedDivisorsEnding3ThreeATwoB, + 7 => CombinedDivisorsEnding7ThreeATwoB, + 9 => CombinedDivisorsEnding9ThreeATwoB, _ => ReadOnlySpan.Empty, }, _ => ReadOnlySpan.Empty,