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