Files
rdkit/Code/GraphMol/StructChecker/Stereo.cpp
2026-02-09 12:10:50 +01:00

973 lines
32 KiB
C++

//
// Copyright (C) 2016 Novartis Institutes for BioMedical Research
//
// @@ 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 <cmath>
#include <boost/format.hpp>
#include "../RDKitBase.h"
#include "../../RDGeneral/types.h"
#include "../../Geometry/point.h"
#include "StructChecker.h"
#include "Utilites.h"
#include "Stereo.h"
namespace RDKit {
namespace StructureCheck {
static const double PI = 3.14159265359;
static const double ANGLE_EPSILON = (5.0 * PI / 180.); // 5 degrees
static const double EPS = 0.0000001; // float type precision ???
/*
// constants for bond definitions
//#define CIS_TRANS_EITHER 0x03
//#define CIS_TRANS_SWAPPED 0x08
*/
struct npoint_t {
double x, y, z;
int number;
};
static double Angle(double x1, double y1, double x2, double y2) {
// Returns the angle between the two vectors (x1,y1) and (x2,y2) at (0,0).
double l1, l2;
double cos_alpha, sin_alpha;
double result;
l1 = sqrt(x1 * x1 + y1 * y1);
l2 = sqrt(x2 * x2 + y2 * y2);
if (l1 < 0.00001 || l2 < 0.00001) {
return (0.0);
}
cos_alpha = (x1 * x2 + y1 * y2) / (l1 * l2);
if (cos_alpha > 1.0) { // safeguard against round off errors
cos_alpha = 1.0;
} else if (cos_alpha < -1.0) {
cos_alpha = -1.0;
}
sin_alpha = (x1 * y2 - x2 * y1) / (l1 * l2);
result = acos(cos_alpha);
if (sin_alpha < 0.0) {
result = 2 * PI - result;
}
return result;
}
static double Volume(struct npoint_t tetra[4]) {
// Computes the signed volume of the tetrahedron defined by the four points in
// tetra[].
double ax, ay, az, bx, by, bz, cx, cy, cz;
ax = tetra[1].x - tetra[0].x;
ay = tetra[1].y - tetra[0].y;
az = tetra[1].z - tetra[0].z;
bx = tetra[2].x - tetra[0].x;
by = tetra[2].y - tetra[0].y;
bz = tetra[2].z - tetra[0].z;
cx = tetra[3].x - tetra[0].x;
cy = tetra[3].y - tetra[0].y;
cz = tetra[3].z - tetra[0].z;
return (ax * (by * cz - bz * cy) + ay * (bz * cx - bx * cz) +
az * (bx * cy - by * cx));
}
int DubiousStereochemistry(RWMol &mol) {
/*
* Checks if there is some ill-defined stereochemistry in the
* molecule *mp. The function returns a bit set integer which defines
* the problems encountered.
*/
int result = 0;
// int is_allene, ndb, jatom;
std::vector<Neighbourhood> neighbour_array;
SetupNeighbourhood(mol, neighbour_array);
// look for EITHER bonds
for (unsigned i = 0; i < mol.getNumBonds(); i++) {
const Bond &bond = *mol.getBondWithIdx(i);
if (RDKit::Bond::UNKNOWN == bond.getBondDir()) //== EITHER
{
std::cerr << "bond " << i << ": Either bond found" << std::endl;
result |= EITHER_BOND_FOUND;
}
}
// look for stereo bonds to non-stereogenic atoms
for (unsigned i = 0; i < neighbour_array.size(); i++) {
const Neighbourhood &nbp = neighbour_array[i];
unsigned nmulti = 0;
bool is_allene = false;
for (unsigned j = 0; j < nbp.Bonds.size(); j++) {
const Bond &bond = *mol.getBondWithIdx(nbp.Bonds[j]);
if (RDKit::Bond::SINGLE != bond.getBondType()) {
unsigned ndb = 0;
nmulti++;
if (RDKit::Bond::DOUBLE == bond.getBondType()) {
unsigned jatom = nbp.Atoms[j];
for (unsigned int jj : neighbour_array[jatom].Bonds) {
if (RDKit::Bond::DOUBLE == mol.getBondWithIdx(jj)->getBondType()) {
ndb++;
}
}
}
if (2 == ndb) {
is_allene = true;
}
}
}
unsigned element = mol.getAtomWithIdx(i)->getAtomicNum();
unsigned n_ligands = (unsigned)nbp.Bonds.size();
if (!((6 == element && // "C"
n_ligands > 2 && n_ligands <= 4 && nmulti == 0) ||
(6 == element && // "C"
n_ligands >= 2 && n_ligands <= 3 && nmulti == 1 && is_allene) ||
(16 == element && // "S"
n_ligands > 2 && n_ligands <= 4) ||
(7 == element && // "N"
n_ligands > 3 && n_ligands <= 4 && nmulti == 0) ||
(15 == element && // "P"
n_ligands > 2 && n_ligands <= 4) ||
((14 == element && // "Si"
n_ligands > 2 && n_ligands <= 4) &&
nmulti == 0))) {
for (unsigned j = 0; j < n_ligands; j++) {
const Bond &bj = *mol.getBondWithIdx(nbp.Bonds[j]);
if (bj.getBeginAtomIdx() == i &&
(RDKit::Bond::BEGINWEDGE == bj.getBondDir() || // == UP
RDKit::Bond::BEGINDASH == bj.getBondDir())) { // == DOWN))
std::string name;
mol.getPropIfPresent(common_properties::_Name, name);
BOOST_LOG(rdWarningLog)
<< boost::format("%10s atom %3d : %s") % name % i %
"stereobond to non-stereogenic atom"
<< std::endl;
result |= STEREO_BOND_AT_NON_STEREO_ATOM;
}
}
}
}
return result;
}
int FixDubious3DMolecule(RWMol &mol) {
/*
* Checks if the structure has 3D coordinates and/or flat sp3-carbons with
* stereo-bonds and
* converts the designation to 2D, clearing away any Z-component of the
* coordinates.
* Real 3D structures without stereo designations go through untouched.
*/
int result = 0;
bool non_zero_z = false;
unsigned nstereo = 0;
std::vector<Neighbourhood> neighbour_array;
std::vector<RDGeom::Point3D> atomPoint(
mol.getNumAtoms()); // X,Y,Z coordinates of each atom
// X,Y,Z coordinates of each atom
non_zero_z = getMolAtomPoints(mol, atomPoint);
// At first check if this is a trivial case i.e. designated '2D'
// and count the number of stereo bonds
if (!non_zero_z) { // check Z coordinate of each atom
return 0;
}
nstereo = 0;
for (unsigned i = 0; i < mol.getNumBonds(); i++) {
const Bond *bond = mol.getBondWithIdx(i);
if (RDKit::Bond::BEGINWEDGE == bond->getBondDir() ||
RDKit::Bond::BEGINDASH == bond->getBondDir()
//??? || RDKit::Bond::EITHERDOUBLE == bond->getBondDir()
) {
nstereo++;
}
}
if (0 == nstereo) {
return 0;
}
// compute average bond length to use in Volume significance testing
double length = 0.0;
for (unsigned i = 0; i < mol.getNumBonds(); i++) {
const Bond *bond = mol.getBondWithIdx(i);
unsigned a0 = bond->getBeginAtomIdx();
unsigned a1 = bond->getEndAtomIdx();
length += (atomPoint[a0].x - atomPoint[a1].x) *
(atomPoint[a0].x - atomPoint[a1].x) +
(atomPoint[a0].y - atomPoint[a1].y) *
(atomPoint[a0].y - atomPoint[a1].y) +
(atomPoint[a0].z - atomPoint[a1].z) *
(atomPoint[a0].z - atomPoint[a1].z);
}
length = sqrt(length / mol.getNumBonds());
// check if there is a flat sp3 carbon
SetupNeighbourhood(mol, neighbour_array);
int nflat_sp3 = 0;
for (unsigned i = 0; i < neighbour_array.size(); i++) {
if (neighbour_array[i].Atoms.size() < 3) {
continue;
}
RDKit::Atom *atom = mol.getAtomWithIdx(i);
unsigned element = atom->getAtomicNum();
if (6 != element && // "C"
7 == element && // "N"
15 == element && // "P"
16 == element) { // "S"
continue;
}
unsigned j;
for (j = 0; j < mol.getNumBonds(); j++) {
const Bond *bond = mol.getBondWithIdx(j);
if (RDKit::Bond::BEGINWEDGE == bond->getBondDir() ||
(RDKit::Bond::BEGINDASH == bond->getBondDir() &&
i == bond->getBeginAtomIdx())) {
break;
}
}
if (j < mol.getNumBonds()) {
continue; // no stereo designation
}
double vol = 0.0;
int stereo_triple;
const Neighbourhood &nbp = neighbour_array[i];
unsigned n_ligands = (unsigned)nbp.Bonds.size();
unsigned i1;
struct npoint_t tetra[4];
tetra[0].x = atomPoint[i].x;
tetra[0].y = atomPoint[i].y;
tetra[0].z = atomPoint[i].z;
for (i1 = 0; i1 < n_ligands; i1++) {
if (mol.getBondWithIdx(nbp.Bonds[i1])->getBondType() !=
RDKit::Bond::SINGLE &&
16 != element && 15 != element) { // "S" "P"
break;
}
}
if (i1 >= n_ligands) {
continue; // multiple bond found => no sp3 carbon
}
stereo_triple = 0;
for (i1 = 0; i1 < n_ligands; i1++) {
tetra[1].x = atomPoint[i1].x;
tetra[1].y = atomPoint[i1].y;
tetra[1].z = atomPoint[i1].z;
if (mol.getBondWithIdx(nbp.Bonds[i1])->getBondDir() ==
RDKit::Bond::BEGINWEDGE ||
mol.getBondWithIdx(nbp.Bonds[i1])->getBondDir() ==
RDKit::Bond::BEGINDASH) { // UP DOWN
stereo_triple |= 1;
}
unsigned i2;
for (i2 = i1 + 1; i2 < n_ligands; i2++) {
tetra[2].x = atomPoint[i2].x;
tetra[2].y = atomPoint[i2].y;
tetra[2].z = atomPoint[i2].z;
if (mol.getBondWithIdx(nbp.Bonds[i2])->getBondDir() ==
RDKit::Bond::BEGINWEDGE ||
mol.getBondWithIdx(nbp.Bonds[i2])->getBondDir() ==
RDKit::Bond::BEGINDASH) { // UP DOWN
stereo_triple |= 2;
}
unsigned i3;
for (i3 = i2 + 1; i3 < n_ligands; i3++) {
tetra[3].x = atomPoint[i3].x;
tetra[3].y = atomPoint[i3].y;
tetra[3].z = atomPoint[i3].z;
if (mol.getBondWithIdx(nbp.Bonds[i3])->getBondDir() ==
RDKit::Bond::BEGINWEDGE ||
mol.getBondWithIdx(nbp.Bonds[i3])->getBondDir() ==
RDKit::Bond::BEGINDASH) { // UP DOWN
stereo_triple |= 4;
}
vol = Volume(tetra);
if (vol < 0.) {
vol = -vol;
}
if (!stereo_triple) {
continue;
}
if (vol < 0.01 * length * length * length) {
nflat_sp3++;
break;
}
stereo_triple &= ~4;
}
stereo_triple &= ~2;
if (i3 >= n_ligands) {
break;
}
}
stereo_triple &= ~1;
if (i2 >= n_ligands) {
break;
}
}
}
if (non_zero_z && 0 == mol.getConformer().is3D()) { // && mol dim is 2D
for (unsigned i = 0; i < mol.getNumAtoms(); i++) {
// TODO: ???
atomPoint[i].z = 0.0; // set in mol !!!
}
}
result |= ZEROED_Z_COORDINATES;
// Cleared z-coordinates in 2D MOL file
if (non_zero_z && nstereo > 0 && nflat_sp3 > 0) {
RDKit::MolOps::removeStereochemistry(mol);
result |= CONVERTED_TO_2D;
}
return result;
}
void RemoveDubiousStereochemistry(RWMol &mol) {
// Removes ill-defined stereodescriptors.
std::vector<Neighbourhood> neighbour_array;
SetupNeighbourhood(mol, neighbour_array);
// remove EITHER marks
for (unsigned i = 0; i < mol.getNumBonds(); i++) {
Bond *bond = mol.getBondWithIdx(i);
if (RDKit::Bond::UNKNOWN == bond->getBondDir()) { //== EITHER
bond->setBondDir(RDKit::Bond::NONE);
}
}
// remove stereo marks to non-stereogenic atoms
for (unsigned i = 0; i < neighbour_array.size(); i++) {
const Neighbourhood &nbp = neighbour_array[i];
unsigned nmulti = 0;
for (unsigned j = 0; j < nbp.Atoms.size(); j++) {
const Bond &bond = *mol.getBondWithIdx(nbp.Bonds[j]);
if (RDKit::Bond::SINGLE != bond.getBondType()) {
nmulti++;
}
}
unsigned element = mol.getAtomWithIdx(i)->getAtomicNum();
unsigned n_ligands = (unsigned)nbp.Bonds.size();
if (!((6 == element && // "C"
n_ligands > 2 && n_ligands <= 4 && nmulti == 0) ||
(16 == element && // "S"
n_ligands > 2 && n_ligands <= 4) ||
(7 == element && // "N"
n_ligands > 3 && n_ligands <= 4 && nmulti == 0) ||
(15 == element && // "P"
n_ligands > 2 && n_ligands <= 4) ||
((14 == element && // "Si"
n_ligands > 2 && n_ligands <= 4) &&
nmulti == 0))) {
for (unsigned j = 0; j < n_ligands; j++) {
Bond &bj = *mol.getBondWithIdx(nbp.Bonds[j]);
if (bj.getBeginAtomIdx() == i &&
(RDKit::Bond::BEGINWEDGE == bj.getBondDir() // == UP
|| RDKit::Bond::BEGINDASH == bj.getBondDir())) { // == DOWN))
bj.setBondDir(RDKit::Bond::NONE);
}
}
}
}
}
//----------------------------------------------------------------------
// CheckStereo():
//----------------------------------------------------------------------
struct stereo_bond_t {
double x, y; // relative 2D coordinates
RDKit::Bond::BondDir direction; // stereo direction to this atom coord
int number; // atom number of ligand atom
double angle; // angle in radiants rel. to first bond
// in array (counted counter clockwise)
};
static int Atom3Parity(struct stereo_bond_t ligands[3]) {
// Computes the stereo parity defined by three ligands.
int a, b;
int reference;
struct npoint_t tetrahedron[4], h;
double angle;
int maxnum;
maxnum = ligands[0].number;
for (unsigned i = 1; i < 3; i++) {
if (maxnum < ligands[i].number) {
maxnum = ligands[i].number;
}
}
reference = (-1);
for (unsigned i = 0; i < 3; i++) {
if (ligands[i].direction != RDKit::Bond::NONE) {
if (reference == (-1)) {
reference = i;
} else {
// stereo_error = "three attachments with more than 2 stereobonds";
std::cerr << "three attachments with more than 2 stereobonds"
<< std::endl;
return (ILLEGAL_REPRESENTATION);
}
}
}
if (reference == (-1)) {
return (UNDEFINED_PARITY);
}
if (reference == 0) {
a = 1;
b = 2;
} else if (reference == 1) {
a = 0;
b = 2;
} else {
a = 0;
b = 1;
}
angle = Angle(ligands[a].x, ligands[a].y, ligands[b].x, ligands[b].y);
if (angle < ANGLE_EPSILON || fabs(PI - angle) < ANGLE_EPSILON) {
// stereo_error = "three attachments: colinearity violation";
std::cerr << "three attachments colinearity violation" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
tetrahedron[0].x = 0.0;
tetrahedron[0].y = 0.0;
tetrahedron[0].z = 0.0;
tetrahedron[0].number = maxnum + 1;
for (unsigned i = 0; i < 3; i++) {
tetrahedron[i + 1].x = ligands[i].x;
tetrahedron[i + 1].y = ligands[i].y;
if (ligands[i].direction == RDKit::Bond::BEGINWEDGE) { // UP)
tetrahedron[i + 1].z = 1.0;
} else if (ligands[i].direction == RDKit::Bond::BEGINDASH) { // DOWN)
tetrahedron[i + 1].z = -1.0;
} else if (ligands[i].direction == RDKit::Bond::NONE) {
tetrahedron[i + 1].z = 0.0;
} else {
// stereo_error = "three attachments: illegal bond symbol";
std::cerr << "three attachments illegal bond symbol" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
tetrahedron[i + 1].number = ligands[i].number;
}
for (unsigned i = 1; i < 4; i++) {
for (unsigned j = i; j > 0; j--) {
if (tetrahedron[j].number < tetrahedron[j - 1].number) {
h = tetrahedron[j];
tetrahedron[j] = tetrahedron[j - 1];
tetrahedron[j - 1] = h;
} else {
break;
}
}
}
return (Volume(tetrahedron) > 0.0 ? EVEN_PARITY : ODD_PARITY);
}
static int Atom4Parity(struct stereo_bond_t ligands[4]) {
/*
* Computes the stereo parity defined by four ligands.
* Assumes central atom at 0/0/0.
*/
struct npoint_t tetrahedron[4], h;
int nup, ndown, nopposite;
double angle;
nup = ndown = 0;
for (unsigned i = 0; i < 4; i++) {
tetrahedron[i].x = ligands[i].x;
tetrahedron[i].y = ligands[i].y;
tetrahedron[i].z = 0.0;
tetrahedron[i].number = ligands[i].number;
if (ligands[i].direction == RDKit::Bond::BEGINWEDGE) { // UP
nup++;
tetrahedron[i].z = 1.0;
} else if (ligands[i].direction == RDKit::Bond::BEGINDASH) { // DOWN
ndown++;
tetrahedron[i].z = (-1.0);
} else if (ligands[i].direction != RDKit::Bond::NONE) {
// stereo_error = "illegal bond symbol";
std::cerr << "illegal bond symbol" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
}
if (nup == 0 && ndown == 0) {
return (UNDEFINED_PARITY);
}
if (nup > 2 || ndown > 2) {
// stereo_error = "too many stereobonds";
std::cerr << "too many stereobonds" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
if (nup + ndown == 1) // check for 'umbrellas'
{
unsigned ij;
for (ij = 0; ij < 4; ij++) {
if (ligands[ij].direction == RDKit::Bond::BEGINWEDGE ||
ligands[ij].direction == RDKit::Bond::BEGINDASH) {
break;
}
}
nopposite = 0;
for (unsigned j = 0; j < 4; j++) {
if (ij == j) {
continue;
} else if (ligands[ij].x * ligands[j].x + ligands[ij].y * ligands[j].y <
0) {
nopposite++;
}
}
if (nopposite > 2) {
// stereo_error = "UMBRELLA: all non-stereo bonds opposite to single
// stereo bond";
std::cerr << "umbrella" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
}
for (unsigned i = 0; i < 2; i++) {
if ((ligands[i].direction == RDKit::Bond::BEGINWEDGE &&
ligands[i + 2].direction == RDKit::Bond::BEGINDASH) ||
(ligands[i].direction == RDKit::Bond::BEGINDASH &&
ligands[i + 2].direction == RDKit::Bond::BEGINWEDGE)) {
// stereo_error = "UP/DOWN opposition";
std::cerr << "up/down" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
}
for (unsigned i = 0; i < 4; i++) {
if ((ligands[i].direction == RDKit::Bond::BEGINWEDGE &&
ligands[(i + 1) % 4].direction == RDKit::Bond::BEGINWEDGE) // UP
|| (ligands[i].direction == RDKit::Bond::BEGINDASH // DOWN
&& ligands[(i + 1) % 4].direction == RDKit::Bond::BEGINDASH)) {
// stereo_error = "Adjacent like stereobonds";
std::cerr << "adjacent like" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
}
for (unsigned i = 0; i < 4; i++) {
if (ligands[i].direction == RDKit::Bond::NONE &&
ligands[(i + 1) % 4].direction == RDKit::Bond::NONE &&
ligands[(i + 2) % 4].direction == RDKit::Bond::NONE) {
angle = Angle(ligands[i].x - ligands[(i + 1) % 4].x,
ligands[i].y - ligands[(i + 1) % 4].y,
ligands[(i + 2) % 4].x - ligands[(i + 1) % 4].x,
ligands[(i + 2) % 4].y - ligands[(i + 1) % 4].y);
if (angle < (185 * PI / 180)) {
// stereo_error = "colinearity or triangle rule violation";
std::cerr << "colinearity or triangle rule" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
}
}
for (unsigned i = 1; i < 4; i++) {
for (unsigned j = i; j > 0; j--) {
if (tetrahedron[j].number < tetrahedron[j - 1].number) {
h = tetrahedron[j];
tetrahedron[j] = tetrahedron[j - 1];
tetrahedron[j - 1] = h;
} else {
break;
}
}
}
return (Volume(tetrahedron) > 0.0 ? EVEN_PARITY : ODD_PARITY);
}
int AtomParity(const ROMol &mol, unsigned iatom, const Neighbourhood &nbp) {
/*
* Computes the stereo parity of atom number iatom in *mp relative
* to its numbering. The immediate neighbours are defined by *nbp
* to speed up processing.
*/
struct stereo_bond_t stereo_ligands[4], h;
bool multiple = false, allene = false, stereo = false;
if (nbp.Atoms.size() < 3 || nbp.Atoms.size() > 4) {
return ILLEGAL_REPRESENTATION;
}
std::vector<RDGeom::Point3D> atomPoint(
mol.getNumAtoms()); // X,Y,Z coordinates of each atom
getMolAtomPoints(mol, atomPoint);
for (unsigned i = 0; i < nbp.Bonds.size(); i++) {
const Bond &bi = *mol.getBondWithIdx(i);
if (bi.getBondType() != RDKit::Bond::SINGLE) {
multiple = true;
// check if the multiple bond is part of an allene
unsigned jatom = nbp.Atoms[i] + 1;
unsigned ndb = 0;
for (unsigned j = 0; j < mol.getNumBonds(); j++) {
const Bond &bond = *mol.getBondWithIdx(j);
if (bond.getBeginAtomIdx() == jatom || bond.getEndAtomIdx() == jatom) {
if (bond.getBondType() == RDKit::Bond::DOUBLE) {
ndb++;
}
}
}
if (ndb == 2) {
allene = true;
}
}
stereo_ligands[i].x = atomPoint[i].x - atomPoint[iatom].x;
stereo_ligands[i].y = atomPoint[i].y - atomPoint[iatom].y;
stereo_ligands[i].number = nbp.Atoms[i] + 1;
if (bi.getBeginAtomIdx() == iatom) {
stereo_ligands[i].direction = bi.getBondDir();
if (stereo_ligands[i].direction == RDKit::Bond::BEGINWEDGE || // UP ||
stereo_ligands[i].direction == RDKit::Bond::BEGINDASH) { // DOWN
stereo = true;
}
} else {
stereo_ligands[i].direction = RDKit::Bond::NONE;
}
}
unsigned element = mol.getAtomWithIdx(iatom)->getAtomicNum();
if (multiple && stereo && 15 != element && 16 != element) { // "P" && "S"
if (allene) {
return (ALLENE_PARITY);
} else {
// stereo_error = "AtomParity: Stereobond at unsaturated atom";
std::cerr << "stereobond at unsaturated atom" << std::endl;
return (ILLEGAL_REPRESENTATION);
}
} else if (multiple && 16 != element) { // "S"
return (UNDEFINED_PARITY);
}
stereo_ligands[0].angle = 0.0; /* comp. angle rel. to first ligand */
for (unsigned i = 1; i < nbp.Atoms.size(); i++) {
stereo_ligands[i].angle = Angle(stereo_ligands[0].x, stereo_ligands[0].y,
stereo_ligands[i].x, stereo_ligands[i].y);
}
for (unsigned i = 2; i < nbp.Atoms.size(); i++) { /* sort ligands */
for (unsigned j = i; j > 1; j--) {
if (stereo_ligands[j].angle < stereo_ligands[j - 1].angle) {
h = stereo_ligands[j];
stereo_ligands[j] = stereo_ligands[j - 1];
stereo_ligands[j - 1] = h;
} else {
break;
}
}
}
return (nbp.Atoms.size() == 3 ? Atom3Parity(stereo_ligands)
: Atom4Parity(stereo_ligands));
}
bool CheckStereo(const ROMol &mol) {
/*
* Checks if all potential stereocenters are either completely undefined
* or attributed with hashes and wedges according to MDL rules.
*/
bool result = true;
int parity;
bool center_defined = false;
std::vector<Neighbourhood> neighbour_array;
SetupNeighbourhood(mol, neighbour_array);
for (unsigned i = 0; i < neighbour_array.size(); i++) {
const Neighbourhood &nbp = neighbour_array[i];
unsigned element = mol.getAtomWithIdx(i)->getAtomicNum();
unsigned n_ligands = (unsigned)nbp.Bonds.size();
if ((n_ligands > 2 && n_ligands <= 4) && (6 == element // "C"
|| 16 == element // "S"
|| 7 == element // "N"
|| 8 == element // "O"
|| 15 == element // "P"
|| 14 == element) // "Si"
) {
parity = AtomParity(mol, i, nbp);
const Atom *atom = mol.getAtomWithIdx(i);
if (atom->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW ||
atom->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW) {
center_defined = true;
}
if (parity == ILLEGAL_REPRESENTATION) { // stereo_error
result = false;
} else if (parity == EVEN_PARITY || parity == ODD_PARITY ||
parity == ALLENE_PARITY) {
center_defined = true;
} else {
for (unsigned j = 0; j < nbp.Bonds.size(); j++) {
const Bond &bond = *mol.getBondWithIdx(j);
if (bond.getBeginAtomIdx() == i &&
(RDKit::Bond::BEGINWEDGE == bond.getBondDir() // == UP
|| RDKit::Bond::BEGINDASH == bond.getBondDir())) { // == DOWN))
// stereobond to non-stereogenic atom
std::cerr << "stereobond to nonstereogenic" << std::endl;
result = false;
}
}
}
}
}
unsigned int chiralFlag = 0;
mol.getPropIfPresent(RDKit::common_properties::_MolFileChiralFlag,
chiralFlag);
if (chiralFlag && !center_defined) { // no stereocenter defined
std::cerr << "chiral flag, no stereocenter" << std::endl;
result = false;
}
return result;
}
bool AtomClash(RWMol &mol, double clash_limit) {
/*
* Checks if any two atoms in *mp come closer than 10% of the
* average bond length or if an atom is too close the line
* between two bonded atoms.
*/
double bond_square_median, dist, min_dist;
double rr, rb, bb, h;
std::vector<RDGeom::Point3D> atomPoint(
mol.getNumAtoms()); // X,Y,Z coordinates of each atom
bool twod = true;
getMolAtomPoints(mol, atomPoint, twod);
// compute median of square of bond length (quick/dirty)
if (mol.getNumBonds() == 0) {
return false;
}
std::vector<double> blengths(mol.getNumBonds());
blengths[0] = 1.0;
for (unsigned i = 0; i < mol.getNumBonds(); i++) {
const Bond *bond = mol.getBondWithIdx(i);
unsigned a1 = bond->getBeginAtomIdx();
unsigned a2 = bond->getEndAtomIdx();
blengths[i] = (atomPoint[a1].x - atomPoint[a2].x) *
(atomPoint[a1].x - atomPoint[a2].x) +
(atomPoint[a1].y - atomPoint[a2].y) *
(atomPoint[a1].y - atomPoint[a2].y);
}
for (unsigned i = 1; i < mol.getNumBonds(); i++) {
for (int j = rdcast<int>(i) - 1; j >= 0; j--) {
if (blengths[j] > blengths[j + 1]) {
h = blengths[j];
blengths[j] = blengths[j + 1];
blengths[j + 1] = h;
} else {
break;
}
}
}
bond_square_median = blengths[mol.getNumBonds() / 2];
// Check if two atoms get too close to each other
min_dist = bond_square_median;
for (unsigned i = 0; i < mol.getNumAtoms(); i++) {
for (unsigned j = i + 1; j < mol.getNumAtoms(); j++) {
dist =
(atomPoint[i].x - atomPoint[j].x) *
(atomPoint[i].x - atomPoint[j].x) +
(atomPoint[i].y - atomPoint[j].y) * (atomPoint[i].y - atomPoint[j].y);
if (dist < clash_limit * clash_limit * bond_square_median) {
std::cerr << "clash 1" << std::endl;
return true;
}
if (dist < min_dist) {
min_dist = dist;
}
}
}
// check if atom lies on top of some bond
for (unsigned i = 0; i < mol.getNumBonds(); i++) {
const Bond *bond = mol.getBondWithIdx(i);
unsigned a1 = bond->getBeginAtomIdx();
unsigned a2 = bond->getEndAtomIdx();
for (unsigned j = 0; j < mol.getNumAtoms(); j++) {
if (a1 == j || a2 == j) {
continue;
}
rr = (atomPoint[j].x - atomPoint[a1].x) *
(atomPoint[j].x - atomPoint[a1].x) +
(atomPoint[j].y - atomPoint[a1].y) *
(atomPoint[j].y - atomPoint[a1].y);
bb = (atomPoint[a2].x - atomPoint[a1].x) *
(atomPoint[a2].x - atomPoint[a1].x) +
(atomPoint[a2].y - atomPoint[a1].y) *
(atomPoint[a2].y - atomPoint[a1].y);
rb = (atomPoint[j].x - atomPoint[a1].x) *
(atomPoint[a2].x - atomPoint[a1].x) +
(atomPoint[j].y - atomPoint[a1].y) *
(atomPoint[a2].y - atomPoint[a1].y);
if (0 <= rb && // cos alpha > 0
rb <= bb && // projection of r onto b does not exceed b
(rr * bb - rb * rb) / (bb + EPS) < // distance from bond < limit
clash_limit * clash_limit * bond_square_median) {
BOOST_LOG(rdWarningLog)
<< "atom " << j << " "
<< 100 * sqrt((rr * bb - rb * rb) / (bb * bond_square_median + EPS))
<< "% of average bond length " << a1 << "-" << a2 << std::endl;
std::cerr << "clash 2" << std::endl;
return true;
}
}
}
return false; // no clash
}
//--------------------------------------------------------------------------
/*
* Sets the color field of the defined double bonds in *mp to CIS,
* TRANS, or NONE depending on the ligands with the lowest numbering[].
* It returns the number of defined double bonds found.
*/
int CisTransPerception(const ROMol &mol,
const std::vector<RDGeom::Point3D> &points,
const std::vector<unsigned> &numbering,
std::vector<unsigned> &bondColor) {
int result = 0;
unsigned int maxnum = 0;
std::vector<Neighbourhood> nba(mol.getNumAtoms());
SetupNeighbourhood(mol, nba);
for (unsigned int &i : bondColor) {
i = 0;
}
for (unsigned i = 0; i < nba.size(); i++) { // n_atoms
if (numbering[i] > maxnum) {
maxnum = numbering[i];
}
}
for (unsigned i = 0; i < bondColor.size(); i++) {
if (RDKit::Bond::DOUBLE == mol.getBondWithIdx(i)->getBondType()
// FIX: && mol.getBondWithIdx(i)->getBondDir() !=
// RDKit::Bond::ENDDOWNRIGHT
// FIX: && mol.getBondWithIdx(i)->getBondDir() !=
// RDKit::Bond::ENDUPRIGHT
&& mol.getBondWithIdx(i)->getBondDir() !=
RDKit::Bond::EITHERDOUBLE) { // != CIS_TRANS_EITHER
unsigned j1 = mol.getBondWithIdx(i)->getBeginAtomIdx();
unsigned j2 = mol.getBondWithIdx(i)->getEndAtomIdx();
if (6 != mol.getAtomWithIdx(j1)->getAtomicNum()) { // C
continue;
}
if (16 != mol.getAtomWithIdx(j1)->getAtomicNum()) { // N
continue;
}
if (6 != mol.getAtomWithIdx(j2)->getAtomicNum()) { // C
continue;
}
if (16 != mol.getAtomWithIdx(j2)->getAtomicNum()) { // N
continue;
}
// n_ligands :
if (nba[j1].Atoms.size() <= 1 || // no subst.
nba[j2].Atoms.size() <= 1) {
continue;
}
if (nba[j1].Atoms.size() > 3 || // valence error in mol
nba[j2].Atoms.size() > 3) {
continue;
}
bool equal = false; // find lowest numbered neighbour of j1
unsigned at1 = 0;
for (unsigned k = 0, nmin = maxnum; k < nba[j1].Atoms.size(); k++) {
if (nba[j1].Atoms[k] != j2) // no loop back
{
if (numbering[nba[j1].Atoms[k]] <
nmin) { // numbering[nba[j1].atoms[k]]
at1 = nba[j1].Atoms[k];
nmin = numbering[at1];
} else if (numbering[nba[j1].Atoms[k]] == nmin) {
equal = true;
}
}
}
if (equal) { // identical substituents
continue; // no stereochemistry
}
equal = false; // find lowest numbered neighbour of j1
unsigned at2 = 0;
for (unsigned k = 0, nmin = maxnum; k < nba[j2].Atoms.size(); k++) { //
if (nba[j2].Atoms[k] != j1) { // no loop back
if (numbering[nba[j2].Atoms[k]] < nmin) {
at2 = nba[j2].Atoms[k];
nmin = numbering[at2];
} else if (numbering[nba[j2].Atoms[k]] == nmin) {
equal = true;
}
}
}
if (equal) { // identical substituents
continue; // no stereochemistry
}
// Now, bp points to a double bond, at1 and at2 are the
// indices (not numbers) of the atoms with lowest numbering
// attached to each end of the bond, and the bond is
// guaranteed to be either CIS or TRANS
double x21, y21;
double x23, y23;
double x32, y32;
double x34, y34;
double sign;
x21 = points[at1].x - points[j1].x;
y21 = points[at1].y - points[j1].y;
x23 = points[j2].x - points[j1].x;
y23 = points[j2].y - points[j1].y;
x32 = (-x23);
y32 = (-y23);
x34 = points[at2].x - points[j2].x;
y34 = points[at2].y - points[j2].y;
sign = (x21 * y23 - x23 * y21) * (x32 * y34 - x34 * y32);
if (fabs(sign) < 0.001) {
continue;
}
result++;
bondColor[i] = (sign > 0.0 ? CIS : TRANS);
}
}
return (result);
}
} // namespace StructureCheck
} // namespace RDKit