* Fixes #2757

Note that fix here really means changing the way the doctest uses the code

* add py38 to build matrix

* adjust to more modern boost

* update another set of expected results

* re-enable the other CI configs
This commit is contained in:
Greg Landrum
2020-07-03 19:17:54 +02:00
committed by GitHub
parent 1630540ef0
commit bc82d81c45
5 changed files with 330 additions and 210 deletions

View File

@@ -5,9 +5,9 @@ steps:
conda config --set always_yes yes --set changeps1 no
conda update -q conda
conda info -a
conda create --name rdkit_build $(compiler) cmake \
conda create --name rdkit_build $(python) $(compiler) cmake \
boost-cpp=$(boost_version) boost=$(boost_version) \
py-boost=$(boost_version) libboost=$(boost_version) \
py-boost=$(boost_version) \
numpy matplotlib pillow eigen pandas \
sphinx recommonmark jupyter
conda activate rdkit_build
@@ -36,6 +36,7 @@ steps:
-DRDK_BUILD_THREADSAFE_SSS=ON \
-DRDK_TEST_MULTITHREADED=ON \
-DBoost_NO_SYSTEM_PATHS=ON \
-DBoost_NO_BOOST_CMAKE=TRUE \
-DRDK_BOOST_PYTHON3_NAME=$(python_name) \
-DPYTHON_EXECUTABLE=${CONDA_PREFIX}/bin/python3 \
-DCMAKE_INCLUDE_PATH="${CONDA_PREFIX}/include" \

View File

@@ -0,0 +1,74 @@
steps:
- bash: |
source ${CONDA}/etc/profile.d/conda.sh
sudo chown -R ${USER} ${CONDA}
conda config --set always_yes yes --set changeps1 no
conda update -q conda
conda info -a
conda create --name rdkit_build $(compiler) cmake \
boost-cpp=$(boost_version) boost=$(boost_version) \
py-boost=$(boost_version) libboost=$(boost_version) \
numpy matplotlib pillow eigen pandas \
sphinx recommonmark jupyter
conda activate rdkit_build
conda install -c rdkit nox cairo=1.14.6
displayName: Setup build environment
- bash: |
source ${CONDA}/etc/profile.d/conda.sh
conda activate rdkit_build
mkdir build && cd build && \
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DRDK_INSTALL_INTREE=ON \
-DRDK_INSTALL_STATIC_LIBS=OFF \
-DRDK_BUILD_CPP_TESTS=ON \
-DRDK_BUILD_PYTHON_WRAPPERS=ON \
-DRDK_USE_BOOST_REGEX=ON \
-DRDK_BUILD_COORDGEN_SUPPORT=ON \
-DRDK_OPTIMIZE_POPCNT=ON \
-DRDK_BUILD_TEST_GZIP=ON \
-DRDK_BUILD_FREESASA_SUPPORT=ON \
-DRDK_BUILD_AVALON_SUPPORT=ON \
-DRDK_BUILD_INCHI_SUPPORT=ON \
-DRDK_BUILD_CAIRO_SUPPORT=ON \
-DRDK_BUILD_SWIG_WRAPPERS=OFF \
-DRDK_SWIG_STATIC=OFF \
-DRDK_BUILD_THREADSAFE_SSS=ON \
-DRDK_TEST_MULTITHREADED=ON \
-DBoost_NO_SYSTEM_PATHS=ON \
-DRDK_BOOST_PYTHON3_NAME=$(python_name) \
-DPYTHON_EXECUTABLE=${CONDA_PREFIX}/bin/python3 \
-DCMAKE_INCLUDE_PATH="${CONDA_PREFIX}/include" \
-DCMAKE_LIBRARY_PATH="${CONDA_PREFIX}/lib"
displayName: Configure build (Run CMake)
- bash: |
source ${CONDA}/etc/profile.d/conda.sh
conda activate rdkit_build
cd build
make -j $( $(number_of_cores) ) install
displayName: Build
- bash: |
source ${CONDA}/etc/profile.d/conda.sh
conda activate rdkit_build
export RDBASE=`pwd`
export PYTHONPATH=${RDBASE}:${PYTHONPATH}
export LD_LIBRARY_PATH=${RDBASE}/lib:${LD_LIBRARY_PATH}
export QT_QPA_PLATFORM='offscreen'
cd build
ctest -j $( $(number_of_cores) ) --output-on-failure -T Test
displayName: Run tests
- bash: |
source ${CONDA}/etc/profile.d/conda.sh
conda activate rdkit_build
export RDBASE=`pwd`
export PYTHONPATH=${RDBASE}:${PYTHONPATH}
export LD_LIBRARY_PATH=${RDBASE}/lib:${LD_LIBRARY_PATH}
export QT_QPA_PLATFORM='offscreen'
cd Docs/Book
make doctest
displayName: Run documentation tests
- task: PublishTestResults@2
inputs:
testResultsFormat: 'CTest'
testResultsFiles: 'build/Testing/*/Test.xml'
testRunTitle: $(system.phasedisplayname) CTest Test Run

View File

@@ -4,7 +4,19 @@ trigger:
- dev/*
jobs:
- job: Ubuntu_16_04_x64
- job: Ubuntu_18_04_x64
timeoutInMinutes: 90
pool:
vmImage: ubuntu-18.04
variables:
python: python=3.8
boost_version: 1.71.0
compiler: gxx_linux-64
number_of_cores: nproc
python_name: python38
steps:
- template: .azure-pipelines/linux_build.yml
- job: Ubuntu_16_04_x64_py37
timeoutInMinutes: 90
pool:
vmImage: ubuntu-16.04
@@ -14,7 +26,7 @@ jobs:
number_of_cores: nproc
python_name: python37
steps:
- template: .azure-pipelines/linux_build.yml
- template: .azure-pipelines/linux_build_py37.yml
- job: macOS_10_13_x64
timeoutInMinutes: 90
pool:

View File

@@ -4,7 +4,7 @@ from rdkit.Chem.rdDistGeom import EmbedMolecule
class StereoEnumerationOptions(object):
"""
"""
- tryEmbedding: if set the process attempts to generate a standard RDKit distance geometry
conformation for the stereisomer. If this fails, we assume that the stereoisomer is
non-physical and don't return it. NOTE that this is computationally expensive and is
@@ -24,116 +24,115 @@ class StereoEnumerationOptions(object):
- onlyStereoGroups: Only find stereoisomers that differ at the
StereoGroups associated with the molecule.
"""
__slots__ = ('tryEmbedding', 'onlyUnassigned',
'onlyStereoGroups', 'maxIsomers', 'rand', 'unique')
__slots__ = ('tryEmbedding', 'onlyUnassigned',
'onlyStereoGroups', 'maxIsomers', 'rand', 'unique')
def __init__(self, tryEmbedding=False, onlyUnassigned=True,
maxIsomers=1024, rand=None, unique=True,
onlyStereoGroups=False):
self.tryEmbedding = tryEmbedding
self.onlyUnassigned = onlyUnassigned
self.onlyStereoGroups = onlyStereoGroups
self.maxIsomers = maxIsomers
self.rand = rand
self.unique = unique
def __init__(self, tryEmbedding=False, onlyUnassigned=True,
maxIsomers=1024, rand=None, unique=True,
onlyStereoGroups=False):
self.tryEmbedding = tryEmbedding
self.onlyUnassigned = onlyUnassigned
self.onlyStereoGroups = onlyStereoGroups
self.maxIsomers = maxIsomers
self.rand = rand
self.unique = unique
class _BondFlipper(object):
def __init__(self, bond):
self.bond = bond
def __init__(self, bond):
self.bond = bond
def flip(self, flag):
if flag:
self.bond.SetStereo(Chem.BondStereo.STEREOCIS)
else:
self.bond.SetStereo(Chem.BondStereo.STEREOTRANS)
def flip(self, flag):
if flag:
self.bond.SetStereo(Chem.BondStereo.STEREOCIS)
else:
self.bond.SetStereo(Chem.BondStereo.STEREOTRANS)
class _AtomFlipper(object):
def __init__(self, atom):
self.atom = atom
def __init__(self, atom):
self.atom = atom
def flip(self, flag):
if flag:
self.atom.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CW)
else:
self.atom.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CCW)
def flip(self, flag):
if flag:
self.atom.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CW)
else:
self.atom.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CCW)
class _StereoGroupFlipper(object):
def __init__(self, group):
self._original_parities = [(a, a.GetChiralTag()) for a in group.GetAtoms()]
def __init__(self, group):
self._original_parities = [(a, a.GetChiralTag()) for a in group.GetAtoms()]
def flip(self, flag):
if flag:
for a, original_parity in self._original_parities:
a.SetChiralTag(original_parity)
else:
for a, original_parity in self._original_parities:
if original_parity == Chem.ChiralType.CHI_TETRAHEDRAL_CW:
a.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CCW)
elif original_parity == Chem.ChiralType.CHI_TETRAHEDRAL_CCW:
a.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CW)
def flip(self, flag):
if flag:
for a, original_parity in self._original_parities:
a.SetChiralTag(original_parity)
else:
for a, original_parity in self._original_parities:
if original_parity == Chem.ChiralType.CHI_TETRAHEDRAL_CW:
a.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CCW)
elif original_parity == Chem.ChiralType.CHI_TETRAHEDRAL_CCW:
a.SetChiralTag(Chem.ChiralType.CHI_TETRAHEDRAL_CW)
def _getFlippers(mol, options):
Chem.FindPotentialStereoBonds(mol)
Chem.FindPotentialStereoBonds(mol)
flippers = []
if not options.onlyStereoGroups:
for atom in mol.GetAtoms():
if atom.HasProp("_ChiralityPossible"):
if (not options.onlyUnassigned
or atom.GetChiralTag() == Chem.ChiralType.CHI_UNSPECIFIED):
flippers.append(_AtomFlipper(atom))
flippers = []
if not options.onlyStereoGroups:
for atom in mol.GetAtoms():
if atom.HasProp("_ChiralityPossible"):
if (not options.onlyUnassigned
or atom.GetChiralTag() == Chem.ChiralType.CHI_UNSPECIFIED):
flippers.append(_AtomFlipper(atom))
for bond in mol.GetBonds():
bstereo = bond.GetStereo()
if bstereo != Chem.BondStereo.STEREONONE:
if (not options.onlyUnassigned
or bstereo == Chem.BondStereo.STEREOANY):
flippers.append(_BondFlipper(bond))
for bond in mol.GetBonds():
bstereo = bond.GetStereo()
if bstereo != Chem.BondStereo.STEREONONE:
if (not options.onlyUnassigned
or bstereo == Chem.BondStereo.STEREOANY):
flippers.append(_BondFlipper(bond))
if options.onlyUnassigned:
# otherwise these will be counted twice
for group in mol.GetStereoGroups():
if group.GetGroupType() != Chem.StereoGroupType.STEREO_ABSOLUTE:
flippers.append(_StereoGroupFlipper(group))
if options.onlyUnassigned:
# otherwise these will be counted twice
for group in mol.GetStereoGroups():
if group.GetGroupType() != Chem.StereoGroupType.STEREO_ABSOLUTE:
flippers.append(_StereoGroupFlipper(group))
return flippers
return flippers
class _RangeBitsGenerator(object):
def __init__(self, nCenters):
self.nCenters = nCenters
def __iter__(self):
for val in range(2**self.nCenters):
yield val
def __init__(self, nCenters):
self.nCenters = nCenters
def __iter__(self):
for val in range(2**self.nCenters):
yield val
class _UniqueRandomBitsGenerator(object):
def __init__(self, nCenters, maxIsomers, rand):
self.nCenters = nCenters
self.maxIsomers = maxIsomers
self.rand = rand
self.already_seen = set()
def __init__(self, nCenters, maxIsomers, rand):
self.nCenters = nCenters
self.maxIsomers = maxIsomers
self.rand = rand
self.already_seen = set()
def __iter__(self):
# note: important that this is not 'while True' otherwise it
# would be possible to have an infinite loop caused by all
# isomers failing the embedding process
while len(self.already_seen) < 2**self.nCenters:
bits = self.rand.getrandbits(self.nCenters)
if bits in self.already_seen:
continue
def __iter__(self):
# note: important that this is not 'while True' otherwise it
# would be possible to have an infinite loop caused by all
# isomers failing the embedding process
while len(self.already_seen) < 2**self.nCenters:
bits = self.rand.getrandbits(self.nCenters)
if bits in self.already_seen:
continue
self.already_seen.add(bits)
yield bits
self.already_seen.add(bits)
yield bits
def GetStereoisomerCount(m, options=StereoEnumerationOptions()):
""" returns an estimate (upper bound) of the number of possible stereoisomers for a molecule
""" returns an estimate (upper bound) of the number of possible stereoisomers for a molecule
Arguments:
- m: the molecule to work with
@@ -156,13 +155,12 @@ def GetStereoisomerCount(m, options=StereoEnumerationOptions()):
8
"""
tm = Chem.Mol(m)
flippers = _getFlippers(tm, options)
return 2**len(flippers)
tm = Chem.Mol(m)
flippers = _getFlippers(tm, options)
return 2**len(flippers)
def EnumerateStereoisomers(m, options=StereoEnumerationOptions(), verbose=False):
""" returns a generator that yields possible stereoisomers for a molecule
""" returns a generator that yields possible stereoisomers for a molecule
Arguments:
- m: the molecule to work with
@@ -278,75 +276,74 @@ def EnumerateStereoisomers(m, options=StereoEnumerationOptions(), verbose=False)
F[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)Br
F[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)Br
Or randomly sample a small subset:
Or randomly sample a small subset. Note that if we want that sampling to be consistent
across python versions we need to provide a random number seed:
>>> m = Chem.MolFromSmiles('Br' + '[CH](Cl)' * 20 + 'F')
>>> opts = StereoEnumerationOptions(maxIsomers=3)
>>> opts = StereoEnumerationOptions(maxIsomers=3,rand=0xf00d)
>>> isomers = EnumerateStereoisomers(m, options=opts)
>>> for smi in sorted(Chem.MolToSmiles(x, isomericSmiles=True) for x in isomers):
... print(smi)
F[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)Br
F[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)Br
F[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)Br
>>> for smi in isomers: #sorted(Chem.MolToSmiles(x, isomericSmiles=True) for x in isomers):
... print(Chem.MolToSmiles(smi))
F[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)Br
F[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)Br
F[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H](Cl)Br
"""
tm = Chem.Mol(m)
for atom in tm.GetAtoms():
atom.ClearProp("_CIPCode")
# FIX: deal with bonds here too
flippers = _getFlippers(tm, options)
nCenters = len(flippers)
if not nCenters:
yield tm
return
tm = Chem.Mol(m)
for atom in tm.GetAtoms():
atom.ClearProp("_CIPCode")
# FIX: deal with bonds here too
flippers = _getFlippers(tm, options)
nCenters = len(flippers)
if not nCenters:
yield tm
return
if (options.maxIsomers == 0 or 2**nCenters <= options.maxIsomers):
bitsource = _RangeBitsGenerator(nCenters)
if (options.maxIsomers == 0 or 2**nCenters <= options.maxIsomers):
bitsource = _RangeBitsGenerator(nCenters)
else:
if options.rand is None:
# deterministic random seed invariant to input atom order
seed = hash(tuple(sorted([(a.GetDegree(), a.GetAtomicNum()) for a in tm.GetAtoms()])))
rand = random.Random(seed)
elif isinstance(options.rand, random.Random):
# other implementations of Python random number generators
# can inherit from this class to pick up utility methods
rand = options.rand
else:
if options.rand is None:
# deterministic random seed invariant to input atom order
seed = hash(tuple(sorted([(a.GetDegree(), a.GetAtomicNum()) for a in tm.GetAtoms()])))
rand = random.Random(seed)
elif isinstance(options.rand, random.Random):
# other implementations of Python random number generators
# can inherit from this class to pick up utility methods
rand = options.rand
else:
rand = random.Random(options.rand)
rand = random.Random(options.rand)
bitsource = _UniqueRandomBitsGenerator(nCenters, options.maxIsomers, rand)
bitsource = _UniqueRandomBitsGenerator(nCenters, options.maxIsomers, rand)
isomersSeen = set()
numIsomers = 0
for bitflag in bitsource:
for i in range(nCenters):
flag = bool(bitflag & (1 << i))
flippers[i].flip(flag)
isomersSeen = set()
numIsomers = 0
for bitflag in bitsource:
for i in range(nCenters):
flag = bool(bitflag & (1 << i))
flippers[i].flip(flag)
isomer = Chem.Mol(tm)
if options.unique:
cansmi = Chem.MolToSmiles(isomer, isomericSmiles=True)
if cansmi in isomersSeen:
continue
isomersSeen.add(cansmi)
if options.tryEmbedding:
ntm = Chem.AddHs(isomer)
cid = EmbedMolecule(ntm, randomSeed=bitflag)
if cid >= 0:
conf = Chem.Conformer(isomer.GetNumAtoms())
for aid in range(isomer.GetNumAtoms()):
conf.SetAtomPosition(aid, ntm.GetConformer().GetAtomPosition(aid))
isomer.AddConformer(conf)
else:
cid = 1
if cid >= 0:
yield isomer
numIsomers += 1
if options.maxIsomers != 0 and numIsomers >= options.maxIsomers:
break
elif verbose:
print("%s failed to embed" % (Chem.MolToSmiles(isomer, isomericSmiles=True)))
isomer = Chem.Mol(tm)
if options.unique:
cansmi = Chem.MolToSmiles(isomer, isomericSmiles=True)
if cansmi in isomersSeen:
continue
isomersSeen.add(cansmi)
if options.tryEmbedding:
ntm = Chem.AddHs(isomer)
cid = EmbedMolecule(ntm, randomSeed=bitflag)
if cid >= 0:
conf = Chem.Conformer(isomer.GetNumAtoms())
for aid in range(isomer.GetNumAtoms()):
conf.SetAtomPosition(aid, ntm.GetConformer().GetAtomPosition(aid))
isomer.AddConformer(conf)
else:
cid = 1
if cid >= 0:
yield isomer
numIsomers += 1
if options.maxIsomers != 0 and numIsomers >= options.maxIsomers:
break
elif verbose:
print("%s failed to embed" % (Chem.MolToSmiles(isomer, isomericSmiles=True)))

View File

@@ -3,6 +3,7 @@
"""
from rdkit import RDConfig
import unittest, os
import sys
import random
from rdkit import Chem
from rdkit.Chem import AllChem
@@ -126,8 +127,8 @@ class TestCase(unittest.TestCase):
def testTorsionFingerprintsColinearBonds(self):
# test that single bonds adjacent to triple bonds are ignored
mol = Chem.MolFromSmiles('CCC#CCC')
tors_list, tors_list_rings = TorsionFingerprints.CalculateTorsionLists(mol,
ignoreColinearBonds=True)
tors_list, tors_list_rings = TorsionFingerprints.CalculateTorsionLists(
mol, ignoreColinearBonds=True)
self.assertEqual(len(tors_list), 0)
weights = TorsionFingerprints.CalculateTorsionWeights(mol, ignoreColinearBonds=True)
self.assertEqual(len(weights), 0)
@@ -142,8 +143,8 @@ class TestCase(unittest.TestCase):
# test that single bonds adjacent to terminal triple bonds are always ignored
mol = Chem.MolFromSmiles('C#CCC')
tors_list, tors_list_rings = TorsionFingerprints.CalculateTorsionLists(mol,
ignoreColinearBonds=True)
tors_list, tors_list_rings = TorsionFingerprints.CalculateTorsionLists(
mol, ignoreColinearBonds=True)
self.assertEqual(len(tors_list), 0)
tors_list, tors_list_rings = TorsionFingerprints.CalculateTorsionLists(
mol, ignoreColinearBonds=False)
@@ -191,30 +192,39 @@ class TestCase(unittest.TestCase):
def testEnumerateStereoisomersBasic(self):
mol = Chem.MolFromSmiles('CC(F)=CC(Cl)C')
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol))
self.assertEqual(len(smiles), 4)
def testEnumerateStereoisomersLargeRandomSample(self):
# near max number of stereo centers allowed
mol = Chem.MolFromSmiles('CC(F)=CC(Cl)C' * 31)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol))
self.assertEqual(len(smiles), 1024)
def testEnumerateStereoisomersWithCrazyNumberOfCenters(self):
# insanely large numbers of isomers aren't a problem
mol = Chem.MolFromSmiles('CC(F)=CC(Cl)C' * 101)
opts = AllChem.StereoEnumerationOptions(rand=None, maxIsomers=13)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(len(smiles), 13)
def testEnumerateStereoisomersRandomSamplingShouldBeDeterministicAndPortable(self):
mol = Chem.MolFromSmiles('CC(F)=CC(Cl)C=C(Br)C(I)N')
opts = AllChem.StereoEnumerationOptions(maxIsomers=2)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
expected = set([
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
# Python 3.8 switch how tuples were hashed, so we get different results there
if sys.version_info < (3, 8):
expected = set([
'C/C(F)=C/[C@@H](Cl)/C=C(/Br)[C@@H](N)I',
'C/C(F)=C/[C@H](Cl)/C=C(\\Br)[C@H](N)I',
])
])
else:
expected = set(
['C/C(F)=C\\[C@H](Cl)/C=C(\\Br)[C@H](N)I', 'C/C(F)=C/[C@@H](Cl)/C=C(\\Br)[C@H](N)I'])
self.assertEqual(smiles, expected)
def testEnumerateStereoisomersMaxIsomersShouldBeReturnedEvenWithTryEmbedding(self):
@@ -225,7 +235,8 @@ class TestCase(unittest.TestCase):
isomers.add(Chem.MolToSmiles(x, isomericSmiles=True))
self.assertEqual(len(isomers), 8)
def testEnumerateStereoisomersTryEmbeddingShouldNotInfiniteLoopWhenMaxIsomersIsLargerThanActual(self):
def testEnumerateStereoisomersTryEmbeddingShouldNotInfiniteLoopWhenMaxIsomersIsLargerThanActual(
self):
m = Chem.MolFromSmiles('BrC=CC1OC(C2)(F)C2(Cl)C1')
opts = AllChem.StereoEnumerationOptions(tryEmbedding=True, maxIsomers=1024)
isomers = set()
@@ -236,18 +247,25 @@ class TestCase(unittest.TestCase):
def testEnumerateStereoisomersRandomSeeding(self):
opts = AllChem.StereoEnumerationOptions(rand=None, maxIsomers=3)
mol = Chem.MolFromSmiles('CC(F)=CC(Cl)C')
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles, set(['C/C(F)=C/[C@@H](C)Cl', 'C/C(F)=C\\[C@H](C)Cl', 'C/C(F)=C\\[C@@H](C)Cl']))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles,
set(['C/C(F)=C/[C@@H](C)Cl', 'C/C(F)=C\\[C@H](C)Cl', 'C/C(F)=C\\[C@@H](C)Cl']))
opts = AllChem.StereoEnumerationOptions(rand=0xDEADBEEF)
mol = Chem.MolFromSmiles('c1ccc2c(c1)C(=O)N(C2=O)C3CCC(=O)NC3=O')
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles, set(['O=C1CC[C@@H](N2C(=O)c3ccccc3C2=O)C(=O)N1', 'O=C1CC[C@H](N2C(=O)c3ccccc3C2=O)C(=O)N1']))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(
smiles,
set(['O=C1CC[C@@H](N2C(=O)c3ccccc3C2=O)C(=O)N1', 'O=C1CC[C@H](N2C(=O)c3ccccc3C2=O)C(=O)N1']))
class DeterministicRandom(random.Random):
def __init__(self):
random.Random.__init__(self)
self.count = 0
def getrandbits(self, n_bits):
c = self.count
self.count += 1
@@ -256,75 +274,94 @@ class TestCase(unittest.TestCase):
rand = DeterministicRandom()
opts = AllChem.StereoEnumerationOptions(rand=rand, maxIsomers=3)
mol = Chem.MolFromSmiles('CCCC(=C(CCl)C(C)CBr)[C@H](F)C(C)C')
smiles = [Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)]
self.assertEqual(smiles, ['CCC/C(=C(\\CCl)[C@H](C)CBr)[C@H](F)C(C)C', 'CCC/C(=C(\\CCl)[C@@H](C)CBr)[C@H](F)C(C)C', 'CCC/C(=C(/CCl)[C@H](C)CBr)[C@H](F)C(C)C'])
smiles = [
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)
]
self.assertEqual(smiles, [
'CCC/C(=C(\\CCl)[C@H](C)CBr)[C@H](F)C(C)C', 'CCC/C(=C(\\CCl)[C@@H](C)CBr)[C@H](F)C(C)C',
'CCC/C(=C(/CCl)[C@H](C)CBr)[C@H](F)C(C)C'
])
def testEnumerateStereoisomersOnlyUnassigned(self):
# shouldn't enumerate anything
fully_assigned = Chem.MolFromSmiles('C/C(F)=C/[C@@H](C)Cl')
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(fully_assigned))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True)
for i in AllChem.EnumerateStereoisomers(fully_assigned))
self.assertEqual(smiles, set(['C/C(F)=C/[C@@H](C)Cl']))
# should only enumerate the bond stereo
partially_assigned = Chem.MolFromSmiles('CC(F)=C[C@@H](C)Cl')
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(partially_assigned))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True)
for i in AllChem.EnumerateStereoisomers(partially_assigned))
self.assertEqual(smiles, set(['C/C(F)=C/[C@@H](C)Cl', 'C/C(F)=C\\[C@@H](C)Cl']))
# should enumerate everything
opts = AllChem.StereoEnumerationOptions(onlyUnassigned=False)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(fully_assigned, opts))
self.assertEqual(smiles, set(['C/C(F)=C\\[C@@H](C)Cl',
'C/C(F)=C\\[C@H](C)Cl',
'C/C(F)=C/[C@H](C)Cl',
'C/C(F)=C/[C@@H](C)Cl',]))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True)
for i in AllChem.EnumerateStereoisomers(fully_assigned, opts))
self.assertEqual(
smiles,
set([
'C/C(F)=C\\[C@@H](C)Cl',
'C/C(F)=C\\[C@H](C)Cl',
'C/C(F)=C/[C@H](C)Cl',
'C/C(F)=C/[C@@H](C)Cl',
]))
def testEnumerateStereoisomersOnlyUnique(self):
mol = Chem.MolFromSmiles('FC(Cl)C(Cl)F')
opts = AllChem.StereoEnumerationOptions(unique=False)
smiles = [Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)]
smiles = [
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)
]
self.assertEqual(len(smiles), 4)
self.assertEqual(len(set(smiles)), 3)
mol = Chem.MolFromSmiles('FC(Cl)C(Cl)F')
opts = AllChem.StereoEnumerationOptions(unique=True)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles, set(['F[C@@H](Cl)[C@@H](F)Cl',
'F[C@@H](Cl)[C@H](F)Cl',
'F[C@H](Cl)[C@H](F)Cl']))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(
smiles, set(['F[C@@H](Cl)[C@@H](F)Cl', 'F[C@@H](Cl)[C@H](F)Cl', 'F[C@H](Cl)[C@H](F)Cl']))
mol = Chem.MolFromSmiles('CC=CC=CC')
opts = AllChem.StereoEnumerationOptions(unique=False)
smiles = [Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)]
smiles = [
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)
]
self.assertEqual(len(smiles), 4)
self.assertEqual(len(set(smiles)), 3)
mol = Chem.MolFromSmiles('CC=CC=CC')
opts = AllChem.StereoEnumerationOptions(unique=True)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles, set(['C/C=C/C=C/C',
'C/C=C\\C=C\\C',
'C/C=C\\C=C/C']))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles, set(['C/C=C/C=C/C', 'C/C=C\\C=C\\C', 'C/C=C\\C=C/C']))
mol = Chem.MolFromSmiles('FC(Cl)C=CC=CC(F)Cl')
opts = AllChem.StereoEnumerationOptions(unique=False)
smiles = [Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)]
smiles = [
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts)
]
self.assertEqual(len(smiles), 16)
self.assertEqual(len(set(smiles)), 10)
mol = Chem.MolFromSmiles('FC(Cl)C=CC=CC(F)Cl')
opts = AllChem.StereoEnumerationOptions(unique=True)
smiles = set(Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(smiles, set(['F[C@H](Cl)/C=C\\C=C\\[C@@H](F)Cl',
'F[C@H](Cl)/C=C/C=C/[C@H](F)Cl',
'F[C@H](Cl)/C=C/C=C/[C@@H](F)Cl',
'F[C@H](Cl)/C=C\\C=C/[C@@H](F)Cl',
'F[C@H](Cl)/C=C\\C=C\\[C@H](F)Cl',
'F[C@H](Cl)/C=C\\C=C/[C@H](F)Cl',
'F[C@@H](Cl)/C=C/C=C/[C@@H](F)Cl',
'F[C@@H](Cl)/C=C\\C=C\\[C@H](F)Cl',
'F[C@@H](Cl)/C=C\\C=C\\[C@@H](F)Cl',
'F[C@@H](Cl)/C=C\\C=C/[C@@H](F)Cl']))
smiles = set(
Chem.MolToSmiles(i, isomericSmiles=True) for i in AllChem.EnumerateStereoisomers(mol, opts))
self.assertEqual(
smiles,
set([
'F[C@H](Cl)/C=C\\C=C\\[C@@H](F)Cl', 'F[C@H](Cl)/C=C/C=C/[C@H](F)Cl',
'F[C@H](Cl)/C=C/C=C/[C@@H](F)Cl', 'F[C@H](Cl)/C=C\\C=C/[C@@H](F)Cl',
'F[C@H](Cl)/C=C\\C=C\\[C@H](F)Cl', 'F[C@H](Cl)/C=C\\C=C/[C@H](F)Cl',
'F[C@@H](Cl)/C=C/C=C/[C@@H](F)Cl', 'F[C@@H](Cl)/C=C\\C=C\\[C@H](F)Cl',
'F[C@@H](Cl)/C=C\\C=C\\[C@@H](F)Cl', 'F[C@@H](Cl)/C=C\\C=C/[C@@H](F)Cl'
]))
def testEnumerateStereoisomersOnlyEnhancedStereo(self):
rdbase = os.environ["RDBASE"]
@@ -332,8 +369,7 @@ class TestCase(unittest.TestCase):
mol = Chem.MolFromMolFile(filename)
smiles = set(Chem.MolToSmiles(m) for m in AllChem.EnumerateStereoisomers(mol))
# switches the centers linked by an "OR", but not the absolute group
self.assertEqual(smiles, {r'C[C@@H](F)[C@@H](C)[C@@H](C)Br',
r'C[C@H](F)[C@H](C)[C@@H](C)Br'})
self.assertEqual(smiles, {r'C[C@@H](F)[C@@H](C)[C@@H](C)Br', r'C[C@H](F)[C@H](C)[C@@H](C)Br'})
original_smiles = Chem.MolToSmiles(mol)
self.assertIn(original_smiles, smiles)
@@ -361,24 +397,24 @@ class TestCase(unittest.TestCase):
def testIssue3231(self):
mol = Chem.MolFromSmiles(
'C[C@H](OC1=C(N)N=CC(C2=CN(C3C[C@H](C)NCC3)N=C2)=C1)C4=C(Cl)C=CC(F)=C4Cl')
Chem.AssignStereochemistry(mol,force=True,flagPossibleStereoCenters=True)
l = Chem.FindMolChiralCenters(mol,includeUnassigned=True)
Chem.AssignStereochemistry(mol, force=True, flagPossibleStereoCenters=True)
l = Chem.FindMolChiralCenters(mol, includeUnassigned=True)
self.assertEqual(l, [(1, 'S'), (12, '?'), (14, 'S')])
enumsi_opt = AllChem.StereoEnumerationOptions(maxIsomers=20, onlyUnassigned=False)
isomers = list(AllChem.EnumerateStereoisomers(mol, enumsi_opt))
chi_cents = []
for iso in isomers:
Chem.AssignStereochemistry(iso)
chi_cents.append(Chem.FindMolChiralCenters(iso,includeUnassigned=True))
chi_cents.append(Chem.FindMolChiralCenters(iso, includeUnassigned=True))
self.assertEqual(sorted(chi_cents),
[[(1, 'R'), (12, 'R'), (14, 'R')],
[(1, 'R'), (12, 'R'), (14, 'S')],
[(1, 'R'), (12, 'S'), (14, 'R')],
[(1, 'R'), (12, 'S'), (14, 'S')],
[(1, 'S'), (12, 'R'), (14, 'R')],
[(1, 'S'), (12, 'R'), (14, 'S')],
[(1, 'S'), (12, 'S'), (14, 'R')],
[(1, 'S'), (12, 'S'), (14, 'S')]])
[[(1, 'R'), (12, 'R'),
(14, 'R')], [(1, 'R'), (12, 'R'),
(14, 'S')], [(1, 'R'), (12, 'S'),
(14, 'R')], [(1, 'R'), (12, 'S'), (14, 'S')],
[(1, 'S'), (12, 'R'),
(14, 'R')], [(1, 'S'), (12, 'R'),
(14, 'S')], [(1, 'S'), (12, 'S'),
(14, 'R')], [(1, 'S'), (12, 'S'), (14, 'S')]])
if __name__ == '__main__':