public void AppendMove(string moveStr) { MGPosition mgPos = MGPosition.FromPosition(FinalPosition); MGMove thisMove = MGMoveFromString.ParseMove(mgPos, moveStr); if (thisMove.IsNull) { throw new Exception("Unexpected null move"); } // Verify move is legal from this position MGMoveList moves = new MGMoveList(); MGMoveGen.GenerateMoves(in mgPos, moves); if (Array.IndexOf(moves.MovesArray, thisMove) == -1) { throw new Exception($"The move {moveStr} is not legal from position {FinalPosition.FEN}"); } Moves.Add(MGMoveFromString.ParseMove(mgPos, moveStr)); if (haveFinalized) { InitPositionsAndFinalPosMG(); } }
/// <summary> /// Runs CPU benchmark and outputs summary results, /// with an overall statistic provided (index to 100 on a Intel Skylake 6142). /// </summary> /// <returns>Relative CPU index (baseline 100)</returns> static int DumpCPUBenchmark() { Console.WriteLine("-----------------------------------------------------------------------------------"); Console.WriteLine("CPU BENCHMARK"); Position ps = Position.StartPosition; EncodedPositionBoard zb = default; MGMove nmove = ConverterMGMoveEncodedMove.EncodedMoveToMGChessMove(new EncodedMove("e2e4"), MGChessPositionConverter.MGChessPositionFromFEN(ps.FEN)); float ops1 = Benchmarking.DumpOperationTimeAndMemoryStats(() => MGPosition.FromPosition(ps), "MGPosition.FromPosition"); float ops2 = Benchmarking.DumpOperationTimeAndMemoryStats(() => MGChessPositionConverter.MGChessPositionFromFEN(ps.FEN), "MGChessPositionFromFEN"); float ops3 = Benchmarking.DumpOperationTimeAndMemoryStats(() => ConverterMGMoveEncodedMove.MGChessMoveToEncodedMove(nmove), "MGChessMoveToLZPositionMove"); float ops4 = Benchmarking.DumpOperationTimeAndMemoryStats(() => EncodedBoardZobrist.ZobristHash(zb), "ZobristHash"); // Performance metric is against a baseline system (Intel Skylake 6142) const float REFERENCE_BM1_OPS = 2160484; const float REFERENCE_BM2_OPS = 448074; const float REFERENCE_BM3_OPS = 157575582; const float REFERENCE_BM4_OPS = 112731351; float relative1 = ops1 / REFERENCE_BM1_OPS; float relative2 = ops2 / REFERENCE_BM2_OPS; float relative3 = ops3 / REFERENCE_BM3_OPS; float relative4 = ops4 / REFERENCE_BM4_OPS; float avg = StatUtils.Average(relative1, relative2, relative3, relative4); Console.WriteLine(); Console.WriteLine($"CERES CPU BENCHMARK SCORE: {avg*100,4:F0}"); return((int)MathF.Round(avg * 100, 0)); }
/// <summary> /// Constructs a new MGMoveSequence given a starting position (as a FEN) /// and an optional string containing a sequence of subsequent moves (in coordiante notation). /// </summary> /// <param name="fen"></param> /// <param name="movesStr"></param> /// <returns></returns> public static PositionWithHistory FromFENAndMovesUCI(string fen, string movesStr) { MGPosition mgPos = MGPosition.FromFEN(fen); PositionWithHistory ret = new PositionWithHistory(mgPos); if (movesStr != null && movesStr != "") { string[] parts = movesStr.Split(" "); for (int i = 0; i < parts.Length; i++) { string moveStr = parts[i]; if (moveStr != "") { MGMove mgMove = MGMoveFromString.ParseMove(mgPos, moveStr); ret.Moves.Add(mgMove); mgPos.MakeMove(mgMove); } } } return(ret); }
// -------------------------------------------------------------------------------------------- /// <summary> /// Converts from MGChessMove. /// TO DO: shouldn't this be in an Extension class instead? /// </summary> /// <param name="mgMove"></param> /// <returns></returns> public static Move ToMove(MGMove mgMove) { if (mgMove.CastleShort) { return(new Move(Move.MoveType.MoveCastleShort)); } if (mgMove.CastleLong) { return(new Move(Move.MoveType.MoveCastleLong)); } PieceType promoPiece = PieceType.None; if (mgMove.PromoteQueen) { promoPiece = PieceType.Queen; } else if (mgMove.PromoteRook) { promoPiece = PieceType.Rook; } else if (mgMove.PromoteBishop) { promoPiece = PieceType.Bishop; } else if (mgMove.PromoteKnight) { promoPiece = PieceType.Knight; } Square fromSquare = new Square((int)mgMove.FromSquareIndex, Square.SquareIndexType.BottomToTopRightToLeft); Square toSquare = new Square((int)mgMove.ToSquareIndex, Square.SquareIndexType.BottomToTopRightToLeft); return(new Move(fromSquare, toSquare, promoPiece)); }
public void AppendMove(MGMove move) { Moves.Add(move); if (haveFinalized) { InitPositionsAndFinalPosMG(); } }
/// <summary> /// /// </summary> /// <param name="node"></param> /// <param name="parentAnnotation">optionally a precomputed parent annotation (otherwise computed)</param> /// <returns></returns> public unsafe void Annotate(MCTSNode node) { if (node.Annotation.IsInitialized) { return; } NumAnnotations++; // Get the position corresponding to this node MGPosition newPos; if (!node.IsRoot) { // Apply move for this node to the prior position node.Parent.Annotate(); newPos = node.Parent.Annotation.PosMG; newPos.MakeMove(ConverterMGMoveEncodedMove.EncodedMoveToMGChessMove(node.PriorMove, in newPos, true)); } else { newPos = PriorMoves.FinalPosMG; } MGMove priorMoveMG = default; if (!node.IsRoot) { priorMoveMG = ConverterMGMoveEncodedMove.EncodedMoveToMGChessMove(node.PriorMove, in node.Parent.Annotation.PosMG, true); } bool isRoot = node.IsRoot; Position newPosAsPos = newPos.ToPosition; // Create history, with prepended move representing this move Span <Position> posHistory = GetPriorHistoryPositions(node.Parent, in newPosAsPos, PosScratchBuffer, node.Context.ParamsSearch.DrawByRepetitionLookbackPlies, true); // Determine the set (possibly a subset) of positions over which to compute hash Span <Position> posHistoryForCaching = posHistory; int numCacheHashPositions = node.Context.EvaluatorDef.NumCacheHashPositions; if (posHistory.Length > numCacheHashPositions) { posHistoryForCaching = posHistory.Slice(posHistory.Length - numCacheHashPositions, numCacheHashPositions); } // Compute the actual hash ulong zobristHashForCaching = EncodedBoardZobrist.ZobristHash(posHistoryForCaching, node.Context.EvaluatorDef.HashMode); node.LastAccessedSequenceCounter = node.Context.Tree.SEQUENCE_COUNTER++; node.Annotation.PriorMoveMG = priorMoveMG; node.Annotation.Pos = posHistory[^ 1]; // this will have had its repetition count set
MovesAndProbabilities(Position startPosition, float minProbability = 0.0f, int topN = int.MaxValue) { // (EncodedMove bestMoveEncoded, float probability) = result.Policy.PolicyInfoAtIndex(posIndex); MGPosition mgPos = MGPosition.FromPosition(in startPosition); foreach ((EncodedMove move, float probability) in ProbabilitySummary(minProbability, topN)) { MGMove mgMove = ConverterMGMoveEncodedMove.EncodedMoveToMGChessMove(move, in mgPos); Move moveRet = MGMoveConverter.ToMove(mgMove); yield return(moveRet, probability); } }
internal static int FindMoveIndex(MGMove[] moves, MGMove move, int startIndex, int numMovesUsed) { byte moveToSquare = move.ToSquareIndex; for (int i = startIndex; i < numMovesUsed; i++) { if (moves[i].ToSquareIndex == moveToSquare) { if (moves[i] == move) { return(i); } } } return(-1); }
/// <summary> /// Attempts to find a subnode by following specified moves from root. /// </summary> /// <param name="priorRoot"></param> /// <param name="movesMade"></param> /// <returns></returns> static MCTSNode FollowMovesToNode(MCTSNode priorRoot, IEnumerable <MGMove> movesMade) { PositionWithHistory startingPriorMove = priorRoot.Context.StartPosAndPriorMoves; MGPosition position = startingPriorMove.FinalPosMG; MCTSIterator context = priorRoot.Context; // Advance root node and update prior moves MCTSNode newRoot = priorRoot; foreach (MGMove moveMade in movesMade) { bool foundChild = false; // Find this new root node (after these moves) foreach (MCTSNodeStructChild child in newRoot.Ref.Children) { if (child.IsExpanded) { MGMove thisChildMove = ConverterMGMoveEncodedMove.EncodedMoveToMGChessMove(child.Move, in position); if (thisChildMove == moveMade) { // Advance new root to reflect this move newRoot = context.Tree.GetNode(child.ChildIndex, newRoot); // Advance position position.MakeMove(thisChildMove); // Done looking for match foundChild = true; break; } } } if (!foundChild) { return(null); } } // Found it return(newRoot); }
public SuiteTestResultItem(MCTSManager manager, MGMove bestMove) { using (new SearchContextExecutionBlock(manager.Context)) { if (manager.TablebaseImmediateBestMove != default(MGMove)) { Q = 1.0; BestMove = manager.TablebaseImmediateBestMove; PickedNonTopNMoveStr = " "; } else { Q = manager.Root.Q; UCIInfoString = UCIManager.UCIInfoString(manager); // SearchPrincipalVariation pv1 = new SearchPrincipalVariation(worker1.Root); BestMove = bestMove; N = manager.Context.Root.N; NumNodesWhenChoseTopNNode = manager.NumNodesWhenChoseTopNNode; NumNNBatches = manager.Context.NumNNBatches; NumNNNodes = manager.Context.NumNNNodes; TopNNodeN = manager.TopNNode.N; FractionNumNodesWhenChoseTopNNode = manager.FractionNumNodesWhenChoseTopNNode; AvgDepth = manager.Context.AvgDepth; MAvg = manager.Context.Root.MAvg; NodeSelectionYieldFrac = manager.Context.NodeSelectionYieldFrac; // TODO: try removing this, clean up try { PickedNonTopNMoveStr = !manager.Root.BestMove(false).Equals(manager.Root.ChildWithLargestValue(node => node.N)) ? "!" : " "; } catch (Exception excp) { // TODO: resolve this PickedNonTopNMoveStr = "?"; } } } }
/// <summary> /// Writes extensive descriptive information to a specified TextWriter, /// including verbose move statistics, principal variation, and move timing information. /// </summary> /// <param name="searchRootNode"></param> /// <param name="writer"></param> /// <param name="description"></param> public void DumpFullInfo(MGMove bestMove, MCTSNode searchRootNode = null, TextWriter writer = null, string description = null) { searchRootNode = searchRootNode ?? Root; writer = writer ?? Console.Out; int moveIndex = searchRootNode.Tree.Store.Nodes.PriorMoves.Moves.Count; writer.WriteLine(); writer.WriteLine("================================================================================="); writer.Write(DateTime.Now + " SEARCH RESULT INFORMATION, Move = " + ((1 + moveIndex / 2))); writer.WriteLine($" Thread = {Thread.CurrentThread.ManagedThreadId}"); if (description != null) { writer.WriteLine(description); } writer.WriteLine(); writer.WriteLine("Tree root : " + Context.Root); if (searchRootNode != Root) { writer.WriteLine("Search root : " + searchRootNode); } writer.WriteLine(); MCTSNode[] nodesSortedN = null; MCTSNode[] nodesSortedQ = null; string bestMoveInfo = ""; if (searchRootNode.NumChildrenExpanded > 0 && StopStatus != SearchStopStatus.TablebaseImmediateMove && StopStatus != SearchStopStatus.OnlyOneLegalMove) { MCTSNode[] childrenSortedN = searchRootNode.ChildrenSorted(node => - node.N); MCTSNode[] childrenSortedQ = searchRootNode.ChildrenSorted(node => (float)node.Q); bool isTopN = childrenSortedN[0].Annotation.PriorMoveMG == bestMove; bool isTopQ = childrenSortedQ[0].Annotation.PriorMoveMG == bestMove; if (isTopN && isTopQ) { bestMoveInfo = "(TopN and TopQ)"; } else if (isTopN) { bestMoveInfo = "(TopN)"; } else if (isTopQ) { bestMoveInfo = "(TopQ)"; } } // Output position (with history) information. writer.WriteLine("Position : " + searchRootNode.Annotation.Pos.FEN); writer.WriteLine("Tree root position : " + Context.Tree.Store.Nodes.PriorMoves); writer.WriteLine("Search stop status : " + StopStatus); writer.WriteLine("Best move selected : " + bestMove.MoveStr(MGMoveNotationStyle.LC0Coordinate) + " " + bestMoveInfo); writer.WriteLine(); using (new SearchContextExecutionBlock(Context)) { string infoUpdate = UCIInfo.UCIInfoString(this, searchRootNode); writer.WriteLine(infoUpdate); writer.WriteLine(); DumpTimeInfo(writer); writer.WriteLine(); searchRootNode.Dump(1, 1, writer: writer); writer.WriteLine(); MCTSPosTreeNodeDumper.DumpPV(searchRootNode, true, writer); } }
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,