forked from enviPath/enviPy
[Feature] Generate Compounds by Molfile (#423)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#423
This commit is contained in:
148
epdb/models.py
148
epdb/models.py
@ -31,7 +31,10 @@ from sklearn.model_selection import ShuffleSplit
|
||||
|
||||
from bridge.contracts import Property
|
||||
from bridge.dto import RunResult, PropertyPrediction
|
||||
from epdb.exceptions import InvalidSMILESException
|
||||
from epdb.exceptions import (
|
||||
InvalidMolfileException,
|
||||
InvalidSMILESException,
|
||||
)
|
||||
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
|
||||
from utilities.ml import (
|
||||
ApplicabilityDomainPCA,
|
||||
@ -789,6 +792,13 @@ class Compound(
|
||||
|
||||
external_identifiers = GenericRelation("ExternalIdentifier")
|
||||
|
||||
def get_structure_by_smiles(self, smiles: str) -> "CompoundStructure":
|
||||
for struct in self.structures.all():
|
||||
if struct.smiles == smiles:
|
||||
return struct
|
||||
|
||||
raise ValueError(f"No structure with SMILES {smiles} found for {self.get_name()}")
|
||||
|
||||
@property
|
||||
def structures(self) -> QuerySet:
|
||||
return CompoundStructure.objects.filter(compound=self)
|
||||
@ -857,8 +867,24 @@ class Compound(
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create(
|
||||
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs
|
||||
package: "Package",
|
||||
smiles: str,
|
||||
molfile: str | None = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "Compound":
|
||||
# Molfile has precendence over SMILES
|
||||
if molfile is not None and molfile.strip() != "":
|
||||
mol = FormatConverter.from_molfile(molfile)
|
||||
|
||||
if mol is None:
|
||||
raise InvalidMolfileException("Given molfile is invalid")
|
||||
else:
|
||||
# Overwrite SMILES from molfile
|
||||
smiles = FormatConverter.to_smiles(mol)
|
||||
|
||||
if smiles is None or smiles.strip() == "":
|
||||
raise InvalidSMILESException("SMILES is required")
|
||||
|
||||
@ -878,11 +904,16 @@ class Compound(
|
||||
if CompoundStructure.objects.filter(
|
||||
smiles=standardized_smiles, compound__package=package
|
||||
).exists():
|
||||
# TODO should we add a structure?
|
||||
return CompoundStructure.objects.get(
|
||||
found_c = CompoundStructure.objects.get(
|
||||
smiles=standardized_smiles, compound__package=package
|
||||
).compound
|
||||
|
||||
# As a direct match wasn't found add a new structure
|
||||
# TODO return newly created structure
|
||||
_ = found_c.add_structure(smiles, molfile=molfile, name=name, description=description)
|
||||
|
||||
return found_c
|
||||
|
||||
# Generate Compound
|
||||
c = Compound()
|
||||
c.package = package
|
||||
@ -914,7 +945,12 @@ class Compound(
|
||||
)
|
||||
|
||||
cs = CompoundStructure.create(
|
||||
c, smiles, name=name, description=description, normalized_structure=is_standardized
|
||||
c,
|
||||
smiles,
|
||||
molfile=molfile,
|
||||
name=name,
|
||||
description=description,
|
||||
normalized_structure=is_standardized,
|
||||
)
|
||||
|
||||
c.default_structure = cs
|
||||
@ -927,11 +963,22 @@ class Compound(
|
||||
self,
|
||||
smiles: str,
|
||||
name: str = None,
|
||||
molfile: str = None,
|
||||
description: str = None,
|
||||
default_structure: bool = False,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "CompoundStructure":
|
||||
# Molfile has precendence over SMILES
|
||||
if molfile is not None and molfile.strip() != "":
|
||||
mol = FormatConverter.from_molfile(molfile)
|
||||
|
||||
if mol is None:
|
||||
raise InvalidMolfileException("Given molfile is invalid")
|
||||
else:
|
||||
# Overwrite SMILES from molfile
|
||||
smiles = FormatConverter.to_smiles(mol)
|
||||
|
||||
if smiles is None or smiles == "":
|
||||
raise ValueError("SMILES is required")
|
||||
|
||||
@ -951,16 +998,28 @@ class Compound(
|
||||
)
|
||||
|
||||
if is_standardized:
|
||||
CompoundStructure.objects.get(smiles__in=smiles, compound__package=self.package)
|
||||
CompoundStructure.objects.get(smiles=smiles, compound__package=self.package)
|
||||
|
||||
# Check if we find a direct match for a given SMILES and/or its standardized SMILES
|
||||
if CompoundStructure.objects.filter(
|
||||
smiles__in=smiles, compound__package=self.package
|
||||
).exists():
|
||||
return CompoundStructure.objects.get(smiles__in=smiles, compound__package=self.package)
|
||||
if CompoundStructure.objects.filter(smiles=smiles, compound__package=self.package).exists():
|
||||
found_cs = CompoundStructure.objects.get(smiles=smiles, compound__package=self.package)
|
||||
|
||||
if found_cs.molfile is None and (molfile is not None and molfile.strip() != ""):
|
||||
logger.info(
|
||||
f"Setting molfile for found CompoundStructure(uuid={found_cs.uuid}) as it was empty"
|
||||
)
|
||||
found_cs.molfile = molfile
|
||||
found_cs.save()
|
||||
|
||||
return found_cs
|
||||
|
||||
cs = CompoundStructure.create(
|
||||
self, smiles, name=name, description=description, normalized_structure=is_standardized
|
||||
self,
|
||||
smiles,
|
||||
name=name,
|
||||
molfile=molfile,
|
||||
description=description,
|
||||
normalized_structure=is_standardized,
|
||||
)
|
||||
|
||||
if default_structure:
|
||||
@ -1141,10 +1200,35 @@ class CompoundStructure(
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create(
|
||||
compound: Compound, smiles: str, name: str = None, description: str = None, *args, **kwargs
|
||||
compound: Compound,
|
||||
smiles: str,
|
||||
molfile: str = None,
|
||||
name: str = None,
|
||||
description: str = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
# Molfile has precendence over SMILES
|
||||
if molfile is not None and molfile.strip() != "":
|
||||
mol = FormatConverter.from_molfile(molfile)
|
||||
|
||||
if mol is None:
|
||||
raise InvalidMolfileException("Given molfile is invalid")
|
||||
else:
|
||||
# Overwrite SMILES from molfile
|
||||
smiles = FormatConverter.to_smiles(mol)
|
||||
|
||||
if CompoundStructure.objects.filter(compound=compound, smiles=smiles).exists():
|
||||
return CompoundStructure.objects.get(compound=compound, smiles=smiles)
|
||||
found_cs = CompoundStructure.objects.get(smiles=smiles, compound=compound)
|
||||
|
||||
if found_cs.molfile is None and (molfile is not None and molfile.strip() != ""):
|
||||
logger.info(
|
||||
f"Setting molfile for found CompoundStructure(uuid={found_cs.uuid}) as it was empty"
|
||||
)
|
||||
found_cs.molfile = molfile
|
||||
found_cs.save()
|
||||
|
||||
return found_cs
|
||||
|
||||
if compound.pk is None:
|
||||
raise ValueError("Unpersisted Compound! Persist compound first!")
|
||||
@ -2174,11 +2258,12 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
||||
def add_node(
|
||||
self,
|
||||
smiles: str,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
depth: Optional[int] = -1,
|
||||
molfile: str | None = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
depth: int = -1,
|
||||
):
|
||||
return Node.create(self, smiles, depth, name=name, description=description)
|
||||
return Node.create(self, smiles, depth, molfile=molfile, name=name, description=description)
|
||||
|
||||
@transaction.atomic
|
||||
def add_edge(
|
||||
@ -2336,28 +2421,43 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
||||
pathway: "Pathway",
|
||||
smiles: str,
|
||||
depth: int,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
molfile: str | None = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
):
|
||||
# Molfile has precendence over SMILES
|
||||
if molfile is not None and molfile.strip() != "":
|
||||
mol = FormatConverter.from_molfile(molfile)
|
||||
|
||||
if mol is None:
|
||||
raise InvalidMolfileException("Given molfile is invalid")
|
||||
else:
|
||||
# Overwrite SMILES from molfile
|
||||
smiles = FormatConverter.to_smiles(mol)
|
||||
|
||||
stereo_removed = False
|
||||
if pathway.predicted and FormatConverter.has_stereo(smiles):
|
||||
smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
||||
stereo_removed = True
|
||||
|
||||
c = Compound.create(pathway.package, smiles, name=name, description=description)
|
||||
c = Compound.create(
|
||||
pathway.package, smiles, molfile=molfile, name=name, description=description
|
||||
)
|
||||
|
||||
if Node.objects.filter(pathway=pathway, default_node_label=c.default_structure).exists():
|
||||
return Node.objects.get(pathway=pathway, default_node_label=c.default_structure)
|
||||
structure = c.get_structure_by_smiles(smiles)
|
||||
|
||||
if Node.objects.filter(pathway=pathway, default_node_label=structure).exists():
|
||||
return Node.objects.get(pathway=pathway, default_node_label=structure)
|
||||
|
||||
n = Node()
|
||||
n.stereo_removed = stereo_removed
|
||||
n.pathway = pathway
|
||||
n.depth = depth
|
||||
|
||||
n.default_node_label = c.default_structure
|
||||
n.default_node_label = structure
|
||||
n.save()
|
||||
|
||||
n.node_labels.add(c.default_structure)
|
||||
n.node_labels.add(structure)
|
||||
n.save()
|
||||
|
||||
return n
|
||||
|
||||
Reference in New Issue
Block a user