Files
rdkit/Code/GraphMol/FileParsers/molfile_stereo_catch.cpp
Ricardo Rodriguez 434714e7d4 Fixes #9270 (#9272)
* fix handling double bond stereo extraction

* add tests

* Update Code/GraphMol/Subset.cpp

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

---------

Co-authored-by: Greg Landrum <greg.landrum@gmail.com>
2026-05-16 07:01:25 +02:00

820 lines
25 KiB
C++

//
// Copyright (C) 2022 Greg Landrum 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 "RDGeneral/test.h"
#include <catch2/catch_all.hpp>
#include <RDGeneral/Invariant.h>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/Chirality.h>
#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/FileParsers/MolFileStereochem.h>
#include <GraphMol/Subgraphs/Subgraphs.h>
#include <GraphMol/Subgraphs/SubgraphUtils.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/FileParsers/MolWriters.h>
#include <GraphMol/CIPLabeler/CIPLabeler.h>
#include <GraphMol/test_fixtures.h>
using namespace RDKit;
TEST_CASE("Github #5863: failure in WedgeMolBonds") {
SECTION("as reported") {
auto mol =
"C[C@]12C=CC(=O)C=C1CC[C@@H]1[C@@H]2[C@@H](O)C[C@@]2(C)[C@H]1CC[C@]2(O)C(=O)CO |(1.94354,1.43772,;2.70098,0.14301,;3.44351,1.44633,;4.94349,1.45494,;5.70093,0.160228,;7.20091,0.168838,;4.9584,-1.14309,;3.45842,-1.1517,;2.71589,-2.45502,;1.21592,-2.46363,;0.458474,-1.16892,;1.20101,0.1344,;0.443562,1.42911,;1.18609,2.73243,;-1.05641,1.4205,;-1.79895,0.117181,;-2.53364,1.42493,;-1.0415,-1.17753,;-2.03878,-2.29799,;-3.41258,-1.69576,;-3.26435,-0.203103,;-3.6102,1.25648,;-4.76433,-0.211712,;-5.50686,-1.51503,;-5.52177,1.083,;-7.02175,1.07439,)|"_smiles;
REQUIRE(mol);
unsigned int radius = 2;
unsigned int atomId = 2;
auto env = findAtomEnvironmentOfRadiusN(*mol, radius + 1, atomId);
std::unique_ptr<ROMol> frag(Subgraphs::pathToSubmol(*mol, env));
REQUIRE(frag);
Chirality::wedgeMolBonds(*frag, &frag->getConformer());
INFO(MolToV3KMolBlock(*frag));
CHECK(frag->getBondBetweenAtoms(9, 10)->getBondDir() !=
Bond::BondDir::NONE);
}
}
TEST_CASE("translating the chiral flag to stereo groups") {
SECTION("basics") {
auto withFlag = R"CTAB(
Mrv2211 03302308372D
5 4 0 0 1 0 999 V2000
-6.5625 3.9286 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-5.8480 4.3411 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0
-5.1336 3.9286 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
-6.1775 5.0826 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
-5.4384 5.0893 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
2 5 1 1 0 0 0
2 4 1 6 0 0 0
M END
)CTAB"_ctab;
REQUIRE(withFlag);
int flag = 0;
CHECK(withFlag->getPropIfPresent(common_properties::_MolFileChiralFlag,
flag));
CHECK(flag == 1);
RWMol zeroFlag(*withFlag);
zeroFlag.setProp(common_properties::_MolFileChiralFlag, 0);
RWMol noFlag(*withFlag);
noFlag.clearProp(common_properties::_MolFileChiralFlag);
CHECK(withFlag->getStereoGroups().empty());
translateChiralFlagToStereoGroups(*withFlag);
CHECK(!withFlag->hasProp(common_properties::_MolFileChiralFlag));
auto sgs = withFlag->getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
CHECK(sgs[0].getAtoms().size() == 1);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
{
RWMol cp(zeroFlag);
translateChiralFlagToStereoGroups(cp);
CHECK(!cp.hasProp(common_properties::_MolFileChiralFlag));
auto sgs = cp.getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_AND);
CHECK(sgs[0].getAtoms().size() == 1);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
}
{
RWMol cp(zeroFlag);
translateChiralFlagToStereoGroups(cp, StereoGroupType::STEREO_OR);
CHECK(!cp.hasProp(common_properties::_MolFileChiralFlag));
auto sgs = cp.getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_OR);
CHECK(sgs[0].getAtoms().size() == 1);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
}
translateChiralFlagToStereoGroups(noFlag);
CHECK(!noFlag.hasProp(common_properties::_MolFileChiralFlag));
CHECK(noFlag.getStereoGroups().empty());
}
SECTION("explicit zero chiral flag") {
auto zeroFlag = R"CTAB(
Mrv2211 03302308372D
5 4 0 0 o 0 999 V2000
-6.5625 3.9286 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-5.8480 4.3411 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0
-5.1336 3.9286 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
-6.1775 5.0826 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
-5.4384 5.0893 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
2 5 1 1 0 0 0
2 4 1 6 0 0 0
M END
)CTAB"_ctab;
REQUIRE(zeroFlag);
int flag = 0;
CHECK(zeroFlag->getPropIfPresent(common_properties::_MolFileChiralFlag,
flag));
CHECK(flag == 0);
{
RWMol cp(*zeroFlag);
translateChiralFlagToStereoGroups(cp);
CHECK(!cp.hasProp(common_properties::_MolFileChiralFlag));
auto sgs = cp.getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_AND);
CHECK(sgs[0].getAtoms().size() == 1);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
}
}
SECTION("multiple stereocenters") {
auto noFlag = "C[C@@](N)(F)C[C@](C)(O)F"_smiles;
REQUIRE(noFlag);
RWMol withFlag(*noFlag);
withFlag.setProp(common_properties::_MolFileChiralFlag, 1);
CHECK(withFlag.getStereoGroups().empty());
translateChiralFlagToStereoGroups(withFlag);
CHECK(!withFlag.hasProp(common_properties::_MolFileChiralFlag));
auto sgs = withFlag.getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
CHECK(sgs[0].getAtoms().size() == 2);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
CHECK(sgs[0].getAtoms()[1]->getIdx() == 5);
{
RWMol zeroFlag(*noFlag);
zeroFlag.setProp(common_properties::_MolFileChiralFlag, 0);
translateChiralFlagToStereoGroups(zeroFlag);
CHECK(!zeroFlag.hasProp(common_properties::_MolFileChiralFlag));
auto sgs = zeroFlag.getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_AND);
CHECK(sgs[0].getAtoms().size() == 2);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
CHECK(sgs[0].getAtoms()[1]->getIdx() == 5);
}
}
SECTION("pre-existing ABS stereogroup") {
auto withFlag = "C[C@@](N)(F)C[C@](C)(O)F |a:1|"_smiles;
REQUIRE(withFlag);
withFlag->setProp(common_properties::_MolFileChiralFlag, 1);
CHECK(withFlag->getStereoGroups().size() == 1);
translateChiralFlagToStereoGroups(*withFlag);
CHECK(!withFlag->hasProp(common_properties::_MolFileChiralFlag));
auto sgs = withFlag->getStereoGroups();
REQUIRE(sgs.size() == 1);
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
CHECK(sgs[0].getAtoms().size() == 2);
CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
CHECK(sgs[0].getAtoms()[1]->getIdx() == 5);
}
}
TEST_CASE("github #6310: crossed ring bonds not written to mol blocks") {
SECTION("as reported") {
std::vector<std::string> smileses = {"C1C=CCCCCCCCCC1", "C1C=CCCCCC1"};
for (const auto &smiles : smileses) {
auto m = std::unique_ptr<RWMol>(SmilesToMol(smiles));
REQUIRE(m);
CHECK(m->getBondWithIdx(1)->getBondType() == Bond::BondType::DOUBLE);
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREONONE);
auto mb = MolToV3KMolBlock(*m);
CHECK(mb.find("CFG=2") != std::string::npos);
mb = MolToMolBlock(*m);
CHECK(mb.find("2 3 2 3") != std::string::npos);
// make sure we also write the crossed bond when it's explicitly crossed
m->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
mb = MolToV3KMolBlock(*m);
CHECK(mb.find("CFG=2") != std::string::npos);
mb = MolToMolBlock(*m);
CHECK(mb.find("2 3 2 3") != std::string::npos);
}
}
SECTION("don't cross bonds in small rings") {
auto m = "C1C=CCCCC1"_smiles;
REQUIRE(m);
CHECK(m->getBondWithIdx(1)->getBondType() == Bond::BondType::DOUBLE);
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREONONE);
auto mb = MolToV3KMolBlock(*m);
CHECK(mb.find("CFG=2") == std::string::npos);
mb = MolToMolBlock(*m);
CHECK(mb.find("2 3 2 3") == std::string::npos);
m->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
mb = MolToV3KMolBlock(*m);
CHECK(mb.find("CFG=2") != std::string::npos);
mb = MolToMolBlock(*m);
CHECK(mb.find("2 3 2 3") != std::string::npos);
}
}
void testStereoExample(const std::string &mb, unsigned int aidx,
Atom::ChiralType expected,
const std::string &expectedCIP) {
INFO(mb);
std::unique_ptr<RWMol> m(MolBlockToMol(mb));
REQUIRE(m);
CHECK(m->getAtomWithIdx(aidx)->getChiralTag() == expected);
CIPLabeler::assignCIPLabels(*m);
std::string CIP = "";
CHECK(m->getAtomWithIdx(aidx)->getPropIfPresent(common_properties::_CIPCode,
CIP));
CHECK(CIP == expectedCIP);
}
TEST_CASE("IUPAC recommendations") {
SECTION("simple examples") {
std::vector<std::string> mbs = {
R"CTAB(
Mrv2211 06082308462D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 0
M V30 BEGIN ATOM
M V30 1 C -7.875 5.3333 0 0
M V30 2 C -6.5413 6.1033 0 0
M V30 3 O -5.2076 5.3333 0 0
M V30 4 F -7.3292 7.3957 0 0
M V30 5 Cl -5.4654 7.2052 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 2 3
M V30 3 1 2 4 CFG=1
M V30 4 1 2 5 CFG=3
M V30 END BOND
M V30 END CTAB
M END
)CTAB",
R"CTAB(
Mrv2211 06082309052D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 0
M V30 BEGIN ATOM
M V30 1 C -7.875 5.3333 0 0
M V30 2 C -6.5413 6.1033 0 0 CFG=2
M V30 3 O -5.2076 5.3333 0 0
M V30 4 F -6.5413 8 0 0
M V30 5 Cl -5.4654 7.2052 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 2 3
M V30 3 1 2 4 CFG=1
M V30 4 1 2 5 CFG=3
M V30 END BOND
M V30 END CTAB
M END
)CTAB",
R"CTAB(
Mrv2211 06082309052D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 0
M V30 BEGIN ATOM
M V30 1 C -7.875 5.3333 0 0
M V30 2 C -6.5413 6.1033 0 0 CFG=2
M V30 3 O -5.2076 5.3333 0 0
M V30 4 F -4.9369 6.9639 0 0
M V30 5 Cl -6.5413 7.9602 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 2 3
M V30 3 1 2 4 CFG=1
M V30 4 1 2 5 CFG=3
M V30 END BOND
M V30 END CTAB
M END
)CTAB",
R"CTAB(IUPAC does not like this one
Mrv2211 06082309142D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 0
M V30 BEGIN ATOM
M V30 1 C -7.875 5.3333 0 0
M V30 2 C -6.5413 6.1033 0 0
M V30 3 O -5.2076 5.3333 0 0
M V30 4 F -6.5413 4.2897 0 0
M V30 5 Cl -6.5413 7.9602 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 2 3
M V30 3 1 2 4 CFG=1
M V30 4 1 2 5
M V30 END BOND
M V30 END CTAB
M END
)CTAB",
R"CTAB(IUPAC does not like this one2
Mrv2211 06082309142D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 0
M V30 BEGIN ATOM
M V30 1 C -7.875 5.3333 0 0
M V30 2 C -6.5413 6.1033 0 0
M V30 3 O -5.2076 5.3333 0 0
M V30 4 F -6.5413 4.2897 0 0
M V30 5 Cl -6.5413 7.9602 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2
M V30 2 1 2 3
M V30 3 1 2 4
M V30 4 1 2 5 CFG=1
M V30 END BOND
M V30 END CTAB
M END
)CTAB",
};
for (const auto &mb : mbs) {
testStereoExample(mb, 1, Atom::ChiralType::CHI_TETRAHEDRAL_CCW, "S");
if (mb.find("CFG=3") != std::string::npos &&
mb.find("CFG=1") != std::string::npos) {
std::string cp = mb;
testStereoExample(cp.replace(mb.find("CFG=1"), 5, " "), 1,
Atom::ChiralType::CHI_TETRAHEDRAL_CCW, "S");
cp = mb;
testStereoExample(cp.replace(mb.find("CFG=3"), 5, " "), 1,
Atom::ChiralType::CHI_TETRAHEDRAL_CCW, "S");
}
}
}
SECTION("three coordinate") {
auto m = R"CTAB(
Mrv2108 01192209042D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 8 7 0 0 0
M V30 BEGIN ATOM
M V30 1 C 2.31 4.001 0 0
M V30 2 C 1.54 2.6674 0 0 CFG=2
M V30 3 O -0 2.6674 0 0
M V30 4 C 2.31 1.3337 0 0 CFG=1
M V30 5 F 3.85 1.3337 0 0
M V30 6 C 1.54 0 0 0 CFG=2
M V30 7 C 2.31 -1.3337 0 0
M V30 8 O 0 0 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 2 1
M V30 2 1 2 3 CFG=3
M V30 3 1 4 2
M V30 4 1 4 5 CFG=1
M V30 5 1 4 6
M V30 6 1 6 7
M V30 7 1 6 8 CFG=3
M V30 END BOND
M V30 END CTAB
M END)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
CHECK(m->getAtomWithIdx(5)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
}
SECTION("this came up") {
std::string rdbase = getenv("RDBASE");
std::string fName = rdbase + "/Code/GraphMol/test_data/github87.mol";
std::unique_ptr<RWMol> m{MolFileToMol(fName)};
REQUIRE(m);
CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
}
SECTION("narrow angle") {
auto m = R"CTAB(
Mrv2211 06092305312D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 0
M V30 BEGIN ATOM
M V30 1 C -1.4875 5.1589 0 0
M V30 2 C -1.5412 6.698 0 0
M V30 3 F -2.2685 4.0178 0 0
M V30 4 Br -0.7027 4.0359 0 0
M V30 5 Cl -1.4337 3.6198 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 1 2 CFG=1
M V30 2 1 1 3
M V30 3 1 1 4
M V30 4 1 1 5
M V30 END BOND
M V30 END CTAB
M END
)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
}
SECTION("track bond starting points") {
auto m = R"CTAB(blah
RDKit 2D
7 7 0 0 0 0 0 0 0 0999 V2000
-3.1960 -20.3395 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-3.1960 -19.5114 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
-3.1921 -21.7775 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
-2.7763 -21.0619 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0577 -21.4777 0.0000 Cl 0 0 0 0 0 0 0 0 0 0 0 0
-4.0179 -20.3565 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0
-2.3411 -20.6109 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
1 4 1 6
1 6 1 0
1 7 1 0
4 3 1 1
4 5 1 0
4 7 1 0
M END)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(0)->getChiralTag() !=
Atom::ChiralType::CHI_UNSPECIFIED);
CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
Atom::ChiralType::CHI_UNSPECIFIED);
}
SECTION("linear arrangements") {
{
auto m = R"CTAB(
Mrv2211 06102314502D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 6 5 0 0 1
M V30 BEGIN ATOM
M V30 1 O 9.0665 0.9156 0 0
M V30 2 N 9.6304 4.5593 0 0
M V30 3 C 8.2965 2.2493 0 0 MASS=14
M V30 4 C 6.9628 1.4792 0 0
M V30 5 C 9.6304 3.0193 0 0
M V30 6 H 7.8191 3.0761 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 3 1 CFG=1
M V30 2 1 2 5
M V30 3 1 3 4
M V30 4 1 3 5
M V30 5 1 3 6
M V30 END BOND
M V30 END CTAB
M END
)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
}
{
auto m = R"CTAB(opposing stereo
Mrv2211 06102314502D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 6 5 0 0 1
M V30 BEGIN ATOM
M V30 1 O 9.0665 0.9156 0 0
M V30 2 N 9.6304 4.5593 0 0
M V30 3 C 8.2965 2.2493 0 0 MASS=14
M V30 4 C 6.9628 1.4792 0 0
M V30 5 C 9.6304 3.0193 0 0
M V30 6 H 7.8191 3.0761 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 3 1 CFG=1
M V30 2 1 2 5
M V30 3 1 3 4
M V30 4 1 3 5
M V30 5 1 3 6 CFG=3
M V30 END BOND
M V30 END CTAB
M END
)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
Atom::ChiralType::CHI_UNSPECIFIED);
}
{
// std::cerr<<"11111111111111"<<std::endl;
auto m = R"CTAB(opposing stereo, order change
Mrv2211 06102314502D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 6 5 0 0 1
M V30 BEGIN ATOM
M V30 1 O 9.0665 0.9156 0 0
M V30 2 N 9.6304 4.5593 0 0
M V30 3 C 8.2965 2.2493 0 0 MASS=14
M V30 4 C 6.9628 1.4792 0 0
M V30 5 C 9.6304 3.0193 0 0
M V30 6 H 7.8191 3.0761 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 3 1 CFG=1
M V30 2 1 2 5
M V30 3 1 3 4
M V30 4 1 3 6 CFG=3
M V30 5 1 3 5
M V30 END BOND
M V30 END CTAB
M END)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
Atom::ChiralType::CHI_UNSPECIFIED);
}
{
// IUPAC (ST-1.2.12) says this one is wrong. It definitely requires making
// an assumption about where the H is.
auto m = R"CTAB(three-coordinate, T shaped, wedge in the middle
Mrv2211 06102314502D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 5 4 0 0 1
M V30 BEGIN ATOM
M V30 1 O 9.0665 0.9156 0 0
M V30 2 N 9.6304 4.5593 0 0
M V30 3 C 8.2965 2.2493 0 0 MASS=14
M V30 4 C 6.9628 1.4792 0 0
M V30 5 C 9.6304 3.0193 0 0
M V30 END ATOM
M V30 BEGIN BOND
M V30 1 1 3 1 CFG=1
M V30 2 1 2 5
M V30 3 1 3 4
M V30 4 1 3 5
M V30 END BOND
M V30 END CTAB
M END
)CTAB"_ctab;
REQUIRE(m);
CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
}
}
}
TEST_CASE(
"GitHub Issue #6502: MolToMolBlock writes \"either\" stereo for double bonds"
"which shouldn't be stereo.",
"[bug][molblock][stereo]") {
auto m = "CP1(O)=NP(C)(O)=NP(C)(O)=NP(C)(O)=N1"_smiles;
REQUIRE(m);
auto mb = MolToV3KMolBlock(*m);
CHECK(mb.find("CFG=2") == std::string::npos);
}
TEST_CASE("stereo in ring", "[molblock][stereo]") {
SECTION("test 1") {
auto molblock = R"CTAB(
Mrv2311 10242314442D
0 0 0 0 0 999 V3000
M V30 BEGIN CTAB
M V30 COUNTS 10 11 0 0 1
M V30 BEGIN ATOM
M V30 1 C -2.6673 -0.77 0 0
M V30 2 C -2.6673 0.77 0 0
M V30 3 C -1.3337 1.54 0 0
M V30 4 C 0 0.77 0 0
M V30 5 C 1.3336 1.54 0 0
M V30 6 C 2.6673 0.77 0 0
M V30 7 C 2.6673 -0.77 0 0
M V30 8 C 1.3336 -1.54 0 0
M V30 9 C 0 -0.77 0 0
M V30 10 C -1.3337 -1.54 0 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 3 4
M V30 4 1 4 5
M V30 5 1 5 6
M V30 6 1 6 7
M V30 7 1 7 8
M V30 8 1 8 9
M V30 9 1 4 9
M V30 10 1 9 10
M V30 11 1 1 10
M V30 END BOND
M V30 END CTAB
M END
)CTAB";
std::unique_ptr<RWMol> m(MolBlockToMol(molblock, true, false, false));
REQUIRE(m);
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREONONE);
}
}
TEST_CASE("github #9020: implicit/explicit H labels depend on 3D conformers") {
SECTION("as reported") {
auto mb3 = R"CTAB(
RDKit 3D
6 5 0 0 0 0 0 0 0 0999 V2000
-0.3698 0.0026 0.0028 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8980 -0.5748 -0.1191 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6741 -0.1194 1.0508 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.3142 1.0665 -0.3155 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.0830 -0.5246 -0.6430 H 0 0 0 0 0 0 0 0 0 0 0 0
1.5432 0.1497 0.0240 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
1 3 1 0
1 4 1 0
1 5 1 0
2 6 1 0
M END)CTAB";
{
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 3);
}
{
v2::FileParsers::MolFileParserParams params;
params.removeHs = false;
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3, params);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 0);
MolOps::removeHs(*mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 3);
}
}
SECTION("With a quaternary N") {
auto mb3 = R"CTAB(
RDKit 3D
6 5 0 0 0 0 0 0 0 0999 V2000
-0.3698 0.0026 0.0028 N 0 0 0 0 0 0 0 0 0 0 0 0
0.8980 -0.5748 -0.1191 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6741 -0.1194 1.0508 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.3142 1.0665 -0.3155 H 0 0 0 0 0 0 0 0 0 0 0 0
-1.0830 -0.5246 -0.6430 H 0 0 0 0 0 0 0 0 0 0 0 0
1.5432 0.1497 0.0240 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
1 3 1 0
1 4 1 0
1 5 1 0
2 6 1 0
M CHG 1 1 1
M END)CTAB";
{
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3);
REQUIRE(mol3);
// mol3->debugMol(std::cerr);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 3);
CHECK(mol3->getAtomWithIdx(1)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(1)->getNumImplicitHs() == 1);
}
{
v2::FileParsers::MolFileParserParams params;
params.removeHs = false;
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3, params);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 0);
MolOps::removeHs(*mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 3);
CHECK(mol3->getAtomWithIdx(1)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(1)->getNumImplicitHs() == 1);
}
}
SECTION("actually chiral") {
auto mb3 = R"CTAB(
RDKit 3D
6 5 0 0 0 0 0 0 0 0999 V2000
-0.3698 0.0026 0.0028 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8980 -0.5748 -0.1191 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6741 -0.1194 1.0508 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.3142 1.0665 -0.3155 F 0 0 0 0 0 0 0 0 0 0 0 0
-1.0830 -0.5246 -0.6430 Cl 0 0 0 0 0 0 0 0 0 0 0 0
1.5432 0.1497 0.0240 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
1 3 1 0
1 4 1 0
1 5 1 0
2 6 1 0
M END)CTAB";
{
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 1);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 0);
}
{
v2::FileParsers::MolFileParserParams params;
params.removeHs = false;
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3, params);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 0);
MolOps::removeHs(*mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 1);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 0);
}
}
SECTION("not chiral, one H") {
auto mb3 = R"CTAB(
RDKit 3D
6 5 0 0 0 0 0 0 0 0999 V2000
-0.3698 0.0026 0.0028 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8980 -0.5748 -0.1191 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6741 -0.1194 1.0508 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.3142 1.0665 -0.3155 F 0 0 0 0 0 0 0 0 0 0 0 0
-1.0830 -0.5246 -0.6430 F 0 0 0 0 0 0 0 0 0 0 0 0
1.5432 0.1497 0.0240 H 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0
1 3 1 0
1 4 1 0
1 5 1 0
2 6 1 0
M END)CTAB";
{
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 1);
}
{
v2::FileParsers::MolFileParserParams params;
params.removeHs = false;
auto mol3 = v2::FileParsers::MolFromMolBlock(mb3, params);
REQUIRE(mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 0);
MolOps::removeHs(*mol3);
CHECK(mol3->getAtomWithIdx(0)->getNumExplicitHs() == 0);
CHECK(mol3->getAtomWithIdx(0)->getNumImplicitHs() == 1);
}
}
}
TEST_CASE("GitHub #9270: Segfault when calling MolToSmiles on submol") {
SECTION("as reported") {
UseLegacyStereoPerceptionFixture useLegacyFixture(false);
auto mol =
"CC1/C=C(/C)[O][Ir]23(<-[O]=1)([c]1ccccc1c1cncc[n]->21)[c]1ccccc1c1cncc[n]->31"_smiles;
REQUIRE(mol);
auto dblBond = mol->getBondWithIdx(2);
REQUIRE(dblBond->getBondType() == Bond::BondType::DOUBLE);
REQUIRE(dblBond->getStereo() == Bond::BondStereo::STEREOTRANS);
REQUIRE(dblBond->getStereoAtoms() == std::vector<int>{1, 4});
constexpr unsigned int radius = 3;
constexpr unsigned int atomId = 7;
auto env = findAtomEnvironmentOfRadiusN(*mol, radius, atomId);
std::unique_ptr<ROMol> frag(Subgraphs::pathToSubmol(*mol, env));
REQUIRE(frag);
REQUIRE_NOTHROW(MolToSmiles(*frag));
dblBond = frag->getBondWithIdx(2);
REQUIRE(dblBond->getBondType() == Bond::BondType::DOUBLE);
REQUIRE(dblBond->getStereo() == Bond::BondStereo::STEREOCIS);
REQUIRE(dblBond->getStereoAtoms() == std::vector<int>{1, 4});
}
}