diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index 72052df..1d72484 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -1,10 +1,11 @@ using FluentAssertions; using Xunit; -using PerfectNumbers.Core; -using PerfectNumbers.Core.Gpu; using System; using System.Numerics; using System.Reflection; +using System.Text; +using PerfectNumbers.Core; +using PerfectNumbers.Core.Gpu; namespace EvenPerfectBitScanner.Tests; @@ -246,6 +247,85 @@ public class ProgramTests } } + [Fact] + public void Composite_candidates_flagged_for_residue_output_skip() + { + var useDivisorField = typeof(Program).GetField("_useDivisor", BindingFlags.NonPublic | BindingFlags.Static)!; + var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; + var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; + var residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!; + var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; + var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu"); + + useDivisorField.SetValue(null, false); + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true)); + primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); + residueField.SetValue(null, new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + forceCpuProp!.SetValue(null, true); + + try + { + compositeField.SetValue(null, false); + + Program.IsEvenPerfectCandidate(9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); + searched.Should().BeFalse(); + detailed.Should().BeFalse(); + ((bool)compositeField.GetValue(null)!).Should().BeTrue(); + + Program.IsEvenPerfectCandidate(11UL, 0UL, out searched, out detailed); + searched.Should().BeTrue(); + ((bool)compositeField.GetValue(null)!).Should().BeFalse(); + } + finally + { + mersenneField.SetValue(null, null); + primeField.SetValue(null, null); + residueField.SetValue(null, null); + compositeField.SetValue(null, false); + forceCpuProp!.SetValue(null, false); + } + } + + [Fact] + public void Residue_mode_skips_composite_results_in_output() + { + var residueModeField = typeof(Program).GetField("_useResidueMode", BindingFlags.NonPublic | BindingFlags.Static)!; + var outputField = typeof(Program).GetField("_outputBuilder", BindingFlags.NonPublic | BindingFlags.Static)!; + var writeIndexField = typeof(Program).GetField("_writeIndex", BindingFlags.NonPublic | BindingFlags.Static)!; + var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; + var printResult = typeof(Program).GetMethod("PrintResult", BindingFlags.NonPublic | BindingFlags.Static)!; + + var previousResidueMode = (bool)residueModeField.GetValue(null)!; + var previousOutput = (StringBuilder?)outputField.GetValue(null); + var previousWriteIndex = (int)writeIndexField.GetValue(null)!; + var previousComposite = (bool)compositeField.GetValue(null)!; + + var builder = StringBuilderPool.Rent(); + + try + { + builder.Clear(); + residueModeField.SetValue(null, true); + compositeField.SetValue(null, true); + outputField.SetValue(null, builder); + writeIndexField.SetValue(null, 0); + + printResult.Invoke(null, new object[] { 9UL, false, false, false }); + + builder.Length.Should().Be(0); + ((int)writeIndexField.GetValue(null)!).Should().Be(0); + } + finally + { + outputField.SetValue(null, previousOutput); + writeIndexField.SetValue(null, previousWriteIndex); + residueModeField.SetValue(null, previousResidueMode); + compositeField.SetValue(null, previousComposite); + builder.Clear(); + StringBuilderPool.Return(builder); + } + } + [Fact] [Trait("Category", "Fast")] public void Main_displays_help_when_requested() diff --git a/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj b/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj index b57e28b..c1a0f2a 100644 --- a/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj +++ b/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj @@ -12,4 +12,8 @@ <_Parameter1>EvenPerfectBitScanner.Tests + + + + diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index cc59080..7502d7e 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -1,6 +1,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Text; +using Open.Numeric.Primes; using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; @@ -25,15 +26,20 @@ internal static class Program private static int _primeCount; private static bool _primeFoundAfterInit; private static bool _useGcdFilter; - private static bool _useDivisor; - private static UInt128 _divisor; - private static MersenneNumberDivisorGpuTester? _divisorTester; - private static ulong? _orderWarmupLimitOverride; - private static unsafe delegate* _transformP; - private static long _state; - private static bool _limitReached; - private static string? _resultsDir; - private static string? _resultsPrefix; + private static bool _useDivisor; + private static bool _useResidueMode; + private static UInt128 _divisor; + private static MersenneNumberDivisorGpuTester? _divisorTester; + private static ulong? _orderWarmupLimitOverride; + private static unsafe delegate* _transformP; + private static long _state; + private static bool _limitReached; + private static string? _resultsDir; + private static string? _resultsPrefix; + private static readonly Optimized PrimeIterator = new(); + + [ThreadStatic] + private static bool _lastCompositeP; // RLE/bit-pattern filtering options private static string? _rleBlacklistPath; @@ -408,9 +414,21 @@ internal static class Program Console.WriteLine("Divisor cycles are ready"); - _useDivisor = useDivisor; - _divisor = divisor; - _transformP = useBitTransform ? &TransformPBit : &TransformPAdd; + _useDivisor = useDivisor; + _useResidueMode = useResidue; + _divisor = divisor; + if (useBitTransform) + { + _transformP = &TransformPBit; + } + else if (useResidue) + { + _transformP = &TransformPAdd; + } + else + { + _transformP = &TransformPAddPrimes; + } PResidue = new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, initialNumber: currentP, initialized: true), trackAllValues: true); PrimeTesters = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); // Note: --primes-device controls default device for library kernels; p primality remains CPU here. @@ -771,22 +789,27 @@ internal static class Program } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void PrintResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool isPerfect) - { - if (isPerfect) - { - int newCount = Interlocked.Increment(ref _primeCount); - if (newCount >= 2) - { - Volatile.Write(ref _primeFoundAfterInit, true); - } - } + private static void PrintResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool isPerfect) + { + if (isPerfect) + { + int newCount = Interlocked.Increment(ref _primeCount); + if (newCount >= 2) + { + Volatile.Write(ref _primeFoundAfterInit, true); + } + } - bool primeFlag = false, printToConsole = false; + if (_useResidueMode && _lastCompositeP) + { + return; + } - StringBuilder localBuilder = StringBuilderPool.Rent(); - _ = localBuilder - .Append(currentP).Append(',') + bool primeFlag = false, printToConsole = false; + + StringBuilder localBuilder = StringBuilderPool.Rent(); + _ = localBuilder + .Append(currentP).Append(',') .Append(searchedMersenne).Append(',') .Append(detailedCheck).Append(',') .Append(isPerfect).Append('\n'); @@ -853,6 +876,20 @@ internal static class Program return (ulong)BitOperations.PopCount(value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong GetNextAddDiff(ulong remainder) + { + return remainder switch + { + 0UL => 1UL, + 1UL => 4UL, + 2UL => 3UL, + 3UL => 2UL, + 4UL => 1UL, + _ => 2UL, + }; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong TransformPBit(ulong value, ref ulong remainder) { @@ -894,43 +931,99 @@ internal static class Program internal static ulong TransformPAdd(ulong value, ref ulong remainder) { ulong next = value; - value = remainder switch - { - 0UL => 1UL, - 1UL => 4UL, - 2UL => 3UL, - 3UL => 2UL, - 4UL => 1UL, - _ => 2UL, - }; // 'value' now holds diff + ulong diff = GetNextAddDiff(remainder); - if (next > ulong.MaxValue - value) + if (next > ulong.MaxValue - diff) { Volatile.Write(ref _limitReached, true); return next; } - remainder += value; + remainder += diff; remainder -= (remainder >= 6UL) ? 6UL : 0UL; - return next + value; + return next + diff; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong TransformPAddPrimes(ulong value, ref ulong remainder) + { + ulong originalRemainder = remainder; + ulong addRemainder = remainder; + ulong candidate = value; + ulong primeCandidate = value; + bool advanceAdd = true; + bool advancePrime = true; + + while (true) + { + if (advanceAdd) + { + ulong diff = GetNextAddDiff(addRemainder); + if (candidate > ulong.MaxValue - diff) + { + Volatile.Write(ref _limitReached, true); + remainder = originalRemainder; + return candidate; + } + + candidate += diff; + addRemainder += diff; + addRemainder -= (addRemainder >= 6UL) ? 6UL : 0UL; + } + + if (advancePrime) + { + ulong nextPrime; + try + { + nextPrime = PrimeIterator.Next(in primeCandidate); + } + catch (InvalidOperationException) + { + Volatile.Write(ref _limitReached, true); + remainder = originalRemainder; + return value; + } + + if (nextPrime <= primeCandidate) + { + Volatile.Write(ref _limitReached, true); + remainder = originalRemainder; + return value; + } + + primeCandidate = nextPrime; + } + + if (candidate == primeCandidate) + { + remainder = addRemainder; + return candidate; + } + + advanceAdd = candidate < primeCandidate; + advancePrime = candidate > primeCandidate; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsEvenPerfectCandidate(ulong p, ulong divisorCyclesSearchLimit) => IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out _, out _); - internal static bool IsEvenPerfectCandidate(ulong p, ulong divisorCyclesSearchLimit, out bool searchedMersenne, out bool detailedCheck) - { - searchedMersenne = false; - detailedCheck = false; + internal static bool IsEvenPerfectCandidate(ulong p, ulong divisorCyclesSearchLimit, out bool searchedMersenne, out bool detailedCheck) + { + searchedMersenne = false; + detailedCheck = false; + _lastCompositeP = false; - if (_useGcdFilter && IsCompositeByGcd(p)) - { - return false; - } + if (_useGcdFilter && IsCompositeByGcd(p)) + { + _lastCompositeP = true; + return false; + } - // Optional: RLE blacklist and binary-threshold filters on p (safe only when configured) - if (p <= _rleHardMaxP) + // Optional: RLE blacklist and binary-threshold filters on p (safe only when configured) + if (p <= _rleHardMaxP) { if (!_rleOnlyLast7 || (p % 10UL) == 7UL) { @@ -964,23 +1057,25 @@ internal static class Program } } - // Fast residue-based composite check for p using small primes - if (!_useDivisor && IsCompositeByResidues(p)) - { - searchedMersenne = false; - detailedCheck = false; - return false; - } + // Fast residue-based composite check for p using small primes + if (!_useDivisor && IsCompositeByResidues(p)) + { + searchedMersenne = false; + detailedCheck = false; + _lastCompositeP = true; + return false; + } - // If primes-device=gpu, route p primality through GPU-assisted sieve (optional MR later) - // TODO: Add deterministic Miller–Rabin rounds in GPU path for 64-bit range. - bool isPrimeP = GpuContextPool.ForceCpu - ? PrimeTesters.Value!.IsPrime(p, CancellationToken.None) - : PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None); - if (!isPrimeP) - { - return false; - } + // If primes-device=gpu, route p primality through GPU-assisted sieve (optional MR later) + // TODO: Add deterministic Miller–Rabin rounds in GPU path for 64-bit range. + bool isPrimeP = GpuContextPool.ForceCpu + ? PrimeTesters.Value!.IsPrime(p, CancellationToken.None) + : PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None); + if (!isPrimeP) + { + _lastCompositeP = true; + return false; + } searchedMersenne = true; if (_useDivisor)