Exemplo n.º 1
0
 protected override LeafEvaluationResult DoTryEvaluate(MCTSNode node) => default;
Exemplo n.º 2
0
        /// <summary>
        /// Initializes a specified EncodedPosition to reflect the a specified node's position.
        /// </summary>
        /// <param name="node"></param>
        /// <param name="boardsHistory"></param>
        internal unsafe void CalcRawPosition(MCTSNode node, ref EncodedPositionWithHistory boardsHistory)
        {
            Span <EncodedPositionBoard> destBoards = new Span <EncodedPositionBoard>(Unsafe.AsPointer(ref boardsHistory), EncodedPositionBoards.NUM_MOVES_HISTORY);

            // Now we fill in the history boards, extracted from up to 4 distinct places:
            //   1. the current node's board (always)
            //   2. possibly sequence of boards by ascending tree toward root
            //   3. possibly sequence of boards coming from the prior history that
            //      this search was launched with (nodes before the root)
            //   4. possibly one or more fill-in boards, which can be
            //      either zero (if history fill-in feature turned off)
            //      otherwise the last actual board in the history repeated
            destBoards[0] = LC0BoardPosition;

            MCTSNode priorNode = node.Parent;

            // Ascend in tree copying positions from ancestors
            int nextBoardIndex = 1;

            while (nextBoardIndex < EncodedPositionBoards.NUM_MOVES_HISTORY && priorNode != null)
            {
                if (nextBoardIndex % 2 == 1)
                {
                    destBoards[nextBoardIndex++] = priorNode.Annotation.LC0BoardPosition.ReversedAndFlipped;
                }
                else
                {
                    destBoards[nextBoardIndex++] = priorNode.Annotation.LC0BoardPosition;
                }

                priorNode = priorNode.Parent;
            }

            // Boards from prior history
            int priorPositionsIndex = 0;

            while (nextBoardIndex < EncodedPositionBoards.NUM_MOVES_HISTORY && priorPositionsIndex < node.Tree.EncodedPriorPositions.Count)
            {
                if (nextBoardIndex % 2 == 1)
                {
                    destBoards[nextBoardIndex++] = node.Tree.EncodedPriorPositions[priorPositionsIndex].ReversedAndFlipped;
                }
                else
                {
                    destBoards[nextBoardIndex++] = node.Tree.EncodedPriorPositions[priorPositionsIndex];
                }

                priorPositionsIndex++;
            }

            // Finally, set last boards either with repeated last position (if fill in) or zeros
            int  indexBoardToRepeat = nextBoardIndex - 1;
            bool historyFillIn      = node.Context.ParamsSearch.HistoryFillIn;

            while (nextBoardIndex < EncodedPositionBoards.NUM_MOVES_HISTORY)
            {
                if (historyFillIn)
                {
                    destBoards[nextBoardIndex++] = destBoards[indexBoardToRepeat];
                }
                else
                {
                    destBoards[nextBoardIndex++] = default;
                }
            }

            boardsHistory.SetMiscInfo(new EncodedTrainingPositionMiscInfo(MiscInfo, default));
        }
Exemplo n.º 3
0
 internal void DoVisitLeafNode(MCTSNode node, int numVisits)
 {
     ref MCTSNodeStruct nodeRef = ref node.Ref;
Exemplo n.º 4
0
        static void DumpNodeStr(PositionWithHistory priorMoves, MCTSNode node, int depth, int countTimesSeen, bool fullDetail)
        {
            node.Context.Tree.Annotate(node);

            char extraChar = ' ';

            if (node.Terminal == GameResult.Checkmate)
            {
                extraChar = 'C';
            }
            else if (node.Terminal == GameResult.Draw)
            {
                extraChar = 'D';
            }
            else if (countTimesSeen > 1)
            {
                extraChar = countTimesSeen > 9 ? '9' : countTimesSeen.ToString()[0];
            }

            float multiplier = depth % 2 == 0 ? 1.0f : -1.0f;

            float pctOfVisits = node.IsRoot ? 100.0f : (100.0f * node.N / node.Parent.N);

            MCTSNode bestMove = null;

// TODO: someday show this too      MCTSNode nextBestMove = null;
            if (!node.IsRoot)
            {
                MCTSNode[] parentsChildrenSortedQ = node.ChildrenSorted(innerNode => - multiplier * (float)innerNode.Q);
                if (parentsChildrenSortedQ.Length > 0)
                {
                    bestMove = parentsChildrenSortedQ[0];
                }
//        if (parentsChildrenSortedQ.Length > 1) nextBestMove = parentsChildrenSortedQ[1];
            }

            // Depth, move
            Console.Write($"{depth,3}. ");
            Console.Write(extraChar);
            Console.Write($" {node.NumPolicyMoves,3} ");

            Console.Write($"{node.Index,13:N0}");

            string san = node.IsRoot ? "" : MGMoveConverter.ToMove(node.Annotation.PriorMoveMG).ToSAN(in node.Parent.Annotation.Pos);

//      string sanNextBest = node.IsRoot ? "" : MGMoveConverter.ToMove(nextBestMove.Annotation.PriorMoveMG).ToSAN(in node.Parent.Annotation.Pos);
            if (node.Annotation.Pos.MiscInfo.SideToMove == SideType.White)
            {
                Console.Write($"      ");
                Console.Write($"{san,6}");
            }
            else
            {
                Console.Write($"{san,6}");
                Console.Write($"      ");
            }

//      float diffBestNextBestQ = 0;
//      if (nextBestMove != null) diffBestNextBestQ = (float)(bestMove.Q - nextBestMove.Q);
//      Console.Write($"{  (nextBestMove?.Annotation == null ? "" : nextBestMove.Annotation.PriorMoveMG.ToString()),8}");
//      Console.Write($"{diffBestNextBestQ,8:F2}");


            Console.Write($"{node.N,13:N0} ");
            Console.Write($" {pctOfVisits,5:F0}%");
            Console.Write($"   {100.0 * node.P,6:F2}%  ");
            DumpWithColor(multiplier * node.V, $" {multiplier * node.V,6:F3}  ", -0.2f, 0.2f);
//      DumpWithColor(multiplier * node.VSecondary, $" {multiplier * node.VSecondary,6:F3} ", -0.2f, 0.2f);
            double q = multiplier * node.Q;

            DumpWithColor((float)q, $" {q,6:F3} ", -0.2f, 0.2f);

            //      float qStdDev = MathF.Sqrt(node.Ref.VVariance);
            //      if (float.IsNaN(qStdDev))
            //        Console.WriteLine("found negative var");
            //      Console.Write($" +/-{qStdDev,5:F2}  ");

            Console.Write($" {node.WinP,5:F2}/{node.DrawP,5:F2}/{node.LossP,5:F2}  ");

            Console.Write($" {node.WAvg,5:F2}/{node.DAvg,5:F2}/{node.LAvg,5:F2}  ");

            //      Console.Write($"   {node.Ref.QUpdatesWtdAvg,5:F2}  ");
            //      Console.Write($" +/-:{MathF.Sqrt(node.Ref.QUpdatesWtdVariance),5:F2}  ");
            //      Console.Write($" {node.Ref.TrendBonusToP,5:F2}  ");

            Console.Write($" {node.MPosition,3:F0} ");
            Console.Write($" {node.MAvg,3:F0}  ");

            if (fullDetail)
            {
                int numPieces = node.Annotation.Pos.PieceCount;

//        Console.Write($" {PosStr(node.Annotation.Pos)} ");
                Console.Write($" {node.Annotation.Pos.FEN}");
            }


            Console.WriteLine();
        }
Exemplo n.º 5
0
 int NInFlightThisSelector(MCTSNode node) => SelectorID == 0 ? node.NInFlight : node.NInFlight2;
Exemplo n.º 6
0
 /// <summary>
 /// Takes any actions necessary upon visit to an inner note.
 /// </summary>
 /// <param name="node"></param>
 void DoVisitInnerNode(MCTSNode node)
 {
     InsureAnnotated(node);
 }
Exemplo n.º 7
0
        private async Task MCTSRoutine()
        {
            System.Action <string> print = (s) => Logger.Log(s);
            MOARandom r = new MOARandom();

            IActor     playerOne = null;
            IGameState game      = null;

            switch (_testingGame)
            {
            case TestingGame.OX:
                playerOne = new OXActor(_startPlayerOne ? _playerTwo.Name : _playerOne.Name);
                game      = new OXGameState(_playerOne.Name, _playerTwo.Name, playerOne as OXActor);
                break;

            case TestingGame.C4:
                playerOne = new C4Actor(_startPlayerOne ? _playerTwo.Name : _playerOne.Name);
                game      = new C4GameState(_playerOne.Name, _playerTwo.Name, playerOne as C4Actor);
                break;

            case TestingGame.Pentagoesque:
                playerOne = new PentActor(_startPlayerOne ? _playerTwo.Name : _playerOne.Name);
                game      = new PentGameState(_playerOne.Name, _playerTwo.Name, playerOne as PentActor);
                break;

            default:
                break;
            }

            _playerOne.Config = new MCTSConfig
            {
                MaxIterations = (uint)_playerOne.Iterations,
                Verbose       = verbose,
                PrintFn       = print,
                UCTK          = uctExploration
            };
            switch (_playerOne.Behaviour)
            {
            case PlayerBehaviour.MCTS:
                _playerOne.TreeRoot = new MCTSNode(0, null, null, game.ActorJustActed, game);
                break;

            case PlayerBehaviour.UCT:
                _playerOne.TreeRoot = new UCTNode(0, null, null, game.ActorJustActed, game, _playerOne.Config.UCTK);
                break;
            }

            _playerTwo.Config = new MCTSConfig
            {
                MaxIterations = (uint)_playerTwo.Iterations,
                Verbose       = verbose,
                PrintFn       = print,
                UCTK          = uctExploration
            };
            switch (_playerTwo.Behaviour)
            {
            case PlayerBehaviour.MCTS:
                _playerTwo.TreeRoot = new MCTSNode(0, null, null, game.ActorJustActed, game);
                break;

            case PlayerBehaviour.UCT:
                _playerTwo.TreeRoot = new UCTNode(0, null, null, game.ActorJustActed, game, _playerTwo.Config.UCTK);
                break;
            }

            while (!game.IsTerminal() && Application.isPlaying)
            {
                IAction action = null;

                Player actingPlayer = null;
                Player otherPlayer  = null;

                if (game.ActorJustActed.Name == _playerOne.Name)
                {
                    actingPlayer = _playerTwo;
                    otherPlayer  = _playerOne;
                }
                else
                {
                    actingPlayer = _playerOne;
                    otherPlayer  = _playerTwo;
                }

                Logger.Log(GetType().Name, $"Player Acting: {actingPlayer.Name}, Behaviour: {actingPlayer.Behaviour}");

                MCTSNode node = null;
                switch (actingPlayer.Behaviour)
                {
                case PlayerBehaviour.Random:
                    action = game.GetRandomMove();
                    break;

                case PlayerBehaviour.MCTS:
                    node = await MCTS.ProcessTree(
                        actingPlayer.RetainTree?actingPlayer.TreeRoot : new MCTSNode(0, null, null, game.ActorJustActed, game),
                        game,
                        actingPlayer.Config
                        );

                    action = node.IncomingAction;
                    actingPlayer.TreeRoot = node.ConstructAsRoot();
                    break;

                case PlayerBehaviour.UCT:
                    node = await MCTS.ProcessTree(
                        actingPlayer.RetainTree?actingPlayer.TreeRoot : new UCTNode(0, null, null, game.ActorJustActed, game, actingPlayer.Config.UCTK),
                        game,
                        actingPlayer.Config
                        );

                    action = node.IncomingAction;
                    actingPlayer.TreeRoot = node.ConstructAsRoot();
                    break;

                default:
                    break;
                }

                if (action != null)
                {
                    game = action.DoAction();

                    Logger.Log(action.ASCIIRepresentation);

                    otherPlayer.TreeRoot = otherPlayer.TreeRoot?.GetChildWithState(game)?.ConstructAsRoot();
                }
                else
                {
                    Logger.Log("STALLING!!");
                    break;
                }

                Logger.Log(game.ASCIIRepresentation);

                if (waitTime != 0f)
                {
                    await Task.Delay((int)(waitTime * 1000));
                }
            }

            Logger.LogFormat("{0} {1}",
                             game.ActorJustActed.Name, game.GetResult(game.ActorJustActed));
        }
 /// <summary>
 /// Update the root node
 /// </summary>
 /// <param name="spot"></param>
 public override void UpdateLastMove(Spot spot)
 {
     root = root.NextRoot(spot);
 }
 public MonteCarloAI(GlobalGame game, int turn, Color color, Sprite sprite, string name, float time) : base(game, turn, color, sprite, name)
 {
     root      = new MCTSNode(game, null, null);
     this.time = Math.Max(float.Epsilon, time);
 }
Exemplo n.º 10
0
        /// <summary>
        /// Worker method that actually computes and updates the statistic.
        /// </summary>
        private void DoSetEarlyStopMoveSecondaryFlags()
        {
            if (Manager.Root.NumChildrenExpanded == 0)
            {
                return;
            }

            float aggressiveness = Context.ParamsSearch.MoveFutilityPruningAggressiveness;

            if (aggressiveness >= 1.5f)
            {
                throw new Exception("Ceres configuration error: maximum value of EarlyStopMoveSecondaryAggressiveness is 1.5.");
            }

            float MIN_BEST_N_FRAC_REQUIRED = ManagerChooseRootMove.MIN_FRAC_N_REQUIRED_MIN;

            // Calibrate aggressiveness such that :
            //   - at maximum value of 1.0 we assume 50% visits go to second best move(s)
            //   - at reasonable default value 0.5 we assume 75% of visits to go second best move(s)
            float aggressivenessMultiplier = 1.0f / (1.0f - aggressiveness * 0.5f);

            int?numRemainingSteps = Manager.EstimatedNumVisitsRemaining();

            // Can't make any determiniation if we can't estimate how many steps left
            if (numRemainingSteps is null)
            {
                return;
            }

            int numStepsTotal = (int)(numRemainingSteps / Manager.FractionSearchRemaining);

            MCTSNode[] nodesSortedN = Root.ChildrenSorted(n => - n.N);
            MCTSNode   bestNNode    = nodesSortedN[0];

            int minN = Root.N / 5;// (int)(bestQNode.N * MIN_BEST_N_FRAC_REQUIRED);

            MCTSNode[] nodesSortedQ = Root.ChildrenSorted(n => n.N < minN ? int.MaxValue : (float)n.Q);
            MCTSNode   bestQNode    = nodesSortedQ[0];

            float bestQ = (float)bestQNode.Q;

            ManagerChooseRootMove      bestMoveChoser = new(Context.Root, false, Context.ParamsSearch.MLHBonusFactor);
            Span <MCTSNodeStructChild> children       = Root.Ref.Children;

            for (int i = 0; i < Root.Ref.NumChildrenExpanded; i++)
            {
                // Never shut down second best move unless the whole search is eligible to shut down
                if (nodesSortedN.Length > 1)
                {
                    MCTSNode secondBestMove   = Context.ParamsSearch.BestMoveMode == ParamsSearch.BestMoveModeEnum.TopN ? nodesSortedN[1] : nodesSortedQ[1];
                    bool     isSecondBestMove = children[i].Move == secondBestMove.PriorMove;
                    if (isSecondBestMove &&
                        !Context.ParamsSearch.FutilityPruningStopSearchEnabled &&
                        Context.RootMovesPruningStatus[i] != MCTSFutilityPruningStatus.PrunedDueToSearchMoves)
                    {
                        Context.RootMovesPruningStatus[i] = MCTSFutilityPruningStatus.NotPruned;
                        continue;
                    }
                }

                float earlyStopGapRaw;
                if (Context.ParamsSearch.BestMoveMode == ParamsSearch.BestMoveModeEnum.TopN)
                {
                    earlyStopGapRaw = nodesSortedN[0].N - children[i].N;
                }
                else
                {
                    int minNRequired = (int)(bestQNode.N * MIN_BEST_N_FRAC_REQUIRED);
                    earlyStopGapRaw = minNRequired - children[i].N;
                }

                float earlyStopGapAdjusted = earlyStopGapRaw * aggressivenessMultiplier;
                bool  earlyStop            = earlyStopGapAdjusted > numRemainingSteps;


                if (Context.RootMovesPruningStatus[i] == MCTSFutilityPruningStatus.NotPruned && earlyStop)
                {
#if NOT_HELPFUL
                    // Never shutdown nodes getting large fraction of all visits
                    float fracVisitsThisMoveRunningAverage = Context.RootMoveTracker != null ? Context.RootMoveTracker.RunningFractionVisits[i] : 0;

                    const float THRESHOLD_VISIT_FRACTION_DO_NOT_SHUTDOWN_CHILD = 0.20f;
                    bool        shouldVeto = (fracVisitsThisMoveRunningAverage > THRESHOLD_VISIT_FRACTION_DO_NOT_SHUTDOWN_CHILD);

                    if (Context.ParamsSearch.TestFlag &&
                        nodesSortedN[0] != Context.Root.ChildAtIndex(i) &&
                        NumberOfNotShutdownChildren() > 1 &&
                        shouldVeto)
                    {
                        MCTSEventSource.TestCounter1++;
                        continue;
                    }
                    else
#endif
                    Context.RootMovesPruningStatus[i] = MCTSFutilityPruningStatus.PrunedDueToFutility;
                    if (MCTSDiagnostics.DumpSearchFutilityShutdown)
                    {
                        Console.WriteLine();
                        Console.WriteLine($"\r\nShutdown {children[i].Move} [{children[i].N}] at root N  {Context.Root.N} with remaning {numRemainingSteps}"
                                          + $" due to raw gapN {earlyStopGapRaw} adusted to {earlyStopGapAdjusted} in mode {Context.ParamsSearch.BestMoveMode} aggmult {aggressivenessMultiplier}");
                        DumpDiagnosticsMoveShutdown();
                    }
                }
                // Console.WriteLine(i + $" EarlyStopMoveSecondary(simple) gap={gapToBest} adjustedGap={inflatedGap} remaining={numRemainingSteps} ");
            }


            // TODO: log this
            //Console.WriteLine($"{Context.RemainingTime,5:F2}sec remains at N={Root.N}, setting  minN to {minN} number still considered {count} " +
            //                  $"using EstimatedNPS {Context.EstimatedNPS} with nodes remaining {Context.EstimatedNumStepsRemaining()} " +
            //                  statsStr);
        }
Exemplo n.º 11
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}");
            }
        }