mirror of
https://github.com/sockspls/badfish
synced 2025-05-05 19:09:35 +00:00
Refactor global variables
This aims to remove some of the annoying global structure which Stockfish has. Overall there is no major elo regression to be expected. Non regression SMP STC (paused, early version): https://tests.stockfishchess.org/tests/view/65983d7979aa8af82b9608f1 LLR: 0.23 (-2.94,2.94) <-1.75,0.25> Total: 76232 W: 19035 L: 19096 D: 38101 Ptnml(0-2): 92, 8735, 20515, 8690, 84 Non regression STC (early version): https://tests.stockfishchess.org/tests/view/6595b3a479aa8af82b95da7f LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 185344 W: 47027 L: 46972 D: 91345 Ptnml(0-2): 571, 21285, 48943, 21264, 609 Non regression SMP STC: https://tests.stockfishchess.org/tests/view/65a0715c79aa8af82b96b7e4 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142936 W: 35761 L: 35662 D: 71513 Ptnml(0-2): 209, 16400, 38135, 16531, 193 These global structures/variables add hidden dependencies and allow data to be mutable from where it shouldn't it be (i.e. options). They also prevent Stockfish from internal selfplay, which would be a nice thing to be able to do, i.e. instantiate two Stockfish instances and let them play against each other. It will also allow us to make Stockfish a library, which can be easier used on other platforms. For consistency with the old search code, `thisThread` has been kept, even though it is not strictly necessary anymore. This the first major refactor of this kind (in recent time), and future changes are required, to achieve the previously described goals. This includes cleaning up the dependencies, transforming the network to be self contained and coming up with a plan to deal with proper tablebase memory management (see comments for more information on this). The removal of these global structures has been discussed in parts with Vondele and Sopel. closes https://github.com/official-stockfish/Stockfish/pull/4968 No functional change
This commit is contained in:
parent
6deb88728f
commit
a107910951
27 changed files with 1200 additions and 1001 deletions
|
@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \
|
||||||
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
|
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
|
||||||
nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
|
nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
|
||||||
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
|
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
|
||||||
tt.h tune.h types.h uci.h
|
tt.h tune.h types.h uci.h ucioption.h
|
||||||
|
|
||||||
OBJS = $(notdir $(SRCS:.cpp=.o))
|
OBJS = $(notdir $(SRCS:.cpp=.o))
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -34,9 +35,10 @@
|
||||||
#include "nnue/evaluate_nnue.h"
|
#include "nnue/evaluate_nnue.h"
|
||||||
#include "nnue/nnue_architecture.h"
|
#include "nnue/nnue_architecture.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "thread.h"
|
#include "search.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
// Macro to embed the default efficiently updatable neural network (NNUE) file
|
// Macro to embed the default efficiently updatable neural network (NNUE) file
|
||||||
// data in the engine binary (using incbin.h, by Dale Weiler).
|
// data in the engine binary (using incbin.h, by Dale Weiler).
|
||||||
|
@ -62,10 +64,6 @@ namespace Stockfish {
|
||||||
|
|
||||||
namespace Eval {
|
namespace Eval {
|
||||||
|
|
||||||
std::unordered_map<NNUE::NetSize, EvalFile> EvalFiles = {
|
|
||||||
{NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None"}},
|
|
||||||
{NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None"}}};
|
|
||||||
|
|
||||||
|
|
||||||
// Tries to load a NNUE network at startup time, or when the engine
|
// 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"
|
// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
|
||||||
|
@ -74,38 +72,45 @@ std::unordered_map<NNUE::NetSize, EvalFile> EvalFiles = {
|
||||||
// network may be embedded in the binary), in the active working directory and
|
// 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
|
// 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.
|
// variable to have the engine search in a special directory in their distro.
|
||||||
void NNUE::init() {
|
NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory,
|
||||||
|
const OptionsMap& options,
|
||||||
|
NNUE::EvalFiles evalFiles) {
|
||||||
|
|
||||||
for (auto& [netSize, evalFile] : EvalFiles)
|
for (auto& [netSize, evalFile] : evalFiles)
|
||||||
{
|
{
|
||||||
// Replace with
|
// Replace with
|
||||||
// Options[evalFile.option_name]
|
// options[evalFile.optionName]
|
||||||
// once fishtest supports the uci option EvalFileSmall
|
// once fishtest supports the uci option EvalFileSmall
|
||||||
std::string user_eval_file =
|
std::string user_eval_file =
|
||||||
netSize == Small ? evalFile.default_name : Options[evalFile.option_name];
|
netSize == Small ? evalFile.defaultName : options[evalFile.optionName];
|
||||||
|
|
||||||
if (user_eval_file.empty())
|
if (user_eval_file.empty())
|
||||||
user_eval_file = evalFile.default_name;
|
user_eval_file = evalFile.defaultName;
|
||||||
|
|
||||||
#if defined(DEFAULT_NNUE_DIRECTORY)
|
#if defined(DEFAULT_NNUE_DIRECTORY)
|
||||||
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory,
|
std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
|
||||||
stringify(DEFAULT_NNUE_DIRECTORY)};
|
stringify(DEFAULT_NNUE_DIRECTORY)};
|
||||||
#else
|
#else
|
||||||
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory};
|
std::vector<std::string> dirs = {"<internal>", "", rootDirectory};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (const std::string& directory : dirs)
|
for (const std::string& directory : dirs)
|
||||||
{
|
{
|
||||||
if (evalFile.selected_name != user_eval_file)
|
if (evalFile.current != user_eval_file)
|
||||||
{
|
{
|
||||||
if (directory != "<internal>")
|
if (directory != "<internal>")
|
||||||
{
|
{
|
||||||
std::ifstream stream(directory + user_eval_file, std::ios::binary);
|
std::ifstream stream(directory + user_eval_file, std::ios::binary);
|
||||||
if (NNUE::load_eval(user_eval_file, stream, netSize))
|
auto description = NNUE::load_eval(stream, netSize);
|
||||||
evalFile.selected_name = user_eval_file;
|
|
||||||
|
if (description.has_value())
|
||||||
|
{
|
||||||
|
evalFile.current = user_eval_file;
|
||||||
|
evalFile.netDescription = description.value();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directory == "<internal>" && user_eval_file == evalFile.default_name)
|
if (directory == "<internal>" && user_eval_file == evalFile.defaultName)
|
||||||
{
|
{
|
||||||
// C++ way to prepare a buffer for a memory stream
|
// C++ way to prepare a buffer for a memory stream
|
||||||
class MemoryBuffer: public std::basic_streambuf<char> {
|
class MemoryBuffer: public std::basic_streambuf<char> {
|
||||||
|
@ -124,28 +129,36 @@ void NNUE::init() {
|
||||||
(void) gEmbeddedNNUESmallEnd;
|
(void) gEmbeddedNNUESmallEnd;
|
||||||
|
|
||||||
std::istream stream(&buffer);
|
std::istream stream(&buffer);
|
||||||
if (NNUE::load_eval(user_eval_file, stream, netSize))
|
auto description = NNUE::load_eval(stream, netSize);
|
||||||
evalFile.selected_name = user_eval_file;
|
|
||||||
|
if (description.has_value())
|
||||||
|
{
|
||||||
|
evalFile.current = user_eval_file;
|
||||||
|
evalFile.netDescription = description.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return evalFiles;
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the last net used was loaded successfully
|
// Verifies that the last net used was loaded successfully
|
||||||
void NNUE::verify() {
|
void NNUE::verify(const OptionsMap& options,
|
||||||
|
const std::unordered_map<Eval::NNUE::NetSize, EvalFile>& evalFiles) {
|
||||||
|
|
||||||
for (const auto& [netSize, evalFile] : EvalFiles)
|
for (const auto& [netSize, evalFile] : evalFiles)
|
||||||
{
|
{
|
||||||
// Replace with
|
// Replace with
|
||||||
// Options[evalFile.option_name]
|
// options[evalFile.optionName]
|
||||||
// once fishtest supports the uci option EvalFileSmall
|
// once fishtest supports the uci option EvalFileSmall
|
||||||
std::string user_eval_file =
|
std::string user_eval_file =
|
||||||
netSize == Small ? evalFile.default_name : Options[evalFile.option_name];
|
netSize == Small ? evalFile.defaultName : options[evalFile.optionName];
|
||||||
if (user_eval_file.empty())
|
if (user_eval_file.empty())
|
||||||
user_eval_file = evalFile.default_name;
|
user_eval_file = evalFile.defaultName;
|
||||||
|
|
||||||
if (evalFile.selected_name != user_eval_file)
|
if (evalFile.current != user_eval_file)
|
||||||
{
|
{
|
||||||
std::string msg1 =
|
std::string msg1 =
|
||||||
"Network evaluation parameters compatible with the engine must be available.";
|
"Network evaluation parameters compatible with the engine must be available.";
|
||||||
|
@ -155,7 +168,7 @@ void NNUE::verify() {
|
||||||
"including the directory name, to the network file.";
|
"including the directory name, to the network file.";
|
||||||
std::string msg4 = "The default net can be downloaded from: "
|
std::string msg4 = "The default net can be downloaded from: "
|
||||||
"https://tests.stockfishchess.org/api/nn/"
|
"https://tests.stockfishchess.org/api/nn/"
|
||||||
+ evalFile.default_name;
|
+ evalFile.defaultName;
|
||||||
std::string msg5 = "The engine will be terminated now.";
|
std::string msg5 = "The engine will be terminated now.";
|
||||||
|
|
||||||
sync_cout << "info string ERROR: " << msg1 << sync_endl;
|
sync_cout << "info string ERROR: " << msg1 << sync_endl;
|
||||||
|
@ -183,7 +196,7 @@ int Eval::simple_eval(const Position& pos, Color c) {
|
||||||
|
|
||||||
// Evaluate is the evaluator for the outer world. It returns a static evaluation
|
// 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.
|
// 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, const Search::Worker& workerThread) {
|
||||||
|
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
|
@ -204,7 +217,7 @@ Value Eval::evaluate(const Position& pos) {
|
||||||
Value nnue = smallNet ? NNUE::evaluate<NNUE::Small>(pos, true, &nnueComplexity)
|
Value nnue = smallNet ? NNUE::evaluate<NNUE::Small>(pos, true, &nnueComplexity)
|
||||||
: NNUE::evaluate<NNUE::Big>(pos, true, &nnueComplexity);
|
: NNUE::evaluate<NNUE::Big>(pos, true, &nnueComplexity);
|
||||||
|
|
||||||
int optimism = pos.this_thread()->optimism[stm];
|
int optimism = workerThread.optimism[stm];
|
||||||
|
|
||||||
// Blend optimism and eval with nnue complexity and material imbalance
|
// Blend optimism and eval with nnue complexity and material imbalance
|
||||||
optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512;
|
optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512;
|
||||||
|
@ -227,16 +240,16 @@ Value Eval::evaluate(const Position& pos) {
|
||||||
// a string (suitable for outputting to stdout) that contains the detailed
|
// a string (suitable for outputting to stdout) that contains the detailed
|
||||||
// descriptions and values of each evaluation term. Useful for debugging.
|
// descriptions and values of each evaluation term. Useful for debugging.
|
||||||
// Trace scores are from white's point of view
|
// Trace scores are from white's point of view
|
||||||
std::string Eval::trace(Position& pos) {
|
std::string Eval::trace(Position& pos, Search::Worker& workerThread) {
|
||||||
|
|
||||||
if (pos.checkers())
|
if (pos.checkers())
|
||||||
return "Final evaluation: none (in check)";
|
return "Final evaluation: none (in check)";
|
||||||
|
|
||||||
// Reset any global variable used in eval
|
// Reset any global variable used in eval
|
||||||
pos.this_thread()->bestValue = VALUE_ZERO;
|
workerThread.iterBestValue = VALUE_ZERO;
|
||||||
pos.this_thread()->rootSimpleEval = VALUE_ZERO;
|
workerThread.rootSimpleEval = VALUE_ZERO;
|
||||||
pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
|
workerThread.optimism[WHITE] = VALUE_ZERO;
|
||||||
pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
|
workerThread.optimism[BLACK] = VALUE_ZERO;
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
||||||
|
@ -249,7 +262,7 @@ std::string Eval::trace(Position& pos) {
|
||||||
v = pos.side_to_move() == WHITE ? v : -v;
|
v = pos.side_to_move() == WHITE ? v : -v;
|
||||||
ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n";
|
ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n";
|
||||||
|
|
||||||
v = evaluate(pos);
|
v = evaluate(pos, workerThread);
|
||||||
v = pos.side_to_move() == WHITE ? v : -v;
|
v = pos.side_to_move() == WHITE ? v : -v;
|
||||||
ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)";
|
ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)";
|
||||||
ss << " [with scaled NNUE, ...]";
|
ss << " [with scaled NNUE, ...]";
|
||||||
|
|
|
@ -27,13 +27,18 @@
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
class Position;
|
||||||
|
class OptionsMap;
|
||||||
|
|
||||||
|
namespace Search {
|
||||||
|
class Worker;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Eval {
|
namespace Eval {
|
||||||
|
|
||||||
std::string trace(Position& pos);
|
std::string trace(Position& pos, Search::Worker& workerThread);
|
||||||
|
|
||||||
int simple_eval(const Position& pos, Color c);
|
int simple_eval(const Position& pos, Color c);
|
||||||
Value evaluate(const Position& pos);
|
Value evaluate(const Position& pos, const Search::Worker& workerThread);
|
||||||
|
|
||||||
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
|
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
|
||||||
// for the build process (profile-build and fishtest) to work. Do not change the
|
// for the build process (profile-build and fishtest) to work. Do not change the
|
||||||
|
@ -41,23 +46,28 @@ Value evaluate(const Position& pos);
|
||||||
#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue"
|
#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue"
|
||||||
#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue"
|
#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue"
|
||||||
|
|
||||||
|
struct EvalFile {
|
||||||
|
// UCI option name
|
||||||
|
std::string optionName;
|
||||||
|
// Default net name, will use one of the macros above
|
||||||
|
std::string defaultName;
|
||||||
|
// Selected net name, either via uci option or default
|
||||||
|
std::string current;
|
||||||
|
// Net description extracted from the net file
|
||||||
|
std::string netDescription;
|
||||||
|
};
|
||||||
|
|
||||||
namespace NNUE {
|
namespace NNUE {
|
||||||
|
|
||||||
enum NetSize : int;
|
enum NetSize : int;
|
||||||
|
|
||||||
void init();
|
using EvalFiles = std::unordered_map<Eval::NNUE::NetSize, EvalFile>;
|
||||||
void verify();
|
|
||||||
|
EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles);
|
||||||
|
void verify(const OptionsMap&, const EvalFiles&);
|
||||||
|
|
||||||
} // namespace NNUE
|
} // namespace NNUE
|
||||||
|
|
||||||
struct EvalFile {
|
|
||||||
std::string option_name;
|
|
||||||
std::string default_name;
|
|
||||||
std::string selected_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern std::unordered_map<NNUE::NetSize, EvalFile> EvalFiles;
|
|
||||||
|
|
||||||
} // namespace Eval
|
} // namespace Eval
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
19
src/main.cpp
19
src/main.cpp
|
@ -16,15 +16,13 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
|
||||||
#include "thread.h"
|
|
||||||
#include "tune.h"
|
#include "tune.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
@ -35,17 +33,16 @@ int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
std::cout << engine_info() << std::endl;
|
std::cout << engine_info() << std::endl;
|
||||||
|
|
||||||
CommandLine::init(argc, argv);
|
|
||||||
UCI::init(Options);
|
|
||||||
Tune::init();
|
|
||||||
Bitboards::init();
|
Bitboards::init();
|
||||||
Position::init();
|
Position::init();
|
||||||
Threads.set(size_t(Options["Threads"]));
|
|
||||||
Search::clear(); // After threads are up
|
|
||||||
Eval::NNUE::init();
|
|
||||||
|
|
||||||
UCI::loop(argc, argv);
|
UCI uci(argc, argv);
|
||||||
|
|
||||||
|
Tune::init(uci.options);
|
||||||
|
|
||||||
|
uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles);
|
||||||
|
|
||||||
|
uci.loop();
|
||||||
|
|
||||||
Threads.set(0);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
15
src/misc.cpp
15
src/misc.cpp
|
@ -721,17 +721,13 @@ void bindThisThread(size_t idx) {
|
||||||
#define GETCWD getcwd
|
#define GETCWD getcwd
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace CommandLine {
|
CommandLine::CommandLine(int _argc, char** _argv) :
|
||||||
|
argc(_argc),
|
||||||
std::string argv0; // path+name of the executable binary, as given by argv[0]
|
argv(_argv) {
|
||||||
std::string binaryDirectory; // path of the executable directory
|
|
||||||
std::string workingDirectory; // path of the working directory
|
|
||||||
|
|
||||||
void init([[maybe_unused]] int argc, char* argv[]) {
|
|
||||||
std::string pathSeparator;
|
std::string pathSeparator;
|
||||||
|
|
||||||
// Extract the path+name of the executable binary
|
// Extract the path+name of the executable binary
|
||||||
argv0 = argv[0];
|
std::string argv0 = argv[0];
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
pathSeparator = "\\";
|
pathSeparator = "\\";
|
||||||
|
@ -766,7 +762,4 @@ void init([[maybe_unused]] int argc, char* argv[]) {
|
||||||
binaryDirectory.replace(0, 1, workingDirectory);
|
binaryDirectory.replace(0, 1, workingDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace CommandLine
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
15
src/misc.h
15
src/misc.h
|
@ -176,12 +176,17 @@ namespace WinProcGroup {
|
||||||
void bindThisThread(size_t idx);
|
void bindThisThread(size_t idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace CommandLine {
|
|
||||||
void init(int argc, char* argv[]);
|
|
||||||
|
|
||||||
extern std::string binaryDirectory; // path of the executable directory
|
struct CommandLine {
|
||||||
extern std::string workingDirectory; // path of the working directory
|
public:
|
||||||
}
|
CommandLine(int, char**);
|
||||||
|
|
||||||
|
int argc;
|
||||||
|
char** argv;
|
||||||
|
|
||||||
|
std::string binaryDirectory; // path of the executable directory
|
||||||
|
std::string workingDirectory; // path of the working directory
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,10 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "../evaluate.h"
|
#include "../evaluate.h"
|
||||||
|
@ -51,8 +53,6 @@ AlignedPtr<Network<TransformedFeatureDimensionsBig, L2Big, L3Big>> network
|
||||||
AlignedPtr<Network<TransformedFeatureDimensionsSmall, L2Small, L3Small>> networkSmall[LayerStacks];
|
AlignedPtr<Network<TransformedFeatureDimensionsSmall, L2Small, L3Small>> networkSmall[LayerStacks];
|
||||||
|
|
||||||
// Evaluation function file names
|
// Evaluation function file names
|
||||||
std::string fileName[2];
|
|
||||||
std::string netDescription[2];
|
|
||||||
|
|
||||||
namespace Detail {
|
namespace Detail {
|
||||||
|
|
||||||
|
@ -136,10 +136,10 @@ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const st
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
static bool read_parameters(std::istream& stream, NetSize netSize) {
|
static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) {
|
||||||
|
|
||||||
std::uint32_t hashValue;
|
std::uint32_t hashValue;
|
||||||
if (!read_header(stream, &hashValue, &netDescription[netSize]))
|
if (!read_header(stream, &hashValue, &netDescription))
|
||||||
return false;
|
return false;
|
||||||
if (hashValue != HashValue[netSize])
|
if (hashValue != HashValue[netSize])
|
||||||
return false;
|
return false;
|
||||||
|
@ -158,9 +158,10 @@ static bool read_parameters(std::istream& stream, NetSize netSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write network parameters
|
// Write network parameters
|
||||||
static bool write_parameters(std::ostream& stream, NetSize netSize) {
|
static bool
|
||||||
|
write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) {
|
||||||
|
|
||||||
if (!write_header(stream, HashValue[netSize], netDescription[netSize]))
|
if (!write_header(stream, HashValue[netSize], netDescription))
|
||||||
return false;
|
return false;
|
||||||
if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig))
|
if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig))
|
||||||
return false;
|
return false;
|
||||||
|
@ -424,24 +425,30 @@ std::string trace(Position& pos) {
|
||||||
|
|
||||||
|
|
||||||
// Load eval, from a file stream or a memory stream
|
// Load eval, from a file stream or a memory stream
|
||||||
bool load_eval(const std::string name, std::istream& stream, NetSize netSize) {
|
std::optional<std::string> load_eval(std::istream& stream, NetSize netSize) {
|
||||||
|
|
||||||
initialize(netSize);
|
initialize(netSize);
|
||||||
fileName[netSize] = name;
|
std::string netDescription;
|
||||||
return read_parameters(stream, netSize);
|
return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription)
|
||||||
|
: std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save eval, to a file stream or a memory stream
|
// Save eval, to a file stream or a memory stream
|
||||||
bool save_eval(std::ostream& stream, NetSize netSize) {
|
bool save_eval(std::ostream& stream,
|
||||||
|
NetSize netSize,
|
||||||
|
const std::string& name,
|
||||||
|
const std::string& netDescription) {
|
||||||
|
|
||||||
if (fileName[netSize].empty())
|
if (name.empty() || name == "None")
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return write_parameters(stream, netSize);
|
return write_parameters(stream, netSize, netDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save eval, to a file given by its name
|
// Save eval, to a file given by its name
|
||||||
bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
bool save_eval(const std::optional<std::string>& filename,
|
||||||
|
NetSize netSize,
|
||||||
|
const std::unordered_map<Eval::NNUE::NetSize, Eval::EvalFile>& evalFiles) {
|
||||||
|
|
||||||
std::string actualFilename;
|
std::string actualFilename;
|
||||||
std::string msg;
|
std::string msg;
|
||||||
|
@ -450,7 +457,7 @@ bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
||||||
actualFilename = filename.value();
|
actualFilename = filename.value();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (EvalFiles.at(netSize).selected_name
|
if (evalFiles.at(netSize).current
|
||||||
!= (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig))
|
!= (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig))
|
||||||
{
|
{
|
||||||
msg = "Failed to export a net. "
|
msg = "Failed to export a net. "
|
||||||
|
@ -463,7 +470,8 @@ bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream stream(actualFilename, std::ios_base::binary);
|
std::ofstream stream(actualFilename, std::ios_base::binary);
|
||||||
bool saved = save_eval(stream, netSize);
|
bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current,
|
||||||
|
evalFiles.at(netSize).netDescription);
|
||||||
|
|
||||||
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
|
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,20 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "../misc.h"
|
#include "../misc.h"
|
||||||
|
#include "../types.h"
|
||||||
#include "nnue_architecture.h"
|
#include "nnue_architecture.h"
|
||||||
#include "nnue_feature_transformer.h"
|
#include "nnue_feature_transformer.h"
|
||||||
#include "../types.h"
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
class Position;
|
class Position;
|
||||||
|
|
||||||
|
namespace Eval {
|
||||||
|
struct EvalFile;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
@ -73,9 +79,14 @@ template<NetSize Net_Size>
|
||||||
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
|
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
|
||||||
void hint_common_parent_position(const Position& pos);
|
void hint_common_parent_position(const Position& pos);
|
||||||
|
|
||||||
bool load_eval(const std::string name, std::istream& stream, NetSize netSize);
|
std::optional<std::string> load_eval(std::istream& stream, NetSize netSize);
|
||||||
bool save_eval(std::ostream& stream, NetSize netSize);
|
bool save_eval(std::ostream& stream,
|
||||||
bool save_eval(const std::optional<std::string>& filename, NetSize netSize);
|
NetSize netSize,
|
||||||
|
const std::string& name,
|
||||||
|
const std::string& netDescription);
|
||||||
|
bool save_eval(const std::optional<std::string>& filename,
|
||||||
|
NetSize netSize,
|
||||||
|
const std::unordered_map<Eval::NNUE::NetSize, Eval::EvalFile>&);
|
||||||
|
|
||||||
} // namespace Stockfish::Eval::NNUE
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
@ -36,7 +35,6 @@
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "nnue/nnue_common.h"
|
#include "nnue/nnue_common.h"
|
||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
#include "thread.h"
|
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
|
||||||
|
@ -87,7 +85,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
||||||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||||
|
|
||||||
Position p;
|
Position p;
|
||||||
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
|
p.set(pos.fen(), pos.is_chess960(), &st);
|
||||||
Tablebases::ProbeState s1, s2;
|
Tablebases::ProbeState s1, s2;
|
||||||
Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
|
Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
|
||||||
int dtz = Tablebases::probe_dtz(p, &s2);
|
int dtz = Tablebases::probe_dtz(p, &s2);
|
||||||
|
@ -160,7 +158,7 @@ void Position::init() {
|
||||||
// Initializes the position object with the given FEN string.
|
// Initializes the position object with the given FEN string.
|
||||||
// This function is not very robust - make sure that input FENs are correct,
|
// This function is not very robust - make sure that input FENs are correct,
|
||||||
// this is assumed to be the responsibility of the GUI.
|
// this is assumed to be the responsibility of the GUI.
|
||||||
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
|
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) {
|
||||||
/*
|
/*
|
||||||
A FEN string defines a particular position using only the ASCII character set.
|
A FEN string defines a particular position using only the ASCII character set.
|
||||||
|
|
||||||
|
@ -287,7 +285,6 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
||||||
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
|
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
|
||||||
|
|
||||||
chess960 = isChess960;
|
chess960 = isChess960;
|
||||||
thisThread = th;
|
|
||||||
set_state();
|
set_state();
|
||||||
|
|
||||||
assert(pos_is_ok());
|
assert(pos_is_ok());
|
||||||
|
@ -388,7 +385,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) {
|
||||||
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1]
|
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1]
|
||||||
+ char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
|
+ char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
|
||||||
|
|
||||||
return set(fenStr, false, si, nullptr);
|
return set(fenStr, false, si);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -667,7 +664,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
||||||
assert(m.is_ok());
|
assert(m.is_ok());
|
||||||
assert(&newSt != st);
|
assert(&newSt != st);
|
||||||
|
|
||||||
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
Key k = st->key ^ Zobrist::side;
|
Key k = st->key ^ Zobrist::side;
|
||||||
|
|
||||||
// Copy some fields of the old state to our new StateInfo object except the
|
// Copy some fields of the old state to our new StateInfo object except the
|
||||||
|
@ -959,7 +955,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
|
||||||
|
|
||||||
// Used to do a "null move": it flips
|
// Used to do a "null move": it flips
|
||||||
// the side to move without executing any move on the board.
|
// the side to move without executing any move on the board.
|
||||||
void Position::do_null_move(StateInfo& newSt) {
|
void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) {
|
||||||
|
|
||||||
assert(!checkers());
|
assert(!checkers());
|
||||||
assert(&newSt != st);
|
assert(&newSt != st);
|
||||||
|
@ -982,7 +978,7 @@ void Position::do_null_move(StateInfo& newSt) {
|
||||||
|
|
||||||
st->key ^= Zobrist::side;
|
st->key ^= Zobrist::side;
|
||||||
++st->rule50;
|
++st->rule50;
|
||||||
prefetch(TT.first_entry(key()));
|
prefetch(tt.first_entry(key()));
|
||||||
|
|
||||||
st->pliesFromNull = 0;
|
st->pliesFromNull = 0;
|
||||||
|
|
||||||
|
@ -1235,7 +1231,7 @@ void Position::flip() {
|
||||||
std::getline(ss, token); // Half and full moves
|
std::getline(ss, token); // Half and full moves
|
||||||
f += token;
|
f += token;
|
||||||
|
|
||||||
set(f, is_chess960(), st, this_thread());
|
set(f, is_chess960(), st);
|
||||||
|
|
||||||
assert(pos_is_ok());
|
assert(pos_is_ok());
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
|
class TranspositionTable;
|
||||||
|
|
||||||
// StateInfo struct stores information needed to restore a Position object to
|
// StateInfo struct stores information needed to restore a Position object to
|
||||||
// its previous state when we retract a move. Whenever a move is made on the
|
// its previous state when we retract a move. Whenever a move is made on the
|
||||||
// board (by calling Position::do_move), a StateInfo object must be passed.
|
// board (by calling Position::do_move), a StateInfo object must be passed.
|
||||||
|
@ -75,8 +77,6 @@ using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
|
||||||
// pieces, side to move, hash keys, castling info, etc. Important methods are
|
// pieces, side to move, hash keys, castling info, etc. Important methods are
|
||||||
// do_move() and undo_move(), used by the search to update node info when
|
// do_move() and undo_move(), used by the search to update node info when
|
||||||
// traversing the search tree.
|
// traversing the search tree.
|
||||||
class Thread;
|
|
||||||
|
|
||||||
class Position {
|
class Position {
|
||||||
public:
|
public:
|
||||||
static void init();
|
static void init();
|
||||||
|
@ -86,7 +86,7 @@ class Position {
|
||||||
Position& operator=(const Position&) = delete;
|
Position& operator=(const Position&) = delete;
|
||||||
|
|
||||||
// FEN string input/output
|
// FEN string input/output
|
||||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si);
|
||||||
Position& set(const std::string& code, Color c, StateInfo* si);
|
Position& set(const std::string& code, Color c, StateInfo* si);
|
||||||
std::string fen() const;
|
std::string fen() const;
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ class Position {
|
||||||
void do_move(Move m, StateInfo& newSt);
|
void do_move(Move m, StateInfo& newSt);
|
||||||
void do_move(Move m, StateInfo& newSt, bool givesCheck);
|
void do_move(Move m, StateInfo& newSt, bool givesCheck);
|
||||||
void undo_move(Move m);
|
void undo_move(Move m);
|
||||||
void do_null_move(StateInfo& newSt);
|
void do_null_move(StateInfo& newSt, TranspositionTable& tt);
|
||||||
void undo_null_move();
|
void undo_null_move();
|
||||||
|
|
||||||
// Static Exchange Evaluation
|
// Static Exchange Evaluation
|
||||||
|
@ -155,7 +155,6 @@ class Position {
|
||||||
Color side_to_move() const;
|
Color side_to_move() const;
|
||||||
int game_ply() const;
|
int game_ply() const;
|
||||||
bool is_chess960() const;
|
bool is_chess960() const;
|
||||||
Thread* this_thread() const;
|
|
||||||
bool is_draw(int ply) const;
|
bool is_draw(int ply) const;
|
||||||
bool has_game_cycle(int ply) const;
|
bool has_game_cycle(int ply) const;
|
||||||
bool has_repeated() const;
|
bool has_repeated() const;
|
||||||
|
@ -194,7 +193,6 @@ class Position {
|
||||||
int castlingRightsMask[SQUARE_NB];
|
int castlingRightsMask[SQUARE_NB];
|
||||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||||
Thread* thisThread;
|
|
||||||
StateInfo* st;
|
StateInfo* st;
|
||||||
int gamePly;
|
int gamePly;
|
||||||
Color sideToMove;
|
Color sideToMove;
|
||||||
|
@ -328,8 +326,6 @@ inline bool Position::capture_stage(Move m) const {
|
||||||
|
|
||||||
inline Piece Position::captured_piece() const { return st->capturedPiece; }
|
inline Piece Position::captured_piece() const { return st->capturedPiece; }
|
||||||
|
|
||||||
inline Thread* Position::this_thread() const { return thisThread; }
|
|
||||||
|
|
||||||
inline void Position::put_piece(Piece pc, Square s) {
|
inline void Position::put_piece(Piece pc, Square s) {
|
||||||
|
|
||||||
board[s] = pc;
|
board[s] = pc;
|
||||||
|
|
460
src/search.cpp
460
src/search.cpp
File diff suppressed because it is too large
Load diff
153
src/search.h
153
src/search.h
|
@ -19,19 +19,37 @@
|
||||||
#ifndef SEARCH_H_INCLUDED
|
#ifndef SEARCH_H_INCLUDED
|
||||||
#define SEARCH_H_INCLUDED
|
#define SEARCH_H_INCLUDED
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "movepick.h"
|
#include "movepick.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "timeman.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
// Different node types, used as a template parameter
|
||||||
|
enum NodeType {
|
||||||
|
NonPV,
|
||||||
|
PV,
|
||||||
|
Root
|
||||||
|
};
|
||||||
|
|
||||||
|
class TranspositionTable;
|
||||||
|
class ThreadPool;
|
||||||
|
class OptionsMap;
|
||||||
|
class UCI;
|
||||||
|
|
||||||
namespace Search {
|
namespace Search {
|
||||||
|
|
||||||
|
// Called at startup to initialize various lookup tables, after program startup
|
||||||
|
void init(int);
|
||||||
|
|
||||||
// Stack struct keeps track of the information we need to remember from nodes
|
// Stack struct keeps track of the information we need to remember from nodes
|
||||||
// shallower and deeper in the tree during the search. Each search thread has
|
// shallower and deeper in the tree during the search. Each search thread has
|
||||||
|
@ -61,7 +79,7 @@ struct RootMove {
|
||||||
|
|
||||||
explicit RootMove(Move m) :
|
explicit RootMove(Move m) :
|
||||||
pv(1, m) {}
|
pv(1, m) {}
|
||||||
bool extract_ponder_from_tt(Position& pos);
|
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
|
||||||
bool operator==(const Move& m) const { return pv[0] == m; }
|
bool operator==(const Move& m) const { return pv[0] == m; }
|
||||||
// Sort in descending order
|
// Sort in descending order
|
||||||
bool operator<(const RootMove& m) const {
|
bool operator<(const RootMove& m) const {
|
||||||
|
@ -85,7 +103,6 @@ using RootMoves = std::vector<RootMove>;
|
||||||
|
|
||||||
// LimitsType struct stores information sent by GUI about available time to
|
// LimitsType struct stores information sent by GUI about available time to
|
||||||
// search the current move, maximum depth/time, or if we are in analysis mode.
|
// search the current move, maximum depth/time, or if we are in analysis mode.
|
||||||
|
|
||||||
struct LimitsType {
|
struct LimitsType {
|
||||||
|
|
||||||
// Init explicitly due to broken value-initialization of non POD in MSVC
|
// Init explicitly due to broken value-initialization of non POD in MSVC
|
||||||
|
@ -103,11 +120,137 @@ struct LimitsType {
|
||||||
int64_t nodes;
|
int64_t nodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern LimitsType Limits;
|
|
||||||
|
|
||||||
void init();
|
// The UCI stores the uci options, thread pool, and transposition table.
|
||||||
|
// This struct is used to easily forward data to the Search::Worker class.
|
||||||
|
struct SharedState {
|
||||||
|
SharedState(const OptionsMap& o, ThreadPool& tp, TranspositionTable& t) :
|
||||||
|
options(o),
|
||||||
|
threads(tp),
|
||||||
|
tt(t) {}
|
||||||
|
|
||||||
|
const OptionsMap& options;
|
||||||
|
ThreadPool& threads;
|
||||||
|
TranspositionTable& tt;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Worker;
|
||||||
|
|
||||||
|
// Null Object Pattern, implement a common interface
|
||||||
|
// for the SearchManagers. A Null Object will be given to
|
||||||
|
// non-mainthread workers.
|
||||||
|
class ISearchManager {
|
||||||
|
public:
|
||||||
|
virtual ~ISearchManager() {}
|
||||||
|
virtual void check_time(Search::Worker&) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SearchManager manages the search from the main thread. It is responsible for
|
||||||
|
// keeping track of the time, and storing data strictly related to the main thread.
|
||||||
|
class SearchManager: public ISearchManager {
|
||||||
|
public:
|
||||||
|
void check_time(Search::Worker& worker) override;
|
||||||
|
|
||||||
|
Stockfish::TimeManagement tm;
|
||||||
|
int callsCnt;
|
||||||
|
std::atomic_bool ponder;
|
||||||
|
|
||||||
|
double previousTimeReduction;
|
||||||
|
Value bestPreviousScore;
|
||||||
|
Value bestPreviousAverageScore;
|
||||||
|
Value iterValue[4];
|
||||||
|
bool stopOnPonderhit;
|
||||||
|
|
||||||
|
size_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NullSearchManager: public ISearchManager {
|
||||||
|
public:
|
||||||
|
void check_time(Search::Worker&) override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search::Worker is the class that does the actual search.
|
||||||
|
// It is instantiated once per thread, and it is responsible for keeping track
|
||||||
|
// of the search history, and storing data required for the search.
|
||||||
|
class Worker {
|
||||||
|
public:
|
||||||
|
Worker(SharedState&, std::unique_ptr<ISearchManager>, size_t);
|
||||||
|
|
||||||
|
// Reset histories, usually before a new game
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
// Called when the program receives the UCI 'go'
|
||||||
|
// command. It searches from the root position and outputs the "bestmove".
|
||||||
|
void start_searching();
|
||||||
|
|
||||||
|
bool is_mainthread() const { return thread_idx == 0; }
|
||||||
|
|
||||||
|
// Public because evaluate uses this
|
||||||
|
Value iterBestValue, optimism[COLOR_NB];
|
||||||
|
Value rootSimpleEval;
|
||||||
|
|
||||||
|
// Public because they need to be updatable by the stats
|
||||||
|
CounterMoveHistory counterMoves;
|
||||||
|
ButterflyHistory mainHistory;
|
||||||
|
CapturePieceToHistory captureHistory;
|
||||||
|
ContinuationHistory continuationHistory[2][2];
|
||||||
|
PawnHistory pawnHistory;
|
||||||
|
CorrectionHistory correctionHistory;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void iterative_deepening();
|
||||||
|
|
||||||
|
// Main search function for both PV and non-PV nodes
|
||||||
|
template<NodeType nodeType>
|
||||||
|
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
|
||||||
|
|
||||||
|
// Quiescence search function, which is called by the main search
|
||||||
|
template<NodeType nodeType>
|
||||||
|
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
|
||||||
|
|
||||||
|
Depth reduction(bool i, Depth d, int mn, int delta) {
|
||||||
|
int reductionScale = reductions[d] * reductions[mn];
|
||||||
|
return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024
|
||||||
|
+ (!i && reductionScale > 880);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a pointer to the search manager, only allowed to be called by the
|
||||||
|
// main thread.
|
||||||
|
SearchManager* main_manager() const {
|
||||||
|
assert(thread_idx == 0);
|
||||||
|
return static_cast<SearchManager*>(manager.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
LimitsType limits;
|
||||||
|
|
||||||
|
size_t pvIdx, pvLast;
|
||||||
|
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||||
|
int selDepth, nmpMinPly;
|
||||||
|
|
||||||
|
Position rootPos;
|
||||||
|
StateInfo rootState;
|
||||||
|
RootMoves rootMoves;
|
||||||
|
Depth rootDepth, completedDepth;
|
||||||
|
Value rootDelta;
|
||||||
|
|
||||||
|
size_t thread_idx;
|
||||||
|
|
||||||
|
// Reductions lookup table initialized at startup
|
||||||
|
int reductions[MAX_MOVES]; // [depth or moveNumber]
|
||||||
|
|
||||||
|
// The main thread has a SearchManager, the others have a NullSearchManager
|
||||||
|
std::unique_ptr<ISearchManager> manager;
|
||||||
|
|
||||||
|
const OptionsMap& options;
|
||||||
|
ThreadPool& threads;
|
||||||
|
TranspositionTable& tt;
|
||||||
|
|
||||||
|
friend class Stockfish::ThreadPool;
|
||||||
|
friend class Stockfish::UCI;
|
||||||
|
friend class SearchManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Search
|
} // namespace Search
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
#include "../position.h"
|
#include "../position.h"
|
||||||
#include "../search.h"
|
#include "../search.h"
|
||||||
#include "../types.h"
|
#include "../types.h"
|
||||||
#include "../uci.h"
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
@ -1574,7 +1573,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
||||||
// Use the DTZ tables to rank root moves.
|
// Use the DTZ tables to rank root moves.
|
||||||
//
|
//
|
||||||
// A return value false indicates that not all probes were successful.
|
// A return value false indicates that not all probes were successful.
|
||||||
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) {
|
||||||
|
|
||||||
ProbeState result = OK;
|
ProbeState result = OK;
|
||||||
StateInfo st;
|
StateInfo st;
|
||||||
|
@ -1585,7 +1584,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
||||||
// Check whether a position was repeated since the last zeroing move.
|
// Check whether a position was repeated since the last zeroing move.
|
||||||
bool rep = pos.has_repeated();
|
bool rep = pos.has_repeated();
|
||||||
|
|
||||||
int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1;
|
int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1;
|
||||||
|
|
||||||
// Probe and rank each move
|
// Probe and rank each move
|
||||||
for (auto& m : rootMoves)
|
for (auto& m : rootMoves)
|
||||||
|
@ -1647,7 +1646,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
||||||
// This is a fallback for the case that some or all DTZ tables are missing.
|
// This is a fallback for the case that some or all DTZ tables are missing.
|
||||||
//
|
//
|
||||||
// A return value false indicates that not all probes were successful.
|
// A return value false indicates that not all probes were successful.
|
||||||
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) {
|
||||||
|
|
||||||
static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};
|
static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};
|
||||||
|
|
||||||
|
@ -1655,7 +1654,6 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
||||||
StateInfo st;
|
StateInfo st;
|
||||||
WDLScore wdl;
|
WDLScore wdl;
|
||||||
|
|
||||||
bool rule50 = Options["Syzygy50MoveRule"];
|
|
||||||
|
|
||||||
// Probe and rank each move
|
// Probe and rank each move
|
||||||
for (auto& m : rootMoves)
|
for (auto& m : rootMoves)
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
class Position;
|
class Position;
|
||||||
|
class OptionsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Stockfish::Tablebases {
|
namespace Stockfish::Tablebases {
|
||||||
|
@ -47,12 +48,13 @@ enum ProbeState {
|
||||||
|
|
||||||
extern int MaxCardinality;
|
extern int MaxCardinality;
|
||||||
|
|
||||||
|
|
||||||
void init(const std::string& paths);
|
void init(const std::string& paths);
|
||||||
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
||||||
int probe_dtz(Position& pos, ProbeState* result);
|
int probe_dtz(Position& pos, ProbeState* result);
|
||||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves);
|
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves);
|
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||||
void rank_root_moves(Position& pos, Search::RootMoves& rootMoves);
|
void rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves);
|
||||||
|
|
||||||
} // namespace Stockfish::Tablebases
|
} // namespace Stockfish::Tablebases
|
||||||
|
|
||||||
|
|
128
src/thread.cpp
128
src/thread.cpp
|
@ -23,9 +23,8 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <initializer_list>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
|
@ -33,18 +32,21 @@
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
|
#include "timeman.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
#include "uci.h"
|
#include "types.h"
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
ThreadPool Threads; // Global object
|
|
||||||
|
|
||||||
|
|
||||||
// Constructor launches the thread and waits until it goes to sleep
|
// Constructor launches the thread and waits until it goes to sleep
|
||||||
// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
||||||
Thread::Thread(size_t n) :
|
Thread::Thread(Search::SharedState& sharedState,
|
||||||
|
std::unique_ptr<Search::ISearchManager> sm,
|
||||||
|
size_t n) :
|
||||||
|
worker(std::make_unique<Search::Worker>(sharedState, std::move(sm), n)),
|
||||||
idx(n),
|
idx(n),
|
||||||
|
nthreads(sharedState.options["Threads"]),
|
||||||
stdThread(&Thread::idle_loop, this) {
|
stdThread(&Thread::idle_loop, this) {
|
||||||
|
|
||||||
wait_for_search_finished();
|
wait_for_search_finished();
|
||||||
|
@ -62,24 +64,6 @@ Thread::~Thread() {
|
||||||
stdThread.join();
|
stdThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Reset histories, usually before a new game
|
|
||||||
void Thread::clear() {
|
|
||||||
|
|
||||||
counterMoves.fill(Move::none());
|
|
||||||
mainHistory.fill(0);
|
|
||||||
captureHistory.fill(0);
|
|
||||||
pawnHistory.fill(0);
|
|
||||||
correctionHistory.fill(0);
|
|
||||||
|
|
||||||
for (bool inCheck : {false, true})
|
|
||||||
for (StatsType c : {NoCaptures, Captures})
|
|
||||||
for (auto& to : continuationHistory[inCheck][c])
|
|
||||||
for (auto& h : to)
|
|
||||||
h->fill(-71);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Wakes up the thread that will start the search
|
// Wakes up the thread that will start the search
|
||||||
void Thread::start_searching() {
|
void Thread::start_searching() {
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
|
@ -108,7 +92,7 @@ void Thread::idle_loop() {
|
||||||
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
|
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
|
||||||
// just check if running threads are below a threshold, in this case, all this
|
// just check if running threads are below a threshold, in this case, all this
|
||||||
// NUMA machinery is not needed.
|
// NUMA machinery is not needed.
|
||||||
if (Options["Threads"] > 8)
|
if (nthreads > 8)
|
||||||
WinProcGroup::bindThisThread(idx);
|
WinProcGroup::bindThisThread(idx);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -123,36 +107,41 @@ void Thread::idle_loop() {
|
||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
|
|
||||||
search();
|
worker->start_searching();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates/destroys threads to match the requested number.
|
// Creates/destroys threads to match the requested number.
|
||||||
// Created and launched threads will immediately go to sleep in idle_loop.
|
// Created and launched threads will immediately go to sleep in idle_loop.
|
||||||
// Upon resizing, threads are recreated to allow for binding if necessary.
|
// Upon resizing, threads are recreated to allow for binding if necessary.
|
||||||
void ThreadPool::set(size_t requested) {
|
void ThreadPool::set(Search::SharedState sharedState) {
|
||||||
|
|
||||||
if (threads.size() > 0) // destroy any existing thread(s)
|
if (threads.size() > 0) // destroy any existing thread(s)
|
||||||
{
|
{
|
||||||
main()->wait_for_search_finished();
|
main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
while (threads.size() > 0)
|
while (threads.size() > 0)
|
||||||
delete threads.back(), threads.pop_back();
|
delete threads.back(), threads.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const size_t requested = sharedState.options["Threads"];
|
||||||
|
|
||||||
if (requested > 0) // create new thread(s)
|
if (requested > 0) // create new thread(s)
|
||||||
{
|
{
|
||||||
threads.push_back(new MainThread(0));
|
threads.push_back(new Thread(
|
||||||
|
sharedState, std::unique_ptr<Search::ISearchManager>(new Search::SearchManager()), 0));
|
||||||
|
|
||||||
|
|
||||||
while (threads.size() < requested)
|
while (threads.size() < requested)
|
||||||
threads.push_back(new Thread(threads.size()));
|
threads.push_back(new Thread(
|
||||||
|
sharedState, std::unique_ptr<Search::ISearchManager>(new Search::NullSearchManager()),
|
||||||
|
threads.size()));
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
// Reallocate the hash with the new threadpool size
|
main_thread()->wait_for_search_finished();
|
||||||
TT.resize(size_t(Options["Hash"]));
|
|
||||||
|
|
||||||
// Init thread number dependent search params.
|
// Reallocate the hash with the new threadpool size
|
||||||
Search::init();
|
sharedState.tt.resize(sharedState.options["Hash"], requested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,28 +150,31 @@ void ThreadPool::set(size_t requested) {
|
||||||
void ThreadPool::clear() {
|
void ThreadPool::clear() {
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
th->clear();
|
th->worker->clear();
|
||||||
|
|
||||||
main()->callsCnt = 0;
|
main_manager()->callsCnt = 0;
|
||||||
main()->bestPreviousScore = VALUE_INFINITE;
|
main_manager()->bestPreviousScore = VALUE_INFINITE;
|
||||||
main()->bestPreviousAverageScore = VALUE_INFINITE;
|
main_manager()->bestPreviousAverageScore = VALUE_INFINITE;
|
||||||
main()->previousTimeReduction = 1.0;
|
main_manager()->previousTimeReduction = 1.0;
|
||||||
|
main_manager()->tm.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Wakes up main thread waiting in idle_loop() and
|
// Wakes up main thread waiting in idle_loop() and
|
||||||
// returns immediately. Main thread will wake up other threads and start the search.
|
// returns immediately. Main thread will wake up other threads and start the search.
|
||||||
void ThreadPool::start_thinking(Position& pos,
|
void ThreadPool::start_thinking(const OptionsMap& options,
|
||||||
|
Position& pos,
|
||||||
StateListPtr& states,
|
StateListPtr& states,
|
||||||
const Search::LimitsType& limits,
|
Search::LimitsType limits,
|
||||||
bool ponderMode) {
|
bool ponderMode) {
|
||||||
|
|
||||||
main()->wait_for_search_finished();
|
main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
|
main_manager()->stopOnPonderhit = stop = false;
|
||||||
|
main_manager()->ponder = ponderMode;
|
||||||
|
|
||||||
main()->stopOnPonderhit = stop = false;
|
|
||||||
increaseDepth = true;
|
increaseDepth = true;
|
||||||
main()->ponder = ponderMode;
|
|
||||||
Search::Limits = limits;
|
|
||||||
Search::RootMoves rootMoves;
|
Search::RootMoves rootMoves;
|
||||||
|
|
||||||
for (const auto& m : MoveList<LEGAL>(pos))
|
for (const auto& m : MoveList<LEGAL>(pos))
|
||||||
|
@ -191,7 +183,7 @@ void ThreadPool::start_thinking(Position& pos,
|
||||||
rootMoves.emplace_back(m);
|
rootMoves.emplace_back(m);
|
||||||
|
|
||||||
if (!rootMoves.empty())
|
if (!rootMoves.empty())
|
||||||
Tablebases::rank_root_moves(pos, rootMoves);
|
Tablebases::rank_root_moves(options, pos, rootMoves);
|
||||||
|
|
||||||
// After ownership transfer 'states' becomes empty, so if we stop the search
|
// After ownership transfer 'states' becomes empty, so if we stop the search
|
||||||
// and call 'go' again without setting a new position states.get() == nullptr.
|
// and call 'go' again without setting a new position states.get() == nullptr.
|
||||||
|
@ -207,15 +199,17 @@ void ThreadPool::start_thinking(Position& pos,
|
||||||
// since they are read-only.
|
// since they are read-only.
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
{
|
{
|
||||||
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
th->worker->limits = limits;
|
||||||
th->rootDepth = th->completedDepth = 0;
|
th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly =
|
||||||
th->rootMoves = rootMoves;
|
th->worker->bestMoveChanges = 0;
|
||||||
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
|
th->worker->rootDepth = th->worker->completedDepth = 0;
|
||||||
th->rootState = setupStates->back();
|
th->worker->rootMoves = rootMoves;
|
||||||
th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move());
|
th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState);
|
||||||
|
th->worker->rootState = setupStates->back();
|
||||||
|
th->worker->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move());
|
||||||
}
|
}
|
||||||
|
|
||||||
main()->start_searching();
|
main_thread()->start_searching();
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread* ThreadPool::get_best_thread() const {
|
Thread* ThreadPool::get_best_thread() const {
|
||||||
|
@ -226,30 +220,32 @@ Thread* ThreadPool::get_best_thread() const {
|
||||||
|
|
||||||
// Find the minimum score of all threads
|
// Find the minimum score of all threads
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
minScore = std::min(minScore, th->rootMoves[0].score);
|
minScore = std::min(minScore, th->worker->rootMoves[0].score);
|
||||||
|
|
||||||
// Vote according to score and depth, and select the best thread
|
// Vote according to score and depth, and select the best thread
|
||||||
auto thread_value = [minScore](Thread* th) {
|
auto thread_value = [minScore](Thread* th) {
|
||||||
return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
votes[th->rootMoves[0].pv[0]] += thread_value(th);
|
votes[th->worker->rootMoves[0].pv[0]] += thread_value(th);
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||||
{
|
{
|
||||||
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
||||||
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score)
|
||||||
bestThread = th;
|
bestThread = th;
|
||||||
}
|
}
|
||||||
else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
||||||
|| (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
|| (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
||||||
&& (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|
&& (votes[th->worker->rootMoves[0].pv[0]]
|
||||||
|| (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
|
> votes[bestThread->worker->rootMoves[0].pv[0]]
|
||||||
&& thread_value(th) * int(th->rootMoves[0].pv.size() > 2)
|
|| (votes[th->worker->rootMoves[0].pv[0]]
|
||||||
|
== votes[bestThread->worker->rootMoves[0].pv[0]]
|
||||||
|
&& thread_value(th) * int(th->worker->rootMoves[0].pv.size() > 2)
|
||||||
> thread_value(bestThread)
|
> thread_value(bestThread)
|
||||||
* int(bestThread->rootMoves[0].pv.size() > 2)))))
|
* int(bestThread->worker->rootMoves[0].pv.size() > 2)))))
|
||||||
bestThread = th;
|
bestThread = th;
|
||||||
|
|
||||||
return bestThread;
|
return bestThread;
|
||||||
|
@ -257,7 +253,7 @@ Thread* ThreadPool::get_best_thread() const {
|
||||||
|
|
||||||
|
|
||||||
// Start non-main threads
|
// Start non-main threads
|
||||||
|
// Will be invoked by main thread after it has started searching
|
||||||
void ThreadPool::start_searching() {
|
void ThreadPool::start_searching() {
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
|
|
101
src/thread.h
101
src/thread.h
|
@ -23,88 +23,73 @@
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "movepick.h"
|
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread_win32_osx.h"
|
#include "thread_win32_osx.h"
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
// Thread class keeps together all the thread-related stuff.
|
class OptionsMap;
|
||||||
|
using Value = int;
|
||||||
|
|
||||||
|
// Abstraction of a thread. It contains a pointer to the worker and a native thread.
|
||||||
|
// After construction, the native thread is started with idle_loop()
|
||||||
|
// waiting for a signal to start searching.
|
||||||
|
// When the signal is received, the thread starts searching and when
|
||||||
|
// the search is finished, it goes back to idle_loop() waiting for a new signal.
|
||||||
class Thread {
|
class Thread {
|
||||||
|
|
||||||
std::mutex mutex;
|
|
||||||
std::condition_variable cv;
|
|
||||||
size_t idx;
|
|
||||||
bool exit = false, searching = true; // Set before starting std::thread
|
|
||||||
NativeThread stdThread;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Thread(size_t);
|
Thread(Search::SharedState&, std::unique_ptr<Search::ISearchManager>, size_t);
|
||||||
virtual ~Thread();
|
virtual ~Thread();
|
||||||
virtual void search();
|
|
||||||
void clear();
|
|
||||||
void idle_loop();
|
void idle_loop();
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished();
|
void wait_for_search_finished();
|
||||||
size_t id() const { return idx; }
|
size_t id() const { return idx; }
|
||||||
|
|
||||||
size_t pvIdx, pvLast;
|
std::unique_ptr<Search::Worker> worker;
|
||||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
|
||||||
int selDepth, nmpMinPly;
|
|
||||||
Value bestValue;
|
|
||||||
|
|
||||||
int optimism[COLOR_NB];
|
private:
|
||||||
|
std::mutex mutex;
|
||||||
Position rootPos;
|
std::condition_variable cv;
|
||||||
StateInfo rootState;
|
size_t idx, nthreads;
|
||||||
Search::RootMoves rootMoves;
|
bool exit = false, searching = true; // Set before starting std::thread
|
||||||
Depth rootDepth, completedDepth;
|
NativeThread stdThread;
|
||||||
int rootDelta;
|
|
||||||
Value rootSimpleEval;
|
|
||||||
CounterMoveHistory counterMoves;
|
|
||||||
ButterflyHistory mainHistory;
|
|
||||||
CapturePieceToHistory captureHistory;
|
|
||||||
ContinuationHistory continuationHistory[2][2];
|
|
||||||
PawnHistory pawnHistory;
|
|
||||||
CorrectionHistory correctionHistory;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// MainThread is a derived class specific for main thread
|
|
||||||
struct MainThread: public Thread {
|
|
||||||
|
|
||||||
using Thread::Thread;
|
|
||||||
|
|
||||||
void search() override;
|
|
||||||
void check_time();
|
|
||||||
|
|
||||||
double previousTimeReduction;
|
|
||||||
Value bestPreviousScore;
|
|
||||||
Value bestPreviousAverageScore;
|
|
||||||
Value iterValue[4];
|
|
||||||
int callsCnt;
|
|
||||||
bool stopOnPonderhit;
|
|
||||||
std::atomic_bool ponder;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ThreadPool struct handles all the threads-related stuff like init, starting,
|
// ThreadPool struct handles all the threads-related stuff like init, starting,
|
||||||
// parking and, most importantly, launching a thread. All the access to threads
|
// parking and, most importantly, launching a thread. All the access to threads
|
||||||
// is done through this class.
|
// is done through this class.
|
||||||
struct ThreadPool {
|
class ThreadPool {
|
||||||
|
|
||||||
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
public:
|
||||||
|
~ThreadPool() {
|
||||||
|
// destroy any existing thread(s)
|
||||||
|
if (threads.size() > 0)
|
||||||
|
{
|
||||||
|
main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
|
while (threads.size() > 0)
|
||||||
|
delete threads.back(), threads.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false);
|
||||||
void clear();
|
void clear();
|
||||||
void set(size_t);
|
void set(Search::SharedState);
|
||||||
|
|
||||||
MainThread* main() const { return static_cast<MainThread*>(threads.front()); }
|
Search::SearchManager* main_manager() const {
|
||||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
return static_cast<Search::SearchManager*>(main_thread()->worker.get()->manager.get());
|
||||||
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
};
|
||||||
|
Thread* main_thread() const { return threads.front(); }
|
||||||
|
uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); }
|
||||||
|
uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); }
|
||||||
Thread* get_best_thread() const;
|
Thread* get_best_thread() const;
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished() const;
|
void wait_for_search_finished() const;
|
||||||
|
@ -122,17 +107,15 @@ struct ThreadPool {
|
||||||
StateListPtr setupStates;
|
StateListPtr setupStates;
|
||||||
std::vector<Thread*> threads;
|
std::vector<Thread*> threads;
|
||||||
|
|
||||||
uint64_t accumulate(std::atomic<uint64_t> Thread::*member) const {
|
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::*member) const {
|
||||||
|
|
||||||
uint64_t sum = 0;
|
uint64_t sum = 0;
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
sum += (th->*member).load(std::memory_order_relaxed);
|
sum += (th->worker.get()->*member).load(std::memory_order_relaxed);
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ThreadPool Threads;
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef THREAD_H_INCLUDED
|
#endif // #ifndef THREAD_H_INCLUDED
|
||||||
|
|
|
@ -30,31 +30,35 @@
|
||||||
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
// free function to be passed to pthread_create()
|
||||||
|
inline void* start_routine(void* ptr) {
|
||||||
template<class T, class P = std::pair<T*, void (T::*)()>>
|
auto func = reinterpret_cast<std::function<void()>*>(ptr);
|
||||||
void* start_routine(void* ptr) {
|
(*func)(); // Call the function
|
||||||
P* p = reinterpret_cast<P*>(ptr);
|
delete func;
|
||||||
(p->first->*(p->second))(); // Call member function pointer
|
|
||||||
delete p;
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NativeThread {
|
class NativeThread {
|
||||||
|
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
|
|
||||||
|
static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<class T, class P = std::pair<T*, void (T::*)()>>
|
template<class Function, class... Args>
|
||||||
explicit NativeThread(void (T::*fun)(), T* obj) {
|
explicit NativeThread(Function&& fun, Args&&... args) {
|
||||||
|
auto func = new std::function<void()>(
|
||||||
|
std::bind(std::forward<Function>(fun), std::forward<Args>(args)...));
|
||||||
|
|
||||||
pthread_attr_t attr_storage, *attr = &attr_storage;
|
pthread_attr_t attr_storage, *attr = &attr_storage;
|
||||||
pthread_attr_init(attr);
|
pthread_attr_init(attr);
|
||||||
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
||||||
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
pthread_create(&thread, attr, start_routine, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
void join() { pthread_join(thread, nullptr); }
|
void join() { pthread_join(thread, nullptr); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,30 +19,47 @@
|
||||||
#include "timeman.h"
|
#include "timeman.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "uci.h"
|
#include "ucioption.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
TimeManagement Time; // Our global time management object
|
|
||||||
|
|
||||||
|
TimePoint TimeManagement::optimum() const { return optimumTime; }
|
||||||
|
TimePoint TimeManagement::maximum() const { return maximumTime; }
|
||||||
|
TimePoint TimeManagement::elapsed(size_t nodes) const {
|
||||||
|
return useNodesTime ? TimePoint(nodes) : now() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeManagement::clear() {
|
||||||
|
availableNodes = 0; // When in 'nodes as time' mode
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
|
||||||
|
assert(useNodesTime);
|
||||||
|
availableNodes += nodes;
|
||||||
|
}
|
||||||
|
|
||||||
// Called at the beginning of the search and calculates
|
// Called at the beginning of the search and calculates
|
||||||
// the bounds of time allowed for the current game ply. We currently support:
|
// the bounds of time allowed for the current game ply. We currently support:
|
||||||
// 1) x basetime (+ z increment)
|
// 1) x basetime (+ z increment)
|
||||||
// 2) x moves in y seconds (+ z increment)
|
// 2) x moves in y seconds (+ z increment)
|
||||||
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
void TimeManagement::init(Search::LimitsType& limits,
|
||||||
|
Color us,
|
||||||
|
int ply,
|
||||||
|
const OptionsMap& options) {
|
||||||
// If we have no time, no need to initialize TM, except for the start time,
|
// If we have no time, no need to initialize TM, except for the start time,
|
||||||
// which is used by movetime.
|
// which is used by movetime.
|
||||||
startTime = limits.startTime;
|
startTime = limits.startTime;
|
||||||
if (limits.time[us] == 0)
|
if (limits.time[us] == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
|
TimePoint moveOverhead = TimePoint(options["Move Overhead"]);
|
||||||
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
TimePoint npmsec = TimePoint(options["nodestime"]);
|
||||||
|
|
||||||
// optScale is a percentage of available time to use for the current move.
|
// optScale is a percentage of available time to use for the current move.
|
||||||
// maxScale is a multiplier applied to optimumTime.
|
// maxScale is a multiplier applied to optimumTime.
|
||||||
|
@ -54,6 +71,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
||||||
// must be much lower than the real engine speed.
|
// must be much lower than the real engine speed.
|
||||||
if (npmsec)
|
if (npmsec)
|
||||||
{
|
{
|
||||||
|
useNodesTime = true;
|
||||||
|
|
||||||
if (!availableNodes) // Only once at game start
|
if (!availableNodes) // Only once at game start
|
||||||
availableNodes = npmsec * limits.time[us]; // Time is in msec
|
availableNodes = npmsec * limits.time[us]; // Time is in msec
|
||||||
|
|
||||||
|
@ -100,7 +119,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
||||||
maximumTime =
|
maximumTime =
|
||||||
TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
|
TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
|
||||||
|
|
||||||
if (Options["Ponder"])
|
if (options["Ponder"])
|
||||||
optimumTime += optimumTime / 4;
|
optimumTime += optimumTime / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,35 +19,41 @@
|
||||||
#ifndef TIMEMAN_H_INCLUDED
|
#ifndef TIMEMAN_H_INCLUDED
|
||||||
#define TIMEMAN_H_INCLUDED
|
#define TIMEMAN_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "search.h"
|
|
||||||
#include "thread.h"
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
|
class OptionsMap;
|
||||||
|
|
||||||
|
namespace Search {
|
||||||
|
struct LimitsType;
|
||||||
|
}
|
||||||
|
|
||||||
// The TimeManagement class computes the optimal time to think depending on
|
// The TimeManagement class computes the optimal time to think depending on
|
||||||
// the maximum available time, the game move number, and other parameters.
|
// the maximum available time, the game move number, and other parameters.
|
||||||
class TimeManagement {
|
class TimeManagement {
|
||||||
public:
|
public:
|
||||||
void init(Search::LimitsType& limits, Color us, int ply);
|
void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options);
|
||||||
TimePoint optimum() const { return optimumTime; }
|
|
||||||
TimePoint maximum() const { return maximumTime; }
|
|
||||||
TimePoint elapsed() const {
|
|
||||||
return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t availableNodes; // When in 'nodes as time' mode
|
TimePoint optimum() const;
|
||||||
|
TimePoint maximum() const;
|
||||||
|
TimePoint elapsed(std::size_t nodes) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void advance_nodes_time(std::int64_t nodes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TimePoint startTime;
|
TimePoint startTime;
|
||||||
TimePoint optimumTime;
|
TimePoint optimumTime;
|
||||||
TimePoint maximumTime;
|
TimePoint maximumTime;
|
||||||
};
|
|
||||||
|
|
||||||
extern TimeManagement Time;
|
std::int64_t availableNodes = 0; // When in 'nodes as time' mode
|
||||||
|
bool useNodesTime = false; // True if we are in 'nodes as time' mode
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
|
|
31
src/tt.cpp
31
src/tt.cpp
|
@ -26,16 +26,13 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "thread.h"
|
|
||||||
#include "uci.h"
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
TranspositionTable TT; // Our global transposition table
|
|
||||||
|
|
||||||
// Populates the TTEntry with a new node's data, possibly
|
// Populates the TTEntry with a new node's data, possibly
|
||||||
// overwriting an old position. The update is not atomic and can be racy.
|
// overwriting an old position. The update is not atomic and can be racy.
|
||||||
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
void TTEntry::save(
|
||||||
|
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
|
||||||
|
|
||||||
// Preserve any existing move for the same position
|
// Preserve any existing move for the same position
|
||||||
if (m || uint16_t(k) != key16)
|
if (m || uint16_t(k) != key16)
|
||||||
|
@ -49,7 +46,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev)
|
||||||
|
|
||||||
key16 = uint16_t(k);
|
key16 = uint16_t(k);
|
||||||
depth8 = uint8_t(d - DEPTH_OFFSET);
|
depth8 = uint8_t(d - DEPTH_OFFSET);
|
||||||
genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b);
|
genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);
|
||||||
value16 = int16_t(v);
|
value16 = int16_t(v);
|
||||||
eval16 = int16_t(ev);
|
eval16 = int16_t(ev);
|
||||||
}
|
}
|
||||||
|
@ -59,10 +56,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev)
|
||||||
// Sets the size of the transposition table,
|
// Sets the size of the transposition table,
|
||||||
// measured in megabytes. Transposition table consists of a power of 2 number
|
// measured in megabytes. Transposition table consists of a power of 2 number
|
||||||
// of clusters and each cluster consists of ClusterSize number of TTEntry.
|
// of clusters and each cluster consists of ClusterSize number of TTEntry.
|
||||||
void TranspositionTable::resize(size_t mbSize) {
|
void TranspositionTable::resize(size_t mbSize, int threadCount) {
|
||||||
|
|
||||||
Threads.main()->wait_for_search_finished();
|
|
||||||
|
|
||||||
aligned_large_pages_free(table);
|
aligned_large_pages_free(table);
|
||||||
|
|
||||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||||
|
@ -74,28 +68,25 @@ void TranspositionTable::resize(size_t mbSize) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear();
|
clear(threadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initializes the entire transposition table to zero,
|
// Initializes the entire transposition table to zero,
|
||||||
// in a multi-threaded way.
|
// in a multi-threaded way.
|
||||||
void TranspositionTable::clear() {
|
void TranspositionTable::clear(size_t threadCount) {
|
||||||
|
|
||||||
std::vector<std::thread> threads;
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx)
|
for (size_t idx = 0; idx < size_t(threadCount); ++idx)
|
||||||
{
|
{
|
||||||
threads.emplace_back([this, idx]() {
|
threads.emplace_back([this, idx, threadCount]() {
|
||||||
// Thread binding gives faster search on systems with a first-touch policy
|
// Thread binding gives faster search on systems with a first-touch policy
|
||||||
if (Options["Threads"] > 8)
|
if (threadCount > 8)
|
||||||
WinProcGroup::bindThisThread(idx);
|
WinProcGroup::bindThisThread(idx);
|
||||||
|
|
||||||
// Each thread will zero its part of the hash table
|
// Each thread will zero its part of the hash table
|
||||||
const size_t stride = size_t(clusterCount / Options["Threads"]),
|
const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx),
|
||||||
start = size_t(stride * idx),
|
len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start;
|
||||||
len =
|
|
||||||
idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start;
|
|
||||||
|
|
||||||
std::memset(&table[start], 0, len * sizeof(Cluster));
|
std::memset(&table[start], 0, len * sizeof(Cluster));
|
||||||
});
|
});
|
||||||
|
|
14
src/tt.h
14
src/tt.h
|
@ -45,7 +45,7 @@ struct TTEntry {
|
||||||
Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); }
|
Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); }
|
||||||
bool is_pv() const { return bool(genBound8 & 0x4); }
|
bool is_pv() const { return bool(genBound8 & 0x4); }
|
||||||
Bound bound() const { return Bound(genBound8 & 0x3); }
|
Bound bound() const { return Bound(genBound8 & 0x3); }
|
||||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);
|
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class TranspositionTable;
|
friend class TranspositionTable;
|
||||||
|
@ -88,23 +88,23 @@ class TranspositionTable {
|
||||||
void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things
|
void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things
|
||||||
TTEntry* probe(const Key key, bool& found) const;
|
TTEntry* probe(const Key key, bool& found) const;
|
||||||
int hashfull() const;
|
int hashfull() const;
|
||||||
void resize(size_t mbSize);
|
void resize(size_t mbSize, int threadCount);
|
||||||
void clear();
|
void clear(size_t threadCount);
|
||||||
|
|
||||||
TTEntry* first_entry(const Key key) const {
|
TTEntry* first_entry(const Key key) const {
|
||||||
return &table[mul_hi64(key, clusterCount)].entry[0];
|
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t generation() const { return generation8; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct TTEntry;
|
friend struct TTEntry;
|
||||||
|
|
||||||
size_t clusterCount;
|
size_t clusterCount;
|
||||||
Cluster* table;
|
Cluster* table = nullptr;
|
||||||
uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
|
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
|
||||||
};
|
};
|
||||||
|
|
||||||
extern TranspositionTable TT;
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef TT_H_INCLUDED
|
#endif // #ifndef TT_H_INCLUDED
|
||||||
|
|
19
src/tune.cpp
19
src/tune.cpp
|
@ -24,14 +24,15 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "uci.h"
|
#include "ucioption.h"
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
bool Tune::update_on_last;
|
bool Tune::update_on_last;
|
||||||
const UCI::Option* LastOption = nullptr;
|
const Option* LastOption = nullptr;
|
||||||
|
OptionsMap* Tune::options;
|
||||||
static std::map<std::string, int> TuneResults;
|
static std::map<std::string, int> TuneResults;
|
||||||
|
|
||||||
string Tune::next(string& names, bool pop) {
|
string Tune::next(string& names, bool pop) {
|
||||||
|
@ -53,13 +54,13 @@ string Tune::next(string& names, bool pop) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_tune(const UCI::Option& o) {
|
static void on_tune(const Option& o) {
|
||||||
|
|
||||||
if (!Tune::update_on_last || LastOption == &o)
|
if (!Tune::update_on_last || LastOption == &o)
|
||||||
Tune::read_options();
|
Tune::read_options();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void make_option(const string& n, int v, const SetRange& r) {
|
static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) {
|
||||||
|
|
||||||
// Do not generate option when there is nothing to tune (ie. min = max)
|
// Do not generate option when there is nothing to tune (ie. min = max)
|
||||||
if (r(v).first == r(v).second)
|
if (r(v).first == r(v).second)
|
||||||
|
@ -68,8 +69,8 @@ static void make_option(const string& n, int v, const SetRange& r) {
|
||||||
if (TuneResults.count(n))
|
if (TuneResults.count(n))
|
||||||
v = TuneResults[n];
|
v = TuneResults[n];
|
||||||
|
|
||||||
Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
|
(*options)[n] << Option(v, r(v).first, r(v).second, on_tune);
|
||||||
LastOption = &Options[n];
|
LastOption = &((*options)[n]);
|
||||||
|
|
||||||
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
||||||
std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << ","
|
std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << ","
|
||||||
|
@ -79,13 +80,13 @@ static void make_option(const string& n, int v, const SetRange& r) {
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void Tune::Entry<int>::init_option() {
|
void Tune::Entry<int>::init_option() {
|
||||||
make_option(name, value, range);
|
make_option(options, name, value, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void Tune::Entry<int>::read_option() {
|
void Tune::Entry<int>::read_option() {
|
||||||
if (Options.count(name))
|
if (options->count(name))
|
||||||
value = int(Options[name]);
|
value = int((*options)[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instead of a variable here we have a PostUpdate function: just call it
|
// Instead of a variable here we have a PostUpdate function: just call it
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
|
class OptionsMap;
|
||||||
|
|
||||||
using Range = std::pair<int, int>; // Option's min-max values
|
using Range = std::pair<int, int>; // Option's min-max values
|
||||||
using RangeFun = Range(int);
|
using RangeFun = Range(int);
|
||||||
|
|
||||||
|
@ -151,7 +153,8 @@ class Tune {
|
||||||
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2),
|
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2),
|
||||||
args...); // Remove trailing parenthesis
|
args...); // Remove trailing parenthesis
|
||||||
}
|
}
|
||||||
static void init() {
|
static void init(OptionsMap& o) {
|
||||||
|
options = &o;
|
||||||
for (auto& e : instance().list)
|
for (auto& e : instance().list)
|
||||||
e->init_option();
|
e->init_option();
|
||||||
read_options();
|
read_options();
|
||||||
|
@ -160,7 +163,9 @@ class Tune {
|
||||||
for (auto& e : instance().list)
|
for (auto& e : instance().list)
|
||||||
e->read_option();
|
e->read_option();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool update_on_last;
|
static bool update_on_last;
|
||||||
|
static OptionsMap* options;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()
|
// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()
|
||||||
|
|
527
src/uci.cpp
527
src/uci.cpp
|
@ -22,111 +22,156 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "benchmark.h"
|
#include "benchmark.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
#include "misc.h"
|
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "nnue/evaluate_nnue.h"
|
#include "nnue/evaluate_nnue.h"
|
||||||
#include "nnue/nnue_architecture.h"
|
#include "nnue/nnue_architecture.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread.h"
|
#include "syzygy/tbprobe.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||||
|
constexpr int NormalizeToPawnValue = 328;
|
||||||
|
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||||
|
|
||||||
// FEN string for the initial position in standard chess
|
UCI::UCI(int argc, char** argv) :
|
||||||
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
cli(argc, argv) {
|
||||||
|
|
||||||
|
evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}},
|
||||||
|
{Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}};
|
||||||
|
|
||||||
|
|
||||||
// Called when the engine receives the "position" UCI command.
|
options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); });
|
||||||
// It sets up the position that is described in the given FEN string ("fen") or
|
|
||||||
// the initial position ("startpos") and then makes the moves given in the following
|
|
||||||
// move list ("moves").
|
|
||||||
void position(Position& pos, std::istringstream& is, StateListPtr& states) {
|
|
||||||
|
|
||||||
Move m;
|
options["Threads"] << Option(1, 1, 1024, [this](const Option&) {
|
||||||
std::string token, fen;
|
threads.set({options, threads, tt});
|
||||||
|
});
|
||||||
|
|
||||||
is >> token;
|
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) {
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
tt.resize(o, options["Threads"]);
|
||||||
|
});
|
||||||
|
|
||||||
if (token == "startpos")
|
options["Clear Hash"] << Option(true, [this](const Option&) { search_clear(); });
|
||||||
{
|
options["Ponder"] << Option(false);
|
||||||
fen = StartFEN;
|
options["MultiPV"] << Option(1, 1, 500);
|
||||||
is >> token; // Consume the "moves" token, if any
|
options["Skill Level"] << Option(20, 0, 20);
|
||||||
}
|
options["Move Overhead"] << Option(10, 0, 5000);
|
||||||
else if (token == "fen")
|
options["nodestime"] << Option(0, 0, 10000);
|
||||||
while (is >> token && token != "moves")
|
options["UCI_Chess960"] << Option(false);
|
||||||
fen += token + " ";
|
options["UCI_LimitStrength"] << Option(false);
|
||||||
else
|
options["UCI_Elo"] << Option(1320, 1320, 3190);
|
||||||
return;
|
options["UCI_ShowWDL"] << Option(false);
|
||||||
|
options["SyzygyPath"] << Option("<empty>", [](const Option& o) { Tablebases::init(o); });
|
||||||
|
options["SyzygyProbeDepth"] << Option(1, 1, 100);
|
||||||
|
options["Syzygy50MoveRule"] << Option(true);
|
||||||
|
options["SyzygyProbeLimit"] << Option(7, 0, 7);
|
||||||
|
options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) {
|
||||||
|
evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles);
|
||||||
|
});
|
||||||
|
|
||||||
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
|
threads.set({options, threads, tt});
|
||||||
pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
|
|
||||||
|
|
||||||
// Parse the move list, if any
|
search_clear(); // After threads are up
|
||||||
while (is >> token && (m = UCI::to_move(pos, token)) != Move::none())
|
|
||||||
{
|
|
||||||
states->emplace_back();
|
|
||||||
pos.do_move(m, states->back());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the evaluation of the current position,
|
void UCI::loop() {
|
||||||
// consistent with the UCI options set so far.
|
|
||||||
void trace_eval(Position& pos) {
|
|
||||||
|
|
||||||
|
Position pos;
|
||||||
|
std::string token, cmd;
|
||||||
StateListPtr states(new std::deque<StateInfo>(1));
|
StateListPtr states(new std::deque<StateInfo>(1));
|
||||||
Position p;
|
|
||||||
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
|
|
||||||
|
|
||||||
Eval::NNUE::verify();
|
pos.set(StartFEN, false, &states->back());
|
||||||
|
|
||||||
sync_cout << "\n" << Eval::trace(p) << sync_endl;
|
for (int i = 1; i < cli.argc; ++i)
|
||||||
|
cmd += std::string(cli.argv[i]) + " ";
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (cli.argc == 1
|
||||||
|
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
|
||||||
|
cmd = "quit";
|
||||||
|
|
||||||
|
std::istringstream is(cmd);
|
||||||
|
|
||||||
|
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
|
||||||
|
is >> std::skipws >> token;
|
||||||
|
|
||||||
|
if (token == "quit" || token == "stop")
|
||||||
|
threads.stop = true;
|
||||||
|
|
||||||
|
// The GUI sends 'ponderhit' to tell that the user has played the expected move.
|
||||||
|
// So, 'ponderhit' is sent if pondering was done on the same move that the user
|
||||||
|
// has played. The search should continue, but should also switch from pondering
|
||||||
|
// to the normal search.
|
||||||
|
else if (token == "ponderhit")
|
||||||
|
threads.main_manager()->ponder = false; // Switch to the normal search
|
||||||
|
|
||||||
|
else if (token == "uci")
|
||||||
|
sync_cout << "id name " << engine_info(true) << "\n"
|
||||||
|
<< options << "\nuciok" << sync_endl;
|
||||||
|
|
||||||
|
else if (token == "setoption")
|
||||||
|
setoption(is);
|
||||||
|
else if (token == "go")
|
||||||
|
go(pos, is, states);
|
||||||
|
else if (token == "position")
|
||||||
|
position(pos, is, states);
|
||||||
|
else if (token == "ucinewgame")
|
||||||
|
search_clear();
|
||||||
|
else if (token == "isready")
|
||||||
|
sync_cout << "readyok" << sync_endl;
|
||||||
|
|
||||||
|
// Add custom non-UCI commands, mainly for debugging purposes.
|
||||||
|
// These commands must not be used during a search!
|
||||||
|
else if (token == "flip")
|
||||||
|
pos.flip();
|
||||||
|
else if (token == "bench")
|
||||||
|
bench(pos, is, states);
|
||||||
|
else if (token == "d")
|
||||||
|
sync_cout << pos << sync_endl;
|
||||||
|
else if (token == "eval")
|
||||||
|
trace_eval(pos);
|
||||||
|
else if (token == "compiler")
|
||||||
|
sync_cout << compiler_info() << sync_endl;
|
||||||
|
else if (token == "export_net")
|
||||||
|
{
|
||||||
|
std::optional<std::string> filename;
|
||||||
|
std::string f;
|
||||||
|
if (is >> std::skipws >> f)
|
||||||
|
filename = f;
|
||||||
|
Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles);
|
||||||
|
}
|
||||||
|
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
|
||||||
|
sync_cout
|
||||||
|
<< "\nStockfish is a powerful chess engine for playing and analyzing."
|
||||||
|
"\nIt is released as free software licensed under the GNU GPLv3 License."
|
||||||
|
"\nStockfish is normally used with a graphical user interface (GUI) and implements"
|
||||||
|
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
|
||||||
|
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
|
||||||
|
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
|
||||||
|
<< sync_endl;
|
||||||
|
else if (!token.empty() && token[0] != '#')
|
||||||
|
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
|
||||||
|
<< sync_endl;
|
||||||
|
|
||||||
|
} while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||||
|
|
||||||
// Called when the engine receives the "setoption" UCI command.
|
|
||||||
// The function updates the UCI option ("name") to the given value ("value").
|
|
||||||
|
|
||||||
void setoption(std::istringstream& is) {
|
|
||||||
|
|
||||||
Threads.main()->wait_for_search_finished();
|
|
||||||
|
|
||||||
std::string token, name, value;
|
|
||||||
|
|
||||||
is >> token; // Consume the "name" token
|
|
||||||
|
|
||||||
// Read the option name (can contain spaces)
|
|
||||||
while (is >> token && token != "value")
|
|
||||||
name += (name.empty() ? "" : " ") + token;
|
|
||||||
|
|
||||||
// Read the option value (can contain spaces)
|
|
||||||
while (is >> token)
|
|
||||||
value += (value.empty() ? "" : " ") + token;
|
|
||||||
|
|
||||||
if (Options.count(name))
|
|
||||||
Options[name] = value;
|
|
||||||
else
|
|
||||||
sync_cout << "No such option: " << name << sync_endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Called when the engine receives the "go" UCI command. The function sets the
|
|
||||||
// thinking time and other parameters from the input string then stars with a search
|
|
||||||
|
|
||||||
void go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
|
||||||
|
|
||||||
Search::LimitsType limits;
|
Search::LimitsType limits;
|
||||||
std::string token;
|
std::string token;
|
||||||
|
@ -137,7 +182,7 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||||
while (is >> token)
|
while (is >> token)
|
||||||
if (token == "searchmoves") // Needs to be the last command on the line
|
if (token == "searchmoves") // Needs to be the last command on the line
|
||||||
while (is >> token)
|
while (is >> token)
|
||||||
limits.searchmoves.push_back(UCI::to_move(pos, token));
|
limits.searchmoves.push_back(to_move(pos, token));
|
||||||
|
|
||||||
else if (token == "wtime")
|
else if (token == "wtime")
|
||||||
is >> limits.time[WHITE];
|
is >> limits.time[WHITE];
|
||||||
|
@ -164,16 +209,12 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||||
else if (token == "ponder")
|
else if (token == "ponder")
|
||||||
ponderMode = true;
|
ponderMode = true;
|
||||||
|
|
||||||
Threads.start_thinking(pos, states, limits, ponderMode);
|
Eval::NNUE::verify(options, evalFiles);
|
||||||
|
|
||||||
|
threads.start_thinking(options, pos, states, limits, ponderMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||||
// Called when the engine receives the "bench" command.
|
|
||||||
// First, a list of UCI commands is set up according to the bench
|
|
||||||
// parameters, then it is run one by one, printing a summary at the end.
|
|
||||||
|
|
||||||
void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
|
||||||
|
|
||||||
std::string token;
|
std::string token;
|
||||||
uint64_t num, nodes = 0, cnt = 1;
|
uint64_t num, nodes = 0, cnt = 1;
|
||||||
|
|
||||||
|
@ -196,8 +237,8 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||||
if (token == "go")
|
if (token == "go")
|
||||||
{
|
{
|
||||||
go(pos, is, states);
|
go(pos, is, states);
|
||||||
Threads.main()->wait_for_search_finished();
|
threads.main_thread()->wait_for_search_finished();
|
||||||
nodes += Threads.nodes_searched();
|
nodes += threads.nodes_searched();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
trace_eval(pos);
|
trace_eval(pos);
|
||||||
|
@ -208,9 +249,9 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||||
position(pos, is, states);
|
position(pos, is, states);
|
||||||
else if (token == "ucinewgame")
|
else if (token == "ucinewgame")
|
||||||
{
|
{
|
||||||
Search::clear();
|
search_clear(); // Search::clear() may take a while
|
||||||
elapsed = now();
|
elapsed = now();
|
||||||
} // Search::clear() may take a while
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
|
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
|
||||||
|
@ -222,6 +263,160 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||||
<< "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
|
<< "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UCI::trace_eval(Position& pos) {
|
||||||
|
StateListPtr states(new std::deque<StateInfo>(1));
|
||||||
|
Position p;
|
||||||
|
p.set(pos.fen(), options["UCI_Chess960"], &states->back());
|
||||||
|
|
||||||
|
Eval::NNUE::verify(options, evalFiles);
|
||||||
|
|
||||||
|
sync_cout << "\n" << Eval::trace(p, *threads.main_thread()->worker.get()) << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::search_clear() {
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
|
tt.clear(options["Threads"]);
|
||||||
|
threads.clear();
|
||||||
|
Tablebases::init(options["SyzygyPath"]); // Free mapped files
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::setoption(std::istringstream& is) {
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
options.setoption(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||||
|
Move m;
|
||||||
|
std::string token, fen;
|
||||||
|
|
||||||
|
is >> token;
|
||||||
|
|
||||||
|
if (token == "startpos")
|
||||||
|
{
|
||||||
|
fen = StartFEN;
|
||||||
|
is >> token; // Consume the "moves" token, if any
|
||||||
|
}
|
||||||
|
else if (token == "fen")
|
||||||
|
while (is >> token && token != "moves")
|
||||||
|
fen += token + " ";
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
|
||||||
|
pos.set(fen, options["UCI_Chess960"], &states->back());
|
||||||
|
|
||||||
|
// Parse the move list, if any
|
||||||
|
while (is >> token && (m = to_move(pos, token)) != Move::none())
|
||||||
|
{
|
||||||
|
states->emplace_back();
|
||||||
|
pos.do_move(m, states->back());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; }
|
||||||
|
|
||||||
|
std::string UCI::value(Value v) {
|
||||||
|
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||||
|
ss << "cp " << to_cp(v);
|
||||||
|
else if (std::abs(v) <= VALUE_TB)
|
||||||
|
{
|
||||||
|
const int ply = VALUE_TB - std::abs(v); // recompute ss->ply
|
||||||
|
ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UCI::square(Square s) {
|
||||||
|
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UCI::move(Move m, bool chess960) {
|
||||||
|
if (m == Move::none())
|
||||||
|
return "(none)";
|
||||||
|
|
||||||
|
if (m == Move::null())
|
||||||
|
return "0000";
|
||||||
|
|
||||||
|
Square from = m.from_sq();
|
||||||
|
Square to = m.to_sq();
|
||||||
|
|
||||||
|
if (m.type_of() == CASTLING && !chess960)
|
||||||
|
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
|
||||||
|
|
||||||
|
std::string move = square(from) + square(to);
|
||||||
|
|
||||||
|
if (m.type_of() == PROMOTION)
|
||||||
|
move += " pnbrqk"[m.promotion_type()];
|
||||||
|
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UCI::pv(const Search::Worker& workerThread,
|
||||||
|
TimePoint elapsed,
|
||||||
|
uint64_t nodesSearched,
|
||||||
|
uint64_t tb_hits,
|
||||||
|
int hashfull,
|
||||||
|
bool rootInTB) {
|
||||||
|
std::stringstream ss;
|
||||||
|
TimePoint time = elapsed + 1;
|
||||||
|
const auto& rootMoves = workerThread.rootMoves;
|
||||||
|
const auto& depth = workerThread.completedDepth;
|
||||||
|
const auto& pos = workerThread.rootPos;
|
||||||
|
size_t pvIdx = workerThread.pvIdx;
|
||||||
|
size_t multiPV = std::min(size_t(workerThread.options["MultiPV"]), rootMoves.size());
|
||||||
|
uint64_t tbHits = tb_hits + (rootInTB ? rootMoves.size() : 0);
|
||||||
|
|
||||||
|
|
||||||
|
for (size_t i = 0; i < multiPV; ++i)
|
||||||
|
{
|
||||||
|
bool updated = rootMoves[i].score != -VALUE_INFINITE;
|
||||||
|
|
||||||
|
if (depth == 1 && !updated && i > 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Depth d = updated ? depth : std::max(1, depth - 1);
|
||||||
|
Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore;
|
||||||
|
|
||||||
|
if (v == -VALUE_INFINITE)
|
||||||
|
v = VALUE_ZERO;
|
||||||
|
|
||||||
|
bool tb = rootInTB && std::abs(v) <= VALUE_TB;
|
||||||
|
v = tb ? rootMoves[i].tbScore : v;
|
||||||
|
|
||||||
|
if (ss.rdbuf()->in_avail()) // Not at first line
|
||||||
|
ss << "\n";
|
||||||
|
|
||||||
|
ss << "info"
|
||||||
|
<< " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1
|
||||||
|
<< " score " << value(v);
|
||||||
|
|
||||||
|
if (workerThread.options["UCI_ShowWDL"])
|
||||||
|
ss << wdl(v, pos.game_ply());
|
||||||
|
|
||||||
|
if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact
|
||||||
|
ss << (rootMoves[i].scoreLowerbound
|
||||||
|
? " lowerbound"
|
||||||
|
: (rootMoves[i].scoreUpperbound ? " upperbound" : ""));
|
||||||
|
|
||||||
|
ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / time << " hashfull "
|
||||||
|
<< hashfull << " tbhits " << tbHits << " time " << time << " pv";
|
||||||
|
|
||||||
|
for (Move m : rootMoves[i].pv)
|
||||||
|
ss << " " << move(m, pos.is_chess960());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
// The win rate model returns the probability of winning (in per mille units) given an
|
// The win rate model returns the probability of winning (in per mille units) given an
|
||||||
// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
|
// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
|
||||||
int win_rate_model(Value v, int ply) {
|
int win_rate_model(Value v, int ply) {
|
||||||
|
@ -236,7 +431,7 @@ int win_rate_model(Value v, int ply) {
|
||||||
constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330};
|
constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330};
|
||||||
|
|
||||||
// Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
|
// Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
|
||||||
static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
|
static_assert(NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
|
||||||
|
|
||||||
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
||||||
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||||
|
@ -247,132 +442,9 @@ int win_rate_model(Value v, int ply) {
|
||||||
// Return the win rate in per mille units, rounded to the nearest integer
|
// Return the win rate in per mille units, rounded to the nearest integer
|
||||||
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
|
||||||
// Waits for a command from the stdin, parses it, and then calls the appropriate
|
|
||||||
// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a
|
|
||||||
// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments,
|
|
||||||
// like running 'bench', the function returns immediately after the command is executed.
|
|
||||||
// In addition to the UCI ones, some additional debug commands are also supported.
|
|
||||||
void UCI::loop(int argc, char* argv[]) {
|
|
||||||
|
|
||||||
Position pos;
|
|
||||||
std::string token, cmd;
|
|
||||||
StateListPtr states(new std::deque<StateInfo>(1));
|
|
||||||
|
|
||||||
pos.set(StartFEN, false, &states->back(), Threads.main());
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
cmd += std::string(argv[i]) + " ";
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (argc == 1
|
|
||||||
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
|
|
||||||
cmd = "quit";
|
|
||||||
|
|
||||||
std::istringstream is(cmd);
|
|
||||||
|
|
||||||
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
|
|
||||||
is >> std::skipws >> token;
|
|
||||||
|
|
||||||
if (token == "quit" || token == "stop")
|
|
||||||
Threads.stop = true;
|
|
||||||
|
|
||||||
// The GUI sends 'ponderhit' to tell that the user has played the expected move.
|
|
||||||
// So, 'ponderhit' is sent if pondering was done on the same move that the user
|
|
||||||
// has played. The search should continue, but should also switch from pondering
|
|
||||||
// to the normal search.
|
|
||||||
else if (token == "ponderhit")
|
|
||||||
Threads.main()->ponder = false; // Switch to the normal search
|
|
||||||
|
|
||||||
else if (token == "uci")
|
|
||||||
sync_cout << "id name " << engine_info(true) << "\n"
|
|
||||||
<< Options << "\nuciok" << sync_endl;
|
|
||||||
|
|
||||||
else if (token == "setoption")
|
|
||||||
setoption(is);
|
|
||||||
else if (token == "go")
|
|
||||||
go(pos, is, states);
|
|
||||||
else if (token == "position")
|
|
||||||
position(pos, is, states);
|
|
||||||
else if (token == "ucinewgame")
|
|
||||||
Search::clear();
|
|
||||||
else if (token == "isready")
|
|
||||||
sync_cout << "readyok" << sync_endl;
|
|
||||||
|
|
||||||
// Add custom non-UCI commands, mainly for debugging purposes.
|
|
||||||
// These commands must not be used during a search!
|
|
||||||
else if (token == "flip")
|
|
||||||
pos.flip();
|
|
||||||
else if (token == "bench")
|
|
||||||
bench(pos, is, states);
|
|
||||||
else if (token == "d")
|
|
||||||
sync_cout << pos << sync_endl;
|
|
||||||
else if (token == "eval")
|
|
||||||
trace_eval(pos);
|
|
||||||
else if (token == "compiler")
|
|
||||||
sync_cout << compiler_info() << sync_endl;
|
|
||||||
else if (token == "export_net")
|
|
||||||
{
|
|
||||||
std::optional<std::string> filename;
|
|
||||||
std::string f;
|
|
||||||
if (is >> std::skipws >> f)
|
|
||||||
filename = f;
|
|
||||||
Eval::NNUE::save_eval(filename, Eval::NNUE::Big);
|
|
||||||
}
|
|
||||||
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
|
|
||||||
sync_cout
|
|
||||||
<< "\nStockfish is a powerful chess engine for playing and analyzing."
|
|
||||||
"\nIt is released as free software licensed under the GNU GPLv3 License."
|
|
||||||
"\nStockfish is normally used with a graphical user interface (GUI) and implements"
|
|
||||||
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
|
|
||||||
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
|
|
||||||
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
|
|
||||||
<< sync_endl;
|
|
||||||
else if (!token.empty() && token[0] != '#')
|
|
||||||
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
|
|
||||||
<< sync_endl;
|
|
||||||
|
|
||||||
} while (token != "quit" && argc == 1); // The command-line arguments are one-shot
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Turns a Value to an integer centipawn number,
|
|
||||||
// without treatment of mate and similar special scores.
|
|
||||||
int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; }
|
|
||||||
|
|
||||||
// Converts a Value to a string by adhering to the UCI protocol specification:
|
|
||||||
//
|
|
||||||
// cp <x> The score from the engine's point of view in centipawns.
|
|
||||||
// mate <y> Mate in 'y' moves (not plies). If the engine is getting mated,
|
|
||||||
// uses negative values for 'y'.
|
|
||||||
std::string UCI::value(Value v) {
|
|
||||||
|
|
||||||
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
|
|
||||||
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
|
||||||
ss << "cp " << UCI::to_cp(v);
|
|
||||||
else if (std::abs(v) <= VALUE_TB)
|
|
||||||
{
|
|
||||||
const int ply = VALUE_TB - std::abs(v); // recompute ss->ply
|
|
||||||
ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
|
|
||||||
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Reports the win-draw-loss (WDL) statistics given an evaluation
|
|
||||||
// and a game ply based on the data gathered for fishtest LTC games.
|
|
||||||
std::string UCI::wdl(Value v, int ply) {
|
std::string UCI::wdl(Value v, int ply) {
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
||||||
int wdl_w = win_rate_model(v, ply);
|
int wdl_w = win_rate_model(v, ply);
|
||||||
|
@ -383,49 +455,12 @@ std::string UCI::wdl(Value v, int ply) {
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Converts a Square to a string in algebraic notation (g1, a7, etc.)
|
|
||||||
std::string UCI::square(Square s) {
|
|
||||||
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Converts a Move to a string in coordinate notation (g1f3, a7a8q).
|
|
||||||
// The only special case is castling where the e1g1 notation is printed in
|
|
||||||
// standard chess mode and in e1h1 notation it is printed in Chess960 mode.
|
|
||||||
// Internally, all castling moves are always encoded as 'king captures rook'.
|
|
||||||
std::string UCI::move(Move m, bool chess960) {
|
|
||||||
|
|
||||||
if (m == Move::none())
|
|
||||||
return "(none)";
|
|
||||||
|
|
||||||
if (m == Move::null())
|
|
||||||
return "0000";
|
|
||||||
|
|
||||||
Square from = m.from_sq();
|
|
||||||
Square to = m.to_sq();
|
|
||||||
|
|
||||||
if (m.type_of() == CASTLING && !chess960)
|
|
||||||
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
|
|
||||||
|
|
||||||
std::string move = UCI::square(from) + UCI::square(to);
|
|
||||||
|
|
||||||
if (m.type_of() == PROMOTION)
|
|
||||||
move += " pnbrqk"[m.promotion_type()];
|
|
||||||
|
|
||||||
return move;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Converts a string representing a move in coordinate notation
|
|
||||||
// (g1f3, a7a8q) to the corresponding legal Move, if any.
|
|
||||||
Move UCI::to_move(const Position& pos, std::string& str) {
|
Move UCI::to_move(const Position& pos, std::string& str) {
|
||||||
|
|
||||||
if (str.length() == 5)
|
if (str.length() == 5)
|
||||||
str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased
|
str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased
|
||||||
|
|
||||||
for (const auto& m : MoveList<LEGAL>(pos))
|
for (const auto& m : MoveList<LEGAL>(pos))
|
||||||
if (str == UCI::move(m, pos.is_chess960()))
|
if (str == move(m, pos.is_chess960()))
|
||||||
return m;
|
return m;
|
||||||
|
|
||||||
return Move::none();
|
return Move::none();
|
||||||
|
|
105
src/uci.h
105
src/uci.h
|
@ -19,77 +19,70 @@
|
||||||
#ifndef UCI_H_INCLUDED
|
#ifndef UCI_H_INCLUDED
|
||||||
#define UCI_H_INCLUDED
|
#define UCI_H_INCLUDED
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstdint>
|
||||||
#include <iosfwd>
|
#include <iostream>
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "types.h"
|
#include "evaluate.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "tt.h"
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
namespace Eval::NNUE {
|
||||||
|
enum NetSize : int;
|
||||||
|
}
|
||||||
|
|
||||||
namespace UCI {
|
namespace Search {
|
||||||
|
class Worker;
|
||||||
|
}
|
||||||
|
|
||||||
// Normalizes the internal value as reported by evaluate or search
|
class Move;
|
||||||
// to the UCI centipawn result used in output. This value is derived from
|
enum Square : int;
|
||||||
// the win_rate_model() such that Stockfish outputs an advantage of
|
using Value = int;
|
||||||
// "100 centipawns" for a position if the engine has a 50% probability to win
|
|
||||||
// from this position in self-play at fishtest LTC time control.
|
|
||||||
const int NormalizeToPawnValue = 328;
|
|
||||||
|
|
||||||
class Option;
|
|
||||||
|
|
||||||
// Define a custom comparator, because the UCI options should be case-insensitive
|
|
||||||
struct CaseInsensitiveLess {
|
|
||||||
bool operator()(const std::string&, const std::string&) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The options container is defined as a std::map
|
|
||||||
using OptionsMap = std::map<std::string, Option, CaseInsensitiveLess>;
|
|
||||||
|
|
||||||
// The Option class implements each option as specified by the UCI protocol
|
|
||||||
class Option {
|
|
||||||
|
|
||||||
using OnChange = void (*)(const Option&);
|
|
||||||
|
|
||||||
|
class UCI {
|
||||||
public:
|
public:
|
||||||
Option(OnChange = nullptr);
|
UCI(int argc, char** argv);
|
||||||
Option(bool v, OnChange = nullptr);
|
|
||||||
Option(const char* v, OnChange = nullptr);
|
|
||||||
Option(double v, int minv, int maxv, OnChange = nullptr);
|
|
||||||
Option(const char* v, const char* cur, OnChange = nullptr);
|
|
||||||
|
|
||||||
Option& operator=(const std::string&);
|
void loop();
|
||||||
void operator<<(const Option&);
|
|
||||||
operator int() const;
|
static int to_cp(Value v);
|
||||||
operator std::string() const;
|
static std::string value(Value v);
|
||||||
bool operator==(const char*) const;
|
static std::string square(Square s);
|
||||||
|
static std::string move(Move m, bool chess960);
|
||||||
|
static std::string pv(const Search::Worker& workerThread,
|
||||||
|
TimePoint elapsed,
|
||||||
|
uint64_t nodesSearched,
|
||||||
|
uint64_t tb_hits,
|
||||||
|
int hashfull,
|
||||||
|
bool rootInTB);
|
||||||
|
static std::string wdl(Value v, int ply);
|
||||||
|
static Move to_move(const Position& pos, std::string& str);
|
||||||
|
|
||||||
|
const std::string& workingDirectory() const { return cli.workingDirectory; }
|
||||||
|
|
||||||
|
OptionsMap options;
|
||||||
|
|
||||||
|
std::unordered_map<Eval::NNUE::NetSize, Eval::EvalFile> evalFiles;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
TranspositionTable tt;
|
||||||
|
ThreadPool threads;
|
||||||
|
CommandLine cli;
|
||||||
|
|
||||||
std::string defaultValue, currentValue, type;
|
void go(Position& pos, std::istringstream& is, StateListPtr& states);
|
||||||
int min, max;
|
void bench(Position& pos, std::istream& args, StateListPtr& states);
|
||||||
size_t idx;
|
void position(Position& pos, std::istringstream& is, StateListPtr& states);
|
||||||
OnChange on_change;
|
void trace_eval(Position& pos);
|
||||||
|
void search_clear();
|
||||||
|
void setoption(std::istringstream& is);
|
||||||
};
|
};
|
||||||
|
|
||||||
void init(OptionsMap&);
|
|
||||||
void loop(int argc, char* argv[]);
|
|
||||||
int to_cp(Value v);
|
|
||||||
std::string value(Value v);
|
|
||||||
std::string square(Square s);
|
|
||||||
std::string move(Move m, bool chess960);
|
|
||||||
std::string pv(const Position& pos, Depth depth);
|
|
||||||
std::string wdl(Value v, int ply);
|
|
||||||
Move to_move(const Position& pos, std::string& str);
|
|
||||||
|
|
||||||
} // namespace UCI
|
|
||||||
|
|
||||||
extern UCI::OptionsMap Options;
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef UCI_H_INCLUDED
|
#endif // #ifndef UCI_H_INCLUDED
|
||||||
|
|
|
@ -16,104 +16,53 @@
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstddef>
|
#include <iostream>
|
||||||
#include <iosfwd>
|
|
||||||
#include <istream>
|
|
||||||
#include <map>
|
|
||||||
#include <ostream>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <utility>
|
||||||
|
|
||||||
#include "evaluate.h"
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "search.h"
|
|
||||||
#include "syzygy/tbprobe.h"
|
|
||||||
#include "thread.h"
|
|
||||||
#include "tt.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "uci.h"
|
|
||||||
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
UCI::OptionsMap Options; // Global object
|
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const {
|
||||||
|
|
||||||
namespace UCI {
|
return std::lexicographical_compare(
|
||||||
|
s1.begin(), s1.end(), s2.begin(), s2.end(),
|
||||||
// 'On change' actions, triggered by an option's value change
|
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
|
||||||
static void on_clear_hash(const Option&) { Search::clear(); }
|
|
||||||
static void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
|
|
||||||
static void on_logger(const Option& o) { start_logger(o); }
|
|
||||||
static void on_threads(const Option& o) { Threads.set(size_t(o)); }
|
|
||||||
static void on_tb_path(const Option& o) { Tablebases::init(o); }
|
|
||||||
static void on_eval_file(const Option&) { Eval::NNUE::init(); }
|
|
||||||
|
|
||||||
// Our case insensitive less() function as required by UCI protocol
|
|
||||||
bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const {
|
|
||||||
|
|
||||||
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
|
|
||||||
[](char c1, char c2) { return tolower(c1) < tolower(c2); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OptionsMap::setoption(std::istringstream& is) {
|
||||||
|
std::string token, name, value;
|
||||||
|
|
||||||
// Initializes the UCI options to their hard-coded default values
|
is >> token; // Consume the "name" token
|
||||||
void init(OptionsMap& o) {
|
|
||||||
|
|
||||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
// Read the option name (can contain spaces)
|
||||||
|
while (is >> token && token != "value")
|
||||||
|
name += (name.empty() ? "" : " ") + token;
|
||||||
|
|
||||||
o["Debug Log File"] << Option("", on_logger);
|
// Read the option value (can contain spaces)
|
||||||
o["Threads"] << Option(1, 1, 1024, on_threads);
|
while (is >> token)
|
||||||
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
value += (value.empty() ? "" : " ") + token;
|
||||||
o["Clear Hash"] << Option(on_clear_hash);
|
|
||||||
o["Ponder"] << Option(false);
|
if (options_map.count(name))
|
||||||
o["MultiPV"] << Option(1, 1, MAX_MOVES);
|
options_map[name] = value;
|
||||||
o["Skill Level"] << Option(20, 0, 20);
|
else
|
||||||
o["Move Overhead"] << Option(10, 0, 5000);
|
sync_cout << "No such option: " << name << sync_endl;
|
||||||
o["nodestime"] << Option(0, 0, 10000);
|
|
||||||
o["UCI_Chess960"] << Option(false);
|
|
||||||
o["UCI_LimitStrength"] << Option(false);
|
|
||||||
o["UCI_Elo"] << Option(1320, 1320, 3190);
|
|
||||||
o["UCI_ShowWDL"] << Option(false);
|
|
||||||
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
|
||||||
o["SyzygyProbeDepth"] << Option(1, 1, 100);
|
|
||||||
o["Syzygy50MoveRule"] << Option(true);
|
|
||||||
o["SyzygyProbeLimit"] << Option(7, 0, 7);
|
|
||||||
o["EvalFile"] << Option(EvalFileDefaultNameBig, on_eval_file);
|
|
||||||
// Enable this after fishtest workers support EvalFileSmall
|
|
||||||
// o["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, on_eval_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Option OptionsMap::operator[](const std::string& name) const {
|
||||||
// Used to print all the options default values in chronological
|
auto it = options_map.find(name);
|
||||||
// insertion order (the idx field) and in the format defined by the UCI protocol.
|
return it != options_map.end() ? it->second : Option();
|
||||||
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < om.size(); ++idx)
|
|
||||||
for (const auto& it : om)
|
|
||||||
if (it.second.idx == idx)
|
|
||||||
{
|
|
||||||
const Option& o = it.second;
|
|
||||||
os << "\noption name " << it.first << " type " << o.type;
|
|
||||||
|
|
||||||
if (o.type == "string" || o.type == "check" || o.type == "combo")
|
|
||||||
os << " default " << o.defaultValue;
|
|
||||||
|
|
||||||
if (o.type == "spin")
|
|
||||||
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
|
|
||||||
<< o.max;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return os;
|
Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; }
|
||||||
}
|
|
||||||
|
|
||||||
|
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
|
||||||
// Option class constructors and conversion operators
|
|
||||||
|
|
||||||
Option::Option(const char* v, OnChange f) :
|
Option::Option(const char* v, OnChange f) :
|
||||||
type("string"),
|
type("string"),
|
||||||
|
@ -184,19 +133,19 @@ void Option::operator<<(const Option& o) {
|
||||||
// Updates currentValue and triggers on_change() action. It's up to
|
// Updates currentValue and triggers on_change() action. It's up to
|
||||||
// the GUI to check for option's limits, but we could receive the new value
|
// the GUI to check for option's limits, but we could receive the new value
|
||||||
// from the user by console window, so let's check the bounds anyway.
|
// from the user by console window, so let's check the bounds anyway.
|
||||||
Option& Option::operator=(const string& v) {
|
Option& Option::operator=(const std::string& v) {
|
||||||
|
|
||||||
assert(!type.empty());
|
assert(!type.empty());
|
||||||
|
|
||||||
if ((type != "button" && type != "string" && v.empty())
|
if ((type != "button" && type != "string" && v.empty())
|
||||||
|| (type == "check" && v != "true" && v != "false")
|
|| (type == "check" && v != "true" && v != "false")
|
||||||
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
|
|| (type == "spin" && (std::stof(v) < min || std::stof(v) > max)))
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
if (type == "combo")
|
if (type == "combo")
|
||||||
{
|
{
|
||||||
OptionsMap comboMap; // To have case insensitive compare
|
OptionsMap comboMap; // To have case insensitive compare
|
||||||
string token;
|
std::string token;
|
||||||
std::istringstream ss(defaultValue);
|
std::istringstream ss(defaultValue);
|
||||||
while (ss >> token)
|
while (ss >> token)
|
||||||
comboMap[token] << Option();
|
comboMap[token] << Option();
|
||||||
|
@ -213,6 +162,24 @@ Option& Option::operator=(const string& v) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace UCI
|
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
|
||||||
|
for (size_t idx = 0; idx < om.options_map.size(); ++idx)
|
||||||
|
for (const auto& it : om.options_map)
|
||||||
|
if (it.second.idx == idx)
|
||||||
|
{
|
||||||
|
const Option& o = it.second;
|
||||||
|
os << "\noption name " << it.first << " type " << o.type;
|
||||||
|
|
||||||
} // namespace Stockfish
|
if (o.type == "string" || o.type == "check" || o.type == "combo")
|
||||||
|
os << " default " << o.defaultValue;
|
||||||
|
|
||||||
|
if (o.type == "spin")
|
||||||
|
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
|
||||||
|
<< o.max;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
81
src/ucioption.h
Normal file
81
src/ucioption.h
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
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
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef UCIOPTION_H_INCLUDED
|
||||||
|
#define UCIOPTION_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
// Define a custom comparator, because the UCI options should be case-insensitive
|
||||||
|
struct CaseInsensitiveLess {
|
||||||
|
bool operator()(const std::string&, const std::string&) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Option;
|
||||||
|
|
||||||
|
class OptionsMap {
|
||||||
|
public:
|
||||||
|
void setoption(std::istringstream&);
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
||||||
|
|
||||||
|
Option operator[](const std::string&) const;
|
||||||
|
Option& operator[](const std::string&);
|
||||||
|
|
||||||
|
std::size_t count(const std::string&) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The options container is defined as a std::map
|
||||||
|
using OptionsStore = std::map<std::string, Option, CaseInsensitiveLess>;
|
||||||
|
|
||||||
|
OptionsStore options_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Option class implements each option as specified by the UCI protocol
|
||||||
|
class Option {
|
||||||
|
public:
|
||||||
|
using OnChange = std::function<void(const Option&)>;
|
||||||
|
|
||||||
|
Option(OnChange = nullptr);
|
||||||
|
Option(bool v, OnChange = nullptr);
|
||||||
|
Option(const char* v, OnChange = nullptr);
|
||||||
|
Option(double v, int minv, int maxv, OnChange = nullptr);
|
||||||
|
Option(const char* v, const char* cur, OnChange = nullptr);
|
||||||
|
|
||||||
|
Option& operator=(const std::string&);
|
||||||
|
void operator<<(const Option&);
|
||||||
|
operator int() const;
|
||||||
|
operator std::string() const;
|
||||||
|
bool operator==(const char*) const;
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string defaultValue, currentValue, type;
|
||||||
|
int min, max;
|
||||||
|
size_t idx;
|
||||||
|
OnChange on_change;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // #ifndef UCIOPTION_H_INCLUDED
|
Loading…
Add table
Reference in a new issue