예제 #1
0
        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);
        }
예제 #2
0
        /// <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));
예제 #3
0
        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));
예제 #4
0
        /// <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;
        }
예제 #5
0
        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,
예제 #7
0
        /// <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);
        }
예제 #8
0
        /// <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}");
            }
        }