Time management simplification
authorIIvec <ivan.ivec@gmail.com>
Fri, 7 Oct 2016 16:12:19 +0000 (18:12 +0200)
committerMarco Costalba <mcostalba@gmail.com>
Thu, 17 Aug 2017 21:42:22 +0000 (14:42 -0700)
STC (http://tests.stockfishchess.org/tests/view/598188a40ebc5916ff64a21b):
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 25363 W: 4658 L: 4545 D: 16160

LTC (http://tests.stockfishchess.org/tests/view/5981d59a0ebc5916ff64a229):
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 75356 W: 9690 L: 9640 D: 56026

40/10 TC (http://tests.stockfishchess.org/tests/view/5980c5780ebc5916ff64a1ed):
LLR: 2.96 (-2.94,2.94) [-3.00,1.00]
Total: 19377 W: 3650 L: 3526 D: 12201

15+0 TC (http://tests.stockfishchess.org/tests/view/5982cb730ebc5916ff64a25d):
LLR: 2.95 (-2.94,2.94) [-3.00,1.00]
Total: 5913 W: 1217 L: 1069 D: 3627

This time management handles base time and movestogo cases separatelly. One can test one case without affecting the other. Also, increment usage can be tested separately without (necessarily) affecting sudden death or x moves in y seconds performance.

On stable machines there are no time losses on 0.1+0.001 time control (tested on i7 + Windows 10 platform).

Bench 5608839

src/timeman.cpp
src/ucioption.cpp

index 4ed54cdb43e4e508435f51b94cd775c9eda16b53..55d98c1fa98b693a0b948d2d1175e8017d466e6c 100644 (file)
@@ -32,41 +32,40 @@ namespace {
 
   enum TimeType { OptimumTime, MaxTime };
 
-  const int MoveHorizon   = 50;   // Plan time management at most this many moves ahead
-  const double MaxRatio   = 7.09; // When in trouble, we can step over reserved time with this ratio
-  const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio
-
-
-  // move_importance() is a skew-logistic function based on naive statistical
-  // analysis of "how many games are still undecided after n half-moves". Game
-  // is considered "undecided" as long as neither side has >275cp advantage.
-  // Data was extracted from the CCRL game database with some simple filtering criteria.
-
-  double move_importance(int ply) {
-
-    const double XScale = 7.64;
-    const double XShift = 58.4;
-    const double Skew   = 0.183;
-
-    return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
-  }
-
-  template<TimeType T>
-  int remaining(int myTime, int movesToGo, int ply, int slowMover) {
-
-    const double TMaxRatio   = (T == OptimumTime ? 1 : MaxRatio);
-    const double TStealRatio = (T == OptimumTime ? 0 : StealRatio);
-
-    double moveImportance = (move_importance(ply) * slowMover) / 100;
-    double otherMovesImportance = 0;
-
-    for (int i = 1; i < movesToGo; ++i)
-        otherMovesImportance += move_importance(ply + 2 * i);
-
-    double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
-    double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);
-
-    return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
+  int remaining(int myTime, int myInc, int moveOverhead,
+                int movesToGo, int ply, TimeType type) {
+
+    if (myTime <= 0)
+        return 0;
+
+    int moveNumber = (ply + 1) / 2;
+    double ratio;    // Which ratio of myTime we are going to use. It is <= 1
+    double sd = 8.5;
+
+    // Usage of increment follows quadratic distribution with the maximum at move 25
+    double inc = myInc * std::max(55.0, 120.0 - 0.12 * (moveNumber - 25) * (moveNumber - 25));
+
+    // In moves-to-go we distribute time according to a quadratic function with
+    // the maximum around move 20 for 40 moves in y time case.
+    if (movesToGo)
+    {
+        ratio = (type == OptimumTime ? 1.0 : 6.0) / std::min(50, movesToGo);
+
+        if (moveNumber <= 40)
+            ratio *= 1.1 - 0.001 * (moveNumber - 20) * (moveNumber - 20);
+        else
+            ratio *= 1.5;
+    }
+    // Otherwise we increase usage of remaining time as the game goes on
+    else
+    {
+        sd = 1 + 20 * moveNumber / (500.0 + moveNumber);
+        ratio = (type == OptimumTime ? 0.017 : 0.07) * sd;
+    }
+
+    ratio = std::min(1.0, ratio * (1 + inc / (myTime * sd)));
+
+    return int(ratio * std::max(0, myTime - moveOverhead));
   }
 
 } // namespace
@@ -81,11 +80,9 @@ namespace {
 ///  inc >  0 && movestogo == 0 means: x basetime + z increment
 ///  inc >  0 && movestogo != 0 means: x moves in y minutes + z increment
 
-void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
-
-  int minThinkingTime = Options["Minimum Thinking Time"];
+void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
+{
   int moveOverhead    = Options["Move Overhead"];
-  int slowMover       = Options["Slow Mover"];
   int npmsec          = Options["nodestime"];
 
   // If we have to play in 'nodes as time' mode, then convert from time
@@ -104,28 +101,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
   }
 
   startTime = limits.startTime;
-  optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
-
-  const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;
-
-  // We calculate optimum time usage for different hypothetical "moves to go"-values
-  // and choose the minimum of calculated search time values. Usually the greatest
-  // hypMTG gives the minimum values.
-  for (int hypMTG = 1; hypMTG <= MaxMTG; ++hypMTG)
-  {
-      // Calculate thinking time for hypothetical "moves to go"-value
-      int hypMyTime =  limits.time[us]
-                     + limits.inc[us] * (hypMTG - 1)
-                     - moveOverhead * (2 + std::min(hypMTG, 40));
-
-      hypMyTime = std::max(hypMyTime, 0);
-
-      int t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
-      int t2 = minThinkingTime + remaining<MaxTime    >(hypMyTime, hypMTG, ply, slowMover);
-
-      optimumTime = std::min(t1, optimumTime);
-      maximumTime = std::min(t2, maximumTime);
-  }
+  optimumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, limits.movestogo, ply, OptimumTime);
+  maximumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, limits.movestogo, ply, MaxTime);
 
   if (Options["Ponder"])
       optimumTime += optimumTime / 4;
index 0e049046f9b407cc17b52d95724806de1e835977..8bc4e4364383b01ba986b56bf6c6719b49dfc0b0 100644 (file)
@@ -66,8 +66,6 @@ void init(OptionsMap& o) {
   o["MultiPV"]               << Option(1, 1, 500);
   o["Skill Level"]           << Option(20, 0, 20);
   o["Move Overhead"]         << Option(30, 0, 5000);
-  o["Minimum Thinking Time"] << Option(20, 0, 5000);
-  o["Slow Mover"]            << Option(89, 10, 1000);
   o["nodestime"]             << Option(0, 0, 10000);
   o["UCI_Chess960"]          << Option(false);
   o["SyzygyPath"]            << Option("<empty>", on_tb_path);