Example #1
0
        /// <summary>
        /// Actually runs a search with specified limits.
        /// </summary>
        /// <param name="searchLimit"></param>
        /// <returns></returns>
        private GameEngineSearchResultCeres RunSearch(SearchLimit searchLimit)
        {
            DateTime lastInfoUpdate = DateTime.Now;

            int numUpdatesSent = 0;

            MCTSManager.MCTSProgressCallback callback =
                (manager) =>
            {
                curManager = manager;

                DateTime now = DateTime.Now;
                float    timeSinceLastUpdate = (float)(now - lastInfoUpdate).TotalSeconds;

                bool  isFirstUpdate           = numUpdatesSent == 0;
                float UPDATE_INTERVAL_SECONDS = isFirstUpdate ? 0.1f : 0.5f;
                if (curManager != null && timeSinceLastUpdate > UPDATE_INTERVAL_SECONDS && curManager.Root.N > 0)
                {
                    Send(UCIInfoString(curManager));

                    numUpdatesSent++;
                    lastInfoUpdate = now;
                }
            };

            GameEngineCeresInProcess.ProgressCallback callbackPlain = obj => callback((MCTSManager)obj);

            // use this? movesSinceNewGame

            // Search from this position (possibly with tree reuse)
            GameEngineSearchResultCeres result = CeresEngine.Search(curPositionAndMoves, searchLimit, gameMoveHistory, callbackPlain) as GameEngineSearchResultCeres;

            GameMoveStat moveStat = new GameMoveStat(gameMoveHistory.Count,
                                                     curPositionAndMoves.FinalPosition.MiscInfo.SideToMove,
                                                     result.ScoreQ, result.ScoreCentipawns,
                                                     float.NaN, //engine1.CumulativeSearchTimeSeconds,
                                                     curPositionAndMoves.FinalPosition.PieceCount,
                                                     result.MAvg, result.FinalN, result.FinalN - result.StartingN,
                                                     searchLimit,
                                                     (float)result.TimingStats.ElapsedTimeSecs);

            gameMoveHistory.Add(moveStat);

            if (SearchFinishedEvent != null)
            {
                SearchFinishedEvent(result.Search.Manager);
            }

            // Send the final info string (unless this was an instamove).
            Send(UCIInfoString(result.Search.Manager, result.Search.BestMoveRoot));

            // Send the best move
            Send("bestmove " + result.Search.BestMove.MoveStr(MGMoveNotationStyle.LC0Coordinate));
            if (debug)
            {
                Send("info string " + result.Search.BestMoveRoot.BestMoveInfo(false));
            }

            return(result);
        }
Example #2
0
        /// <summary>
        /// Handles special case that move was selected immediately at root from tablebase.
        /// </summary>
        /// <param name="manager"></param>
        /// <param name="searchRootNode"></param>
        /// <param name="scoreAsQ"></param>
        /// <returns></returns>
        static string OutputUCIInfoTablebaseImmediate(MCTSManager manager, MCTSNode searchRootNode, bool scoreAsQ)
        {
            GameResult result = searchRootNode.Ref.Terminal;

            string scoreStr;

            if (result == GameResult.Checkmate)
            {
                scoreStr = scoreAsQ ? "1.0" : "9999";
            }
            else if (result == GameResult.Draw)
            {
                scoreStr = "0";
            }
            else
            {
                // TODO: cleanup, see comment in MCTSManager.TrySetImmediateBestMove
                //       explaining special meeting of Unknown status to actually mean loss.
                scoreStr = scoreAsQ ? "-1.0" : "-9999";
            }

            string moveStr = manager.TablebaseImmediateBestMove.MoveStr(MGMoveNotationStyle.LC0Coordinate);
            string str     = $"info depth 1 seldepth 1 time 0 nodes 1 score cp {scoreStr} pv {moveStr}";

            return(str);
        }
Example #3
0
        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);
            }
        }
        private void TryAddSupplementalNodes(MCTSManager manager, int maxNodes,
                                             MCTSNodesSelectedSet selectedNodes, ILeafSelector selector)
        {
            foreach ((MCTSNode parentNode, int selectorID, int childIndex) in ((LeafSelectorMulti)selector).supplementalCandidates) // TODO: remove cast
            {
                if (childIndex <= parentNode.NumChildrenExpanded - 1)
                {
                    // This child was already selected as part of the normal leaf gathering process.
                    continue;
                }
                else
                {
                    MCTSEventSource.TestCounter1++;

                    // Record visit to this child in the parent (also increments the child NInFlight counter)
                    parentNode.UpdateRecordVisitsToChild(selectorID, childIndex, 1);

                    MCTSNode node = parentNode.CreateChild(childIndex);

                    ((LeafSelectorMulti)selector).DoVisitLeafNode(node, 1);// TODO: remove cast

                    if (!parentNode.IsRoot)
                    {
                        if (selectorID == 0)
                        {
                            parentNode.Parent.Ref.BackupIncrementInFlight(1, 0);
                        }
                        else
                        {
                            parentNode.Parent.Ref.BackupIncrementInFlight(0, 1);
                        }
                    }

                    // Try to process this node
                    int nodesBefore = selectedNodes.NodesNN.Count;
                    selector.InsureAnnotated(node);
                    selectedNodes.ProcessNode(node);
                    bool wasSentToNN = selectedNodes.NodesNN.Count != nodesBefore;
                    //if (wasSentToNN) MCTSEventSource.TestCounter2++;

                    // dje: add counter?
                }
            }
        }
Example #5
0
        private void TryAddRootPreloadNodes(MCTSManager manager, int maxNodes, MCTSNodesSelectedSet selectedNodes, ILeafSelector selector)
        {
            if (rootPreloader == null)
            {
                return;
            }

            List <MCTSNode> rootPreloadNodes = rootPreloader.GetRootPreloadNodes(manager.Root, selector.SelectorID, maxNodes, MCTSRootPreloader.PRELOAD_MIN_P);

            if (rootPreloadNodes != null)
            {
                for (int i = 0; i < rootPreloadNodes.Count; i++)
                {
                    MCTSNode node = rootPreloadNodes[i];
                    selector.InsureAnnotated(node);
                    selectedNodes.ProcessNode(node);
                }
            }
        }
Example #6
0
        public static string UCIInfoString(MCTSManager manager, MCTSNode bestMoveRoot = null)
        {
            // If no override bestMoveRoot was specified
            // then it is assumed the move chosen was from the root (not an instamove)
            if (bestMoveRoot == null)
            {
                bestMoveRoot = manager.Root;
            }

            bool wasInstamove = manager.Root != bestMoveRoot;

            float elapsedTimeSeconds = wasInstamove ? 0 : (float)(DateTime.Now - manager.StartTimeThisSearch).TotalSeconds;

            float scoreCentipawn = MathF.Round(EncodedEvalLogistic.LogisticToCentipawn((float)bestMoveRoot.Q), 0);
            float nps            = manager.NumStepsTakenThisSearch / elapsedTimeSeconds;

            SearchPrincipalVariation pv;

            using (new SearchContextExecutionBlock(manager.Context))
            {
                pv = new SearchPrincipalVariation(bestMoveRoot);
            }

            //info depth 12 seldepth 27 time 30440 nodes 51100 score cp 105 hashfull 241 nps 1678 tbhits 0 pv e6c6 c5b4 d5e4 d1e1
            int selectiveDepth        = pv.Nodes.Count - 1;
            int depthOfBestMoveInTree = wasInstamove ? bestMoveRoot.Depth : 0;
            int depth = (int)MathF.Round(manager.Context.AvgDepth - depthOfBestMoveInTree, 0);


            if (wasInstamove)
            {
                // Note that the correct tablebase hits cannot be easily calculated and reported
                return($"info depth {depth} seldepth {selectiveDepth} time 0 "
                       + $"nodes {bestMoveRoot.N:F0} score cp {scoreCentipawn:F0} tbhits {manager.CountTablebaseHits} nps 0 "
                       + $"pv {pv.ShortStr()} string M= {bestMoveRoot.MAvg:F0} instamove");
            }
            else
            {
                return($"info depth {depth} seldepth {selectiveDepth} time {elapsedTimeSeconds * 1000.0f:F0} "
                       + $"nodes {manager.Root.N:F0} score cp {scoreCentipawn:F0} tbhits {manager.CountTablebaseHits} nps {nps:F0} "
                       + $"pv {pv.ShortStr()} string M= {manager.Root.MAvg:F0}");
            }
        }
Example #7
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);
                }
            }

            MCTSManager worker1, worker2 = default;
            MGMove      bestMoveMG1, bestMoveMG2 = default;
            TimingStats stats1, stats2 = default;

            // 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).
            if (epdNum % 2 == 0 || Def.CeresEngine2Def == null)
            {
                (worker1, bestMoveMG1, stats1)
                    = MCTSLaunch.SearchOnFEN(evaluatorSet1, Def.Engine1Def.SelectParams, Def.Engine1Def.SearchParams, null, null, null,
                                             epdToUse.FEN, epd.StartMoves,
                                             MakeCeresSearchLimit(Def, otherEngineAnalysis2, ceresSearchLimit1), false, null, true, null);

                MCTSIterator shareContext = null;
                if (Def.RunCeres2Engine)
                {
                    if (Def.Engine2Def.SearchParams.ReusePositionEvaluationsFromOtherTree)
                    {
                        shareContext = worker1.Context;
                    }

                    (worker2, bestMoveMG2, stats2)
                        = MCTSLaunch.SearchOnFEN(evaluatorSet2, Def.Engine2Def.SelectParams, Def.Engine2Def.SearchParams, null, null, shareContext,
                                                 epdToUse.FEN, epd.StartMoves,
                                                 MakeCeresSearchLimit(Def, otherEngineAnalysis2, ceresSearchLimit2), false, null, true, null);
                }
            }
            else
            {
                (worker2, bestMoveMG2, stats2)
                    = MCTSLaunch.SearchOnFEN(evaluatorSet2, Def.Engine2Def.SelectParams, Def.Engine2Def.SearchParams, null, null, null,
                                             epdToUse.FEN, epd.StartMoves,
                                             MakeCeresSearchLimit(Def, otherEngineAnalysis2, ceresSearchLimit2), false, null, true, null);

                MCTSIterator shareContext = null;
                if (Def.Engine1Def.SearchParams.ReusePositionEvaluationsFromOtherTree)
                {
                    shareContext = worker2.Context;
                }

                (worker1, bestMoveMG1, stats1)
                    = MCTSLaunch.SearchOnFEN(evaluatorSet1, Def.Engine1Def.SelectParams, Def.Engine1Def.SearchParams, null, null, shareContext,
                                             epdToUse.FEN, epd.StartMoves,
                                             MakeCeresSearchLimit(Def, otherEngineAnalysis2, ceresSearchLimit1), false, null, true, null);
            }

            // 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(bestMoveMG1);
            Move bestMoveCeres2 = MGMoveConverter.ToMove(bestMoveMG2);

            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(worker1, bestMoveMG1);
            SearchResultInfo result2 = worker2 == null ? null : new SearchResultInfo(worker2, bestMoveMG2);

            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 && (worker2 == null || scoreCeres2 > 0))
            {
                accWCeres1 += (scoreCeres1 == 0) ? result1.N : result1.NumNodesWhenChoseTopNNode;
                if (worker2 != 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 = worker2 == null ? 0 : result2.NumNNBatches;
            int evalNumPos2     = worker2 == 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)stats1.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)stats2.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 = worker2 != 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", $"{worker1.BestMoveMG,7}", 9);
            if (c2)
            {
                writer.Add("MC2", $"{worker2.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", $"{stats1.ElapsedTimeSecs,7:F2}", 9);
            if (c2)
            {
                writer.Add("TimeC2", $"{stats2.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", $"{worker1.CountTablebaseHits,8:N0}", 10);
            if (c2)
            {
                writer.Add("TBase2", $"{worker2.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");

            worker1?.Dispose();
            if (!object.ReferenceEquals(worker1, worker2))
            {
                worker2?.Dispose();
            }
        }
Example #8
0
        /// <summary>
        /// Returns an UCI info string appropriate for a given search state.
        /// </summary>
        /// <param name="manager"></param>
        /// <param name="overrideRootMove"></param>
        /// <returns></returns>
        public static string UCIInfoString(MCTSManager manager,
                                           MCTSNode overrideRootMove           = null,
                                           MCTSNode overrideBestMoveNodeAtRoot = null,
                                           int?multiPVIndex = null,
                                           bool useParentN  = true,
                                           bool showWDL     = false,
                                           bool scoreAsQ    = false)
        {
            if (manager.TablebaseImmediateBestMove != default)
            {
                if (multiPVIndex.HasValue && multiPVIndex != 1)
                {
                    return(null);
                }
                else
                {
                    return(OutputUCIInfoTablebaseImmediate(manager, overrideRootMove ?? manager.Root, scoreAsQ));
                }
            }

            bool wasInstamove = manager.Root != overrideRootMove;

            // If no override bestMoveRoot was specified
            // then it is assumed the move chosen was from the root (not an instamove)
            MCTSNode thisRootNode = overrideRootMove ?? manager.Root;

            if (thisRootNode.NumPolicyMoves == 0)
            {
                // Terminal position, nothing to output
                return(null);
            }

            float elapsedTimeSeconds = wasInstamove ? 0 : (float)(DateTime.Now - manager.StartTimeThisSearch).TotalSeconds;

            // Get the principal variation (the first move of which will be the best move)
            SearchPrincipalVariation pv;

            using (new SearchContextExecutionBlock(manager.Context))
            {
                pv = new SearchPrincipalVariation(thisRootNode, overrideBestMoveNodeAtRoot);
            }

            MCTSNode bestMoveNode = pv.Nodes.Count > 1 ? pv.Nodes[1] : pv.Nodes[0];

            // The score displayed corresponds to
            // the Q (average visit value) of the move to be made.
            float scoreToShow;

            if (scoreAsQ)
            {
                scoreToShow = MathF.Round((float)-bestMoveNode.Q * 1000, 0);
            }
            else
            {
                scoreToShow = MathF.Round(EncodedEvalLogistic.LogisticToCentipawn((float)-bestMoveNode.Q), 0);
            }

            float nps = manager.NumStepsTakenThisSearch / elapsedTimeSeconds;

            //info depth 12 seldepth 27 time 30440 nodes 51100 score cp 105 hashfull 241 nps 1678 tbhits 0 pv e6c6 c5b4 d5e4 d1e1
            int selectiveDepth        = pv.Nodes.Count;
            int depthOfBestMoveInTree = wasInstamove ? thisRootNode.Depth : 0;
            int depth = 1 + (int)MathF.Round(manager.Context.AvgDepth - depthOfBestMoveInTree, 0);

            string pvString = multiPVIndex.HasValue ? $"multipv {multiPVIndex} pv {pv.ShortStr()}"
                                              : $"pv {pv.ShortStr()}";

            int n = thisRootNode.N;

            if (!useParentN && overrideBestMoveNodeAtRoot != null)
            {
                n = overrideBestMoveNodeAtRoot.N;
            }

            //score cp 27 wdl 384 326 290
            string strWDL = "";

            if (showWDL)
            {
                // Note that win and loss inverted to reverse perspective.
                strWDL = $" wdl {Math.Round(bestMoveNode.LossP * 1000)} "
                         + $"{Math.Round(bestMoveNode.DrawP * 1000)} "
                         + $"{Math.Round(bestMoveNode.WinP * 1000)}";
            }

            if (wasInstamove)
            {
                // Note that the correct tablebase hits cannot be easily calculated and reported
                return($"info depth {depth} seldepth {selectiveDepth} time 0 "
                       + $"nodes {n:F0} score cp {scoreToShow}{strWDL} tbhits {manager.CountTablebaseHits} nps 0 "
                       + $"{pvString} string M= {thisRootNode.MAvg:F0} ");
            }
            else
            {
                return($"info depth {depth} seldepth {selectiveDepth} time {elapsedTimeSeconds * 1000.0f:F0} "
                       + $"nodes {n:F0} score cp {scoreToShow}{strWDL} tbhits {manager.CountTablebaseHits} nps {nps:F0} "
                       + $"{pvString} string M= {thisRootNode.MAvg:F0}");
            }
        }