mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-03 21:44:30 +08:00
* 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:
@@ -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" \
|
||||
|
||||
74
.azure-pipelines/linux_build_py37.yml
Normal file
74
.azure-pipelines/linux_build_py37.yml
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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__':
|
||||
|
||||
Reference in New Issue
Block a user