/// <summary> /// Writes extensive descriptive information to a specified TextWriter, /// including verbose move statistics, principal variation, and move timing information. /// </summary> /// <param name="searchRootNode"></param> /// <param name="writer"></param> /// <param name="description"></param> public void DumpFullInfo(MGMove bestMove, MCTSNode searchRootNode = null, TextWriter writer = null, string description = null) { searchRootNode = searchRootNode ?? Root; writer = writer ?? Console.Out; int moveIndex = searchRootNode.Tree.Store.Nodes.PriorMoves.Moves.Count; writer.WriteLine(); writer.WriteLine("================================================================================="); writer.Write(DateTime.Now + " SEARCH RESULT INFORMATION, Move = " + ((1 + moveIndex / 2))); writer.WriteLine($" Thread = {Thread.CurrentThread.ManagedThreadId}"); if (description != null) { writer.WriteLine(description); } writer.WriteLine(); writer.WriteLine("Tree root : " + Context.Root); if (searchRootNode != Root) { writer.WriteLine("Search root : " + searchRootNode); } writer.WriteLine(); MCTSNode[] nodesSortedN = null; MCTSNode[] nodesSortedQ = null; string bestMoveInfo = ""; if (searchRootNode.NumChildrenExpanded > 0 && StopStatus != SearchStopStatus.TablebaseImmediateMove && StopStatus != SearchStopStatus.OnlyOneLegalMove) { MCTSNode[] childrenSortedN = searchRootNode.ChildrenSorted(node => - node.N); MCTSNode[] childrenSortedQ = searchRootNode.ChildrenSorted(node => (float)node.Q); bool isTopN = childrenSortedN[0].Annotation.PriorMoveMG == bestMove; bool isTopQ = childrenSortedQ[0].Annotation.PriorMoveMG == bestMove; if (isTopN && isTopQ) { bestMoveInfo = "(TopN and TopQ)"; } else if (isTopN) { bestMoveInfo = "(TopN)"; } else if (isTopQ) { bestMoveInfo = "(TopQ)"; } } // Output position (with history) information. writer.WriteLine("Position : " + searchRootNode.Annotation.Pos.FEN); writer.WriteLine("Tree root position : " + Context.Tree.Store.Nodes.PriorMoves); writer.WriteLine("Search stop status : " + StopStatus); writer.WriteLine("Best move selected : " + bestMove.MoveStr(MGMoveNotationStyle.LC0Coordinate) + " " + bestMoveInfo); writer.WriteLine(); using (new SearchContextExecutionBlock(Context)) { string infoUpdate = UCIInfo.UCIInfoString(this, searchRootNode); writer.WriteLine(infoUpdate); writer.WriteLine(); DumpTimeInfo(writer); writer.WriteLine(); searchRootNode.Dump(1, 1, writer: writer); writer.WriteLine(); MCTSPosTreeNodeDumper.DumpPV(searchRootNode, true, writer); } }
/// <summary> /// Overriden virtual method which executes search. /// </summary> /// <param name="curPositionAndMoves"></param> /// <param name="searchLimit"></param> /// <param name="gameMoveHistory"></param> /// <param name="callback"></param> /// <returns></returns> protected override GameEngineSearchResult DoSearch(PositionWithHistory curPositionAndMoves, SearchLimit searchLimit, List <GameMoveStat> gameMoveHistory, ProgressCallback callback, bool verbose) { if (LastSearch != null && curPositionAndMoves.InitialPosMG != LastSearch.Manager.Context.StartPosAndPriorMoves.InitialPosMG) { throw new Exception("ResetGame must be called if not continuing same line"); } MCTSearch searchResult; // Set up callback passthrough if provided MCTSManager.MCTSProgressCallback callbackMCTS = null; if (callback != null) { callbackMCTS = callbackContext => callback((MCTSManager)callbackContext); } // Possibly use the context of opponent to reuse position evaluations MCTSIterator shareContext = null; if (OpponentEngine is GameEngineCeresInProcess) { GameEngineCeresInProcess ceresOpponentEngine = OpponentEngine as GameEngineCeresInProcess; if (LastSearch is not null && LastSearch.Manager.Context.ParamsSearch.ReusePositionEvaluationsFromOtherTree && ceresOpponentEngine?.LastSearch.Manager != null && LeafEvaluatorReuseOtherTree.ContextsCompatibleForReuse(LastSearch.Manager.Context, ceresOpponentEngine.LastSearch.Manager.Context)) { shareContext = ceresOpponentEngine.LastSearch.Manager.Context; // Clear any prior shared context from the shared context // to prevent unlimited backward chaining (keeping unneeded prior contexts alive) shareContext.ClearSharedContext(); } } void InnerCallback(MCTSManager manager) { callbackMCTS?.Invoke(manager); } // Run the search searchResult = RunSearchPossiblyTreeReuse(shareContext, curPositionAndMoves, gameMoveHistory, searchLimit, InnerCallback, verbose); int scoreCeresCP = (int)Math.Round(EncodedEvalLogistic.LogisticToCentipawn((float)searchResult.Manager.Root.Q), 0); MGMove bestMoveMG = searchResult.BestMove; int N = (int)searchResult.SearchRootNode.N; // Save (do not dispose) last search in case we can reuse it next time LastSearch = searchResult; isFirstMoveOfGame = false; // TODO is the RootNWhenSearchStarted correct because we may be following a continuation (BestMoveRoot) GameEngineSearchResultCeres result = new GameEngineSearchResultCeres(bestMoveMG.MoveStr(MGMoveNotationStyle.LC0Coordinate), (float)searchResult.SearchRootNode.Q, scoreCeresCP, searchResult.SearchRootNode.MAvg, searchResult.Manager.SearchLimit, default,