示例#1
0
def route_score(
    tree_dict: StrDict,
    mol_costs: Dict[bool, float] = None,
    average_yield=0.8,
    reaction_cost=1.0,
) -> float:
    """
    Calculate the score of route using the method from
    (Badowski et al. Chem Sci. 2019, 10, 4640).

    The reaction cost is constant and the yield is an average yield.
    The starting materials are assigned a cost based on whether they are in
    stock or not. By default starting material in stock is assigned a
    cost of 1 and starting material not in stock is assigned a cost of 10.

    To be accurate, each molecule node need to have an extra
    boolean property called `in_stock`.

    :param tree_dict: the route to analyze
    :param mol_costs: the starting material cost
    :param average_yield: the average yield, defaults to 0.8
    :param reaction_cost: the reaction cost, defaults to 1.0
    :return: the computed cost
    """
    mol_cost = mol_costs or {True: 1, False: 10}

    reactions = tree_dict.get("children", [])
    if not reactions:
        return mol_cost[tree_dict.get("in_stock", True)]

    child_sum = sum(1 / average_yield * route_score(child)
                    for child in reactions[0]["children"])
    return reaction_cost + child_sum
示例#2
0
 def find_leaves_not_in_stock(tree_dict: StrDict) -> None:
     children = tree_dict.get("children", [])
     if not children and not tree_dict.get("in_stock", True):
         raise ValueError(f"child not in stock {tree_dict}")
     elif children:
         for child in children:
             find_leaves_not_in_stock(child)
 def _make_base_copy(node: StrDict) -> StrDict:
     return {
         "type": node["type"],
         "smiles": node.get("smiles", ""),
         "metadata": node.get("metadata"),
         "fingerprint": node["fingerprint"],
         "sort_key": node["sort_key"],
         "children": [],
     }
示例#4
0
 def traverse(tree_dict: StrDict, leaves: Set[str]) -> None:
     children = tree_dict.get("children", [])
     if children:
         for child in children:
             traverse(child, leaves)
     else:
         leaves.add(tree_dict["smiles"])
示例#5
0
def add_node_index(node: StrDict, n: int = 0) -> int:
    """ Add an index to the node and all its children """
    node["index"] = n
    for child in node.get("children", []):
        n += 1
        n = add_node_index(child, n)
    return n
示例#6
0
def calc_depth(tree_dict: StrDict, depth: int = 0) -> int:
    """
    Calculate the depth of a route, recursively

    :param tree_dict: the route
    :param depth: the current depth, don't specify for route
    """
    children = tree_dict.get("children", [])
    if children:
        return max(calc_depth(child, depth + 1) for child in children)
    return depth
示例#7
0
def gather_node_attributes(node: StrDict, key: str) -> List[Any]:
    """
    Collect node attributes by recursively traversing the tree

    :param node: the current node in the tree
    :param key: the name of the attribute to extract
    :return: the list of attributes gathered
    """
    features = [node[key]]
    for child in node.get("children", []):
        features.extend(gather_node_attributes(child, key))
    return features
    def _remove_children_nodes(tree: StrDict) -> StrDict:
        new_tree = ReactionTreeWrapper._make_base_copy(tree)

        if tree.get("children"):
            new_tree["children"] = []
            for child in tree["children"]:
                new_tree["children"].extend(
                    [
                        ReactionTreeWrapper._remove_children_nodes(grandchild)
                        for grandchild in child.get("children", [])
                    ]
                )
        return new_tree
示例#9
0
def remove_reactions(tree: StrDict) -> StrDict:
    """
    Remove reaction nodes from the input tree

    Does not overwrite the original tree.
    """
    new_tree = {"smiles": tree["smiles"]}
    if tree.get("children"):
        new_tree["children"] = [
            remove_reactions(grandchild)
            for grandchild in tree["children"][0]["children"]
        ]
    return new_tree
示例#10
0
def gather_adjacency_list(node: StrDict) -> List[List[int]]:
    """
    Create the adjacency list of a tree

    :param node: the current node in the tree
    :return: the adjacency list
    """
    adjacency_list = []
    for child in node.get("children", []):
        adjacency_list.append([node["index"], child["index"]])
        adjacency_list.extend(gather_adjacency_list(child))

    return adjacency_list
示例#11
0
 def _create_tree_recursively(
     self,
     node: StrDict,
     order_list: List[List[int]],
 ) -> StrDict:
     new_tree = self._make_base_copy(node)
     children = node.get("children", [])
     if children:
         child_order = order_list.pop(0)
         assert len(child_order) == len(children)
         new_children = [
             self._create_tree_recursively(child, order_list) for child in children
         ]
         new_tree["children"] = [new_children[idx] for idx in child_order]
     return new_tree
示例#12
0
    def __init__(
        self,
        reaction_tree: StrDict,
        content: Union[str, TreeContent] = TreeContent.MOLECULES,
        exhaustive_limit: int = 20,
        fp_factory: Callable[[StrDict, Optional[StrDict]], None] = None,
        dist_func: Callable[[np.ndarray, np.ndarray], float] = None,
    ) -> None:
        validate_dict(reaction_tree)
        single_node_tree = not bool(reaction_tree.get("children", []))
        if single_node_tree and content == TreeContent.REACTIONS:
            raise ValueError(
                "Cannot create wrapping with content = reactions for a tree without reactions"
            )

        self._logger = getLogger("route_distances")
        # Will convert string input automatically
        self._content = TreeContent(content)
        self._base_tree = deepcopy(reaction_tree)

        self._fp_factory = fp_factory or StandardFingerprintFactory()
        self._add_fingerprints(self._base_tree)

        if self._content != TreeContent.MOLECULES and not single_node_tree:
            self._add_fingerprints(self._base_tree["children"][0], self._base_tree)

        if self._content == TreeContent.MOLECULES:
            self._base_tree = self._remove_children_nodes(self._base_tree)
        elif not single_node_tree and self._content == TreeContent.REACTIONS:
            self._base_tree = self._remove_children_nodes(
                self._base_tree["children"][0]
            )

        self._trees = []
        self._tree_count, self._node_index_list = self._inspect_tree()
        self._enumeration = self._tree_count <= exhaustive_limit

        if self._enumeration:
            self._create_all_trees()
        else:
            self._trees.append(self._base_tree)

        self._dist_func = dist_func
示例#13
0
def add_fingerprints(
    tree: StrDict,
    radius: int = 2,
    nbits: int = defaults.FP_SIZE,
) -> None:
    """
    Add Morgan fingerprints to the input tree

    :param tree: the input tree
    :param radius: the radius of the Morgan calculation
    :param nbits: the length of the bitvector
    """
    mol = Chem.MolFromSmiles(tree["smiles"])
    rd_fp = AllChem.GetMorganFingerprintAsBitVect(mol,
                                                  radius=radius,
                                                  nBits=nbits,
                                                  useFeatures=False,
                                                  useChirality=True)
    np_fp = np.empty(radius, np.int8)
    DataStructs.ConvertToNumpyArray(rd_fp, np_fp)
    tree["fingerprint"] = np_fp
    for child in tree.get("children", []):
        add_fingerprints(child, radius, nbits)