Introduce and use SearchLimits
authorMarco Costalba <mcostalba@gmail.com>
Fri, 22 Apr 2011 13:52:03 +0000 (15:52 +0200)
committerMarco Costalba <mcostalba@gmail.com>
Sat, 23 Apr 2011 12:11:03 +0000 (13:11 +0100)
Pack a bit of global variables related to search limits in
a single struct.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
src/benchmark.cpp
src/search.cpp
src/search.h
src/timeman.cpp
src/timeman.h
src/uci.cpp

index bb43f4e6aa14db2c90fdde2f366ceb4d828b91a4..9cf04532d59f61584ce23bf5caeff5e90f731036 100644 (file)
@@ -73,7 +73,7 @@ void benchmark(int argc, char* argv[]) {
 
   vector<string> positions;
   string ttSize, threads, valStr, posFile, valType;
-  int val, secsPerPos, maxDepth, maxNodes;
+  int val, maxTime, maxDepth, maxNodes;
 
   ttSize  = argc > 2 ? argv[2] : "128";
   threads = argc > 3 ? argv[3] : "1";
@@ -87,13 +87,13 @@ void benchmark(int argc, char* argv[]) {
   Options["Use Search Log"].set_value("true");
   Options["Search Log Filename"].set_value("bench.txt");
 
-  secsPerPos = maxDepth = maxNodes = 0;
+  maxTime = maxDepth = maxNodes = 0;
   val = atoi(valStr.c_str());
 
   if (valType == "depth" || valType == "perft")
       maxDepth = val;
   else if (valType == "time")
-      secsPerPos = val * 1000;
+      maxTime = val * 1000;
   else
       maxNodes = val;
 
@@ -124,8 +124,7 @@ void benchmark(int argc, char* argv[]) {
 
   for (it = positions.begin(); it != positions.end(); ++it, ++cnt)
   {
-      Move moves[1] = { MOVE_NONE };
-      int dummy[2] = { 0, 0 };
+      Move moves[] = { MOVE_NONE };
       Position pos(*it, false, 0);
       cerr << "\nBench position: " << cnt << '/' << positions.size() << endl << endl;
       if (valType == "perft")
@@ -134,7 +133,7 @@ void benchmark(int argc, char* argv[]) {
           cerr << "\nPerft " << maxDepth << " result (nodes searched): " << perftCnt << endl << endl;
           totalNodes += perftCnt;
       } else {
-          if (!think(pos, false, false, dummy, dummy, 0, maxDepth, maxNodes, secsPerPos, moves))
+          if (!think(pos, SearchLimits(0, 0, 0, maxTime, maxDepth, maxNodes, false, false), moves))
               break;
           totalNodes += pos.nodes_searched();
       }
index 461c057790e18beed7d88940c9df5cbc8626a29a..677455ec05b0c26bb3722df69f6645a86f0c14d9 100644 (file)
@@ -116,8 +116,8 @@ namespace {
 
     void extract_pv_from_tt(Position& pos);
     void insert_pv_in_tt(Position& pos);
-    std::string pv_info_to_uci(Position& pos, int depth, int selDepth, Value alpha, Value beta, int pvIdx);
-
+    std::string pv_info_to_uci(Position& pos, int depth, int selDepth,
+                               Value alpha, Value beta, int pvIdx);
     int64_t nodes;
     Value pv_score;
     Value non_pv_score;
@@ -125,7 +125,7 @@ namespace {
   };
 
 
-  // RootMoveList struct is just a std::vector<> of RootMove objects,
+  // RootMoveList struct is just a vector of RootMove objects,
   // with an handful of methods above the standard ones.
 
   struct RootMoveList : public std::vector<RootMove> {
@@ -188,8 +188,8 @@ namespace {
 
   // Step 11. Decide the new search depth
 
-  // Extensions. Configurable UCI options
-  // Array index 0 is used at non-PV nodes, index 1 at PV nodes.
+  // Extensions. Configurable UCI options. Array index 0 is used at
+  // non-PV nodes, index 1 at PV nodes.
   Depth CheckExtension[2], PawnPushTo7thExtension[2];
   Depth PassedPawnExtension[2], PawnEndgameExtension[2];
 
@@ -203,14 +203,14 @@ namespace {
 
   // Futility lookup tables (initialized at startup) and their access functions
   Value FutilityMarginsMatrix[16][64]; // [depth][moveNumber]
-  int FutilityMoveCountArray[32]; // [depth]
+  int FutilityMoveCountArray[32];      // [depth]
 
   inline Value futility_margin(Depth d, int mn) { return d < 7 * ONE_PLY ? FutilityMarginsMatrix[Max(d, 1)][Min(mn, 63)] : 2 * VALUE_INFINITE; }
   inline int futility_move_count(Depth d) { return d < 16 * ONE_PLY ? FutilityMoveCountArray[d] : 512; }
 
   // Step 14. Reduced search
 
-  // Reduction lookup tables (initialized at startup) and their getter functions
+  // Reduction lookup tables (initialized at startup) and their access function
   int8_t ReductionMatrix[2][64][64]; // [pv][depth][moveNumber]
 
   template <NodeType PV>
@@ -233,10 +233,9 @@ namespace {
   int MultiPV, UCIMultiPV;
 
   // Time management variables
-  int MaxNodes, MaxDepth, ExactMaxTime;
-  bool UseTimeManagement, InfiniteSearch, Pondering, StopOnPonderhit;
-  bool FirstRootMove, StopRequest, QuitRequest, AspirationFailLow;
+  bool StopOnPonderhit, FirstRootMove, StopRequest, QuitRequest, AspirationFailLow;
   TimeManager TimeMgr;
+  SearchLimits Limits;
 
   // Log file
   bool UseLogFile;
@@ -304,7 +303,7 @@ namespace {
 #endif
 
 
-  // MovePickerExt is an extended MovePicker used to choose at compile time
+  // MovePickerExt is an extended MovePicker class used to choose at compile time
   // the proper move source according to the type of node.
   template<bool SpNode, bool Root> struct MovePickerExt;
 
@@ -439,25 +438,30 @@ int64_t perft(Position& pos, Depth depth) {
 
 /// think() is the external interface to Stockfish's search, and is called when
 /// the program receives the UCI 'go' command. It initializes various global
-/// variables, and calls id_loop(). It returns false when a quit command is
+/// variables, and calls id_loop(). It returns false when a "quit" command is
 /// received during the search.
 
-bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[],
-           int movesToGo, int maxDepth, int maxNodes, int maxTime, Move searchMoves[]) {
+bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) {
 
   // Initialize global search-related variables
   StopOnPonderhit = StopRequest = QuitRequest = AspirationFailLow = SendSearchedNodes = false;
   NodesSincePoll = 0;
   current_search_time(get_system_time());
-  ExactMaxTime = maxTime;
-  MaxDepth = maxDepth;
-  MaxNodes = maxNodes;
-  InfiniteSearch = infinite;
-  Pondering = ponder;
-  UseTimeManagement = !ExactMaxTime && !MaxDepth && !MaxNodes && !InfiniteSearch;
+  Limits = limits;
+  TimeMgr.init(Limits, pos.startpos_ply_counter());
+
+  // Set best NodesBetweenPolls interval to avoid lagging under time pressure
+  if (Limits.maxNodes)
+      NodesBetweenPolls = Min(Limits.maxNodes, 30000);
+  else if (Limits.time && Limits.time < 1000)
+      NodesBetweenPolls = 1000;
+  else if (Limits.time && Limits.time < 5000)
+      NodesBetweenPolls = 5000;
+  else
+      NodesBetweenPolls = 30000;
 
   // Look for a book move, only during games, not tests
-  if (UseTimeManagement && Options["OwnBook"].value<bool>())
+  if (Limits.useTimeManagement() && Options["OwnBook"].value<bool>())
   {
       if (Options["Book File"].value<std::string>() != OpeningBook.name())
           OpeningBook.open(Options["Book File"].value<std::string>());
@@ -465,7 +469,7 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
       Move bookMove = OpeningBook.get_move(pos, Options["Best Book Move"].value<bool>());
       if (bookMove != MOVE_NONE)
       {
-          if (Pondering)
+          if (Limits.ponder)
               wait_for_stop_or_ponderhit();
 
           cout << "bestmove " << bookMove << endl;
@@ -511,34 +515,18 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
       ThreadsMgr[i].maxPly = 0;
   }
 
-  // Set thinking time
-  int myTime = time[pos.side_to_move()];
-  int myIncrement = increment[pos.side_to_move()];
-  if (UseTimeManagement)
-      TimeMgr.init(myTime, myIncrement, movesToGo, pos.startpos_ply_counter());
-
-  // Set best NodesBetweenPolls interval to avoid lagging under time pressure
-  if (MaxNodes)
-      NodesBetweenPolls = Min(MaxNodes, 30000);
-  else if (myTime && myTime < 1000)
-      NodesBetweenPolls = 1000;
-  else if (myTime && myTime < 5000)
-      NodesBetweenPolls = 5000;
-  else
-      NodesBetweenPolls = 30000;
-
-  // Write search information to log file
+  // Write to log file and keep it open to be accessed during the search
   if (UseLogFile)
   {
       std::string name = Options["Search Log Filename"].value<std::string>();
       LogFile.open(name.c_str(), std::ios::out | std::ios::app);
 
       LogFile << "\nSearching: "  << pos.to_fen()
-              << "\ninfinite: "   << infinite
-              << " ponder: "      << ponder
-              << " time: "        << myTime
-              << " increment: "   << myIncrement
-              << " moves to go: " << movesToGo
+              << "\ninfinite: "   << Limits.infinite
+              << " ponder: "      << Limits.ponder
+              << " time: "        << Limits.time
+              << " increment: "   << Limits.increment
+              << " moves to go: " << Limits.movesToGo
               << endl;
   }
 
@@ -546,15 +534,15 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
   Move ponderMove = MOVE_NONE;
   Move bestMove = id_loop(pos, searchMoves, &ponderMove);
 
-  // Print final search statistics
   cout << "info" << speed_to_uci(pos.nodes_searched()) << endl;
 
+  // Write final search statistics and close log file
   if (UseLogFile)
   {
       int t = current_search_time();
 
       LogFile << "Nodes: "          << pos.nodes_searched()
-              << "\nNodes/second: " << (t > 0 ? int(pos.nodes_searched() * 1000 / t) : 0)
+              << "\nNodes/second: " << (t > 0 ? pos.nodes_searched() * 1000 / t : 0)
               << "\nBest move: "    << move_to_san(pos, bestMove);
 
       StateInfo st;
@@ -569,7 +557,7 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
 
   // If we are pondering or in infinite search, we shouldn't print the
   // best move before we are told to do so.
-  if (!StopRequest && (Pondering || InfiniteSearch))
+  if (!StopRequest && (Limits.ponder || Limits.infinite))
       wait_for_stop_or_ponderhit();
 
   // Could be MOVE_NONE when searching on a stalemate position
@@ -624,7 +612,7 @@ namespace {
     }
 
     // Iterative deepening loop
-    while (++depth <= PLY_MAX && (!MaxDepth || depth <= MaxDepth) && !StopRequest)
+    while (++depth <= PLY_MAX && (!Limits.maxDepth || depth <= Limits.maxDepth) && !StopRequest)
     {
         Rml.bestMoveChanges = 0;
         cout << set960(pos.is_chess960()) << "info depth " << depth << endl;
@@ -708,7 +696,7 @@ namespace {
         else if (bestMove != easyMove)
             easyMove = MOVE_NONE;
 
-        if (UseTimeManagement && !StopRequest)
+        if (Limits.useTimeManagement() && !StopRequest)
         {
             // Time to stop?
             bool noMoreTime = false;
@@ -743,7 +731,7 @@ namespace {
 
             if (noMoreTime)
             {
-                if (Pondering)
+                if (Limits.ponder)
                     StopOnPonderhit = true;
                 else
                     break;
@@ -1907,7 +1895,7 @@ split_point_start: // At split points actual search starts from here
         if (!std::getline(std::cin, command) || command == "quit")
         {
             // Quit the program as soon as possible
-            Pondering = false;
+            Limits.ponder = false;
             QuitRequest = StopRequest = true;
             return;
         }
@@ -1915,7 +1903,7 @@ split_point_start: // At split points actual search starts from here
         {
             // Stop calculating as soon as possible, but still send the "bestmove"
             // and possibly the "ponder" token when finishing the search.
-            Pondering = false;
+            Limits.ponder = false;
             StopRequest = true;
         }
         else if (command == "ponderhit")
@@ -1923,7 +1911,7 @@ split_point_start: // At split points actual search starts from here
             // The opponent has played the expected move. GUI sends "ponderhit" if
             // we were told to ponder on the same move the opponent has played. We
             // should continue searching but switching from pondering to normal search.
-            Pondering = false;
+            Limits.ponder = false;
 
             if (StopOnPonderhit)
                 StopRequest = true;
@@ -1951,7 +1939,7 @@ split_point_start: // At split points actual search starts from here
     }
 
     // Should we stop the search?
-    if (Pondering)
+    if (Limits.ponder)
         return;
 
     bool stillAtFirstMove =    FirstRootMove
@@ -1961,9 +1949,9 @@ split_point_start: // At split points actual search starts from here
     bool noMoreTime =   t > TimeMgr.maximum_time()
                      || stillAtFirstMove;
 
-    if (   (UseTimeManagement && noMoreTime)
-        || (ExactMaxTime && t >= ExactMaxTime)
-        || (MaxNodes && pos.nodes_searched() >= MaxNodes)) // FIXME
+    if (   (Limits.useTimeManagement() && noMoreTime)
+        || (Limits.maxTime && t >= Limits.maxTime)
+        || (Limits.maxNodes && pos.nodes_searched() >= Limits.maxNodes)) // FIXME
         StopRequest = true;
   }
 
index 65b3b4a7107d03668032db961be252dbb59a6981..a2e783b110895a6fb92fa71665c4f9edc0cc9820 100644 (file)
 #include "move.h"
 #include "types.h"
 
+class Position;
+struct SplitPoint;
 
 /// The SearchStack struct keeps track of the information we need to remember
 /// from nodes shallower and deeper in the tree during the search.  Each
 /// search thread has its own array of SearchStack objects, indexed by the
 /// current ply.
-struct EvalInfo;
-struct SplitPoint;
 
 struct SearchStack {
   int ply;
@@ -45,12 +45,27 @@ struct SearchStack {
   SplitPoint* sp;
 };
 
-class Position;
+
+/// The SearchLimits struct stores information sent by GUI about available time
+/// to search the current move, maximum depth/time, if we are in analysis mode
+/// or if we have to ponder while is our opponent's side to move.
+
+struct SearchLimits {
+
+  SearchLimits() {}
+  SearchLimits(int t, int i, int mtg, int mt, int md, int mn, bool inf, bool pon)
+              : time(t), increment(i), movesToGo(mtg), maxTime(mt), maxDepth(md),
+                maxNodes(mn), infinite(inf), ponder(pon) {}
+
+  bool useTimeManagement() const { return !(maxTime | maxDepth | maxNodes | int(infinite)); }
+
+  int time, increment, movesToGo, maxTime, maxDepth, maxNodes;
+  bool infinite, ponder;
+};
 
 extern void init_threads();
 extern void exit_threads();
 extern int64_t perft(Position& pos, Depth depth);
-extern bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[],
-                  int movesToGo, int maxDepth, int maxNodes, int maxTime, Move searchMoves[]);
+extern bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]);
 
 #endif // !defined(SEARCH_H_INCLUDED)
index 86e6729cd1fcf087ce6faf1402240b7364eeb80f..f07f27e71b1e4a2e393294ef4532676867a3c5dc 100644 (file)
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-
-////
-//// Includes
-////
-
 #include <cmath>
 
 #include "misc.h"
+#include "search.h"
 #include "timeman.h"
 #include "ucioption.h"
 
-////
-//// Local definitions
-////
-
 namespace {
 
   /// Constants
@@ -84,31 +76,28 @@ namespace {
 }
 
 
-////
-//// Functions
-////
-
 void TimeManager::pv_instability(int curChanges, int prevChanges) {
 
     unstablePVExtraTime =  curChanges  * (optimumSearchTime / 2)
                          + prevChanges * (optimumSearchTime / 3);
 }
 
-void TimeManager::init(int myTime, int myInc, int movesToGo, int currentPly)
+
+void TimeManager::init(const SearchLimits& limits, int currentPly)
 {
   /* We support four different kind of time controls:
 
-      Inc == 0 && movesToGo == 0 means: x basetime  [sudden death!]
-      Inc == 0 && movesToGo != 0 means: (x moves) / (y minutes)
-      Inc > 0  && movesToGo == 0 means: x basetime + z inc.
-      Inc > 0  && movesToGo != 0 means: (x moves) / (y minutes) + z inc
+      increment == 0 && movesToGo == 0 means: x basetime  [sudden death!]
+      increment == 0 && movesToGo != 0 means: x moves in y minutes
+      increment >  0 && movesToGo == 0 means: x basetime + z increment
+      increment >  0 && movesToGo != 0 means: x moves in y minutes + z increment
 
     Time management is adjusted by following UCI parameters:
 
-      emergencyMoveHorizon :Be prepared to always play at least this many moves
-      emergencyBaseTime    :Always attempt to keep at least this much time (in ms) at clock
-      emergencyMoveTime    :Plus attempt to keep at least this much time for each remaining emergency move
-      minThinkingTime      :No matter what, use at least this much thinking before doing the move
+      emergencyMoveHorizonBe prepared to always play at least this many moves
+      emergencyBaseTime   Always attempt to keep at least this much time (in ms) at clock
+      emergencyMoveTime   Plus attempt to keep at least this much time for each remaining emergency move
+      minThinkingTime     No matter what, use at least this much thinking before doing the move
   */
 
   int hypMTG, hypMyTime, t1, t2;
@@ -121,14 +110,19 @@ void TimeManager::init(int myTime, int myInc, int movesToGo, int currentPly)
 
   // Initialize to maximum values but unstablePVExtraTime that is reset
   unstablePVExtraTime = 0;
-  optimumSearchTime = maximumSearchTime = myTime;
+  optimumSearchTime = maximumSearchTime = limits.time;
 
   // We calculate optimum time usage for different hypothetic "moves to go"-values and choose the
   // minimum of calculated search time values. Usually the greatest hypMTG gives the minimum values.
-  for (hypMTG = 1; hypMTG <= (movesToGo ? Min(movesToGo, MoveHorizon) : MoveHorizon); hypMTG++)
+  for (hypMTG = 1; hypMTG <= (limits.movesToGo ? Min(limits.movesToGo, MoveHorizon) : MoveHorizon); hypMTG++)
   {
       // Calculate thinking time for hypothetic "moves to go"-value
-      hypMyTime = Max(myTime + (hypMTG - 1) * myInc - emergencyBaseTime - Min(hypMTG, emergencyMoveHorizon) * emergencyMoveTime, 0);
+      hypMyTime =  limits.time
+                 + limits.increment * (hypMTG - 1)
+                 - emergencyBaseTime
+                 - emergencyMoveTime * Min(hypMTG, emergencyMoveHorizon);
+
+      hypMyTime = Max(hypMyTime, 0);
 
       t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, currentPly);
       t2 = minThinkingTime + remaining<MaxTime>(hypMyTime, hypMTG, currentPly);
@@ -144,9 +138,6 @@ void TimeManager::init(int myTime, int myInc, int movesToGo, int currentPly)
   optimumSearchTime = Min(optimumSearchTime, maximumSearchTime);
 }
 
-////
-//// Local functions
-////
 
 namespace {
 
index dfff89abd9a3c27ad21e42d3fb50b8cb49b69164..bf489b7f9fbc662771ceceeabad53105f28050ff 100644 (file)
 #if !defined(TIMEMAN_H_INCLUDED)
 #define TIMEMAN_H_INCLUDED
 
+struct SearchLimits;
+
 class TimeManager {
 public:
 
-  void init(int myTime, int myInc, int movesToGo, int currentPly);
+  void init(const SearchLimits& limits, int currentPly);
   void pv_instability(int curChanges, int prevChanges);
   int available_time() const { return optimumSearchTime + unstablePVExtraTime; }
   int maximum_time() const { return maximumSearchTime; }
index 8b90c22707574d1b41b21b53e11d74016309a504..9593b593c897fd1d626d452d62fdb8b8dc6b5df9 100644 (file)
@@ -203,50 +203,48 @@ namespace {
   bool go(Position& pos, UCIParser& up) {
 
     string token;
-    Move searchMoves[MOVES_MAX];
-    int movesToGo, depth, nodes, moveTime, numOfMoves;
-    bool infinite, ponder;
-    int time[2] = {0, 0}, inc[2] = {0, 0};
-
-    searchMoves[0] = MOVE_NONE;
-    infinite = ponder = false;
-    movesToGo = depth = nodes = moveTime = numOfMoves = 0;
+    int time[] = { 0, 0 }, inc[] = { 0, 0 };
+    SearchLimits limits(0, 0, 0, 0, 0, 0, false, false);
+    Move searchMoves[MOVES_MAX] = { MOVE_NONE };
+    Move* cur = searchMoves;
 
     while (up >> token)
     {
         if (token == "infinite")
-            infinite = true;
+            limits.infinite = true;
         else if (token == "ponder")
-            ponder = true;
+            limits.ponder = true;
         else if (token == "wtime")
-            up >> time[0];
+            up >> time[WHITE];
         else if (token == "btime")
-            up >> time[1];
+            up >> time[BLACK];
         else if (token == "winc")
-            up >> inc[0];
+            up >> inc[WHITE];
         else if (token == "binc")
-            up >> inc[1];
+            up >> inc[BLACK];
         else if (token == "movestogo")
-            up >> movesToGo;
+            up >> limits.movesToGo;
         else if (token == "depth")
-            up >> depth;
+            up >> limits.maxDepth;
         else if (token == "nodes")
-            up >> nodes;
+            up >> limits.maxNodes;
         else if (token == "movetime")
-            up >> moveTime;
+            up >> limits.maxTime;
         else if (token == "searchmoves")
         {
             while (up >> token)
-                searchMoves[numOfMoves++] = move_from_uci(pos, token);
+                *cur++ = move_from_uci(pos, token);
 
-            searchMoves[numOfMoves] = MOVE_NONE;
+            *cur = MOVE_NONE;
         }
     }
 
     assert(pos.is_ok());
 
-    return think(pos, infinite, ponder, time, inc, movesToGo,
-                 depth, nodes, moveTime, searchMoves);
+    limits.time = time[pos.side_to_move()];
+    limits.increment = inc[pos.side_to_move()];
+
+    return think(pos, limits, searchMoves);
   }
 
   void perft(Position& pos, UCIParser& up) {