Skip to content

Commit be9f7fc

Browse files
committed
Version 3.3.0 public release
1 parent 52243b9 commit be9f7fc

33 files changed

+2508
-8
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# CHANGELOG
22

3+
## Version 3.3.0 2022-02-24
4+
5+
### Features
6+
7+
- Support for Retro* tree search
8+
- Support for breadth-first exhaustive tree search
9+
- Support for depth-first proof-number tree search
10+
- Possible to save concatenated reaction trees to separate file
11+
12+
### Bugfixes
13+
14+
- RouteCostScorer fix for rare routes
15+
316
## Version 3.2.0 2022-02-24
417

518
### Features

aizynthfinder/context/cost/collection.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ def load_from_config(self, **costs_config: Any) -> None:
7171
)
7272
self._logger.info(f"Loaded cost: '{repr(obj)}'{config_str}")
7373
self._items[repr(obj)] = obj
74+
self.select_last()

aizynthfinder/context/scoring/scorers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def _score_node(self, node: MctsNode) -> float:
297297
updated_scores = {
298298
id(mol): scores[id(mol)]
299299
for mol in pnode.state.mols
300-
if mol != reaction.mol
300+
if mol is not reaction.mol
301301
}
302302
child_sum = sum(
303303
1 / self.average_yield * score

aizynthfinder/search/andor_trees.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ def __init__(
8383
else:
8484
self._sampling_cutoff = max_routes
8585
self._partition_search_tree(graph, root_node)
86-
self.routes = [
86+
routes_list = [
8787
ReactionTreeFromAndOrTrace(trace, stock).tree for trace in self._traces
8888
]
89+
routes_map = {route.hash_key(): route for route in routes_list}
90+
self.routes = list(routes_map.values())
8991

9092
def _partition_search_tree(self, graph: _AndOrTrace, node: TreeNodeMixin) -> None:
9193
# fmt: off
@@ -190,6 +192,8 @@ def _load(self, andor_trace: nx.DiGraph, stock: Stock) -> None: # type: ignore
190192
in_stock=self._trace_root.prop["mol"] in self._stock,
191193
)
192194
for node1, node2 in andor_trace.edges():
195+
if "reaction" in node2.prop and not andor_trace[node2]:
196+
continue
193197
rt_node1 = self._make_rt_node(node1)
194198
rt_node2 = self._make_rt_node(node2)
195199
self.tree.graph.add_edge(rt_node1, rt_node2)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
""" Sub-package containing breadth first routines
2+
"""
3+
from aizynthfinder.search.breadth_first.search_tree import SearchTree
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
""" Module containing a classes representation various tree nodes
2+
"""
3+
from __future__ import annotations
4+
from typing import TYPE_CHECKING
5+
6+
from aizynthfinder.chem import TreeMolecule
7+
from aizynthfinder.search.andor_trees import TreeNodeMixin
8+
from aizynthfinder.chem.serialization import deserialize_action, serialize_action
9+
10+
if TYPE_CHECKING:
11+
from aizynthfinder.context.config import Configuration
12+
from aizynthfinder.chem.serialization import (
13+
MoleculeDeserializer,
14+
MoleculeSerializer,
15+
)
16+
from aizynthfinder.utils.type_utils import (
17+
StrDict,
18+
Sequence,
19+
Set,
20+
List,
21+
)
22+
from aizynthfinder.chem import RetroReaction
23+
24+
25+
class MoleculeNode(TreeNodeMixin):
26+
"""
27+
An OR node representing a molecule
28+
29+
:ivar expandable: if True, this node is part of the frontier
30+
:ivar mol: the molecule represented by the node
31+
:ivar in_stock: if True the molecule is in stock and hence should not be expanded
32+
:ivar parent: the parent of the node
33+
34+
:param mol: the molecule to be represented by the node
35+
:param config: the configuration of the search
36+
:param parent: the parent of the node, optional
37+
"""
38+
39+
def __init__(
40+
self, mol: TreeMolecule, config: Configuration, parent: ReactionNode = None
41+
) -> None:
42+
self.mol = mol
43+
self._config = config
44+
self.in_stock = mol in config.stock
45+
self.parent = parent
46+
47+
self._children: List[ReactionNode] = []
48+
# Makes it unexpandable if we have reached maximum depth
49+
self.expandable = self.mol.transform <= self._config.max_transforms
50+
51+
if self.in_stock:
52+
self.expandable = False
53+
54+
@classmethod
55+
def create_root(cls, smiles: str, config: Configuration) -> "MoleculeNode":
56+
"""
57+
Create a root node for a tree using a SMILES.
58+
59+
:param smiles: the SMILES representation of the root state
60+
:param config: settings of the tree search algorithm
61+
:return: the created node
62+
"""
63+
mol = TreeMolecule(parent=None, transform=0, smiles=smiles)
64+
return MoleculeNode(mol=mol, config=config)
65+
66+
@classmethod
67+
def from_dict(
68+
cls,
69+
dict_: StrDict,
70+
config: Configuration,
71+
molecules: MoleculeDeserializer,
72+
parent: ReactionNode = None,
73+
) -> "MoleculeNode":
74+
"""
75+
Create a new node from a dictionary, i.e. deserialization
76+
77+
:param dict_: the serialized node
78+
:param config: settings of the tree search algorithm
79+
:param molecules: the deserialized molecules
80+
:param parent: the parent node
81+
:return: a deserialized node
82+
"""
83+
mol = molecules.get_tree_molecules([dict_["mol"]])[0]
84+
node = MoleculeNode(mol, config, parent)
85+
node.expandable = dict_["expandable"]
86+
node.children = [
87+
ReactionNode.from_dict(child, config, molecules, parent=node)
88+
for child in dict_["children"]
89+
]
90+
return node
91+
92+
@property # type: ignore
93+
def children(self) -> List[ReactionNode]: # type: ignore
94+
""" Gives the reaction children nodes """
95+
return self._children
96+
97+
@children.setter
98+
def children(self, value: List[ReactionNode]) -> None:
99+
self._children = value
100+
101+
@property
102+
def prop(self) -> StrDict:
103+
return {"solved": self.in_stock, "mol": self.mol}
104+
105+
def add_stub(self, reaction: RetroReaction) -> Sequence[MoleculeNode]:
106+
"""
107+
Add a stub / sub-tree to this node
108+
109+
:param reaction: the reaction creating the stub
110+
:return: list of all newly added molecular nodes
111+
"""
112+
reactants = reaction.reactants[reaction.index]
113+
if not reactants:
114+
return []
115+
116+
ancestors = self.ancestors()
117+
for mol in reactants:
118+
if mol in ancestors:
119+
return []
120+
121+
rxn_node = ReactionNode.create_stub(
122+
reaction=reaction, parent=self, config=self._config
123+
)
124+
self._children.append(rxn_node)
125+
126+
return rxn_node.children
127+
128+
def ancestors(self) -> Set[TreeMolecule]:
129+
"""
130+
Return the ancestors of this node
131+
132+
:return: the ancestors
133+
:rtype: set
134+
"""
135+
if not self.parent:
136+
return {self.mol}
137+
138+
ancestors = self.parent.parent.ancestors()
139+
ancestors.add(self.mol)
140+
return ancestors
141+
142+
def serialize(self, molecule_store: MoleculeSerializer) -> StrDict:
143+
"""
144+
Serialize the node object to a dictionary
145+
146+
:param molecule_store: the serialized molecules
147+
:return: the serialized node
148+
"""
149+
dict_: StrDict = {"expandable": self.expandable}
150+
dict_["mol"] = molecule_store[self.mol]
151+
dict_["children"] = [child.serialize(molecule_store) for child in self.children]
152+
return dict_
153+
154+
155+
class ReactionNode(TreeNodeMixin):
156+
"""
157+
An AND node representing a reaction
158+
159+
:ivar parent: the parent of the node
160+
:ivar reaction: the reaction represented by the node
161+
162+
:param cost: the cost of the reaction
163+
:param reaction: the reaction to be represented by the node
164+
:param parent: the parent of the node
165+
"""
166+
167+
def __init__(self, reaction: RetroReaction, parent: MoleculeNode) -> None:
168+
self.parent = parent
169+
self.reaction = reaction
170+
171+
self._children: List[MoleculeNode] = []
172+
173+
@classmethod
174+
def create_stub(
175+
cls,
176+
reaction: RetroReaction,
177+
parent: MoleculeNode,
178+
config: Configuration,
179+
) -> ReactionNode:
180+
"""
181+
Create a ReactionNode and creates all the MoleculeNode objects
182+
that are the children of the node.
183+
184+
:param reaction: the reaction to be represented by the node
185+
:param parent: the parent of the node
186+
:param config: the configuration of the search tree
187+
"""
188+
node = cls(reaction, parent)
189+
reactants = reaction.reactants[reaction.index]
190+
node.children = [
191+
MoleculeNode(mol=mol, config=config, parent=node) for mol in reactants
192+
]
193+
return node
194+
195+
@classmethod
196+
def from_dict(
197+
cls,
198+
dict_: StrDict,
199+
config: Configuration,
200+
molecules: MoleculeDeserializer,
201+
parent: MoleculeNode,
202+
) -> ReactionNode:
203+
"""
204+
Create a new node from a dictionary, i.e. deserialization
205+
206+
:param dict_: the serialized node
207+
:param config: the configuration of the tree search
208+
:param molecules: the deserialized molecules
209+
:param parent: the parent node
210+
:return: a deserialized node
211+
"""
212+
reaction = deserialize_action(dict_["reaction"], molecules)
213+
node = cls(reaction, parent)
214+
215+
node.children = [
216+
MoleculeNode.from_dict(child, config, molecules, parent=node)
217+
for child in dict_["children"]
218+
]
219+
return node
220+
221+
@property # type: ignore
222+
def children(self) -> List[MoleculeNode]: # type: ignore
223+
""" Gives the molecule children nodes """
224+
return self._children
225+
226+
@children.setter
227+
def children(self, value: List[MoleculeNode]) -> None:
228+
self._children = value
229+
230+
@property
231+
def prop(self) -> StrDict:
232+
return {"solved": False, "reaction": self.reaction}
233+
234+
def serialize(self, molecule_store: MoleculeSerializer) -> StrDict:
235+
"""
236+
Serialize the node object to a dictionary
237+
238+
:param molecule_store: the serialized molecules
239+
:return: the serialized node
240+
"""
241+
dict_ = {
242+
"reaction": serialize_action(self.reaction, molecule_store),
243+
"children": [child.serialize(molecule_store) for child in self.children],
244+
}
245+
return dict_

0 commit comments

Comments
 (0)