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