diff --git a/src/Lynx.Dev/Program.cs b/src/Lynx.Dev/Program.cs index d825e3ef9..bea4d0971 100644 --- a/src/Lynx.Dev/Program.cs +++ b/src/Lynx.Dev/Program.cs @@ -1083,19 +1083,19 @@ static void TesSize(int size) //var entry = transpositionTable.ProbeHash(position, maxDepth: 5, depth: 3, alpha: 1, beta: 2); transpositionTable.RecordHash(position, position.StaticEvaluation().Score, depth: 5, ply: 3, score: +19, nodeType: NodeType.Alpha, move: 1234); - var entry = transpositionTable.ProbeHash(position, depth: 5, ply: 3, alpha: 20, beta: 30); + var entry = transpositionTable.ProbeHash(position, ply: 3); Console.WriteLine(entry); // Expected 20 transpositionTable.RecordHash(position, position.StaticEvaluation().Score, depth: 5, ply: 3, score: +21, nodeType: NodeType.Alpha, move: 1234); - entry = transpositionTable.ProbeHash(position, depth: 5, ply: 3, alpha: 20, beta: 30); + entry = transpositionTable.ProbeHash(position, ply: 3); Console.WriteLine(entry); // Expected 12_345_678 transpositionTable.RecordHash(position, position.StaticEvaluation().Score, depth: 5, ply: 3, score: +29, nodeType: NodeType.Beta, move: 1234); - entry = transpositionTable.ProbeHash(position, depth: 5, ply: 3, alpha: 20, beta: 30); + entry = transpositionTable.ProbeHash(position, ply: 3); Console.WriteLine(entry); // Expected 12_345_678 transpositionTable.RecordHash(position, position.StaticEvaluation().Score, depth: 5, ply: 3, score: +31, nodeType: NodeType.Beta, move: 1234); - entry = transpositionTable.ProbeHash(position, depth: 5, ply: 3, alpha: 20, beta: 30); + entry = transpositionTable.ProbeHash(position, ply: 3); Console.WriteLine(entry); // Expected 30 } diff --git a/src/Lynx/Model/TranspositionTable.cs b/src/Lynx/Model/TranspositionTable.cs index b1ab33e9a..5a1d337a6 100644 --- a/src/Lynx/Model/TranspositionTable.cs +++ b/src/Lynx/Model/TranspositionTable.cs @@ -59,7 +59,7 @@ public void PrefetchTTEntry(Position position) /// /// Ply [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Score, ShortMove BestMove, NodeType NodeType, int RawScore, int StaticEval) ProbeHash(Position position, int depth, int ply, int alpha, int beta) + public (int Score, ShortMove BestMove, NodeType NodeType, int StaticEval, int Depth) ProbeHash(Position position, int ply) { var ttIndex = CalculateTTIndex(position.UniqueIdentifier); var entry = _tt[ttIndex]; @@ -69,25 +69,11 @@ public void PrefetchTTEntry(Position position) return (EvaluationConstants.NoHashEntry, default, default, default, default); } - var type = entry.Type; - var rawScore = entry.Score; - var score = EvaluationConstants.NoHashEntry; + // We want to translate the checkmate position relative to the saved node to our root position from which we're searching + // If the recorded score is a checkmate in 3 and we are at depth 5, we want to read checkmate in 8 + var recalculatedScore = RecalculateMateScores(entry.Score, ply); - if (entry.Depth >= depth) - { - var recalculatedScore = RecalculateMateScores(rawScore, ply); - - if (type == NodeType.Exact - || (type == NodeType.Alpha && recalculatedScore <= alpha) - || (type == NodeType.Beta && recalculatedScore >= beta)) - { - // We want to translate the checkmate position relative to the saved node to our root position from which we're searching - // If the recorded score is a checkmate in 3 and we are at depth 5, we want to read checkmate in 8 - score = recalculatedScore; - } - } - - return (score, entry.Move, entry.Type, rawScore, entry.StaticEval); + return (recalculatedScore, entry.Move, entry.Type, entry.StaticEval, entry.Depth); } /// diff --git a/src/Lynx/Model/TranspositionTableElement.cs b/src/Lynx/Model/TranspositionTableElement.cs index b484eb355..450d37179 100644 --- a/src/Lynx/Model/TranspositionTableElement.cs +++ b/src/Lynx/Model/TranspositionTableElement.cs @@ -7,8 +7,17 @@ public enum NodeType : byte #pragma warning restore S4022 // Enumerations should have "Int32" storage { Unknown, // Making it 0 instead of -1 because of default struct initialization + Exact, + + /// + /// UpperBound + /// Alpha, + + /// + /// LowerBound + /// Beta } diff --git a/src/Lynx/Search/NegaMax.cs b/src/Lynx/Search/NegaMax.cs index 0d8d0cd06..31b1c304b 100644 --- a/src/Lynx/Search/NegaMax.cs +++ b/src/Lynx/Search/NegaMax.cs @@ -40,17 +40,22 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool cutnode, Cance ShortMove ttBestMove = default; NodeType ttElementType = default; int ttScore = default; - int ttRawScore = default; int ttStaticEval = int.MinValue; + int ttDepth = default; Debug.Assert(!pvNode || !cutnode); if (!isRoot) { - (ttScore, ttBestMove, ttElementType, ttRawScore, ttStaticEval) = _tt.ProbeHash(position, depth, ply, alpha, beta); + (ttScore, ttBestMove, ttElementType, ttStaticEval, ttDepth) = _tt.ProbeHash(position, ply); // TT cutoffs - if (!pvNode && ttScore != EvaluationConstants.NoHashEntry) + if (!pvNode + && ttScore != EvaluationConstants.NoHashEntry + && ttDepth >= depth + && (ttElementType == NodeType.Exact + || (ttElementType == NodeType.Alpha && ttScore <= alpha) + || (ttElementType == NodeType.Beta && ttScore >= beta))) { return ttScore; } @@ -124,9 +129,9 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool cutnode, Cance // If the score is outside what the current bounds are, but it did match flag and depth, // then we can trust that this score is more accurate than the current static evaluation, // and we can update our static evaluation for better accuracy in pruning - if (ttElementType != default && ttElementType != (ttRawScore > staticEval ? NodeType.Alpha : NodeType.Beta)) + if (ttElementType != default && ttElementType != (ttScore > staticEval ? NodeType.Alpha : NodeType.Beta)) { - staticEval = ttRawScore; + staticEval = ttScore; } bool isNotGettingCheckmated = staticEval > EvaluationConstants.NegativeCheckmateDetectionLimit; @@ -190,7 +195,7 @@ private int NegaMax(int depth, int ply, int alpha, int beta, bool cutnode, Cance && staticEvalBetaDiff >= 0 && !parentWasNullMove && phase > 2 // Zugzwang risk reduction: pieces other than pawn presents - && (ttElementType != NodeType.Alpha || ttRawScore >= beta)) // TT suggests NMP will fail: entry must not be a fail-low entry with a score below beta - Stormphrax and Ethereal + && (ttElementType != NodeType.Alpha || ttScore >= beta)) // TT suggests NMP will fail: entry must not be a fail-low entry with a score below beta - Stormphrax and Ethereal { var nmpReduction = Configuration.EngineSettings.NMP_BaseDepthReduction + ((depth + Configuration.EngineSettings.NMP_DepthIncrement) / Configuration.EngineSettings.NMP_DepthDivisor) // Clarity @@ -531,16 +536,27 @@ public int QuiescenceSearch(int ply, int alpha, int beta, CancellationToken canc var nextPvIndex = PVTable.Indexes[ply + 1]; _pVTable[pvIndex] = _defaultMove; // Nulling the first value before any returns - var ttProbeResult = _tt.ProbeHash(position, 0, ply, alpha, beta); - if (ttProbeResult.Score != EvaluationConstants.NoHashEntry) + var ttProbeResult = _tt.ProbeHash(position, ply); + var ttScore = ttProbeResult.Score; + var ttNodeType = ttProbeResult.NodeType; + var ttHit = ttNodeType != NodeType.Unknown; + + // QS TT cutoff + Debug.Assert(ttProbeResult.Depth >= 0, "Assertion failed", "We would need to add it as a TT cutoff condition"); + + if (ttHit + && (ttNodeType == NodeType.Exact + || (ttNodeType == NodeType.Alpha && ttScore <= alpha) + || (ttNodeType == NodeType.Beta && ttScore >= beta))) { - return ttProbeResult.Score; + return ttScore; } + ShortMove ttBestMove = ttProbeResult.BestMove; _maxDepthReached[ply] = ply; - var staticEval = ttProbeResult.NodeType != NodeType.Unknown + var staticEval = ttHit // TODO check if static eval ? ttProbeResult.StaticEval : position.StaticEvaluation(Game.HalfMovesWithoutCaptureOrPawnMove).Score; diff --git a/tests/Lynx.Test/Model/TranspositionTableTest.cs b/tests/Lynx.Test/Model/TranspositionTableTest.cs index c3b1bc22c..f09ff4fcd 100644 --- a/tests/Lynx.Test/Model/TranspositionTableTest.cs +++ b/tests/Lynx.Test/Model/TranspositionTableTest.cs @@ -24,11 +24,9 @@ public void RecalculateMateScores(int evaluation, int depth, int expectedEvaluat Assert.AreEqual(expectedEvaluation, TranspositionTable.RecalculateMateScores(evaluation, depth)); } - [TestCase(+19, NodeType.Alpha, +20, +30, +19)] - [TestCase(+21, NodeType.Alpha, +20, +30, NoHashEntry)] - [TestCase(+29, NodeType.Beta, +20, +30, NoHashEntry)] - [TestCase(+31, NodeType.Beta, +20, +30, +31)] - public void RecordHash_ProbeHash(int recordedEval, NodeType recordNodeType, int probeAlpha, int probeBeta, int expectedProbeEval) + [TestCase(+19, NodeType.Alpha, +19)] + [TestCase(+31, NodeType.Beta, +31)] + public void RecordHash_ProbeHash(int recordedEval, NodeType recordNodeType, int expectedProbeEval) { var position = new Position(Constants.InitialPositionFEN); var transpositionTable = new TranspositionTable(); @@ -37,7 +35,7 @@ public void RecordHash_ProbeHash(int recordedEval, NodeType recordNodeType, int transpositionTable.RecordHash(position, staticEval, depth: 5, ply: 3, score: recordedEval, nodeType: recordNodeType, move: 1234); - var ttEntry = transpositionTable.ProbeHash(position, depth: 5, ply: 3, alpha: probeAlpha, beta: probeBeta); + var ttEntry = transpositionTable.ProbeHash(position, ply: 3); Assert.AreEqual(expectedProbeEval, ttEntry.Score); Assert.AreEqual(staticEval, ttEntry.StaticEval); } @@ -52,7 +50,7 @@ public void RecordHash_ProbeHash_CheckmateSameDepth(int recordedEval) transpositionTable.RecordHash(position, recordedEval, depth: 10, ply: sharedDepth, score: recordedEval, nodeType: NodeType.Exact, move: 1234); - var ttEntry = transpositionTable.ProbeHash(position, depth: 7, ply: sharedDepth, alpha: 50, beta: 100); + var ttEntry = transpositionTable.ProbeHash(position, ply: sharedDepth); Assert.AreEqual(recordedEval, ttEntry.Score); Assert.AreEqual(recordedEval, ttEntry.StaticEval); } @@ -68,7 +66,7 @@ public void RecordHash_ProbeHash_CheckmateDifferentDepth(int recordedEval, int r transpositionTable.RecordHash(position, recordedEval, depth: 10, ply: recordedDeph, score: recordedEval, nodeType: NodeType.Exact, move: 1234); - var ttEntry = transpositionTable.ProbeHash(position, depth: 7, ply: probeDepth, alpha: 50, beta: 100); + var ttEntry = transpositionTable.ProbeHash(position, ply: probeDepth); Assert.AreEqual(expectedProbeEval, ttEntry.Score); Assert.AreEqual(recordedEval, ttEntry.StaticEval); }