public static void Analyze(string fenAndMoves, SearchLimit searchLimit, NNEvaluatorDef evaluatorDef, bool forceDisablePruning, LC0Engine lc0Engine = null, GameEngine comparisonEngine = null, bool verbose = false) { Console.WriteLine("============================================================================="); Console.WriteLine("Analyzing FEN : " + fenAndMoves); Console.WriteLine("Search limit : " + searchLimit.ToString()); Console.WriteLine("Ceres evaluator : " + evaluatorDef.ToString()); if (comparisonEngine != null) { Console.WriteLine("Opponent : " + comparisonEngine.ToString()); } Console.WriteLine(); Console.WriteLine(); NNEvaluatorSet nnEvaluators = new NNEvaluatorSet(evaluatorDef); // Warmup (in parallel) lc0Engine?.DoSearchPrepare(); Parallel.Invoke( () => nnEvaluators.Warmup(true), () => comparisonEngine?.Warmup()); bool ceresDone = false; lastInfoUpdate = DateTime.Now; UCISearchInfo lastCeresInfo = null; // Launch Ceres MCTSearch ceresResults = null; Task searchCeres = Task.Run(() => { ParamsSearch searchParams = new ParamsSearch(); searchParams.FutilityPruningStopSearchEnabled = !forceDisablePruning; PositionWithHistory positionWithHistory = PositionWithHistory.FromFENAndMovesUCI(fenAndMoves); ceresResults = new MCTSearch(); ceresResults.Search(nnEvaluators, new ParamsSelect(), searchParams, null, null, null, positionWithHistory, searchLimit, verbose, DateTime.Now, null, manager => lastCeresInfo = new UCISearchInfo(UCIInfo.UCIInfoString(manager), null, null), false, true); }); // Possibly launch search for other engine Task searchComparison = null; if (lc0Engine != null || comparisonEngine != null) { searchComparison = Task.Run(() => { if (lc0Engine != null) { lc0Engine.DoSearchPrepare(); lc0Engine.AnalyzePositionFromFENAndMoves(fenAndMoves, searchLimit); } else { comparisonEngine.Search(PositionWithHistory.FromFENAndMovesUCI(fenAndMoves), searchLimit, verbose: true); } }); } ; while (!searchCeres.IsCompleted || (searchComparison != null && !searchComparison.IsCompleted)) { Thread.Sleep(1000); //Console.WriteLine(DateTime.Now + " --> " + lastCeresInfo?.PVString + " OTHER " + comparisonEngine?.UCIInfo?.RawString); int numCharactersSame = int.MaxValue; if (lastCeresInfo?.PVString != null || comparisonEngine?.UCIInfo?.RawString != null) { if (lastCeresInfo != null && comparisonEngine?.UCIInfo != null) { numCharactersSame = 0; string pv1 = lastCeresInfo.PVString; UCISearchInfo lastComparisonInfo = comparisonEngine.UCIInfo; string pv2 = lastComparisonInfo.PVString; while (pv1.Length > numCharactersSame && pv2.Length > numCharactersSame && pv1[numCharactersSame] == pv2[numCharactersSame]) { numCharactersSame++; } } } if (lastCeresInfo != null) { WriteUCI("Ceres", lastCeresInfo, numCharactersSame); } if (comparisonEngine != null) { WriteUCI(comparisonEngine.ID, comparisonEngine.UCIInfo, numCharactersSame); } Console.WriteLine(); } searchCeres.Wait(); searchComparison?.Wait(); string infoUpdate = UCIInfo.UCIInfoString(ceresResults.Manager); double q2 = ceresResults.SearchRootNode.Q; //SearchPrincipalVariation pv2 = new SearchPrincipalVariation(worker2.Root); MCTSPosTreeNodeDumper.DumpPV(ceresResults.SearchRootNode, true); }
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(); } }