X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fevaluate.cpp;h=a5c049a88adb8446cb14720a51b3ab109effa45d;hp=54216b97206b24cab5b90ba18601a3e9d5801df9;hb=HEAD;hpb=4c4cb185aaaa0b3175ca35ab6473f17e9ec64055 diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 54216b97..586cadc0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -16,23 +16,25 @@ along with this program. If not, see . */ +#include "evaluate.h" + #include #include +#include +#include #include #include -#include #include -#include +#include #include -#include "bitboard.h" -#include "evaluate.h" +#include "incbin/incbin.h" #include "misc.h" +#include "nnue/evaluate_nnue.h" +#include "position.h" #include "thread.h" -#include "timeman.h" +#include "types.h" #include "uci.h" -#include "incbin/incbin.h" -#include "nnue/evaluate_nnue.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). @@ -42,48 +44,46 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) - INCBIN(EmbeddedNNUE, EvalFileDefaultName); +INCBIN(EmbeddedNNUE, EvalFileDefaultName); #else - const unsigned char gEmbeddedNNUEData[1] = {0x0}; - const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; - const unsigned int gEmbeddedNNUESize = 1; +const unsigned char gEmbeddedNNUEData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; +const unsigned int gEmbeddedNNUESize = 1; #endif -using namespace std; - namespace Stockfish { namespace Eval { - string currentEvalFileName = "None"; - - /// 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. - /// 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 - /// variable to have the engine search in a special directory in their distro. +std::string currentEvalFileName = "None"; - void 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. +// 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 +// variable to have the engine search in a special directory in their distro. +void NNUE::init() { - string eval_file = string(Options["EvalFile"]); + std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; - #if defined(DEFAULT_NNUE_DIRECTORY) - vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; - #else - vector dirs = { "" , "" , CommandLine::binaryDirectory }; - #endif +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", CommandLine::binaryDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", CommandLine::binaryDirectory}; +#endif - for (const string& directory : dirs) + for (const std::string& directory : dirs) if (currentEvalFileName != eval_file) { if (directory != "") { - ifstream stream(directory + eval_file, ios::binary); + std::ifstream stream(directory + eval_file, std::ios::binary); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -91,36 +91,45 @@ namespace Eval { if (directory == "" && eval_file == EvalFileDefaultName) { // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public basic_streambuf { - public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } }; - MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable + MemoryBuffer buffer( + const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); + (void) gEmbeddedNNUEEnd; // Silence warning on unused variable - istream stream(&buffer); + std::istream stream(&buffer); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } } - } +} - /// NNUE::verify() verifies that the last net used was loaded successfully - void NNUE::verify() { +// Verifies that the last net used was loaded successfully +void NNUE::verify() { - string eval_file = string(Options["EvalFile"]); + std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; if (currentEvalFileName != eval_file) { - string msg1 = "Network evaluation parameters compatible with the engine must be available."; - string msg2 = "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/" + std::string(EvalFileDefaultName); - string msg5 = "The engine will be terminated now."; + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + std::string(EvalFileDefaultName); + std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; sync_cout << "info string ERROR: " << msg2 << sync_endl; @@ -132,78 +141,93 @@ namespace Eval { } sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; - } +} } -/// 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) { +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the given color. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. +Value Eval::simple_eval(const Position& pos, Color c) { + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); +} - assert(!pos.checkers()); - Value v; +// 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) { - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; + assert(!pos.checkers()); - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; + Value v; + Color stm = pos.side_to_move(); + int shuffling = pos.rule50_count(); + int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling + + std::abs(pos.this_thread()->bestValue) + + std::abs(pos.this_thread()->rootSimpleEval); - int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) - + 126 * (pos.count(stm) - pos.count(~stm)); + if (lazy) + v = Value(simpleEval); + else + { + int nnueComplexity; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(material - nnue)) / 32768; + Value optimism = pos.this_thread()->optimism[stm]; - v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm + pos.count())) / 1024; + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; - // Damp down the evaluation linearly when shuffling - v = v * (200 - pos.rule50_count()) / 214; + int npm = pos.non_pawn_material() / 64; + v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; + } - // 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); + // Damp down the evaluation linearly when shuffling + v = v * (200 - shuffling) / 214; - return v; -} + // 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); -/// 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 + return v; +} +// 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(Position& pos) { - if (pos.checkers()) - return "Final evaluation: none (in check)"; + if (pos.checkers()) + return "Final evaluation: none (in check)"; - // 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; + // Reset any global variable used in eval + pos.this_thread()->bestValue = VALUE_ZERO; + pos.this_thread()->rootSimpleEval = VALUE_ZERO; + pos.this_thread()->optimism[WHITE] = VALUE_ZERO; + pos.this_thread()->optimism[BLACK] = VALUE_ZERO; - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos) << '\n'; - ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; + Value v; + v = NNUE::evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; - ss << " [with scaled NNUE, ...]"; - ss << "\n"; + v = evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; + ss << " [with scaled NNUE, ...]"; + ss << "\n"; - return ss.str(); + return ss.str(); } -} // namespace Stockfish +} // namespace Stockfish