Пример #1
0
        /// <summary>
        /// Processes a specified position command,
        /// with the side effect of resetting the curPositionAndMoves.
        /// </summary>
        /// <param name="command"></param>
        private void ProcessPosition(string command)
        {
            command = StringUtils.WhitespaceRemoved(command);

            string commandLower = command.ToLower();

            string posString;

            if (commandLower.StartsWith("position fen "))
            {
                posString = command.Substring(13);
            }
            else if (commandLower.StartsWith("position startpos"))
            {
                posString = command.Substring(9);
            }
            else
            {
                throw new Exception($"Illegal position command, expected to start with position fen or position startpos");
            }

            PositionWithHistory newPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(posString);

            curPositionIsContinuationOfPrior = newPositionAndMoves.IsIdenticalToPriorToLastMove(curPositionAndMoves);
            if (!curPositionIsContinuationOfPrior && CeresEngine != null)
            {
                CeresEngine.ResetGame();
            }

            // Switch to the new position and moves
            curPositionAndMoves = newPositionAndMoves;
        }
Пример #2
0
        /// <summary>
        /// Analyzes a position until a specified search limit is exhausted.
        /// </summary>
        /// <param name="fenAndMovesStr"></param>
        /// <param name="searchLimit"></param>
        /// <returns></returns>
        public LC0VerboseMoveStats AnalyzePositionFromFENAndMoves(string fenAndMovesStr, SearchLimit searchLimit)
        {
            List <LC0VerboseMoveStat> moves = new List <LC0VerboseMoveStat>();
            PositionWithHistory       pwh   = PositionWithHistory.FromFENAndMovesUCI(fenAndMovesStr);

            UCISearchInfo searchInfo;

            int searchValueMilliseconds = (int)((float)searchLimit.Value * 1000.0f);

            switch (searchLimit.Type)
            {
            case SearchLimitType.NodesPerMove:
                searchInfo = Runner.EvalPositionToNodes(fenAndMovesStr, (int)searchLimit.Value);
                break;

            case SearchLimitType.SecondsPerMove:
                searchInfo = Runner.EvalPositionToMovetime(fenAndMovesStr, searchValueMilliseconds);
                break;

            case SearchLimitType.NodesForAllMoves:
                throw new Exception("NodesForAllMoves not supported for Leela Chess Zero");

            case SearchLimitType.SecondsForAllMoves:
                bool weAreWhite = pwh.FinalPosition.MiscInfo.SideToMove == SideType.White;

                searchInfo = Runner.EvalPositionRemainingTime(fenAndMovesStr,
                                                              weAreWhite,
                                                              searchLimit.MaxMovesToGo,
                                                              (int)(searchLimit.Value * 1000),
                                                              (int)(searchLimit.ValueIncrement * 1000));
                break;

            default:
                throw new Exception($"Unknown SeachLimit.Type {searchLimit.Type}");
            }

            double elapsed = 0;//engine.EngineProcess.TotalProcessorTime.TotalSeconds - startTime;

            // no more, we now assume  win_percentages is requested     LeelaVerboseMoveStats ret = new LeelaVerboseMoveStats(positionEnd, searchInfo.BestMove, elapsed, searchInfo.Nodes, LZPositionEvalLogistic.CentipawnToLogistic2018(searchInfo.Score));
            float scoreLogistic     = searchInfo.ScoreLogistic;
            LC0VerboseMoveStats ret = new LC0VerboseMoveStats(pwh.FinalPosition, searchInfo.BestMove, elapsed, searchInfo.Nodes, scoreLogistic, searchInfo);

            searchInfo.Infos.Reverse();
            foreach (string info in searchInfo.Infos)
            {
                if (info.Contains("P:"))
                {
                    moves.Add(new LC0VerboseMoveStat(ret, info));
                }
            }

            ret.SetMoves(moves);

            return(LastAnalyzedPositionStats = ret);
        }
Пример #3
0
        /// <summary>
        /// Processes a specified position command,
        /// with the side effect of resetting the curPositionAndMoves.
        /// </summary>
        /// <param name="command"></param>
        private void ProcessPosition(string command)
        {
            string[] parts = command.Split(" ");

            string fen;
            int    nextIndex;

            string startFEN;

            switch (parts[1])
            {
            case "fen":
                fen       = command.Substring(command.IndexOf("fen") + 4);
                nextIndex = 2;
                while (parts.Length > nextIndex && parts[nextIndex] != "moves")
                {
                    fen = fen + " " + parts[nextIndex++];
                }
                startFEN = fen;
                break;

            case "startpos":
                startFEN  = Position.StartPosition.FEN;
                nextIndex = 2;
                break;

            default:
                throw new Exception("invalid " + command);
            }

            string movesSubstring = "";

            if (parts.Length > nextIndex && parts[nextIndex] == "moves")
            {
                for (int i = nextIndex + 1; i < parts.Length; i++)
                {
                    movesSubstring += parts[i] + " ";
                }
            }

            PositionWithHistory newPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(startFEN, movesSubstring);

            curPositionIsContinuationOfPrior = newPositionAndMoves.IsIdenticalToPriorToLastMove(curPositionAndMoves);
            if (!curPositionIsContinuationOfPrior && CeresEngine != null)
            {
                CeresEngine.ResetGame();
            }

            // Switch to the new position and moves
            curPositionAndMoves = newPositionAndMoves;
        }
Пример #4
0
        /// <summary>
        /// Analyzes a specified position until a specified limit is exhausted.
        ///
        /// TODO: This method is highly redundant (and inferior to?) the next method AnalyzePositionFromFENAndMoves, delete it.
        /// </summary>
        /// <param name="fenOrFENAndMoves">a FEN</param>
        /// <param name="nodes"></param>
        /// <returns></returns>
        public UCISearchInfo AnalyzePositionFromFEN(string fenAndMovesString, SearchLimit searchLimit)
        {
            List <LC0VerboseMoveStat> moves = new List <LC0VerboseMoveStat>();

            Runner.EvalPositionPrepare();

            UCISearchInfo searchInfo;

            if (searchLimit.Type == SearchLimitType.SecondsPerMove)
            {
                searchInfo = Runner.EvalPositionToMovetime(fenAndMovesString, (int)(searchLimit.Value * 1000.0f));
            }
            else if (searchLimit.Type == SearchLimitType.NodesPerMove)
            {
                searchInfo = Runner.EvalPositionToNodes(fenAndMovesString, (int)searchLimit.Value);
            }
            else
            {
                throw new Exception("Unknown search limit " + searchLimit.Type);
            }

            double elapsed = searchInfo.EngineReportedSearchTime / 1000.0f;

            // no more, we now assume  win_percentages is requested     LeelaVerboseMoveStats ret = new LeelaVerboseMoveStats(positionEnd, searchInfo.BestMove, elapsed, searchInfo.Nodes, LZPositionEvalLogistic.CentipawnToLogistic2018(searchInfo.Score));
            float scoreConverted    = 2.0f * (((float)searchInfo.ScoreCentipawns / 10_000f) - 0.5f);
            PositionWithHistory pwh = PositionWithHistory.FromFENAndMovesUCI(fenAndMovesString);
            LC0VerboseMoveStats ret = new LC0VerboseMoveStats(pwh.FinalPosition, searchInfo.BestMove, elapsed, searchInfo.Nodes, scoreConverted, searchInfo);

            foreach (string info in searchInfo.Infos)
            {
                if (info.Contains("P:"))
                {
                    moves.Add(new LC0VerboseMoveStat(ret, info));
                }
            }

            ret.SetMoves(moves);

            // TODO: Someday perhaps make LeelaVerboseMoveStats a subclass of UCISearchInfo so this is more elegant
            UCISearchInfo uciInfo = new UCISearchInfo(null, ret.BestMove, null);

            uciInfo.Nodes = ret.NumNodes;
            uciInfo.EngineReportedSearchTime = (int)(1000.0f * ret.ElapsedTime);
            uciInfo.ExtraInfo = ret;
            uciInfo.BestMove  = ret.BestMove;

            return(uciInfo);
        }
Пример #5
0
        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));
        }
Пример #6
0
        /// <summary>
        /// Runs the UCI loop.
        /// </summary>
        public void PlayUCI()
        {
            // Default to the startpos.
            curPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(Position.StartPosition.FEN, null);
            curManager          = null;
            gameMoveHistory     = new List <GameMoveStat>();

            while (true)
            {
                string command = InStream.ReadLine();

                switch (command)
                {
                case null:
                case "":
                    break;

                case "uci":
                    Send("id name Ceres"); // TODO: Add executable version
                    Send("id author David Elliott and the Ceres Authors");
                    // todo output options such as:
                    //   option name Logfile type check default false
                    Send("uciok");
                    break;

                case "setoption":
                    OutStream.WriteLine("Not processing option " + command);
                    return;

                case "stop":
                    if (taskSearchCurrentlyExecuting != null && !stopIsPending)
                    {
                        stopIsPending = true;

                        // TODO: cleanup
                        //       Possible race condition, curManager is only set in search callback which may not have hit yet
                        //       Fix eventually by rewriting SerchManager to have a constructor and then non-static Search method,
                        //       os we can get the context in this class directly after construction
                        while (curManager == null)
                        {
                            Thread.Sleep(1);              // **** TEMPORARY ***
                        }
                        curManager.ExternalStopRequested = true;
                        if (taskSearchCurrentlyExecuting != null)
                        {
                            taskSearchCurrentlyExecuting.Wait();
                            if (!debug && taskSearchCurrentlyExecuting != null)
                            {
                                taskSearchCurrentlyExecuting.Result?.Manager?.Dispose();
                            }
                            taskSearchCurrentlyExecuting = null;
                        }
                    }

                    curManager    = null;
                    stopIsPending = false;

                    break;

                case "ponderhit":
                    throw new NotImplementedException("Ceres does not yet support UCI ponder mode.");
                    return;

                case "xboard":
                    // ignore
                    break;

                case "debug on":
                    debug = true;
                    break;

                case "debug off":
                    debug = false;
                    break;

                case "isready":
                    InitializeEngineIfNeeded();
                    Send("readyok");
                    break;

                case "ucinewgame":
                    gameMoveHistory = new List <GameMoveStat>();
                    CeresEngine?.ResetGame();
                    break;

                case "quit":
                    if (curManager != null)
                    {
                        curManager.ExternalStopRequested = true;
                        taskSearchCurrentlyExecuting?.Wait();
                    }

                    if (CeresEngine != null)
                    {
                        CeresEngine.Dispose();
                    }

                    System.Environment.Exit(0);
                    break;

                case string c when c.StartsWith("go"):
                    if (taskSearchCurrentlyExecuting != null)
                    {
                        throw new Exception("Received go command when another search was running and not stopped first");
                    }

                    InitializeEngineIfNeeded();

                    taskSearchCurrentlyExecuting = ProcessGo(command);
                    break;

                case string c when c.StartsWith("position"):
                    try
                    {
                        ProcessPosition(c);
                    }
                    catch (Exception e)
                    {
                        Send($"Illegal position command: \"{c}\"" + System.Environment.NewLine + e.ToString());
                    }
                    break;

                // Proprietary commands
                case "lc0-config":
                    if (curManager != null)
                    {
                        string             netID  = EvaluatorDef.Nets[0].Net.NetworkID;
                        INNWeightsFileInfo netDef = NNWeightsFiles.LookupNetworkFile(netID);
                        (string exe, string options) = LC0EngineConfigured.GetLC0EngineOptions(null, null, curContext.EvaluatorDef, netDef, false, false);
                        Console.WriteLine("info string " + exe + " " + options);
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }

                    break;

                case "dump-params":
                    if (curManager != null)
                    {
                        curManager.DumpParams();
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-processor":
                    HardwareManager.DumpProcessorInfo();
                    break;

                case "dump-time":
                    if (curManager != null)
                    {
                        curManager.DumpTimeInfo();
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-store":
                    if (curManager != null)
                    {
                        using (new SearchContextExecutionBlock(curContext))
                            curManager.Context.Tree.Store.Dump(true);
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-move-stats":
                    if (curManager != null)
                    {
                        using (new SearchContextExecutionBlock(curContext))
                            curManager.Context.Root.Dump(1, 1, prefixString: "info string ");
                    }
                    else
                    {
                        Console.WriteLine("info string No search manager created");
                    }
                    break;

                case "dump-pv":
                    DumpPV(false);
                    break;

                case "dump-pv-detail":
                    DumpPV(true);
                    break;

                case "dump-nvidia":
                    NVML.DumpInfo();
                    break;


                case "waitdone": // proprietary verb
                    taskSearchCurrentlyExecuting?.Wait();
                    break;

                default:
                    Console.WriteLine($"error Unknown command: {command}");
                    break;
                }
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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();
                                  }
        }
Пример #9
0
        /// <summary>
        /// Runs the UCI loop.
        /// </summary>
        public void PlayUCI()
        {
            // Default to the startpos.
            curPositionAndMoves = PositionWithHistory.FromFENAndMovesUCI(Position.StartPosition.FEN);
            gameMoveHistory     = new List <GameMoveStat>();

            while (true)
            {
                string command = InStream.ReadLine();
                if (uciLogWriter != null)
                {
                    LogWriteLine("IN:", command);
                }

                switch (command)
                {
                case null:
                case "":
                    break;

                case "uci":
                    UCIWriteLine($"id name Ceres {CeresVersion.VersionString}");
                    UCIWriteLine("id author David Elliott and the Ceres Authors");
                    UCIWriteLine(SetOptionUCIDescriptions);
                    UCIWriteLine("uciok");
                    break;

                case string c when c.StartsWith("setoption"):
                    ProcessSetOption(command);

                    break;

                case "stop":
                    if (taskSearchCurrentlyExecuting != null && !stopIsPending)
                    {
                        stopIsPending = true;

                        // Avoid race condition by mkaing sure the search is already created.
                        while (CeresEngine.Search?.Manager == null)
                        {
                            Thread.Sleep(20);
                        }

                        CeresEngine.Search.Manager.ExternalStopRequested = true;
                        if (taskSearchCurrentlyExecuting != null)
                        {
                            taskSearchCurrentlyExecuting.Wait();
                            //                if (!debug && taskSearchCurrentlyExecuting != null) taskSearchCurrentlyExecuting.Result?.Search?.Manager?.Dispose();
                            taskSearchCurrentlyExecuting = null;
                        }
                    }

                    stopIsPending = false;

                    break;

                case "ponderhit":
                    throw new NotImplementedException("Ceres does not yet support UCI ponder mode.");
                    return;

                case "xboard":
                    // ignore
                    break;

                case "debug on":
                    debug = true;
                    break;

                case "debug off":
                    debug = false;
                    break;

                case "isready":
                    InitializeEngineIfNeeded();
                    UCIWriteLine("readyok");
                    break;

                case "ucinewgame":
                    gameMoveHistory = new List <GameMoveStat>();
                    CeresEngine?.ResetGame();
                    break;

                case "quit":
                    if (taskSearchCurrentlyExecuting != null)
                    {
                        CeresEngine.Search.Manager.ExternalStopRequested = true;
                        taskSearchCurrentlyExecuting?.Wait();
                    }

                    if (CeresEngine != null)
                    {
                        CeresEngine.Dispose();
                    }

                    System.Environment.Exit(0);
                    break;

                case string c when c.StartsWith("go"):

                    // Possibly another search is already executing.
                    // The UCI specification is unclear about what to do in this situation.
                    // Some engines seem to enqueue these for later execution (e.g. Stockfish)
                    // whereas others (e.g. Python chess) report this as an error condition.
                    // Currently Ceres waits only a short while for any possible pending search
                    // to finish (e.g. to avoid a race condition if it is in the process of being shutdown)
                    // and aborts with an error if search is still in progress.
                    // It is not viable to wait indefinitely, since (among other reasons)
                    // the engine needs to monitor for stop commands.
                    const int MAX_MILLISECONDS_WAIT = 500;

                    taskSearchCurrentlyExecuting?.Wait(MAX_MILLISECONDS_WAIT);

                    if (taskSearchCurrentlyExecuting != null && !taskSearchCurrentlyExecuting.IsCompleted)
                    {
                        throw new Exception("Received go command when another search was running and not stopped first.");
                    }

                    InitializeEngineIfNeeded();

                    taskSearchCurrentlyExecuting = ProcessGo(command);
                    break;

                case string c when c.StartsWith("position"):
                    try
                    {
                        ProcessPosition(c);
                    }
                    catch (Exception e)
                    {
                        UCIWriteLine($"Illegal position command: \"{c}\"" + System.Environment.NewLine + e.ToString());
                    }
                    break;

                // Proprietary commands
                case "lc0-config":
                    if (CeresEngine?.Search != null)
                    {
                        string             netID  = EvaluatorDef.Nets[0].Net.NetworkID;
                        INNWeightsFileInfo netDef = NNWeightsFiles.LookupNetworkFile(netID);
                        (string exe, string options) = LC0EngineConfigured.GetLC0EngineOptions(null, null, CeresEngine.Search.Manager.Context.EvaluatorDef, netDef, false, false);
                        UCIWriteLine("info string " + exe + " " + options);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }

                    break;

                case "dump-params":
                    if (CeresEngine?.Search != null)
                    {
                        CeresEngine?.Search.Manager.DumpParams();
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-processor":
                    HardwareManager.DumpProcessorInfo();
                    break;

                case "dump-time":
                    if (CeresEngine?.Search != null)
                    {
                        CeresEngine?.Search.Manager.DumpTimeInfo(OutStream);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-store":
                    if (CeresEngine?.Search != null)
                    {
                        using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context))
                            CeresEngine.Search.Manager.Context.Tree.Store.Dump(true);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-move-stats":
                    if (CeresEngine?.Search != null)
                    {
                        OutputVerboseMoveStats(CeresEngine.Search.SearchRootNode);
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "dump-pv":
                    DumpPV(false);
                    break;

                case "dump-pv-detail":
                    DumpPV(true);
                    break;

                case "dump-nvidia":
                    NVML.DumpInfo();
                    break;

                case "show-tree-plot":
                    if (CeresEngine?.Search != null)
                    {
                        using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context))
                        {
                            TreePlot.Show(CeresEngine.Search.Manager.Context.Root.Ref);
                        }
                    }
                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case string c when c.StartsWith("save-tree-plot"):
                    if (CeresEngine?.Search != null)
                    {
                        string[] parts = command.Split(" ");
                        if (parts.Length == 2)
                        {
                            string fileName = parts[1];
                            using (new SearchContextExecutionBlock(CeresEngine.Search.Manager.Context))
                            {
                                TreePlot.Save(CeresEngine.Search.Manager.Context.Root.Ref, fileName);
                            }
                        }
                        else if (parts.Length == 1)
                        {
                            UCIWriteLine("Filename was not provided");
                        }
                        else
                        {
                            UCIWriteLine("Filename cannot contain spaces");
                        }
                    }

                    else
                    {
                        UCIWriteLine("info string No search manager created");
                    }
                    break;

                case "waitdone": // proprietary verb used for test driver
                    taskSearchCurrentlyExecuting?.Wait();
                    break;

                default:
                    UCIWriteLine($"error Unknown command: {command}");
                    break;
                }
            }
        }