/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "misc.h"
#include "pawns.h"
#include "thread.h"
+#include "timeman.h"
#include "uci.h"
#include "incbin/incbin.h"
+#include "nnue/evaluate_nnue.h"
-
-// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler).
+// Macro to embed the default efficiently updatable neural network (NNUE) file
+// data in the engine binary (using incbin.h, by Dale Weiler).
// This macro invocation will declare the following three variables
// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
-// Note that this does not work in Microsof Visual Studio.
+// Note that this does not work in Microsoft Visual Studio.
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
#else
using namespace std;
-using namespace Eval::NNUE;
+
+namespace Stockfish {
namespace Eval {
bool useNNUE;
- string eval_file_loaded = "None";
+ string currentEvalFileName = "None";
- /// NNUE::init() tries to load a nnue network at startup time, or when the engine
+ /// NNUE::init() tries to load a NNUE network at startup time, or when the engine
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
- /// The name of the nnue network is always retrieved from the EvalFile option.
+ /// The name of the NNUE network is always retrieved from the EvalFile option.
/// We search the given network in three locations: internally (the default
/// network may be embedded in the binary), in the active working directory and
/// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
return;
string eval_file = string(Options["EvalFile"]);
+ if (eval_file.empty())
+ eval_file = EvalFileDefaultName;
#if defined(DEFAULT_NNUE_DIRECTORY)
- #define stringify2(x) #x
- #define stringify(x) stringify2(x)
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
#else
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
#endif
- for (string directory : dirs)
- if (eval_file_loaded != eval_file)
+ for (const string& directory : dirs)
+ if (currentEvalFileName != eval_file)
{
if (directory != "<internal>")
{
ifstream stream(directory + eval_file, ios::binary);
- if (load_eval(eval_file, stream))
- eval_file_loaded = eval_file;
+ if (NNUE::load_eval(eval_file, stream))
+ currentEvalFileName = eval_file;
}
if (directory == "<internal>" && eval_file == EvalFileDefaultName)
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
size_t(gEmbeddedNNUESize));
+ (void) gEmbeddedNNUEEnd; // Silence warning on unused variable
istream stream(&buffer);
- if (load_eval(eval_file, stream))
- eval_file_loaded = eval_file;
+ if (NNUE::load_eval(eval_file, stream))
+ currentEvalFileName = eval_file;
}
}
}
void NNUE::verify() {
string eval_file = string(Options["EvalFile"]);
+ if (eval_file.empty())
+ eval_file = EvalFileDefaultName;
- if (useNNUE && eval_file_loaded != eval_file)
+ if (useNNUE && currentEvalFileName != eval_file)
{
- UCI::OptionsMap defaults;
- UCI::init(defaults);
string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
- string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]);
+ string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName);
string msg5 = "The engine will be terminated now.";
sync_cout << "info string ERROR: " << msg1 << sync_endl;
Score scores[TERM_NB][COLOR_NB];
- double to_cp(Value v) { return double(v) / PawnValueEg; }
+ static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; }
- void add(int idx, Color c, Score s) {
+ static void add(int idx, Color c, Score s) {
scores[idx][c] = s;
}
- void add(int idx, Score w, Score b = SCORE_ZERO) {
+ static void add(int idx, Score w, Score b = SCORE_ZERO) {
scores[idx][WHITE] = w;
scores[idx][BLACK] = b;
}
- std::ostream& operator<<(std::ostream& os, Score s) {
+ static std::ostream& operator<<(std::ostream& os, Score s) {
os << std::setw(5) << to_cp(mg_value(s)) << " "
<< std::setw(5) << to_cp(eg_value(s));
return os;
}
- std::ostream& operator<<(std::ostream& os, Term t) {
+ static std::ostream& operator<<(std::ostream& os, Term t) {
if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
os << " ---- ----" << " | " << " ---- ----";
else
os << scores[t][WHITE] << " | " << scores[t][BLACK];
- os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n";
+ os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n";
return os;
}
}
namespace {
// Threshold for lazy and space evaluation
- constexpr Value LazyThreshold1 = Value(1400);
- constexpr Value LazyThreshold2 = Value(1300);
- constexpr Value SpaceThreshold = Value(12222);
- constexpr Value NNUEThreshold1 = Value(550);
- constexpr Value NNUEThreshold2 = Value(150);
+ constexpr Value LazyThreshold1 = Value(3622);
+ constexpr Value LazyThreshold2 = Value(1962);
+ constexpr Value SpaceThreshold = Value(11551);
// KingAttackWeights[PieceType] contains king attack weights by piece type
- constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
+ constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 };
// SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
// higher if multiple safe checks are possible for that piece type.
constexpr int SafeCheck[][2] = {
- {}, {}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132}
+ {}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128}
};
#define S(mg, eg) make_score(mg, eg)
{ S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop
S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87),
S( 91, 88), S( 96, 98) },
- { S(-61,-82), S(-20,-17), S( 2, 23) ,S( 3, 40), S( 4, 72), S( 11,100), // Rook
- S( 22,104), S( 31,120), S( 39,134), S(40 ,138), S( 41,158), S( 47,163),
- S( 59,168), S( 60,169), S( 64,173) },
+ { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook
+ S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160),
+ S( 57,165), S( 58,170), S( 67,175) },
{ S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen
S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101),
S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140),
S(112,178), S(114,185), S(114,187), S(119,221) }
};
+ // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on
+ // squares of the same color as our bishop.
+ constexpr Score BishopPawns[int(FILE_NB) / 2] = {
+ S(3, 8), S(3, 9), S(2, 7), S(3, 7)
+ };
+
// KingProtector[knight/bishop] contains penalty for each distance unit to own king
- constexpr Score KingProtector[] = { S(8, 9), S(6, 9) };
+ constexpr Score KingProtector[] = { S(9, 9), S(7, 9) };
// Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a
// pawn protected square on rank 4 to 6 which is also safe from a pawn attack.
- constexpr Score Outpost[] = { S(56, 34), S(31, 23) };
+ constexpr Score Outpost[] = { S(54, 34), S(31, 25) };
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn
constexpr Score PassedRank[RANK_NB] = {
- S(0, 0), S(9, 28), S(15, 31), S(17, 39), S(64, 70), S(171, 177), S(277, 260)
+ S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269)
};
- // RookOnFile[semiopen/open] contains bonuses for each rook when there is
- // no (friendly) pawn on the rook file.
- constexpr Score RookOnFile[] = { S(19, 7), S(48, 27) };
+ constexpr Score RookOnClosedFile = S(10, 5);
+ constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) };
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
// which piece type attacks which one. Attacks on lesser pieces which are
// pawn-defended are not considered.
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
- S(0, 0), S(5, 32), S(55, 41), S(77, 56), S(89, 119), S(79, 162)
+ S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163)
};
constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
- S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43)
+ S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39)
};
+ constexpr Value CorneredBishop = Value(50);
+
// Assorted bonuses and penalties
- constexpr Score BadOutpost = S( -7, 36);
+ constexpr Score UncontestedOutpost = S( 0, 10);
constexpr Score BishopOnKingRing = S( 24, 0);
- constexpr Score BishopPawns = S( 3, 7);
constexpr Score BishopXRayPawns = S( 4, 5);
- constexpr Score CorneredBishop = S( 50, 50);
constexpr Score FlankAttacks = S( 8, 0);
- constexpr Score Hanging = S( 69, 36);
+ constexpr Score Hanging = S( 72, 40);
constexpr Score KnightOnQueen = S( 16, 11);
constexpr Score LongDiagonalBishop = S( 45, 0);
constexpr Score MinorBehindPawn = S( 18, 3);
- constexpr Score PassedFile = S( 11, 8);
- constexpr Score PawnlessFlank = S( 17, 95);
- constexpr Score ReachableOutpost = S( 31, 22);
- constexpr Score RestrictedPiece = S( 7, 7);
+ constexpr Score PassedFile = S( 13, 8);
+ constexpr Score PawnlessFlank = S( 19, 97);
+ constexpr Score ReachableOutpost = S( 33, 19);
+ constexpr Score RestrictedPiece = S( 6, 7);
constexpr Score RookOnKingRing = S( 16, 0);
- constexpr Score SliderOnQueen = S( 60, 18);
- constexpr Score ThreatByKing = S( 24, 89);
+ constexpr Score SliderOnQueen = S( 62, 21);
+ constexpr Score ThreatByKing = S( 24, 87);
constexpr Score ThreatByPawnPush = S( 48, 39);
- constexpr Score ThreatBySafePawn = S(173, 94);
+ constexpr Score ThreatBySafePawn = S(167, 99);
constexpr Score TrappedRook = S( 55, 13);
constexpr Score WeakQueenProtection = S( 14, 0);
- constexpr Score WeakQueen = S( 56, 15);
+ constexpr Score WeakQueen = S( 57, 19);
#undef S
template<Tracing T> template<Color Us, PieceType Pt>
Score Evaluation<T>::pieces() {
- constexpr Color Them = ~Us;
- constexpr Direction Down = -pawn_push(Us);
- constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
- : Rank5BB | Rank4BB | Rank3BB);
- const Square* pl = pos.squares<Pt>(Us);
-
+ constexpr Color Them = ~Us;
+ [[maybe_unused]] constexpr Direction Down = -pawn_push(Us);
+ [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
+ : Rank5BB | Rank4BB | Rank3BB);
+ Bitboard b1 = pos.pieces(Us, Pt);
Bitboard b, bb;
Score score = SCORE_ZERO;
attackedBy[Us][Pt] = 0;
- for (Square s = *pl; s != SQ_NONE; s = *++pl)
+ while (b1)
{
+ Square s = pop_lsb(b1);
+
// Find attacked squares, including x-ray attacks for bishops and rooks
b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
: Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
score += BishopOnKingRing;
int mob = popcount(b & mobilityArea[Us]);
-
mobility[Us] += MobilityBonus[Pt - 2][mob];
- if (Pt == BISHOP || Pt == KNIGHT)
+ if constexpr (Pt == BISHOP || Pt == KNIGHT)
{
// Bonus if the piece is on an outpost square or can reach one
- // Reduced bonus for knights (BadOutpost) if few relevant targets
+ // Bonus for knights (UncontestedOutpost) if few relevant targets
bb = OutpostRanks & (attackedBy[Us][PAWN] | shift<Down>(pos.pieces(PAWN)))
& ~pe->pawn_attacks_span(Them);
Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
&& bb & s & ~CenterFiles // on a side outpost
&& !(b & targets) // no relevant attacks
&& (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
- score += BadOutpost;
+ score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide));
else if (bb & s)
score += Outpost[Pt == BISHOP];
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
// Penalty if the piece is far from the king
score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
- if (Pt == BISHOP)
+ if constexpr (Pt == BISHOP)
{
// Penalty according to the number of our pawns on the same color square as the
// bishop, bigger when the center files are blocked with pawns and smaller
// when the bishop is outside the pawn chain.
Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
- score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
+ score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s)
* (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
// Penalty for all enemy pawns x-rayed
{
Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
if (pos.piece_on(s + d) == make_piece(Us, PAWN))
- score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4
- : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
- : CorneredBishop;
+ score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop)
+ : 3 * make_score(CorneredBishop, CorneredBishop);
}
}
}
- if (Pt == ROOK)
+ if constexpr (Pt == ROOK)
{
- // Bonus for rook on an open or semi-open file
+ // Bonuses for rook on a (semi-)open or closed file
if (pos.is_on_semiopen_file(Us, s))
- score += RookOnFile[pos.is_on_semiopen_file(Them, s)];
-
- // Penalty when trapped by the king, even more if the king cannot castle
- else if (mob <= 3)
{
- File kf = file_of(pos.square<KING>(Us));
- if ((kf < FILE_E) == (file_of(s) < kf))
- score -= TrappedRook * (1 + !pos.castling_rights(Us));
+ score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)];
+ }
+ else
+ {
+ // If our pawn on this file is blocked, increase penalty
+ if ( pos.pieces(Us, PAWN)
+ & shift<Down>(pos.pieces())
+ & file_bb(s))
+ {
+ score -= RookOnClosedFile;
+ }
+
+ // Penalty when trapped by the king, even more if the king cannot castle
+ if (mob <= 3)
+ {
+ File kf = file_of(pos.square<KING>(Us));
+ if ((kf < FILE_E) == (file_of(s) < kf))
+ score -= TrappedRook * (1 + !pos.castling_rights(Us));
+ }
}
}
- if (Pt == QUEEN)
+ if constexpr (Pt == QUEEN)
{
// Penalty if any relative pin or discovered attack against the queen
Bitboard queenPinners;
score -= WeakQueen;
}
}
- if (T)
+ if constexpr (T)
Trace::add(Pt, Us, score);
return score;
int kingFlankAttack = popcount(b1) + popcount(b2);
int kingFlankDefense = popcount(b3);
- kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them]
- + 185 * popcount(kingRing[Us] & weak)
- + 148 * popcount(unsafeChecks)
- + 98 * popcount(pos.blockers_for_king(Us))
- + 69 * kingAttacksCount[Them]
- + 3 * kingFlankAttack * kingFlankAttack / 8
- + mg_value(mobility[Them] - mobility[Us])
- - 873 * !pos.count<QUEEN>(Them)
- - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
- - 6 * mg_value(score) / 8
- - 4 * kingFlankDefense
- + 37;
+ kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo)
+ + 183 * popcount(kingRing[Us] & weak) // (~15 Elo)
+ + 148 * popcount(unsafeChecks) // (~4 Elo)
+ + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo)
+ + 69 * kingAttacksCount[Them] // (~0.5 Elo)
+ + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo)
+ + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo)
+ - 873 * !pos.count<QUEEN>(Them) // (~24 Elo)
+ - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo)
+ - 6 * mg_value(score) / 8 // (~8 Elo)
+ - 4 * kingFlankDefense // (~5 Elo)
+ + 37; // (~0.5 Elo)
// Transform the kingDanger units into a Score, and subtract it from the evaluation
if (kingDanger > 100)
// Penalty if king flank is under attack, potentially moving toward the king
score -= FlankAttacks * kingFlankAttack;
- if (T)
+ if constexpr (T)
Trace::add(KING, Us, score);
return score;
{
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
while (b)
- score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))];
+ score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))];
b = weak & attackedBy[Us][ROOK];
while (b)
- score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))];
+ score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))];
if (weak & attackedBy[Us][KING])
score += ThreatByKing;
score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
}
- if (T)
+ if constexpr (T)
Trace::add(THREAT, Us, score);
return score;
while (b)
{
- Square s = pop_lsb(&b);
+ Square s = pop_lsb(b);
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN);
if (!(pos.pieces(Them) & bb))
- unsafeSquares &= attackedBy[Them][ALL_PIECES];
+ unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them);
- // If there are no enemy attacks on passed pawn span, assign a big bonus.
+ // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus.
+ // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus.
// Otherwise assign a smaller bonus if the path to queen is not attacked
// and even smaller bonus if it is attacked but block square is not.
- int k = !unsafeSquares ? 35 :
- !(unsafeSquares & squaresToQueen) ? 20 :
- !(unsafeSquares & blockSq) ? 9 :
+ int k = !unsafeSquares ? 36 :
+ !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 :
+ !(unsafeSquares & squaresToQueen) ? 17 :
+ !(unsafeSquares & blockSq) ? 7 :
0 ;
// Assign a larger bonus if the block square is defended
score += bonus - PassedFile * edge_distance(file_of(s));
}
- if (T)
+ if constexpr (T)
Trace::add(PASSED, Us, score);
return score;
behind |= shift<Down>(behind);
behind |= shift<Down+Down>(behind);
+ // Compute space score based on the number of safe squares and number of our pieces
+ // increased with number of total blocked pawns in position.
int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
Score score = make_score(bonus * weight * weight / 16, 0);
- if (T)
+ if constexpr (T)
Trace::add(SPACE, Us, score);
return score;
Value Evaluation<T>::winnable(Score score) const {
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
+ + int(rank_of(pos.square<KING>(WHITE)) - rank_of(pos.square<KING>(BLACK)));
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
&& (pos.pieces(PAWN) & KingSide);
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
int sf = me->scale_factor(pos, strongSide);
- // If scale factor is not already specific, scale down via general heuristics
+ // If scale factor is not already specific, scale up/down via general heuristics
if (sf == SCALE_FACTOR_NORMAL)
{
if (pos.opposite_bishops())
{
+ // For pure opposite colored bishops endgames use scale factor
+ // based on the number of passed pawns of the strong side.
if ( pos.non_pawn_material(WHITE) == BishopValueMg
&& pos.non_pawn_material(BLACK) == BishopValueMg)
sf = 18 + 4 * popcount(pe->passed_pawns(strongSide));
+ // For every other opposite colored bishops endgames use scale factor
+ // based on the number of all pieces of the strong side.
else
sf = 22 + 3 * pos.count<ALL_PIECES>(strongSide);
}
+ // For rook endgames with strong side not having overwhelming pawn number advantage
+ // and its pawns being on one flank and weak side protecting its pieces with a king
+ // use lower scale factor.
else if ( pos.non_pawn_material(WHITE) == RookValueMg
&& pos.non_pawn_material(BLACK) == RookValueMg
&& pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
&& bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
&& (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
sf = 36;
+ // For queen vs no queen endgames use scale factor
+ // based on number of minors of side that doesn't have queen.
else if (pos.count<QUEEN>() == 1)
sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
: pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
+ // In every other case use scale factor based on
+ // the number of pawns of the strong side reduced if pawns are on a single flank.
else
sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
-
+
+ // Reduce scale factor in case of pawns being on a single flank
sf -= 4 * !pawnsOnBothFlanks;
}
+ eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
v /= PHASE_MIDGAME;
- if (T)
+ if constexpr (T)
{
Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score)));
Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL));
// Initialize score by reading the incrementally updated scores included in
// the position object (material + piece square tables) and the material
// imbalance. Score is computed internally from the white point of view.
- Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt;
+ Score score = pos.psq_score() + me->imbalance();
// Probe the pawn hash table
pe = Pawns::probe(pos);
// Early exit if score is high
auto lazy_skip = [&](Value lazyThreshold) {
- return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64;
+ return abs(mg_value(score) + eg_value(score)) > lazyThreshold
+ + std::abs(pos.this_thread()->bestValue) * 5 / 4
+ + pos.non_pawn_material() / 32;
};
if (lazy_skip(LazyThreshold1))
Value v = winnable(score);
// In case of tracing add all remaining individual evaluation terms
- if (T)
+ if constexpr (T)
{
Trace::add(MATERIAL, pos.psq_score());
Trace::add(IMBALANCE, me->imbalance());
v = (v / 16) * 16;
// Side to move point of view
- v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
+ v = (pos.side_to_move() == WHITE ? v : -v);
return v;
}
-} // namespace
+} // namespace Eval
/// evaluate() is the evaluator for the outer world. It returns a static
/// evaluation of the position from the point of view of the side to move.
-Value Eval::evaluate(const Position& pos) {
+Value Eval::evaluate(const Position& pos, int* complexity) {
+
+ assert(!pos.checkers());
Value v;
+ Value psq = pos.psq_eg_stm();
- if (!Eval::useNNUE)
+ // We use the much less accurate but faster Classical eval when the NNUE
+ // option is set to false. Otherwise we use the NNUE eval unless the
+ // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC)
+ bool useClassical = !useNNUE || abs(psq) > 2048;
+
+ if (useClassical)
v = Evaluation<NO_TRACE>(pos).value();
else
{
- // Scale and shift NNUE for compatibility with search and classical evaluation
- auto adjusted_NNUE = [&](){
- int mat = pos.non_pawn_material() + PieceValue[MG][PAWN] * pos.count<PAWN>();
- return NNUE::evaluate(pos) * (720 + mat / 32) / 1024 + Tempo;
- };
-
- // If there is PSQ imbalance use classical eval, with small probability if it is small
- Value psq = Value(abs(eg_value(pos.psq_score())));
- int r50 = 16 + pos.rule50_count();
- bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50;
- bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
-
- v = classical ? Evaluation<NO_TRACE>(pos).value() : adjusted_NNUE();
-
- // If the classical eval is small and imbalance large, use NNUE nevertheless.
- // For the case of opposite colored bishops, switch to NNUE eval with
- // small probability if the classical eval is less than the threshold.
- if ( largePsq
- && (abs(v) * 16 < NNUEThreshold2 * r50
- || ( pos.opposite_bishops()
- && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50
- && !(pos.this_thread()->nodes & 0xB))))
- v = adjusted_NNUE();
+ int nnueComplexity;
+ int scale = 1001 + 5 * pos.count<PAWN>() + 61 * pos.non_pawn_material() / 4096;
+
+ Color stm = pos.side_to_move();
+ Value optimism = pos.this_thread()->optimism[stm];
+
+ Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
+
+ // Blend nnue complexity with (semi)classical complexity
+ nnueComplexity = ( 406 * nnueComplexity
+ + (424 + optimism) * abs(psq - nnue)
+ ) / 1024;
+
+ // Return hybrid NNUE complexity to caller
+ if (complexity)
+ *complexity = nnueComplexity;
+
+ optimism = optimism * (272 + nnueComplexity) / 256;
+ v = (nnue * scale + optimism * (scale - 748)) / 1024;
}
// Damp down the evaluation linearly when shuffling
- v = v * (100 - pos.rule50_count()) / 100;
+ v = v * (200 - pos.rule50_count()) / 214;
// Guarantee evaluation does not hit the tablebase range
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
+ // When not using NNUE, return classical complexity to caller
+ if (complexity && useClassical)
+ *complexity = abs(v - psq);
+
return v;
}
/// descriptions and values of each evaluation term. Useful for debugging.
/// Trace scores are from white's point of view
-std::string Eval::trace(const Position& pos) {
+std::string Eval::trace(Position& pos) {
if (pos.checkers())
return "Final evaluation: none (in check)";
std::memset(scores, 0, sizeof(scores));
- pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
+ // Reset any global variable used in eval
+ pos.this_thread()->bestValue = VALUE_ZERO;
+ pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
+ pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
v = Evaluation<TRACE>(pos).value();
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
- << " Term | White | Black | Total \n"
- << " | MG EG | MG EG | MG EG \n"
- << " ------------+-------------+-------------+------------\n"
- << " Material | " << Term(MATERIAL)
- << " Imbalance | " << Term(IMBALANCE)
- << " Pawns | " << Term(PAWN)
- << " Knights | " << Term(KNIGHT)
- << " Bishops | " << Term(BISHOP)
- << " Rooks | " << Term(ROOK)
- << " Queens | " << Term(QUEEN)
- << " Mobility | " << Term(MOBILITY)
- << " King safety | " << Term(KING)
- << " Threats | " << Term(THREAT)
- << " Passed | " << Term(PASSED)
- << " Space | " << Term(SPACE)
- << " Winnable | " << Term(WINNABLE)
- << " ------------+-------------+-------------+------------\n"
- << " Total | " << Term(TOTAL);
+ << " Contributing terms for the classical eval:\n"
+ << "+------------+-------------+-------------+-------------+\n"
+ << "| Term | White | Black | Total |\n"
+ << "| | MG EG | MG EG | MG EG |\n"
+ << "+------------+-------------+-------------+-------------+\n"
+ << "| Material | " << Term(MATERIAL)
+ << "| Imbalance | " << Term(IMBALANCE)
+ << "| Pawns | " << Term(PAWN)
+ << "| Knights | " << Term(KNIGHT)
+ << "| Bishops | " << Term(BISHOP)
+ << "| Rooks | " << Term(ROOK)
+ << "| Queens | " << Term(QUEEN)
+ << "| Mobility | " << Term(MOBILITY)
+ << "|King safety | " << Term(KING)
+ << "| Threats | " << Term(THREAT)
+ << "| Passed | " << Term(PASSED)
+ << "| Space | " << Term(SPACE)
+ << "| Winnable | " << Term(WINNABLE)
+ << "+------------+-------------+-------------+-------------+\n"
+ << "| Total | " << Term(TOTAL)
+ << "+------------+-------------+-------------+-------------+\n";
- v = pos.side_to_move() == WHITE ? v : -v;
+ if (Eval::useNNUE)
+ ss << '\n' << NNUE::trace(pos) << '\n';
- ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
+ ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
+ v = pos.side_to_move() == WHITE ? v : -v;
+ ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n";
if (Eval::useNNUE)
{
- v = NNUE::evaluate(pos);
+ v = NNUE::evaluate(pos, false);
v = pos.side_to_move() == WHITE ? v : -v;
- ss << "\nNNUE evaluation: " << to_cp(v) << " (white side)\n";
+ ss << "NNUE evaluation " << to_cp(v) << " (white side)\n";
}
v = evaluate(pos);
v = pos.side_to_move() == WHITE ? v : -v;
- ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";
+ ss << "Final evaluation " << to_cp(v) << " (white side)";
+ if (Eval::useNNUE)
+ ss << " [with scaled NNUE, hybrid, ...]";
+ ss << "\n";
return ss.str();
}
+
+} // namespace Stockfish