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

Chemical identity comparison for Molecules and Species #1329

Merged
merged 20 commits into from May 22, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3da3c0e
Refactor some class properties using @property decorator
mliu49 Jan 25, 2018
db1e492
Refactor Molecule SMILES and InChI attributes
mliu49 Jan 25, 2018
aeaf3b5
Add read-only inchi property to Species
mliu49 Jan 25, 2018
3594fc5
Add read-only fingerprint property to Species
mliu49 Jan 25, 2018
010513e
Add read-only multiplicity property to Species
mliu49 Feb 5, 2018
1b9884b
Add unit tests for new Species properties
mliu49 Jan 26, 2018
3d3473f
Add strict argument for isomorphism comparison
mliu49 Aug 27, 2018
da870ca
Add strict argument to Species.isIsomorphic
mliu49 Apr 3, 2019
e36c78e
Remove generate_res argument from Species.isIsomorphic
mliu49 Apr 4, 2019
4422e9c
Rename isomorphic_species_lists to same_species_lists
mliu49 Apr 3, 2019
f2a2296
Add strict argument to Reaction.isIsomorphic
mliu49 Apr 3, 2019
75da1f3
Refactor CERM.checkForExistingSpecies using strict=False isomorphism
mliu49 Apr 4, 2019
de012ad
Refactor product checking in __generateReactions
mliu49 Sep 13, 2018
9e8a491
Revise test for prod_resonance option for generating reactions
mliu49 Sep 13, 2018
7b4604e
Add strict option to Graph.isMappingValid
mliu49 Apr 4, 2019
9052975
Add strict argument to isIdentical methods
mliu49 Apr 4, 2019
78f8a2d
Do not generate resonance structures for degeneracy determination
mliu49 Apr 4, 2019
4084b1e
Make sure selected molecule is reactive
mliu49 Oct 30, 2018
27baa4e
Enable Species instantiation by SMILES or InChI argument
mliu49 Apr 4, 2019
b3ff5c5
Fix reaction degeneracy bug with keep_isomorphic argument
mliu49 Apr 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 12 additions & 3 deletions rmgpy/molecule/group.py
Expand Up @@ -1010,9 +1010,18 @@ def draw(self, format):
img = graph.create(prog='neato', format=format)
return img

def __getAtoms(self): return self.vertices
def __setAtoms(self, atoms): self.vertices = atoms
atoms = property(__getAtoms, __setAtoms)
@property
def atoms(self):
"""
List of atoms contained in the current molecule.

Renames the inherited vertices attribute of :class:`Graph`.
"""
return self.vertices

@atoms.setter
def atoms(self, atoms):
self.vertices = atoms

def addAtom(self, atom):
"""
Expand Down
7 changes: 4 additions & 3 deletions rmgpy/molecule/molecule.pxd
Expand Up @@ -139,10 +139,11 @@ cdef class Molecule(Graph):
cdef public bint reactive
cdef public object rdMol
cdef public int rdMolConfId
cdef str _fingerprint
cdef public str InChI
cdef public dict props

cdef str _fingerprint
cdef str _inchi
cdef str _smiles

cpdef addAtom(self, Atom atom)

cpdef addBond(self, Bond bond)
Expand Down
79 changes: 59 additions & 20 deletions rmgpy/molecule/molecule.py
Expand Up @@ -780,33 +780,44 @@ class Molecule(Graph):
======================= =========== ========================================
Attribute Type Description
======================= =========== ========================================
`atoms` ``list`` A list of Atom objects in the molecule
`symmetryNumber` ``float`` The (estimated) external + internal symmetry number of the molecule, modified for chirality
`multiplicity` ``int`` The multiplicity of this species, multiplicity = 2*total_spin+1
`reactive` ``bool`` ``True`` (by default) if the molecule participates in reaction families.
It is set to ``False`` by the filtration functions if a non
representative resonance structure was generated by a template reaction
`props` ``dict`` A list of properties describing the state of the molecule.
`InChI` ``str`` A string representation of the molecule in InChI
`atoms` ``list`` A list of Atom objects in the molecule
`SMILES` ``str`` A string representation of the molecule in SMILES
`fingerprint` ``str`` A representation for fast comparison, set as molecular formula
======================= =========== ========================================

A new molecule object can be easily instantiated by passing the `SMILES` or
`InChI` string representing the molecular structure.
"""

def __init__(self, atoms=None, symmetry=-1, multiplicity=-187, reactive=True, props=None, SMILES=''):
def __init__(self, atoms=None, symmetry=-1, multiplicity=-187, reactive=True, props=None, InChI='', SMILES=''):
Graph.__init__(self, atoms)
self.symmetryNumber = symmetry
self.multiplicity = multiplicity
self.reactive = reactive
self._fingerprint = None
self.InChI = ''
if SMILES != '': self.fromSMILES(SMILES)
self._inchi = None
self._smiles = None
self.props = props or {}

if InChI and SMILES:
logging.warning('Both InChI and SMILES provided for Molecule instantiation, using InChI and ignoring SMILES.')
if InChI:
self.fromInChI(InChI)
self._inchi = InChI
elif SMILES:
self.fromSMILES(SMILES)
self._smiles = SMILES

if multiplicity != -187: # it was set explicitly, so re-set it (fromSMILES etc may have changed it)
self.multiplicity = multiplicity

def __deepcopy__(self, memo):
return self.copy(deep=True)

Expand Down Expand Up @@ -857,37 +868,65 @@ def __reduce__(self):
"""
return (Molecule, (self.vertices, self.symmetryNumber, self.multiplicity, self.reactive, self.props))

def __getAtoms(self): return self.vertices
def __setAtoms(self, atoms): self.vertices = atoms
atoms = property(__getAtoms, __setAtoms)
@property
def atoms(self):
"""
List of atoms contained in the current molecule.

Renames the inherited vertices attribute of :class:`Graph`.
"""
return self.vertices

@atoms.setter
def atoms(self, atoms):
self.vertices = atoms

def __getFingerprint(self):
@property
def fingerprint(self):
"""
Return a string containing the "fingerprint" used to accelerate graph
isomorphism comparisons with other molecules. The fingerprint is a
short string containing a summary of selected information about the
molecule. Two fingerprint strings matching is a necessary (but not
sufficient) condition for the associated molecules to be isomorphic.
Fingerprint used to accelerate graph isomorphism comparisons with
other molecules. The fingerprint is a short string containing a
summary of selected information about the molecule. Two fingerprint
strings matching is a necessary (but not sufficient) condition for
the associated molecules to be isomorphic.

Currently, the fingerprint is simply the chemical formula.
"""
if self._fingerprint is None:
self.fingerprint = self.getFormula()
return self._fingerprint
def __setFingerprint(self, fingerprint): self._fingerprint = fingerprint
fingerprint = property(__getFingerprint, __setFingerprint)

@fingerprint.setter
def fingerprint(self, fingerprint):
self._fingerprint = fingerprint

@property
def InChI(self):
"""InChI string for this molecule. Read-only."""
if self._inchi is None:
self._inchi = self.toInChI()
return self._inchi

@property
def SMILES(self):
"""SMILES string for this molecule. Read-only."""
if self._smiles is None:
self._smiles = self.toSMILES()
return self._smiles

def addAtom(self, atom):
"""
Add an `atom` to the graph. The atom is initialized with no bonds.
"""
self._fingerprint = None
self._fingerprint = self._inchi = self._smiles = None
return self.addVertex(atom)

def addBond(self, bond):
"""
Add a `bond` to the graph as an edge connecting the two atoms `atom1`
and `atom2`.
"""
self._fingerprint = None
self._fingerprint = self._inchi = self._smiles = None
return self.addEdge(bond)

def getBonds(self, atom):
Expand Down Expand Up @@ -936,7 +975,7 @@ def removeAtom(self, atom):
not remove atoms that no longer have any bonds as a result of this
removal.
"""
self._fingerprint = None
self._fingerprint = self._inchi = self._smiles = None
return self.removeVertex(atom)

def removeBond(self, bond):
Expand All @@ -945,7 +984,7 @@ def removeBond(self, bond):
Does not remove atoms that no longer have any bonds as a result of
this removal.
"""
self._fingerprint = None
self._fingerprint = self._inchi = self._smiles = None
return self.removeEdge(bond)

def removeVanDerWaalsBonds(self):
Expand Down
2 changes: 0 additions & 2 deletions rmgpy/molecule/translator.py
Expand Up @@ -260,8 +260,6 @@ def fromInChI(mol, inchistr, backend='try-all'):
a user-specified backend for conversion, currently supporting
rdkit (default) and openbabel.
"""
mol.InChI = inchistr

if inchiutil.INCHI_PREFIX in inchistr:
return _read(mol, inchistr, 'inchi', backend)
else:
Expand Down
14 changes: 11 additions & 3 deletions rmgpy/reaction.py
Expand Up @@ -200,10 +200,19 @@ def __reduce__(self):
self.comment
))

def __getDegneneracy(self):
@property
def degeneracy(self):
"""
The reaction path degeneracy for this reaction.

If the reaction has kinetics, changing the degeneracy
will adjust the reaction rate by a ratio of the new
degeneracy to the old degeneracy.
"""
return self._degeneracy

def __setDegeneracy(self, new):
@degeneracy.setter
def degeneracy(self, new):
# modify rate if kinetics exists
if self.kinetics is not None:
if self._degeneracy < 2:
Expand All @@ -220,7 +229,6 @@ def __setDegeneracy(self, new):
self.kinetics.changeRate(degeneracyRatio)
# set new degeneracy
self._degeneracy = new
degeneracy = property(__getDegneneracy, __setDegeneracy)

def toChemkin(self, speciesList=None, kinetics=True):
"""
Expand Down
1 change: 1 addition & 0 deletions rmgpy/species.pxd
Expand Up @@ -53,6 +53,7 @@ cdef class Species:
cdef public bint isSolvent
cdef public int creationIteration
cdef public bint explicitlyAllowed
cdef str _inchi

cpdef generate_resonance_structures(self, bint keep_isomorphic=?, bint filter_structures=?)

Expand Down
18 changes: 15 additions & 3 deletions rmgpy/species.py
Expand Up @@ -106,6 +106,7 @@ def __init__(self, index=-1, label='', thermo=None, conformer=None,
self.isSolvent = False
self.creationIteration = creationIteration
self.explicitlyAllowed = explicitlyAllowed
self._inchi = None
# Check multiplicity of each molecule is the same
if molecule is not None and len(molecule)>1:
mult = molecule[0].multiplicity
Expand Down Expand Up @@ -154,6 +155,14 @@ def __reduce__(self):
"""
return (Species, (self.index, self.label, self.thermo, self.conformer, self.molecule, self.transportData, self.molecularWeight, self.energyTransferModel, self.reactive, self.props))

@property
def InChI(self):
"""InChI string representation of this species. Read-only."""
if self._inchi is None:
if self.molecule:
self._inchi = self.molecule[0].InChI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is self.molecule[0].InChI always not None?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Molecule.InChI is a property which will set the InChI if it's not already there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

return self._inchi

@property
def molecularWeight(self):
"""The molecular weight of the species. (Note: value_si is in kg/molecule not kg/mol)"""
Expand Down Expand Up @@ -781,11 +790,14 @@ def __reduce__(self):
"""
return (TransitionState, (self.label, self.conformer, self.frequency, self.tunneling, self.degeneracy))

def getFrequency(self):
@property
def frequency(self):
"""The negative frequency of the first-order saddle point."""
return self._frequency
def setFrequency(self, value):

@frequency.setter
def frequency(self, value):
self._frequency = quantity.Frequency(value)
frequency = property(getFrequency, setFrequency, """The negative frequency of the first-order saddle point.""")

def getPartitionFunction(self, T):
"""
Expand Down