BitContradiction mode modified to check for contradictions from both sides.
This commit is contained in:
1 parent
458e6d14a0
commit
2b803abcac
4 files changed
+484
-201
No files matched your search
@@ -10,50 +10,46 @@ public class BitContradictionSolverTests
|
||||
[Fact]
|
||||
public void ComputeColumnBounds_TracksForcedAndPossibleOnes()
|
||||
{
|
||||
BitState[] a = [BitState.One, BitState.Unknown];
|
||||
BitState[] q = [BitState.One, BitState.One];
|
||||
bool[] a = [true, false];
|
||||
bool[] q = [true, true];
|
||||
|
||||
ColumnBounds bounds = ComputeColumnBounds(a, q, 1);
|
||||
|
||||
bounds.ForcedOnes.Should().Be(1);
|
||||
bounds.PossibleOnes.Should().Be(2);
|
||||
bounds.PossibleOnes.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPropagateCarry_FailsWhenParityUnreachable()
|
||||
{
|
||||
CarryRange carry = CarryRange.Single(0);
|
||||
|
||||
bool success = TryPropagateCarry(
|
||||
carry,
|
||||
currentCarry: CarryRange.Single(0),
|
||||
forcedOnes: 0,
|
||||
possibleOnes: 0,
|
||||
requiredResultBit: 1,
|
||||
out CarryRange next,
|
||||
out var nextCarry,
|
||||
out ContradictionReason reason);
|
||||
|
||||
success.Should().BeFalse();
|
||||
reason.Should().Be(ContradictionReason.ParityUnreachable);
|
||||
next.Min.Should().Be(0);
|
||||
next.Max.Should().Be(0);
|
||||
nextCarry.Min.Should().Be(0);
|
||||
nextCarry.Max.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPropagateCarry_ComputesNextCarryRange()
|
||||
{
|
||||
CarryRange carry = new(0, 1);
|
||||
|
||||
bool success = TryPropagateCarry(
|
||||
carry,
|
||||
currentCarry: new CarryRange(0, 1),
|
||||
forcedOnes: 1,
|
||||
possibleOnes: 3,
|
||||
requiredResultBit: 1,
|
||||
out CarryRange next,
|
||||
out var nextCarry,
|
||||
out ContradictionReason reason);
|
||||
|
||||
success.Should().BeTrue();
|
||||
reason.Should().Be(ContradictionReason.None);
|
||||
next.Min.Should().Be(0);
|
||||
next.Max.Should().Be(1);
|
||||
nextCarry.Min.Should().Be(0);
|
||||
nextCarry.Max.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.ByDivisor;
|
||||
|
||||
@@ -9,57 +10,38 @@ namespace PerfectNumbers.Core.ByDivisor;
|
||||
/// </summary>
|
||||
internal static class BitContradictionSolver
|
||||
{
|
||||
internal enum BitState
|
||||
private const int PingPongBlockColumns = 64;
|
||||
private const int TailCarryBatchColumns = 1024;
|
||||
private const int TailCarryBatchRepeats = 1;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<long, sbyte>? _aBitsBuffer;
|
||||
[ThreadStatic]
|
||||
private static TopDownPruneFailure? _lastTopDownFailure;
|
||||
|
||||
internal readonly struct TopDownPruneFailure(long column, long carryMin, long carryMax, long unknown)
|
||||
{
|
||||
Zero = 0,
|
||||
One = 1,
|
||||
Unknown = 2,
|
||||
public readonly long Column = column;
|
||||
public readonly long CarryMin = carryMin;
|
||||
public readonly long CarryMax = carryMax;
|
||||
public readonly long Unknown = unknown;
|
||||
}
|
||||
|
||||
internal readonly struct ColumnBounds
|
||||
{
|
||||
public readonly long ForcedOnes;
|
||||
public readonly long PossibleOnes;
|
||||
internal static TopDownPruneFailure? LastTopDownFailure => _lastTopDownFailure;
|
||||
|
||||
public ColumnBounds(long forcedOnes, long possibleOnes)
|
||||
{
|
||||
if (forcedOnes < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(forcedOnes));
|
||||
}
|
||||
internal readonly struct ColumnBounds(long forcedOnes, long possibleOnes)
|
||||
{
|
||||
public readonly long ForcedOnes = forcedOnes;
|
||||
public readonly long PossibleOnes = possibleOnes;
|
||||
}
|
||||
|
||||
if (possibleOnes < forcedOnes)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(possibleOnes), "possibleOnes must be >= forcedOnes.");
|
||||
}
|
||||
internal readonly struct CarryRange(long min, long max)
|
||||
{
|
||||
public readonly long Min = min;
|
||||
public readonly long Max = max;
|
||||
|
||||
ForcedOnes = forcedOnes;
|
||||
PossibleOnes = possibleOnes;
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct CarryRange
|
||||
{
|
||||
public readonly long Min;
|
||||
public readonly long Max;
|
||||
|
||||
public CarryRange(long min, long max)
|
||||
{
|
||||
if (min < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(min));
|
||||
}
|
||||
|
||||
if (max < min)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(max), "max must be >= min.");
|
||||
}
|
||||
|
||||
Min = min;
|
||||
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);
|
||||
}
|
||||
|
||||
internal enum ContradictionReason
|
||||
@@ -71,33 +53,27 @@ internal static class BitContradictionSolver
|
||||
}
|
||||
|
||||
internal static ColumnBounds ComputeColumnBounds(
|
||||
ReadOnlySpan<BitState> multiplicand,
|
||||
ReadOnlySpan<BitState> multiplier,
|
||||
ReadOnlySpan<bool> multiplicand,
|
||||
ReadOnlySpan<bool> multiplier,
|
||||
int columnIndex)
|
||||
{
|
||||
if (columnIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(columnIndex));
|
||||
}
|
||||
|
||||
long forced = 0;
|
||||
long possible = 0;
|
||||
|
||||
int multiplicandLength = multiplicand.Length;
|
||||
int multiplierLength = multiplier.Length;
|
||||
for (int i = 0; i <= columnIndex; i++)
|
||||
{
|
||||
BitState a = i < multiplicand.Length ? multiplicand[i] : BitState.Zero;
|
||||
BitState q = columnIndex - i < multiplier.Length ? multiplier[columnIndex - i] : BitState.Zero;
|
||||
|
||||
if (a == BitState.Zero || q == BitState.Zero)
|
||||
bool a = i < multiplicandLength && multiplicand[i];
|
||||
bool q = columnIndex - i < multiplierLength && multiplier[columnIndex - i];
|
||||
if (!a && !q)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (a == BitState.One && q == BitState.One)
|
||||
if (a && q)
|
||||
{
|
||||
forced++;
|
||||
possible++;
|
||||
continue;
|
||||
}
|
||||
|
||||
possible++;
|
||||
@@ -114,26 +90,21 @@ internal static class BitContradictionSolver
|
||||
out CarryRange nextCarry,
|
||||
out ContradictionReason reason)
|
||||
{
|
||||
if (forcedOnes < 0 || possibleOnes < forcedOnes)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(possibleOnes), "possibleOnes must be >= forcedOnes and both non-negative.");
|
||||
}
|
||||
|
||||
long minSum = currentCarry.Min + forcedOnes;
|
||||
long maxSum = currentCarry.Max + possibleOnes;
|
||||
int parity = requiredResultBit & 1;
|
||||
|
||||
long minAligned = AlignUpToParity(minSum, parity);
|
||||
long maxAligned = AlignDownToParity(maxSum, parity);
|
||||
minSum = AlignUpToParity(minSum, parity);
|
||||
maxSum = AlignDownToParity(maxSum, parity);
|
||||
|
||||
if (minAligned > maxAligned)
|
||||
if (minSum > maxSum)
|
||||
{
|
||||
nextCarry = default;
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return false;
|
||||
}
|
||||
|
||||
nextCarry = new CarryRange((minAligned - requiredResultBit) >> 1, (maxAligned - requiredResultBit) >> 1);
|
||||
nextCarry = new CarryRange((minSum - requiredResultBit) >> 1, (maxSum - requiredResultBit) >> 1);
|
||||
reason = ContradictionReason.None;
|
||||
return true;
|
||||
}
|
||||
@@ -149,65 +120,164 @@ internal static class BitContradictionSolver
|
||||
out ContradictionReason reason)
|
||||
{
|
||||
divides = false;
|
||||
reason = ContradictionReason.None;
|
||||
_lastTopDownFailure = null;
|
||||
|
||||
if (p == 0 || qOneOffsets.Length == 0)
|
||||
{
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return true;
|
||||
}
|
||||
// These conditions will never be true on EvenPerfectBitScanner execution path.
|
||||
// if (p == 0 || qOneOffsets.Length == 0)
|
||||
// {
|
||||
// reason = ContradictionReason.ParityUnreachable;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// While the scanner supports p >= 31, it's a tiny subset of all checked exponents. No need to support it.
|
||||
// Exact small-case evaluation to short-circuit both success and failure deterministically.
|
||||
int highestBit = qOneOffsets[^1];
|
||||
if (p <= 64 && highestBit < 63)
|
||||
{
|
||||
ulong q = 0;
|
||||
foreach (int bit in qOneOffsets)
|
||||
{
|
||||
q |= 1UL << bit;
|
||||
}
|
||||
// int highestBit = qOneOffsets[^1];
|
||||
// if (p <= 64 && highestBit < 63)
|
||||
// {
|
||||
// ulong q = 0;
|
||||
// foreach (int bit in qOneOffsets)
|
||||
// {
|
||||
// q |= 1UL << bit;
|
||||
// }
|
||||
|
||||
ulong mersenne = (1UL << (int)p) - 1UL;
|
||||
divides = q != 0 && (mersenne % q == 0);
|
||||
reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable;
|
||||
return true;
|
||||
}
|
||||
// ulong mersenne = (1UL << (int)p) - 1UL;
|
||||
// divides = q != 0 && (mersenne % q == 0);
|
||||
// reason = divides ? ContradictionReason.None : ContradictionReason.ParityUnreachable;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
int maxOffset = qOneOffsets[^1];
|
||||
int qOneOffsetsLength = qOneOffsets.Length;
|
||||
int maxOffset = qOneOffsets[qOneOffsetsLength - 1];
|
||||
if ((ulong)(maxOffset + 1) >= p)
|
||||
{
|
||||
divides = false;
|
||||
reason = ContradictionReason.TruncatedLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (qOneOffsets[0] != 0)
|
||||
// q from EvenPerfectBitScanner will always have the correct 2kp+1 form.
|
||||
// if (qOneOffsets[0] != 0)
|
||||
// {
|
||||
// reason = ContradictionReason.ParityUnreachable;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
Dictionary<long, sbyte> aBits = _aBitsBuffer ??= new Dictionary<long, sbyte>(capacity: qOneOffsetsLength << 2);
|
||||
// aBits.EnsureCapacity(qOneOffsetsLength << 2);
|
||||
aBits.Clear();
|
||||
long maxKnownA = -1L;
|
||||
long pLong = (long)p;
|
||||
long maxAllowedA = pLong - (maxOffset + 1L);
|
||||
if (maxAllowedA < 0)
|
||||
{
|
||||
reason = ContradictionReason.TruncatedLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryTopDownCarryPrune(qOneOffsets, qOneOffsetsLength, pLong, maxAllowedA, out _lastTopDownFailure))
|
||||
{
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return true;
|
||||
}
|
||||
|
||||
long lowColumn = 0;
|
||||
long highColumn = pLong - 1;
|
||||
CarryRange carryLow = CarryRange.Zero;
|
||||
CarryRange carryHigh = CarryRange.Zero;
|
||||
bool processTop = true;
|
||||
|
||||
while (lowColumn <= highColumn)
|
||||
{
|
||||
int remaining = (int)(highColumn - lowColumn + 1);
|
||||
int blockSize = remaining < PingPongBlockColumns ? remaining : PingPongBlockColumns;
|
||||
|
||||
if (processTop)
|
||||
{
|
||||
if (!TryProcessTopDownBlock(qOneOffsets, qOneOffsetsLength, maxAllowedA, highColumn, blockSize, carryHigh, out carryHigh, out _lastTopDownFailure))
|
||||
{
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return true;
|
||||
}
|
||||
|
||||
highColumn -= blockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryProcessBottomUpBlock(qOneOffsets, aBits, maxAllowedA, lowColumn, blockSize, ref carryLow, ref maxKnownA, out reason))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
lowColumn += blockSize;
|
||||
}
|
||||
|
||||
processTop = !processTop;
|
||||
}
|
||||
|
||||
long carryMin = carryLow.Min > carryHigh.Min ? carryLow.Min : carryHigh.Min;
|
||||
long carryMax = carryLow.Max < carryHigh.Max ? carryLow.Max : carryHigh.Max;
|
||||
if (carryMin > carryMax)
|
||||
{
|
||||
divides = false;
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return true;
|
||||
}
|
||||
|
||||
var aBits = new Dictionary<long, sbyte>(capacity: qOneOffsets.Length * 4);
|
||||
long carryMin = 0;
|
||||
long carryMax = 0;
|
||||
long maxKnownA = -1;
|
||||
long maxAllowedA = (long)p - (maxOffset + 1L);
|
||||
if (maxAllowedA < 0)
|
||||
divides = true;
|
||||
reason = ContradictionReason.None;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool TryCheckDivisibilityFromOneOffsets(
|
||||
ReadOnlySpan<int> qOneOffsets,
|
||||
ulong p,
|
||||
out bool divides) => TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool TryAdvanceZeroColumns(ref CarryRange carry, int columnCount)
|
||||
{
|
||||
long carryMin = carry.Min;
|
||||
long carryMax = carry.Max;
|
||||
while (columnCount > 0)
|
||||
{
|
||||
divides = false;
|
||||
reason = ContradictionReason.TruncatedLength;
|
||||
return true;
|
||||
int step = columnCount > 62 ? 62 : columnCount;
|
||||
long add = (1L << step) - 1L;
|
||||
long nextMin = (carryMin + add) >> step;
|
||||
long nextMax = carryMax >> step;
|
||||
if (nextMin > nextMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
carryMin = nextMin;
|
||||
carryMax = nextMax;
|
||||
columnCount -= step;
|
||||
}
|
||||
|
||||
for (long column = 0; column < (long)p; column++)
|
||||
{
|
||||
long forced = 0;
|
||||
long unknown = 0;
|
||||
long chosenIndex = -1;
|
||||
carry = new CarryRange(carryMin, carryMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (int offset in qOneOffsets)
|
||||
private static bool TryProcessBottomUpBlock(
|
||||
ReadOnlySpan<int> qOneOffsets,
|
||||
Dictionary<long, sbyte> aBits,
|
||||
long maxAllowedA,
|
||||
long startColumn,
|
||||
int columnCount,
|
||||
ref CarryRange carry,
|
||||
ref long maxKnownA,
|
||||
out ContradictionReason reason)
|
||||
{
|
||||
long endColumn = startColumn + columnCount;
|
||||
int qOneOffsetsLength = qOneOffsets.Length;
|
||||
for (long column = startColumn; column < endColumn; column++)
|
||||
{
|
||||
long forced = 0L;
|
||||
long unknown = 0L;
|
||||
long chosenIndex = -1L;
|
||||
|
||||
for (int offsetIndex = 0; offsetIndex < qOneOffsetsLength; offsetIndex++)
|
||||
{
|
||||
int offset = qOneOffsets[offsetIndex];
|
||||
long aIndex = column - offset;
|
||||
if (aIndex < 0)
|
||||
{
|
||||
@@ -229,29 +299,51 @@ internal static class BitContradictionSolver
|
||||
else
|
||||
{
|
||||
unknown++;
|
||||
if (chosenIndex == -1)
|
||||
if (chosenIndex == -1L)
|
||||
{
|
||||
chosenIndex = aIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long minSum = carryMin + forced;
|
||||
long maxSum = carryMax + forced + unknown;
|
||||
long minSum = carry.Min;
|
||||
long maxSum = carry.Max;
|
||||
if (unknown == 1 && ((minSum ^ maxSum) & 1) == 0)
|
||||
{
|
||||
int requiredUnknownParity = 1 ^ ((int)minSum & 1) ^ ((int)forced & 1);
|
||||
if (requiredUnknownParity != 0)
|
||||
{
|
||||
if (chosenIndex >= 0)
|
||||
{
|
||||
aBits[chosenIndex] = 1;
|
||||
forced++;
|
||||
unknown = 0;
|
||||
if (chosenIndex > maxKnownA)
|
||||
{
|
||||
maxKnownA = chosenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
minSum += forced;
|
||||
maxSum += forced + unknown;
|
||||
const int requiredParity = 1;
|
||||
|
||||
if (minSum == maxSum)
|
||||
{
|
||||
if ((minSum & 1) != requiredParity)
|
||||
{
|
||||
divides = false;
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
long nextCarry = (minSum - requiredParity) >> 1;
|
||||
carryMin = nextCarry;
|
||||
carryMax = nextCarry;
|
||||
carry = CarryRange.Single(nextCarry);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -259,8 +351,8 @@ internal static class BitContradictionSolver
|
||||
{
|
||||
if (unknown == 0)
|
||||
{
|
||||
divides = false;
|
||||
return true;
|
||||
reason = ContradictionReason.ParityUnreachable;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chosenIndex >= 0)
|
||||
@@ -276,25 +368,22 @@ internal static class BitContradictionSolver
|
||||
}
|
||||
|
||||
if (!TryPropagateCarry(
|
||||
new CarryRange(carryMin, carryMax),
|
||||
carry,
|
||||
forced,
|
||||
forced + unknown,
|
||||
requiredParity,
|
||||
out var next,
|
||||
out var propagateReason))
|
||||
{
|
||||
divides = false;
|
||||
reason = propagateReason;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
carryMin = next.Min;
|
||||
carryMax = next.Max;
|
||||
carry = next;
|
||||
}
|
||||
|
||||
if (chosenIndex >= 0 && !aBits.ContainsKey(chosenIndex))
|
||||
if (chosenIndex >= 0 && !aBits.TryAdd(chosenIndex, 1))
|
||||
{
|
||||
aBits[chosenIndex] = 1;
|
||||
if (chosenIndex > maxKnownA)
|
||||
{
|
||||
maxKnownA = chosenIndex;
|
||||
@@ -302,74 +391,155 @@ internal static class BitContradictionSolver
|
||||
}
|
||||
}
|
||||
|
||||
// Tail: keep propagating until carry collapses to zero. No fixed budget; carry halves every column when no ones remain.
|
||||
for (long column = (long)p; carryMax > 0; column++)
|
||||
{
|
||||
long forced = 0;
|
||||
long unknown = 0;
|
||||
foreach (int offset in qOneOffsets)
|
||||
{
|
||||
long aIndex = column - offset;
|
||||
if (aIndex < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (aIndex > maxAllowedA)
|
||||
{
|
||||
continue; // forced zero
|
||||
}
|
||||
|
||||
if (aBits.TryGetValue(aIndex, out sbyte aVal))
|
||||
{
|
||||
if (aVal == 1)
|
||||
{
|
||||
forced++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TryPropagateCarry(
|
||||
new CarryRange(carryMin, carryMax),
|
||||
forced,
|
||||
forced + unknown,
|
||||
requiredResultBit: 0,
|
||||
out var next,
|
||||
out var reasonTail))
|
||||
{
|
||||
divides = false;
|
||||
reason = reasonTail;
|
||||
return true;
|
||||
}
|
||||
|
||||
carryMin = next.Min;
|
||||
carryMax = next.Max;
|
||||
}
|
||||
|
||||
divides = carryMin == 0 && carryMax == 0;
|
||||
reason = divides ? ContradictionReason.None : ContradictionReason.TailNotZero;
|
||||
reason = ContradictionReason.None;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool TryCheckDivisibilityFromOneOffsets(
|
||||
private static bool TryProcessTopDownBlock(
|
||||
ReadOnlySpan<int> qOneOffsets,
|
||||
ulong p,
|
||||
out bool divides)
|
||||
int qOneOffsetsLength,
|
||||
long maxAllowedA,
|
||||
long startHighColumn,
|
||||
int columnCount,
|
||||
CarryRange carryOut,
|
||||
out CarryRange nextCarryOut,
|
||||
out TopDownPruneFailure? failure)
|
||||
{
|
||||
return TryCheckDivisibilityFromOneOffsets(qOneOffsets, p, out divides, out _);
|
||||
failure = null;
|
||||
long carryOutMin = carryOut.Min,
|
||||
carryOutMax = carryOut.Max,
|
||||
value;
|
||||
int windowStart = qOneOffsetsLength;
|
||||
int windowEnd = qOneOffsetsLength;
|
||||
long endColumn = startHighColumn - columnCount + 1;
|
||||
|
||||
for (long column = startHighColumn; column >= endColumn; column--)
|
||||
{
|
||||
// value is used as high column index
|
||||
value = column;
|
||||
while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value)
|
||||
{
|
||||
windowEnd--;
|
||||
}
|
||||
|
||||
// value is used as low column index
|
||||
value = column - maxAllowedA;
|
||||
if (value < 0)
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
|
||||
while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value)
|
||||
{
|
||||
windowStart--;
|
||||
}
|
||||
|
||||
if (windowStart > windowEnd)
|
||||
{
|
||||
windowStart = windowEnd;
|
||||
}
|
||||
|
||||
// value is unknown bits count here
|
||||
value = windowEnd - windowStart;
|
||||
// carryOutMin now becomes nextCarryMin to limit registry pressure
|
||||
carryOutMin = (carryOutMin << 1) + 1 - value;
|
||||
// carryOutMax now becomes nextCarryMax to limit registry pressure
|
||||
carryOutMax = (carryOutMax << 1) + 1;
|
||||
if (carryOutMin < 0)
|
||||
{
|
||||
carryOutMin = 0;
|
||||
}
|
||||
|
||||
if (carryOutMax < 0)
|
||||
{
|
||||
carryOutMax = 0;
|
||||
}
|
||||
|
||||
if (carryOutMin > carryOutMax)
|
||||
{
|
||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||
nextCarryOut = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nextCarryOut = new CarryRange(carryOutMin, carryOutMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static long AlignUpToParity(long value, int parity)
|
||||
{
|
||||
return (value & 1) == parity ? value : value + 1;
|
||||
}
|
||||
private static long AlignUpToParity(long value, int parity) => (value & 1) == parity ? value : value + 1;
|
||||
|
||||
private static long AlignDownToParity(long value, int parity)
|
||||
private static long AlignDownToParity(long value, int parity) => (value & 1) == parity ? value : value - 1;
|
||||
|
||||
private static bool TryTopDownCarryPrune(
|
||||
ReadOnlySpan<int> qOneOffsets,
|
||||
int qOneOffsetsLength,
|
||||
long pLong,
|
||||
long maxAllowedA,
|
||||
out TopDownPruneFailure? failure)
|
||||
{
|
||||
return (value & 1) == parity ? value : value - 1;
|
||||
failure = null;
|
||||
long carryOutMin = 0,
|
||||
carryOutMax = 0,
|
||||
value;
|
||||
int windowStart = qOneOffsetsLength;
|
||||
int windowEnd = qOneOffsetsLength;
|
||||
|
||||
for (long column = pLong - 1; column >= 0; column--)
|
||||
{
|
||||
// value is used as high column index
|
||||
value = column;
|
||||
while (windowEnd > 0 && qOneOffsets[windowEnd - 1] > value)
|
||||
{
|
||||
windowEnd--;
|
||||
}
|
||||
|
||||
// value is used as low column index
|
||||
value = column - maxAllowedA;
|
||||
if (value < 0)
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
|
||||
while (windowStart > 0 && qOneOffsets[windowStart - 1] >= value)
|
||||
{
|
||||
windowStart--;
|
||||
}
|
||||
|
||||
if (windowStart > windowEnd)
|
||||
{
|
||||
windowStart = windowEnd;
|
||||
}
|
||||
|
||||
// value is used as unknown bits count here
|
||||
value = windowEnd - windowStart;
|
||||
// carryOutMin now becomes nextCarryMin to limit registry pressure
|
||||
carryOutMin = (carryOutMin << 1) + 1 - value;
|
||||
// carryOutMax now becomes nextCarryMax to limit registry pressure
|
||||
carryOutMax = (carryOutMax << 1) + 1;
|
||||
if (carryOutMin < 0)
|
||||
{
|
||||
carryOutMin = 0;
|
||||
}
|
||||
|
||||
if (carryOutMax < 0)
|
||||
{
|
||||
carryOutMax = 0;
|
||||
}
|
||||
|
||||
if (carryOutMin > carryOutMax)
|
||||
{
|
||||
failure = new TopDownPruneFailure(column, carryOutMin, carryOutMax, value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (carryOutMin > 0)
|
||||
{
|
||||
failure = new TopDownPruneFailure(0, carryOutMin, carryOutMax, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
|
||||
namespace PerfectNumbers.Core.ByDivisor;
|
||||
|
||||
public static class BitContradictionTailCarryLut8
|
||||
{
|
||||
private const int Rows = 8;
|
||||
private const int Columns = 8;
|
||||
private const int WindowBits = Rows + Columns - 1;
|
||||
private const int CarryStates = Rows + 1;
|
||||
private const byte FullMask = (1 << Rows) - 1;
|
||||
|
||||
private static ushort[]? _table;
|
||||
private static readonly ushort[]?[] _tablesByMask = new ushort[(1 << Rows)][];
|
||||
|
||||
public static void WarmUp()
|
||||
{
|
||||
_table ??= BuildTable(FullMask);
|
||||
}
|
||||
|
||||
public static void WarmUpAll()
|
||||
{
|
||||
for (int mask = 0; mask < (1 << Rows); mask++)
|
||||
{
|
||||
_ = GetTable((byte)mask);
|
||||
}
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<ushort> Table => _table ?? throw new InvalidOperationException("BitContradictionTailCarryLut8 not initialized.");
|
||||
|
||||
public static ReadOnlySpan<ushort> GetTable(byte rowMask)
|
||||
{
|
||||
ushort[]? table = _tablesByMask[rowMask];
|
||||
if (table is null)
|
||||
{
|
||||
table = BuildTable(rowMask);
|
||||
_tablesByMask[rowMask] = table;
|
||||
if (rowMask == FullMask)
|
||||
{
|
||||
_table ??= table;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private static ushort[] BuildTable(byte rowMask)
|
||||
{
|
||||
int entries = (1 << WindowBits) * CarryStates;
|
||||
var table = new ushort[entries];
|
||||
int entryIndex = 0;
|
||||
|
||||
for (int window = 0; window < (1 << WindowBits); window++)
|
||||
{
|
||||
for (int carryIn = 0; carryIn < CarryStates; carryIn++)
|
||||
{
|
||||
int carry = carryIn;
|
||||
ushort result = 0;
|
||||
|
||||
for (int column = 0; column < Columns; column++)
|
||||
{
|
||||
int ones = 0;
|
||||
for (int shift = 0; shift < Rows; shift++)
|
||||
{
|
||||
if (((rowMask >> shift) & 1) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowIndex = column - shift + (Rows - 1);
|
||||
if ((uint)windowIndex < WindowBits && ((window >> windowIndex) & 1) != 0)
|
||||
{
|
||||
ones++;
|
||||
}
|
||||
}
|
||||
|
||||
int sum = carry + ones;
|
||||
result |= (ushort)((sum & 1) << column);
|
||||
carry = sum >> 1;
|
||||
}
|
||||
|
||||
table[entryIndex++] = (ushort)(result | (carry << 8));
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// #define DivisorSet_BitContradiction
|
||||
#define DivisorSet_BitTree
|
||||
// #define DivisorSet_BitTree
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -1396,6 +1397,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryExactBitTreeCheck(ulong exponent, BigInteger divisor, out bool divides)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
int qBitLength = GetBitLengthPortable(divisor);
|
||||
int aBitLength = GetBitLengthPortable(exponent);
|
||||
|
||||
@@ -1501,11 +1503,13 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
||||
return true;
|
||||
}
|
||||
}
|
||||
sw.Stop();
|
||||
|
||||
ArrayPool<byte>.Shared.Return(aWindowArray);
|
||||
|
||||
divides = carry == 0;
|
||||
Console.WriteLine($"[bittree] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}");
|
||||
var elapsed = sw.Elapsed;
|
||||
Console.WriteLine($"[bittree ({elapsed})] Prime={exponent}, Divisor={divisor} Decided=True Divides={divides}");
|
||||
if (!divides)
|
||||
{
|
||||
string message = $"[bittree] p={exponent} ruled out divisor={divisor} (parity/carry exhaustion)";
|
||||
@@ -4317,10 +4321,18 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
||||
BigInteger currentK = minK;
|
||||
bool hasLimit = !allowedMax.IsZero;
|
||||
BigInteger maxK = hasLimit ? (allowedMax - BigInteger.One) / step : BigInteger.Zero;
|
||||
int stepMod8 = (int)(step % 8);
|
||||
int kRem8 = (int)(currentK % 8);
|
||||
|
||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||
while (true)
|
||||
{
|
||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||
int remainder8 = (stepMod8 * kRem8 + 1) & 7;
|
||||
if (remainder8 != 1 && remainder8 != 7)
|
||||
{
|
||||
goto MOVE_NEXT;
|
||||
}
|
||||
|
||||
if (
|
||||
divisor <= ulong.MaxValue &&
|
||||
!PrimeTesterByLastDigit.IsPrimeCpu((ulong)divisor)
|
||||
@@ -4422,6 +4434,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
||||
}
|
||||
|
||||
currentK += BigInteger.One;
|
||||
divisor += step;
|
||||
kRem8 = (kRem8 + 1) & 7;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -4482,7 +4496,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryExactBitContradictionCheck(ulong prime, BigInteger divisor, ulong effectiveExponent, out bool divides)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
divides = false;
|
||||
const ulong DebugTopDownDivisor = 1209708008767UL;
|
||||
|
||||
if (prime == 0 || divisor <= BigInteger.One)
|
||||
{
|
||||
@@ -4542,14 +4558,27 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
|
||||
}
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
if (decided && !divides)
|
||||
{
|
||||
if (reason == BitContradictionSolver.ContradictionReason.ParityUnreachable &&
|
||||
divisor <= ulong.MaxValue &&
|
||||
(ulong)divisor == DebugTopDownDivisor)
|
||||
{
|
||||
var failure = BitContradictionSolver.LastTopDownFailure;
|
||||
if (failure.HasValue)
|
||||
{
|
||||
var info = failure.Value;
|
||||
Console.WriteLine($"[bit-contradiction] top-down prune failed at column={info.Column} carry=[{info.CarryMin},{info.CarryMax}] unknown={info.Unknown}");
|
||||
}
|
||||
}
|
||||
|
||||
string message = $"[bit-contradiction] p={prime} ruled out divisor={divisor} ({reason})";
|
||||
Console.WriteLine(message);
|
||||
// Console.WriteLine(message);
|
||||
_bitContradictionStateRepository?.Upsert(prime, message);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[bitcontradiction] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides}");
|
||||
Console.WriteLine($"[bitcontradiction ({sw.Elapsed})] Prime={prime}, Divisor={divisor} Decided={decided} Divides={divides} Reason={reason}");
|
||||
return decided;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in new issue
Block a user