#include <algorithm>
#include <cassert>
-#include <cfloat>
#include <cmath>
#include <cstring>
#include <iostream>
namespace {
- // Set to true to force running with one thread. Used for debugging
- const bool FakeSplit = false;
-
// Different node types, used as template parameter
enum NodeType { Root, PV, NonPV };
// Dynamic razoring margin based on depth
- inline Value razor_margin(Depth d) { return Value(512 + 16 * d); }
+ inline Value razor_margin(Depth d) { return Value(512 + 32 * d); }
// Futility lookup tables (initialized at startup) and their access functions
int FutilityMoveCounts[2][32]; // [improving][depth]
inline Value futility_margin(Depth d) {
- return Value(100 * d);
+ return Value(200 * d);
}
// Reduction lookup tables (initialized at startup) and their access function
int8_t Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber]
template <bool PvNode> inline Depth reduction(bool i, Depth d, int mn) {
-
- return (Depth) Reductions[PvNode][i][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)];
+ return (Depth) Reductions[PvNode][i][std::min(int(d), 63)][std::min(mn, 63)];
}
- size_t MultiPV, PVIdx;
+ size_t PVIdx;
TimeManager TimeMgr;
double BestMoveChanges;
Value DrawValue[COLOR_NB];
string uci_pv(const Position& pos, int depth, Value alpha, Value beta);
struct Skill {
- Skill(int l) : level(l), best(MOVE_NONE) {}
+ Skill(int l, size_t rootSize) : level(l),
+ candidates(l < 20 ? std::min(4, (int)rootSize) : 0),
+ best(MOVE_NONE) {}
~Skill() {
- if (enabled()) // Swap best PV line with the sub-optimal one
+ if (candidates) // Swap best PV line with the sub-optimal one
std::swap(RootMoves[0], *std::find(RootMoves.begin(),
RootMoves.end(), best ? best : pick_move()));
}
- bool enabled() const { return level < 20; }
+ size_t candidates_size() const { return candidates; }
bool time_to_pick(int depth) const { return depth == 1 + level; }
Move pick_move();
int level;
+ size_t candidates;
Move best;
};
{
double pvRed = 0.00 + log(double(hd)) * log(double(mc)) / 3.00;
double nonPVRed = 0.33 + log(double(hd)) * log(double(mc)) / 2.25;
- Reductions[1][1][hd][mc] = int8_t( pvRed >= 1.0 ? pvRed * int(ONE_PLY) : 0);
- Reductions[0][1][hd][mc] = int8_t(nonPVRed >= 1.0 ? nonPVRed * int(ONE_PLY) : 0);
+
+ Reductions[1][1][hd][mc] = int8_t( pvRed >= 1.0 ? pvRed + 0.5: 0);
+ Reductions[0][1][hd][mc] = int8_t(nonPVRed >= 1.0 ? nonPVRed + 0.5: 0);
Reductions[1][0][hd][mc] = Reductions[1][1][hd][mc];
Reductions[0][0][hd][mc] = Reductions[0][1][hd][mc];
- if (Reductions[0][0][hd][mc] > 2 * ONE_PLY)
- Reductions[0][0][hd][mc] += ONE_PLY;
-
- else if (Reductions[0][0][hd][mc] > 1 * ONE_PLY)
- Reductions[0][0][hd][mc] += ONE_PLY / 2;
+ if (Reductions[0][0][hd][mc] >= 2)
+ Reductions[0][0][hd][mc] += 1;
}
// Init futility move count array
for (d = 0; d < 32; ++d)
{
- FutilityMoveCounts[0][d] = int(2.4 + 0.222 * pow(d + 0.00, 1.8));
- FutilityMoveCounts[1][d] = int(3.0 + 0.300 * pow(d + 0.98, 1.8));
+ FutilityMoveCounts[0][d] = int(2.4 + 0.222 * pow(d * 2 + 0.00, 1.8));
+ FutilityMoveCounts[1][d] = int(3.0 + 0.300 * pow(d * 2 + 0.98, 1.8));
}
}
/// Search::perft() is our utility to verify move generation. All the leaf nodes
/// up to the given depth are generated and counted and the sum returned.
-
-static uint64_t perft(Position& pos, Depth depth) {
+template<bool Root>
+uint64_t Search::perft(Position& pos, Depth depth) {
StateInfo st;
- uint64_t cnt = 0;
+ uint64_t cnt, nodes = 0;
CheckInfo ci(pos);
- const bool leaf = depth == 2 * ONE_PLY;
+ const bool leaf = (depth == 2 * ONE_PLY);
for (MoveList<LEGAL> it(pos); *it; ++it)
{
- pos.do_move(*it, st, ci, pos.gives_check(*it, ci));
- cnt += leaf ? MoveList<LEGAL>(pos).size() : ::perft(pos, depth - ONE_PLY);
- pos.undo_move(*it);
+ if (Root && depth <= ONE_PLY)
+ cnt = 1, nodes++;
+ else
+ {
+ pos.do_move(*it, st, ci, pos.gives_check(*it, ci));
+ cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - ONE_PLY);
+ nodes += cnt;
+ pos.undo_move(*it);
+ }
+ if (Root)
+ sync_cout << move_to_uci(*it, pos.is_chess960()) << ": " << cnt << sync_endl;
}
- return cnt;
+ return nodes;
}
-uint64_t Search::perft(Position& pos, Depth depth) {
- return depth > ONE_PLY ? ::perft(pos, depth) : MoveList<LEGAL>(pos).size();
-}
+template uint64_t Search::perft<true>(Position& pos, Depth depth);
+
/// Search::think() is the external interface to Stockfish's search, and is
/// called by the main thread when the program receives the UCI 'go' command. It
TimeMgr.init(Limits, RootPos.game_ply(), RootPos.side_to_move());
- int cf = Options["Contempt Factor"] * PawnValueEg / 100; // From centipawns
+ int cf = Options["Contempt"] * PawnValueEg / 100; // From centipawns
DrawValue[ RootPos.side_to_move()] = VALUE_DRAW - Value(cf);
DrawValue[~RootPos.side_to_move()] = VALUE_DRAW + Value(cf);
goto finalize;
}
- if (Options["Write Search Log"])
- {
- Log log(Options["Search Log Filename"]);
- log << "\nSearching: " << RootPos.fen()
- << "\ninfinite: " << Limits.infinite
- << " ponder: " << Limits.ponder
- << " time: " << Limits.time[RootPos.side_to_move()]
- << " increment: " << Limits.inc[RootPos.side_to_move()]
- << " moves to go: " << Limits.movestogo
- << "\n" << std::endl;
- }
-
// Reset the threads, still sleeping: will wake up at split time
for (size_t i = 0; i < Threads.size(); ++i)
Threads[i]->maxPly = 0;
Threads.timer->run = false; // Stop the timer
- if (Options["Write Search Log"])
- {
- Time::point elapsed = Time::now() - SearchTime + 1;
-
- Log log(Options["Search Log Filename"]);
- log << "Nodes: " << RootPos.nodes_searched()
- << "\nNodes/second: " << RootPos.nodes_searched() * 1000 / elapsed
- << "\nBest move: " << move_to_san(RootPos, RootMoves[0].pv[0]);
-
- StateInfo st;
- RootPos.do_move(RootMoves[0].pv[0], st);
- log << "\nPonder move: " << move_to_san(RootPos, RootMoves[0].pv[1]) << std::endl;
- RootPos.undo_move(RootMoves[0].pv[0]);
- }
-
finalize:
// When search is stopped this info is not printed
Countermoves.clear();
Followupmoves.clear();
- MultiPV = Options["MultiPV"];
- Skill skill(Options["Skill Level"]);
+ size_t multiPV = Options["MultiPV"];
+ Skill skill(Options["Skill Level"], RootMoves.size());
// Do we have to play with skill handicap? In this case enable MultiPV search
// that we will use behind the scenes to retrieve a set of possible moves.
- if (skill.enabled() && MultiPV < 4)
- MultiPV = 4;
-
- MultiPV = std::min(MultiPV, RootMoves.size());
+ multiPV = std::max(multiPV, skill.candidates_size());
// Iterative deepening loop until requested to stop or target depth reached
while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth))
RootMoves[i].prevScore = RootMoves[i].score;
// MultiPV loop. We perform a full root search for each PV line
- for (PVIdx = 0; PVIdx < MultiPV && !Signals.stop; ++PVIdx)
+ for (PVIdx = 0; PVIdx < std::min(multiPV, RootMoves.size()) && !Signals.stop; ++PVIdx)
{
// Reset aspiration window starting size
if (depth >= 5)
else
break;
- delta += delta / 2;
+ delta += 3 * delta / 8;
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
}
// Sort the PV lines searched so far and update the GUI
std::stable_sort(RootMoves.begin(), RootMoves.begin() + PVIdx + 1);
- if (PVIdx + 1 == MultiPV || Time::now() - SearchTime > 3000)
+ if (PVIdx + 1 == std::min(multiPV, RootMoves.size()) || Time::now() - SearchTime > 3000)
sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl;
}
// If skill levels are enabled and time is up, pick a sub-optimal best move
- if (skill.enabled() && skill.time_to_pick(depth))
+ if (skill.candidates_size() && skill.time_to_pick(depth))
skill.pick_move();
- if (Options["Write Search Log"])
- {
- RootMove& rm = RootMoves[0];
- if (skill.best != MOVE_NONE)
- rm = *std::find(RootMoves.begin(), RootMoves.end(), skill.best);
-
- Log log(Options["Search Log Filename"]);
- log << pretty_pv(pos, depth, rm.score, Time::now() - SearchTime, &rm.pv[0])
- << std::endl;
- }
-
// Have we found a "mate in x"?
if ( Limits.mate
&& bestValue >= VALUE_MATE_IN_MAX_PLY
if (Limits.use_time_management() && !Signals.stop && !Signals.stopOnPonderhit)
{
// Take some extra time if the best move has changed
- if (depth > 4 && depth < 50 && MultiPV == 1)
+ if (depth > 4 && multiPV == 1)
TimeMgr.pv_instability(BestMoveChanges);
// Stop the search if only one legal move is available or all
&& depth < 4 * ONE_PLY
&& eval + razor_margin(depth) <= alpha
&& ttMove == MOVE_NONE
- && abs(beta) < VALUE_MATE_IN_MAX_PLY
&& !pos.pawn_on_7th(pos.side_to_move()))
{
if ( depth <= ONE_PLY
&& !ss->skipNullMove
&& depth < 7 * ONE_PLY
&& eval - futility_margin(depth) >= beta
- && abs(beta) < VALUE_MATE_IN_MAX_PLY
- && abs(eval) < VALUE_KNOWN_WIN
+ && eval < VALUE_KNOWN_WIN // Do not return unproven wins
&& pos.non_pawn_material(pos.side_to_move()))
return eval - futility_margin(depth);
&& !ss->skipNullMove
&& depth >= 2 * ONE_PLY
&& eval >= beta
- && abs(beta) < VALUE_MATE_IN_MAX_PLY
&& pos.non_pawn_material(pos.side_to_move()))
{
ss->currentMove = MOVE_NULL;
assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and value
- Depth R = 3 * ONE_PLY
- + depth / 4
- + int(eval - beta) / PawnValueMg * ONE_PLY;
+ Depth R = (3 + depth / 4 + std::min(int(eval - beta) / PawnValueMg, 3)) * ONE_PLY;
pos.do_null_move(st);
(ss+1)->skipNullMove = true;
if (nullValue >= VALUE_MATE_IN_MAX_PLY)
nullValue = beta;
- if (depth < 12 * ONE_PLY)
+ if (depth < 12 * ONE_PLY && abs(beta) < VALUE_KNOWN_WIN)
return nullValue;
// Do verification search at high depths
&& !ttMove
&& (PvNode || ss->staticEval + 256 >= beta))
{
- Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4);
-
+ Depth d = 2 * (depth - 2 * ONE_PLY) - (PvNode ? DEPTH_ZERO : depth / 2);
ss->skipNullMove = true;
- search<PvNode ? PV : NonPV, false>(pos, ss, alpha, beta, d, true);
+ search<PvNode ? PV : NonPV, false>(pos, ss, alpha, beta, d / 2, true);
ss->skipNullMove = false;
tte = TT.probe(posKey);
&& !SpNode
&& depth >= 8 * ONE_PLY
&& ttMove != MOVE_NONE
+ /* && ttValue != VALUE_NONE Already implicit in the next condition */
+ && abs(ttValue) < VALUE_KNOWN_WIN
&& !excludedMove // Recursive singular search is not allowed
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3 * ONE_PLY;
Signals.firstRootMove = (moveCount == 1);
if (thisThread == Threads.main() && Time::now() - SearchTime > 3000)
- sync_cout << "info depth " << depth / ONE_PLY
+ sync_cout << "info depth " << depth
<< " currmove " << move_to_uci(move, pos.is_chess960())
<< " currmovenumber " << moveCount + PVIdx << sync_endl;
}
if ( singularExtensionNode
&& move == ttMove
&& !ext
- && pos.legal(move, ci.pinned)
- && abs(ttValue) < VALUE_KNOWN_WIN)
+ && pos.legal(move, ci.pinned))
{
- assert(ttValue != VALUE_NONE);
-
- Value rBeta = ttValue - int(depth);
+ Value rBeta = ttValue - int(2 * depth);
ss->excludedMove = move;
ss->skipNullMove = true;
value = search<NonPV, false>(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode);
{
// Move count based pruning
if ( depth < 16 * ONE_PLY
- && moveCount >= FutilityMoveCounts[improving][depth] )
+ && moveCount >= FutilityMoveCounts[improving][depth])
{
if (SpNode)
splitPoint->mutex.lock();
// Futility pruning: parent node
if (predictedDepth < 7 * ONE_PLY)
{
- futilityValue = ss->staticEval + futility_margin(predictedDepth)
- + 128 + Gains[pos.moved_piece(move)][to_sq(move)];
+ futilityValue = ss->staticEval + futility_margin(predictedDepth)
+ + 128 + Gains[pos.moved_piece(move)][to_sq(move)];
if (futilityValue <= alpha)
{
}
}
+ // Speculative prefetch
+ prefetch((char*)TT.first_entry(pos.hash_after_move(move)));
+
// Check for legality just before making the move
if (!RootNode && !SpNode && !pos.legal(move, ci.pinned))
{
{
ss->reduction = reduction<PvNode>(improving, depth, moveCount);
- if (!PvNode && cutNode)
+ if ( (!PvNode && cutNode)
+ || History[pos.piece_on(to_sq(move))][to_sq(move)] < 0)
ss->reduction += ONE_PLY;
- else if (History[pos.piece_on(to_sq(move))][to_sq(move)] < 0)
- ss->reduction += ONE_PLY / 2;
-
if (move == countermoves[0] || move == countermoves[1])
ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY);
if (SpNode)
alpha = splitPoint->alpha;
- value = newDepth < ONE_PLY ?
- givesCheck ? -qsearch<NonPV, true>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
- : -qsearch<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
- : - search<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
+ value = newDepth < ONE_PLY ?
+ givesCheck ? -qsearch<NonPV, true>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
+ : -qsearch<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
+ : - search<NonPV, false>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
}
// For PV nodes only, do a full PV search on the first move or after a fail
// high (in the latter case search only if value < beta), otherwise let the
// parent node fail low with value <= alpha and to try another move.
if (PvNode && (pvMove || (value > alpha && (RootNode || value < beta))))
- value = newDepth < ONE_PLY ?
- givesCheck ? -qsearch<PV, true>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
- : -qsearch<PV, false>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
- : - search<PV, false>(pos, ss+1, -beta, -alpha, newDepth, false);
+ value = newDepth < ONE_PLY ?
+ givesCheck ? -qsearch<PV, true>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
+ : -qsearch<PV, false>(pos, ss+1, -beta, -alpha, DEPTH_ZERO)
+ : - search<PV, false>(pos, ss+1, -beta, -alpha, newDepth, false);
// Step 17. Undo move
pos.undo_move(move);
{
assert(bestValue > -VALUE_INFINITE && bestValue < beta);
- thisThread->split<FakeSplit>(pos, ss, alpha, beta, &bestValue, &bestMove,
- depth, moveCount, &mp, NT, cutNode);
+ thisThread->split(pos, ss, alpha, beta, &bestValue, &bestMove,
+ depth, moveCount, &mp, NT, cutNode);
if (Signals.stop || thisThread->cutoff_occurred())
return VALUE_ZERO;
&& pos.see_sign(move) < VALUE_ZERO)
continue;
+ // Speculative prefetch
+ prefetch((char*)TT.first_entry(pos.hash_after_move(move)));
+
// Check for legality just before making the move
if (!pos.legal(move, ci.pinned))
continue;
// Increase history value of the cut-off move and decrease all the other
// played quiet moves.
- Value bonus = Value(int(depth) * int(depth));
+ Value bonus = Value(4 * int(depth) * int(depth));
History.update(pos.moved_piece(move), to_sq(move), bonus);
for (int i = 0; i < quietsCnt; ++i)
{
}
- // When playing with a strength handicap, choose best move among the MultiPV
- // set using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
+ // When playing with a strength handicap, choose best move among the first 'candidates'
+ // RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
Move Skill::pick_move() {
rk.rand<unsigned>();
// RootMoves are already sorted by score in descending order
- int variance = std::min(RootMoves[0].score - RootMoves[MultiPV - 1].score, PawnValueMg);
+ int variance = std::min(RootMoves[0].score - RootMoves[candidates - 1].score, PawnValueMg);
int weakness = 120 - 2 * level;
int max_s = -VALUE_INFINITE;
best = MOVE_NONE;
// Choose best move. For each move score we add two terms both dependent on
// weakness. One deterministic and bigger for weaker moves, and one random,
// then we choose the move with the resulting highest score.
- for (size_t i = 0; i < MultiPV; ++i)
+ for (size_t i = 0; i < candidates; ++i)
{
int s = RootMoves[i].score;
// Don't allow crazy blunders even at very low skills
- if (i > 0 && RootMoves[i-1].score > s + 2 * PawnValueMg)
+ if (i > 0 && RootMoves[i - 1].score > s + 2 * PawnValueMg)
break;
// This is our magic formula
assert(!this_sp || (this_sp->masterThread == this && searching));
- while (true)
+ while (!exit)
{
- // If we are not searching, wait for a condition to be signaled instead of
- // wasting CPU time polling for work.
- while (!searching || exit)
- {
- if (exit)
- {
- assert(!this_sp);
- return;
- }
-
- // Grab the lock to avoid races with Thread::notify_one()
- mutex.lock();
-
- // If we are master and all slaves have finished then exit idle_loop
- if (this_sp && this_sp->slavesMask.none())
- {
- mutex.unlock();
- break;
- }
-
- // Do sleep after retesting sleep conditions under lock protection. In
- // particular we need to avoid a deadlock in case a master thread has,
- // in the meanwhile, allocated us and sent the notify_one() call before
- // we had the chance to grab the lock.
- if (!searching && !exit)
- sleepCondition.wait(mutex);
-
- mutex.unlock();
- }
-
// If this thread has been assigned work, launch a search
- if (searching)
+ while (searching)
{
- assert(!exit);
-
Threads.mutex.lock();
- assert(searching);
assert(activeSplitPoint);
SplitPoint* sp = activeSplitPoint;
}
}
- // If this thread is the master of a split point and all slaves have finished
- // their work at this split point, return from the idle loop.
+ // Grab the lock to avoid races with Thread::notify_one()
+ mutex.lock();
+
+ // If we are master and all slaves have finished then exit idle_loop
if (this_sp && this_sp->slavesMask.none())
{
- this_sp->mutex.lock();
- bool finished = this_sp->slavesMask.none(); // Retest under lock protection
- this_sp->mutex.unlock();
- if (finished)
- return;
+ assert(!searching);
+ mutex.unlock();
+ break;
}
+
+ // If we are not searching, wait for a condition to be signaled instead of
+ // wasting CPU time polling for work.
+ if (!searching && !exit)
+ sleepCondition.wait(mutex);
+
+ mutex.unlock();
}
}