Esempio n. 1
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);
        }
Esempio n. 2
0
        /// <summary>
        /// Overridden virtual method that executs the search
        /// by issuing UCI commands to the LC0 engine with appropriate search limit parameters.
        /// </summary>
        /// <param name="curPositionAndMoves"></param>
        /// <param name="searchLimit"></param>
        /// <param name="gameMoveHistory"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        protected override GameEngineSearchResult DoSearch(PositionWithHistory curPositionAndMoves,
                                                           SearchLimit searchLimit,
                                                           List <GameMoveStat> gameMoveHistory,
                                                           ProgressCallback callback, bool verbose)
        {
            DoSearchPrepare();

            if (SetupAction != null)
            {
                SetupAction();
            }

            // Run the analysis
            LC0VerboseMoveStats lc0Analysis = LC0Engine.AnalyzePositionFromFENAndMoves(curPositionAndMoves.FENAndMovesString, 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));
Esempio n. 3
0
        /// <summary>
        /// Experimental sample code of runnings suites via the API.
        /// </summary>
        public static void RunSuiteTest()
        {
            const int PARALLELISM = 1;

            string deviceSuffix = PARALLELISM > 1 ? ":POOLED" : "";

            NNEvaluatorDef evalDef1 = NNEvaluatorDefFactory.FromSpecification("LC0:j92-280", $"GPU:1{deviceSuffix}");
            NNEvaluatorDef evalDef2 = NNEvaluatorDefFactory.FromSpecification("LC0:66733", $"GPU:1{deviceSuffix}");

            SearchLimit limit = SearchLimit.NodesPerMove(10_000);

            string[] extraUCI = new string[] { "setoption name Contempt value 5000" };

            GameEngineDef     ged1 = new GameEngineDefCeres("Ceres1", evalDef1);
            GameEngineDef     ged2 = new GameEngineDefCeres("Ceres1", evalDef2);
            GameEngineUCISpec geSF = new GameEngineUCISpec("SF12", @"\\synology\dev\chess\engines\stockfish_20090216_x64_avx2.exe",
                                                           32, 2048, CeresUserSettingsManager.Settings.DirTablebases,
                                                           uciSetOptionCommands: extraUCI);

            EnginePlayerDef ceresEngineDef1 = new EnginePlayerDef(ged1, limit);
            EnginePlayerDef ceresEngineDef2 = new EnginePlayerDef(ged2, limit);

            GameEngineDefUCI sf12EngineDef = new GameEngineDefUCI("SF12", geSF);
            EnginePlayerDef  sfEngine      = new EnginePlayerDef(sf12EngineDef, limit * 875);

            SuiteTestDef def = new SuiteTestDef("Test1", @"\\synology\dev\chess\data\epd\ERET_VESELY203.epd",
                                                ceresEngineDef1, ceresEngineDef2, sfEngine);

            def.MaxNumPositions = 1500;

            SuiteTestRunner ser = new SuiteTestRunner(def);

            ser.Run(PARALLELISM, true);
        }
Esempio n. 4
0
        /// <summary>
        /// Actually runs a search with specified limits.
        /// </summary>
        /// <param name="searchLimit"></param>
        /// <returns></returns>
        private GameEngineSearchResultCeres RunSearch(SearchLimit searchLimit)
        {
            DateTime lastInfoUpdate = DateTime.Now;

            int numUpdatesSent = 0;

            MCTSManager.MCTSProgressCallback callback =
                (manager) =>
            {
                curManager = manager;

                DateTime now = DateTime.Now;
                float    timeSinceLastUpdate = (float)(now - lastInfoUpdate).TotalSeconds;

                bool  isFirstUpdate           = numUpdatesSent == 0;
                float UPDATE_INTERVAL_SECONDS = isFirstUpdate ? 0.1f : 0.5f;
                if (curManager != null && timeSinceLastUpdate > UPDATE_INTERVAL_SECONDS && curManager.Root.N > 0)
                {
                    Send(UCIInfoString(curManager));

                    numUpdatesSent++;
                    lastInfoUpdate = now;
                }
            };

            GameEngineCeresInProcess.ProgressCallback callbackPlain = obj => callback((MCTSManager)obj);

            // use this? movesSinceNewGame

            // Search from this position (possibly with tree reuse)
            GameEngineSearchResultCeres result = CeresEngine.Search(curPositionAndMoves, searchLimit, gameMoveHistory, callbackPlain) as GameEngineSearchResultCeres;

            GameMoveStat moveStat = new GameMoveStat(gameMoveHistory.Count,
                                                     curPositionAndMoves.FinalPosition.MiscInfo.SideToMove,
                                                     result.ScoreQ, result.ScoreCentipawns,
                                                     float.NaN, //engine1.CumulativeSearchTimeSeconds,
                                                     curPositionAndMoves.FinalPosition.PieceCount,
                                                     result.MAvg, result.FinalN, result.FinalN - result.StartingN,
                                                     searchLimit,
                                                     (float)result.TimingStats.ElapsedTimeSecs);

            gameMoveHistory.Add(moveStat);

            if (SearchFinishedEvent != null)
            {
                SearchFinishedEvent(result.Search.Manager);
            }

            // Send the final info string (unless this was an instamove).
            Send(UCIInfoString(result.Search.Manager, result.Search.BestMoveRoot));

            // Send the best move
            Send("bestmove " + result.Search.BestMove.MoveStr(MGMoveNotationStyle.LC0Coordinate));
            if (debug)
            {
                Send("info string " + result.Search.BestMoveRoot.BestMoveInfo(false));
            }

            return(result);
        }
Esempio n. 5
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);
        }
Esempio n. 6
0
        /// <summary>
        /// Launches the search with specified limit.
        /// </summary>
        /// <param name="searchLimit"></param>
        /// <param name="progressCallback"></param>
        /// <param name="nnRemoteEvaluatorExtraSuffix"></param>
        /// <returns></returns>
        internal (TimingStats, MCTSNode) DoSearch(SearchLimit searchLimit,
                                                  MCTSProgressCallback progressCallback)
        {
            CheckMemoryExhaustion();

            ThreadSearchContext = this.Context;

            Context.ProgressCallback = progressCallback;

            TimingStats stats = new TimingStats();

            int numMCTSNodesProcessedTotal = 0;

            using (new TimingBlock($"MCTS SEARCH {searchLimit}", stats, TimingBlock.LoggingType.None))
            {
                flow = new MCTSSearchFlow(this, Context);

                int batchNum = 0;

                int  hardLimitNumNodes = -1;
                bool shouldProcess     = true;
                if (searchLimit.Type == SearchLimitType.NodesPerMove)
                {
                    if (Root.N >= searchLimit.Value)
                    {
                        shouldProcess = false;
                    }
                    else
                    {
                        hardLimitNumNodes = (int)searchLimit.Value - Root.N;
                    }
                }

                StartTimeFirstVisit = DateTime.Now;
                if (shouldProcess)
                {
                    flow.ProcessDirectOverlapped(this, hardLimitNumNodes, batchNum, null);
                }

                batchNum++;
            }

            // Make sure nothing was left in flight after the search
            if ((Root.NInFlight != 0 || Root.NInFlight2 != 0) && !haveWarned)
            {
                Console.WriteLine($"Internal error: search ended with N={Root.N} NInFlight={Root.NInFlight} NInFlight2={Root.NInFlight2} {Root}");
                haveWarned = true;
            }

            // Possibly validate tree integrity.
            if (MCTSDiagnostics.VerifyTreeIntegrityAtSearchEnd)
            {
                using (new SearchContextExecutionBlock(Context))
                {
                    Context.Tree.Store.Validate();
                }
            }

            return(stats, Root);
        }
Esempio n. 7
0
 /// <summary>
 /// Sets the search limit to be applied to the iterated search.
 /// </summary>
 /// <param name="overallLimit"></param>
 public void SetForSearchLimit(SearchLimit overallLimit)
 {
     foreach (IteratedMCTSStepDef step in StepDefs)
     {
         step.SetForOverallLimit(overallLimit);
     }
 }
 /// <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);
 }
Esempio n. 9
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="store"></param>
        /// <param name="reuseOtherContextForEvaluatedNodes"></param>
        /// <param name="reusePositionCache"></param>
        /// <param name="reuseTranspositionRoots"></param>
        /// <param name="nnEvaluators"></param>
        /// <param name="searchParams"></param>
        /// <param name="childSelectParams"></param>
        /// <param name="searchLimit"></param>
        /// <param name="paramsSearchExecutionPostprocessor"></param>
        /// <param name="limitManager"></param>
        /// <param name="startTime"></param>
        /// <param name="priorManager"></param>
        /// <param name="gameMoveHistory"></param>
        /// <param name="isFirstMoveOfGame"></param>
        public MCTSManager(MCTSNodeStore store,
                           MCTSIterator reuseOtherContextForEvaluatedNodes,
                           PositionEvalCache reusePositionCache,
                           TranspositionRootsDict reuseTranspositionRoots,
                           NNEvaluatorSet nnEvaluators,
                           ParamsSearch searchParams,
                           ParamsSelect childSelectParams,
                           SearchLimit searchLimit,
                           ParamsSearchExecutionModifier paramsSearchExecutionPostprocessor,
                           IManagerGameLimit limitManager,
                           DateTime startTime,
                           MCTSManager priorManager,
                           List <GameMoveStat> gameMoveHistory,
                           bool isFirstMoveOfGame)
        {
            if (searchLimit.IsPerGameLimit)
            {
                throw new Exception("Per game search limits not supported");
            }

            StartTimeThisSearch                = startTime;
            RootNWhenSearchStarted             = store.Nodes.nodes[store.RootIndex.Index].N;
            ParamsSearchExecutionPostprocessor = paramsSearchExecutionPostprocessor;
            IsFirstMoveOfGame = isFirstMoveOfGame;
            SearchLimit       = searchLimit;


            // Make our own copy of move history.
            PriorMoveStats = new List <GameMoveStat>();
            if (gameMoveHistory != null)
            {
                PriorMoveStats.AddRange(gameMoveHistory);
            }

            // Possibly autoselect new optimal parameters
            ParamsSearchExecutionChooser paramsChooser = new ParamsSearchExecutionChooser(nnEvaluators.EvaluatorDef,
                                                                                          searchParams, childSelectParams, searchLimit);

            // TODO: technically this is overwriting the params belonging to the prior search, that's ugly (but won't actually cause a problem)
            paramsChooser.ChooseOptimal(searchLimit.EstNumNodes(50_000, false), paramsSearchExecutionPostprocessor); // TODO: make 50_000 smarter


            int estNumNodes = EstimatedNumSearchNodesForEvaluator(searchLimit, nnEvaluators);

            // Adjust the nodes estimate if we are continuing an existing search
            if (searchLimit.Type == SearchLimitType.NodesPerMove && RootNWhenSearchStarted > 0)
            {
                estNumNodes = Math.Max(0, estNumNodes - RootNWhenSearchStarted);
            }
            Context = new MCTSIterator(store, reuseOtherContextForEvaluatedNodes, reusePositionCache, reuseTranspositionRoots,
                                       nnEvaluators, searchParams, childSelectParams, searchLimit, estNumNodes);
            ThreadSearchContext = Context;

            TerminationManager = new MCTSFutilityPruning(this, searchLimit.SearchMoves);
            LimitManager       = limitManager;

            CeresEnvironment.LogInfo("MCTS", "Init", $"SearchManager created for store {store}", InstanceID);
        }
Esempio n. 10
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);
            }
        }
Esempio n. 11
0
        /// <summary>
        /// Analyzes a position until a specified search limit is exhausted.
        /// </summary>
        /// <param name="fen">a FEN</param>
        /// <param name="nodes"></param>
        /// <returns></returns>
        public LC0VerboseMoveStats AnalyzePositionFromFENAndMoves(string startFEN, string movesStr, string endFEN,
                                                                  SearchLimit searchLimit)
        {
            Position positionEnd            = Position.FromFEN(endFEN);
            List <LC0VerboseMoveStat> moves = new List <LC0VerboseMoveStat>();

            UCISearchInfo searchInfo;

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

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

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

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

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

                searchInfo = Runner.EvalPositionRemainingTime(startFEN, movesStr,
                                                              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(positionEnd, 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);
        }
Esempio n. 12
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));
        /// <summary>
        /// Resturns a SeachLimit by parsing a string limit specification.
        /// </summary>
        /// <param name="specificationString"></param>
        /// <returns></returns>
        public static SearchLimit Parse(string specificationString)
        {
            SearchLimit limit = TryParse(specificationString, out string errorString);

            if (limit == null)
            {
                throw new Exception($"Error parsing SearchLimit specification string {specificationString}");
            }
            else
            {
                return(limit);
            }
        }
        /// <summary>
        /// Resturns a SeachLimit by parsing a string limit specification.
        /// </summary>
        /// <param name="specificationString"></param>
        /// <returns></returns>
        public static SearchLimit Parse(string specificationString)
        {
            SearchLimit limit = TryParse(specificationString, out string errorString);

            if (limit == null)
            {
                throw new Exception($"Error parsing SearchLimit specification string {specificationString}. "
                                    + "Expecting a number followed by NM, NG, SM or SG (nodes/seconds per move/game).");
            }
            else
            {
                return(limit);
            }
        }
Esempio n. 15
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);
        }
Esempio n. 16
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="nnEvaluatorDef"></param>
        /// <param name="paramsSearch"></param>
        /// <param name="paramsSelect"></param>
        /// <param name="searchLimit"></param>
        public ParamsSearchExecutionChooser(NNEvaluatorDef nnEvaluatorDef,
                                            ParamsSearch paramsSearch,
                                            ParamsSelect paramsSelect,
                                            SearchLimit searchLimit)
        {
            // Make sure params arguments look initialized
            if (nnEvaluatorDef == null)
            {
                throw new ArgumentNullException(nameof(nnEvaluatorDef));
            }

            NNEvaluatorDef = nnEvaluatorDef;
            ParamsSearch   = paramsSearch;
            ParamsSelect   = paramsSelect;
            SearchLimit    = searchLimit with {
            };
        }
Esempio n. 17
0
        /// <summary>
        /// Parses a specification of search time in UCI format into an equivalent SearchLimit.
        /// Returns null if parsing failed.
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        private SearchLimit GetSearchLimit(string command)
        {
            bool weAreWhite           = curPositionAndMoves.FinalPosition.MiscInfo.SideToMove == SideType.White;
            UCIGoCommandParsed goInfo = new UCIGoCommandParsed(command, weAreWhite);

            if (!goInfo.IsValid)
            {
                return(null);
            }

            if (goInfo.Nodes.HasValue)
            {
                return(SearchLimit.NodesPerMove(goInfo.Nodes.Value));
            }
            else if (goInfo.MoveTime.HasValue)
            {
                return(SearchLimit.SecondsPerMove(goInfo.MoveTime.Value / 1000.0f));
            }
            else if (goInfo.Infinite)
            {
                // TODO: determine a reasonable maximum value based on memory in computer
                return(SearchLimit.NodesPerMove(MCTSNodeStore.MAX_NODES));
            }
            else if (goInfo.TimeOurs.HasValue)
            {
                float increment = 0;
                if (goInfo.IncrementOurs.HasValue)
                {
                    increment = goInfo.IncrementOurs.Value / 1000.0f;
                }

                int?movesToGo = null;
                if (goInfo.MovesToGo.HasValue)
                {
                    movesToGo = goInfo.MovesToGo.Value;
                }

                return(SearchLimit.SecondsForAllMoves(goInfo.TimeOurs.Value / 1000.0f, increment, movesToGo, true));
            }
            else
            {
                Console.WriteLine($"Unsupported time control in UCI go command {command}");
                return(null);
            }
        }
Esempio n. 18
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));
        }
Esempio n. 19
0
        private void Search()
        {
            int seed = 2; //This is a good seed to show the crash

            /* Assigning first tools */
            DecisionBuilder myToolAssignmentPhase =
                new RandomSelectToolHeuristic(this, seed);

            /* Ranking of the tools */
            DecisionBuilder sequencingPhase =
                solver.MakePhase(allToolSequences, Solver.SEQUENCE_DEFAULT);

            /* Then fixing time of tasks as early as possible */
            DecisionBuilder timingPhase = solver.MakePhase(
                makespan, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);

            /* Overall phase */
            DecisionBuilder mainPhase =
                solver.Compose(myToolAssignmentPhase, sequencingPhase, timingPhase);

            /* Logging */
            const int     logFrequency = 1000000;
            SearchMonitor searchLog    = solver.MakeSearchLog(logFrequency, objective);

            /* Restarts */
            SearchMonitor searchRestart = solver.MakeLubyRestart(100);

            /* Search Limit in ms */
            SearchLimit limit = solver.MakeTimeLimit(180 * 1000);

            /* Collecting best solution */
            SolutionCollector collector = solver.MakeLastSolutionCollector();

            collector.AddObjective(makespan);

            //collector.Add( pile.ToArray() );
            solver.NewSearch(mainPhase, searchLog, searchRestart, objective, limit);
            while (solver.NextSolution())
            {
                Console.WriteLine("MAKESPAN: " + makespan.Value());
            }
        }
Esempio n. 20
0
        public static void TestSF(int index, bool gitVersion)
        {
            NNEvaluatorDef     evalDef1        = NNEvaluatorDefFactory.FromSpecification("LC0:j94-100", "GPU:" + index);
            GameEngineDefCeres engineDefCeres1 = new GameEngineDefCeres("CeresInProc", evalDef1,
                                                                        new ParamsSearch(), null, new ParamsSelect(),
                                                                        null, "CeresSF.log.txt");

            SearchLimit limitCeres = SearchLimit.SecondsForAllMoves(60, 1.25f) * 0.15f;
            SearchLimit limitSF    = limitCeres * 1.5f;

            GameEngineDef   engineDefCeresUCIGit = new GameEngineDefCeresUCI("CeresUCIGit", evalDef1, overrideEXE: @"C:\ceres\releases\v0.88\ceres.exe");
            EnginePlayerDef playerCeres          = new EnginePlayerDef(gitVersion ? engineDefCeresUCIGit : engineDefCeres1,
                                                                       limitCeres);


            EnginePlayerDef playerSF = new EnginePlayerDef(engineDefStockfish13, limitSF);

            TournamentDef def = new TournamentDef("TOURN", playerCeres, playerSF);

            def.OpeningsFileName = "TCEC1819.pgn";
            //def.NumGamePairs = 10;
            def.ShowGameMoves = false;

            TournamentManager runner = new TournamentManager(def, 1);

            TournamentResultStats results;
            TimingStats           stats = new TimingStats();

            using (new TimingBlock(stats, TimingBlock.LoggingType.None))
            {
                results = runner.RunTournament();
            }

            Console.WriteLine();
            Console.WriteLine($"Tournament completed in {stats.ElapsedTimeSecs,8:F2} seconds.");
            Console.WriteLine(playerCeres + " " + results.GameOutcomesString);
        }
Esempio n. 21
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);
        }
Esempio n. 22
0
    //  We don't need helper functions here
    //  Csharp syntax is easier than C++ syntax!

    private static void CPisFun(int kBase, int time_limit_param, bool print)
    {
        // Use some profiling and change the default parameters of the solver
        SolverParameters solver_params = new SolverParameters();

        // Change the profile level
        solver_params.profile_level = SolverParameters.NORMAL_PROFILING;

        //  Constraint Programming engine
        Solver solver = new Solver("CP is fun!", solver_params);

        // Decision variables
        IntVar c = solver.MakeIntVar(1, kBase - 1, "C");
        IntVar p = solver.MakeIntVar(0, kBase - 1, "P");
        IntVar i = solver.MakeIntVar(1, kBase - 1, "I");
        IntVar s = solver.MakeIntVar(0, kBase - 1, "S");
        IntVar f = solver.MakeIntVar(1, kBase - 1, "F");
        IntVar u = solver.MakeIntVar(0, kBase - 1, "U");
        IntVar n = solver.MakeIntVar(0, kBase - 1, "N");
        IntVar t = solver.MakeIntVar(1, kBase - 1, "T");
        IntVar r = solver.MakeIntVar(0, kBase - 1, "R");
        IntVar e = solver.MakeIntVar(0, kBase - 1, "E");

        // We need to group variables in a vector to be able to use
        // the global constraint AllDifferent
        IntVar[] letters = new IntVar[] { c, p, i, s, f, u, n, t, r, e };

        // Check if we have enough digits
        if (kBase < letters.Length)
        {
            throw new Exception("kBase < letters.Length");
        }

        //  Constraints
        solver.Add(letters.AllDifferent());

        // CP + IS + FUN = TRUE
        solver.Add(p + s + n + kBase * (c + i + u) + kBase * kBase * f ==
                   e + kBase * u + kBase * kBase * r + kBase * kBase * kBase * t);

        SolutionCollector all_solutions = solver.MakeAllSolutionCollector();

        //  Add the interesting variables to the SolutionCollector
        all_solutions.Add(letters);

        //  Decision Builder: hot to scour the search tree
        DecisionBuilder db = solver.MakePhase(letters,
                                              Solver.CHOOSE_FIRST_UNBOUND,
                                              Solver.ASSIGN_MIN_VALUE);

        // Add some time limit
        SearchLimit time_limit = solver.MakeTimeLimit(time_limit_param);

        solver.Solve(db, all_solutions, time_limit);

        //  Retrieve the solutions
        int numberSolutions = all_solutions.SolutionCount();

        Console.WriteLine("Number of solutions: " + numberSolutions);

        if (print)
        {
            for (int index = 0; index < numberSolutions; ++index)
            {
                Console.Write("C=" + all_solutions.Value(index, c));
                Console.Write(" P=" + all_solutions.Value(index, p));
                Console.Write(" I=" + all_solutions.Value(index, i));
                Console.Write(" S=" + all_solutions.Value(index, s));
                Console.Write(" F=" + all_solutions.Value(index, f));
                Console.Write(" U=" + all_solutions.Value(index, u));
                Console.Write(" N=" + all_solutions.Value(index, n));
                Console.Write(" T=" + all_solutions.Value(index, t));
                Console.Write(" R=" + all_solutions.Value(index, r));
                Console.Write(" E=" + all_solutions.Value(index, e));
                Console.WriteLine();
            }
        }

        // Save profile in file
        solver.ExportProfilingOverview("profile.txt");
    }
        /// <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,
Esempio n. 24
0
        /// <summary>
        /// Test code. Currently configured for 703810 using 2A100 versus LC0.
        /// </summary>
        public static void Test()
        {
            string ETHERAL_EXE = @"\\synology\dev\chess\engines\Ethereal12.75-x64-popcnt-avx2.exe";
            string SF11_EXE    = @"\\synology\dev\chess\engines\stockfish_11_x64_bmi2.exe";
            string SF12_EXE    = @"\\synology\dev\chess\engines\stockfish_20090216_x64_avx2.exe";

            GameEngineUCISpec specEthereal = new GameEngineUCISpec("Ethereal12", ETHERAL_EXE);
            GameEngineUCISpec specSF       = new GameEngineUCISpec("SF12", SF12_EXE);
            GameEngineUCISpec specLC0      = new GameEngineUCISpec("LC0", "lc0.exe");
            // 66511
            //NNEvaluatorDef def1 = NNEvaluatorDefFactory.SingleNet("j92-280", NNEvaluatorType.LC0Dll,1);

//      NNEvaluatorDef def0 = NNEvaluatorDefFactory.FromSpecification("LC0:j92-280", "GPU:1:POOLED");// POOLED");//:POOLED");
//      NNEvaluatorDef def1 = NNEvaluatorDefFactory.FromSpecification("LC0:66666", "GPU:1:POOLED");// POOLED");//:POOLED");
            NNEvaluatorDef evalDef1 = NNEvaluatorDefFactory.FromSpecification("LC0:703810", "GPU:0,1"); // POOLED");//:POOLED");
            NNEvaluatorDef evalDef2 = NNEvaluatorDefFactory.FromSpecification("LC0:703810", "GPU:0,1"); // POOLED");//:POOLED");

            // sv5300 j104.0-10000
            //      def1.MakePersistent();
            //def1.PersistentID = "PERSIST";

            //      NNEvaluatorDef def2 = NNEvaluatorDefFactory.FromSpecification("LC0:66581", "GPU:3");//:POOLED");


            //      SearchLimit slLC0 = SearchLimit.NodesPerMove(10_000);
            //      SearchLimit slEthereal = slLC0 * 875;
            //      SearchLimit slSF = slLC0 * 875;

            //specEthereal = specSF;

            string[] extraUCI = null;// new string[] {"setoption name Contempt value 5000" };

            const int NUM_THREADS  = 32;
            const int HASH_SIZE_MB = 2048;
            string    TB_PATH      = CeresUserSettingsManager.Settings.DirTablebases;

            SearchLimit limit = SearchLimit.NodesPerMove(1_000);

            //limit = SearchLimit.SecondsForAllMoves(60);
            //limit = SearchLimit.SecondsForAllMoves(60, 1f);
            limit = SearchLimit.SecondsForAllMoves(120, 0.5f);


            GameEngineDefCeres engineDefCeres1 = new GameEngineDefCeres("Ceres1", evalDef1, new ParamsSearch(), null, new ParamsSelect(), null);
            GameEngineDefCeres engineDefCeres2 = new GameEngineDefCeres("Ceres2", evalDef2, new ParamsSearch(), null, new ParamsSelect(), null);

            bool forceDisableSmartPruning = limit.IsNodesLimit;

            if (forceDisableSmartPruning)
            {
                engineDefCeres1.SearchParams.FutilityPruningStopSearchEnabled = false;
                engineDefCeres2.SearchParams.FutilityPruningStopSearchEnabled = false;
            }
            GameEngineDef engineDefEthereal    = new GameEngineDefUCI("Etheral", new GameEngineUCISpec("Etheral", ETHERAL_EXE, NUM_THREADS, HASH_SIZE_MB, TB_PATH, uciSetOptionCommands: extraUCI));
            GameEngineDef engineDefStockfish11 = new GameEngineDefUCI("SF11", new GameEngineUCISpec("SF11", SF11_EXE, NUM_THREADS, HASH_SIZE_MB, TB_PATH, uciSetOptionCommands: extraUCI));
            GameEngineDef engineDefStockfish12 = new GameEngineDefUCI("SF12", new GameEngineUCISpec("SF12", SF12_EXE, NUM_THREADS, HASH_SIZE_MB, TB_PATH, uciSetOptionCommands: extraUCI));

            //GameEngineDef engineDefCeresUCI = new GameEngineDefUCI("CeresUCI", new GameEngineUCISpec("CeresUCI", @"c:\dev\ceres\artifacts\release\net5.0\ceres.exe"));
            GameEngineDef engineDefCeresUCI = new GameEngineDefCeresUCI("CeresUCI", evalDef1, overrideEXE: @"c:\dev\ceres\artifacts\release\net5.0\ceres.exe");


            GameEngineDefLC0 engineDefLC1 = new GameEngineDefLC0("LC0_0", evalDef1, forceDisableSmartPruning, null, null);
            GameEngineDefLC0 engineDefLC2 = new GameEngineDefLC0("LC0_1", evalDef2, forceDisableSmartPruning, null, null);


            EnginePlayerDef playerCeres1UCI = new EnginePlayerDef(engineDefCeresUCI, limit);
            EnginePlayerDef playerCeres2UCI = new EnginePlayerDef(engineDefCeresUCI, limit);

            EnginePlayerDef playerCeres1      = new EnginePlayerDef(engineDefCeres1, limit);
            EnginePlayerDef playerCeres2      = new EnginePlayerDef(engineDefCeres2, limit);
            EnginePlayerDef playerEthereal    = new EnginePlayerDef(engineDefEthereal, limit);
            EnginePlayerDef playerStockfish11 = new EnginePlayerDef(engineDefStockfish11, limit);
            EnginePlayerDef playerStockfish12 = new EnginePlayerDef(engineDefStockfish12, limit);
            EnginePlayerDef playerLC0         = new EnginePlayerDef(engineDefLC1, limit);
            EnginePlayerDef playerLC1         = new EnginePlayerDef(engineDefLC2, limit);

            //      def.SearchLimitEngine1 = def.SearchLimitEngine2 = SearchLimit.SecondsForAllMoves(15, 0.25f);
            //      def.SearchLimitEngine2 = def.SearchLimitEngine2 = SearchLimit.SecondsForAllMoves(15, 0.25f);


//(playerCeres1.EngineDef as GameEngineDefCeres).SearchParams.DrawByRepetitionLookbackPlies = 40;

            if (false)
            {
                // ===============================================================================
                SuiteTestDef suiteDef = new SuiteTestDef("Suite", @"\\synology\dev\chess\data\epd\endgame2.epd", playerCeres1, playerCeres2, null);
//        suiteDef.MaxNumPositions = 100;
                SuiteTestRunner suiteRunner = new SuiteTestRunner(suiteDef);
                suiteRunner.Run(1, true, false);
                return;
                // ===============================================================================
            }

            //      engineDefCeres2.SearchParams.TwofoldDrawEnabled = false;
            //engineDefCeres1.SearchParams.TreeReuseEnabled = false;
            //engineDefCeres2.SearchParams.TreeReuseEnabled = false;
//engineDefCeres1.SearchParams.FutilityPruningStopSearchEnabled= false;
//engineDefCeres2.SearchParams.FutilityPruningStopSearchEnabled= false;
//engineDefLC0.SearchParamsEmulate.FutilityPruningStopSearchEnabled= false;

            //TournamentDef def = new TournamentDef("TOURN", playerCeres1, playerLC0);
            TournamentDef def = new TournamentDef("TOURN", playerCeres1, playerLC0);

            def.NumGamePairs = 50;
//      def.ShowGameMoves = false;

//      def.OpeningsFileName = @"HERT_2017\Hert500.pgn";

//      def.StartingFEN = "1q6/2n4k/1r1p1pp1/RP1P2p1/2Q1P1P1/2N4P/3K4/8 b - - 8 71";
//      def.OpeningsFileName = @"\\synology\dev\chess\data\openings\Drawkiller_500pos_reordered.pgn";//
            def.OpeningsFileName = "TCEC19_NoomenSelect.pgn";
//      def.OpeningsFileName = "TCEC1819.pgn";

            //      def.AdjudicationThresholdCentipawns = 500;
            //      def.AdjudicationThresholdNumMoves = 3;

            const int         CONCURRENCY = 1;// 15;
            TournamentManager runner      = new TournamentManager(def, CONCURRENCY);

            TournamentResultStats results;

//UCIEngineProcess.VERBOSE = true;

            TimingStats stats = new TimingStats();

            using (new TimingBlock(stats, TimingBlock.LoggingType.None))
            {
                results = runner.RunTournament();
            }

            Console.WriteLine();
            Console.WriteLine($"Tournament completed in {stats.ElapsedTimeSecs,8:F2} seconds.");
            Console.WriteLine(results.GameOutcomesString);
        }
Esempio n. 25
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
        }
Esempio n. 26
0
        public static void Solve(FlexibleJobShopData data, bool chatty = false)
        {
            // Compute horizon
            var horizon = data.JobList.Sum(
                j => j.TaskList.Sum(
                    t => t.Durations.Max()));


            // ----- Create all intervals and vars -----

            /*
             * // Use some profiling and change the default parameters of the solver
             * SolverParameters solverParams = new SolverParameters();
             * // Change the profile level
             * solverParams.profile_level = SolverParameters.NORMAL_PROFILING;
             * // Constraint programming engine
             * Solver solver = new Solver("JobShop", solverParams);
             */
            // Constraint programming engine
            Solver solver = new Solver($"FlexibleJobShop: {data.Name}");

            // Initialize dictionaries to hold references to task intervals
            var tasksByJobId = new Dictionary <uint, List <TaskAlternative> >();

            foreach (var job in data.JobList)
            {
                tasksByJobId[job.Id] = new List <TaskAlternative>(job.TaskList.Count);
            }
            var tasksByMachineId = new Dictionary <uint, IntervalVarVector>();

            foreach (var machine in data.MachineList)
            {
                tasksByMachineId[machine.Id] = new IntervalVarVector();
            }

            // Creates all individual interval variables and collect in dictionaries
            foreach (var job in data.JobList)
            {
                foreach (var task in job.TaskList)
                {
                    var alternative = new TaskAlternative(job.Id);
                    tasksByJobId[job.Id].Add(alternative);
                    var activeVariables = new IntVarVector();
                    var hasAlternatives = task.AlternativesCount > 1;
                    for (int alt = 0; alt < task.AlternativesCount; alt++)
                    {
                        var         machine  = task.Machines[alt];
                        var         duration = task.Durations[alt];
                        var         name     = $"{task.Name}; Alternative {alt}: {machine.Name}, Duration {duration}";
                        IntervalVar interval = solver.MakeFixedDurationIntervalVar(0, horizon, duration, hasAlternatives, name);
                        alternative.Add(interval);
                        tasksByMachineId[machine.Id].Add(interval);
                        if (hasAlternatives)
                        {
                            activeVariables.Add(interval.PerformedExpr().Var());
                        }
                    }
                    alternative.AlternativeVar = solver.MakeIntVar(0, task.AlternativesCount - 1, task.Name);
                    if (hasAlternatives)
                    {
                        solver.Add(solver.MakeMapDomain(alternative.AlternativeVar, activeVariables));
                    }
                }
            }


            // ----- Create model -----

            // Create precedences inside jobs
            foreach (var taskAltList in tasksByJobId.Values)
            {
                for (int i = 0; i < taskAltList.Count - 1; i++)
                {
                    TaskAlternative currentTaskAlt = taskAltList[i];
                    TaskAlternative nextTaskAlt    = taskAltList[i + 1];
                    foreach (var alt1 in currentTaskAlt)
                    {
                        foreach (var alt2 in nextTaskAlt)
                        {
                            solver.Add(solver.MakeIntervalVarRelation(
                                           alt2, Solver.STARTS_AFTER_END, alt1));
                        }
                    }
                }
            }

            // Collect alternative variables.
            IntVarVector alternativeVariableVec = new IntVarVector();

            foreach (var taskAltList in tasksByJobId.Values)
            {
                foreach (var taskAlt in taskAltList)
                {
                    if (!taskAlt.AlternativeVar.Bound())
                    {
                        alternativeVariableVec.Add(taskAlt.AlternativeVar);
                    }
                }
            }

            // Add disjunctive constraints on unary resources, and create
            // sequence variables. A sequence variable is a dedicated variable
            // whose job is to sequence interval variables.
            SequenceVarVector allSequences = new SequenceVarVector();

            foreach (var machine in data.MachineList)
            {
                DisjunctiveConstraint disjCt = solver.MakeDisjunctiveConstraint(
                    tasksByMachineId[machine.Id], machine.Name);
                solver.Add(disjCt);
                allSequences.Add(disjCt.SequenceVar());
            }

            // Create array of end_times of jobs
            IntVarVector endsVec = new IntVarVector();

            foreach (var taskAltList in tasksByJobId.Values)
            {
                TaskAlternative lastTaskAlt = taskAltList.Last();
                foreach (var alt in lastTaskAlt)
                {
                    endsVec.Add(alt.SafeEndExpr(-1).Var());
                }
            }

            // Objective: minimize the makespan (maximum end times of all tasks)
            // of the problem.
            IntVar      objectiveVar     = solver.MakeMax(endsVec).Var();
            OptimizeVar objectiveMonitor = solver.MakeMinimize(objectiveVar, 1);


            // ----- Search monitors and decision builder -----

            // This decision builder will assign all alternative variables.
            DecisionBuilder alternativePhase = solver.MakePhase(alternativeVariableVec, Solver.CHOOSE_MIN_SIZE, Solver.ASSIGN_MIN_VALUE);

            // This decision builder will rank all tasks on all machines.
            DecisionBuilder sequencePhase = solver.MakePhase(allSequences, Solver.SEQUENCE_DEFAULT);

            // After the ranking of tasks, the schedule is still loose and any
            // task can be postponed at will. But, because the problem is now a PERT
            // (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
            // we can schedule each task at its earliest start time. This is
            // conveniently done by fixing the objective variable to its
            // minimum value.
            DecisionBuilder objectivePhase = solver.MakePhase(objectiveVar, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);

            // The main decision builder (ranks all tasks, then fixes the
            // objective_variable).
            DecisionBuilder mainPhase = solver.Compose(alternativePhase, sequencePhase, objectivePhase);

            // Search log
            const int     kLogFrequency = 1000000;
            SearchMonitor searchLog     = solver.MakeSearchLog(kLogFrequency, objectiveMonitor);

            const long  FLAGS_time_limit_in_ms = 1000 * 60 * 20;
            SearchLimit limit = null;

            if (FLAGS_time_limit_in_ms > 0)
            {
                limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms);
            }

            SolutionCollector collector = solver.MakeLastSolutionCollector();

            collector.AddObjective(objectiveVar);
            collector.Add(alternativeVariableVec);
            collector.Add(allSequences);

            foreach (var taskVec in tasksByMachineId.Values)
            {
                foreach (var task in taskVec)
                {
                    collector.Add(task.StartExpr().Var());
                }
            }


            // ----- Search -----

            bool solutionFound = solver.Solve(mainPhase, searchLog, objectiveMonitor, limit, collector);

            if (solutionFound)
            {
                // The index of the solution from the collector
                const int  SOLUTION_INDEX = 0;
                Assignment solution       = collector.Solution(SOLUTION_INDEX);

                Console.WriteLine();
                uint machineIdx = 0;
                foreach (var seq in allSequences)
                {
                    machineIdx++;
                    var taskSeq = collector.ForwardSequence(SOLUTION_INDEX, seq);
                    Console.WriteLine($"{seq.Name()}:");
                    Console.WriteLine("  Tasks: " + string.Join(", ", taskSeq));

                    //foreach (var taskIndex in storedSequence)
                    //{
                    //    IntervalVar task = sequence.Interval(taskIndex);
                    //    long startMin = solution.StartMin(task);
                    //    long startMax = solution.StartMax(task);
                    //    if (startMin == startMax)
                    //    {
                    //        Console.WriteLine($"Task {task.Name()} starts at {startMin}.");
                    //    }
                    //    else
                    //    {
                    //        Console.WriteLine($"Task {task.Name()} starts between {startMin} and {startMax}.");
                    //    }
                    //}


                    Console.WriteLine();
                    Console.Write("  Starting times:");
                    foreach (var s in taskSeq)
                    {
                        Console.Write(" " + collector.Value(0, tasksByMachineId[machineIdx][s].StartExpr().Var()).ToString());
                    }
                    Console.WriteLine();
                }
                //var x = tasksByMachineId[1][0].StartExpr().Var();
                //var xValStr = collector.Value(0, x).ToString();
                Console.WriteLine("objective function value = " + solution.ObjectiveValue());  //TEMP
            }

            // Save profile in file
            //solver.ExportProfilingOverview("profile.txt");

            // Done
            solver.EndSearch();
        }
Esempio n. 27
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();
                                  }
        }
Esempio n. 28
0
        public SuiteTestResult Run(int numConcurrentSuiteThreads = 1, bool outputDetail = true, bool saveCacheWhenDone = true)
        {
            // Tree reuse is no help, indicate that we won't need it
            Def.Engine1Def.SearchParams.TreeReuseEnabled = false;
            if (Def.Engine2Def != null)
            {
                Def.Engine2Def.SearchParams.TreeReuseEnabled = false;
            }

            // Disable dump for now, the execution parameters are modified
            // for the warmup which is confusing because different parameters
            // will be chosen for the actual search.
            //DumpParams(Def.Output, true);

            // Create evaluators
            evaluatorSet1 = new NNEvaluatorSet(Def.Engine1Def.EvaluatorDef);
            if (Def.Engine2Def != null)
            {
                evaluatorSet2 = new NNEvaluatorSet(Def.Engine2Def.EvaluatorDef);
            }

            numConcurrentSuiteThreads = numConcurrentSuiteThreads;

            int timerFiredCount = 0;

            // TODO: add path automatically
            List <EPDEntry> epds = EPDEntry.EPDEntriesInEPDFile(Def.EPDFileName, int.MaxValue);

            if (Def.MaxNumPositions == 0)
            {
                Def.MaxNumPositions = epds.Count;
            }

            Def.Output.WriteLine();
            Def.Output.WriteLine("C1 = " + Def.Engine1Def.EvaluatorDef);
            if (Def.RunCeres2Engine)
            {
                Def.Output.WriteLine("C2 = " + Def.Engine2Def.EvaluatorDef);
            }
            if (Def.ExternalEngineDef != null)
            {
                Def.Output.WriteLine("EX = " + Def.ExternalEngineDef.EngineDef);
            }

#if NOT
            // To make up for the fact that LZ0 "cheats" by sometimes running over specified number of nodes
            // (she seems to always fill the batch even if reached limit), add half a batch extra for Ceres as compensation
            if (searchLimitCeres1.Type == SearchLimit.LimitType.NodesPerMove)
            {
                searchLimitCeres1 = new SearchLimit(searchLimit.Type, searchLimit.Value + paramsSearch1.BATCH_SIZE_PRIMARY / 2);
                searchLimitCeres2 = new SearchLimit(searchLimit.Type, searchLimit.Value + paramsSearch2.BATCH_SIZE_PRIMARY / 2);
            }
#endif

            //Def.Output.WriteLine($"MAX_CERES_GAME_THREADS {numConcurrentCeresGames} MAX_LEELA_GAME_THREADS {MAX_LEELA_GAME_THREADS}");

            // Turn of position reuse if evaluators produce different results
            if (Def.RunCeres2Engine && !Def.Engine1Def.EvaluatorDef.NetEvaluationsIdentical(Def.Engine2Def.EvaluatorDef))
            {
                Def.Engine1Def.SearchParams.ReusePositionEvaluationsFromOtherTree = false;
                Def.Engine2Def.SearchParams.ReusePositionEvaluationsFromOtherTree = false;
            }

            if (Def.RunCeres2Engine && (Def.Engine1Def.SearchParams.ReusePositionEvaluationsFromOtherTree ||
                                        Def.Engine2Def.SearchParams.ReusePositionEvaluationsFromOtherTree))
            {
                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("\r\nWARNING: REUSE_POSITION_EVALUATIONS_FROM_OTHER_TREE is turned on for one or both evaluators\r\n"
                                  + "(alternating between the two evaluators). This may cause slight differences in search behavior and speed.\r\n");
                Console.ForegroundColor = ConsoleColor.White;
            }

            Def.Output.WriteLine();

            if (Def.MaxNumPositions > epds.Count)
            {
                Def.MaxNumPositions = epds.Count;
            }
            epds = epds.GetRange(Def.FirstTestPosition, Def.MaxNumPositions);

            int numExternalGameProcesses = 1;

            numConcurrentSuiteThreads = Math.Min(Def.MaxNumPositions, numConcurrentSuiteThreads);

            if (numConcurrentSuiteThreads > 1)
            {
                bool evaluator1NonPooled = Def.Engine1Def.EvaluatorDef != null && Def.Engine1Def.EvaluatorDef.DeviceCombo != Chess.NNEvaluators.Defs.NNEvaluatorDeviceComboType.Pooled;
                bool evaluator2NonPooled = Def.Engine2Def.EvaluatorDef != null && Def.Engine2Def.EvaluatorDef.DeviceCombo != Chess.NNEvaluators.Defs.NNEvaluatorDeviceComboType.Pooled;

                if (evaluator1NonPooled || evaluator2NonPooled)
                {
                    throw new Exception("Must use POOLED neural network evaluator when running suites with parallelism");;
                }

                if (Def.ExternalEngineDef != null)
                {
                    // For safety (to not overflow main or GPU memory) we limit number of LC0 processes.
                    const int MAX_LC0_PROCESSES = 4;
                    numExternalGameProcesses = Math.Min(MAX_LC0_PROCESSES, numConcurrentSuiteThreads);
                }
            }

            bool          leelaVerboseMovesStats = true;//xxx Def.NumTestPos == 1;
            Func <object> makeExternalEngine     = null;

            if (Def.ExternalEngineDef != null)
            {
                if (Def.ExternalEngineDef.EngineDef is GameEngineDefLC0)
                {
                    bool forceDisableSmartPruning = (Def.ExternalEngineDef.EngineDef as GameEngineDefLC0).ForceDisableSmartPruning;
                    makeExternalEngine = () =>
                    {
                        LC0Engine engine = LC0EngineConfigured.GetLC0Engine(null, null, Def.Engine1Def.EvaluatorDef,
                                                                            NNWeightsFiles.LookupNetworkFile(Def.Engine1Def.EvaluatorDef.Nets[0].Net.NetworkID),
                                                                            true,
                                                                            false, leelaVerboseMovesStats, forceDisableSmartPruning);
                        // WARMUP
                        engine.AnalyzePositionFromFEN(Position.StartPosition.FEN, null, SearchLimit.NodesPerMove(1));
                        return(engine);
                    };
                }
                else
                {
                    bool resetMovesBetweenMoves = !Def.Engine2Def.SearchParams.TreeReuseEnabled;
                    bool enableTranpsositions   = Def.Engine2Def.SearchParams.Execution.TranspositionMode != TranspositionMode.None;
                    bool enableTablebases       = Def.Engine2Def.SearchParams.EnableTablebases;

                    makeExternalEngine = () => Def.ExternalEngineDef.EngineDef.CreateEngine();
                }
            }

            // Don't create too many non_Ceres threads since each one will consume seaprate GPU memory or threads
            int maxLeelaThreads = Math.Min(numExternalGameProcesses, numConcurrentSuiteThreads);
            ObjectPool <object> externalEnginePool = new ObjectPool <object>(makeExternalEngine, maxLeelaThreads);

            using (new TimingBlock("EPDS"))
            {
                Parallel.For(0, epds.Count,
                             new ParallelOptions()
                {
                    MaxDegreeOfParallelism = numConcurrentSuiteThreads
                },
                             delegate(int gameNum)
                {
                    try
                    {
                        EPDEntry epd = epds[gameNum];

                        // Skip positions which are already draws
                        if (epd.Position.CheckDrawBasedOnMaterial == Position.PositionDrawStatus.DrawByInsufficientMaterial)
                        {
                            return;
                        }
                        // TODO: also do this for checkmate?

                        ProcessEPD(gameNum, epds[gameNum], outputDetail, externalEnginePool);
                    }
                    catch (Exception exc)
                    {
                        Def.Output.WriteLine("Error in ProcessEPD " + exc);
                        throw exc;
                    }
                });
            }

            Def.Output.WriteLine();

            Def.Output.WriteLine();
            if (Def.ExternalEngineDef != null)
            {
                Def.Output.WriteLine($"Total {Def.ExternalEngineDef.ID} Time {totalTimeOther,6:F2}");
            }
            Def.Output.WriteLine($"Total C1 Time {totalTimeCeres1,6:F2}");
            if (Def.CeresEngine2Def != null)
            {
                Def.Output.WriteLine($"Total C2 Time {totalTimeCeres2,6:F2}");
            }

            Def.Output.WriteLine();
            if (Def.ExternalEngineDef != null)
            {
                Def.Output.WriteLine($"Avg {Def.ExternalEngineDef.ID} pos/sec    {totalNodesOther / totalTimeOther,8:F2}");
            }
            Def.Output.WriteLine($"Avg Ceres    pos/sec    {totalNodes1 / totalTimeCeres1,8:F2}");
            if (Def.CeresEngine2Def != null)
            {
                Def.Output.WriteLine($"Avg Ceres2    pos/sec    {totalNodes2 / totalTimeCeres2,8:F2}");
            }

            Def.Output.WriteLine();
            Def.Output.WriteLine();


            EngineCeres1.Dispose();
            EngineCeres2?.Dispose();
            EngineExternal?.Dispose();

            externalEnginePool.Shutdown(engineObj => (engineObj as LC0Engine).Dispose());
            evaluatorSet1.Dispose();
            evaluatorSet2?.Dispose();

            return(new SuiteTestResult(Def)
            {
                AvgScore1 = (float)accCeres1 / numSearches,
                AvgScore2 = (float)accCeres2 / numSearches,
                AvgWScore1 = (float)accWCeres1 / numSearches,
                AvgWScore2 = (float)accWCeres2 / numSearches,
                AvgScoreLC0 = (float)avgOther / numSearches,

                TotalRuntimeLC0 = totalTimeOther,
                TotalRuntime1 = totalTimeCeres1,
                TotalRuntime2 = totalTimeCeres2,

                TotalNodesLC0 = totalNodesOther,
                TotalNodes1 = totalNodes1,
                TotalNodes2 = totalNodes2
            });
        }
Esempio n. 29
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="store"></param>
        /// <param name="nnParams"></param>
        /// <param name="searchParams"></param>
        /// <param name="childSelectParams"></param>
        /// <param name="priorMoves">if null, the prior moves are taken from the passed store</param>
        /// <param name="searchLimit"></param>
        public MCTSManager(MCTSNodeStore store,
                           MCTSIterator reuseOtherContextForEvaluatedNodes,
                           PositionEvalCache reusePositionCache,
                           TranspositionRootsDict reuseTranspositionRoots,
                           NNEvaluatorSet nnEvaluators,
                           ParamsSearch searchParams,
                           ParamsSelect childSelectParams,
                           SearchLimit searchLimit,
                           ParamsSearchExecutionModifier paramsSearchExecutionPostprocessor,
                           IManagerGameLimit timeManager,
                           DateTime startTime,
                           MCTSManager priorManager,
                           List <GameMoveStat> gameMoveHistory,
                           bool isFirstMoveOfGame)
        {
            StartTimeThisSearch                = startTime;
            RootNWhenSearchStarted             = store.Nodes.nodes[store.RootIndex.Index].N;
            ParamsSearchExecutionPostprocessor = paramsSearchExecutionPostprocessor;
            IsFirstMoveOfGame = isFirstMoveOfGame;

            PriorMoveStats = new List <GameMoveStat>();

            // Make our own copy of move history.
            if (gameMoveHistory != null)
            {
                PriorMoveStats.AddRange(gameMoveHistory);
            }

            // Possibly convert time limit per game into time for this move.
            if (searchLimit.IsPerGameLimit)
            {
                SearchLimitType type = searchLimit.Type == SearchLimitType.SecondsForAllMoves
                                                       ? SearchLimitType.SecondsPerMove
                                                       : SearchLimitType.NodesPerMove;
                float rootQ = priorManager == null ? float.NaN : (float)store.RootNode.Q;
                ManagerGameLimitInputs timeManagerInputs = new(store.Nodes.PriorMoves.FinalPosition,
                                                               searchParams, PriorMoveStats,
                                                               type, store.RootNode.N, rootQ,
                                                               searchLimit.Value, searchLimit.ValueIncrement,
                                                               float.NaN, float.NaN,
                                                               maxMovesToGo : searchLimit.MaxMovesToGo,
                                                               isFirstMoveOfGame : isFirstMoveOfGame);

                ManagerGameLimitOutputs timeManagerOutputs = timeManager.ComputeMoveAllocation(timeManagerInputs);
                SearchLimit = timeManagerOutputs.LimitTarget;
            }
            else
            {
                SearchLimit = searchLimit;
            }

            // Possibly autoselect new optimal parameters
            ParamsSearchExecutionChooser paramsChooser = new ParamsSearchExecutionChooser(nnEvaluators.EvaluatorDef,
                                                                                          searchParams, childSelectParams, searchLimit);

            // TODO: technically this is overwriting the params belonging to the prior search, that's ugly (but won't actually cause a problem)
            paramsChooser.ChooseOptimal(searchLimit.EstNumNodes(50_000), paramsSearchExecutionPostprocessor); // TODO: make 50_000 smarter


            int estNumNodes = EstimatedNumSearchNodesForEvaluator(searchLimit, nnEvaluators);

            // Adjust the nodes estimate if we are continuing an existing search
            if (searchLimit.Type == SearchLimitType.NodesPerMove && RootNWhenSearchStarted > 0)
            {
                estNumNodes = Math.Max(0, estNumNodes - RootNWhenSearchStarted);
            }
            Context = new MCTSIterator(store, reuseOtherContextForEvaluatedNodes, reusePositionCache, reuseTranspositionRoots,
                                       nnEvaluators, searchParams, childSelectParams, searchLimit, estNumNodes);
            ThreadSearchContext = Context;

            TerminationManager = new MCTSFutilityPruning(this, Context);
            LimitManager       = timeManager;

            CeresEnvironment.LogInfo("MCTS", "Init", $"SearchManager created for store {store}", InstanceID);
        }
Esempio n. 30
0
        /// <summary>
        /// Parses a specification of search time in UCI format into an equivalent SearchLimit.
        /// Returns null if parsing failed.
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        private SearchLimit GetSearchLimit(string command)
        {
            SearchLimit        searchLimit;
            bool               weAreWhite = curPositionAndMoves.FinalPosition.MiscInfo.SideToMove == SideType.White;
            UCIGoCommandParsed goInfo     = new UCIGoCommandParsed(command, weAreWhite);

            if (!goInfo.IsValid)
            {
                return(null);
            }

            if (goInfo.Nodes.HasValue)
            {
                searchLimit = SearchLimit.NodesPerMove(goInfo.Nodes.Value);
            }
            else if (goInfo.MoveTime.HasValue)
            {
                searchLimit = SearchLimit.SecondsPerMove(goInfo.MoveTime.Value / 1000.0f);
            }
            else if (goInfo.Infinite)
            {
                searchLimit = SearchLimit.NodesPerMove(MCTSNodeStore.MAX_NODES);
            }
            else if (goInfo.TimeOurs.HasValue)
            {
                float increment = 0;
                if (goInfo.IncrementOurs.HasValue)
                {
                    increment = goInfo.IncrementOurs.Value / 1000.0f;
                }

                int?movesToGo = null;
                if (goInfo.MovesToGo.HasValue)
                {
                    movesToGo = goInfo.MovesToGo.Value;
                }

                searchLimit = SearchLimit.SecondsForAllMoves(goInfo.TimeOurs.Value / 1000.0f, increment, movesToGo, true);
            }
            else if (goInfo.NodesOurs.HasValue)
            {
                float increment = 0;
                if (goInfo.IncrementOurs.HasValue)
                {
                    increment = goInfo.IncrementOurs.Value;
                }

                int?movesToGo = null;
                if (goInfo.MovesToGo.HasValue)
                {
                    movesToGo = goInfo.MovesToGo.Value;
                }

                searchLimit = SearchLimit.NodesForAllMoves(goInfo.NodesOurs.Value, (int)increment, movesToGo, true);
            }
            else
            {
                UCIWriteLine($"Unsupported time control in UCI go command {command}");
                return(null);
            }

            // Add on possible search moves restriction.
            return(searchLimit with {
                SearchMoves = goInfo.SearchMoves
            });
        }