]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Implement a fallback system when aspiration search fails low and we are out of time.
[stockfish] / src / search.cpp
index f18f2f2f65035a9c3740d92b551707e4030e8bfd..76b79dda74cb189693857247157ac21600e2fb1f 100644 (file)
@@ -47,6 +47,60 @@ namespace {
 
   /// Types
 
+  //The IterationInfoType is used to store search history
+  //iteration by iteration.
+  //
+  //Because we use relatively small (dynamic) aspiration window,
+  //there happens many fail highs and fail lows in root. And
+  //because we don't do researches in those cases, "value" stored
+  //here is not necessarily exact. Instead in case of fail high/low
+  //we guess what the right value might be and store our guess
+  //as "speculated value" and then move on...
+
+  class IterationInfoType {
+    private:
+      Value _value;
+      Value _speculatedValue;
+      bool _failHigh;
+      bool _failLow;
+    public:
+      IterationInfoType() {
+        clear();
+      }
+
+      inline void clear() {
+        set(Value(0));
+      }
+
+      inline void set(Value v) {
+        set(v, v, false, false);
+      }
+
+      inline void set(Value v, Value specV, bool fHigh, bool fLow) {
+        _value = v;
+        _speculatedValue = specV;
+        _failHigh = fHigh;
+        _failLow = fLow;
+      }
+
+      inline Value value() {
+        return _value;
+      }
+
+      inline Value speculated_value() {
+        return _speculatedValue;
+      }
+
+      inline bool fail_high() {
+        return _failHigh;
+      }
+
+      inline bool fail_low() {
+        return _failLow;
+      }
+  };
+
+
   // The BetaCounterType class is used to order moves at ply one.
   // Apart for the first one that has its score, following moves
   // normally have score -VALUE_INFINITE, so are ordered according
@@ -196,7 +250,7 @@ namespace {
   BetaCounterType BetaCounter;
 
   // Scores and number of times the best move changed for each iteration:
-  Value ValueByIteration[PLY_MAX_PLUS_2];
+  IterationInfoType IterationInfo[PLY_MAX_PLUS_2];
   int BestMoveChangesByIteration[PLY_MAX_PLUS_2];
 
   // MultiPV mode
@@ -205,7 +259,7 @@ namespace {
   // Time managment variables
   int SearchStartTime;
   int MaxNodes, MaxDepth;
-  int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime;
+  int MaxSearchTime, AbsoluteMaxSearchTime, EmergencyMaxSearchTime, ExtraSearchTime;
   Move EasyMove;
   int RootMoveNumber;
   bool InfiniteSearch;
@@ -214,6 +268,7 @@ namespace {
   bool AbortSearch;
   bool Quit;
   bool FailHigh;
+  bool FailLow;
   bool Problem;
   bool PonderingEnabled;
   int ExactMaxTime;
@@ -246,7 +301,8 @@ namespace {
   /// Functions
 
   Value id_loop(const Position &pos, Move searchMoves[]);
-  Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml);
+  Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml,
+                    Value alpha, Value beta);
   Value search_pv(Position &pos, SearchStack ss[], Value alpha, Value beta,
                   Depth depth, int ply, int threadID);
   Value search(Position &pos, SearchStack ss[], Value beta,
@@ -325,7 +381,6 @@ void SearchStack::init(int ply) {
   pv[ply] = pv[ply + 1] = MOVE_NONE;
   currentMove = threatMove = MOVE_NONE;
   reduction = Depth(0);
-  currentMoveCaptureValue = Value(0);
 }
 
 void SearchStack::initKillers() {
@@ -381,6 +436,7 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
   AbortSearch = false;
   Quit = false;
   FailHigh = false;
+  FailLow = false;
   Problem = false;
   ExactMaxTime = maxTime;
 
@@ -464,9 +520,11 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
       {
           MaxSearchTime = myTime / 30 + myIncrement;
           AbsoluteMaxSearchTime = Max(myTime / 4, myIncrement - 100);
+          EmergencyMaxSearchTime = Max(myTime / 2, myIncrement - 100);
       } else { // Blitz game without increment
           MaxSearchTime = myTime / 30;
           AbsoluteMaxSearchTime = myTime / 8;
+          EmergencyMaxSearchTime = myTime / 4;
       }
   }
   else // (x moves) / (y minutes)
@@ -475,9 +533,11 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
       {
           MaxSearchTime = myTime / 2;
           AbsoluteMaxSearchTime = Min(myTime / 2, myTime - 500);
+          EmergencyMaxSearchTime = myTime - 500;
       } else {
           MaxSearchTime = myTime / Min(movesToGo, 20);
           AbsoluteMaxSearchTime = Min((4 * myTime) / movesToGo, myTime / 3);
+          EmergencyMaxSearchTime = Min((8 * myTime) / movesToGo, myTime / 2);
       }
   }
 
@@ -644,6 +704,9 @@ namespace {
     Position p(pos);
     SearchStack ss[PLY_MAX_PLUS_2];
 
+    Value alpha;
+    Value beta;
+
     // searchMoves are verified, copied, scored and sorted
     RootMoveList rml(p, searchMoves);
 
@@ -655,14 +718,13 @@ namespace {
         ss[i].init(i);
         ss[i].initKillers();
     }
-    ValueByIteration[0] = Value(0);
-    ValueByIteration[1] = rml.get_move_score(0);
+    IterationInfo[1].set(rml.get_move_score(0));
     Iteration = 1;
 
     EasyMove = rml.scan_for_easy_move();
 
     // Iterative deepening loop
-    while (!AbortSearch && Iteration < PLY_MAX)
+    while (Iteration < PLY_MAX)
     {
         // Initialize iteration
         rml.sort();
@@ -673,8 +735,57 @@ namespace {
 
         std::cout << "info depth " << Iteration << std::endl;
 
+        //Calculate dynamic search window based on previous iterations.
+        if (MultiPV == 1 && Iteration >= 6) {
+          Value prevDelta1 = IterationInfo[Iteration - 1].speculated_value() - IterationInfo[Iteration - 2].speculated_value();
+          Value prevDelta2 = IterationInfo[Iteration - 2].speculated_value() - IterationInfo[Iteration - 3].speculated_value();
+
+          Value delta = Max((2 * Abs(prevDelta1) + Abs(prevDelta2)) , ProblemMargin);
+
+          alpha = IterationInfo[Iteration - 1].value() - delta;
+          beta  = IterationInfo[Iteration - 1].value() + delta;
+          if (alpha < - VALUE_INFINITE) alpha = - VALUE_INFINITE;
+          if (beta  >   VALUE_INFINITE) beta = VALUE_INFINITE;
+
+        } else {
+          alpha = - VALUE_INFINITE;
+          beta  =   VALUE_INFINITE;
+        }
+
         // Search to the current depth
-        ValueByIteration[Iteration] = root_search(p, ss, rml);
+        Value value = root_search(p, ss, rml, alpha, beta);
+
+        // Write PV to transposition table, in case the relevant entries have
+        // been overwritten during the search:
+        TT.insert_pv(p, ss[0].pv);
+
+        if (AbortSearch)
+          break; //Value cannot be trusted. Break out immediately!
+
+        //Save info about search result
+        Value speculated_value = value;
+        bool fHigh = false;
+        bool fLow = false;
+
+        Value prev_value = IterationInfo[Iteration - 1].value();
+        Value delta = value - prev_value;
+
+        if (value >= beta) {
+          fHigh = true;
+          speculated_value = prev_value + 2 * delta;
+          BestMoveChangesByIteration[Iteration] += 2; //This is used to tell time management to allocate more time
+        } else if (value <= alpha) {
+          fLow = true;
+          speculated_value = prev_value + 2 * delta;
+          BestMoveChangesByIteration[Iteration] += 3; //This is used to tell time management to allocate more time
+        } else {
+          //nothing
+        }
+
+        if (speculated_value < - VALUE_INFINITE) speculated_value = - VALUE_INFINITE;
+        if (speculated_value >   VALUE_INFINITE) speculated_value =   VALUE_INFINITE;
+
+        IterationInfo[Iteration].set(value, speculated_value, fHigh, fLow);
 
         // Erase the easy move if it differs from the new best move
         if (ss[0].pv[0] != EasyMove)
@@ -693,13 +804,13 @@ namespace {
 
             // Stop search early when the last two iterations returned a mate score
             if (  Iteration >= 6
-                && abs(ValueByIteration[Iteration]) >= abs(VALUE_MATE) - 100
-                && abs(ValueByIteration[Iteration-1]) >= abs(VALUE_MATE) - 100)
+                && abs(IterationInfo[Iteration].value()) >= abs(VALUE_MATE) - 100
+                && abs(IterationInfo[Iteration-1].value()) >= abs(VALUE_MATE) - 100)
                 stopSearch = true;
 
             // Stop search early if one move seems to be much better than the rest
             int64_t nodes = nodes_searched();
-            if (   Iteration >= 8
+            if (   Iteration >= 8 && !fLow && !fHigh
                 && EasyMove == ss[0].pv[0]
                 && (  (   rml.get_move_cumulative_nodes(0) > (nodes * 85) / 100
                        && current_search_time() > MaxSearchTime / 16)
@@ -726,14 +837,39 @@ namespace {
                     StopOnPonderhit = true;
             }
         }
-        // Write PV to transposition table, in case the relevant entries have
-        // been overwritten during the search:
-        TT.insert_pv(p, ss[0].pv);
 
         if (MaxDepth && Iteration >= MaxDepth)
             break;
     }
 
+    if (FailLow)
+    {
+      //Here we face the rare, but extremely difficult case:
+      //Our aspiration search has failed low and we've run out of time!
+      //So we have no move to play!
+      //Now use the emergency time and try as quickly as possible to
+      //find even one playable move.
+
+      //FIXME: this is only for grepping logs purpose. Remove me when we are sure that this stuff really works!!
+      if (AbortSearch)
+        std::cout << "info depth " << 999 << std::endl;
+      else
+        std::cout << "info depth " << 998 << std::endl;
+
+      //Prepare variables for emergency search
+      AbortSearch = false;
+      FailLow = false;
+      AbsoluteMaxSearchTime = EmergencyMaxSearchTime;
+      MaxSearchTime = EmergencyMaxSearchTime;
+      ExtraSearchTime = 0;
+      rml.sort();
+
+      std::cout << "info depth " << Iteration << std::endl;
+
+     //Cause we failed low, it's _likely_ that we couldn't get over alpha anyway.
+      root_search(p, ss, rml, -VALUE_INFINITE, alpha);
+    }
+
     rml.sort();
 
     // If we are pondering, we shouldn't print the best move before we
@@ -785,15 +921,21 @@ namespace {
   // scheme (perhaps we should try to use this at internal PV nodes, too?)
   // and prints some information to the standard output.
 
-  Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml) {
+  Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml, Value alpha, Value beta) {
 
-    Value alpha = -VALUE_INFINITE;
-    Value beta = VALUE_INFINITE, value;
+    Value oldAlpha = alpha;
+    Value value;
     Bitboard dcCandidates = pos.discovered_check_candidates(pos.side_to_move());
 
     // Loop through all the moves in the root move list
     for (int i = 0; i <  rml.move_count() && !AbortSearch; i++)
     {
+        if (alpha >= beta) {
+          rml.set_move_score(i, -VALUE_INFINITE);
+          //Leave node-counters and beta-counters as they are.
+          continue;
+        }
+
         int64_t nodes;
         Move move;
         StateInfo st;
@@ -826,12 +968,12 @@ namespace {
 
         if (i < MultiPV)
         {
-            value = -search_pv(pos, ss, -beta, VALUE_INFINITE, newDepth, 1, 0);
+            value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0);
             // If the value has dropped a lot compared to the last iteration,
             // set the boolean variable Problem to true. This variable is used
             // for time managment: When Problem is true, we try to complete the
             // current iteration before playing a move.
-            Problem = (Iteration >= 2 && value <= ValueByIteration[Iteration-1] - ProblemMargin);
+            Problem = (Iteration >= 2 && value <= IterationInfo[Iteration-1].value() - ProblemMargin);
 
             if (Problem && StopOnPonderhit)
                 StopOnPonderhit = false;
@@ -907,11 +1049,12 @@ namespace {
                     LogFile << pretty_pv(pos, current_search_time(), Iteration, nodes_searched(), value, ss[0].pv)
                             << std::endl;
 
-                alpha = value;
+                if (value > alpha)
+                  alpha = value;
 
                 // Reset the global variable Problem to false if the value isn't too
                 // far below the final value from the last iteration.
-                if (value > ValueByIteration[Iteration - 1] - NoProblemMargin)
+                if (value > IterationInfo[Iteration - 1].value() - NoProblemMargin)
                     Problem = false;
             }
             else // MultiPV > 1
@@ -936,6 +1079,12 @@ namespace {
                 alpha = rml.get_move_score(Min(i, MultiPV-1));
             }
         }
+
+        if (alpha <= oldAlpha)
+          FailLow = true;
+        else
+          FailLow = false;
+
     }
     return alpha;
   }
@@ -1015,12 +1164,6 @@ namespace {
 
       movesSearched[moveCount++] = ss[ply].currentMove = move;
 
-      if (moveIsCapture)
-          ss[ply].currentMoveCaptureValue =
-          move_is_ep(move)? PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move));
-      else
-          ss[ply].currentMoveCaptureValue = Value(0);
-
       // Decide the new search depth
       bool dangerous;
       Depth ext = extension(pos, move, true, moveIsCapture, moveIsCheck, singleReply, mateThreat, &dangerous);
@@ -1090,7 +1233,7 @@ namespace {
           // (from the computer's point of view) since the previous iteration:
           if (   ply == 1
               && Iteration >= 2
-              && -value <= ValueByIteration[Iteration-1] - ProblemMargin)
+              && -value <= IterationInfo[Iteration-1].value() - ProblemMargin)
               Problem = true;
       }
 
@@ -1199,7 +1342,7 @@ namespace {
 
         StateInfo st;
         pos.do_null_move(st);
-        int R = (depth >= 4 * OnePly ? 4 : 3); // Null move dynamic reduction
+        int R = (depth >= 5 * OnePly ? 4 : 3); // Null move dynamic reduction
 
         Value nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID);
 
@@ -1721,12 +1864,6 @@ namespace {
 
       assert(move_is_ok(move));
 
-      if (moveIsCapture)
-          ss[sp->ply].currentMoveCaptureValue =
-          move_is_ep(move)? PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move));
-      else
-          ss[sp->ply].currentMoveCaptureValue = Value(0);
-
       lock_grab(&(sp->lock));
       int moveCount = ++sp->moves;
       lock_release(&(sp->lock));
@@ -1809,7 +1946,7 @@ namespace {
         // (from the computer's point of view) since the previous iteration.
         if (   sp->ply == 1
             && Iteration >= 2
-            && -value <= ValueByIteration[Iteration-1] - ProblemMargin)
+            && -value <= IterationInfo[Iteration-1].value() - ProblemMargin)
             Problem = true;
       }
       lock_release(&(sp->lock));
@@ -2438,8 +2575,8 @@ namespace {
         return;
 
     bool overTime =     t > AbsoluteMaxSearchTime
-                     || (RootMoveNumber == 1 && t > MaxSearchTime + ExtraSearchTime)
-                     || (  !FailHigh && !fail_high_ply_1() && !Problem
+                     || (RootMoveNumber == 1 && t > MaxSearchTime + ExtraSearchTime && !FailLow) //FIXME: BUG??
+                     || (  !FailHigh && !FailLow && !fail_high_ply_1() && !Problem
                          && t > 6*(MaxSearchTime + ExtraSearchTime));
 
     if (   (Iteration >= 3 && (!InfiniteSearch && overTime))
@@ -2460,8 +2597,8 @@ namespace {
        (!InfiniteSearch && (StopOnPonderhit ||
                             t > AbsoluteMaxSearchTime ||
                             (RootMoveNumber == 1 &&
-                             t > MaxSearchTime + ExtraSearchTime) ||
-                            (!FailHigh && !fail_high_ply_1() && !Problem &&
+                             t > MaxSearchTime + ExtraSearchTime && !FailLow) ||
+                            (!FailHigh && !FailLow && !fail_high_ply_1() && !Problem &&
                              t > 6*(MaxSearchTime + ExtraSearchTime)))))
       AbortSearch = true;
   }