diff --git a/src/evaluate.cpp b/src/evaluate.cpp index dcc7401be2c..5c49f6658f9 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -65,6 +65,8 @@ namespace Eval { string eval_file_loaded = "None"; int NNUE::RandomEvalPerturb = 0; + int NNUE::UCI_Elo; + bool NNUE::UCI_LimitStrength = false; /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -1114,7 +1116,9 @@ Value Eval::evaluate(const Position& pos) { int r50 = pos.rule50_count(); Value psq = Value(abs(eg_value(pos.psq_score()))); bool classical = psq * 5 > (850 + pos.non_pawn_material() / 64) * (5 + r50); - classical = false; + if (NNUE::UCI_LimitStrength) { + Stockfish::Search::Limits.nodes = 400000; + } v = classical ? Evaluation(pos).value() // classical : adjusted_NNUE(); // NNUE @@ -1122,12 +1126,14 @@ Value Eval::evaluate(const Position& pos) { // Damp down the evaluation linearly when shuffling v = v * (100 - pos.rule50_count()) / 100; + if (NNUE::UCI_LimitStrength) { + NNUE::RandomEvalPerturb = ((3200 - (NNUE::UCI_Elo)) / 28) + NNUE::UCI_Elo / 225 ; + std::normal_distribution d(0.0, RandomValue); + float r = d(tls_rng); + r = std::clamp(r, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - std::normal_distribution d(0.0, RookValueEg); - float r = d(tls_rng); - r = std::clamp(r, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - - v = (NNUE::RandomEvalPerturb * Value(r) + (100 - NNUE::RandomEvalPerturb) * v) / 100; + v = (NNUE::RandomEvalPerturb * Value(r) + (100 - NNUE::RandomEvalPerturb) * v) / 100; + } // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/evaluate.h b/src/evaluate.h index 1b087f1bce9..f2b238544a9 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -44,6 +44,8 @@ namespace Eval { namespace NNUE { extern int RandomEvalPerturb; + extern int UCI_Elo; + extern bool UCI_LimitStrength; std::string trace(Position& pos); Value evaluate(const Position& pos, bool adjusted = false); diff --git a/src/search.cpp b/src/search.cpp index c48b74bcc76..5c464897c7a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -91,17 +91,6 @@ namespace { return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); } - // Skill structure is used to implement strength limit - struct Skill { - explicit Skill(int l) : level(l) {} - bool enabled() const { return level < 20; } - bool time_to_pick(Depth depth) const { return depth == 1 + level; } - Move pick_best(size_t multiPV); - - int level; - Move best = MOVE_NONE; - }; - template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -196,6 +185,10 @@ void MainThread::search() { } else { + if(Options["Search_Nodes"]) + Limits.nodes = int(Options["Search_Nodes"]); + if ( Options["Search_Depth"]) + Limits.depth = int(Options["Search_Depth"]); Threads.start_searching(); // start non-main threads Thread::search(); // main thread start searching } @@ -225,7 +218,6 @@ void MainThread::search() { if ( int(Options["MultiPV"]) == 1 && !Limits.depth - && !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) && rootMoves[0].pv[0] != MOVE_NONE) bestThread = Threads.get_best_thread(); @@ -290,25 +282,6 @@ void Thread::search() { std::fill(&lowPlyHistory[MAX_LPH - 2][0], &lowPlyHistory.back().back() + 1, 0); size_t multiPV = size_t(Options["MultiPV"]); - - // Pick integer skill levels, but non-deterministically round up or down - // such that the average integer skill corresponds to the input floating point one. - // UCI_Elo is converted to a suitable fractional skill level, using anchoring - // to CCRL Elo (goldfish 1.13 = 2000) and a fit through Ordo derived Elo - // for match (TC 60+0.6) results spanning a wide range of k values. - PRNG rng(now()); - double floatLevel = Options["UCI_LimitStrength"] ? - std::clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) : - double(Options["Skill Level"]); - int intLevel = int(floatLevel) + - ((floatLevel - int(floatLevel)) * 1024 > rng.rand() % 1024 ? 1 : 0); - Skill skill(intLevel); - - // When playing with strength handicap enable MultiPV search that we will - // use behind the scenes to retrieve a set of possible moves. - if (skill.enabled()) - multiPV = std::max(multiPV, (size_t)4); - multiPV = std::min(multiPV, rootMoves.size()); ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2; @@ -445,10 +418,6 @@ void Thread::search() { if (!mainThread) continue; - // If skill level is enabled and time is up, pick a sub-optimal best move - if (skill.enabled() && skill.time_to_pick(rootDepth)) - skill.pick_best(multiPV); - // Do we have time for the next iteration? Can we stop searching now? if ( Limits.use_time_management() && !Threads.stop @@ -504,10 +473,6 @@ void Thread::search() { mainThread->previousTimeReduction = timeReduction; - // If skill level is enabled, swap best PV line with the sub-optimal one - if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), - skill.best ? skill.best : skill.pick_best(multiPV))); } @@ -1720,39 +1685,6 @@ namespace { thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7); } - // When playing with strength handicap, choose best move among a set of RootMoves - // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - - Move Skill::pick_best(size_t multiPV) { - - const RootMoves& rootMoves = Threads.main()->rootMoves; - static PRNG rng(now()); // PRNG sequence should be non-deterministic - - // RootMoves are already sorted by score in descending order - Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); - int weakness = 120 - 2 * level; - int maxScore = -VALUE_INFINITE; - - // Choose best move. For each move score we add two terms, both dependent on - // weakness. One is deterministic and bigger for weaker levels, and one is - // random. Then we choose the move with the resulting highest score. - for (size_t i = 0; i < multiPV; ++i) - { - // This is our magic formula - int push = ( weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % weakness)) / 128; - - if (rootMoves[i].score + push >= maxScore) - { - maxScore = rootMoves[i].score + push; - best = rootMoves[i].pv[0]; - } - } - - return best; - } - } // namespace diff --git a/src/types.h b/src/types.h index 0bd4a1c4090..3910adcc2ec 100644 --- a/src/types.h +++ b/src/types.h @@ -191,7 +191,7 @@ enum Value : int { BishopValueMg = 825, BishopValueEg = 915, RookValueMg = 1276, RookValueEg = 1380, QueenValueMg = 2538, QueenValueEg = 2682, - + RandomValue = 1200, MidgameLimit = 15258, EndgameLimit = 3915 }; diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 886e583af8e..bb1718e5526 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -43,6 +43,8 @@ void on_hash_size(const Option& o) { TT.resize(size_t(o)); } void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(size_t(o)); } void on_eval_perturb(const Option& o) { Eval::NNUE::RandomEvalPerturb = o; } +void on_eval_elo(const Option& o) { Eval::NNUE::UCI_Elo = o; } +void on_eval_str(const Option& o) { Eval::NNUE::UCI_LimitStrength = o; } void on_tb_path(const Option& o) { Tablebases::init(o); } void on_use_NNUE(const Option& ) { Eval::NNUE::init(); } void on_eval_file(const Option& ) { Eval::NNUE::init(); } @@ -68,19 +70,21 @@ void init(OptionsMap& o) { o["Clear Hash"] << Option(on_clear_hash); o["Ponder"] << Option(false); o["MultiPV"] << Option(1, 1, 500); - o["Skill Level"] << Option(20, 0, 20); o["Move Overhead"] << Option(10, 0, 5000); o["Slow Mover"] << Option(100, 10, 1000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1350, 1350, 2850); + o["UCI_Elo"] << Option(1500, 1000, 3000); o["UCI_ShowWDL"] << Option(false); - o["SyzygyPath"] << Option("", on_tb_path); + o["Search_Depth"] << Option(0, 0, 60); + o["Search_Nodes"] << Option(0, 0, 10000000); + o["SyzygyPath"] << Option("C:\\syzygy", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["Tactical"] << Option(0, 0, 8); o["Use NNUE"] << Option(true, on_use_NNUE); o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); }