/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2020 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 <algorithm>
#include <cassert>
+#include <cstdlib>
#include <cstring> // For std::memset
#include <iomanip>
#include <sstream>
+#include <iostream>
#include "bitboard.h"
#include "evaluate.h"
#include "material.h"
#include "pawns.h"
#include "thread.h"
+#include "uci.h"
+
+namespace Eval {
+
+ bool useNNUE;
+ std::string eval_file_loaded="None";
+
+ void init_NNUE() {
+
+ useNNUE = Options["Use NNUE"];
+ std::string eval_file = std::string(Options["EvalFile"]);
+ if (useNNUE && eval_file_loaded != eval_file)
+ if (Eval::NNUE::load_eval_file(eval_file))
+ eval_file_loaded = eval_file;
+ }
+
+ void verify_NNUE() {
+
+ std::string eval_file = std::string(Options["EvalFile"]);
+ if (useNNUE && eval_file_loaded != eval_file)
+ {
+ UCI::OptionsMap defaults;
+ UCI::init(defaults);
+
+ sync_cout << "info string ERROR: NNUE evaluation used, but the network file " << eval_file << " was not loaded successfully." << sync_endl;
+ sync_cout << "info string ERROR: The UCI option EvalFile might need to specify the full path, including the directory/folder name, to the file." << sync_endl;
+ sync_cout << "info string ERROR: The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/"+std::string(defaults["EvalFile"]) << sync_endl;
+ sync_cout << "info string ERROR: If the UCI option Use NNUE is set to true, network evaluation parameters compatible with the program must be available." << sync_endl;
+ sync_cout << "info string ERROR: The engine will be terminated now." << sync_endl;
+ std::exit(EXIT_FAILURE);
+ }
+
+ if (useNNUE)
+ sync_cout << "info string NNUE evaluation using " << eval_file << " enabled." << sync_endl;
+ else
+ sync_cout << "info string classical evaluation enabled." << sync_endl;
+ }
+}
namespace Trace {
namespace {
// Threshold for lazy and space evaluation
- constexpr Value LazyThreshold = Value(1400);
+ 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);
// KingAttackWeights[PieceType] contains king attack weights by piece type
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
};
// Assorted bonuses and penalties
+ constexpr Score BadOutpost = S( -7, 36);
constexpr Score BishopOnKingRing = S( 24, 0);
constexpr Score BishopPawns = S( 3, 7);
constexpr Score BishopXRayPawns = S( 4, 5);
constexpr Score MinorBehindPawn = S( 18, 3);
constexpr Score PassedFile = S( 11, 8);
constexpr Score PawnlessFlank = S( 17, 95);
- constexpr Score QueenInfiltration = S( -2, 14);
constexpr Score ReachableOutpost = S( 31, 22);
constexpr Score RestrictedPiece = S( 7, 7);
constexpr Score RookOnKingRing = S( 16, 0);
attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
// Init our king safety tables
- Square s = make_square(Utility::clamp(file_of(ksq), FILE_B, FILE_G),
- Utility::clamp(rank_of(ksq), RANK_2, RANK_7));
+ Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G),
+ std::clamp(rank_of(ksq), RANK_2, RANK_7));
kingRing[Us] = attacks_bb<KING>(s) | s;
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
if (Pt == BISHOP || Pt == KNIGHT)
{
- // Bonus if piece is on an outpost square or can reach one
- bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them);
- if (bb & s)
+ // Bonus if the piece is on an outpost square or can reach one
+ // Reduced bonus for knights (BadOutpost) 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);
+
+ if ( Pt == KNIGHT
+ && bb & s & ~CenterFiles // on a side outpost
+ && !(b & targets) // no relevant attacks
+ && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
+ score += BadOutpost;
+ else if (bb & s)
score += Outpost[Pt == BISHOP];
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
score += ReachableOutpost;
Bitboard queenPinners;
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
score -= WeakQueen;
-
- // Bonus for queen on weak square in enemy camp
- if (relative_rank(Us, s) > RANK_4 && (~pe->pawn_attacks_span(Them) & s))
- score += QueenInfiltration;
}
}
if (T)
// Bonus for threats on the next moves against enemy queen
if (pos.count<QUEEN>(Them) == 1)
{
+ bool queenImbalance = pos.count<QUEEN>() == 1;
+
Square s = pos.square<QUEEN>(Them);
- safe = mobilityArea[Us] & ~stronglyProtected;
+ safe = mobilityArea[Us]
+ & ~pos.pieces(Us, PAWN)
+ & ~stronglyProtected;
b = attackedBy[Us][KNIGHT] & attacks_bb<KNIGHT>(s);
- score += KnightOnQueen * popcount(b & safe);
+ score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance);
b = (attackedBy[Us][BISHOP] & attacks_bb<BISHOP>(s, pos.pieces()))
| (attackedBy[Us][ROOK ] & attacks_bb<ROOK >(s, pos.pieces()));
- score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]);
+ score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
}
if (T)
Square blockSq = s + Up;
// Adjust bonus based on the king's proximity
- bonus += make_score(0, ( (king_proximity(Them, blockSq) * 19) / 4
- - king_proximity(Us, blockSq) * 2) * w);
+ bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4
+ - king_proximity(Us, blockSq) * 2) * w);
// If blockSq is not the queening square then consider also a second push
if (r != RANK_7)
// Evaluation::space() computes a space evaluation for a given side, aiming to improve game
- // play in the opening. It is based on the number of safe squares on the 4 central files
+ // play in the opening. It is based on the number of safe squares on the four central files
// on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
// Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
}
- // Evaluation::winnable() adjusts the mg and eg score components based on the
- // known attacking/defending status of the players. A single value is derived
- // by interpolation from the mg and eg values and returned.
+ // Evaluation::winnable() adjusts the midgame and endgame score components, based on
+ // the known attacking/defending status of the players. The final value is derived
+ // by interpolation from the midgame and endgame values.
template<Tracing T>
Value Evaluation<T>::winnable(Score score) const {
// Now apply the bonus: note that we find the attacking side by extracting the
// sign of the midgame or endgame values, and that we carefully cap the bonus
// so that the midgame and endgame scores do not change sign after the bonus.
- int u = ((mg > 0) - (mg < 0)) * Utility::clamp(complexity + 50, -abs(mg), 0);
+ int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0);
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
mg += u;
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
int sf = me->scale_factor(pos, strongSide);
- // If scale is not already specific, scale down the endgame via general heuristics
+ // If scale factor is not already specific, scale down via general heuristics
if (sf == SCALE_FACTOR_NORMAL)
{
if (pos.opposite_bishops())
score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
// Early exit if score is high
- Value v = (mg_value(score) + eg_value(score)) / 2;
- if (abs(v) > LazyThreshold + pos.non_pawn_material() / 64)
- return pos.side_to_move() == WHITE ? v : -v;
+ auto lazy_skip = [&](Value lazyThreshold) {
+ return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64;
+ };
+
+ if (lazy_skip(LazyThreshold1))
+ goto make_v;
// Main evaluation begins here
initialize<WHITE>();
// More complex interactions that require fully populated attack bitboards
score += king< WHITE>() - king< BLACK>()
- + threats<WHITE>() - threats<BLACK>()
- + passed< WHITE>() - passed< BLACK>()
+ + passed< WHITE>() - passed< BLACK>();
+
+ if (lazy_skip(LazyThreshold2))
+ goto make_v;
+
+ score += threats<WHITE>() - threats<BLACK>()
+ space< WHITE>() - space< BLACK>();
+make_v:
// Derive single value from mg and eg parts of score
- v = winnable(score);
+ Value v = winnable(score);
// In case of tracing add all remaining individual evaluation terms
if (T)
// Side to move point of view
v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
- // Damp down the evaluation linearly when shuffling
- v = v * (100 - pos.rule50_count()) / 100;
-
return v;
}
/// evaluation of the position from the point of view of the side to move.
Value Eval::evaluate(const Position& pos) {
- return Evaluation<NO_TRACE>(pos).value();
-}
+ bool classical = !Eval::useNNUE
+ || abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
+ Value v = classical ? Evaluation<NO_TRACE>(pos).value()
+ : NNUE::evaluate(pos) * 5 / 4 + Tempo;
+
+ if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
+ v = NNUE::evaluate(pos) * 5 / 4 + Tempo;
+
+ // Damp down the evaluation linearly when shuffling
+ v = v * (100 - pos.rule50_count()) / 100;
+
+ // 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);
+
+ return v;
+}
/// trace() is like evaluate(), but instead of returning a value, it returns
/// a string (suitable for outputting to stdout) that contains the detailed
/// 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) {
if (pos.checkers())
- return "Total evaluation: none (in check)";
+ return "Final evaluation: none (in check)";
+
+ std::stringstream ss;
+ ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
+
+ Value v;
std::memset(scores, 0, sizeof(scores));
pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
- Value v = Evaluation<TRACE>(pos).value();
+ v = Evaluation<TRACE>(pos).value();
- v = pos.side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view
-
- std::stringstream ss;
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
<< " Term | White | Black | Total \n"
<< " | MG EG | MG EG | MG EG \n"
<< " ------------+-------------+-------------+------------\n"
<< " Total | " << Term(TOTAL);
- ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";
+ 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 = pos.side_to_move() == WHITE ? v : -v;
+ ss << "\nNNUE 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";
return ss.str();
}