mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-04 21:54:27 +08:00
556 lines
19 KiB
C++
556 lines
19 KiB
C++
//
|
|
// Copyright (C) 2025 David Cosgrove and other RDKit contributors.
|
|
//
|
|
// @@ All Rights Reserved @@
|
|
// This file is part of the RDKit.
|
|
// The contents are covered by the terms of the BSD license
|
|
// which is included in the file license.txt, found at the root
|
|
// of the RDKit source tree.
|
|
//
|
|
|
|
#include <ranges>
|
|
#include <unordered_set>
|
|
|
|
#include <GraphMol/CIPLabeler/CIPLabeler.h>
|
|
#include <GraphMol/Chirality.h>
|
|
#include <GraphMol/MolOps.h>
|
|
#include <GraphMol/EnumerateStereoisomers/EnumerateStereoisomers.h>
|
|
#include <GraphMol/FileParsers/FileParsers.h>
|
|
#include <GraphMol/SmilesParse/SmilesParse.h>
|
|
#include <GraphMol/SmilesParse/SmilesWrite.h>
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
|
|
using namespace RDKit;
|
|
using namespace RDKit::EnumerateStereoisomers;
|
|
|
|
TEST_CASE("Simple test") {
|
|
auto m1 = "BrC=CC1OC(C2)(F)C2(Cl)C1"_smiles;
|
|
REQUIRE(m1);
|
|
{
|
|
StereoisomerEnumerator enu(*m1);
|
|
CHECK(enu.getStereoisomerCount() == 16);
|
|
const static std::unordered_set<std::string> expected{
|
|
R"(F[C@@]12C[C@@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@@]12C[C@@]1(Cl)C[C@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@@]1(Cl)C[C@H](/C=C\Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@]1(Cl)C[C@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@]1(Cl)C[C@H](/C=C\Br)O2)",
|
|
};
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Embedding") {
|
|
auto m1 = "BrC=CC1OC(C2)(F)C2(Cl)C1"_smiles;
|
|
REQUIRE(m1);
|
|
|
|
StereoEnumerationOptions opts;
|
|
opts.tryEmbedding = true;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
const static std::unordered_set<std::string> expected{
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@H](/C=C\Br)O2)",
|
|
};
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
|
|
// Check we get the right number even with maxIsomers.
|
|
opts.maxIsomers = 8;
|
|
StereoisomerEnumerator enu2(*m1, opts);
|
|
got.clear();
|
|
while (auto isomer = enu2.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
|
|
// Check no infinite loop when maxIsomers greater than
|
|
// possible.
|
|
opts.maxIsomers = 1024;
|
|
StereoisomerEnumerator enu3(*m1, opts);
|
|
got.clear();
|
|
while (auto isomer = enu3.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
}
|
|
|
|
TEST_CASE("Unique") {
|
|
auto m1 = "FC(Cl)C=CC=CC(F)Cl"_smiles;
|
|
REQUIRE(m1);
|
|
StereoEnumerationOptions opts;
|
|
opts.unique = true;
|
|
StereoisomerEnumerator enu(*m1, opts);
|
|
const static std::unordered_set<std::string> expected{
|
|
R"(F[C@@H](Cl)/C=C/C=C/[C@@H](F)Cl)",
|
|
R"(F[C@@H](Cl)/C=C\C=C/[C@@H](F)Cl)",
|
|
R"(F[C@@H](Cl)/C=C\C=C\[C@@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C/C=C/[C@@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C/C=C/[C@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C/C=C\[C@@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C\C=C/[C@@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C\C=C/[C@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C\C=C\[C@@H](F)Cl)",
|
|
R"(F[C@H](Cl)/C=C\C=C\[C@H](F)Cl)",
|
|
};
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
}
|
|
|
|
TEST_CASE("Unassigned") {
|
|
auto m1 = "C/C(F)=C/[C@@H](C)Cl"_smiles;
|
|
REQUIRE(m1);
|
|
StereoEnumerationOptions opts;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
CHECK(enu1.getStereoisomerCount() == 1);
|
|
std::unordered_set<std::string> expected{"C/C(F)=C/[C@@H](C)Cl"};
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
CHECK(!isomer->hasProp("_MolFileChiralFlag"));
|
|
}
|
|
CHECK(got == expected);
|
|
|
|
// Enumerate bond stereo only
|
|
auto m4 = "CC(F)=C[C@@H](C)Cl"_smiles;
|
|
REQUIRE(m4);
|
|
StereoisomerEnumerator enu4(*m4, opts);
|
|
CHECK(enu4.getStereoisomerCount() == 2);
|
|
got.clear();
|
|
while (auto isomer = enu4.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
expected = std::unordered_set<std::string>{
|
|
R"(C/C(F)=C/[C@@H](C)Cl)",
|
|
R"(C/C(F)=C\[C@@H](C)Cl)",
|
|
};
|
|
CHECK(got == expected);
|
|
|
|
auto m2 = "BrC=C[C@H]1OC(C2)(F)C2(Cl)C1"_smiles;
|
|
REQUIRE(m2);
|
|
StereoisomerEnumerator enu2(*m2, opts);
|
|
CHECK(enu2.getStereoisomerCount() == 8);
|
|
expected = std::unordered_set<std::string>{
|
|
R"(F[C@@]12C[C@@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@@]12C[C@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
R"(F[C@]12C[C@]1(Cl)C[C@@H](/C=C/Br)O2)",
|
|
R"(F[C@]12C[C@]1(Cl)C[C@@H](/C=C\Br)O2)",
|
|
};
|
|
got.clear();
|
|
while (auto isomer = enu2.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
|
|
opts.onlyUnassigned = false;
|
|
StereoisomerEnumerator enu3(*m2, opts);
|
|
CHECK(enu3.getStereoisomerCount() == 16);
|
|
}
|
|
|
|
TEST_CASE("Subset") {
|
|
std::string smi("Br");
|
|
for (int i = 0; i < 20; ++i) {
|
|
smi += "[CH](Cl)";
|
|
}
|
|
smi += "F";
|
|
auto m1 = v2::SmilesParse::MolFromSmiles(smi);
|
|
StereoEnumerationOptions opts;
|
|
opts.randomSeed = 1066;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
CHECK(enu1.getStereoisomerCount() == 1048576);
|
|
const static std::unordered_set<std::string> expected1{
|
|
R"(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)",
|
|
R"(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)",
|
|
R"(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)",
|
|
R"(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)",
|
|
R"(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)",
|
|
};
|
|
std::unordered_set<std::string> got;
|
|
for (int i = 0; i < 5; ++i) {
|
|
auto isomer = enu1.next();
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected1);
|
|
|
|
opts.maxIsomers = 3;
|
|
StereoisomerEnumerator enu2(*m1, opts);
|
|
const static std::unordered_set<std::string> expected2{
|
|
R"(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)",
|
|
R"(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)",
|
|
R"(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)"};
|
|
got.clear();
|
|
while (auto isomer = enu2.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected2);
|
|
}
|
|
|
|
TEST_CASE("EnhancedStereo") {
|
|
const char *rdbase = getenv("RDBASE");
|
|
REQUIRE(rdbase);
|
|
std::string fname = std::string(rdbase) +
|
|
"/Code/GraphMol/FileParsers/test_data/two_centers_or.mol";
|
|
auto m1 = v2::FileParsers::MolFromMolFile(fname);
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
const static std::unordered_set<std::string> expected{
|
|
R"(C[C@H]([C@@H](C)F)[C@@H](C)Br)", R"(C[C@@H]([C@H](C)F)[C@@H](C)Br)"};
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == expected);
|
|
#if 0
|
|
// Check the original was in there
|
|
auto m2 = RWMol(*m1);
|
|
m2.setStereoGroups(std::vector<StereoGroup>());
|
|
auto origSmi = MolToSmiles(m2);
|
|
auto it = got.find(origSmi);
|
|
CHECK(it != got.end());
|
|
|
|
// Make sure there are no extras if enumerating all
|
|
StereoEnumerationOptions opts;
|
|
opts.unique = false;
|
|
opts.onlyUnassigned = false;
|
|
got.clear();
|
|
StereoisomerEnumerator enu2(*m1, opts);
|
|
while (auto isomer = enu2.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
CHECK(got.insert(MolToSmiles(*isomer)).second);
|
|
}
|
|
CHECK(got.size() == 8);
|
|
#endif
|
|
}
|
|
|
|
TEST_CASE("Issue 2890") {
|
|
auto m1 = "CC=CC"_smiles;
|
|
m1->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
}
|
|
|
|
TEST_CASE("Issue 3231") {
|
|
// EnumerateStereochemistry should clear CIP labels. The original test
|
|
// uses FindMolChiralCenters but that's Python only, too. There are only
|
|
// so may yaks that can be shaved in one go!
|
|
auto getCIPLabels =
|
|
[](ROMol &mol) -> std::vector<std::pair<unsigned int, std::string>> {
|
|
MolOps::assignStereochemistry(mol);
|
|
CIPLabeler::assignCIPLabels(mol);
|
|
std::vector<std::pair<unsigned int, std::string>> result;
|
|
for (auto &si : Chirality::findPotentialStereo(mol, true, true)) {
|
|
if (si.type == Chirality::StereoType::Atom_Tetrahedral) {
|
|
if (si.specified == Chirality::StereoSpecified::Unspecified) {
|
|
result.push_back(std::make_pair(si.centeredOn, "?"));
|
|
} else {
|
|
auto atom = mol.getAtomWithIdx(si.centeredOn);
|
|
auto cipLabel =
|
|
atom->getProp<std::string>(common_properties::_CIPCode);
|
|
result.push_back(std::make_pair(si.centeredOn, cipLabel));
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
auto m1 =
|
|
"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"_smiles;
|
|
REQUIRE(m1);
|
|
auto origLabels = getCIPLabels(*m1);
|
|
CHECK(origLabels == std::vector<std::pair<unsigned int, std::string>>{
|
|
{1, "S"}, {12, "?"}, {14, "S"}});
|
|
StereoEnumerationOptions opts;
|
|
opts.onlyUnassigned = false;
|
|
opts.maxIsomers = 20;
|
|
StereoisomerEnumerator enu(*m1, opts);
|
|
std::vector<std::vector<std::pair<unsigned int, std::string>>> chiCents;
|
|
while (auto isomer = enu.next()) {
|
|
chiCents.push_back(getCIPLabels(*isomer));
|
|
}
|
|
std::ranges::sort(chiCents);
|
|
chiCents.erase(std::unique(chiCents.begin(), chiCents.end()), chiCents.end());
|
|
|
|
std::vector<std::vector<std::pair<unsigned int, std::string>>> expCents{
|
|
{{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"}}};
|
|
CHECK(chiCents.size() == expCents.size());
|
|
}
|
|
|
|
TEST_CASE("Issue 3505") {
|
|
auto m1 = "CCC(C)Br"_smiles;
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
int count = 0;
|
|
while (auto isomer = enu1.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
auto at = isomer->getAtomWithIdx(2);
|
|
CHECK((at->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW ||
|
|
at->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CCW));
|
|
CHECK(at->hasProp("_ChiralityPossible"));
|
|
++count;
|
|
}
|
|
CHECK(count == 2);
|
|
}
|
|
|
|
TEST_CASE("Either or Double Stereo") {
|
|
const char *rdbase = getenv("RDBASE");
|
|
REQUIRE(rdbase);
|
|
std::string fname = std::string(rdbase) +
|
|
"/Code/GraphMol/FileParsers/test_data/simple_either.mol";
|
|
auto m1 = v2::FileParsers::MolFromMolFile(fname);
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == std::unordered_set<std::string>{R"(C/C=C/C)", R"(C/C=C\C)"});
|
|
}
|
|
|
|
TEST_CASE("Embed many chirals") {
|
|
std::string smi("C1");
|
|
for (int i = 0; i < 40; ++i) {
|
|
smi += "C(Cl)(Br)";
|
|
}
|
|
smi += "C1";
|
|
auto m1 = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m1);
|
|
StereoEnumerationOptions opts;
|
|
opts.maxIsomers = 2;
|
|
opts.tryEmbedding = true;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
}
|
|
|
|
TEST_CASE("Issue 6405") {
|
|
auto m1 = "O[C@H](Br)[C@H](F)C |&1:1,3|"_smiles;
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
while (auto isomer = enu1.next()) {
|
|
std::string prop;
|
|
CHECK(isomer->getPropIfPresent<std::string>("_MolFileChiralFlag", prop));
|
|
CHECK(prop == "1");
|
|
CHECK(isomer->getStereoGroups().empty());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Issue 7516") {
|
|
auto m1 = "CC1CC(C)C1"_smiles;
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got == std::unordered_set<std::string>{R"(C[C@H]1C[C@@H](C)C1)",
|
|
R"(C[C@H]1C[C@H](C)C1)"});
|
|
|
|
auto m2 = "COC(=O)C1CC(NC(N)=O)C1"_smiles;
|
|
REQUIRE(m2);
|
|
StereoisomerEnumerator enu2(*m2);
|
|
got.clear();
|
|
while (auto isomer = enu2.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
|
|
auto m3 = "O=C(NC1CC2[NH+](C(C1)CC2)Cc3ccccc3)N"_smiles;
|
|
REQUIRE(m2);
|
|
StereoisomerEnumerator enu3(*m3);
|
|
got.clear();
|
|
while (auto isomer = enu3.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 8);
|
|
}
|
|
|
|
TEST_CASE("Issue 7608") {
|
|
auto m1 = "N=C(N1CCC1)NCCOc2ccc(C(F)(F)F)cc2"_smiles;
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu1(*m1);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 1);
|
|
}
|
|
|
|
TEST_CASE("Atropisomers") {
|
|
auto m1 = "CC1=CC=CC(I)=C1N1C(C)=CC=C1Br |wD:7.7,wU:8.9|"_smiles;
|
|
REQUIRE(m1);
|
|
StereoEnumerationOptions opts;
|
|
opts.onlyUnassigned = false;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToCXSmiles(*isomer, SmilesWriteParams(),
|
|
SmilesWrite::CXSmilesFields::CX_ALL_BUT_COORDS));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
|
|
auto m2 =
|
|
"C1=C(C2=CCCCCCCCCCC2)CCC1 |(-1.71835,-1.22732,;-1.61655,-0.232318,;-0.751953,0.270082,;-0.754753,1.27008,;0.109847,1.77248,;0.977247,1.27488,;1.84185,1.77728,;2.70925,1.27968,;2.71205,0.279682,;1.84745,-0.222718,;1.85025,-1.22272,;0.985647,-1.72512,;0.118247,-1.22752,;0.115447,-0.227518,;-2.53135,0.171882,;-3.19835,-0.573118,;-2.69595,-1.43772,),wU:1.14|"_smiles;
|
|
REQUIRE(m1);
|
|
StereoisomerEnumerator enu2(*m2, opts);
|
|
got.clear();
|
|
while (auto isomer = enu2.next()) {
|
|
got.insert(MolToCXSmiles(*isomer, SmilesWriteParams(),
|
|
SmilesWrite::CXSmilesFields::CX_ALL_BUT_COORDS));
|
|
}
|
|
CHECK(got.size() == 4);
|
|
|
|
{
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 20 21 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 N -0.024300 -1.051200 0.000000 0
|
|
M V30 2 C 0.690200 -1.463700 0.000000 0
|
|
M V30 3 O 0.690200 -2.288700 0.000000 0
|
|
M V30 4 N 1.404500 -1.051200 0.000000 0
|
|
M V30 5 C 2.119100 -1.463700 0.000000 0
|
|
M V30 6 C 2.119100 -2.288700 0.000000 0
|
|
M V30 7 C 1.404500 -2.701200 0.000000 0
|
|
M V30 8 C 2.833500 -2.701200 0.000000 0
|
|
M V30 9 C 3.547900 -2.288700 0.000000 0
|
|
M V30 10 N 3.547900 -1.463700 0.000000 0
|
|
M V30 11 C 2.833600 -1.051200 0.000000 0
|
|
M V30 12 C 3.363800 -0.419100 0.000000 0
|
|
M V30 13 C 4.176300 -0.562300 0.000000 0
|
|
M V30 14 C 3.081600 0.356100 0.000000 0
|
|
M V30 15 C 1.404500 -0.226000 0.000000 0
|
|
M V30 16 N 2.155300 0.191900 0.000000 0
|
|
M V30 17 C 2.161900 1.051300 0.000000 0
|
|
M V30 18 C 1.417800 1.480900 0.000000 0
|
|
M V30 19 C 0.676800 1.045500 0.000000 0
|
|
M V30 20 C 0.690200 0.186400 0.000000 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 2
|
|
M V30 2 2 2 3
|
|
M V30 3 1 4 2
|
|
M V30 4 1 4 5
|
|
M V30 5 2 5 6
|
|
M V30 6 1 6 7
|
|
M V30 7 1 6 8
|
|
M V30 8 2 8 9
|
|
M V30 9 1 9 10
|
|
M V30 10 2 10 11
|
|
M V30 11 1 11 12
|
|
M V30 12 1 12 13
|
|
M V30 13 1 12 14
|
|
M V30 14 1 4 15
|
|
M V30 15 2 15 16
|
|
M V30 16 1 16 17
|
|
M V30 17 2 17 18
|
|
M V30 18 1 18 19
|
|
M V30 19 2 19 20
|
|
M V30 20 1 5 11 CFG=1
|
|
M V30 21 1 20 15
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
StereoEnumerationOptions opts;
|
|
opts.onlyUnassigned = false;
|
|
StereoisomerEnumerator enu1(*m, opts);
|
|
std::unordered_set<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.insert(MolToCXSmiles(*isomer, SmilesWriteParams(),
|
|
SmilesWrite::CXSmilesFields::CX_ALL_BUT_COORDS));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("wiggly bonds and EnumerateStereoisomers") {
|
|
SECTION("as reported (bonds)") {
|
|
auto m1 = "CC=CC |w:1.0|"_smiles;
|
|
REQUIRE(m1);
|
|
StereoEnumerationOptions opts;
|
|
opts.onlyUnassigned = false;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
std::vector<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.push_back(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
CHECK(std::find(got.begin(), got.end(), "C/C=C/C") != got.end());
|
|
CHECK(std::find(got.begin(), got.end(), "C/C=C\\C") != got.end());
|
|
}
|
|
SECTION("chiral centers") {
|
|
auto m1 = "CC(F)(Cl)(Br) |w:1.0|"_smiles;
|
|
REQUIRE(m1);
|
|
StereoEnumerationOptions opts;
|
|
opts.onlyUnassigned = false;
|
|
StereoisomerEnumerator enu1(*m1, opts);
|
|
std::vector<std::string> got;
|
|
while (auto isomer = enu1.next()) {
|
|
got.push_back(MolToSmiles(*isomer));
|
|
}
|
|
CHECK(got.size() == 2);
|
|
CHECK(std::find(got.begin(), got.end(), "C[C@](F)(Cl)Br") != got.end());
|
|
CHECK(std::find(got.begin(), got.end(), "C[C@@](F)(Cl)Br") != got.end());
|
|
}
|
|
}
|