示例#1
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="nnEvaluator"></param>
        /// <param name="searchParams"></param>
        /// <param name="childSelectParams"></param>
        /// <param name="gameLimitManager"></param>
        /// <param name="paramsSearchExecutionModifier"></param>
        public GameEngineCeresInProcess(string id, NNEvaluatorDef evaluatorDef,
                                        ParamsSearch searchParams          = null,
                                        ParamsSelect childSelectParams     = null,
                                        IManagerGameLimit gameLimitManager = null,
                                        ParamsSearchExecutionModifier paramsSearchExecutionModifier = null) : base(id)
        {
            if (evaluatorDef == null)
            {
                throw new ArgumentNullException(nameof(evaluatorDef));
            }

            // Use default settings for search and select params if not specified.
            if (searchParams == null)
            {
                searchParams = new ParamsSearch();
            }
            if (childSelectParams == null)
            {
                childSelectParams = new ParamsSelect();
            }

            // Use default limit manager if not specified.
            if (gameLimitManager == null)
            {
                gameLimitManager = new ManagerGameLimitCeres();
            }

            ParamsSearchExecutionModifier = paramsSearchExecutionModifier;
            EvaluatorDef      = evaluatorDef;
            SearchParams      = searchParams;
            GameLimitManager  = gameLimitManager;
            ChildSelectParams = childSelectParams;
        }
示例#2
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="id">descriptive identifier</param>
 /// <param name="paramsNN">specification of the neural network to be used</param>
 /// <param name="forceDisableSmartPruning"></param>
 /// <param name="emulateCeresSettings"></param>
 /// <param name="searchParams"></param>
 /// <param name="selectParams"></param>
 /// <param name="uciSetOptionCommands"></param>
 /// <param name="callback"></param>
 /// <param name="overrideEXE">optinally the full name of the executable file(otherwise looks for Ceres executable in working directory)</param>
 public GameEngineCeresUCI(string id,
                           NNEvaluatorDef paramsNN,
                           bool forceDisableSmartPruning = false,
                           bool emulateCeresSettings     = false,
                           ParamsSearch searchParams     = null, ParamsSelect selectParams = null,
                           string[] uciSetOptionCommands = null,
                           ProgressCallback callback     = null,
                           string overrideEXE            = null)
     : base(id, GetExecutableFN(overrideEXE), null, null, null, uciSetOptionCommands, callback, false, ExtraArgsForEvaluator(paramsNN))
 {
     // TODO: support some limited emulation of options
     if (searchParams != null || selectParams != null)
     {
         throw new NotSupportedException("Customized searchParams and selectParams not yet supported");
     }
     if (paramsNN == null)
     {
         throw new ArgumentNullException(nameof(paramsNN));
     }
     if (forceDisableSmartPruning)
     {
         throw new NotImplementedException(nameof(forceDisableSmartPruning));
     }
     if (emulateCeresSettings)
     {
         throw new NotImplementedException(nameof(emulateCeresSettings));
     }
 }
示例#3
0
        static GameEngine GetEngine(GameEngineUCISpec engineSpec, string suffix,
                                    NNEvaluatorDef evaluatorDef,
                                    ParamsSearch paramsSearch, ParamsSelect paramsSelect, IManagerGameLimit timeManager)
        {
            bool resetMovesBetweenMoves = !paramsSearch.TreeReuseEnabled;
            bool enableTranpsositions   = paramsSearch.Execution.TranspositionMode != TranspositionMode.None;

            // Create requested type of engine
            if (engineSpec == null)
            {
                return(new GameEngineCeresInProcess("Ceres_" + suffix, evaluatorDef, paramsSearch, paramsSelect, timeManager, null));
            }
            else if (engineSpec.Name == "LC0")
            {
                if (evaluatorDef == null)
                {
                    throw new Exception("EvaluatorDef must be specified when running LC0 engine");
                }

                // TODO: do we really want to emulate always here? probably not
                // WARNING: above.
                bool forceDisableSmartPruning = false;
                return(new GameEngineLC0("LZ0_" + suffix, evaluatorDef.Nets[0].Net.NetworkID,
                                         forceDisableSmartPruning, false,
                                         paramsSearch, paramsSelect, evaluatorDef,
                                         null, CeresUserSettingsManager.GetLC0ExecutableFileName()));
            }
            else
            {
                return(engineSpec.CreateEngine());
            }
        }
示例#4
0
        static float ScoreCalc(float uctNumeratorPower, float uctDenominatorPower, float cpuct, int N, float p, float w, int n)
        {
            float q           = w / n;
            float denominator = uctDenominatorPower == 1.0f ? (n + 1) : MathF.Pow(n + 1, uctDenominatorPower);
            float u           = cpuct * p * (ParamsSelect.UCTParentMultiplier(N, uctNumeratorPower) / denominator);

            return(q + u);
        }
示例#5
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);
            }
        }
示例#6
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);
        }
示例#7
0
        /// <summary>
        /// Entry point for the score calculation.
        ///
        /// Given a set of information about the current node
        /// the number of visits to be made to the subtree,
        /// an array of visit counts is returned
        /// indicating the number of visits that should be made to each child.
        /// </summary>
        /// <param name="dualSelectorMode"></param>
        /// <param name="paramsSelect"></param>
        /// <param name="selectorID"></param>
        /// <param name="dynamicVLossBoost"></param>
        /// <param name="parentIsRoot"></param>
        /// <param name="parentN"></param>
        /// <param name="parentNInFlight"></param>
        /// <param name="qParent"></param>
        /// <param name="parentSumPVisited"></param>
        /// <param name="p"></param>
        /// <param name="w"></param>
        /// <param name="n"></param>
        /// <param name="nInFlight"></param>
        /// <param name="numChildren"></param>
        /// <param name="numVisitsToCompute"></param>
        /// <param name="outputScores"></param>
        /// <param name="outputChildVisitCounts"></param>
        public static void ScoreCalcMulti(bool dualSelectorMode, ParamsSelect paramsSelect,
                                          int selectorID, float dynamicVLossBoost,
                                          bool parentIsRoot, float parentN, float parentNInFlight,
                                          float qParent, float parentSumPVisited,
                                          Span <float> p, Span <float> w, Span <float> n, Span <float> nInFlight,
                                          int numChildren, int numVisitsToCompute,
                                          Span <float> outputScores, Span <short> outputChildVisitCounts)
        {
            // Saving output scores only makes sense when a single visit being computed
            Debug.Assert(!(outputScores != default && numVisitsToCompute > 1));

            Debug.Assert(p.Length == MAX_CHILDREN);
            Debug.Assert(w.Length == MAX_CHILDREN);
            Debug.Assert(n.Length == MAX_CHILDREN);
            Debug.Assert(nInFlight.Length == MAX_CHILDREN);
            Debug.Assert(numChildren <= MAX_CHILDREN);

            Debug.Assert(outputScores == default || outputScores.Length >= numChildren);
            Debug.Assert(outputChildVisitCounts.Length >= numChildren);

            int numBlocks = (numChildren / 8) + ((numChildren % 8 == 0) ? 0 : 1);

            float virtualLossMultiplier;

            if (ParamsSelect.VLossRelative)
            {
                virtualLossMultiplier = (qParent + paramsSelect.VirtualLossDefaultRelative + dynamicVLossBoost);
            }
            else
            {
                virtualLossMultiplier = paramsSelect.VirtualLossDefaultAbsolute;
            }

            float cpuctValue = paramsSelect.CalcCPUCT(parentIsRoot, dualSelectorMode, selectorID, parentN);

            // Compute qWhenNoChildren
            float fpuValue = -paramsSelect.CalcFPUValue(parentIsRoot);

            // TODO: to be more precise, parentSumPVisited should possibly be updated as we visit children
            bool  useFPUReduction = paramsSelect.GetFPUMode(parentIsRoot) == ParamsSelect.FPUType.Reduction;
            float qWhenNoChildren = useFPUReduction ? (+qParent + fpuValue * MathF.Sqrt(parentSumPVisited)) : fpuValue;

            if (parentIsRoot &&
                parentN > paramsSelect.RootCPUCTExtraMultiplierDivisor &&
                paramsSelect.RootCPUCTExtraMultiplierExponent != 0)
            {
                cpuctValue *= MathF.Pow(parentN / paramsSelect.RootCPUCTExtraMultiplierDivisor,
                                        paramsSelect.RootCPUCTExtraMultiplierExponent);
            }

            Compute(parentN, parentNInFlight, p, w, n, nInFlight, numChildren, numVisitsToCompute, outputScores,
                    outputChildVisitCounts, numBlocks, virtualLossMultiplier,
                    parentIsRoot ? paramsSelect.UCTRootNumeratorExponent : paramsSelect.UCTNonRootNumeratorExponent,
                    cpuctValue, qWhenNoChildren,
                    parentIsRoot ? paramsSelect.UCTRootDenominatorExponent : paramsSelect.UCTNonRootDenominatorExponent);
        }
示例#8
0
 /// <summary>
 /// Constructor.
 /// </summary>
 public GameEngineDefCeres(string id, NNEvaluatorDef evaluatorDef, ParamsSearch searchParams = null,
                           ParamsSearchExecutionModifier paramsSearchExecutionPostprocessor  = null,
                           ParamsSelect selectParams = null, IManagerGameLimit overrideTimeManager = null)
     : base(id)
 {
     EvaluatorDef = evaluatorDef;
     SearchParams = searchParams ?? new ParamsSearch();
     ParamsSearchExecutionPostprocessor = paramsSearchExecutionPostprocessor;
     SelectParams        = selectParams ?? new ParamsSelect();
     OverrideTimeManager = overrideTimeManager;
 }
示例#9
0
 /// <summary>
 /// Constructor.
 /// </summary>
 public GameEngineDefCeres(string id, NNEvaluatorDef evaluatorDef, ParamsSearch searchParams = null,
                           ParamsSearchExecutionModifier paramsSearchExecutionPostprocessor  = null,
                           ParamsSelect selectParams = null, IManagerGameLimit overrideTimeManager = null)
     : base(id)
 {
     // Make a defensive clone of the EvaluatorDef so it will definitely not be shared.
     EvaluatorDef = ObjUtils.DeepClone(evaluatorDef);
     SearchParams = searchParams ?? new ParamsSearch();
     ParamsSearchExecutionPostprocessor = paramsSearchExecutionPostprocessor;
     SelectParams        = selectParams ?? new ParamsSelect();
     OverrideTimeManager = overrideTimeManager;
 }
示例#10
0
        /// <summary>
        /// Construtor.
        /// </summary>
        /// <param name="evaluatorDef"></param>
        /// <param name="inStream"></param>
        /// <param name="outStream"></param>
        /// <param name="searchFinishedEvent"></param>
        public UCIManager(NNEvaluatorDef evaluatorDef,
                          TextReader inStream = null, TextWriter outStream = null,
                          Action <MCTSManager> searchFinishedEvent = null)
        {
            InStream            = inStream ?? Console.In;
            OutStream           = outStream ?? Console.Out;
            SearchFinishedEvent = searchFinishedEvent;

            EvaluatorDef = evaluatorDef;

            ParamsSearch = new ParamsSearch();
            ParamsSelect = new ParamsSelect();
        }
示例#11
0
 /// <summary>
 /// Returns an LC0Engine object configured according to specified settings.
 /// </summary>
 /// <param name="paramsSearch"></param>
 /// <param name="paramsSelect"></param>
 /// <param name="evaluatorDef"></param>
 /// <param name="network"></param>
 /// <param name="resetStateAndCachesBeforeMoves"></param>
 /// <param name="emulateCeresOptions"></param>
 /// <param name="verboseOutput"></param>
 /// <param name="overrideEXE"></param>
 /// <returns></returns>
 public static LC0Engine GetLC0Engine(ParamsSearch paramsSearch,
                                      ParamsSelect paramsSelect,
                                      NNEvaluatorDef evaluatorDef,
                                      INNWeightsFileInfo network,
                                      bool resetStateAndCachesBeforeMoves,
                                      bool emulateCeresOptions,
                                      bool verboseOutput,
                                      bool forceDisableSmartPruning,
                                      string overrideEXE     = null,
                                      bool alwaysFillHistory = false)
 {
     (string EXE, string lzOptions) = GetLC0EngineOptions(paramsSearch, paramsSelect, evaluatorDef, network,
                                                          emulateCeresOptions, verboseOutput, overrideEXE,
                                                          forceDisableSmartPruning, alwaysFillHistory);
     return(new LC0Engine(EXE, lzOptions, resetStateAndCachesBeforeMoves));
 }
示例#12
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));
        }
示例#13
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="evaluatorDef"></param>
        /// <param name="forceDisableSmartPruning"></param>
        /// <param name="searchParamsEmulate"></param>
        /// <param name="selectParamsEmulate"></param>
        /// <param name="overrideEXE"></param>
        /// <param name="extraCommandLineArgs"></param>
        public GameEngineDefLC0(string id,
                                NNEvaluatorDef evaluatorDef,
                                bool forceDisableSmartPruning,
                                ParamsSearch searchParamsEmulate = null,
                                ParamsSelect selectParamsEmulate = null,
                                string overrideEXE          = null,
                                string extraCommandLineArgs = null)
            : base(id)
        {
            if ((SearchParamsEmulate == null) != (SelectParamsEmulate == null))
            {
                throw new ArgumentException("SearchParamsEmulate and SelectParamsEmulate must be both provided or not");
            }

            // Verify compatability of evaluator for LC0
            if (evaluatorDef == null)
            {
                throw new ArgumentNullException(nameof(evaluatorDef));
            }
            if (evaluatorDef.Nets.Length != 1)
            {
                throw new Exception("Exactly one network must be specified for use with LC0.");
            }
            if (evaluatorDef.Nets[0].Net.Type != NNEvaluatorType.LC0Library)
            {
                throw new Exception("Network Type must be LC0Library");
            }
            if (evaluatorDef.NetCombo != NNEvaluatorNetComboType.Single)
            {
                throw new Exception("Network Type must be Single");
            }

            ID = id;

            // Make a defensive clone of the EvaluatorDef so it will definitely not be shared.
            EvaluatorDef = ObjUtils.DeepClone(evaluatorDef);

            ForceDisableSmartPruning = forceDisableSmartPruning;

            SearchParamsEmulate  = searchParamsEmulate;
            SelectParamsEmulate  = selectParamsEmulate;
            OverrideEXE          = overrideEXE;
            ExtraCommandLineArgs = extraCommandLineArgs;
        }
示例#14
0
        /// <summary>
        /// Construtor.
        /// </summary>
        /// <param name="evaluatorDef"></param>
        /// <param name="inStream"></param>
        /// <param name="outStream"></param>
        /// <param name="searchFinishedEvent"></param>
        public UCIManager(NNEvaluatorDef evaluatorDef,
                          TextReader inStream = null, TextWriter outStream = null,
                          Action <MCTSManager> searchFinishedEvent = null,
                          bool disablePruning = false)
        {
            InStream            = inStream ?? Console.In;
            OutStream           = outStream ?? Console.Out;
            SearchFinishedEvent = searchFinishedEvent;

            EvaluatorDef = evaluatorDef;

            ParamsSearch = new ParamsSearch();
            ParamsSelect = new ParamsSelect();

            if (disablePruning)
            {
                ParamsSearch.FutilityPruningStopSearchEnabled = false;
            }
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="evaluatorDef"></param>
        /// <param name="searchParams"></param>
        /// <param name="childSelectParams"></param>
        /// <param name="gameLimitManager"></param>
        /// <param name="paramsSearchExecutionModifier"></param>
        /// <param name="logFileName"></param>
        public GameEngineCeresInProcess(string id, NNEvaluatorDef evaluatorDef,
                                        ParamsSearch searchParams          = null,
                                        ParamsSelect childSelectParams     = null,
                                        IManagerGameLimit gameLimitManager = null,
                                        ParamsSearchExecutionModifier paramsSearchExecutionModifier = null,
                                        string logFileName = null) : base(id)
        {
            if (evaluatorDef == null)
            {
                throw new ArgumentNullException(nameof(evaluatorDef));
            }

            // Use default settings for search and select params if not specified.
            if (searchParams == null)
            {
                searchParams = new ParamsSearch();
            }
            if (childSelectParams == null)
            {
                childSelectParams = new ParamsSelect();
            }

            // Use default limit manager if not specified.
            if (gameLimitManager == null)
            {
                gameLimitManager = new ManagerGameLimitCeres();
            }

            ParamsSearchExecutionModifier = paramsSearchExecutionModifier;
            EvaluatorDef      = evaluatorDef;
            SearchParams      = searchParams;
            GameLimitManager  = gameLimitManager;
            ChildSelectParams = childSelectParams;
            SearchLogFileName = logFileName;
            VerboseMoveStats  = CeresUserSettingsManager.Settings.VerboseMoveStats;

            if (!string.IsNullOrEmpty(CeresUserSettingsManager.Settings.SearchLogFile))
            {
                SearchLogFileName = CeresUserSettingsManager.Settings.SearchLogFile;
            }
        }
示例#16
0
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="searchParams"></param>
        /// <param name="selectParams"></param>
        /// <param name="paramsNN"></param>
        /// <param name="id"></param>
        /// <param name="networkID"></param>
        /// <param name="emulateCeresSettings"></param>
        /// <param name="setupAction"></param>
        /// <param name="overrideEXE"></param>
        public GameEngineLC0(string id, string networkID, bool forceDisableSmartPruning = false,
                             bool emulateCeresSettings = false,
                             ParamsSearch searchParams = null, ParamsSelect selectParams = null,
                             NNEvaluatorDef paramsNN   = null,
                             Action setupAction        = null,
                             string overrideEXE        = null,
                             bool verbose           = false,
                             bool alwaysFillHistory = false) : base(id)
        {
            SetupAction = setupAction;
            if (SetupAction != null)
            {
                SetupAction();
            }
            bool resetStateAndCachesBeforeMoves = searchParams != null && !searchParams.TreeReuseEnabled;

            LC0Engine = LC0EngineConfigured.GetLC0Engine(searchParams, selectParams, paramsNN,
                                                         NNWeightsFiles.LookupNetworkFile(networkID), emulateCeresSettings,
                                                         resetStateAndCachesBeforeMoves, verbose,
                                                         forceDisableSmartPruning, overrideEXE,
                                                         alwaysFillHistory);
        }
示例#17
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="evaluatorDef"></param>
        /// <param name="forceDisableSmartPruning"></param>
        /// <param name="searchParamsEmulate"></param>
        /// <param name="selectParamsEmulate"></param>
        /// <param name="overrideEXE"></param>
        public GameEngineDefLC0(string id,
                                NNEvaluatorDef evaluatorDef,
                                bool forceDisableSmartPruning,
                                ParamsSearch searchParamsEmulate = null,
                                ParamsSelect selectParamsEmulate = null,
                                string overrideEXE = null)
            : base(id)
        {
            if ((SearchParamsEmulate == null) != (SelectParamsEmulate == null))
            {
                throw new ArgumentException("SearchParamsEmulate and SelectParamsEmulate must be both provided or not");
            }

            // Verify compatability of evaluator for LC0
            if (evaluatorDef == null)
            {
                throw new ArgumentNullException(nameof(evaluatorDef));
            }
            if (evaluatorDef.Nets.Length != 1)
            {
                throw new Exception("Exactly one network must be specified for use with LC0.");
            }
            if (evaluatorDef.Nets[0].Net.Type != NNEvaluatorType.LC0Library)
            {
                throw new Exception("Network Type must be LC0Library");
            }
            if (evaluatorDef.NetCombo != NNEvaluatorNetComboType.Single)
            {
                throw new Exception("Network Type must be Single");
            }

            ID                       = id;
            EvaluatorDef             = evaluatorDef ?? throw new ArgumentNullException(nameof(evaluatorDef));
            ForceDisableSmartPruning = forceDisableSmartPruning;

            SearchParamsEmulate = searchParamsEmulate;
            SelectParamsEmulate = selectParamsEmulate;
            OverrideEXE         = overrideEXE;
        }
示例#18
0
        /// <summary>
        /// Dump two sets of parameters, optionally showing only differences.
        /// </summary>
        public static void DumpParams(TextWriter writer, bool differentOnly,
                                      GameEngineUCISpec externalEngine1Spec, GameEngineUCISpec externalEngine2Spec,
                                      NNEvaluatorDef evaluatorDef1, NNEvaluatorDef evaluatorDef2,
                                      SearchLimit searchLimit1, SearchLimit searchLimit2,
                                      ParamsSelect selectParams1, ParamsSelect selectParams2,
                                      ParamsSearch searchParams1, ParamsSearch searchParams2,
                                      IManagerGameLimit timeManager1, IManagerGameLimit timeManager2,
                                      ParamsSearchExecution paramsSearchExecution1,
                                      ParamsSearchExecution paramsSearchExecution2)
        {
            writer.WriteLine("\r\n-----------------------------------------------------------------------");
            writer.WriteLine("ENGINE 1 Options Modifications from Default");

            if (evaluatorDef1 != null)
            {
                writer.WriteLine("Ceres Evaluator : " + evaluatorDef1.ToString());
            }
            else
            {
                writer.Write("External UCI : " + externalEngine1Spec);
            }

            writer.Write(ObjUtils.FieldValuesDumpString <SearchLimit>(searchLimit1, SearchLimit.NodesPerMove(1), differentOnly));
            //      writer.Write(ObjUtils.FieldValuesDumpString<NNEvaluatorDef>(Def.NNEvaluators1.EvaluatorDef, new ParamsNN(), differentOnly));
            writer.Write(ObjUtils.FieldValuesDumpString <ParamsSelect>(selectParams1, new ParamsSelect(), differentOnly));
            writer.Write(ObjUtils.FieldValuesDumpString <ParamsSearch>(searchParams1, new ParamsSearch(), differentOnly));
            DumpTimeManagerDifference(true, null, timeManager1);
            writer.Write(ObjUtils.FieldValuesDumpString <ParamsSearchExecution>(paramsSearchExecution1, new ParamsSearchExecution(), differentOnly));

            writer.WriteLine("\r\n-----------------------------------------------------------------------");
            writer.WriteLine("ENGINE 2 Options Modifications from Engine 1");
            bool evaluatorsDifferent = false;

            bool eval2TypeDifferent = (evaluatorDef1 == null) != (evaluatorDef2 == null);

            if (eval2TypeDifferent)
            {
                evaluatorsDifferent = true;
            }
            else
            {
                if (evaluatorDef1 != null)
                {
                    evaluatorsDifferent = evaluatorDef1.ToString() != evaluatorDef2.ToString();
                }
                else
                {
                    evaluatorsDifferent = externalEngine1Spec.ToString() != externalEngine2Spec.ToString();
                }
            }
            if (!differentOnly || evaluatorsDifferent)
            {
                if (evaluatorDef1 != null && evaluatorDef2 != null)
                {
                    writer.WriteLine("Ceres Evaluator : " + evaluatorDef2.ToString());
                }
                else
                {
                    writer.Write("External UCI : " + externalEngine2Spec);
                }
            }

            if (searchLimit2 != null)
            {
                writer.Write(ObjUtils.FieldValuesDumpString <SearchLimit>(searchLimit2, searchLimit1, differentOnly));
                //      writer.Write(ObjUtils.FieldValuesDumpString<NNEvaluatorDef>(Def.NNEvaluators1.EvaluatorDef, Def.NNEvaluators2.EvaluatorDef, differentOnly));
                writer.Write(ObjUtils.FieldValuesDumpString <ParamsSelect>(selectParams2, selectParams1, differentOnly));
                writer.Write(ObjUtils.FieldValuesDumpString <ParamsSearch>(searchParams2, searchParams1, differentOnly));
                DumpTimeManagerDifference(differentOnly, timeManager1, timeManager2);
                writer.Write(ObjUtils.FieldValuesDumpString <ParamsSearchExecution>(paramsSearchExecution2, paramsSearchExecution1, differentOnly));
            }
        }
示例#19
0
        /// <summary>
        /// Worker method that coordinates looping over all the requested visits,
        /// including a performance optimization that attempts to
        /// detect the condition where many consecutive visits will be made to the same child.
        /// </summary>
        /// <param name="parentN"></param>
        /// <param name="parentNInFlight"></param>
        /// <param name="p"></param>
        /// <param name="w"></param>
        /// <param name="n"></param>
        /// <param name="nInFlight"></param>
        /// <param name="numChildren"></param>
        /// <param name="numVisitsToCompute"></param>
        /// <param name="outputScores"></param>
        /// <param name="outputChildVisitCounts"></param>
        /// <param name="numBlocks"></param>
        /// <param name="virtualLossMultiplier"></param>
        /// <param name="uctParentPower"></param>
        /// <param name="cpuctValue"></param>
        /// <param name="qWhenNoChildren"></param>
        /// <param name="uctDenominatorPower"></param>
        private static void Compute(float parentN, float parentNInFlight,
                                    Span <float> p, Span <float> w, Span <float> n, Span <float> nInFlight,
                                    int numChildren, int numVisitsToCompute,
                                    Span <float> outputScores, Span <short> outputChildVisitCounts,
                                    int numBlocks, float virtualLossMultiplier, float uctParentPower,
                                    float cpuctValue, float qWhenNoChildren, float uctDenominatorPower)
        {
            // Load the vectors that do not change
            Vector256 <float> vVirtualLossMultiplier = Vector256.Create(virtualLossMultiplier);

            // Make sure ThreadStatics are initialized, and get local copies for efficient access
            float[] localResultAVXScratch = childScoresTempBuffer;
            if (localResultAVXScratch == null)
            {
                InitializedForThread();
                localResultAVXScratch = childScoresTempBuffer;
            }

            int numVisits = 0;

            while (numVisits < numVisitsToCompute)
            {
                // Get constant term handy
                float numVisitsByParentToChildren = parentNInFlight + ((parentN < 2) ? 1 : parentN - 1);
                float cpuctSqrtParentN            = cpuctValue * ParamsSelect.UCTParentMultiplier(numVisitsByParentToChildren, uctParentPower);
                ComputeChildScores(p, w, n, nInFlight, numBlocks, qWhenNoChildren, vVirtualLossMultiplier,
                                   localResultAVXScratch,
                                   cpuctSqrtParentN, uctDenominatorPower);

                // Save back to output scores (if these were requested)
                if (outputScores != default)
                {
                    Debug.Assert(numVisits <= 1);

                    Span <float> scoresSpan = new Span <float>(localResultAVXScratch).Slice(0, numChildren);
                    scoresSpan.CopyTo(outputScores);
                }

                // Find the best child and record this visit
                int maxIndex = ArrayUtils.IndexOfElementWithMaxValue(localResultAVXScratch, numChildren);

                // Update either 3 or 4 items items to reflect this visit
                parentNInFlight     += 1;
                nInFlight[maxIndex] += 1;
                numVisits           += 1;
                if (outputChildVisitCounts != default)
                {
                    outputChildVisitCounts[maxIndex] += 1;
                }

                int numRemainingVisits = numVisitsToCompute - numVisits;

                // If we just found our first child we repeatedly try to
                // "jump ahead" by 10 visits at a time
                // as long as the first top child child remains the best child.
                // This optimizes for the common case that one child is dominant,
                // and empriically reduces the number of calls to ComputeChildScores by more than 30%.
                //
                // Note that would be possible to try this "jump ahead" technique
                // after not only the first visit, but in practice this did not improve performance.
                const int NUM_ADDITIONAL_TRY_VISITS_PER_ITERATION = 10;
                if (numVisits == 1 && numRemainingVisits > NUM_ADDITIONAL_TRY_VISITS_PER_ITERATION + 5)
                {
                    int numSuccessfulVisitsAllIterations = 0;

                    do
                    {
                        // Modify state to simulate additional visits to this top child
                        float newNInFlight = nInFlight[maxIndex] += NUM_ADDITIONAL_TRY_VISITS_PER_ITERATION;

                        // Compute new child scores
                        numVisitsByParentToChildren = newNInFlight + parentNInFlight + ((parentN < 2) ? 1 : parentN - 1);
                        cpuctSqrtParentN            = cpuctValue * ParamsSelect.UCTParentMultiplier(numVisitsByParentToChildren, uctParentPower);
                        ComputeChildScores(p, w, n, nInFlight, numBlocks, qWhenNoChildren, vVirtualLossMultiplier,
                                           localResultAVXScratch, cpuctSqrtParentN, uctDenominatorPower);

                        // Check if the best child was still the same
                        if (maxIndex == ArrayUtils.IndexOfElementWithMaxValue(localResultAVXScratch, numChildren))
                        {
                            // Child remained same, increment successful count
                            numSuccessfulVisitsAllIterations += NUM_ADDITIONAL_TRY_VISITS_PER_ITERATION;
                        }
                        else
                        {
                            // Failed, back out the last update to nInFlight and stop iterating
                            nInFlight[maxIndex] -= NUM_ADDITIONAL_TRY_VISITS_PER_ITERATION;

                            break;
                        }
                    } while (numRemainingVisits - numSuccessfulVisitsAllIterations > NUM_ADDITIONAL_TRY_VISITS_PER_ITERATION);

                    if (numSuccessfulVisitsAllIterations > 0)
                    {
                        // The nInFlight have already been kept continuously up to date
                        // but need to update the other items to reflect these visits
                        parentNInFlight += numSuccessfulVisitsAllIterations;
                        numVisits       += numSuccessfulVisitsAllIterations;
                        if (outputChildVisitCounts != default)
                        {
                            outputChildVisitCounts[maxIndex] += (short)numSuccessfulVisitsAllIterations;
                        }
                    }
                }
            }
        }
示例#20
0
        /// <summary>
        /// Returns the executable location and program arguments appropriate
        /// for LC0 given specified Ceres parameters
        /// (optionally emulating them to the degree possible).
        /// </summary>
        /// <param name="paramsSearch"></param>
        /// <param name="paramsSelect"></param>
        /// <param name="evaluatorDef"></param>
        /// <param name="network"></param>
        /// <param name="emulateCeresOptions"></param>
        /// <param name="verboseOutput"></param>
        /// <param name="overrideEXE"></param>
        /// <param name="forceDisableSmartPruning"></param>
        /// <param name="alwaysFillHistory"></param>
        /// <returns></returns>
        public static (string, string) GetLC0EngineOptions(ParamsSearch paramsSearch,
                                                           ParamsSelect paramsSelect,
                                                           NNEvaluatorDef evaluatorDef,
                                                           INNWeightsFileInfo network,
                                                           bool emulateCeresOptions,
                                                           bool verboseOutput,
                                                           string overrideEXE            = null,
                                                           bool forceDisableSmartPruning = false,
                                                           bool alwaysFillHistory        = false)
        {
            if (paramsSearch == null)
            {
                paramsSearch = new ParamsSearch();
            }
            if (paramsSelect == null)
            {
                paramsSelect = new ParamsSelect();
            }

            //fail int8  string precisionStr = MCTSParams.PRECISION == WFEvalNetTensorRT.TRTPrecision.Int8 ? "trt-int8" : "cudnn-fp16";

            // Must reverse values to conform to LZ0 convention
            float FPU_MULTIPLIER = paramsSelect.FPUMode == ParamsSelect.FPUType.Reduction ? -1.0f : 1.0f;

            // TODO: plug in both versions of Centiapwn to low level Leela code
            string fpuVals     = $"--fpu-value={FPU_MULTIPLIER * paramsSelect.FPUValue} --fpu-value-at-root={FPU_MULTIPLIER * paramsSelect.FPUValueAtRoot} ";
            string strategyStr = paramsSelect.FPUMode == ParamsSelect.FPUType.Absolute ? "absolute " : "reduction ";
            string fpuStr      = fpuVals + "--fpu-strategy=" + strategyStr;

            string strategyAtRootStr = paramsSelect.FPUModeAtRoot == ParamsSelect.FPUType.Absolute ? "absolute " : "reduction ";
            string fpuStrRoot        = paramsSelect.FPUModeAtRoot == ParamsSelect.FPUType.Same ? "--fpu-strategy-at-root=same "
                                                                          : "--fpu-strategy-at-root=" + strategyAtRootStr;

            string netSourceFile = network.FileName;

            int minibatchSize = 256; // LC0 default

            // If GPUs have equal fractions then we use demux where only 2 threads needed,
            // otherwise we use roundrobin and best is 1 + number of GPUS
            // Note that with increasing threads Leela plays significantly non-deterministically and somewhat worse
            int NUM_THREADS = evaluatorDef.EqualFractions ? 2 : evaluatorDef.Devices.Length + 1;

            string lzOptions = "--nodes-as-playouts "; // essential to get same behavior as Ceres with go nodes command

            lzOptions += "--multi-gather ";            // greatly improves search speed

            if (forceDisableSmartPruning || (emulateCeresOptions && !paramsSearch.FutilityPruningStopSearchEnabled))
            {
                lzOptions += " --smart-pruning-factor=0 ";
            }

            // Default nncache is only 200_000 but big tournaments (TCEC 19) have used as high as 20_000_000.
            // To keep memory requires reasonable for typical systems we default to a value in between.
            // However note that for very small nets such as 128x10 it may be faster to uze zero nncache.
            const int LC0_CACHE_SIZE = 5_000_000;

            int MOVE_OVERHEAD = (int)(new ParamsSearch().MoveOverheadSeconds * 1000);

            lzOptions += $"--move-overhead={MOVE_OVERHEAD} ";
            if (USE_LC0_SMALL_SEARCH_SETTINGS)
            {
                // Works better for smaller searchs (such as 1000 nodes)
                lzOptions += " --max-collision-visits=32 ";
            }
            else
            {
                // Much faster for large searches with multigather enabled.
                lzOptions += " --max-out-of-order-evals-factor=2.4 --max-collision-events=500 --max-collision-visits=500 ";
            }

            if (alwaysFillHistory)
            {
                lzOptions += $" --history-fill=always ";
            }

            if (emulateCeresOptions)
            {
                bool useNNCache = evaluatorDef.CacheMode > PositionEvalCache.CacheMode.None ||
                                  paramsSearch.Execution.TranspositionMode > TranspositionMode.None;
                int cacheSize = useNNCache ? LC0_CACHE_SIZE : 0;

                lzOptions += $@"-w {netSourceFile} -t {NUM_THREADS} --minibatch-size={minibatchSize} " +
                             //        " --policy-softmax-temp=2.2  --backend=cudnn-fp16 ";
                             $" --policy-softmax-temp={paramsSelect.PolicySoftmax} --cache-history-length={evaluatorDef.NumCacheHashPositions - 1} " +
//                         $" --score-type=win_percentage" +
                             BackendArgumentsString(evaluatorDef) +
                             //"--backend=multiplexing --backend-opts=(backend=cudnn-fp16,gpu=0),(backend=cudnn-fp16,gpu=1),(backend=cudnn-fp16,gpu=2),(backend=cudnn-fp16,gpu=3) " +
                             //                         $"--backend={precisionStr} --backend-opts=gpu={SearchParamsNN.GPU_ID_LEELA_UCI} " +

                             $"{fpuStr} {fpuStrRoot} " +
                             $" --no-sticky-endgames ";

                // + --no-out-of-order-eval"; // *** NOTE: if we add this flag, LZ0 seems to play a little different and better. TODO: study this, should we adopt?
                lzOptions += $" --cpuct-factor={paramsSelect.CPUCTFactor} --cpuct-base={paramsSelect.CPUCTBase} --cpuct={paramsSelect.CPUCT} --nncache={cacheSize} ";
                //        lzOptions += $" --max-collision-visits={paramsSearch.MAX_COLLISIONS + 1 }"; // Must increment by 1 to make comparable (also, LC0 hangs at value ot zero)
            }
            else
            {
                // Mostly we let Leela use default options, except to make it fair
                // we use a large nncache and number of threads appropriate for the number of GPUs in use
                //        lzOptions = $@"-w {weightsDir}\{netSourceFile} --minibatch-size={minibatchSize} -t {paramsNN.NNEVAL_NUM_GPUS + 1} " +
                lzOptions += $@"-w {netSourceFile} -t {NUM_THREADS} " +
//                    $"--score-type=win_percentage " +
                             $"--nncache={LC0_CACHE_SIZE} " +
                             // like TCEC 10, only 5% benefit     $"--max-prefetch=160 --max-collision-events=917 " +
                             BackendArgumentsString(evaluatorDef);
            }

            string tbPath = CeresUserSettingsManager.Settings.TablebaseDirectory;

            if (paramsSearch.EnableTablebases)
            {
                lzOptions += (@$ " --syzygy-paths=#{tbPath}# ").Replace("#", "\"");
            }

            if (verboseOutput)
            {
                lzOptions += " --verbose-move-stats ";
            }

            string EXE = CeresUserSettingsManager.GetLC0ExecutableFileName();

#if EXPERIMENTAL
            const bool LZ_USE_TRT = false; // NOTE: if true, the GPU is seemingly currently hardcoded to 3. The max batch size is 512

            if (LZ_USE_TRT)
            {
                if (network.NetworkID == "59999")
                {
                    EXE = @"C:\dev\lc0\19May\lc0\build\lc0_59999.exe";
                }
                else if (network.NetworkID == "42767")
                {
                    EXE = @"C:\dev\lc0\19May\lc0\build\lc0_42767.exe";
                }
                else
                {
                    throw new Exception("Unknown net for EXE " + network.NetworkID);
                }

                if (evaluatorDef.Nets[0].Net.Precision == NNEvaluatorPrecision.Int8)
                {
                    lzOptions = lzOptions.Replace("cudnn-fp16", "trt-int8");
                }
                else if (evaluatorDef.Nets[0].Net.Precision == NNEvaluatorPrecision.FP16)
                {
                    lzOptions = lzOptions.Replace("cudnn-fp16", "trt-fp16");
                }
                else
                {
                    throw new NotImplementedException();
                }
            }
#endif

            if (overrideEXE != null)
            {
                EXE = overrideEXE;
            }

            return(EXE, lzOptions);
        }
示例#21
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);
        }