from copy import deepcopy
from typing import List
from rdkit import Chem
from icolos.core.step_utils.obabel_structconvert import OBabelStructConvert
from icolos.utils.enums.compound_enums import (
CompoundContainerEnum,
EnumerationContainerEnum,
)
from icolos.utils.enums.program_parameters import SchrodingerExecutablesEnum
from icolos.core.step_utils.structconvert import StructConvert
from icolos.utils.general.icolos_exceptions import ContainerCorrupted
from icolos.utils.enums.write_out_enums import WriteOutEnum
from typing import Union
import numpy as np
import os
_WE = WriteOutEnum()
[docs]class Enumeration:
"""This class bundles all information on an enumeration, especially all conformers generated."""
def __init__(
self,
compound_object=None,
smile: str = "",
molecule: Chem.Mol = None,
original_smile: str = None,
enumeration_id: int = None,
):
self._MC = CompoundContainerEnum()
self._EC = EnumerationContainerEnum()
self._smile = smile
self._compound_object = compound_object
self._molecule = molecule
self._original_smile = original_smile
self._enumeration_id = enumeration_id
self._conformers = []
[docs] def empty(self) -> bool:
if len(self.get_conformers()) == 0:
return True
return False
[docs] def get_compound_name(self) -> str:
if self.get_compound_object() is not None:
return self.get_compound_object().get_name()
def _get_next_conformer_id(self) -> int:
ids = [conf.get_conformer_id() for conf in self.get_conformers()]
if len(ids) == 0:
return 0
else:
return max(ids) + 1
# for ligand in self.ligands:
# ligand.set_conformers(sorted(ligand.get_conformers(),
# key=lambda x: float(x.GetProp(_ROE.GLIDE_DOCKING_SCORE)), reverse=False))
# ligand.add_tags_to_conformers()
[docs] def get_index_string(self) -> str:
comp_obj = self.get_compound_object()
comp_str = ""
if comp_obj is not None:
comp_str = comp_obj.get_index_string()
enum_str = ""
if self.get_enumeration_id() is not None:
enum_str = str(self.get_enumeration_id())
return ":".join([comp_str, enum_str])
[docs] def clear_molecule(self):
self._molecule = None
[docs] def set_compound_object(self, compound_object):
self._compound_object = compound_object
[docs] def get_compound_object(self):
return self._compound_object
[docs] def set_enumeration_id(self, enumeration_id: int):
self._enumeration_id = enumeration_id
[docs] def get_enumeration_id(self) -> int:
return self._enumeration_id
[docs] def set_smile(self, smile: str):
self._smile = smile
[docs] def get_smile(self) -> str:
return self._smile
[docs] def set_molecule(self, molecule: Chem.Mol):
self._molecule = molecule
[docs] def get_molecule(self) -> Chem.Mol:
return self._molecule
[docs] def set_original_smile(self, original_smile: str):
self._original_smile = original_smile
[docs] def get_original_smile(self) -> str:
return self._original_smile
def _clone(self):
clone = Enumeration(
compound_object=self.get_compound_object(),
smile=self.get_smile(),
molecule=deepcopy(self.get_molecule()),
original_smile=self.get_original_smile(),
enumeration_id=self.get_enumeration_id(),
)
for conf in self.get_conformers():
conf = deepcopy(conf)
conf.set_enumeration_object(enumeration_object=clone)
clone.add_conformer(conf, auto_update=False)
return clone
def __copy__(self):
return self._clone()
def __deepcopy__(self, memo):
return self._clone()
def __repr__(self):
parent_compound_id = (
None
if self.get_compound_object() is None
else self.get_compound_object().get_compound_number()
)
return (
"<Icolos enumeration: id=%s, smile=%s, parent compound: %s, num_conformers: %i>"
% (
self.get_enumeration_id(),
self.get_smile(),
parent_compound_id,
len(self._conformers),
)
)
def __str__(self):
return self.__repr__()
def __iter__(self):
return iter(self._conformers)
def __getitem__(self, key: int) -> Conformer:
return self._conformers[key]
def __len__(self) -> int:
return len(self.get_conformers())
[docs]class Compound:
"""This class bundles all information on a molecule and serves mainly to group enumerations."""
def __init__(self, name: str = "", compound_number: int = None):
self._CC = CompoundContainerEnum()
self._EC = EnumerationContainerEnum()
self._name = name
self._compound_number = compound_number
self._enumerations = []
def __repr__(self):
return "<Icolos compound: name=%s, compound_number=%s, enumerations=%s>" % (
self.get_name(),
self.get_compound_number(),
len(self.get_enumerations()),
)
def __str__(self):
return self.__repr__()
[docs] def get_index_string(self) -> str:
if self.get_compound_number() is not None:
return str(self.get_compound_number())
else:
return ""
[docs] def set_name(self, name: str):
self._name = name
[docs] def get_name(self) -> str:
return self._name
[docs] def set_compound_number(self, compound_number: int):
self._compound_number = compound_number
[docs] def get_compound_number(self) -> int:
return self._compound_number
[docs] def add_enumeration(self, enumeration: Enumeration, auto_update: bool = True):
"""Add a new enumeration. If "auto_update" is True, the Compound class will be set to "self" and
the enumeration_id will be set to the next free index."""
enumeration = deepcopy(enumeration)
if auto_update:
enumeration.set_compound_object(self)
enumeration.set_enumeration_id(self._get_next_enumeration_id())
self._enumerations.append(enumeration)
[docs] def add_enumerations(
self, enumerations: List[Enumeration], auto_update: bool = True
):
"""Add new enumerations. If "auto_update" is True, the Compound class will be set to "self" and
the enumeration_id will be set to the next free index."""
for enumeration in enumerations:
self.add_enumeration(enumeration=enumeration, auto_update=auto_update)
[docs] def clear_enumerations(self):
self._enumerations = []
def find_enumeration(self, idx: int):
for enum in self.get_enumerations():
if enum.get_enumeration_id() == idx:
return enum
[docs] def get_enumerations(self) -> List[Enumeration]:
return self._enumerations
def _clone(self):
clone = Compound(
name=self.get_name(), compound_number=self.get_compound_number()
)
for enum in self.get_enumerations():
enum = deepcopy(enum)
enum.set_compound_object(compound_object=clone)
clone.add_enumeration(enum, auto_update=False)
return clone
def __iter__(self):
return iter(self._enumerations)
def __copy__(self):
return self._clone()
def __deepcopy__(self, memo):
return self._clone()
def __getitem__(self, key: int) -> Enumeration:
return self._enumerations[key]
def __len__(self) -> int:
return len(self.get_enumerations())
def _get_next_enumeration_id(self):
ids = [enum.get_enumeration_id() for enum in self.get_enumerations()]
if len(ids) == 0:
return 0
else:
return max(ids) + 1
[docs] def find_enumeration(self, enumeration_id: int) -> Enumeration:
enum = [
enum
for enum in self.get_enumerations()
if enum.get_enumeration_id() == enumeration_id
]
if len(enum) == 0:
raise IndexError(f"Could not find enumeration with id {enumeration_id}.")
elif len(enum) > 1:
raise ContainerCorrupted(
f"More than one enumeration with id {enumeration_id} found in the same Compound instance (compound_number: {self.get_compound_number()})."
)
return enum[0]
[docs] def get_enumeration_ids(self) -> List[int]:
ids = [enum.get_enumeration_id() for enum in self.get_enumerations()]
return ids
[docs] def reset_enumeration_ids(self):
for new_id, enum in enumerate(self.get_enumerations()):
enum.set_enumeration_id(enumeration_id=new_id)
[docs] def reset_all_ids(self):
self.reset_enumeration_ids()
for enum in self.get_enumerations():
enum.reset_conformer_ids()
[docs] def update_all_relations(self):
for enum in self.get_enumerations():
enum.set_compound_object(self)
for conf in enum.get_conformers():
conf.set_enumeration_object(enum)
[docs] def empty(self) -> bool:
if len(self.get_enumerations()) == 0:
return True
return False
# TODO: Replacing these three functions by a wrapper object
[docs]def get_compound_by_id(compounds: List[Compound], id: int) -> Compound:
for compound in compounds:
if compound.get_compound_number() == id:
return compound
raise ValueError(
f"Could not find compound with id {id} in list of length {len(compounds)}."
)
[docs]def get_compound_by_name(compounds: List[Compound], name: str) -> Compound:
for compound in compounds:
if compound.get_name() == name:
return compound
raise ValueError(
f"Could not find compound with name {name} in list of length {len(compounds)}."
)
[docs]def unroll_enumerations(compounds: List[Compound]) -> List[Enumeration]:
all_enumerations = []
for comp in compounds:
all_enumerations = all_enumerations + comp.get_enumerations()
return all_enumerations