Example #1
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));
Example #2
0
        /// <summary>
        /// Constructor for selector over specified MCTSIterator.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="selectorID"></param>
        /// <param name="priorSequence"></param>
        /// <param name="guessNumLeaves"></param>
        public LeafSelectorMulti(MCTSIterator context, int selectorID, PositionWithHistory priorSequence, int guessNumLeaves)
        {
            Debug.Assert(selectorID < ILeafSelector.MAX_SELECTORS);

            if (USE_CUSTOM_THREADPOOL)
            {
                tpm = tpmPool.Value.GetFromPool();
            }

            SelectorID      = selectorID;
            PriorSequence   = priorSequence;
            paramsExecution = context.ParamsSearch.Execution;

            int maxNodesPerBatchForRootPreload = context.ParamsSearch.Execution.RootPreloadDepth > 0 ? MCTSSearchFlow.MAX_PRELOAD_NODES_PER_BATCH : 0;
            int extraLeafsDynamic = 0;

            if (context.ParamsSearch.PaddedBatchSizing)
            {
                extraLeafsDynamic = context.ParamsSearch.PaddedExtraNodesBase + (int)(context.ParamsSearch.PaddedExtraNodesMultiplier * guessNumLeaves);
            }

            leafs = new ListBounded <MCTSNode>(guessNumLeaves + maxNodesPerBatchForRootPreload + extraLeafsDynamic);

            Context = context;
        }
Example #3
0
        /// <summary>
        /// Runs a search, calling DoSearch and adjusting the cumulative search time
        /// </summary>
        /// <param name="curPositionAndMoves"></param>
        /// <param name="searchLimit"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public GameEngineSearchResult Search(PositionWithHistory curPositionAndMoves,
                                             SearchLimit searchLimit,
                                             List <GameMoveStat> gameMoveHistory = null,
                                             ProgressCallback callback           = null,
                                             bool verbose = false)
        {
            // Execute any preparation which should not be counted against thinking time
            // For example, Stockfish can require hundreds of milliseconds to process "ucinewgame"
            // which is used to reset state/hash table when the tree reuse option is enabled.
            DoSearchPrepare();

            TimingStats            stats = new TimingStats();
            GameEngineSearchResult result;

            using (new TimingBlock(stats, TimingBlock.LoggingType.None))
            {
                result = DoSearch(curPositionAndMoves, searchLimit, gameMoveHistory, callback, verbose);
            }

            CumulativeSearchTimeSeconds += (float)stats.ElapsedTimeSecs;
            CumulativeNodes             += result.FinalN;

// XXY Console.WriteLine(this.GetType() + " limit " + searchLimit + " elapsed " + stats.ElapsedTimeSecs);
            result.TimingStats = stats;
            return(result);
        }
Example #4
0
        /// <summary>
        /// Constructor to create a store of specified maximum size.
        /// </summary>
        /// <param name="maxNodes"></param>
        /// <param name="priorMoves"></param>
        public MCTSNodeStore(int maxNodes, PositionWithHistory priorMoves = null)
        {
            if (priorMoves == null)
            {
                priorMoves = PositionWithHistory.StartPosition;
            }

            MaxNodes = maxNodes;
            int allocNodes = maxNodes;

            Nodes = new MCTSNodeStructStorage(allocNodes, null,
                                              MCTSParamsFixed.STORAGE_USE_INCREMENTAL_ALLOC,
                                              MCTSParamsFixed.STORAGE_LARGE_PAGES,
                                              MCTSParamsFixed.STORAGE_USE_EXISTING_SHARED_MEM);

            long reserveChildren = maxNodes * (long)AVG_CHILDREN_PER_NODE;

            Children = new MCTSNodeStructChildStorage(this, reserveChildren);

            // Save a copy of the prior moves
            Nodes.PriorMoves = new PositionWithHistory(priorMoves);

            CeresEnvironment.LogInfo("NodeStore", "Init", $"MCTSNodeStore created with max {maxNodes} nodes, max {reserveChildren} children");

            MCTSNodeStruct.ValidateMCTSNodeStruct();
            RootIndex = new MCTSNodeStructIndex(1);
        }
        /// <summary>
        /// Makes node within the tree the root child, reorganizing the nodes and child arrays.
        ///
        /// Critically, we do this operation in situ to avoid having to transiently allocate
        /// extremely large memory objects.
        ///
        /// The operation consists of 3 stages:
        ///   - traverse the subtree breadth-first starting at the new root,
        ///     building a bitmap of which nodes are children.
        ///
        ///   - traverse the node array sequentially, processing each node that is a member of this bitmap by
        ///     moving it into the new position in the store (including modifying associated child and parent references)
        ///     Also we build a table of all the new children that need to be moved.
        ///
        ///   - using the child table built above, shift all children down. Because nodes may have children written
        ///     out of order, we don't know for sure there is enough space available. Therefore we sort the table
        ///     based on their new position in the table and then shift them down, insuring we don't overwrite
        ///     children yet to be shifted.
        ///
        /// Additionally we may have to recreate the transposition roots dictionary because
        /// the set of roots is smaller (the retined subtree only) and the node indices will change.
        /// </summary>
        /// <param name="store"></param>
        /// <param name="newRootChild"></param>
        /// <param name="newPriorMoves"></param>
        /// <param name="transpositionRoots"></param>
        public static void MakeChildNewRoot(MCTSNodeStore store, float policySoftmax,
                                            ref MCTSNodeStruct newRootChild,
                                            PositionWithHistory newPriorMoves,
                                            PositionEvalCache cacheNonRetainedNodes,
                                            TranspositionRootsDict transpositionRoots)
        {
#if DEBUG
            store.Validate();
#endif

            COUNT++;

            // Nothing to do if the requested node is already currently the root
            if (newRootChild.Index == store.RootNode.Index)
            {
                // Nothing changing in the tree, just flush the cache references
                store.ClearAllCacheIndices();
            }
            else
            {
                DoMakeChildNewRoot(store, policySoftmax, ref newRootChild, newPriorMoves, cacheNonRetainedNodes, transpositionRoots);
            }

#if DEBUG
            store.Validate();
#endif
        }
Example #6
0
        /// <summary>
        /// Runs a search, calling DoSearch and adjusting the cumulative search time
        /// </summary>
        /// <param name="curPositionAndMoves"></param>
        /// <param name="searchLimit"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public GameEngineSearchResult Search(PositionWithHistory curPositionAndMoves,
                                             SearchLimit searchLimit,
                                             List <GameMoveStat> gameMoveHistory = null,
                                             ProgressCallback callback           = null,
                                             bool verbose = false)
        {
            if (inSearch)
            {
                throw new Exception("GameEngine.Search cannot be called concurrently by more than one thread.");
            }
            inSearch = true;

            // Execute any preparation which should not be counted against thinking time
            // For example, Stockfish can require hundreds of milliseconds to process "ucinewgame"
            // which is used to reset state/hash table when the tree reuse option is enabled.
            DoSearchPrepare();

            TimingStats            stats = new TimingStats();
            GameEngineSearchResult result;

            using (new TimingBlock(stats, TimingBlock.LoggingType.None))
            {
                result = DoSearch(curPositionAndMoves, searchLimit, gameMoveHistory, callback, verbose);
            }

            CumulativeSearchTimeSeconds += (float)stats.ElapsedTimeSecs;
            CumulativeNodes             += result.FinalN;

            result.TimingStats = stats;

            inSearch = false;
            return(result);
        }
Example #7
0
        /// <summary>
        /// Processes a specified position command,
        /// with the side effect of resetting the curPositionAndMoves.
        /// </summary>
        /// <param name="command"></param>
        private void ProcessPosition(string command)
        {
            command = StringUtils.WhitespaceRemoved(command);

            string commandLower = command.ToLower();

            string posString;

            if (commandLower.StartsWith("position fen "))
            {
                posString = command.Substring(13);
            }
            else if (commandLower.StartsWith("position startpos"))
            {
                posString = command.Substring(9);
            }
            else
            {
                throw new Exception($"Illegal position command, expected to start with position fen or position startpos");
            }

            PositionWithHistory newPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(posString);

            curPositionIsContinuationOfPrior = newPositionAndMoves.IsIdenticalToPriorToLastMove(curPositionAndMoves);
            if (!curPositionIsContinuationOfPrior && CeresEngine != null)
            {
                CeresEngine.ResetGame();
            }

            // Switch to the new position and moves
            curPositionAndMoves = newPositionAndMoves;
        }
 /// <summary>
 /// Runs a search, calling DoSearch and adjusting the cumulative search time
 /// (convenience method with same functionality but returns the as the subclass
 /// GameEngineSearchResultCeres.
 /// </summary>
 /// <param name="curPositionAndMoves"></param>
 /// <param name="searchLimit"></param>
 /// <param name="callback"></param>
 /// <returns></returns>
 public GameEngineSearchResultCeres SearchCeres(PositionWithHistory curPositionAndMoves,
                                                SearchLimit searchLimit,
                                                List <GameMoveStat> gameMoveHistory = null,
                                                ProgressCallback callback           = null,
                                                bool verbose = false)
 {
     return(Search(curPositionAndMoves, searchLimit, gameMoveHistory, callback, verbose) as GameEngineSearchResultCeres);
 }
Example #9
0
        /// <summary>
        /// Runs a new search.
        /// </summary>
        /// <param name="nnEvaluators"></param>
        /// <param name="paramsSelect"></param>
        /// <param name="paramsSearch"></param>
        /// <param name="limitManager"></param>
        /// <param name="paramsSearchExecutionPostprocessor"></param>
        /// <param name="reuseOtherContextForEvaluatedNodes"></param>
        /// <param name="priorMoves"></param>
        /// <param name="searchLimit"></param>
        /// <param name="verbose"></param>
        /// <param name="startTime"></param>
        /// <param name="gameMoveHistory"></param>
        /// <param name="progressCallback"></param>
        /// <param name="possiblyUsePositionCache"></param>
        /// <param name="isFirstMoveOfGame"></param>
        public void Search(NNEvaluatorSet nnEvaluators,
                           ParamsSelect paramsSelect,
                           ParamsSearch paramsSearch,
                           IManagerGameLimit limitManager,
                           ParamsSearchExecutionModifier paramsSearchExecutionPostprocessor,
                           MCTSIterator reuseOtherContextForEvaluatedNodes,
                           PositionWithHistory priorMoves,
                           SearchLimit searchLimit, bool verbose,
                           DateTime startTime,
                           List <GameMoveStat> gameMoveHistory,
                           MCTSManager.MCTSProgressCallback progressCallback = null,
                           bool possiblyUsePositionCache = false,
                           bool isFirstMoveOfGame        = false)
        {
            searchLimit = AdjustedSearchLimit(searchLimit, paramsSearch);

            int maxNodes;

            if (MCTSParamsFixed.STORAGE_USE_INCREMENTAL_ALLOC)
            {
                // In this mode, we are just reserving virtual address space
                // from a very large pool (e.g. 256TB for Windows).
                // Therefore it is safe to reserve a very large block.
                maxNodes = (int)(1.1f * MCTSNodeStore.MAX_NODES);
            }
            else
            {
                if (searchLimit.SearchCanBeExpanded)
                {
                    throw new Exception("STORAGE_USE_INCREMENTAL_ALLOC must be true when SearchCanBeExpanded.");
                }

                if (searchLimit.Type != SearchLimitType.NodesPerMove)
                {
                    maxNodes = (int)searchLimit.Value + 5_000;
                }
                else
                {
                    throw new Exception("STORAGE_USE_INCREMENTAL_ALLOC must be true when using time search limits.");
                }
            }

            MCTSNodeStore store = new MCTSNodeStore(maxNodes, priorMoves);

            SearchLimit searchLimitToUse = ConvertedSearchLimit(priorMoves.FinalPosition, searchLimit, 0, 0,
                                                                paramsSearch, limitManager,
                                                                gameMoveHistory, isFirstMoveOfGame);

            Manager = new MCTSManager(store, reuseOtherContextForEvaluatedNodes, null, null,
                                      nnEvaluators, paramsSearch, paramsSelect,
                                      searchLimitToUse, paramsSearchExecutionPostprocessor, limitManager,
                                      startTime, null, gameMoveHistory, isFirstMoveOfGame);

            using (new SearchContextExecutionBlock(Manager.Context))
            {
                (BestMove, TimingInfo) = MCTSManager.Search(Manager, verbose, progressCallback, possiblyUsePositionCache);
            }
        }
Example #10
0
        /// <summary>
        /// Analyzes a position until a specified search limit is exhausted.
        /// </summary>
        /// <param name="fenAndMovesStr"></param>
        /// <param name="searchLimit"></param>
        /// <returns></returns>
        public LC0VerboseMoveStats AnalyzePositionFromFENAndMoves(string fenAndMovesStr, SearchLimit searchLimit)
        {
            List <LC0VerboseMoveStat> moves = new List <LC0VerboseMoveStat>();
            PositionWithHistory       pwh   = PositionWithHistory.FromFENAndMovesUCI(fenAndMovesStr);

            UCISearchInfo searchInfo;

            int searchValueMilliseconds = (int)((float)searchLimit.Value * 1000.0f);

            switch (searchLimit.Type)
            {
            case SearchLimitType.NodesPerMove:
                searchInfo = Runner.EvalPositionToNodes(fenAndMovesStr, (int)searchLimit.Value);
                break;

            case SearchLimitType.SecondsPerMove:
                searchInfo = Runner.EvalPositionToMovetime(fenAndMovesStr, searchValueMilliseconds);
                break;

            case SearchLimitType.NodesForAllMoves:
                throw new Exception("NodesForAllMoves not supported for Leela Chess Zero");

            case SearchLimitType.SecondsForAllMoves:
                bool weAreWhite = pwh.FinalPosition.MiscInfo.SideToMove == SideType.White;

                searchInfo = Runner.EvalPositionRemainingTime(fenAndMovesStr,
                                                              weAreWhite,
                                                              searchLimit.MaxMovesToGo,
                                                              (int)(searchLimit.Value * 1000),
                                                              (int)(searchLimit.ValueIncrement * 1000));
                break;

            default:
                throw new Exception($"Unknown SeachLimit.Type {searchLimit.Type}");
            }

            double elapsed = 0;//engine.EngineProcess.TotalProcessorTime.TotalSeconds - startTime;

            // no more, we now assume  win_percentages is requested     LeelaVerboseMoveStats ret = new LeelaVerboseMoveStats(positionEnd, searchInfo.BestMove, elapsed, searchInfo.Nodes, LZPositionEvalLogistic.CentipawnToLogistic2018(searchInfo.Score));
            float scoreLogistic     = searchInfo.ScoreLogistic;
            LC0VerboseMoveStats ret = new LC0VerboseMoveStats(pwh.FinalPosition, searchInfo.BestMove, elapsed, searchInfo.Nodes, scoreLogistic, searchInfo);

            searchInfo.Infos.Reverse();
            foreach (string info in searchInfo.Infos)
            {
                if (info.Contains("P:"))
                {
                    moves.Add(new LC0VerboseMoveStat(ret, info));
                }
            }

            ret.SetMoves(moves);

            return(LastAnalyzedPositionStats = ret);
        }
Example #11
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));
Example #12
0
        public static void DumpPV(PositionWithHistory priorMoves, MCTSNode node,
                                  bool fullDetail, List <MGMove> subvariation = null)
        {
            if (subvariation != null)
            {
                List <MGMove> allMoves = new List <MGMove>();
                allMoves.AddRange(priorMoves.Moves);
                allMoves.AddRange(subvariation);
                DumpPV(new PositionWithHistory(priorMoves.InitialPosMG, allMoves),
                       DescendMovesToNode(node, subvariation), fullDetail);
            }

            Console.WriteLine();
            WriteHeaders(fullDetail);


            List <Position> seenPositions = new List <Position>();

            int CountDuplicatePos(Position pos)
            {
                int count = 0;

                foreach (Position priorPos in seenPositions)
                {
                    if (pos.EqualAsRepetition(priorPos))
                    {
                        count++;
                    }
                }

                return(count);
            }

            int depth = 0;

            while (true)
            {
                node.Context.Tree.Annotate(node);
                seenPositions.Add(node.Annotation.Pos);
                int countSeen = CountDuplicatePos(node.Annotation.Pos);

                DumpNodeStr(priorMoves, node, depth, countSeen, fullDetail);

                if (node.NumChildrenVisited == 0)
                {
                    return;
                }
                node = node.BestMove(false);

                depth++;
            }
        }
Example #13
0
        /// <summary>
        /// Processes a specified position command,
        /// with the side effect of resetting the curPositionAndMoves.
        /// </summary>
        /// <param name="command"></param>
        private void ProcessPosition(string command)
        {
            string[] parts = command.Split(" ");

            string fen;
            int    nextIndex;

            string startFEN;

            switch (parts[1])
            {
            case "fen":
                fen       = command.Substring(command.IndexOf("fen") + 4);
                nextIndex = 2;
                while (parts.Length > nextIndex && parts[nextIndex] != "moves")
                {
                    fen = fen + " " + parts[nextIndex++];
                }
                startFEN = fen;
                break;

            case "startpos":
                startFEN  = Position.StartPosition.FEN;
                nextIndex = 2;
                break;

            default:
                throw new Exception("invalid " + command);
            }

            string movesSubstring = "";

            if (parts.Length > nextIndex && parts[nextIndex] == "moves")
            {
                for (int i = nextIndex + 1; i < parts.Length; i++)
                {
                    movesSubstring += parts[i] + " ";
                }
            }

            PositionWithHistory newPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(startFEN, movesSubstring);

            curPositionIsContinuationOfPrior = newPositionAndMoves.IsIdenticalToPriorToLastMove(curPositionAndMoves);
            if (!curPositionIsContinuationOfPrior && CeresEngine != null)
            {
                CeresEngine.ResetGame();
            }

            // Switch to the new position and moves
            curPositionAndMoves = newPositionAndMoves;
        }
Example #14
0
        /// <summary>
        /// Analyzes a specified position until a specified limit is exhausted.
        ///
        /// TODO: This method is highly redundant (and inferior to?) the next method AnalyzePositionFromFENAndMoves, delete it.
        /// </summary>
        /// <param name="fenOrFENAndMoves">a FEN</param>
        /// <param name="nodes"></param>
        /// <returns></returns>
        public UCISearchInfo AnalyzePositionFromFEN(string fenAndMovesString, SearchLimit searchLimit)
        {
            List <LC0VerboseMoveStat> moves = new List <LC0VerboseMoveStat>();

            Runner.EvalPositionPrepare();

            UCISearchInfo searchInfo;

            if (searchLimit.Type == SearchLimitType.SecondsPerMove)
            {
                searchInfo = Runner.EvalPositionToMovetime(fenAndMovesString, (int)(searchLimit.Value * 1000.0f));
            }
            else if (searchLimit.Type == SearchLimitType.NodesPerMove)
            {
                searchInfo = Runner.EvalPositionToNodes(fenAndMovesString, (int)searchLimit.Value);
            }
            else
            {
                throw new Exception("Unknown search limit " + searchLimit.Type);
            }

            double elapsed = searchInfo.EngineReportedSearchTime / 1000.0f;

            // no more, we now assume  win_percentages is requested     LeelaVerboseMoveStats ret = new LeelaVerboseMoveStats(positionEnd, searchInfo.BestMove, elapsed, searchInfo.Nodes, LZPositionEvalLogistic.CentipawnToLogistic2018(searchInfo.Score));
            float scoreConverted    = 2.0f * (((float)searchInfo.ScoreCentipawns / 10_000f) - 0.5f);
            PositionWithHistory pwh = PositionWithHistory.FromFENAndMovesUCI(fenAndMovesString);
            LC0VerboseMoveStats ret = new LC0VerboseMoveStats(pwh.FinalPosition, searchInfo.BestMove, elapsed, searchInfo.Nodes, scoreConverted, searchInfo);

            foreach (string info in searchInfo.Infos)
            {
                if (info.Contains("P:"))
                {
                    moves.Add(new LC0VerboseMoveStat(ret, info));
                }
            }

            ret.SetMoves(moves);

            // TODO: Someday perhaps make LeelaVerboseMoveStats a subclass of UCISearchInfo so this is more elegant
            UCISearchInfo uciInfo = new UCISearchInfo(null, ret.BestMove, null);

            uciInfo.Nodes = ret.NumNodes;
            uciInfo.EngineReportedSearchTime = (int)(1000.0f * ret.ElapsedTime);
            uciInfo.ExtraInfo = ret;
            uciInfo.BestMove  = ret.BestMove;

            return(uciInfo);
        }
Example #15
0
        SearchOnFEN(NNEvaluatorSet nnEvaluators,
                    ParamsSelect paramsChildSelect,
                    ParamsSearch paramsSearch,
                    IManagerGameLimit timeManager,
                    ParamsSearchExecutionModifier paramsSearchExecutionPostprocessor,
                    MCTSIterator reuseOtherContextForEvaluatedNodes,
                    string fen, string movesStr, SearchLimit searchLimit,
                    bool verbose = false,
                    MCTSManager.MCTSProgressCallback progressCallback = null,
                    bool possiblyEnablePositionCache    = false,
                    List <GameMoveStat> gameMoveHistory = null)
        {
            PositionWithHistory priorMoves = PositionWithHistory.FromFENAndMovesUCI(fen, movesStr);

            return(Search(nnEvaluators, paramsChildSelect, paramsSearch, timeManager, paramsSearchExecutionPostprocessor,
                          reuseOtherContextForEvaluatedNodes, priorMoves, searchLimit, verbose,
                          DateTime.Now, gameMoveHistory, progressCallback, possiblyEnablePositionCache));
        }
Example #16
0
        /// <summary>
        /// Attempts to find a subnode by following specified moves from root.
        /// </summary>
        /// <param name="priorRoot"></param>
        /// <param name="movesMade"></param>
        /// <returns></returns>
        static MCTSNode FollowMovesToNode(MCTSNode priorRoot, IEnumerable <MGMove> movesMade)
        {
            PositionWithHistory startingPriorMove = priorRoot.Context.StartPosAndPriorMoves;
            MGPosition          position          = startingPriorMove.FinalPosMG;
            MCTSIterator        context           = priorRoot.Context;

            // Advance root node and update prior moves
            MCTSNode newRoot = priorRoot;

            foreach (MGMove moveMade in movesMade)
            {
                bool foundChild = false;

                // Find this new root node (after these moves)
                foreach (MCTSNodeStructChild child in newRoot.Ref.Children)
                {
                    if (child.IsExpanded)
                    {
                        MGMove thisChildMove = ConverterMGMoveEncodedMove.EncodedMoveToMGChessMove(child.Move, in position);
                        if (thisChildMove == moveMade)
                        {
                            // Advance new root to reflect this move
                            newRoot = context.Tree.GetNode(child.ChildIndex, newRoot);

                            // Advance position
                            position.MakeMove(thisChildMove);

                            // Done looking for match
                            foundChild = true;
                            break;
                        }
                    }
                }

                if (!foundChild)
                {
                    return(null);
                }
            }

            // Found it
            return(newRoot);
        }
Example #17
0
        /// <summary>
        /// Runs the UCI loop.
        /// </summary>
        public void PlayUCI()
        {
            // Default to the startpos.
            curPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(Position.StartPosition.FEN, null);
            curManager          = null;
            gameMoveHistory     = new List <GameMoveStat>();

            while (true)
            {
                string command = InStream.ReadLine();

                switch (command)
                {
                case null:
                case "":
                    break;

                case "uci":
                    Send("id name Ceres"); // TODO: Add executable version
                    Send("id author David Elliott and the Ceres Authors");
                    // todo output options such as:
                    //   option name Logfile type check default false
                    Send("uciok");
                    break;

                case "setoption":
                    OutStream.WriteLine("Not processing option " + command);
                    return;

                case "stop":
                    if (taskSearchCurrentlyExecuting != null && !stopIsPending)
                    {
                        stopIsPending = true;

                        // TODO: cleanup
                        //       Possible race condition, curManager is only set in search callback which may not have hit yet
                        //       Fix eventually by rewriting SerchManager to have a constructor and then non-static Search method,
                        //       os we can get the context in this class directly after construction
                        while (curManager == null)
                        {
                            Thread.Sleep(1);              // **** TEMPORARY ***
                        }
                        curManager.ExternalStopRequested = true;
                        if (taskSearchCurrentlyExecuting != null)
                        {
                            taskSearchCurrentlyExecuting.Wait();
                            if (!debug && taskSearchCurrentlyExecuting != null)
                            {
                                taskSearchCurrentlyExecuting.Result?.Manager?.Dispose();
                            }
                            taskSearchCurrentlyExecuting = null;
                        }
                    }

                    curManager    = null;
                    stopIsPending = false;

                    break;

                case "ponderhit":
                    throw new NotImplementedException("Ceres does not yet support UCI ponder mode.");
                    return;

                case "xboard":
                    // ignore
                    break;

                case "debug on":
                    debug = true;
                    break;

                case "debug off":
                    debug = false;
                    break;

                case "isready":
                    InitializeEngineIfNeeded();
                    Send("readyok");
                    break;

                case "ucinewgame":
                    gameMoveHistory = new List <GameMoveStat>();
                    CeresEngine?.ResetGame();
                    break;

                case "quit":
                    if (curManager != null)
                    {
                        curManager.ExternalStopRequested = true;
                        taskSearchCurrentlyExecuting?.Wait();
                    }

                    if (CeresEngine != null)
                    {
                        CeresEngine.Dispose();
                    }

                    System.Environment.Exit(0);
                    break;

                case string c when c.StartsWith("go"):
                    if (taskSearchCurrentlyExecuting != null)
                    {
                        throw new Exception("Received go command when another search was running and not stopped first");
                    }

                    InitializeEngineIfNeeded();

                    taskSearchCurrentlyExecuting = ProcessGo(command);
                    break;

                case string c when c.StartsWith("position"):
                    try
                    {
                        ProcessPosition(c);
                    }
                    catch (Exception e)
                    {
                        Send($"Illegal position command: \"{c}\"" + System.Environment.NewLine + e.ToString());
                    }
                    break;

                // Proprietary commands
                case "lc0-config":
                    if (curManager != null)
                    {
                        string             netID  = EvaluatorDef.Nets[0].Net.NetworkID;
                        INNWeightsFileInfo netDef = NNWeightsFiles.LookupNetworkFile(netID);
                        (string exe, string options) = LC0EngineConfigured.GetLC0EngineOptions(null, null, curContext.EvaluatorDef, netDef, false, false);
                        Console.WriteLine("info string " + exe + " " + options);
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }

                    break;

                case "dump-params":
                    if (curManager != null)
                    {
                        curManager.DumpParams();
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-processor":
                    HardwareManager.DumpProcessorInfo();
                    break;

                case "dump-time":
                    if (curManager != null)
                    {
                        curManager.DumpTimeInfo();
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-store":
                    if (curManager != null)
                    {
                        using (new SearchContextExecutionBlock(curContext))
                            curManager.Context.Tree.Store.Dump(true);
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-move-stats":
                    if (curManager != null)
                    {
                        using (new SearchContextExecutionBlock(curContext))
                            curManager.Context.Root.Dump(1, 1, prefixString: "info string ");
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-pv":
                    DumpPV(false);
                    break;

                case "dump-pv-detail":
                    DumpPV(true);
                    break;

                case "dump-nvidia":
                    NVML.DumpInfo();
                    break;


                case "waitdone": // proprietary verb
                    taskSearchCurrentlyExecuting?.Wait();
                    break;

                default:
                    Console.WriteLine($"error Unknown command: {command}");
                    break;
                }
            }
        }
Example #18
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="movesMade"></param>
        /// <param name="thresholdFractionNodesRetained"></param>
        /// <returns></returns>
        public bool ResetRootAssumingMovesMade(IEnumerable <MGMove> movesMade, float thresholdFractionNodesRetained)
        {
            PositionWithHistory staringPriorMove = Nodes.PriorMoves;
            MGPosition          position         = Nodes.PriorMoves.FinalPosMG;

            ref MCTSNodeStruct priorRoot = ref RootNode;
        static void DoMakeChildNewRoot(MCTSNodeStore store, float policySoftmax, ref MCTSNodeStruct newRootChild,
                                       PositionWithHistory newPriorMoves,
                                       PositionEvalCache cacheNonRetainedNodes,
                                       TranspositionRootsDict transpositionRoots)
        {
            ChildStartIndexToNodeIndex[] childrenToNodes;

            uint     numNodesUsed;
            uint     numChildrenUsed;
            BitArray includedNodes;

            int newRootChildIndex   = newRootChild.Index.Index;
            int newIndexOfNewParent = -1;

            int nextAvailableNodeIndex = 1;

            // Traverse this subtree, building a bit array of visited nodes
            includedNodes = MCTSNodeStructUtils.BitArrayNodesInSubtree(store, ref newRootChild, out numNodesUsed);

            //using (new TimingBlock("Build position cache "))
            if (cacheNonRetainedNodes != null)
            {
                long estNumNodes = store.RootNode.N - numNodesUsed;
                cacheNonRetainedNodes.InitializeWithSize((int)estNumNodes);
                ExtractPositionCacheNonRetainedNodes(store, policySoftmax, includedNodes, in newRootChild, cacheNonRetainedNodes);
            }

            // We will constract a table indicating the starting index and length of
            // children associated with the nodes we are extracting
            childrenToNodes = GC.AllocateUninitializedArray <ChildStartIndexToNodeIndex>((int)numNodesUsed);

            void RewriteNodes()
            {
                // TODO: Consider that the above is possibly all we need to do in some case
                //       Suppose the subtree is very large relative to the whole
                //       This approach would be much faster, and orphan an only small part of the storage

                // Now scan all above nodes.
                // If they don't belong, ignore.
                // If they do belong, swap them down to the next available lower location
                // Note that this can't be parallelized, since we have to do it strictly in order of node index
                int numRewrittenNodesDone = 0;

                for (int i = 2; i < store.Nodes.nextFreeIndex; i++)
                {
                    if (includedNodes.Get(i))
                    {
                        ref MCTSNodeStruct thisNode = ref store.Nodes.nodes[i];

                        // Reset any cache entry
                        thisNode.CacheIndex = 0;

                        // Not possible to support transposition linked nodes,
                        // since the root may be in a part of the tree that is not retained
                        // and possibly already overwritten.
                        // We expect them to have already been materialized by the time we reach this point.
                        Debug.Assert(!thisNode.IsTranspositionLinked);
                        Debug.Assert(thisNode.NumNodesTranspositionExtracted == 0);

                        // Remember this location if this is the new parent
                        if (i == newRootChildIndex)
                        {
                            newIndexOfNewParent = nextAvailableNodeIndex;
                        }

                        // Move the actual node
                        MoveNodePosition(store, new MCTSNodeStructIndex(i), new MCTSNodeStructIndex(nextAvailableNodeIndex));

                        // Reset all transposition information
                        thisNode.NextTranspositionLinked = 0;

                        childrenToNodes[numRewrittenNodesDone] = new ChildStartIndexToNodeIndex(thisNode.childStartBlockIndex, nextAvailableNodeIndex, thisNode.NumPolicyMoves);

                        // Re-insert this into the transpositionRoots (with the updated node index)
                        if (transpositionRoots != null)
                        {
                            transpositionRoots.TryAdd(thisNode.ZobristHash, nextAvailableNodeIndex);
                        }

                        Debug.Assert(thisNode.NumNodesTranspositionExtracted == 0);

                        numRewrittenNodesDone++;
                        nextAvailableNodeIndex++;
                    }
                }
            }
Example #20
0
        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);
        }
Example #21
0
 protected abstract GameEngineSearchResult DoSearch(PositionWithHistory curPositionAndMoves,
                                                    SearchLimit searchLimit,
                                                    List <GameMoveStat> gameMoveHistory,
                                                    ProgressCallback callback,
                                                    bool verbose);
Example #22
0
        /// <summary>
        /// Runs a search, possibly continuing from node
        /// nested in a prior search (tree reuse).
        /// </summary>
        /// <param name="priorSearch"></param>
        /// <param name="reuseOtherContextForEvaluatedNodes"></param>
        /// <param name="moves"></param>
        /// <param name="newPositionAndMoves"></param>
        /// <param name="gameMoveHistory"></param>
        /// <param name="searchLimit"></param>
        /// <param name="verbose"></param>
        /// <param name="startTime"></param>
        /// <param name="progressCallback"></param>
        /// <param name="thresholdMinFractionNodesRetained"></param>
        /// <param name="isFirstMoveOfGame"></param>
        public void SearchContinue(MCTSearch priorSearch,
                                   MCTSIterator reuseOtherContextForEvaluatedNodes,
                                   IEnumerable <MGMove> moves, PositionWithHistory newPositionAndMoves,
                                   List <GameMoveStat> gameMoveHistory,
                                   SearchLimit searchLimit,
                                   bool verbose, DateTime startTime,
                                   MCTSManager.MCTSProgressCallback progressCallback,
                                   float thresholdMinFractionNodesRetained,
                                   bool isFirstMoveOfGame = false)
        {
            CountSearchContinuations = priorSearch.CountSearchContinuations;
            Manager = priorSearch.Manager;

            MCTSIterator  priorContext    = Manager.Context;
            MCTSNodeStore store           = priorContext.Tree.Store;
            int           numNodesInitial = Manager == null ? 0 : Manager.Root.N;

            MCTSNodeStructIndex newRootIndex;

            using (new SearchContextExecutionBlock(priorContext))
            {
                MCTSNode newRoot = FollowMovesToNode(Manager.Root, moves);

                // New root is not useful if contained no search
                // (for example if it was resolved via tablebase)
                // thus in that case we pretend as if we didn't find it
                if (newRoot != null && (newRoot.N == 0 || newRoot.NumPolicyMoves == 0))
                {
                    newRoot = null;
                }

                // Check for possible instant move
                (MCTSManager, MGMove, TimingStats)instamove = CheckInstamove(Manager, searchLimit, newRoot);

                if (instamove != default)
                {
                    // Modify in place to point to the new root
                    continationSubroot = newRoot;
                    BestMove           = instamove.Item2;
                    TimingInfo         = new TimingStats();
                    return;
                }
                else
                {
                    CountSearchContinuations = 0;
                }

                // TODO: don't reuse tree if it would cause the nodes in use
                //       to exceed a reasonable value for this machine
#if NOT
// NOTE: abandoned, small subtrees will be fast to rewrite so we can always do this
                // Only rewrite the store with the subtree reused
                // if it is not tiny relative to the current tree
                // (otherwise the scan/rewrite is not worth it
                float       fracTreeReuse        = newRoot.N / store.Nodes.NumUsedNodes;
                const float THRESHOLD_REUSE_TREE = 0.02f;
#endif
                // Inform contempt manager about the opponents move
                // (compared to the move we believed was optimal)
                if (newRoot != null && newRoot.Depth == 2)
                {
                    MCTSNode opponentsPriorMove = newRoot;
                    MCTSNode bestMove           = opponentsPriorMove.Parent.ChildrenSorted(n => (float)n.Q)[0];
                    if (bestMove.N > opponentsPriorMove.N / 10)
                    {
                        float bestQ   = (float)bestMove.Q;
                        float actualQ = (float)opponentsPriorMove.Q;
                        Manager.Context.ContemptManager.RecordOpponentMove(actualQ, bestQ);
                        //Console.WriteLine("Record " + actualQ + " vs best " + bestQ + " target contempt " + priorManager.Context.ContemptManager.TargetContempt);
                    }
                }

                bool storeIsAlmostFull          = priorContext.Tree.Store.FractionInUse > 0.9f;
                bool newRootIsBigEnoughForReuse = newRoot != null && newRoot.N >= (priorContext.Root.N * thresholdMinFractionNodesRetained);
                if (priorContext.ParamsSearch.TreeReuseEnabled && newRootIsBigEnoughForReuse && !storeIsAlmostFull)
                {
                    SearchLimit searchLimitAdjusted = searchLimit;

                    if (Manager.Context.ParamsSearch.Execution.TranspositionMode != TranspositionMode.None)
                    {
                        // The MakeChildNewRoot method is not able to handle transposition linkages
                        // (this would be complicated and could involve linkages to nodes no longer in the retained subtree).
                        // Therefore we first materialize any transposition linked nodes in the subtree.
                        // Since this is not currently multithreaded we can turn off tree node locking for the duration.
                        newRoot.Tree.ChildCreateLocks.LockingActive = false;
                        newRoot.MaterializeAllTranspositionLinks();
                        newRoot.Tree.ChildCreateLocks.LockingActive = true;
                    }

                    // Now rewrite the tree nodes and children "in situ"
                    PositionEvalCache reusePositionCache = null;
                    if (Manager.Context.ParamsSearch.TreeReuseRetainedPositionCacheEnabled)
                    {
                        reusePositionCache = new PositionEvalCache(0);
                    }

                    TranspositionRootsDict newTranspositionRoots = null;
                    if (priorContext.Tree.TranspositionRoots != null)
                    {
                        int estNumNewTranspositionRoots = newRoot.N + newRoot.N / 3; // somewhat oversize to allow for growth in subsequent search
                        newTranspositionRoots = new TranspositionRootsDict(estNumNewTranspositionRoots);
                    }

                    // TODO: Consider sometimes or always skip rebuild via MakeChildNewRoot,
                    //       instead just set a new root (move it into place as first node).
                    //       Perhaps rebuild only if the MCTSNodeStore would become excessively large.
                    TimingStats makeNewRootTimingStats = new TimingStats();
                    using (new TimingBlock(makeNewRootTimingStats, TimingBlock.LoggingType.None))
                    {
                        MCTSNodeStructStorage.MakeChildNewRoot(store, Manager.Context.ParamsSelect.PolicySoftmax, ref newRoot.Ref, newPositionAndMoves,
                                                               reusePositionCache, newTranspositionRoots);
                    }
                    MCTSManager.TotalTimeSecondsInMakeNewRoot += (float)makeNewRootTimingStats.ElapsedTimeSecs;

                    CeresEnvironment.LogInfo("MCTS", "MakeChildNewRoot", $"Select {newRoot.N:N0} from {numNodesInitial:N0} "
                                             + $"in {(int)(makeNewRootTimingStats.ElapsedTimeSecs/1000.0)}ms");

                    // Finally if nodes adjust based on current nodes
                    if (searchLimit.Type == SearchLimitType.NodesPerMove)
                    {
                        searchLimitAdjusted = new SearchLimit(SearchLimitType.NodesPerMove, searchLimit.Value + store.RootNode.N);
                    }

                    // Construct a new search manager reusing this modified store and modified transposition roots
                    MCTSManager manager = new MCTSManager(store, reuseOtherContextForEvaluatedNodes, reusePositionCache, newTranspositionRoots,
                                                          priorContext.NNEvaluators, priorContext.ParamsSearch, priorContext.ParamsSelect,
                                                          searchLimitAdjusted, Manager.ParamsSearchExecutionPostprocessor, Manager.LimitManager,
                                                          startTime, Manager, gameMoveHistory, isFirstMoveOfGame: isFirstMoveOfGame);
                    manager.Context.ContemptManager = priorContext.ContemptManager;

                    bool possiblyUsePositionCache = false; // TODO could this be relaxed?
                    (MGMove move, TimingStats stats)result = MCTSManager.Search(manager, verbose, progressCallback, possiblyUsePositionCache);
                    BestMove   = result.move;
                    TimingInfo = result.stats;
                    Manager    = manager;
                }

                else
                {
                    // We decided not to (or couldn't find) that path in the existing tree
                    // Just run the search from scratch
                    if (verbose)
                    {
                        Console.WriteLine("\r\nFailed nSearchFollowingMoves.");
                    }

                    Search(Manager.Context.NNEvaluators, Manager.Context.ParamsSelect,
                           Manager.Context.ParamsSearch, Manager.LimitManager,
                           null, reuseOtherContextForEvaluatedNodes, newPositionAndMoves, searchLimit, verbose,
                           startTime, gameMoveHistory, progressCallback, false);
                }
            }


#if NOT
            // This code partly or completely works
            // We don't rely upon it because it could result in uncontained growth of the store,
            // since detached nodes are left
            // But if the subtree chosen is almost the whole tree, maybe we could indeed use this techinque as an alternate in these cases
            if (store.ResetRootAssumingMovesMade(moves, thresholdFractionNodesRetained))
            {
                SearchManager manager = new SearchManager(store, priorContext.ParamsNN,
                                                          priorContext.ParamsSearch, priorContext.ParamsSelect,
                                                          null, limit);
                manager.Context.TranspositionRoots = priorContext.TranspositionRoots;

                return(Search(manager, false, verbose, progressCallback, false));
            }
#endif
        }
        /// <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,
Example #24
0
        void ProcessEPD(int epdNum, EPDEntry epd, bool outputDetail, ObjectPool <object> otherEngines)
        {
            UCISearchInfo otherEngineAnalysis2 = default;

            EPDEntry epdToUse = epd;

            Task RunNonCeres()
            {
                if (Def.ExternalEngineDef != null)
                {
                    object engineObj = otherEngines.GetFromPool();

                    if (engineObj is LC0Engine)
                    {
                        LC0Engine le = (LC0Engine)engineObj;

                        // Run test 2 first since that's the one we dump in detail, to avoid any possible caching effect from a prior run
                        otherEngineAnalysis2 = le.AnalyzePositionFromFEN(epdToUse.FEN, epdToUse.StartMoves, Def.ExternalEngineDef.SearchLimit);
                        //            leelaAnalysis2 = le.AnalyzePositionFromFEN(epdToUse.FEN, new SearchLimit(SearchLimit.LimitType.NodesPerMove, 2)); // **** TEMP
                        otherEngines.RestoreToPool(le);
                    }
                    else
                    {
                        UCIGameRunner runner = (engineObj is UCIGameRunner) ? (engineObj as UCIGameRunner)
            : (engineObj as GameEngineUCI).UCIRunner;
                        string moveType  = Def.ExternalEngineDef.SearchLimit.Type == SearchLimitType.NodesPerMove ? "nodes" : "movetime";
                        int    moveValue = moveType == "nodes" ? (int)Def.ExternalEngineDef.SearchLimit.Value : (int)Def.ExternalEngineDef.SearchLimit.Value * 1000;
                        runner.EvalPositionPrepare();
                        otherEngineAnalysis2 = runner.EvalPosition(epdToUse.FEN, epdToUse.StartMoves, moveType, moveValue, null);
                        otherEngines.RestoreToPool(runner);
                        //          public UCISearchInfo EvalPosition(int engineNum, string fenOrPositionCommand, string moveType, int moveMetric, bool shouldCache = false)
                    }
                }
                return(Task.CompletedTask);
            }

            bool EXTERNAL_CONCURRENT = numConcurrentSuiteThreads > 1;

            Task lzTask = EXTERNAL_CONCURRENT ? Task.Run(RunNonCeres) : RunNonCeres();

            // Comptue search limit
            // If possible, adjust for the fact that LC0 "cheats" by going slightly over node budget
            SearchLimit ceresSearchLimit1 = Def.CeresEngine1Def.SearchLimit;
            SearchLimit ceresSearchLimit2 = Def.CeresEngine2Def?.SearchLimit;

            if (Def.CeresEngine1Def.SearchLimit.Type == SearchLimitType.NodesPerMove &&
                otherEngineAnalysis2 != null &&
                !Def.Engine1Def.SearchParams.FutilityPruningStopSearchEnabled)
            {
                if (Def.CeresEngine1Def.SearchLimit.Type == SearchLimitType.NodesPerMove)
                {
                    ceresSearchLimit1 = new SearchLimit(SearchLimitType.NodesPerMove, otherEngineAnalysis2.Nodes);
                }
                if (Def.CeresEngine1Def.SearchLimit.Type == SearchLimitType.NodesPerMove)
                {
                    ceresSearchLimit2 = new SearchLimit(SearchLimitType.NodesPerMove, otherEngineAnalysis2.Nodes);
                }
            }

            PositionWithHistory pos = PositionWithHistory.FromFENAndMovesSAN(epdToUse.FEN, epdToUse.StartMoves);

            // TODO: should this be switched to GameEngineCeresInProcess?

            // Note that if we are running both Ceres1 and Ceres2 we alternate which search goes first.
            // This prevents any systematic difference/benefit that might come from order
            // (for example if we reuse position evaluations from the other tree, which can benefit only one of the two searches).
            MCTSearch search1 = null;
            MCTSearch search2 = null;

            if (epdNum % 2 == 0 || Def.CeresEngine2Def == null)
            {
                search1 = new MCTSearch();
                search1.Search(evaluatorSet1, Def.Engine1Def.SelectParams, Def.Engine1Def.SearchParams, null, null, null,
                               pos, ceresSearchLimit1, false, DateTime.Now, null, null, true);

                MCTSIterator shareContext = null;
                if (Def.RunCeres2Engine)
                {
                    if (Def.Engine2Def.SearchParams.ReusePositionEvaluationsFromOtherTree)
                    {
                        shareContext = search1.Manager.Context;
                    }

                    search2 = new MCTSearch();
                    search2.Search(evaluatorSet2, Def.Engine2Def.SelectParams, Def.Engine2Def.SearchParams, null, null, shareContext,
                                   pos, ceresSearchLimit2, false, DateTime.Now, null, null, true);
                }
            }
            else
            {
                search2 = new MCTSearch();
                search2.Search(evaluatorSet2, Def.Engine2Def.SelectParams, Def.Engine2Def.SearchParams, null, null, null,
                               pos, ceresSearchLimit2, false, DateTime.Now, null, null, true);

                MCTSIterator shareContext = null;
                if (Def.Engine1Def.SearchParams.ReusePositionEvaluationsFromOtherTree)
                {
                    shareContext = search2.Manager.Context;
                }

                search1 = new MCTSearch();
                search1.Search(evaluatorSet1, Def.Engine1Def.SelectParams, Def.Engine1Def.SearchParams, null, null, shareContext,
                               pos, ceresSearchLimit1, false, DateTime.Now, null, null, true);
            }

            // Wait for LZ analysis
            if (EXTERNAL_CONCURRENT)
            {
                lzTask.Wait();
            }

            Move bestMoveOtherEngine = default;

            if (Def.ExternalEngineDef != null)
            {
                MGPosition thisPosX = PositionWithHistory.FromFENAndMovesUCI(epdToUse.FEN, epdToUse.StartMoves).FinalPosMG;

                MGMove lzMoveMG1 = MGMoveFromString.ParseMove(thisPosX, otherEngineAnalysis2.BestMove);
                bestMoveOtherEngine = MGMoveConverter.ToMove(lzMoveMG1);
            }

            Move bestMoveCeres1 = MGMoveConverter.ToMove(search1.BestMove);

            Move bestMoveCeres2 = search2 == null ? default : MGMoveConverter.ToMove(search2.BestMove);

                                  char CorrectStr(Move move) => epdToUse.CorrectnessScore(move, 10) == 10 ? '+' : '.';

                                  int scoreCeres1      = epdToUse.CorrectnessScore(bestMoveCeres1, 10);
                                  int scoreCeres2      = epdToUse.CorrectnessScore(bestMoveCeres2, 10);
                                  int scoreOtherEngine = epdToUse.CorrectnessScore(bestMoveOtherEngine, 10);

                                  SearchResultInfo result1 = new SearchResultInfo(search1.Manager, search1.BestMove);
                                  SearchResultInfo result2 = search2 == null ? null : new SearchResultInfo(search2.Manager, search2.BestMove);

                                  accCeres1 += scoreCeres1;
                                  accCeres2 += scoreCeres2;

                                  // Accumulate how many nodes were required to find one of the correct moves
                                  // (in the cases where both succeeded)
                                  if (scoreCeres1 > 0 && (search2 == null || scoreCeres2 > 0))
                                  {
                                      accWCeres1 += (scoreCeres1 == 0) ? result1.N : result1.NumNodesWhenChoseTopNNode;
                                      if (search2 != null)
                                      {
                                          accWCeres2 += (scoreCeres2 == 0) ? result2.N : result2.NumNodesWhenChoseTopNNode;
                                      }
                                      numSearchesBothFound++;
                                  }
                                  this.avgOther += scoreOtherEngine;

                                  numSearches++;

                                  float avgCeres1  = (float)accCeres1 / numSearches;
                                  float avgCeres2  = (float)accCeres2 / numSearches;
                                  float avgWCeres1 = (float)accWCeres1 / numSearchesBothFound;
                                  float avgWCeres2 = (float)accWCeres2 / numSearchesBothFound;

                                  float avgOther = (float)this.avgOther / numSearches;

                                  string MoveIfWrong(Move m) => m.IsNull || epdToUse.CorrectnessScore(m, 10) == 10 ? "    " : m.ToString().ToLower();

                                  int diff1 = scoreCeres1 - scoreOtherEngine;

                                  //NodeEvaluatorNeuralNetwork
                                  int evalNumBatches1 = result1.NumNNBatches;
                                  int evalNumPos1     = result1.NumNNNodes;
                                  int evalNumBatches2 = search2 == null ? 0 : result2.NumNNBatches;
                                  int evalNumPos2     = search2 == null ? 0 : result2.NumNNNodes;

                                  string correctMove = null;

                                  if (epdToUse.AMMoves != null)
                                  {
                                      correctMove = "-" + epdToUse.AMMoves[0];
                                  }
                                  else if (epdToUse.BMMoves != null)
                                  {
                                      correctMove = epdToUse.BMMoves[0];
                                  }

                                  float otherEngineTime = otherEngineAnalysis2 == null ? 0 : (float)otherEngineAnalysis2.EngineReportedSearchTime / 1000.0f;

                                  totalTimeOther  += otherEngineTime;
                                  totalTimeCeres1 += (float)search1.TimingInfo.ElapsedTimeSecs;

                                  totalNodesOther += otherEngineAnalysis2 == null ? 0 : (int)otherEngineAnalysis2.Nodes;
                                  totalNodes1     += (int)result1.N;

                                  sumEvalNumPosOther += otherEngineAnalysis2 == null ? 0 : (int)otherEngineAnalysis2.Nodes;
                                  sumEvalNumBatches1 += evalNumBatches1;
                                  sumEvalNumPos1     += evalNumPos1;

                                  if (Def.RunCeres2Engine)
                                  {
                                      totalTimeCeres2    += (float)search2.TimingInfo.ElapsedTimeSecs;
                                      totalNodes2        += (int)result2.N;
                                      sumEvalNumBatches2 += evalNumBatches2;
                                      sumEvalNumPos2     += evalNumPos2;
                                  }

                                  float Adjust(int score, float frac) => score == 0 ? 0 : Math.Max(1.0f, MathF.Round(frac * 100.0f, 0));

                                  string worker1PickedNonTopNMoveStr = result1.PickedNonTopNMoveStr;
                                  string worker2PickedNonTopNMoveStr = result2?.PickedNonTopNMoveStr;

                                  bool ex = otherEngineAnalysis2 != null;
                                  bool c2 = search2 != null;

                                  Writer writer = new Writer(epdNum == 0);

                                  writer.Add("#", $"{epdNum,4}", 6);

                                  if (ex)
                                  {
                                      writer.Add("CEx", $"{avgOther,5:F2}", 7);
                                  }
                                  writer.Add("CC", $"{avgCeres1,5:F2}", 7);
                                  if (c2)
                                  {
                                      writer.Add("CC2", $"{avgCeres2,5:F2}", 7);
                                  }

                                  writer.Add("P", $" {0.001f * avgWCeres1,7:f2}", 9);
                                  if (c2)
                                  {
                                      writer.Add("P2", $" {0.001f * avgWCeres2,7:f2}", 9);
                                  }

                                  if (ex)
                                  {
                                      writer.Add("SEx", $"{scoreOtherEngine,3}", 5);
                                  }
                                  writer.Add("SC", $"{scoreCeres1,3}", 5);
                                  if (c2)
                                  {
                                      writer.Add("SC2", $"{scoreCeres2,3}", 5);
                                  }

                                  if (ex)
                                  {
                                      writer.Add("MEx", $"{otherEngineAnalysis2.BestMove,7}", 9);
                                  }
                                  writer.Add("MC", $"{search1.Manager.BestMoveMG,7}", 9);
                                  if (c2)
                                  {
                                      writer.Add("MC2", $"{search2.Manager.BestMoveMG,7}", 9);
                                  }

                                  writer.Add("Fr", $"{worker1PickedNonTopNMoveStr}{ 100.0f * result1.TopNNodeN / result1.N,3:F0}%", 9);
                                  if (c2)
                                  {
                                      writer.Add("Fr2", $"{worker2PickedNonTopNMoveStr}{ 100.0f * result2?.TopNNodeN / result2?.N,3:F0}%", 9);
                                  }

                                  writer.Add("Yld", $"{result1.NodeSelectionYieldFrac,6:f3}", 9);
                                  if (c2)
                                  {
                                      writer.Add("Yld2", $"{result2.NodeSelectionYieldFrac,6:f3}", 9);
                                  }

                                  // Search time
                                  if (ex)
                                  {
                                      writer.Add("TimeEx", $"{otherEngineTime,7:F2}", 9);
                                  }
                                  writer.Add("TimeC", $"{search1.TimingInfo.ElapsedTimeSecs,7:F2}", 9);
                                  if (c2)
                                  {
                                      writer.Add("TimeC2", $"{search2.TimingInfo.ElapsedTimeSecs,7:F2}", 9);
                                  }

                                  writer.Add("Dep", $"{result1.AvgDepth,5:f1}", 7);
                                  if (c2)
                                  {
                                      writer.Add("Dep2", $"{result2.AvgDepth,5:f1}", 7);
                                  }

                                  // Nodes
                                  if (ex)
                                  {
                                      writer.Add("NEx", $"{otherEngineAnalysis2.Nodes,12:N0}", 14);
                                  }
                                  writer.Add("Nodes", $"{result1.N,12:N0}", 14);
                                  if (c2)
                                  {
                                      writer.Add("Nodes2", $"{result2.N,12:N0}", 14);
                                  }

                                  // Fraction when chose top N
                                  writer.Add("Frac", $"{Adjust(scoreCeres1, result1.FractionNumNodesWhenChoseTopNNode),4:F0}", 6);
                                  if (c2)
                                  {
                                      writer.Add("Frac2", $"{Adjust(scoreCeres2, result2.FractionNumNodesWhenChoseTopNNode),4:F0}", 6);
                                  }

                                  // Score (Q)
                                  if (ex)
                                  {
                                      writer.Add("QEx", $"{otherEngineAnalysis2.ScoreLogistic,6:F3}", 8);
                                  }
                                  writer.Add("QC", $"{result1.Q,6:F3}", 8);
                                  if (c2)
                                  {
                                      writer.Add("QC2", $"{result2.Q,6:F3}", 8);
                                  }

                                  // Num batches&positions
                                  writer.Add("Batches", $"{evalNumBatches1,8:N0}", 10);
                                  writer.Add("NNEvals", $"{evalNumPos1,11:N0}", 13);
                                  if (c2)
                                  {
                                      writer.Add("Batches2", $"{evalNumBatches2,8:N0}", 10);
                                      writer.Add("NNEvals2", $"{evalNumPos2,11:N0}", 13);
                                  }

                                  // Tablebase hits
                                  writer.Add("TBase", $"{(search1.CountSearchContinuations > 0 ? 0 : search1.Manager.CountTablebaseHits),8:N0}", 10);
                                  if (c2)
                                  {
                                      writer.Add("TBase2", $"{(search2.CountSearchContinuations > 0 ? 0 : search2.Manager.CountTablebaseHits),8:N0}", 10);
                                  }

//      writer.Add("EPD", $"{epdToUse.ID,-30}", 32);

                                  if (outputDetail)
                                  {
                                      if (epdNum == 0)
                                      {
                                          Def.Output.WriteLine(writer.ids.ToString());
                                          Def.Output.WriteLine(writer.dividers.ToString());
                                      }
                                      Def.Output.WriteLine(writer.text.ToString());
                                  }

                                  //      MCTSNodeStorageSerialize.Save(worker1.Context.Store, @"c:\temp", "TESTSORE");

                                  search1?.Manager?.Dispose();
                                  if (!object.ReferenceEquals(search1?.Manager, search2?.Manager))
                                  {
                                      search2?.Manager?.Dispose();
                                  }
        }
Example #25
0
        /// <summary>
        /// Runs the UCI loop.
        /// </summary>
        public void PlayUCI()
        {
            // Default to the startpos.
            curPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(Position.StartPosition.FEN);
            gameMoveHistory     = new List <GameMoveStat>();

            while (true)
            {
                string command = InStream.ReadLine();
                if (uciLogWriter != null)
                {
                    LogWriteLine("IN:", command);
                }

                switch (command)
                {
                case null:
                case "":
                    break;

                case "uci":
                    UCIWriteLine($"id name Ceres {CeresVersion.VersionString}");
                    UCIWriteLine("id author David Elliott and the Ceres Authors");
                    UCIWriteLine(SetOptionUCIDescriptions);
                    UCIWriteLine("uciok");
                    break;

                case string c when c.StartsWith("setoption"):
                    ProcessSetOption(command);

                    break;

                case "stop":
                    if (taskSearchCurrentlyExecuting != null && !stopIsPending)
                    {
                        stopIsPending = true;

                        // Avoid race condition by mkaing sure the search is already created.
                        while (CeresEngine.Search?.Manager == null)
                        {
                            Thread.Sleep(20);
                        }

                        CeresEngine.Search.Manager.ExternalStopRequested = true;
                        if (taskSearchCurrentlyExecuting != null)
                        {
                            taskSearchCurrentlyExecuting.Wait();
                            //                if (!debug && taskSearchCurrentlyExecuting != null) taskSearchCurrentlyExecuting.Result?.Search?.Manager?.Dispose();
                            taskSearchCurrentlyExecuting = null;
                        }
                    }

                    stopIsPending = false;

                    break;

                case "ponderhit":
                    throw new NotImplementedException("Ceres does not yet support UCI ponder mode.");
                    return;

                case "xboard":
                    // ignore
                    break;

                case "debug on":
                    debug = true;
                    break;

                case "debug off":
                    debug = false;
                    break;

                case "isready":
                    InitializeEngineIfNeeded();
                    UCIWriteLine("readyok");
                    break;

                case "ucinewgame":
                    gameMoveHistory = new List <GameMoveStat>();
                    CeresEngine?.ResetGame();
                    break;

                case "quit":
                    if (taskSearchCurrentlyExecuting != null)
                    {
                        CeresEngine.Search.Manager.ExternalStopRequested = true;
                        taskSearchCurrentlyExecuting?.Wait();
                    }

                    if (CeresEngine != null)
                    {
                        CeresEngine.Dispose();
                    }

                    System.Environment.Exit(0);
                    break;

                case string c when c.StartsWith("go"):

                    // Possibly another search is already executing.
                    // The UCI specification is unclear about what to do in this situation.
                    // Some engines seem to enqueue these for later execution (e.g. Stockfish)
                    // whereas others (e.g. Python chess) report this as an error condition.
                    // Currently Ceres waits only a short while for any possible pending search
                    // to finish (e.g. to avoid a race condition if it is in the process of being shutdown)
                    // and aborts with an error if search is still in progress.
                    // It is not viable to wait indefinitely, since (among other reasons)
                    // the engine needs to monitor for stop commands.
                    const int MAX_MILLISECONDS_WAIT = 500;

                    taskSearchCurrentlyExecuting?.Wait(MAX_MILLISECONDS_WAIT);

                    if (taskSearchCurrentlyExecuting != null && !taskSearchCurrentlyExecuting.IsCompleted)
                    {
                        throw new Exception("Received go command when another search was running and not stopped first.");
                    }

                    InitializeEngineIfNeeded();

                    taskSearchCurrentlyExecuting = ProcessGo(command);
                    break;

                case string c when c.StartsWith("position"):
                    try
                    {
                        ProcessPosition(c);
                    }
                    catch (Exception e)
                    {
                        UCIWriteLine($"Illegal position command: \"{c}\"" + System.Environment.NewLine + e.ToString());
                    }
                    break;

                // Proprietary commands
                case "lc0-config":
                    if (CeresEngine?.Search != null)
                    {
                        string             netID  = EvaluatorDef.Nets[0].Net.NetworkID;
                        INNWeightsFileInfo netDef = NNWeightsFiles.LookupNetworkFile(netID);
                        (string exe, string options) = LC0EngineConfigured.GetLC0EngineOptions(null, null, CeresEngine.Search.Manager.Context.EvaluatorDef, netDef, false, false);
                        UCIWriteLine("info string " + exe + " " + options);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }

                    break;

                case "dump-params":
                    if (CeresEngine?.Search != null)
                    {
                        CeresEngine?.Search.Manager.DumpParams();
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-processor":
                    HardwareManager.DumpProcessorInfo();
                    break;

                case "dump-time":
                    if (CeresEngine?.Search != null)
                    {
                        CeresEngine?.Search.Manager.DumpTimeInfo(OutStream);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-store":
                    if (CeresEngine?.Search != null)
                    {
                        using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context))
                            CeresEngine.Search.Manager.Context.Tree.Store.Dump(true);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-move-stats":
                    if (CeresEngine?.Search != null)
                    {
                        OutputVerboseMoveStats(CeresEngine.Search.SearchRootNode);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-pv":
                    DumpPV(false);
                    break;

                case "dump-pv-detail":
                    DumpPV(true);
                    break;

                case "dump-nvidia":
                    NVML.DumpInfo();
                    break;

                case "show-tree-plot":
                    if (CeresEngine?.Search != null)
                    {
                        using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context))
                        {
                            TreePlot.Show(CeresEngine.Search.Manager.Context.Root.Ref);
                        }
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case string c when c.StartsWith("save-tree-plot"):
                    if (CeresEngine?.Search != null)
                    {
                        string[] parts = command.Split(" ");
                        if (parts.Length == 2)
                        {
                            string fileName = parts[1];
                            using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context))
                            {
                                TreePlot.Save(CeresEngine.Search.Manager.Context.Root.Ref, fileName);
                            }
                        }
                        else if (parts.Length == 1)
                        {
                            UCIWriteLine("Filename was not provided");
                        }
                        else
                        {
                            UCIWriteLine("Filename cannot contain spaces");
                        }
                    }

                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "waitdone": // proprietary verb used for test driver
                    taskSearchCurrentlyExecuting?.Wait();
                    break;

                default:
                    UCIWriteLine($"error Unknown command: {command}");
                    break;
                }
            }
        }
Example #26
0
        static void DumpNodeStr(PositionWithHistory priorMoves, MCTSNode node, int depth, int countTimesSeen, bool fullDetail)
        {
            node.Context.Tree.Annotate(node);

            char extraChar = ' ';

            if (node.Terminal == GameResult.Checkmate)
            {
                extraChar = 'C';
            }
            else if (node.Terminal == GameResult.Draw)
            {
                extraChar = 'D';
            }
            else if (countTimesSeen > 1)
            {
                extraChar = countTimesSeen > 9 ? '9' : countTimesSeen.ToString()[0];
            }

            float multiplier = depth % 2 == 0 ? 1.0f : -1.0f;

            float pctOfVisits = node.IsRoot ? 100.0f : (100.0f * node.N / node.Parent.N);

            MCTSNode bestMove = null;

// TODO: someday show this too      MCTSNode nextBestMove = null;
            if (!node.IsRoot)
            {
                MCTSNode[] parentsChildrenSortedQ = node.ChildrenSorted(innerNode => - multiplier * (float)innerNode.Q);
                if (parentsChildrenSortedQ.Length > 0)
                {
                    bestMove = parentsChildrenSortedQ[0];
                }
//        if (parentsChildrenSortedQ.Length > 1) nextBestMove = parentsChildrenSortedQ[1];
            }

            // Depth, move
            Console.Write($"{depth,3}. ");
            Console.Write(extraChar);
            Console.Write($" {node.NumPolicyMoves,3} ");

            Console.Write($"{node.Index,13:N0}");

            string san = node.IsRoot ? "" : MGMoveConverter.ToMove(node.Annotation.PriorMoveMG).ToSAN(in node.Parent.Annotation.Pos);

//      string sanNextBest = node.IsRoot ? "" : MGMoveConverter.ToMove(nextBestMove.Annotation.PriorMoveMG).ToSAN(in node.Parent.Annotation.Pos);
            if (node.Annotation.Pos.MiscInfo.SideToMove == SideType.White)
            {
                Console.Write($"      ");
                Console.Write($"{san,6}");
            }
            else
            {
                Console.Write($"{san,6}");
                Console.Write($"      ");
            }

//      float diffBestNextBestQ = 0;
//      if (nextBestMove != null) diffBestNextBestQ = (float)(bestMove.Q - nextBestMove.Q);
//      Console.Write($"{  (nextBestMove?.Annotation == null ? "" : nextBestMove.Annotation.PriorMoveMG.ToString()),8}");
//      Console.Write($"{diffBestNextBestQ,8:F2}");


            Console.Write($"{node.N,13:N0} ");
            Console.Write($" {pctOfVisits,5:F0}%");
            Console.Write($"   {100.0 * node.P,6:F2}%  ");
            DumpWithColor(multiplier * node.V, $" {multiplier * node.V,6:F3}  ", -0.2f, 0.2f);
//      DumpWithColor(multiplier * node.VSecondary, $" {multiplier * node.VSecondary,6:F3} ", -0.2f, 0.2f);
            double q = multiplier * node.Q;

            DumpWithColor((float)q, $" {q,6:F3} ", -0.2f, 0.2f);

            //      float qStdDev = MathF.Sqrt(node.Ref.VVariance);
            //      if (float.IsNaN(qStdDev))
            //        Console.WriteLine("found negative var");
            //      Console.Write($" +/-{qStdDev,5:F2}  ");

            Console.Write($" {node.WinP,5:F2}/{node.DrawP,5:F2}/{node.LossP,5:F2}  ");

            Console.Write($" {node.WAvg,5:F2}/{node.DAvg,5:F2}/{node.LAvg,5:F2}  ");

            //      Console.Write($"   {node.Ref.QUpdatesWtdAvg,5:F2}  ");
            //      Console.Write($" +/-:{MathF.Sqrt(node.Ref.QUpdatesWtdVariance),5:F2}  ");
            //      Console.Write($" {node.Ref.TrendBonusToP,5:F2}  ");

            Console.Write($" {node.MPosition,3:F0} ");
            Console.Write($" {node.MAvg,3:F0}  ");

            if (fullDetail)
            {
                int numPieces = node.Annotation.Pos.PieceCount;

//        Console.Write($" {PosStr(node.Annotation.Pos)} ");
                Console.Write($" {node.Annotation.Pos.FEN}");
            }


            Console.WriteLine();
        }