Skip to content

Commit

Permalink
Merge pull request #77 from xiaoruiDong/resonance
Browse files Browse the repository at this point in the history
Add a dedicated resonance module
  • Loading branch information
xiaoruiDong authored Nov 28, 2023
2 parents fb1a95b + a660267 commit 9978724
Show file tree
Hide file tree
Showing 18 changed files with 4,762 additions and 263 deletions.
2 changes: 1 addition & 1 deletion rdmc/mol.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def Copy(
Returns:
RDKitMol: a copied molecule
"""
new_mol = RDKitMol(Chem.RWMol(self._mol, quickCopy, confId=confId))
new_mol = RDKitMol(Chem.RWMol(self._mol, quickCopy, confId))
copy_attrs = copy_attrs or []
for attr in copy_attrs:
setattr(new_mol, attr, copy.deepcopy(getattr(self, attr)))
Expand Down
9 changes: 4 additions & 5 deletions rdmc/mol_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from rdkit.Chem.rdMolDescriptors import CalcMolFormula

from rdmc.resonance import generate_resonance_structures


def get_resonance_structure_match(
mol1_res: List["RDKitMol"],
Expand Down Expand Up @@ -121,9 +123,6 @@ def is_same_complex(
Returns:
bool: Whether the two complexes are the same.
"""
if resonance:
from rdmc.resonance import generate_radical_resonance_structures

if not isinstance(complex1, (list, tuple)):
complex1 = list(complex1.GetMolFrags(asMols=True))
if not isinstance(complex2, (list, tuple)):
Expand All @@ -140,7 +139,7 @@ def is_same_complex(

for mol1 in mol1s:
mol1_res = (
generate_radical_resonance_structures(mol1[0], kekulize=True)
generate_resonance_structures(mol1[0])
if resonance
else [mol1[0]]
)
Expand All @@ -153,7 +152,7 @@ def is_same_complex(
mol2_res = mol2_res_dict.get(i)
if mol2_res is None:
mol2_res = (
generate_radical_resonance_structures(mol2[0], kekulize=True)
generate_resonance_structures(mol2[0])
if resonance
else [mol2[0]]
)
Expand Down
11 changes: 5 additions & 6 deletions rdmc/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from rdmc import RDKitMol
from rdmc.mol_compare import is_same_complex, is_equivalent_reaction
from rdmc.resonance import generate_radical_resonance_structures
from rdmc.resonance import generate_resonance_structures
from rdmc.ts import get_all_changing_bonds


Expand Down Expand Up @@ -339,7 +339,6 @@ def is_resonance_corrected(self) -> bool:
def apply_resonance_correction(
self,
inplace: bool = True,
kekulize: bool = True,
) -> "Reaction":
"""
Apply resonance correction to the reactant and product complexes.
Expand All @@ -350,14 +349,14 @@ def apply_resonance_correction(
# TODO: when the reactant and product are changed
return self
try:
rcps = generate_radical_resonance_structures(
self.reactant_complex, kekulize=kekulize
rcps = generate_resonance_structures(
self.reactant_complex,
)
except BaseException:
rcps = [self.reactant_complex]
try:
pcps = generate_radical_resonance_structures(
self.product_complex, kekulize=kekulize
pcps = generate_resonance_structures(
self.product_complex,
)
except BaseException:
pcps = [self.product_complex]
Expand Down
5 changes: 4 additions & 1 deletion rdmc/resonance/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from rdmc.resonance.resonance import generate_radical_resonance_structures
from rdmc.resonance.base import generate_resonance_structures
# Make sure resonance algorithms are registered
import rdmc.resonance.rdkit
import rdmc.resonance.rmg
94 changes: 94 additions & 0 deletions rdmc/resonance/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from rdmc.resonance.utils import is_equivalent_structure


class ResonanceAlgoRegistry:
_registry = {}

@classmethod
def register(cls, name: str):
def decorator(some_class):
cls._registry[name] = some_class
return some_class

return decorator

@classmethod
def get(cls, name: str):
return cls._registry.get(name)


def _merge_resonance_structures(
known_structs: list,
new_structs: list,
keep_isomorphic: bool = False,
):
"""
Merge resonance structures by removing duplicates.
This is only used in combining resonance structures from different backends.
Args:
known_structs (list): A list of known resonance structures. This list will be modified in place.
new_structs (list): A list of new resonance structures.
"""
if len(new_structs) <= 1:
# The new algorithm failed or only return the original molecule
return

structs_to_add = []
# Each method has its own de-duplicate method
# so don't append to known structs until all checked
for new_struct in new_structs:
for known_struct in known_structs:
if is_equivalent_structure(
ref_mol=known_struct,
qry_mol=new_struct,
isomorphic_equivalent=not keep_isomorphic,
):
break
else:
structs_to_add.append(new_struct)
known_structs.extend(structs_to_add)


def generate_resonance_structures(
mol: "Chem.RWMol",
keep_isomorphic: bool = False,
copy: bool = True,
backend: str = "all",
**kwargs,
):
"""
Generate resonance structures for a molecule.
"""
if backend == "all":
algos = list(ResonanceAlgoRegistry._registry.values())
known_structs = algos[0](
mol=mol,
keep_isomorphic=keep_isomorphic,
copy=copy,
**kwargs,
)
for algo in algos[1:]:
new_structs = algo(
mol=mol,
keep_isomorphic=keep_isomorphic,
copy=copy,
**kwargs,
)
_merge_resonance_structures(known_structs, new_structs)

return known_structs

algo = ResonanceAlgoRegistry.get(backend)
if algo:
return algo(
mol=mol,
keep_isomorphic=keep_isomorphic,
copy=copy,
**kwargs,
)
else:
raise ValueError(f"Invalid backend {backend}")
Loading

0 comments on commit 9978724

Please sign in to comment.