Skip to content

Commit

Permalink
From Issues official-stockfish#3635 submitted by @Sopel97. The idea i…
Browse files Browse the repository at this point in the history
…s her to connect the RandomEvalPerturb value and loosely connect it to a desired Elo based on his quick test results. Through testig , iwas ab;e to dertermine the rnadom function produce more reliable results using 400K nodes/move per move (as opposed to 100K nodes/move). Adjusted accordingly. This will be used in my Android Beth Harmon Chess app ( clone of Droidfish). The beauty of this method is that is uses move randomization so that a book is not needed, but still can be used,

Also, turned off pure mode as that did have some undesired weakness at weaker levels.

									Calculated
									RandomEvalPerturb  (REP) value
									based on Elo
{A)	{B)	{C)	(D)	(E)	(F)	(G)	(H)	(I)	(J)
Factor -1	UCI_Elo	(A)-(B)	Factor 2	(C)/(D)	Factor 3	(E)/(F)	Factor 4	(B)/(H)	(G)+(I)	Sopel Tests official-stockfish#3635
3200	3000	200	2.8	71	10	7	225	13	20	  23 stockfish_pure_20_100k     	20	3011
3200	2900	300	2.8	107	10	10	225	12	22
3200	2800	400	2.8	142	10	14	225	12	26	  25 stockfish_pure_25_100k     	25	2896
3200	2700	500	2.8	178	10	17	225	12	29	  26 stockfish_pure_30_100k     	30	2748
3200	2600	600	2.8	214	10	21	225	11	32
3200	2500	700	2.8	250	10	25	225	11	36	  28 stockfish_pure_35_100k     	35	2592
3200	2400	800	2.8	285	10	28	225	10	38
3200	2300	900	2.8	321	10	32	225	10	42	  29 stockfish_pure_40_100k     	40	2430
3200	2200	1000	2.8	357	10	35	225	9	44	  32 stockfish_pure_45_100k     	45	2295
3200	2100	1100	2.8	392	10	39	225	9	48
3200	2000	1200	2.8	428	10	42	225	8	50	  34 stockfish_pure_50_100k     	50	2100	 * Elo 2000 Achored to REP 50
3200	1900	1300	2.8	464	10	46	225	8	54	  36 stockfish_pure_55_100k     	55	1928
3200	1800	1400	2.8	500	10	50	225	8	58
3200	1700	1500	2.8	535	10	53	225	7	60	  38 stockfish_pure_60_100k     	60	1797
3200	1600	1600	2.8	571	10	57	225	7	64
3200	1500	1700	2.8	607	10	60	225	6	66	  39 stockfish_pure_65_100k     	65	1570
3200	1400	1800	2.8	642	10	64	225	6	70	  40 stockfish_pure_70_100k     	70	1325
3200	1300	1900	2.8	678	10	67	225	5	72
3200	1200	2000	2.8	714	10	71	225	5	76	  41 stockfish_pure_75_100k     	75	1184
3200	1100	2100	2.8	750	10	75	225	4	79
3200	1000	2200	2.8	785	10	78	225	4	82
  • Loading branch information
MichaelB7 committed Aug 17, 2021
1 parent de13aa9 commit 18480ca
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 82 deletions.
18 changes: 12 additions & 6 deletions src/evaluate.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -1114,20 +1116,24 @@ 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<NO_TRACE>(pos).value() // classical
: adjusted_NNUE(); // NNUE
}

// 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<float> d(0.0, RandomValue);
float r = d(tls_rng);
r = std::clamp<float>(r, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);

std::normal_distribution<float> d(0.0, RookValueEg);
float r = d(tls_rng);
r = std::clamp<float>(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);
Expand Down
2 changes: 2 additions & 0 deletions src/evaluate.h
Expand Up @@ -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);
Expand Down
76 changes: 4 additions & 72 deletions src/search.cpp
Expand Up @@ -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 <NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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<unsigned>() % 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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)));
}


Expand Down Expand Up @@ -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<unsigned>() % weakness)) / 128;

if (rootMoves[i].score + push >= maxScore)
{
maxScore = rootMoves[i].score + push;
best = rootMoves[i].pv[0];
}
}

return best;
}

} // namespace


Expand Down
2 changes: 1 addition & 1 deletion src/types.h
Expand Up @@ -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
};

Expand Down
10 changes: 7 additions & 3 deletions src/ucioption.cpp
Expand Up @@ -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(); }
Expand All @@ -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("<empty>", 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);
}
Expand Down

0 comments on commit 18480ca

Please sign in to comment.