mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Merge commit '60351b9df901ff5278f208a9cf3a40059ff54832' into cluster
This is the last commit before there are more conflicts.
This commit is contained in:
+149
-151
@@ -20,18 +20,18 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <ratio>
|
||||
|
||||
#include "cluster.h"
|
||||
#include "evaluate.h"
|
||||
@@ -47,7 +47,6 @@
|
||||
#include "thread.h"
|
||||
#include "timeman.h"
|
||||
#include "tt.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
@@ -68,7 +67,7 @@ namespace {
|
||||
|
||||
// Futility margin
|
||||
Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) {
|
||||
Value futilityMult = 122 - 37 * noTtCutNode;
|
||||
Value futilityMult = 118 - 33 * noTtCutNode;
|
||||
Value improvingDeduction = improving * futilityMult * 2;
|
||||
Value worseningDeduction = oppWorsening * futilityMult / 3;
|
||||
|
||||
@@ -82,61 +81,46 @@ constexpr int futility_move_count(bool improving, Depth depth) {
|
||||
// Add correctionHistory value to raw staticEval and guarantee evaluation
|
||||
// does not hit the tablebase range.
|
||||
Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) {
|
||||
auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index<Correction>(pos)];
|
||||
v += 66 * cv / 512;
|
||||
const Color us = pos.side_to_move();
|
||||
const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)];
|
||||
const auto mcv = w.materialCorrectionHistory[us][material_index(pos)];
|
||||
const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)];
|
||||
const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)];
|
||||
const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)];
|
||||
const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)];
|
||||
const auto cv =
|
||||
(98198 * pcv + 68968 * mcv + 54353 * macv + 85174 * micv + 85581 * (wnpcv + bnpcv)) / 2097152;
|
||||
v += cv;
|
||||
return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||
}
|
||||
|
||||
// History and stats update bonus, based on depth
|
||||
int stat_bonus(Depth d) { return std::min(190 * d - 108, 1596); }
|
||||
int stat_bonus(Depth d) { return std::min(179 * d - 108, 1598); }
|
||||
|
||||
// History and stats update malus, based on depth
|
||||
int stat_malus(Depth d) { return std::min(736 * d - 268, 2044); }
|
||||
int stat_malus(Depth d) { return std::min(820 * d - 261, 2246); }
|
||||
|
||||
// Add a small random component to draw evaluations to avoid 3-fold blindness
|
||||
Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }
|
||||
|
||||
// Skill structure is used to implement strength limit. If we have a UCI_Elo,
|
||||
// we convert it to an appropriate skill level, anchored to the Stash engine.
|
||||
// This method is based on a fit of the Elo results for games played between
|
||||
// Stockfish at various skill levels and various versions of the Stash engine.
|
||||
// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately
|
||||
// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2
|
||||
struct Skill {
|
||||
Skill(int skill_level, int uci_elo) {
|
||||
if (uci_elo)
|
||||
{
|
||||
double e = double(uci_elo - 1320) / (3190 - 1320);
|
||||
level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
|
||||
}
|
||||
else
|
||||
level = double(skill_level);
|
||||
}
|
||||
bool enabled() const { return level < 20.0; }
|
||||
bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
|
||||
Move pick_best(const RootMoves&, size_t multiPV);
|
||||
|
||||
double level;
|
||||
Move best = Move::none();
|
||||
};
|
||||
|
||||
Value value_to_tt(Value v, int ply);
|
||||
Value value_from_tt(Value v, int ply, int r50c);
|
||||
void update_pv(Move* pv, Move move, const Move* childPv);
|
||||
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
|
||||
void update_killer(Stack* ss, Move move);
|
||||
void update_quiet_histories(
|
||||
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
|
||||
void update_quiet_stats(
|
||||
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
|
||||
void update_all_stats(const Position& pos,
|
||||
Stack* ss,
|
||||
Search::Worker& workerThread,
|
||||
Move bestMove,
|
||||
Square prevSq,
|
||||
ValueList<Move, 32>& quietsSearched,
|
||||
ValueList<Move, 32>& capturesSearched,
|
||||
Depth depth);
|
||||
void update_quiet_histories(const Position& pos,
|
||||
Stack* ss,
|
||||
Search::Worker& workerThread,
|
||||
Move move,
|
||||
int bonus,
|
||||
bool rootNode);
|
||||
void update_all_stats(const Position& pos,
|
||||
Stack* ss,
|
||||
Search::Worker& workerThread,
|
||||
Move bestMove,
|
||||
Square prevSq,
|
||||
ValueList<Move, 32>& quietsSearched,
|
||||
ValueList<Move, 32>& capturesSearched,
|
||||
Depth depth,
|
||||
bool rootNode);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -156,6 +140,12 @@ Search::Worker::Worker(SharedState& sharedState,
|
||||
clear();
|
||||
}
|
||||
|
||||
void Search::Worker::ensure_network_replicated() {
|
||||
// Access once to force lazy initialization.
|
||||
// We do this because we want to avoid initialization during search.
|
||||
(void) (networks[numaAccessToken]);
|
||||
}
|
||||
|
||||
void Search::Worker::start_searching() {
|
||||
|
||||
// Non-main threads go directly to iterative_deepening()
|
||||
@@ -193,7 +183,7 @@ void Search::Worker::start_searching() {
|
||||
} // Busy wait for a stop or a ponder reset
|
||||
|
||||
// Stop the threads if not already stopped (also raise the stop if
|
||||
// "ponderhit" just reset threads.ponder).
|
||||
// "ponderhit" just reset threads.ponder)
|
||||
threads.stop = true;
|
||||
|
||||
// Signal and synchronize all other ranks
|
||||
@@ -289,7 +279,7 @@ void Search::Worker::iterative_deepening() {
|
||||
|
||||
// Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):
|
||||
// (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),
|
||||
// (ss + 2) is needed for initialization of cutOffCnt and killers.
|
||||
// (ss + 2) is needed for initialization of cutOffCnt.
|
||||
Stack stack[MAX_PLY + 10] = {};
|
||||
Stack* ss = stack + 7;
|
||||
|
||||
@@ -325,6 +315,8 @@ void Search::Worker::iterative_deepening() {
|
||||
|
||||
int searchAgainCounter = 0;
|
||||
|
||||
rootHistory.fill(0);
|
||||
|
||||
// Iterative deepening loop until requested to stop or the target depth is reached
|
||||
while (++rootDepth < MAX_PLY && !threads.stop
|
||||
&& !(limits.depth && mainThread && Distributed::is_root() && rootDepth > limits.depth))
|
||||
@@ -360,12 +352,12 @@ void Search::Worker::iterative_deepening() {
|
||||
|
||||
// Reset aspiration window starting size
|
||||
Value avg = rootMoves[pvIdx].averageScore;
|
||||
delta = 5 + avg * avg / 13424;
|
||||
delta = 5 + avg * avg / 11797;
|
||||
alpha = std::max(avg - delta, -VALUE_INFINITE);
|
||||
beta = std::min(avg + delta, VALUE_INFINITE);
|
||||
|
||||
// Adjust optimism based on root move's averageScore (~4 Elo)
|
||||
optimism[us] = 125 * avg / (std::abs(avg) + 89);
|
||||
optimism[us] = 132 * avg / (std::abs(avg) + 89);
|
||||
optimism[~us] = -optimism[us];
|
||||
|
||||
// Start with a small aspiration window and, in the case of a fail
|
||||
@@ -555,18 +547,24 @@ void Search::Worker::iterative_deepening() {
|
||||
// Reset histories, usually before a new game
|
||||
void Search::Worker::clear() {
|
||||
mainHistory.fill(0);
|
||||
captureHistory.fill(-700);
|
||||
pawnHistory.fill(-1188);
|
||||
correctionHistory.fill(0);
|
||||
rootHistory.fill(0);
|
||||
captureHistory.fill(-753);
|
||||
pawnHistory.fill(-1152);
|
||||
pawnCorrectionHistory.fill(0);
|
||||
materialCorrectionHistory.fill(0);
|
||||
majorPieceCorrectionHistory.fill(0);
|
||||
minorPieceCorrectionHistory.fill(0);
|
||||
nonPawnCorrectionHistory[WHITE].fill(0);
|
||||
nonPawnCorrectionHistory[BLACK].fill(0);
|
||||
|
||||
for (bool inCheck : {false, true})
|
||||
for (StatsType c : {NoCaptures, Captures})
|
||||
for (auto& to : continuationHistory[inCheck][c])
|
||||
for (auto& h : to)
|
||||
h->fill(-58);
|
||||
h->fill(-678);
|
||||
|
||||
for (size_t i = 1; i < reductions.size(); ++i)
|
||||
reductions[i] = int((18.62 + std::log(size_t(options["Threads"])) / 2) * std::log(i));
|
||||
reductions[i] = int((18.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i));
|
||||
|
||||
refreshTable.clear(networks[numaAccessToken]);
|
||||
}
|
||||
@@ -579,6 +577,7 @@ Value Search::Worker::search(
|
||||
|
||||
constexpr bool PvNode = nodeType != NonPV;
|
||||
constexpr bool rootNode = nodeType == Root;
|
||||
const bool allNode = !(PvNode || cutNode);
|
||||
|
||||
// Dive into quiescence search when the depth reaches zero
|
||||
if (depth <= 0)
|
||||
@@ -657,7 +656,6 @@ Value Search::Worker::search(
|
||||
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
||||
|
||||
bestMove = Move::none();
|
||||
(ss + 1)->killer = Move::none();
|
||||
(ss + 2)->cutoffCnt = 0;
|
||||
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
|
||||
ss->statScore = 0;
|
||||
@@ -688,7 +686,7 @@ Value Search::Worker::search(
|
||||
{
|
||||
// Bonus for a quiet ttMove that fails high (~2 Elo)
|
||||
if (!ttCapture)
|
||||
update_quiet_stats(pos, ss, *this, ttData.move, stat_bonus(depth));
|
||||
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth), rootNode);
|
||||
|
||||
// Extra penalty for early quiet moves of
|
||||
// the previous ply (~1 Elo on STC, ~2 Elo on LTC)
|
||||
@@ -805,7 +803,7 @@ Value Search::Worker::search(
|
||||
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
|
||||
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
|
||||
{
|
||||
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1664, 1471) + 752;
|
||||
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1641, 1423) + 760;
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus;
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
@@ -823,7 +821,7 @@ Value Search::Worker::search(
|
||||
// Step 7. Razoring (~1 Elo)
|
||||
// If eval is really low, check with qsearch if we can exceed alpha. If the
|
||||
// search suggests we cannot exceed alpha, return a speculative fail low.
|
||||
if (eval < alpha - 494 - 290 * depth * depth)
|
||||
if (eval < alpha - 501 - 272 * depth * depth)
|
||||
{
|
||||
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
|
||||
if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
@@ -834,22 +832,21 @@ Value Search::Worker::search(
|
||||
// The depth condition is important for mate finding.
|
||||
if (!ss->ttPv && depth < 13
|
||||
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
|
||||
- (ss - 1)->statScore / 260
|
||||
- (ss - 1)->statScore / 272
|
||||
>= beta
|
||||
&& eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
&& eval < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
return beta + (eval - beta) / 3;
|
||||
|
||||
// Step 9. Null move search with verification search (~35 Elo)
|
||||
if (cutNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14389
|
||||
&& eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove
|
||||
&& pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly
|
||||
&& beta > VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta
|
||||
&& ss->staticEval >= beta - 23 * depth + 400 && !excludedMove && pos.non_pawn_material(us)
|
||||
&& ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY)
|
||||
{
|
||||
assert(eval - beta >= 0);
|
||||
|
||||
// Null move dynamic reduction based on depth and eval
|
||||
Depth R = std::min(int(eval - beta) / 202, 6) + depth / 3 + 5;
|
||||
Depth R = std::min(int(eval - beta) / 209, 6) + depth / 3 + 5;
|
||||
|
||||
ss->currentMove = Move::null();
|
||||
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
|
||||
@@ -898,7 +895,7 @@ Value Search::Worker::search(
|
||||
// Step 11. ProbCut (~10 Elo)
|
||||
// If we have a good enough capture (or queen promotion) and a reduced search
|
||||
// returns a value much above beta, we can (almost) safely prune the previous move.
|
||||
probCutBeta = beta + 184 - 53 * improving;
|
||||
probCutBeta = beta + 189 - 53 * improving;
|
||||
if (!PvNode && depth > 3
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
// If value from transposition table is lower than probCutBeta, don't attempt
|
||||
@@ -968,9 +965,10 @@ Value Search::Worker::search(
|
||||
moves_loop: // When in check, search starts here
|
||||
|
||||
// Step 12. A small Probcut idea (~4 Elo)
|
||||
probCutBeta = beta + 390;
|
||||
probCutBeta = beta + 379;
|
||||
if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
|
||||
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
return probCutBeta;
|
||||
|
||||
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
|
||||
@@ -981,8 +979,8 @@ moves_loop: // When in check, search starts here
|
||||
(ss - 6)->continuationHistory};
|
||||
|
||||
|
||||
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory,
|
||||
contHist, &thisThread->pawnHistory, ss->killer);
|
||||
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->rootHistory,
|
||||
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, rootNode);
|
||||
|
||||
value = bestValue;
|
||||
|
||||
@@ -1051,15 +1049,15 @@ moves_loop: // When in check, search starts here
|
||||
// Futility pruning for captures (~2 Elo)
|
||||
if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
|
||||
{
|
||||
Value futilityValue = ss->staticEval + 285 + 251 * lmrDepth
|
||||
Value futilityValue = ss->staticEval + 300 + 238 * lmrDepth
|
||||
+ PieceValue[capturedPiece] + captHist / 7;
|
||||
if (futilityValue <= alpha)
|
||||
continue;
|
||||
}
|
||||
|
||||
// SEE based pruning for captures and checks (~11 Elo)
|
||||
int seeHist = std::clamp(captHist / 32, -182 * depth, 166 * depth);
|
||||
if (!pos.see_ge(move, -168 * depth - seeHist))
|
||||
int seeHist = std::clamp(captHist / 32, -159 * depth, 160 * depth);
|
||||
if (!pos.see_ge(move, -167 * depth - seeHist))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -1070,15 +1068,15 @@ moves_loop: // When in check, search starts here
|
||||
+ thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()];
|
||||
|
||||
// Continuation history based pruning (~2 Elo)
|
||||
if (history < -4165 * depth)
|
||||
if (history < -4071 * depth)
|
||||
continue;
|
||||
|
||||
history += 2 * thisThread->mainHistory[us][move.from_to()];
|
||||
|
||||
lmrDepth += history / 3853;
|
||||
lmrDepth += history / 3653;
|
||||
|
||||
Value futilityValue =
|
||||
ss->staticEval + (bestValue < ss->staticEval - 51 ? 143 : 52) + 135 * lmrDepth;
|
||||
ss->staticEval + (bestValue < ss->staticEval - 51 ? 145 : 49) + 144 * lmrDepth;
|
||||
|
||||
// Futility pruning: parent node (~13 Elo)
|
||||
if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha)
|
||||
@@ -1119,7 +1117,7 @@ moves_loop: // When in check, search starts here
|
||||
&& std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER)
|
||||
&& ttData.depth >= depth - 3)
|
||||
{
|
||||
Value singularBeta = ttData.value - (54 + 76 * (ss->ttPv && !PvNode)) * depth / 64;
|
||||
Value singularBeta = ttData.value - (54 + 77 * (ss->ttPv && !PvNode)) * depth / 64;
|
||||
Depth singularDepth = newDepth / 2;
|
||||
|
||||
ss->excludedMove = move;
|
||||
@@ -1129,13 +1127,13 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
if (value < singularBeta)
|
||||
{
|
||||
int doubleMargin = 293 * PvNode - 195 * !ttCapture;
|
||||
int tripleMargin = 107 + 259 * PvNode - 260 * !ttCapture + 98 * ss->ttPv;
|
||||
int doubleMargin = 262 * PvNode - 204 * !ttCapture;
|
||||
int tripleMargin = 97 + 266 * PvNode - 255 * !ttCapture + 94 * ss->ttPv;
|
||||
|
||||
extension = 1 + (value < singularBeta - doubleMargin)
|
||||
+ (value < singularBeta - tripleMargin);
|
||||
|
||||
depth += ((!PvNode) && (depth < 16));
|
||||
depth += ((!PvNode) && (depth < 14));
|
||||
}
|
||||
|
||||
// Multi-cut pruning
|
||||
@@ -1168,7 +1166,7 @@ moves_loop: // When in check, search starts here
|
||||
else if (PvNode && move.to_sq() == prevSq
|
||||
&& thisThread->captureHistory[movedPiece][move.to_sq()]
|
||||
[type_of(pos.piece_on(move.to_sq()))]
|
||||
> 3994)
|
||||
> 4299)
|
||||
extension = 1;
|
||||
}
|
||||
|
||||
@@ -1206,38 +1204,36 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// Increase reduction for cut nodes (~4 Elo)
|
||||
if (cutNode)
|
||||
r += 2 - (ttData.depth >= depth && ss->ttPv)
|
||||
+ (!ss->ttPv && move != ttData.move && move != ss->killer);
|
||||
r += 2 - (ttData.depth >= depth && ss->ttPv);
|
||||
|
||||
// Increase reduction if ttMove is a capture (~3 Elo)
|
||||
if (ttCapture)
|
||||
// Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo)
|
||||
if (ttCapture && !capture)
|
||||
r++;
|
||||
|
||||
// Increase reduction if next ply has a lot of fail high (~5 Elo)
|
||||
if ((ss + 1)->cutoffCnt > 3)
|
||||
r += 1 + !(PvNode || cutNode);
|
||||
r += 1 + allNode;
|
||||
|
||||
// For first picked move (ttMove) reduce reduction, but never allow
|
||||
// reduction to go below 0 (~3 Elo)
|
||||
// For first picked move (ttMove) reduce reduction (~3 Elo)
|
||||
else if (move == ttData.move)
|
||||
r = std::max(0, r - 2);
|
||||
r -= 2;
|
||||
|
||||
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
|
||||
+ (*contHist[0])[movedPiece][move.to_sq()]
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()] - 4664;
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()] - 4410;
|
||||
|
||||
// Decrease/increase reduction for moves with a good/bad history (~8 Elo)
|
||||
r -= ss->statScore / 10898;
|
||||
r -= ss->statScore / 11016;
|
||||
|
||||
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
|
||||
if (depth >= 2 && moveCount > 1 + (rootNode && depth < 10))
|
||||
if (depth >= 2 && moveCount > 1)
|
||||
{
|
||||
// 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.
|
||||
// To prevent problems when the max value is less than the min value,
|
||||
// std::clamp has been replaced by a more robust implementation.
|
||||
Depth d = std::max(1, std::min(newDepth - r, newDepth + 1));
|
||||
Depth d = std::max(1, std::min(newDepth - r, newDepth + !allNode));
|
||||
|
||||
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);
|
||||
|
||||
@@ -1246,8 +1242,8 @@ moves_loop: // When in check, search starts here
|
||||
{
|
||||
// Adjust full-depth search based on LMR results - if the result was
|
||||
// good enough search deeper, if it was bad enough search shallower.
|
||||
const bool doDeeperSearch = value > (bestValue + 35 + 2 * newDepth); // (~1 Elo)
|
||||
const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo)
|
||||
const bool doDeeperSearch = value > (bestValue + 38 + 2 * newDepth); // (~1 Elo)
|
||||
const bool doShallowerSearch = value < bestValue + 8; // (~2 Elo)
|
||||
|
||||
newDepth += doDeeperSearch - doShallowerSearch;
|
||||
|
||||
@@ -1255,9 +1251,7 @@ moves_loop: // When in check, search starts here
|
||||
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
|
||||
|
||||
// Post LMR continuation history updates (~1 Elo)
|
||||
int bonus = value <= alpha ? -stat_malus(newDepth)
|
||||
: value >= beta ? stat_bonus(newDepth)
|
||||
: 0;
|
||||
int bonus = value >= beta ? stat_bonus(newDepth) : -stat_malus(newDepth);
|
||||
|
||||
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus);
|
||||
}
|
||||
@@ -1281,6 +1275,10 @@ moves_loop: // When in check, search starts here
|
||||
(ss + 1)->pv = pv;
|
||||
(ss + 1)->pv[0] = Move::none();
|
||||
|
||||
// Extend move from transposition table if we are about to dive into qsearch.
|
||||
if (move == ttData.move && ss->ply <= thisThread->rootDepth * 2)
|
||||
newDepth = std::max(newDepth, 1);
|
||||
|
||||
value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);
|
||||
}
|
||||
|
||||
@@ -1346,9 +1344,9 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// 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 + ss->ply / 32 >= thisThread->rootDepth
|
||||
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
|
||||
int inc =
|
||||
(value == bestValue && (int(nodes) & 15) == 0 && ss->ply + 2 >= thisThread->rootDepth
|
||||
&& std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY);
|
||||
|
||||
if (value + inc > bestValue)
|
||||
{
|
||||
@@ -1363,7 +1361,7 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
if (value >= beta)
|
||||
{
|
||||
ss->cutoffCnt += 1 + !ttData.move - (extension >= 2);
|
||||
ss->cutoffCnt += !ttData.move + (extension < 2);
|
||||
assert(value >= beta); // Fail high
|
||||
break;
|
||||
}
|
||||
@@ -1408,25 +1406,25 @@ moves_loop: // When in check, search starts here
|
||||
// If there is a move that produces search value greater than alpha,
|
||||
// we update the stats of searched moves.
|
||||
else if (bestMove)
|
||||
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth);
|
||||
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth,
|
||||
rootNode);
|
||||
|
||||
// Bonus for prior countermove that caused the fail low
|
||||
else if (!priorCapture && prevSq != SQ_NONE)
|
||||
{
|
||||
int bonus = (138 * (depth > 5) + 58 * (PvNode || cutNode) + 160 * ((ss - 1)->moveCount > 8)
|
||||
+ 84 * (!ss->inCheck && bestValue <= ss->staticEval - 108)
|
||||
+ 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76)
|
||||
+ 32 * (!(ss - 1)->inCheck && bestValue > -(ss - 1)->staticEval + 76));
|
||||
int bonus = (118 * (depth > 5) + 38 * !allNode + 169 * ((ss - 1)->moveCount > 8)
|
||||
+ 116 * (!ss->inCheck && bestValue <= ss->staticEval - 101)
|
||||
+ 133 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 92));
|
||||
|
||||
// Proportional to "how much damage we have to undo"
|
||||
bonus += std::clamp(-(ss - 1)->statScore / 100, -94, 300);
|
||||
bonus += std::min(-(ss - 1)->statScore / 102, 305);
|
||||
|
||||
bonus = std::max(bonus, 0);
|
||||
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
|
||||
stat_bonus(depth) * bonus / 100);
|
||||
stat_bonus(depth) * bonus / 107);
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
|
||||
<< stat_bonus(depth) * bonus / 200;
|
||||
<< stat_bonus(depth) * bonus / 174;
|
||||
|
||||
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
@@ -1434,6 +1432,10 @@ moves_loop: // When in check, search starts here
|
||||
<< stat_bonus(depth) * bonus / 25;
|
||||
}
|
||||
|
||||
// Bonus when search fails low and there is a TT move
|
||||
else if (ttData.move && !allNode)
|
||||
thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) / 4;
|
||||
|
||||
if (PvNode)
|
||||
bestValue = std::min(bestValue, maxValue);
|
||||
|
||||
@@ -1459,7 +1461,12 @@ moves_loop: // When in check, search starts here
|
||||
{
|
||||
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
|
||||
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
|
||||
thisThread->correctionHistory[us][pawn_structure_index<Correction>(pos)] << bonus;
|
||||
thisThread->pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)] << bonus;
|
||||
thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus;
|
||||
thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus;
|
||||
thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus;
|
||||
thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)] << bonus;
|
||||
thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)] << bonus;
|
||||
}
|
||||
|
||||
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
|
||||
@@ -1475,14 +1482,13 @@ moves_loop: // When in check, search starts here
|
||||
// See https://www.chessprogramming.org/Horizon_Effect
|
||||
// and https://www.chessprogramming.org/Quiescence_Search
|
||||
template<NodeType nodeType>
|
||||
Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
|
||||
Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) {
|
||||
|
||||
static_assert(nodeType != Root);
|
||||
constexpr bool PvNode = nodeType == PV;
|
||||
|
||||
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
|
||||
assert(PvNode || (alpha == beta - 1));
|
||||
assert(depth <= 0);
|
||||
|
||||
// Check if we have an upcoming move that draws by repetition (~1 Elo)
|
||||
if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
|
||||
@@ -1577,7 +1583,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 && !PvNode)
|
||||
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
bestValue = (3 * bestValue + beta) / 4;
|
||||
if (!ss->ttHit)
|
||||
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
|
||||
@@ -1591,19 +1597,20 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
if (bestValue > alpha)
|
||||
alpha = bestValue;
|
||||
|
||||
futilityBase = ss->staticEval + 299;
|
||||
futilityBase = ss->staticEval + 280;
|
||||
}
|
||||
|
||||
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
|
||||
(ss - 2)->continuationHistory};
|
||||
|
||||
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
|
||||
|
||||
// Initialize a MovePicker object for the current position, and prepare to search
|
||||
// the moves. We presently use two stages of move generator in quiescence search:
|
||||
// first captures+checks, then captures only (but when in check, we simply search
|
||||
// all evasions).
|
||||
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
|
||||
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory,
|
||||
contHist, &thisThread->pawnHistory);
|
||||
// captures, or evasions only when in check.
|
||||
MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->rootHistory,
|
||||
&thisThread->captureHistory, contHist, &thisThread->pawnHistory,
|
||||
nodeType == Root);
|
||||
|
||||
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
|
||||
// cutoff occurs.
|
||||
@@ -1662,11 +1669,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
+ (*contHist[1])[pos.moved_piece(move)][move.to_sq()]
|
||||
+ thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)]
|
||||
[move.to_sq()]
|
||||
<= 4643)
|
||||
<= 5036)
|
||||
continue;
|
||||
|
||||
// Do not search moves with bad enough SEE values (~5 Elo)
|
||||
if (!pos.see_ge(move, -83))
|
||||
if (!pos.see_ge(move, -82))
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1682,7 +1689,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
// Step 7. Make and search the move
|
||||
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
|
||||
pos.do_move(move, st, givesCheck);
|
||||
value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha, depth - 1);
|
||||
value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha);
|
||||
pos.undo_move(move);
|
||||
|
||||
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
|
||||
@@ -1732,7 +1739,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
|
||||
|
||||
Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {
|
||||
int reductionScale = reductions[d] * reductions[mn];
|
||||
return (reductionScale + 1274 - delta * 746 / rootDelta) / 1024 + (!i && reductionScale > 1293);
|
||||
return (reductionScale + 1239 - delta * 795 / rootDelta) / 1024 + (!i && reductionScale > 1341);
|
||||
}
|
||||
|
||||
// elapsed() returns the time elapsed since the search started. If the
|
||||
@@ -1820,7 +1827,8 @@ void update_all_stats(const Position& pos,
|
||||
Square prevSq,
|
||||
ValueList<Move, 32>& quietsSearched,
|
||||
ValueList<Move, 32>& capturesSearched,
|
||||
Depth depth) {
|
||||
Depth depth,
|
||||
bool rootNode) {
|
||||
|
||||
CapturePieceToHistory& captureHistory = workerThread.captureHistory;
|
||||
Piece moved_piece = pos.moved_piece(bestMove);
|
||||
@@ -1831,11 +1839,11 @@ void update_all_stats(const Position& pos,
|
||||
|
||||
if (!pos.capture_stage(bestMove))
|
||||
{
|
||||
update_quiet_stats(pos, ss, workerThread, bestMove, quietMoveBonus);
|
||||
update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus, rootNode);
|
||||
|
||||
// Decrease stats for all non-best quiet moves
|
||||
for (Move move : quietsSearched)
|
||||
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus);
|
||||
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus, rootNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1844,12 +1852,9 @@ void update_all_stats(const Position& pos,
|
||||
captureHistory[moved_piece][bestMove.to_sq()][captured] << quietMoveBonus;
|
||||
}
|
||||
|
||||
// Extra penalty for a quiet early move that was not a TT move or
|
||||
// main killer move in previous ply when it gets refuted.
|
||||
if (prevSq != SQ_NONE
|
||||
&& ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit
|
||||
|| ((ss - 1)->currentMove == (ss - 1)->killer))
|
||||
&& !pos.captured_piece())
|
||||
// Extra penalty for a quiet early move that was not a TT move in
|
||||
// previous ply when it gets refuted.
|
||||
if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece())
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus);
|
||||
|
||||
// Decrease stats for all non-best capture moves
|
||||
@@ -1866,7 +1871,7 @@ void update_all_stats(const Position& pos,
|
||||
// at ply -1, -2, -3, -4, and -6 with current move.
|
||||
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
|
||||
|
||||
bonus = bonus * 52 / 64;
|
||||
bonus = bonus * 53 / 64;
|
||||
|
||||
for (int i : {1, 2, 3, 4, 6})
|
||||
{
|
||||
@@ -1879,17 +1884,18 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
|
||||
}
|
||||
|
||||
// Updates move sorting heuristics
|
||||
void update_killer(Stack* ss, Move move) {
|
||||
|
||||
// Update killers
|
||||
ss->killer = move;
|
||||
}
|
||||
|
||||
void update_quiet_histories(
|
||||
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
|
||||
void update_quiet_histories(const Position& pos,
|
||||
Stack* ss,
|
||||
Search::Worker& workerThread,
|
||||
Move move,
|
||||
int bonus,
|
||||
bool rootNode) {
|
||||
|
||||
Color us = pos.side_to_move();
|
||||
workerThread.mainHistory[us][move.from_to()] << bonus;
|
||||
if (rootNode)
|
||||
workerThread.rootHistory[us][move.from_to()] << bonus;
|
||||
|
||||
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus);
|
||||
|
||||
@@ -1897,14 +1903,6 @@ void update_quiet_histories(
|
||||
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2;
|
||||
}
|
||||
|
||||
// Updates move sorting heuristics
|
||||
void update_quiet_stats(
|
||||
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
|
||||
|
||||
update_killer(ss, move);
|
||||
update_quiet_histories(pos, ss, workerThread, move, bonus);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// When playing with strength handicap, choose the best move among a set of
|
||||
|
||||
Reference in New Issue
Block a user