/// <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); } }
/// <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); }
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)); }
/// <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); }