/// Determines how much time or nodes resource to /// allocate to the the current move in a game subject to /// a limit on total numbrer of time or nodes over /// some number of moves (or possibly all moves). public ManagerGameLimitOutputs ComputeMoveAllocation(ManagerGameLimitInputs inputs) { if (inputs.MaxMovesToGo.HasValue && inputs.MaxMovesToGo < 2) { return(new ManagerGameLimitOutputs(new SearchLimit(inputs.TargetLimitType, inputs.RemainingFixedSelf * 0.99f))); } ManagerGameLimitOutputs outputs = DoComputeMoveTime(inputs); return(outputs); }
/// <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); }