Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding intermediate generator to konnektor #51

Merged
merged 10 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/konnektor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# This code is part of OpenFE and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/konnektor

# basic gufe types:
from gufe import Component, SmallMoleculeComponent, ProteinComponent

# Konnektor content
from .network_planners import (
MaximalNetworkGenerator,
HeuristicMaximalNetworkGenerator,
Expand Down
2 changes: 2 additions & 0 deletions src/konnektor/network_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from .clustering.scaffold_clustering import ScaffoldClusterer
from .clustering.component_diversity_clustering import ComponentsDiversityClusterer

from .intermediate_generators.imerge_intermediator import ImergeIntermediator

from .network_handling import (
merge_two_networks,
merge_networks,
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import abc
import inspect
from typing import Iterator

from gufe.tokenization import GufeTokenizable
from gufe import SmallMoleculeComponent


class Intermediator(GufeTokenizable):
def __call__(self, *args, **kwargs) -> Iterator:
return self.generate_intermediate(*args, **kwargs)

@classmethod
def _defaults(cls):
sig = inspect.signature(cls.__init__)

defaults = {
param.name: param.default
for param in sig.parameters.values()
if param.default is not inspect.Parameter.empty
}

return defaults

@classmethod
def _from_dict(cls, dct: dict):
init_args = cls._defaults()
additional_vas = {}

for key, value in dct.items():
if key in init_args:
init_args.update({key: value})
else:
additional_vas[key] = value

new_obj = cls(**init_args)
[setattr(new_obj, key, value) for key, value in additional_vas.items()]

return new_obj

def _to_dict(self, include_defaults=True) -> dict:
self_dict = {k: v for k, v in vars(self).items() if not k.startswith("_")}

if include_defaults:
add_vars = {k: v for k, v in self.defaults().items() if k not in self_dict}
self_dict.update(add_vars)

return self_dict

@abc.abstractmethod
def generate_intermediate(
self, molA: SmallMoleculeComponent, molB: SmallMoleculeComponent
) -> Iterator[SmallMoleculeComponent]:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# This code is part of OpenFE and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/konnektor

from rdkit import Chem

try:
# R-group enumeration intermediate generator
from rgroupinterm import rgroupenumeration
from rgroupinterm import pruners
except:
pass

from gufe import SmallMoleculeComponent

from ._abstract_intermediator import Intermediator
from ...utils.optional_import import requires_package


@requires_package("rgroupinterm")
class ImergeIntermediator(Intermediator):
def __init__(
self,
enumerate_kekule: bool = False,
permutate: bool = False,
insert_small: bool = False,
):
"""

Parameters
----------
enumerate_kekule: bool, optional
(default: False)
permutate: bool, optional
(default: False)
insert_small: bool, optional
(default: False)
"""
self.enumerate_kekule = enumerate_kekule
self.permutate = permutate
self.insert_small = insert_small

def generate_intermediate(
self, molA: SmallMoleculeComponent, molB: SmallMoleculeComponent
) -> SmallMoleculeComponent:
"""
Uses the rgroupinterm package to generate a intermediate between molA and molB.

Parameters
----------
molA: SmallMoleculeComponent
molB: SmallMoleculeComponent

Returns
-------
Iterator[SmallMoleculeComponent]
returns the small molecule intermediate between molA and molB.

"""

rdmolA = Chem.MolFromSmiles(Chem.MolToSmiles(molA.to_rdkit()))
rdmolB = Chem.MolFromSmiles(Chem.MolToSmiles(molB.to_rdkit()))

# get rgroups
generator = rgroupenumeration.EnumRGroups(
self.enumerate_kekule, self.permutate, self.insert_small
)
df_interm, _ = generator.generate_intermediates([rdmolA, rdmolB])

# prune groups to get intermediates
pruner = pruners.BasePruner(
[
pruners.TanimotoScorer(
transformer=pruners.HarmonicMeanTransformer(exponent=4)
)
],
topn=1,
)

df_interm["Parent_1"] = rdmolA
df_interm["Parent_2"] = rdmolB
df_interm["Pair"] = 0
pruned_df = pruner(df_interm)

# intermediate generation
rd_mol_intermediate = pruned_df["Intermediate"].values[0]
rd_mol_intermediate = Chem.AddHs(rd_mol_intermediate)
Chem.AllChem.EmbedMolecule(rd_mol_intermediate)

mol_intermediate = SmallMoleculeComponent.from_rdkit(
rdkit=rd_mol_intermediate,
name=molA.name + "_" + molB.name + "_intermediate",
)

yield mol_intermediate
49 changes: 49 additions & 0 deletions src/konnektor/tests/network_tools/test_intermediator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This code is part of OpenFE and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/konnektor

import pytest

from rdkit import Chem
from rdkit.Chem import AllChem

from gufe import SmallMoleculeComponent
from ...network_tools import ImergeIntermediator


@pytest.fixture
def example_mols():
rdmolA = Chem.AddHs(
Chem.MolFromSmiles(
"CC(C)(C)C1=CC=C2N[C@@H](C3=CC=C(O)C=C3)[C@@H]3CCCO[C@@H]3C2=C1"
)
)
Chem.rdDistGeom.EmbedMolecule(rdmolA)
molA = SmallMoleculeComponent.from_rdkit(rdmolA, name="molA")
rdmolB = Chem.AddHs(
Chem.MolFromSmiles(
"[NH3+]C[C@H]1CC[C@@H]2[C@H](O1)C1=CC(C(F)(F)F)=CC=C1N[C@H]2C1=CC=CC=C1"
)
)
Chem.rdDistGeom.EmbedMolecule(rdmolB)
molB = SmallMoleculeComponent.from_rdkit(rdmolB, name="molB")
return molA, molB


@pytest.importorskip("rgroupinterm")
def test_imerge_imediator_gen_intermediate(example_mols):
molA, molB = example_mols

expected_rdmol = Chem.AddHs(
Chem.MolFromSmiles(
"CC(C)(C)c1ccc2c(c1)[C@H]1O[C@@H](C[NH3+])CC[C@H]1[C@H](c1ccc(O)cc1)N2"
)
)
Chem.rdDistGeom.EmbedMolecule(expected_rdmol)
expected_mol = SmallMoleculeComponent.from_rdkit(
expected_rdmol, name="intermediate"
)

intermediator = ImergeIntermediator()
intermediate_mol = next(intermediator(molA, molB))

assert expected_mol == intermediate_mol