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