1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-04-30 00:33:09 +00:00

Simplify time management and fix 'ponder on' bug

Simplify time management code by removing hard stops for unchanging first root moves.
Search is now stopped earlier at the end iteration if it did not have fail-lows at root.

This simplification also fixes pondering bug. Ponder flag was true by default
and cutechess-cli doesn't change it to false even though no pondering is possible.
Fix the issue by setting the default value of 'Ponder' flag to false.

10+0.1:
ELO: 3.51 +-3.0 (95%) LOS: 99.0%
Total: 20000 W: 3898 L: 3696 D: 12406

40+0.4:
ELO: 1.39 +-2.7 (95%) LOS: 84.7%
Total: 20000 W: 3104 L: 3024 D: 13872

60+0.06:
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 37231 W: 5333 L: 5236 D: 26662

Stopped run at 100+1:
LLR: 1.09 (-2.94,2.94) [-3.00,1.00]
Total: 37253 W: 4862 L: 4856 D: 27535

Resolves #523
Fixes #510
This commit is contained in:
Leonid Pechenik 2015-12-09 02:07:34 -05:00 committed by Joona Kiiski
parent 7904a7d930
commit 69240a982d
6 changed files with 25 additions and 41 deletions

View file

@ -127,6 +127,7 @@ namespace {
}; };
EasyMoveManager EasyMove; EasyMoveManager EasyMove;
bool easyPlayed, failedLow;
double BestMoveChanges; double BestMoveChanges;
Value DrawValue[COLOR_NB]; Value DrawValue[COLOR_NB];
CounterMovesHistoryStats CounterMovesHistory; CounterMovesHistoryStats CounterMovesHistory;
@ -368,6 +369,7 @@ void Thread::search() {
{ {
easyMove = EasyMove.get(rootPos.key()); easyMove = EasyMove.get(rootPos.key());
EasyMove.clear(); EasyMove.clear();
easyPlayed = false;
BestMoveChanges = 0; BestMoveChanges = 0;
TT.new_search(); TT.new_search();
} }
@ -391,7 +393,7 @@ void Thread::search() {
// Age out PV variability metric // Age out PV variability metric
if (isMainThread) if (isMainThread)
BestMoveChanges *= 0.5; BestMoveChanges *= 0.505, failedLow = false;
// Save the last iteration's scores before first PV line is searched and // Save the last iteration's scores before first PV line is searched and
// all the move scores except the (new) PV are set to -VALUE_INFINITE. // all the move scores except the (new) PV are set to -VALUE_INFINITE.
@ -452,7 +454,7 @@ void Thread::search() {
if (isMainThread) if (isMainThread)
{ {
Signals.failedLowAtRoot = true; failedLow = true;
Signals.stopOnPonderhit = false; Signals.stopOnPonderhit = false;
} }
} }
@ -512,10 +514,10 @@ void Thread::search() {
// of the available time has been used or we matched an easyMove // of the available time has been used or we matched an easyMove
// from the previous search and just did a fast verification. // from the previous search and just did a fast verification.
if ( rootMoves.size() == 1 if ( rootMoves.size() == 1
|| Time.elapsed() > Time.available() || Time.elapsed() > Time.available() * (failedLow? 641 : 315)/640
|| ( rootMoves[0].pv[0] == easyMove || ( easyPlayed = ( rootMoves[0].pv[0] == easyMove
&& BestMoveChanges < 0.03 && BestMoveChanges < 0.03
&& Time.elapsed() > Time.available() / 10)) && Time.elapsed() > Time.available() / 8)))
{ {
// If we are allowed to ponder do not stop the search now but // If we are allowed to ponder do not stop the search now but
// keep pondering until the GUI sends "ponderhit" or "stop". // keep pondering until the GUI sends "ponderhit" or "stop".
@ -538,7 +540,7 @@ void Thread::search() {
// Clear any candidate easy move that wasn't stable for the last search // Clear any candidate easy move that wasn't stable for the last search
// iterations; the second condition prevents consecutive fast moves. // iterations; the second condition prevents consecutive fast moves.
if (EasyMove.stableCnt < 6 || Time.elapsed() < Time.available()) if (EasyMove.stableCnt < 6 || easyPlayed)
EasyMove.clear(); EasyMove.clear();
// If skill level is enabled, swap best PV line with the sub-optimal one // If skill level is enabled, swap best PV line with the sub-optimal one
@ -859,15 +861,10 @@ moves_loop: // When in check search starts from here
ss->moveCount = ++moveCount; ss->moveCount = ++moveCount;
if (RootNode && thisThread == Threads.main()) if (RootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
{ sync_cout << "info depth " << depth / ONE_PLY
Signals.firstRootMove = (moveCount == 1); << " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
if (Time.elapsed() > 3000)
sync_cout << "info depth " << depth / ONE_PLY
<< " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl;
}
if (PvNode) if (PvNode)
(ss+1)->pv = nullptr; (ss+1)->pv = nullptr;
@ -1486,19 +1483,9 @@ moves_loop: // When in check search starts from here
if (Limits.ponder) if (Limits.ponder)
return; return;
if (Limits.use_time_management()) if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10)
{ || (Limits.movetime && elapsed >= Limits.movetime)
bool stillAtFirstMove = Signals.firstRootMove.load(std::memory_order_relaxed) || (Limits.nodes && Threads.nodes_searched() >= Limits.nodes))
&& !Signals.failedLowAtRoot.load(std::memory_order_relaxed)
&& elapsed > Time.available() * 3 / 4;
if (stillAtFirstMove || elapsed > Time.maximum() - 10)
Signals.stop = true;
}
else if (Limits.movetime && elapsed >= Limits.movetime)
Signals.stop = true;
else if (Limits.nodes && Threads.nodes_searched() >= Limits.nodes)
Signals.stop = true; Signals.stop = true;
} }

View file

@ -93,7 +93,7 @@ struct LimitsType {
/// typically in an async fashion e.g. to stop the search by the GUI. /// typically in an async fashion e.g. to stop the search by the GUI.
struct SignalsType { struct SignalsType {
std::atomic_bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot; std::atomic_bool stop, stopOnPonderhit;
}; };
typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr; typedef std::unique_ptr<std::stack<StateInfo>> StateStackPtr;

View file

@ -174,8 +174,7 @@ void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits,
main()->wait_for_search_finished(); main()->wait_for_search_finished();
Signals.stopOnPonderhit = Signals.firstRootMove = false; Signals.stopOnPonderhit = Signals.stop = false;
Signals.stop = Signals.failedLowAtRoot = false;
main()->rootMoves.clear(); main()->rootMoves.clear();
main()->rootPos = pos; main()->rootPos = pos;

View file

@ -32,8 +32,8 @@ namespace {
enum TimeType { OptimumTime, MaxTime }; enum TimeType { OptimumTime, MaxTime };
const int MoveHorizon = 50; // Plan time management at most this many moves ahead const int MoveHorizon = 50; // Plan time management at most this many moves ahead
const double MaxRatio = 7.0; // When in trouble, we can step over reserved time with this ratio const double MaxRatio = 6.93; // When in trouble, we can step over reserved time with this ratio
const double StealRatio = 0.33; // However we must not steal time from remaining moves over this ratio const double StealRatio = 0.36; // However we must not steal time from remaining moves over this ratio
// move_importance() is a skew-logistic function based on naive statistical // move_importance() is a skew-logistic function based on naive statistical
@ -43,9 +43,9 @@ namespace {
double move_importance(int ply) { double move_importance(int ply) {
const double XScale = 9.3; const double XScale = 8.27;
const double XShift = 59.8; const double XShift = 59.;
const double Skew = 0.172; const double Skew = 0.179;
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
} }
@ -129,6 +129,4 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
if (Options["Ponder"]) if (Options["Ponder"])
optimumTime += optimumTime / 4; optimumTime += optimumTime / 4;
optimumTime = std::min(optimumTime, maximumTime);
} }

View file

@ -31,7 +31,7 @@ class TimeManagement {
public: public:
void init(Search::LimitsType& limits, Color us, int ply); void init(Search::LimitsType& limits, Color us, int ply);
void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; } void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; }
int available() const { return int(optimumTime * unstablePvFactor * 0.76); } int available() const { return int(optimumTime * unstablePvFactor * 1.016); }
int maximum() const { return maximumTime; } int maximum() const { return maximumTime; }
int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); } int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); }

View file

@ -61,12 +61,12 @@ void init(OptionsMap& o) {
o["Threads"] << Option(1, 1, 128, on_threads); o["Threads"] << Option(1, 1, 128, on_threads);
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
o["Clear Hash"] << Option(on_clear_hash); o["Clear Hash"] << Option(on_clear_hash);
o["Ponder"] << Option(true); o["Ponder"] << Option(false);
o["MultiPV"] << Option(1, 1, 500); o["MultiPV"] << Option(1, 1, 500);
o["Skill Level"] << Option(20, 0, 20); o["Skill Level"] << Option(20, 0, 20);
o["Move Overhead"] << Option(30, 0, 5000); o["Move Overhead"] << Option(30, 0, 5000);
o["Minimum Thinking Time"] << Option(20, 0, 5000); o["Minimum Thinking Time"] << Option(20, 0, 5000);
o["Slow Mover"] << Option(80, 10, 1000); o["Slow Mover"] << Option(84, 10, 1000);
o["nodestime"] << Option(0, 0, 10000); o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false); o["UCI_Chess960"] << Option(false);
o["SyzygyPath"] << Option("<empty>", on_tb_path); o["SyzygyPath"] << Option("<empty>", on_tb_path);