]> git.sesse.net Git - stockfish/blobdiff - src/evaluate.cpp
Merge remote-tracking branch 'upstream/master'
[stockfish] / src / evaluate.cpp
index 46ebbb49920021d90b56ed4ea8422f7a85720fdb..221ccde8b8b4d9309087c4eabcc8c52430709b50 100644 (file)
@@ -1,6 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
+  Copyright (C) 2004-2024 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
 
   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 <algorithm>
 #include <cassert>
+#include <cmath>
 #include <cstdlib>
 #include <cstdlib>
-#include <fstream>
 #include <iomanip>
 #include <iostream>
 #include <iomanip>
 #include <iostream>
+#include <memory>
 #include <sstream>
 #include <sstream>
-#include <vector>
+#include <tuple>
 
 
-#include "incbin/incbin.h"
-#include "misc.h"
-#include "nnue/evaluate_nnue.h"
+#include "nnue/network.h"
+#include "nnue/nnue_misc.h"
 #include "position.h"
 #include "position.h"
-#include "thread.h"
 #include "types.h"
 #include "uci.h"
 #include "types.h"
 #include "uci.h"
-
-// 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 Microsoft Visual Studio.
-#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
-  INCBIN(EmbeddedNNUE, EvalFileDefaultName);
-#else
-  const unsigned char        gEmbeddedNNUEData[1] = {0x0};
-  const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
-  const unsigned int         gEmbeddedNNUESize = 1;
-#endif
-
-
-using namespace std;
+#include "nnue/nnue_accumulator.h"
 
 namespace Stockfish {
 
 
 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.
-
-  void NNUE::init() {
-
-    string eval_file = string(Options["EvalFile"]);
-    if (eval_file.empty())
-        eval_file = EvalFileDefaultName;
-
-    #if defined(DEFAULT_NNUE_DIRECTORY)
-    vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
-    #else
-    vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
-    #endif
-
-    for (const string& directory : dirs)
-        if (currentEvalFileName != eval_file)
-        {
-            if (directory != "<internal>")
-            {
-                ifstream stream(directory + eval_file, ios::binary);
-                if (NNUE::load_eval(eval_file, stream))
-                    currentEvalFileName = eval_file;
-            }
-
-            if (directory == "<internal>" && eval_file == EvalFileDefaultName)
-            {
-                // C++ way to prepare a buffer for a memory stream
-                class MemoryBuffer : public basic_streambuf<char> {
-                    public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
-                };
-
-                MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
-                                    size_t(gEmbeddedNNUESize));
-                (void) gEmbeddedNNUEEnd; // Silence warning on unused variable
-
-                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() {
-
-    string eval_file = 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.";
-
-        sync_cout << "info string ERROR: " << msg1 << sync_endl;
-        sync_cout << "info string ERROR: " << msg2 << sync_endl;
-        sync_cout << "info string ERROR: " << msg3 << sync_endl;
-        sync_cout << "info string ERROR: " << msg4 << sync_endl;
-        sync_cout << "info string ERROR: " << msg5 << sync_endl;
-
-        exit(EXIT_FAILURE);
-    }
-
-    sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
-  }
+// 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.
+int Eval::simple_eval(const Position& pos, Color c) {
+    return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c))
+         + (pos.non_pawn_material(c) - pos.non_pawn_material(~c));
 }
 
 }
 
-
-/// simple_eval() 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<PAWN>(c)       - pos.count<PAWN>(~c))
-           +           (pos.non_pawn_material(c) - pos.non_pawn_material(~c));
+bool Eval::use_smallnet(const Position& pos) {
+    int simpleEval = simple_eval(pos, pos.side_to_move());
+    return std::abs(simpleEval) > 962;
 }
 
 }
 
+// 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 Eval::NNUE::Networks&    networks,
+                     const Position&                pos,
+                     Eval::NNUE::AccumulatorCaches& caches,
+                     int                            optimism) {
 
 
-/// 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) {
+    assert(!pos.checkers());
 
 
-  assert(!pos.checkers());
+    bool smallNet = use_smallnet(pos);
+    int  v;
 
 
-  Value v;
-  Color stm      = pos.side_to_move();
-  int shuffling  = pos.rule50_count();
-  int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3);
+    auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small)
+                                       : networks.big.evaluate(pos, &caches.big);
 
 
-  bool lazy = abs(simpleEval) >=   RookValue + KnightValue
-                                 + 16 * shuffling * shuffling
-                                 + abs(pos.this_thread()->bestValue)
-                                 + abs(pos.this_thread()->rootSimpleEval);
+    Value nnue = (125 * psqt + 131 * positional) / 128;
 
 
-  if (lazy)
-      v = Value(simpleEval);
-  else
-  {
-      int nnueComplexity;
-      Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
+    // Re-evaluate the position when higher eval accuracy is worth the time spent
+    if (smallNet && (nnue * psqt < 0 || std::abs(nnue) < 227))
+    {
+        std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big);
+        nnue                       = (125 * psqt + 131 * positional) / 128;
+        smallNet                   = false;
+    }
 
 
-      Value optimism = pos.this_thread()->optimism[stm];
+    // Blend optimism and eval with nnue complexity
+    int nnueComplexity = std::abs(psqt - positional);
+    optimism += optimism * nnueComplexity / (smallNet ? 433 : 453);
+    nnue -= nnue * nnueComplexity / (smallNet ? 18815 : 17864);
 
 
-      // Blend optimism and eval with nnue complexity and material imbalance
-      optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512;
-      nnue     -= nnue     * (nnueComplexity + abs(simpleEval - nnue)) / 32768;
+    int material = (smallNet ? 553 : 532) * pos.count<PAWN>() + pos.non_pawn_material();
+    v = (nnue * (73921 + material) + optimism * (8112 + material)) / (smallNet ? 68104 : 74715);
 
 
-      int npm = pos.non_pawn_material() / 64;
-      v = (  nnue     * (915 + npm + 9 * pos.count<PAWN>())
-           + optimism * (154 + npm +     pos.count<PAWN>())) / 1024;
-  }
+    // Evaluation grain (to get more alpha-beta cuts) with randomization (for robustness)
+    v = (v / 16) * 16 - 1 + (pos.key() & 0x2);
 
 
-  // Damp down the evaluation linearly when shuffling
-  v = v * (200 - shuffling) / 214;
+    // Damp down the evaluation linearly when shuffling
+    v -= v * pos.rule50_count() / 212;
 
 
-  // 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);
+    // 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;
+    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(Position& pos) {
+// 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, const Eval::NNUE::Networks& networks) {
 
 
-  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()->rootSimpleEval  = VALUE_ZERO;
-  pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
-  pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
+    auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
 
 
-  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, networks, *caches) << '\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";
+    auto [psqt, positional] = networks.big.evaluate(pos, &caches->big);
+    Value v                 = psqt + positional;
+    v                       = pos.side_to_move() == WHITE ? v : -v;
+    ss << "NNUE evaluation        " << 0.01 * UCIEngine::to_cp(v, pos) << " (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(networks, pos, *caches, VALUE_ZERO);
+    v = pos.side_to_move() == WHITE ? v : -v;
+    ss << "Final evaluation       " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
+    ss << " [with scaled NNUE, ...]";
+    ss << "\n";
 
 
-  return ss.str();
+    return ss.str();
 }
 
 }
 
-} // namespace Stockfish
+}  // namespace Stockfish