void OutputUCIInfo(MCTSManager manager, MCTSNode searchRootNode, bool isFinalInfo = false) { BestMoveInfo best = searchRootNode.BestMoveInfo(false); if (numPV == 1) { UCIWriteLine(UCIInfo.UCIInfoString(manager, searchRootNode, best?.BestMoveNode, showWDL: showWDL, scoreAsQ: scoreAsQ)); } else { // Send top move UCIWriteLine(UCIInfo.UCIInfoString(manager, searchRootNode, best.BestMoveNode, 1, showWDL: showWDL, useParentN: !perPVCounters, scoreAsQ: scoreAsQ)); // Send other moves visited MCTSNode[] sortedN = searchRootNode.ChildrenSorted(s => - (float)s.N); int multiPVIndex = 2; for (int i = 0; i < sortedN.Length && i < numPV; i++) { if (!object.ReferenceEquals(sortedN[i], best.BestMoveNode)) { UCIWriteLine(UCIInfo.UCIInfoString(manager, searchRootNode, sortedN[i], multiPVIndex, showWDL: showWDL, useParentN: !perPVCounters, scoreAsQ: scoreAsQ)); multiPVIndex++; } } // Finally show moves that had no visits float elapsedTimeSeconds = (float)(DateTime.Now - manager.StartTimeThisSearch).TotalSeconds; string timeStr = $"{ elapsedTimeSeconds * 1000.0f:F0}"; for (int i = multiPVIndex - 1; i < searchRootNode.NumPolicyMoves; i++) { (MCTSNode node, EncodedMove move, FP16 p)info = searchRootNode.ChildAtIndexInfo(i); if (info.node == null) { bool isWhite = searchRootNode.Annotation.Pos.MiscInfo.SideToMove == SideType.White; EncodedMove moveCorrectPerspective = isWhite ? info.move : info.move.Flipped; string str = $"info depth 0 seldepth 0 time { timeStr } nodes 1 score cp 0 tbhits 0 " + $"multipv {multiPVIndex} pv {moveCorrectPerspective.AlgebraicStr} "; UCIWriteLine(str); multiPVIndex++; } } } if (verboseMoveStats && (logLiveStats || isFinalInfo)) { OutputVerboseMoveStats(CeresEngine.Search.SearchRootNode); } }
static void MCTSProgressCallback(MCTSManager manager) { int numUpdatesSent = 0; DateTime now = DateTime.Now; float timeSinceLastUpdate = (float)(now - lastInfoUpdate).TotalSeconds; bool isFirstUpdate = numUpdatesSent == 0; float UPDATE_INTERVAL_SECONDS = 1;// isFirstUpdate ? 0.1f : 3f; if (timeSinceLastUpdate > UPDATE_INTERVAL_SECONDS && manager.Root.N > 0) { Console.WriteLine(UCIInfo.UCIInfoString(manager)); lastInfoUpdate = now; } }
public static void Analyze(string fenAndMoves, SearchLimit searchLimit, NNEvaluatorDef evaluatorDef, bool forceDisablePruning, LC0Engine lc0Engine = null, GameEngine comparisonEngine = null, bool verbose = false) { Console.WriteLine("============================================================================="); Console.WriteLine("Analyzing FEN : " + fenAndMoves); Console.WriteLine("Search limit : " + searchLimit.ToString()); Console.WriteLine("Ceres evaluator : " + evaluatorDef.ToString()); if (comparisonEngine != null) { Console.WriteLine("Opponent : " + comparisonEngine.ToString()); } Console.WriteLine(); Console.WriteLine(); NNEvaluatorSet nnEvaluators = new NNEvaluatorSet(evaluatorDef); // Warmup (in parallel) lc0Engine?.DoSearchPrepare(); Parallel.Invoke( () => nnEvaluators.Warmup(true), () => comparisonEngine?.Warmup()); bool ceresDone = false; lastInfoUpdate = DateTime.Now; UCISearchInfo lastCeresInfo = null; // Launch Ceres MCTSearch ceresResults = null; Task searchCeres = Task.Run(() => { ParamsSearch searchParams = new ParamsSearch(); searchParams.FutilityPruningStopSearchEnabled = !forceDisablePruning; PositionWithHistory positionWithHistory = PositionWithHistory.FromFENAndMovesUCI(fenAndMoves); ceresResults = new MCTSearch(); ceresResults.Search(nnEvaluators, new ParamsSelect(), searchParams, null, null, null, positionWithHistory, searchLimit, verbose, DateTime.Now, null, manager => lastCeresInfo = new UCISearchInfo(UCIInfo.UCIInfoString(manager), null, null), false, true); }); // Possibly launch search for other engine Task searchComparison = null; if (lc0Engine != null || comparisonEngine != null) { searchComparison = Task.Run(() => { if (lc0Engine != null) { lc0Engine.DoSearchPrepare(); lc0Engine.AnalyzePositionFromFENAndMoves(fenAndMoves, searchLimit); } else { comparisonEngine.Search(PositionWithHistory.FromFENAndMovesUCI(fenAndMoves), searchLimit, verbose: true); } }); } ; while (!searchCeres.IsCompleted || (searchComparison != null && !searchComparison.IsCompleted)) { Thread.Sleep(1000); //Console.WriteLine(DateTime.Now + " --> " + lastCeresInfo?.PVString + " OTHER " + comparisonEngine?.UCIInfo?.RawString); int numCharactersSame = int.MaxValue; if (lastCeresInfo?.PVString != null || comparisonEngine?.UCIInfo?.RawString != null) { if (lastCeresInfo != null && comparisonEngine?.UCIInfo != null) { numCharactersSame = 0; string pv1 = lastCeresInfo.PVString; UCISearchInfo lastComparisonInfo = comparisonEngine.UCIInfo; string pv2 = lastComparisonInfo.PVString; while (pv1.Length > numCharactersSame && pv2.Length > numCharactersSame && pv1[numCharactersSame] == pv2[numCharactersSame]) { numCharactersSame++; } } } if (lastCeresInfo != null) { WriteUCI("Ceres", lastCeresInfo, numCharactersSame); } if (comparisonEngine != null) { WriteUCI(comparisonEngine.ID, comparisonEngine.UCIInfo, numCharactersSame); } Console.WriteLine(); } searchCeres.Wait(); searchComparison?.Wait(); string infoUpdate = UCIInfo.UCIInfoString(ceresResults.Manager); double q2 = ceresResults.SearchRootNode.Q; //SearchPrincipalVariation pv2 = new SearchPrincipalVariation(worker2.Root); MCTSPosTreeNodeDumper.DumpPV(ceresResults.SearchRootNode, true); }
/// <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); } }