Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
official-stockfish
GitHub Repository: official-stockfish/Stockfish
Path: blob/master/src/search.cpp
632 views
1
/*
2
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
3
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
4
5
Stockfish is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9
10
Stockfish is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
14
15
You should have received a copy of the GNU General Public License
16
along with this program. If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#include "search.h"
20
21
#include <algorithm>
22
#include <array>
23
#include <atomic>
24
#include <cassert>
25
#include <chrono>
26
#include <cmath>
27
#include <cstdint>
28
#include <cstdlib>
29
#include <initializer_list>
30
#include <iostream>
31
#include <list>
32
#include <ratio>
33
#include <string>
34
#include <utility>
35
36
#include "bitboard.h"
37
#include "evaluate.h"
38
#include "history.h"
39
#include "misc.h"
40
#include "movegen.h"
41
#include "movepick.h"
42
#include "nnue/network.h"
43
#include "nnue/nnue_accumulator.h"
44
#include "position.h"
45
#include "syzygy/tbprobe.h"
46
#include "thread.h"
47
#include "timeman.h"
48
#include "tt.h"
49
#include "types.h"
50
#include "uci.h"
51
#include "ucioption.h"
52
53
namespace Stockfish {
54
55
namespace TB = Tablebases;
56
57
void syzygy_extend_pv(const OptionsMap& options,
58
const Search::LimitsType& limits,
59
Stockfish::Position& pos,
60
Stockfish::Search::RootMove& rootMove,
61
Value& v);
62
63
using namespace Search;
64
65
namespace {
66
67
constexpr int SEARCHEDLIST_CAPACITY = 32;
68
using SearchedList = ValueList<Move, SEARCHEDLIST_CAPACITY>;
69
70
// (*Scalers):
71
// The values with Scaler asterisks have proven non-linear scaling.
72
// They are optimized to time controls of 180 + 1.8 and longer,
73
// so changing them or adding conditions that are similar requires
74
// tests at these types of time controls.
75
76
// (*Scaler) All tuned parameters at time controls shorter than
77
// optimized for require verifications at longer time controls
78
79
int correction_value(const Worker& w, const Position& pos, const Stack* const ss) {
80
const Color us = pos.side_to_move();
81
const auto m = (ss - 1)->currentMove;
82
const auto& shared = w.sharedHistory;
83
const int pcv = shared.pawn_correction_entry(pos).at(us).pawn;
84
const int micv = shared.minor_piece_correction_entry(pos).at(us).minor;
85
const int wnpcv = shared.nonpawn_correction_entry<WHITE>(pos).at(us).nonPawnWhite;
86
const int bnpcv = shared.nonpawn_correction_entry<BLACK>(pos).at(us).nonPawnBlack;
87
const int cntcv =
88
m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]
89
+ (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]
90
: 8;
91
92
return 10347 * pcv + 8821 * micv + 11665 * (wnpcv + bnpcv) + 7841 * cntcv;
93
}
94
95
// Add correctionHistory value to raw staticEval and guarantee evaluation
96
// does not hit the tablebase range.
97
Value to_corrected_static_eval(const Value v, const int cv) {
98
return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
99
}
100
101
void update_correction_history(const Position& pos,
102
Stack* const ss,
103
Search::Worker& workerThread,
104
const int bonus) {
105
const Move m = (ss - 1)->currentMove;
106
const Color us = pos.side_to_move();
107
108
constexpr int nonPawnWeight = 178;
109
auto& shared = workerThread.sharedHistory;
110
111
shared.pawn_correction_entry(pos).at(us).pawn << bonus;
112
shared.minor_piece_correction_entry(pos).at(us).minor << bonus * 156 / 128;
113
shared.nonpawn_correction_entry<WHITE>(pos).at(us).nonPawnWhite << bonus * nonPawnWeight / 128;
114
shared.nonpawn_correction_entry<BLACK>(pos).at(us).nonPawnBlack << bonus * nonPawnWeight / 128;
115
116
// Branchless: use mask to zero bonus when move is not ok
117
const int mask = int(m.is_ok());
118
const Square to = m.to_sq_unchecked();
119
const Piece pc = pos.piece_on(to);
120
const int bonus2 = (bonus * 127 / 128) * mask;
121
const int bonus4 = (bonus * 59 / 128) * mask;
122
(*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus2;
123
(*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus4;
124
}
125
126
// Add a small random component to draw evaluations to avoid 3-fold blindness
127
Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }
128
Value value_to_tt(Value v, int ply);
129
Value value_from_tt(Value v, int ply, int r50c);
130
void update_pv(Move* pv, Move move, const Move* childPv);
131
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
132
void update_quiet_histories(
133
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
134
void update_all_stats(const Position& pos,
135
Stack* ss,
136
Search::Worker& workerThread,
137
Move bestMove,
138
Square prevSq,
139
SearchedList& quietsSearched,
140
SearchedList& capturesSearched,
141
Depth depth,
142
Move TTMove,
143
int moveCount);
144
145
bool is_shuffling(Move move, Stack* const ss, const Position& pos) {
146
if (pos.capture_stage(move) || pos.rule50_count() < 10)
147
return false;
148
if (pos.state()->pliesFromNull <= 6 || ss->ply < 20)
149
return false;
150
return move.from_sq() == (ss - 2)->currentMove.to_sq()
151
&& (ss - 2)->currentMove.from_sq() == (ss - 4)->currentMove.to_sq();
152
}
153
154
} // namespace
155
156
Search::Worker::Worker(SharedState& sharedState,
157
std::unique_ptr<ISearchManager> sm,
158
size_t threadId,
159
size_t numaThreadId,
160
size_t numaTotalThreads,
161
NumaReplicatedAccessToken token) :
162
// Unpack the SharedState struct into member variables
163
sharedHistory(sharedState.sharedHistories.at(token.get_numa_index())),
164
threadIdx(threadId),
165
numaThreadIdx(numaThreadId),
166
numaTotal(numaTotalThreads),
167
numaAccessToken(token),
168
manager(std::move(sm)),
169
options(sharedState.options),
170
threads(sharedState.threads),
171
tt(sharedState.tt),
172
networks(sharedState.networks),
173
refreshTable(networks[token]) {
174
clear();
175
}
176
177
void Search::Worker::ensure_network_replicated() {
178
// Access once to force lazy initialization.
179
// We do this because we want to avoid initialization during search.
180
(void) (networks[numaAccessToken]);
181
}
182
183
void Search::Worker::start_searching() {
184
185
accumulatorStack.reset();
186
187
// Non-main threads go directly to iterative_deepening()
188
if (!is_mainthread())
189
{
190
iterative_deepening();
191
return;
192
}
193
194
main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options,
195
main_manager()->originalTimeAdjust);
196
tt.new_search();
197
198
if (rootMoves.empty())
199
{
200
rootMoves.emplace_back(Move::none());
201
main_manager()->updates.onUpdateNoMoves(
202
{0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}});
203
}
204
else
205
{
206
threads.start_searching(); // start non-main threads
207
iterative_deepening(); // main thread start searching
208
}
209
210
// When we reach the maximum depth, we can arrive here without a raise of
211
// threads.stop. However, if we are pondering or in an infinite search,
212
// the UCI protocol states that we shouldn't print the best move before the
213
// GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
214
// until the GUI sends one of those commands.
215
while (!threads.stop && (main_manager()->ponder || limits.infinite))
216
{} // Busy wait for a stop or a ponder reset
217
218
// Stop the threads if not already stopped (also raise the stop if
219
// "ponderhit" just reset threads.ponder)
220
threads.stop = true;
221
222
// Wait until all threads have finished
223
threads.wait_for_search_finished();
224
225
// When playing in 'nodes as time' mode, subtract the searched nodes from
226
// the available ones before exiting.
227
if (limits.npmsec)
228
main_manager()->tm.advance_nodes_time(threads.nodes_searched()
229
- limits.inc[rootPos.side_to_move()]);
230
231
Worker* bestThread = this;
232
Skill skill =
233
Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0);
234
235
if (int(options["MultiPV"]) == 1 && !limits.depth && !limits.mate && !skill.enabled()
236
&& rootMoves[0].pv[0] != Move::none())
237
bestThread = threads.get_best_thread()->worker.get();
238
239
main_manager()->bestPreviousScore = bestThread->rootMoves[0].score;
240
main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore;
241
242
// Send again PV info if we have a new best thread
243
if (bestThread != this)
244
main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth);
245
246
std::string ponder;
247
248
if (bestThread->rootMoves[0].pv.size() > 1
249
|| bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos))
250
ponder = UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
251
252
auto bestmove = UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
253
main_manager()->updates.onBestmove(bestmove, ponder);
254
}
255
256
// Main iterative deepening loop. It calls search()
257
// repeatedly with increasing depth until the allocated thinking time has been
258
// consumed, the user stops the search, or the maximum search depth is reached.
259
void Search::Worker::iterative_deepening() {
260
261
SearchManager* mainThread = (is_mainthread() ? main_manager() : nullptr);
262
263
Move pv[MAX_PLY + 1];
264
265
Depth lastBestMoveDepth = 0;
266
Value lastBestScore = -VALUE_INFINITE;
267
auto lastBestPV = std::vector{Move::none()};
268
269
Value alpha, beta;
270
Value bestValue = -VALUE_INFINITE;
271
Color us = rootPos.side_to_move();
272
double timeReduction = 1, totBestMoveChanges = 0;
273
int delta, iterIdx = 0;
274
275
// Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):
276
// (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),
277
// (ss + 2) is needed for initialization of cutOffCnt.
278
Stack stack[MAX_PLY + 10] = {};
279
Stack* ss = stack + 7;
280
281
for (int i = 7; i > 0; --i)
282
{
283
(ss - i)->continuationHistory =
284
&continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
285
(ss - i)->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0];
286
(ss - i)->staticEval = VALUE_NONE;
287
}
288
289
for (int i = 0; i <= MAX_PLY + 2; ++i)
290
(ss + i)->ply = i;
291
292
ss->pv = pv;
293
294
if (mainThread)
295
{
296
if (mainThread->bestPreviousScore == VALUE_INFINITE)
297
mainThread->iterValue.fill(VALUE_ZERO);
298
else
299
mainThread->iterValue.fill(mainThread->bestPreviousScore);
300
}
301
302
size_t multiPV = size_t(options["MultiPV"]);
303
Skill skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0);
304
305
// When playing with strength handicap enable MultiPV search that we will
306
// use behind-the-scenes to retrieve a set of possible moves.
307
if (skill.enabled())
308
multiPV = std::max(multiPV, size_t(4));
309
310
multiPV = std::min(multiPV, rootMoves.size());
311
312
int searchAgainCounter = 0;
313
314
lowPlyHistory.fill(97);
315
316
for (Color c : {WHITE, BLACK})
317
for (int i = 0; i < UINT_16_HISTORY_SIZE; i++)
318
mainHistory[c][i] = mainHistory[c][i] * 3 / 4;
319
320
// Iterative deepening loop until requested to stop or the target depth is reached
321
while (++rootDepth < MAX_PLY && !threads.stop
322
&& !(limits.depth && mainThread && rootDepth > limits.depth))
323
{
324
// Age out PV variability metric
325
if (mainThread)
326
totBestMoveChanges /= 2;
327
328
// Save the last iteration's scores before the first PV line is searched and
329
// all the move scores except the (new) PV are set to -VALUE_INFINITE.
330
for (RootMove& rm : rootMoves)
331
rm.previousScore = rm.score;
332
333
size_t pvFirst = 0;
334
pvLast = 0;
335
336
if (!threads.increaseDepth)
337
searchAgainCounter++;
338
339
// MultiPV loop. We perform a full root search for each PV line
340
for (pvIdx = 0; pvIdx < multiPV; ++pvIdx)
341
{
342
if (pvIdx == pvLast)
343
{
344
pvFirst = pvLast;
345
for (pvLast++; pvLast < rootMoves.size(); pvLast++)
346
if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank)
347
break;
348
}
349
350
// Reset UCI info selDepth for each depth and each PV line
351
selDepth = 0;
352
353
// Reset aspiration window starting size
354
delta = 5 + threadIdx % 8 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 9000;
355
Value avg = rootMoves[pvIdx].averageScore;
356
alpha = std::max(avg - delta, -VALUE_INFINITE);
357
beta = std::min(avg + delta, VALUE_INFINITE);
358
359
// Adjust optimism based on root move's averageScore
360
optimism[us] = 142 * avg / (std::abs(avg) + 91);
361
optimism[~us] = -optimism[us];
362
363
// Start with a small aspiration window and, in the case of a fail
364
// high/low, re-search with a bigger window until we don't fail
365
// high/low anymore.
366
int failedHighCnt = 0;
367
while (true)
368
{
369
// Adjust the effective depth searched, but ensure at least one
370
// effective increment for every four searchAgain steps (see issue #2717).
371
Depth adjustedDepth =
372
std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
373
rootDelta = beta - alpha;
374
bestValue = search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
375
376
// Bring the best move to the front. It is critical that sorting
377
// is done with a stable algorithm because all the values but the
378
// first and eventually the new best one is set to -VALUE_INFINITE
379
// and we want to keep the same order for all the moves except the
380
// new PV that goes to the front. Note that in the case of MultiPV
381
// search the already searched PV lines are preserved.
382
std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast);
383
384
// If search has been stopped, we break immediately. Sorting is
385
// safe because RootMoves is still valid, although it refers to
386
// the previous iteration.
387
if (threads.stop)
388
break;
389
390
// When failing high/low give some update before a re-search. To avoid
391
// excessive output that could hang GUIs like Fritz 19, only start
392
// at nodes > 10M (rather than depth N, which can be reached quickly)
393
if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta)
394
&& nodes > 10000000)
395
main_manager()->pv(*this, threads, tt, rootDepth);
396
397
// In case of failing low/high increase aspiration window and re-search,
398
// otherwise exit the loop.
399
if (bestValue <= alpha)
400
{
401
beta = alpha;
402
alpha = std::max(bestValue - delta, -VALUE_INFINITE);
403
404
failedHighCnt = 0;
405
if (mainThread)
406
mainThread->stopOnPonderhit = false;
407
}
408
else if (bestValue >= beta)
409
{
410
alpha = std::max(beta - delta, alpha);
411
beta = std::min(bestValue + delta, VALUE_INFINITE);
412
++failedHighCnt;
413
}
414
else
415
break;
416
417
delta += delta / 3;
418
419
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
420
}
421
422
// Sort the PV lines searched so far and update the GUI
423
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
424
425
if (mainThread
426
&& (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000)
427
// A thread that aborted search can have mated-in/TB-loss PV and
428
// score that cannot be trusted, i.e. it can be delayed or refuted
429
// if we would have had time to fully search other root-moves. Thus
430
// we suppress this output and below pick a proven score/PV for this
431
// thread (from the previous iteration).
432
&& !(threads.abortedSearch && is_loss(rootMoves[0].uciScore)))
433
main_manager()->pv(*this, threads, tt, rootDepth);
434
435
if (threads.stop)
436
break;
437
}
438
439
if (!threads.stop)
440
completedDepth = rootDepth;
441
442
// We make sure not to pick an unproven mated-in score,
443
// in case this thread prematurely stopped search (aborted-search).
444
if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE
445
&& is_loss(rootMoves[0].score))
446
{
447
// Bring the last best move to the front for best thread selection.
448
Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)](
449
const auto& rm) { return rm == lastBestPV[0]; });
450
rootMoves[0].pv = lastBestPV;
451
rootMoves[0].score = rootMoves[0].uciScore = lastBestScore;
452
}
453
else if (rootMoves[0].pv[0] != lastBestPV[0])
454
{
455
lastBestPV = rootMoves[0].pv;
456
lastBestScore = rootMoves[0].score;
457
lastBestMoveDepth = rootDepth;
458
}
459
460
if (!mainThread)
461
continue;
462
463
// Have we found a "mate in x"?
464
if (limits.mate && rootMoves[0].score == rootMoves[0].uciScore
465
&& ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY
466
&& VALUE_MATE - rootMoves[0].score <= 2 * limits.mate)
467
|| (rootMoves[0].score != -VALUE_INFINITE
468
&& rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY
469
&& VALUE_MATE + rootMoves[0].score <= 2 * limits.mate)))
470
threads.stop = true;
471
472
// If the skill level is enabled and time is up, pick a sub-optimal best move
473
if (skill.enabled() && skill.time_to_pick(rootDepth))
474
skill.pick_best(rootMoves, multiPV);
475
476
// Use part of the gained time from a previous stable move for the current move
477
for (auto&& th : threads)
478
{
479
totBestMoveChanges += th->worker->bestMoveChanges;
480
th->worker->bestMoveChanges = 0;
481
}
482
483
// Do we have time for the next iteration? Can we stop searching now?
484
if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit)
485
{
486
uint64_t nodesEffort =
487
rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes));
488
489
double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue)
490
+ 0.93 * (mainThread->iterValue[iterIdx] - bestValue))
491
/ 100.0;
492
fallingEval = std::clamp(fallingEval, 0.57, 1.70);
493
494
// If the bestMove is stable over several iterations, reduce time accordingly
495
double k = 0.51;
496
double center = lastBestMoveDepth + 12.15;
497
498
timeReduction = 0.66 + 0.85 / (0.98 + std::exp(-k * (completedDepth - center)));
499
500
double reduction = (1.43 + mainThread->previousTimeReduction) / (2.28 * timeReduction);
501
502
double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size();
503
504
double highBestMoveEffort = nodesEffort >= 93340 ? 0.76 : 1.0;
505
506
double totalTime = mainThread->tm.optimum() * fallingEval * reduction
507
* bestMoveInstability * highBestMoveEffort;
508
509
// Cap used time in case of a single legal move for a better viewer experience
510
if (rootMoves.size() == 1)
511
totalTime = std::min(502.0, totalTime);
512
513
auto elapsedTime = elapsed();
514
515
// Stop the search if we have exceeded the totalTime or maximum
516
if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum())))
517
{
518
// If we are allowed to ponder do not stop the search now but
519
// keep pondering until the GUI sends "ponderhit" or "stop".
520
if (mainThread->ponder)
521
mainThread->stopOnPonderhit = true;
522
else
523
threads.stop = true;
524
}
525
else
526
threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.50;
527
}
528
529
mainThread->iterValue[iterIdx] = bestValue;
530
iterIdx = (iterIdx + 1) & 3;
531
}
532
533
if (!mainThread)
534
return;
535
536
mainThread->previousTimeReduction = timeReduction;
537
538
// If the skill level is enabled, swap the best PV line with the sub-optimal one
539
if (skill.enabled())
540
std::swap(rootMoves[0],
541
*std::find(rootMoves.begin(), rootMoves.end(),
542
skill.best ? skill.best : skill.pick_best(rootMoves, multiPV)));
543
}
544
545
546
void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss) {
547
do_move(pos, move, st, pos.gives_check(move), ss);
548
}
549
550
void Search::Worker::do_move(
551
Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) {
552
bool capture = pos.capture_stage(move);
553
// Preferable over fetch_add to avoid locking instructions
554
nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);
555
556
auto [dirtyPiece, dirtyThreats] = accumulatorStack.push();
557
pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt, &sharedHistory);
558
559
if (ss != nullptr)
560
{
561
ss->currentMove = move;
562
ss->continuationHistory =
563
&continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()];
564
ss->continuationCorrectionHistory =
565
&continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()];
566
}
567
}
568
569
void Search::Worker::do_null_move(Position& pos, StateInfo& st, Stack* const ss) {
570
pos.do_null_move(st);
571
ss->currentMove = Move::null();
572
ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0];
573
ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0];
574
}
575
576
void Search::Worker::undo_move(Position& pos, const Move move) {
577
pos.undo_move(move);
578
accumulatorStack.pop();
579
}
580
581
void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); }
582
583
584
// Reset histories, usually before a new game
585
void Search::Worker::clear() {
586
mainHistory.fill(0);
587
captureHistory.fill(-689);
588
589
// Each thread is responsible for clearing their part of shared history
590
sharedHistory.correctionHistory.clear_range(0, numaThreadIdx, numaTotal);
591
sharedHistory.pawnHistory.clear_range(-1238, numaThreadIdx, numaTotal);
592
593
ttMoveHistory = 0;
594
595
for (auto& to : continuationCorrectionHistory)
596
for (auto& h : to)
597
h.fill(8);
598
599
for (bool inCheck : {false, true})
600
for (StatsType c : {NoCaptures, Captures})
601
for (auto& to : continuationHistory[inCheck][c])
602
for (auto& h : to)
603
h.fill(-529);
604
605
for (size_t i = 1; i < reductions.size(); ++i)
606
reductions[i] = int(2747 / 128.0 * std::log(i));
607
608
refreshTable.clear(networks[numaAccessToken]);
609
}
610
611
612
// Main search function for both PV and non-PV nodes
613
template<NodeType nodeType>
614
Value Search::Worker::search(
615
Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
616
617
constexpr bool PvNode = nodeType != NonPV;
618
constexpr bool rootNode = nodeType == Root;
619
const bool allNode = !(PvNode || cutNode);
620
621
// Dive into quiescence search when the depth reaches zero
622
if (depth <= 0)
623
return qsearch<PvNode ? PV : NonPV>(pos, ss, alpha, beta);
624
625
// Limit the depth if extensions made it too large
626
depth = std::min(depth, MAX_PLY - 1);
627
628
// Check if we have an upcoming move that draws by repetition
629
if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
630
{
631
alpha = value_draw(nodes);
632
if (alpha >= beta)
633
return alpha;
634
}
635
636
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
637
assert(PvNode || (alpha == beta - 1));
638
assert(0 < depth && depth < MAX_PLY);
639
assert(!(PvNode && cutNode));
640
641
Move pv[MAX_PLY + 1];
642
StateInfo st;
643
644
Key posKey;
645
Move move, excludedMove, bestMove;
646
Depth extension, newDepth;
647
Value bestValue, value, eval, maxValue, probCutBeta;
648
bool givesCheck, improving, priorCapture, opponentWorsening;
649
bool capture, ttCapture;
650
int priorReduction;
651
Piece movedPiece;
652
653
SearchedList capturesSearched;
654
SearchedList quietsSearched;
655
656
// Step 1. Initialize node
657
ss->inCheck = pos.checkers();
658
priorCapture = pos.captured_piece();
659
Color us = pos.side_to_move();
660
ss->moveCount = 0;
661
bestValue = -VALUE_INFINITE;
662
maxValue = VALUE_INFINITE;
663
664
// Check for the available remaining time
665
if (is_mainthread())
666
main_manager()->check_time(*this);
667
668
// Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)
669
if (PvNode && selDepth < ss->ply + 1)
670
selDepth = ss->ply + 1;
671
672
if (!rootNode)
673
{
674
// Step 2. Check for aborted search and immediate draw
675
if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply)
676
|| ss->ply >= MAX_PLY)
677
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : value_draw(nodes);
678
679
// Step 3. Mate distance pruning. Even if we mate at the next move our score
680
// would be at best mate_in(ss->ply + 1), but if alpha is already bigger because
681
// a shorter mate was found upward in the tree then there is no need to search
682
// because we will never beat the current alpha. Same logic but with reversed
683
// signs apply also in the opposite condition of being mated instead of giving
684
// mate. In this case, return a fail-high score.
685
alpha = std::max(mated_in(ss->ply), alpha);
686
beta = std::min(mate_in(ss->ply + 1), beta);
687
if (alpha >= beta)
688
return alpha;
689
}
690
691
assert(0 <= ss->ply && ss->ply < MAX_PLY);
692
693
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
694
bestMove = Move::none();
695
priorReduction = (ss - 1)->reduction;
696
(ss - 1)->reduction = 0;
697
ss->statScore = 0;
698
(ss + 2)->cutoffCnt = 0;
699
700
// Step 4. Transposition table lookup
701
excludedMove = ss->excludedMove;
702
posKey = pos.key();
703
auto [ttHit, ttData, ttWriter] = tt.probe(posKey);
704
// Need further processing of the saved data
705
ss->ttHit = ttHit;
706
ttData.move = rootNode ? rootMoves[pvIdx].pv[0] : ttHit ? ttData.move : Move::none();
707
ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE;
708
ss->ttPv = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv);
709
ttCapture = ttData.move && pos.capture_stage(ttData.move);
710
711
// Step 6. Static evaluation of the position
712
Value unadjustedStaticEval = VALUE_NONE;
713
const auto correctionValue = correction_value(*this, pos, ss);
714
// Skip early pruning when in check
715
if (ss->inCheck)
716
ss->staticEval = eval = (ss - 2)->staticEval;
717
else if (excludedMove)
718
unadjustedStaticEval = eval = ss->staticEval;
719
else if (ss->ttHit)
720
{
721
// Never assume anything about values stored in TT
722
unadjustedStaticEval = ttData.eval;
723
if (!is_valid(unadjustedStaticEval))
724
unadjustedStaticEval = evaluate(pos);
725
726
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);
727
728
// ttValue can be used as a better position evaluation
729
if (is_valid(ttData.value)
730
&& (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER)))
731
eval = ttData.value;
732
}
733
else
734
{
735
unadjustedStaticEval = evaluate(pos);
736
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);
737
738
// Static evaluation is saved as it was before adjustment by correction history
739
ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(),
740
unadjustedStaticEval, tt.generation());
741
}
742
743
// Set up the improving flag, which is true if current static evaluation is
744
// bigger than the previous static evaluation at our turn (if we were in
745
// check at our previous move we go back until we weren't in check) and is
746
// false otherwise. The improving flag is used in various pruning heuristics.
747
// Similarly, opponentWorsening is true if our static evaluation is better
748
// for us than at the last ply.
749
improving = ss->staticEval > (ss - 2)->staticEval;
750
opponentWorsening = ss->staticEval > -(ss - 1)->staticEval;
751
752
// Hindsight adjustment of reductions based on static evaluation difference.
753
if (priorReduction >= 3 && !opponentWorsening)
754
depth++;
755
if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173)
756
depth--;
757
758
// At non-PV nodes we check for an early TT cutoff
759
if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta)
760
&& is_valid(ttData.value) // Can happen when !ttHit or when access race in probe()
761
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))
762
&& (cutNode == (ttData.value >= beta) || depth > 5))
763
{
764
// If ttMove is quiet, update move sorting heuristics on TT hit
765
if (ttData.move && ttData.value >= beta)
766
{
767
// Bonus for a quiet ttMove that fails high
768
if (!ttCapture)
769
update_quiet_histories(pos, ss, *this, ttData.move,
770
std::min(132 * depth - 72, 985));
771
772
// Extra penalty for early quiet moves of the previous ply
773
if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture)
774
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060);
775
}
776
777
// Partial workaround for the graph history interaction problem
778
// For high rule50 counts don't produce transposition table cutoffs.
779
if (pos.rule50_count() < 96)
780
{
781
if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move)
782
&& !is_decisive(ttData.value))
783
{
784
pos.do_move(ttData.move, st);
785
Key nextPosKey = pos.key();
786
auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey);
787
pos.undo_move(ttData.move);
788
789
// Check that the ttValue after the tt move would also trigger a cutoff
790
if (!is_valid(ttDataNext.value))
791
return ttData.value;
792
793
if ((ttData.value >= beta) == (-ttDataNext.value >= beta))
794
return ttData.value;
795
}
796
else
797
return ttData.value;
798
}
799
}
800
801
// Step 5. Tablebases probe
802
if (!rootNode && !excludedMove && tbConfig.cardinality)
803
{
804
int piecesCount = pos.count<ALL_PIECES>();
805
806
if (piecesCount <= tbConfig.cardinality
807
&& (piecesCount < tbConfig.cardinality || depth >= tbConfig.probeDepth)
808
&& pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING))
809
{
810
TB::ProbeState err;
811
TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
812
813
// Force check of time on the next occasion
814
if (is_mainthread())
815
main_manager()->callsCnt = 0;
816
817
if (err != TB::ProbeState::FAIL)
818
{
819
// Preferable over fetch_add to avoid locking instructions
820
tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);
821
822
int drawScore = tbConfig.useRule50 ? 1 : 0;
823
824
Value tbValue = VALUE_TB - ss->ply;
825
826
// Use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score
827
value = wdl < -drawScore ? -tbValue
828
: wdl > drawScore ? tbValue
829
: VALUE_DRAW + 2 * wdl * drawScore;
830
831
Bound b = wdl < -drawScore ? BOUND_UPPER
832
: wdl > drawScore ? BOUND_LOWER
833
: BOUND_EXACT;
834
835
if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha))
836
{
837
ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, b,
838
std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE,
839
tt.generation());
840
841
return value;
842
}
843
844
if (PvNode)
845
{
846
if (b == BOUND_LOWER)
847
bestValue = value, alpha = std::max(alpha, bestValue);
848
else
849
maxValue = value;
850
}
851
}
852
}
853
}
854
855
if (ss->inCheck)
856
goto moves_loop;
857
858
// Use static evaluation difference to improve quiet move ordering
859
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
860
{
861
int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -209, 167) + 59;
862
mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9;
863
if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN
864
&& ((ss - 1)->currentMove).type_of() != PROMOTION)
865
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << evalDiff * 13;
866
}
867
868
869
// Step 7. Razoring
870
// If eval is really low, skip search entirely and return the qsearch value.
871
// For PvNodes, we must have a guard against mates being returned.
872
if (!PvNode && eval < alpha - 485 - 281 * depth * depth)
873
return qsearch<NonPV>(pos, ss, alpha, beta);
874
875
// Step 8. Futility pruning: child node
876
// The depth condition is important for mate finding.
877
{
878
auto futility_margin = [&](Depth d) {
879
Value futilityMult = 76 - 23 * !ss->ttHit;
880
881
return futilityMult * d
882
- (2474 * improving + 331 * opponentWorsening) * futilityMult / 1024 //
883
+ std::abs(correctionValue) / 174665;
884
};
885
886
if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta
887
&& (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval))
888
return (2 * beta + eval) / 3;
889
}
890
891
// Step 9. Null move search with verification search
892
if (cutNode && ss->staticEval >= beta - 18 * depth + 350 && !excludedMove
893
&& pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta))
894
{
895
assert((ss - 1)->currentMove != Move::null());
896
897
// Null move dynamic reduction based on depth
898
Depth R = 7 + depth / 3;
899
do_null_move(pos, st, ss);
900
901
Value nullValue = -search<NonPV>(pos, ss + 1, -beta, -beta + 1, depth - R, false);
902
903
undo_null_move(pos);
904
905
// Do not return unproven mate or TB scores
906
if (nullValue >= beta && !is_win(nullValue))
907
{
908
if (nmpMinPly || depth < 16)
909
return nullValue;
910
911
assert(!nmpMinPly); // Recursive verification is not allowed
912
913
// Do verification search at high depths, with null move pruning disabled
914
// until ply exceeds nmpMinPly.
915
nmpMinPly = ss->ply + 3 * (depth - R) / 4;
916
917
Value v = search<NonPV>(pos, ss, beta - 1, beta, depth - R, false);
918
919
nmpMinPly = 0;
920
921
if (v >= beta)
922
return nullValue;
923
}
924
}
925
926
improving |= ss->staticEval >= beta;
927
928
// Step 10. Internal iterative reductions
929
// At sufficient depth, reduce depth for PV/Cut nodes without a TTMove.
930
// (*Scaler) Making IIR more aggressive scales poorly.
931
if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3)
932
depth--;
933
934
// Step 11. ProbCut
935
// If we have a good enough capture (or queen promotion) and a reduced search
936
// returns a value much above beta, we can (almost) safely prune the previous move.
937
probCutBeta = beta + 235 - 63 * improving;
938
if (depth >= 3
939
&& !is_decisive(beta)
940
// If value from transposition table is lower than probCutBeta, don't attempt
941
// probCut there
942
&& !(is_valid(ttData.value) && ttData.value < probCutBeta))
943
{
944
assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);
945
946
MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory);
947
Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 315, 0, depth);
948
949
while ((move = mp.next_move()) != Move::none())
950
{
951
assert(move.is_ok());
952
953
if (move == excludedMove || !pos.legal(move))
954
continue;
955
956
assert(pos.capture_stage(move));
957
958
do_move(pos, move, st, ss);
959
960
// Perform a preliminary qsearch to verify that the move holds
961
value = -qsearch<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1);
962
963
// If the qsearch held, perform the regular search
964
if (value >= probCutBeta && probCutDepth > 0)
965
value = -search<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth,
966
!cutNode);
967
968
undo_move(pos, move);
969
970
if (value >= probCutBeta)
971
{
972
// Save ProbCut data into transposition table
973
ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER,
974
probCutDepth + 1, move, unadjustedStaticEval, tt.generation());
975
976
if (!is_decisive(value))
977
return value - (probCutBeta - beta);
978
}
979
}
980
}
981
982
moves_loop: // When in check, search starts here
983
984
// Step 12. A small Probcut idea
985
probCutBeta = beta + 418;
986
if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta
987
&& !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value))
988
return probCutBeta;
989
990
const PieceToHistory* contHist[] = {
991
(ss - 1)->continuationHistory, (ss - 2)->continuationHistory, (ss - 3)->continuationHistory,
992
(ss - 4)->continuationHistory, (ss - 5)->continuationHistory, (ss - 6)->continuationHistory};
993
994
995
MovePicker mp(pos, ttData.move, depth, &mainHistory, &lowPlyHistory, &captureHistory, contHist,
996
&sharedHistory, ss->ply);
997
998
value = bestValue;
999
1000
int moveCount = 0;
1001
1002
// Step 13. Loop through all pseudo-legal moves until no moves remain
1003
// or a beta cutoff occurs.
1004
while ((move = mp.next_move()) != Move::none())
1005
{
1006
assert(move.is_ok());
1007
1008
if (move == excludedMove)
1009
continue;
1010
1011
// Check for legality
1012
if (!pos.legal(move))
1013
continue;
1014
1015
// At root obey the "searchmoves" option and skip moves not listed in Root
1016
// Move List. In MultiPV mode we also skip PV moves that have been already
1017
// searched and those of lower "TB rank" if we are in a TB root position.
1018
if (rootNode && !std::count(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast, move))
1019
continue;
1020
1021
ss->moveCount = ++moveCount;
1022
1023
if (rootNode && is_mainthread() && nodes > 10000000)
1024
{
1025
main_manager()->updates.onIter(
1026
{depth, UCIEngine::move(move, pos.is_chess960()), moveCount + pvIdx});
1027
}
1028
if (PvNode)
1029
(ss + 1)->pv = nullptr;
1030
1031
extension = 0;
1032
capture = pos.capture_stage(move);
1033
movedPiece = pos.moved_piece(move);
1034
givesCheck = pos.gives_check(move);
1035
1036
// Calculate new depth for this move
1037
newDepth = depth - 1;
1038
1039
int delta = beta - alpha;
1040
1041
Depth r = reduction(improving, depth, moveCount, delta);
1042
1043
// Increase reduction for ttPv nodes (*Scaler)
1044
// Larger values scale well
1045
if (ss->ttPv)
1046
r += 946;
1047
1048
// Step 14. Pruning at shallow depths.
1049
// Depth conditions are important for mate finding.
1050
if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue))
1051
{
1052
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
1053
if (moveCount >= (3 + depth * depth) / (2 - improving))
1054
mp.skip_quiet_moves();
1055
1056
// Reduced depth of the next LMR search
1057
int lmrDepth = newDepth - r / 1024;
1058
1059
if (capture || givesCheck)
1060
{
1061
Piece capturedPiece = pos.piece_on(move.to_sq());
1062
int captHist = captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)];
1063
1064
// Futility pruning for captures
1065
if (!givesCheck && lmrDepth < 7)
1066
{
1067
Value futilityValue = ss->staticEval + 232 + 217 * lmrDepth
1068
+ PieceValue[capturedPiece] + 131 * captHist / 1024;
1069
1070
if (futilityValue <= alpha)
1071
continue;
1072
}
1073
1074
// SEE based pruning for captures and checks
1075
// Avoid pruning sacrifices of our last piece for stalemate
1076
int margin = std::max(166 * depth + captHist / 29, 0);
1077
if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece])
1078
&& !pos.see_ge(move, -margin))
1079
continue;
1080
}
1081
else
1082
{
1083
int history = (*contHist[0])[movedPiece][move.to_sq()]
1084
+ (*contHist[1])[movedPiece][move.to_sq()]
1085
+ sharedHistory.pawn_entry(pos)[movedPiece][move.to_sq()];
1086
1087
// Continuation history based pruning
1088
if (history < -4083 * depth)
1089
continue;
1090
1091
history += 69 * mainHistory[us][move.raw()] / 32;
1092
1093
// (*Scaler): Generally, lower divisors scales well
1094
lmrDepth += history / 3208;
1095
1096
Value futilityValue = ss->staticEval + 42 + 161 * !bestMove + 127 * lmrDepth
1097
+ 85 * (ss->staticEval > alpha);
1098
1099
// Futility pruning: parent node
1100
// (*Scaler): Generally, more frequent futility pruning
1101
// scales well
1102
if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha)
1103
{
1104
if (bestValue <= futilityValue && !is_decisive(bestValue)
1105
&& !is_win(futilityValue))
1106
bestValue = futilityValue;
1107
continue;
1108
}
1109
1110
lmrDepth = std::max(lmrDepth, 0);
1111
1112
// Prune moves with negative SEE
1113
if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth))
1114
continue;
1115
}
1116
}
1117
1118
// Step 15. Extensions
1119
// Singular extension search. If all moves but one
1120
// fail low on a search of (alpha-s, beta-s), and just one fails high on
1121
// (alpha, beta), then that move is singular and should be extended. To
1122
// verify this we do a reduced search on the position excluding the ttMove
1123
// and if the result is lower than ttValue minus a margin, then we will
1124
// extend the ttMove. Recursive singular search is avoided.
1125
1126
// (*Scaler) Generally, higher singularBeta (i.e closer to ttValue)
1127
// and lower extension margins scale well.
1128
if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv
1129
&& is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER)
1130
&& ttData.depth >= depth - 3 && !is_shuffling(move, ss, pos))
1131
{
1132
Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60;
1133
Depth singularDepth = newDepth / 2;
1134
1135
ss->excludedMove = move;
1136
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
1137
ss->excludedMove = Move::none();
1138
1139
if (value < singularBeta)
1140
{
1141
int corrValAdj = std::abs(correctionValue) / 230673;
1142
int doubleMargin = -4 + 199 * PvNode - 201 * !ttCapture - corrValAdj
1143
- 897 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 42;
1144
int tripleMargin = 73 + 302 * PvNode - 248 * !ttCapture + 90 * ss->ttPv - corrValAdj
1145
- (ss->ply > rootDepth) * 48;
1146
1147
extension =
1148
1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin);
1149
1150
depth++;
1151
}
1152
1153
// Multi-cut pruning
1154
// Our ttMove is assumed to fail high based on the bound of the TT entry,
1155
// and if after excluding the ttMove with a reduced search we fail high
1156
// over the original beta, we assume this expected cut-node is not
1157
// singular (multiple moves fail high), and we can prune the whole
1158
// subtree by returning a softbound.
1159
else if (value >= beta && !is_decisive(value))
1160
{
1161
ttMoveHistory << std::max(-400 - 100 * depth, -4000);
1162
return value;
1163
}
1164
1165
// Negative extensions
1166
// If other moves failed high over (ttValue - margin) without the
1167
// ttMove on a reduced search, but we cannot do multi-cut because
1168
// (ttValue - margin) is lower than the original beta, we do not know
1169
// if the ttMove is singular or can do a multi-cut, so we reduce the
1170
// ttMove in favor of other moves based on some conditions:
1171
1172
// If the ttMove is assumed to fail high over current beta
1173
else if (ttData.value >= beta)
1174
extension = -3;
1175
1176
// If we are on a cutNode but the ttMove is not assumed to fail high
1177
// over current beta
1178
else if (cutNode)
1179
extension = -2;
1180
}
1181
1182
// Step 16. Make the move
1183
do_move(pos, move, st, givesCheck, ss);
1184
1185
// Add extension to new depth
1186
newDepth += extension;
1187
uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0;
1188
1189
// Decrease reduction for PvNodes (*Scaler)
1190
if (ss->ttPv)
1191
r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922
1192
+ (ttData.depth >= depth) * (934 + cutNode * 1011);
1193
1194
r += 714; // Base reduction offset to compensate for other tweaks
1195
r -= moveCount * 73;
1196
r -= std::abs(correctionValue) / 30370;
1197
1198
// Increase reduction for cut nodes
1199
if (cutNode)
1200
r += 3372 + 997 * !ttData.move;
1201
1202
// Increase reduction if ttMove is a capture
1203
if (ttCapture)
1204
r += 1119;
1205
1206
// Increase reduction if next ply has a lot of fail high
1207
if ((ss + 1)->cutoffCnt > 1)
1208
r += 256 + 1024 * ((ss + 1)->cutoffCnt > 2) + 1024 * allNode;
1209
1210
// For first picked move (ttMove) reduce reduction
1211
if (move == ttData.move)
1212
r -= 2151;
1213
1214
if (capture)
1215
ss->statScore = 868 * int(PieceValue[pos.captured_piece()]) / 128
1216
+ captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())];
1217
else
1218
ss->statScore = 2 * mainHistory[us][move.raw()]
1219
+ (*contHist[0])[movedPiece][move.to_sq()]
1220
+ (*contHist[1])[movedPiece][move.to_sq()];
1221
1222
// Decrease/increase reduction for moves with a good/bad history
1223
r -= ss->statScore * 850 / 8192;
1224
1225
// Scale up reductions for expected ALL nodes
1226
if (allNode)
1227
r += r / (depth + 1);
1228
1229
// Step 17. Late moves reduction / extension (LMR)
1230
if (depth >= 2 && moveCount > 1)
1231
{
1232
// In general we want to cap the LMR depth search at newDepth, but when
1233
// reduction is negative, we allow this move a limited search extension
1234
// beyond the first move depth.
1235
// To prevent problems when the max value is less than the min value,
1236
// std::clamp has been replaced by a more robust implementation.
1237
Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + 2)) + PvNode;
1238
1239
ss->reduction = newDepth - d;
1240
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);
1241
ss->reduction = 0;
1242
1243
// Do a full-depth search when reduced LMR search fails high
1244
// (*Scaler) Shallower searches here don't scale well
1245
if (value > alpha)
1246
{
1247
// Adjust full-depth search based on LMR results - if the result was
1248
// good enough search deeper, if it was bad enough search shallower.
1249
const bool doDeeperSearch = d < newDepth && value > bestValue + 50;
1250
const bool doShallowerSearch = value < bestValue + 9;
1251
1252
newDepth += doDeeperSearch - doShallowerSearch;
1253
1254
if (newDepth > d)
1255
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
1256
1257
// Post LMR continuation history updates
1258
update_continuation_histories(ss, movedPiece, move.to_sq(), 1365);
1259
}
1260
}
1261
1262
// Step 18. Full-depth search when LMR is skipped
1263
else if (!PvNode || moveCount > 1)
1264
{
1265
// Increase reduction if ttMove is not present
1266
if (!ttData.move)
1267
r += 1140;
1268
1269
// Note that if expected reduction is high, we reduce search depth here
1270
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha,
1271
newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode);
1272
}
1273
1274
// For PV nodes only, do a full PV search on the first move or after a fail high,
1275
// otherwise let the parent node fail low with value <= alpha and try another move.
1276
if (PvNode && (moveCount == 1 || value > alpha))
1277
{
1278
(ss + 1)->pv = pv;
1279
(ss + 1)->pv[0] = Move::none();
1280
1281
// Extend move from transposition table if we are about to dive into qsearch.
1282
// decisive score handling improves mate finding and retrograde analysis.
1283
if (move == ttData.move
1284
&& ((is_valid(ttData.value) && is_decisive(ttData.value) && ttData.depth > 0)
1285
|| ttData.depth > 1))
1286
newDepth = std::max(newDepth, 1);
1287
1288
value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);
1289
}
1290
1291
// Step 19. Undo move
1292
undo_move(pos, move);
1293
1294
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
1295
1296
// Step 20. Check for a new best move
1297
// Finished searching the move. If a stop occurred, the return value of
1298
// the search cannot be trusted, and we return immediately without updating
1299
// best move, principal variation nor transposition table.
1300
if (threads.stop.load(std::memory_order_relaxed))
1301
return VALUE_ZERO;
1302
1303
if (rootNode)
1304
{
1305
RootMove& rm = *std::find(rootMoves.begin(), rootMoves.end(), move);
1306
1307
rm.effort += nodes - nodeCount;
1308
1309
rm.averageScore =
1310
rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value;
1311
1312
rm.meanSquaredScore = rm.meanSquaredScore != -VALUE_INFINITE * VALUE_INFINITE
1313
? (value * std::abs(value) + rm.meanSquaredScore) / 2
1314
: value * std::abs(value);
1315
1316
// PV move or new best move?
1317
if (moveCount == 1 || value > alpha)
1318
{
1319
rm.score = rm.uciScore = value;
1320
rm.selDepth = selDepth;
1321
rm.scoreLowerbound = rm.scoreUpperbound = false;
1322
1323
if (value >= beta)
1324
{
1325
rm.scoreLowerbound = true;
1326
rm.uciScore = beta;
1327
}
1328
else if (value <= alpha)
1329
{
1330
rm.scoreUpperbound = true;
1331
rm.uciScore = alpha;
1332
}
1333
1334
rm.pv.resize(1);
1335
1336
assert((ss + 1)->pv);
1337
1338
for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m)
1339
rm.pv.push_back(*m);
1340
1341
// We record how often the best move has been changed in each iteration.
1342
// This information is used for time management. In MultiPV mode,
1343
// we must take care to only do this for the first PV line.
1344
if (moveCount > 1 && !pvIdx)
1345
++bestMoveChanges;
1346
}
1347
else
1348
// All other moves but the PV, are set to the lowest value: this
1349
// is not a problem when sorting because the sort is stable and the
1350
// move position in the list is preserved - just the PV is pushed up.
1351
rm.score = -VALUE_INFINITE;
1352
}
1353
1354
// In case we have an alternative move equal in eval to the current bestmove,
1355
// promote it to bestmove by pretending it just exceeds alpha (but not beta).
1356
int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 14) == 0
1357
&& !is_win(std::abs(value) + 1));
1358
1359
if (value + inc > bestValue)
1360
{
1361
bestValue = value;
1362
1363
if (value + inc > alpha)
1364
{
1365
bestMove = move;
1366
1367
if (PvNode && !rootNode) // Update pv even in fail-high case
1368
update_pv(ss->pv, move, (ss + 1)->pv);
1369
1370
if (value >= beta)
1371
{
1372
// (*Scaler) Infrequent and small updates scale well
1373
ss->cutoffCnt += (extension < 2) || PvNode;
1374
assert(value >= beta); // Fail high
1375
break;
1376
}
1377
1378
// Reduce other moves if we have found at least one score improvement
1379
if (depth > 2 && depth < 14 && !is_decisive(value))
1380
depth -= 2;
1381
1382
assert(depth > 0);
1383
alpha = value; // Update alpha! Always alpha < beta
1384
}
1385
}
1386
1387
// If the move is worse than some previously searched move,
1388
// remember it, to update its stats later.
1389
if (move != bestMove && moveCount <= SEARCHEDLIST_CAPACITY)
1390
{
1391
if (capture)
1392
capturesSearched.push_back(move);
1393
else
1394
quietsSearched.push_back(move);
1395
}
1396
}
1397
1398
// Step 21. Check for mate and stalemate
1399
// All legal moves have been searched and if there are no legal moves, it
1400
// must be a mate or a stalemate. If we are in a singular extension search then
1401
// return a fail low score.
1402
1403
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
1404
1405
// Adjust best value for fail high cases
1406
if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(alpha))
1407
bestValue = (bestValue * depth + beta) / (depth + 1);
1408
1409
if (!moveCount)
1410
bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW;
1411
1412
// If there is a move that produces search value greater than alpha,
1413
// we update the stats of searched moves.
1414
else if (bestMove)
1415
{
1416
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth,
1417
ttData.move, moveCount);
1418
if (!PvNode)
1419
ttMoveHistory << (bestMove == ttData.move ? 809 : -865);
1420
}
1421
1422
// Bonus for prior quiet countermove that caused the fail low
1423
else if (!priorCapture && prevSq != SQ_NONE)
1424
{
1425
int bonusScale = -215;
1426
bonusScale -= (ss - 1)->statScore / 100;
1427
bonusScale += std::min(56 * depth, 489);
1428
bonusScale += 184 * ((ss - 1)->moveCount > 8);
1429
bonusScale += 147 * (!ss->inCheck && bestValue <= ss->staticEval - 107);
1430
bonusScale += 156 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 65);
1431
1432
bonusScale = std::max(bonusScale, 0);
1433
1434
// scaledBonus ranges from 0 to roughly 2.3M, overflows happen for multipliers larger than 900
1435
const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale;
1436
1437
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
1438
scaledBonus * 406 / 32768);
1439
1440
mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768;
1441
1442
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
1443
sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << scaledBonus * 290 / 8192;
1444
}
1445
1446
// Bonus for prior capture countermove that caused the fail low
1447
else if (priorCapture && prevSq != SQ_NONE)
1448
{
1449
Piece capturedPiece = pos.captured_piece();
1450
assert(capturedPiece != NO_PIECE);
1451
captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012;
1452
}
1453
1454
if (PvNode)
1455
bestValue = std::min(bestValue, maxValue);
1456
1457
// If no good move is found and the previous position was ttPv, then the previous
1458
// opponent move is probably good and the new position is added to the search tree.
1459
if (bestValue <= alpha)
1460
ss->ttPv = ss->ttPv || (ss - 1)->ttPv;
1461
1462
// Write gathered information in transposition table. Note that the
1463
// static evaluation is saved as it was before correction history.
1464
if (!excludedMove && !(rootNode && pvIdx))
1465
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv,
1466
bestValue >= beta ? BOUND_LOWER
1467
: PvNode && bestMove ? BOUND_EXACT
1468
: BOUND_UPPER,
1469
moveCount != 0 ? depth : std::min(MAX_PLY - 1, depth + 6), bestMove,
1470
unadjustedStaticEval, tt.generation());
1471
1472
// Adjust correction history if the best move is not a capture
1473
// and the error direction matches whether we are above/below bounds.
1474
if (!ss->inCheck && !(bestMove && pos.capture(bestMove))
1475
&& (bestValue > ss->staticEval) == bool(bestMove))
1476
{
1477
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (bestMove ? 10 : 8),
1478
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
1479
update_correction_history(pos, ss, *this, bonus);
1480
}
1481
1482
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
1483
1484
return bestValue;
1485
}
1486
1487
1488
// Quiescence search function, which is called by the main search function with
1489
// depth zero, or recursively with further decreasing depth. With depth <= 0, we
1490
// "should" be using static eval only, but tactical moves may confuse the static eval.
1491
// To fight this horizon effect, we implement this qsearch of tactical moves.
1492
// See https://www.chessprogramming.org/Horizon_Effect
1493
// and https://www.chessprogramming.org/Quiescence_Search
1494
template<NodeType nodeType>
1495
Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) {
1496
1497
static_assert(nodeType != Root);
1498
constexpr bool PvNode = nodeType == PV;
1499
1500
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
1501
assert(PvNode || (alpha == beta - 1));
1502
1503
// Check if we have an upcoming move that draws by repetition
1504
if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
1505
{
1506
alpha = value_draw(nodes);
1507
if (alpha >= beta)
1508
return alpha;
1509
}
1510
1511
Move pv[MAX_PLY + 1];
1512
StateInfo st;
1513
1514
Key posKey;
1515
Move move, bestMove;
1516
Value bestValue, value, futilityBase;
1517
bool pvHit, givesCheck, capture;
1518
int moveCount;
1519
1520
// Step 1. Initialize node
1521
if (PvNode)
1522
{
1523
(ss + 1)->pv = pv;
1524
ss->pv[0] = Move::none();
1525
}
1526
1527
bestMove = Move::none();
1528
ss->inCheck = pos.checkers();
1529
moveCount = 0;
1530
1531
// Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)
1532
if (PvNode && selDepth < ss->ply + 1)
1533
selDepth = ss->ply + 1;
1534
1535
// Step 2. Check for an immediate draw or maximum ply reached
1536
if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY)
1537
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;
1538
1539
assert(0 <= ss->ply && ss->ply < MAX_PLY);
1540
1541
// Step 3. Transposition table lookup
1542
posKey = pos.key();
1543
auto [ttHit, ttData, ttWriter] = tt.probe(posKey);
1544
// Need further processing of the saved data
1545
ss->ttHit = ttHit;
1546
ttData.move = ttHit ? ttData.move : Move::none();
1547
ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE;
1548
pvHit = ttHit && ttData.is_pv;
1549
1550
// At non-PV nodes we check for an early TT cutoff
1551
if (!PvNode && ttData.depth >= DEPTH_QS
1552
&& is_valid(ttData.value) // Can happen when !ttHit or when access race in probe()
1553
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)))
1554
return ttData.value;
1555
1556
// Step 4. Static evaluation of the position
1557
Value unadjustedStaticEval = VALUE_NONE;
1558
if (ss->inCheck)
1559
bestValue = futilityBase = -VALUE_INFINITE;
1560
else
1561
{
1562
const auto correctionValue = correction_value(*this, pos, ss);
1563
1564
if (ss->ttHit)
1565
{
1566
// Never assume anything about values stored in TT
1567
unadjustedStaticEval = ttData.eval;
1568
1569
if (!is_valid(unadjustedStaticEval))
1570
unadjustedStaticEval = evaluate(pos);
1571
1572
ss->staticEval = bestValue =
1573
to_corrected_static_eval(unadjustedStaticEval, correctionValue);
1574
1575
// ttValue can be used as a better position evaluation
1576
if (is_valid(ttData.value) && !is_decisive(ttData.value)
1577
&& (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER)))
1578
bestValue = ttData.value;
1579
}
1580
else
1581
{
1582
unadjustedStaticEval = evaluate(pos);
1583
ss->staticEval = bestValue =
1584
to_corrected_static_eval(unadjustedStaticEval, correctionValue);
1585
}
1586
1587
// Stand pat. Return immediately if static value is at least beta
1588
if (bestValue >= beta)
1589
{
1590
if (!is_decisive(bestValue))
1591
bestValue = (bestValue + beta) / 2;
1592
1593
if (!ss->ttHit)
1594
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
1595
DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval,
1596
tt.generation());
1597
return bestValue;
1598
}
1599
1600
if (bestValue > alpha)
1601
alpha = bestValue;
1602
1603
futilityBase = ss->staticEval + 351;
1604
}
1605
1606
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory};
1607
1608
Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE;
1609
1610
// Initialize a MovePicker object for the current position, and prepare to search
1611
// the moves. We presently use two stages of move generator in quiescence search:
1612
// captures, or evasions only when in check.
1613
MovePicker mp(pos, ttData.move, DEPTH_QS, &mainHistory, &lowPlyHistory, &captureHistory,
1614
contHist, &sharedHistory, ss->ply);
1615
1616
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
1617
// cutoff occurs.
1618
while ((move = mp.next_move()) != Move::none())
1619
{
1620
assert(move.is_ok());
1621
1622
if (!pos.legal(move))
1623
continue;
1624
1625
givesCheck = pos.gives_check(move);
1626
capture = pos.capture_stage(move);
1627
1628
moveCount++;
1629
1630
// Step 6. Pruning
1631
if (!is_loss(bestValue))
1632
{
1633
// Futility pruning and moveCount pruning
1634
if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase)
1635
&& move.type_of() != PROMOTION)
1636
{
1637
if (moveCount > 2)
1638
continue;
1639
1640
Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())];
1641
1642
// If static eval + value of piece we are going to capture is
1643
// much lower than alpha, we can prune this move.
1644
if (futilityValue <= alpha)
1645
{
1646
bestValue = std::max(bestValue, futilityValue);
1647
continue;
1648
}
1649
1650
// If static exchange evaluation is low enough
1651
// we can prune this move.
1652
if (!pos.see_ge(move, alpha - futilityBase))
1653
{
1654
bestValue = std::max(bestValue, std::min(alpha, futilityBase));
1655
continue;
1656
}
1657
}
1658
1659
// Skip non-captures
1660
if (!capture)
1661
continue;
1662
1663
// Do not search moves with bad enough SEE values
1664
if (!pos.see_ge(move, -80))
1665
continue;
1666
}
1667
1668
// Step 7. Make and search the move
1669
do_move(pos, move, st, givesCheck, ss);
1670
1671
value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha);
1672
undo_move(pos, move);
1673
1674
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
1675
1676
// Step 8. Check for a new best move
1677
if (value > bestValue)
1678
{
1679
bestValue = value;
1680
1681
if (value > alpha)
1682
{
1683
bestMove = move;
1684
1685
if (PvNode) // Update pv even in fail-high case
1686
update_pv(ss->pv, move, (ss + 1)->pv);
1687
1688
if (value < beta) // Update alpha here!
1689
alpha = value;
1690
else
1691
break; // Fail high
1692
}
1693
}
1694
}
1695
1696
// Step 9. Check for mate
1697
// All legal moves have been searched. A special case: if we are
1698
// in check and no legal moves were found, it is checkmate.
1699
if (ss->inCheck && bestValue == -VALUE_INFINITE)
1700
{
1701
assert(!MoveList<LEGAL>(pos).size());
1702
return mated_in(ss->ply); // Plies to mate from the root
1703
}
1704
1705
if (!is_decisive(bestValue) && bestValue > beta)
1706
bestValue = (bestValue + beta) / 2;
1707
1708
Color us = pos.side_to_move();
1709
if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us)
1710
&& type_of(pos.captured_piece()) >= ROOK)
1711
{
1712
if (!((us == WHITE ? shift<NORTH>(pos.pieces(us, PAWN))
1713
: shift<SOUTH>(pos.pieces(us, PAWN)))
1714
& ~pos.pieces())) // no pawn pushes available
1715
{
1716
pos.state()->checkersBB = Rank1BB; // search for legal king-moves only
1717
if (!MoveList<LEGAL>(pos).size()) // stalemate
1718
bestValue = VALUE_DRAW;
1719
pos.state()->checkersBB = 0;
1720
}
1721
}
1722
1723
// Save gathered info in transposition table. The static evaluation
1724
// is saved as it was before adjustment by correction history.
1725
ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit,
1726
bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, DEPTH_QS, bestMove,
1727
unadjustedStaticEval, tt.generation());
1728
1729
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
1730
1731
return bestValue;
1732
}
1733
1734
Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {
1735
int reductionScale = reductions[d] * reductions[mn];
1736
return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182;
1737
}
1738
1739
// elapsed() returns the time elapsed since the search started. If the
1740
// 'nodestime' option is enabled, it will return the count of nodes searched
1741
// instead. This function is called to check whether the search should be
1742
// stopped based on predefined thresholds like time limits or nodes searched.
1743
//
1744
// elapsed_time() returns the actual time elapsed since the start of the search.
1745
// This function is intended for use only when printing PV outputs, and not used
1746
// for making decisions within the search algorithm itself.
1747
TimePoint Search::Worker::elapsed() const {
1748
return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); });
1749
}
1750
1751
TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); }
1752
1753
Value Search::Worker::evaluate(const Position& pos) {
1754
return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable,
1755
optimism[pos.side_to_move()]);
1756
}
1757
1758
namespace {
1759
// Adjusts a mate or TB score from "plies to mate from the root" to
1760
// "plies to mate from the current position". Standard scores are unchanged.
1761
// The function is called before storing a value in the transposition table.
1762
Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; }
1763
1764
1765
// Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition
1766
// table (which refers to the plies to mate/be mated from current position) to
1767
// "plies to mate/be mated (TB win/loss) from the root". However, to avoid
1768
// potentially false mate or TB scores related to the 50 moves rule and the
1769
// graph history interaction, we return the highest non-TB score instead.
1770
Value value_from_tt(Value v, int ply, int r50c) {
1771
1772
if (!is_valid(v))
1773
return VALUE_NONE;
1774
1775
// handle TB win or better
1776
if (is_win(v))
1777
{
1778
// Downgrade a potentially false mate score
1779
if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c)
1780
return VALUE_TB_WIN_IN_MAX_PLY - 1;
1781
1782
// Downgrade a potentially false TB score.
1783
if (VALUE_TB - v > 100 - r50c)
1784
return VALUE_TB_WIN_IN_MAX_PLY - 1;
1785
1786
return v - ply;
1787
}
1788
1789
// handle TB loss or worse
1790
if (is_loss(v))
1791
{
1792
// Downgrade a potentially false mate score.
1793
if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c)
1794
return VALUE_TB_LOSS_IN_MAX_PLY + 1;
1795
1796
// Downgrade a potentially false TB score.
1797
if (VALUE_TB + v > 100 - r50c)
1798
return VALUE_TB_LOSS_IN_MAX_PLY + 1;
1799
1800
return v + ply;
1801
}
1802
1803
return v;
1804
}
1805
1806
1807
// Adds current move and appends child pv[]
1808
void update_pv(Move* pv, Move move, const Move* childPv) {
1809
1810
for (*pv++ = move; childPv && *childPv != Move::none();)
1811
*pv++ = *childPv++;
1812
*pv = Move::none();
1813
}
1814
1815
1816
// Updates stats at the end of search() when a bestMove is found
1817
void update_all_stats(const Position& pos,
1818
Stack* ss,
1819
Search::Worker& workerThread,
1820
Move bestMove,
1821
Square prevSq,
1822
SearchedList& quietsSearched,
1823
SearchedList& capturesSearched,
1824
Depth depth,
1825
Move ttMove,
1826
int moveCount) {
1827
1828
CapturePieceToHistory& captureHistory = workerThread.captureHistory;
1829
Piece movedPiece = pos.moved_piece(bestMove);
1830
PieceType capturedPiece;
1831
1832
int bonus =
1833
std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove) + (ss - 1)->statScore / 32;
1834
int malus = std::min(848 * depth - 207, 2446) - 17 * moveCount;
1835
1836
if (!pos.capture_stage(bestMove))
1837
{
1838
update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 910 / 1024);
1839
1840
int i = 0;
1841
// Decrease stats for all non-best quiet moves
1842
for (Move move : quietsSearched)
1843
{
1844
i++;
1845
int actualMalus = malus * 1085 / 1024;
1846
if (i > 5)
1847
actualMalus -= actualMalus * (i - 5) / i;
1848
update_quiet_histories(pos, ss, workerThread, move, -actualMalus);
1849
}
1850
}
1851
else
1852
{
1853
// Increase stats for the best move in case it was a capture move
1854
capturedPiece = type_of(pos.piece_on(bestMove.to_sq()));
1855
captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1395 / 1024;
1856
}
1857
1858
// Extra penalty for a quiet early move that was not a TT move in
1859
// previous ply when it gets refuted.
1860
if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece())
1861
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 602 / 1024);
1862
1863
// Decrease stats for all non-best capture moves
1864
for (Move move : capturesSearched)
1865
{
1866
movedPiece = pos.moved_piece(move);
1867
capturedPiece = type_of(pos.piece_on(move.to_sq()));
1868
captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1448 / 1024;
1869
}
1870
}
1871
1872
1873
// Updates histories of the move pairs formed by moves
1874
// at ply -1, -2, -3, -4, and -6 with current move.
1875
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
1876
static std::array<ConthistBonus, 6> conthist_bonuses = {
1877
{{1, 1133}, {2, 683}, {3, 312}, {4, 582}, {5, 149}, {6, 474}}};
1878
1879
for (const auto [i, weight] : conthist_bonuses)
1880
{
1881
// Only update the first 2 continuation histories if we are in check
1882
if (ss->inCheck && i > 2)
1883
break;
1884
1885
if (((ss - i)->currentMove).is_ok())
1886
(*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2);
1887
}
1888
}
1889
1890
// Updates move sorting heuristics
1891
1892
void update_quiet_histories(
1893
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
1894
1895
Color us = pos.side_to_move();
1896
workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort
1897
1898
if (ss->ply < LOW_PLY_HISTORY_SIZE)
1899
workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 805 / 1024;
1900
1901
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024);
1902
1903
workerThread.sharedHistory.pawn_entry(pos)[pos.moved_piece(move)][move.to_sq()]
1904
<< bonus * (bonus > 0 ? 905 : 505) / 1024;
1905
}
1906
1907
}
1908
1909
// When playing with strength handicap, choose the best move among a set of
1910
// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
1911
Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) {
1912
static PRNG rng(now()); // PRNG sequence should be non-deterministic
1913
1914
// RootMoves are already sorted by score in descending order
1915
Value topScore = rootMoves[0].score;
1916
int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue));
1917
int maxScore = -VALUE_INFINITE;
1918
double weakness = 120 - 2 * level;
1919
1920
// Choose best move. For each move score we add two terms, both dependent on
1921
// weakness. One is deterministic and bigger for weaker levels, and one is
1922
// random. Then we choose the move with the resulting highest score.
1923
for (size_t i = 0; i < multiPV; ++i)
1924
{
1925
// This is our magic formula
1926
int push = int(weakness * int(topScore - rootMoves[i].score)
1927
+ delta * (rng.rand<unsigned>() % int(weakness)))
1928
/ 128;
1929
1930
if (rootMoves[i].score + push >= maxScore)
1931
{
1932
maxScore = rootMoves[i].score + push;
1933
best = rootMoves[i].pv[0];
1934
}
1935
}
1936
1937
return best;
1938
}
1939
1940
// Used to print debug info and, more importantly, to detect
1941
// when we are out of available time and thus stop the search.
1942
void SearchManager::check_time(Search::Worker& worker) {
1943
if (--callsCnt > 0)
1944
return;
1945
1946
// When using nodes, ensure checking rate is not lower than 0.1% of nodes
1947
callsCnt = worker.limits.nodes ? std::min(512, int(worker.limits.nodes / 1024)) : 512;
1948
1949
static TimePoint lastInfoTime = now();
1950
1951
TimePoint elapsed = tm.elapsed([&worker]() { return worker.threads.nodes_searched(); });
1952
TimePoint tick = worker.limits.startTime + elapsed;
1953
1954
if (tick - lastInfoTime >= 1000)
1955
{
1956
lastInfoTime = tick;
1957
dbg_print();
1958
}
1959
1960
// We should not stop pondering until told so by the GUI
1961
if (ponder)
1962
return;
1963
1964
if (
1965
// Later we rely on the fact that we can at least use the mainthread previous
1966
// root-search score and PV in a multithreaded environment to prove mated-in scores.
1967
worker.completedDepth >= 1
1968
&& ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit))
1969
|| (worker.limits.movetime && elapsed >= worker.limits.movetime)
1970
|| (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes)))
1971
worker.threads.stop = worker.threads.abortedSearch = true;
1972
}
1973
1974
// Used to correct and extend PVs for moves that have a TB (but not a mate) score.
1975
// Keeps the search based PV for as long as it is verified to maintain the game
1976
// outcome, truncates afterwards. Finally, extends to mate the PV, providing a
1977
// possible continuation (but not a proven mating line).
1978
void syzygy_extend_pv(const OptionsMap& options,
1979
const Search::LimitsType& limits,
1980
Position& pos,
1981
RootMove& rootMove,
1982
Value& v) {
1983
1984
auto t_start = std::chrono::steady_clock::now();
1985
int moveOverhead = int(options["Move Overhead"]);
1986
bool rule50 = bool(options["Syzygy50MoveRule"]);
1987
1988
// Do not use more than moveOverhead / 2 time, if time management is active
1989
auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool {
1990
auto t_end = std::chrono::steady_clock::now();
1991
return limits.use_time_management()
1992
&& 2 * std::chrono::duration<double, std::milli>(t_end - t_start).count()
1993
> moveOverhead;
1994
};
1995
1996
std::list<StateInfo> sts;
1997
1998
// Step 0, do the rootMove, no correction allowed, as needed for MultiPV in TB.
1999
auto& stRoot = sts.emplace_back();
2000
pos.do_move(rootMove.pv[0], stRoot);
2001
int ply = 1;
2002
2003
// Step 1, walk the PV to the last position in TB with correct decisive score
2004
while (size_t(ply) < rootMove.pv.size())
2005
{
2006
Move& pvMove = rootMove.pv[ply];
2007
2008
RootMoves legalMoves;
2009
for (const auto& m : MoveList<LEGAL>(pos))
2010
legalMoves.emplace_back(m);
2011
2012
Tablebases::Config config =
2013
Tablebases::rank_root_moves(options, pos, legalMoves, false, time_abort);
2014
RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove);
2015
2016
if (legalMoves[0].tbRank != rm.tbRank)
2017
break;
2018
2019
ply++;
2020
2021
auto& st = sts.emplace_back();
2022
pos.do_move(pvMove, st);
2023
2024
// Do not allow for repetitions or drawing moves along the PV in TB regime
2025
if (config.rootInTB && ((rule50 && pos.is_draw(ply)) || pos.is_repetition(ply)))
2026
{
2027
pos.undo_move(pvMove);
2028
ply--;
2029
break;
2030
}
2031
2032
// Full PV shown will thus be validated and end in TB.
2033
// If we cannot validate the full PV in time, we do not show it.
2034
if (config.rootInTB && time_abort())
2035
break;
2036
}
2037
2038
// Resize the PV to the correct part
2039
rootMove.pv.resize(ply);
2040
2041
// Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info
2042
// using top ranked moves (minimal DTZ), which gives optimal mates only for simple
2043
// endgames e.g. KRvK.
2044
while (!(rule50 && pos.is_draw(0)))
2045
{
2046
if (time_abort())
2047
break;
2048
2049
RootMoves legalMoves;
2050
for (const auto& m : MoveList<LEGAL>(pos))
2051
{
2052
auto& rm = legalMoves.emplace_back(m);
2053
StateInfo tmpSI;
2054
pos.do_move(m, tmpSI);
2055
// Give a score of each move to break DTZ ties restricting opponent mobility,
2056
// but not giving the opponent a capture.
2057
for (const auto& mOpp : MoveList<LEGAL>(pos))
2058
rm.tbRank -= pos.capture(mOpp) ? 100 : 1;
2059
pos.undo_move(m);
2060
}
2061
2062
// Mate found
2063
if (legalMoves.size() == 0)
2064
break;
2065
2066
// Sort moves according to their above assigned rank.
2067
// This will break ties for moves with equal DTZ in rank_root_moves.
2068
std::stable_sort(
2069
legalMoves.begin(), legalMoves.end(),
2070
[](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });
2071
2072
// The winning side tries to minimize DTZ, the losing side maximizes it
2073
Tablebases::Config config =
2074
Tablebases::rank_root_moves(options, pos, legalMoves, true, time_abort);
2075
2076
// If DTZ is not available we might not find a mate, so we bail out
2077
if (!config.rootInTB || config.cardinality > 0)
2078
break;
2079
2080
ply++;
2081
2082
Move& pvMove = legalMoves[0].pv[0];
2083
rootMove.pv.push_back(pvMove);
2084
auto& st = sts.emplace_back();
2085
pos.do_move(pvMove, st);
2086
}
2087
2088
// Finding a draw in this function is an exceptional case, that cannot happen when rule50 is false or
2089
// during engine game play, since we have a winning score, and play correctly
2090
// with TB support. However, it can be that a position is draw due to the 50 move
2091
// rule if it has been been reached on the board with a non-optimal 50 move counter
2092
// (e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 ) which TB with dtz counter rounding
2093
// cannot always correctly rank. See also
2094
// https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495
2095
// We adjust the score to match the found PV. Note that a TB loss score can be
2096
// displayed if the engine did not find a drawing move yet, but eventually search
2097
// will figure it out (e.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 )
2098
if (pos.is_draw(0))
2099
v = VALUE_DRAW;
2100
2101
// Undo the PV moves
2102
for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it)
2103
pos.undo_move(*it);
2104
2105
// Inform if we couldn't get a full extension in time
2106
if (time_abort())
2107
sync_cout
2108
<< "info string Syzygy based PV extension requires more time, increase Move Overhead as needed."
2109
<< sync_endl;
2110
}
2111
2112
void SearchManager::pv(Search::Worker& worker,
2113
const ThreadPool& threads,
2114
const TranspositionTable& tt,
2115
Depth depth) {
2116
2117
const auto nodes = threads.nodes_searched();
2118
auto& rootMoves = worker.rootMoves;
2119
auto& pos = worker.rootPos;
2120
size_t pvIdx = worker.pvIdx;
2121
size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size());
2122
uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0);
2123
2124
for (size_t i = 0; i < multiPV; ++i)
2125
{
2126
bool updated = rootMoves[i].score != -VALUE_INFINITE;
2127
2128
if (depth == 1 && !updated && i > 0)
2129
continue;
2130
2131
Depth d = updated ? depth : std::max(1, depth - 1);
2132
Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore;
2133
2134
if (v == -VALUE_INFINITE)
2135
v = VALUE_ZERO;
2136
2137
bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB;
2138
v = tb ? rootMoves[i].tbScore : v;
2139
2140
bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact
2141
2142
// Potentially correct and extend the PV, and in exceptional cases v
2143
if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY
2144
&& ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact))
2145
syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v);
2146
2147
std::string pv;
2148
for (Move m : rootMoves[i].pv)
2149
pv += UCIEngine::move(m, pos.is_chess960()) + " ";
2150
2151
// Remove last whitespace
2152
if (!pv.empty())
2153
pv.pop_back();
2154
2155
auto wdl = worker.options["UCI_ShowWDL"] ? UCIEngine::wdl(v, pos) : "";
2156
auto bound = rootMoves[i].scoreLowerbound
2157
? "lowerbound"
2158
: (rootMoves[i].scoreUpperbound ? "upperbound" : "");
2159
2160
InfoFull info;
2161
2162
info.depth = d;
2163
info.selDepth = rootMoves[i].selDepth;
2164
info.multiPV = i + 1;
2165
info.score = {v, pos};
2166
info.wdl = wdl;
2167
2168
if (!isExact)
2169
info.bound = bound;
2170
2171
TimePoint time = std::max(TimePoint(1), tm.elapsed_time());
2172
info.timeMs = time;
2173
info.nodes = nodes;
2174
info.nps = nodes * 1000 / time;
2175
info.tbHits = tbHits;
2176
info.pv = pv;
2177
info.hashfull = tt.hashfull();
2178
2179
updates.onUpdateFull(info);
2180
}
2181
}
2182
2183
// Called in case we have no ponder move before exiting the search,
2184
// for instance, in case we stop the search during a fail high at root.
2185
// We try hard to have a ponder move to return to the GUI,
2186
// otherwise in case of 'ponder on' we have nothing to think about.
2187
bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) {
2188
2189
StateInfo st;
2190
2191
assert(pv.size() == 1);
2192
if (pv[0] == Move::none())
2193
return false;
2194
2195
pos.do_move(pv[0], st, &tt);
2196
2197
auto [ttHit, ttData, ttWriter] = tt.probe(pos.key());
2198
if (ttHit)
2199
{
2200
if (MoveList<LEGAL>(pos).contains(ttData.move))
2201
pv.push_back(ttData.move);
2202
}
2203
2204
pos.undo_move(pv[0]);
2205
return pv.size() > 1;
2206
}
2207
2208
2209
} // namespace Stockfish
2210
2211