diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 122610a749c..a2bece21df0 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -126,7 +126,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; if (pc != NO_PIECE) board[y + 1][x + 4] = PieceToChar[pc]; - if (value != VALUE_NONE) + if (is_valid(value)) format_cp_compact(value, &board[y + 2][x + 2], pos); }; diff --git a/src/score.cpp b/src/score.cpp index 292f53406e2..179796d2080 100644 --- a/src/score.cpp +++ b/src/score.cpp @@ -29,7 +29,7 @@ namespace Stockfish { Score::Score(Value v, const Position& pos) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + if (!is_decisive(v)) { score = InternalUnits{UCIEngine::to_cp(v, pos)}; } diff --git a/src/search.cpp b/src/search.cpp index 560b031bae7..94d10378b61 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -388,7 +388,7 @@ void Search::Worker::iterative_deepening() { // if we would have had time to fully search other root-moves. Thus // we suppress this output and below pick a proven score/PV for this // thread (from the previous iteration). - && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) + && !(threads.abortedSearch && is_loss(rootMoves[0].uciScore))) main_manager()->pv(*this, threads, tt, rootDepth); if (threads.stop) @@ -401,7 +401,7 @@ void Search::Worker::iterative_deepening() { // We make sure not to pick an unproven mated-in score, // in case this thread prematurely stopped search (aborted-search). if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE - && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + && is_loss(rootMoves[0].score)) { // Bring the last best move to the front for best thread selection. Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)]( @@ -635,7 +635,7 @@ Value Search::Worker::search( // At non-PV nodes we check for an early TT cutoff if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) - && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() + && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) && (cutNode == (ttData.value >= beta) || depth > 8)) { @@ -732,7 +732,7 @@ Value Search::Worker::search( { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; - if (unadjustedStaticEval == VALUE_NONE) + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); else if (PvNode) @@ -742,7 +742,7 @@ Value Search::Worker::search( to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // ttValue can be used as a better position evaluation (~7 Elo) - if (ttData.value != VALUE_NONE + if (is_valid(ttData.value) && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttData.value; } @@ -782,7 +782,7 @@ Value Search::Worker::search( if (eval < alpha - 469 - 307 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); - if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + if (value < alpha && !is_decisive(value)) return value; } @@ -792,8 +792,7 @@ Value Search::Worker::search( && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 290 >= beta - && eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY - && eval < VALUE_TB_WIN_IN_MAX_PLY) + && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; improving |= ss->staticEval >= beta + 100; @@ -801,7 +800,7 @@ Value Search::Worker::search( // Step 9. Null move search with verification search (~35 Elo) if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta && ss->staticEval >= beta - 21 * depth + 421 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) + && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); @@ -819,7 +818,7 @@ Value Search::Worker::search( pos.undo_null_move(); // Do not return unproven mate or TB scores - if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) + if (nullValue >= beta && !is_win(nullValue)) { if (thisThread->nmpMinPly || depth < 16) return nullValue; @@ -858,12 +857,12 @@ Value Search::Worker::search( // returns a value much above beta, we can (almost) safely prune the previous move. probCutBeta = beta + 187 - 53 * improving - 27 * opponentWorsening; if (!PvNode && depth > 3 - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt // probCut there and in further interactions with transposition table cutoff // depth is set to depth - 3 because probCut search has depth set to depth - 4 // but we also do a move before it. So effective depth is equal to depth - 3. - && !(ttData.depth >= depth - 3 && ttData.value != VALUE_NONE && ttData.value < probCutBeta)) + && !(ttData.depth >= depth - 3 && is_valid(ttData.value) && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); @@ -915,8 +914,7 @@ Value Search::Worker::search( // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval, tt.generation()); - return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) - : value; + return is_decisive(value) ? value : value - (probCutBeta - beta); } } @@ -928,8 +926,7 @@ Value Search::Worker::search( // Step 12. A small Probcut idea (~4 Elo) probCutBeta = beta + 417; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY) + && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -992,7 +989,7 @@ Value Search::Worker::search( // Step 14. Pruning at shallow depth (~120 Elo). // Depth conditions are important for mate finding. - if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) if (moveCount >= futility_move_count(improving, depth)) @@ -1042,8 +1039,8 @@ Value Search::Worker::search( // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { - if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY - && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) + if (bestValue <= futilityValue + && !is_decisive(bestValue) && !is_win(futilityValue)) bestValue = futilityValue; continue; } @@ -1075,8 +1072,8 @@ Value Search::Worker::search( if (!rootNode && move == ttData.move && !excludedMove && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv - && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3) + && is_valid(ttData.value) && !is_decisive(ttData.value) + && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; @@ -1103,7 +1100,7 @@ Value Search::Worker::search( // over the original beta, we assume this expected cut-node is not // singular (multiple moves fail high), and we can prune the whole // subtree by returning a softbound. - else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + else if (value >= beta && !is_decisive(value)) return value; // Negative extensions @@ -1313,9 +1310,8 @@ Value Search::Worker::search( // In case we have an alternative move equal in eval to the current bestmove, // promote it to bestmove by pretending it just exceeds alpha (but not beta). - int inc = - (value == bestValue && (int(nodes) & 15) == 0 && ss->ply + 2 >= thisThread->rootDepth - && std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY); + int inc = (value == bestValue && ss->ply + 2 >= thisThread->rootDepth + && (int(nodes) & 15) == 0 && !is_win(std::abs(value) + 1)); if (value + inc > bestValue) { @@ -1337,7 +1333,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 14 && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + if (depth > 2 && depth < 14 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1365,8 +1361,8 @@ Value Search::Worker::search( assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + if (!PvNode && bestValue >= beta && !is_decisive(bestValue) + && !is_decisive(beta) && !is_decisive(alpha)) bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) @@ -1518,7 +1514,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // At non-PV nodes we check for an early TT cutoff if (!PvNode && ttData.depth >= DEPTH_QS - && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() + && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttData.value; @@ -1532,14 +1528,14 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; - if (unadjustedStaticEval == VALUE_NONE) + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // ttValue can be used as a better position evaluation (~13 Elo) - if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY + if (is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttData.value; } @@ -1557,7 +1553,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) + if (!is_decisive(bestValue)) bestValue = (bestValue + beta) / 2; if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, @@ -1598,10 +1594,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) moveCount++; // Step 6. Pruning - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) + if (!is_loss(bestValue) && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) && move.type_of() != PROMOTION) { if (moveCount > 2) @@ -1688,7 +1684,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return mated_in(ss->ply); // Plies to mate from the root } - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta) + if (!is_decisive(bestValue) && bestValue >= beta) bestValue = (3 * bestValue + beta) / 4; // Save gathered info in transposition table. The static evaluation @@ -1726,11 +1722,7 @@ namespace { // Adjusts a mate or TB score from "plies to mate from the root" to // "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. -Value value_to_tt(Value v, int ply) { - - assert(v != VALUE_NONE); - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; -} +Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } // Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition @@ -1740,11 +1732,11 @@ Value value_to_tt(Value v, int ply) { // graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { - if (v == VALUE_NONE) + if (!is_valid(v)) return VALUE_NONE; // handle TB win or better - if (v >= VALUE_TB_WIN_IN_MAX_PLY) + if (is_win(v)) { // Downgrade a potentially false mate score if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) @@ -1758,7 +1750,7 @@ Value value_from_tt(Value v, int ply, int r50c) { } // handle TB loss or worse - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) + if (is_loss(v)) { // Downgrade a potentially false mate score. if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) @@ -2097,7 +2089,7 @@ void SearchManager::pv(Search::Worker& worker, bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact // Potentially correct and extend the PV, and in exceptional cases v - if (std::abs(v) >= VALUE_TB_WIN_IN_MAX_PLY && std::abs(v) < VALUE_MATE_IN_MAX_PLY + if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY && ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact)) syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v); diff --git a/src/thread.cpp b/src/thread.cpp index b5d51594c54..5f73771effa 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -329,13 +329,13 @@ Thread* ThreadPool::get_best_thread() const { const auto bestThreadMoveVote = votes[bestThreadPV[0]]; const auto newThreadMoveVote = votes[newThreadPV[0]]; - const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; - const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + const bool bestThreadInProvenWin = is_win(bestThreadScore); + const bool newThreadInProvenWin = is_win(newThreadScore); const bool bestThreadInProvenLoss = - bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore); const bool newThreadInProvenLoss = - newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore); // We make sure not to pick a thread with truncated principal variation const bool betterVotingValue = @@ -355,7 +355,7 @@ Thread* ThreadPool::get_best_thread() const { bestThread = th.get(); } else if (newThreadInProvenWin || newThreadInProvenLoss - || (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY + || (!is_loss(newThreadScore) && (newThreadMoveVote > bestThreadMoveVote || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) bestThread = th.get(); diff --git a/src/types.h b/src/types.h index b12491d6cdd..5644460113f 100644 --- a/src/types.h +++ b/src/types.h @@ -155,6 +155,21 @@ constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + +constexpr bool is_valid(Value value) { return value != VALUE_NONE; } + +constexpr bool is_win(Value value) { + assert(is_valid(value)); + return value >= VALUE_TB_WIN_IN_MAX_PLY; +} + +constexpr bool is_loss(Value value) { + assert(is_valid(value)); + return value <= VALUE_TB_LOSS_IN_MAX_PLY; +} + +constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } + // In the code, we make the assumption that these values // are such that non_pawn_material() can be used to uniquely // identify the material on the board.