From 97d2cc9a9c1c4b6ff1b470676fa18c7fc6509886 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 14 Oct 2018 20:33:24 +0200 Subject: [PATCH] Randomize draw eval The patch adds a small random component (+-1) to VALUE_DRAW for the evaluation of draw positions (mostly 3folds). This random component is not static, but potentially different for each visit of the node (hence derived from the node counter). The effect is that in positions with many 3fold draw lines, different lines are followed at each iteration. This keeps the search much more dynamic, as opposed to being locked to one particular 3fold. An example of a position where master suffers from 3fold-blindness and this patch solves quickly is the famous TCEC game 53: FEN: 3r2k1/pr6/1p3q1p/5R2/3P3p/8/5RP1/3Q2K1 b - - 0 51 master doesn't see that this is a lost position (draw eval up to depth 50) as Qf6-e6 d4-d5 (found by patch at depth 23) leads to a loss. The 3fold-blindness is more important at longer TC, the patch was yellow STC and LTC, but passed VLTC: STC LLR: -2.95 (-2.94,2.94) [0.00,5.00] Total: 46328 W: 10048 L: 9953 D: 26327 http://tests.stockfishchess.org/tests/view/5b9c0ca20ebc592cf275f7c7 LTC LLR: -2.95 (-2.94,2.94) [0.00,5.00] Total: 54663 W: 8938 L: 8846 D: 36879 http://tests.stockfishchess.org/tests/view/5b9ca1610ebc592cf27601d3 VLTC LLR: 2.95 (-2.94,2.94) [0.00,5.00] Total: 31789 W: 4512 L: 4284 D: 22993 http://tests.stockfishchess.org/tests/view/5b9d1a670ebc592cf276076d Credit to @crossbr for pointing to this problem repeatedly, and giving the hint that many draw lines are typical in those situations. Bench: 4756639 --- src/search.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5445256f..7dc67488 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -85,6 +85,13 @@ namespace { return d > 17 ? 0 : 29 * d * d + 138 * d - 134; } + // Add a small random component to draw evaluations to keep search dynamic + // and to avoid 3fold-blindness. + Value value_draw(Depth depth, Thread* thisThread) { + return depth < 4 ? VALUE_DRAW + : VALUE_DRAW + Value(2 * (thisThread->nodes.load(std::memory_order_relaxed) % 2) - 1); + } + // Skill structure is used to implement strength limit struct Skill { explicit Skill(int l) : level(l) {} @@ -535,7 +542,7 @@ namespace { && !rootNode && pos.has_game_cycle(ss->ply)) { - alpha = VALUE_DRAW; + alpha = value_draw(depth, pos.this_thread()); if (alpha >= beta) return alpha; } @@ -584,7 +591,8 @@ namespace { if ( Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) + : value_draw(depth, pos.this_thread()); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because -- 2.39.2