Files
rdkit/Code/GraphMol/ChemReactions/Wrap/testReactionWrapper.py
Rachael Pirie b2fe43a9ad add Reaction From Smiles python wrapper (#8843)
* add Reaction From Smiles python wrapper

* Update Code/GraphMol/ChemReactions/Wrap/rdChemReactions.cpp

Co-authored-by: Greg Landrum <greg.landrum@gmail.com>

* fix Greg changes

---------

Co-authored-by: Greg Landrum <greg.landrum@gmail.com>
2025-10-08 16:13:06 +02:00

1263 lines
51 KiB
Python

#
# Copyright (c) 2007-2023, Novartis Institutes for BioMedical Research Inc. and
# other RDKit contributors
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Novartis Institutes for BioMedical Research Inc.
# nor the names of its contributors may be used to endorse or promote
# products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
import doctest
import importlib.util
import os
import pickle
import sys
import unittest
import tempfile
from rdkit import Chem, Geometry, RDConfig, rdBase
from rdkit.Chem import AllChem, rdChemReactions
from rdkit.Chem.SimpleEnum import Enumerator
def feq(v1, v2, tol2=1e-4):
return abs(v1 - v2) <= tol2
def ptEq(pt1, pt2, tol=1e-4):
return feq(pt1.x, pt2.x, tol) and feq(pt1.y, pt2.y, tol) and feq(pt1.z, pt2.z, tol)
# Boost functions are NOT found by doctest, this "fixes" them
# by adding the doctests to a fake module
spec = importlib.util.spec_from_loader("TestPreprocess", loader=None)
TestPreprocess = importlib.util.module_from_spec(spec)
code = """
from rdkit.Chem import rdChemReactions
def PreprocessReaction(*a, **kw):
'''%s
'''
return rdChemReactions.PreprocessReaction(*a, **kw)
""" % "\n".join([x.lstrip() for x in rdChemReactions.PreprocessReaction.__doc__.split("\n")])
exec(code, TestPreprocess.__dict__)
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(Enumerator))
tests.addTests(doctest.DocTestSuite(TestPreprocess))
return tests
class TestCase(unittest.TestCase):
def setUp(self):
self.dataDir = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'ChemReactions', 'testData')
def test1Basics(self):
rxna = rdChemReactions.ChemicalReaction()
# also tests empty copy constructor
for rxn in [rxna, rdChemReactions.ChemicalReaction(rxna)]:
self.assertTrue(rxn.GetNumReactantTemplates() == 0)
self.assertTrue(rxn.GetNumProductTemplates() == 0)
r1 = Chem.MolFromSmarts('[C:1](=[O:2])O')
rxn.AddReactantTemplate(r1)
self.assertTrue(rxn.GetNumReactantTemplates() == 1)
r1 = Chem.MolFromSmarts('[N:3]')
rxn.AddReactantTemplate(r1)
self.assertTrue(rxn.GetNumReactantTemplates() == 2)
r1 = Chem.MolFromSmarts('[C:1](=[O:2])[N:3]')
rxn.AddProductTemplate(r1)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
reacts = (Chem.MolFromSmiles('C(=O)O'), Chem.MolFromSmiles('N'))
ps = rxn.RunReactants(reacts)
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
self.assertTrue(ps[0][0].GetNumAtoms() == 3)
ps = rxn.RunReactants(list(reacts))
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
self.assertTrue(ps[0][0].GetNumAtoms() == 3)
def test2DaylightParser(self):
rxna = rdChemReactions.ReactionFromSmarts('[C:1](=[O:2])O.[N:3]>>[C:1](=[O:2])[N:3]')
for rxn in [rxna, rdChemReactions.ChemicalReaction(rxna)]:
self.assertTrue(rxn)
self.assertTrue(rxn.GetNumReactantTemplates() == 2)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
self.assertTrue(rxn._getImplicitPropertiesFlag())
reacts = (Chem.MolFromSmiles('C(=O)O'), Chem.MolFromSmiles('N'))
ps = rxn.RunReactants(reacts)
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
self.assertTrue(ps[0][0].GetNumAtoms() == 3)
reacts = (Chem.MolFromSmiles('CC(=O)OC'), Chem.MolFromSmiles('CN'))
ps = rxn.RunReactants(reacts)
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
self.assertTrue(ps[0][0].GetNumAtoms() == 5)
def test3MDLParsers(self):
fileN = os.path.join(self.dataDir, 'AmideBond.rxn')
rxna = rdChemReactions.ReactionFromRxnFile(fileN)
print("*" * 44)
print(fileN)
print(rxna)
for rxn in [rxna, rdChemReactions.ChemicalReaction(rxna)]:
self.assertTrue(rxn)
self.assertFalse(rxn._getImplicitPropertiesFlag())
self.assertTrue(rxn.GetNumReactantTemplates() == 2)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
reacts = (Chem.MolFromSmiles('C(=O)O'), Chem.MolFromSmiles('N'))
ps = rxn.RunReactants(reacts)
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
self.assertTrue(ps[0][0].GetNumAtoms() == 3)
with open(fileN, 'r') as rxnF:
rxnBlock = rxnF.read()
rxn = rdChemReactions.ReactionFromRxnBlock(rxnBlock)
self.assertTrue(rxn)
self.assertTrue(rxn.GetNumReactantTemplates() == 2)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
reacts = (Chem.MolFromSmiles('C(=O)O'), Chem.MolFromSmiles('N'))
ps = rxn.RunReactants(reacts)
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
self.assertTrue(ps[0][0].GetNumAtoms() == 3)
def test4ErrorHandling(self):
self.assertRaises(
ValueError,
lambda x='[C:1](=[O:2])Q.[N:3]>>[C:1](=[O:2])[N:3]': rdChemReactions.ReactionFromSmarts(x))
self.assertRaises(
ValueError,
lambda x='[C:1](=[O:2])O.[N:3]>>[C:1](=[O:2])[N:3]Q': rdChemReactions.ReactionFromSmarts(x))
self.assertRaises(
ValueError, lambda x='[C:1](=[O:2])O.[N:3]>>[C:1](=[O:2])[N:3]>>CC': rdChemReactions.
ReactionFromSmarts(x))
block = """$RXN
ISIS 082120061354
3 1
$MOL
-ISIS- 08210613542D
3 2 0 0 0 0 0 0 0 0999 V2000
-1.4340 -0.6042 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0
-0.8639 -0.9333 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
-1.4340 0.0542 0.0000 O 0 0 0 0 0 0 0 0 0 1 0 0
1 2 1 0 0 0 0
1 3 2 0 0 0 0
M END
$MOL
-ISIS- 08210613542D
1 0 0 0 0 0 0 0 0 0999 V2000
2.2125 -0.7833 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
M END
$MOL
-ISIS- 08210613542D
3 2 0 0 0 0 0 0 0 0999 V2000
9.5282 -0.8083 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
8.9579 -0.4792 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0
8.9579 0.1792 0.0000 O 0 0 0 0 0 0 0 0 0 1 0 0
1 2 1 0 0 0 0
2 3 2 0 0 0 0
M END
"""
self.assertRaises(ValueError, lambda x=block: rdChemReactions.ReactionFromRxnBlock(x))
block = """$RXN
ISIS 082120061354
2 1
$MOL
-ISIS- 08210613542D
4 2 0 0 0 0 0 0 0 0999 V2000
-1.4340 -0.6042 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0
-0.8639 -0.9333 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
-1.4340 0.0542 0.0000 O 0 0 0 0 0 0 0 0 0 1 0 0
1 2 1 0 0 0 0
1 3 2 0 0 0 0
M END
$MOL
-ISIS- 08210613542D
1 0 0 0 0 0 0 0 0 0999 V2000
2.2125 -0.7833 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
M END
$MOL
-ISIS- 08210613542D
3 2 0 0 0 0 0 0 0 0999 V2000
9.5282 -0.8083 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
8.9579 -0.4792 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0
8.9579 0.1792 0.0000 O 0 0 0 0 0 0 0 0 0 1 0 0
1 2 1 0 0 0 0
2 3 2 0 0 0 0
M END
"""
#self.assertRaises(ValueError,lambda x=block:rdChemReactions.ReactionFromRxnBlock(x))
block = """$RXN
ISIS 082120061354
2 1
$MOL
-ISIS- 08210613542D
3 2 0 0 0 0 0 0 0 0999 V2000
-1.4340 -0.6042 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0
-0.8639 -0.9333 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
-1.4340 0.0542 0.0000 O 0 0 0 0 0 0 0 0 0 1 0 0
1 2 1 0 0 0 0
1 3 2 0 0 0 0
M END
$MOL
-ISIS- 08210613542D
1 0 0 0 0 0 0 0 0 0999 V2000
2.2125 -0.7833 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
M END
$MOL
-ISIS- 08210613542D
3 1 0 0 0 0 0 0 0 0999 V2000
9.5282 -0.8083 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
8.9579 -0.4792 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0
8.9579 0.1792 0.0000 O 0 0 0 0 0 0 0 0 0 1 0 0
1 2 1 0 0 0 0
2 3 2 0 0 0 0
M END
"""
#self.assertRaises(ValueError,lambda x=block:rdChemReactions.ReactionFromRxnBlock(x))
def test5Validation(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:1](=[O:2])O.[N:3]>>[C:1](=[O:2])[N:3]')
self.assertTrue(rxn)
self.assertTrue(rxn.Validate() == (0, 0))
rxn = rdChemReactions.ReactionFromSmarts('[C:1](=[O:1])O.[N:3]>>[C:1](=[O:2])[N:3]')
self.assertTrue(rxn)
self.assertTrue(rxn.Validate() == (1, 1))
rxn = rdChemReactions.ReactionFromSmarts('[C:1](=[O:2])[O:4].[N:3]>>[C:1](=[O:2])[N:3]')
self.assertTrue(rxn)
self.assertTrue(rxn.Validate() == (1, 0))
rxn = rdChemReactions.ReactionFromSmarts('[C:1](=[O:2])O.[N:3]>>[C:1](=[O:2])[N:3][C:5]')
self.assertTrue(rxn)
self.assertTrue(rxn.Validate() == (1, 0))
def test6Exceptions(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:1]Cl>>[C:1]')
self.assertTrue(rxn)
self.assertRaises(ValueError, lambda x=rxn: x.RunReactants(()))
self.assertRaises(
ValueError, lambda x=rxn: x.RunReactants((Chem.MolFromSmiles('CC'), Chem.MolFromSmiles('C'))))
ps = rxn.RunReactants((Chem.MolFromSmiles('CCCl'), ))
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
def _test7Leak(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:1]Cl>>[C:1]')
self.assertTrue(rxn)
print('running: ')
for i in range(1e5):
ps = rxn.RunReactants((Chem.MolFromSmiles('CCCl'), ))
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
if not i % 1000:
print(i)
def test8Properties(self):
rxn = rdChemReactions.ReactionFromSmarts('[O:1]>>[O:1][3#0]')
self.assertTrue(rxn)
ps = rxn.RunReactants((Chem.MolFromSmiles('CO'), ))
self.assertTrue(len(ps) == 1)
self.assertTrue(len(ps[0]) == 1)
Chem.SanitizeMol(ps[0][0])
self.assertEqual(ps[0][0].GetAtomWithIdx(1).GetIsotope(), 3)
def test9AromaticityTransfer(self):
# this was issue 2664121
mol = Chem.MolFromSmiles('c1ccc(C2C3(Cc4c(cccc4)C2)CCCC3)cc1')
rxn = rdChemReactions.ReactionFromSmarts(
'[A:1]1~[*:2]~[*:3]~[*:4]~[*:5]~[A:6]-;@1>>[*:1]~[*:2]~[*:3]~[*:4]~[*:5]~[*:6]')
products = rxn.RunReactants([mol])
self.assertEqual(len(products), 6)
for p in products:
self.assertEqual(len(p), 1)
Chem.SanitizeMol(p[0])
def test10DotSeparation(self):
# 08/05/14
# This test is changed due to a new behavior of the smarts
# reaction parser which now allows using parenthesis in products
# as well. original smiles: '[C:1]1[O:2][N:3]1>>[C:1]1[O:2].[N:3]1'
rxn = rdChemReactions.ReactionFromSmarts('[C:1]1[O:2][N:3]1>>([C:1]1[O:2].[N:3]1)')
mol = Chem.MolFromSmiles('C1ON1')
products = rxn.RunReactants([mol])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(p[0].GetNumAtoms(), 3)
self.assertEqual(p[0].GetNumBonds(), 2)
def test11ImplicitProperties(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:1]O>>[C:1]')
mol = Chem.MolFromSmiles('CCO')
products = rxn.RunReactants([mol])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(Chem.MolToSmiles(p[0]), 'CC')
mol2 = Chem.MolFromSmiles('C[CH-]O')
products = rxn.RunReactants([mol2])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(Chem.MolToSmiles(p[0]), '[CH2-]C')
rxn._setImplicitPropertiesFlag(False)
products = rxn.RunReactants([mol])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(Chem.MolToSmiles(p[0]), 'CC')
products = rxn.RunReactants([mol2])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(Chem.MolToSmiles(p[0]), 'CC')
def test12Pickles(self):
# 08/05/14
# This test is changed due to a new behavior of the smarts
# reaction parser which now allows using parenthesis in products
# as well. original smiles: '[C:1]1[O:2][N:3]1>>[C:1]1[O:2].[N:3]1'
rxn = rdChemReactions.ReactionFromSmarts('[C:1]1[O:2][N:3]1>>([C:1]1[O:2].[N:3]1)')
pkl = pickle.dumps(rxn)
rxn = pickle.loads(pkl)
mol = Chem.MolFromSmiles('C1ON1')
products = rxn.RunReactants([mol])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(p[0].GetNumAtoms(), 3)
self.assertEqual(p[0].GetNumBonds(), 2)
rxn = rdChemReactions.ChemicalReaction(rxn.ToBinary())
products = rxn.RunReactants([mol])
self.assertEqual(len(products), 1)
for p in products:
self.assertEqual(len(p), 1)
self.assertEqual(p[0].GetNumAtoms(), 3)
self.assertEqual(p[0].GetNumBonds(), 2)
def test13GetTemplates(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:1]1[O:2][N:3]1>>[C:1][O:2].[N:3]')
r1 = rxn.GetReactantTemplate(0)
sma = Chem.MolToSmarts(r1)
self.assertEqual(sma, '[C:1]1[O:2][N:3]1')
p1 = rxn.GetProductTemplate(0)
sma = Chem.MolToSmarts(p1)
self.assertEqual(sma, '[C:1][O:2]')
p2 = rxn.GetProductTemplate(1)
sma = Chem.MolToSmarts(p2)
self.assertEqual(sma, '[N:3]')
self.assertRaises(ValueError, lambda: rxn.GetProductTemplate(2))
self.assertRaises(ValueError, lambda: rxn.GetReactantTemplate(1))
def test14Matchers(self):
rxn = rdChemReactions.ReactionFromSmarts(
'[C;!$(C(-O)-O):1](=[O:2])[O;H,-1].[N;!H0:3]>>[C:1](=[O:2])[N:3]')
self.assertTrue(rxn)
rxn.Initialize()
self.assertTrue(rxn.IsMoleculeReactant(Chem.MolFromSmiles('OC(=O)C')))
self.assertFalse(rxn.IsMoleculeReactant(Chem.MolFromSmiles('OC(=O)O')))
self.assertTrue(rxn.IsMoleculeReactant(Chem.MolFromSmiles('CNC')))
self.assertFalse(rxn.IsMoleculeReactant(Chem.MolFromSmiles('CN(C)C')))
self.assertTrue(rxn.IsMoleculeProduct(Chem.MolFromSmiles('NC(=O)C')))
self.assertTrue(rxn.IsMoleculeProduct(Chem.MolFromSmiles('CNC(=O)C')))
self.assertFalse(rxn.IsMoleculeProduct(Chem.MolFromSmiles('COC(=O)C')))
def test15Replacements(self):
rxn = rdChemReactions.ReactionFromSmarts(
'[{amine}:1]>>[*:1]-C',
replacements={'{amine}': '$([N;!H0;$(N-[#6]);!$(N-[!#6;!#1]);!$(N-C=[O,N,S])])'})
self.assertTrue(rxn)
rxn.Initialize()
reactants = (Chem.MolFromSmiles('CCN'), )
ps = rxn.RunReactants(reactants)
self.assertEqual(len(ps), 1)
self.assertEqual(len(ps[0]), 1)
self.assertEqual(ps[0][0].GetNumAtoms(), 4)
def test16GetReactingAtoms(self):
rxn = rdChemReactions.ReactionFromSmarts("[O:1][C:2].[N:3]>>[N:1][C:2].[N:3]")
self.assertTrue(rxn)
rxn.Initialize()
rAs = rxn.GetReactingAtoms()
self.assertEqual(len(rAs), 2)
self.assertEqual(len(rAs[0]), 1)
self.assertEqual(len(rAs[1]), 0)
rxn = rdChemReactions.ReactionFromSmarts("[O:1]C>>[O:1]C")
self.assertTrue(rxn)
rxn.Initialize()
rAs = rxn.GetReactingAtoms()
self.assertEqual(len(rAs), 1)
self.assertEqual(len(rAs[0]), 2)
rAs = rxn.GetReactingAtoms(True)
self.assertEqual(len(rAs), 1)
self.assertEqual(len(rAs[0]), 1)
def test17AddRecursiveQueriesToReaction(self):
rxn = rdChemReactions.ReactionFromSmarts("[C:1][O:2].[N:3]>>[C:1][N:2]")
self.assertTrue(rxn)
rxn.Initialize()
qs = {'aliphatic': Chem.MolFromSmiles('CC')}
rxn.GetReactantTemplate(0).GetAtomWithIdx(0).SetProp('query', 'aliphatic')
rxn.AddRecursiveQueriesToReaction(qs, 'query')
q = rxn.GetReactantTemplate(0)
m = Chem.MolFromSmiles('CCOC')
self.assertTrue(m.HasSubstructMatch(q))
m = Chem.MolFromSmiles('CO')
self.assertFalse(m.HasSubstructMatch(q))
rxn = rdChemReactions.ReactionFromSmarts("[C:1][O:2].[N:3]>>[C:1][N:2]")
rxn.Initialize()
rxn.GetReactantTemplate(0).GetAtomWithIdx(0).SetProp('query', 'aliphatic')
labels = rxn.AddRecursiveQueriesToReaction(qs, 'query', getLabels=True)
self.assertTrue(len(labels), 1)
def test17bAddRecursiveQueriesToReaction(self):
from rdkit.Chem import FilterCatalog
rxn = rdChemReactions.ReactionFromSmarts("[C:1][O:2].[N:3]>>[C:1][N:2]")
self.assertTrue(rxn)
rxn.Initialize()
rxn.GetReactantTemplate(0).GetAtomWithIdx(0).SetProp('query', 'carboxylicacid')
querydefs = {
k.lower(): v
for k, v in FilterCatalog.GetFlattenedFunctionalGroupHierarchy().items()
}
self.assertTrue('CarboxylicAcid' in FilterCatalog.GetFlattenedFunctionalGroupHierarchy())
rxn.AddRecursiveQueriesToReaction(querydefs, 'query')
q = rxn.GetReactantTemplate(0)
m = Chem.MolFromSmiles('C(=O)[O-].N')
self.assertTrue(m.HasSubstructMatch(q))
m = Chem.MolFromSmiles('C.N')
self.assertFalse(m.HasSubstructMatch(q))
def test18GithubIssue16(self):
rxn = rdChemReactions.ReactionFromSmarts("[F:1]>>[Cl:1]")
self.assertTrue(rxn)
rxn.Initialize()
self.assertRaises(ValueError, lambda: rxn.RunReactants((None, )))
def test19RemoveUnmappedMoleculesToAgents(self):
rxn = rdChemReactions.ReactionFromSmarts(
"[C:1]=[O:2].[N:3].C(=O)O>[OH2].[Na].[Cl]>[N:3]~[C:1]=[O:2]")
self.assertTrue(rxn)
rxn.Initialize()
self.assertTrue(rxn.GetNumReactantTemplates() == 3)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
self.assertTrue(rxn.GetNumAgentTemplates() == 3)
rxn.RemoveUnmappedReactantTemplates()
rxn.RemoveUnmappedProductTemplates()
self.assertTrue(rxn.GetNumReactantTemplates() == 2)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
self.assertTrue(rxn.GetNumAgentTemplates() == 4)
rxn = rdChemReactions.ReactionFromSmarts("[C:1]=[O:2].[N:3].C(=O)O>>[N:3]~[C:1]=[O:2].[OH2]")
self.assertTrue(rxn)
rxn.Initialize()
self.assertTrue(rxn.GetNumReactantTemplates() == 3)
self.assertTrue(rxn.GetNumProductTemplates() == 2)
self.assertTrue(rxn.GetNumAgentTemplates() == 0)
agentList = []
rxn.RemoveUnmappedReactantTemplates(moveToAgentTemplates=False, targetList=agentList)
rxn.RemoveUnmappedProductTemplates(targetList=agentList)
self.assertTrue(rxn.GetNumReactantTemplates() == 2)
self.assertTrue(rxn.GetNumProductTemplates() == 1)
self.assertTrue(rxn.GetNumAgentTemplates() == 1)
self.assertTrue(len(agentList) == 2)
def test20CheckCopyConstructedReactionAtomProps(self):
RLABEL = "_MolFileRLabel"
amine_rxn = '$RXN\n\n ISIS 090220091541\n\n 2 1\n$MOL\n\n -ISIS- 09020915412D\n\n 3 2 0 0 0 0 0 0 0 0999 V2000\n -2.9083 -0.4708 0.0000 R# 0 0 0 0 0 0 0 0 0 1 0 0\n -2.3995 -0.1771 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0\n -2.4042 0.4125 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0 0\n 2 3 2 0 0 0 0\nV 2 aldehyde\nM RGP 1 1 1\nM END\n$MOL\n\n -ISIS- 09020915412D\n\n 2 1 0 0 0 0 0 0 0 0999 V2000\n 2.8375 -0.2500 0.0000 R# 0 0 0 0 0 0 0 0 0 3 0 0\n 3.3463 0.0438 0.0000 N 0 0 0 0 0 0 0 0 0 4 0 0\n 1 2 1 0 0 0 0\nV 2 amine\nM RGP 1 1 2\nM END\n$MOL\n\n -ISIS- 09020915412D\n\n 4 3 0 0 0 0 0 0 0 0999 V2000\n 13.3088 0.9436 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0\n 13.8206 1.2321 0.0000 R# 0 0 0 0 0 0 0 0 0 1 0 0\n 13.3028 0.3561 0.0000 N 0 0 0 0 0 0 0 0 0 4 0 0\n 12.7911 0.0676 0.0000 R# 0 0 0 0 0 0 0 0 0 3 0 0\n 1 3 1 0 0 0 0\n 1 2 1 0 0 0 0\n 3 4 1 0 0 0 0\nM RGP 2 2 1 4 2\nM END\n'
rxn = rdChemReactions.ReactionFromRxnBlock(amine_rxn)
res = []
for atom in rxn.GetReactantTemplate(0).GetAtoms():
if atom.HasProp(RLABEL):
res.append((atom.GetIdx(), atom.GetProp(RLABEL)))
rxn2 = rdChemReactions.ChemicalReaction(rxn)
res2 = []
for atom in rxn2.GetReactantTemplate(0).GetAtoms():
if atom.HasProp(RLABEL):
res2.append((atom.GetIdx(), atom.GetProp(RLABEL)))
self.assertEqual(res, res2)
# currently ToBinary does not save atom props
# rxn2 = rdChemReactions.ChemicalReaction(rxn.ToBinary())
def test21CheckRawIters(self):
RLABEL = "_MolFileRLabel"
amine_rxn = '$RXN\n\n ISIS 090220091541\n\n 2 1\n$MOL\n\n -ISIS- 09020915412D\n\n 3 2 0 0 0 0 0 0 0 0999 V2000\n -2.9083 -0.4708 0.0000 R# 0 0 0 0 0 0 0 0 0 1 0 0\n -2.3995 -0.1771 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0\n -2.4042 0.4125 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0 0\n 2 3 2 0 0 0 0\nV 2 aldehyde\nM RGP 1 1 1\nM END\n$MOL\n\n -ISIS- 09020915412D\n\n 2 1 0 0 0 0 0 0 0 0999 V2000\n 2.8375 -0.2500 0.0000 R# 0 0 0 0 0 0 0 0 0 3 0 0\n 3.3463 0.0438 0.0000 N 0 0 0 0 0 0 0 0 0 4 0 0\n 1 2 1 0 0 0 0\nV 2 amine\nM RGP 1 1 2\nM END\n$MOL\n\n -ISIS- 09020915412D\n\n 4 3 0 0 0 0 0 0 0 0999 V2000\n 13.3088 0.9436 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 0\n 13.8206 1.2321 0.0000 R# 0 0 0 0 0 0 0 0 0 1 0 0\n 13.3028 0.3561 0.0000 N 0 0 0 0 0 0 0 0 0 4 0 0\n 12.7911 0.0676 0.0000 R# 0 0 0 0 0 0 0 0 0 3 0 0\n 1 3 1 0 0 0 0\n 1 2 1 0 0 0 0\n 3 4 1 0 0 0 0\nM RGP 2 2 1 4 2\nM END\n'
rxn = rdChemReactions.ReactionFromRxnBlock(amine_rxn)
reactants = rxn.GetReactants()
self.assertEqual(len(reactants), rxn.GetNumReactantTemplates())
products = rxn.GetProducts()
self.assertEqual(len(products), rxn.GetNumProductTemplates())
agents = rxn.GetAgents()
self.assertEqual(len(agents), rxn.GetNumAgentTemplates())
for i in range(rxn.GetNumReactantTemplates()):
p = rxn.GetReactantTemplate(i)
mb1 = Chem.MolToMolBlock(p)
mb2 = Chem.MolToMolBlock(reactants[i])
self.assertEqual(mb1, mb2)
def test22RunSingleReactant(self):
# from
# A Collection of Robust Organic Synthesis Reactions for In Silico Molecule Design
# Markus Hartenfeller,*, Martin Eberle, Peter Meier, Cristina Nieto-Oberhuber,
# Karl-Heinz Altmann, Gisbert Schneider, Edgar Jacoby, and Steffen Renner
# Novartis Institutes for BioMedical Research, Novartis Pharma AG, Forum 1,
# Novartis Campus, CH-4056 Basel, Switzerland Swiss Federal Institute of Technology (ETH)
# Zurich, Switzerland
smirks_thiourea = "[N;$(N-[#6]):3]=[C;$(C=S):1].[N;$(N[#6]);!$(N=*);!$([N-]);!$(N#*);!$([ND3]);!$([ND4]);!$(N[O,N]);!$(N[C,S]=[S,O,N]):2]>>[N:3]-[C:1]-[N+0:2]"
rxn = rdChemReactions.ReactionFromSmarts(smirks_thiourea)
reagents = [Chem.MolFromSmiles(x) for x in ['C=CCN=C=S', 'NCc1ncc(Cl)cc1Br']]
res = rxn.RunReactants(reagents)
self.assertTrue(res)
expected_result = [Chem.MolToSmiles(Chem.MolFromSmiles("C=CCNC(N)=S"))]
expected_result.sort()
sidechains_expected_result = [
Chem.MolToSmiles(Chem.MolFromSmiles("[*:1]=S.[*:3]CC=C"), isomericSmiles=True)
]
sidechains_nodummy_expected_result = [[0, [
3,
], [
1,
]], [3, [
1,
], [
2,
]]]
sidechains_nodummy = []
sidechains_expected_result.sort()
for addDummy in [True, False]:
res = rxn.RunReactant(reagents[0], 0)
assert res
result = []
sidechains = []
for match in res:
for mol in match:
result.append(Chem.MolToSmiles(mol, isomericSmiles=True))
sidechain = rdChemReactions.ReduceProductToSideChains(mol, addDummy)
sidechains.append(Chem.MolToSmiles(sidechain, isomericSmiles=True))
if not addDummy:
for atom in sidechain.GetAtoms():
if atom.HasProp("_rgroupAtomMaps"):
sidechains_nodummy.append([
atom.GetIdx(),
eval(atom.GetProp("_rgroupAtomMaps")),
eval(atom.GetProp("_rgroupBonds")),
])
result.sort()
sidechains.sort()
if addDummy:
self.assertEqual(result, expected_result)
self.assertEqual(sidechains, sidechains_expected_result)
else:
self.assertEqual(sidechains_nodummy, sidechains_nodummy_expected_result)
expected_result = [Chem.MolToSmiles(Chem.MolFromSmiles("NCNCc1ncc(Cl)cc1Br"))]
expected_result.sort()
sidechains_expected_result = [
Chem.MolToSmiles(Chem.MolFromSmiles("[*:2]Cc1ncc(Cl)cc1Br"), isomericSmiles=True)
]
sidechains_expected_result.sort()
res = rxn.RunReactant(reagents[1], 1)
result = []
sidechains = []
for match in res:
for mol in match:
result.append(Chem.MolToSmiles(mol, isomericSmiles=True))
sidechains.append(
Chem.MolToSmiles(rdChemReactions.ReduceProductToSideChains(mol), isomericSmiles=True))
result.sort()
self.assertEqual(result, expected_result)
self.assertEqual(sidechains, sidechains_expected_result)
self.assertFalse(rxn.RunReactant(reagents[0], 1))
self.assertFalse(rxn.RunReactant(reagents[1], 0))
# try a broken ring based side-chain
sidechains_expected_result = ['c1ccc2c(c1)nc1n2CC[*:2]1']
reactant = Chem.MolFromSmiles('c1ccc2c(c1)nc1n2CCN1')
res = rxn.RunReactant(reactant, 1)
result = []
sidechains = []
for match in res:
for mol in match:
result.append(Chem.MolToSmiles(mol, isomericSmiles=True))
sidechains.append(
Chem.MolToSmiles(rdChemReactions.ReduceProductToSideChains(mol), isomericSmiles=True))
sidechain = rdChemReactions.ReduceProductToSideChains(mol, addDummyAtoms=False)
self.assertEqual(sidechains, sidechains_expected_result)
def test23CheckNonProduct(self):
smirks_thiourea = "[N;$(N-[#6]):3]=[C;$(C=S):1].[N;$(N[#6]);!$(N=*);!$([N-]);!$(N#*);!$([ND3]);!$([ND4]);!$(N[O,N]);!$(N[C,S]=[S,O,N]):2]>>[N:3]-[C:1]-[N+0:2]"
rxn = rdChemReactions.ReactionFromSmarts(smirks_thiourea)
mol = Chem.MolFromSmiles("CCCCCCCC")
m = rdChemReactions.ReduceProductToSideChains(mol)
self.assertTrue(m.GetNumAtoms() == 0)
mol = Chem.AddHs(mol)
m = rdChemReactions.ReduceProductToSideChains(mol)
self.assertTrue(m.GetNumAtoms() == 0)
def testPreprocess(self):
testFile = os.path.join(RDConfig.RDCodeDir, 'Chem', 'SimpleEnum', 'test_data', 'boronic1.rxn')
rxn = rdChemReactions.ReactionFromRxnFile(testFile)
rxn.Initialize()
res = rdChemReactions.PreprocessReaction(rxn)
self.assertEqual(res,
(0, 0, 2, 1, (((0, 'halogen.bromine.aromatic'), ), ((1, 'boronicacid'), ))))
def testProperties(self):
smirks_thiourea = "[N;$(N-[#6]):3]=[C;$(C=S):1].[N;$(N[#6]);!$(N=*);!$([N-]);!$(N#*);!$([ND3]);!$([ND4]);!$(N[O,N]);!$(N[C,S]=[S,O,N]):2]>>[N:3]-[C:1]-[N+0:2]"
rxn = rdChemReactions.ReactionFromSmarts(smirks_thiourea)
self.assertFalse(rxn.HasProp("fooprop"))
rxn.SetProp("fooprop", "bar", computed=True)
rxn.SetIntProp("intprop", 3)
self.assertTrue(rxn.HasProp("fooprop"))
self.assertTrue(rxn.HasProp("intprop"))
self.assertEqual(rxn.GetIntProp("intprop"), 3)
nrxn = rdChemReactions.ChemicalReaction(rxn.ToBinary())
self.assertFalse(nrxn.HasProp("fooprop"))
nrxn = rdChemReactions.ChemicalReaction(rxn.ToBinary(Chem.PropertyPickleOptions.AllProps))
self.assertTrue(nrxn.HasProp("fooprop"))
nrxn.ClearComputedProps()
self.assertFalse(nrxn.HasProp("fooprop"))
self.assertTrue(nrxn.HasProp("intprop"))
self.assertEqual(nrxn.GetIntProp("intprop"), 3)
def testRoundTripException(self):
smarts = '[C:1]([C@:3]1([OH:24])[CH2:8][CH2:7][C@H:6]2[C@H:9]3[C@H:19]([C@@H:20]([F:22])[CH2:21][C@:4]12[CH3:5])[C@:17]1([CH3:18])[C:12](=[CH:13][C:14](=[O:23])[CH2:15][CH2:16]1)[CH:11]=[CH:10]3)#[CH:2].C(Cl)CCl.ClC1C=CC=C(C(OO)=[O:37])C=1.C(O)(C)(C)C>C(OCC)(=O)C>[C:1]([C@:3]1([OH:24])[CH2:8][CH2:7][C@H:6]2[C@H:9]3[C@H:19]([C@@H:20]([F:22])[CH2:21][C@:4]12[CH3:5])[C@:17]1([CH3:18])[C:12](=[CH:13][C:14](=[O:23])[CH2:15][CH2:16]1)[C@H:11]1[O:37][C@@H:10]31)#[CH:2]'
rxn = rdChemReactions.ReactionFromSmarts(smarts)
# this shouldn't throw an exception
smarts = rdChemReactions.ReactionToSmarts(rxn)
def testMaxProducts(self):
smarts = "[c:1]1[c:2][c:3][c:4][c:5][c:6]1>>[c:1]1[c:2][c:3][c:4][c:5][c:6]1"
rxn = rdChemReactions.ReactionFromSmarts(smarts)
m = Chem.MolFromSmiles("c1ccccc1")
prods = rxn.RunReactants([m])
self.assertEqual(len(prods), 12)
prods = rxn.RunReactants([m], 1)
self.assertEqual(len(prods), 1)
def testGitHub1868(self):
fileN = os.path.join(self.dataDir, 'v3k.AmideBond.rxn')
for i in range(100):
_rxn = rdChemReactions.ReactionFromRxnFile(fileN)
_rxn.Initialize()
_reacts = [Chem.MolToSmarts(r) for r in _rxn.GetReactants()]
_prods = [Chem.MolToSmarts(p) for p in _rxn.GetProducts()]
def testReactionFromSmiles(self):
smiles = "CC(=O)O.OCC>Cl.OCC>CC(=O)OCC"
rxn_smi = rdChemReactions.ReactionFromSmiles(smiles)
rxn_smarts = rdChemReactions.ReactionFromSmarts(smiles, useSmiles=True)
self.assertEqual(rdChemReactions.ReactionToSmiles(rxn_smi), rdChemReactions.ReactionToSmiles(rxn_smarts))
@unittest.skipUnless(hasattr(rdChemReactions, 'ReactionFromPNGFile'),
"RDKit not built with iostreams support")
def test_PNGMetadata(self):
fname = os.path.join(self.dataDir, 'reaction1.smarts.png')
rxn = rdChemReactions.ReactionFromPNGFile(fname)
self.assertFalse(rxn is None)
self.assertEqual(rxn.GetNumReactantTemplates(), 2)
self.assertEqual(rxn.GetNumProductTemplates(), 1)
fname = os.path.join(self.dataDir, 'reaction1.no_metadata.png')
npng1 = rdChemReactions.ReactionMetadataToPNGFile(rxn, fname)
png = open(fname, 'rb').read()
npng2 = rdChemReactions.ReactionMetadataToPNGString(rxn, png)
self.assertEqual(npng1, npng2)
nrxn = rdChemReactions.ReactionFromPNGString(npng2)
self.assertFalse(nrxn is None)
self.assertEqual(nrxn.GetNumReactantTemplates(), 2)
self.assertEqual(nrxn.GetNumProductTemplates(), 1)
opts = [
{
'includePkl': True,
'includeSmiles': False,
'includeSmarts': False,
'includeRxn': False
},
{
'includePkl': False,
'includeSmiles': True,
'includeSmarts': False,
'includeRxn': False
},
{
'includePkl': False,
'includeSmiles': False,
'includeSmarts': True,
'includeRxn': False
},
{
'includePkl': False,
'includeSmiles': False,
'includeSmarts': False,
'includeRxn': True
},
]
for opt in opts:
npng = rdChemReactions.ReactionMetadataToPNGString(rxn, png, **opt)
nrxn = rdChemReactions.ReactionFromPNGString(npng)
self.assertFalse(nrxn is None)
self.assertEqual(nrxn.GetNumReactantTemplates(), 2)
self.assertEqual(nrxn.GetNumProductTemplates(), 1)
def _getProductCXSMILES(product):
"""
Clear properties.
Mapping properties show up in CXSMILES make validation less readable.
"""
for a in product.GetAtoms():
for k in a.GetPropsAsDict():
a.ClearProp(k)
return Chem.MolToCXSmiles(product)
def _reactAndSummarize(rxn_smarts, *smiles):
"""
Run a reaction and combine the products in a single string.
Makes errors readable ish
"""
rxn = rdChemReactions.ReactionFromSmarts(rxn_smarts)
mols = [Chem.MolFromSmiles(s) for s in smiles]
products = []
for prods in rxn.RunReactants(mols):
products.append(' + '.join(map(_getProductCXSMILES, prods)))
products = ' OR '.join(products)
return products
class StereoGroupTests(unittest.TestCase):
def test_reaction_preserves_stereo(self):
"""
StereoGroup atoms are in the reaction, but the reaction doesn't affect
the chirality at the stereo centers
-> preserve stereo group
"""
reaction = '[C@:1]>>[C@:1]'
reactants = ['F[C@H](Cl)Br |o1:1|', 'F[C@H](Cl)Br |&1:1|', 'FC(Cl)Br']
for reactant in reactants:
products = _reactAndSummarize(reaction, reactant)
self.assertEqual(products, reactant)
def test_reaction_ignores_stereo(self):
"""
StereoGroup atoms are in the reaction, but the reaction doesn't specify the
chirality at the stereo centers
-> preserve stereo group
"""
reaction = '[C:1]>>[C:1]'
reactants = ['F[C@H](Cl)Br |o1:1|', 'F[C@H](Cl)Br |&1:1|', 'FC(Cl)Br']
for reactant in reactants:
products = _reactAndSummarize(reaction, reactant)
self.assertEqual(products, reactant)
def test_reaction_inverts_stereo(self):
"""
StereoGroup atoms are in the reaction, and the reaction inverts the specified
chirality at the stereo centers.
-> preserve stereo group
"""
reaction = '[C@:1]>>[C@@:1]'
products = _reactAndSummarize(reaction, 'F[C@H](Cl)Br |o1:1|')
self.assertEqual(products, 'F[C@H](Cl)Br |o1:1|')
products = _reactAndSummarize(reaction, 'F[C@@H](Cl)Br |&1:1|')
self.assertEqual(products, 'F[C@H](Cl)Br |&1:1|')
products = _reactAndSummarize(reaction, 'FC(Cl)Br')
self.assertEqual(products, 'FC(Cl)Br')
def test_reaction_destroys_stereo(self):
"""
StereoGroup atoms are in the reaction, and the reaction destroys the specified
chirality at the stereo centers
-> invalidate stereo center, preserve the rest of the stereo group.
"""
reaction = '[C@:1]>>[C:1]'
products = _reactAndSummarize(reaction, 'F[C@H](Cl)Br |o1:1|')
self.assertEqual(products, 'FC(Cl)Br')
products = _reactAndSummarize(reaction, 'F[C@@H](Cl)Br |&1:1|')
self.assertEqual(products, 'FC(Cl)Br')
products = _reactAndSummarize(reaction, 'FC(Cl)Br')
self.assertEqual(products, 'FC(Cl)Br')
reaction = '[C@:1]F>>[C:1]F'
# Reaction destroys stereo (but preserves unaffected group
products = _reactAndSummarize(reaction, 'F[C@H](Cl)[C@@H](Cl)Br |o1:1,&2:3|')
self.assertEqual(products, 'FC(Cl)[C@H](Cl)Br |&1:3|')
# Reaction destroys stereo (but preserves the rest of the group
products = _reactAndSummarize(reaction, 'F[C@H](Cl)[C@@H](Cl)Br |&1:1,3|')
self.assertEqual(products, 'FC(Cl)[C@H](Cl)Br |&1:3|')
def test_reaction_defines_stereo(self):
"""
StereoGroup atoms are in the reaction, and the reaction creates the specified
chirality at the stereo centers
-> remove the stereo center from
-> invalidate stereo group
"""
products = _reactAndSummarize('[C:1]>>[C@@:1]', 'F[C@H](Cl)Br |o1:1|')
self.assertEqual(products, 'F[C@@H](Cl)Br')
products = _reactAndSummarize('[C:1]>>[C@@:1]', 'F[C@@H](Cl)Br |&1:1|')
self.assertEqual(products, 'F[C@@H](Cl)Br')
products = _reactAndSummarize('[C:1]>>[C@@:1]', 'FC(Cl)Br')
self.assertEqual(products, 'F[C@@H](Cl)Br')
# Remove group with defined stereo
products = _reactAndSummarize('[C:1]F>>[C@@:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:1,&2:3|')
self.assertEqual(products, 'F[C@@H](Cl)[C@H](Cl)Br |&1:3|')
# Remove atoms with defined stereo from group
products = _reactAndSummarize('[C:1]F>>[C@@:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:1,3|')
self.assertEqual(products, 'F[C@@H](Cl)[C@H](Cl)Br |o1:3|')
def test_stereogroup_is_spectator_to_reaction(self):
"""
StereoGroup atoms are not in the reaction
-> stereo group is unaffected
"""
# 5a. Reaction preserves unrelated stereo
products = _reactAndSummarize('[C@:1]F>>[C@:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:3|')
self.assertEqual(products, 'F[C@H](Cl)[C@H](Cl)Br |o1:3|')
# 5b. Reaction ignores unrelated stereo'
products = _reactAndSummarize('[C:1]F>>[C:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:3|')
self.assertEqual(products, 'F[C@H](Cl)[C@H](Cl)Br |o1:3|')
# 5c. Reaction inverts unrelated stereo'
products = _reactAndSummarize('[C@:1]F>>[C@@:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:3|')
self.assertEqual(products, 'F[C@@H](Cl)[C@H](Cl)Br |o1:3|')
# 5d. Reaction destroys unrelated stereo' 1:3|
products = _reactAndSummarize('[C@:1]F>>[C:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:3|')
self.assertEqual(products, 'FC(Cl)[C@H](Cl)Br |o1:3|')
# 5e. Reaction assigns unrelated stereo'
products = _reactAndSummarize('[C:1]F>>[C@@:1]F', 'F[C@H](Cl)[C@@H](Cl)Br |o1:3|')
self.assertEqual(products, 'F[C@@H](Cl)[C@H](Cl)Br |o1:3|')
def test_reaction_splits_stereogroup(self):
"""
StereoGroup atoms are split into two products by the reaction
-> Should the group be invalidated or trimmed?
"""
products = _reactAndSummarize('[C:1]OO[C:2]>>[C:2]O.O[C:1]',
'F[C@H](Cl)OO[C@@H](Cl)Br |o1:1,5|')
# Two product sets, each with two mols:
self.assertEqual(products.count('|o1:1|'), 4)
def test_reaction_copies_stereogroup(self):
"""
If multiple copies of an atom in StereoGroup show up in the product, they
should all be part of the same product StereoGroup.
"""
# Stereogroup atoms are in the reaction with multiple copies in the product
products = _reactAndSummarize('[O:1].[C:2]=O>>[O:1][C:2][O:1]',
'Cl[C@@H](Br)C[C@H](Br)CCO |&1:1,4|', 'CC(=O)C')
# stereogroup manually checked, product SMILES assumed correct.
self.assertEqual(products,
'CC(C)(OCC[C@H](Br)C[C@H](Cl)Br)OCC[C@H](Br)C[C@H](Cl)Br |&1:6,9,15,18|')
# Stereogroup atoms are not in the reaction, but have multiple copies in the
# product.
products = _reactAndSummarize('[O:1].[C:2]=O>>[O:1][C:2][O:1]',
'Cl[C@@H](Br)C[C@H](Br)CCO |&1:1,4|', 'CC(=O)C')
# stereogroup manually checked, product SMILES assumed correct.
self.assertEqual(products,
'CC(C)(OCC[C@H](Br)C[C@H](Cl)Br)OCC[C@H](Br)C[C@H](Cl)Br |&1:6,9,15,18|')
def test_github(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:2][C:3]>>[C:2][C][*:3]')
mol = Chem.MolFromSmiles("C/C=C/C")
# this shouldn't raise
rxn.RunReactants([mol])
def test_rxnblock_removehs(self):
rxnblock = """$RXN
Dummy 0
Dummy 0123456789
1 1
$MOL
Dummy 01234567892D
10 10 0 0 0 0 999 V2000
7.0222 -11.1783 0.0000 C 0 0 0 0 0 0 0 0 0 1 0 0
8.0615 -11.7783 0.0000 O 0 0 0 0 0 0 0 0 0 2 0 0
7.0222 -9.6783 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
5.7231 -8.9283 0.0000 C 0 0 0 0 0 0 0 0 0 4 0 0
5.7231 -7.7283 0.0000 A 0 0 0 0 0 0 0 0 0 5 0 0
4.4242 -9.6783 0.0000 C 0 0 0 0 0 0 0 0 0 6 0 0
4.4242 -11.1783 0.0000 C 0 0 0 0 0 0 0 0 0 7 0 0
3.3849 -11.7783 0.0000 A 0 0 0 0 0 0 0 0 0 8 0 0
5.7231 -11.9283 0.0000 N 0 0 0 0 0 0 0 0 0 9 0 0
5.7231 -13.1094 0.0000 H 0 0
1 2 2 0 0 0 8
1 3 1 0 0 0 8
3 4 2 0 0 0 8
4 5 1 0 0 0 2
4 6 1 0 0 0 8
6 7 2 0 0 0 8
7 8 1 0 0 0 2
7 9 1 0 0 0 8
9 1 1 0 0 0 8
9 10 1 0
M SUB 1 9 2
M END
$MOL
Dummy 01234567892D
9 9 0 0 0 0 999 V2000
17.0447 -11.1783 0.0000 C 0 0 0 0 0 0 0 0 0 1 0 0
18.0840 -11.7783 0.0000 O 0 0 0 0 0 0 0 0 0 2 0 0
17.0447 -9.6783 0.0000 N 0 0 0 0 0 0 0 0 0 3 0 0
15.7457 -8.9283 0.0000 C 0 0 0 0 0 0 0 0 0 4 0 0
15.7457 -7.7283 0.0000 A 0 0 0 0 0 0 0 0 0 5 0 0
14.4467 -9.6783 0.0000 C 0 0 0 0 0 0 0 0 0 6 0 0
14.4467 -11.1783 0.0000 C 0 0 0 0 0 0 0 0 0 7 0 0
13.4074 -11.7783 0.0000 A 0 0 0 0 0 0 0 0 0 8 0 0
15.7457 -11.9283 0.0000 N 0 0 0 0 0 0 0 0 0 9 0 0
1 2 1 0 0 0 8
1 3 1 0 0 0 8
3 4 2 0 0 0 8
4 5 1 0 0 0 2
4 6 1 0 0 0 8
6 7 2 0 0 0 8
7 8 1 0 0 0 2
7 9 1 0 0 0 8
9 1 2 0 0 0 8
M END
"""
mol = Chem.MolFromSmiles("c1(=O)nc([Cl])cc([F])[nH]1")
rxn = rdChemReactions.ReactionFromRxnBlock(rxnblock)
self.assertIsNotNone(rxn)
prods = rxn.RunReactants((mol, ))
# if the explicit hydrogen is not removed and the reactant template
# is not sanitized, the reactant template is not aromatic and our
# aromatic reactant won't match
self.assertEqual(len(prods), 0)
rxn = rdChemReactions.ReactionFromRxnBlock(rxnblock, removeHs=True, sanitize=True)
self.assertIsNotNone(rxn)
prods = rxn.RunReactants((mol, ))
self.assertEqual(len(prods), 2)
def testReactionInPlace(self):
rxn = rdChemReactions.ReactionFromSmarts('[C:1][N:2][C:3]=[O:4]>>[C:1][O:2][C:3]=[O:4]')
self.assertIsNotNone(rxn)
reactant = Chem.MolFromSmiles('O=C(C)NCC')
self.assertTrue(rxn.RunReactantInPlace(reactant))
Chem.SanitizeMol(reactant)
self.assertEqual(Chem.MolToSmiles(reactant), 'CCOC(C)=O')
self.assertFalse(rxn.RunReactantInPlace(reactant))
self.assertEqual(Chem.MolToSmiles(reactant), 'CCOC(C)=O')
rxn = rdChemReactions.ReactionFromSmarts('CC[N:1]>>[N:1]')
self.assertIsNotNone(rxn)
reactant = Chem.MolFromSmiles('CCCN.Cl')
self.assertTrue(rxn.RunReactantInPlace(reactant))
Chem.SanitizeMol(reactant)
self.assertEqual(Chem.MolToSmiles(reactant), 'N')
reactant = Chem.MolFromSmiles('CCCN.Cl')
self.assertTrue(rxn.RunReactantInPlace(reactant, removeUnmatchedAtoms=False))
Chem.SanitizeMol(reactant)
self.assertEqual(Chem.MolToSmiles(reactant), 'C.Cl.N')
def testGithub4651(self):
mol_sulfonylchloride = Chem.MolFromSmiles("Nc1c(CCCSNCC)cc(cc1)S(=O)(=O)Cl")
mol_amine = Chem.MolFromSmiles("Nc2cc(C)on2")
mol_sulfonamide = Chem.MolFromSmiles("CCNSCCCc1cc(S(=O)(=O)Nc2cc(C)on2)ccc1N")
smirks_fwd = (
"[S;$(S(=O)(=O)[C,c,N]):1](=O)(=O)(-[Cl])"
"."
"[N;$([N&H2&D1,N&H1&D2])"
# N aliphatic and not aromatic bond to carbon
"&$(N(-&!@[#6]))"
"&!$(N-C=[O,N,S]):2]"
">>"
"[S:1](=O)(=O)-[N+0:2]")
smirks_reverse = ">>".join(smirks_fwd.split(">>")[::-1])
reaction_fwd = rdChemReactions.ReactionFromSmarts(smirks_fwd)
reaction_reverse = rdChemReactions.ReactionFromSmarts(smirks_reverse)
product = reaction_fwd.RunReactants((mol_sulfonylchloride, mol_amine))[0][0]
reagent_sulfonylchloride, reagent_amine = reaction_reverse.RunReactants((mol_sulfonamide, ))[0]
# trigger bug
with self.assertRaises(RuntimeError):
reaction_fwd.RunReactants((reagent_sulfonylchloride, reagent_amine))
# The second call used to deadlock
with self.assertRaises(RuntimeError):
reaction_fwd.RunReactants((reagent_sulfonylchloride, reagent_amine))
def testV3000Reactions(self):
reaction = rdChemReactions.ReactionFromSmarts(
'[cH:1]1[cH:2][cH:3][cH:4][cH:5][c:6]1-[Br].[#6:7]B(O)O>>[cH:1]1[cH:2][cH:3][cH:4][cH:5][c:6]1-[#6:7]'
)
self.assertIsNotNone(reaction)
mb1 = rdChemReactions.ReactionToRxnBlock(reaction, forceV3000=True)
mb2 = rdChemReactions.ReactionToV3KRxnBlock(reaction)
self.assertEqual(mb1, mb2)
reaction2 = rdChemReactions.ReactionFromRxnBlock(mb1)
self.assertIsNotNone(reaction2)
self.assertEqual(reaction.GetNumReactantTemplates(), reaction2.GetNumReactantTemplates())
def testGithub6138(self):
mol = Chem.MolFromSmiles("COc1ccccc1Oc1nc(Nc2cc(C)[nH]n2)cc2ccccc12")
rxn = AllChem.ReactionFromSmarts(
"([c:1]:[n&H1&+0&D2:3]:[n:2])>>([c:1]:[n&H0&+0&D3:3](:[n:2])-C1-C-C-C-C-O-1)")
def run(r):
return Chem.MolToSmiles(r.RunReactants((mol, ))[0][0])
rxn_reloaded = pickle.loads(pickle.dumps(rxn))
res1 = Chem.MolToSmiles(rxn.RunReactants((mol, ))[0][0])
res2 = Chem.MolToSmiles(rxn_reloaded.RunReactants((mol, ))[0][0])
rxn_reloaded_after_use = pickle.loads(pickle.dumps(rxn))
res3 = Chem.MolToSmiles(rxn_reloaded_after_use.RunReactants((mol, ))[0][0])
self.assertEqual(res1, res2)
self.assertEqual(res1, res3)
def testGithub6211(self):
rxn = AllChem.ReactionFromSmarts("[C:1][C@:2]([N:3])[O:4]>>[C:1][C@@:2]([N:3])[O:4]")
mol = Chem.MolFromSmiles("CC[C@@H](N)O")
self.assertEqual(Chem.MolToSmiles(rxn.RunReactants((mol, ))[0][0]), "CC[C@H](N)O")
rxn.GetSubstructParams().useChirality = True
self.assertEqual(len(rxn.RunReactants((mol, ))), 0)
def testMrvBlockContainsReaction(self):
fn1 = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
'aspirin.mrv')
with open(fn1, 'r') as inf:
ind1 = inf.read()
fn2 = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
'aspirineSynthesisWithAttributes.mrv')
with open(fn2, 'r') as inf:
ind2 = inf.read()
self.assertFalse(rdChemReactions.MrvFileIsReaction(fn1))
self.assertTrue(rdChemReactions.MrvFileIsReaction(fn2))
self.assertFalse(rdChemReactions.MrvBlockIsReaction(ind1))
self.assertTrue(rdChemReactions.MrvBlockIsReaction(ind2))
def testMrvOutput(self):
fn2 = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MarvinParse', 'test_data',
'aspirineSynthesisWithAttributes.mrv')
rxn = rdChemReactions.ReactionFromMrvFile(fn2)
self.assertIsNotNone(rxn)
rxnb = rdChemReactions.ReactionToMrvBlock(rxn)
self.assertTrue('<reaction>' in rxnb)
fName = tempfile.NamedTemporaryFile(suffix='.mrv').name
self.assertFalse(os.path.exists(fName))
rdChemReactions.ReactionToMrvFile(rxn, fName)
self.assertTrue(os.path.exists(fName))
os.unlink(fName)
def testCDXML(self):
fname = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'test_data', 'CDXML', 'rxn2.cdxml')
rxns = AllChem.ReactionsFromCDXMLFile(fname)
self.assertEqual(len(rxns), 1)
self.assertEqual(
AllChem.ReactionToSmarts(rxns[0]),
"[#6&D2:2]1:[#6&D2:3]:[#6&D2:4]:[#6&D3:1](:[#6&D2:5]:[#6&D2:6]:1)-[#17&D1].[#6&D3](-[#5&D2]-[#6&D3:7]1:[#6&D2:8]:[#6&D2:9]:[#6&D2:10]:[#6&D2:11]:[#6&D2:12]:1)(-[#8&D1])-[#8&D1]>>[#6&D2:1]1:[#6&D2:5]:[#6&D3:6](:[#6&D2:2]:[#6&D2:3]:[#6&D2:4]:1)-[#6&D3:7]1:[#6&D2:8]:[#6&D2:9]:[#6&D2:10]:[#6&D2:11]:[#6&D2:12]:1"
)
rxns = AllChem.ReactionsFromCDXMLFile('does-not-exist.cdxml')
self.assertEqual(len(rxns), 0)
with open(fname, 'r') as inf:
cdxml = inf.read()
rxns = AllChem.ReactionsFromCDXMLBlock(cdxml)
self.assertEqual(len(rxns), 1)
self.assertEqual(
AllChem.ReactionToSmarts(rxns[0]),
"[#6&D2:2]1:[#6&D2:3]:[#6&D2:4]:[#6&D3:1](:[#6&D2:5]:[#6&D2:6]:1)-[#17&D1].[#6&D3](-[#5&D2]-[#6&D3:7]1:[#6&D2:8]:[#6&D2:9]:[#6&D2:10]:[#6&D2:11]:[#6&D2:12]:1)(-[#8&D1])-[#8&D1]>>[#6&D2:1]1:[#6&D2:5]:[#6&D3:6](:[#6&D2:2]:[#6&D2:3]:[#6&D2:4]:1)-[#6&D3:7]1:[#6&D2:8]:[#6&D2:9]:[#6&D2:10]:[#6&D2:11]:[#6&D2:12]:1"
)
rxns = AllChem.ReactionsFromCDXMLBlock('')
self.assertEqual(len(rxns), 0)
def testSanitizeRxnAsMols(self):
rxn = AllChem.ReactionFromSmarts("C1=CC=CC=C1>CN(=O)=O>C1=CC=CC=N1", useSmiles=True)
self.assertIsNotNone(rxn)
self.assertFalse(rxn.GetReactantTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertFalse(rxn.GetProductTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertEqual(rxn.GetAgentTemplate(0).GetAtomWithIdx(1).GetFormalCharge(), 0)
AllChem.SanitizeRxnAsMols(rxn)
self.assertTrue(rxn.GetReactantTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertTrue(rxn.GetProductTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertEqual(rxn.GetAgentTemplate(0).GetAtomWithIdx(1).GetFormalCharge(), 1)
rxn = AllChem.ReactionFromSmarts("C1=CC=CC=C1>CN(=O)=O>C1=CC=CC=N1", useSmiles=True)
self.assertIsNotNone(rxn)
self.assertFalse(rxn.GetReactantTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertFalse(rxn.GetProductTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertEqual(rxn.GetAgentTemplate(0).GetAtomWithIdx(1).GetFormalCharge(), 0)
AllChem.SanitizeRxnAsMols(rxn, Chem.SanitizeFlags.SANITIZE_CLEANUP)
self.assertFalse(rxn.GetReactantTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertFalse(rxn.GetProductTemplate(0).GetBondWithIdx(0).GetIsAromatic())
self.assertEqual(rxn.GetAgentTemplate(0).GetAtomWithIdx(1).GetFormalCharge(), 1)
def testSmilesWriteParams(self):
rxn = AllChem.ReactionFromSmarts(
"[C:1]-[C:2].[NH3:3]->[Fe:4]-[NH2:5]>>[C:1]=[C:2].[NH3:3]->[Fe:4]-[NH2:5]")
self.assertIsNotNone(rxn)
params = AllChem.SmilesWriteParams()
self.assertEqual(
AllChem.ReactionToSmiles(rxn, params),
"[CH3:1][CH3:2].[NH3:3]->[Fe:4][NH2:5]>>[CH2:1]=[CH2:2].[NH3:3]->[Fe:4][NH2:5]")
self.assertEqual(
AllChem.ReactionToSmarts(rxn, params),
"[C:1]-[C:2].[N&H3:3]->[#26:4]-[N&H2:5]>>[C:1]=[C:2].[N&H3:3]->[#26:4]-[N&H2:5]")
params.includeDativeBonds = False
self.assertEqual(AllChem.ReactionToSmiles(rxn, params),
"[CH3:1][CH3:2].[NH3:3][Fe:4][NH2:5]>>[CH2:1]=[CH2:2].[NH3:3][Fe:4][NH2:5]")
self.assertEqual(
AllChem.ReactionToSmarts(rxn, params),
"[C:1]-[C:2].[N&H3:3]-[#26:4]-[N&H2:5]>>[C:1]=[C:2].[N&H3:3]-[#26:4]-[N&H2:5]")
class CXExtensionsTests(unittest.TestCase):
def test_cxsmarts_reaction(self):
CXSmarts_string = (
"[CH3:1][CH:2]([CH3:3])[*:4].[OH:5][CH2:6][*:7]>O=C=O>"
"[CH3:1][CH:2]([CH3:3])[CH2:6][OH:5] |$;;;_AP1;;;_AP1;;;;;;;;$|"
)
rxnCXSmarts = rdChemReactions.ReactionFromSmarts(CXSmarts_string)
self.assertIsNotNone(rxnCXSmarts)
params = Chem.SmilesWriteParams()
flags = Chem.CXSmilesFields.CX_ALL ^ Chem.CXSmilesFields.CX_ATOM_PROPS
rxnCXSmarts_string = rdChemReactions.ReactionToCXSmarts(rxnCXSmarts, params, flags)
expected_rxnCXSmarts_string = (
"[C&H3:1][C&H1:2]([C&H3:3])[*:4].[O&H1:5][C&H2:6][*:7]>O=C=O>"
"[C&H3:1][C&H1:2]([C&H3:3])[C&H2:6][O&H1:5] |$;;;_AP1;;;_AP1;;;;;;;;$|"
)
self.assertEqual(rxnCXSmarts_string, expected_rxnCXSmarts_string)
def test_cxsmiles_reaction(self):
reactant1 = '[CH3:1][CH:2]([CH3:3])[*:4]'
reactant2 = '[OH:5][CH2:6][*:7]'
product = '[CH3:1][CH:2]([CH3:3])[CH2:6][OH:5]'
cxExtension = "|$;;;_R1;;;_R2;;;;;;;;$|"
reaction_smarts = f'{reactant1}.{reactant2}>O=C=O>{product} {cxExtension}'
reaction = rdChemReactions.ReactionFromSmarts(reaction_smarts)
params = Chem.SmilesWriteParams()
flags = Chem.CXSmilesFields.CX_ALL ^ Chem.CXSmilesFields.CX_ATOM_PROPS
cxsmiles_reaction_string = Chem.rdChemReactions.ReactionToCXSmiles(reaction, params, flags)
self.assertIsNotNone(cxsmiles_reaction_string)
expected_cxsmiles_reaction_string = (
"[CH3:1][CH:2]([CH3:3])[*:4].[OH:5][CH2:6][*:7]>O=C=O>"
"[CH3:1][CH:2]([CH3:3])[CH2:6][OH:5] |$;;;_R1;;;_R2;;;;;;;;$|"
)
self.assertEqual(cxsmiles_reaction_string, expected_cxsmiles_reaction_string)
if __name__ == '__main__':
unittest.main(verbosity=True)