// History and stats update bonus, based on depth
int stat_bonus(Depth d) {
- return std::min((9 * d + 270) * d - 311 , 2145);
+ return std::min((8 * d + 240) * d - 276 , 1907);
}
// Add a small random component to draw evaluations to avoid 3-fold blindness
multiPV = std::min(multiPV, rootMoves.size());
- complexityAverage.set(202, 1);
+ complexityAverage.set(174, 1);
trend = SCORE_ZERO;
optimism[ us] = Value(39);
int failedHighCnt = 0;
while (true)
{
- Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter);
+ // Adjust the effective depth searched, but ensuring at least one effective increment for every
+ // four searchAgain steps (see issue #2717).
+ Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
// Bring the best move to the front. It is critical that sorting
double reduction = (1.56 + mainThread->previousTimeReduction) / (2.20 * timeReduction);
double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size();
int complexity = mainThread->complexityAverage.value();
- double complexPosition = std::clamp(1.0 + (complexity - 326) / 1618.1, 0.5, 1.5);
+ double complexPosition = std::min(1.0 + (complexity - 277) / 1819.1, 1.5);
double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition;
Move ttMove, move, excludedMove, bestMove;
Depth extension, newDepth;
Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
- bool givesCheck, improving, didLMR, priorCapture;
- bool capture, doFullDepthSearch, moveCountPruning, ttCapture;
+ bool givesCheck, improving, priorCapture, singularQuietLMR;
+ bool capture, moveCountPruning, ttCapture;
Piece movedPiece;
int moveCount, captureCount, quietCount, improvement, complexity;
// Step 1. Initialize node
Thread* thisThread = pos.this_thread();
- thisThread->depth = depth;
ss->inCheck = pos.checkers();
priorCapture = pos.captured_piece();
Color us = pos.side_to_move();
// At non-PV nodes we check for an early TT cutoff
if ( !PvNode
&& ss->ttHit
- && tte->depth() > depth - ((int)thisThread->id() & 0x1)
+ && tte->depth() > depth - (tte->bound() == BOUND_EXACT)
&& ttValue != VALUE_NONE // Possible in case of TT access race
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
{
// Never assume anything about values stored in TT
ss->staticEval = eval = tte->eval();
if (eval == VALUE_NONE)
- ss->staticEval = eval = evaluate(pos);
-
- // Randomize draw evaluation
- if (eval == VALUE_DRAW)
- eval = value_draw(thisThread);
+ ss->staticEval = eval = evaluate(pos, &complexity);
+ else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost
+ complexity = abs(ss->staticEval - pos.psq_eg_stm());
// ttValue can be used as a better position evaluation (~4 Elo)
if ( ttValue != VALUE_NONE
}
else
{
- ss->staticEval = eval = evaluate(pos);
+ ss->staticEval = eval = evaluate(pos, &complexity);
// Save static evaluation into transposition table
if (!excludedMove)
tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
}
+ thisThread->complexityAverage.update(complexity);
+
// Use static evaluation difference to improve quiet move ordering (~3 Elo)
if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture)
{
improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
: (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval
: 175;
-
improving = improvement > 0;
- complexity = abs(ss->staticEval - (us == WHITE ? eg_value(pos.psq_score()) : -eg_value(pos.psq_score())));
-
- thisThread->complexityAverage.update(complexity);
// Step 7. Razoring.
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
&& (ss-1)->statScore < 14695
&& eval >= beta
&& eval >= ss->staticEval
- && ss->staticEval >= beta - 15 * depth - improvement / 15 + 198 + complexity / 28
+ && ss->staticEval >= beta - 15 * depth - improvement / 15 + 201 + complexity / 24
&& !excludedMove
&& pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
assert(eval - beta >= 0);
// Null move dynamic reduction based on depth, eval and complexity of position
- Depth R = std::min(int(eval - beta) / 147, 5) + depth / 3 + 4 - (complexity > 753);
+ Depth R = std::min(int(eval - beta) / 147, 5) + depth / 3 + 4 - (complexity > 650);
ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
assert(probCutBeta < VALUE_INFINITE);
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, depth - 3, &captureHistory);
- bool ttPv = ss->ttPv;
- ss->ttPv = false;
while ((move = mp.next_move()) != MOVE_NONE)
if (move != excludedMove && pos.legal(move))
if (value >= probCutBeta)
{
// Save ProbCut data into transposition table
- tte->save(posKey, value_to_tt(value, ss->ply), ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval);
+ tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval);
return value;
}
}
- ss->ttPv = ttPv;
}
// Step 11. If the position is not in TT, decrease depth by 3.
ss->killers);
value = bestValue;
- moveCountPruning = false;
+ moveCountPruning = singularQuietLMR = false;
// Indicate PvNodes that will probably fail low if the node was searched
// at a depth equal or greater than the current depth, and the result of this search was a fail low.
&& history < -3875 * (depth - 1))
continue;
- history += thisThread->mainHistory[us][from_to(move)];
+ history += 2 * thisThread->mainHistory[us][from_to(move)];
// Futility pruning: parent node (~9 Elo)
if ( !ss->inCheck
if (value < singularBeta)
{
extension = 1;
+ singularQuietLMR = !ttCapture;
// Avoid search explosion by limiting the number of double extensions
if ( !PvNode
// Step 16. Make the move
pos.do_move(move, st, givesCheck);
- bool doDeeperSearch = false;
-
// Step 17. Late moves reduction / extension (LMR, ~98 Elo)
// We use various heuristics for the sons of a node after the first son has
// been searched. In general we would like to reduce them, but there are many
r--;
// Increase reduction for cut nodes (~3 Elo)
- if (cutNode && move != ss->killers[0])
+ if (cutNode)
r += 2;
// Increase reduction if ttMove is a capture (~3 Elo)
if (PvNode)
r -= 1 + 15 / (3 + depth);
+ // Decrease reduction if ttMove has been singularly extended (~1 Elo)
+ if (singularQuietLMR)
+ r--;
+
// Increase reduction if next ply has a lot of fail high else reset count to 0
if ((ss+1)->cutoffCnt > 3 && !PvNode)
r++;
- ss->statScore = thisThread->mainHistory[us][from_to(move)]
+ ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)]
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
r -= ss->statScore / 15914;
- // In general we want to cap the LMR depth search at newDepth. But if reductions
- // are really negative and movecount is low, we allow this move to be searched
- // deeper than the first move (this may lead to hidden double extensions).
- int deeper = r >= -1 ? 0
- : moveCount <= 4 ? 2
- : PvNode || cutNode ? 1
- : 0;
-
- Depth d = std::clamp(newDepth - r, 1, newDepth + deeper);
+ // In general we want to cap the LMR depth search at newDepth, but when
+ // reduction is negative, we allow this move a limited search extension
+ // beyond the first move depth. This may lead to hidden double extensions.
+ Depth d = std::clamp(newDepth - r, 1, newDepth + 1);
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
- // If the son is reduced and fails high it will be re-searched at full depth
- doFullDepthSearch = value > alpha && d < newDepth;
- doDeeperSearch = value > (alpha + 78 + 11 * (newDepth - d));
- didLMR = true;
- }
- else
- {
- doFullDepthSearch = !PvNode || moveCount > 1;
- didLMR = false;
- }
-
- // Step 18. Full depth search when LMR is skipped or fails high
- if (doFullDepthSearch)
- {
- value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode);
-
- // If the move passed LMR update its stats
- if (didLMR)
+ // Do full depth search when reduced LMR search fails high
+ if (value > alpha && d < newDepth)
{
+ const bool doDeeperSearch = value > (alpha + 78 + 11 * (newDepth - d));
+ value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode);
+
int bonus = value > alpha ? stat_bonus(newDepth)
: -stat_bonus(newDepth);
}
}
+ // Step 18. Full depth search when LMR is skipped
+ else if (!PvNode || moveCount > 1)
+ {
+ value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
+ }
+
// For PV nodes only, do a full PV search on the first move or after a fail
// high (in the latter case search only if value < beta), otherwise let the
// parent node fail low with value <= alpha and try another move.