]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Fix incorrect assert(PvNode == (alpha != beta - 1))
[stockfish] / src / search.cpp
index 0839bd0fcc73999bc245b441abe083157d83b3ee..eb9f63f15bca317f4fa148bf304472f7eb204edf 100644 (file)
@@ -24,7 +24,6 @@
 #include <iomanip>
 #include <iostream>
 #include <sstream>
-#include <vector>
 
 #include "book.h"
 #include "evaluate.h"
@@ -42,7 +41,7 @@ namespace Search {
 
   volatile SignalsType Signals;
   LimitsType Limits;
-  std::set<Move> SearchMoves;
+  std::vector<RootMove> RootMoves;
   Position RootPosition;
 }
 
@@ -59,33 +58,6 @@ namespace {
   // Different node types, used as template parameter
   enum NodeType { Root, PV, NonPV, SplitPointRoot, SplitPointPV, SplitPointNonPV };
 
-  // RootMove struct is used for moves at the root of the tree. For each root
-  // move we store a score, a node count, and a PV (really a refutation in the
-  // case of moves which fail low). Score is normally set at -VALUE_INFINITE for
-  // all non-pv moves.
-  struct RootMove {
-
-    RootMove(){}
-    RootMove(Move m) {
-      score = prevScore = -VALUE_INFINITE;
-      pv.push_back(m);
-      pv.push_back(MOVE_NONE);
-    }
-
-    bool operator<(const RootMove& m) const { return score < m.score; }
-    bool operator==(const Move& m) const { return pv[0] == m; }
-
-    void extract_pv_from_tt(Position& pos);
-    void insert_pv_in_tt(Position& pos);
-
-    Value score;
-    Value prevScore;
-    std::vector<Move> pv;
-  };
-
-
-  /// Constants
-
   // Lookup table to check if a Piece is a slider and its access function
   const bool Slidings[18] = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1 };
   inline bool piece_is_slider(Piece p) { return Slidings[p]; }
@@ -135,17 +107,14 @@ namespace {
     return (Depth) Reductions[PvNode][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)];
   }
 
-  // Easy move margin. An easy move candidate must be at least this much
-  // better than the second best move.
+  // Easy move margin. An easy move candidate must be at least this much better
+  // than the second best move.
   const Value EasyMoveMargin = Value(0x150);
 
   // This is the minimum interval in msec between two check_time() calls
   const int TimerResolution = 5;
 
 
-  /// Namespace variables
-
-  std::vector<RootMove> RootMoves;
   size_t MultiPV, UCIMultiPV, PVIdx;
   TimeManager TimeMgr;
   int BestMoveChanges;
@@ -154,8 +123,6 @@ namespace {
   History H;
 
 
-  /// Local functions
-
   template <NodeType NT>
   Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth);
 
@@ -282,20 +249,12 @@ void Search::think() {
 
   static Book book; // Defined static to initialize the PRNG only once
 
-  Move bm;
   Position& pos = RootPosition;
   Chess960 = pos.is_chess960();
   elapsed_time(true);
   TimeMgr.init(Limits, pos.startpos_ply_counter());
   TT.new_search();
   H.clear();
-  RootMoves.clear();
-
-  // Populate RootMoves with all the legal moves (default) or, if a SearchMoves
-  // is given, with the subset of legal moves to search.
-  for (MoveList<MV_LEGAL> ml(pos); !ml.end(); ++ml)
-      if (SearchMoves.empty() || SearchMoves.count(ml.move()))
-          RootMoves.push_back(RootMove(ml.move()));
 
   if (RootMoves.empty())
   {
@@ -306,12 +265,15 @@ void Search::think() {
       goto finalize;
   }
 
-  if (   Options["OwnBook"]
-      && (bm = book.probe(pos, Options["Book File"], Options["Best Book Move"])) != MOVE_NONE
-      && count(RootMoves.begin(), RootMoves.end(), bm))
+  if (Options["OwnBook"])
   {
-      std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), bm));
-      goto finalize;
+      Move bookMove = book.probe(pos, Options["Book File"], Options["Best Book Move"]);
+
+      if (bookMove && count(RootMoves.begin(), RootMoves.end(), bookMove))
+      {
+          std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), bookMove));
+          goto finalize;
+      }
   }
 
   // Read UCI options: GUI could change UCI parameters during the game
@@ -582,7 +544,7 @@ namespace {
     const bool RootNode = (NT == Root || NT == SplitPointRoot);
 
     assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
-    assert(PvNode == (alpha != beta - 1));
+    assert((alpha == beta - 1) || PvNode);
     assert(depth > DEPTH_ZERO);
     assert(pos.thread() >= 0 && pos.thread() < Threads.size());
 
@@ -627,6 +589,10 @@ namespace {
     }
 
     // Step 2. Check for aborted search and immediate draw
+    // Enforce node limit here. FIXME: This only works with 1 search thread.
+    if (Limits.maxNodes && pos.nodes_searched() >= Limits.maxNodes)
+        Signals.stop = true;
+
     if ((   Signals.stop
          || pos.is_draw<false>()
          || ss->ply > MAX_PLY) && !RootNode)
@@ -809,6 +775,7 @@ namespace {
         Depth rdepth = depth - ONE_PLY - 3 * ONE_PLY;
 
         assert(rdepth >= ONE_PLY);
+        assert((ss-1)->currentMove != MOVE_NONE);
 
         MovePicker mp(pos, ttMove, H, pos.captured_piece_type());
         CheckInfo ci(pos);
@@ -816,6 +783,7 @@ namespace {
         while ((move = mp.next_move()) != MOVE_NONE)
             if (pos.pl_move_is_legal(move, ci.pinned))
             {
+                ss->currentMove = move;
                 pos.do_move(move, st, ci, pos.move_gives_check(move, ci));
                 value = -search<NonPV>(pos, ss+1, -rbeta, -rbeta+1, rdepth);
                 pos.undo_move(move);
@@ -1004,7 +972,7 @@ split_point_start: // At split points actual search starts from here
 
       // Step 15. Reduced depth search (LMR). If the move fails high will be
       // re-searched at full depth.
-      if (   depth > 3 * ONE_PLY
+      if (   depth > 4 * ONE_PLY
           && !isPvMove
           && !captureOrPromotion
           && !dangerous
@@ -1013,11 +981,10 @@ split_point_start: // At split points actual search starts from here
           &&  ss->killers[1] != move)
       {
           ss->reduction = reduction<PvNode>(depth, moveCount);
-          Depth d = newDepth - ss->reduction;
+          Depth d = std::max(newDepth - ss->reduction, ONE_PLY);
           alpha = SpNode ? sp->alpha : alpha;
 
-          value = d < ONE_PLY ? -qsearch<NonPV>(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO)
-                              : - search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d);
+          value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d);
 
           doFullDepthSearch = (value > alpha && ss->reduction != DEPTH_ZERO);
           ss->reduction = DEPTH_ZERO;
@@ -1053,7 +1020,7 @@ split_point_start: // At split points actual search starts from here
           alpha = sp->alpha;
       }
 
-      // Finished searching the move. If StopRequest is true, the search
+      // Finished searching the move. If Signals.stop is true, the search
       // was aborted because the user interrupted the search or because we
       // ran out of time. In this case, the return value of the search cannot
       // be trusted, and we don't update the best move and/or PV.
@@ -1114,7 +1081,7 @@ split_point_start: // At split points actual search starts from here
     // Step 20. Check for mate and stalemate
     // All legal moves have been searched and if there are no legal moves, it
     // must be mate or stalemate. Note that we can have a false positive in
-    // case of StopRequest or thread.cutoff_occurred() are set, but this is
+    // case of Signals.stop or thread.cutoff_occurred() are set, but this is
     // harmless because return value is discarded anyhow in the parent nodes.
     // If we are in a singular extension search then return a fail low score.
     if (!moveCount)
@@ -1187,7 +1154,7 @@ split_point_start: // At split points actual search starts from here
 
     assert(NT == PV || NT == NonPV);
     assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
-    assert(PvNode == (alpha != beta - 1));
+    assert((alpha == beta - 1) || PvNode);
     assert(depth <= DEPTH_ZERO);
     assert(pos.thread() >= 0 && pos.thread() < Threads.size());
 
@@ -1798,74 +1765,74 @@ split_point_start: // At split points actual search starts from here
     return best;
   }
 
+} // namespace
 
-  // extract_pv_from_tt() builds a PV by adding moves from the transposition table.
-  // We consider also failing high nodes and not only VALUE_TYPE_EXACT nodes. This
-  // allow to always have a ponder move even when we fail high at root and also a
-  // long PV to print that is important for position analysis.
 
-  void RootMove::extract_pv_from_tt(Position& pos) {
+/// RootMove::extract_pv_from_tt() builds a PV by adding moves from the TT table.
+/// We consider also failing high nodes and not only VALUE_TYPE_EXACT nodes so
+/// to allow to always have a ponder move even when we fail high at root, and
+/// a long PV to print that is important for position analysis.
 
-    StateInfo state[MAX_PLY_PLUS_2], *st = state;
-    TTEntry* tte;
-    int ply = 1;
-    Move m = pv[0];
-
-    assert(m != MOVE_NONE && pos.is_pseudo_legal(m));
-
-    pv.clear();
-    pv.push_back(m);
-    pos.do_move(m, *st++);
-
-    while (   (tte = TT.probe(pos.key())) != NULL
-           && tte->move() != MOVE_NONE
-           && pos.is_pseudo_legal(tte->move())
-           && pos.pl_move_is_legal(tte->move(), pos.pinned_pieces())
-           && ply < MAX_PLY
-           && (!pos.is_draw<false>() || ply < 2))
-    {
-        pv.push_back(tte->move());
-        pos.do_move(tte->move(), *st++);
-        ply++;
-    }
-    pv.push_back(MOVE_NONE);
+void RootMove::extract_pv_from_tt(Position& pos) {
+
+  StateInfo state[MAX_PLY_PLUS_2], *st = state;
+  TTEntry* tte;
+  int ply = 1;
+  Move m = pv[0];
 
-    do pos.undo_move(pv[--ply]); while (ply);
+  assert(m != MOVE_NONE && pos.is_pseudo_legal(m));
+
+  pv.clear();
+  pv.push_back(m);
+  pos.do_move(m, *st++);
+
+  while (   (tte = TT.probe(pos.key())) != NULL
+         && tte->move() != MOVE_NONE
+         && pos.is_pseudo_legal(tte->move())
+         && pos.pl_move_is_legal(tte->move(), pos.pinned_pieces())
+         && ply < MAX_PLY
+         && (!pos.is_draw<false>() || ply < 2))
+  {
+      pv.push_back(tte->move());
+      pos.do_move(tte->move(), *st++);
+      ply++;
   }
+  pv.push_back(MOVE_NONE);
 
+  do pos.undo_move(pv[--ply]); while (ply);
+}
 
-  // insert_pv_in_tt() is called at the end of a search iteration, and inserts
-  // the PV back into the TT. This makes sure the old PV moves are searched
-  // first, even if the old TT entries have been overwritten.
 
-  void RootMove::insert_pv_in_tt(Position& pos) {
+/// RootMove::insert_pv_in_tt() is called at the end of a search iteration, and
+/// inserts the PV back into the TT. This makes sure the old PV moves are searched
+/// first, even if the old TT entries have been overwritten.
 
-    StateInfo state[MAX_PLY_PLUS_2], *st = state;
-    TTEntry* tte;
-    Key k;
-    Value v, m = VALUE_NONE;
-    int ply = 0;
+void RootMove::insert_pv_in_tt(Position& pos) {
 
-    assert(pv[ply] != MOVE_NONE && pos.is_pseudo_legal(pv[ply]));
+  StateInfo state[MAX_PLY_PLUS_2], *st = state;
+  TTEntry* tte;
+  Key k;
+  Value v, m = VALUE_NONE;
+  int ply = 0;
 
-    do {
-        k = pos.key();
-        tte = TT.probe(k);
+  assert(pv[ply] != MOVE_NONE && pos.is_pseudo_legal(pv[ply]));
 
-        // Don't overwrite existing correct entries
-        if (!tte || tte->move() != pv[ply])
-        {
-            v = (pos.in_check() ? VALUE_NONE : evaluate(pos, m));
-            TT.store(k, VALUE_NONE, VALUE_TYPE_NONE, DEPTH_NONE, pv[ply], v, m);
-        }
-        pos.do_move(pv[ply], *st++);
+  do {
+      k = pos.key();
+      tte = TT.probe(k);
 
-    } while (pv[++ply] != MOVE_NONE);
+      // Don't overwrite existing correct entries
+      if (!tte || tte->move() != pv[ply])
+      {
+          v = (pos.in_check() ? VALUE_NONE : evaluate(pos, m));
+          TT.store(k, VALUE_NONE, VALUE_TYPE_NONE, DEPTH_NONE, pv[ply], v, m);
+      }
+      pos.do_move(pv[ply], *st++);
 
-    do pos.undo_move(pv[--ply]); while (ply);
-  }
+  } while (pv[++ply] != MOVE_NONE);
 
-} // namespace
+  do pos.undo_move(pv[--ply]); while (ply);
+}
 
 
 /// Thread::idle_loop() is where the thread is parked when it has no work to do.
@@ -1984,7 +1951,6 @@ void check_time() {
                    || stillAtFirstMove;
 
   if (   (Limits.use_time_management() && noMoreTime)
-      || (Limits.maxTime && e >= Limits.maxTime)
-         /* missing nodes limit */ ) // FIXME
+      || (Limits.maxTime && e >= Limits.maxTime))
       Signals.stop = true;
 }