/*1Stockfish, a UCI chess playing engine derived from Glaurung 2.12Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)34Stockfish is free software: you can redistribute it and/or modify5it under the terms of the GNU General Public License as published by6the Free Software Foundation, either version 3 of the License, or7(at your option) any later version.89Stockfish is distributed in the hope that it will be useful,10but WITHOUT ANY WARRANTY; without even the implied warranty of11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12GNU General Public License for more details.1314You should have received a copy of the GNU General Public License15along with this program. If not, see <http://www.gnu.org/licenses/>.16*/1718#include "timeman.h"1920#include <algorithm>21#include <cassert>22#include <cmath>23#include <cstdint>2425#include "search.h"26#include "ucioption.h"2728namespace Stockfish {2930TimePoint TimeManagement::optimum() const { return optimumTime; }31TimePoint TimeManagement::maximum() const { return maximumTime; }3233void TimeManagement::clear() {34availableNodes = -1; // When in 'nodes as time' mode35}3637void TimeManagement::advance_nodes_time(std::int64_t nodes) {38assert(useNodesTime);39availableNodes = std::max(int64_t(0), availableNodes - nodes);40}4142// Called at the beginning of the search and calculates43// the bounds of time allowed for the current game ply. We currently support:44// 1) x basetime (+ z increment)45// 2) x moves in y seconds (+ z increment)46void TimeManagement::init(Search::LimitsType& limits,47Color us,48int ply,49const OptionsMap& options,50double& originalTimeAdjust) {51TimePoint npmsec = TimePoint(options["nodestime"]);5253// If we have no time, we don't need to fully initialize TM.54// startTime is used by movetime and useNodesTime is used in elapsed calls.55startTime = limits.startTime;56useNodesTime = npmsec != 0;5758if (limits.time[us] == 0)59return;6061TimePoint moveOverhead = TimePoint(options["Move Overhead"]);6263// optScale is a percentage of available time to use for the current move.64// maxScale is a multiplier applied to optimumTime.65double optScale, maxScale;6667// If we have to play in 'nodes as time' mode, then convert from time68// to nodes, and use resulting values in time management formulas.69// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)70// must be much lower than the real engine speed.71if (useNodesTime)72{73if (availableNodes == -1) // Only once at game start74availableNodes = npmsec * limits.time[us]; // Time is in msec7576// Convert from milliseconds to nodes77limits.time[us] = TimePoint(availableNodes);78limits.inc[us] *= npmsec;79limits.npmsec = npmsec;80moveOverhead *= npmsec;81}8283// These numbers are used where multiplications, divisions or comparisons84// with constants are involved.85const int64_t scaleFactor = useNodesTime ? npmsec : 1;86const TimePoint scaledTime = limits.time[us] / scaleFactor;8788// Maximum move horizon89int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051;9091// If less than one second, gradually reduce mtg92if (scaledTime < 1000)93centiMTG = scaledTime * 5.051;9495// Make sure timeLeft is > 0 since we may use it as a divisor96TimePoint timeLeft =97std::max(TimePoint(1),98limits.time[us]99+ (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100);100101// x basetime (+ z increment)102// If there is a healthy increment, timeLeft can exceed the actual available103// game time for the current move, so also cap to a percentage of available game time.104if (limits.movestogo == 0)105{106// Extra time according to timeLeft107if (originalTimeAdjust < 0)108originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354;109110// Calculate time constants based on current time left.111double logTimeInSec = std::log10(scaledTime / 1000.0);112double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017);113double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761);114115optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant,1160.213035 * limits.time[us] / timeLeft)117* originalTimeAdjust;118119maxScale = std::min(6.67704, maxConstant + ply / 11.9847);120}121122// x moves in y seconds (+ z increment)123else124{125optScale =126std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft);127maxScale = 1.3 + 0.11 * (centiMTG / 100.0);128}129130// Limit the maximum possible time for this move131optimumTime = TimePoint(optScale * timeLeft);132maximumTime =133TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;134135if (options["Ponder"])136optimumTime += optimumTime / 4;137}138139} // namespace Stockfish140141142