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));
protected override GameEngineSearchResult DoSearch(PositionWithHistory curPositionAndMoves, SearchLimit searchLimit, List <GameMoveStat> gameMoveHistory, ProgressCallback callback, bool verbose) { DoSearchPrepare(); bool weAreWhite = curPositionAndMoves.FinalPosition.MiscInfo.SideToMove == SideType.White; UCISearchInfo gameInfo; switch (searchLimit.Type) { case SearchLimitType.SecondsPerMove: gameInfo = UCIRunner.EvalPositionToMovetime(curPositionAndMoves.FENAndMovesString, (int)(searchLimit.Value * 1000)); break; case SearchLimitType.NodesPerMove: gameInfo = UCIRunner.EvalPositionToNodes(curPositionAndMoves.FENAndMovesString, (int)(searchLimit.Value)); break; case SearchLimitType.NodesForAllMoves: using (new TimingBlock(new TimingStats(), TimingBlock.LoggingType.None)) { gameInfo = UCIRunner.EvalPositionRemainingNodes(curPositionAndMoves.FENAndMovesString, weAreWhite, searchLimit.MaxMovesToGo, (int)(searchLimit.Value), (int)(searchLimit.ValueIncrement)); } break; case SearchLimitType.SecondsForAllMoves: using (new TimingBlock(new TimingStats(), TimingBlock.LoggingType.None)) { gameInfo = UCIRunner.EvalPositionRemainingTime(curPositionAndMoves.FENAndMovesString, weAreWhite, searchLimit.MaxMovesToGo, (int)(searchLimit.Value * 1000), (int)(searchLimit.ValueIncrement * 1000)); } break; default: throw new NotSupportedException($"Unsupported MoveType {searchLimit.Type}"); } float q = EncodedEvalLogistic.CentipawnToLogistic(gameInfo.ScoreCentipawns); return(new GameEngineSearchResult(gameInfo.BestMove, q, gameInfo.ScoreCentipawns, float.NaN, searchLimit, default, 0, (int)gameInfo.Nodes, gameInfo.Depth));
/// <summary> /// Constructor /// </summary> /// <param name="values"></param> /// <param name="policyLogisticVectors"></param> /// <param name="draws"></param> public ONNXRuntimeExecutorResultBatch(bool isWDL, FP16[] values, float[][] policyLogisticVectors, float[][] valueFCActiviations, int numPositionsUsed) { ValuesRaw = values; if (!isWDL) { Values = EncodedEvalLogistic.FromLogisticArray(values); } PolicyVectors = policyLogisticVectors; // still in logistic form ValueFCActivations = valueFCActiviations; NumPositionsUsed = numPositionsUsed; IsWDL = isWDL; }
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> /// Parses the LC0 output line. /// </summary> /// <param name="line"></param> /// <returns></returns> bool ParseLine(string line) { // info string e2e4 (322 ) N: 15347 (+ 0) (V: 3.96%) (P: 10.09%) (Q: 0.03780) (U: 0.00707) (Q+U: 0.04487) // info string c2c4(264 ) N: 30259(+0)(V: 4.38 %)(P: 14.09 %)(Q: 0.03985)(U: 0.00501)(Q + U: 0.04486) string[] split = line.Split(new char[] { ' ', '(', ')', }); if (split[0] != "info" || split[1] != "string") { return(false); } MoveString = split[2]; int nextPos = 3; double GetNextNum() { do { if (nextPos >= split.Length) { return(double.NaN); } while (nextPos < split.Length && split[nextPos] == "") { nextPos++; } if (double.TryParse(split[nextPos++].Replace("%", ""), NumberStyles.Any, CultureInfo.InvariantCulture, out double val)) { return(val); } } while(true); } // info string e2e4(322 ) N: 15347(+0)(V: 3.96 %)(P: 10.09 %)(Q: 0.03780)(U: 0.00707)(Q + U: 0.04487) //new info string d2d3(288 ) N: 11(+2)(P: 2.85 %)(WL: -0.00802)(D: 0.315)(M: 134.9)(Q: -0.00802)(U: 0.15138)(S: 0.14336)(V: 0.0092) MoveCode = (int)GetNextNum(); nextPos++; VisitCount = (int)GetNextNum(); VisitInFlightCount = (int)GetNextNum(); Dictionary <string, float> stats = ExtractLC0VerboseMoveStats(line); stats.TryGetValue("P", out P); stats.TryGetValue("WL", out WL); stats.TryGetValue("D", out D); stats.TryGetValue("M", out M); float rawQ; stats.TryGetValue("QL", out rawQ); Q = EncodedEvalLogistic.FromLogistic(rawQ); stats.TryGetValue("D", out D); stats.TryGetValue("M", out M); stats.TryGetValue("U", out U); float S; stats.TryGetValue("S", out S); float rawV; stats.TryGetValue("V", out rawV); V = EncodedEvalLogistic.FromLogistic(rawV); return(true); }
/// <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}"); } }