]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Vary reduction aggressiveness as a function of thinking time
[stockfish] / src / search.cpp
index 8539d66f41c6902ce7d226dbc6c0395f2a25f4a2..ab37e480fc5d50bdb10f79b6e3f08b8e2d0d1e5a 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,7 +84,7 @@ 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);
@@ -215,12 +215,14 @@ namespace {
 
   // Step 14. Reduced search
 
+  int ReductionLevel = 2; // 0 = most aggressive reductions, 7 = minimum reductions
+
   // Reduction lookup tables (initialized at startup) and their getter functions
-  int8_t    PVReductionMatrix[64][64]; // [depth][moveNumber]
-  int8_t NonPVReductionMatrix[64][64]; // [depth][moveNumber]
+  int8_t    PVReductionMatrix[8][64][64]; // [depth][moveNumber]
+  int8_t NonPVReductionMatrix[8][64][64]; // [depth][moveNumber]
 
-  inline Depth    pv_reduction(Depth d, int mn) { return (Depth)    PVReductionMatrix[Min(d / 2, 63)][Min(mn, 63)]; }
-  inline Depth nonpv_reduction(Depth d, int mn) { return (Depth) NonPVReductionMatrix[Min(d / 2, 63)][Min(mn, 63)]; }
+  inline Depth    pv_reduction(Depth d, int mn) { return (Depth)    PVReductionMatrix[ReductionLevel][Min(d / 2, 63)][Min(mn, 63)]; }
+  inline Depth nonpv_reduction(Depth d, int mn) { return (Depth) NonPVReductionMatrix[ReductionLevel][Min(d / 2, 63)][Min(mn, 63)]; }
 
   // Common adjustments
 
@@ -254,10 +256,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;
+  bool FirstRootMove, AbortSearch, Quit, AspirationFailLow;
 
   // Show current line?
   bool ShowCurrentLine;
@@ -282,7 +284,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);
@@ -373,6 +375,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();
@@ -440,10 +443,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
@@ -547,20 +546,31 @@ bool think(const Position& pos, bool infinite, bool ponder, int side_to_move,
   return !Quit;
 }
 
+// init_reduction_tables()
 
-/// init_search() is called during startup. It initializes various lookup tables
-
-void init_search() {
+void init_reduction_tables(int8_t pvTable[64][64], int8_t nonPvTable[64][64], int pvInhib, int nonPvInhib)
+{
+  double pvBase = 1.001 - log(3.0) * log(16.0) / pvInhib;
+  double nonPvBase = 1.001 - log(3.0) * log(4.0) / nonPvInhib;
 
-  // Init our reduction lookup tables
+  // Init reduction lookup tables
   for (int i = 1; i < 64; i++) // i == depth (OnePly = 1)
       for (int j = 1; j < 64; j++) // j == moveNumber
       {
-          double    pvRed = 0.5 + log(double(i)) * log(double(j)) / 6.0;
-          double nonPVRed = 0.5 + log(double(i)) * log(double(j)) / 3.0;
-          PVReductionMatrix[i][j]    = (int8_t) (   pvRed >= 1.0 ? floor(   pvRed * int(OnePly)) : 0);
-          NonPVReductionMatrix[i][j] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(OnePly)) : 0);
+          double    pvRed = pvBase    + log(double(i)) * log(double(j)) / pvInhib;
+          double nonPVRed = nonPvBase + log(double(i)) * log(double(j)) / nonPvInhib;
+
+          pvTable[i][j]    = (int8_t) (   pvRed >= 1.0 ? floor(   pvRed * int(OnePly)) : 0);
+          nonPvTable[i][j] = (int8_t) (nonPVRed >= 1.0 ? floor(nonPVRed * int(OnePly)) : 0);
       }
+}
+
+// init_search() is called during startup. It initializes various lookup tables
+
+void init_search() {
+
+  for (int i = 0; i < 8; i++)
+      init_reduction_tables(PVReductionMatrix[i], NonPVReductionMatrix[i], 4.0 * pow(1.3, i), 2.0 * pow(1.3, i));
 
   // Init futility margins array
   for (int i = 0; i < 16; i++) // i == depth (OnePly = 2)
@@ -647,8 +657,6 @@ namespace {
         // Initialize iteration
         Iteration++;
         BestMoveChangesByIteration[Iteration] = 0;
-        if (Iteration <= 5)
-            ExtraSearchTime = 0;
 
         cout << "info depth " << Iteration << endl;
 
@@ -665,8 +673,21 @@ 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);
+        // Choose optimum reduction level
+        ReductionLevel = 2;
+
+        if (UseTimeManagement)
+        {
+            int level = int(floor(log(float(MaxSearchTime) / current_search_time()) / log(2.0) + 1.0));
+            ReductionLevel = Min(Max(level, 0), 7);
+        }
+        else
+        {
+            //FIXME
+        }
+
+        // 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 +807,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 +852,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 +867,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 +953,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 +974,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 +1000,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 +1026,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
 
@@ -1219,7 +1242,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;
     }
 
@@ -1339,7 +1362,9 @@ namespace {
         Value rbeta = beta - razor_margin(depth);
         Value v = qsearch(pos, ss, rbeta-1, rbeta, Depth(0), ply, threadID);
         if (v < rbeta)
-          return v; //FIXME: Logically should be: return (v + razor_margin(depth));
+            // Logically we should return (v + razor_margin(depth)), but
+            // surprisingly this did slightly weaker in tests.
+            return v;
     }
 
     // Step 7. Static null move pruning
@@ -1379,13 +1404,17 @@ namespace {
 
         if (nullValue >= beta)
         {
+            // Do not return unproven mate scores
+            if (nullValue >= value_mate_in(PLY_MAX))
+                nullValue = beta;
+
             if (depth < 6 * OnePly)
-                return beta;
+                return nullValue;
 
             // Do zugzwang verification search
             Value v = search(pos, ss, beta, depth-5*OnePly, ply, false, threadID);
             if (v >= beta)
-                return beta;
+                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
@@ -1544,7 +1573,7 @@ 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;
     }
 
@@ -1780,7 +1809,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) {
 
@@ -1817,7 +1845,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
@@ -1915,7 +1943,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) {
 
@@ -1951,7 +1978,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
@@ -2480,7 +2507,7 @@ namespace {
     if (PonderSearch)
         return;
 
-    bool stillAtFirstMove =    RootMoveNumber == 1
+    bool stillAtFirstMove =    FirstRootMove
                            && !AspirationFailLow
                            &&  t > MaxSearchTime + ExtraSearchTime;
 
@@ -2503,7 +2530,7 @@ namespace {
     int t = current_search_time();
     PonderSearch = false;
 
-    bool stillAtFirstMove =    RootMoveNumber == 1
+    bool stillAtFirstMove =    FirstRootMove
                            && !AspirationFailLow
                            &&  t > MaxSearchTime + ExtraSearchTime;
 
@@ -2892,7 +2919,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);
@@ -2927,6 +2954,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;