]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Use a flag in TT to track null search values
[stockfish] / src / search.cpp
index f929501648c2949009b0d9463a3fca92a353af49..cd874ff816156bfcfd036c65de3443a917325341 100644 (file)
@@ -1,7 +1,7 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
   Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2009 Marco Costalba
+  Copyright (C) 2008-2010 Marco Costalba, Joona Kiiski, Tord Romstad
 
   Stockfish is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -84,10 +84,10 @@ namespace {
     void put_threads_to_sleep();
     void idle_loop(int threadID, SplitPoint* waitSp);
     bool split(const Position& pos, SearchStack* ss, int ply, Value* alpha, const Value beta, Value* bestValue,
-               Depth depth, int* moves, MovePicker* mp, int master, bool pvNode);
+               Depth depth, bool mateThreat, int* moves, MovePicker* mp, int master, bool pvNode);
 
   private:
-    friend void poll(SearchStack ss[], int ply);
+    friend void poll();
 
     int ActiveThreads;
     volatile bool AllThreadsShouldExit, AllThreadsShouldSleep;
@@ -232,7 +232,7 @@ namespace {
   const Value EasyMoveMargin = Value(0x200);
 
   // Last seconds noise filtering (LSN)
-  const bool UseLSNFiltering = true;
+  const bool UseLSNFiltering = false;
   const int LSNTime = 4000; // In milliseconds
   const Value LSNValue = value_from_centipawns(200);
   bool loseOnTime = false;
@@ -254,13 +254,10 @@ namespace {
   int MultiPV;
 
   // Time managment variables
-  int RootMoveNumber, SearchStartTime, MaxNodes, MaxDepth;
-  int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime, ExactMaxTime;
+  int SearchStartTime, MaxNodes, MaxDepth, MaxSearchTime;
+  int AbsoluteMaxSearchTime, ExtraSearchTime, ExactMaxTime;
   bool UseTimeManagement, InfiniteSearch, PonderSearch, StopOnPonderhit;
-  bool AbortSearch, Quit, AspirationFailLow;
-
-  // Show current line?
-  bool ShowCurrentLine;
+  bool FirstRootMove, AbortSearch, Quit, AspirationFailLow;
 
   // Log file
   bool UseLogFile;
@@ -282,7 +279,7 @@ namespace {
   /// Local functions
 
   Value id_loop(const Position& pos, Move searchMoves[]);
-  Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value& oldAlpha, Value& beta);
+  Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value* alphaPtr, Value* betaPtr);
   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, Depth depth, int ply, bool allowNullmove, int threadID, Move excludedMove = MOVE_NONE);
   Value qsearch(Position& pos, SearchStack ss[], Value alpha, Value beta, Depth depth, int ply, int threadID);
@@ -305,7 +302,7 @@ namespace {
 
   int current_search_time();
   int nps();
-  void poll(SearchStack ss[], int ply);
+  void poll();
   void ponderhit();
   void wait_for_stop_or_ponderhit();
   void init_ss_array(SearchStack ss[]);
@@ -373,6 +370,7 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
 
   // Initialize global search variables
   StopOnPonderhit = AbortSearch = Quit = AspirationFailLow = false;
+  MaxSearchTime = AbsoluteMaxSearchTime = ExtraSearchTime = 0;
   NodesSincePoll = 0;
   TM.resetNodeCounters();
   SearchStartTime = get_system_time();
@@ -424,7 +422,6 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
 
   MinimumSplitDepth       = get_option_value_int("Minimum Split Depth") * OnePly;
   MaxThreadsPerSplitPoint = get_option_value_int("Maximum Number of Threads per Split Point");
-  ShowCurrentLine         = get_option_value_bool("UCI_ShowCurrLine");
   MultiPV                 = get_option_value_int("MultiPV");
   Chess960                = get_option_value_bool("UCI_Chess960");
   UseLogFile              = get_option_value_bool("Use Search Log");
@@ -440,10 +437,6 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
   {
       TM.set_active_threads(newActiveThreads);
       init_eval(TM.active_threads());
-      // HACK: init_eval() destroys the static castleRightsMask[] array in the
-      // Position class. The below line repairs the damage.
-      Position p(pos.to_fen());
-      assert(pos.is_ok());
   }
 
   // Wake up sleeping threads
@@ -647,8 +640,6 @@ namespace {
         // Initialize iteration
         Iteration++;
         BestMoveChangesByIteration[Iteration] = 0;
-        if (Iteration <= 5)
-            ExtraSearchTime = 0;
 
         cout << "info depth " << Iteration << endl;
 
@@ -665,8 +656,8 @@ namespace {
             beta  = Min(ValueByIteration[Iteration - 1] + AspirationDelta,  VALUE_INFINITE);
         }
 
-        // Search to the current depth, rml is updated and sorted
-        value = root_search(p, ss, rml, alpha, beta);
+        // Search to the current depth, rml is updated and sorted, alpha and beta could change
+        value = root_search(p, ss, rml, &alpha, &beta);
 
         // Write PV to transposition table, in case the relevant entries have
         // been overwritten during the search.
@@ -786,18 +777,21 @@ namespace {
   // scheme, prints some information to the standard output and handles
   // the fail low/high loops.
 
-  Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value& oldAlpha, Value& beta) {
+  Value root_search(Position& pos, SearchStack ss[], RootMoveList& rml, Value* alphaPtr, Value* betaPtr) {
 
     EvalInfo ei;
     StateInfo st;
+    CheckInfo ci(pos);
     int64_t nodes;
     Move move;
     Depth depth, ext, newDepth;
-    Value value, alpha;
+    Value value, alpha, beta;
     bool isCheck, moveIsCheck, captureOrPromotion, dangerous;
-    int researchCount = 0;
-    CheckInfo ci(pos);
-    alpha = oldAlpha;
+    int researchCountFH, researchCountFL;
+
+    researchCountFH = researchCountFL = 0;
+    alpha = *alphaPtr;
+    beta = *betaPtr;
     isCheck = pos.is_check();
 
     // Step 1. Initialize node and poll (omitted at root, but I can see no good reason for this, FIXME)
@@ -828,8 +822,8 @@ namespace {
         // Step 10. Loop through all moves in the root move list
         for (int i = 0; i <  rml.move_count() && !AbortSearch; i++)
         {
-            // This is used by time management and starts from 1
-            RootMoveNumber = i + 1;
+            // This is used by time management
+            FirstRootMove = (i == 0);
 
             // Save the current node count before the move is searched
             nodes = TM.nodes_searched();
@@ -843,7 +837,7 @@ namespace {
 
             if (current_search_time() >= 1000)
                 cout << "info currmove " << move
-                     << " currmovenumber " << RootMoveNumber << endl;
+                     << " currmovenumber " << i + 1 << endl;
 
             moveIsCheck = pos.move_is_check(move);
             captureOrPromotion = pos.move_is_capture_or_promotion(move);
@@ -929,8 +923,8 @@ namespace {
                 print_pv_info(pos, ss, alpha, beta, value);
 
                 // Prepare for a research after a fail high, each time with a wider window
-                researchCount++;
-                beta = Min(beta + AspirationDelta * (1 << researchCount), VALUE_INFINITE);
+                *betaPtr = beta = Min(beta + AspirationDelta * (1 << researchCountFH), VALUE_INFINITE);
+                researchCountFH++;
 
             } // End of fail high loop
 
@@ -950,6 +944,7 @@ namespace {
             rml.set_move_nodes(i, TM.nodes_searched() - nodes);
 
             assert(value >= -VALUE_INFINITE && value <= VALUE_INFINITE);
+            assert(value < beta);
 
             // Step 17. Check for new best move
             if (value <= alpha && i >= MultiPV)
@@ -975,8 +970,7 @@ namespace {
                     // Print information to the standard output
                     print_pv_info(pos, ss, alpha, beta, value);
 
-                    // Raise alpha to setup proper non-pv search upper bound, note
-                    // that we can end up with alpha >= beta and so get a fail high.
+                    // Raise alpha to setup proper non-pv search upper bound
                     if (value > alpha)
                         alpha = value;
                 }
@@ -1002,22 +996,21 @@ namespace {
                 }
             } // PV move or new best move
 
-            assert(alpha >= oldAlpha);
+            assert(alpha >= *alphaPtr);
 
-            AspirationFailLow = (alpha == oldAlpha);
+            AspirationFailLow = (alpha == *alphaPtr);
 
             if (AspirationFailLow && StopOnPonderhit)
                 StopOnPonderhit = false;
         }
 
         // Can we exit fail low loop ?
-        if (AbortSearch || alpha > oldAlpha)
+        if (AbortSearch || !AspirationFailLow)
             break;
 
         // Prepare for a research after a fail low, each time with a wider window
-        researchCount++;
-        alpha = Max(alpha - AspirationDelta * (1 << researchCount), -VALUE_INFINITE);
-        oldAlpha = alpha;
+        *alphaPtr = alpha = Max(alpha - AspirationDelta * (1 << researchCountFL), -VALUE_INFINITE);
+        researchCountFL++;
 
     } // Fail low loop
 
@@ -1104,14 +1097,13 @@ namespace {
         tte = TT.retrieve(pos.get_key());
     }
 
-    // Step 10. Loop through moves
-    // Loop through all legal moves until no moves remain or a beta cutoff occurs
-
     // Initialize a MovePicker object for the current position
     mateThreat = pos.has_mate_threat(opposite_color(pos.side_to_move()));
     MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply]);
     CheckInfo ci(pos);
 
+    // Step 10. Loop through moves
+    // Loop through all legal moves until no moves remain or a beta cutoff occurs
     while (   alpha < beta
            && (move = mp.get_next_move()) != MOVE_NONE
            && !TM.thread_should_stop(threadID))
@@ -1219,7 +1211,7 @@ namespace {
           && !AbortSearch
           && !TM.thread_should_stop(threadID)
           && TM.split(pos, ss, ply, &alpha, beta, &bestValue,
-                      depth, &moveCount, &mp, threadID, true))
+                      depth, mateThreat, &moveCount, &mp, threadID, true))
           break;
     }
 
@@ -1328,12 +1320,12 @@ namespace {
     }
 
     // Step 6. Razoring
-    if (   !value_is_mate(beta)
+    if (    refinedValue < beta - razor_margin(depth)
+        &&  ttMove == MOVE_NONE
+        &&  ss[ply - 1].currentMove != MOVE_NULL
+        &&  depth < RazorDepth
         && !isCheck
-        && depth < RazorDepth
-        && refinedValue < beta - razor_margin(depth)
-        && ss[ply - 1].currentMove != MOVE_NULL
-        && ttMove == MOVE_NONE
+        && !value_is_mate(beta)
         && !pos.has_pawn_on_7th(pos.side_to_move()))
     {
         Value rbeta = beta - razor_margin(depth);
@@ -1346,11 +1338,13 @@ namespace {
 
     // Step 7. Static null move pruning
     // We're betting that the opponent doesn't have a move that will reduce
-    // the score by more than fuility_margin(depth) if we do a null move.
-    if (  !isCheck
-        && allowNullmove
-        && depth < RazorDepth
-        && refinedValue - futility_margin(depth, 0) >= beta)
+    // the score by more than futility_margin(depth) if we do a null move.
+    if (    allowNullmove
+        &&  depth < RazorDepth
+        && !isCheck
+        && !value_is_mate(beta)
+        &&  ok_to_do_nullmove(pos)
+        &&  refinedValue >= beta + futility_margin(depth, 0))
         return refinedValue - futility_margin(depth, 0);
 
     // Step 8. Null move search with verification search
@@ -1366,8 +1360,6 @@ namespace {
     {
         ss[ply].currentMove = MOVE_NULL;
 
-        pos.do_null_move(st);
-
         // Null move dynamic reduction based on depth
         int R = 3 + (depth >= 5 * OnePly ? depth / 8 : 0);
 
@@ -1375,19 +1367,30 @@ namespace {
         if (refinedValue - beta > PawnValueMidgame)
             R++;
 
+        pos.do_null_move(st);
+
         nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID);
 
         pos.undo_null_move();
 
         if (nullValue >= beta)
         {
-            if (depth < 6 * OnePly)
-                return beta;
+            // Do not return unproven mate scores
+            if (nullValue >= value_mate_in(PLY_MAX))
+                nullValue = beta;
+
+            // Do zugzwang verification search for high depths, don't store in TT
+            // if search was stopped.
+            if (   (   depth < 6 * OnePly
+                    || search(pos, ss, beta, depth-5*OnePly, ply, false, threadID) >= beta)
+                && !AbortSearch
+                && !TM.thread_should_stop(threadID))
+            {
+                assert(value_to_tt(nullValue, ply) == nullValue);
 
-            // Do zugzwang verification search
-            Value v = search(pos, ss, beta, depth-5*OnePly, ply, false, threadID);
-            if (v >= beta)
-                return beta;
+                TT.store(posKey, nullValue, VALUE_TYPE_NS_LO, depth, MOVE_NONE);
+                return nullValue;
+            }
         } else {
             // The null move failed low, which means that we may be faced with
             // some kind of threat. If the previous move was reduced, check if
@@ -1417,13 +1420,12 @@ namespace {
         tte = TT.retrieve(posKey);
     }
 
-    // Step 10. Loop through moves
-    // Loop through all legal moves until no moves remain or a beta cutoff occurs
-
     // Initialize a MovePicker object for the current position
     MovePicker mp = MovePicker(pos, ttMove, depth, H, &ss[ply], beta);
     CheckInfo ci(pos);
 
+    // Step 10. Loop through moves
+    // Loop through all legal moves until no moves remain or a beta cutoff occurs
     while (   bestValue < beta
            && (move = mp.get_next_move()) != MOVE_NONE
            && !TM.thread_should_stop(threadID))
@@ -1446,7 +1448,7 @@ namespace {
       if (   depth >= SingularExtensionDepthAtNonPVNodes
           && tte
           && move == tte->move()
-          && !excludedMove // Do not allow recursive single-reply search
+          && !excludedMove // Do not allow recursive singular extension search
           && ext < OnePly
           && is_lower_bound(tte->type())
           && tte->depth() >= depth - 3 * OnePly)
@@ -1496,8 +1498,8 @@ namespace {
       // Step 13. Make the move
       pos.do_move(move, st, ci, moveIsCheck);
 
-      // Step 14. Reduced search
-      // if the move fails high will be re-searched at full depth.
+      // Step 14. Reduced search, if the move fails high
+      // will be re-searched at full depth.
       bool doFullDepthSearch = true;
 
       if (    depth >= 3*OnePly
@@ -1546,16 +1548,16 @@ namespace {
           && !AbortSearch
           && !TM.thread_should_stop(threadID)
           && TM.split(pos, ss, ply, NULL, beta, &bestValue,
-                      depth, &moveCount, &mp, threadID, false))
+                      depth, mateThreat, &moveCount, &mp, threadID, false))
           break;
     }
 
     // Step 19. Check for mate and stalemate
-    // All legal moves have been searched and if there were
+    // All legal moves have been searched and if there are
     // no legal moves, it must be mate or stalemate.
-    // If one move was excluded return fail low.
+    // If one move was excluded return fail low score.
     if (!moveCount)
-        return excludedMove ? beta - 1 : (pos.is_check() ? value_mated_in(ply) : VALUE_DRAW);
+        return excludedMove ? beta - 1 : (isCheck ? value_mated_in(ply) : VALUE_DRAW);
 
     // Step 20. Update tables
     // If the search is not aborted, update the transposition table,
@@ -1664,7 +1666,7 @@ namespace {
         alpha = bestValue;
 
     // If we are near beta then try to get a cutoff pushing checks a bit further
-    bool deepChecks = depth == -OnePly && staticValue >= beta - PawnValueMidgame / 8;
+    bool deepChecks = (depth == -OnePly && staticValue >= beta - PawnValueMidgame / 8);
 
     // Initialize a MovePicker object for the current position, and prepare
     // to search the moves. Because the depth is <= 0 here, only captures,
@@ -1675,8 +1677,7 @@ namespace {
     enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame;
     futilityBase = staticValue + FutilityMarginQS + ei.futilityMargin[pos.side_to_move()];
 
-    // Loop through the moves until no moves remain or a beta cutoff
-    // occurs.
+    // Loop through the moves until no moves remain or a beta cutoff occurs
     while (   alpha < beta
            && (move = mp.get_next_move()) != MOVE_NONE)
     {
@@ -1745,7 +1746,7 @@ namespace {
 
     // All legal moves have been searched. A special case: If we're in check
     // and no legal moves were found, it is checkmate.
-    if (!moveCount && pos.is_check()) // Mate!
+    if (!moveCount && isCheck) // Mate!
         return value_mated_in(ply);
 
     // Update transposition table
@@ -1782,7 +1783,6 @@ namespace {
   // splitting, we don't have to repeat all this work in sp_search().  We
   // also don't need to store anything to the hash table here:  This is taken
   // care of after we return from the split point.
-  // FIXME: We are currently ignoring mateThreat flag here
 
   void sp_search(SplitPoint* sp, int threadID) {
 
@@ -1819,7 +1819,7 @@ namespace {
       captureOrPromotion = pos.move_is_capture_or_promotion(move);
 
       // Step 11. Decide the new search depth
-      ext = extension(pos, move, false, captureOrPromotion, moveIsCheck, false, false, &dangerous);
+      ext = extension(pos, move, false, captureOrPromotion, moveIsCheck, false, sp->mateThreat, &dangerous);
       newDepth = sp->depth - OnePly + ext;
 
       // Update current move
@@ -1917,7 +1917,6 @@ namespace {
   // don't have to repeat all this work in sp_search_pv().  We also don't
   // need to store anything to the hash table here: This is taken care of
   // after we return from the split point.
-  // FIXME: We are ignoring mateThreat flag!
 
   void sp_search_pv(SplitPoint* sp, int threadID) {
 
@@ -1953,7 +1952,7 @@ namespace {
       captureOrPromotion = pos.move_is_capture_or_promotion(move);
 
       // Step 11. Decide the new search depth
-      ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, false, false, &dangerous);
+      ext = extension(pos, move, true, captureOrPromotion, moveIsCheck, false, sp->mateThreat, &dangerous);
       newDepth = sp->depth - OnePly + ext;
 
       // Update current move
@@ -2052,7 +2051,7 @@ namespace {
         NodesSincePoll++;
         if (NodesSincePoll >= NodesBetweenPolls)
         {
-            poll(ss, ply);
+            poll();
             NodesSincePoll = 0;
         }
     }
@@ -2415,7 +2414,7 @@ namespace {
   // looks at the time consumed so far and decides if it's time to abort the
   // search.
 
-  void poll(SearchStack ss[], int ply) {
+  void poll() {
 
     static int lastInfoTime;
     int t = current_search_time();
@@ -2466,23 +2465,13 @@ namespace {
 
         cout << "info nodes " << TM.nodes_searched() << " nps " << nps()
              << " time " << t << " hashfull " << TT.full() << endl;
-
-        // We only support current line printing in single thread mode
-        if (ShowCurrentLine && TM.active_threads() == 1)
-        {
-            cout << "info currline";
-            for (int p = 0; p < ply; p++)
-                cout << " " << ss[p].currentMove;
-
-            cout << endl;
-        }
     }
 
     // Should we stop the search?
     if (PonderSearch)
         return;
 
-    bool stillAtFirstMove =    RootMoveNumber == 1
+    bool stillAtFirstMove =    FirstRootMove
                            && !AspirationFailLow
                            &&  t > MaxSearchTime + ExtraSearchTime;
 
@@ -2505,7 +2494,7 @@ namespace {
     int t = current_search_time();
     PonderSearch = false;
 
-    bool stillAtFirstMove =    RootMoveNumber == 1
+    bool stillAtFirstMove =    FirstRootMove
                            && !AspirationFailLow
                            &&  t > MaxSearchTime + ExtraSearchTime;
 
@@ -2894,7 +2883,7 @@ namespace {
 
   bool ThreadsManager::split(const Position& p, SearchStack* sstck, int ply,
              Value* alpha, const Value beta, Value* bestValue,
-             Depth depth, int* moves, MovePicker* mp, int master, bool pvNode) {
+             Depth depth, bool mateThreat, int* moves, MovePicker* mp, int master, bool pvNode) {
 
     assert(p.is_ok());
     assert(sstck != NULL);
@@ -2929,6 +2918,7 @@ namespace {
     splitPoint->stopRequest = false;
     splitPoint->ply = ply;
     splitPoint->depth = depth;
+    splitPoint->mateThreat = mateThreat;
     splitPoint->alpha = pvNode ? *alpha : beta - 1;
     splitPoint->beta = beta;
     splitPoint->pvNode = pvNode;