// Futility lookup tables (initialized at startup) and their access functions
Value FutilityMargins[16][64]; // [depth][moveNumber]
- int FutilityMoveCounts[32]; // [depth]
+ int FutilityMoveCounts[2][32]; // [improving][depth]
inline Value futility_margin(Depth d, int mn) {
}
// Reduction lookup tables (initialized at startup) and their access function
- int8_t Reductions[2][64][64]; // [pv][depth][moveNumber]
+ int8_t Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber]
- template <bool PvNode> inline Depth reduction(Depth d, int mn) {
+ template <bool PvNode> inline Depth reduction(bool i, Depth d, int mn) {
- return (Depth) Reductions[PvNode][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)];
+ return (Depth) Reductions[PvNode][i][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)];
}
size_t PVSize, PVIdx;
TimeManager TimeMgr;
- int BestMoveChanges;
+ float BestMoveChanges;
Value DrawValue[COLOR_NB];
HistoryStats History;
GainsStats Gains;
void id_loop(Position& pos);
Value value_to_tt(Value v, int ply);
Value value_from_tt(Value v, int ply);
- bool check_is_dangerous(const Position& pos, Move move, Value futilityBase, Value beta);
bool allows(const Position& pos, Move first, Move second);
bool refutes(const Position& pos, Move first, Move second);
string uci_pv(const Position& pos, int depth, Value alpha, Value beta);
{
double pvRed = log(double(hd)) * log(double(mc)) / 3.0;
double nonPVRed = 0.33 + log(double(hd)) * log(double(mc)) / 2.25;
- Reductions[1][hd][mc] = (int8_t) ( pvRed >= 1.0 ? floor( pvRed * int(ONE_PLY)) : 0);
- Reductions[0][hd][mc] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(ONE_PLY)) : 0);
+ Reductions[1][1][hd][mc] = (int8_t) ( pvRed >= 1.0 ? floor( pvRed * int(ONE_PLY)) : 0);
+ Reductions[0][1][hd][mc] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(ONE_PLY)) : 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;
}
// Init futility margins array
// Init futility move count array
for (d = 0; d < 32; d++)
- FutilityMoveCounts[d] = int(3.001 + 0.3 * pow(double(d), 1.8));
+ {
+ FutilityMoveCounts[0][d] = int(3 + 0.3 * pow(double(d ), 1.8)) * 3/4 + (2 < d && d < 5);
+ FutilityMoveCounts[1][d] = int(3 + 0.3 * pow(double(d + 0.98), 1.8));
+ }
}
Threads.sleepWhileIdle = Options["Idle Threads Sleep"];
// Set best timer interval to avoid lagging under time pressure. Timer is
- // used to check for remaining available thinking time.
+ // used to check for remaining available thinking time. Timer will be started
+ // at the end of first iteration to avoid returning with a random move.
Threads.timer->msec =
Limits.use_time_management() ? std::min(100, std::max(TimeMgr.available_time() / 16, TimerResolution)) :
- Limits.nodes ? 2 * TimerResolution
- : 100;
-
- Threads.timer->notify_one(); // Wake up the recurring timer
+ Limits.nodes ? 2 * TimerResolution : 100;
id_loop(RootPos); // Let's start searching !
void id_loop(Position& pos) {
- Stack stack[MAX_PLY_PLUS_2], *ss = stack+1; // To allow referencing (ss-1)
- int depth, prevBestMoveChanges;
+ Stack stack[MAX_PLY_PLUS_6], *ss = stack+2; // To allow referencing (ss-2)
+ int depth;
Value bestValue, alpha, beta, delta;
- std::memset(ss-1, 0, 4 * sizeof(Stack));
+ std::memset(ss-2, 0, 5 * sizeof(Stack));
(ss-1)->currentMove = MOVE_NULL; // Hack to skip update gains
- depth = BestMoveChanges = 0;
+ depth = 0;
+ BestMoveChanges = 0;
bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
// Iterative deepening loop until requested to stop or target depth reached
while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth))
{
+ // Age out PV variability metric
+ BestMoveChanges *= 0.8f;
+
// Save last iteration's scores before first PV line is searched and all
// the move scores but the (new) PV are set to -VALUE_INFINITE.
for (size_t i = 0; i < RootMoves.size(); i++)
RootMoves[i].prevScore = RootMoves[i].score;
- prevBestMoveChanges = BestMoveChanges; // Only sensible when PVSize == 1
- BestMoveChanges = 0;
-
// MultiPV loop. We perform a full root search for each PV line
for (PVIdx = 0; PVIdx < PVSize; PVIdx++)
{
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
}
+ // Wake up the recurring timer after first iteration is finished
+ if (depth == 1)
+ Threads.timer->notify_one();
+
// Sort the PV lines searched so far and update the GUI
std::stable_sort(RootMoves.begin(), RootMoves.begin() + PVIdx + 1);
// Take in account some extra time if the best move has changed
if (depth > 4 && depth < 50 && PVSize == 1)
- TimeMgr.pv_instability(BestMoveChanges, prevBestMoveChanges);
+ TimeMgr.pv_instability(BestMoveChanges);
// Stop search if most of available time is already consumed. We
// probably don't have enough time to search the first move at the
Depth ext, newDepth;
Value bestValue, value, ttValue;
Value eval, nullValue, futilityValue;
- bool inCheck, givesCheck, pvMove, singularExtensionNode;
+ bool inCheck, givesCheck, pvMove, singularExtensionNode, improving;
bool captureOrPromotion, dangerous, doFullDepthSearch;
int moveCount, quietCount;
// Step 8. Null move search with verification search (is omitted in PV nodes)
if ( !PvNode
&& !ss->skipNullMove
- && depth > ONE_PLY
+ && depth >= 2 * ONE_PLY
&& eval >= beta
&& abs(beta) < VALUE_MATE_IN_MAX_PLY
&& pos.non_pawn_material(pos.side_to_move()))
MovePicker mp(pos, ttMove, depth, History, countermoves, ss);
CheckInfo ci(pos);
value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc
+ improving = ss->staticEval >= (ss-2)->staticEval
+ || ss->staticEval == VALUE_NONE
+ ||(ss-2)->staticEval == VALUE_NONE;
+
singularExtensionNode = !RootNode
&& !SpNode
- && depth >= (PvNode ? 6 * ONE_PLY : 8 * ONE_PLY)
+ && depth >= 8 * ONE_PLY
&& ttMove != MOVE_NONE
&& !excludedMove // Recursive singular search is not allowed
&& (tte->bound() & BOUND_LOWER)
{
Signals.firstRootMove = (moveCount == 1);
- if (thisThread == Threads.main_thread() && Time::now() - SearchTime > 3000)
+ if (thisThread == Threads.main() && Time::now() - SearchTime > 3000)
sync_cout << "info depth " << depth / ONE_PLY
<< " currmove " << move_to_uci(move, pos.is_chess960())
<< " currmovenumber " << moveCount + PVIdx << sync_endl;
{
// Move count based pruning
if ( depth < 16 * ONE_PLY
- && moveCount >= FutilityMoveCounts[depth]
+ && moveCount >= FutilityMoveCounts[improving][depth]
&& (!threatMove || !refutes(pos, move, threatMove)))
{
if (SpNode)
// Value based pruning
// We illogically ignore reduction condition depth >= 3*ONE_PLY for predicted depth,
// but fixing this made program slightly weaker.
- Depth predictedDepth = newDepth - reduction<PvNode>(depth, moveCount);
+ Depth predictedDepth = newDepth - reduction<PvNode>(improving, depth, moveCount);
futilityValue = ss->staticEval + ss->evalMargin + futility_margin(predictedDepth, moveCount)
+ Gains[pos.piece_moved(move)][to_sq(move)];
// Step 15. Reduced depth search (LMR). If the move fails high will be
// re-searched at full depth.
- if ( depth > 3 * ONE_PLY
+ if ( depth >= 3 * ONE_PLY
&& !pvMove
&& !captureOrPromotion
- && !dangerous
&& move != ttMove
&& move != ss->killers[0]
&& move != ss->killers[1])
{
- ss->reduction = reduction<PvNode>(depth, moveCount);
+ ss->reduction = reduction<PvNode>(improving, depth, moveCount);
if (!PvNode && cutNode)
ss->reduction += ONE_PLY;
&& !givesCheck
&& move != ttMove
&& type_of(move) != PROMOTION
+ && futilityBase > -VALUE_KNOWN_WIN
&& !pos.is_passed_pawn_push(move))
{
futilityValue = futilityBase
&& pos.see_sign(move) < 0)
continue;
- // Don't search useless checks
- if ( !PvNode
- && !InCheck
- && givesCheck
- && move != ttMove
- && !pos.is_capture_or_promotion(move)
- && ss->staticEval + PawnValueMg / 4 < beta
- && !check_is_dangerous(pos, move, futilityBase, beta))
- continue;
-
// Check for legality only before to do the move
if (!pos.pl_move_is_legal(move, ci.pinned))
continue;
}
- // check_is_dangerous() tests if a checking move can be pruned in qsearch()
-
- bool check_is_dangerous(const Position& pos, Move move, Value futilityBase, Value beta)
- {
- Piece pc = pos.piece_moved(move);
- Square from = from_sq(move);
- Square to = to_sq(move);
- Color them = ~pos.side_to_move();
- Square ksq = pos.king_square(them);
- Bitboard enemies = pos.pieces(them);
- Bitboard kingAtt = pos.attacks_from<KING>(ksq);
- Bitboard occ = pos.pieces() ^ from ^ ksq;
- Bitboard oldAtt = pos.attacks_from(pc, from, occ);
- Bitboard newAtt = pos.attacks_from(pc, to, occ);
-
- // Checks which give opponent's king at most one escape square are dangerous
- if (!more_than_one(kingAtt & ~(enemies | newAtt | to)))
- return true;
-
- // Queen contact check is very dangerous
- if (type_of(pc) == QUEEN && (kingAtt & to))
- return true;
-
- // Creating new double threats with checks is dangerous
- Bitboard b = (enemies ^ ksq) & newAtt & ~oldAtt;
- while (b)
- {
- // Note that here we generate illegal "double move"!
- if (futilityBase + PieceValue[EG][pos.piece_on(pop_lsb(&b))] >= beta)
- return true;
- }
-
- return false;
- }
-
-
// allows() tests whether the 'first' move at previous ply somehow makes the
// 'second' move possible, for instance if the moving piece is the same in
// both moves. Normally the second move is the threat (the best move returned
assert(is_ok(first));
assert(is_ok(second));
assert(color_of(pos.piece_on(from_sq(second))) == ~pos.side_to_move());
- assert(color_of(pos.piece_on(to_sq(first))) == ~pos.side_to_move());
+ assert(type_of(first) == CASTLE || color_of(pos.piece_on(to_sq(first))) == ~pos.side_to_move());
Square m1from = from_sq(first);
Square m2from = from_sq(second);
Square m2to = to_sq(second);
// The piece is the same or second's destination was vacated by the first move
- if (m1to == m2from || m2to == m1from)
+ // We exclude the trivial case where a sliding piece does in two moves what
+ // it could do in one move: eg. Ra1a2, Ra2a3.
+ if ( m2to == m1from
+ || (m1to == m2from && !squares_aligned(m1from, m2from, m2to)))
return true;
// Second one moves through the square vacated by first one
void RootMove::extract_pv_from_tt(Position& pos) {
- StateInfo state[MAX_PLY_PLUS_2], *st = state;
+ StateInfo state[MAX_PLY_PLUS_6], *st = state;
const TTEntry* tte;
int ply = 0;
Move m = pv[0];
void RootMove::insert_pv_in_tt(Position& pos) {
- StateInfo state[MAX_PLY_PLUS_2], *st = state;
+ StateInfo state[MAX_PLY_PLUS_6], *st = state;
const TTEntry* tte;
int ply = 0;
Threads.mutex.unlock();
- Stack stack[MAX_PLY_PLUS_2], *ss = stack+1; // To allow referencing (ss-1)
+ Stack stack[MAX_PLY_PLUS_6], *ss = stack+2; // To allow referencing (ss-2)
Position pos(*sp->pos, this);
- std::memcpy(ss-1, sp->ss-1, 4 * sizeof(Stack));
+ std::memcpy(ss-2, sp->ss-2, 5 * sizeof(Stack));
ss->splitPoint = sp;
sp->mutex.lock();