public static string UCIInfoString(MCTSManager manager) { float elapsedTimeSeconds = (float)(DateTime.Now - manager.StartTimeThisSearch).TotalSeconds; float scoreCentipawn = MathF.Round(EncodedEvalLogistic.LogisticToCentipawn((float)manager.Root.Q), 0); float nps = manager.NumStepsTakenThisSearch / elapsedTimeSeconds; SearchPrincipalVariation pv; using (new SearchContextExecutionBlock(manager.Context)) { pv = new SearchPrincipalVariation(manager.Root); } //info depth 12 seldepth 27 time 30440 nodes 51100 score cp 105 hashfull 241 nps 1678 tbhits 0 pv e6c6 c5b4 d5e4 d1e1 int selectiveDepth = pv.Nodes.Count - 1; int depth = (int)MathF.Round(manager.Context.AvgDepth, 0); // TODO: tb, hashfull string infoUpdate = $"info depth {depth} seldepth {selectiveDepth} time {elapsedTimeSeconds * 1000.0f:F0} " + $"nodes {manager.Root.N:F0} score cp {scoreCentipawn:F0} tbhits {manager.CountTablebaseHits} nps {nps:F0} " + $"pv {pv.ShortStr()} string M= {manager.Root.MAvg:F0}"; return(infoUpdate); }
/// <summary> /// Overridden virtual method that executs the search /// by issuing UCI commands to the LC0 engine with appropriate search limit parameters. /// </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) { DoSearchPrepare(); if (SetupAction != null) { SetupAction(); } string fen = curPositionAndMoves.InitialPosition.FEN; string endFEN = curPositionAndMoves.FinalPosition.FEN; string moveStr = curPositionAndMoves.MovesStr; // Run the analysis LC0VerboseMoveStats lc0Analysis = LC0Engine.AnalyzePositionFromFENAndMoves(fen, moveStr, endFEN, searchLimit); if (verbose) { lc0Analysis.Dump(); } float scoreLC0 = (int)MathF.Round(EncodedEvalLogistic.LogisticToCentipawn(lc0Analysis.SearchEvalLogistic), 0); // TODO: can we somehow correctly set the staring N arugment here? return(new GameEngineSearchResult(lc0Analysis.BestMove, lc0Analysis.SearchEvalLogistic, scoreLC0, float.NaN, searchLimit, default, 0, (int)lc0Analysis.NumNodes, (int)lc0Analysis.UCIInfo.Depth));
public static string UCIInfoString(MCTSManager manager, MCTSNode bestMoveRoot = null) { // If no override bestMoveRoot was specified // then it is assumed the move chosen was from the root (not an instamove) if (bestMoveRoot == null) { bestMoveRoot = manager.Root; } bool wasInstamove = manager.Root != bestMoveRoot; float elapsedTimeSeconds = wasInstamove ? 0 : (float)(DateTime.Now - manager.StartTimeThisSearch).TotalSeconds; float scoreCentipawn = MathF.Round(EncodedEvalLogistic.LogisticToCentipawn((float)bestMoveRoot.Q), 0); float nps = manager.NumStepsTakenThisSearch / elapsedTimeSeconds; SearchPrincipalVariation pv; using (new SearchContextExecutionBlock(manager.Context)) { pv = new SearchPrincipalVariation(bestMoveRoot); } //info depth 12 seldepth 27 time 30440 nodes 51100 score cp 105 hashfull 241 nps 1678 tbhits 0 pv e6c6 c5b4 d5e4 d1e1 int selectiveDepth = pv.Nodes.Count - 1; int depthOfBestMoveInTree = wasInstamove ? bestMoveRoot.Depth : 0; int depth = (int)MathF.Round(manager.Context.AvgDepth - depthOfBestMoveInTree, 0); if (wasInstamove) { // Note that the correct tablebase hits cannot be easily calculated and reported return($"info depth {depth} seldepth {selectiveDepth} time 0 " + $"nodes {bestMoveRoot.N:F0} score cp {scoreCentipawn:F0} tbhits {manager.CountTablebaseHits} nps 0 " + $"pv {pv.ShortStr()} string M= {bestMoveRoot.MAvg:F0} instamove"); } else { return($"info depth {depth} seldepth {selectiveDepth} time {elapsedTimeSeconds * 1000.0f:F0} " + $"nodes {manager.Root.N:F0} score cp {scoreCentipawn:F0} tbhits {manager.CountTablebaseHits} nps {nps:F0} " + $"pv {pv.ShortStr()} string M= {manager.Root.MAvg:F0}"); } }
/// <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,
/// <summary> /// Returns an UCI info string appropriate for a given search state. /// </summary> /// <param name="manager"></param> /// <param name="overrideRootMove"></param> /// <returns></returns> public static string UCIInfoString(MCTSManager manager, MCTSNode overrideRootMove = null, MCTSNode overrideBestMoveNodeAtRoot = null, int?multiPVIndex = null, bool useParentN = true, bool showWDL = false, bool scoreAsQ = false) { if (manager.TablebaseImmediateBestMove != default) { if (multiPVIndex.HasValue && multiPVIndex != 1) { return(null); } else { return(OutputUCIInfoTablebaseImmediate(manager, overrideRootMove ?? manager.Root, scoreAsQ)); } } bool wasInstamove = manager.Root != overrideRootMove; // If no override bestMoveRoot was specified // then it is assumed the move chosen was from the root (not an instamove) MCTSNode thisRootNode = overrideRootMove ?? manager.Root; if (thisRootNode.NumPolicyMoves == 0) { // Terminal position, nothing to output return(null); } float elapsedTimeSeconds = wasInstamove ? 0 : (float)(DateTime.Now - manager.StartTimeThisSearch).TotalSeconds; // Get the principal variation (the first move of which will be the best move) SearchPrincipalVariation pv; using (new SearchContextExecutionBlock(manager.Context)) { pv = new SearchPrincipalVariation(thisRootNode, overrideBestMoveNodeAtRoot); } MCTSNode bestMoveNode = pv.Nodes.Count > 1 ? pv.Nodes[1] : pv.Nodes[0]; // The score displayed corresponds to // the Q (average visit value) of the move to be made. float scoreToShow; if (scoreAsQ) { scoreToShow = MathF.Round((float)-bestMoveNode.Q * 1000, 0); } else { scoreToShow = MathF.Round(EncodedEvalLogistic.LogisticToCentipawn((float)-bestMoveNode.Q), 0); } float nps = manager.NumStepsTakenThisSearch / elapsedTimeSeconds; //info depth 12 seldepth 27 time 30440 nodes 51100 score cp 105 hashfull 241 nps 1678 tbhits 0 pv e6c6 c5b4 d5e4 d1e1 int selectiveDepth = pv.Nodes.Count; int depthOfBestMoveInTree = wasInstamove ? thisRootNode.Depth : 0; int depth = 1 + (int)MathF.Round(manager.Context.AvgDepth - depthOfBestMoveInTree, 0); string pvString = multiPVIndex.HasValue ? $"multipv {multiPVIndex} pv {pv.ShortStr()}" : $"pv {pv.ShortStr()}"; int n = thisRootNode.N; if (!useParentN && overrideBestMoveNodeAtRoot != null) { n = overrideBestMoveNodeAtRoot.N; } //score cp 27 wdl 384 326 290 string strWDL = ""; if (showWDL) { // Note that win and loss inverted to reverse perspective. strWDL = $" wdl {Math.Round(bestMoveNode.LossP * 1000)} " + $"{Math.Round(bestMoveNode.DrawP * 1000)} " + $"{Math.Round(bestMoveNode.WinP * 1000)}"; } if (wasInstamove) { // Note that the correct tablebase hits cannot be easily calculated and reported return($"info depth {depth} seldepth {selectiveDepth} time 0 " + $"nodes {n:F0} score cp {scoreToShow}{strWDL} tbhits {manager.CountTablebaseHits} nps 0 " + $"{pvString} string M= {thisRootNode.MAvg:F0} "); } else { return($"info depth {depth} seldepth {selectiveDepth} time {elapsedTimeSeconds * 1000.0f:F0} " + $"nodes {n:F0} score cp {scoreToShow}{strWDL} tbhits {manager.CountTablebaseHits} nps {nps:F0} " + $"{pvString} string M= {thisRootNode.MAvg:F0}"); } }