BitContradiction improvements - 0.043s, p=138000001+
This commit is contained in:
1 parent
e97ea151e1
commit
41e421e1eb
9 files changed
+3789
-533
No files matched your search
@@ -22,34 +22,30 @@ public class BitContradictionSolverTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void TryPropagateCarry_FailsWhenParityUnreachable()
|
public void TryPropagateCarry_FailsWhenParityUnreachable()
|
||||||
{
|
{
|
||||||
|
CarryRange carry = CarryRange.Single(0);
|
||||||
bool success = TryPropagateCarry(
|
bool success = TryPropagateCarry(
|
||||||
currentCarry: CarryRange.Single(0),
|
currentCarry: ref carry,
|
||||||
forcedOnes: 0,
|
forcedOnes: 0,
|
||||||
possibleOnes: 0,
|
possibleOnes: 0,
|
||||||
requiredResultBit: 1,
|
requiredResultBit: 1);
|
||||||
out var nextCarry,
|
|
||||||
out ContradictionReason reason);
|
|
||||||
|
|
||||||
success.Should().BeFalse();
|
success.Should().BeFalse();
|
||||||
reason.Should().Be(ContradictionReason.ParityUnreachable);
|
carry.Min.Should().Be(0);
|
||||||
nextCarry.Min.Should().Be(0);
|
carry.Max.Should().Be(0);
|
||||||
nextCarry.Max.Should().Be(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryPropagateCarry_ComputesNextCarryRange()
|
public void TryPropagateCarry_ComputesNextCarryRange()
|
||||||
{
|
{
|
||||||
|
CarryRange carry = new(0, 1);
|
||||||
bool success = TryPropagateCarry(
|
bool success = TryPropagateCarry(
|
||||||
currentCarry: new CarryRange(0, 1),
|
currentCarry: ref carry,
|
||||||
forcedOnes: 1,
|
forcedOnes: 1,
|
||||||
possibleOnes: 3,
|
possibleOnes: 3,
|
||||||
requiredResultBit: 1,
|
requiredResultBit: 1);
|
||||||
out var nextCarry,
|
|
||||||
out ContradictionReason reason);
|
|
||||||
|
|
||||||
success.Should().BeTrue();
|
success.Should().BeTrue();
|
||||||
reason.Should().Be(ContradictionReason.None);
|
carry.Min.Should().Be(0);
|
||||||
nextCarry.Min.Should().Be(0);
|
carry.Max.Should().Be(1);
|
||||||
nextCarry.Max.Should().Be(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -3,36 +3,28 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace PerfectNumbers.Core.ByDivisor;
|
namespace PerfectNumbers.Core.ByDivisor;
|
||||||
|
|
||||||
public readonly struct TopDownPruneFailure(long column, long carryMin, long carryMax, long unknown)
|
public readonly struct ColumnBounds(int forcedOnes, int possibleOnes)
|
||||||
{
|
{
|
||||||
public readonly long Column = column;
|
public readonly int ForcedOnes = forcedOnes;
|
||||||
public readonly long CarryMin = carryMin;
|
public readonly int PossibleOnes = possibleOnes;
|
||||||
public readonly long CarryMax = carryMax;
|
}
|
||||||
public readonly long Unknown = unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct ColumnBounds(long forcedOnes, long possibleOnes)
|
public readonly struct CarryRange(long min, long max)
|
||||||
{
|
{
|
||||||
public readonly long ForcedOnes = forcedOnes;
|
|
||||||
public readonly long PossibleOnes = possibleOnes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct CarryRange(long min, long max)
|
|
||||||
{
|
|
||||||
public readonly long Min = min;
|
public readonly long Min = min;
|
||||||
public readonly long Max = max;
|
public readonly long Max = max;
|
||||||
|
|
||||||
public static CarryRange Single(long value) => new(value, value);
|
public static CarryRange Single(long value) => new(value, value);
|
||||||
public static readonly CarryRange Zero = Single(0);
|
public static readonly CarryRange Zero = Single(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContradictionReason
|
public enum ContradictionReason
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
ParityUnreachable,
|
ParityUnreachable,
|
||||||
TruncatedLength,
|
TruncatedLength,
|
||||||
TailNotZero,
|
TailNotZero,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper utilities for the BitContradiction divisor search. These methods implement the
|
/// Helper utilities for the BitContradiction divisor search. These methods implement the
|
||||||
@@ -44,26 +36,15 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
private const int PingPongBlockColumns = 64;
|
private const int PingPongBlockColumns = 64;
|
||||||
private const int TailCarryBatchColumns = 1024;
|
private const int TailCarryBatchColumns = 1024;
|
||||||
private const int TailCarryBatchRepeats = 1;
|
private const int TailCarryBatchRepeats = 1;
|
||||||
|
|
||||||
[ThreadStatic]
|
|
||||||
private static TopDownPruneFailure? _lastTopDownFailure;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static BottomUpFailure? _lastBottomUpFailure;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static bool _debugEnabled;
|
|
||||||
|
|
||||||
private const int TopDownCacheCapacity = 16;
|
private const int TopDownCacheCapacity = 16;
|
||||||
|
|
||||||
private readonly struct TopDownCacheKey(long hash, int length, long maxAllowedA, long pLong)
|
#if DETAILED_LOG
|
||||||
|
public readonly struct TopDownPruneFailure(int column, int carryMin, int carryMax, int unknown)
|
||||||
{
|
{
|
||||||
public readonly long Hash = hash;
|
public readonly int Column = column;
|
||||||
public readonly int Length = length;
|
public readonly int CarryMin = carryMin;
|
||||||
public readonly long MaxAllowedA = maxAllowedA;
|
public readonly int CarryMax = carryMax;
|
||||||
public readonly long PLong = pLong;
|
public readonly int Unknown = unknown;
|
||||||
|
|
||||||
public bool Equals(TopDownCacheKey other) => Hash == other.Hash && Length == other.Length && MaxAllowedA == other.MaxAllowedA && PLong == other.PLong;
|
|
||||||
public override bool Equals(object? obj) => obj is TopDownCacheKey other && Equals(other);
|
|
||||||
public override int GetHashCode() => HashCode.Combine(Hash, Length, MaxAllowedA, PLong);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct TopDownCacheValue(bool success, TopDownPruneFailure? failure)
|
private readonly struct TopDownCacheValue(bool success, TopDownPruneFailure? failure)
|
||||||
@@ -72,11 +53,44 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
public readonly TopDownPruneFailure? Failure = failure;
|
public readonly TopDownPruneFailure? Failure = failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal readonly struct BottomUpFailure(int column, int carryMin, int carryMax, int forced, int unknown)
|
||||||
|
{
|
||||||
|
public readonly int Column = column;
|
||||||
|
public readonly int CarryMin = carryMin;
|
||||||
|
public readonly int CarryMax = carryMax;
|
||||||
|
public readonly int Forced = forced;
|
||||||
|
public readonly int Unknown = unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static TopDownPruneFailure? _lastTopDownFailure;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static BottomUpFailure? _lastBottomUpFailure;
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static Dictionary<TopDownCacheKey, TopDownCacheValue>? _topDownCache;
|
private static Dictionary<TopDownCacheKey, TopDownCacheValue>? _topDownCache;
|
||||||
|
|
||||||
|
internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure;
|
||||||
|
internal static BottomUpFailure? LastBottomUpFailure => _lastBottomUpFailure;
|
||||||
|
#else
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<TopDownCacheKey, bool>? _topDownCache;
|
||||||
|
#endif
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static List<TopDownCacheKey>? _topDownCacheOrder;
|
private static List<TopDownCacheKey>? _topDownCacheOrder;
|
||||||
|
|
||||||
|
private readonly struct TopDownCacheKey(long hash, int length, int maxAllowedA, int pLong)
|
||||||
|
{
|
||||||
|
public readonly long Hash = hash;
|
||||||
|
public readonly int Length = length;
|
||||||
|
public readonly int MaxAllowedA = maxAllowedA;
|
||||||
|
public readonly int PLong = pLong;
|
||||||
|
|
||||||
|
public bool Equals(TopDownCacheKey other) => Hash == other.Hash && Length == other.Length && MaxAllowedA == other.MaxAllowedA && PLong == other.PLong;
|
||||||
|
public override bool Equals(object? obj) => obj is TopDownCacheKey other && Equals(other);
|
||||||
|
public override int GetHashCode() => HashCode.Combine(Hash, Length, MaxAllowedA, PLong);
|
||||||
|
}
|
||||||
|
|
||||||
private static long ComputeQOffsetsHash(ReadOnlySpan<int> qOneOffsets)
|
private static long ComputeQOffsetsHash(ReadOnlySpan<int> qOneOffsets)
|
||||||
{
|
{
|
||||||
const long fnvOffset = unchecked((long)1469598103934665603);
|
const long fnvOffset = unchecked((long)1469598103934665603);
|
||||||
@@ -91,61 +105,62 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
private static bool TryGetTopDownCache(in TopDownCacheKey key, out TopDownCacheValue value)
|
private static bool TryGetTopDownCache(in TopDownCacheKey key, out TopDownCacheValue value)
|
||||||
|
#else
|
||||||
|
private static bool TryGetTopDownCache(in TopDownCacheKey key, out bool value)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
if (_topDownCache == null)
|
var cache = _topDownCache;
|
||||||
|
if (cache == null)
|
||||||
{
|
{
|
||||||
|
cache = [];
|
||||||
|
_topDownCache = cache;
|
||||||
value = default;
|
value = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _topDownCache.TryGetValue(key, out value);
|
return cache.TryGetValue(key, out value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
private static void StoreTopDownCache(in TopDownCacheKey key, in TopDownCacheValue value)
|
private static void StoreTopDownCache(in TopDownCacheKey key, in TopDownCacheValue value)
|
||||||
|
#else
|
||||||
|
private static void StoreTopDownCache(in TopDownCacheKey key, bool value)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
_topDownCache ??= new Dictionary<TopDownCacheKey, TopDownCacheValue>();
|
// _topDownCache is always non-null here on EvenPerfectBitScanner execution path.
|
||||||
_topDownCacheOrder ??= new List<TopDownCacheKey>(TopDownCacheCapacity + 1);
|
var topDownCache = _topDownCache!;
|
||||||
|
if (topDownCache.ContainsKey(key))
|
||||||
if (_topDownCache.ContainsKey(key))
|
|
||||||
{
|
{
|
||||||
_topDownCache[key] = value;
|
topDownCache[key] = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_topDownCacheOrder.Count >= TopDownCacheCapacity)
|
List<TopDownCacheKey>? topDownCacheOrder = _topDownCacheOrder;
|
||||||
|
if (topDownCacheOrder == null)
|
||||||
{
|
{
|
||||||
var evict = _topDownCacheOrder[0];
|
topDownCacheOrder = new List<TopDownCacheKey>(TopDownCacheCapacity + 1);
|
||||||
_topDownCacheOrder.RemoveAt(0);
|
_topDownCacheOrder = topDownCacheOrder;
|
||||||
_topDownCache.Remove(evict);
|
|
||||||
}
|
}
|
||||||
|
else if (topDownCacheOrder.Count >= TopDownCacheCapacity)
|
||||||
_topDownCache[key] = value;
|
|
||||||
_topDownCacheOrder.Add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure;
|
|
||||||
internal static BottomUpFailure? LastBottomUpFailure => _lastBottomUpFailure;
|
|
||||||
|
|
||||||
internal readonly struct BottomUpFailure(long column, long carryMin, long carryMax, long forced, long unknown)
|
|
||||||
{
|
{
|
||||||
public readonly long Column = column;
|
var evict = topDownCacheOrder[0];
|
||||||
public readonly long CarryMin = carryMin;
|
topDownCacheOrder.RemoveAt(0);
|
||||||
public readonly long CarryMax = carryMax;
|
topDownCache.Remove(evict);
|
||||||
public readonly long Forced = forced;
|
|
||||||
public readonly long Unknown = unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void SetDebugEnabled(bool enabled) => _debugEnabled = enabled;
|
topDownCache[key] = value;
|
||||||
|
topDownCacheOrder.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
internal static ColumnBounds ComputeColumnBounds(
|
internal static ColumnBounds ComputeColumnBounds(
|
||||||
ReadOnlySpan<bool> multiplicand,
|
in ReadOnlySpan<bool> multiplicand,
|
||||||
ReadOnlySpan<bool> multiplier,
|
in ReadOnlySpan<bool> multiplier,
|
||||||
int columnIndex)
|
int columnIndex)
|
||||||
{
|
{
|
||||||
long forced = 0;
|
int forced = 0;
|
||||||
long possible = 0;
|
int possible = 0;
|
||||||
|
|
||||||
int multiplicandLength = multiplicand.Length;
|
int multiplicandLength = multiplicand.Length;
|
||||||
int multiplierLength = multiplier.Length;
|
int multiplierLength = multiplier.Length;
|
||||||
@@ -169,6 +184,7 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
return new ColumnBounds(forced, possible);
|
return new ColumnBounds(forced, possible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
internal static bool TryPropagateCarry(
|
internal static bool TryPropagateCarry(
|
||||||
CarryRange currentCarry,
|
CarryRange currentCarry,
|
||||||
long forcedOnes,
|
long forcedOnes,
|
||||||
@@ -176,6 +192,14 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
int requiredResultBit,
|
int requiredResultBit,
|
||||||
out CarryRange nextCarry,
|
out CarryRange nextCarry,
|
||||||
out ContradictionReason reason)
|
out ContradictionReason reason)
|
||||||
|
#else
|
||||||
|
internal static bool TryPropagateCarry(
|
||||||
|
CarryRange currentCarry,
|
||||||
|
int forcedOnes,
|
||||||
|
int possibleOnes,
|
||||||
|
int requiredResultBit,
|
||||||
|
out CarryRange nextCarry)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
long minSum = currentCarry.Min + forcedOnes;
|
long minSum = currentCarry.Min + forcedOnes;
|
||||||
long maxSum = currentCarry.Max + possibleOnes;
|
long maxSum = currentCarry.Max + possibleOnes;
|
||||||
@@ -187,12 +211,16 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
if (minSum > maxSum)
|
if (minSum > maxSum)
|
||||||
{
|
{
|
||||||
nextCarry = default;
|
nextCarry = default;
|
||||||
|
#if DETAILED_LOG
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextCarry = new CarryRange((minSum - requiredResultBit) >> 1, (maxSum - requiredResultBit) >> 1);
|
nextCarry = new CarryRange((minSum - requiredResultBit) >> 1, (maxSum - requiredResultBit) >> 1);
|
||||||
|
#if DETAILED_LOG
|
||||||
reason = ContradictionReason.None;
|
reason = ContradictionReason.None;
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +235,10 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
out ContradictionReason reason)
|
out ContradictionReason reason)
|
||||||
{
|
{
|
||||||
divides = false;
|
divides = false;
|
||||||
|
#if DETAILED_LOG
|
||||||
_lastTopDownFailure = null;
|
_lastTopDownFailure = null;
|
||||||
_lastBottomUpFailure = null;
|
_lastBottomUpFailure = null;
|
||||||
|
#endif
|
||||||
|
|
||||||
// These conditions will never be true on EvenPerfectBitScanner execution path.
|
// These conditions will never be true on EvenPerfectBitScanner execution path.
|
||||||
// if (p == 0 || qOneOffsets.Length == 0)
|
// if (p == 0 || qOneOffsets.Length == 0)
|
||||||
@@ -253,8 +283,8 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
var segmentState0 = new sbyte[windowSize];
|
var segmentState0 = new sbyte[windowSize];
|
||||||
var segmentState1 = new sbyte[windowSize];
|
var segmentState1 = new sbyte[windowSize];
|
||||||
long segmentBase = 0;
|
long segmentBase = 0;
|
||||||
long pLong = (long)p;
|
int pLong = (int)p;
|
||||||
long maxAllowedA = pLong - (maxOffset + 1L);
|
int maxAllowedA = pLong - (maxOffset + 1);
|
||||||
if (maxAllowedA < 0)
|
if (maxAllowedA < 0)
|
||||||
{
|
{
|
||||||
reason = ContradictionReason.TruncatedLength;
|
reason = ContradictionReason.TruncatedLength;
|
||||||
@@ -265,29 +295,47 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong);
|
var cacheKey = new TopDownCacheKey(qHash, qOneOffsetsLength, maxAllowedA, pLong);
|
||||||
if (TryGetTopDownCache(in cacheKey, out var cached))
|
if (TryGetTopDownCache(in cacheKey, out var cached))
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
_lastTopDownFailure = cached.Failure;
|
_lastTopDownFailure = cached.Failure;
|
||||||
if (!cached.Success)
|
if (!cached.Success)
|
||||||
|
#else
|
||||||
|
if (!cached)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
|
#if DETAILED_LOG
|
||||||
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
|
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
|
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
|
||||||
|
#else
|
||||||
|
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
StoreTopDownCache(in cacheKey, new TopDownCacheValue(false, _lastTopDownFailure));
|
StoreTopDownCache(in cacheKey, new TopDownCacheValue(false, _lastTopDownFailure));
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
|
||||||
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
|
Console.WriteLine($"[bit-contradiction] parity unreachable in top-down prune (failure={_lastTopDownFailure.HasValue})");
|
||||||
|
#else
|
||||||
|
StoreTopDownCache(in cacheKey, false);
|
||||||
|
#endif
|
||||||
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
StoreTopDownCache(in cacheKey, new TopDownCacheValue(true, null));
|
StoreTopDownCache(in cacheKey, new TopDownCacheValue(true, null));
|
||||||
|
#else
|
||||||
|
StoreTopDownCache(in cacheKey, true);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
long lowColumn = 0;
|
int lowColumn = 0;
|
||||||
long highColumn = pLong - 1;
|
int highColumn = pLong - 1;
|
||||||
CarryRange carryLow = CarryRange.Zero;
|
CarryRange carryLow = CarryRange.Zero;
|
||||||
CarryRange carryHigh = CarryRange.Zero;
|
CarryRange carryHigh = CarryRange.Zero;
|
||||||
int bottomWindowStart = 0;
|
int bottomWindowStart = 0;
|
||||||
@@ -304,7 +352,11 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
|
|
||||||
if (processTop)
|
if (processTop)
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh, out _lastTopDownFailure))
|
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh, out _lastTopDownFailure))
|
||||||
|
#else
|
||||||
|
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, ref topWindowStart, ref topWindowEnd, out carryHigh))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
return true;
|
return true;
|
||||||
@@ -329,7 +381,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
long carryMax = carryLow.Max < carryHigh.Max ? carryLow.Max : carryHigh.Max;
|
long carryMax = carryLow.Max < carryHigh.Max ? carryLow.Max : carryHigh.Max;
|
||||||
if (carryMin > carryMax)
|
if (carryMin > carryMax)
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
Console.WriteLine($"[bit-contradiction] parity unreachable after ping-pong carry merge (low=[{carryLow.Min},{carryLow.Max}] high=[{carryHigh.Min},{carryHigh.Max}])");
|
Console.WriteLine($"[bit-contradiction] parity unreachable after ping-pong carry merge (low=[{carryLow.Min},{carryLow.Max}] high=[{carryHigh.Min},{carryHigh.Max}])");
|
||||||
|
#endif
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
divides = false;
|
divides = false;
|
||||||
return true;
|
return true;
|
||||||
@@ -352,8 +406,8 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
long carryMax = carry.Max;
|
long carryMax = carry.Max;
|
||||||
while (columnCount > 0)
|
while (columnCount > 0)
|
||||||
{
|
{
|
||||||
int step = columnCount > 62 ? 62 : columnCount;
|
int step = columnCount > 30 ? 30 : columnCount;
|
||||||
long add = (1L << step) - 1L;
|
int add = (1 << step) - 1;
|
||||||
long nextMin = (carryMin + add) >> step;
|
long nextMin = (carryMin + add) >> step;
|
||||||
long nextMax = carryMax >> step;
|
long nextMax = carryMax >> step;
|
||||||
if (nextMin > nextMax)
|
if (nextMin > nextMax)
|
||||||
@@ -372,8 +426,8 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
|
|
||||||
private static bool TryProcessBottomUpBlock(
|
private static bool TryProcessBottomUpBlock(
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
ReadOnlySpan<int> qOneOffsets,
|
||||||
long maxAllowedA,
|
int maxAllowedA,
|
||||||
long startColumn,
|
int startColumn,
|
||||||
int columnCount,
|
int columnCount,
|
||||||
ref CarryRange carry,
|
ref CarryRange carry,
|
||||||
ref long segmentBase,
|
ref long segmentBase,
|
||||||
@@ -386,12 +440,12 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
long endColumn = startColumn + columnCount;
|
long endColumn = startColumn + columnCount;
|
||||||
int qOneOffsetsLength = qOneOffsets.Length;
|
int qOneOffsetsLength = qOneOffsets.Length;
|
||||||
int windowSize = segmentState0.Length;
|
int windowSize = segmentState0.Length;
|
||||||
for (long column = startColumn; column < endColumn; column++)
|
for (int column = startColumn; column < endColumn; column++)
|
||||||
{
|
{
|
||||||
long forced = 0L;
|
int forced = 0;
|
||||||
long unknown = 0L;
|
int unknown = 0;
|
||||||
long chosenIndex = -1L;
|
int chosenIndex = -1;
|
||||||
long newBase = (column / windowSize) * windowSize;
|
int newBase = (column / windowSize) * windowSize;
|
||||||
if (newBase != segmentBase)
|
if (newBase != segmentBase)
|
||||||
{
|
{
|
||||||
if (newBase == segmentBase + windowSize)
|
if (newBase == segmentBase + windowSize)
|
||||||
@@ -413,7 +467,7 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
windowEndIdx++;
|
windowEndIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
long lowerBound = column - maxAllowedA;
|
int lowerBound = column - maxAllowedA;
|
||||||
while (windowStartIdx < windowEndIdx && qOneOffsets[windowStartIdx] < lowerBound)
|
while (windowStartIdx < windowEndIdx && qOneOffsets[windowStartIdx] < lowerBound)
|
||||||
{
|
{
|
||||||
windowStartIdx++;
|
windowStartIdx++;
|
||||||
@@ -422,7 +476,7 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
for (int offsetIndex = windowStartIdx; offsetIndex < windowEndIdx; offsetIndex++)
|
for (int offsetIndex = windowStartIdx; offsetIndex < windowEndIdx; offsetIndex++)
|
||||||
{
|
{
|
||||||
int offset = qOneOffsets[offsetIndex];
|
int offset = qOneOffsets[offsetIndex];
|
||||||
long aIndex = column - offset;
|
int aIndex = column - offset;
|
||||||
if (aIndex < 0)
|
if (aIndex < 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -513,10 +567,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
long carryMax = AlignDownToParity(carry.Max, requiredCarryParity);
|
long carryMax = AlignDownToParity(carry.Max, requiredCarryParity);
|
||||||
if (carryMin > carryMax)
|
if (carryMin > carryMax)
|
||||||
{
|
{
|
||||||
if (_debugEnabled)
|
#if DETAILED_LOG
|
||||||
{
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carryMin, carryMax, forced, unknown);
|
_lastBottomUpFailure = new BottomUpFailure(column, carryMin, carryMax, forced, unknown);
|
||||||
}
|
#endif
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -534,10 +587,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
if ((minSum & 1) != requiredParity)
|
if ((minSum & 1) != requiredParity)
|
||||||
{
|
{
|
||||||
if (_debugEnabled)
|
#if DETAILED_LOG
|
||||||
{
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
||||||
}
|
#endif
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -549,14 +601,14 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
if ((minSum & 1) != requiredParity && unknown == 0)
|
if ((minSum & 1) != requiredParity && unknown == 0)
|
||||||
{
|
{
|
||||||
if (_debugEnabled)
|
#if DETAILED_LOG
|
||||||
{
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
||||||
}
|
#endif
|
||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
if (!TryPropagateCarry(
|
if (!TryPropagateCarry(
|
||||||
carry,
|
carry,
|
||||||
forced,
|
forced,
|
||||||
@@ -564,12 +616,21 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
requiredParity,
|
requiredParity,
|
||||||
out var next,
|
out var next,
|
||||||
out var propagateReason))
|
out var propagateReason))
|
||||||
|
#else
|
||||||
|
if (!TryPropagateCarry(
|
||||||
|
carry,
|
||||||
|
forced,
|
||||||
|
forced + unknown,
|
||||||
|
requiredParity,
|
||||||
|
out var next))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
if (_debugEnabled)
|
#if DETAILED_LOG
|
||||||
{
|
|
||||||
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
_lastBottomUpFailure = new BottomUpFailure(column, carry.Min, carry.Max, forced, unknown);
|
||||||
}
|
|
||||||
reason = propagateReason;
|
reason = propagateReason;
|
||||||
|
#else
|
||||||
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,6 +643,7 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
private static bool TryProcessTopDownBlock(
|
private static bool TryProcessTopDownBlock(
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
ReadOnlySpan<int> qOneOffsets,
|
||||||
int qOneOffsetsLength,
|
int qOneOffsetsLength,
|
||||||
@@ -593,14 +655,28 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
ref int windowEnd,
|
ref int windowEnd,
|
||||||
out CarryRange nextCarryOut,
|
out CarryRange nextCarryOut,
|
||||||
out TopDownPruneFailure? failure)
|
out TopDownPruneFailure? failure)
|
||||||
|
#else
|
||||||
|
private static bool TryProcessTopDownBlock(
|
||||||
|
ReadOnlySpan<int> qOneOffsets,
|
||||||
|
int qOneOffsetsLength,
|
||||||
|
int maxAllowedA,
|
||||||
|
int startHighColumn,
|
||||||
|
int columnCount,
|
||||||
|
CarryRange carryOut,
|
||||||
|
ref int windowStart,
|
||||||
|
ref int windowEnd,
|
||||||
|
out CarryRange nextCarryOut)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = null;
|
failure = null;
|
||||||
|
#endif
|
||||||
long carryOutMin = carryOut.Min,
|
long carryOutMin = carryOut.Min,
|
||||||
carryOutMax = carryOut.Max,
|
carryOutMax = carryOut.Max;
|
||||||
value;
|
int value;
|
||||||
long endColumn = startHighColumn - columnCount + 1;
|
int endColumn = startHighColumn - columnCount + 1;
|
||||||
|
|
||||||
long column = startHighColumn;
|
int column = startHighColumn;
|
||||||
while (column >= endColumn)
|
while (column >= endColumn)
|
||||||
{
|
{
|
||||||
// value is used as high column index
|
// value is used as high column index
|
||||||
@@ -631,9 +707,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
value = windowEnd - windowStart;
|
value = windowEnd - windowStart;
|
||||||
if (value == 0)
|
if (value == 0)
|
||||||
{
|
{
|
||||||
long lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : long.MinValue;
|
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
||||||
long lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : long.MinValue;
|
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
||||||
long chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
||||||
if (chunkEnd < endColumn)
|
if (chunkEnd < endColumn)
|
||||||
{
|
{
|
||||||
chunkEnd = endColumn;
|
chunkEnd = endColumn;
|
||||||
@@ -644,7 +720,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
|
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||||
|
#endif
|
||||||
nextCarryOut = default;
|
nextCarryOut = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -655,9 +733,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
}
|
}
|
||||||
else if (value == 1)
|
else if (value == 1)
|
||||||
{
|
{
|
||||||
long lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : long.MinValue;
|
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
||||||
long lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : long.MinValue;
|
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
||||||
long chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
||||||
if (chunkEnd < endColumn)
|
if (chunkEnd < endColumn)
|
||||||
{
|
{
|
||||||
chunkEnd = endColumn;
|
chunkEnd = endColumn;
|
||||||
@@ -668,7 +746,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
|
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||||
|
#endif
|
||||||
nextCarryOut = default;
|
nextCarryOut = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -694,7 +774,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
|
|
||||||
if (carryOutMin > carryOutMax)
|
if (carryOutMin > carryOutMax)
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||||
|
#endif
|
||||||
nextCarryOut = default;
|
nextCarryOut = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -706,26 +788,35 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long AlignUpToParity(long value, int parity) => (value & 1) == parity ? value : value + 1;
|
private static long AlignUpToParity(long value, int parity) => (value & 1L) == parity ? value : value + 1L;
|
||||||
|
private static long AlignDownToParity(long value, int parity) => (value & 1L) == parity ? value : value - 1L;
|
||||||
private static long AlignDownToParity(long value, int parity) => (value & 1) == parity ? value : value - 1;
|
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
private static bool TryTopDownCarryPrune(
|
private static bool TryTopDownCarryPrune(
|
||||||
ReadOnlySpan<int> qOneOffsets,
|
ReadOnlySpan<int> qOneOffsets,
|
||||||
int qOneOffsetsLength,
|
int qOneOffsetsLength,
|
||||||
long pLong,
|
long pLong,
|
||||||
long maxAllowedA,
|
long maxAllowedA,
|
||||||
out TopDownPruneFailure? failure)
|
out TopDownPruneFailure? failure)
|
||||||
|
#else
|
||||||
|
private static bool TryTopDownCarryPrune(
|
||||||
|
ReadOnlySpan<int> qOneOffsets,
|
||||||
|
int qOneOffsetsLength,
|
||||||
|
int pLong,
|
||||||
|
int maxAllowedA)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = null;
|
failure = null;
|
||||||
|
#endif
|
||||||
long carryOutMin = 0,
|
long carryOutMin = 0,
|
||||||
carryOutMax = 0,
|
carryOutMax = 0;
|
||||||
value;
|
int value;
|
||||||
|
|
||||||
int windowStart = qOneOffsetsLength;
|
int windowStart = qOneOffsetsLength;
|
||||||
int windowEnd = qOneOffsetsLength;
|
int windowEnd = qOneOffsetsLength;
|
||||||
|
|
||||||
long column = pLong - 1;
|
int column = pLong - 1;
|
||||||
while (column >= 0)
|
while (column >= 0)
|
||||||
{
|
{
|
||||||
// value is used as high column index
|
// value is used as high column index
|
||||||
@@ -756,9 +847,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
value = windowEnd - windowStart;
|
value = windowEnd - windowStart;
|
||||||
if (value == 0)
|
if (value == 0)
|
||||||
{
|
{
|
||||||
long lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : long.MinValue;
|
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
||||||
long lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : long.MinValue;
|
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
||||||
long chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
||||||
if (chunkEnd < 0)
|
if (chunkEnd < 0)
|
||||||
{
|
{
|
||||||
chunkEnd = 0;
|
chunkEnd = 0;
|
||||||
@@ -769,7 +860,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
|
if (!AdvanceTopDownUnknown0(ref carryOutMin, ref carryOutMax, steps))
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,9 +872,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
}
|
}
|
||||||
else if (value == 1)
|
else if (value == 1)
|
||||||
{
|
{
|
||||||
long lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : long.MinValue;
|
int lastEnd = windowEnd > 0 ? qOneOffsets[windowEnd - 1] : int.MinValue;
|
||||||
long lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : long.MinValue;
|
int lastStart = windowStart > 0 ? qOneOffsets[windowStart - 1] + maxAllowedA : int.MinValue;
|
||||||
long chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
int chunkEnd = lastEnd > lastStart ? lastEnd : lastStart;
|
||||||
if (chunkEnd < 0)
|
if (chunkEnd < 0)
|
||||||
{
|
{
|
||||||
chunkEnd = 0;
|
chunkEnd = 0;
|
||||||
@@ -792,7 +885,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
|
if (!AdvanceTopDownUnknown1(ref carryOutMin, ref carryOutMax, steps))
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,7 +912,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
|
|
||||||
if (carryOutMin > carryOutMax)
|
if (carryOutMin > carryOutMax)
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,7 +923,9 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
|
|
||||||
if (carryOutMin > 0)
|
if (carryOutMin > 0)
|
||||||
{
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
failure = new TopDownPruneFailure(0, carryOutMin, carryOutMax, 0);
|
failure = new TopDownPruneFailure(0, carryOutMin, carryOutMax, 0);
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,13 +936,13 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
while (steps > 0)
|
while (steps > 0)
|
||||||
{
|
{
|
||||||
int step = steps > 62 ? 62 : steps;
|
int step = steps > 30 ? 30 : steps;
|
||||||
long pow = 1L << step;
|
int pow = 1 << step;
|
||||||
|
|
||||||
if (carryOutMin > (long.MaxValue >> step) || carryOutMax > ((long.MaxValue - (pow - 1)) >> step))
|
if (carryOutMin > (long.MaxValue >> step) || carryOutMax > ((long.MaxValue - (pow - 1)) >> step))
|
||||||
{
|
{
|
||||||
carryOutMin = 0;
|
carryOutMin = 0;
|
||||||
carryOutMax = long.MaxValue;
|
carryOutMax = int.MaxValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,12 +958,12 @@ internal static class BitContradictionSolverWithQMultiplier
|
|||||||
{
|
{
|
||||||
while (steps > 0)
|
while (steps > 0)
|
||||||
{
|
{
|
||||||
int step = steps > 62 ? 62 : steps;
|
int step = steps > 30 ? 30 : steps;
|
||||||
long add = (1L << step) - 1L;
|
int add = (1 << step) - 1;
|
||||||
if (carryOutMin > ((long.MaxValue - add) >> step) || carryOutMax > ((long.MaxValue - add) >> step))
|
if (carryOutMin > ((int.MaxValue - add) >> step) || carryOutMax > ((int.MaxValue - add) >> step))
|
||||||
{
|
{
|
||||||
carryOutMin = 0;
|
carryOutMin = 0;
|
||||||
carryOutMax = long.MaxValue;
|
carryOutMax = int.MaxValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,452 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Block-ordered streaming divisor scan for M_p = 2^p - 1 in the family q = (2p)k + 1,
|
||||||
|
/// using a 4-bit (base-16) residue automaton.
|
||||||
|
///
|
||||||
|
/// Key design change vs column-wise branching:
|
||||||
|
/// - We always assign an a-nibble (4 planned a-bits) before stepping the 4 columns.
|
||||||
|
/// - With planned bits present, each of the 4 columns becomes deterministic (no 0/1 branching),
|
||||||
|
/// so we can test each a-nibble in O(1) per column and cache allowed masks by signatures.
|
||||||
|
///
|
||||||
|
/// This makes the "residue per block" perspective operational while staying exact for the explored tree.
|
||||||
|
/// </summary>
|
||||||
|
public static class MersenneResidueNibbleAutomatonScanner
|
||||||
|
{
|
||||||
|
public enum ScanResult
|
||||||
|
{
|
||||||
|
RuledOut = 0,
|
||||||
|
Candidate = 1,
|
||||||
|
FoundDivisor = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int MaxSupportedQBits = 8192;
|
||||||
|
|
||||||
|
// Cache allowed a-nibble masks only for moderate carry values.
|
||||||
|
// For larger carry we compute directly to avoid any risk of false pruning.
|
||||||
|
private const int CarryCacheMax = 511;
|
||||||
|
|
||||||
|
// Reuse the same block-0 filter as the original scanner: q odd and q mod 8 in {1,7}.
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsAllowedQ0Nibble(byte q0)
|
||||||
|
{
|
||||||
|
if ((q0 & 0x1) == 0) return false;
|
||||||
|
int mod8 = q0 & 0x7;
|
||||||
|
return mod8 == 0x1 || mod8 == 0x7;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct AllowedMaskKey(
|
||||||
|
int pBits,
|
||||||
|
int maxQBits,
|
||||||
|
int block,
|
||||||
|
int carry,
|
||||||
|
byte qNibble,
|
||||||
|
ulong qSig,
|
||||||
|
ulong aSig) : IEquatable<AllowedMaskKey>
|
||||||
|
{
|
||||||
|
public readonly int PBits = pBits;
|
||||||
|
public readonly int MaxQBits = maxQBits;
|
||||||
|
public readonly int Block = block;
|
||||||
|
public readonly int Carry = carry;
|
||||||
|
public readonly byte QNibble = qNibble;
|
||||||
|
public readonly ulong QSig = qSig;
|
||||||
|
public readonly ulong ASig = aSig;
|
||||||
|
|
||||||
|
public bool Equals(AllowedMaskKey other)
|
||||||
|
=> PBits == other.PBits
|
||||||
|
&& MaxQBits == other.MaxQBits
|
||||||
|
&& Block == other.Block
|
||||||
|
&& Carry == other.Carry
|
||||||
|
&& QNibble == other.QNibble
|
||||||
|
&& QSig == other.QSig
|
||||||
|
&& ASig == other.ASig;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => obj is AllowedMaskKey k && Equals(k);
|
||||||
|
public override int GetHashCode() => HashCode.Combine(PBits, MaxQBits, Block, Carry, QNibble, QSig, ASig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct State
|
||||||
|
{
|
||||||
|
public int Block;
|
||||||
|
public int BitIndex;
|
||||||
|
public int Carry;
|
||||||
|
|
||||||
|
public bool KTerminated;
|
||||||
|
public byte KFlushRemaining;
|
||||||
|
public byte PostTermZeroQNibbles;
|
||||||
|
|
||||||
|
public MersenneCombinedDivisorScannerFullStreamExtended.DynamicBitset QBits;
|
||||||
|
public MersenneCombinedDivisorScannerFullStreamExtended.ABitsWindow AWindow;
|
||||||
|
public MersenneCombinedDivisorScannerFullStreamExtended.QGenState16 QState;
|
||||||
|
|
||||||
|
public State CloneShallow() => new()
|
||||||
|
{
|
||||||
|
Block = Block,
|
||||||
|
BitIndex = BitIndex,
|
||||||
|
Carry = Carry,
|
||||||
|
KTerminated = KTerminated,
|
||||||
|
KFlushRemaining = KFlushRemaining,
|
||||||
|
PostTermZeroQNibbles = PostTermZeroQNibbles,
|
||||||
|
QBits = QBits.CloneShared(),
|
||||||
|
AWindow = AWindow.Clone(),
|
||||||
|
QState = QState
|
||||||
|
};
|
||||||
|
|
||||||
|
public static State CreateInitial(MersenneCombinedDivisorScannerFullStreamExtended.QGenState16 initQ, int maxQBits)
|
||||||
|
{
|
||||||
|
var s = new State
|
||||||
|
{
|
||||||
|
Block = initQ.NibbleIndex,
|
||||||
|
BitIndex = 0,
|
||||||
|
Carry = 0,
|
||||||
|
QBits = new MersenneCombinedDivisorScannerFullStreamExtended.DynamicBitset(maxQBits),
|
||||||
|
AWindow = new MersenneCombinedDivisorScannerFullStreamExtended.ABitsWindow(maxQBits),
|
||||||
|
QState = initQ,
|
||||||
|
KTerminated = false,
|
||||||
|
KFlushRemaining = 0,
|
||||||
|
PostTermZeroQNibbles = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
s.QBits.Set(0); // q0 = 1
|
||||||
|
s.AWindow.SetPlanned(0, 1); // a0 = 1
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to find a divisor q (within maxQBits) for M_p by streaming q via QNibbleGenerator16 and
|
||||||
|
/// validating with the deterministic planned-nibble automaton.
|
||||||
|
/// </summary>
|
||||||
|
public static ScanResult TryFindDivisorByResidueAutomaton(
|
||||||
|
ulong prime,
|
||||||
|
int maxQBits,
|
||||||
|
bool verifyWithModPow,
|
||||||
|
out BigInteger foundQ)
|
||||||
|
{
|
||||||
|
foundQ = BigInteger.Zero;
|
||||||
|
|
||||||
|
if (prime == 0) return ScanResult.RuledOut;
|
||||||
|
if (maxQBits < 2) throw new ArgumentOutOfRangeException(nameof(maxQBits));
|
||||||
|
if (maxQBits > MaxSupportedQBits) return ScanResult.Candidate;
|
||||||
|
|
||||||
|
int pBits = checked((int)prime);
|
||||||
|
int maxBlocks = (maxQBits + 3) / 4;
|
||||||
|
|
||||||
|
var qGen = new MersenneCombinedDivisorScannerFullStreamExtended.QNibbleGenerator16(prime);
|
||||||
|
|
||||||
|
// Block-ordered exploration (BFS by block index).
|
||||||
|
var openByBlock = new Dictionary<int, Queue<State>>(capacity: 4096);
|
||||||
|
var termByBlock = new Dictionary<int, Queue<State>>(capacity: 4096);
|
||||||
|
var activeBlocks = new SortedSet<int>();
|
||||||
|
|
||||||
|
void Enqueue(State s)
|
||||||
|
{
|
||||||
|
var dict = s.KTerminated ? termByBlock : openByBlock;
|
||||||
|
if (!dict.TryGetValue(s.Block, out var q))
|
||||||
|
{
|
||||||
|
q = new Queue<State>();
|
||||||
|
dict[s.Block] = q;
|
||||||
|
}
|
||||||
|
q.Enqueue(s);
|
||||||
|
activeBlocks.Add(s.Block);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryDequeue(out State s)
|
||||||
|
{
|
||||||
|
s = default;
|
||||||
|
if (activeBlocks.Count == 0) return false;
|
||||||
|
int b = activeBlocks.Min;
|
||||||
|
|
||||||
|
if (termByBlock.TryGetValue(b, out var tq) && tq.Count > 0) s = tq.Dequeue();
|
||||||
|
else if (openByBlock.TryGetValue(b, out var oq) && oq.Count > 0) s = oq.Dequeue();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activeBlocks.Remove(b);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool termEmpty = !termByBlock.TryGetValue(b, out var tq2) || tq2.Count == 0;
|
||||||
|
bool openEmpty = !openByBlock.TryGetValue(b, out var oq2) || oq2.Count == 0;
|
||||||
|
if (termEmpty && openEmpty) activeBlocks.Remove(b);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache of allowed a-nibble masks for (state signature, qNibble, carry).
|
||||||
|
var allowedMaskCache = new Dictionary<AllowedMaskKey, ushort>(capacity: 1 << 16);
|
||||||
|
|
||||||
|
// Reusable buffers.
|
||||||
|
Span<byte> qNibByKn = stackalloc byte[16];
|
||||||
|
Span<MersenneCombinedDivisorScannerFullStreamExtended.QGenState16> nextByKn = stackalloc MersenneCombinedDivisorScannerFullStreamExtended.QGenState16[16];
|
||||||
|
Span<int> countsByQn = stackalloc int[16];
|
||||||
|
|
||||||
|
var init = State.CreateInitial(MersenneCombinedDivisorScannerFullStreamExtended.QNibbleGenerator16.InitialState, maxQBits);
|
||||||
|
Enqueue(init);
|
||||||
|
|
||||||
|
while (TryDequeue(out var st))
|
||||||
|
{
|
||||||
|
if (st.Block >= maxBlocks)
|
||||||
|
{
|
||||||
|
if (FinishScanToEndDeterministic(pBits, maxQBits, ref st) && st.Carry == 0)
|
||||||
|
{
|
||||||
|
BigInteger qVal = st.QBits.ToBigInteger();
|
||||||
|
if (qVal > BigInteger.One)
|
||||||
|
{
|
||||||
|
foundQ = qVal;
|
||||||
|
if (verifyWithModPow)
|
||||||
|
{
|
||||||
|
bool ok = BigInteger.ModPow(2, prime, foundQ) == BigInteger.One;
|
||||||
|
if (!ok) return ScanResult.Candidate;
|
||||||
|
}
|
||||||
|
return ScanResult.FoundDivisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int startBit = st.Block << 2;
|
||||||
|
|
||||||
|
// Reset buffers
|
||||||
|
for (int i = 0; i < 16; i++) { qNibByKn[i] = 0xFF; nextByKn[i] = default; countsByQn[i] = 0; }
|
||||||
|
|
||||||
|
if (st.KTerminated)
|
||||||
|
{
|
||||||
|
byte kn = 0;
|
||||||
|
qGen.ComputeNext(st.QState, kn, out byte qn, out var ns);
|
||||||
|
|
||||||
|
// Advance generator state and post-termination flushing
|
||||||
|
st.QState = ns;
|
||||||
|
if (st.KFlushRemaining > 0) st.KFlushRemaining--;
|
||||||
|
|
||||||
|
ApplyQNibble(ref st, startBit, qn, maxQBits);
|
||||||
|
|
||||||
|
if (qn == 0) st.PostTermZeroQNibbles++;
|
||||||
|
else st.PostTermZeroQNibbles = 0;
|
||||||
|
|
||||||
|
int requiredZeroRun = qGen.KWinLen + 1;
|
||||||
|
if (st.KFlushRemaining == 0 && st.QState.Carry16 == 0 && st.PostTermZeroQNibbles >= requiredZeroRun)
|
||||||
|
{
|
||||||
|
// Freeze q and finish
|
||||||
|
var done = st.CloneShallow();
|
||||||
|
done.Block = maxBlocks;
|
||||||
|
Enqueue(done);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qNibByKn[kn] = qn;
|
||||||
|
nextByKn[kn] = ns;
|
||||||
|
countsByQn[qn] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qGen.ComputeAllNext(st.QState, qNibByKn, nextByKn, countsByQn);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int qnVal = 0; qnVal < 16; qnVal++)
|
||||||
|
{
|
||||||
|
if (countsByQn[qnVal] == 0) continue;
|
||||||
|
byte qn = (byte)qnVal;
|
||||||
|
|
||||||
|
if (st.Block == 0 && !IsAllowedQ0Nibble(qn))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Compute allowed a-nibbles deterministically for this (state, qn).
|
||||||
|
int sigEndBit = Math.Min(startBit + 3, pBits + maxQBits);
|
||||||
|
ulong qSig = st.QBits.Signature64WithNibbleUpTo(sigEndBit, startBit, qn, maxQBits);
|
||||||
|
ulong aSig = st.AWindow.Signature64();
|
||||||
|
|
||||||
|
ushort allowedMask;
|
||||||
|
int carryForCache = st.Carry;
|
||||||
|
if ((uint)carryForCache <= (uint)CarryCacheMax)
|
||||||
|
{
|
||||||
|
var key = new AllowedMaskKey(pBits, maxQBits, st.Block, carryForCache, qn, qSig, aSig);
|
||||||
|
if (!allowedMaskCache.TryGetValue(key, out allowedMask))
|
||||||
|
{
|
||||||
|
allowedMask = ComputeAllowedANibbleMaskDeterministic(pBits, maxQBits, st, startBit, qn);
|
||||||
|
allowedMaskCache[key] = allowedMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Large carry: compute without caching.
|
||||||
|
allowedMask = ComputeAllowedANibbleMaskDeterministic(pBits, maxQBits, st, startBit, qn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowedMask == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Expand k-nibbles mapping to this q-nibble.
|
||||||
|
for (int kn = 0; kn < 16; kn++)
|
||||||
|
{
|
||||||
|
if (qNibByKn[kn] != qn) continue;
|
||||||
|
if (st.Block == 0 && kn == 0) continue; // keep k>=1 (so q>1)
|
||||||
|
|
||||||
|
var child = st.CloneShallow();
|
||||||
|
child.QState = nextByKn[kn];
|
||||||
|
|
||||||
|
bool canTerminateHere = !st.KTerminated && kn == 0 && st.Block > 0;
|
||||||
|
|
||||||
|
ApplyQNibble(ref child, startBit, qn, maxQBits);
|
||||||
|
|
||||||
|
// normal branch
|
||||||
|
ExpandANibbles(pBits, maxQBits, maxQBits, ref child, startBit, allowedMask, Enqueue);
|
||||||
|
|
||||||
|
// terminated branch
|
||||||
|
if (canTerminateHere)
|
||||||
|
{
|
||||||
|
var termChild = st.CloneShallow();
|
||||||
|
termChild.QState = nextByKn[kn];
|
||||||
|
termChild.KTerminated = true;
|
||||||
|
termChild.KFlushRemaining = (byte)qGen.KWinLen;
|
||||||
|
termChild.PostTermZeroQNibbles = 0;
|
||||||
|
|
||||||
|
ApplyQNibble(ref termChild, startBit, qn, maxQBits);
|
||||||
|
|
||||||
|
ExpandANibbles(pBits, maxQBits, maxQBits, ref termChild, startBit, allowedMask, Enqueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScanResult.RuledOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExpandANibbles(
|
||||||
|
int pBits,
|
||||||
|
int maxQBits,
|
||||||
|
int windowBits,
|
||||||
|
ref State baseState,
|
||||||
|
int startBit,
|
||||||
|
ushort allowedMask,
|
||||||
|
Action<State> push)
|
||||||
|
{
|
||||||
|
// Iterate allowed a-nibbles.
|
||||||
|
for (int aNib = 0; aNib < 16; aNib++)
|
||||||
|
{
|
||||||
|
if ((allowedMask & (1 << aNib)) == 0) continue;
|
||||||
|
if (startBit == 0 && (aNib & 1) == 0) continue; // enforce a0=1
|
||||||
|
|
||||||
|
var st = baseState.CloneShallow();
|
||||||
|
ApplyANibblePlanned(ref st, startBit, (byte)aNib);
|
||||||
|
|
||||||
|
// Deterministically step the 4 columns (planned bits remove branching).
|
||||||
|
if (!StepColumnsForBlockDeterministic(pBits, maxQBits, ref st, startBit))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
st.Block = st.QState.NibbleIndex;
|
||||||
|
push(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort ComputeAllowedANibbleMaskDeterministic(int pBits, int maxQBits, State st, int startBit, byte qNibble)
|
||||||
|
{
|
||||||
|
// Ensure qNibble is applied to the state for reading q bits within this block.
|
||||||
|
var baseState = st.CloneShallow();
|
||||||
|
ApplyQNibble(ref baseState, startBit, qNibble, maxQBits);
|
||||||
|
|
||||||
|
ushort mask = 0;
|
||||||
|
for (int aNib = 0; aNib < 16; aNib++)
|
||||||
|
{
|
||||||
|
if (startBit == 0 && (aNib & 1) == 0) continue;
|
||||||
|
|
||||||
|
var tmp = baseState.CloneShallow();
|
||||||
|
ApplyANibblePlanned(ref tmp, startBit, (byte)aNib);
|
||||||
|
|
||||||
|
// Only test the 4 columns deterministically.
|
||||||
|
if (StepColumnsForBlockDeterministic(pBits, maxQBits, ref tmp, startBit))
|
||||||
|
mask |= (ushort)(1 << aNib);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool StepColumnsForBlockDeterministic(int pBits, int maxQBits, ref State st, int startBit)
|
||||||
|
{
|
||||||
|
int endBit = Math.Min(startBit + 3, pBits + maxQBits);
|
||||||
|
while (st.BitIndex <= endBit)
|
||||||
|
{
|
||||||
|
if (!StepSingleColumnDeterministic(pBits, maxQBits, ref st))
|
||||||
|
return false;
|
||||||
|
st.BitIndex++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FinishScanToEndDeterministic(int pBits, int maxQBits, ref State st)
|
||||||
|
{
|
||||||
|
int end = pBits + 1;
|
||||||
|
while (st.BitIndex <= end)
|
||||||
|
{
|
||||||
|
int startBit = (st.BitIndex >> 2) << 2;
|
||||||
|
if (!StepColumnsForBlockDeterministic(pBits, maxQBits, ref st, startBit))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool StepSingleColumnDeterministic(int pBits, int maxQBits, ref State st)
|
||||||
|
{
|
||||||
|
int j = st.BitIndex;
|
||||||
|
int requiredBit = (j < pBits) ? 1 : 0;
|
||||||
|
|
||||||
|
int sumOther = st.Carry;
|
||||||
|
|
||||||
|
int maxT = Math.Min(j, maxQBits - 1);
|
||||||
|
|
||||||
|
// Contributions from q_t=1 for t>0: add a_{j-t}
|
||||||
|
foreach (int t in st.QBits.EnumerateSetBitsUpTo(maxT))
|
||||||
|
{
|
||||||
|
if (t == 0) continue;
|
||||||
|
int aIndex = j - t;
|
||||||
|
if (aIndex < 0) continue;
|
||||||
|
if (st.AWindow.GetBitRelative(aIndex) != 0)
|
||||||
|
sumOther++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deterministic a_j: must be planned or already committed; if missing, treat as unknown => not deterministic
|
||||||
|
if (!st.AWindow.TryGetKnownBit(j, out int aJ))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int sum = sumOther + aJ;
|
||||||
|
if ((sum & 1) != requiredBit)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int newCarry = (requiredBit == 1) ? ((sum - 1) >> 1) : (sum >> 1);
|
||||||
|
st.Carry = newCarry;
|
||||||
|
|
||||||
|
// Commit bit into window (PushBit also clears planned for j).
|
||||||
|
st.AWindow.PushBit(j, aJ);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void ApplyQNibble(ref State st, int startBit, byte qNibble, int maxQBits)
|
||||||
|
{
|
||||||
|
for (int b = 0; b < 4; b++)
|
||||||
|
{
|
||||||
|
int bitPos = startBit + b;
|
||||||
|
if (bitPos >= maxQBits) continue;
|
||||||
|
int bit = (qNibble >> b) & 1;
|
||||||
|
if (bit != 0) st.QBits.Set(bitPos);
|
||||||
|
}
|
||||||
|
st.QBits.Set(0); // enforce q0=1
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void ApplyANibblePlanned(ref State st, int startBit, byte aNibble)
|
||||||
|
{
|
||||||
|
for (int b = 0; b < 4; b++)
|
||||||
|
{
|
||||||
|
int bitPos = startBit + b;
|
||||||
|
int bit = (aNibble >> b) & 1;
|
||||||
|
st.AWindow.SetPlanned(bitPos, bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,465 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jump-capable (power-of-two composition) residue automaton for M_p = 2^p - 1.
|
||||||
|
///
|
||||||
|
/// For a fixed candidate divisor q (bit-length <= maxQBits), this is an exact decision procedure:
|
||||||
|
/// it checks whether there exists an a-bitstream such that a*q equals 2^p-1, i.e. the output bits are
|
||||||
|
/// - 1 for columns j = 0..p-1
|
||||||
|
/// - 0 for columns j >= p (and the multiplication "drains" to zero).
|
||||||
|
///
|
||||||
|
/// No powmod2 is used.
|
||||||
|
///
|
||||||
|
/// NOTE: This algorithm is intended for your "sparse shifted-p" family:
|
||||||
|
/// q = 1 + Σ_{u in U} (p << u)
|
||||||
|
/// with small |U|, where q itself is not supplied as ulong but can be materialized as BigInteger at the end.
|
||||||
|
///
|
||||||
|
/// IMPORTANT: This implementation supports q of arbitrary size (bounded by maxQBits for the run), but
|
||||||
|
/// the state space can still be large for big maxQBits.
|
||||||
|
/// </summary>
|
||||||
|
public static class MersenneShiftedPJumpResidueScanner64
|
||||||
|
{
|
||||||
|
public enum ScanResult
|
||||||
|
{
|
||||||
|
RuledOut = 0,
|
||||||
|
FoundDivisor = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------- Bit window (immutable) ---------------------------
|
||||||
|
|
||||||
|
private sealed class BitWindow : IEquatable<BitWindow>
|
||||||
|
{
|
||||||
|
private readonly ulong[] _words; // little endian words; bit i in word i>>6
|
||||||
|
private readonly ulong _lastMask;
|
||||||
|
private readonly int _hash;
|
||||||
|
|
||||||
|
public int Bits { get; }
|
||||||
|
public int WordCount => _words.Length;
|
||||||
|
|
||||||
|
public BitWindow(int bits, bool setBit0)
|
||||||
|
{
|
||||||
|
if (bits <= 0) throw new ArgumentOutOfRangeException(nameof(bits));
|
||||||
|
Bits = bits;
|
||||||
|
_words = new ulong[(bits + 63) >> 6];
|
||||||
|
int lastBits = bits - ((_words.Length - 1) << 6);
|
||||||
|
_lastMask = lastBits == 64 ? ulong.MaxValue : ((1UL << lastBits) - 1UL);
|
||||||
|
if (setBit0) _words[0] = 1UL;
|
||||||
|
_words[^1] &= _lastMask;
|
||||||
|
_hash = ComputeHash(_words);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitWindow(int bits, ulong[] words, ulong lastMask, int hash)
|
||||||
|
{
|
||||||
|
Bits = bits;
|
||||||
|
_words = words;
|
||||||
|
_lastMask = lastMask;
|
||||||
|
_hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int GetBit(int idx)
|
||||||
|
{
|
||||||
|
if ((uint)idx >= (uint)Bits) return 0;
|
||||||
|
return (int)((_words[idx >> 6] >> (idx & 63)) & 1UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shift left by 1 within the fixed width and OR-in nextBit at position 0 (i.e. new a_j at LSB).
|
||||||
|
/// This matches the representation: bit0 = a_j, bit i = a_{j-i}.
|
||||||
|
/// </summary>
|
||||||
|
public BitWindow ShiftAddBit(int nextBit)
|
||||||
|
{
|
||||||
|
ulong carry = (uint)nextBit & 1U;
|
||||||
|
var nw = new ulong[_words.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < _words.Length; i++)
|
||||||
|
{
|
||||||
|
ulong w = _words[i];
|
||||||
|
ulong nextCarry = w >> 63;
|
||||||
|
ulong shifted = (w << 1) | carry;
|
||||||
|
nw[i] = shifted;
|
||||||
|
carry = nextCarry;
|
||||||
|
}
|
||||||
|
|
||||||
|
nw[^1] &= _lastMask;
|
||||||
|
int h = ComputeHash(nw);
|
||||||
|
return new BitWindow(Bits, nw, _lastMask, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsZero()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _words.Length; i++)
|
||||||
|
if (_words[i] != 0) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(BitWindow? other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
if (other is null) return false;
|
||||||
|
if (Bits != other.Bits) return false;
|
||||||
|
if (_hash != other._hash) return false;
|
||||||
|
if (_words.Length != other._words.Length) return false;
|
||||||
|
for (int i = 0; i < _words.Length; i++)
|
||||||
|
if (_words[i] != other._words[i]) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => obj is BitWindow bw && Equals(bw);
|
||||||
|
public override int GetHashCode() => _hash;
|
||||||
|
|
||||||
|
private static int ComputeHash(ulong[] words)
|
||||||
|
{
|
||||||
|
// FNV-1a-ish
|
||||||
|
const ulong FnvOffset = 1469598103934665603UL;
|
||||||
|
const ulong FnvPrime = 1099511628211UL;
|
||||||
|
ulong h = FnvOffset;
|
||||||
|
for (int i = 0; i < words.Length; i++)
|
||||||
|
{
|
||||||
|
h ^= words[i];
|
||||||
|
h *= FnvPrime;
|
||||||
|
}
|
||||||
|
return unchecked((int)(h ^ (h >> 32)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ DP state ------------------------------
|
||||||
|
|
||||||
|
private readonly struct State : IEquatable<State>
|
||||||
|
{
|
||||||
|
public readonly int Carry;
|
||||||
|
public readonly BitWindow AWin;
|
||||||
|
|
||||||
|
public State(int carry, BitWindow aWin)
|
||||||
|
{
|
||||||
|
Carry = carry;
|
||||||
|
AWin = aWin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(State other) => Carry == other.Carry && AWin.Equals(other.AWin);
|
||||||
|
public override bool Equals(object? obj) => obj is State s && Equals(s);
|
||||||
|
public override int GetHashCode() => HashCode.Combine(Carry, AWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ q representation ------------------------------
|
||||||
|
|
||||||
|
private sealed class QBits
|
||||||
|
{
|
||||||
|
private readonly ulong[] _words;
|
||||||
|
public int Bits { get; }
|
||||||
|
public int PopCount { get; }
|
||||||
|
|
||||||
|
public QBits(BigInteger q, int maxQBits)
|
||||||
|
{
|
||||||
|
if (maxQBits <= 0) throw new ArgumentOutOfRangeException(nameof(maxQBits));
|
||||||
|
Bits = maxQBits;
|
||||||
|
int wc = (maxQBits + 63) >> 6;
|
||||||
|
_words = new ulong[wc];
|
||||||
|
|
||||||
|
BigInteger tmp = q;
|
||||||
|
for (int i = 0; i < wc; i++)
|
||||||
|
{
|
||||||
|
_words[i] = (ulong)(tmp & ulong.MaxValue);
|
||||||
|
tmp >>= 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask out bits above maxQBits
|
||||||
|
int lastBits = maxQBits - ((wc - 1) << 6);
|
||||||
|
ulong lastMask = lastBits == 64 ? ulong.MaxValue : ((1UL << lastBits) - 1UL);
|
||||||
|
_words[^1] &= lastMask;
|
||||||
|
|
||||||
|
int pc = 0;
|
||||||
|
for (int i = 0; i < wc; i++) pc += BitOperations.PopCount(_words[i]);
|
||||||
|
PopCount = pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int GetBit(int idx)
|
||||||
|
{
|
||||||
|
if ((uint)idx >= (uint)Bits) return 0;
|
||||||
|
return (int)((_words[idx >> 6] >> (idx & 63)) & 1UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Enumerate indices of set bits (0..Bits-1).</summary>
|
||||||
|
public IEnumerable<int> EnumerateOnes()
|
||||||
|
{
|
||||||
|
for (int wi = 0; wi < _words.Length; wi++)
|
||||||
|
{
|
||||||
|
ulong w = _words[wi];
|
||||||
|
while (w != 0)
|
||||||
|
{
|
||||||
|
int tz = BitOperations.TrailingZeroCount(w);
|
||||||
|
int bit = (wi << 6) + tz;
|
||||||
|
if (bit >= Bits) yield break;
|
||||||
|
yield return bit;
|
||||||
|
w &= w - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------- One-step transition --------------------------
|
||||||
|
|
||||||
|
private static void NextStatesOneStep(
|
||||||
|
QBits qBits,
|
||||||
|
int maxOnes,
|
||||||
|
int targetBit,
|
||||||
|
in State s,
|
||||||
|
List<State> dst)
|
||||||
|
{
|
||||||
|
int sum = s.Carry;
|
||||||
|
|
||||||
|
// Σ_{i where q_i=1} a_{j-i} -- note: AWin bit i is a_{j-i}
|
||||||
|
foreach (int i in qBits.EnumerateOnes())
|
||||||
|
{
|
||||||
|
sum += s.AWin.GetBit(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sum & 1) != targetBit) return;
|
||||||
|
|
||||||
|
int newCarry = sum >> 1;
|
||||||
|
|
||||||
|
// Very conservative ceiling to avoid runaway state blow-up on junk candidates.
|
||||||
|
// For your sparse q, maxOnes is typically small.
|
||||||
|
if (newCarry > (maxOnes + 256)) return;
|
||||||
|
|
||||||
|
// branch on next a-bit (a_{j+1} ∈ {0,1})
|
||||||
|
dst.Add(new State(newCarry, s.AWin.ShiftAddBit(0)));
|
||||||
|
dst.Add(new State(newCarry, s.AWin.ShiftAddBit(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------- Jump table (targetBit=1) --------------------------
|
||||||
|
|
||||||
|
private sealed class JumpTable
|
||||||
|
{
|
||||||
|
private readonly QBits _qBits;
|
||||||
|
private readonly int _maxOnes;
|
||||||
|
private readonly List<Dictionary<State, HashSet<State>>> _pow = new();
|
||||||
|
|
||||||
|
public JumpTable(QBits qBits)
|
||||||
|
{
|
||||||
|
_qBits = qBits;
|
||||||
|
_maxOnes = qBits.PopCount;
|
||||||
|
_pow.Add(new Dictionary<State, HashSet<State>>(capacity: 1024)); // k=0
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<State> GetPow0(in State s)
|
||||||
|
{
|
||||||
|
var d0 = _pow[0];
|
||||||
|
if (d0.TryGetValue(s, out var set)) return set;
|
||||||
|
|
||||||
|
var tmp = new List<State>(2);
|
||||||
|
NextStatesOneStep(_qBits, _maxOnes, targetBit: 1, s, tmp);
|
||||||
|
|
||||||
|
set = new HashSet<State>(tmp);
|
||||||
|
d0[s] = set;
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<State> GetPowK(int k, in State s)
|
||||||
|
{
|
||||||
|
EnsurePow(k);
|
||||||
|
var dk = _pow[k];
|
||||||
|
if (dk.TryGetValue(s, out var set)) return set;
|
||||||
|
|
||||||
|
if (k == 0) return GetPow0(s);
|
||||||
|
|
||||||
|
var mid = GetPowK(k - 1, s);
|
||||||
|
var outSet = new HashSet<State>();
|
||||||
|
foreach (var m in mid)
|
||||||
|
{
|
||||||
|
foreach (var e in GetPowK(k - 1, m))
|
||||||
|
outSet.Add(e);
|
||||||
|
}
|
||||||
|
dk[s] = outSet;
|
||||||
|
return outSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsurePow(int k)
|
||||||
|
{
|
||||||
|
while (_pow.Count <= k)
|
||||||
|
_pow.Add(new Dictionary<State, HashSet<State>>(capacity: 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<State> Advance(HashSet<State> start, ulong steps)
|
||||||
|
{
|
||||||
|
if (steps == 0) return start;
|
||||||
|
|
||||||
|
int hi = 63 - BitOperations.LeadingZeroCount(steps);
|
||||||
|
|
||||||
|
var cur = start;
|
||||||
|
for (int k = 0; k <= hi; k++)
|
||||||
|
{
|
||||||
|
if (((steps >> k) & 1UL) == 0) continue;
|
||||||
|
|
||||||
|
var next = new HashSet<State>();
|
||||||
|
foreach (var s in cur)
|
||||||
|
{
|
||||||
|
foreach (var e in GetPowK(k, s))
|
||||||
|
next.Add(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = next;
|
||||||
|
if (cur.Count == 0) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MaxOnes => _maxOnes;
|
||||||
|
public QBits QBits => _qBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ Tail drain (targetBit=0) ------------------------------
|
||||||
|
|
||||||
|
private static bool CanDrainToZero(QBits qBits, int maxOnes, HashSet<State> afterMain)
|
||||||
|
{
|
||||||
|
var frontier = new HashSet<State>(afterMain);
|
||||||
|
var seen = new HashSet<State>(afterMain);
|
||||||
|
|
||||||
|
int maxSteps = qBits.Bits + 256; // conservative
|
||||||
|
|
||||||
|
for (int step = 0; step < maxSteps; step++)
|
||||||
|
{
|
||||||
|
if (frontier.Count == 0) break;
|
||||||
|
|
||||||
|
var nextFrontier = new HashSet<State>();
|
||||||
|
|
||||||
|
foreach (var s in frontier)
|
||||||
|
{
|
||||||
|
if (s.Carry == 0 && s.AWin.IsZero())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var tmp = new List<State>(2);
|
||||||
|
NextStatesOneStep(qBits, maxOnes, targetBit: 0, s, tmp);
|
||||||
|
foreach (var ns in tmp)
|
||||||
|
if (seen.Add(ns))
|
||||||
|
nextFrontier.Add(ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
frontier = nextFrontier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ Public API ------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exact decision: does there exist an a-bitstream such that a*q == 2^p-1 and no higher bits,
|
||||||
|
/// using only the automaton (no powmod2)?
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsDivisorByJumpAutomaton(ulong primeP, BigInteger q, int maxQBits)
|
||||||
|
{
|
||||||
|
if (maxQBits <= 0) throw new ArgumentOutOfRangeException(nameof(maxQBits));
|
||||||
|
if (q.Sign <= 0) return false;
|
||||||
|
if ((q & 1) == 0) return false;
|
||||||
|
if (q == BigInteger.One) return false;
|
||||||
|
|
||||||
|
// Require q to fit within maxQBits.
|
||||||
|
if (BitLength(q) > maxQBits) return false;
|
||||||
|
|
||||||
|
// quick low-bit filter: q mod 8 in {1,7} (necessary in your current search family)
|
||||||
|
int mod8 = (int)(q & 7);
|
||||||
|
if (mod8 != 1 && mod8 != 7) return false;
|
||||||
|
|
||||||
|
var qb = new QBits(q, maxQBits);
|
||||||
|
|
||||||
|
// Initial state at j=0: a0=1 => AWin bit0=1; carry=0
|
||||||
|
var initA = new BitWindow(maxQBits, setBit0: true);
|
||||||
|
var initSet = new HashSet<State> { new State(0, initA) };
|
||||||
|
|
||||||
|
var jt = new JumpTable(qb);
|
||||||
|
var afterMain = jt.Advance(initSet, primeP);
|
||||||
|
if (afterMain.Count == 0) return false;
|
||||||
|
|
||||||
|
return CanDrainToZero(qb, jt.MaxOnes, afterMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerate sparse U combinations and test q via the jump automaton.
|
||||||
|
/// Returns the found q as BigInteger (materialized) with no 64-bit limit.
|
||||||
|
/// </summary>
|
||||||
|
public static ScanResult TryFindDivisorBySparseShiftU(
|
||||||
|
ulong primeP,
|
||||||
|
int maxQBits,
|
||||||
|
int maxShiftTerms,
|
||||||
|
out BigInteger foundQ)
|
||||||
|
{
|
||||||
|
foundQ = BigInteger.Zero;
|
||||||
|
|
||||||
|
if (maxQBits <= 0) throw new ArgumentOutOfRangeException(nameof(maxQBits));
|
||||||
|
if (maxShiftTerms <= 0) throw new ArgumentOutOfRangeException(nameof(maxShiftTerms));
|
||||||
|
|
||||||
|
// Candidate shifts u are in [1, maxQBits-1]
|
||||||
|
int maxU = maxQBits - 1;
|
||||||
|
if (maxU <= 0) return ScanResult.RuledOut;
|
||||||
|
|
||||||
|
BigInteger pBI = new BigInteger(primeP);
|
||||||
|
|
||||||
|
for (int terms = 1; terms <= maxShiftTerms; terms++)
|
||||||
|
{
|
||||||
|
if (terms > maxU) break;
|
||||||
|
|
||||||
|
var comb = new int[terms];
|
||||||
|
for (int i = 0; i < terms; i++) comb[i] = i + 1;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
BigInteger q = BigInteger.One;
|
||||||
|
for (int i = 0; i < comb.Length; i++)
|
||||||
|
{
|
||||||
|
int u = comb[i];
|
||||||
|
q += (pBI << u);
|
||||||
|
|
||||||
|
// Early cut: if already exceeds bit budget, skip building further.
|
||||||
|
if (BitLength(q) > maxQBits)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitLength(q) <= maxQBits)
|
||||||
|
{
|
||||||
|
if (IsDivisorByJumpAutomaton(primeP, q, maxQBits))
|
||||||
|
{
|
||||||
|
foundQ = q;
|
||||||
|
return ScanResult.FoundDivisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// next combination
|
||||||
|
int pos = terms - 1;
|
||||||
|
while (pos >= 0 && comb[pos] == maxU - (terms - 1 - pos)) pos--;
|
||||||
|
if (pos < 0) break;
|
||||||
|
comb[pos]++;
|
||||||
|
for (int j = pos + 1; j < terms; j++)
|
||||||
|
comb[j] = comb[j - 1] + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScanResult.RuledOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------ Helpers ------------------------------
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int BitLength(in BigInteger x)
|
||||||
|
{
|
||||||
|
if (x.Sign == 0) return 0;
|
||||||
|
// BigInteger uses two's complement; for positive numbers this is OK.
|
||||||
|
byte[] bytes = x.ToByteArray(isUnsigned: true, isBigEndian: true);
|
||||||
|
if (bytes.Length == 0) return 0;
|
||||||
|
byte msb = bytes[0];
|
||||||
|
int lz = 0;
|
||||||
|
for (int b = 7; b >= 0; b--)
|
||||||
|
{
|
||||||
|
if (((msb >> b) & 1) != 0) break;
|
||||||
|
lz++;
|
||||||
|
}
|
||||||
|
return bytes.Length * 8 - lz;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,30 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||||
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
|
private static readonly ConcurrentDictionary<ulong, byte> _checkedPow2MinusOne = new();
|
||||||
private ulong _currentPrime;
|
private ulong _currentPrime;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, ulong>? _pow2DivisorPowModCache;
|
||||||
|
#if DETAILED_LOG
|
||||||
|
[ThreadStatic]
|
||||||
|
private static long _pow2CacheHits;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static long _pow2CacheMisses;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static long _pow2CacheLookups;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static long _pow2CacheEligible;
|
||||||
|
#endif
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void ResetPow2Cache()
|
||||||
|
{
|
||||||
|
_pow2DivisorPowModCache?.Clear();
|
||||||
|
#if DETAILED_LOG
|
||||||
|
_pow2CacheHits = 0;
|
||||||
|
_pow2CacheMisses = 0;
|
||||||
|
_pow2CacheLookups = 0;
|
||||||
|
_pow2CacheEligible = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if DivisorSet_BitContradiction
|
#if DivisorSet_BitContradiction
|
||||||
private static Persistence.BitContradictionStateRepository? _bitContradictionStateRepository;
|
private static Persistence.BitContradictionStateRepository? _bitContradictionStateRepository;
|
||||||
#endif
|
#endif
|
||||||
@@ -125,14 +149,15 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int GetBitLengthPortable(in BigInteger value)
|
private static int GetBitLengthPortable(in BigInteger value)
|
||||||
{
|
{
|
||||||
if (value.IsZero)
|
// if (value.IsZero)
|
||||||
{
|
// {
|
||||||
return 0;
|
// return 0;
|
||||||
}
|
// }
|
||||||
|
|
||||||
byte[] bytes = value.ToByteArray(isUnsigned: true, isBigEndian: false);
|
// byte[] bytes = value.ToByteArray(isUnsigned: true, isBigEndian: false);
|
||||||
byte msb = bytes[^1];
|
// byte msb = bytes[^1];
|
||||||
return ((bytes.Length - 1) * 8) + (BitOperations.Log2(msb) + 1);
|
// return ((bytes.Length - 1) * 8) + (BitOperations.Log2(msb) + 1);
|
||||||
|
return (int)(value.GetBitLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -152,25 +177,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bits;
|
int bits = checked((int)prime);
|
||||||
try
|
BigInteger mersenne = (BigInteger.One << bits) - BigInteger.One;
|
||||||
{
|
|
||||||
bits = checked((int)prime);
|
|
||||||
}
|
|
||||||
catch (OverflowException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger mersenne;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mersenne = (BigInteger.One << bits) - BigInteger.One;
|
|
||||||
}
|
|
||||||
catch (OutOfMemoryException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
BigInteger denominator = step;
|
BigInteger denominator = step;
|
||||||
if (denominator <= BigInteger.Zero)
|
if (denominator <= BigInteger.Zero)
|
||||||
{
|
{
|
||||||
@@ -271,6 +279,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public void ResetStateTracking()
|
public void ResetStateTracking()
|
||||||
{
|
{
|
||||||
|
ResetPow2Cache();
|
||||||
_stateCounter = 0;
|
_stateCounter = 0;
|
||||||
_pow2MinusOneChecked = false;
|
_pow2MinusOneChecked = false;
|
||||||
_currentPrime = 0UL;
|
_currentPrime = 0UL;
|
||||||
@@ -290,6 +299,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
||||||
{
|
{
|
||||||
|
ResetPow2Cache();
|
||||||
StateFilePath = stateFile;
|
StateFilePath = stateFile;
|
||||||
_lastSavedK = lastSavedK;
|
_lastSavedK = lastSavedK;
|
||||||
MinK = minK;
|
MinK = minK;
|
||||||
@@ -356,6 +366,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||||
{
|
{
|
||||||
_currentPrime = prime;
|
_currentPrime = prime;
|
||||||
|
ResetPow2Cache();
|
||||||
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
|
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
|
||||||
|
|
||||||
// if (TryFindPowerOfTwoMinusOneDivisor(prime, allowedMax, out divisor))
|
// if (TryFindPowerOfTwoMinusOneDivisor(prime, allowedMax, out divisor))
|
||||||
@@ -454,6 +465,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
return compositePow2;
|
return compositePow2;
|
||||||
#else
|
#else
|
||||||
#if DivisorSet_BitContradiction
|
#if DivisorSet_BitContradiction
|
||||||
|
BitContradictionSolverWithAMultiplier.Initialize();
|
||||||
bool compositeBitContradiction = CheckDivisorsBitContradiction(
|
bool compositeBitContradiction = CheckDivisorsBitContradiction(
|
||||||
prime,
|
prime,
|
||||||
primeDecimalMask,
|
primeDecimalMask,
|
||||||
@@ -514,7 +526,14 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
if (fits64 && composite64)
|
if (fits64 && composite64)
|
||||||
{
|
{
|
||||||
allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
||||||
composite64 = CheckDivisors64Bit(
|
// composite64 = CheckDivisors64Bit(
|
||||||
|
// prime,
|
||||||
|
// primeDecimalMask,
|
||||||
|
// allowedMax64,
|
||||||
|
// (ulong)normalizedMinK,
|
||||||
|
// out processedAll,
|
||||||
|
// out foundDivisor64);
|
||||||
|
composite64 = CheckDivisorsSequentialPow2Cache64(
|
||||||
prime,
|
prime,
|
||||||
primeDecimalMask,
|
primeDecimalMask,
|
||||||
allowedMax64,
|
allowedMax64,
|
||||||
@@ -528,7 +547,14 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
if (fits64 && allowedMax > ulong.MaxValue)
|
if (fits64 && allowedMax > ulong.MaxValue)
|
||||||
{
|
{
|
||||||
allowedMax64 = ulong.MaxValue;
|
allowedMax64 = ulong.MaxValue;
|
||||||
composite64 = CheckDivisors64Bit(
|
// composite64 = CheckDivisors64Bit(
|
||||||
|
// prime,
|
||||||
|
// primeDecimalMask,
|
||||||
|
// allowedMax64,
|
||||||
|
// (ulong)normalizedMinK,
|
||||||
|
// out processedAll,
|
||||||
|
// out foundDivisor64);
|
||||||
|
composite64 = CheckDivisorsSequentialPow2Cache64(
|
||||||
prime,
|
prime,
|
||||||
primeDecimalMask,
|
primeDecimalMask,
|
||||||
allowedMax64,
|
allowedMax64,
|
||||||
@@ -554,7 +580,15 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return CheckDivisorsLarge(
|
// return CheckDivisorsLarge(
|
||||||
|
// prime,
|
||||||
|
// primeDecimalMask,
|
||||||
|
// allowedMax,
|
||||||
|
// normalizedMinK,
|
||||||
|
// step,
|
||||||
|
// out processedAll,
|
||||||
|
// out foundDivisor);
|
||||||
|
return CheckDivisorsSequentialPow2CacheBig(
|
||||||
prime,
|
prime,
|
||||||
primeDecimalMask,
|
primeDecimalMask,
|
||||||
allowedMax,
|
allowedMax,
|
||||||
@@ -750,16 +784,23 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
// Default: frontier/windowed scan over (q,a) space.
|
// Default: frontier/windowed scan over (q,a) space.
|
||||||
MersenneCombinedDivisorScannerFullStreamExtended.QNibbleGenerator16.SelfTest(138000001);
|
MersenneCombinedDivisorScannerFullStreamExtended.QNibbleGenerator16.SelfTest(138000001);
|
||||||
// MersenneBitTreeDivisorScannerWithAMultiplier.ScanResult scanResult = MersenneBitTreeDivisorScannerWithAMultiplier.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
|
// MersenneBitTreeDivisorScannerWithAMultiplier.ScanResult scanResult = MersenneBitTreeDivisorScannerWithAMultiplier.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
|
||||||
MersenneCombinedDivisorScannerFullStreamExtended.ScanResult scanResult = MersenneCombinedDivisorScannerFullStreamExtended.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
|
// MersenneCombinedDivisorScannerFullStreamExtended.ScanResult scanResult = MersenneCombinedDivisorScannerFullStreamExtended.TryFindDivisorByStreamingScan(prime, 128, false, out BigInteger branchDivisor);
|
||||||
|
// MersenneResidueNibbleAutomatonScanner.ScanResult scanResult = MersenneResidueNibbleAutomatonScanner.TryFindDivisorByResidueAutomaton(prime, 4096, false, out BigInteger branchDivisor);
|
||||||
|
MersenneShiftedPJumpResidueScanner64.ScanResult scanResult = MersenneShiftedPJumpResidueScanner64.TryFindDivisorBySparseShiftU(prime, 4096, 10, out BigInteger branchDivisor);
|
||||||
|
|
||||||
// if (TryExactBitTreeCheck(prime, allowedMax, minK, step, out BigInteger branchDivisor))
|
// if (TryExactBitTreeCheck(prime, allowedMax, minK, step, out BigInteger branchDivisor))
|
||||||
if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.FoundDivisor)
|
// if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.FoundDivisor)
|
||||||
|
// if (scanResult == MersenneResidueNibbleAutomatonScanner.ScanResult.FoundDivisor)
|
||||||
|
if (scanResult == MersenneShiftedPJumpResidueScanner64.ScanResult.FoundDivisor)
|
||||||
{
|
{
|
||||||
foundDivisor = branchDivisor;
|
foundDivisor = branchDivisor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.Candidate)
|
else if (scanResult == MersenneShiftedPJumpResidueScanner64.ScanResult.RuledOut)
|
||||||
|
// else if (scanResult == MersenneResidueNibbleAutomatonScanner.ScanResult.Candidate)
|
||||||
|
// else if (scanResult == MersenneCombinedDivisorScannerFullStreamExtended.ScanResult.Candidate)
|
||||||
{
|
{
|
||||||
processedAll = false;
|
processedAll = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -1552,7 +1593,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
// return true;
|
// return true;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ulong exponent = effectiveExponent > 0UL ? effectiveExponent : prime;
|
// ulong exponent64 = effectiveExponent > 0UL ? effectiveExponent : prime;
|
||||||
// int pBits;
|
// int pBits;
|
||||||
// try
|
// try
|
||||||
// {
|
// {
|
||||||
@@ -2744,6 +2785,227 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsPowerOfTwo(ulong value) => value != 0 && (value & (value - 1)) == 0;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong PowModUlong(ulong exponent, ulong modulus)
|
||||||
|
{
|
||||||
|
ulong result = 1 % modulus;
|
||||||
|
ulong baseVal = 2 % modulus;
|
||||||
|
ulong exp = exponent;
|
||||||
|
while (exp > 0)
|
||||||
|
{
|
||||||
|
if ((exp & 1UL) != 0)
|
||||||
|
{
|
||||||
|
result = (ulong)(((UInt128)result * baseVal) % modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseVal = (ulong)(((UInt128)baseVal * baseVal) % modulus);
|
||||||
|
exp >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool TryGetPow2PowModCached(ulong divisor, out ulong powMod)
|
||||||
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
|
_pow2CacheLookups++;
|
||||||
|
#endif
|
||||||
|
_pow2DivisorPowModCache ??= new Dictionary<ulong, ulong>(64);
|
||||||
|
if (_pow2DivisorPowModCache.TryGetValue(divisor, out powMod))
|
||||||
|
{
|
||||||
|
#if DETAILED_LOG
|
||||||
|
_pow2CacheHits++;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DETAILED_LOG
|
||||||
|
_pow2CacheMisses++;
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void StorePow2PowModCached(ulong divisor, ulong powMod)
|
||||||
|
{
|
||||||
|
// EvenPerfectBitScanner's logic forces the divisor to be initialized here. We first get, then store.
|
||||||
|
_pow2DivisorPowModCache![divisor] = powMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private bool CheckDivisorsSequentialPow2Cache64(
|
||||||
|
ulong prime,
|
||||||
|
ushort primeDecimalMask,
|
||||||
|
ulong allowedMax,
|
||||||
|
ulong minK,
|
||||||
|
out bool processedAll,
|
||||||
|
out ulong foundDivisor)
|
||||||
|
{
|
||||||
|
processedAll = true;
|
||||||
|
foundDivisor = 0UL;
|
||||||
|
|
||||||
|
ulong step = prime << 1;
|
||||||
|
if (step == 0UL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong effectiveMax = allowedMax == 0UL ? ulong.MaxValue : allowedMax;
|
||||||
|
ulong maxKAllowed = (effectiveMax - 1UL) / step;
|
||||||
|
if (maxKAllowed < 1UL || minK > maxKAllowed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 step128 = step;
|
||||||
|
UInt128 divisor128 = (step128 * minK) + UInt128.One;
|
||||||
|
if (divisor128 > effectiveMax)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong divisor = (ulong)divisor128;
|
||||||
|
ulong currentK = minK;
|
||||||
|
var residueStepper = new MersenneDivisorResidueStepper(prime, new GpuUInt128(step), new GpuUInt128(divisor));
|
||||||
|
|
||||||
|
while (divisor <= effectiveMax)
|
||||||
|
{
|
||||||
|
if (residueStepper.IsAdmissible())
|
||||||
|
{
|
||||||
|
// Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
RecordState(currentK);
|
||||||
|
ulong powMod;
|
||||||
|
bool isPow2 = IsPowerOfTwo(currentK);
|
||||||
|
#if DETAILED_LOG
|
||||||
|
if (isPow2)
|
||||||
|
{
|
||||||
|
_pow2CacheEligible++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!isPow2 || !TryGetPow2PowModCached(divisor, out powMod))
|
||||||
|
{
|
||||||
|
powMod = PowModUlong(prime, divisor);
|
||||||
|
if (isPow2)
|
||||||
|
{
|
||||||
|
StorePow2PowModCached(divisor, powMod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (powMod == 1UL)
|
||||||
|
{
|
||||||
|
// sw.Stop();
|
||||||
|
foundDivisor = divisor;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sw.Stop();
|
||||||
|
// Console.WriteLine($"[sequential-pow2] p={prime} divisor={divisor} powmod={powMod} isPow2={isPow2} pow2Eligible={_pow2CacheEligible} lookups={_pow2CacheLookups} cacheHits={_pow2CacheHits} cacheMisses={_pow2CacheMisses} elapsed={sw.Elapsed}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentK == maxKAllowed)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
divisor += step;
|
||||||
|
currentK++;
|
||||||
|
residueStepper.Advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private bool CheckDivisorsSequentialPow2CacheBig(
|
||||||
|
ulong prime,
|
||||||
|
ushort primeDecimalMask,
|
||||||
|
BigInteger allowedMax,
|
||||||
|
BigInteger currentK,
|
||||||
|
BigInteger step,
|
||||||
|
out bool processedAll,
|
||||||
|
out BigInteger foundDivisor)
|
||||||
|
{
|
||||||
|
foundDivisor = BigInteger.Zero;
|
||||||
|
if (allowedMax.IsZero)
|
||||||
|
{
|
||||||
|
processedAll = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
processedAll = true;
|
||||||
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||||
|
if (divisor > allowedMax)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger maxKAllowed = (allowedMax - BigInteger.One) / step;
|
||||||
|
TryClampMaxKForMersenneTail(prime, step, ref maxKAllowed);
|
||||||
|
if (currentK > maxKAllowed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger remainingIterationsBig = maxKAllowed - currentK + BigInteger.One;
|
||||||
|
|
||||||
|
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
||||||
|
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
||||||
|
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
||||||
|
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
||||||
|
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
||||||
|
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
||||||
|
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
||||||
|
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
||||||
|
|
||||||
|
byte remainder10 = rem10.Initialize(divisor);
|
||||||
|
BigInteger primeBig = (BigInteger)prime;
|
||||||
|
while (remainingIterationsBig.Sign > 0)
|
||||||
|
{
|
||||||
|
bool passesSmallModuli = remainder10 == 1 || remainder10 == 7;
|
||||||
|
if (passesSmallModuli && ((primeDecimalMask >> remainder10) & 1) != 0)
|
||||||
|
{
|
||||||
|
if (rem3.NextIsNotDivisible(divisor) &&
|
||||||
|
rem7.NextIsNotDivisible(divisor) &&
|
||||||
|
rem11.NextIsNotDivisible(divisor) &&
|
||||||
|
rem13.NextIsNotDivisible(divisor) &&
|
||||||
|
rem17.NextIsNotDivisible(divisor) &&
|
||||||
|
rem19.NextIsNotDivisible(divisor))
|
||||||
|
{
|
||||||
|
byte remainder8 = rem8.ComputeNext(divisor);
|
||||||
|
if (remainder8 == 1 || remainder8 == 7)
|
||||||
|
{
|
||||||
|
RecordState(currentK);
|
||||||
|
BigInteger powResult = BigInteger.ModPow(BigIntegerNumbers.Two, primeBig, divisor);
|
||||||
|
if (powResult.IsOne)
|
||||||
|
{
|
||||||
|
foundDivisor = divisor;
|
||||||
|
processedAll = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentK++;
|
||||||
|
divisor += step;
|
||||||
|
remainder10 = rem10.ComputeNext(divisor);
|
||||||
|
|
||||||
|
remainingIterationsBig--;
|
||||||
|
if (remainingIterationsBig.IsZero)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processedAll = true;
|
||||||
|
foundDivisor = BigInteger.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#if DivisorSet_Pow2Groups
|
#if DivisorSet_Pow2Groups
|
||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||||
private bool ProcessPow2Phase(
|
private bool ProcessPow2Phase(
|
||||||
@@ -4311,6 +4573,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
|
|
||||||
#if DivisorSet_BitContradiction
|
#if DivisorSet_BitContradiction
|
||||||
private static readonly BigInteger Five = (BigInteger)5;
|
private static readonly BigInteger Five = (BigInteger)5;
|
||||||
|
private static int _consolePrintOut;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||||
private bool CheckDivisorsBitContradiction(
|
private bool CheckDivisorsBitContradiction(
|
||||||
@@ -4332,7 +4595,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
int kRem8 = (int)(currentK % 8);
|
int kRem8 = (int)(currentK % 8);
|
||||||
|
|
||||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||||
|
_qBits = new int[(int)prime];
|
||||||
Stopwatch sw = new Stopwatch();
|
Stopwatch sw = new Stopwatch();
|
||||||
|
// sw.Start();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int remainder8 = (stepMod8 * kRem8 + 1) & 7;
|
int remainder8 = (stepMod8 * kRem8 + 1) & 7;
|
||||||
@@ -4341,21 +4606,20 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
goto MOVE_NEXT;
|
goto MOVE_NEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
divisor <= ulong.MaxValue &&
|
// divisor <= ulong.MaxValue &&
|
||||||
!PrimeTesterByLastDigit.IsPrimeCpu((ulong)divisor)
|
// !PrimeTesterByLastDigit.IsPrimeCpu((ulong)divisor)
|
||||||
)
|
// )
|
||||||
{
|
// {
|
||||||
goto MOVE_NEXT;
|
// goto MOVE_NEXT;
|
||||||
}
|
// }
|
||||||
|
|
||||||
bool divides;
|
bool divides;
|
||||||
// divides = BigInteger.ModPow(BigIntegerNumbers.Two, prime, divisor) == 1;
|
// divides = BigInteger.ModPow(BigIntegerNumbers.Two, prime, divisor) == 1;
|
||||||
|
|
||||||
ulong effectiveExponent = 0UL;
|
|
||||||
sw.Reset();
|
sw.Reset();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
bool decided = TryExactBitContradictionCheck(prime, divisor, effectiveExponent, out divides);
|
bool decided = TryExactBitContradictionCheck(prime, divisor, out divides);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
if (!decided)
|
if (!decided)
|
||||||
{
|
{
|
||||||
@@ -4367,18 +4631,27 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
foundDivisor = divisor;
|
foundDivisor = divisor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
Console.WriteLine($"Prime {prime} ({sw.Elapsed}), excluded {divisor}");
|
|
||||||
}
|
|
||||||
|
|
||||||
RecordState(currentK);
|
|
||||||
MOVE_NEXT:
|
|
||||||
if (hasLimit && currentK >= maxK)
|
|
||||||
{
|
{
|
||||||
break;
|
// var printToConsole = (_consolePrintOut++) == 1;
|
||||||
|
// if (printToConsole)
|
||||||
|
// {
|
||||||
|
// sw.Stop();
|
||||||
|
Console.WriteLine($"Prime {prime} ({sw.Elapsed}: {divisor})");
|
||||||
|
_consolePrintOut = 0;
|
||||||
|
// sw.Reset();
|
||||||
|
// sw.Start();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
currentK += BigInteger.One;
|
// RecordState(currentK);
|
||||||
|
MOVE_NEXT:
|
||||||
|
// if (hasLimit && currentK >= maxK)
|
||||||
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// currentK += BigInteger.One;
|
||||||
divisor += step;
|
divisor += step;
|
||||||
kRem8 = (kRem8 + 1) & 7;
|
kRem8 = (kRem8 + 1) & 7;
|
||||||
}
|
}
|
||||||
@@ -4438,12 +4711,12 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
// out foundDivisor);
|
// out foundDivisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static int[]? _qBits;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides)
|
private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, out bool divides)
|
||||||
{
|
{
|
||||||
#if DETAILED_LOG
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
|
||||||
#endif
|
|
||||||
divides = false;
|
divides = false;
|
||||||
// const ulong DebugTopDownDivisor = 1209708008767UL;
|
// const ulong DebugTopDownDivisor = 1209708008767UL;
|
||||||
// const ulong DebugTopDownDivisor = 1007991562955719UL;
|
// const ulong DebugTopDownDivisor = 1007991562955719UL;
|
||||||
@@ -4477,8 +4750,31 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (divisor <= ulong.MaxValue)
|
||||||
|
// {
|
||||||
|
// // ulong exponent64 = effectiveExponent > 0UL ? effectiveExponent : prime;
|
||||||
|
// // ContradictionReason reason64;
|
||||||
|
// // bool decided64 = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromDivisor((ulong)divisor, exponent64, out divides, out reason64);
|
||||||
|
// // if (decided64 && divides)
|
||||||
|
// // {
|
||||||
|
// // bool structuralOk = ((divisor - BigInteger.One) % (((BigInteger)prime) << 1)) == BigInteger.Zero;
|
||||||
|
// // bool dividesExact = structuralOk && BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
|
||||||
|
// divides = BigInteger.ModPow(2, prime, divisor) == BigInteger.One;
|
||||||
|
// return true;
|
||||||
|
// // if (!dividesExact)
|
||||||
|
// // {
|
||||||
|
// // divides = false;
|
||||||
|
// // reason64 = ContradictionReason.ParityUnreachable;
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // return decided64;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Console.WriteLine($"Switching to bit-contradiction mode for {prime}");
|
||||||
// Iterative column-wise propagation without storing all columns; we only keep assigned bits of 'a'.
|
// Iterative column-wise propagation without storing all columns; we only keep assigned bits of 'a'.
|
||||||
Span<int> oneOffsets = stackalloc int[qBitLength];
|
int[] oneOffsets = _qBits;
|
||||||
|
// Span<int> oneOffsets = stackalloc int[qBitLength];
|
||||||
int oneCount = 0;
|
int oneCount = 0;
|
||||||
for (int i = 0; i < qBitLength; i++)
|
for (int i = 0; i < qBitLength; i++)
|
||||||
{
|
{
|
||||||
@@ -4495,9 +4791,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<int> oneOffsetsSlice = oneOffsets[..oneCount];
|
ReadOnlySpan<int> oneOffsetsSlice = oneOffsets[..oneCount];
|
||||||
ulong exponent = effectiveExponent > 0UL ? effectiveExponent : prime;
|
|
||||||
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(true);
|
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(true);
|
||||||
bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
|
bool decided = BitContradictionSolverWithAMultiplier.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, prime, out divides, out var reason);
|
||||||
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(false);
|
// BitContradictionSolverWithQMultiplier.SetDebugEnabled(false);
|
||||||
// bool decided = BitContradictionSolverBaseline.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
|
// bool decided = BitContradictionSolverBaseline.TryCheckDivisibilityFromOneOffsets(oneOffsetsSlice, exponent, out divides, out var reason);
|
||||||
if (decided && divides)
|
if (decided && divides)
|
||||||
@@ -4511,32 +4806,28 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
|||||||
reason = ContradictionReason.ParityUnreachable;
|
reason = ContradictionReason.ParityUnreachable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DETAILED_LOG
|
|
||||||
sw.Stop();
|
|
||||||
#endif
|
|
||||||
if (decided && !divides)
|
if (decided && !divides)
|
||||||
{
|
{
|
||||||
#if DETAILED_LOG
|
#if DETAILED_LOG
|
||||||
if (reason == ContradictionReason.ParityUnreachable)
|
if (reason == ContradictionReason.ParityUnreachable)
|
||||||
{
|
{
|
||||||
var failure = BitContradictionSolverWithQMultiplier.LastTopDownFailure;
|
// var failure = BitContradictionSolverWithQMultiplier.LastTopDownFailure;
|
||||||
if (failure.HasValue)
|
// if (failure.HasValue)
|
||||||
{
|
// {
|
||||||
var info = failure.Value;
|
// var info = failure.Value;
|
||||||
Console.WriteLine($"[bit-contradiction] top-down prune failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] unknown={info.Unknown}");
|
// Console.WriteLine($"[bit-contradiction] top-down prune failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] unknown={info.Unknown}");
|
||||||
}
|
// }
|
||||||
|
|
||||||
var bottomUp = BitContradictionSolverWithQMultiplier.LastBottomUpFailure;
|
// var bottomUp = BitContradictionSolverWithQMultiplier.LastBottomUpFailure;
|
||||||
if (bottomUp.HasValue)
|
// if (bottomUp.HasValue)
|
||||||
{
|
// {
|
||||||
var info = bottomUp.Value;
|
// var info = bottomUp.Value;
|
||||||
Console.WriteLine($"[bit-contradiction] bottom-up failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] forced={info.Forced} unknown={info.Unknown}");
|
// Console.WriteLine($"[bit-contradiction] bottom-up failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] forced={info.Forced} unknown={info.Unknown}");
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_bitContradictionStateRepository?.Upsert(prime, "excluded");
|
// _bitContradictionStateRepository?.Upsert(prime, "excluded");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DETAILED_LOG
|
#if DETAILED_LOG
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ internal static class DivisorGenerator
|
|||||||
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
private const ushort DecimalMaskWhenLastIsThree = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
private const ushort DecimalMaskWhenLastIsThree = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
private const ushort DecimalMaskWhenLastIsNine = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
private const ushort DecimalMaskWhenLastIsNine = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
|
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
|
|
||||||
static DivisorGenerator()
|
static DivisorGenerator()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace PerfectNumbers.Core;
|
|||||||
internal struct MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
|
internal struct MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
|
||||||
{
|
{
|
||||||
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
|
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
|
|
||||||
private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
|
private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
|
||||||
private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6);
|
private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6);
|
||||||
@@ -92,7 +92,7 @@ internal struct MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, i
|
|||||||
internal struct MersenneDivisorResidueStepperDescending(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
|
internal struct MersenneDivisorResidueStepperDescending(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
|
||||||
{
|
{
|
||||||
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
|
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
|
|
||||||
private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
|
private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
|
||||||
private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6);
|
private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6);
|
||||||
|
|||||||
@@ -8,10 +8,13 @@
|
|||||||
<DefineConstants>$(DefineConstants);DEVICE_CPU</DefineConstants>
|
<DefineConstants>$(DefineConstants);DEVICE_CPU</DefineConstants>
|
||||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
<CompilerGeneratedFilesOutputPath>$(IntermediateOutputPath)generated</CompilerGeneratedFilesOutputPath>
|
<CompilerGeneratedFilesOutputPath>$(IntermediateOutputPath)generated</CompilerGeneratedFilesOutputPath>
|
||||||
|
<!-- <DefineConstants>$(DefineConstants);DETAILED_LOG</DefineConstants> -->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="**/*.generated.cs" />
|
<Compile Remove="**/*.generated.cs" />
|
||||||
|
<Compile Remove="**/*.temp..cs" />
|
||||||
|
<Compile Remove="**/*.temp.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in new issue
Block a user