Source code for chipiron.players.move_selector.treevalue.tree_manager.tree_manager

"""
This module contains the TreeManager class, which is responsible for managing a tree by opening new nodes and updating the values and indexes on the nodes.
"""

import typing
from typing import Any

import chipiron.environments.chess_env.board as board_mod
import chipiron.players.move_selector.treevalue.nodes as node
import chipiron.players.move_selector.treevalue.trees as trees
from chipiron.environments.chess_env.move.imove import moveKey
from chipiron.players.move_selector.treevalue.node_factory.node_factory import (
    TreeNodeFactory,
)
from chipiron.players.move_selector.treevalue.node_selector.opening_instructions import (
    OpeningInstructions,
)
from chipiron.players.move_selector.treevalue.tree_manager.tree_expander import (
    TreeExpansion,
    TreeExpansions,
)

# todo should we use a discount? and discounted per round reward?
# todo maybe convenient to seperate this object into openner updater and dsiplayer
# todo have the reward with a discount
# DISCOUNT = 1/.99999
if typing.TYPE_CHECKING:
    import chipiron.players.move_selector.treevalue.node_selector as node_sel


[docs]class TreeManager: """ This class manages a tree by opening new nodes and updating the values and indexes on the nodes. """ node_factory: TreeNodeFactory[node.ITreeNode[Any]] def __init__(self, node_factory: TreeNodeFactory[node.ITreeNode[Any]]) -> None: self.node_factory = node_factory
[docs] def open_node_move( self, tree: trees.MoveAndValueTree, parent_node: node.ITreeNode[Any], move: moveKey, ) -> TreeExpansion: """ Opening a Node that contains a board following a move. Args: tree: The tree object. parent_node: The parent node that we want to expand. move: The move to play to expand the node. Returns: The tree expansion object. """ # The parent board is copied, we only copy the stack (history of previous board) if the depth is smaller than 2 # Having the stack information allows checking for draw by repetition. # To limit computation we limit copying it all the time. The resulting policy will only be aware of immediate # risk of draw by repetition copy_stack: bool = tree.node_depth(parent_node) < 2 board: board_mod.IBoard = parent_node.board.copy( stack=copy_stack, deep_copy_legal_moves=False, # trick to win time (the original legal moves is assume to not be changed as # moves are not supposed to be played anymore on that board and therefore this allows copy by reference ) # The move is played. The board is now a new board modifications: board_mod.BoardModificationP | None = board.play_move_key( move=move ) return self.open_node( tree=tree, parent_node=parent_node, board=board, modifications=modifications, move=move, )
[docs] def open_node( self, tree: trees.MoveAndValueTree, parent_node: node.ITreeNode[Any], board: board_mod.IBoard, modifications: board_mod.BoardModificationP | None, move: moveKey, ) -> TreeExpansion: """ Opening a Node that contains a board given the modifications. Checks if the new node needs to be created or if the new_board already existed in the tree (was reached from a different serie of move) Args: tree: The tree object. parent_node: The parent node that we want to expand. board: The board object that is a move forward compared to the board in the parent node modifications: The board modifications. move: The move to play to expand the node. Returns: The tree expansion object. """ # Creation of the child node. If the board already exited in another node, that node is returned as child_node. half_move: int = parent_node.half_move + 1 fast_rep: board_mod.boardKey = board.fast_representation child_node: node.ITreeNode[Any] need_creation_child_node: bool = ( tree.descendants.is_new_generation(half_move) or fast_rep not in tree.descendants.descendants_at_half_move[half_move] ) if need_creation_child_node: child_node = self.node_factory.create( board=board, half_move=half_move, count=tree.nodes_count, move_from_parent=move, parent_node=parent_node, modifications=modifications, ) tree.nodes_count += 1 tree.descendants.add_descendant( child_node ) # add it to the list of descendants else: # the node already exists child_node = tree.descendants[half_move][fast_rep] child_node.add_parent(move=move, new_parent_node=parent_node) tree_expansion: TreeExpansion = TreeExpansion( child_node=child_node, parent_node=parent_node, board_modifications=modifications, creation_child_node=need_creation_child_node, move=move, ) # add it to the list of opened move and out of the non-opened moves parent_node.moves_children[move] = tree_expansion.child_node # parent_node.tree_node.non_opened_legal_moves.remove(move) tree.move_count += 1 # counting moves return tree_expansion
[docs] def open_instructions( self, tree: trees.MoveAndValueTree, opening_instructions: OpeningInstructions ) -> TreeExpansions: """ Opening multiple nodes based on the opening instructions. Args: tree: The tree object. opening_instructions: The opening instructions. Returns: The tree expansions that have been performed. """ # place to store the tree expansion logs generated by the openings tree_expansions: TreeExpansions = TreeExpansions() opening_instruction: node_sel.OpeningInstruction for opening_instruction in opening_instructions.values(): # open tree_expansion: TreeExpansion = self.open_node_move( tree=tree, parent_node=opening_instruction.node_to_open, move=opening_instruction.move_to_play, ) # concatenate the tree expansions tree_expansions.add(tree_expansion=tree_expansion) return tree_expansions
[docs] def print_some_stats( self, tree: trees.MoveAndValueTree, ) -> None: """ Print some statistics about the tree. Args: tree: The tree object. """ print( "Tree stats: move_count", tree.move_count, " node_count", tree.descendants.get_count(), ) sum_ = 0 tree.descendants.print_stats() for half_move in tree.descendants: sum_ += len(tree.descendants[half_move]) print("half_move", half_move, len(tree.descendants[half_move]), sum_)
[docs] def test_count( self, tree: trees.MoveAndValueTree, ) -> None: """ Test the count of nodes in the tree. Args: tree: The tree object. """ assert tree.descendants.get_count() == tree.nodes_count
[docs] def print_best_line( self, tree: trees.MoveAndValueTree, ) -> None: """ Print the best line in the tree. Args: tree: The tree object. """ raise Exception("should not be called no? Think about modifying...")