]> git.sesse.net Git - stockfish/blobdiff - src/search.cpp
Revert Chess960 fix
[stockfish] / src / search.cpp
index 771963fbb98cca6d4f6ec7770f36a2c05b17658c..0e249705fa59a89e00d1f689d714bf33bbe024f9 100644 (file)
@@ -129,6 +129,7 @@ namespace {
 
     void extract_pv_from_tt(Position& pos);
     void insert_pv_in_tt(Position& pos);
+    std::string pv_info_to_uci(const Position& pos, Value alpha, Value beta, int pvLine = 0);
 
     int64_t nodes;
     Value pv_score;
@@ -148,7 +149,7 @@ namespace {
     void set_non_pv_scores(const Position& pos);
 
     void sort() { insertion_sort<RootMove, Base::iterator>(begin(), end()); }
-    void sort_multipv(int n) { insertion_sort<RootMove, Base::iterator>(begin(), begin() + n); }
+    void sort_multipv(int n) { insertion_sort<RootMove, Base::iterator>(begin(), begin() + n + 1); }
   };
 
 
@@ -253,8 +254,8 @@ namespace {
 
   // Time managment variables
   int SearchStartTime, MaxNodes, MaxDepth, ExactMaxTime;
-  bool UseTimeManagement, InfiniteSearch, PonderSearch, StopOnPonderhit;
-  bool FirstRootMove, AbortSearch, Quit, AspirationFailLow;
+  bool UseTimeManagement, InfiniteSearch, Pondering, StopOnPonderhit;
+  bool FirstRootMove, StopRequest, QuitRequest, AspirationFailLow;
   TimeManager TimeMgr;
 
   // Log file
@@ -266,6 +267,7 @@ namespace {
 
   // Node counters, used only by thread[0] but try to keep in different cache
   // lines (64 bytes each) from the heavy multi-thread read accessed variables.
+  bool SendSearchedNodes;
   int NodesSincePoll;
   int NodesBetweenPolls = 30000;
 
@@ -274,7 +276,7 @@ namespace {
 
   /// Local functions
 
-  Value id_loop(Position& pos, Move searchMoves[]);
+  Move id_loop(Position& pos, Move searchMoves[], Move* ponderMove);
   Value root_search(Position& pos, SearchStack* ss, Value alpha, Value beta, Depth depth, RootMoveList& rml);
 
   template <NodeType PvNode, bool SpNode>
@@ -309,10 +311,8 @@ namespace {
   std::string value_to_uci(Value v);
   int nps(const Position& pos);
   void poll(const Position& pos);
-  void ponderhit();
   void wait_for_stop_or_ponderhit();
   void init_ss_array(SearchStack* ss, int size);
-  void print_pv_info(const Position& pos, Move pv[], Value alpha, Value beta, Value value);
 
 #if !defined(_MSC_VER)
   void* init_thread(void* threadID);
@@ -401,14 +401,14 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
            int movesToGo, int maxDepth, int maxNodes, int maxTime, Move searchMoves[]) {
 
   // Initialize global search variables
-  StopOnPonderhit = AbortSearch = Quit = AspirationFailLow = false;
+  StopOnPonderhit = StopRequest = QuitRequest = AspirationFailLow = SendSearchedNodes = false;
   NodesSincePoll = 0;
   SearchStartTime = get_system_time();
   ExactMaxTime = maxTime;
   MaxDepth = maxDepth;
   MaxNodes = maxNodes;
   InfiniteSearch = infinite;
-  PonderSearch = ponder;
+  Pondering = ponder;
   UseTimeManagement = !ExactMaxTime && !MaxDepth && !MaxNodes && !InfiniteSearch;
 
   // Look for a book move, only during games, not tests
@@ -420,11 +420,11 @@ 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 (PonderSearch)
+          if (Pondering)
               wait_for_stop_or_ponderhit();
 
           cout << "bestmove " << bookMove << endl;
-          return true;
+          return !QuitRequest;
       }
   }
 
@@ -451,10 +451,7 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
   MultiPV                   = Options["MultiPV"].value<int>();
   UseLogFile                = Options["Use Search Log"].value<bool>();
 
-  if (UseLogFile)
-      LogFile.open(Options["Search Log Filename"].value<std::string>().c_str(), std::ios::out | std::ios::app);
-
-  read_weights(pos.side_to_move());
+  read_evaluation_uci_options(pos.side_to_move());
 
   // Set the number of active threads
   ThreadsMgr.read_uci_options();
@@ -483,23 +480,57 @@ bool think(Position& pos, bool infinite, bool ponder, int time[], int increment[
 
   // Write search information to log file
   if (UseLogFile)
-      LogFile << "Searching: " << pos.to_fen() << endl
-              << "infinite: "  << infinite
-              << " ponder: "   << ponder
-              << " time: "     << myTime
+  {
+      std::string name = Options["Search Log Filename"].value<std::string>();
+      LogFile.open(name.c_str(), std::ios::out | std::ios::app);
+
+      LogFile << "Searching: "  << pos.to_fen()
+              << "\ninfinite: " << infinite
+              << " ponder: "    << ponder
+              << " time: "      << myTime
               << " increment: " << myIncrement
               << " moves to go: " << movesToGo << endl;
+  }
 
   // We're ready to start thinking. Call the iterative deepening loop function
-  id_loop(pos, searchMoves);
+  Move ponderMove = MOVE_NONE;
+  Move bestMove = id_loop(pos, searchMoves, &ponderMove);
+
+  // Print final search statistics
+  cout << "info nodes " << pos.nodes_searched()
+       << " nps " << nps(pos)
+       << " time " << current_search_time() << endl;
 
   if (UseLogFile)
+  {
+      LogFile << "\nNodes: " << pos.nodes_searched()
+              << "\nNodes/second: " << nps(pos)
+              << "\nBest move: " << move_to_san(pos, bestMove);
+
+      StateInfo st;
+      pos.do_move(bestMove, st);
+      LogFile << "\nPonder move: "
+              << move_to_san(pos, ponderMove) // Works also with MOVE_NONE
+              << endl;
+
+      // Return from think() with unchanged position
+      pos.undo_move(bestMove);
+
       LogFile.close();
+  }
 
   // This makes all the threads to go to sleep
   ThreadsMgr.set_active_threads(1);
 
-  return !Quit;
+  // 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))
+      wait_for_stop_or_ponderhit();
+
+  // Could be both MOVE_NONE when searching on a stalemate position
+  cout << "bestmove " << bestMove << " ponder " << ponderMove << endl;
+
+  return !QuitRequest;
 }
 
 
@@ -510,35 +541,26 @@ namespace {
   // been consumed, the user stops the search, or the maximum search depth is
   // reached.
 
-  Value id_loop(Position& pos, Move searchMoves[]) {
+  Move id_loop(Position& pos, Move searchMoves[], Move* ponderMove) {
 
     SearchStack ss[PLY_MAX_PLUS_2];
     Depth depth;
     Move EasyMove = MOVE_NONE;
     Value value, alpha = -VALUE_INFINITE, beta = VALUE_INFINITE;
 
-    // Moves to search are verified, copied, scored and sorted
+    // Moves to search are verified, scored and sorted
     RootMoveList rml(pos, searchMoves);
 
     // Handle special case of searching on a mate/stale position
     if (rml.size() == 0)
     {
-        if (PonderSearch)
-            wait_for_stop_or_ponderhit();
+        Value s = (pos.is_check() ? -VALUE_MATE : VALUE_DRAW);
 
-        return pos.is_check() ? -VALUE_MATE : VALUE_DRAW;
-    }
+        cout << "info depth " << 1
+             << " score " << value_to_uci(s) << endl;
 
-    // Print RootMoveList startup scoring to the standard output,
-    // so to output information also for iteration 1.
-    cout << set960(pos.is_chess960()) // Is enough to set once at the beginning
-         << "info depth " << 1
-         << "\ninfo depth " << 1
-         << " score " << value_to_uci(rml[0].pv_score)
-         << " time " << current_search_time()
-         << " nodes " << pos.nodes_searched()
-         << " nps " << nps(pos)
-         << " pv " << rml[0].pv[0] << "\n";
+        return MOVE_NONE;
+    }
 
     // Initialize
     TT.new_search();
@@ -547,6 +569,11 @@ namespace {
     ValueByIteration[1] = rml[0].pv_score;
     Iteration = 1;
 
+    // Send initial RootMoveList scoring (iteration 1)
+    cout << set960(pos.is_chess960()) // Is enough to set once at the beginning
+         << "info depth " << Iteration
+         << "\n" << rml[0].pv_info_to_uci(pos, alpha, beta) << endl;
+
     // Is one move significantly better than others after initial scoring ?
     if (   rml.size() == 1
         || rml[0].pv_score > rml[1].pv_score + EasyMoveMargin)
@@ -579,7 +606,7 @@ namespace {
         // Search to the current depth, rml is updated and sorted
         value = root_search(pos, ss, alpha, beta, depth, rml);
 
-        if (AbortSearch)
+        if (StopRequest)
             break; // Value cannot be trusted. Break out immediately!
 
         //Save info about search result
@@ -627,7 +654,7 @@ namespace {
 
             if (stopSearch)
             {
-                if (PonderSearch)
+                if (Pondering)
                     StopOnPonderhit = true;
                 else
                     break;
@@ -638,43 +665,8 @@ namespace {
             break;
     }
 
-    // If we are pondering or in infinite search, we shouldn't print the
-    // best move before we are told to do so.
-    if (!AbortSearch && (PonderSearch || InfiniteSearch))
-        wait_for_stop_or_ponderhit();
-    else
-        // Print final search statistics
-        cout << "info nodes " << pos.nodes_searched()
-             << " nps " << nps(pos)
-             << " time " << current_search_time() << endl;
-
-    // Print the best move and the ponder move to the standard output
-    cout << "bestmove " << rml[0].pv[0];
-
-    if (rml[0].pv[1] != MOVE_NONE)
-        cout << " ponder " << rml[0].pv[1];
-
-    cout << endl;
-
-    if (UseLogFile)
-    {
-        if (dbg_show_mean)
-            dbg_print_mean(LogFile);
-
-        if (dbg_show_hit_rate)
-            dbg_print_hit_rate(LogFile);
-
-        LogFile << "\nNodes: " << pos.nodes_searched()
-                << "\nNodes/second: " << nps(pos)
-                << "\nBest move: " << move_to_san(pos, rml[0].pv[0]);
-
-        StateInfo st;
-        pos.do_move(rml[0].pv[0], st);
-        LogFile << "\nPonder move: "
-                << move_to_san(pos, rml[0].pv[1]) // Works also with MOVE_NONE
-                << endl;
-    }
-    return rml[0].pv_score;
+    *ponderMove = rml[0].pv[1];
+    return rml[0].pv[0];
   }
 
 
@@ -724,7 +716,7 @@ namespace {
         rml.sort();
 
         // Step 10. Loop through all moves in the root move list
-        for (int i = 0; i < (int)rml.size() && !AbortSearch; i++)
+        for (int i = 0; i < (int)rml.size() && !StopRequest; i++)
         {
             // This is used by time management
             FirstRootMove = (i == 0);
@@ -732,6 +724,16 @@ namespace {
             // Save the current node count before the move is searched
             nodes = pos.nodes_searched();
 
+            // If it's time to send nodes info, do it here where we have the
+            // correct accumulated node counts searched by each thread.
+            if (SendSearchedNodes)
+            {
+                SendSearchedNodes = false;
+                cout << "info nodes " << nodes
+                     << " nps " << nps(pos)
+                     << " time " << current_search_time() << endl;
+            }
+
             // Pick the next root move, and print the move and the move number to
             // the standard output.
             move = ss->currentMove = rml[i].pv[0];
@@ -811,7 +813,7 @@ namespace {
                 pos.undo_move(move);
 
                 // Can we exit fail high loop ?
-                if (AbortSearch || value < beta)
+                if (StopRequest || value < beta)
                     break;
 
                 // We are failing high and going to do a research. It's important to update
@@ -820,8 +822,8 @@ namespace {
                 rml[i].pv_score = value;
                 rml[i].extract_pv_from_tt(pos);
 
-                // Print information to the standard output
-                print_pv_info(pos, rml[i].pv, alpha, beta, value);
+                // Inform GUI that PV has changed
+                cout << rml[i].pv_info_to_uci(pos, alpha, beta) << endl;
 
                 // Prepare for a research after a fail high, each time with a wider window
                 beta = Min(beta + AspirationDelta * (1 << researchCountFH), VALUE_INFINITE);
@@ -834,7 +836,7 @@ namespace {
             // ran out of time. In this case, the return value of the search cannot
             // be trusted, and we break out of the loop without updating the best
             // move and/or PV.
-            if (AbortSearch)
+            if (StopRequest)
                 break;
 
             // Remember searched nodes counts for this move
@@ -855,41 +857,29 @@ namespace {
                 rml[i].pv_score = value;
                 rml[i].extract_pv_from_tt(pos);
 
-                if (MultiPV == 1)
-                {
-                    // We record how often the best move has been changed in each
-                    // iteration. This information is used for time managment: When
-                    // the best move changes frequently, we allocate some more time.
-                    if (i > 0)
-                        BestMoveChangesByIteration[Iteration]++;
+                // We record how often the best move has been changed in each
+                // iteration. This information is used for time managment: When
+                // the best move changes frequently, we allocate some more time.
+                if (MultiPV == 1 && i > 0)
+                    BestMoveChangesByIteration[Iteration]++;
 
-                    // Print information to the standard output
-                    print_pv_info(pos, rml[i].pv, alpha, beta, value);
+                // Inform GUI that PV has changed, in case of multi-pv UCI protocol
+                // requires we send all the PV lines properly sorted.
+                rml.sort_multipv(i);
 
+                for (int j = 0; j < Min(MultiPV, (int)rml.size()); j++)
+                    cout << rml[j].pv_info_to_uci(pos, alpha, beta, j) << endl;
+
+                // Update alpha. In multi-pv we don't use aspiration window
+                if (MultiPV == 1)
+                {
                     // Raise alpha to setup proper non-pv search upper bound
                     if (value > alpha)
                         alpha = value;
                 }
-                else // MultiPV > 1
-                {
-                    rml.sort_multipv(i);
-                    for (int j = 0; j < Min(MultiPV, (int)rml.size()); j++)
-                    {
-                        cout << "info multipv " << j + 1
-                             << " score " << value_to_uci(rml[j].pv_score)
-                             << " depth " << (j <= i ? Iteration : Iteration - 1)
-                             << " time " << current_search_time()
-                             << " nodes " << pos.nodes_searched()
-                             << " nps " << nps(pos)
-                             << " pv ";
-
-                        for (int k = 0; rml[j].pv[k] != MOVE_NONE && k < PLY_MAX; k++)
-                            cout << rml[j].pv[k] << " ";
-
-                        cout << endl;
-                    }
+                else // Set alpha equal to minimum score among the PV lines
                     alpha = rml[Min(i, MultiPV - 1)].pv_score;
-                }
+
             } // PV move or new best move
 
             assert(alpha >= oldAlpha);
@@ -898,10 +888,11 @@ namespace {
 
             if (AspirationFailLow && StopOnPonderhit)
                 StopOnPonderhit = false;
-        }
+
+        } // Root moves loop
 
         // Can we exit fail low loop ?
-        if (AbortSearch || !AspirationFailLow)
+        if (StopRequest || !AspirationFailLow)
             break;
 
         // Prepare for a research after a fail low, each time with a wider window
@@ -915,7 +906,7 @@ namespace {
 
     // Write PV lines to transposition table, in case the relevant entries
     // have been overwritten during the search.
-    for (int i = 0; i < MultiPV; i++)
+    for (int i = 0; i < Min(MultiPV, (int)rml.size()); i++)
         rml[i].insert_pv_in_tt(pos);
 
     return alpha;
@@ -978,7 +969,7 @@ namespace {
     }
 
     // Step 2. Check for aborted search and immediate draw
-    if (   AbortSearch
+    if (   StopRequest
         || ThreadsMgr.cutoff_at_splitpoint(threadID)
         || pos.is_draw()
         || ply >= PLY_MAX - 1)
@@ -1366,7 +1357,7 @@ split_point_start: // At split points actual search starts from here
           && ThreadsMgr.active_threads() > 1
           && bestValue < beta
           && ThreadsMgr.available_thread_exists(threadID)
-          && !AbortSearch
+          && !StopRequest
           && !ThreadsMgr.cutoff_at_splitpoint(threadID)
           && Iteration <= 99)
           ThreadsMgr.split<FakeSplit>(pos, ss, ply, &alpha, beta, &bestValue, depth,
@@ -1383,7 +1374,7 @@ split_point_start: // At split points actual search starts from here
     // Step 20. Update tables
     // If the search is not aborted, update the transposition table,
     // history counters, and killer moves.
-    if (!SpNode && !AbortSearch && !ThreadsMgr.cutoff_at_splitpoint(threadID))
+    if (!SpNode && !StopRequest && !ThreadsMgr.cutoff_at_splitpoint(threadID))
     {
         move = bestValue <= oldAlpha ? MOVE_NONE : ss->bestMove;
         vt   = bestValue <= oldAlpha ? VALUE_TYPE_UPPER
@@ -1939,12 +1930,21 @@ split_point_start: // At split points actual search starts from here
   }
 
 
-  // current_search_time() returns the number of milliseconds which have passed
-  // since the beginning of the current search.
+  // init_ss_array() does a fast reset of the first entries of a SearchStack
+  // array and of all the excludedMove and skipNullMove entries.
 
-  int current_search_time() {
+  void init_ss_array(SearchStack* ss, int size) {
 
-    return get_system_time() - SearchStartTime;
+    for (int i = 0; i < size; i++, ss++)
+    {
+        ss->excludedMove = MOVE_NONE;
+        ss->skipNullMove = false;
+        ss->reduction = DEPTH_ZERO;
+        ss->sp = NULL;
+
+        if (i < 3)
+            ss->killers[0] = ss->killers[1] = ss->mateKiller = MOVE_NONE;
+    }
   }
 
 
@@ -1967,7 +1967,17 @@ split_point_start: // At split points actual search starts from here
     return s.str();
   }
 
-  // nps() computes the current nodes/second count.
+
+  // current_search_time() returns the number of milliseconds which have passed
+  // since the beginning of the current search.
+
+  int current_search_time() {
+
+    return get_system_time() - SearchStartTime;
+  }
+
+
+  // nps() computes the current nodes/second count
 
   int nps(const Position& pos) {
 
@@ -1996,18 +2006,28 @@ split_point_start: // At split points actual search starts from here
 
         if (command == "quit")
         {
-            AbortSearch = true;
-            PonderSearch = false;
-            Quit = true;
+            // Quit the program as soon as possible
+            Pondering = false;
+            QuitRequest = StopRequest = true;
             return;
         }
         else if (command == "stop")
         {
-            AbortSearch = true;
-            PonderSearch = false;
+            // Stop calculating as soon as possible, but still send the "bestmove"
+            // and possibly the "ponder" token when finishing the search.
+            Pondering = false;
+            StopRequest = true;
         }
         else if (command == "ponderhit")
-            ponderhit();
+        {
+            // 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;
+
+            if (StopOnPonderhit)
+                StopRequest = true;
+        }
     }
 
     // Print search information
@@ -2029,12 +2049,12 @@ split_point_start: // At split points actual search starts from here
         if (dbg_show_hit_rate)
             dbg_print_hit_rate();
 
-        cout << "info nodes " << pos.nodes_searched() << " nps " << nps(pos)
-             << " time " << t << endl;
+        // Send info on searched nodes as soon as we return to root
+        SendSearchedNodes = true;
     }
 
     // Should we stop the search?
-    if (PonderSearch)
+    if (Pondering)
         return;
 
     bool stillAtFirstMove =    FirstRootMove
@@ -2044,49 +2064,10 @@ split_point_start: // At split points actual search starts from here
     bool noMoreTime =   t > TimeMgr.maximum_time()
                      || stillAtFirstMove;
 
-    if (   (Iteration >= 3 && UseTimeManagement && noMoreTime)
+    if (   (UseTimeManagement && noMoreTime)
         || (ExactMaxTime && t >= ExactMaxTime)
-        || (Iteration >= 3 && MaxNodes && pos.nodes_searched() >= MaxNodes))
-        AbortSearch = true;
-  }
-
-
-  // ponderhit() is called when the program is pondering (i.e. thinking while
-  // it's the opponent's turn to move) in order to let the engine know that
-  // it correctly predicted the opponent's move.
-
-  void ponderhit() {
-
-    int t = current_search_time();
-    PonderSearch = false;
-
-    bool stillAtFirstMove =    FirstRootMove
-                           && !AspirationFailLow
-                           &&  t > TimeMgr.available_time();
-
-    bool noMoreTime =   t > TimeMgr.maximum_time()
-                     || stillAtFirstMove;
-
-    if (Iteration >= 3 && UseTimeManagement && (noMoreTime || StopOnPonderhit))
-        AbortSearch = true;
-  }
-
-
-  // init_ss_array() does a fast reset of the first entries of a SearchStack
-  // array and of all the excludedMove and skipNullMove entries.
-
-  void init_ss_array(SearchStack* ss, int size) {
-
-    for (int i = 0; i < size; i++, ss++)
-    {
-        ss->excludedMove = MOVE_NONE;
-        ss->skipNullMove = false;
-        ss->reduction = DEPTH_ZERO;
-        ss->sp = NULL;
-
-        if (i < 3)
-            ss->killers[0] = ss->killers[1] = ss->mateKiller = MOVE_NONE;
-    }
+        || (MaxNodes && pos.nodes_searched() >= MaxNodes)) // FIXME
+        StopRequest = true;
   }
 
 
@@ -2095,7 +2076,7 @@ split_point_start: // At split points actual search starts from here
   // the UCI protocol: When pondering, the engine is not allowed to give a
   // "bestmove" before the GUI sends it a "stop" or "ponderhit" command.
   // We simply wait here until one of these commands is sent, and return,
-  // after which the bestmove and pondermove will be printed (in id_loop()).
+  // after which the bestmove and pondermove will be printed.
 
   void wait_for_stop_or_ponderhit() {
 
@@ -2103,12 +2084,13 @@ split_point_start: // At split points actual search starts from here
 
     while (true)
     {
+        // Wait for a command from stdin
         if (!std::getline(std::cin, command))
             command = "quit";
 
         if (command == "quit")
         {
-            Quit = true;
+            QuitRequest = true;
             break;
         }
         else if (command == "ponderhit" || command == "stop")
@@ -2117,34 +2099,6 @@ split_point_start: // At split points actual search starts from here
   }
 
 
-  // print_pv_info() prints to standard output and eventually to log file information on
-  // the current PV line. It is called at each iteration or after a new pv is found.
-
-  void print_pv_info(const Position& pos, Move pv[], Value alpha, Value beta, Value value) {
-
-    cout << "info depth " << Iteration
-         << " score "     << value_to_uci(value)
-         << (value >= beta ? " lowerbound" : value <= alpha ? " upperbound" : "")
-         << " time "  << current_search_time()
-         << " nodes " << pos.nodes_searched()
-         << " nps "   << nps(pos)
-         << " pv ";
-
-    for (Move* m = pv; *m != MOVE_NONE; m++)
-        cout << *m << " ";
-
-    cout << endl;
-
-    if (UseLogFile)
-    {
-        ValueType t = value >= beta  ? VALUE_TYPE_LOWER :
-                      value <= alpha ? VALUE_TYPE_UPPER : VALUE_TYPE_EXACT;
-
-        LogFile << pretty_pv(pos, current_search_time(), Iteration, value, t, pv) << endl;
-    }
-  }
-
-
   // init_thread() is the function which is called when a new thread is
   // launched. It simply calls the idle_loop() function with the supplied
   // threadID. There are two versions of this function; one for POSIX
@@ -2655,6 +2609,38 @@ split_point_start: // At split points actual search starts from here
     do pos.undo_move(pv[--ply]); while (ply);
   }
 
+  // pv_info_to_uci() returns a string with information on the current PV line
+  // formatted according to UCI specification and eventually writes the info
+  // to a log file. It is called at each iteration or after a new pv is found.
+
+  std::string RootMove::pv_info_to_uci(const Position& pos, Value alpha, Value beta, int pvLine) {
+
+    std::stringstream s, l;
+    Move* m = pv;
+
+    while (*m != MOVE_NONE)
+        l << *m++ << " ";
+
+    s << "info depth " << Iteration // FIXME
+      << " seldepth " << int(m - pv)
+      << " multipv " << pvLine + 1
+      << " score " << value_to_uci(pv_score)
+      << (pv_score >= beta ? " lowerbound" : pv_score <= alpha ? " upperbound" : "")
+      << " time "  << current_search_time()
+      << " nodes " << pos.nodes_searched()
+      << " nps "   << nps(pos)
+      << " pv "    << l.str();
+
+    if (UseLogFile && pvLine == 0)
+    {
+        ValueType t = pv_score >= beta  ? VALUE_TYPE_LOWER :
+                      pv_score <= alpha ? VALUE_TYPE_UPPER : VALUE_TYPE_EXACT;
+
+        LogFile << pretty_pv(pos, current_search_time(), Iteration, pv_score, t, pv) << endl;
+    }
+    return s.str();
+  }
+
 
   RootMoveList::RootMoveList(Position& pos, Move searchMoves[]) {