public void BatchGenerate(MCTSIterator context, Span <MCTSNode> nodes, EvalResultTarget resultTarget) { try { if (EvaluatorDef.Location == NNEvaluatorDef.LocationType.Remote) { throw new NotImplementedException(); //SetBatch(context, nodes); //RunRemote(nodes, resultTarget); } else { SetBatch(context, nodes); RunLocal(nodes, resultTarget); } } catch (Exception exc) { Console.WriteLine("Error in NodeEvaluatorNN " + exc); throw exc; } NUM_BATCHES_EVALUATED++; NUM_POSITIONS_EVALUATED += (ulong)Batch.NumPos; }
/// <summary> /// Constructor for selector over specified MCTSIterator. /// </summary> /// <param name="context"></param> /// <param name="selectorID"></param> /// <param name="priorSequence"></param> /// <param name="guessNumLeaves"></param> public LeafSelectorMulti(MCTSIterator context, int selectorID, PositionWithHistory priorSequence, int guessNumLeaves) { Debug.Assert(selectorID < ILeafSelector.MAX_SELECTORS); if (USE_CUSTOM_THREADPOOL) { tpm = tpmPool.Value.GetFromPool(); } SelectorID = selectorID; PriorSequence = priorSequence; paramsExecution = context.ParamsSearch.Execution; int maxNodesPerBatchForRootPreload = context.ParamsSearch.Execution.RootPreloadDepth > 0 ? MCTSSearchFlow.MAX_PRELOAD_NODES_PER_BATCH : 0; int extraLeafsDynamic = 0; if (context.ParamsSearch.PaddedBatchSizing) { extraLeafsDynamic = context.ParamsSearch.PaddedExtraNodesBase + (int)(context.ParamsSearch.PaddedExtraNodesMultiplier * guessNumLeaves); } leafs = new ListBounded <MCTSNode>(guessNumLeaves + maxNodesPerBatchForRootPreload + extraLeafsDynamic); Context = context; }
public MCTSSearchFlow(MCTSManager manager, MCTSIterator context) { Manager = manager; Context = context; int numSelectors = context.ParamsSearch.Execution.FlowDirectOverlapped ? 2 : 1; batchingManagers = new MCTSBatchParamsManager[numSelectors]; for (int i = 0; i < numSelectors; i++) { batchingManagers[i] = new MCTSBatchParamsManager(manager.Context.ParamsSelect.UseDynamicVLoss); } bool shouldCache = context.EvaluatorDef.CacheMode != Chess.PositionEvalCaching.PositionEvalCache.CacheMode.None; string instanceID = "0"; //Params.Evaluator1 : Params.Evaluator2; const bool LOW_PRIORITY_PRIMARY = false; LeafEvaluatorNN nodeEvaluator1 = new LeafEvaluatorNN(context.EvaluatorDef, context.NNEvaluators.Evaluator1, shouldCache, LOW_PRIORITY_PRIMARY, context.Tree.PositionCache, null);// context.ParamsNN.DynamicNNSelectorFunc); BlockNNEval1 = new MCTSNNEvaluator(nodeEvaluator1, true); if (context.ParamsSearch.Execution.FlowDirectOverlapped) { // Create a second evaluator (configured like the first) on which to do overlapping. LeafEvaluatorNN nodeEvaluator2 = new LeafEvaluatorNN(context.EvaluatorDef, context.NNEvaluators.Evaluator2, shouldCache, false, context.Tree.PositionCache, null);// context.ParamsNN.DynamicNNSelectorFunc); BlockNNEval2 = new MCTSNNEvaluator(nodeEvaluator2, true); } if (context.EvaluatorDef.SECONDARY_NETWORK_ID != null) { throw new NotImplementedException(); //NodeEvaluatorNN nodeEvaluatorSecondary = new NodeEvaluatorNN(context.EvaluatorDef, context.ParamsNN.Evaluators.EvaluatorSecondary, false, false, null, null); //BlockNNEvalSecondaryNet = new MCTSNNEvaluate(nodeEvaluatorSecondary, false); } BlockApply = new MCTSApply(context.FirstMoveSampler); if (context.ParamsSearch.Execution.RootPreloadDepth > 0) { rootPreloader = new MCTSRootPreloader(); } if (context.ParamsSearch.Execution.SmartSizeBatches) { context.NNEvaluators.CalcStatistics(true, 1f); } }
/// <summary> /// Constructor which creates an MCTSNode wrapper for the raw node at specified index. /// </summary> /// <param name="context"></param> /// <param name="index"></param> /// <param name="parent">optionally the parent node</param> internal MCTSNode(MCTSIterator context, MCTSNodeStructIndex index, MCTSNode parent = null) { Debug.Assert(context.Tree.Store.Nodes != null); Debug.Assert(index.Index <= context.Tree.Store.Nodes.MaxNodes); Context = context; Tree = context.Tree; this.parent = parent; Span <MCTSNodeStruct> parentArray = context.Tree.Store.Nodes.Span; ptr = (MCTSNodeStruct *)Unsafe.AsPointer(ref parentArray[index.Index]); this.index = index; }
/// <summary> /// Constructor. /// </summary> /// <param name="store"></param> /// <param name="context"></param> /// <param name="maxNodesBound"></param> /// <param name="positionCache"></param> public MCTSTree(MCTSNodeStore store, MCTSIterator context, int maxNodesBound, int estimatedNumNodes, PositionEvalCache positionCache) { if (context.ParamsSearch.DrawByRepetitionLookbackPlies > MAX_LENGTH_POS_HISTORY) { throw new Exception($"DrawByRepetitionLookbackPlies exceeds maximum length of {MAX_LENGTH_POS_HISTORY}"); } Store = store; Context = context; PositionCache = positionCache; ChildCreateLocks = new LockSet(128); const int ANNOTATION_MIN_CACHE_SIZE = 50_000; int annotationCacheSize = Math.Min(maxNodesBound, context.ParamsSearch.Execution.NodeAnnotationCacheSize); if (annotationCacheSize < ANNOTATION_MIN_CACHE_SIZE && annotationCacheSize < maxNodesBound) { throw new Exception($"NODE_ANNOTATION_CACHE_SIZE is below minimum size of {ANNOTATION_MIN_CACHE_SIZE}"); } if (maxNodesBound <= annotationCacheSize && !context.ParamsSearch.TreeReuseEnabled) { // We know with certainty the maximum size, and it will fit inside the cache // without purging needed - so just use a simple fixed size cache cache = new MCTSNodeCacheArrayFixed(this, maxNodesBound); } else { cache = new MCTSNodeCacheArrayPurgeableSet(this, annotationCacheSize, estimatedNumNodes); } // Populate EncodedPriorPositions with encoded boards // corresponding to possible prior moves (before the root of this search) EncodedPriorPositions = new List <EncodedPositionBoard>(); Position[] priorPositions = new Position[9]; // Get prior positions (last position has highest index) priorPositions = PositionHistoryGatherer.DoGetHistoryPositions(PriorMoves, priorPositions, 0, 8, false).ToArray(); for (int i = priorPositions.Length - 1; i >= 0; i--) { EncodedPositionBoard thisBoard = EncodedPositionBoard.GetBoard(in priorPositions[i], priorPositions[i].MiscInfo.SideToMove, false); EncodedPriorPositions.Add(thisBoard); } }
public ListBounded <MCTSNode> Evaluate(MCTSIterator context, ListBounded <MCTSNode> nodes) { nodes[0].Context.NumNNBatches++; nodes[0].Context.NumNNNodes += nodes.Count; NUM_EVALUATED += nodes.Count; if (resultTarget == LeafEvaluatorNN.EvalResultTarget.PrimaryEvalResult) { Debug.Assert(nodes[0].EvalResult.IsNull); // null evaluator indicates should have been sent here } else if (resultTarget == LeafEvaluatorNN.EvalResultTarget.SecondaryEvalResult) { Debug.Assert(nodes[0].EvalResultSecondary.IsNull); // null evaluator indicates should have been sent here } Evaluator.BatchGenerate(context, nodes.AsSpan, resultTarget); return(nodes); }
/// <summary> /// Coordinates (possibly parallelized) application of /// evauation results for all nodes in a specified batch. /// </summary> /// <param name="selectorID"></param> /// <param name="batchlet"></param> void DoApply(int selectorID, ListBounded <MCTSNode> batchlet) { DebugVerifyNoDuplicatesAndInFlight(batchlet); TOTAL_APPLIED += batchlet.Count; if (batchlet.Count == 0) { return; } MCTSIterator context = batchlet[0].Context; if (batchlet.Count > context.ParamsSearch.Execution.SetPoliciesNumPoliciesPerThread) { Parallel.Invoke( () => { DoApplySetPolicies(batchlet); }, () => { using (new SearchContextExecutionBlock(context)) DoApplyBackup(selectorID, batchlet); }); } else { DoApplySetPolicies(batchlet); DoApplyBackup(selectorID, batchlet); } #if CRASHES // The main two operations to be performed are independently and // can possibly be performed in parallel const int PARALLEL_THRESHOLD = MCTSParamsFixed.APPLY_NUM_POLICIES_PER_THREAD + (MCTSParamsFixed.APPLY_NUM_POLICIES_PER_THREAD / 2); if (false && MCTSParamsFixed.APPLY_PARALLEL_ENABLED && batchlet.Count > PARALLEL_THRESHOLD) { Parallel.Invoke ( () => DoApplySetPolicies(batchlet), () => DoApplyBackup(batchlet) ); } else { DoApplySetPolicies(batchlet); // must go first DoApplyBackup(batchlet); } //foreach (var node in batchlet.Nodes) node.EvalResult = null; #endif }
/// <summary> /// Sets the Batch field with set of positions coming from a specified Span<MCTSNode>. /// </summary> /// <param name="context"></param> /// <param name="nodes"></param> /// <returns></returns> int SetBatch(MCTSIterator context, Span <MCTSNode> nodes) { //Console.WriteLine(nodes.Length + " BATCH NODES"); if (nodes.Length > NNEvaluatorDef.MAX_BATCH_SIZE) { throw new Exception($"Requested batch is larger than {NNEvaluatorDef.MAX_BATCH_SIZE}"); } if (nodes.Length > 0) { for (int i = 0; i < nodes.Length; i++) { if (EvaluatorDef.PositionTransform == NNEvaluatorDef.PositionTransformType.Mirror) { if (rawPosArray[i].MiscInfo.InfoPosition.MirrorEquivalent) { EncodedPositionWithHistory mirrored = rawPosArray[i].Mirrored; nodes[i].Annotation.CalcRawPosition(nodes[i], ref mirrored); } } else { nodes[i].Annotation.CalcRawPosition(nodes[i], ref rawPosArray[i]); } } if (EvaluatorDef.Location == NNEvaluatorDef.LocationType.Local) { Batch.Set(rawPosArray, nodes.Length); } if (BatchEvaluatorIndexDynamicSelector != null) { Batch.PreferredEvaluatorIndex = (short)BatchEvaluatorIndexDynamicSelector(context); } } return(nodes.Length); }
public static void DumpAllNodes(MCTSIterator context, ref MCTSNodeStruct node, Base.DataType.Trees.TreeTraversalType type = Base.DataType.Trees.TreeTraversalType.BreadthFirst, bool childDetail = false) { int index = 1; // Visit all nodes and verify various conditions are true node.Traverse(context.Tree.Store, (ref MCTSNodeStruct node) => { Console.WriteLine(index + " " + node); if (childDetail) { int childIndex = 0; foreach (MCTSNodeStructChild childInfo in node.Children) { Console.WriteLine($" {childIndex++,3} {childInfo}"); } } index++; return(true); }, type); }
/// <summary> /// Constructor (with provided refernece to another MCTSIterator with which to share). /// </summary> /// <param name="otherContext"></param> public LeafEvaluatorReuseOtherTree(MCTSIterator otherContext) { OtherContext = otherContext; }
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(); } }
/// <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,