Exemplo n.º 1
0
    def get_best_meld_combins_from_arr(cls, arr: list) -> list:
        sorted_arr = arr[:]
        sorted_arr.sort()
        tree = Tree()
        identifier = 'root'
        tree.create_node([], identifier)
        MjMath.get_melds_tree_from_sorted_arr(sorted_arr, tree, identifier)
        paths = tree.paths_to_leaves()
        _combinations = []
        for path in paths:
            _combin = []
            for identifier in path:
                meld = tree.get_node(identifier).tag
                _combin.append(meld)
            _combin.sort()
            if _combin not in _combinations:
                _combinations.append(_combin)

        len_arr = [len(x) for x in _combinations]
        max_len = max(len_arr)
        best_combinations = []
        for _combin in _combinations:
            if len(_combin) == max_len:
                best_combinations.append(_combin)

        return best_combinations
Exemplo n.º 2
0
def find_deepest_child(data):
    tree = Tree()
    root = tree.create_node("root", "root")
    tree = build_tree(data=data, tree=tree, parent=root)
    res = tree.paths_to_leaves()
    depth = tree.depth()
    for path in res:
        if len(path) == depth + 1:
            return path[-1]
            break
Exemplo n.º 3
0
def _prune(tree: treelib.Tree, retain: Set[str]) -> treelib.Tree:
    res = treelib.Tree()
    res.create_node(tree[tree.root].tag, tree.root, None, tree[tree.root].data)
    for path in tree.paths_to_leaves():
        if path[-1] in retain:
            for i, node in enumerate(path[1:]):
                if node not in res:
                    curr = tree[node]
                    res.create_node(curr.tag, node, res[path[i]], curr.data)

    return res
Exemplo n.º 4
0
    def spaces(self, output="dictionary"):
        spaces = self.get_all_spaces()
        page_labels = self.get_page_labels()
        spaces_tree = Tree()

        #Add PDF name as top node
        spaces_tree.create_node(self.file_name, self.file_name)

        #Add pages nodes
        for key, value in page_labels.items():
            spaces_tree.create_node(value, value, parent=self.file_name)
            spaces_tree = self.spacestree(spaces[key], spaces_tree, value)

        if output == "tree":
            return (spaces_tree)
        elif output == "hierarchy":
            #Getting a master space hierarchy by column
            #Max depth starts calculating outside the doc name and sheet name
            max_depth = max([len(i)
                             for i in spaces_tree.paths_to_leaves()]) - 2
            space_depth = []
            for level in range(max_depth):
                space_depth.append("Space " + str(level + 1))

            #Create the dictionary to hold the values
            space_dict = {}
            for idx, level in enumerate(space_depth):
                space_dict[level] = []

            #Loop through all space items
            for item in spaces_tree.paths_to_leaves():
                for level, space in enumerate(item[2:]):
                    res = list(space_dict.keys())[level]
                    if str(space)[7:-6] not in space_dict[res]:
                        space_dict[res].append(str(space)[7:-6])
            return (space_dict)
        else:
            # Return the Python dictionary
            spaces_dict = {'spaces': []}
            spaces_dict['spaces'].append(spaces_tree.to_dict())
            return (spaces_dict)
Exemplo n.º 5
0
def pack():
    root = tk.Tk()
    root.withdraw()

    dialogPath = filedialog.askdirectory()
    if not dialogPath.strip():
        print('您已取消打包')
        return 'cancleDialog'

    packPath = os.path.join(dialogPath, 'package')
    zipPath = os.path.join(dialogPath, 'zip')
    if os.path.exists(packPath):
        shutil.rmtree(packPath)
        os.makedirs(packPath)
    if os.path.exists(zipPath):
        shutil.rmtree(zipPath)
    # print(packPath)
    print('打包中...')

    leafPathList = Tree.paths_to_leaves(addFileTree)

    for pathList in leafPathList:
        curLeafPath = filePath
        targetLeafPath = packPath
        for path in pathList:
            if path != 'id':
                pathName = addFileTree.get_node(path).tag[:-(len(path) + 2)]
                curLeafPath = os.path.join(curLeafPath, pathName)
                targetLeafPath = os.path.join(targetLeafPath, pathName)

        # print(curLeafPath)
        # print(targetLeafPath)
        targetLeafPath = targetLeafPath.replace('/', '\\')
        if curLeafPath.find('.') == -1:
            shutil.copytree(curLeafPath, targetLeafPath)
        else:
            targetLeafParent = os.path.split(targetLeafPath)[0]
            if os.path.exists(targetLeafParent):
                shutil.copy(curLeafPath, targetLeafPath)
            else:
                os.makedirs(targetLeafParent)
                shutil.copy(curLeafPath, targetLeafPath)

    print('\n打包完成,开始压缩文件...')

    os.makedirs(zipPath)
    outZipPath = os.path.join(zipPath, 'dist.zip')
    zipDir(packPath, outZipPath)

    print('\n打包完成,模块输出路径:' + zipPath + '\dist.zip')
    return 'success'
def getAllSubPathOfTree(file_name):
    global index_node
    index_node = 0
    parent_node = ""
    tree = Tree()
    root = ET.parse(file_name).getroot()
    walkData(root, parent_node, tree)
    treePaths_id_list = tree.paths_to_leaves()  #树的数据路径
    treePaths_list = []
    for paths in treePaths_id_list :
        path_list = []
        for i in range(1, paths.__len__()):
            path = paths[i]
            path = removeLastIntegerNumber(path)
            if path.endswith('true', path.__len__() - 4, path.__len__()):
                path_list.append(path)
        treePaths_list.append(path_list)
    return treePaths_list
def getAllSubPathOfTree(file_name):
    global index_node
    index_node = 0
    parent_node = ""
    tree = Tree()
    root = ET.parse(file_name).getroot()
    walkData(root, parent_node, tree)
    treePaths_id_list = tree.paths_to_leaves()  #映射成为树的节点的ID形成的路径,和树的数据的路径还不大一样
    treePaths_list = []
    for paths in treePaths_id_list:
        path_list = []
        for path in paths:
            path_list.append(removeLastIntegerNumber(path))
        treePaths_list.append(path_list)
    ## 这里要处理include的属性
    filePath = os.path.dirname(os.path.realpath(file_name))  #获取当前文件所在文件夹
    resTreePaths_list = addIncludeXMlTree(treePaths_list, filePath)
    # print(treePaths_list)
    return resTreePaths_list
Exemplo n.º 8
0
class GradientBoostingTree(object):
    def __init__(self,
                 X,
                 y,
                 k=4,
                 epochs=200,
                 loss='mse',
                 metrics=['mae'],
                 learning_rate=0.001,
                 layers=3,
                 ending_units=256,
                 optimizers=[Adam, Nadam, RMSprop],
                 early_stop=None,
                 seed=None):
        self.X = X
        self.y = y
        self.k = k
        self.epochs = epochs
        self.loss = loss
        self.metrics = metrics
        self.learning_rate = learning_rate
        self.layers = layers
        self.ending_units = ending_units
        self.optimizers = optimizers
        self.early_stop = early_stop
        self.generator = secrets.SystemRandom(seed)
        self.models = []
        self.tree = Tree()

    def fit(self):
        y = self.y
        preds = np.zeros(len(y))

        def fit_leaf(preds, y, i):
            optimizer = self.optimizers[self.generator.randint(
                0,
                len(self.optimizers) - 1)](learning_rate=self.learning_rate)
            model = network_builder(layers=self.layers,
                                    ending_units=self.ending_units)
            model.compile(loss=self.loss,
                          optimizer=optimizer,
                          metrics=self.metrics)
            model.fit(self.X,
                      y,
                      epochs=self.epochs * (i + 1),
                      callbacks=[self.early_stop] if self.early_stop else [])
            new_preds = preds + model.predict(self.X).reshape(-1)
            new_y = self.y - new_preds

            return new_preds, new_y, model

        def fit_tree(k, preds, y, i, parent=None):
            if k > 0:
                l_preds, l_y, l_model = fit_leaf(preds, y, i)
                self.tree.add_node(Node(identifier=parent + 'l' + str(k),
                                        data=l_model),
                                   parent=parent)
                fit_tree(k - 1, l_preds, l_y, i + 1, parent + 'l' + str(k))

                r_preds, r_y, r_model = fit_leaf(preds, y, i)
                self.tree.add_node(Node(identifier=parent + 'r' + str(k),
                                        data=r_model),
                                   parent=parent)
                fit_tree(k - 1, r_preds, r_y, i + 1, parent + 'r' + str(k))

        i = 0
        preds, y, root_model = fit_leaf(preds, y, i)
        self.tree.add_node(Node(identifier='root', data=root_model))
        fit_tree(self.k, preds, y, i + 1, parent='root')

    def predict(self, X):
        preds = []
        leafs = self.tree.leaves('root')
        for path in self.tree.paths_to_leaves():
            p = .0
            for id in path:
                p += self.tree.get_node(id).data.predict(X)
            preds.append(p)

        return sum(preds) / len(leafs)
Exemplo n.º 9
0
class TreeT(object):
    def __init__(self, max_id=0):
        self.tree = Tree()

    def from_ptb_to_tree(self, line, max_id=0, leaf_id=1, parent_id=None):
        # starts by ['(', 'pos']
        pos_tag = line[1]
        if parent_id is None:
            pos_id = 0
        else:
            pos_id = max_id
            max_id += 1

        self.tree.create_node(pos_tag, pos_id, parent_id, TreeData())

        parent_id = pos_id
        total_offset = 2

        if line[2] != '(':
            # sub-tree is leaf
            # line[0:3] = ['(', 'pos', 'word', ')']
            word_tag = line[2]
            self.tree.create_node(word_tag, leaf_id, parent_id, TreeData())
            return 4, max_id, leaf_id + 1

        line = line[2:]

        while line[0] != ')':
            offset, max_id, leaf_id = self.from_ptb_to_tree(
                line, max_id, leaf_id, parent_id)
            total_offset += offset
            line = line[offset:]

        return total_offset + 1, max_id, leaf_id

    def add_height(self, tree_dep):

        for n in self.tree.all_nodes():
            n.data.leaves = []

        for leaf in self.tree.leaves():
            lid = leaf.identifier
            hid = tree_dep[lid]
            if hid == self.tree.root:
                self.tree[lid].data.height = self.tree.depth(self.tree[lid])
                for cid in [
                        p for p in self.tree.paths_to_leaves() if lid in p
                ][0]:
                    self.tree[cid].data.leaves += [lid]
            else:
                height = -1
                cid = lid
                cond = True
                while cond:
                    self.tree[cid].data.leaves += [lid]
                    height += 1
                    cid = self.tree.parent(cid).identifier
                    cid_leaves = [l.identifier for l in self.tree.leaves(cid)]
                    cid_l_dep = [tree_dep[l] for l in cid_leaves if l != lid]
                    cond = set(cid_l_dep).issubset(set(cid_leaves))
                self.tree[lid].data.height = height

        x_nodes = [
            n.identifier for n in self.tree.all_nodes() if n.data.leaves == []
        ]
        for x_node in x_nodes[::-1]:
            min_id = min(self.tree.children(x_node),
                         key=lambda c: c.data.height)
            _lid = min_id.data.leaves[0]
            self.tree[_lid].data.height += 1
            self.tree[x_node].data.leaves += [_lid]

        return True

    def _from_tree_to_ptb(self, nid):
        nid = self.tree.subtree(nid).root
        if self.tree[nid].is_leaf():
            return ' (' + self.tree[nid].tag + ' ' + self.tree[
                nid].data.word + ')'

        res = ' (' + self.tree[nid].tag

        for c_nid in sorted(self.tree.children(nid),
                            key=lambda x: x.identifier):
            res += self._from_tree_to_ptb(c_nid.identifier)

        return res + ')'

    def from_tree_to_ptb(self):
        return self._from_tree_to_ptb(self.tree.root)

    def from_tag_to_tree(self, tag, word, pos_id=0):
        parent_id = None
        for tag_nodes in tag:
            if tag_nodes[0] in [CL, CR]:
                c_side = tag_nodes[0]
                _tag_nodes = tag_nodes[1:] if len(tag_nodes) > 1 else ['']
            else:
                c_side = ''
                _tag_nodes = tag_nodes
            self.tree.create_node(_tag_nodes[0],
                                  pos_id,
                                  parent=parent_id,
                                  data=TreeData(comb_side=c_side))

            parent_id = pos_id
            pos_id += 1
            for tag_node in _tag_nodes[1:]:
                self.tree.create_node(tag_node[1:],
                                      pos_id,
                                      parent=parent_id,
                                      data=TreeData(miss_side=tag_node[0]))
                pos_id += 1
        for l in self.tree.leaves():
            if l.data.miss_side == '':
                l.data.word = word
                break
        return pos_id

    @memoize
    def is_combine_to(self, side):
        return self.tree[self.tree.root].data.comb_side == side

    @memoize
    def is_combine_right(self):
        return self.is_combine_to(CR)

    @memoize
    def is_combine_left(self):
        return self.is_combine_to(CL)

    @memoize
    def is_complete_tree(self):
        return all([n.data.miss_side == '' for n in self.tree.all_nodes()])

    @memoize
    def get_missing_leaves_to(self, miss_val, side):
        return [
            l.identifier for l in self.tree.leaves(self.tree.root)
            if l.data.miss_side == side and l.tag == miss_val
        ]

    @memoize
    def get_missing_leaves_left(self, miss_val):
        return self.get_missing_leaves_to(miss_val, L)

    @memoize
    def get_missing_leaves_right(self, miss_val):
        return self.get_missing_leaves_to(miss_val, R)

    @memoize
    def root_tag(self):
        return self.tree[self.tree.root].tag

    @memoize
    def is_no_missing_leaves(self):
        return all(
            [l.data.miss_side == '' for l in self.tree.leaves(self.tree.root)])

    @memoize
    def combine_tree(self, _tree, comb_leaf):
        self.tree.paste(comb_leaf, _tree.tree)
        self.tree.link_past_node(comb_leaf)
        return self

    def tree_to_path(self, nid, path):

        # Stop condition
        if self.tree[nid].is_leaf():
            path[nid] = []
            return nid, self.tree[nid].data.height

        # Recursion
        flag = CR
        for child in self.tree.children(nid):
            cid = child.identifier
            leaf_id, height = self.tree_to_path(cid, path)

            if (height == 0):
                # Reached end of path can add flag
                path[leaf_id].insert(0, flag)
                # path[leaf_id].append(flag)

            if height > 0:
                path[leaf_id].insert(0, nid)
                # only single child will have height>0
                # and its value will be the one that is returned
                # to the parent
                ret_leaf_id, ret_height = leaf_id, height - 1

                # once we reached a height>0, it means that
                # this path includes the parent, and thus flag
                # direction should flip
                flag = CL

        return ret_leaf_id, ret_height

    def path_to_tags(self, path):
        tags = []
        for p in path:
            _res = []
            _p = copy.copy(p)
            if _p[0] in [CL, CR]:
                _res.append(_p[0])
                _p = _p[1:]
            while _p[:-1]:
                el_p = _p.pop(0)
                _res.append(self.tree[el_p].tag)
                for c in self.tree.children(el_p):
                    if c.identifier != _p[0]:
                        _res.append(R + c.tag if c.identifier > _p[0] else L +
                                    c.tag)
            _res.append(self.tree[_p[0]].tag)
            tags.append(_res)
        return tags

    def path_to_words(self, path):
        return [self.tree[k].tag for k in path]

    def from_tree_to_tag(self):
        path = {}
        self.tree_to_path(self.tree.root, path)
        return {
            'tags': self.path_to_tags(path.values()),
            'words': self.path_to_words(path.keys())
        }

    def from_ptb_to_tag(self, line, max_id, depend):
        self.from_ptb_to_tree(line, max_id)
        self.add_height(depend)
        path = {}
        self.tree_to_path(self.tree.root, path)
        return self.path_to_tags(path.values())
from Helper.Source import connect_to_db
Conn_Odin = connect_to_db()

# Submission of interest
AllSubmissions = pd.read_sql_query("select * from Submission_Info", Conn_Odin)
AllSubmissions[["ID_Submission", "Title"]].tail(60)
SubmissionOfInterest = "kboh0h"

Temp_Query = "select CI.ID_Comment, CI.ID_ParentID, CI.created_utc from Comment_Information CI " +\
             "where ID_Submission= '{}'".format(SubmissionOfInterest)

AllComments = pd.read_sql_query(Temp_Query, Conn_Odin)
AllComments2 = AllComments.copy()
AllComments2["created_utc"] = pd.to_datetime(AllComments2.created_utc)
AllComments2 = AllComments2.sort_values("created_utc").reset_index(drop=True)

tree1 = Tree()
tree1.create_node(identifier=SubmissionOfInterest)  # root node
for CommentIndex in range(len(AllComments2)):
    # CommentIndex=0
    ChildID = AllComments2.iloc[CommentIndex]["ID_Comment"]
    ParentID = AllComments2.iloc[CommentIndex]["ID_ParentID"]

    try:
        tree1.create_node(identifier=ChildID, parent=ParentID)
    except:
        print("An exception occurred")

tree1.show()
tree1.paths_to_leaves()
len(tree1.paths_to_leaves())
Exemplo n.º 11
0
unique_ids = set(nodeids)
tree.create_node(tag=rootid,identifier=rootid)


while len(unique_ids) != len(tree.all_nodes()):
    for rightNode in list(parent_check.keys()):
        if tree.get_node(parent_check[rightNode]) is not None and tree.get_node(rightNode) is None:
            tree.create_node(tag=rightNode, identifier=rightNode,
                             parent=tree.get_node(parent_check[rightNode]))

for node in tree.all_nodes():
    sum += tree.depth(node)
print(sum)


def intersection(lst1, lst2):
    lst3 = [value for value in lst1 if value in lst2]
    return lst3

sanIndex = 0
youInex = 0

for path in tree.paths_to_leaves():
    if path[len(path) -1] == "SAN":
        sanIndex = path[path.index('7LD'):]
    elif path[len(path) - 1] == "YOU":
        youIndex = path[path.index('7LD'):]
print (len(sanIndex) + len(youIndex) - 4)

Exemplo n.º 12
0
def find_path(startpose, endpose, path_step):

    #tree DST used to store path solutions.
    #each path is uniquely denoted by its leaf pointer.
    soln_tree = Tree()
    root = soln_tree.create_node(data=startpose)  # root :)

    def path_from_leaf(node):
        path = [node.data]
        while not node.is_root():
            predecessor = node.predecessor(soln_tree.identifier)
            path.append(soln_tree.get_node(predecessor).data)
            node = soln_tree.get_node(predecessor)
        path.reverse()
        return path

    frontier = [root]
    next_frontier = []

    pass_ct = 0

    #let rocket start upside down, but remove cases where it's
    #upside down once it's righted itself
    righted = False

    while frontier:
        pose_node = frontier.pop()
        current_pose = pose_node.data

        def create_node(data):
            return soln_tree.create_node(data=data, parent=pose_node)

        if random.random() < (1 / 1000):
            path = path_from_leaf(pose_node)
            x = [s.x for s in path]
            y = [s.y for s in path]
            plt.plot(x, y, linewidth=2, color='blue')
            plt.savefig(f"plot{random.random()}.png")
            plt.close()

        #bruteforce with large set of P, G
        rs = RocketSimulation(current_pose.x, current_pose.y, current_pose.dx,
                              current_pose.dy, current_pose.theta,
                              current_pose.dtheta)
        next_states = [
            rs.clone().update_n(p, g, path_step)
            for p in np.arange(0, 1, 0.05) for g in np.arange(-0.2, 0.2, 0.05)
        ]

        def normalize_angle(angle):
            angle = angle % (2 * math.pi)
            angle = (angle + (2 * math.pi)) % (2 * math.pi)
            if angle > math.pi:
                angle -= (2 * math.pi)
            return angle

        def collision_filter(pose):
            L = 40
            W = 2
            s = math.sin(pose.theta)
            c = math.cos(pose.theta)

            outer_points = [
                Vector2(0, L / 2),
                Vector2(1.8 * W, -(L / 2) - W),
                Vector2(-1.8 * W, -(L / 2) - W)
            ]

            for p in outer_points:
                if ((p.x * s) + (p.y * c) + pose.y) < 0:
                    return False
            return True

        def orientation_filter(pose):
            if abs(normalize_angle(pose.theta)) <= math.pi / 4:
                return True
            else:
                return not righted

        if not righted:
            for state in next_states:
                if abs(normalize_angle(state.theta)) <= math.pi / 4:
                    righted = True

        #filter states
        filters = [
            lambda pose: pose.y > 0,
            #lambda pose: pose_dist(startpose, endpose) > pose_dist(pose, endpose), #closer
            #lambda pose: abs(normalize_angle(pose.theta)) <= math.pi / 4, #upside-down bad
            collision_filter,
            orientation_filter
        ]

        next_states = list(
            filter(lambda x: all(f(x) for f in filters), next_states))

        correct_solns = list(
            filter(lambda pose: pose_eq(pose, endpose), next_states))

        if (correct_solns):
            print("Correct solutions found!!!")
            return [
                path_from_leaf(create_node(soln)) for soln in correct_solns
            ]

        next_states = sorted(next_states,
                             key=lambda x: pose_dist(x, endpose),
                             reverse=False)  #try promising cases first

        if random.random() <= 1 / 500:
            print(
                f"Produced {len(next_states)} states during pass (t={pass_ct * path_step * 0.02})"
            )
            if len(next_states) != 0:
                print(
                    f"Closest distance: {pose_dist(next_states[0], endpose)}")

        if len(next_states) != 0:
            #always choose the best soln, also choose 2 random members
            next_frontier.append(create_node(next_states.pop()))
            #next_frontier.extend(
            #        [create_node(x) for x in random.sample(next_states[:30], 3)]
            #)
            next_frontier.extend([
                create_node(x)
                for x in random.sample(next_states, min(len(next_states), 2))
            ])

        if not frontier:
            if not next_frontier:
                print("No next frontier! Exiting!")

            frontier = next_frontier
            next_frontier = []
            print(
                f"Round {pass_ct} completed. Produced {len(frontier)} states.")
            pass_ct += 1

            #generate summary plots
            if (pass_ct != 0) and (pass_ct % 15 == 0):
                all_paths = filter(lambda x: len(x) == pass_ct + 1,
                                   soln_tree.paths_to_leaves())
                for identifier_list in all_paths:
                    node_list = [
                        soln_tree.get_node(id) for id in identifier_list
                    ]
                    pose_list = [pose_node.data for pose_node in node_list]
                    x = [s.x for s in pose_list]
                    y = [s.y for s in pose_list]
                    plt.plot(x, y, linewidth=2, color='blue')

                plt.savefig(f"summary_plt{random.random()}.png")
                plt.close()

            #perform larger culling step in case of overfull frontier
            if len(frontier) > 4000:
                #Bucketization
                buckets = defaultdict(list)
                #bucket sizes
                bx, by, btheta = 2.5, 2.5, 0.5
                for f in frontier:
                    buckets[hash(
                        (math.floor(f.data.x / bx), math.floor(f.data.y / by),
                         math.floor(f.data.theta / btheta)))].append(f)

                #pick representatives from buckets
                new_frontier = []
                for bucket, nodes in buckets.items():
                    #pick ideal and random candidate
                    sorted_poses = sorted(nodes,
                                          key=lambda node: weigthed_pose_dist(
                                              node.data, endpose))
                    new_frontier.append(sorted_poses[0])

                    if len(sorted_poses) > 1:
                        #make sure same candidate isnt chosen twice
                        sorted_poses.pop(0)
                        new_frontier.extend(
                            random.sample(sorted_poses,
                                          min(len(sorted_poses), 2)))

                        #re-add any exceptional cases
                        #for node in sorted_poses:
                        #    if node.data.y < 85:
                        #        new_frontier.append(node)

                print(
                    f"Bucketization: removed {len(frontier) - len(new_frontier)}/{len(frontier)} elements"
                )
                frontier = new_frontier

    print(f"frontier: {frontier}\nnext_frontier: {next_frontier}")
    return []
Exemplo n.º 13
0
Arquivo: hfe.py Projeto: greermj/Lokki
class HFE(FeatureTransformChoice):
    def __init__(self, dataset_shape, taxonomy=None):
        self.dataset_shape = dataset_shape
        self.taxonomy = taxonomy

    def fit(self, hyperparameters, X, y):

        # Initialize new tree
        self.tree = Tree()

        if isinstance(self.taxonomy, type(None)):
            print(
                'Warning: Could not execute HFE algorithm no taxonomy provided'
            )
        else:

            # Add otus to the internal tree structure
            for i, otu in enumerate(
                [a for a in X.columns.values if a.lower().startswith('otu')]):
                self._add_otu_to_tree(self.tree, otu, X.copy())

            # Perform filtering then determine which nodes in the tree will act as the final features
            self._correlation_filter()
            self._path_ig_filter(y)
            self._leaf_ig_filter(y)
            self.valid_ids = [
                x.identifier for x in self.tree.all_nodes() if x.data['valid']
            ]

    def transform(self, X, y=None):

        # Build a new tree (Note: The self.tree is built on training data, so we need a new tree for unseen data)
        new_tree = Tree()
        for i, otu in enumerate(
            [a for a in X.columns.values if a.lower().startswith('otu')]):
            self._add_otu_to_tree(new_tree, otu, X.copy())

        # Extract the final features and store in dataframe
        result = pd.DataFrame()

        for i, current_id in enumerate(self.valid_ids):
            result[i] = new_tree[current_id].data['feature_vector']

        return result

    def get_name(self):
        return 'HFE'

    def hyperparameter_grid(self):
        return None

    # Description: Populates tree structure
    def _add_otu_to_tree(self, tree, otu_name, otu_table):

        raw_string = self.taxonomy['Taxonomy'][otu_name.lower() == np.array(
            [x.lower() for x in self.taxonomy['OTU']])].values[0]
        taxonomic_string = re.sub(r'[()0-9]', '', raw_string)
        taxonomic_levels = taxonomic_string.split(';')

        feature_vector = otu_table.loc[:, otu_name].values.copy()

        for i, level in enumerate(taxonomic_levels):

            # Some of the levels might be empty if there an extra ; in the taxonomy file or two back to back. These should be skipped
            if level == '':
                continue

            if tree.contains(level):
                # Increment node OTU vector with argument OTU vector (ie node vector + new otu vector)
                tree[level].data['feature_vector'] += feature_vector.copy()

            else:

                # Create new node with dictionary of OTU vector and boolean flag indicating valid node
                tree.create_node(
                    tag=level,
                    identifier=level,
                    parent=taxonomic_levels[i - 1] if i != 0 else None,
                    data={
                        'feature_vector': feature_vector.copy(),
                        'valid': True
                    })

    # Description: Removes all child nodes whose feature vector is correlated with their parent node by switching the valid flag for that node to False
    def _correlation_filter(self, threshold=0.80):

        paths = self.tree.paths_to_leaves()

        for path in paths:

            if len(path) < 1:
                continue

            for i in range(1, len(path)):
                parent_feature_vector = self.tree[path[
                    -1 - i + 1]].data['feature_vector']
                child_feature_vector = self.tree[path[
                    -1 - i]].data['feature_vector']
                current_correlation = pearsonr(parent_feature_vector,
                                               child_feature_vector)[0]

                if current_correlation > threshold:
                    self.tree[path[-1 - i]].data['valid'] = False

    # Description: Filters nodes based on average path information gain (IG)
    def _path_ig_filter(self, labels):

        paths = self.tree.paths_to_leaves()

        for path in paths:

            added = 0
            avg_path_IG = None

            for i, bacteria in enumerate(path):

                # For thoses nodes that passed the correlation filter compute the running IG average
                if self.tree[bacteria].data['valid']:
                    if added == 0:
                        avg_path_IG = mutual_info_classif(
                            X=self.tree[bacteria].data['feature_vector'].
                            reshape(len(labels), 1),
                            y=labels,
                            random_state=0)
                        added += 1
                    else:
                        avg_path_IG = avg_path_IG * (added / (added + 1)) + (
                            mutual_info_classif(X=self.tree[bacteria].data[
                                'feature_vector'].reshape(len(labels), 1),
                                                y=labels,
                                                random_state=0) / (added + 1))
                        added += 1

            # If a node in the path is less than the average or it is uninformative (ie all zeros) remove the node
            for bacteria in path:

                current_bacteria_IG = mutual_info_classif(
                    X=self.tree[bacteria].data['feature_vector'].reshape(
                        len(labels), 1),
                    y=labels,
                    random_state=0)

                if (avg_path_IG
                        == None) or (current_bacteria_IG < avg_path_IG) or sum(
                            self.tree[bacteria].data['feature_vector']) == 0:
                    self.tree[bacteria].data['valid'] = False

    # Description: Filter leaf nodes in incomplete paths based on global information gain (IG)
    def _leaf_ig_filter(self, labels):

        # Returns whether a path is incomplete
        def incomplete_path(path):
            incomplete = False
            for bacteria in path:
                if not self.tree[bacteria].data['valid']:
                    incomplete = True
            return incomplete

        paths = self.tree.paths_to_leaves()
        avg_tree_IG = None
        added = 0

        # Compute global avg IG
        for path in paths:
            for bacteria in path:
                # Only the remaining nodes contribute to global avg IG
                if self.tree[bacteria].data['valid']:
                    if added == 0:
                        avg_tree_IG = mutual_info_classif(
                            X=self.tree[bacteria].data['feature_vector'].
                            reshape(len(labels), 1),
                            y=labels,
                            random_state=0)
                        added += 1
                    else:
                        avg_tree_IG = avg_tree_IG * (added / (added + 1)) + (
                            mutual_info_classif(X=self.tree[bacteria].data[
                                'feature_vector'].reshape(len(labels), 1),
                                                y=labels,
                                                random_state=0) / (added + 1))
                        added += 1

        # The leaf nodes are the final element in the path. If the leaf node IG is zero or less than global IG and is a part of an incomplete path it should be removed
        for path in paths:

            leaf_node_IG = mutual_info_classif(
                X=self.tree[path[-1]].data['feature_vector'].reshape(
                    len(labels), 1),
                y=labels,
                random_state=0)

            if incomplete_path(path) and ((leaf_node_IG == 0) or
                                          (leaf_node_IG < avg_tree_IG)):
                self.tree[path[-1]].data['valid'] = False
Exemplo n.º 14
0
class MonteCarlo:
    N_THREADS = 1
    PERCENTILE = 100

    def __init__(self, engine=None, hero=None):
        # self.last_ev = 0
        # self.rolling_10 = deque(maxlen=10)
        # self.rolling_40 = deque(maxlen=40)
        self.ev_history = {}
        self.time_start = None
        self.duration = None
        self.queue = None
        self.leaf_path = None

        if not engine:
            # logger.info('engine not given, loading from file...')
            self.engine_checksum = None
            self.load_engine(hero)
        else:
            # logger.info('engine given')
            self.init(engine, hero)

    @property
    def current_actions(self):
        return [(c.data['action'], c.data['ev'], c.data['traversed'])
                for c in self.tree.children(self.tree.root)]

    def is_time_left(self):
        return time.time() - self.time_start < self.duration

    @retrace.retry(on_exception=(EOFError, KeyError), interval=0.1, limit=None)
    def load_engine(self, hero):
        with shelve.open(Engine.FILE) as shlv:
            if shlv['hash'] != self.engine_checksum:
                # logger.info('loading engine from file...')
                self.engine_checksum = shlv['hash']
                self.init(shlv['engine'], hero)

    def init(self, engine, hero):
        # logger.info('init state')
        self.engine = engine
        self.hero = hero or self.engine.q[0][0]
        self.hero_pocket = self.engine.data[self.hero]['hand']
        for s in self.engine.data:
            self.ev_history[s] = deque(maxlen=50)
        # logger.info('HERO is at seat {} with {}'.format(self.hero, self.hero_pocket))

        self.watched = False
        self.init_tree()

    def init_tree(self):
        """create the tree. Add a root; available action will add the first level of children"""
        # self.traversed_ceiling = 1
        self.tree = Tree()
        root = self.tree.create_node('root', identifier='root', data={'traversed': 0, 'ev': 0, 'stats': 1, 'cum_stats': 1})
        # # logger.info('tree:\n{}'.format(self.tree.show()))
        # input('new tree')

    def watch(self):
        """Runs when engine file changes. Just kicks off run for 3s sprints"""
        # logger.info('Monte Carlo watching every {}s...'.format(self.timeout))
        while True:

            # loads new engine file if checksum changed
            self.load_engine()

            # do not analyze if game finished
            if self.engine.phase in [self.engine.PHASE_SHOWDOWN, self.engine.PHASE_GG]:
                if not self.watched:
                    # logger.error('game is finished')
                    self.watched = True
                time.sleep(3)
                continue

            # do not analyze if hero does not have pocket
            if self.hero_pocket in [['__', '__'], ['  ', '  ']]:
                if not self.watched:
                    # logger.error('hero does not have a pocket')
                    self.watched = True
                time.sleep(0.5)
                continue

            # do not analyze if hero is not to play
            if self.hero != self.engine.q[0][0]:
                if not self.watched:
                    # logger.error('hero is not to act')
                    self.watched = True
                time.sleep(0.5)
                continue

            if self.is_complete:
                if not self.watched:
                    # logger.error('mc is complete')
                    self.watched = True
                time.sleep(2)
                continue

            # run a few sims
            # logger.debug('running now with timeout {}'.format(self.timeout))
            self.run()
            self.timeout += 0.1

    def run(self, duration):
        """Run simulations
        For x:
         - clone engine
         - start at root
          -- iterate and find next unprocessed node
          -- action engine to that node parent
          -- process that node
         - keep processing
         - with return EV

         Levelling:
            extremely huge iterations when many players. So
            do the most probably actions only till all done.

        Handling close action approximations:
        """
        # logger.info('Monte Carlo started')
        total_traversions_start = sum(a[2] for a in self.current_actions)

        # cannot run if engine in showdown or gg
        if self.engine.phase in [self.engine.PHASE_SHOWDOWN, self.engine.PHASE_GG]:
            logger.warning('cannot run mc with no actions')
            return

        self.duration = duration
        self.time_start = time.time()

        self.queue = PriorityQueue()
        # threads = []
        # for _ in range(self.N_THREADS):
        #     t = MCWorker(self)
        #     # t.start()
        #     threads.append(t)

        # self.traversed_focus = 0
        leaves = self.tree.paths_to_leaves()
        # logger.debug('leaves from tree: {}'.format(len(leaves)))
        # leaves.sort(key=lambda lp: len(lp) + sum(int(lpn.split('_')[0]) for lpn in lp), reverse=True)
        # # logger.debug('{} leaves are now sorted by formula'.format(len(leaves)))
        # logger.debug('{}'.format(json.dumps(leaves[:3], indent=4, default=str)))
        # leaves.sort(key=len)
        # logger.debug('{} leaves are now sorted by length'.format(len(leaves)))
        # logger.debug('{}'.format(json.dumps(leaves[:3], indent=4, default=str)))
        # leaves.sort(key=lambda lp: int(lp[-1][:3]), reverse=True)
        # logger.debug('{} leaves are now sorted by rank'.format(len(leaves)))
        # logger.error(json.dumps(leaves, indent=4, default=str))
        # input('>>')
        for leaf_path in leaves:
            node = self.tree[leaf_path[-1]]
            item = (
                1 - node.data['cum_stats'],
                leaf_path,
            )
            self.queue.put(item)

        # for t in threads:
        #     t.start()
        #
        # for t in threads:
        #     t.join()
        #     if t.error:
        #         raise Exception().with_traceback(t.error[2])

        while self.is_time_left() and not self.queue.empty():
            priority, self.leaf_path = self.queue.get_nowait()
            self.run_item(self.leaf_path)

        if self.queue.empty():
            logger.info(f'Everything was processed in queue!')

        total_traversions_end = sum(a[2] for a in self.current_actions)
        if total_traversions_end <= total_traversions_start:
            logger.warning(f'No new traversion added to {total_traversions_start}')

    def run_item(self, path):
        # logger.debug('running this path: {}'.format(path))
        e = deepcopy(self.engine)
        e.mc = True
        """To calculate the investment for the loss EV, the total amounts used till end is required. Cannot
         use final player balance on engine as that might have winnings allocated to it by the engine. Instead
         the difference from all the new matched bets from the current matched bets will be used.
         Need to add current contrib
        """
        e.matched_start = e.data[self.hero]['matched'] + e.data[self.hero]['contrib']
        # logger.info('hero starting with matched = {} from {} + {}'.format(
        #     e.matched_start, e.data[self.hero]['matched'], e.data[self.hero]['contrib']))

        # self.tree.show()
        self.fast_forward(e, path)
        # logger.info('{}'.format('-' * 200))
        # input('check item')

    def show_best_action(self):
        """Calculates best action on root"""
        # logger.error("\n\n")
        sum_traversed = 0
        delta = 0
        max_ev = float('-inf')
        action = None
        amount = None
        for nid in self.tree[self.tree.root].fpointer:
            child = self.tree[nid]
            # logger.debug('{} {}'.format(child.tag, child.data))
            dat = child.data
            sum_traversed += dat['traversed']
            # logger.error('{} @{} => {}'.format(dat['action'], dat['traversed'], round(dat['ev'], 4)))

            # delta += abs(1 - (self.convergence.get(dat['action'], 1) / dat['ev'] if dat['ev'] else 1))
            # self.convergence[dat['action']] = dat['ev']

            if dat['ev'] > max_ev:
                max_ev = dat['ev']
                action = dat['action']
                if action.startswith('bet') or action.startswith('raise') or action.startswith('allin'):
                    amount = dat['amount']

        best_action = '{}{}'.format(action, ' with {}'.format(amount) if amount else '')

        # self.convergence['deq'].append(round(delta, 1))
        self.convergence['deq'].append(best_action)
        # # logger.error('deq: {}'.format(list(self.convergence['deq'])))

        # logger.error('')
        # logger.error('Timeout: {}'.format(round(self.timeout, 1)))
        # logger.error('Traversed: {}'.format(sum_traversed))
        deq_cnts = Counter(list(self.convergence['deq']))
        # # logger.error('deq: {}'.format(deq_cnts.most_common()))

        # logger.error('{}% for {}'.format(
            # 100 * sum(dq == deq_list[-1] for dq in deq_list[:-1]) // (len(deq_list) - 1)
            # 100 * (deq_cnts.most_common()[0][1] - deq_cnts.most_common()[1][1]) // self.convergence_size
            # if len(deq_cnts) > 1 else 100 * len(self.convergence['deq']) // self.convergence_size,
            # deq_cnts.most_common()[0][0]
        # ))

    def fast_forward(self, e, path):
        """Do actions on engine till the leaf is reached. Need to do available_actions before
        every DO

        First check if the leave is already processed, then skip this path. When the leaf is reached
        then process from that node.

        Remember to send through only the first letter for the action.

        Then update the nodes from this leaf back up the tree
        """
        # logger.info('Fast forwarding {} nodes'.format(len(path)))

        if len(path) == 1:
            # logger.info('processing root for first time')
            self.process_node(e, self.tree[path[0]])
            return

        leaf_node = self.tree[path[-1]]
        # logger.debug('checking if last node has been processed:')
        # logger.debug('last node leaf {} has node data {}'.format(leaf_node.tag, leaf_node.data))
        if leaf_node.data['traversed']:
            # logger.info('This leaf node ({}) above focus level {}'.format(leaf_node.tag, self.traversed_focus))
            # can happen as all actions are added, but then one was chosen to continue on
            # and that path for that action wasn't removed from the queue
            return

        for nid in path[1:]:
            node = self.tree[nid]
            # logger.debug('fast forwarding action for node {}'.format(node.tag))
            e.available_actions()
            cmd = [node.data['action'][0]]
            if 'amount' in node.data:
                cmd.append(node.data['amount'])
                # logger.debug('Adding bet value of {}'.format(node.data['amount']))
            # logger.debug('Executing path action {} for {}'.format(cmd, node.tag))
            # logger.debug('Executing path action {} with data {}'.format(cmd, node.data))
            e.do(cmd)

            if node.is_leaf():
                # logger.debug('{} is a leaf node, processing next...'.format(node.tag))
                self.process_node(e, node)

                logger.info('nodes processed, now updating nodes that were fast forwarded')
                for processed_nid in reversed(path[1:]):
                    processed_node = self.tree[processed_nid]
                    self.update_node(processed_node)

        self.ev_history[self.engine.s].append(sum(a[1] for a in self.current_actions))

    def process_node(self, e, n):
        """Process node
        Get actions available for node
        Pick action to traverse with UCT
        Process action selected
        Return EV
        """
        # logger.info('processing node {} with data {}'.format(n.tag, n.data))

        # this node is the hero folding (to prevent this being processed as leaf)
        # was created with other children (but not most probable at that time to be proc as child)
        # if hero folding, then make this node a leaf node with fold eq
        # exiting before adding children alleviates the need to remove the immediately again thereafter
        # bug: cannot use engine.q as it already rotated after taking action getting here
        if not n.is_root() and n.data['action'] == 'fold' and self.hero == n.data['seat']:
            winnings, losses = self.net(e)
            result = {
                'ev': losses,
                'traversed': 1,
            }
            # logger.info('hero has folded this node given: {}'.format(result))
            n.data.update(result)
            # logger.info('node data after fold: {}'.format(n.data))
            return

        # add the children of the node
        if not n.fpointer:
            self.add_actions(e, n)

        # this node is a leaf (no more actions to take!)
        # either the game finished and we have winner and pot
        # or we have to use pokereval.winners
        if n.is_leaf():
            # logger.info('node {} is the final action in the game'.format(n.tag))
            # winner given (easy resolution)
            if e.winner:
                # logger.debug('engine gave winner {}'.format(e.winner))
                winnings, losses = self.net(e)
                ev = winnings if self.hero in e.winner else losses
            # else if the winner is unknown
            # then calculate winners and use
            # percentage of hero as amt
            else:
                if 'in' not in e.data[self.hero]['status']:
                    # hero fold is handled before in method
                    # and thus for equities calc it is just 0
                    # logger.debug('Hero {} is not in game'.format(self.hero))
                    ev = 0
                else:
                    winnings, losses = self.net(e)
                    equities = PE.showdown_equities(e)
                    # equities = self.get_showdown_equities(e)
                    ev_pos = winnings * equities[self.hero]
                    # logger.debug('ev_pos = {} from winnings {} * eq {}'.format(ev_pos, winnings, equities[self.hero]))
                    ev_neg = losses * (1 - equities[self.hero])
                    # logger.debug('ev_neg = {} from losses {} * -eq {}'.format(ev_neg, losses, (1 - equities[self.hero])))
                    ev = ev_pos + ev_neg
                    logger.info('Net EV: {} from {} + {}'.format(ev, ev_pos, ev_neg))
            result = {
                'ev': ev,
                'traversed': 1,
            }
            # logger.info('{} leaf has result {}'.format(n.tag, result))
            n.data.update(result)
            return

        # node is all good (not leaf (has children) and not hero folding)
        # get child actions and process most probable action
        a_node = self.most_probable_action(n)
        action = a_node.data['action']
        # logger.info('taking next child node action {}'.format(action))

        # if it is hero and he folds,
        # it is not necessarily an immediate ZERO equity
        # since my previous contrib needs to be added to the pot (i.e. contribs after starting mc)
        # i.e. make this a leaf node implicitly
        # no child nodes to remove for fold
        if action == 'fold' and self.hero == a_node.data['seat']:
            winnings, losses = self.net(e)
            result = {
                'ev': losses,
                'traversed': 1,
            }
            # logger.info('hero has folded the child node selected: {}'.format(result))
            a_node.data.update(result)
            # logger.info('a_node data after: {}'.format(a_node.data))

        # else we must process the node
        else:
            # logger.info('taking action {} and processing that node'.format(action))
            cmd = [action[0]]
            if 'amount' in a_node.data:
                cmd.append(a_node.data['amount'])
                # logger.debug('Adding bet value of {}'.format(a_node.data['amount']))
            e.do(cmd)
            self.process_node(e, a_node)

        # action node has been processed, now update node
        self.update_node(n)

    def update_node(self, node):
        """Update the node's data

        If leaf, then it was already calculated during processing, and now
        do not change it: the ev is the ev

        Minimax applied, hero pick best and foe picks min after p

        Traversed will stay the traversed_focus level for leaves, but for parent nodes
        the traversed will be the number of leaves reached from that node.
        """
        is_hero = node.data.get('seat') == self.hero
        # logger.debug('is hero? {}'.format(is_hero))

        # it will traverse back up to the root
        # root can be skipped
        if node.is_root():
            # input('hero {} node data {}'.format(self.hero, node.data.get('seat')))
            # if is_hero:
            #     self.rolling_10.append(abs(self.last_ev))
            #     self.rolling_40.append(abs(self.last_ev))
            #     logger.debug('Added {} ev to collection'.format(self.last_ev))
            #     input('Added {} ev to collection'.format(self.last_ev))
            # logger.debug('reached the root')
            # self.update_ev_change()
            return

        # fast forwarding will send here, just ignore node if leaf
        if node.is_leaf():
            # logger.debug('not updating {}: it is final game result (no leaf nodes)'.format(node.tag))
            # logger.debug('not updating {}: final data {}'.format(node.tag, node.data))
            return

        depth = self.tree.depth(node)
        # logger.info('updating node {} at depth {}'.format(node.tag, depth))
        # logger.info('node has {} before update'.format(node.data))

        if not len(node.fpointer):
            # logger.error('node {} with {} as no children...'.format(node.tag, node.data))
            raise Exception('not necessary to process leaves')
        # logger.debug('extracting data from {} children nodes...'.format(len(node.fpointer)))

        n_ev = float('-inf') if is_hero else 0
        n_traversed = 0
        for child_nid in node.fpointer:
            child_node = self.tree[child_nid]
            # logger.debug('child node {} has {}'.format(child_node.tag, child_node.data))
            dat = child_node.data
            if not dat['traversed']:
                # logger.debug('skipping untraversed {}'.format(child_node.tag))
                continue

            # get max for hero
            if is_hero:
                # todo is this +ev dampening necessary
                # todo this should be fixed when setting for hand range
                # equities = PE.showdown_equities(self.engine)
                # n_ev = max(n_ev, dat['ev'] * equities.get(self.hero, 0))
                n_ev = max(n_ev, dat['ev'])

            # get min for foe
            else:
                # ev_adj = dat['ev'] * dat['stats']
                # logger.debug('foe min between {} and {}'.format(n_ev, ev_adj))
                # n_ev = min(n_ev, ev_adj)
                n_ev += dat['ev'] * dat['stats'] / dat['divider']

            n_traversed += dat['traversed']
            # logger.debug('added {} traversed: now have {} so far'.format(dat['traversed'], n_traversed))

        self.last_ev = node.data['ev'] - n_ev
        node.data.update({
            'ev': n_ev,
            'traversed': n_traversed,
        })
        # logger.info('now node has {} ev~{} after {}'.format(node.tag, round(n_ev, 3), n_traversed))

        if not node.data['traversed']:
            raise Exception('node cannot be untraversed')

    def net(self, e):
        """Stored the balance at the start of sim.
        Now calculate difference as player total matched contrib.
        Winnings will be less initial starting contrib.
        """
        e.gather_the_money()
        p = e.players[self.hero]
        d = e.data[self.hero]

        matched_diff = d['matched'] - e.matched_start
        # logger.debug('matched diff = {} from {} - {}'.format(matched_diff, d['matched'], e.matched_start))

        winnings = int(e.pot - matched_diff)
        # logger.debug('winnings diff = {} from pot {} less matched {}'.format(winnings, e.pot, matched_diff))

        losses = int(-matched_diff)
        # logger.info('Winnings = {} and losses = {}'.format(winnings, losses))
        return winnings, losses

    def most_probable_action(self, parent):
        """All nodes will be processed once at least but it will never happen. Just return
        the most probable node for most accurate play. Using stats fields on data
        There should not be any untraversed nodes. So first get untraversed, then sort
        and pop first one"""
        # logger.info('getting most probable action after {}'.format(parent.tag))
        children = self.tree.children(parent.identifier)
        children = [c for c in children if not c.data['traversed']]
        if not children:
            raise MonteCarloError('Cannot choose most probable action when all nodes are traversed')
        children.sort(key=lambda c: c.data['stats'], reverse=True)
        child = children[0]
        # logger.debug('{} is untraversed, returning that node for actioning'.format(child.tag))
        self.leaf_path.append(child.identifier)
        return child

    def add_actions(self, e, parent):
        """Add actions available to this node
        If in GG phase then no actions possible, ever.
        Remove 'hand'
        Bets:
            - preflop are 2-4x BB
            - postflop are 40-100% pot
        Raise:
            - always double
        Allin:
            - only on river
            - if out of money then converted to allin

        Scale non-fold probabilities even though it should not have an effect.
        """
        # logger.info('adding actions to {}'.format(parent.tag))
        actions = e.available_actions()
        s, p = e.q[0]
        d = e.data[s]
        balance_left = p['balance'] - d['contrib']

        if not actions:
            # logger.warn('no actions to add to node')
            return

        if 'gg' in actions:
            # logger.debug('no actions available, got gg')
            return

        actions.remove('hand')

        # remove fold if player can check
        if 'check' in actions:
            actions.remove('fold')
            # # logger.debug('removed fold when check available')

        # remove fold for hero
        # if s == self.hero and 'fold' in actions:
        #     actions.remove('fold')
        #     # logger.debug('removed fold from hero')

        # remove raise if player has already been aggressive
        if 'raise' in actions and any(pa['action'] in 'br' for pa in d[e.phase]):
            actions.remove('raise')
            # # logger.debug('removed raise as player has already been aggressive')

        # remove allin, but add it later with final stats (if increased from bet/raised)
        if 'allin' in actions:
            actions.remove('allin')
        # logger.debug('removed allin by default')

        # load stats (codes with counts)
        stats = ES.player_stats(e, s)
        max_contrib = max(pd['contrib'] for pd in e.data.values())
        # contrib_short = max_contrib - d['contrib']

        # allin needs to be the doc count
        # where bets and raises result in allin, add those prob dists to this
        # that will give proper probability
        go_allin = stats['actions'].get('a', 0)

        # # logger.info('filtered actions: {}'.format(actions))
        # ev 0 instead of none because of root node sum when not all traversed it gives error
        action_nodes = []
        for a in actions:
            node_data = {
                'stats': stats['actions'].get(ACTIONS_TO_ABBR[a], 0.01),
                'divider': 1,
                'action': a,
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
            }

            if a in ['bet', 'raise']:
                btps_and_amts = []
                total_pot = sum(pd['contrib'] for pd in e.data.values()) + e.pot

                # for preflop only do 2x and 3x
                if e.phase == e.PHASE_PREFLOP:
                    btps_and_amts.append(('double', e.bb_amt * 2))
                    btps_and_amts.append(('triple', e.bb_amt * 3))
                # else do half and full pots
                else:
                    btps_and_amts.append(('half_pot', total_pot * 0.50))
                    btps_and_amts.append(('full_pot', total_pot * 1.00))
                    # round bets up to a BB
                    # btps_and_amts = [(btp, -(amt // -e.bb_amt) * e.bb_amt)
                    #                  for btp, amt in btps_and_amts]

                betting_info = []
                amts_seen = []
                for btp, amt in btps_and_amts:
                    if amt in amts_seen:
                        # logger.debug('already using {}, skipping duplicate'.format(amt))
                        continue
                    if a == 'bet' and amt < e.bb_amt:
                        # logger.debug('bet cannot be less than BB {}'.format(e.bb_amt))
                        continue
                    if a == 'raise' and amt < (max_contrib * 2):
                        # logger.debug('raise cannot be less than 2x contrib  of {}'.format(max_contrib * 2))
                        continue
                    betting_info.append((btp, amt))
                    amts_seen.append(amt)

                # change raises that cause allin
                betting_info_final = []
                for btp, amt in betting_info:
                    # if amt is more than player balance, it is an allin
                    if amt >= balance_left:
                        go_allin += node_data['stats'] / len(betting_info)
                    else:
                        betting_info_final.append((btp, amt))

                # all good, can have this bet as option
                for btp, amt in betting_info_final:
                    node_data_copy = deepcopy(node_data)
                    node_data_copy['divider'] = len(betting_info_final)
                    node_data_copy['action'] = f'{a}_{btp}'
                    node_data_copy['amount'] = amt
                    action_nodes.append(node_data_copy)

            else:
                action_nodes.append(node_data)

        # allin will have doc counts (from stat, maybe from bets, maybe from raise)
        if go_allin:
            node_data = {
                'stats': go_allin,
                'divider': 1,
                'action': 'allin',
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
                'amount': balance_left,
            }
            action_nodes.append(node_data)
            # logger.debug('added allin to actions with stat {}'.format(node_data['stats']))

        # scale the stats (it is currently term counts aka histogram) and it is required to be
        # a probability distribution (p~1)
        # Also, certain actions like fold can be removed, and the total stats is not 1
        total_stats = sum(an['stats'] / an['divider'] for an in action_nodes)
        for action_node in action_nodes:
            action_node['stats'] = max(0.01, action_node['stats'] / action_node['divider'] / total_stats)
            action_node['cum_stats'] = parent.data['cum_stats'] * action_node['stats']
            node_tag = f'{action_node["action"]}_{s}_{e.phase}'
            identifier = f'{node_tag}_{str(uuid.uuid4())[:8]}'
            self.tree.create_node(identifier=identifier, tag=node_tag, parent=parent.identifier, data=action_node)
            # logger.debug('new {} for {} with data {}'.format(node_tag, s, action_node))
            item = (
                1 - action_node['cum_stats'],
                self.leaf_path + [identifier]
            )
            self.queue.put(item)
            # logger.debug('new {} for {} with data {}'.format(node_tag, s, action_node))
        # logger.info('{} node actions added'.format(len(action_nodes)))

    def analyze_tree(self):
        """Analyze tree to inspect best action from ev"""
        # self.tree.show()

        # check all finished paths
        for path in self.tree.paths_to_leaves():

            # skip untraversed end
            last_node = self.tree[path[-1]]
            if not last_node.data['traversed']:
                logger.debug('skipping untraversed endpoint {}'.format(last_node.tag))
                continue

            # show all actions
            for nid in path:
                node = self.tree[nid]
                d = node.data
                logger.info('Node: {} ev={}'.format(node.tag, d['ev']))


        0/0
        input('$ check tree')

    def get_showdown_equities(self, e):
        """instead of using pokereval, use hs from se"""
        hss = {}
        for s, d in e.data.items():
            if 'in' in d['status']:
                hss[s] = ES.showdown_hs(e, s, percentile=self.PERCENTILE)
        # calculate for hero
        if self.hero in hss:
            d = e.data[self.hero]
            hss[self.hero] = PE.hand_strength(d['hand'], e.board, e.rivals)
        # normalize
        total = sum(hs for hs in hss.values())
        equities = {s: hs / total for s, hs in hss.items()}
        return equities
# Build the orbit tree
tree = Tree()
tree.create_node("COM", "COM")

to_treat_nodes = ["COM"]

while len(to_treat_nodes) != 0:
    current_node = to_treat_nodes.pop()

    for orbit, center in orbit_map.items():
        if center == current_node:
            tree.create_node(orbit, orbit, parent=center)
            to_treat_nodes.append(orbit)

pertinent_paths = [
    path for path in tree.paths_to_leaves() if path[-1] in ["YOU", "SAN"]
]

# Get lowest common ancestor dsitance from COM
lowest_common_ancestor = 0
for idx, pair in enumerate(zip(*pertinent_paths)):
    if pair[0] != pair[1]:
        lowest_common_ancestor = idx
        break

# Calculate node distance between YOU and SAN
distance = len(pertinent_paths[0]) + len(
    pertinent_paths[1]) - 2 * lowest_common_ancestor - 2

print("The number of orbit jumps to join Santa is {0}".format(distance))
Exemplo n.º 16
0
class MonteCarlo:
    N_THREADS = 1
    PERCENTILE = 100

    def __init__(self, engine=None, hero=None):
        # self.last_ev = 0
        # self.rolling_10 = deque(maxlen=10)
        # self.rolling_40 = deque(maxlen=40)
        self.ev_history = {}
        self.time_start = None
        self.duration = None
        self.queue = None
        self.leaf_path = None

        if not engine:
            # logger.info('engine not given, loading from file...')
            self.engine_checksum = None
            self.load_engine(hero)
        else:
            # logger.info('engine given')
            self.init(engine, hero)

    @property
    def current_actions(self):
        return [(c.data['action'], c.data['ev'], c.data['traversed'])
                for c in self.tree.children(self.tree.root)]

    def is_time_left(self):
        return time.time() - self.time_start < self.duration

    @retrace.retry(on_exception=(EOFError, KeyError), interval=0.1, limit=None)
    def load_engine(self, hero):
        with shelve.open(Engine.FILE) as shlv:
            if shlv['hash'] != self.engine_checksum:
                # logger.info('loading engine from file...')
                self.engine_checksum = shlv['hash']
                self.init(shlv['engine'], hero)

    def init(self, engine, hero):
        # logger.info('init state')
        self.engine = engine
        self.hero = hero or self.engine.q[0][0]
        self.hero_pocket = self.engine.data[self.hero]['hand']
        for s in self.engine.data:
            self.ev_history[s] = deque(maxlen=50)
        # logger.info('HERO is at seat {} with {}'.format(self.hero, self.hero_pocket))

        self.watched = False
        self.init_tree()

    def init_tree(self):
        """create the tree. Add a root; available action will add the first level of children"""
        # self.traversed_ceiling = 1
        self.tree = Tree()
        root = self.tree.create_node('root',
                                     identifier='root',
                                     data={
                                         'traversed': 0,
                                         'ev': 0,
                                         'stats': 1,
                                         'cum_stats': 1
                                     })
        # # logger.info('tree:\n{}'.format(self.tree.show()))
        # input('new tree')

    def watch(self):
        """Runs when engine file changes. Just kicks off run for 3s sprints"""
        # logger.info('Monte Carlo watching every {}s...'.format(self.timeout))
        while True:

            # loads new engine file if checksum changed
            self.load_engine()

            # do not analyze if game finished
            if self.engine.phase in [
                    self.engine.PHASE_SHOWDOWN, self.engine.PHASE_GG
            ]:
                if not self.watched:
                    # logger.error('game is finished')
                    self.watched = True
                time.sleep(3)
                continue

            # do not analyze if hero does not have pocket
            if self.hero_pocket in [['__', '__'], ['  ', '  ']]:
                if not self.watched:
                    # logger.error('hero does not have a pocket')
                    self.watched = True
                time.sleep(0.5)
                continue

            # do not analyze if hero is not to play
            if self.hero != self.engine.q[0][0]:
                if not self.watched:
                    # logger.error('hero is not to act')
                    self.watched = True
                time.sleep(0.5)
                continue

            if self.is_complete:
                if not self.watched:
                    # logger.error('mc is complete')
                    self.watched = True
                time.sleep(2)
                continue

            # run a few sims
            # logger.debug('running now with timeout {}'.format(self.timeout))
            self.run()
            self.timeout += 0.1

    def run(self, duration):
        """Run simulations
        For x:
         - clone engine
         - start at root
          -- iterate and find next unprocessed node
          -- action engine to that node parent
          -- process that node
         - keep processing
         - with return EV

         Levelling:
            extremely huge iterations when many players. So
            do the most probably actions only till all done.

        Handling close action approximations:
        """
        # logger.info('Monte Carlo started')
        total_traversions_start = sum(a[2] for a in self.current_actions)

        # cannot run if engine in showdown or gg
        if self.engine.phase in [
                self.engine.PHASE_SHOWDOWN, self.engine.PHASE_GG
        ]:
            logger.warning('cannot run mc with no actions')
            return

        self.duration = duration
        self.time_start = time.time()

        self.queue = PriorityQueue()
        # threads = []
        # for _ in range(self.N_THREADS):
        #     t = MCWorker(self)
        #     # t.start()
        #     threads.append(t)

        # self.traversed_focus = 0
        leaves = self.tree.paths_to_leaves()
        # logger.debug('leaves from tree: {}'.format(len(leaves)))
        # leaves.sort(key=lambda lp: len(lp) + sum(int(lpn.split('_')[0]) for lpn in lp), reverse=True)
        # # logger.debug('{} leaves are now sorted by formula'.format(len(leaves)))
        # logger.debug('{}'.format(json.dumps(leaves[:3], indent=4, default=str)))
        # leaves.sort(key=len)
        # logger.debug('{} leaves are now sorted by length'.format(len(leaves)))
        # logger.debug('{}'.format(json.dumps(leaves[:3], indent=4, default=str)))
        # leaves.sort(key=lambda lp: int(lp[-1][:3]), reverse=True)
        # logger.debug('{} leaves are now sorted by rank'.format(len(leaves)))
        # logger.error(json.dumps(leaves, indent=4, default=str))
        # input('>>')
        for leaf_path in leaves:
            node = self.tree[leaf_path[-1]]
            item = (
                1 - node.data['cum_stats'],
                leaf_path,
            )
            self.queue.put(item)

        # for t in threads:
        #     t.start()
        #
        # for t in threads:
        #     t.join()
        #     if t.error:
        #         raise Exception().with_traceback(t.error[2])

        while self.is_time_left() and not self.queue.empty():
            priority, self.leaf_path = self.queue.get_nowait()
            self.run_item(self.leaf_path)

        if self.queue.empty():
            logger.info(f'Everything was processed in queue!')

        total_traversions_end = sum(a[2] for a in self.current_actions)
        if total_traversions_end <= total_traversions_start:
            logger.warning(
                f'No new traversion added to {total_traversions_start}')

    def run_item(self, path):
        # logger.debug('running this path: {}'.format(path))
        e = deepcopy(self.engine)
        e.mc = True
        """To calculate the investment for the loss EV, the total amounts used till end is required. Cannot
         use final player balance on engine as that might have winnings allocated to it by the engine. Instead
         the difference from all the new matched bets from the current matched bets will be used.
         Need to add current contrib
        """
        e.matched_start = e.data[self.hero]['matched'] + e.data[
            self.hero]['contrib']
        # logger.info('hero starting with matched = {} from {} + {}'.format(
        #     e.matched_start, e.data[self.hero]['matched'], e.data[self.hero]['contrib']))

        # self.tree.show()
        self.fast_forward(e, path)
        # logger.info('{}'.format('-' * 200))
        # input('check item')

    def show_best_action(self):
        """Calculates best action on root"""
        # logger.error("\n\n")
        sum_traversed = 0
        delta = 0
        max_ev = float('-inf')
        action = None
        amount = None
        for nid in self.tree[self.tree.root].fpointer:
            child = self.tree[nid]
            # logger.debug('{} {}'.format(child.tag, child.data))
            dat = child.data
            sum_traversed += dat['traversed']
            # logger.error('{} @{} => {}'.format(dat['action'], dat['traversed'], round(dat['ev'], 4)))

            # delta += abs(1 - (self.convergence.get(dat['action'], 1) / dat['ev'] if dat['ev'] else 1))
            # self.convergence[dat['action']] = dat['ev']

            if dat['ev'] > max_ev:
                max_ev = dat['ev']
                action = dat['action']
                if action.startswith('bet') or action.startswith(
                        'raise') or action.startswith('allin'):
                    amount = dat['amount']

        best_action = '{}{}'.format(
            action, ' with {}'.format(amount) if amount else '')

        # self.convergence['deq'].append(round(delta, 1))
        self.convergence['deq'].append(best_action)
        # # logger.error('deq: {}'.format(list(self.convergence['deq'])))

        # logger.error('')
        # logger.error('Timeout: {}'.format(round(self.timeout, 1)))
        # logger.error('Traversed: {}'.format(sum_traversed))
        deq_cnts = Counter(list(self.convergence['deq']))
        # # logger.error('deq: {}'.format(deq_cnts.most_common()))

        # logger.error('{}% for {}'.format(
        # 100 * sum(dq == deq_list[-1] for dq in deq_list[:-1]) // (len(deq_list) - 1)
        # 100 * (deq_cnts.most_common()[0][1] - deq_cnts.most_common()[1][1]) // self.convergence_size
        # if len(deq_cnts) > 1 else 100 * len(self.convergence['deq']) // self.convergence_size,
        # deq_cnts.most_common()[0][0]
        # ))

    def fast_forward(self, e, path):
        """Do actions on engine till the leaf is reached. Need to do available_actions before
        every DO

        First check if the leave is already processed, then skip this path. When the leaf is reached
        then process from that node.

        Remember to send through only the first letter for the action.

        Then update the nodes from this leaf back up the tree
        """
        # logger.info('Fast forwarding {} nodes'.format(len(path)))

        if len(path) == 1:
            # logger.info('processing root for first time')
            self.process_node(e, self.tree[path[0]])
            return

        leaf_node = self.tree[path[-1]]
        # logger.debug('checking if last node has been processed:')
        # logger.debug('last node leaf {} has node data {}'.format(leaf_node.tag, leaf_node.data))
        if leaf_node.data['traversed']:
            # logger.info('This leaf node ({}) above focus level {}'.format(leaf_node.tag, self.traversed_focus))
            # can happen as all actions are added, but then one was chosen to continue on
            # and that path for that action wasn't removed from the queue
            return

        for nid in path[1:]:
            node = self.tree[nid]
            # logger.debug('fast forwarding action for node {}'.format(node.tag))
            e.available_actions()
            cmd = [node.data['action'][0]]
            if 'amount' in node.data:
                cmd.append(node.data['amount'])
                # logger.debug('Adding bet value of {}'.format(node.data['amount']))
            # logger.debug('Executing path action {} for {}'.format(cmd, node.tag))
            # logger.debug('Executing path action {} with data {}'.format(cmd, node.data))
            e.do(cmd)

            if node.is_leaf():
                # logger.debug('{} is a leaf node, processing next...'.format(node.tag))
                self.process_node(e, node)

                logger.info(
                    'nodes processed, now updating nodes that were fast forwarded'
                )
                for processed_nid in reversed(path[1:]):
                    processed_node = self.tree[processed_nid]
                    self.update_node(processed_node)

        self.ev_history[self.engine.s].append(
            sum(a[1] for a in self.current_actions))

    def process_node(self, e, n):
        """Process node
        Get actions available for node
        Pick action to traverse with UCT
        Process action selected
        Return EV
        """
        # logger.info('processing node {} with data {}'.format(n.tag, n.data))

        # this node is the hero folding (to prevent this being processed as leaf)
        # was created with other children (but not most probable at that time to be proc as child)
        # if hero folding, then make this node a leaf node with fold eq
        # exiting before adding children alleviates the need to remove the immediately again thereafter
        # bug: cannot use engine.q as it already rotated after taking action getting here
        if not n.is_root(
        ) and n.data['action'] == 'fold' and self.hero == n.data['seat']:
            winnings, losses = self.net(e)
            result = {
                'ev': losses,
                'traversed': 1,
            }
            # logger.info('hero has folded this node given: {}'.format(result))
            n.data.update(result)
            # logger.info('node data after fold: {}'.format(n.data))
            return

        # add the children of the node
        if not n.fpointer:
            self.add_actions(e, n)

        # this node is a leaf (no more actions to take!)
        # either the game finished and we have winner and pot
        # or we have to use pokereval.winners
        if n.is_leaf():
            # logger.info('node {} is the final action in the game'.format(n.tag))
            # winner given (easy resolution)
            if e.winner:
                # logger.debug('engine gave winner {}'.format(e.winner))
                winnings, losses = self.net(e)
                ev = winnings if self.hero in e.winner else losses
            # else if the winner is unknown
            # then calculate winners and use
            # percentage of hero as amt
            else:
                if 'in' not in e.data[self.hero]['status']:
                    # hero fold is handled before in method
                    # and thus for equities calc it is just 0
                    # logger.debug('Hero {} is not in game'.format(self.hero))
                    ev = 0
                else:
                    winnings, losses = self.net(e)
                    equities = PE.showdown_equities(e)
                    # equities = self.get_showdown_equities(e)
                    ev_pos = winnings * equities[self.hero]
                    # logger.debug('ev_pos = {} from winnings {} * eq {}'.format(ev_pos, winnings, equities[self.hero]))
                    ev_neg = losses * (1 - equities[self.hero])
                    # logger.debug('ev_neg = {} from losses {} * -eq {}'.format(ev_neg, losses, (1 - equities[self.hero])))
                    ev = ev_pos + ev_neg
                    logger.info('Net EV: {} from {} + {}'.format(
                        ev, ev_pos, ev_neg))
            result = {
                'ev': ev,
                'traversed': 1,
            }
            # logger.info('{} leaf has result {}'.format(n.tag, result))
            n.data.update(result)
            return

        # node is all good (not leaf (has children) and not hero folding)
        # get child actions and process most probable action
        a_node = self.most_probable_action(n)
        action = a_node.data['action']
        # logger.info('taking next child node action {}'.format(action))

        # if it is hero and he folds,
        # it is not necessarily an immediate ZERO equity
        # since my previous contrib needs to be added to the pot (i.e. contribs after starting mc)
        # i.e. make this a leaf node implicitly
        # no child nodes to remove for fold
        if action == 'fold' and self.hero == a_node.data['seat']:
            winnings, losses = self.net(e)
            result = {
                'ev': losses,
                'traversed': 1,
            }
            # logger.info('hero has folded the child node selected: {}'.format(result))
            a_node.data.update(result)
            # logger.info('a_node data after: {}'.format(a_node.data))

        # else we must process the node
        else:
            # logger.info('taking action {} and processing that node'.format(action))
            cmd = [action[0]]
            if 'amount' in a_node.data:
                cmd.append(a_node.data['amount'])
                # logger.debug('Adding bet value of {}'.format(a_node.data['amount']))
            e.do(cmd)
            self.process_node(e, a_node)

        # action node has been processed, now update node
        self.update_node(n)

    def update_node(self, node):
        """Update the node's data

        If leaf, then it was already calculated during processing, and now
        do not change it: the ev is the ev

        Minimax applied, hero pick best and foe picks min after p

        Traversed will stay the traversed_focus level for leaves, but for parent nodes
        the traversed will be the number of leaves reached from that node.
        """
        is_hero = node.data.get('seat') == self.hero
        # logger.debug('is hero? {}'.format(is_hero))

        # it will traverse back up to the root
        # root can be skipped
        if node.is_root():
            # input('hero {} node data {}'.format(self.hero, node.data.get('seat')))
            # if is_hero:
            #     self.rolling_10.append(abs(self.last_ev))
            #     self.rolling_40.append(abs(self.last_ev))
            #     logger.debug('Added {} ev to collection'.format(self.last_ev))
            #     input('Added {} ev to collection'.format(self.last_ev))
            # logger.debug('reached the root')
            # self.update_ev_change()
            return

        # fast forwarding will send here, just ignore node if leaf
        if node.is_leaf():
            # logger.debug('not updating {}: it is final game result (no leaf nodes)'.format(node.tag))
            # logger.debug('not updating {}: final data {}'.format(node.tag, node.data))
            return

        depth = self.tree.depth(node)
        # logger.info('updating node {} at depth {}'.format(node.tag, depth))
        # logger.info('node has {} before update'.format(node.data))

        if not len(node.fpointer):
            # logger.error('node {} with {} as no children...'.format(node.tag, node.data))
            raise Exception('not necessary to process leaves')
        # logger.debug('extracting data from {} children nodes...'.format(len(node.fpointer)))

        n_ev = float('-inf') if is_hero else 0
        n_traversed = 0
        for child_nid in node.fpointer:
            child_node = self.tree[child_nid]
            # logger.debug('child node {} has {}'.format(child_node.tag, child_node.data))
            dat = child_node.data
            if not dat['traversed']:
                # logger.debug('skipping untraversed {}'.format(child_node.tag))
                continue

            # get max for hero
            if is_hero:
                # todo is this +ev dampening necessary
                # todo this should be fixed when setting for hand range
                # equities = PE.showdown_equities(self.engine)
                # n_ev = max(n_ev, dat['ev'] * equities.get(self.hero, 0))
                n_ev = max(n_ev, dat['ev'])

            # get min for foe
            else:
                # ev_adj = dat['ev'] * dat['stats']
                # logger.debug('foe min between {} and {}'.format(n_ev, ev_adj))
                # n_ev = min(n_ev, ev_adj)
                n_ev += dat['ev'] * dat['stats'] / dat['divider']

            n_traversed += dat['traversed']
            # logger.debug('added {} traversed: now have {} so far'.format(dat['traversed'], n_traversed))

        self.last_ev = node.data['ev'] - n_ev
        node.data.update({
            'ev': n_ev,
            'traversed': n_traversed,
        })
        # logger.info('now node has {} ev~{} after {}'.format(node.tag, round(n_ev, 3), n_traversed))

        if not node.data['traversed']:
            raise Exception('node cannot be untraversed')

    def net(self, e):
        """Stored the balance at the start of sim.
        Now calculate difference as player total matched contrib.
        Winnings will be less initial starting contrib.
        """
        e.gather_the_money()
        p = e.players[self.hero]
        d = e.data[self.hero]

        matched_diff = d['matched'] - e.matched_start
        # logger.debug('matched diff = {} from {} - {}'.format(matched_diff, d['matched'], e.matched_start))

        winnings = int(e.pot - matched_diff)
        # logger.debug('winnings diff = {} from pot {} less matched {}'.format(winnings, e.pot, matched_diff))

        losses = int(-matched_diff)
        # logger.info('Winnings = {} and losses = {}'.format(winnings, losses))
        return winnings, losses

    def most_probable_action(self, parent):
        """All nodes will be processed once at least but it will never happen. Just return
        the most probable node for most accurate play. Using stats fields on data
        There should not be any untraversed nodes. So first get untraversed, then sort
        and pop first one"""
        # logger.info('getting most probable action after {}'.format(parent.tag))
        children = self.tree.children(parent.identifier)
        children = [c for c in children if not c.data['traversed']]
        if not children:
            raise MonteCarloError(
                'Cannot choose most probable action when all nodes are traversed'
            )
        children.sort(key=lambda c: c.data['stats'], reverse=True)
        child = children[0]
        # logger.debug('{} is untraversed, returning that node for actioning'.format(child.tag))
        self.leaf_path.append(child.identifier)
        return child

    def add_actions(self, e, parent):
        """Add actions available to this node
        If in GG phase then no actions possible, ever.
        Remove 'hand'
        Bets:
            - preflop are 2-4x BB
            - postflop are 40-100% pot
        Raise:
            - always double
        Allin:
            - only on river
            - if out of money then converted to allin

        Scale non-fold probabilities even though it should not have an effect.
        """
        # logger.info('adding actions to {}'.format(parent.tag))
        actions = e.available_actions()
        s, p = e.q[0]
        d = e.data[s]
        balance_left = p['balance'] - d['contrib']

        if not actions:
            # logger.warn('no actions to add to node')
            return

        if 'gg' in actions:
            # logger.debug('no actions available, got gg')
            return

        actions.remove('hand')

        # remove fold if player can check
        if 'check' in actions:
            actions.remove('fold')
            # # logger.debug('removed fold when check available')

        # remove fold for hero
        # if s == self.hero and 'fold' in actions:
        #     actions.remove('fold')
        #     # logger.debug('removed fold from hero')

        # remove raise if player has already been aggressive
        if 'raise' in actions and any(pa['action'] in 'br'
                                      for pa in d[e.phase]):
            actions.remove('raise')
            # # logger.debug('removed raise as player has already been aggressive')

        # remove allin, but add it later with final stats (if increased from bet/raised)
        if 'allin' in actions:
            actions.remove('allin')
        # logger.debug('removed allin by default')

        # load stats (codes with counts)
        stats = ES.player_stats(e, s)
        max_contrib = max(pd['contrib'] for pd in e.data.values())
        # contrib_short = max_contrib - d['contrib']

        # allin needs to be the doc count
        # where bets and raises result in allin, add those prob dists to this
        # that will give proper probability
        go_allin = stats['actions'].get('a', 0)

        # # logger.info('filtered actions: {}'.format(actions))
        # ev 0 instead of none because of root node sum when not all traversed it gives error
        action_nodes = []
        for a in actions:
            node_data = {
                'stats': stats['actions'].get(ACTIONS_TO_ABBR[a], 0.01),
                'divider': 1,
                'action': a,
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
            }

            if a in ['bet', 'raise']:
                btps_and_amts = []
                total_pot = sum(pd['contrib']
                                for pd in e.data.values()) + e.pot

                # for preflop only do 2x and 3x
                if e.phase == e.PHASE_PREFLOP:
                    btps_and_amts.append(('double', e.bb_amt * 2))
                    btps_and_amts.append(('triple', e.bb_amt * 3))
                # else do half and full pots
                else:
                    btps_and_amts.append(('half_pot', total_pot * 0.50))
                    btps_and_amts.append(('full_pot', total_pot * 1.00))
                    # round bets up to a BB
                    # btps_and_amts = [(btp, -(amt // -e.bb_amt) * e.bb_amt)
                    #                  for btp, amt in btps_and_amts]

                betting_info = []
                amts_seen = []
                for btp, amt in btps_and_amts:
                    if amt in amts_seen:
                        # logger.debug('already using {}, skipping duplicate'.format(amt))
                        continue
                    if a == 'bet' and amt < e.bb_amt:
                        # logger.debug('bet cannot be less than BB {}'.format(e.bb_amt))
                        continue
                    if a == 'raise' and amt < (max_contrib * 2):
                        # logger.debug('raise cannot be less than 2x contrib  of {}'.format(max_contrib * 2))
                        continue
                    betting_info.append((btp, amt))
                    amts_seen.append(amt)

                # change raises that cause allin
                betting_info_final = []
                for btp, amt in betting_info:
                    # if amt is more than player balance, it is an allin
                    if amt >= balance_left:
                        go_allin += node_data['stats'] / len(betting_info)
                    else:
                        betting_info_final.append((btp, amt))

                # all good, can have this bet as option
                for btp, amt in betting_info_final:
                    node_data_copy = deepcopy(node_data)
                    node_data_copy['divider'] = len(betting_info_final)
                    node_data_copy['action'] = f'{a}_{btp}'
                    node_data_copy['amount'] = amt
                    action_nodes.append(node_data_copy)

            else:
                action_nodes.append(node_data)

        # allin will have doc counts (from stat, maybe from bets, maybe from raise)
        if go_allin:
            node_data = {
                'stats': go_allin,
                'divider': 1,
                'action': 'allin',
                'phase': e.phase,
                'seat': s,
                'name': p['name'],
                'traversed': 0,
                'ev': 0,
                'amount': balance_left,
            }
            action_nodes.append(node_data)
            # logger.debug('added allin to actions with stat {}'.format(node_data['stats']))

        # scale the stats (it is currently term counts aka histogram) and it is required to be
        # a probability distribution (p~1)
        # Also, certain actions like fold can be removed, and the total stats is not 1
        total_stats = sum(an['stats'] / an['divider'] for an in action_nodes)
        for action_node in action_nodes:
            action_node['stats'] = max(
                0.01,
                action_node['stats'] / action_node['divider'] / total_stats)
            action_node[
                'cum_stats'] = parent.data['cum_stats'] * action_node['stats']
            node_tag = f'{action_node["action"]}_{s}_{e.phase}'
            identifier = f'{node_tag}_{str(uuid.uuid4())[:8]}'
            self.tree.create_node(identifier=identifier,
                                  tag=node_tag,
                                  parent=parent.identifier,
                                  data=action_node)
            # logger.debug('new {} for {} with data {}'.format(node_tag, s, action_node))
            item = (1 - action_node['cum_stats'],
                    self.leaf_path + [identifier])
            self.queue.put(item)
            # logger.debug('new {} for {} with data {}'.format(node_tag, s, action_node))
        # logger.info('{} node actions added'.format(len(action_nodes)))

    def analyze_tree(self):
        """Analyze tree to inspect best action from ev"""
        # self.tree.show()

        # check all finished paths
        for path in self.tree.paths_to_leaves():

            # skip untraversed end
            last_node = self.tree[path[-1]]
            if not last_node.data['traversed']:
                logger.debug('skipping untraversed endpoint {}'.format(
                    last_node.tag))
                continue

            # show all actions
            for nid in path:
                node = self.tree[nid]
                d = node.data
                logger.info('Node: {} ev={}'.format(node.tag, d['ev']))

        0 / 0
        input('$ check tree')

    def get_showdown_equities(self, e):
        """instead of using pokereval, use hs from se"""
        hss = {}
        for s, d in e.data.items():
            if 'in' in d['status']:
                hss[s] = ES.showdown_hs(e, s, percentile=self.PERCENTILE)
        # calculate for hero
        if self.hero in hss:
            d = e.data[self.hero]
            hss[self.hero] = PE.hand_strength(d['hand'], e.board, e.rivals)
        # normalize
        total = sum(hs for hs in hss.values())
        equities = {s: hs / total for s, hs in hss.items()}
        return equities
Exemplo n.º 17
0
with open("./aoc06.txt") as file:
    input = list(file.read().splitlines())

# root, first = input[0].split(')')

tree = Tree()
# tree.create_node(root, root)  # root node
# tree.create_node(first, first, parent=root)

for i in range(0, len(input)):
    parent, n = input[i].split(')')
    try:
        tree.create_node(n, n, parent=parent)
    except NodeIDAbsentError:
        tree.create_node(parent, parent)
        tree.create_node(n, n, parent=parent)

paths = tree.paths_to_leaves()

result = 0
planets = []
for path in paths:
    text = ''
    for i, planet in enumerate(path):
        text += planet
        if text not in planets:
            planets.append(text)
            result += i

print(result)
Exemplo n.º 18
0
print()
print(x.identifier)
print()
y = tree.parent("m")
print(y.tag)
print()
print(y.identifier)
print()
z = tree.get_node("h2")
print(z.tag)
print()
print(z.is_root())
print()
print(z.is_leaf())
print()
print(tree.paths_to_leaves())


def duplicate_node_path_check(tree, node):

    check_node = tree.get_node(node)
    current_node = check_node
    while not current_node.is_root():
        current_node = tree.parent(current_node.identifier)
        if check_node.tag == current_node.tag:
            return True
    return False


print(duplicate_node_path_check(tree, 'h2'))