mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-21 01:27:16 +08:00
Time management simplification
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
This commit is contained in:
@@ -32,41 +32,40 @@ namespace {
|
|||||||
|
|
||||||
enum TimeType { OptimumTime, MaxTime };
|
enum TimeType { OptimumTime, MaxTime };
|
||||||
|
|
||||||
const int MoveHorizon = 50; // Plan time management at most this many moves ahead
|
int remaining(int myTime, int myInc, int moveOverhead,
|
||||||
const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio
|
int movesToGo, int ply, TimeType type) {
|
||||||
const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio
|
|
||||||
|
|
||||||
|
if (myTime <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
// move_importance() is a skew-logistic function based on naive statistical
|
int moveNumber = (ply + 1) / 2;
|
||||||
// analysis of "how many games are still undecided after n half-moves". Game
|
double ratio; // Which ratio of myTime we are going to use. It is <= 1
|
||||||
// is considered "undecided" as long as neither side has >275cp advantage.
|
double sd = 8.5;
|
||||||
// Data was extracted from the CCRL game database with some simple filtering criteria.
|
|
||||||
|
|
||||||
double move_importance(int ply) {
|
// 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));
|
||||||
|
|
||||||
const double XScale = 7.64;
|
// In moves-to-go we distribute time according to a quadratic function with
|
||||||
const double XShift = 58.4;
|
// the maximum around move 20 for 40 moves in y time case.
|
||||||
const double Skew = 0.183;
|
if (movesToGo)
|
||||||
|
{
|
||||||
|
ratio = (type == OptimumTime ? 1.0 : 6.0) / std::min(50, movesToGo);
|
||||||
|
|
||||||
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<TimeType T>
|
ratio = std::min(1.0, ratio * (1 + inc / (myTime * sd)));
|
||||||
int remaining(int myTime, int movesToGo, int ply, int slowMover) {
|
|
||||||
|
|
||||||
const double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio);
|
return int(ratio * std::max(0, myTime - moveOverhead));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -81,11 +80,9 @@ namespace {
|
|||||||
/// inc > 0 && movestogo == 0 means: x basetime + z increment
|
/// inc > 0 && movestogo == 0 means: x basetime + z increment
|
||||||
/// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment
|
/// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment
|
||||||
|
|
||||||
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply)
|
||||||
|
{
|
||||||
int minThinkingTime = Options["Minimum Thinking Time"];
|
|
||||||
int moveOverhead = Options["Move Overhead"];
|
int moveOverhead = Options["Move Overhead"];
|
||||||
int slowMover = Options["Slow Mover"];
|
|
||||||
int npmsec = Options["nodestime"];
|
int npmsec = Options["nodestime"];
|
||||||
|
|
||||||
// If we have to play in 'nodes as time' mode, then convert from time
|
// 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;
|
startTime = limits.startTime;
|
||||||
optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
|
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);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Options["Ponder"])
|
if (Options["Ponder"])
|
||||||
optimumTime += optimumTime / 4;
|
optimumTime += optimumTime / 4;
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ void init(OptionsMap& o) {
|
|||||||
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["Slow Mover"] << Option(89, 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);
|
||||||
|
|||||||
Reference in New Issue
Block a user