Skip to content

Commit

Permalink
Prepare minor release
Browse files Browse the repository at this point in the history
  • Loading branch information
SGenheden committed Dec 21, 2021
1 parent 42f83c9 commit 543ee45
Show file tree
Hide file tree
Showing 23 changed files with 2,338 additions and 1,300 deletions.
32 changes: 27 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
# CHANGELOG

## Version 3.0.0 2020-07-26
## Version 3.1.0 2021-12-21

### Features

- ReactantsCountFiler (Github issue 42) to filter reactions with incompatible number of reactants
- ForwardRegenerationFiler to filter reactions where the forward reaction is incompatible
- Possible to skip quick Keras filter for specific policies
- Possible to select more than one policy in the GUI application
- Reaction classes has a hash function
- Possible to extract sub trees from ReactionTree objects
- RDKit can be used instead of RDChiral for expansions

### Bugfixes

- Possible to use more than depth 6 in the GUI application
- Fix failure in MctsNode class when expansion policy return no molecules

### Trivial changes

- Update type hints to be compatible with latest numpy release
- Update route-distances dependency

## Version 3.0.0 2021-07-26

### Features

Expand Down Expand Up @@ -32,7 +54,7 @@
- Documentation updates
- Extensive re-factoring of test cases

## Version 2.6.0 - 2020-06-11 (2020-05-03)
## Version 2.6.0 - 2021-06-11 (2021-05-03)

### Features

Expand All @@ -42,7 +64,7 @@
- Reaction tree objects now has property `is_branched`


## Version 2.5.0 - 2020-06-11 (2020-03-30)
## Version 2.5.0 - 2021-06-11 (2021-03-30)

### Features

Expand All @@ -58,7 +80,7 @@

- `scikit-learn` is now imported before `tensorflow`, according to Github issue 30

## Version 2.4.0 - 2020-02-22 (2020-02-22)
## Version 2.4.0 - 2021-02-22 (2021-02-22)


### Features
Expand All @@ -72,7 +94,7 @@
- Update of black version causing some re-formatting
- Small changes to documentation

## Version 2.3.0 - 2020-02-22 (2020-01-20)
## Version 2.3.0 - 2021-02-22 (2021-01-20)

### Features

Expand Down
9 changes: 8 additions & 1 deletion aizynthfinder/aizynthfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,14 @@ def build_routes(
raise ValueError("Search tree not initialized")

self.analysis = TreeAnalysis(self.tree, scorer=self.scorers[scorer])
self.routes = RouteCollection.from_analysis(self.analysis, selection)
config_selection = RouteSelectionArguments(
nmin=self.config.post_processing.min_routes,
nmax=self.config.post_processing.max_routes,
return_all=self.config.post_processing.all_routes,
)
self.routes = RouteCollection.from_analysis(
self.analysis, selection or config_selection
)

def extract_statistics(self) -> StrDict:
"""Extracts tree statistics as a dictionary"""
Expand Down
52 changes: 48 additions & 4 deletions aizynthfinder/chem/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
RdReaction,
StrDict,
Iterable,
Any,
)
from aizynthfinder.chem.mol import UniqueMolecule

Expand All @@ -46,7 +47,7 @@ def fingerprint(self, radius: int, nbits: int = None) -> np.ndarray:
reactants_fp = sum(
mol.fingerprint(radius, nbits) for mol in self._reactants_getter() # type: ignore
)
return reactants_fp - product_fp
return reactants_fp - product_fp # type: ignore

def hash_list(self) -> List[str]:
"""
Expand All @@ -57,6 +58,19 @@ def hash_list(self) -> List[str]:
mols = self.reaction_smiles().replace(".", ">>").split(">>")
return [hashlib.sha224(mol.encode("utf8")).hexdigest() for mol in mols]

def hash_key(self) -> str:
"""
Return a code that can be use to identify the reaction
:return: the hash code
"""
reactants = sorted([mol.inchi_key for mol in self._reactants_getter()]) # type: ignore
products = sorted([mol.inchi_key for mol in self._products_getter()]) # type: ignore
hash_ = hashlib.sha224()
for item in reactants + [">>"] + products:
hash_.update(item.encode())
return hash_.hexdigest()

def rd_reaction_from_smiles(self) -> RdReaction:
"""
The reaction as a RDkit reaction object but created from the reaction smiles
Expand Down Expand Up @@ -199,7 +213,7 @@ class RetroReaction(abc.ABC, _ReactionInterfaceMixin):
_required_kwargs: List[str] = []

def __init__(
self, mol: TreeMolecule, index: int = 0, metadata: StrDict = None, **kwargs: str
self, mol: TreeMolecule, index: int = 0, metadata: StrDict = None, **kwargs: Any
) -> None:
if any(name not in kwargs for name in self._required_kwargs):
raise KeyError(
Expand Down Expand Up @@ -297,10 +311,11 @@ class TemplatedRetroReaction(RetroReaction):
_required_kwargs = ["smarts"]

def __init__(
self, mol: TreeMolecule, index: int = 0, metadata: StrDict = None, **kwargs: str
self, mol: TreeMolecule, index: int = 0, metadata: StrDict = None, **kwargs: Any
):
super().__init__(mol, index, metadata, **kwargs)
self.smarts: str = kwargs["smarts"]
self._use_rdchiral: bool = kwargs.get("use_rdchiral", True)
self._rd_reaction: Optional[RdReaction] = None

def __str__(self) -> str:
Expand Down Expand Up @@ -331,6 +346,11 @@ def to_dict(self) -> StrDict:
return dict_

def _apply(self) -> Tuple[Tuple[TreeMolecule, ...], ...]:
if self._use_rdchiral:
return self._apply_with_rdchiral()
return self._apply_with_rdkit()

def _apply_with_rdchiral(self) -> Tuple[Tuple[TreeMolecule, ...], ...]:
"""
Apply a reactions smarts to a molecule and return the products (reactants for retro templates)
Will try to sanitize the reactants, and if that fails it will not return that molecule
Expand Down Expand Up @@ -362,6 +382,30 @@ def _apply(self) -> Tuple[Tuple[TreeMolecule, ...], ...]:

return self._reactants

def _apply_with_rdkit(self) -> Tuple[Tuple[TreeMolecule, ...], ...]:
rxn = AllChem.ReactionFromSmarts(self.smarts)
try:
self.mol.sanitize()
except MoleculeException:
reactants_list = []
else:
reactants_list = rxn.RunReactants([self.mol.rd_mol])

outcomes = []
for reactants in reactants_list:
try:
mols = tuple(
TreeMolecule(parent=self.mol, rd_mol=mol, sanitize=True)
for mol in reactants
)
except MoleculeException:
pass
else:
outcomes.append(mols)
self._reactants = tuple(outcomes)

return self._reactants

def _make_smiles(self):
return AllChem.ReactionToSmiles(self.rd_reaction)

Expand All @@ -381,7 +425,7 @@ class SmilesBasedRetroReaction(RetroReaction):
_required_kwargs = ["reactants_str"]

def __init__(
self, mol: TreeMolecule, index: int = 0, metadata: StrDict = None, **kwargs: str
self, mol: TreeMolecule, index: int = 0, metadata: StrDict = None, **kwargs: Any
):
super().__init__(mol, index, metadata, **kwargs)
self.reactants_str: str = kwargs["reactants_str"]
Expand Down
22 changes: 19 additions & 3 deletions aizynthfinder/context/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@


if TYPE_CHECKING:
from aizynthfinder.utils.type_utils import StrDict, Any, Dict, Union
from aizynthfinder.utils.type_utils import StrDict, Any, Dict, Union, Optional


@dataclass
class _PostprocessingConfiguration:
min_routes: int = 5
max_routes: int = 25
all_routes: bool = False
route_distance_model: Optional[str] = None


@dataclass
Expand All @@ -36,6 +44,7 @@ class Configuration: # pylint: disable=R0902
cutoff_cumulative: float = 0.995
cutoff_number: int = 50
additive_expansion: bool = False
use_rdchiral: bool = True
max_transforms: int = 6
default_prior: float = 0.5
use_prior: bool = True
Expand All @@ -48,6 +57,7 @@ class Configuration: # pylint: disable=R0902
prune_cycles_in_search: bool = True
use_remote_models: bool = False
search_algorithm: str = "mcts"
post_processing: _PostprocessingConfiguration = _PostprocessingConfiguration()
stock: Stock = None # type: ignore
expansion_policy: ExpansionPolicy = None # type: ignore
filter_policy: FilterPolicy = None # type: ignore
Expand Down Expand Up @@ -107,8 +117,7 @@ def from_file(cls, filename: str) -> "Configuration":

@property
def properties(self) -> Dict[str, Union[int, float, str, bool]]:
""" Return the basic properties of the config as a dictionary
"""
"""Return the basic properties of the config as a dictionary"""
dict_ = {}
for item in dir(self):
if item == "properties" or item.startswith("_"):
Expand All @@ -132,6 +141,10 @@ def properties(self, dict_: Dict[str, Union[int, float, str, bool]]) -> None:
continue
if not hasattr(self, setting):
raise AttributeError(f"Could not find attribute to set: {setting}")
if not isinstance(value, (int, float, str, bool)):
raise ValueError(
f"Trying to set property with invalid value {setting}={value}"
)
setattr(self, setting, value)
self._logger.info(f"Setting {setting.replace('_', ' ')} to {value}")

Expand All @@ -141,4 +154,7 @@ def _update_from_config(self, config: StrDict) -> None:
dict_.update(config.get("policy", {}).pop("properties", {}))
dict_.update(config.get("filter", {}).pop("properties", {}))
dict_.update(config.pop("properties", {}))
self.post_processing = _PostprocessingConfiguration(
**dict_.pop("post_processing", {})
)
self.properties = dict_
3 changes: 2 additions & 1 deletion aizynthfinder/context/policy/expansion_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def get_actions(
mol,
smarts=move[self._config.template_column],
metadata=metadata,
use_rdchiral=self._config.use_rdchiral,
)
)
return possible_actions, priors # type: ignore
Expand All @@ -148,7 +149,7 @@ def _cutoff_predictions(self, predictions: np.ndarray) -> np.ndarray:
sortidx = np.argsort(predictions)[::-1]
cumsum: np.ndarray = np.cumsum(predictions[sortidx])
if any(cumsum >= self._config.cutoff_cumulative):
maxidx = np.argmin(cumsum < self._config.cutoff_cumulative)
maxidx = int(np.argmin(cumsum < self._config.cutoff_cumulative))
else:
maxidx = len(cumsum)
maxidx = min(maxidx, self._config.cutoff_number) or 1
Expand Down
6 changes: 6 additions & 0 deletions aizynthfinder/context/policy/filter_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,14 @@ def __init__(self, key: str, config: Configuration, **kwargs: Any) -> None:
self.model = load_model(source, key, self._config.use_remote_models)
self._prod_fp_name = kwargs.get("prod_fp_name", "input_1")
self._rxn_fp_name = kwargs.get("rxn_fp_name", "input_2")
self._exclude_from_policy: List[str] = kwargs.get(
"exclude_from_policy", []
)

def apply(self, reaction: RetroReaction) -> None:
if reaction.metadata.get("policy_name", "") in self._exclude_from_policy:
return

feasible, prob = self.feasibility(reaction)
if not feasible:
raise RejectionException(f"{reaction} was filtered out with prob {prob}")
Expand Down
21 changes: 15 additions & 6 deletions aizynthfinder/interfaces/aizynthapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Dropdown,
BoundedIntText,
BoundedFloatText,
SelectMultiple,
)
from rdkit import Chem
from IPython.display import display, HTML
Expand Down Expand Up @@ -116,16 +117,24 @@ def _create_input_widgets(self) -> None:
layout={"border": "1px solid silver"},
)

self._input["policy"] = widgets.Dropdown(
init_value = (
[self.finder.expansion_policy.items[0]]
if self.finder.expansion_policy
else []
)
self._input["policy"] = SelectMultiple(
options=self.finder.expansion_policy.items,
value=init_value,
description="Expansion Policy:",
style={"description_width": "initial"},
rows=min(len(self.finder.expansion_policy.items) + 1, 4),
)

self._input["filter"] = widgets.Dropdown(
options=["None"] + self.finder.filter_policy.items,
self._input["filter"] = SelectMultiple(
options=self.finder.filter_policy.items,
description="Filter Policy:",
style={"description_width": "initial"},
rows=min(len(self.finder.filter_policy.items) + 1, 4),
)

max_time_box = self._make_slider_input("time_limit", "Time (min)", 1, 120)
Expand Down Expand Up @@ -153,7 +162,7 @@ def _create_input_widgets(self) -> None:
self._input["max_transforms"] = BoundedIntText(
description="Max steps for substrates",
min=1,
max=6,
max=20,
value=self.finder.config.max_transforms,
style={"description_width": "initial"},
)
Expand Down Expand Up @@ -305,10 +314,10 @@ def _prepare_search(self) -> None:
atom_count_limits[cb_input.description] = value_input.value
self.finder.stock.set_stop_criteria({"counts": atom_count_limits})
self.finder.expansion_policy.select(self._input["policy"].value)
if self._input["filter"].value == "None":
if not self._input["filter"].value:
self.finder.filter_policy.deselect()
else:
self.finder.filter_policy.select(self._input["policy"].value)
self.finder.filter_policy.select(self._input["filter"].value)
self.finder.config.properties = {
"C": self._input["C"].value,
"max_transforms": self._input["max_transforms"].value,
Expand Down
Loading

0 comments on commit 543ee45

Please sign in to comment.