diff --git a/rmgpy/molecule/converter.py b/rmgpy/molecule/converter.py index e7f120b4ae..178c791720 100644 --- a/rmgpy/molecule/converter.py +++ b/rmgpy/molecule/converter.py @@ -69,6 +69,8 @@ def toRDKitMol(mol, removeHs=True, returnMapping=False, sanitize=True): rdkitmol = Chem.rdchem.EditableMol(Chem.rdchem.Mol()) for index, atom in enumerate(mol.vertices): rdAtom = Chem.rdchem.Atom(atom.element.symbol) + if atom.element.isotope != -1: + rdAtom.SetIsotope(atom.element.isotope) rdAtom.SetNumRadicalElectrons(atom.radicalElectrons) rdAtom.SetFormalCharge(atom.charge) if atom.element.symbol == 'C' and atom.lonePairs == 1 and mol.multiplicity == 1: rdAtom.SetNumRadicalElectrons(2) @@ -131,7 +133,8 @@ def fromRDKitMol(mol, rdkitmol): # Use atomic number as key for element number = rdkitatom.GetAtomicNum() - element = elements.getElement(number) + isotope = rdkitatom.GetIsotope() + element = elements.getElement(number, isotope or -1) # Process charge charge = rdkitatom.GetFormalCharge() @@ -214,6 +217,8 @@ def toOBMol(mol, returnMapping=False): for atom in atoms: a = obmol.NewAtom() a.SetAtomicNum(atom.number) + if atom.element.isotope != -1: + a.SetIsotope(atom.element.isotope) a.SetFormalCharge(atom.charge) obAtomIds[atom] = a.GetId() orders = {1: 1, 2: 2, 3: 3, 1.5: 5} @@ -255,7 +260,8 @@ def fromOBMol(mol, obmol): for obatom in openbabel.OBMolAtomIter(obmol): # Use atomic number as key for element number = obatom.GetAtomicNum() - element = elements.getElement(number) + isotope = obatom.GetIsotope() + element = elements.getElement(number, isotope or -1) # Process charge charge = obatom.GetFormalCharge() obatom_multiplicity = obatom.GetSpinMultiplicity() diff --git a/rmgpy/molecule/converterTest.py b/rmgpy/molecule/converterTest.py index ac701ed1ac..76f1a50c7d 100644 --- a/rmgpy/molecule/converterTest.py +++ b/rmgpy/molecule/converterTest.py @@ -35,7 +35,7 @@ import unittest from rmgpy.exceptions import AtomTypeError -from rmgpy.molecule.converter import debugRDKitMol, toRDKitMol, fromRDKitMol +from rmgpy.molecule.converter import debugRDKitMol, toRDKitMol, fromRDKitMol, toOBMol, fromOBMol from rmgpy.molecule.molecule import Molecule @@ -123,3 +123,38 @@ def test_atom_mapping_2(self): rdkitmol.GetBondBetweenAtoms(rdAtomIndices[at1], rdAtomIndices[at2]) except RuntimeError: self.fail("RDKit failed in finding the bond in the original atom!") + +class ConverterTest(unittest.TestCase): + + def setUp(self): + """Function run before each test in this class.""" + self.test_mols = [ + Molecule().fromSMILES('C'), + Molecule().fromSMILES('O'), + Molecule().fromSMILES('N'), + Molecule().fromSMILES('S'), + Molecule().fromSMILES('[CH2]C'), + Molecule().fromSMILES('[CH]C'), + Molecule().fromSMILES('C=CC=C'), + Molecule().fromSMILES('C#C[CH2]'), + Molecule().fromSMILES('c1ccccc1'), + Molecule().fromSMILES('[13CH3]C') + ] + + def test_rdkit_round_trip(self): + """Test conversion to and from RDKitMol""" + for mol in self.test_mols: + rdkit_mol = toRDKitMol(mol) + new_mol = fromRDKitMol(Molecule(), rdkit_mol) + + self.assertTrue(mol.isIsomorphic(new_mol)) + self.assertEqual(mol.get_element_count(), new_mol.get_element_count()) + + def test_ob_round_trip(self): + """Test conversion to and from OBMol""" + for mol in self.test_mols: + ob_mol = toOBMol(mol) + new_mol = fromOBMol(Molecule(), ob_mol) + + self.assertTrue(mol.isIsomorphic(new_mol)) + self.assertEqual(mol.get_element_count(), new_mol.get_element_count()) diff --git a/rmgpy/molecule/translatorTest.py b/rmgpy/molecule/translatorTest.py index b58225faa2..254796f8ce 100644 --- a/rmgpy/molecule/translatorTest.py +++ b/rmgpy/molecule/translatorTest.py @@ -414,6 +414,22 @@ def test_disconnected_molecule(self): self.assertEqual(mol.toInChI(), inchi) + def test_isotopic_molecule_1(self): + """Test that we can generate an InChI for an isotopic molecule.""" + mol = Molecule().fromSMILES('[13CH4]') + + inchi = 'InChI=1S/CH4/h1H4/i1+1' + + self.assertEqual(mol.toInChI(), inchi) + + def test_isotopic_molecule_2(self): + """Test that we can generate an InChI for an isotopic molecule.""" + mol = Molecule().fromSMILES('[13CH3]C') + + inchi = 'InChI=1S/C2H6/c1-2/h1-2H3/i1+1' + + self.assertEqual(mol.toInChI(), inchi) + class SMILESGenerationTest(unittest.TestCase): def compare(self, adjlist, smiles): @@ -1372,3 +1388,17 @@ def test_NO(self): inchi = 'InChI=1S/NO/c1-2' u_indices = [1] self.compare(inchi, u_indices) + + def test_isotopic_molecule_1(self): + """Test that we can parse an InChI for an isotopic molecule.""" + mol = Molecule().fromInChI('InChI=1S/CH4/h1H4/i1+1') + + self.assertTrue(len(mol.atoms), 4) + self.assertEqual([atom.element.isotope for atom in mol.atoms].count(13), 1) + + def test_isotopic_molecule_2(self): + """Test that we can parse an InChI for an isotopic molecule.""" + mol = Molecule().fromInChI('InChI=1S/C2H6/c1-2/h1-2H3/i1+1') + + self.assertTrue(len(mol.atoms), 6) + self.assertEqual([atom.element.isotope for atom in mol.atoms].count(13), 1)