const int skipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 };
// Razoring and futility margin based on depth
- const int razor_margin[4] = { 483, 570, 603, 554 };
+ // razor_margin[0] is unused as long as depth >= ONE_PLY in search
+ const int razor_margin[] = { 0, 570, 603, 554 };
Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); }
// Futility and reductions lookup tables, initialized at startup
int FutilityMoveCounts[2][16]; // [improving][depth]
int Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber]
+ // Threshold used for countermoves based pruning
+ const int CounterMovePruneThreshold = 0;
+
template <bool PvNode> Depth reduction(bool i, Depth d, int mn) {
return Reductions[PvNode][i][std::min(d / ONE_PLY, 63)][std::min(mn, 63)] * ONE_PLY;
}
// History and stats update bonus, based on depth
- Value stat_bonus(Depth depth) {
- int d = depth / ONE_PLY ;
- return d > 17 ? VALUE_ZERO : Value(d * d + 2 * d - 2);
+ int stat_bonus(Depth depth) {
+ int d = depth / ONE_PLY;
+ return d > 17 ? 0 : d * d + 2 * d - 2;
}
// Skill structure is used to implement strength limit
Value value_to_tt(Value v, int ply);
Value value_from_tt(Value v, int ply);
void update_pv(Move* pv, Move move, Move* childPv);
- void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus);
- void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, Value bonus);
+ void update_cm_stats(Stack* ss, Piece pc, Square s, int bonus);
+ void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, int bonus);
void check_time();
} // namespace
}
-/// Search::clear() resets search state to zero, to obtain reproducible results
+/// Search::clear() resets search state to its initial value, to obtain reproducible results
void Search::clear() {
for (Thread* th : Threads)
{
- th->counterMoves.clear();
- th->history.clear();
- th->counterMoveHistory.clear();
th->resetCalls = true;
+ th->counterMoves.fill(MOVE_NONE);
+ th->history.fill(0);
+
+ for (auto& to : th->counterMoveHistory)
+ for (auto& h : to)
+ h.fill(0);
+
+ th->counterMoveHistory[NO_PIECE][0].fill(CounterMovePruneThreshold - 1);
}
Threads.main()->previousScore = VALUE_INFINITE;
Color us = rootPos.side_to_move();
Time.init(Limits, us, rootPos.game_ply());
+ TT.new_search();
int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns
DrawValue[ us] = VALUE_DRAW - Value(contempt);
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
std::memset(ss-4, 0, 7 * sizeof(Stack));
- for(int i = 4; i > 0; i--)
- (ss-i)->counterMoves = &this->counterMoveHistory[NO_PIECE][0]; // Use as sentinel
+ for (int i = 4; i > 0; i--)
+ (ss-i)->history = &this->counterMoveHistory[NO_PIECE][0]; // Use as sentinel
bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
EasyMove.clear();
mainThread->easyMovePlayed = mainThread->failedLow = false;
mainThread->bestMoveChanges = 0;
- TT.new_search();
}
size_t multiPV = Options["MultiPV"];
multiPV = std::min(multiPV, rootMoves.size());
// Iterative deepening loop until requested to stop or the target depth is reached
- while ( (rootDepth += ONE_PLY) < DEPTH_MAX
+ while ( (rootDepth = rootDepth + ONE_PLY) < DEPTH_MAX
&& !Signals.stop
&& (!Limits.depth || Threads.main()->rootDepth / ONE_PLY <= Limits.depth))
{
{
bestValue = ::search<PV>(rootPos, ss, alpha, beta, rootDepth, false, false);
+ this->tbHits = rootPos.tb_hits();
+ this->nodes = rootPos.nodes_searched();
+
// Bring the best move to the front. It is critical that sorting
// is done with a stable algorithm because all the values but the
// first and eventually the new best one are set to -VALUE_INFINITE
Depth extension, newDepth;
Value bestValue, value, ttValue, eval;
bool ttHit, inCheck, givesCheck, singularExtensionNode, improving;
- bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets;
+ bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture;
Piece moved_piece;
int moveCount, quietCount;
// Step 1. Initialize node
Thread* thisThread = pos.this_thread();
inCheck = pos.checkers();
- moveCount = quietCount = ss->moveCount = 0;
- ss->history = VALUE_ZERO;
+ moveCount = quietCount = ss->moveCount = 0;
+ ss->statScore = 0;
bestValue = -VALUE_INFINITE;
ss->ply = (ss-1)->ply + 1;
if (thisThread->resetCalls.load(std::memory_order_relaxed))
{
thisThread->resetCalls = false;
+
+ thisThread->tbHits = pos.tb_hits();
+ thisThread->nodes = pos.nodes_searched();
+
// At low node count increase the checking rate to about 0.1% of nodes
// otherwise use a default value.
- thisThread->callsCnt = Limits.nodes ? std::min((int64_t)4096, Limits.nodes / 1024)
+ thisThread->callsCnt = Limits.nodes ? std::min(4096, int(Limits.nodes / 1024))
: 4096;
}
assert(0 <= ss->ply && ss->ply < MAX_PLY);
ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
- ss->counterMoves = &thisThread->counterMoveHistory[NO_PIECE][0];
+ ss->history = &thisThread->counterMoveHistory[NO_PIECE][0];
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
Square prevSq = to_sq((ss-1)->currentMove);
// Penalty for a quiet ttMove that fails low
else if (!pos.capture_or_promotion(ttMove))
{
- Value penalty = -stat_bonus(depth);
+ int penalty = -stat_bonus(depth);
thisThread->history.update(pos.side_to_move(), ttMove, penalty);
update_cm_stats(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
}
if (err != TB::ProbeState::FAIL)
{
- thisThread->tbHits++;
+ pos.increment_tbHits();
int drawScore = TB::UseRule50 ? 1 : 0;
Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY;
ss->currentMove = MOVE_NULL;
- ss->counterMoves = &thisThread->counterMoveHistory[NO_PIECE][0];
+ ss->history = &thisThread->counterMoveHistory[NO_PIECE][0];
pos.do_null_move(st);
Value nullValue = depth-R < ONE_PLY ? -qsearch<NonPV, false>(pos, ss+1, -beta, -beta+1)
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
{
Value rbeta = std::min(beta + 200, VALUE_INFINITE);
- Depth rdepth = depth - 4 * ONE_PLY;
- assert(rdepth >= ONE_PLY);
assert(is_ok((ss-1)->currentMove));
MovePicker mp(pos, ttMove, rbeta - ss->staticEval);
if (pos.legal(move))
{
ss->currentMove = move;
- ss->counterMoves = &thisThread->counterMoveHistory[pos.moved_piece(move)][to_sq(move)];
+ ss->history = &thisThread->counterMoveHistory[pos.moved_piece(move)][to_sq(move)];
+ assert(depth >= 5 * ONE_PLY);
pos.do_move(move, st);
- value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode, false);
+ value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode, false);
pos.undo_move(move);
if (value >= rbeta)
return value;
moves_loop: // When in check search starts from here
- const CounterMoveStats& cmh = *(ss-1)->counterMoves;
- const CounterMoveStats& fmh = *(ss-2)->counterMoves;
- const CounterMoveStats& fm2 = *(ss-4)->counterMoves;
- const bool cm_ok = is_ok((ss-1)->currentMove);
- const bool fm_ok = is_ok((ss-2)->currentMove);
- const bool f2_ok = is_ok((ss-4)->currentMove);
+ const PieceToHistory& cmh = *(ss-1)->history;
+ const PieceToHistory& fmh = *(ss-2)->history;
+ const PieceToHistory& fm2 = *(ss-4)->history;
MovePicker mp(pos, ttMove, depth, ss);
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3 * ONE_PLY;
skipQuiets = false;
+ ttCapture = false;
// Step 11. Loop through moves
// Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs
// (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move
// is singular and should be extended. To verify this we do a reduced search
// on all the other moves but the ttMove and if the result is lower than
- // ttValue minus a margin then we extend the ttMove.
+ // ttValue minus a margin then we will extend the ttMove.
if ( singularExtensionNode
&& move == ttMove
&& pos.legal(move))
if (value < rBeta)
extension = ONE_PLY;
}
- else if ( givesCheck
+ else if ( givesCheck
&& !moveCountPruning
- && pos.see_ge(move, VALUE_ZERO))
+ && pos.see_ge(move))
extension = ONE_PLY;
// Calculate new depth for this move
{
if ( !captureOrPromotion
&& !givesCheck
- && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= 5000))
+ && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= Value(5000)))
{
// Move count based pruning
- if (moveCountPruning) {
+ if (moveCountPruning)
+ {
skipQuiets = true;
continue;
}
// Countermoves based pruning
if ( lmrDepth < 3
- && ((cmh[moved_piece][to_sq(move)] < VALUE_ZERO) || !cm_ok)
- && ((fmh[moved_piece][to_sq(move)] < VALUE_ZERO) || !fm_ok)
- && ((fm2[moved_piece][to_sq(move)] < VALUE_ZERO) || !f2_ok || (cm_ok && fm_ok)))
+ && (cmh[moved_piece][to_sq(move)] < CounterMovePruneThreshold)
+ && (fmh[moved_piece][to_sq(move)] < CounterMovePruneThreshold))
continue;
// Futility pruning: parent node
ss->moveCount = --moveCount;
continue;
}
+
+ if (move == ttMove && captureOrPromotion)
+ ttCapture = true;
// Update the current move (this must be done after singular extension search)
ss->currentMove = move;
- ss->counterMoves = &thisThread->counterMoveHistory[moved_piece][to_sq(move)];
+ ss->history = &thisThread->counterMoveHistory[moved_piece][to_sq(move)];
// Step 14. Make the move
pos.do_move(move, st, givesCheck);
r -= r ? ONE_PLY : DEPTH_ZERO;
else
{
+
+ // Increase reduction if ttMove is a capture
+ if (ttCapture)
+ r += ONE_PLY;
+
// Increase reduction for cut nodes
if (cutNode)
r += 2 * ONE_PLY;
// Decrease reduction for moves that escape a capture. Filter out
// castling moves, because they are coded as "king captures rook" and
// hence break make_move().
- else if ( type_of(move) == NORMAL
- && !pos.see_ge(make_move(to_sq(move), from_sq(move)), VALUE_ZERO))
+ else if ( type_of(move) == NORMAL
+ && !pos.see_ge(make_move(to_sq(move), from_sq(move))))
r -= 2 * ONE_PLY;
- ss->history = cmh[moved_piece][to_sq(move)]
- + fmh[moved_piece][to_sq(move)]
- + fm2[moved_piece][to_sq(move)]
- + thisThread->history.get(~pos.side_to_move(), move)
- - 4000; // Correction factor
+ ss->statScore = cmh[moved_piece][to_sq(move)]
+ + fmh[moved_piece][to_sq(move)]
+ + fm2[moved_piece][to_sq(move)]
+ + thisThread->history[~pos.side_to_move()][from_to(move)]
+ - 4000; // Correction factor
// Decrease/increase reduction by comparing opponent's stat score
- if (ss->history > VALUE_ZERO && (ss-1)->history < VALUE_ZERO)
+ if (ss->statScore > 0 && (ss-1)->statScore < 0)
r -= ONE_PLY;
- else if (ss->history < VALUE_ZERO && (ss-1)->history > VALUE_ZERO)
+ else if (ss->statScore < 0 && (ss-1)->statScore > 0)
r += ONE_PLY;
// Decrease/increase reduction for moves with a good/bad history
- r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->history / 20000) * ONE_PLY);
+ r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 20000) * ONE_PLY);
}
Depth d = std::max(newDepth - r, ONE_PLY);
: inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()];
else if (bestMove)
{
-
// Quiet best move: update move sorting heuristics
if (!pos.capture_or_promotion(bestMove))
update_stats(pos, ss, bestMove, quietsSearched, quietCount, stat_bonus(depth));
// Bonus for prior countermove that caused the fail low
else if ( depth >= 3 * ONE_PLY
&& !pos.captured_piece()
- && cm_ok)
+ && is_ok((ss-1)->currentMove))
update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth));
- if(!excludedMove)
+ if (!excludedMove)
tte->save(posKey, value_to_tt(bestValue, ss->ply),
- bestValue >= beta ? BOUND_LOWER :
- PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
- depth, bestMove, ss->staticEval, TT.generation());
+ bestValue >= beta ? BOUND_LOWER :
+ PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
+ depth, bestMove, ss->staticEval, TT.generation());
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha;
bool ttHit, givesCheck, evasionPrunable;
Depth ttDepth;
+ int moveCount;
if (PvNode)
{
ss->currentMove = bestMove = MOVE_NONE;
ss->ply = (ss-1)->ply + 1;
+ moveCount = 0;
// Check for an instant draw or if the maximum ply has been reached
if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY)
? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move)
: pos.gives_check(move);
+ moveCount++;
+
// Futility pruning
if ( !InCheck
&& !givesCheck
// Detect non-capture evasions that are candidates to be pruned
evasionPrunable = InCheck
+ && (depth != DEPTH_ZERO || moveCount > 2)
&& bestValue > VALUE_MATED_IN_MAX_PLY
&& !pos.capture(move);
// Don't search moves with negative SEE values
if ( (!InCheck || evasionPrunable)
&& type_of(move) != PROMOTION
- && !pos.see_ge(move, VALUE_ZERO))
+ && !pos.see_ge(move))
continue;
// Speculative prefetch as early as possible
// Check for legality just before making the move
if (!pos.legal(move))
+ {
+ moveCount--;
continue;
+ }
ss->currentMove = move;
// update_cm_stats() updates countermove and follow-up move history
- void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus) {
+ void update_cm_stats(Stack* ss, Piece pc, Square s, int bonus) {
for (int i : {1, 2, 4})
if (is_ok((ss-i)->currentMove))
- (ss-i)->counterMoves->update(pc, s, bonus);
+ (ss-i)->history->update(pc, s, bonus);
}
// update_stats() updates move sorting heuristics when a new quiet best move is found
void update_stats(const Position& pos, Stack* ss, Move move,
- Move* quiets, int quietsCnt, Value bonus) {
+ Move* quiets, int quietsCnt, int bonus) {
if (ss->killers[0] != move)
{
if (is_ok((ss-1)->currentMove))
{
Square prevSq = to_sq((ss-1)->currentMove);
- thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move);
+ thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]=move;
}
// Decrease all the other played quiet moves
void check_time() {
- static TimePoint lastInfoTime = now();
+ static std::atomic<TimePoint> lastInfoTime = { now() };
int elapsed = Time.elapsed();
TimePoint tick = Limits.startTime + elapsed;
for (size_t i = 0; i < multiPV; ++i)
{
- bool updated = (i <= PVIdx);
+ bool updated = (i <= PVIdx && rootMoves[i].score != -VALUE_INFINITE);
if (depth == ONE_PLY && !updated)
continue;