mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-03 21:44:30 +08:00
The standard re-wedging pattern is:
clearSingleBondDirFlags(mol); // saves _UnknownStereo=1, clears BondDir to NONE
WedgeMolBonds(mol, &conf); // re-derives wedges from chiral tags
// ... caller restores BondDir::UNKNOWN on bonds with _UnknownStereo=1 ...
Wiggly bonds at chiral centers should survive this round-trip but did
not: countChiralNbrs only checked BondDir to decide whether a chiral
atom's stereo was already expressed, missing the _UnknownStereo=1
marker that clearSingleBondDirFlags saved when it cleared BondDir to
NONE. With the marker invisible to countChiralNbrs, pickBondToWedge
would pick the wiggly bond itself (terminal neighbors are scored
lower), and the caller's subsequent restore of BondDir::UNKNOWN would
erase the wedge, leaving the chiral atom with no visible stereo.
Fix: extend countChiralNbrs to recognize bonds with _UnknownStereo=1
as equivalent to BondDir::UNKNOWN. The chiral atom is then pre-skipped
in pickBondsToWedge, the same way it already is for bonds that still
have BondDir::UNKNOWN set.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
6634 lines
225 KiB
C++
6634 lines
225 KiB
C++
//
|
|
// Copyright (C) 2020-2025 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 <cstdlib>
|
|
#include <optional>
|
|
#include <ranges>
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
|
|
#include <boost/noncopyable.hpp>
|
|
|
|
#include <GraphMol/RDKitBase.h>
|
|
#include <GraphMol/StereoGroup.h>
|
|
#include <GraphMol/Chirality.h>
|
|
#include <GraphMol/MolOps.h>
|
|
#include <GraphMol/new_canon.h>
|
|
#include <GraphMol/test_fixtures.h>
|
|
|
|
#include <GraphMol/FileParsers/FileParsers.h>
|
|
#include <GraphMol/FileParsers/MolFileStereochem.h>
|
|
#include <GraphMol/FileParsers/MolSupplier.h>
|
|
#include <GraphMol/SmilesParse/SmilesParse.h>
|
|
#include <GraphMol/SmilesParse/SmilesWrite.h>
|
|
#include <GraphMol/CIPLabeler/CIPLabeler.h>
|
|
|
|
using namespace RDKit;
|
|
|
|
unsigned count_wedged_bonds(const ROMol &mol) {
|
|
unsigned nWedged = 0;
|
|
for (const auto bond : mol.bonds()) {
|
|
if (bond->getBondDir() != Bond::BondDir::NONE) {
|
|
++nWedged;
|
|
}
|
|
}
|
|
return nWedged;
|
|
}
|
|
|
|
TEST_CASE("bond StereoInfo", "[unittest]") {
|
|
SECTION("basics") {
|
|
{
|
|
auto mol = "CC=C(C#C)C=C"_smiles;
|
|
REQUIRE(mol);
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 5);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
{
|
|
auto mol = "CC=NC=N"_smiles;
|
|
REQUIRE(mol);
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == Atom::NOATOM);
|
|
}
|
|
}
|
|
SECTION("stereo") {
|
|
auto useLegacy = GENERATE(true, false);
|
|
CAPTURE(useLegacy);
|
|
UseLegacyStereoPerceptionFixture fx(useLegacy);
|
|
|
|
{
|
|
auto mol = "C/C=C(/C#C)C"_smiles;
|
|
REQUIRE(mol);
|
|
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2);
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[0] == 0);
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[1] == 3);
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 5);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Bond_Trans);
|
|
}
|
|
{ // check an example where one of the stereo atoms isn't the first
|
|
// neighbor (only true with legacy chirality)
|
|
auto mol = "C/C=C(/C)C#C"_smiles;
|
|
REQUIRE(mol);
|
|
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2);
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[0] == 0);
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[1] == (useLegacy ? 4 : 3));
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 4);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Bond_Trans);
|
|
}
|
|
{
|
|
auto mol = "C/C=C(\\C#C)C"_smiles;
|
|
REQUIRE(mol);
|
|
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2);
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[0] == 0);
|
|
CHECK(mol->getBondWithIdx(1)->getStereoAtoms()[1] == 3);
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 5);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Bond_Cis);
|
|
}
|
|
{ // any bonds
|
|
auto mol = "CC=C(C#C)C"_smiles;
|
|
REQUIRE(mol);
|
|
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getBondWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 5);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Unknown);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE("isBondPotentialStereoBond", "[unittest]") {
|
|
SECTION("basics") {
|
|
{
|
|
auto mol = "CC=C(C#C)C=C"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(
|
|
Chirality::detail::isBondPotentialStereoBond(mol->getBondWithIdx(1)));
|
|
CHECK(!Chirality::detail::isBondPotentialStereoBond(
|
|
mol->getBondWithIdx(5)));
|
|
CHECK(!Chirality::detail::isBondPotentialStereoBond(
|
|
mol->getBondWithIdx(3)));
|
|
CHECK(!Chirality::detail::isBondPotentialStereoBond(
|
|
mol->getBondWithIdx(4)));
|
|
}
|
|
{
|
|
auto mol = "CC=NC=N"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(
|
|
Chirality::detail::isBondPotentialStereoBond(mol->getBondWithIdx(1)));
|
|
CHECK(
|
|
Chirality::detail::isBondPotentialStereoBond(mol->getBondWithIdx(3)));
|
|
}
|
|
{
|
|
SmilesParserParams ps;
|
|
ps.removeHs = false;
|
|
std::unique_ptr<ROMol> mol{SmilesToMol("[H]C=CC=C([H])[H]", ps)};
|
|
REQUIRE(mol);
|
|
CHECK(!Chirality::detail::isBondPotentialStereoBond(
|
|
mol->getBondWithIdx(1)));
|
|
CHECK(!Chirality::detail::isBondPotentialStereoBond(
|
|
mol->getBondWithIdx(3)));
|
|
}
|
|
}
|
|
SECTION("ring size") {
|
|
{
|
|
auto m = "C1=CCCCC1"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(
|
|
!Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(0)));
|
|
}
|
|
{
|
|
auto m = "C1=CCCCCC1"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(
|
|
!Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(0)));
|
|
}
|
|
{
|
|
auto m = "C12=C(CCCC2)CCCCCC1"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(
|
|
!Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(0)));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("atom StereoInfo", "[unittest]") {
|
|
SECTION("basics") {
|
|
{
|
|
auto mol = "CC(F)(Cl)CNC(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == 2);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 4);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
|
|
|
|
sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(6));
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(sinfo.centeredOn == 6);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 3);
|
|
CHECK(sinfo.controllingAtoms[0] == 5);
|
|
CHECK(sinfo.controllingAtoms[1] == 7);
|
|
CHECK(sinfo.controllingAtoms[2] == 8);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
|
|
{
|
|
auto mol = "C[C@](F)(Cl)CNC(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == 2);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
CHECK(sinfo.controllingAtoms[3] == 4);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(sinfo.descriptor == Chirality::StereoDescriptor::Tet_CCW);
|
|
}
|
|
|
|
{
|
|
auto mol = "CN1CC1N(F)C"_smiles;
|
|
REQUIRE(mol);
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 3);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == 2);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
}
|
|
|
|
{
|
|
auto mol = "O[As](F)C[As]C[As]"_smiles;
|
|
REQUIRE(mol);
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(1));
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(sinfo.centeredOn == 1);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 3);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == 2);
|
|
CHECK(sinfo.controllingAtoms[2] == 3);
|
|
|
|
sinfo = Chirality::detail::getStereoInfo(mol->getAtomWithIdx(4));
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(sinfo.centeredOn == 4);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 2);
|
|
CHECK(sinfo.controllingAtoms[0] == 3);
|
|
CHECK(sinfo.controllingAtoms[1] == 5);
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE("isAtomPotentialTetrahedralCenter", "[unittest]") {
|
|
SECTION("basics") {
|
|
{
|
|
auto mol = "CC(F)(Cl)CNC(C)(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(0)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(4)));
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(6)));
|
|
}
|
|
{
|
|
auto mol = "CN1CC1N(F)C"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(4)));
|
|
}
|
|
{
|
|
auto mol = "O=S(F)CC[S+]([O-])CS=O"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(5)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(8)));
|
|
}
|
|
{
|
|
auto mol = "O=[Se](F)CC[Se+]([O-])C[Se]=O"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(5)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(8)));
|
|
}
|
|
{
|
|
auto mol = "OP(F)CPCP"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(4)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(6)));
|
|
}
|
|
{
|
|
auto mol = "O[As](F)C[As]C[As]"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(4)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(6)));
|
|
}
|
|
{
|
|
auto mol = "O[P]([O-])(=O)OC"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE("isAtomPotentialStereoAtom", "[unittest]") {
|
|
SECTION("basics") {
|
|
{
|
|
auto mol = "CC(F)(Cl)CNC(C)(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
for (const auto atom : mol->atoms()) {
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(atom) ==
|
|
Chirality::detail::isAtomPotentialStereoAtom(atom));
|
|
}
|
|
}
|
|
{
|
|
auto mol = "CN1CC1N(F)C"_smiles;
|
|
REQUIRE(mol);
|
|
for (const auto atom : mol->atoms()) {
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(atom) ==
|
|
Chirality::detail::isAtomPotentialStereoAtom(atom));
|
|
}
|
|
}
|
|
{
|
|
auto mol = "O=S(F)CC[S+]([O-])CS=O"_smiles;
|
|
REQUIRE(mol);
|
|
for (const auto atom : mol->atoms()) {
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(atom) ==
|
|
Chirality::detail::isAtomPotentialStereoAtom(atom));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("possible stereochemistry on atoms", "[chirality]") {
|
|
SECTION("specified") {
|
|
{
|
|
auto mol = "CC(C)(O)[C@](Cl)(F)I"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
std::vector<unsigned> catoms = {1, 5, 6, 7};
|
|
CHECK(stereoInfo[0].controllingAtoms == catoms);
|
|
}
|
|
{
|
|
auto mol = "C[C@@H](O)[C@H](C)[C@H](C)O"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 3);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
|
|
CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[2].centeredOn == 5);
|
|
}
|
|
|
|
{
|
|
auto mol = "FC(F)(F)[C@@H](O)[C@H](C)[C@H](C(F)(F)F)O"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 3);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[1].centeredOn == 6);
|
|
|
|
CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[2].centeredOn == 8);
|
|
}
|
|
}
|
|
SECTION("simple unspecified") {
|
|
{
|
|
auto mol = "CC(C)(O)C(Cl)(F)I"_smiles;
|
|
REQUIRE(mol);
|
|
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
std::vector<unsigned> catoms = {1, 5, 6, 7};
|
|
CHECK(stereoInfo[0].controllingAtoms == catoms);
|
|
}
|
|
}
|
|
SECTION("atoms with unknown set, real") {
|
|
auto mol = "FC(O)C"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
}
|
|
SECTION("atoms with unknown set, not real") {
|
|
auto mol = "CC(O)C"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Isotopes") {
|
|
{
|
|
auto mol = "O[C@H](F)[18OH]"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
std::vector<unsigned> catoms = {0, 2, 3};
|
|
CHECK(stereoInfo[0].controllingAtoms == catoms);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("possible stereochemistry on bonds", "[chirality]") {
|
|
SECTION("simplest") {
|
|
{
|
|
auto mol = "CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
std::vector<unsigned> catoms = {0, Atom::NOATOM, 3, Atom::NOATOM};
|
|
CHECK(stereoInfo[0].controllingAtoms == catoms);
|
|
}
|
|
{
|
|
auto mol = "CC=C(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
{
|
|
auto mol = "CC=C"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
{
|
|
auto mol = "CC(F)=C(Cl)C"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 2);
|
|
std::vector<unsigned> catoms = {2, 0, 4, 5};
|
|
CHECK(stereoInfo[0].controllingAtoms == catoms);
|
|
}
|
|
{
|
|
auto mol = "CC=C(Cl)C"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
std::vector<unsigned> catoms = {0, Atom::NOATOM, 3, 4};
|
|
CHECK(stereoInfo[0].controllingAtoms == catoms);
|
|
}
|
|
}
|
|
SECTION("bond with unknown set, real") {
|
|
auto mol = "CC=C(C)F"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
}
|
|
SECTION("bond with unknown set, not real") {
|
|
auto mol = "CC=C(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("para-stereocenters and assignStereochemistry", "[chirality]") {
|
|
SECTION("simplest") {
|
|
auto mol = "CC(F)C(C)C(C)F"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 3);
|
|
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].controllingAtoms.size() == 3);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[1].controllingAtoms.size() == 3);
|
|
CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].centeredOn == 5);
|
|
CHECK(stereoInfo[2].controllingAtoms.size() == 3);
|
|
}
|
|
|
|
SECTION("including bonds") {
|
|
// thanks to Salome Rieder for this nasty example
|
|
auto mol = "CC=CC(C=CC)C(C)C(C=CC)C=CC"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 7);
|
|
|
|
std::sort(stereoInfo.begin(), stereoInfo.end(),
|
|
[](const Chirality::StereoInfo &a,
|
|
const Chirality::StereoInfo &b) -> bool {
|
|
return (a.type < b.type) && (a.centeredOn < b.centeredOn) &&
|
|
(a.specified < b.specified) &&
|
|
(a.descriptor < b.descriptor) &&
|
|
(a.controllingAtoms < b.controllingAtoms);
|
|
});
|
|
REQUIRE(stereoInfo.size() == 7);
|
|
|
|
CHECK(stereoInfo[6].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[6].centeredOn == 13);
|
|
CHECK(stereoInfo[5].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[5].centeredOn == 10);
|
|
CHECK(stereoInfo[4].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[4].centeredOn == 4);
|
|
CHECK(stereoInfo[3].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[3].centeredOn == 1);
|
|
|
|
CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].centeredOn == 9);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 7);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
}
|
|
|
|
SECTION("sugar fun") {
|
|
auto mol = "C1(O)C(O)C(O)C(O)C(O)C1O"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 6);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.centeredOn % 2 == 0);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ring stereochemistry", "[chirality]") {
|
|
SECTION("partially specified") {
|
|
SmilesParserParams ps;
|
|
ps.sanitize = false;
|
|
std::unique_ptr<RWMol> mol{SmilesToMol("C[C@H]1CCC(C)CC1", ps)};
|
|
REQUIRE(mol);
|
|
// std::cerr << "------------ 3 -------------" << std::endl;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
SECTION("specified") {
|
|
auto mol = "C[C@H]1CC[C@@H](C)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
// std::cerr << "------------ 1 -------------" << std::endl;
|
|
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
SECTION("unspecified") {
|
|
auto mol = "CC1CCC(C)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
// std::cerr << "------------ 2 -------------" << std::endl;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
SECTION("four ring") {
|
|
auto mol = "C[C@H]1C[C@@H](C)C1"_smiles;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
SECTION("four ring unspecified") {
|
|
auto mol = "CC1CC(C)C1"_smiles;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("tricky recursive example from Dan Nealschneider", "[chirality]") {
|
|
SECTION("adapted") {
|
|
auto mol = "CC=C1CCC(O)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
mol->updatePropertyCache();
|
|
MolOps::setBondStereoFromDirections(*mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 5);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].centeredOn == 1);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
SECTION("simplified") {
|
|
// can't sanitize this because the current (2020.03) assignStereochemistry
|
|
// code doesn't recognize the stereo here and removes it
|
|
SmilesParserParams ps;
|
|
ps.sanitize = false;
|
|
ps.removeHs = false;
|
|
std::unique_ptr<ROMol> mol(SmilesToMol("C/C=C1/C[C@H](O)C1", ps));
|
|
REQUIRE(mol);
|
|
mol->updatePropertyCache();
|
|
MolOps::setBondStereoFromDirections(*mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].centeredOn == 1);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
// FIX this still isn't working
|
|
SECTION("unspecified") {
|
|
auto mol = "CC=C1C[CH](O)C1"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].centeredOn == 1);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("unknown stereo", "[chirality]") {
|
|
SECTION("atoms") {
|
|
auto mol = "CC(O)C[C@@H](O)F"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
SECTION("atoms2") {
|
|
// artificial situation: "squiggly bond" overrides the specified atomic
|
|
// stereo
|
|
auto mol = "C[C@H](O)C[C@@H](O)F"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
SECTION("bonds") {
|
|
{
|
|
auto mol = "CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(1, 2));
|
|
mol->getBondBetweenAtoms(1, 2)->setBondDir(Bond::BondDir::EITHERDOUBLE);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
}
|
|
{
|
|
auto mol = "CC=CC=C"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(1, 2));
|
|
mol->getBondBetweenAtoms(1, 2)->setBondDir(Bond::BondDir::EITHERDOUBLE);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
}
|
|
}
|
|
SECTION("bonds with squiggle bonds") {
|
|
{ // to begin atom
|
|
auto mol = "CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
mol->getBondBetweenAtoms(0, 1)->setBondDir(Bond::BondDir::UNKNOWN);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
}
|
|
{ // to end atom
|
|
auto mol = "CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(2, 3));
|
|
mol->getBondBetweenAtoms(2, 3)->setBondDir(Bond::BondDir::UNKNOWN);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("cleaning chirality", "[chirality]") {
|
|
SECTION("atoms") {
|
|
auto mol = "CC(O)C"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
{
|
|
// by default we don't clean up, so the chiral center survives even though
|
|
// we don't get any results:
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 0);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol, cleanIt);
|
|
CHECK(stereoInfo.size() == 0);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("bonds") {
|
|
auto mol = "CC=C(C)C"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
{
|
|
// by default we don't clean up, so the stereo bond survives even though
|
|
// we don't get any results:
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.size() == 0);
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOCIS);
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol, cleanIt);
|
|
CHECK(stereoInfo.size() == 0);
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("flagPossible", "[chirality]") {
|
|
SECTION("atoms") {
|
|
auto mol = "CC(O)[C@H](F)O"_smiles;
|
|
REQUIRE(mol);
|
|
{
|
|
// by default we do use flagPossible:
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
{
|
|
bool cleanIt = false;
|
|
bool flagPossible = false;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
}
|
|
SECTION("bonds") {
|
|
auto mol = "CC=C/C=C/C"_smiles;
|
|
REQUIRE(mol);
|
|
{
|
|
// by default we do use flagPossible
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = false;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Bond_Double);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Specified);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("cleanup after removing possible centers", "[chirality]") {
|
|
SECTION("atoms1") {
|
|
auto mol = "FC(Cl)(F)C(C(Cl)(F)F)I"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
SECTION("bonds1") {
|
|
auto mol = "FC(Cl)(F)C(C(Cl)(F)F)=CF"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
SECTION("atoms2") {
|
|
auto mol = "ClC(F)(F)C(=CC(F)C=C(C(F)(F)Cl)C(F)(F)Cl)C(Cl)(F)F"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("findPotentialStereo problems related to #3490", "[chirality][bug]") {
|
|
SECTION("example 1") {
|
|
auto mol = "CC1CC(O)C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
SECTION("example 2a") {
|
|
auto mol = "C(C(C)C1)C12CCN2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("example 2b") {
|
|
auto mol = "CC(C1)CC12CCN2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("example 2c") {
|
|
auto mol = "C([C@H](C)C1)[C@]12CCN2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("example 2d") {
|
|
auto mol = "C[C@H](C1)C[C@]12CCN2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("example 3") {
|
|
auto mol = "C(C(C)C1)C12CN(C3)CCCCC23"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 4); // [1, 4, 6, 12]
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[2].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].centeredOn == 6);
|
|
CHECK(stereoInfo[2].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[3].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[3].centeredOn == 12);
|
|
CHECK(stereoInfo[3].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
}
|
|
TEST_CASE("ring stereo finding is overly aggressive", "[chirality][bug]") {
|
|
SECTION("Finding too much 1a") {
|
|
auto mol = "CC1CCCCC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 1b") {
|
|
auto mol = "CC1CCC(C)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("Finding too much 1c") {
|
|
auto mol = "C[C@H]1CCC(C)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("Finding too much 1d") {
|
|
auto mol = "CC1(C)CCCCC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 1e") {
|
|
auto mol = "CC1(C)CCC(C)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 1f") {
|
|
auto mol = "C2CC2C1(C2CC2)CCCCC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 1g") {
|
|
auto mol = "CC1CC2(CCC2)C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 1h") {
|
|
auto mol = "CC1CC2(CC(C)C2)C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
}
|
|
|
|
SECTION("Finding too much 2a") {
|
|
auto mol = "CC1CCNCC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 2b") {
|
|
auto mol = "CC1CCN(C)CC1"_smiles; // 3-coordinate N is not stereogenic
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION("Finding too much 3a") {
|
|
auto mol = "CC1CCC1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
|
|
SECTION("Finding too much 3b") {
|
|
auto mol = "CC1CC(C)C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("fused rings 1") {
|
|
auto mol = "C1CCC2CCCCC2C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
|
|
SECTION("fused rings 2") {
|
|
auto mol = "C1CC2CCCC2C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
|
|
SECTION("cages 1") {
|
|
auto mol = "CC1CN2CCC1CC2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[1].centeredOn == 3);
|
|
CHECK(stereoInfo[2].centeredOn == 6);
|
|
}
|
|
SECTION("cages 1b") {
|
|
auto mol = "O1CN2CCC1CC2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 2);
|
|
CHECK(stereoInfo[1].centeredOn == 5);
|
|
}
|
|
SECTION("cages 2") {
|
|
auto mol = "C1CC2(O)CCC1(C)CC2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 2);
|
|
CHECK(stereoInfo[1].centeredOn == 6);
|
|
}
|
|
SECTION("cages 3") {
|
|
auto mol = "C1CC2(O)CCC1CC2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 2);
|
|
CHECK(stereoInfo[1].centeredOn == 6);
|
|
}
|
|
SECTION("adamantyl") {
|
|
auto mol = "CC12CC3CC(CC(C3)C1)C2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 4);
|
|
}
|
|
SECTION("bug 1a") {
|
|
// example that came up during testing
|
|
auto mol = "C(=O)C(C(C)N2C=C2)C(=O)"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
}
|
|
SECTION("bug 1b") {
|
|
// example that came up during testing
|
|
auto mol = "C(=O)C(C(CC)c2ccc(Cl)cc2)C(=O)"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
}
|
|
|
|
SECTION("bug 1c") {
|
|
// example that came up during testing
|
|
auto mol = "O=CC(C=O)C(C)n2cccc2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].centeredOn == 5);
|
|
}
|
|
|
|
SECTION("bug 1c") {
|
|
// example that came up during testing
|
|
auto mol = "C(=O)C(C(C)n2cccc2)C(=O)"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
}
|
|
|
|
SECTION("bug 1d") {
|
|
// example that came up during testing
|
|
auto mol = "C(O)C(C(C)n2cccc2)C(O)"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
}
|
|
SECTION("just a bug") {
|
|
// example that came up during testing
|
|
|
|
auto mol = "CC1=CN(C2OC(CNC(=O)C3c4ccccc4Sc4ccccc43)CC2)C(=O)NC1=O"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 2);
|
|
}
|
|
SECTION("bad ring chirality due to pair being in 2 rings") {
|
|
// from canonicalization watch test.
|
|
|
|
auto mol = "[O-]C1=C2C[C@H](C2)[C@@]12CC2"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
SECTION(
|
|
"Removal of stereoatoms requires removing CIS/TRANS when using legacy stereo") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
{
|
|
auto mol = "N/C=C/C"_smiles;
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
auto rwmol = dynamic_cast<RWMol *>(mol.get());
|
|
rwmol->removeBond(0, 1);
|
|
CHECK(mol->getBondWithIdx(0)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
{
|
|
auto mol = "N/C=C/C"_smiles;
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
auto rwmol = dynamic_cast<RWMol *>(mol.get());
|
|
rwmol->removeBond(2, 3);
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github #3631: Ring stereochemistry not properly removed from N atoms",
|
|
"[chirality][bug]") {
|
|
SECTION("basics") {
|
|
SmilesParserParams ps;
|
|
ps.sanitize = false;
|
|
ps.removeHs = false;
|
|
std::unique_ptr<RWMol> mol{SmilesToMol("C[N@]1C[C@@](F)(Cl)C1", ps)};
|
|
REQUIRE(mol);
|
|
MolOps::sanitizeMol(*mol);
|
|
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getAtomWithIdx(3)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
bool force = true;
|
|
{
|
|
RWMol mol2(*mol);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(mol2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
{
|
|
RWMol mol2(*mol);
|
|
MolOps::assignStereochemistry(mol2, cleanIt, force, flagPossible);
|
|
CHECK(mol2.getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol2.getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("default behavior") {
|
|
auto mol = "C[N@]1C[C@@](F)(Cl)C1"_smiles;
|
|
REQUIRE(mol);
|
|
auto smiles = MolToSmiles(*mol);
|
|
CHECK(smiles == "CN1CC(F)(Cl)C1");
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
bool force = true;
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
{
|
|
RWMol mol2(*mol);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(mol2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 0);
|
|
}
|
|
{
|
|
RWMol mol2(*mol);
|
|
MolOps::assignStereochemistry(mol2, cleanIt, force, flagPossible);
|
|
CHECK(mol2.getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol2.getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("don't overcorrect") {
|
|
auto mol = "C[N@]1O[C@@](F)(Cl)C1"_smiles;
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
bool force = true;
|
|
{
|
|
RWMol mol2(*mol);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(mol2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 1);
|
|
CHECK(stereoInfo[0].centeredOn == 3);
|
|
}
|
|
{
|
|
RWMol mol2(*mol);
|
|
MolOps::assignStereochemistry(mol2, cleanIt, force, flagPossible);
|
|
CHECK(mol2.getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol2.getAtomWithIdx(3)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("N Chirality in rings") {
|
|
SECTION("basics 4 coordinate") {
|
|
{
|
|
auto mol = "CC1CC2CC[N@@+]1(C)OC2"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(6)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(6)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{
|
|
auto mol = "C[N@@+](F)(Cl)O"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(1)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("basics 3 coordinate") {
|
|
{
|
|
auto mol = "CC1CC2CC[N@@]1OC2"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(6)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(6)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{
|
|
auto mol = "C1CC[N@]2OCCCC2C1"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(3)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("ring stereo") {
|
|
{ // real chirality
|
|
auto mol = "C[C@H]1CC[N@@+](C)(O)OC1"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(4)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(4)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getAtomWithIdx(1)->getAtomicNum() == 6);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{ // ring stereo
|
|
auto mol = "C[C@H]1CC[N@@+](C)(O)CC1"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(4)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(4)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getAtomWithIdx(1)->getAtomicNum() == 6);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{ // three-ring degree-three ring stereo
|
|
auto mol = "C[C@H]1[C@@H](C)[N@]1C"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(4)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(4)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{ // CHEMBL79374
|
|
auto mol = "Cn1ncc([C@]23CC[N@](CC2)C3)n1"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(8)->getAtomicNum() == 7);
|
|
CHECK(mol->getAtomWithIdx(8)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{ // derived from CHEMBL79374
|
|
auto mol = "Cn1ncc([C@]23CC[C@](CC2)C3)n1"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(8)->getAtomicNum() == 6);
|
|
CHECK(mol->getAtomWithIdx(8)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #4115: RemoveStereochemistry should also remove stereogroups") {
|
|
SECTION("basics") {
|
|
auto mol = "C[C@H](O)[C@@H](C)F |o1:1,3,r|"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getAtomWithIdx(3)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getStereoGroups().size() == 1);
|
|
MolOps::removeStereochemistry(*mol);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol->getStereoGroups().empty());
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #4155: Problem finding stereocenters in bridged bicyclics with "
|
|
"4-rings") {
|
|
SECTION("specified") {
|
|
std::vector<std::string> smis = {
|
|
"C[C@H]1CC[C@H](CC1)C(N)=O", "C[C@]12CC[C@](CC1)(C2)C(N)=O",
|
|
"C[C@H]1C[C@H](C1)C(N)=O", "C[C@]12C[C@](C1)(CC2)C(N)=O"};
|
|
for (const auto &smi : smis) {
|
|
std::unique_ptr<ROMol> mol(SmilesToMol(smi));
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
}
|
|
}
|
|
SECTION("unspecified") {
|
|
std::vector<std::string> smis = {
|
|
"CC1CCC(CC1)C(N)=O", "CC12CCC(CC1)(C2)C(N)=O", "CC1CC(C1)C(N)=O",
|
|
"CC12CC(C1)(CC2)C(N)=O"};
|
|
for (const auto &smi : smis) {
|
|
std::unique_ptr<ROMol> mol(SmilesToMol(smi));
|
|
REQUIRE(mol);
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(*mol, cleanIt, flagPossible);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("pickBondsToWedge() should avoid double bonds") {
|
|
SECTION("simplest") {
|
|
auto mol = "OC=C[C@H](C1CC1)C2CCC2"_smiles;
|
|
REQUIRE(mol);
|
|
auto wedgedBonds = Chirality::pickBondsToWedge(*mol);
|
|
REQUIRE(wedgedBonds.size() == 1);
|
|
auto head = wedgedBonds.begin();
|
|
CHECK(head->first == 3);
|
|
CHECK(head->second->getIdx() == 3);
|
|
}
|
|
SECTION("simplest, specified double bond") {
|
|
auto mol = "OC=C[C@H](C1CC1)C2CCC2"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3);
|
|
mol->getBondBetweenAtoms(1, 2)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
auto wedgedBonds = Chirality::pickBondsToWedge(*mol);
|
|
REQUIRE(wedgedBonds.size() == 1);
|
|
auto head = wedgedBonds.begin();
|
|
CHECK(head->first == 3);
|
|
CHECK(head->second->getIdx() == 3);
|
|
}
|
|
SECTION("prefer unspecified bond stereo") {
|
|
auto mol = "OC=C[C@H](C=CF)(C=CC)"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3);
|
|
mol->getBondBetweenAtoms(1, 2)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
mol->getBondBetweenAtoms(4, 5)->setStereoAtoms(3, 6);
|
|
mol->getBondBetweenAtoms(4, 5)->setStereo(Bond::BondStereo::STEREOANY);
|
|
auto wedgedBonds = Chirality::pickBondsToWedge(*mol);
|
|
REQUIRE(wedgedBonds.size() == 1);
|
|
auto head = wedgedBonds.begin();
|
|
CHECK(head->first == 6);
|
|
CHECK(head->second->getIdx() == 3);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("addWavyBondsForStereoAny()") {
|
|
SECTION("simplest") {
|
|
auto mol = "CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
|
|
addWavyBondsForStereoAny(*mol);
|
|
CHECK(mol->getBondWithIdx(0)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("don't reset flags") {
|
|
auto mol = "CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOANY);
|
|
bool clearFlags = false;
|
|
addWavyBondsForStereoAny(*mol, clearFlags);
|
|
CHECK(mol->getBondWithIdx(0)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
}
|
|
SECTION("avoid double bonds") {
|
|
auto mol = "CC=CC(CC)=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(5)->setStereoAtoms(2, 7);
|
|
mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
|
|
addWavyBondsForStereoAny(*mol);
|
|
CHECK(mol->getBondWithIdx(6)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("avoid chiral atoms") {
|
|
auto mol = "C[C@](F)(Cl)C(C)=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(5)->setStereoAtoms(1, 7);
|
|
mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
|
|
addWavyBondsForStereoAny(*mol);
|
|
CHECK(mol->getBondWithIdx(4)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("prefer atoms with less neighbors") {
|
|
auto mol = "CC(F)(Cl)C(CF)=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(6)->setStereoAtoms(1, 8);
|
|
mol->getBondWithIdx(6)->setStereo(Bond::BondStereo::STEREOANY);
|
|
addWavyBondsForStereoAny(*mol);
|
|
CHECK(mol->getBondWithIdx(7)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("more complex") {
|
|
auto mol = "CC=CC(C=CO)=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(6)->setStereoAtoms(2, 8);
|
|
mol->getBondWithIdx(6)->setStereo(Bond::BondStereo::STEREOANY);
|
|
addWavyBondsForStereoAny(*mol);
|
|
CHECK(mol->getBondWithIdx(7)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("no solution without changing threshold") {
|
|
auto mol = "CC=CC=CC=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(1)->setStereoAtoms(0, 3);
|
|
mol->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
mol->getBondWithIdx(3)->setStereoAtoms(2, 5);
|
|
mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOANY);
|
|
mol->getBondWithIdx(5)->setStereoAtoms(4, 7);
|
|
mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
addWavyBondsForStereoAny(*mol);
|
|
// we didn't actually do anything:
|
|
CHECK(mol->getBondWithIdx(2)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(mol->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
|
|
bool clearDoubleBondFlags = true;
|
|
addWavyBondsForStereoAny(*mol, clearDoubleBondFlags,
|
|
StereoBondThresholds::DBL_BOND_SPECIFIED_STEREO);
|
|
CHECK(mol->getBondWithIdx(2)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
CHECK(mol->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("multiple bonds to wedge") {
|
|
auto mol = "CCC(C)=CC=C(CC)C=CC(C)=CC"_smiles;
|
|
REQUIRE(mol);
|
|
mol->getBondWithIdx(3)->setStereoAtoms(3, 5);
|
|
mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
mol->getBondWithIdx(9)->setStereoAtoms(6, 11);
|
|
mol->getBondWithIdx(9)->setStereo(Bond::BondStereo::STEREOANY);
|
|
mol->getBondWithIdx(5)->setStereoAtoms(4, 7);
|
|
mol->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
|
|
addWavyBondsForStereoAny(*mol);
|
|
CHECK(mol->getBondWithIdx(9)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
CHECK(mol->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
CHECK(mol->getBondWithIdx(8)->getBondDir() == Bond::BondDir::UNKNOWN);
|
|
for (const auto bond : mol->bonds()) {
|
|
if (bond->getBondType() == Bond::BondType::SINGLE &&
|
|
bond->getIdx() != 8) {
|
|
CHECK(bond->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Github #4215: Ring stereo being discarded in spiro systems") {
|
|
// Note: this bug is still there when using the legacy stereochemistry
|
|
// assignment. It's "non-trivial" to fix there and we've opted not to
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
SECTION("original failing example") {
|
|
auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("original failing example, findPossible") {
|
|
// UseLegacyStereoPerceptionFixture inner_reset_stereo_perception(true);
|
|
SmilesParserParams ps2;
|
|
ps2.sanitize = false;
|
|
std::unique_ptr<RWMol> m{
|
|
SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2", ps2)};
|
|
REQUIRE(m);
|
|
MolOps::sanitizeMol(*m);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(11)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
REQUIRE(stereoInfo.size() == 4);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
if (si.centeredOn != 9) {
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(si.descriptor == Chirality::StereoDescriptor::None);
|
|
} else {
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
SECTION("original passing example") {
|
|
auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)CC2"_smiles;
|
|
REQUIRE(m);
|
|
// if the middle is unspecified, the two ends can't be specified
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(si.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = false;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
}
|
|
SECTION("specified chirality on spiro atom") {
|
|
auto m = "C[C@H]1CC[C@@]2(CC[C@H](C)CC2)CC1"_smiles;
|
|
REQUIRE(m);
|
|
// now the middle is specified, so the two ends are as well
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(7)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = false;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
SECTION("three spiro rings, unspecified spiro links") {
|
|
auto m = "C[C@H]1CCC2(CC1)CCC1(CC[C@H](C)CC1)CC2"_smiles;
|
|
REQUIRE(m);
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 4);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(si.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
SECTION("three spiro rings, specified spiro links") {
|
|
auto m = "C[C@H]1CC[C@@]2(CC1)CC[C@]1(CC[C@H](C)CC1)CC2"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(12)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 4);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #4215: Ring stereo being discarded in spiro systems, using deprecated useLegacyStereo option") {
|
|
// Note: this bug is still there when using the legacy stereochemistry
|
|
// assignment. It's "non-trivial" to fix there and we've opted not to
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
SECTION("original failing example") {
|
|
std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2")};
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("original failing example, findPossible") {
|
|
SmilesParserParams ps2;
|
|
ps2.sanitize = false;
|
|
std::unique_ptr<RWMol> m{
|
|
SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2", ps2)};
|
|
REQUIRE(m);
|
|
MolOps::sanitizeMol(*m);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(11)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
REQUIRE(stereoInfo.size() == 4);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
if (si.centeredOn != 9) {
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(si.descriptor == Chirality::StereoDescriptor::None);
|
|
} else {
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
SECTION("original passing example") {
|
|
std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CCC2(CC1)CC[C@H](C)CC2")};
|
|
REQUIRE(m);
|
|
// if the middle is unspecified, the two ends can't be specified
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(si.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = false;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
}
|
|
SECTION("specified chirality on spiro atom") {
|
|
std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CC[C@@]2(CC[C@H](C)CC2)CC1")};
|
|
REQUIRE(m);
|
|
// now the middle is specified, so the two ends are as well
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(7)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = false;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 3);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
SECTION("three spiro rings, unspecified spiro links") {
|
|
std::unique_ptr<RWMol> m{
|
|
SmilesToMol("C[C@H]1CCC2(CC1)CCC1(CC[C@H](C)CC1)CC2")};
|
|
REQUIRE(m);
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 4);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(si.descriptor == Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
SECTION("three spiro rings, specified spiro links") {
|
|
std::unique_ptr<RWMol> m{
|
|
SmilesToMol("C[C@H]1CC[C@@]2(CC1)CC[C@]1(CC[C@H](C)CC1)CC2")};
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(12)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
{
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
RWMol m2(*m);
|
|
auto stereoInfo =
|
|
Chirality::findPotentialStereo(m2, cleanIt, flagPossible);
|
|
CHECK(stereoInfo.size() == 4);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #4279: FindPotentialStereo() doesn't find *marked* ring stereo "
|
|
"when flagPossible=False") {
|
|
SECTION("base") {
|
|
std::unique_ptr<RWMol> m{SmilesToMol("C[C@H]1CC[C@@H](C)CC1")};
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
bool cleanIt = true;
|
|
bool flagPossible = false;
|
|
auto stereoInfo = Chirality::findPotentialStereo(*m, cleanIt, flagPossible);
|
|
for (const auto &si : stereoInfo) {
|
|
CHECK(si.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(si.specified == Chirality::StereoSpecified::Specified);
|
|
CHECK(si.descriptor != Chirality::StereoDescriptor::None);
|
|
}
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("StereoInfo comparisons") {
|
|
Chirality::StereoInfo si1;
|
|
si1.centeredOn = 3;
|
|
CHECK(si1.type == Chirality::StereoType::Unspecified);
|
|
si1.type = Chirality::StereoType::Atom_Tetrahedral;
|
|
Chirality::StereoInfo si2;
|
|
si2.centeredOn = 3;
|
|
si2.type = Chirality::StereoType::Atom_Tetrahedral;
|
|
CHECK(si1 == si2);
|
|
si2.descriptor = Chirality::StereoDescriptor::Tet_CCW;
|
|
CHECK(si1 != si2);
|
|
}
|
|
|
|
TEST_CASE("StereoGroup Testing") {
|
|
SECTION("basics") {
|
|
auto mol = "C[C@H](O)[C@@H](C)[C@H](F)Cl |o1:1,3,&2:5,r|"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getStereoGroups().size() == 2);
|
|
StereoGroup cp(mol->getStereoGroups()[0]);
|
|
CHECK(cp == mol->getStereoGroups()[0]);
|
|
CHECK(cp != mol->getStereoGroups()[1]);
|
|
|
|
std::vector<Atom *> toRemove{mol->getAtomWithIdx(1)};
|
|
std::vector<StereoGroup> &sgs =
|
|
const_cast<std::vector<StereoGroup> &>(mol->getStereoGroups());
|
|
removeGroupsWithAtoms(toRemove, sgs);
|
|
CHECK(mol->getStereoGroups().size() == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Removing stereogroups from unspecified atoms") {
|
|
SECTION("basics") {
|
|
auto mol = "C[C@](O)(Cl)F |o1:1|"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getStereoGroups().size() == 1);
|
|
mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_UNSPECIFIED);
|
|
Chirality::cleanupStereoGroups(*mol);
|
|
CHECK(mol->getStereoGroups().empty());
|
|
}
|
|
SECTION("parsing") {
|
|
auto mol = "C[C@](C)(Cl)F |o1:1|"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getStereoGroups().empty());
|
|
}
|
|
SECTION("partial group removal") {
|
|
auto mol = "C[C@](C)(Cl)[C@H](F)Cl |o1:1,4|"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getStereoGroups().size() == 1);
|
|
CHECK(mol->getStereoGroups()[0].getAtoms().size() == 1);
|
|
CHECK(mol->getStereoGroups()[0].getAtoms()[0]->getIdx() == 4);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("replaceAtom and StereoGroups") {
|
|
SECTION("basics") {
|
|
auto mol = "C[C@](O)(Cl)[C@H](F)Cl |o1:1,4|"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getStereoGroups().size() == 1);
|
|
CHECK(mol->getStereoGroups()[0].getAtoms().size() == 2);
|
|
CHECK(mol->getStereoGroups()[0].getAtoms()[0] == mol->getAtomWithIdx(1));
|
|
|
|
Atom acp(*mol->getAtomWithIdx(1));
|
|
mol->replaceAtom(1, &acp);
|
|
CHECK(mol->getStereoGroups().size() == 1);
|
|
CHECK(mol->getStereoGroups()[0].getAtoms().size() == 2);
|
|
CHECK(mol->getStereoGroups()[0].getAtoms()[0] == mol->getAtomWithIdx(1));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #5200: FindPotentialStereo does not clean stereoflags from atoms "
|
|
"which cannot be stereocenters") {
|
|
auto m = "CCF"_smiles;
|
|
REQUIRE(m);
|
|
m->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
bool cleanIt = true;
|
|
auto sinfo = Chirality::findPotentialStereo(*m, cleanIt);
|
|
CHECK(sinfo.empty());
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #5196: Zero & coordinate bonds are being taken into account for "
|
|
"chirality") {
|
|
RDLog::LogStateSetter setter; // disable irritating warning messages
|
|
auto mol = R"CTAB(
|
|
RDKit 3D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 15 18 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -0.136359 0.025241 -0.986870 0
|
|
M V30 2 C 0.211183 -0.810922 0.138318 0
|
|
M V30 3 C -0.446638 -0.713741 1.305561 0
|
|
M V30 4 C -1.141107 0.914647 -0.916429 0
|
|
M V30 5 R -1.628248 -0.983190 -0.411960 0
|
|
M V30 6 H 0.392055 -0.106505 -1.920607 0
|
|
M V30 7 H 0.974038 -1.568492 0.017171 0
|
|
M V30 8 H -0.209921 -1.406535 2.084966 0
|
|
M V30 9 H -1.378909 1.482059 -1.807349 0
|
|
M V30 10 C -1.544607 0.306162 1.588191 0
|
|
M V30 11 C -1.946856 1.186683 0.358271 0
|
|
M V30 12 H -1.207983 0.944410 2.407927 0
|
|
M V30 13 H -2.419549 -0.225146 1.965589 0
|
|
M V30 14 H -3.006492 1.040978 0.144313 0
|
|
M V30 15 H -1.830875 2.240146 0.620809 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1
|
|
M V30 2 2 3 2
|
|
M V30 3 2 4 1
|
|
M V30 4 0 1 5
|
|
M V30 5 0 2 5
|
|
M V30 6 0 3 5
|
|
M V30 7 0 4 5
|
|
M V30 8 1 1 6
|
|
M V30 9 1 2 7
|
|
M V30 10 1 3 8
|
|
M V30 11 1 4 9
|
|
M V30 12 1 10 3
|
|
M V30 13 1 11 4
|
|
M V30 14 1 11 10
|
|
M V30 15 1 12 10
|
|
M V30 16 1 13 10
|
|
M V30 17 1 14 11
|
|
M V30 18 1 15 11
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(mol);
|
|
SECTION("as reported") {
|
|
MolOps::assignStereochemistryFrom3D(*mol);
|
|
for (auto aidx : {0, 1, 2, 3}) {
|
|
CHECK(mol->getAtomWithIdx(aidx)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("as reported - ZOBs") {
|
|
for (auto bidx : {3, 4, 5, 6}) {
|
|
mol->getBondWithIdx(bidx)->setBondType(Bond::BondType::ZERO);
|
|
}
|
|
MolOps::assignStereochemistryFrom3D(*mol);
|
|
for (auto idx : {0, 1, 2, 3}) {
|
|
CHECK(mol->getAtomWithIdx(idx)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("as reported - datives") {
|
|
for (auto bidx : {3, 4, 5, 6}) {
|
|
mol->getBondWithIdx(bidx)->setBondType(Bond::BondType::DATIVE);
|
|
}
|
|
MolOps::assignStereochemistryFrom3D(*mol);
|
|
for (auto idx : {0, 1, 2, 3}) {
|
|
CHECK(mol->getAtomWithIdx(idx)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("as reported - reversed datives") {
|
|
// structure is bogus, but we want to test
|
|
for (auto bidx : {3, 4, 5, 6}) {
|
|
auto bond = mol->getBondWithIdx(bidx);
|
|
bond->setEndAtomIdx(bond->getBeginAtomIdx());
|
|
bond->setBeginAtomIdx(4);
|
|
bond->setBondType(Bond::BondType::DATIVE);
|
|
}
|
|
MolOps::assignStereochemistryFrom3D(*mol);
|
|
for (auto idx : {0, 1, 2, 3}) {
|
|
CHECK(mol->getAtomWithIdx(idx)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("as reported - singles") {
|
|
// structure is bogus, but we want to test
|
|
for (auto bidx : {3, 4, 5, 6}) {
|
|
mol->getBondWithIdx(bidx)->setBondType(Bond::BondType::SINGLE);
|
|
}
|
|
MolOps::assignStereochemistryFrom3D(*mol);
|
|
for (auto idx : {0, 1, 2, 3}) {
|
|
CHECK(mol->getAtomWithIdx(idx)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("assignStereochemistry") {
|
|
auto mol = "[Fe]C(=C)O |C:1.0|"_smiles;
|
|
REQUIRE(mol);
|
|
for (auto bt : {Bond::BondType::DATIVE, Bond::BondType::ZERO,
|
|
Bond::BondType::UNSPECIFIED}) {
|
|
mol->getAtomWithIdx(1)->setChiralTag(
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
mol->getBondWithIdx(0)->setBondType(bt);
|
|
bool cleanit = true;
|
|
bool force = true;
|
|
MolOps::assignStereochemistry(*mol, cleanit, force);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("isAtomPotentialTetrahedralCenter() and getStereoInfo()") {
|
|
auto mol = "[Fe]C(=C)O |C:1.0|"_smiles;
|
|
REQUIRE(mol);
|
|
for (auto bt : {Bond::BondType::DATIVE, Bond::BondType::ZERO,
|
|
Bond::BondType::UNSPECIFIED}) {
|
|
mol->getAtomWithIdx(1)->setChiralTag(
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
mol->getBondWithIdx(0)->setBondType(bt);
|
|
CHECK(!Chirality::detail::isAtomPotentialStereoAtom(
|
|
mol->getAtomWithIdx(1)));
|
|
bool cleanit = true;
|
|
auto sinfo = Chirality::findPotentialStereo(*mol, cleanit);
|
|
CHECK(sinfo.empty());
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #5239: Precondition violation on chiral Atoms with zero order "
|
|
"bonds") {
|
|
RDLog::LogStateSetter setter; // disable irritating warning messages
|
|
auto molblock = R"CTAB(
|
|
RDKit 3D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 5 4 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -0.446600 -0.713700 1.305600 0
|
|
M V30 2 Fe -1.628200 -0.983200 -0.412000 0
|
|
M V30 3 Cl -0.049300 -1.876700 2.613900 0
|
|
M V30 4 C -1.544600 0.306200 1.588200 0
|
|
M V30 5 F 0.673700 0.029200 0.993700 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 3
|
|
M V30 2 1 1 4 CFG=1
|
|
M V30 3 1 1 5
|
|
M V30 4 0 2 1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END)CTAB";
|
|
bool sanitize = false;
|
|
std::unique_ptr<ROMol> mol(MolBlockToMol(molblock, sanitize));
|
|
REQUIRE(mol);
|
|
MolOps::assignStereochemistryFrom3D(*mol);
|
|
|
|
CHECK(mol->getAtomWithIdx(0)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
TEST_CASE("nontetrahedral stereo from 3D", "[nontetrahedral]") {
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/nontetrahedral_3d.sdf";
|
|
SECTION("basics") {
|
|
SDMolSupplier suppl(pathName);
|
|
while (!suppl.atEnd()) {
|
|
std::unique_ptr<ROMol> m{suppl.next()};
|
|
REQUIRE(m);
|
|
MolOps::assignChiralTypesFrom3D(*m);
|
|
auto ct = m->getProp<std::string>("ChiralType");
|
|
auto cp = m->getProp<unsigned>("ChiralPermutation");
|
|
auto atom = m->getAtomWithIdx(0);
|
|
|
|
if (ct == "SP") {
|
|
CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_SQUAREPLANAR);
|
|
} else if (ct == "TB") {
|
|
CHECK(atom->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
|
|
} else if (ct == "TH") {
|
|
CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL);
|
|
} else if (ct == "OH") {
|
|
CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_OCTAHEDRAL);
|
|
}
|
|
|
|
CHECK(atom->getProp<unsigned>(common_properties::_chiralPermutation) ==
|
|
cp);
|
|
}
|
|
}
|
|
SECTION("disable nontetrahedral stereo") {
|
|
AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
|
|
Chirality::setAllowNontetrahedralChirality(false);
|
|
SDMolSupplier suppl(pathName);
|
|
while (!suppl.atEnd()) {
|
|
std::unique_ptr<ROMol> m{suppl.next()};
|
|
REQUIRE(m);
|
|
MolOps::assignChiralTypesFrom3D(*m);
|
|
auto ct = m->getProp<std::string>("ChiralType");
|
|
auto atom = m->getAtomWithIdx(0);
|
|
|
|
if (ct == "TH") {
|
|
CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL);
|
|
} else {
|
|
CHECK(atom->getChiralTag() == Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("assignStereochemistry shouldn't remove nontetrahedral stereo",
|
|
"[nontetrahedral]") {
|
|
SECTION("basics") {
|
|
SmilesParserParams parseps;
|
|
parseps.sanitize = false;
|
|
parseps.removeHs = false;
|
|
std::unique_ptr<RWMol> m{SmilesToMol("F[Pt@TB1](O)(Br)(N)Cl", parseps)};
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
|
|
bool cleanIt = true;
|
|
bool force = true;
|
|
MolOps::assignStereochemistry(*m, cleanIt, force);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
|
|
}
|
|
SECTION("standard SMILES parsing") {
|
|
std::unique_ptr<RWMol> m{SmilesToMol("F[Pt@TB1](O)(Br)(N)Cl")};
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
|
|
}
|
|
SECTION("SMILES parsing w/o sanitization") {
|
|
SmilesParserParams parseps;
|
|
// we need to skip stereo assignment
|
|
parseps.sanitize = false;
|
|
std::unique_ptr<RWMol> m{SmilesToMol("F[Pt@TB1](O)(Br)(N)Cl", parseps)};
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("remove hs and non-tetrahedral stereo", "[nontetrahedral]") {
|
|
SmilesParserParams parseps;
|
|
parseps.sanitize = false;
|
|
parseps.removeHs = false;
|
|
std::vector<std::string> smiles = {"F[Pt@TB1]([H])(Br)(N)Cl",
|
|
"F[Pt@TB]([H])(Br)(N)Cl"};
|
|
for (const auto &smi : smiles) {
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi, parseps)};
|
|
REQUIRE(m);
|
|
CHECK(m->getNumAtoms(6));
|
|
{
|
|
// the default is to not remove Hs to non-tetrahedral stereocenters
|
|
RWMol molcp(*m);
|
|
MolOps::removeHs(molcp);
|
|
CHECK(molcp.getNumAtoms() == 6);
|
|
}
|
|
{
|
|
// but we can enable it
|
|
RWMol molcp(*m);
|
|
MolOps::RemoveHsParameters ps;
|
|
ps.removeNontetrahedralNeighbors = true;
|
|
MolOps::removeHs(molcp, ps);
|
|
CHECK(molcp.getNumAtoms() == 5);
|
|
}
|
|
{
|
|
// but we can enable it
|
|
RWMol molcp(*m);
|
|
MolOps::removeAllHs(molcp);
|
|
CHECK(molcp.getNumAtoms() == 5);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("getIdealAngle", "[nontetrahedral]") {
|
|
SECTION("TB1") {
|
|
auto m = "S[As@TB1](F)(Cl)(Br)N"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(0)));
|
|
CHECK(Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(5)));
|
|
CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(2)));
|
|
CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(3)));
|
|
CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(4)));
|
|
CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1))
|
|
->getIdx() == 0);
|
|
CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1), -1)
|
|
->getIdx() == 5);
|
|
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(2)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(3)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(4)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(2), m->getAtomWithIdx(3)),
|
|
Catch::Matchers::WithinAbs(120, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(5)),
|
|
Catch::Matchers::WithinAbs(180, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(5), m->getAtomWithIdx(2)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(5), m->getAtomWithIdx(3)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(5), m->getAtomWithIdx(4)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
}
|
|
SECTION("TB1 missing 1") {
|
|
// S[As@TB1](F)(Cl)(Br)* => S[As@TB7](*)(F)(Cl)Br
|
|
auto m = "S[As@TB7](F)(Cl)Br"_smiles;
|
|
REQUIRE(m);
|
|
|
|
CHECK(Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(0)));
|
|
CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(2)));
|
|
CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(3)));
|
|
CHECK(!Chirality::isTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
m->getAtomWithIdx(4)));
|
|
CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1))
|
|
->getIdx() == 0);
|
|
CHECK(Chirality::getTrigonalBipyramidalAxialAtom(m->getAtomWithIdx(1),
|
|
-1) == nullptr);
|
|
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(2)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(3)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(0), m->getAtomWithIdx(4)),
|
|
Catch::Matchers::WithinAbs(90, 0.001));
|
|
CHECK_THAT(
|
|
Chirality::getIdealAngleBetweenLigands(
|
|
m->getAtomWithIdx(1), m->getAtomWithIdx(2), m->getAtomWithIdx(3)),
|
|
Catch::Matchers::WithinAbs(120, 0.001));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("getChiralPermutation", "[nontetrahedral]") {
|
|
SECTION("TB1") {
|
|
// clang-format off
|
|
std::vector<std::pair<std::list<int>, unsigned int>> data = {
|
|
{{2, 3, 4, 5, 6}, 1},
|
|
{{2, 3, 5, 4, 6}, 2},
|
|
|
|
{{2, 3, 4, 6, 5}, 3},
|
|
{{2, 3, 5, 6, 4}, 4},
|
|
|
|
{{2, 3, 6, 4, 5}, 5},
|
|
{{2, 3, 6, 5, 4}, 6},
|
|
|
|
{{2, 6, 3, 4, 5}, 7},
|
|
{{2, 6, 3, 5, 4}, 8},
|
|
|
|
{{3, 2, 4, 5, 6}, 9},
|
|
{{3, 2, 5, 4, 6}, 11},
|
|
|
|
{{3, 2, 4, 6, 5}, 10},
|
|
{{3, 2, 5, 6, 4}, 12},
|
|
|
|
{{3, 2, 6, 4, 5}, 13},
|
|
{{3, 2, 6, 5, 4}, 14},
|
|
|
|
{{3, 4, 2, 5, 6}, 15},
|
|
{{3, 5, 2, 4, 6}, 20},
|
|
|
|
{{3, 4, 2, 6, 5}, 16},
|
|
{{3, 5, 2, 6, 4}, 19},
|
|
|
|
{{3, 4, 5, 2, 6}, 17},
|
|
{{3, 5, 4, 2, 6}, 18},
|
|
|
|
|
|
};
|
|
// clang-format on
|
|
auto m = "CCS[As@TB1](F)(Cl)(Br)N"_smiles;
|
|
REQUIRE(m);
|
|
const auto atm = m->getAtomWithIdx(3);
|
|
for (const auto &pr : data) {
|
|
// std::cerr << "---- " << pr.second << std::endl;
|
|
CHECK(Chirality::getChiralPermutation(atm, pr.first) == pr.second);
|
|
}
|
|
}
|
|
|
|
SECTION("SP1") {
|
|
// clang-format off
|
|
std::vector<std::pair<std::list<int>, unsigned int>> data = {
|
|
{{2, 3, 4, 5}, 1},
|
|
{{2, 4, 3, 5}, 2},
|
|
{{2, 3, 5, 4}, 3},
|
|
};
|
|
// clang-format on
|
|
auto m = "CCC[Pt@SP1](F)(Cl)N"_smiles;
|
|
REQUIRE(m);
|
|
const auto atm = m->getAtomWithIdx(3);
|
|
for (const auto &pr : data) {
|
|
// std::cerr << "---- " << pr.second << std::endl;
|
|
CHECK(Chirality::getChiralPermutation(atm, pr.first) == pr.second);
|
|
}
|
|
}
|
|
SECTION("OH1") {
|
|
// clang-format off
|
|
std::vector<std::pair<std::list<int>, unsigned int>> data = {
|
|
{{2, 3, 4, 5, 6, 7}, 1},
|
|
{{2, 3, 6, 5, 4, 7}, 2},
|
|
|
|
{{2, 3, 4, 5, 7, 6}, 3},
|
|
{{2, 3, 6, 5, 7, 4}, 16},
|
|
|
|
{{2, 3, 4, 7, 5, 6}, 6},
|
|
{{2, 3, 6, 7, 5, 4}, 18},
|
|
|
|
{{2, 3, 7, 4, 5, 6}, 19},
|
|
{{2, 3, 7, 6, 5, 4}, 24},
|
|
|
|
{{2, 7, 3, 4, 5, 6}, 25},
|
|
{{2, 7, 3, 6, 5, 4}, 30},
|
|
|
|
{{2, 3, 4, 6, 5, 7}, 4},
|
|
{{2, 3, 6, 4, 5, 7}, 14},
|
|
|
|
{{2, 3, 4, 6, 7, 5}, 5},
|
|
{{2, 3, 6, 4, 7, 5}, 15},
|
|
|
|
{{2, 3, 4, 7, 6, 5}, 7},
|
|
{{2, 3, 6, 7, 4, 5}, 17},
|
|
|
|
{{2, 3, 7, 4, 6, 5}, 20},
|
|
{{2, 3, 7, 6, 4, 5}, 23},
|
|
|
|
{{2, 7, 3, 4, 6, 5}, 26},
|
|
{{2, 7, 3, 6, 4, 5}, 29},
|
|
|
|
{{2, 3, 5, 6, 4, 7}, 10},
|
|
{{2, 3, 5, 4, 6, 7}, 8},
|
|
|
|
{{2, 3, 5, 6, 7, 4}, 11},
|
|
{{2, 3, 5, 4, 7, 6}, 9},
|
|
|
|
{{2, 3, 5, 7, 6, 4}, 13},
|
|
{{2, 3, 5, 7, 4, 6}, 12},
|
|
|
|
{{2, 3, 7, 5, 6, 4}, 22},
|
|
{{2, 3, 7, 5, 4, 6}, 21},
|
|
|
|
{{2, 7, 3, 5, 6, 4}, 28},
|
|
{{2, 7, 3, 5, 4, 6}, 27},
|
|
|
|
};
|
|
// clang-format on
|
|
auto m = "CCO[Co@OH1](Cl)(C)(N)(F)P"_smiles;
|
|
REQUIRE(m);
|
|
const auto atm = m->getAtomWithIdx(3);
|
|
for (const auto &pr : data) {
|
|
// std::cerr << "---- " << pr.second << std::endl;
|
|
CHECK(Chirality::getChiralPermutation(atm, pr.first) == pr.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("isAtomPotentialNontetrahedralCenter", "[nontetrahedral]") {
|
|
SECTION("basics") {
|
|
{
|
|
auto mol = "C[S+](O)F"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(!Chirality::detail::isAtomPotentialNontetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
}
|
|
{
|
|
auto mol = "C[SH](O)F"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialNontetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
}
|
|
{
|
|
auto mol = "C[S@SP](O)F"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(Chirality::detail::isAtomPotentialNontetrahedralCenter(
|
|
mol->getAtomWithIdx(1)));
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE("nontetrahedral StereoInfo", "[nontetrahedral]") {
|
|
SECTION("SP") {
|
|
auto m = "C[Pt@SP1](F)(Cl)O"_smiles;
|
|
REQUIRE(m);
|
|
auto sinfo = Chirality::findPotentialStereo(*m);
|
|
REQUIRE(sinfo.size() == 1);
|
|
CHECK(sinfo[0].centeredOn == 1);
|
|
CHECK(sinfo[0].type == Chirality::StereoType::Atom_SquarePlanar);
|
|
CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
|
|
CHECK(sinfo[0].permutation == 1);
|
|
CHECK(sinfo[0].controllingAtoms == std::vector<unsigned int>{0, 2, 3, 4});
|
|
}
|
|
SECTION("TB") {
|
|
auto m = "C[Fe@TB4](F)(Cl)(O)N"_smiles;
|
|
REQUIRE(m);
|
|
auto sinfo = Chirality::findPotentialStereo(*m);
|
|
REQUIRE(sinfo.size() == 1);
|
|
CHECK(sinfo[0].centeredOn == 1);
|
|
CHECK(sinfo[0].type == Chirality::StereoType::Atom_TrigonalBipyramidal);
|
|
CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
|
|
CHECK(sinfo[0].permutation == 4);
|
|
CHECK(sinfo[0].controllingAtoms ==
|
|
std::vector<unsigned int>{0, 2, 3, 4, 5});
|
|
}
|
|
SECTION("TB0") {
|
|
auto m = "C[Fe@TB](F)(Cl)(O)N"_smiles;
|
|
REQUIRE(m);
|
|
auto sinfo = Chirality::findPotentialStereo(*m);
|
|
REQUIRE(sinfo.size() == 1);
|
|
CHECK(sinfo[0].centeredOn == 1);
|
|
CHECK(sinfo[0].specified == Chirality::StereoSpecified::Unknown);
|
|
CHECK(sinfo[0].type == Chirality::StereoType::Atom_TrigonalBipyramidal);
|
|
CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
|
|
CHECK(sinfo[0].permutation == 0);
|
|
CHECK(sinfo[0].controllingAtoms ==
|
|
std::vector<unsigned int>{0, 2, 3, 4, 5});
|
|
}
|
|
SECTION("perceived as possible") {
|
|
auto m = "C[Fe](F)(Cl)(O)N"_smiles;
|
|
REQUIRE(m);
|
|
auto sinfo = Chirality::findPotentialStereo(*m);
|
|
REQUIRE(sinfo.size() == 1);
|
|
CHECK(sinfo[0].centeredOn == 1);
|
|
CHECK(sinfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
CHECK(sinfo[0].type == Chirality::StereoType::Atom_TrigonalBipyramidal);
|
|
CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
|
|
CHECK(sinfo[0].permutation == 0);
|
|
CHECK(sinfo[0].controllingAtoms ==
|
|
std::vector<unsigned int>{0, 2, 3, 4, 5});
|
|
}
|
|
|
|
SECTION("OH") {
|
|
auto m = "C[Fe@OH9](F)(Cl)(O)(N)Br"_smiles;
|
|
REQUIRE(m);
|
|
auto sinfo = Chirality::findPotentialStereo(*m);
|
|
REQUIRE(sinfo.size() == 1);
|
|
CHECK(sinfo[0].centeredOn == 1);
|
|
CHECK(sinfo[0].type == Chirality::StereoType::Atom_Octahedral);
|
|
CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
|
|
CHECK(sinfo[0].permutation == 9);
|
|
CHECK(sinfo[0].controllingAtoms ==
|
|
std::vector<unsigned int>{0, 2, 3, 4, 5, 6});
|
|
}
|
|
SECTION("OH missing ligand") {
|
|
// C[Fe@OH9](F)(Cl)(O)(N)* => C[Fe@OH4](*)(F)(Cl)(O)N
|
|
auto m = "C[Fe@OH4](F)(Cl)(O)N"_smiles;
|
|
REQUIRE(m);
|
|
auto sinfo = Chirality::findPotentialStereo(*m);
|
|
REQUIRE(sinfo.size() == 1);
|
|
CHECK(sinfo[0].centeredOn == 1);
|
|
CHECK(sinfo[0].type == Chirality::StereoType::Atom_Octahedral);
|
|
CHECK(sinfo[0].descriptor == Chirality::StereoDescriptor::None);
|
|
CHECK(sinfo[0].permutation == 9);
|
|
CHECK(sinfo[0].controllingAtoms ==
|
|
std::vector<unsigned int>{0, 2, 3, 4, 5});
|
|
}
|
|
}
|
|
|
|
TEST_CASE("github #5328: assignChiralTypesFrom3D() ignores wiggly bonds") {
|
|
SECTION("basics") {
|
|
auto m = R"CTAB(
|
|
RDKit 3D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 5 4 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 0.900794 -0.086835 0.009340 0
|
|
M V30 2 C -0.552652 0.319534 0.077502 0
|
|
M V30 3 F -0.861497 0.413307 1.437370 0
|
|
M V30 4 Cl -0.784572 1.925710 -0.672698 0
|
|
M V30 5 O -1.402227 -0.583223 -0.509512 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=2
|
|
M V30 4 1 2 5
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
MolOps::assignChiralTypesFrom3D(*m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("non-tetrahedral") {
|
|
auto m = R"CTAB(
|
|
Mrv2108 05252216313D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 6 5 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -1.7191 0.2488 -3.5085 0
|
|
M V30 2 As -1.0558 1.9209 -2.6345 0
|
|
M V30 3 F -0.4636 3.422 -1.7567 0
|
|
M V30 4 O -2.808 2.4243 -2.1757 0
|
|
M V30 5 Cl -0.1145 2.6609 -4.5048 0
|
|
M V30 6 Br 0.2255 0.6458 -1.079 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=2
|
|
M V30 5 1 2 6
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
MolOps::assignChiralTypesFrom3D(*m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("useLegacyStereoPerception feature flag") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
|
|
|
|
SECTION("original failing example") {
|
|
Chirality::setUseLegacyStereoPerception(true);
|
|
auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("use new code") {
|
|
Chirality::setUseLegacyStereoPerception(false);
|
|
auto m = "C[C@H]1CCC2(CC1)CC[C@H](C)C(C)C2"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
}
|
|
std::string molblock = R"CTAB(
|
|
Mrv2108 05202206352D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 14 15 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -4.5417 3.165 0 0
|
|
M V30 2 C -5.8753 2.395 0 0
|
|
M V30 3 C -5.8753 0.855 0 0
|
|
M V30 4 C -4.5417 0.085 0 0 CFG=1
|
|
M V30 5 C -3.208 0.855 0 0
|
|
M V30 6 C -3.208 2.395 0 0
|
|
M V30 7 C -4.5417 -1.455 0 0
|
|
M V30 8 C -1.8743 0.085 0 0
|
|
M V30 9 C -4.5417 6.2451 0 0 CFG=2
|
|
M V30 10 C -5.8753 5.4751 0 0
|
|
M V30 11 C -5.8753 3.9351 0 0
|
|
M V30 12 C -3.208 3.9351 0 0
|
|
M V30 13 C -3.208 5.4751 0 0
|
|
M V30 14 C -4.5417 7.7851 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 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 5 6
|
|
M V30 6 1 1 6
|
|
M V30 7 1 4 7 CFG=1
|
|
M V30 8 1 5 8
|
|
M V30 9 1 9 10
|
|
M V30 10 1 10 11
|
|
M V30 11 1 12 13
|
|
M V30 12 1 9 13
|
|
M V30 13 1 11 1
|
|
M V30 14 1 1 12
|
|
M V30 15 1 9 14 CFG=1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB";
|
|
SECTION("original example, from mol block") {
|
|
Chirality::setUseLegacyStereoPerception(true);
|
|
std::unique_ptr<RWMol> m{MolBlockToMol(molblock)};
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(8)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("original example, from mol block, new code") {
|
|
Chirality::setUseLegacyStereoPerception(false);
|
|
std::unique_ptr<RWMol> m{MolBlockToMol(molblock)};
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() != Atom::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(8)->getChiralTag() == Atom::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("wedgeMolBonds to aromatic rings") {
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 10 11 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 2.948889 -2.986305 0.000000 0
|
|
M V30 2 C 2.560660 -4.435194 0.000000 0
|
|
M V30 3 N 1.111771 -4.046965 0.000000 0
|
|
M V30 4 C 1.500000 -2.598076 0.000000 0
|
|
M V30 5 C 0.750000 -1.299038 0.000000 0
|
|
M V30 6 C 1.500000 0.000000 0.000000 0
|
|
M V30 7 C 0.750000 1.299038 0.000000 0
|
|
M V30 8 C -0.750000 1.299038 0.000000 0
|
|
M V30 9 C -1.500000 0.000000 0.000000 0
|
|
M V30 10 C -0.750000 -1.299038 0.000000 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 3 4
|
|
M V30 4 1 4 5 CFG=1
|
|
M V30 5 2 5 6
|
|
M V30 6 1 6 7
|
|
M V30 7 2 7 8
|
|
M V30 8 1 8 9
|
|
M V30 9 2 9 10
|
|
M V30 10 1 4 1
|
|
M V30 11 1 10 5
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getBondWithIdx(2)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(m->getBondWithIdx(3)->getBondDir() == Bond::BondDir::NONE);
|
|
|
|
SECTION("generating mol blocks") {
|
|
auto mb = MolToV3KMolBlock(*m);
|
|
CHECK(mb.find("M V30 10 1 4 1 CFG=1") == std::string::npos);
|
|
CHECK(mb.find("M V30 4 1 4 5 CFG=1") != std::string::npos);
|
|
}
|
|
|
|
SECTION("details: pickBondsWedge()") {
|
|
// this is with aromatic bonds
|
|
auto wedgedBonds = Chirality::pickBondsToWedge(*m);
|
|
CHECK(wedgedBonds.at(3)->getIdx() == 3);
|
|
RWMol cp(*m);
|
|
|
|
// now try kekulized:
|
|
MolOps::Kekulize(cp);
|
|
wedgedBonds = Chirality::pickBondsToWedge(cp);
|
|
CHECK(wedgedBonds.at(3)->getIdx() == 3);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("github 5307: AssignAtomChiralTagsFromStructure ignores Hydrogens") {
|
|
std::string mb = R"CTAB(
|
|
RDKit 3D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 5 4 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -0.022097 0.003215 0.016520 0
|
|
M V30 2 H -0.669009 0.889360 -0.100909 0
|
|
M V30 3 H -0.377788 -0.857752 -0.588296 0
|
|
M V30 4 H 0.096421 -0.315125 1.063781 0
|
|
M V30 5 H 0.972473 0.280302 -0.391096 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 2
|
|
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";
|
|
bool sanitize = true;
|
|
bool removeHs = false;
|
|
std::unique_ptr<RWMol> m{MolBlockToMol(mb, sanitize, removeHs)};
|
|
REQUIRE(m);
|
|
MolOps::assignChiralTypesFrom3D(*m);
|
|
CHECK(m->getAtomWithIdx(0)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
// assignStereochemistryFrom3D() actually checks:
|
|
MolOps::assignStereochemistryFrom3D(*m);
|
|
CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github 5403: Incorrect perception of pseudoasymmetric centers on "
|
|
"non-canonical molecules") {
|
|
SECTION("basics") {
|
|
auto mol1 = "N[C@@]12CC[C@@](CC1)(C2)C(F)F"_smiles;
|
|
REQUIRE(mol1);
|
|
|
|
bool clean = true;
|
|
|
|
auto stereoInfo1 = Chirality::findPotentialStereo(*mol1, clean);
|
|
REQUIRE(stereoInfo1.size() == 2);
|
|
REQUIRE(stereoInfo1[0].centeredOn == 1);
|
|
REQUIRE(stereoInfo1[1].centeredOn == 4);
|
|
CHECK(mol1->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol1->getAtomWithIdx(4)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
|
|
auto mol2 = "C1C[C@]2(C(F)F)CC[C@@]1(N)C2"_smiles;
|
|
REQUIRE(mol2);
|
|
|
|
auto stereoInfo2 = Chirality::findPotentialStereo(*mol2, clean);
|
|
REQUIRE(stereoInfo2.size() == 2);
|
|
CHECK(stereoInfo2[0].centeredOn == 2);
|
|
CHECK(stereoInfo2[1].centeredOn == 8);
|
|
{
|
|
RWMol cp(*mol2);
|
|
Chirality::findPotentialStereo(cp, true, false);
|
|
CHECK(cp.getAtomWithIdx(2)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(cp.getAtomWithIdx(8)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("new stereo") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
auto mol1 = "N[C@@]12CC[C@@](CC1)(C2)C(F)F"_smiles;
|
|
REQUIRE(mol1);
|
|
CHECK(mol1->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(mol1->getAtomWithIdx(4)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
TEST_CASE("assignStereochemistry sets bond stereo with new stereo perception") {
|
|
SECTION("basics") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
{
|
|
auto m = "C/C=C/C"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
|
|
}
|
|
{
|
|
auto m = "C/C=C\\C"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOCIS);
|
|
CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
|
|
}
|
|
{
|
|
auto m = "C(/C)=C/C"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOCIS);
|
|
CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{1, 3});
|
|
}
|
|
{
|
|
auto m = "FC(/C)=C/C"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(m->getBondWithIdx(2)->getStereoAtoms() == std::vector<int>{0, 4});
|
|
}
|
|
{
|
|
auto m = "FC(/C)=C(F)/C"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREOCIS);
|
|
CHECK(m->getBondWithIdx(2)->getStereoAtoms() == std::vector<int>{0, 4});
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("chiral duplicates") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
SECTION("atom basics") {
|
|
auto mol = "C[C@](F)([C@H](F)Cl)[C@H](F)Cl"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(mol->getAtomWithIdx(6)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(mol->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("double bonds and atoms") {
|
|
auto mol = "C/C(O)=C([C@H](F)Cl)/[C@H](F)Cl"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(4)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(mol->getAtomWithIdx(7)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(mol->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("double bonds and atoms 2") {
|
|
auto mol = "C/C(O)=C([C@H](F)Cl)/[C@@H](F)Cl"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(4)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(mol->getAtomWithIdx(7)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
CHECK(mol->getBondWithIdx(2)->getStereo() != Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("double bonds and double bonds") {
|
|
auto mol = "C/C(O)=C(/C=C/C)/C=C/C"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getBondWithIdx(4)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(mol->getBondWithIdx(7)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(mol->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("double bonds and double bonds 2") {
|
|
auto mol = "C/C(O)=C(/C=C/C)/C=C\\C"_smiles;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getBondWithIdx(4)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(mol->getBondWithIdx(7)->getStereo() == Bond::BondStereo::STEREOCIS);
|
|
CHECK(mol->getBondWithIdx(2)->getStereo() != Bond::BondStereo::STEREONONE);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("more findPotential") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
SECTION("basics") {
|
|
{
|
|
auto m = "O[C@H](C)CC(C)C[C@@H](C)O"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 2);
|
|
}
|
|
{
|
|
auto m = "O[C@H](C)CC(C)C[C@H](C)O"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
{
|
|
auto m = "O[CH](C)C[C@H](C)C[CH](C)O"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
{
|
|
auto m = "O[CH](C)C[CH](C)C[CH](C)O"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
}
|
|
SECTION("double bond impact on atoms") {
|
|
{
|
|
auto m = "C[CH](/C=C/C)/C=C\\C"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
{
|
|
auto m = "C[CH](/C=C/C)/C=C/C"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 2);
|
|
}
|
|
{
|
|
auto m = "C[CH](C=CC)C=CC"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
{
|
|
auto m = "C[CH](C=CC)C=CC"_smiles;
|
|
REQUIRE(m);
|
|
m->getBondWithIdx(2)->setStereo(Bond::BondStereo::STEREOANY);
|
|
m->getBondWithIdx(5)->setStereo(Bond::BondStereo::STEREOANY);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
}
|
|
SECTION("atom impact on double bonds") {
|
|
{
|
|
auto m = "CC=C([C@H](F)Cl)[C@@H](F)Cl"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
{
|
|
auto m = "CC=C([C@H](F)Cl)[C@H](F)Cl"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 2);
|
|
}
|
|
{
|
|
auto m = "CC=C([CH](F)Cl)[CH](F)Cl"_smiles;
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
{
|
|
auto m = "CC=C([CH](F)Cl)[CH](F)Cl"_smiles;
|
|
REQUIRE(m);
|
|
m->getBondBetweenAtoms(2, 3)->setBondDir(Bond::UNKNOWN);
|
|
m->getBondBetweenAtoms(2, 6)->setBondDir(Bond::UNKNOWN);
|
|
auto si = Chirality::findPotentialStereo(*m, true, true);
|
|
CHECK(si.size() == 3);
|
|
}
|
|
}
|
|
}
|
|
TEST_CASE("more findPotential and ring stereo") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
SECTION("simple") {
|
|
{
|
|
auto m = "CC1CCC(C)CC1"_smiles;
|
|
REQUIRE(m);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*m, true, true);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].centeredOn == 1);
|
|
CHECK(stereoInfo[0].specified == Chirality::StereoSpecified::Unspecified);
|
|
|
|
CHECK(stereoInfo[1].type == Chirality::StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].centeredOn == 4);
|
|
CHECK(stereoInfo[1].specified == Chirality::StereoSpecified::Unspecified);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github 2984: RDKit misplaces stereochemistry/chirality information for "
|
|
"small ring") {
|
|
using namespace RDKit::Chirality;
|
|
|
|
// UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
|
|
|
|
SECTION("The mol with the issue") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
// With Legacy stereo, parsing the SMILES string will discard the
|
|
// problematic stereo features, so findPotentialStereo (which uses
|
|
// the new algorithm) will still find them, but they will be unspecified.
|
|
// Parsing with the new algorithm will preserve the features, so they
|
|
// can be correctly resolved.
|
|
|
|
auto specified_status = use_legacy ? StereoSpecified::Unspecified
|
|
: StereoSpecified::Specified;
|
|
|
|
auto mol = R"SMI(CC/C=C1\C[C@H](O)C1)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 5);
|
|
CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == specified_status);
|
|
CHECK(stereoInfo[1].centeredOn == 2);
|
|
CHECK(stereoInfo[1].type == StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].specified == specified_status);
|
|
}
|
|
}
|
|
|
|
// The other sections should yield the same results independently
|
|
// of which stereo algoritm is used, new or legacy
|
|
|
|
SECTION("Unspecified, but still potentially stereo") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
auto mol = R"SMI(CCC=C1CC(O)C1)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 5);
|
|
CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].centeredOn == 2);
|
|
CHECK(stereoInfo[1].type == StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].specified == StereoSpecified::Unspecified);
|
|
}
|
|
}
|
|
|
|
SECTION("Specified") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
auto mol = R"SMI(CC/C=C1\CC[C@H]1O)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 2);
|
|
CHECK(stereoInfo[0].centeredOn == 6);
|
|
CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == StereoSpecified::Specified);
|
|
CHECK(stereoInfo[1].centeredOn == 2);
|
|
CHECK(stereoInfo[1].type == StereoType::Bond_Double);
|
|
CHECK(stereoInfo[1].specified == StereoSpecified::Specified);
|
|
}
|
|
}
|
|
|
|
SECTION("More than one ring (1)") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
auto mol = R"SMI(CC\C=C/1C2CCC1[C@H]2O)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 4);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[1].centeredOn == 7);
|
|
CHECK(stereoInfo[1].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].specified == StereoSpecified::Unspecified);
|
|
CHECK(stereoInfo[2].centeredOn == 8);
|
|
CHECK(stereoInfo[2].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].specified == (use_legacy
|
|
? StereoSpecified::Unspecified
|
|
: StereoSpecified::Specified));
|
|
CHECK(stereoInfo[3].centeredOn == 2);
|
|
CHECK(stereoInfo[3].type == StereoType::Bond_Double);
|
|
CHECK(stereoInfo[3].specified == (use_legacy
|
|
? StereoSpecified::Unspecified
|
|
: StereoSpecified::Specified));
|
|
}
|
|
}
|
|
|
|
SECTION("More than one ring (2)") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
// This is the same structure as previos section, but
|
|
// the rings are defined in the opposite order
|
|
auto mol = R"SMI(CC\C=C/1C2[C@H](O)C1CC2)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
REQUIRE(stereoInfo.size() == 4);
|
|
CHECK(stereoInfo[0].centeredOn == 4);
|
|
CHECK(stereoInfo[0].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[0].specified == StereoSpecified::Unspecified);
|
|
|
|
CHECK(stereoInfo[1].centeredOn == 5);
|
|
CHECK(stereoInfo[1].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[1].specified == (use_legacy
|
|
? StereoSpecified::Unspecified
|
|
: StereoSpecified::Specified));
|
|
|
|
CHECK(stereoInfo[2].centeredOn == 7);
|
|
CHECK(stereoInfo[2].type == StereoType::Atom_Tetrahedral);
|
|
CHECK(stereoInfo[2].specified == StereoSpecified::Unspecified);
|
|
|
|
CHECK(stereoInfo[3].centeredOn == 2);
|
|
CHECK(stereoInfo[3].type == StereoType::Bond_Double);
|
|
CHECK(stereoInfo[3].specified == (use_legacy
|
|
? StereoSpecified::Unspecified
|
|
: StereoSpecified::Specified));
|
|
}
|
|
}
|
|
|
|
SECTION("Stereo not possible: symmetric opposite atom") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
auto mol = R"SMI(CCC=C1CC(O)(O)C1)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
}
|
|
|
|
SECTION("Stereo not possible: odd-sized ring") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
|
|
auto mol = R"SMI(CCC=C1CCCC1)SMI"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
CHECK(stereoInfo.empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("double bond stereo with new chirality perception") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
SECTION("chain bonds") {
|
|
{
|
|
auto m = "C/C=C/C"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
|
|
}
|
|
}
|
|
SECTION("ring bonds") {
|
|
{
|
|
auto m = "C1/C=C/CCCCCCC1"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOTRANS);
|
|
CHECK(m->getBondWithIdx(1)->getStereoAtoms() == std::vector<int>{0, 3});
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("false positives from new stereo code") {
|
|
SECTION("elements") {
|
|
std::vector<std::string> examples{"P", "PC", "S", "SC", "S(F)C"};
|
|
for (auto &smi : examples) {
|
|
INFO(smi);
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi)};
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m);
|
|
CHECK(si.empty());
|
|
}
|
|
}
|
|
SECTION("non-tetrahedral and implicit Hs") {
|
|
std::vector<std::string> examples{
|
|
"[SiH4]", "[SiH3]C", "[SH4]", "[PH5]",
|
|
"[PH4]C", "[SH6]", "[SH5]C", //"[SiH2](C)C",
|
|
"[PH3](C)C", "[PH2](C)(C)C", "[SH4](C)C", "[SH3](C)(C)C",
|
|
"[SH2](C)(C)(C)C"};
|
|
{
|
|
AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
|
|
Chirality::setAllowNontetrahedralChirality(false);
|
|
for (auto &smi : examples) {
|
|
INFO(smi);
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi)};
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m);
|
|
CHECK(si.empty());
|
|
}
|
|
}
|
|
{
|
|
AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
|
|
Chirality::setAllowNontetrahedralChirality(true);
|
|
for (auto &smi : examples) {
|
|
INFO(smi);
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi)};
|
|
REQUIRE(m);
|
|
auto si = Chirality::findPotentialStereo(*m);
|
|
CHECK(si.size() == 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #6049: Cyclobutyl group in a macrocycle triggers a stereo center") {
|
|
SECTION("as reported") {
|
|
auto mol = "O=S1(=O)C=CC=C2CCCCC3CC(C3)N21"_smiles;
|
|
REQUIRE(mol);
|
|
auto stereoInfo = Chirality::findPotentialStereo(*mol);
|
|
for (const auto &sg : stereoInfo) {
|
|
CHECK(sg.centeredOn != 15);
|
|
}
|
|
}
|
|
}
|
|
|
|
void testStereoValidationFromMol(std::string molBlock,
|
|
std::string expectedSmiles, bool legacyFlag,
|
|
bool canonicalFlag = false) {
|
|
RDKit::Chirality::setUseLegacyStereoPerception(legacyFlag);
|
|
|
|
std::unique_ptr<RWMol> mol(MolBlockToMol(molBlock, true, false, false));
|
|
REQUIRE(mol);
|
|
|
|
// CHECK(CIPLabeler::validateStereochem(*mol, validationFlags));
|
|
|
|
RDKit::SmilesWriteParams smilesWriteParams;
|
|
smilesWriteParams.doIsomericSmiles = true;
|
|
smilesWriteParams.doKekule = false;
|
|
smilesWriteParams.canonical = canonicalFlag;
|
|
|
|
unsigned int flags = 0 |
|
|
RDKit::SmilesWrite::CXSmilesFields::CX_MOLFILE_VALUES |
|
|
RDKit::SmilesWrite::CXSmilesFields::CX_ATOM_PROPS |
|
|
RDKit::SmilesWrite::CXSmilesFields::CX_BOND_CFG |
|
|
RDKit::SmilesWrite::CXSmilesFields::CX_ENHANCEDSTEREO |
|
|
RDKit::SmilesWrite::CXSmilesFields::CX_SGROUPS |
|
|
RDKit::SmilesWrite::CXSmilesFields::CX_POLYMER;
|
|
|
|
auto outSmiles = MolToCXSmiles(*mol, smilesWriteParams, flags);
|
|
RDKit::Chirality::setUseLegacyStereoPerception(false);
|
|
|
|
CHECK(outSmiles == expectedSmiles);
|
|
}
|
|
|
|
std::string validateStereoMolBlockSpiro = R"(
|
|
Mrv2308 06232316112D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 13 14 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -4.2921 0.9158 0 0
|
|
M V30 2 C -4.2921 -0.6244 0 0
|
|
M V30 3 C -2.9583 -1.3942 0 0 CFG=1
|
|
M V30 4 C -1.6246 -0.6244 0 0
|
|
M V30 5 C -1.6246 0.9158 0 0
|
|
M V30 6 C -2.9583 4.7658 0 0 CFG=2
|
|
M V30 7 C -4.2921 3.9958 0 0
|
|
M V30 8 C -4.2921 2.4556 0 0
|
|
M V30 9 C -2.9583 1.6858 0 0 CFG=2
|
|
M V30 10 C -1.6246 2.4556 0 0
|
|
M V30 11 C -1.6246 3.9958 0 0
|
|
M V30 12 C -2.9583 6.3058 0 0
|
|
M V30 13 Cl -2.9583 -2.9342 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 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 6 7
|
|
M V30 6 1 6 11
|
|
M V30 7 1 7 8
|
|
M V30 8 1 9 8 CFG=1
|
|
M V30 9 1 9 10
|
|
M V30 10 1 10 11
|
|
M V30 11 1 9 1
|
|
M V30 12 1 9 5
|
|
M V30 13 1 6 12 CFG=1
|
|
M V30 14 1 3 13 CFG=1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
|
|
)";
|
|
|
|
std::string validateStereoMolBlockDoubleBondNoStereo = R"(
|
|
Mrv2308 06232316392D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 9 9 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 0.8498 2.3564 0 0
|
|
M V30 2 C 2.1835 1.5864 0 0
|
|
M V30 3 C 2.1835 0.0464 0 0
|
|
M V30 4 C -0.4839 1.5864 0 0
|
|
M V30 5 O -1.8176 2.3564 0 0
|
|
M V30 6 C -1.8176 3.8964 0 0
|
|
M V30 7 C -3.3342 3.629 0 0
|
|
M V30 8 O -0.4839 4.6664 0 0
|
|
M V30 9 C 0.8498 3.8964 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 2 1 2
|
|
M V30 2 1 2 3
|
|
M V30 3 1 1 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 5 6
|
|
M V30 6 1 6 7
|
|
M V30 7 1 6 8
|
|
M V30 8 1 8 9
|
|
M V30 9 1 1 9
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)";
|
|
|
|
std::string validateStereoMolBlockDoubleBondNoStereo2 = R"(
|
|
Mrv0541 07011416342D
|
|
|
|
21 22 0 0 0 0 999 V2000
|
|
-1.9814 1.4834 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.9581 -2.7980 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.3658 -2.0787 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.8189 1.5764 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.5800 0.7796 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.9760 2.3967 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.1333 -2.7836 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.9581 -1.3592 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7256 -2.0690 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.4882 -1.3401 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8471 2.4140 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7304 -0.6351 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.9007 -0.6351 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.4834 0.0700 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.1333 -1.3497 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.3658 0.9831 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.9814 0.0525 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.7598 0.7854 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.3410 0.0700 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.6556 2.2396 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.2722 2.7980 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
20 1 1 0 0 0 0
|
|
4 1 2 0 0 0 0
|
|
5 1 1 0 0 0 0
|
|
2 7 2 0 0 0 0
|
|
3 2 1 0 0 0 0
|
|
3 8 2 0 0 0 0
|
|
6 4 1 0 0 0 0
|
|
16 4 1 0 0 0 0
|
|
18 5 1 0 0 0 0
|
|
17 5 2 0 0 0 0
|
|
21 6 2 0 0 0 0
|
|
7 9 1 0 0 0 0
|
|
8 15 1 0 0 0 0
|
|
9 15 2 0 0 0 0
|
|
10 13 1 0 0 0 0
|
|
11 20 1 0 0 0 0
|
|
13 12 2 0 0 0 0
|
|
15 12 1 0 0 0 0
|
|
14 13 1 0 0 0 0
|
|
19 14 2 0 0 0 0
|
|
19 18 1 0 0 0 0
|
|
21 20 1 0 0 0 0
|
|
M END
|
|
> <Compound Name>
|
|
VM-0411129
|
|
|
|
> <CDD Number>
|
|
CDD-2839271
|
|
|
|
$$$$
|
|
|
|
)";
|
|
|
|
std::string validateStereoError1 = R"(
|
|
-ISIS- -- StrEd --
|
|
|
|
29 32 0 0 0 0 0 0 0 0999 V2000
|
|
-1.2050 -4.7172 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8959 -3.7660 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0823 -3.5582 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7514 -4.3013 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7296 -4.0934 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.0385 -3.1425 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.3694 -2.3993 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.6785 -1.4481 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.0094 -0.7050 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0312 -0.9129 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.9470 -1.1209 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.3183 0.2461 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.2694 0.5550 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.2694 1.5549 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.3183 1.8641 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.0094 2.8151 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.6785 3.5582 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.3694 4.5093 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.3912 4.7172 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.2777 3.9739 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0312 3.0230 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7305 1.0550 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.2694 1.0550 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.7694 1.9210 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.7695 1.9210 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.2694 1.0550 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.7695 0.1889 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.7694 0.1889 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.3912 -2.6071 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
|
|
3 4 1 0 0 0 0
|
|
4 5 2 0 0 0 0
|
|
5 6 1 0 0 0 0
|
|
6 7 2 0 0 0 0
|
|
7 8 1 0 0 0 0
|
|
8 9 2 0 0 0 0
|
|
9 10 1 0 0 0 0
|
|
10 11 3 0 0 0 0
|
|
9 12 1 0 0 0 0
|
|
12 13 2 0 0 0 0
|
|
13 14 1 0 0 0 0
|
|
14 15 2 0 0 0 0
|
|
15 16 1 0 0 0 0
|
|
16 17 1 0 0 0 0
|
|
17 18 1 0 0 0 0
|
|
18 19 1 0 0 0 0
|
|
19 20 1 0 0 0 0
|
|
20 21 1 0 0 0 0
|
|
16 21 1 0 0 0 0
|
|
15 22 1 0 0 0 0
|
|
12 22 1 0 0 0 0
|
|
22 23 1 0 0 0 0
|
|
23 24 1 0 0 0 0
|
|
24 25 2 0 0 0 0
|
|
25 26 1 0 0 0 0
|
|
26 27 2 0 0 0 0
|
|
27 28 1 0 0 0 0
|
|
23 28 2 0 0 0 0
|
|
7 29 1 0 0 0 0
|
|
3 29 2 0 0 0 0
|
|
M END
|
|
> <Compound Name>
|
|
Z362114294
|
|
|
|
> <CDD Number>
|
|
CDD-3164311
|
|
|
|
$$$$
|
|
|
|
)";
|
|
|
|
std::string validateStereoUniq1 = R"(
|
|
Mrv0541 06301412152D
|
|
|
|
15 15 0 0 0 0 999 V2000
|
|
1.0464 -0.3197 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.0464 -1.1460 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7601 -1.5571 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.4739 -1.1460 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.4739 -0.3197 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7601 0.0914 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.3309 -1.5706 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.1912 0.0976 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.9103 -0.3114 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.1853 0.9197 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.9115 -1.1336 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7601 0.9135 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0
|
|
4.6215 -1.5447 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7601 -2.3793 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.1894 -1.5665 0.0000 * 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 6 1 0 0 0 0
|
|
1 2 1 0 0 0 0
|
|
2 3 1 0 0 0 0
|
|
2 7 2 0 0 0 0
|
|
3 4 1 0 0 0 0
|
|
3 14 1 0 0 0 0
|
|
4 15 1 0 0 0 0
|
|
4 5 2 0 0 0 0
|
|
5 6 1 0 0 0 0
|
|
5 8 1 0 0 0 0
|
|
6 12 1 0 0 0 0
|
|
8 9 1 0 0 0 0
|
|
8 10 2 0 0 0 0
|
|
9 11 2 0 0 0 0
|
|
11 13 1 0 0 0 0
|
|
M STY 4 1 SUP 2 SUP 3 SUP 4 SUP
|
|
M SAL 1 1 12
|
|
M SBL 1 1 11
|
|
M SMT 1 R3a
|
|
M SAP 1 1 12 6 1
|
|
M SCL 1 CXN
|
|
M SAL 2 1 13
|
|
M SBL 2 1 15
|
|
M SMT 2 R3b
|
|
M SAP 2 1 13 11 1
|
|
M SCL 2 CXN
|
|
M SAL 3 1 14
|
|
M SBL 3 1 6
|
|
M SMT 3 R1
|
|
M SAP 3 1 14 3 1
|
|
M SCL 3 CXN
|
|
M SAL 4 1 15
|
|
M SBL 4 1 7
|
|
M SMT 4 R2
|
|
M SAP 4 1 15 4 1
|
|
M SCL 4 CXN
|
|
M END
|
|
> <Compound Name>
|
|
VM-0021367
|
|
|
|
> <CDD Number>
|
|
CDD-3832787
|
|
|
|
$$$$
|
|
)";
|
|
|
|
TEST_CASE("ValidateStereo", "[accurateCIP]") {
|
|
SECTION("validateStereoUniqOldCanon1") {
|
|
testStereoValidationFromMol(validateStereoUniq1,
|
|
"*/C=C/C(=O)C1=C(*)N(*)C(=O)NC1*", true, true);
|
|
}
|
|
|
|
SECTION("validateStereoUniqNewCanon1") {
|
|
testStereoValidationFromMol(validateStereoUniq1,
|
|
"*/C=C/C(=O)C1=C(*)N(*)C(=O)NC1*", false, true);
|
|
}
|
|
|
|
SECTION("validateStereoUniqNewNoCanon1") {
|
|
testStereoValidationFromMol(
|
|
validateStereoUniq1, "N1C(=O)N(*)C(*)=C(C(/C=C/*)=O)C1*", false, false);
|
|
}
|
|
|
|
SECTION("SprioChiralLostOldNoCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockSpiro,
|
|
"C1C[C@H](Cl)CCC12CC[C@@H](C)CC2", true, false);
|
|
}
|
|
|
|
SECTION("SprioChiralLostOldCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockSpiro,
|
|
"C[C@H]1CCC2(CC1)CC[C@H](Cl)CC2", true, true);
|
|
}
|
|
|
|
SECTION("SprioChiralNotLostNewNoCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockSpiro,
|
|
"C1C[C@H](Cl)CC[C@]12CC[C@@H](C)CC2", false,
|
|
false);
|
|
}
|
|
|
|
SECTION("SprioChiralNotLostNewCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockSpiro,
|
|
"C[C@H]1CC[C@@]2(CC1)CC[C@H](Cl)CC2", false,
|
|
true);
|
|
}
|
|
|
|
SECTION("DoubleBondMarkedStereoOldNoCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
|
|
"C1(=CC)COC(C)OC1", true, false);
|
|
}
|
|
SECTION("DoubleBondMarkedStereoNewNoCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
|
|
"C1(=CC)COC(C)OC1", false, false);
|
|
}
|
|
|
|
SECTION("DoubleBondMarkedStereoOldCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
|
|
"CC=C1COC(C)OC1", true, true);
|
|
}
|
|
|
|
SECTION("DoubleBondMarkedStereoNewCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo,
|
|
"CC=C1COC(C)OC1", false, true);
|
|
}
|
|
|
|
SECTION("DoubleBondStereo2OldCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
|
|
"CC(/C=N/NC(=O)c1c(Br)cnn1C)=C\\c1ccccc1", true,
|
|
true);
|
|
}
|
|
SECTION("DoubleBondStereo2NewCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
|
|
"CC(=C\\c1ccccc1)/C=N/NC(=O)c1c(Br)cnn1C",
|
|
false, true);
|
|
}
|
|
|
|
SECTION("DoubleBondStereo2NewNoCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
|
|
"c1(C(=O)N/N=C/C(C)=C/c2ccccc2)c(Br)cnn1C",
|
|
false, false);
|
|
}
|
|
SECTION("DoubleBondStereo2OldNoCanon") {
|
|
testStereoValidationFromMol(validateStereoMolBlockDoubleBondNoStereo2,
|
|
"c1(C(=O)N/N=C/C(C)=C/c2ccccc2)c(Br)cnn1C",
|
|
true, false);
|
|
}
|
|
|
|
SECTION("ValidateStereoError1OldCanon") {
|
|
testStereoValidationFromMol(
|
|
validateStereoError1,
|
|
"COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", true, true);
|
|
}
|
|
SECTION("ValidateStereoError1NewCanon") {
|
|
testStereoValidationFromMol(
|
|
validateStereoError1,
|
|
"COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", false, true);
|
|
}
|
|
SECTION("ValidateStereoError1OldNoCanon") {
|
|
testStereoValidationFromMol(
|
|
validateStereoError1,
|
|
"COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", true, false);
|
|
}
|
|
SECTION("ValidateStereoError1NewNoCanon") {
|
|
testStereoValidationFromMol(
|
|
validateStereoError1,
|
|
"COc1cccc(/C=C(\\C#N)c2nnc(N3CCOCC3)n2-c2ccccc2)c1", false, false);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"assignStereochemistry should clear crossed double bonds that can't have stereo") {
|
|
SECTION("basics") {
|
|
auto m = "CC=C(C)C"_smiles;
|
|
REQUIRE(m);
|
|
m->getBondWithIdx(1)->setBondDir(Bond::BondDir::EITHERDOUBLE);
|
|
bool clean = true;
|
|
bool flag = true;
|
|
bool force = true;
|
|
{
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
auto cp(*m);
|
|
RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
|
|
CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
{
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
|
|
auto cp(*m);
|
|
RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
|
|
CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
}
|
|
SECTION("make sure we don't mess with actual potential stereosystems") {
|
|
auto m = "CC=C(C)[13CH3]"_smiles;
|
|
REQUIRE(m);
|
|
m->getBondWithIdx(1)->setBondDir(Bond::BondDir::EITHERDOUBLE);
|
|
bool clean = true;
|
|
bool flag = true;
|
|
bool force = true;
|
|
{
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
auto cp(*m);
|
|
RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
|
|
// the crossed bond dir has been translated to unknown stereo:
|
|
CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
}
|
|
{
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
|
|
auto cp(*m);
|
|
RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
|
|
// the crossed bond dir has been translated to unknown stereo:
|
|
CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondWithIdx(1)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
}
|
|
}
|
|
SECTION("make sure stereoatoms are also cleared") {
|
|
auto m = "CC=C(C)C"_smiles;
|
|
REQUIRE(m);
|
|
m->getBondWithIdx(1)->setBondDir(Bond::BondDir::EITHERDOUBLE);
|
|
m->getBondWithIdx(1)->setStereoAtoms(0, 3);
|
|
bool clean = true;
|
|
bool flag = true;
|
|
bool force = true;
|
|
{
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
auto cp(*m);
|
|
RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
|
|
CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondWithIdx(1)->getStereoAtoms().empty());
|
|
}
|
|
{
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
|
|
auto cp(*m);
|
|
RDKit::MolOps::assignStereochemistry(cp, clean, force, flag);
|
|
CHECK(cp.getBondWithIdx(1)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondWithIdx(1)->getStereoAtoms().empty());
|
|
}
|
|
}
|
|
SECTION("ensure we can enumerate stereo on either double bonds") {
|
|
auto mol = R"CTAB(
|
|
Mrv2004 11072316002D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 8 8 0 0 1
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 1.859 2.7821 0 0
|
|
M V30 2 C 3.2818 2.1928 0 0
|
|
M V30 3 C 3.8711 0.77 0 0
|
|
M V30 4 C 3.2818 -0.6528 0 0
|
|
M V30 5 C 1.859 -1.2421 0 0
|
|
M V30 6 C 0.4362 -0.6528 0 0
|
|
M V30 7 C -0.1533 0.77 0 0
|
|
M V30 8 C 0.4362 2.1928 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 4 3 CFG=2
|
|
M V30 4 2 4 5
|
|
M V30 5 1 5 6
|
|
M V30 6 1 6 7
|
|
M V30 7 1 7 8
|
|
M V30 8 1 1 8
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END)CTAB"_ctab;
|
|
// mol->debugMol(std::cerr);
|
|
std::string smi = MolToCXSmiles(*mol, SmilesWriteParams());
|
|
std::unique_ptr<ROMol> f(SmilesToMol(smi));
|
|
mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOCIS);
|
|
CHECK(MolToSmiles(*mol) == "C1=C\\CCCCCC/1");
|
|
CHECK(MolToSmiles(*f) == "C1=C\\CCCCCC/1");
|
|
mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOTRANS);
|
|
f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOTRANS);
|
|
CHECK(MolToSmiles(*mol) == "C1=C/CCCCCC/1");
|
|
CHECK(MolToSmiles(*f) == "C1=C/CCCCCC/1");
|
|
}
|
|
}
|
|
TEST_CASE("adding two wedges to chiral centers") {
|
|
SECTION("basics") {
|
|
auto mol = R"CTAB(
|
|
Mrv2219 02112315062D
|
|
|
|
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 O -3.6667 2.5 0 0
|
|
M V30 2 C -2.333 3.27 0 0 CFG=1
|
|
M V30 3 F -0.9993 2.5 0 0
|
|
M V30 4 C -3.103 4.6037 0 0
|
|
M V30 5 N -1.3955 4.4918 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"_ctab;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getNumAtoms() == 5);
|
|
|
|
Chirality::BondWedgingParameters ps;
|
|
ps.wedgeTwoBondsIfPossible = true;
|
|
|
|
{
|
|
RWMol cp(*mol);
|
|
Chirality::wedgeMolBonds(cp);
|
|
CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 0)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 2)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
{
|
|
RWMol cp(*mol);
|
|
Chirality::wedgeMolBonds(cp, nullptr, &ps);
|
|
CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() !=
|
|
cp.getBondBetweenAtoms(1, 3)->getBondDir());
|
|
CHECK(cp.getBondBetweenAtoms(1, 0)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 2)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
{
|
|
// Wedge a second bond after we already wedged a first one
|
|
|
|
RWMol cp(*mol);
|
|
Chirality::wedgeMolBonds(cp);
|
|
CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 0)->getBondDir() == Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 2)->getBondDir() == Bond::BondDir::NONE);
|
|
|
|
Chirality::wedgeMolBonds(cp, nullptr, &ps);
|
|
|
|
REQUIRE(count_wedged_bonds(cp) == 2);
|
|
|
|
CHECK(cp.getBondBetweenAtoms(1, 3)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(cp.getBondBetweenAtoms(1, 4)->getBondDir() !=
|
|
cp.getBondBetweenAtoms(1, 3)->getBondDir());
|
|
}
|
|
{
|
|
// What if the first wedged bond is not our preferred one?
|
|
|
|
for (auto wedgedAtomIdx : {0, 2, 4}) {
|
|
INFO("wedgedAtomIdx: " << wedgedAtomIdx);
|
|
|
|
RWMol cp(*mol);
|
|
REQUIRE(count_wedged_bonds(cp) == 0);
|
|
|
|
auto bond = cp.getBondBetweenAtoms(1, wedgedAtomIdx);
|
|
if (bond->getEndAtomIdx() == 1) {
|
|
// One of the bonds in the input is reversed, make sure the chiral
|
|
// atom is always at the start so that the wedge is valid!
|
|
auto tmp = bond->getBeginAtomIdx();
|
|
bond->setBeginAtomIdx(bond->getEndAtomIdx());
|
|
bond->setEndAtomIdx(tmp);
|
|
}
|
|
|
|
// This probably disagrees with the chirality in some of the test cases,
|
|
// but that's not relevant for this test
|
|
bond->setBondDir(Bond::BondDir::BEGINWEDGE);
|
|
|
|
Chirality::wedgeMolBonds(cp, nullptr, &ps);
|
|
|
|
CHECK(count_wedged_bonds(cp) == 2);
|
|
}
|
|
}
|
|
}
|
|
SECTION(
|
|
"more complex 1, this should only have one wedge for each of the two chiral centers") {
|
|
std::string smi =
|
|
"[H][C@@]12CC(=O)N1[C@@H](C(=O)O)C(C)(C)S2(=O)=O |(-2.78382,0.183015,;-1.38222,-0.351313,;-2.12923,-1.65207,;-0.828466,-2.39908,;-0.436905,-3.84707,;-0.0814577,-1.09832,;1.03095,-0.0920638,;2.49888,-0.400554,;2.96569,-1.82607,;3.50001,0.71647,;0.41769,1.27685,;1.8432,1.74365,;0.102447,2.74335,;-1.07373,1.11662,;-1.07718,2.61662,;-2.56587,1.26998,)|";
|
|
SmilesParserParams spps;
|
|
spps.removeHs = false;
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi, spps)};
|
|
REQUIRE(m);
|
|
Chirality::BondWedgingParameters bwps;
|
|
bwps.wedgeTwoBondsIfPossible = true;
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
|
|
unsigned nWedged = 0;
|
|
for (const auto bond : m->bonds()) {
|
|
if (bond->getBondDir() != Bond::BondDir::NONE) {
|
|
++nWedged;
|
|
}
|
|
}
|
|
CHECK(nWedged == 2);
|
|
}
|
|
SECTION("more complex 2, have two wedges around the chiral center") {
|
|
std::string smi =
|
|
"[H][C@@]12CCCN1C(=O)CN1C(=O)[C@](C)(N)O[C@]12O |(-0.888297,0.626611,;-1.19852,-0.840959,;-1.94707,-2.14084,;-3.41464,-1.83061,;-3.5731,-0.339006,;-2.20347,0.272634,;-1.74154,1.69974,;-2.74648,2.81333,;-0.274666,2.01325,;0.730277,0.899655,;2.23028,0.901335,;3.11059,2.11585,;2.6954,-0.52473,;3.44685,-1.82293,;4.06503,0.0869091,;1.48286,-1.40777,;0.26835,-0.527448,;-0.0418744,-1.99502,)|";
|
|
SmilesParserParams spps;
|
|
spps.removeHs = false;
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi, spps)};
|
|
REQUIRE(m);
|
|
Chirality::BondWedgingParameters bwps;
|
|
bwps.wedgeTwoBondsIfPossible = true;
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
|
|
CHECK(m->getBondWithIdx(12)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(m->getBondWithIdx(13)->getBondDir() != Bond::BondDir::NONE);
|
|
}
|
|
SECTION("another one") {
|
|
auto m =
|
|
"CC[C@@]1(O)C(=O)OCc2c1cc1-c3nc4ccccc4cc3Cn1c2=O |(-2.67178,3.55256,;-2.43493,2.07138,;-3.59925,1.12567,;-4.32841,2.43652,;-5.01681,0.635215,;-6.15033,1.61763,;-5.30084,-0.837648,;-4.16731,-1.82006,;-2.74976,-1.3296,;-2.46573,0.143259,;-1.04818,0.633713,;0.085343,-0.348697,;1.57389,-0.163687,;2.43243,1.06631,;3.92692,0.937796,;4.78546,2.1678,;6.27994,2.03928,;6.91588,0.680758,;6.05734,-0.549244,;4.56286,-0.420725,;3.70432,-1.65073,;2.20983,-1.52221,;1.11432,-2.54683,;-0.198688,-1.82156,;-1.61624,-2.31201,;-1.90027,-3.78488,)|"_smiles;
|
|
REQUIRE(m);
|
|
Chirality::BondWedgingParameters bwps;
|
|
bwps.wedgeTwoBondsIfPossible = true;
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
|
|
CHECK(m->getBondWithIdx(1)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(m->getBondWithIdx(1)->getBeginAtomIdx() == 2);
|
|
CHECK(m->getBondWithIdx(2)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(m->getBondWithIdx(2)->getBeginAtomIdx() == 2);
|
|
}
|
|
SECTION("favor degree 1") {
|
|
auto m =
|
|
"[H][C@@]12CC[C@@](C)(O)[C@H](CC[C@@](C)(O)C=C)[C@@]1(C)CCCC2(C)C |(3.59567,-1.0058,;2.33379,-0.194852,;2.4456,-1.69068,;1.20608,-2.53542,;-0.145252,-1.88434,;-1.63777,-1.73471,;-0.551787,-3.3282,;-0.257063,-0.388514,;-1.60839,0.262569,;-2.84791,-0.582176,;-4.19924,0.068907,;-5.55057,0.71999,;-4.85032,-1.28242,;-3.54816,1.42024,;-4.3929,2.65976,;0.982456,0.456231,;-0.368873,1.10731,;0.870645,1.95206,;2.11016,2.7968,;3.46149,2.14572,;3.5733,0.649893,;5.02699,1.01975,;4.35205,-0.632117,)|"_smiles;
|
|
REQUIRE(m);
|
|
Chirality::BondWedgingParameters bwps;
|
|
bwps.wedgeTwoBondsIfPossible = true;
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer(), &bwps);
|
|
CHECK(m->getBondWithIdx(9)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(m->getBondWithIdx(10)->getBondDir() != Bond::BondDir::NONE);
|
|
CHECK(m->getBondWithIdx(11)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"RDKit Issue #6217: Atoms may get flagged with non-tetrahedral stereo even when it is not allowed",
|
|
"[bug][stereo][non-tetrahedral]") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
AllowNontetrahedralChiralityFixture reset_non_tetrahedral_allowed;
|
|
Chirality::setAllowNontetrahedralChirality(false);
|
|
|
|
auto m = "CS(=O)(=O)O"_smiles;
|
|
REQUIRE(m);
|
|
REQUIRE(m->getNumAtoms() == 5);
|
|
|
|
auto stereoInfo = Chirality::findPotentialStereo(*m);
|
|
CHECK(stereoInfo.size() == 0);
|
|
|
|
auto at = m->getAtomWithIdx(1);
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(at);
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
|
|
REQUIRE(at->getAtomicNum() == 16);
|
|
CHECK(!at->hasProp(common_properties::_ChiralityPossible));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"RDKit Issue #6239: Tri-coordinate atom with implicit + neighbor H atom is found potentially chiral",
|
|
"[bug][stereo]") {
|
|
// Parametrize test to run under legacy and new stereo perception
|
|
const auto legacy_stereo = GENERATE(true, false);
|
|
INFO("Legacy stereo perception == " << legacy_stereo);
|
|
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
|
|
|
|
auto p = SmilesParserParams();
|
|
p.removeHs = false;
|
|
|
|
std::unique_ptr<RWMol> m{SmilesToMol("[H]C(C)CC", p)};
|
|
|
|
REQUIRE(m);
|
|
REQUIRE(m->getNumAtoms() == 5);
|
|
|
|
auto at = m->getAtomWithIdx(1);
|
|
REQUIRE(at->getAtomicNum() == 6);
|
|
REQUIRE(at->getDegree() == 3);
|
|
|
|
CHECK(!at->hasProp(common_properties::_ChiralityPossible));
|
|
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(at));
|
|
|
|
auto stereoInfo = Chirality::findPotentialStereo(*m);
|
|
CHECK(stereoInfo.size() == 0);
|
|
|
|
auto sinfo = Chirality::detail::getStereoInfo(at);
|
|
CHECK(sinfo.type == Chirality::StereoType::Atom_Tetrahedral);
|
|
}
|
|
|
|
TEST_CASE("double bonded N with H should be stereogenic", "[bug][stereo]") {
|
|
SECTION("assign stereo") {
|
|
// Parametrize test to run under legacy and new stereo perception
|
|
const auto legacy_stereo = GENERATE(true, false);
|
|
INFO("Legacy stereo perception == " << legacy_stereo);
|
|
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
|
|
auto m = "[H]/N=C/F"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(1)->getStereo() != Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("find potential stereo") {
|
|
auto m = "[H]/N=C/F"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(Chirality::detail::isBondPotentialStereoBond(m->getBondWithIdx(1)));
|
|
bool cleanIt = false;
|
|
bool flagPossible = true;
|
|
auto si = Chirality::findPotentialStereo(*m, cleanIt, flagPossible);
|
|
CHECK(si.size() == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Issue in GitHub #6473", "[bug][stereo]") {
|
|
constexpr const char *mb = R"CTAB(
|
|
RDKit 2D
|
|
|
|
6 5 0 0 0 0 0 0 0 0999 V2000
|
|
2.0443 0.2759 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7038 2.5963 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.3828 2.5961 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.0444 1.8228 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6359 1.8229 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.3827 -0.4985 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 4 1 0
|
|
4 2 1 0
|
|
4 3 2 0
|
|
2 5 1 0
|
|
6 1 1 0
|
|
M END)CTAB";
|
|
|
|
auto use_legacy_stereo = GENERATE(true, false);
|
|
CAPTURE(use_legacy_stereo);
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(use_legacy_stereo);
|
|
|
|
std::unique_ptr<ROMol> mol(MolBlockToMol(mb));
|
|
REQUIRE(mol);
|
|
|
|
// Removal of the stereogenic H will cause loss of stereo information
|
|
// on the imine double bond, and although the bond is still detected as
|
|
// potentially stereo, it will be reverted to "unspecified"
|
|
auto bond = mol->getBondWithIdx(2);
|
|
REQUIRE(bond->getBondType() == Bond::BondType::DOUBLE);
|
|
CHECK(Chirality::detail::isBondPotentialStereoBond(bond));
|
|
CHECK(bond->getStereo() == Bond::BondStereo::STEREONONE);
|
|
|
|
if (!use_legacy_stereo) {
|
|
auto sinfo = Chirality::detail::getStereoInfo(bond);
|
|
REQUIRE(sinfo.type == Chirality::StereoType::Bond_Double);
|
|
CHECK(sinfo.specified == Chirality::StereoSpecified::Unspecified);
|
|
REQUIRE(sinfo.controllingAtoms.size() == 4);
|
|
CHECK(sinfo.controllingAtoms[0] == 0);
|
|
CHECK(sinfo.controllingAtoms[1] == 1);
|
|
CHECK(sinfo.controllingAtoms[2] == Atom::NOATOM);
|
|
CHECK(sinfo.controllingAtoms[3] == Atom::NOATOM);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("GitHub Issue #6640", "[bug][stereo]") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
auto p = SmilesParserParams();
|
|
p.sanitize = false;
|
|
p.removeHs = false;
|
|
std::string smiles{"NC1=NC(=N)N=C(N)C1C"};
|
|
std::unique_ptr<RWMol> mol(SmilesToMol(smiles, p));
|
|
REQUIRE(mol);
|
|
|
|
MolOps::removeHs(*mol);
|
|
|
|
auto cleanIt = true;
|
|
auto force = true;
|
|
auto flagPossibleStereoCenters = true;
|
|
MolOps::assignStereochemistry(*mol, cleanIt, force,
|
|
flagPossibleStereoCenters);
|
|
}
|
|
|
|
TEST_CASE("zero bond-length chirality cases") {
|
|
SECTION("basics") {
|
|
{
|
|
auto m = R"CTAB(derived from CHEMBL3183068
|
|
Mrv2211 07202306222D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 10 10 0 0 1
|
|
M V30 BEGIN ATOM
|
|
M V30 1 N 3.231 0 0 0
|
|
M V30 2 C 4.0137 -1.3378 0 0
|
|
M V30 3 C 2.6757 -2.1004 0 0
|
|
M V30 4 C 2.6757 -3.6389 0 0
|
|
M V30 5 C 1.3378 -1.3378 0 0
|
|
M V30 6 C 5.4672 -0.99 0 0
|
|
M V30 7 C 1.3378 -4.4216 0 0
|
|
M V30 8 C 0 -2.1205 0 0
|
|
M V30 9 C 0 -3.659 0 0
|
|
M V30 10 F 4.0137 -1.3378 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=3
|
|
M V30 2 1 2 3
|
|
M V30 3 1 2 6
|
|
M V30 4 1 2 10
|
|
M V30 5 2 3 4
|
|
M V30 6 1 3 5
|
|
M V30 7 1 4 7
|
|
M V30 8 2 5 8
|
|
M V30 9 2 7 9
|
|
M V30 10 1 8 9
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
{
|
|
auto m = R"CTAB(derived from CHEMBL3183068
|
|
Mrv2211 07202306222D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 10 10 0 0 1
|
|
M V30 BEGIN ATOM
|
|
M V30 1 N 3.231 0 0 0
|
|
M V30 2 C 4.0137 -1.3378 0 0
|
|
M V30 3 C 2.6757 -2.1004 0 0
|
|
M V30 4 C 2.6757 -3.6389 0 0
|
|
M V30 5 C 1.3378 -1.3378 0 0
|
|
M V30 6 C 4.0137 -1.3378 0 0
|
|
M V30 7 C 1.3378 -4.4216 0 0
|
|
M V30 8 C 0 -2.1205 0 0
|
|
M V30 9 C 0 -3.659 0 0
|
|
M V30 10 F 4.5135 -2.6451 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=3
|
|
M V30 2 1 2 3
|
|
M V30 3 1 2 6
|
|
M V30 4 1 2 10
|
|
M V30 5 2 3 4
|
|
M V30 6 1 3 5
|
|
M V30 7 1 4 7
|
|
M V30 8 2 5 8
|
|
M V30 9 2 7 9
|
|
M V30 10 1 8 9
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("t-shaped chirality cases") {
|
|
SECTION("ChEMBL example") {
|
|
{
|
|
auto m = R"CTAB(CHEMBL3183068
|
|
RDKit 2D
|
|
|
|
11 11 0 0 1 0 0 0 0 0999 V2000
|
|
1.7309 0.0000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.1502 -0.7167 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4334 -1.1252 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4334 -1.9494 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7167 -0.7167 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.8669 -0.2974 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7167 -2.3687 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -1.1360 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -1.9602 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.5836 -0.7060 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.5694 -1.4334 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 6
|
|
2 3 1 0
|
|
2 6 1 0
|
|
2 11 1 0
|
|
3 4 2 0
|
|
3 5 1 0
|
|
4 7 1 0
|
|
5 8 2 0
|
|
6 10 1 0
|
|
7 9 2 0
|
|
8 9 1 0
|
|
M END
|
|
> <chembl_id>
|
|
CHEMBL3183068
|
|
|
|
> <chembl_pref_name>
|
|
None
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
{
|
|
auto m = R"CTAB(CHEMBL3183068 (with an H removed)
|
|
RDKit 2D
|
|
|
|
10 10 0 0 1 0 0 0 0 0999 V2000
|
|
1.7309 0.0000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.1502 -0.7167 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4334 -1.1252 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4334 -1.9494 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7167 -0.7167 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.8669 -0.2974 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7167 -2.3687 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -1.1360 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -1.9602 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.5836 -0.7060 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 6
|
|
2 3 1 0
|
|
2 6 1 0
|
|
3 4 2 0
|
|
3 5 1 0
|
|
4 7 1 0
|
|
5 8 2 0
|
|
6 10 1 0
|
|
7 9 2 0
|
|
8 9 1 0
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
}
|
|
|
|
SECTION("four-coordinate") {
|
|
{
|
|
auto m = R"CTAB(
|
|
Mrv2211 07202306492D
|
|
|
|
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 N -3.3332 0.9919 0 0
|
|
M V30 2 C -2.5555 -0.3373 0 0
|
|
M V30 3 O -3.885 -1.095 0 0
|
|
M V30 4 C -1.2263 0.4404 0 0
|
|
M V30 5 F -1.7854 -1.6709 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=3
|
|
M V30 2 1 2 3
|
|
M V30 3 1 2 4
|
|
M V30 4 1 2 5
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
{
|
|
auto m = R"CTAB(
|
|
Mrv2211 07202306492D
|
|
|
|
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 N -3.3332 0.9919 0 0
|
|
M V30 2 C -2.5555 -0.3373 0 0
|
|
M V30 3 O -3.885 -1.095 0 0
|
|
M V30 4 C -1.2263 0.4404 0 0
|
|
M V30 5 F -1.7854 -1.6709 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1
|
|
M V30 2 1 2 3
|
|
M V30 3 1 2 4
|
|
M V30 4 1 2 5 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_CCW);
|
|
}
|
|
{
|
|
auto m = R"CTAB(
|
|
Mrv2211 07202306492D
|
|
|
|
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 N -3.3332 0.9919 0 0
|
|
M V30 2 C -2.5555 -0.3373 0 0
|
|
M V30 3 O -3.885 -1.095 0 0
|
|
M V30 4 C -1.2263 0.4404 0 0
|
|
M V30 5 F -1.7854 -1.6709 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1
|
|
M V30 2 1 2 3 CFG=1
|
|
M V30 3 1 2 4
|
|
M V30 4 1 2 5
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("almost linear, degree 4") {
|
|
SECTION("from chembl 1") {
|
|
auto m = R"CTAB(CHEMBL3680147
|
|
RDKit 2D
|
|
|
|
15 16 0 0 1 0 0 0 0 0999 V2000
|
|
3.8912 -4.9570 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.8933 -3.7570 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.5951 -3.0039 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.5973 -1.5031 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.2990 -0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.2990 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.2990 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.2990 -0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
5.2073 -4.4014 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
6.2131 -3.2886 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
5.4656 -1.9881 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.9978 -2.2972 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
5.9557 -0.8928 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 1
|
|
2 3 1 0
|
|
3 4 1 0
|
|
4 5 1 0
|
|
5 6 2 0
|
|
6 7 1 0
|
|
7 8 2 0
|
|
8 9 1 0
|
|
9 10 2 0
|
|
10 5 1 0
|
|
2 11 1 0
|
|
11 12 1 0
|
|
12 13 1 0
|
|
13 14 2 0
|
|
14 2 1 0
|
|
13 15 1 0
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
SECTION("from chembl 2") {
|
|
auto m = R"CTAB(CHEMBL76346
|
|
RDKit 2D
|
|
|
|
16 17 0 0 1 0 0 0 0 0999 V2000
|
|
3.4292 -0.4250 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.7167 -0.0125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.7167 0.8125 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.4292 -1.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
4.1500 -0.0125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.7667 -1.7292 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
4.1500 0.8208 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.4292 1.2333 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.0250 -2.5167 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.8500 -2.5167 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.0042 -0.4250 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
4.1042 -1.7292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.4250 2.0583 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.2250 -2.7292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.2375 -3.3125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.0042 -3.5250 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 0
|
|
3 2 1 0
|
|
4 1 1 0
|
|
5 1 1 0
|
|
6 4 1 0
|
|
7 5 2 0
|
|
8 7 1 0
|
|
9 6 1 0
|
|
10 12 1 0
|
|
11 2 2 0
|
|
12 4 1 0
|
|
13 8 1 0
|
|
9 14 1 6
|
|
15 9 1 0
|
|
16 14 1 0
|
|
3 8 2 0
|
|
10 9 1 0
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(8)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
SECTION("from chembl 3") {
|
|
auto m = R"CTAB(CHEMBL3577363
|
|
RDKit 2D
|
|
|
|
15 16 0 0 0 0 0 0 0 0999 V2000
|
|
-2.5989 1.5003 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.3863 2.3198 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.8514 3.7459 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.3514 3.7442 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.8133 2.3171 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.2990 -0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.2990 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.2990 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.2990 -0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 2.7000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.3383 -1.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.6369 0.8981 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.1471 4.7175 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 2 1 0
|
|
2 3 1 0
|
|
3 4 1 0
|
|
4 5 1 0
|
|
5 1 1 0
|
|
6 7 2 0
|
|
7 8 1 0
|
|
8 9 2 0
|
|
9 10 1 0
|
|
10 11 2 0
|
|
11 6 1 0
|
|
8 12 1 0
|
|
9 1 1 0
|
|
6 13 1 0
|
|
1 14 1 6
|
|
3 15 2 0
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
SECTION("from chembl 4") {
|
|
auto m =
|
|
R"CTAB(derived from CHEMBL2373651. This was wrong in the RDKit implementation
|
|
Mrv2211 07212313282D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 7 7 0 0 1
|
|
M V30 BEGIN ATOM
|
|
M V30 1 O -7.4486 0.4751 0 0
|
|
M V30 2 C -6.1148 -0.2949 0 0
|
|
M V30 3 C -6.1148 1.2451 0 0
|
|
M V30 4 C -4.7811 -1.0649 0 0
|
|
M V30 5 C -4.7811 2.0151 0 0
|
|
M V30 6 H -6.1148 1.2451 0 0
|
|
M V30 7 H -6.1148 -0.2949 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1
|
|
M V30 2 1 3 1
|
|
M V30 3 1 2 3
|
|
M V30 4 1 2 4
|
|
M V30 5 1 3 5
|
|
M V30 6 1 3 6 CFG=1
|
|
M V30 7 1 2 7 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(2)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
}
|
|
SECTION("cut from CHEMBL4578507") {
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 9 8 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 10.694361 -35.424753 0.000000 0
|
|
M V30 2 C 9.463880 -34.700253 0.000000 0
|
|
M V30 3 C 8.217865 -35.405903 0.000000 0
|
|
M V30 4 C 8.207044 -36.840940 0.000000 0
|
|
M V30 5 N 9.449393 -37.570327 0.000000 0
|
|
M V30 6 C 6.965044 -36.121153 0.000000 0
|
|
M V30 7 O 8.215247 -33.985178 0.000000 0
|
|
M V30 8 H 6.885805 -37.382701 0.000000 0
|
|
M V30 9 H 9.169786 -33.305883 0.000000 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 4 3
|
|
M V30 4 1 4 5
|
|
M V30 5 1 4 6
|
|
M V30 6 1 2 7
|
|
M V30 7 1 4 8 CFG=3
|
|
M V30 8 1 2 9 CFG=3
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
$$$$)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
|
|
SECTION("derived from CHEMBL4578507") {
|
|
auto m = R"CTAB(CHEMBL4578507
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 17 19 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 6.828300 -21.526500 0.000000 0
|
|
M V30 2 C 7.542200 -21.113800 0.000000 0
|
|
M V30 3 C 7.537900 -20.291100 0.000000 0
|
|
M V30 4 C 6.829700 -19.884700 0.000000 0
|
|
M V30 5 C 6.121000 -21.114600 0.000000 0
|
|
M V30 6 C 6.127300 -20.296500 0.000000 0
|
|
M V30 7 C 5.422300 -19.881400 0.000000 0
|
|
M V30 8 C 4.708400 -20.285700 0.000000 0
|
|
M V30 9 C 4.702200 -21.107900 0.000000 0
|
|
M V30 10 N 5.414000 -21.525800 0.000000 0
|
|
M V30 11 C 3.990600 -20.695500 0.000000 0
|
|
M V30 12 O 4.706900 -19.471700 0.000000 0
|
|
M V30 13 C 3.988500 -19.876800 0.000000 0
|
|
M V30 14 O 3.284900 -21.104500 0.000000 0
|
|
M V30 15 F 8.242300 -19.879800 0.000000 0
|
|
M V30 16 H 3.945200 -21.418300 0.000000 0 MASS=2
|
|
M V30 17 H 5.253800 -19.082500 0.000000 0 MASS=2
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 2 5 1
|
|
M V30 2 1 1 2
|
|
M V30 3 2 2 3
|
|
M V30 4 1 3 4
|
|
M V30 5 2 4 6
|
|
M V30 6 1 5 6
|
|
M V30 7 1 5 10
|
|
M V30 8 1 6 7
|
|
M V30 9 1 7 8
|
|
M V30 10 1 9 8
|
|
M V30 11 1 9 10
|
|
M V30 12 1 9 11
|
|
M V30 13 1 7 12
|
|
M V30 14 1 11 13
|
|
M V30 15 1 13 12
|
|
M V30 16 1 11 14 CFG=3
|
|
M V30 17 1 3 15
|
|
M V30 18 1 9 16 CFG=3
|
|
M V30 19 1 7 17 CFG=3
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(6)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(8)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(10)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
|
|
SECTION("derived from CHEMBL123021") {
|
|
auto m = R"CTAB(CHEMBL123021
|
|
RDKit 2D
|
|
|
|
16 17 0 0 1 0 0 0 0 0999 V2000
|
|
-2.8208 -0.5792 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.8208 0.2458 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.5375 -0.1542 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.4083 -0.5792 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.1125 -0.9917 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.4000 0.2458 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.1125 0.6583 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6833 0.6583 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.1083 -1.8167 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.1125 1.4833 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6833 1.4833 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6958 -0.9917 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0250 -0.5875 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0375 1.8958 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.3161 1.1145 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.3141 0.2829 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 0
|
|
1 3 1 0
|
|
4 5 1 0
|
|
5 1 1 0
|
|
6 4 2 0
|
|
7 2 1 0
|
|
8 6 1 0
|
|
9 5 2 0
|
|
10 7 2 0
|
|
11 8 2 0
|
|
12 4 1 0
|
|
13 12 1 0
|
|
14 11 1 0
|
|
2 3 1 0
|
|
7 6 1 0
|
|
2 15 1 6
|
|
1 16 1 6
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
|
|
SECTION("derived from CHEMBL85809") {
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 9 9 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 N 7.185783 -5.677981 0.000000 0
|
|
M V30 2 C 7.207427 -7.106480 0.000000 0
|
|
M V30 3 O 9.032790 -6.219079 0.000000 0
|
|
M V30 4 O 10.172645 -8.527707 0.000000 0
|
|
M V30 5 C 10.886895 -7.106480 0.000000 0
|
|
M V30 6 C 7.900033 -8.527707 0.000000 0
|
|
M V30 7 C 10.865251 -5.677981 0.000000 0
|
|
M V30 8 H 7.185783 -8.534979 0.000000 0 MASS=2
|
|
M V30 9 H 10.865251 -8.534979 0.000000 0 MASS=2
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=1
|
|
M V30 2 1 3 2
|
|
M V30 3 1 4 6
|
|
M V30 4 1 5 3
|
|
M V30 5 1 6 2
|
|
M V30 6 1 5 7 CFG=1
|
|
M V30 7 1 2 8 CFG=3
|
|
M V30 8 1 5 9 CFG=3
|
|
M V30 9 1 4 5
|
|
M V30 END BOND
|
|
M V30 BEGIN COLLECTION
|
|
M V30 MDLV30/STEABS ATOMS=(2 2 5)
|
|
M V30 END COLLECTION
|
|
M V30 END CTAB
|
|
M END
|
|
$$$$
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
CHECK(m->getAtomWithIdx(4)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
}
|
|
|
|
SECTION("derived from CHEMBL2333552") {
|
|
auto m = R"CTAB(blah
|
|
RDKit 2D
|
|
|
|
18 20 0 0 0 0 0 0 0 0999 V2000
|
|
35.6738 -9.2984 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.9598 -8.8850 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.9588 -9.7100 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
32.1164 -7.2274 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
32.1164 -8.0541 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
32.8299 -8.4634 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
32.8299 -6.8099 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
33.5434 -7.2274 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
33.5399 -8.0541 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.9720 -7.2335 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.2572 -6.8149 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
31.3992 -6.8162 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
32.8308 -9.2901 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
33.5319 -8.8767 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.9684 -8.0602 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.2507 -8.4688 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
35.7656 -8.2712 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34.2458 -7.6408 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 0
|
|
3 2 1 0
|
|
4 5 1 0
|
|
4 7 1 0
|
|
5 6 1 0
|
|
6 9 1 0
|
|
8 7 2 0
|
|
8 9 1 0
|
|
8 11 1 0
|
|
9 16 1 0
|
|
15 10 1 0
|
|
10 11 1 0
|
|
4 12 2 0
|
|
6 13 1 6
|
|
9 14 1 6
|
|
16 15 1 0
|
|
2 16 1 0
|
|
15 2 1 0
|
|
15 17 1 6
|
|
16 18 1 6
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(5)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(8)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
CHECK(m->getAtomWithIdx(14)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(15)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
}
|
|
SECTION("CHEMBL94022") {
|
|
auto m = R"CTAB(CHEMBL94022
|
|
RDKit 2D
|
|
|
|
16 18 0 0 1 0 0 0 0 0999 V2000
|
|
2.0917 -2.6875 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.2667 -2.7042 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.6917 -3.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.6750 -3.2667 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.4792 -4.0667 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.5000 -3.2042 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.5500 -2.2917 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.1792 -4.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.8125 -3.9667 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.1708 -2.7042 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.5500 -1.4667 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8833 -2.2917 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.1708 -1.0542 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8833 -1.4667 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.6792 -3.2917 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.7917 -2.2542 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 1 1 0
|
|
3 1 1 0
|
|
1 4 1 1
|
|
5 4 2 0
|
|
6 4 1 0
|
|
7 2 1 0
|
|
8 5 1 0
|
|
9 6 1 0
|
|
10 7 2 0
|
|
11 7 1 0
|
|
12 10 1 0
|
|
13 11 2 0
|
|
14 13 1 0
|
|
2 15 1 1
|
|
1 16 1 6
|
|
3 2 1 0
|
|
8 9 1 0
|
|
14 12 2 0
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(0)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
}
|
|
SECTION("overlapping neighbors") {
|
|
auto m = R"CTAB(derived from CHEMBL3752539
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 14 15 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -1.237143 -0.714286 0.000000 0
|
|
M V30 2 C -1.237143 0.714286 0.000000 0
|
|
M V30 3 C -2.474381 1.428571 0.000000 0
|
|
M V30 4 C -3.711524 0.714286 0.000000 0
|
|
M V30 5 C -2.474381 -1.428571 0.000000 0
|
|
M V30 6 C -3.711524 -0.714286 0.000000 0
|
|
M V30 7 O -2.474381 -2.857143 0.000000 0
|
|
M V30 8 H -4.536286 -0.238095 0.000000 0
|
|
M V30 9 H -1.649524 -1.904762 0.000000 0
|
|
M V30 10 C -4.948762 -1.428571 0.000000 0
|
|
M V30 11 C -2.474381 -1.428571 0.000000 0
|
|
M V30 12 C -3.711524 0.714286 0.000000 0
|
|
M V30 13 O -3.711524 -0.714286 0.000000 0
|
|
M V30 14 H -2.474381 2.380952 0.000000 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 2
|
|
M V30 2 1 1 5
|
|
M V30 3 1 2 3
|
|
M V30 4 1 3 4
|
|
M V30 5 1 4 6
|
|
M V30 6 1 5 6
|
|
M V30 7 1 5 7
|
|
M V30 8 1 6 10
|
|
M V30 9 1 6 8 CFG=1
|
|
M V30 10 1 5 9 CFG=3
|
|
M V30 11 1 1 11 CFG=3
|
|
M V30 12 1 3 12
|
|
M V30 13 1 12 13
|
|
M V30 14 1 11 13
|
|
M V30 15 1 3 14 CFG=1
|
|
M V30 END BOND
|
|
M V30 BEGIN COLLECTION
|
|
M V30 MDLV30/STEABS ATOMS=(1 6)
|
|
M V30 MDLV30/STERAC1 ATOMS=(3 1 3 5)
|
|
M V30 END COLLECTION
|
|
M V30 END CTAB
|
|
M END
|
|
$$$$
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(2)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(0)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
SECTION("bond atoms overlapping central atom at the end of wedge bonds") {
|
|
auto m = R"CTAB(CHEMBL3612237
|
|
RDKit 2D
|
|
|
|
14 15 0 0 0 0 0 0 0 0999 V2000
|
|
-0.6828 -1.6239 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6828 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.6090 0.5905 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.9007 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.9007 -1.6239 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.6090 -2.3805 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.9007 1.3471 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6828 1.3471 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.6090 2.0668 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6319 1.4849 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.5072 2.6784 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.7280 0.9964 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.6828 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.9007 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 2 1 0
|
|
1 6 1 0
|
|
2 3 1 0
|
|
3 4 1 0
|
|
4 5 1 0
|
|
5 6 1 0
|
|
4 7 1 0
|
|
2 8 1 0
|
|
8 9 1 0
|
|
7 9 1 0
|
|
3 10 1 1
|
|
10 11 1 0
|
|
10 12 2 0
|
|
2 13 1 1
|
|
4 14 1 1
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
// if the bond is wedged, then we should have chirality even if the bonded
|
|
// atom overlaps the central atom
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getAtomWithIdx(2)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("github #6931: atom maps influencing chirality perception") {
|
|
SECTION("basics") {
|
|
auto m = "[CH3:1]C([CH3:2])(O)F"_smiles;
|
|
REQUIRE(m);
|
|
bool cleanIt = true;
|
|
bool force = true;
|
|
bool flagPossibleStereoCenters = true;
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
MolOps::assignStereochemistry(*m, cleanIt, force,
|
|
flagPossibleStereoCenters);
|
|
CHECK(
|
|
!m->getAtomWithIdx(1)->hasProp(common_properties::_ChiralityPossible));
|
|
}
|
|
SECTION(
|
|
"github #8391: atom maps on dummy atoms do influence chirality perception") {
|
|
auto m = "[*:1]C([*:2])(O)F"_smiles;
|
|
REQUIRE(m);
|
|
bool cleanIt = true;
|
|
bool force = true;
|
|
bool flagPossibleStereoCenters = true;
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
MolOps::assignStereochemistry(*m, cleanIt, force,
|
|
flagPossibleStereoCenters);
|
|
CHECK(m->getAtomWithIdx(1)->hasProp(common_properties::_ChiralityPossible));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github Issue #6981: Parsing a Mol leaks the \"_needsDetectBondStereo\" property",
|
|
"[bug][stereo]") {
|
|
// Parametrize test to run under legacy and new stereo perception
|
|
const auto legacy_stereo = GENERATE(true, false);
|
|
INFO("Legacy stereo perception == " << legacy_stereo);
|
|
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
|
|
|
|
auto m = R"CTAB(
|
|
Mrv2311 12122315472D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -9.2083 1.8333 0 0
|
|
M V30 2 C -8.0639 0.8029 0 0
|
|
M V30 3 C -6.5239 0.8029 0 0
|
|
M V30 4 C -5.7539 -0.5308 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=2
|
|
M V30 2 2 2 3
|
|
M V30 3 1 3 4
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
|
|
REQUIRE(m);
|
|
REQUIRE(m->getNumAtoms() == 4);
|
|
|
|
CHECK(m->hasProp("_needsDetectBondStereo") == false);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github Issue #7076: new stereo code not properly handling crossed double bonds") {
|
|
// Parametrize test to run under legacy and new stereo perception
|
|
SECTION("second part") {
|
|
const auto legacy_stereo = GENERATE(true, false);
|
|
INFO("Legacy stereo perception == " << legacy_stereo);
|
|
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
|
|
|
|
auto m = R"CTAB(
|
|
Mrv2211 01252410552D
|
|
|
|
10 9 0 0 0 0 999 V2000
|
|
0.0000 -1.4364 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7108 -3.4884 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -3.8988 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8208 -1.4364 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -2.2572 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.7108 -2.6676 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4217 -2.2572 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -4.7196 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.2439 -5.4378 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.7108 -5.1300 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 4 1 0 0 0 0
|
|
1 5 2 0 0 0 0
|
|
2 3 1 0 0 0 0
|
|
2 6 2 3 0 0 0
|
|
3 8 2 0 0 0 0
|
|
5 6 1 0 0 0 0
|
|
6 7 1 0 0 0 0
|
|
8 9 1 0 0 0 0
|
|
8 10 1 0 0 0 0
|
|
M END)CTAB"_ctab;
|
|
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
}
|
|
SECTION("original") {
|
|
std::string ctab = R"CTAB(7630532
|
|
RDKit 2D
|
|
|
|
37 41 0 0 0 0 0 0 0 0999 V2000
|
|
0.0000 -1.7500 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.8660 -4.2500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -4.7500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7500 -1.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.0000 -1.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -2.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8675 0.4975 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.2475 -0.8825 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.4975 -2.6175 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.8675 0.4975 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.2475 -2.6175 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.4975 -0.8825 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.8660 -3.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8675 1.5027 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.2527 -0.8825 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.5027 -2.6175 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.8675 1.5027 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.2527 -2.6175 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.5027 -0.8825 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.0000 2.0104 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.7604 -1.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-3.0104 -1.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.7321 -2.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.0000 -5.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.5155 -6.6250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8660 -6.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.3801 -6.1225 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.8631 -7.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.5126 -7.6250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.7306 -5.7475 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.2507 -6.6251 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-1.7337 -7.7526 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2.3832 -8.1276 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.6012 -6.2501 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3.2566 -7.6302 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-2.6071 -7.2552 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 4 1 0
|
|
1 5 1 0
|
|
1 6 1 0
|
|
1 7 2 0
|
|
2 3 1 0
|
|
2 14 2 3
|
|
3 25 2 0
|
|
4 8 2 0
|
|
4 11 1 0
|
|
5 9 2 0
|
|
5 12 1 0
|
|
6 10 2 0
|
|
6 13 1 0
|
|
7 14 1 0
|
|
8 15 1 0
|
|
9 16 1 0
|
|
10 17 1 0
|
|
11 18 2 0
|
|
12 19 2 0
|
|
13 20 2 0
|
|
14 24 1 0
|
|
15 21 2 0
|
|
16 22 2 0
|
|
17 23 2 0
|
|
18 21 1 0
|
|
19 22 1 0
|
|
20 23 1 0
|
|
25 26 1 0
|
|
25 27 1 0
|
|
26 28 2 0
|
|
26 30 1 0
|
|
27 29 2 0
|
|
27 31 1 0
|
|
28 32 1 0
|
|
29 33 1 0
|
|
30 34 2 0
|
|
31 35 2 0
|
|
32 36 2 0
|
|
33 37 2 0
|
|
34 36 1 0
|
|
35 37 1 0
|
|
M END)CTAB";
|
|
const auto legacy_stereo = GENERATE(true, false);
|
|
INFO("Legacy stereo perception == " << legacy_stereo);
|
|
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(legacy_stereo);
|
|
{
|
|
// normal file parsing
|
|
auto m = std::unique_ptr<RWMol>(MolBlockToMol(ctab));
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
CHECK(m->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
{
|
|
// no sanitization during parsing
|
|
bool sanitize = false;
|
|
bool removeHs = false;
|
|
auto m = std::unique_ptr<RWMol>(MolBlockToMol(ctab, sanitize, removeHs));
|
|
REQUIRE(m);
|
|
MolOps::sanitizeMol(*m);
|
|
bool cleanIt = true;
|
|
bool force = true;
|
|
MolOps::assignStereochemistry(*m, cleanIt, force);
|
|
CHECK(m->getBondWithIdx(5)->getStereo() == Bond::BondStereo::STEREOANY);
|
|
CHECK(m->getBondWithIdx(6)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github #3369: support new CIP code and StereoGroups in addStereoAnnotations()") {
|
|
auto m1 =
|
|
"C[C@@H]1N[C@H](C)[C@@H]([C@H](C)[C@@H]1C)C1[C@@H](C)O[C@@H](C)[C@@H](C)[C@H]1C/C=C/C |a:5,o1:1,8,o2:14,16,&1:18,&2:3,6,r|"_smiles;
|
|
REQUIRE(m1);
|
|
SECTION("defaults") {
|
|
ROMol m2(*m1);
|
|
Chirality::addStereoAnnotations(m2);
|
|
|
|
std::string txt;
|
|
CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "abs (S)");
|
|
CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "and2");
|
|
}
|
|
SECTION("double bonds") {
|
|
ROMol m2(*m1);
|
|
REQUIRE(m2.getBondBetweenAtoms(20, 21));
|
|
m2.getBondBetweenAtoms(20, 21)->setStereo(Bond::BondStereo::STEREOTRANS);
|
|
// initially no label is assigned since we have TRANS
|
|
Chirality::addStereoAnnotations(m2);
|
|
CHECK(
|
|
!m2.getBondBetweenAtoms(20, 21)->hasProp(common_properties::bondNote));
|
|
|
|
CIPLabeler::assignCIPLabels(m2);
|
|
std::string txt;
|
|
CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
|
|
common_properties::_CIPCode, txt));
|
|
CHECK(txt == "E");
|
|
Chirality::addStereoAnnotations(m2);
|
|
CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
|
|
common_properties::bondNote, txt));
|
|
CHECK(txt == "(E)");
|
|
}
|
|
|
|
SECTION("custom labels") {
|
|
ROMol m2(*m1);
|
|
CIPLabeler::assignCIPLabels(m2);
|
|
|
|
std::string absLabel = "abs [{cip}]";
|
|
std::string orLabel = "o{id} ({cip})";
|
|
std::string andLabel = "&{id} ({cip})";
|
|
std::string cipLabel = "[{cip}]";
|
|
std::string bondLabel = "[{cip}]";
|
|
Chirality::addStereoAnnotations(m2, absLabel, orLabel, andLabel, cipLabel,
|
|
bondLabel);
|
|
|
|
std::string txt;
|
|
|
|
CHECK(m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "abs [S]");
|
|
|
|
CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "&2 (R)");
|
|
|
|
CHECK(m2.getAtomWithIdx(1)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "o1 (S)");
|
|
|
|
CHECK(m2.getAtomWithIdx(11)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "[R]");
|
|
|
|
CHECK(m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
|
|
common_properties::bondNote, txt));
|
|
CHECK(txt == "[E]");
|
|
}
|
|
|
|
SECTION("empty labels") {
|
|
ROMol m2(*m1);
|
|
CIPLabeler::assignCIPLabels(m2);
|
|
|
|
std::string absLabel = "";
|
|
std::string orLabel = "o{id} ({cip})";
|
|
std::string andLabel = "&{id} ({cip})";
|
|
std::string cipLabel = "[{cip}]";
|
|
std::string bondLabel = "";
|
|
Chirality::addStereoAnnotations(m2, absLabel, orLabel, andLabel, cipLabel,
|
|
bondLabel);
|
|
|
|
std::string txt;
|
|
|
|
CHECK(!m2.getAtomWithIdx(5)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
|
|
CHECK(m2.getAtomWithIdx(3)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "&2 (R)");
|
|
|
|
CHECK(m2.getAtomWithIdx(1)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "o1 (S)");
|
|
|
|
CHECK(m2.getAtomWithIdx(11)->getPropIfPresent(common_properties::atomNote,
|
|
txt));
|
|
CHECK(txt == "[R]");
|
|
|
|
CHECK(!m2.getBondBetweenAtoms(20, 21)->getPropIfPresent(
|
|
common_properties::bondNote, txt));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("do not wedge bonds to attachment points") {
|
|
SECTION("basics") {
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 0.000000 0.000000 0.000000 0 ATTCHPT=1
|
|
M V30 2 F -1.299038 0.750000 0.000000 0
|
|
M V30 3 Cl -0.000000 -1.500000 0.000000 0
|
|
M V30 4 O 1.299038 0.750000 0.000000 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 2
|
|
M V30 2 1 1 3
|
|
M V30 3 1 1 4 CFG=1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getNumAtoms() == 4);
|
|
MolOps::expandAttachmentPoints(*m);
|
|
CHECK(m->getNumAtoms() == 5);
|
|
CHECK(m->getAtomWithIdx(4)->getTotalValence() == 1);
|
|
Chirality::wedgeMolBonds(*m);
|
|
CHECK(m->getBondBetweenAtoms(0, 4)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
SECTION("cage") {
|
|
auto m = R"CTAB(
|
|
Mrv2305 03052406362D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 8 9 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -3.125 2.5817 0 0 CFG=1
|
|
M V30 2 C -4.4587 1.8117 0 0
|
|
M V30 3 C -4.4587 0.2716 0 0
|
|
M V30 4 C -3.125 -0.4984 0 0
|
|
M V30 5 C -1.7913 0.2716 0 0
|
|
M V30 6 N -1.7913 1.8117 0 0
|
|
M V30 7 O -2.5357 1.1589 0 0
|
|
M V30 8 * -3.125 4.1217 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 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 5 6
|
|
M V30 6 1 1 7
|
|
M V30 7 1 7 4
|
|
M V30 8 1 1 8
|
|
M V30 9 1 1 6 CFG=1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
m->getAtomWithIdx(7)->setProp(common_properties::_fromAttachPoint, 1);
|
|
Chirality::wedgeMolBonds(*m);
|
|
CHECK(m->getBondBetweenAtoms(0, 7)->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github #7203: Remove invalid stereo groups on MolOps::assignStereochemistry") {
|
|
SECTION("single-atom groups") {
|
|
for (auto use_legacy : {false, true}) {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(use_legacy);
|
|
INFO(use_legacy);
|
|
auto m = "C[C@H](N)[C@@H](C)C |o1:1,o2:3|"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getStereoGroups().size() == 1);
|
|
}
|
|
}
|
|
SECTION("two-atom groups") {
|
|
for (auto use_legacy : {false, true}) {
|
|
Chirality::setUseLegacyStereoPerception(use_legacy);
|
|
INFO(use_legacy);
|
|
{ // no removal necessary
|
|
auto m = "C[C@H](N)[C@@H](F)C |o1:1,3|"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
CHECK(m->getStereoGroups().size() == 1);
|
|
CHECK(m->getStereoGroups()[0].getAtoms().size() == 2);
|
|
}
|
|
{ // removal
|
|
auto m = "C[C@H](N)[C@@H](C)C |o1:1,3|"_smiles;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(m->getAtomWithIdx(3)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getStereoGroups().size() == 1);
|
|
CHECK(m->getStereoGroups()[0].getAtoms().size() == 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"GitHub Issue #7346: Trigonal Pyramid Carbon may or not have a parity depending on atom ordering",
|
|
"[bug]") {
|
|
// First atom not in the same plane as the rest
|
|
const auto m0 = R"CTAB(
|
|
3D
|
|
|
|
5 4 0 0 1 0 999 V2000
|
|
-0.2052 0.0804 -0.7454 F 0 0 0 0 0 0
|
|
-0.5696 0.2231 -2.0688 C 0 0 0 0 0 0
|
|
-2.2748 -0.0294 -1.1593 Br 0 0 0 0 0 0
|
|
0.2659 -1.3655 -2.0764 Cl 0 0 0 0 0 0
|
|
-0.1867 1.1526 -2.4961 D 0 0 0 0 0 0
|
|
1 2 1 0 0 0
|
|
2 3 1 0 0 0
|
|
2 4 1 0 0 0
|
|
2 5 1 0 0 0
|
|
M END)CTAB"_ctab;
|
|
|
|
// All atoms in the same plane except the rest
|
|
const auto m1 = R"CTAB(
|
|
3D
|
|
|
|
5 4 0 0 1 0 999 V2000
|
|
-0.1867 1.1526 -2.4961 D 0 0 0 0 0 0
|
|
-0.5696 0.2231 -2.0688 C 0 0 0 0 0 0
|
|
-2.2748 -0.0294 -1.1593 Br 0 0 0 0 0 0
|
|
0.2659 -1.3655 -2.0764 Cl 0 0 0 0 0 0
|
|
-0.2052 0.0804 -0.7454 F 0 0 0 0 0 0
|
|
1 2 1 0 0 0
|
|
2 3 1 0 0 0
|
|
2 4 1 0 0 0
|
|
2 5 1 0 0 0
|
|
M END)CTAB"_ctab;
|
|
|
|
REQUIRE(m0);
|
|
REQUIRE(m1);
|
|
|
|
const auto cAt0 = m0->getAtomWithIdx(1);
|
|
const auto cAt1 = m1->getAtomWithIdx(1);
|
|
REQUIRE(cAt0->getAtomicNum() == 6);
|
|
REQUIRE(cAt1->getAtomicNum() == 6);
|
|
|
|
// Two atoms changes positions in the molblocks, but their coordinates
|
|
// didn't change, so they must have opposite parities in order to
|
|
// preserve the absolute chirality
|
|
CHECK(cAt0->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
CHECK(cAt1->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
|
|
CIPLabeler::assignCIPLabels(*m0);
|
|
CIPLabeler::assignCIPLabels(*m1);
|
|
|
|
CHECK(cAt0->getProp<std::string>(common_properties::_CIPCode) ==
|
|
cAt1->getProp<std::string>(common_properties::_CIPCode));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #7300: incorrect chiral carbon perception for some phosphates") {
|
|
SECTION("basics") {
|
|
auto m = "NC(CP(=O)(O)O)CP(=O)(O)O"_smiles;
|
|
REQUIRE(m);
|
|
auto sis = Chirality::findPotentialStereo(*m);
|
|
CHECK(sis.empty());
|
|
}
|
|
SECTION("make sure Ps can still yield chiral centers") {
|
|
auto m = "NC(CP(=O)(O)[O-])CP(=O)(O)[O-]"_smiles;
|
|
REQUIRE(m);
|
|
auto sis = Chirality::findPotentialStereo(*m);
|
|
CHECK(sis.size() == 3);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("github 7371") {
|
|
SECTION("basics") {
|
|
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);
|
|
CHECK(m->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOATROPCW);
|
|
// after reading in, there's no bond wedging:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer());
|
|
// now there is:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
if (bnd->getIdx() != 19) {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
} else {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
|
|
}
|
|
}
|
|
}
|
|
SECTION("3d") {
|
|
auto m = R"CTAB(
|
|
RDKit 3D
|
|
|
|
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.382755 -0.645531 -2.970173 0
|
|
M V30 2 C 1.191902 -0.080895 -1.951313 0
|
|
M V30 3 O 2.258061 0.469395 -2.326427 0
|
|
M V30 4 N 0.799020 -0.142115 -0.557889 0
|
|
M V30 5 C -0.412487 -0.780398 -0.245786 0
|
|
M V30 6 C -0.494802 -2.127175 0.018641 0
|
|
M V30 7 C 0.666160 -3.010952 -0.001627 0
|
|
M V30 8 C -1.739073 -2.694470 0.315567 0
|
|
M V30 9 C -2.888797 -1.963937 0.355103 0
|
|
M V30 10 N -2.786157 -0.648937 0.093334 0
|
|
M V30 11 C -1.586505 -0.059517 -0.200695 0
|
|
M V30 12 C -1.679580 1.394735 -0.453406 0
|
|
M V30 13 C -1.637015 2.096392 0.888082 0
|
|
M V30 14 C -3.022840 1.757751 -1.085608 0
|
|
M V30 15 C 1.696224 0.471157 0.348870 0
|
|
M V30 16 N 1.608949 1.758263 0.730152 0
|
|
M V30 17 C 2.457188 2.346328 1.581612 0
|
|
M V30 18 C 3.501956 1.626784 2.127981 0
|
|
M V30 19 C 3.644663 0.298166 1.774403 0
|
|
M V30 20 C 2.724579 -0.259689 0.878536 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);
|
|
CHECK(m->getBondWithIdx(3)->getStereo() == Bond::BondStereo::STEREOATROPCW);
|
|
// after reading in, there's no bond wedging:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer());
|
|
// now there is:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
if (bnd->getIdx() != 4) {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
} else {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
|
|
}
|
|
}
|
|
}
|
|
SECTION("favor larger rings") {
|
|
auto m = R"CTAB(
|
|
Mrv2401 04262410272D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 13 14 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 2.5796 -3.2152 0 0
|
|
M V30 2 N 2.1037 -4.6798 0 0
|
|
M V30 3 N 0.5637 -4.6798 0 0
|
|
M V30 4 C 0.0878 -3.2152 0 0
|
|
M V30 5 C -1.3768 -2.7393 0 0
|
|
M V30 6 C 1.3337 -2.31 0 0
|
|
M V30 7 C 1.3337 -0.77 0 0
|
|
M V30 8 C 0 0 0 0
|
|
M V30 9 Cl -1.3337 -0.77 0 0
|
|
M V30 10 C 0 1.54 0 0
|
|
M V30 11 C 1.3337 2.31 0 0
|
|
M V30 12 C 2.6674 1.54 0 0
|
|
M V30 13 C 2.6674 -0 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 2 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 6 4 CFG=1
|
|
M V30 6 2 1 6
|
|
M V30 7 1 6 7
|
|
M V30 8 2 7 8
|
|
M V30 9 1 8 9
|
|
M V30 10 1 8 10
|
|
M V30 11 2 10 11
|
|
M V30 12 1 11 12
|
|
M V30 13 2 12 13
|
|
M V30 14 1 7 13
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
|
|
Bond::BondStereo::STEREOATROPCW);
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer());
|
|
// now there is:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
if (bnd->getIdx() != 13) {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
} else {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
|
|
}
|
|
}
|
|
}
|
|
SECTION("favor larger rings 3D") {
|
|
auto m = R"CTAB(
|
|
RDKit 3D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 13 14 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 1.472629 -1.845227 -0.264711 0
|
|
M V30 2 N 2.829375 -1.724790 -0.334942 0
|
|
M V30 3 N 3.127980 -0.437811 -0.267564 0
|
|
M V30 4 C 1.973684 0.284699 -0.153598 0
|
|
M V30 5 C 1.905135 1.771086 -0.051971 0
|
|
M V30 6 C 0.912735 -0.591792 -0.150090 0
|
|
M V30 7 C -0.495016 -0.243797 -0.046092 0
|
|
M V30 8 C -1.102614 -0.154763 1.189160 0
|
|
M V30 9 Cl -0.084572 -0.483042 2.603743 0
|
|
M V30 10 C -2.434893 0.170808 1.361855 0
|
|
M V30 11 C -3.202022 0.420663 0.247454 0
|
|
M V30 12 C -2.626093 0.340227 -1.000486 0
|
|
M V30 13 C -1.279199 0.010190 -1.157641 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 2
|
|
M V30 2 1 2 3
|
|
M V30 3 2 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 6 4 CFG=3
|
|
M V30 6 2 1 6
|
|
M V30 7 1 6 7
|
|
M V30 8 2 7 8
|
|
M V30 9 1 8 9
|
|
M V30 10 1 8 10
|
|
M V30 11 2 10 11
|
|
M V30 12 1 11 12
|
|
M V30 13 2 12 13
|
|
M V30 14 1 7 13
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
|
|
Bond::BondStereo::STEREOATROPCW);
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
}
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer());
|
|
// now there is:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
if (bnd->getIdx() != 7) {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
} else {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("avoid macrocycles") {
|
|
auto m =
|
|
"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,)|"_smiles;
|
|
REQUIRE(m);
|
|
m->getBondWithIdx(1)->setStereo(Bond::BondStereo::STEREOATROPCW);
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer());
|
|
// now there is:
|
|
for (const auto bnd : m->bonds()) {
|
|
INFO(bnd->getIdx());
|
|
if (bnd->getIdx() != 13) {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::NONE);
|
|
} else {
|
|
CHECK(bnd->getBondDir() == Bond::BondDir::BEGINWEDGE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github #7434: planar amide nitrogen incorrectly flagged as _ChiralityPossible") {
|
|
SECTION("as reported") {
|
|
auto m1 = "O=C1CCCCC[C@@H]2CN1CCO2"_smiles;
|
|
REQUIRE(m1);
|
|
// new stereo
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
m1->getAtomWithIdx(7)));
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
m1->getAtomWithIdx(9)));
|
|
|
|
// force old stereo
|
|
UseLegacyStereoPerceptionFixture resetStereoPerception{true};
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
|
|
CHECK(si.size() == 1);
|
|
}
|
|
SECTION("details") {
|
|
auto m1 = "C1CCCCC[C@@H]2CN1CCO2"_smiles;
|
|
REQUIRE(m1);
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
m1->getAtomWithIdx(6)));
|
|
CHECK(Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
m1->getAtomWithIdx(8)));
|
|
|
|
// force old stereo
|
|
UseLegacyStereoPerceptionFixture resetStereoPerception{true};
|
|
bool cleanIt = true;
|
|
bool flagPossible = true;
|
|
{
|
|
auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
|
|
CHECK(si.size() == 2);
|
|
}
|
|
m1->getAtomWithIdx(8)->setHybridization(Atom::HybridizationType::SP2);
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
m1->getAtomWithIdx(8)));
|
|
{
|
|
auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
|
|
CHECK(si.size() == 1);
|
|
}
|
|
m1->getAtomWithIdx(8)->setHybridization(Atom::HybridizationType::SP3);
|
|
m1->getBondBetweenAtoms(0, 8)->setIsConjugated(true);
|
|
CHECK(!Chirality::detail::isAtomPotentialTetrahedralCenter(
|
|
m1->getAtomWithIdx(8)));
|
|
{
|
|
auto si = Chirality::findPotentialStereo(*m1, cleanIt, flagPossible);
|
|
CHECK(si.size() == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("github #7438: expose code for simplified stereo labels") {
|
|
SECTION("basics") {
|
|
{
|
|
auto m = "C[C@H](N)[C@@H](C)F |o1:1,3|"_smiles;
|
|
REQUIRE(m);
|
|
{
|
|
ROMol m2(*m);
|
|
Chirality::simplifyEnhancedStereo(m2);
|
|
std::string note;
|
|
CHECK(m2.getPropIfPresent(common_properties::molNote, note));
|
|
CHECK(note == "OR enantiomer");
|
|
CHECK(m2.getStereoGroups().empty());
|
|
}
|
|
{
|
|
ROMol m2(*m);
|
|
bool removeAffectedStereoGroups = false;
|
|
Chirality::simplifyEnhancedStereo(m2, removeAffectedStereoGroups);
|
|
std::string note;
|
|
CHECK(m2.getPropIfPresent(common_properties::molNote, note));
|
|
CHECK(note == "OR enantiomer");
|
|
CHECK(m2.getStereoGroups().size() == 1);
|
|
}
|
|
}
|
|
{
|
|
auto m = "C[C@H](N)[C@@H](C)F |&1:1,3|"_smiles;
|
|
REQUIRE(m);
|
|
{
|
|
ROMol m2(*m);
|
|
Chirality::simplifyEnhancedStereo(m2);
|
|
std::string note;
|
|
CHECK(m2.getPropIfPresent(common_properties::molNote, note));
|
|
CHECK(note == "AND enantiomer");
|
|
CHECK(m2.getStereoGroups().empty());
|
|
}
|
|
{
|
|
ROMol m2(*m);
|
|
bool removeAffectedStereoGroups = false;
|
|
Chirality::simplifyEnhancedStereo(m2, removeAffectedStereoGroups);
|
|
std::string note;
|
|
CHECK(m2.getPropIfPresent(common_properties::molNote, note));
|
|
CHECK(note == "AND enantiomer");
|
|
CHECK(m2.getStereoGroups().size() == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("incomplete") {
|
|
auto m = "C[C@H](N)[C@@H](C)F |o1:1|"_smiles;
|
|
REQUIRE(m);
|
|
{
|
|
ROMol m2(*m);
|
|
Chirality::simplifyEnhancedStereo(m2);
|
|
CHECK(!m2.hasProp(common_properties::molNote));
|
|
CHECK(m2.getStereoGroups().size() == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CipLabelAtropsOnRing", "[basic]") {
|
|
SECTION("atropisomers1") {
|
|
std::string molBlock = R"(
|
|
Mrv2304 04172300142D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 24 26 0 0 1
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -0.0002 1.5403 0 0
|
|
M V30 2 N -0.0002 3.0805 0 0 CFG=1
|
|
M V30 3 C -1.334 3.8508 0 0
|
|
M V30 4 C -2.6678 3.0807 0 0
|
|
M V30 5 C -1.334 5.391 0 0
|
|
M V30 6 C 1.3338 5.391 0 0
|
|
M V30 7 C 1.3338 3.8508 0 0
|
|
M V30 8 Br 2.6676 3.0807 0 0
|
|
M V30 9 C -1.3338 0.7702 0 0
|
|
M V30 10 C -2.6678 1.5403 0 0
|
|
M V30 11 C -1.3338 -0.7702 0 0
|
|
M V30 12 C -2.6678 -1.5401 0 0
|
|
M V30 13 C -0.0002 -1.5403 0 0
|
|
M V30 14 N -0.0002 -3.0805 0 0 CFG=1
|
|
M V30 15 C 1.3338 -3.8508 0 0
|
|
M V30 16 C 2.6676 -3.0805 0 0
|
|
M V30 17 C 1.3338 -5.391 0 0
|
|
M V30 18 C -1.334 -5.391 0 0
|
|
M V30 19 C -1.334 -3.8508 0 0
|
|
M V30 20 Br -2.6678 -3.0805 0 0
|
|
M V30 21 C 1.3338 -0.7702 0 0
|
|
M V30 22 C 2.6678 -1.5403 0 0
|
|
M V30 23 C 1.3338 0.7702 0 0
|
|
M V30 24 C 2.6678 1.5404 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 3 4
|
|
M V30 4 2 3 5
|
|
M V30 5 1 5 6
|
|
M V30 6 2 6 7
|
|
M V30 7 1 2 7 CFG=1
|
|
M V30 8 1 7 8
|
|
M V30 9 2 1 9
|
|
M V30 10 1 9 10
|
|
M V30 11 1 9 11
|
|
M V30 12 1 11 12
|
|
M V30 13 2 11 13
|
|
M V30 14 1 13 14
|
|
M V30 15 1 14 15 CFG=1
|
|
M V30 16 1 15 16
|
|
M V30 17 2 15 17
|
|
M V30 18 1 17 18
|
|
M V30 19 2 18 19
|
|
M V30 20 1 14 19
|
|
M V30 21 1 19 20
|
|
M V30 22 1 13 21
|
|
M V30 23 1 21 22
|
|
M V30 24 2 21 23
|
|
M V30 25 1 1 23
|
|
M V30 26 1 23 24
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)";
|
|
|
|
UseLegacyStereoPerceptionFixture useLegacy(false);
|
|
|
|
std::unique_ptr<RWMol> mol(MolBlockToMol(molBlock, true, false));
|
|
|
|
REQUIRE(mol);
|
|
|
|
CHECK(mol->getBondWithIdx(0)->getStereo() ==
|
|
Bond::BondStereo::STEREOATROPCCW);
|
|
CHECK(mol->getBondWithIdx(13)->getStereo() ==
|
|
Bond::BondStereo::STEREOATROPCCW);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"GitHub #7509: atomChiralTypeFromBondDirPseudo3D fails for poorly scaled molecular coordinates") {
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
5 4 0 0 0 0 0 0 0 0999 V2000
|
|
-2.0785 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.7794 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.5196 -0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.8187 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.5196 -1.5000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1 2 1 0
|
|
2 3 1 0
|
|
3 4 1 1
|
|
3 5 1 0
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
|
|
auto at = m->getAtomWithIdx(2);
|
|
REQUIRE(at->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
|
|
auto &pos = m->getConformer().getPositions();
|
|
std::for_each(pos.begin(), pos.end(),
|
|
[](RDGeom::Point3D &pos) { pos *= 6.0; });
|
|
|
|
// Reset chirality and the original bond direction
|
|
// (it was stripped after parsing m for the first time)
|
|
at->setChiralTag(Atom::ChiralType::CHI_UNSPECIFIED);
|
|
m->getBondBetweenAtoms(2, 3)->setBondDir(Bond::BondDir::BEGINWEDGE);
|
|
|
|
MolOps::assignChiralTypesFromBondDirs(*m);
|
|
|
|
CHECK(at->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
}
|
|
|
|
TEST_CASE("findMesoCenters") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
SECTION("basics") {
|
|
std::vector<std::pair<std::string,
|
|
std::vector<std::pair<unsigned int, unsigned int>>>>
|
|
cases{
|
|
{"C[C@@H](Cl)C[C@H](C)Cl", {{1, 4}}},
|
|
{"C[C@@H](Cl)C[C@@H](Cl)C", {{1, 4}}},
|
|
{"C[C@H](Cl)C[C@H](C)Cl", {}},
|
|
{"OC(F)C([C@H](F)O)[C@@H](F)O", {{4, 7}}},
|
|
{"N[C@H]1CC[C@@H](O)CC1", {{1, 4}}},
|
|
{"N[C@H]1CC[C@H](O)CC1", {{1, 4}}},
|
|
{"N[C@H]1CC[C@@H](N)CC1", {{1, 4}}},
|
|
{"N[C@H]1CC[C@H](N)CC1", {{1, 4}}},
|
|
{"N[C@H]1C[C@@H](N)C1", {{1, 3}}},
|
|
{"N[C@H]1C[C@H](N)C1", {{1, 3}}},
|
|
{"C1CC[C@H]2CCCC[C@H]2C1", {{3, 8}}},
|
|
// multiple groups
|
|
{"C[C@@H](Cl)C([C@H](C)Cl)C([C@H](F)O)[C@@H](F)O",
|
|
{{1, 4}, {8, 11}}},
|
|
{"C1[C@H](F)C[C@H]1C[C@H]1C[C@H](N)C1", {{1, 4}, {6, 8}}},
|
|
// not sure if this next one is right:
|
|
{"N[C@H]1[C@H](F)[C@@H](N)[C@@H]1F", {{1, 4}, {2, 6}}},
|
|
};
|
|
for (auto &[smi, expected] : cases) {
|
|
INFO(smi);
|
|
auto m = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m);
|
|
auto res = Chirality::findMesoCenters(*m);
|
|
REQUIRE(res.size() == expected.size());
|
|
CHECK(res == expected);
|
|
for (auto [a1, a2] : res) {
|
|
unsigned int oa = m->getNumAtoms() + 1;
|
|
CHECK(m->getAtomWithIdx(a1)->getPropIfPresent(
|
|
common_properties::_mesoOtherAtom, oa));
|
|
CHECK(oa == a2);
|
|
CHECK(m->getAtomWithIdx(a2)->getPropIfPresent(
|
|
common_properties::_mesoOtherAtom, oa));
|
|
CHECK(oa == a1);
|
|
}
|
|
}
|
|
}
|
|
SECTION("with enhanced stereo") {
|
|
std::vector<std::pair<std::string,
|
|
std::vector<std::pair<unsigned int, unsigned int>>>>
|
|
cases{{"N[C@H]1CC[C@@H](O)CC1 |o1:1,4|", {{1, 4}}}};
|
|
for (auto &[smi, expected] : cases) {
|
|
INFO(smi);
|
|
auto m = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m);
|
|
auto res = Chirality::findMesoCenters(*m);
|
|
REQUIRE(res.size() == expected.size());
|
|
CHECK(res == expected);
|
|
}
|
|
}
|
|
SECTION("options") {
|
|
{
|
|
auto m = "[13CH3][C@@H](C)C[C@@H](C)[13CH3]"_smiles;
|
|
REQUIRE(m);
|
|
auto res = Chirality::findMesoCenters(*m);
|
|
REQUIRE(res.size() == 1);
|
|
CHECK(res == std::vector<std::pair<unsigned int, unsigned int>>{{1, 4}});
|
|
bool includeIsotopes = false;
|
|
res = Chirality::findMesoCenters(*m, includeIsotopes);
|
|
REQUIRE(res.empty());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github #7598: FindPotentialStereo() missing some results if cleanIt is False") {
|
|
SECTION("as reported") {
|
|
std::vector<std::string> smileses = {"C[C@H](F)C(C)[C@H](F)C",
|
|
"CC([C@H](C)F)[C@@H](C)F",
|
|
"CC([C@H](C)F)[C@H](F)C"};
|
|
for (const auto &smi : smileses) {
|
|
auto m = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m);
|
|
bool flagPossible = true;
|
|
for (bool cleanIt : {true, false}) {
|
|
auto si = Chirality::findPotentialStereo(*m, cleanIt, flagPossible);
|
|
INFO(smi + " " + std::to_string(cleanIt));
|
|
CHECK(si.size() == 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"GitHub Issue #7929: AssignStereochemistry(cleanIt=True) does not clean _CIPCode property on bonds") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(true);
|
|
|
|
auto m = "CC"_smiles;
|
|
REQUIRE(m);
|
|
|
|
m->getAtomWithIdx(0)->setProp(common_properties::_CIPCode, "X");
|
|
m->getBondWithIdx(0)->setProp(common_properties::_CIPCode, "X");
|
|
|
|
bool clean = true;
|
|
bool flag = true;
|
|
bool force = true;
|
|
|
|
SECTION("legacy stereo perception") {
|
|
Chirality::setUseLegacyStereoPerception(true);
|
|
|
|
RDKit::MolOps::assignStereochemistry(*m, clean, force, flag);
|
|
|
|
CHECK(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
|
|
CHECK(m->getBondWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
|
|
}
|
|
SECTION("new stereo perception") {
|
|
Chirality::setUseLegacyStereoPerception(false);
|
|
|
|
RDKit::MolOps::assignStereochemistry(*m, clean, force, flag);
|
|
|
|
CHECK(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
|
|
CHECK(m->getBondWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Github issue #7983: stereogroup lost on chiral sulfoxide") {
|
|
SECTION("basics") {
|
|
auto m = R"CTAB(
|
|
Mrv2317 02032512242D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 21 22 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -4.001 -2.31 0 0
|
|
M V30 2 C -2.6674 -3.08 0 0
|
|
M V30 3 C -1.3337 -2.31 0 0
|
|
M V30 4 C 0 -3.08 0 0
|
|
M V30 5 C 0 -4.6198 0 0
|
|
M V30 6 N 1.3337 -5.3898 0 0
|
|
M V30 7 C 2.6674 -4.6198 0 0
|
|
M V30 8 O 2.6674 -3.08 0 0
|
|
M V30 9 O 4.001 -5.3898 0 0
|
|
M V30 10 C 5.3345 -4.6198 0 0
|
|
M V30 11 C 6.6682 -5.3898 0 0
|
|
M V30 12 C 8.0019 -4.6198 0 0
|
|
M V30 13 C 9.3356 -5.3898 0 0
|
|
M V30 14 C 9.3356 -6.9298 0 0
|
|
M V30 15 C 8.0019 -7.6998 0 0
|
|
M V30 16 C 6.6682 -6.9298 0 0
|
|
M V30 17 S 5.3345 -7.6998 0 0 CFG=2
|
|
M V30 18 C 4.001 -6.9298 0 0
|
|
M V30 19 O 5.3345 -9.2398 0 0
|
|
M V30 20 C -1.3337 -5.3898 0 0
|
|
M V30 21 C -2.6674 -4.6198 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 1 2
|
|
M V30 2 4 2 3
|
|
M V30 3 4 3 4
|
|
M V30 4 4 4 5
|
|
M V30 5 1 5 6
|
|
M V30 6 1 6 7
|
|
M V30 7 2 7 8
|
|
M V30 8 1 7 9
|
|
M V30 9 1 9 10
|
|
M V30 10 1 10 11
|
|
M V30 11 4 11 12
|
|
M V30 12 4 12 13
|
|
M V30 13 4 13 14
|
|
M V30 14 4 14 15
|
|
M V30 15 4 15 16
|
|
M V30 16 4 11 16
|
|
M V30 17 1 17 16
|
|
M V30 18 2 17 19
|
|
M V30 19 4 5 20
|
|
M V30 20 4 20 21
|
|
M V30 21 4 2 21
|
|
M V30 22 1 17 18 CFG=1
|
|
M V30 END BOND
|
|
M V30 BEGIN COLLECTION
|
|
M V30 MDLV30/STEABS ATOMS=(1 17)
|
|
M V30 END COLLECTION
|
|
M V30 END CTAB
|
|
M END)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
auto &sgs = m->getStereoGroups();
|
|
REQUIRE(sgs.size() == 1);
|
|
CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
|
|
CHECK(sgs[0].getAtoms().size() == 1);
|
|
CHECK(sgs[0].getAtoms().at(0)->getIdx() == 16);
|
|
}
|
|
SECTION("making sure sulfoxides do not trigger atropisomerism") {
|
|
auto m = R"CTAB(
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 10 10 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 1.892852 4.651432 0.000000 0
|
|
M V30 2 C 3.199931 3.915535 0.000000 0
|
|
M V30 3 C 3.216171 2.415622 0.000000 0
|
|
M V30 4 C 1.925330 1.651602 0.000000 0
|
|
M V30 5 C 0.618251 2.387492 0.000000 0
|
|
M V30 6 C 0.602011 3.887408 0.000000 0
|
|
M V30 7 S -0.705072 4.623298 0.000000 0
|
|
M V30 8 C -0.672591 1.623472 0.000000 0
|
|
M V30 9 O -0.721314 6.123210 0.000000 0
|
|
M V30 10 C -1.995913 3.859276 0.000000 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 2 1 2
|
|
M V30 2 1 2 3
|
|
M V30 3 2 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 2 5 6
|
|
M V30 6 1 6 1 CFG=1
|
|
M V30 7 1 6 7
|
|
M V30 8 1 5 8
|
|
M V30 9 2 7 9
|
|
M V30 10 1 7 10 CFG=1
|
|
M V30 END BOND
|
|
M V30 BEGIN COLLECTION
|
|
M V30 MDLV30/STEABS ATOMS=(1 7)
|
|
M V30 END COLLECTION
|
|
M V30 END CTAB
|
|
M END
|
|
$$$$
|
|
)CTAB"_ctab;
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(6)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("examples from #8323") {
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/Github8323.sdf";
|
|
SDMolSupplier suppl(pathName);
|
|
while (!suppl.atEnd()) {
|
|
std::unique_ptr<ROMol> mol(suppl.next());
|
|
REQUIRE(mol);
|
|
auto &sgs = mol->getStereoGroups();
|
|
REQUIRE(sgs.size() == 1);
|
|
REQUIRE(mol->hasProp("StereoGroupOnAtom"));
|
|
auto aid = std::stoul(mol->getProp<std::string>("StereoGroupOnAtom"));
|
|
CHECK(sgs[0].getAtoms().size() == 1);
|
|
CHECK(sgs[0].getAtoms().at(0)->getIdx() == aid);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Github #8420: imines and crossed bonds") {
|
|
bool useLegacy = GENERATE(true, false);
|
|
CAPTURE(useLegacy);
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(useLegacy);
|
|
SECTION("as reported") {
|
|
std::string ctab = R"CTAB(
|
|
MJ250100
|
|
|
|
7 7 0 0 1 0 0 0 0 0999 V2000
|
|
0.6961 0.4995 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.0196 1.7395 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.7332 0.4968 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4118 0.9090 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
1.4118 1.7395 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
0.6961 2.1583 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-0.0196 0.9101 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
3 7 2 3 0 0 0
|
|
4 5 1 0 0 0 0
|
|
5 6 1 0 0 0 0
|
|
2 6 1 0 0 0 0
|
|
2 7 1 0 0 0 0
|
|
1 4 1 0 0 0 0
|
|
1 7 1 0 0 0 0
|
|
M END)CTAB";
|
|
auto m = v2::FileParsers::MolFromMolBlock(ctab);
|
|
REQUIRE(m);
|
|
CHECK(m->getBondWithIdx(0)->getStereo() == Bond::BondStereo::STEREONONE);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #8712: Modern stereo + 3D SD file leads to bad stereo detection for some molecules") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
|
|
constexpr bool cleanUpStereo = false;
|
|
constexpr bool flagPossibleCenters = true;
|
|
|
|
SECTION("as reported") {
|
|
std::string ctab = R"CTAB(
|
|
RDKit 3D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 23 23 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 2.628222 -0.014974 0.390914 0
|
|
M V30 2 N 1.918195 0.260032 -0.835284 0
|
|
M V30 3 C 0.487110 0.206615 -0.681308 0
|
|
M V30 4 C 0.079788 -1.165009 -0.217881 0
|
|
M V30 5 C -1.428295 -1.378335 -0.390798 0
|
|
M V30 6 S -2.156694 -0.136366 0.671587 0
|
|
M V30 7 O -1.585579 -0.347357 2.045208 0
|
|
M V30 8 O -3.651696 -0.172351 0.647489 0
|
|
M V30 9 C -1.519569 1.415993 0.067834 0
|
|
M V30 10 C -0.010801 1.307036 0.207366 0
|
|
M V30 11 H 1.988100 -0.320407 1.230786 0
|
|
M V30 12 H 3.238742 0.867078 0.711200 0
|
|
M V30 13 H 3.359131 -0.825206 0.185242 0
|
|
M V30 14 H 2.223613 -0.317010 -1.647727 0
|
|
M V30 15 H 0.041778 0.347830 -1.688472 0
|
|
M V30 16 H 0.284353 -1.370499 0.832996 0
|
|
M V30 17 H 0.580676 -1.905979 -0.844599 0
|
|
M V30 18 H -1.673500 -2.379109 -0.016810 0
|
|
M V30 19 H -1.696056 -1.195326 -1.452807 0
|
|
M V30 20 H -1.827609 1.481487 -0.996993 0
|
|
M V30 21 H -1.921584 2.283368 0.626641 0
|
|
M V30 22 H 0.172726 1.108075 1.288878 0
|
|
M V30 23 H 0.468947 2.250412 -0.133459 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 3 4
|
|
M V30 4 1 4 5
|
|
M V30 5 1 5 6
|
|
M V30 6 2 6 7
|
|
M V30 7 2 6 8
|
|
M V30 8 1 6 9
|
|
M V30 9 1 9 10
|
|
M V30 10 1 10 3
|
|
M V30 11 1 1 11
|
|
M V30 12 1 1 12
|
|
M V30 13 1 1 13
|
|
M V30 14 1 2 14
|
|
M V30 15 1 3 15
|
|
M V30 16 1 4 16
|
|
M V30 17 1 4 17
|
|
M V30 18 1 5 18
|
|
M V30 19 1 5 19
|
|
M V30 20 1 9 20
|
|
M V30 21 1 9 21
|
|
M V30 22 1 10 22
|
|
M V30 23 1 10 23
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB";
|
|
auto m = v2::FileParsers::MolFromMolBlock(ctab);
|
|
REQUIRE(m);
|
|
|
|
auto centers =
|
|
Chirality::findPotentialStereo(*m, cleanUpStereo, flagPossibleCenters);
|
|
CHECK(centers.empty());
|
|
}
|
|
SECTION("overcorrection #1") {
|
|
// Do not break this one!
|
|
// This one comes from the doctests. I'm adding it here because it seems we
|
|
// don't have this case in any other C++ test.
|
|
auto m = R"SMI(C1C[C@H](C)[C@H](C)[C@H](C)C1)SMI"_smiles;
|
|
REQUIRE(m);
|
|
auto centers =
|
|
Chirality::findPotentialStereo(*m, cleanUpStereo, flagPossibleCenters);
|
|
CHECK(centers.size() == 3);
|
|
}
|
|
SECTION("overcorrection #2") {
|
|
// Do not break this one either!
|
|
// This case seems to be related to the order of the atoms, since I haven't
|
|
// been able to reproduce with the equivalent SMILES with explicit Hs.
|
|
auto m = R"SMI(C[C@H]1C[C@@H](C)C1)SMI"_smiles;
|
|
REQUIRE(m);
|
|
MolOps::addHs(*m); // This only manifests if Hs are present
|
|
auto centers =
|
|
Chirality::findPotentialStereo(*m, cleanUpStereo, flagPossibleCenters);
|
|
CHECK(centers.size() == 2);
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
TEST_CASE(
|
|
"Github #8689: stereo canonicalization depends on bond iteration order") {
|
|
bool useLegacy = GENERATE(true, false);
|
|
CAPTURE(useLegacy);
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(useLegacy);
|
|
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/";
|
|
|
|
SECTION("simplified") {
|
|
pathName += "github8689_2.sdf";
|
|
v2::FileParsers::SDMolSupplier suppl(pathName);
|
|
auto m1 = suppl[0];
|
|
REQUIRE(m1);
|
|
auto m2 = suppl[1];
|
|
REQUIRE(m2);
|
|
// m1->debugMol(std::cerr);
|
|
// m2->debugMol(std::cerr);
|
|
|
|
CIPLabeler::assignCIPLabels(*m1);
|
|
CIPLabeler::assignCIPLabels(*m2);
|
|
REQUIRE(m1->getAtomWithIdx(7)->hasProp(common_properties::_CIPCode));
|
|
REQUIRE(m1->getAtomWithIdx(8)->hasProp(common_properties::_CIPCode));
|
|
REQUIRE(m2->getAtomWithIdx(7)->hasProp(common_properties::_CIPCode));
|
|
REQUIRE(m2->getAtomWithIdx(8)->hasProp(common_properties::_CIPCode));
|
|
|
|
CHECK(m1->getAtomWithIdx(7)->getProp<std::string>(
|
|
common_properties::_CIPCode) ==
|
|
m2->getAtomWithIdx(7)->getProp<std::string>(
|
|
common_properties::_CIPCode));
|
|
CHECK(m1->getAtomWithIdx(8)->getProp<std::string>(
|
|
common_properties::_CIPCode) ==
|
|
m2->getAtomWithIdx(8)->getProp<std::string>(
|
|
common_properties::_CIPCode));
|
|
|
|
auto smi1 = MolToSmiles(*m1);
|
|
auto smi2 = MolToSmiles(*m2);
|
|
CHECK(smi1 == smi2);
|
|
}
|
|
}
|
|
#endif
|
|
TEST_CASE("extra ring stereo with new stereo perception") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
SECTION("basics") {
|
|
std::string smi = "C2O[C@H]3[C@@]4([C@](CCC(C24))(O)CC=C3)C";
|
|
auto m = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m);
|
|
for (auto idx : {2, 3, 4}) {
|
|
INFO(idx);
|
|
const auto atm = m->getAtomWithIdx(idx);
|
|
REQUIRE(atm);
|
|
CHECK(atm->getChiralTag() != Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(!atm->hasProp(common_properties::_ringStereoAtoms));
|
|
}
|
|
}
|
|
SECTION("don't destroy actual ring stereo") {
|
|
std::string smi = "C[C@@H]1CC[C@@H](C)CC1";
|
|
auto m = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m);
|
|
// m->debugMol(std::cerr);
|
|
// for (const auto atm : m->atoms()) {
|
|
// std::cerr << atm->getIdx() << ": "
|
|
// << atm->getProp<unsigned int>(
|
|
// common_properties::_ChiralAtomRank)
|
|
// << std::endl;
|
|
// }
|
|
for (auto idx : {1, 4}) {
|
|
INFO(idx);
|
|
const auto atm = m->getAtomWithIdx(idx);
|
|
REQUIRE(atm);
|
|
CHECK(atm->getChiralTag() != Atom::ChiralType::CHI_UNSPECIFIED);
|
|
CHECK(atm->hasProp(common_properties::_ringStereoAtoms));
|
|
}
|
|
}
|
|
SECTION("#8956 ensure ring stereochemistry is not inverted on round trip") {
|
|
auto useLegacy = GENERATE(true, false);
|
|
CAPTURE(useLegacy);
|
|
UseLegacyStereoPerceptionFixture fx(useLegacy);
|
|
auto [smi1, smi2] = GENERATE(
|
|
std::make_pair("CC[C@]1(C)CCC[C@](C)(O)C1",
|
|
"CC[C@@]1(C)CCC[C@@](C)(O)C1"),
|
|
std::make_pair("C[C@H]1CCC[C@](C)(O)C1", "C[C@@H]1CCC[C@@](C)(O)C1"));
|
|
auto m1 = v2::SmilesParse::MolFromSmiles(smi1);
|
|
REQUIRE(m1);
|
|
auto m2 = v2::SmilesParse::MolFromSmiles(smi2);
|
|
REQUIRE(m2);
|
|
|
|
MolOps::assignStereochemistry(*m1, true, true, true);
|
|
MolOps::assignStereochemistry(*m2, true, true, true);
|
|
|
|
auto roundtrip1 = MolToSmiles(*m1);
|
|
auto roundtrip2 = MolToSmiles(*m2);
|
|
CHECK(roundtrip1 == smi1);
|
|
CHECK(roundtrip2 == smi2);
|
|
CHECK(roundtrip2 != roundtrip1);
|
|
}
|
|
}
|
|
TEST_CASE("ring stereo basics with new stereo") {
|
|
UseLegacyStereoPerceptionFixture reset_stereo_perception(false);
|
|
auto m = "CC(C)[C@H]1CCCCN1C(=O)[C@H]1CC[C@@H](C)CC1 |a:3,11,&1:14|"_smiles;
|
|
REQUIRE(m);
|
|
auto smi = MolToCXSmiles(*m);
|
|
CHECK(smi == "CC(C)[C@H]1CCCCN1C(=O)[C@H]1CC[C@@H](C)CC1 |a:3,11,&1:14|");
|
|
}
|
|
|
|
TEST_CASE("zero chiral volume and T shape molecule") {
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/";
|
|
|
|
SECTION("simplified") {
|
|
auto m1 = R"CTAB(178 degrees (not T shaped)
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 0.000000 1.000000 0.000000 0
|
|
M V30 2 C 0.000000 0.000000 0.000000 0
|
|
M V30 3 O -0.999847 -0.017452 0.000000 0
|
|
M V30 4 F 0.999847 -0.017452 0.000000 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=1
|
|
M V30 2 1 2 3
|
|
M V30 3 1 2 4
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m1);
|
|
// make sure we perceived it correctly:
|
|
CHECK(m1->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
|
|
// make sure we round trip it:
|
|
auto m1mb = MolToV3KMolBlock(*m1);
|
|
auto m1r = v2::FileParsers::MolFromMolBlock(m1mb);
|
|
REQUIRE(m1r);
|
|
CHECK(MolToSmiles(*m1) == MolToSmiles(*m1r));
|
|
|
|
auto m2 = R"CTAB(179 degrees (T shaped)
|
|
RDKit 2D
|
|
|
|
0 0 0 0 0 0 0 0 0 0999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C 0.000000 1.000000 0.000000 0
|
|
M V30 2 C 0.000000 0.000000 0.000000 0
|
|
M V30 3 O -0.999962 -0.0087265 0.000000 0
|
|
M V30 4 F 0.999962 -0.0087265 0.000000 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 1 CFG=1
|
|
M V30 2 1 2 3
|
|
M V30 3 1 2 4
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(m2);
|
|
CHECK(m2->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
auto m2mb = MolToV3KMolBlock(*m2);
|
|
auto m2r = v2::FileParsers::MolFromMolBlock(m2mb);
|
|
REQUIRE(m2r);
|
|
CHECK(MolToSmiles(*m2) == MolToSmiles(*m2r));
|
|
}
|
|
|
|
SECTION("as reported") {
|
|
pathName += "zero_chiral_volume_1.sdf";
|
|
auto m = v2::FileParsers::MolFromMolFile(pathName);
|
|
REQUIRE(m);
|
|
for (const auto idx : {15, 22, 39}) {
|
|
INFO(idx);
|
|
CHECK(m->getAtomWithIdx(idx)->getChiralTag() !=
|
|
Atom::ChiralType::CHI_UNSPECIFIED);
|
|
}
|
|
}
|
|
SECTION("large rings") {
|
|
for (const std::string fn :
|
|
{"zero_chiral_volume_2.60.mol", "zero_chiral_volume_2.80.mol"}) {
|
|
INFO(fn);
|
|
std::string lpath = pathName + fn;
|
|
auto m = v2::FileParsers::MolFromMolFile(lpath);
|
|
REQUIRE(m);
|
|
CHECK(m->getAtomWithIdx(1)->getChiralTag() ==
|
|
Atom::ChiralType::CHI_TETRAHEDRAL_CW);
|
|
}
|
|
}
|
|
SECTION("mol block round trip") {
|
|
std::string rdbase = getenv("RDBASE");
|
|
std::string fname =
|
|
rdbase + "/Code/GraphMol/test_data/zero_chiral_volume_simple.mol";
|
|
auto mol = v2::FileParsers::MolFromMolFile(fname);
|
|
REQUIRE(mol);
|
|
|
|
{
|
|
ROMol mol2(*mol);
|
|
Chirality::wedgeMolBonds(mol2);
|
|
CHECK(mol2.getBondBetweenAtoms(0, 1)->getBondDir() ==
|
|
Bond::BondDir::BEGINDASH);
|
|
}
|
|
auto mb = MolToMolBlock(*mol);
|
|
auto mol2 = v2::FileParsers::MolFromMolBlock(mb);
|
|
|
|
auto smi = MolToSmiles(*mol);
|
|
auto smimb = MolToSmiles(*mol2);
|
|
CHECK(smi == smimb);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"GitHub Issue #8965: Stereo bond inversion in SMILES Writer canonicalization") {
|
|
const auto legacy_stereo = GENERATE(true, false);
|
|
CAPTURE(legacy_stereo);
|
|
UseLegacyStereoPerceptionFixture stereo_mode(legacy_stereo);
|
|
|
|
// These are all (non-canonical) iterations of mol in the issue
|
|
constexpr std::array<std::string_view, 4> samples = {
|
|
R"smi(C/C=C\C=C(/C=C\C)C(/C=C\C)=C/C)smi", // the reported SMILES
|
|
R"smi(C/C=C(\C=C/C)C(/C=C\C)=C/C=C\C)smi",
|
|
R"smi(C/C=C\C(=C/C)C(/C=C\C)=C/C=C\C)smi",
|
|
R"smi(C/C=C\C(=C/C=C\C)C(/C=C\C)=C/C)smi",
|
|
};
|
|
|
|
// These mol are small and simple enough for CIP calculation to not
|
|
// be too expensive.
|
|
// This is not exhaustive (we should check which bond is what),
|
|
// but it should be enough for this test.
|
|
auto get_bond_stereo_labels = [](auto &m) {
|
|
CIPLabeler::assignCIPLabels(m);
|
|
std::vector<std::string> bond_labels;
|
|
for (auto b : m.bonds()) {
|
|
std::string label;
|
|
if (b->getPropIfPresent(common_properties::_CIPCode, label)) {
|
|
bond_labels.push_back(label);
|
|
}
|
|
}
|
|
std::ranges::sort(bond_labels);
|
|
return bond_labels;
|
|
};
|
|
|
|
std::optional<std::vector<std::string>> labels_reference;
|
|
|
|
std::set<std::string> canonicalized_smiles;
|
|
for (auto smiles : samples) {
|
|
auto m =
|
|
v2::SmilesParse::MolFromSmiles(static_cast<const std::string>(smiles));
|
|
REQUIRE(m);
|
|
|
|
auto canonical_smiles = MolToSmiles(*m);
|
|
canonicalized_smiles.insert(canonical_smiles);
|
|
|
|
// Do this after getting the canonical SMILES, so that
|
|
// it doesn't have any chances to influence the canonicalization
|
|
if (!labels_reference.has_value()) {
|
|
labels_reference = get_bond_stereo_labels(*m);
|
|
} else {
|
|
CHECK(*labels_reference == get_bond_stereo_labels(*m));
|
|
}
|
|
}
|
|
|
|
// All samples should canonicalize to the same SMILES
|
|
REQUIRE(canonicalized_smiles.size() == 1);
|
|
|
|
// ... and the canonical SMILES should preserve stereo labels
|
|
auto m = v2::SmilesParse::MolFromSmiles(*canonicalized_smiles.begin());
|
|
REQUIRE(m);
|
|
CHECK(*labels_reference == get_bond_stereo_labels(*m));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"WedgeMolBonds does not steal a wiggly bond from an adjacent chiral "
|
|
"atom across clearSingleBondDirFlags") {
|
|
// CC[C@@H](Cl)N: atom 2 (C@@H) is the chiral center; atom 4 (N) is a
|
|
// degree-1 terminal neighbor. The existing countChiralNbrs handling for
|
|
// BondDir::UNKNOWN treats a wiggly bond as the stereo annotation for the
|
|
// adjacent chiral atom and pre-skips it. After clearSingleBondDirFlags,
|
|
// the bond's BondDir is NONE and only _UnknownStereo=1 remains as a
|
|
// marker; countChiralNbrs must recognize that marker so the same
|
|
// pre-skip behavior applies. Without this, pickBondToWedge would pick
|
|
// the wiggly bond (terminal neighbors are preferred) and the caller's
|
|
// subsequent restore of BondDir::UNKNOWN would erase the wedge, leaving
|
|
// the chiral atom with no visible stereo.
|
|
std::string smi =
|
|
"CC[C@@H](Cl)N "
|
|
"|(-2.078461,0.000000,;-0.779423,0.750000,;0.519615,-0.000000,;"
|
|
"0.519615,-1.500000,;1.818653,0.750000,)|";
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi)};
|
|
REQUIRE(m);
|
|
|
|
auto *chiralAtom = m->getAtomWithIdx(2);
|
|
REQUIRE(chiralAtom->getChiralTag() != Atom::ChiralType::CHI_UNSPECIFIED);
|
|
const auto originalChiralTag = chiralAtom->getChiralTag();
|
|
|
|
// Mark the bond to N (atom 4, terminal) as a wiggly bond
|
|
auto *wigglyBond = m->getBondBetweenAtoms(2, 4);
|
|
REQUIRE(wigglyBond != nullptr);
|
|
wigglyBond->setBondDir(Bond::BondDir::UNKNOWN);
|
|
|
|
// Simulate the typical re-wedging pattern: clearSingleBondDirFlags saves
|
|
// _UnknownStereo=1 for bonds with BondDir::UNKNOWN and then clears BondDir
|
|
// to NONE, so that stale wedge directions are removed before re-wedging.
|
|
MolOps::clearSingleBondDirFlags(*m);
|
|
REQUIRE(wigglyBond->getBondDir() == Bond::BondDir::NONE);
|
|
|
|
Chirality::wedgeMolBonds(*m, &m->getConformer());
|
|
|
|
// No bond at the chiral center should be wedged: the wiggly bond is the
|
|
// intentional stereo annotation, matching the BondDir::UNKNOWN behavior
|
|
// before clearSingleBondDirFlags.
|
|
for (const auto b : m->atomBonds(chiralAtom)) {
|
|
CHECK(b->getBondDir() != Bond::BondDir::BEGINWEDGE);
|
|
CHECK(b->getBondDir() != Bond::BondDir::BEGINDASH);
|
|
}
|
|
|
|
// Round-trip preserves the chiral tag itself; only the visual wedging
|
|
// representation is affected by the wiggly-bond annotation.
|
|
CHECK(chiralAtom->getChiralTag() == originalChiralTag);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"pickBondsToWedge respects _UnknownStereo=1 marker when called directly") {
|
|
// pickBondsToWedge is part of the public API (Chirality.h); callers can
|
|
// invoke it without going through wedgeMolBonds, so the recognition of
|
|
// the _UnknownStereo=1 marker has to live in countChiralNbrs itself.
|
|
std::string smi =
|
|
"CC[C@@H](Cl)N "
|
|
"|(-2.078461,0.000000,;-0.779423,0.750000,;0.519615,-0.000000,;"
|
|
"0.519615,-1.500000,;1.818653,0.750000,)|";
|
|
std::unique_ptr<RWMol> m{SmilesToMol(smi)};
|
|
REQUIRE(m);
|
|
|
|
auto *wigglyBond = m->getBondBetweenAtoms(2, 4);
|
|
REQUIRE(wigglyBond != nullptr);
|
|
wigglyBond->setBondDir(Bond::BondDir::UNKNOWN);
|
|
MolOps::clearSingleBondDirFlags(*m);
|
|
REQUIRE(wigglyBond->getBondDir() == Bond::BondDir::NONE);
|
|
|
|
auto wedgeBonds =
|
|
Chirality::pickBondsToWedge(*m, nullptr, &m->getConformer());
|
|
|
|
// The chiral atom's stereo is already expressed by the wiggly bond, so
|
|
// pickBondsToWedge must not pick any bond to wedge for it. The molecule
|
|
// has no atropisomers, so the returned map should be empty.
|
|
CHECK(wedgeBonds.empty());
|
|
}
|