// // Copyright (c) 2014-2024, Novartis Institutes for BioMedical Research 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 "MIFDescriptors.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif namespace { constexpr double M_55D = 0.95993108859688126730; // angle C-O-H , C-N-H constexpr double M_70_5D = 1.23045712265600235173; constexpr double M_110D = 1.91986217719376253461; constexpr double CUTOFF = 0.001; constexpr double MIN_CUTOFF_VAL = CUTOFF * CUTOFF; } // namespace namespace RDMIF { std::unique_ptr constructGrid( const RDKit::ROMol &mol, int confId, double margin, double spacing) { PRECONDITION(mol.getNumConformers(), "No conformers available for molecule."); const std::vector &ptVect = mol.getConformer(confId).getPositions(); RDGeom::Point3D originPt(0.0, 0.0, 0.0); RDGeom::Point3D marginPt(margin, margin, margin); const RDGeom::Point3D &firstPoint = (!ptVect.empty() ? ptVect.front() : originPt); auto minPt = firstPoint; auto maxPt = minPt; for (const auto &pt : ptVect) { for (auto i = 0u; i < pt.dimension(); ++i) { minPt[i] = std::min(minPt[i], pt[i]); maxPt[i] = std::max(maxPt[i], pt[i]); } } minPt -= marginPt; maxPt += marginPt; auto res = std::make_unique( maxPt.x - minPt.x, maxPt.y - minPt.y, maxPt.z - minPt.z, spacing, &minPt); return res; } VdWaals::VdWaals(const RDKit::ROMol &mol, int confId, double cutoff) { d_cutoff = std::max(cutoff * cutoff, MIN_CUTOFF_VAL); d_nAtoms = mol.getNumAtoms(); d_pos.reserve(3 * d_nAtoms); d_R_star_ij.reserve(d_nAtoms); d_wellDepth.reserve(d_nAtoms); // this will throw a ConformerException if confId does not exist const RDKit::Conformer &conf = mol.getConformer(confId); d_mol.reset(new RDKit::ROMol(mol, false, conf.getId())); } void VdWaals::fillVectors() { const auto &conf = d_mol->getConformer(); for (unsigned int i = 0; i < d_nAtoms; i++) { const RDGeom::Point3D &pt = conf.getAtomPos(i); d_pos.push_back(pt.x); d_pos.push_back(pt.y); d_pos.push_back(pt.z); fillVdwParamVectors(i); } } MMFFVdWaals::MMFFVdWaals(const RDKit::ROMol &mol, int confId, unsigned int probeAtomType, bool scaling, double cutoff) : VdWaals::VdWaals(mol, confId, cutoff), d_scaling(scaling) { d_props.reset(new RDKit::MMFF::MMFFMolProperties(*d_mol)); if (!d_props->isValid()) { throw ValueErrorException( "No MMFF atom types available for at least one atom in molecule."); } d_mmffVdW = RDKit::MMFF::DefaultParameters::getMMFFVdW(); d_probeParams = (*d_mmffVdW)(probeAtomType); fillVectors(); } void MMFFVdWaals::fillVdwParamVectors(unsigned int atomIdx) { PRECONDITION(atomIdx < d_mol->getNumAtoms(), "atomIdx out of bounds"); const auto iAtomType = d_props->getMMFFAtomType(atomIdx); auto params = (*d_mmffVdW)(iAtomType); auto rStarIJ = ForceFields::MMFF::Utils::calcUnscaledVdWMinimum( d_mmffVdW, params, d_probeParams); d_R_star_ij.push_back(rStarIJ); d_wellDepth.push_back(ForceFields::MMFF::Utils::calcUnscaledVdWWellDepth( rStarIJ, params, d_probeParams)); // scaling for taking undirected H-Bonds into account if (d_scaling) { ForceFields::MMFF::Utils::scaleVdWParams(d_R_star_ij[atomIdx], d_wellDepth[atomIdx], d_mmffVdW, params, d_probeParams); } } UFFVdWaals::UFFVdWaals(const RDKit::ROMol &mol, int confId, const std::string &probeAtomType, double cutoff) : VdWaals::VdWaals(mol, confId, cutoff) { d_uffParamColl = ForceFields::UFF::ParamCollection::getParams(); d_probeParams = (*d_uffParamColl)(probeAtomType); const auto [params, haveParams] = RDKit::UFF::getAtomTypes(mol); if (!haveParams) { throw ValueErrorException( "No UFF atom types available for at least one atom in molecule."); } d_params = std::move(params); fillVectors(); } void UFFVdWaals::fillVdwParamVectors(unsigned int atomIdx) { PRECONDITION(atomIdx < d_mol->getNumAtoms(), "atomIdx out of bounds"); d_R_star_ij.push_back(d_probeParams->x1 * d_params[atomIdx]->x1); d_wellDepth.push_back(ForceFields::UFF::Utils::calcNonbondedDepth( d_probeParams, d_params[atomIdx])); } double VdWaals::operator()(double x, double y, double z, double thres) const { double res = 0.0; for (unsigned int i = 0, j = 0; i < d_nAtoms; ++i) { auto temp = x - d_pos[j++]; auto dist2 = temp * temp; temp = y - d_pos[j++]; dist2 += temp * temp; temp = z - d_pos[j++]; dist2 += temp * temp; if (dist2 < thres) { dist2 = std::max(dist2, d_cutoff); res += calcEnergy(dist2, d_R_star_ij[i], d_wellDepth[i]); } } return res; } double UFFVdWaals::calcEnergy(double dist2, double x_ij, double wellDepth) const { double r6 = x_ij / dist2; r6 *= r6 * r6; double r12 = r6 * r6; return wellDepth * (r12 - 2.0 * r6); } double MMFFVdWaals::calcEnergy(double dist2, double R_star_ij, double wellDepth) const { return ForceFields::MMFF::Utils::calcVdWEnergy(sqrt(dist2), R_star_ij, wellDepth); } namespace CoulombDetail { constexpr double prefactor = 1 / (4.0 * 3.141592 * 8.854188) * 1.602 * 1.602 * 6.02214129 * 10000; } Coulomb::Coulomb(const std::vector &charges, const std::vector &positions, double probeCharge, bool absVal, double alpha, double cutoff) : d_nAtoms(charges.size()), d_absVal(absVal), d_cutoff(cutoff * cutoff), d_probe(probeCharge), d_alpha(alpha), d_charges(charges) { PRECONDITION(d_charges.size() == positions.size(), "Lengths of positions and charges vectors do not match."); d_pos.reserve(3 * d_nAtoms); for (const auto &position : positions) { d_pos.push_back(position.x); d_pos.push_back(position.y); d_pos.push_back(position.z); } if (fabs(d_alpha) < MIN_CUTOFF_VAL) { d_softcore = false; if (d_cutoff < MIN_CUTOFF_VAL) { d_cutoff = CUTOFF; } } else { d_softcore = true; } } Coulomb::Coulomb(const RDKit::ROMol &mol, int confId, double probeCharge, bool absVal, const std::string &prop, double alpha, double cutoff) : d_nAtoms(mol.getNumAtoms()), d_absVal(absVal), d_cutoff(cutoff * cutoff), d_probe(probeCharge), d_alpha(alpha) { d_charges.reserve(d_nAtoms); d_pos.reserve(3 * d_nAtoms); RDKit::Conformer conf = mol.getConformer(confId); for (unsigned int i = 0; i < d_nAtoms; ++i) { d_charges.push_back(mol.getAtomWithIdx(i)->getProp(prop)); const RDGeom::Point3D &pt = conf.getAtomPos(i); d_pos.push_back(pt.x); d_pos.push_back(pt.y); d_pos.push_back(pt.z); } if (fabs(d_alpha) < MIN_CUTOFF_VAL) { d_softcore = false; if (d_cutoff < MIN_CUTOFF_VAL) { d_cutoff = CUTOFF; } } else { d_softcore = true; } } double Coulomb::operator()(double x, double y, double z, double thres) const { double res = 0.0, dist2, temp; if (d_softcore) { for (unsigned int i = 0, j = 0; i < d_nAtoms; i++) { temp = x - d_pos[j++]; dist2 = temp * temp; temp = y - d_pos[j++]; dist2 += temp * temp; temp = z - d_pos[j++]; dist2 += temp * temp; if (dist2 < thres) { res += d_charges[i] * (1.0 / sqrt(d_alpha + dist2)); } } } else { for (unsigned int i = 0, j = 0; i < d_nAtoms; i++) { temp = x - d_pos[j++]; dist2 = temp * temp; temp = y - d_pos[j++]; dist2 += temp * temp; temp = z - d_pos[j++]; dist2 += temp * temp; if (dist2 < thres) { dist2 = std::max(dist2, d_cutoff); res += d_charges[i] * (1.0 / sqrt(dist2)); } } } res *= CoulombDetail::prefactor * d_probe; if (d_absVal) { res = -fabs( res); // takes the negative absolute value of the interaction energy } return res; } CoulombDielectric::CoulombDielectric( const std::vector &charges, const std::vector &positions, double probeCharge, bool absVal, double alpha, double cutoff, double epsilon, double xi) : d_nAtoms(charges.size()), d_absVal(absVal), d_cutoff(cutoff * cutoff), d_probe(probeCharge), d_epsilon(epsilon), d_xi(xi), d_alpha(alpha), d_charges(charges) { PRECONDITION(d_charges.size() == positions.size(), "Lengths of position and charge vectors do not match."); d_dielectric = (d_xi - d_epsilon) / (d_xi + d_epsilon); std::vector neighbors(positions.size(), 0); d_dists.resize(d_nAtoms); d_sp.reserve(d_nAtoms); d_pos.reserve(3 * d_nAtoms); for (unsigned int i = 0; i < positions.size(); ++i) { d_pos.push_back(positions[i].x); d_pos.push_back(positions[i].y); d_pos.push_back(positions[i].z); for (unsigned int j = i + 1; j < positions.size(); ++j) { double dis = (positions[j] - positions[i]).length(); if (dis < 4.0) { ++neighbors[i]; ++neighbors[j]; } } } for (unsigned int neighbor : neighbors) { switch (neighbor) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: d_sp.push_back(0.0); break; case 7: d_sp.push_back(.4); break; case 8: d_sp.push_back(.9); break; case 9: d_sp.push_back(1.4); break; case 10: d_sp.push_back(1.9); break; case 11: d_sp.push_back(2.6); break; default: d_sp.push_back(4.0); } } if (fabs(d_alpha) < MIN_CUTOFF_VAL) { d_softcore = false; if (d_cutoff < MIN_CUTOFF_VAL * MIN_CUTOFF_VAL) { d_cutoff = CUTOFF * CUTOFF; } } else { d_softcore = true; } } CoulombDielectric::CoulombDielectric(const RDKit::ROMol &mol, int confId, double probeCharge, bool absVal, const std::string &prop, double alpha, double cutoff, double epsilon, double xi) : d_nAtoms(mol.getNumAtoms()), d_absVal(absVal), d_cutoff(cutoff * cutoff), d_probe(probeCharge), d_epsilon(epsilon), d_xi(xi), d_alpha(alpha) { PRECONDITION(mol.getNumConformers() > 0, "No Conformers for Molecule"); d_charges.reserve(d_nAtoms); d_sp.reserve(d_nAtoms); d_dists.resize(d_nAtoms); d_pos.reserve(3 * d_nAtoms); RDKit::Conformer conf = mol.getConformer(confId); for (unsigned int i = 0; i < d_nAtoms; ++i) { d_charges.push_back(mol.getAtomWithIdx(i)->getProp(prop)); const RDGeom::Point3D &pt = conf.getAtomPos(i); d_pos.push_back(pt.x); d_pos.push_back(pt.y); d_pos.push_back(pt.z); } d_dielectric = (d_xi - d_epsilon) / (d_xi + d_epsilon); std::vector neighbors(d_charges.size(), 0); for (unsigned int i = 0; i < d_charges.size() - 1; ++i) { for (unsigned int j = i + 1; j < d_charges.size(); ++j) { double temp = d_pos[j * 3] - d_pos[i * 3]; double dist2 = temp * temp; temp = d_pos[j * 3 + 1] - d_pos[i * 3 + 1]; dist2 += temp * temp; temp = d_pos[j * 3 + 2] - d_pos[i * 3 + 2]; dist2 += temp * temp; if (dist2 < 16.0) { neighbors[i]++; neighbors[j]++; } } } for (unsigned int neighbor : neighbors) { switch (neighbor) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: d_sp.push_back(0.0); break; case 7: d_sp.push_back(.4); break; case 8: d_sp.push_back(.9); break; case 9: d_sp.push_back(1.4); break; case 10: d_sp.push_back(1.9); break; case 11: d_sp.push_back(2.6); break; default: d_sp.push_back(4.0); } } if (fabs(d_alpha) < MIN_CUTOFF_VAL) { d_softcore = false; if (d_cutoff < MIN_CUTOFF_VAL * MIN_CUTOFF_VAL) { d_cutoff = CUTOFF * CUTOFF; } } else { d_softcore = true; } } double CoulombDielectric::operator()(double x, double y, double z, double thres) const { int neigh = 0; double res = 0.0, sq = 0.0; for (unsigned int i = 0, j = 0; i < d_nAtoms; ++i, j += 3) { double temp = x - d_pos[j]; double dist2 = temp * temp; temp = y - d_pos[j + 1]; dist2 += temp * temp; temp = z - d_pos[j + 2]; dist2 += temp * temp; d_dists[i] = dist2; if (dist2 < 16.0) { neigh += 1; } } switch (neigh) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: sq = 0.0; break; case 7: sq = .4; break; case 8: sq = .9; break; case 9: sq = 1.4; break; case 10: sq = 1.9; break; case 11: sq = 2.6; break; default: sq = 4.0; } if (d_softcore) { for (unsigned int i = 0; i < d_nAtoms; i++) { if (d_dists[i] < thres) { res += d_charges[i] * (1 / sqrt(d_alpha + d_dists[i]) + d_dielectric / sqrt(d_alpha + d_dists[i] + 4.0 * d_sp[i] * sq)); } } } else { for (unsigned int i = 0; i < d_nAtoms; i++) { if (d_dists[i] < thres) { double dist2 = std::max(d_dists[i], d_cutoff); res += d_charges[i] * (1 / sqrt(dist2) + d_dielectric / sqrt(dist2 + 4.0 * d_sp[i] * sq)); } } } res *= CoulombDetail::prefactor * (1 / d_xi) * d_probe; if (d_absVal) { res = -fabs( res); // takes the negative absolute value of the interaction energy } return res; } namespace HBondDetail { const double em[2][2] = {{-8.368, -11.715}, {-11.715, -16.736}}; const double rm[2][2] = {{3.2, 3.0}, {3.0, 2.8}}; const double K1 = 562.25380293; const double K2 = 0.11697778; const double bondlength[2] = {-0.972, -1.019}; double cos_2(double t, double, double) { if (t < M_PI_2) { double temp = cos(t); temp *= temp; return temp; } else { return 0.0; } }; double cos_2_0(double t, double t_0 = 0.0, double t_i = 1.0) { double temp; if (t_i < M_55D) { temp = cos(t_0); temp *= temp; return temp; } else if (t_i < M_PI_2) { temp = cos(t); temp *= temp; return temp; } else { return 0.0; } }; double cos_2_rot(double t, double, double) { t -= M_70_5D; if (t < M_PI_2) { double temp = cos(t); temp *= temp; return temp; } else { return 0.0; } }; double cos_4(double t, double, double) { if (t < M_PI_2) { double temp = cos(t); temp *= temp; temp *= temp; return temp; } else { return 0.0; } }; double cos_4_rot(double t, double, double) { t -= M_70_5D; if (t < M_PI_2) { double temp = cos(t); temp *= temp; temp *= temp; return temp; } else { return 0.0; } }; double cos_6(double t, double, double) { if (t < M_PI_2) { double temp = cos(t); temp *= temp; temp *= temp * temp; return temp; } else { return 0.0; } }; double cos_6_rot(double t, double, double) { t -= M_70_5D; if (t < M_PI_2) { double temp = cos(t); temp *= temp; temp *= temp * temp; return temp; } else { return 0.0; } }; double cos_acc(double, double t_0, double t_i) { double temp; if (t_i < M_PI_2) { temp = cos(t_0) * (0.9 + 0.1 * sin(2 * t_i)); return temp; } else if (t_i < M_110D) { temp = cos(t_i); temp *= temp; temp = K2 - temp; temp *= temp * temp; temp *= cos(t_0) * K1; return temp; } else { return 0.0; } }; double no_dep(double, double, double) { return 1.0; }; } // namespace HBondDetail HBond::HBond(const RDKit::ROMol &mol, int confId, const std::string &probeAtomType, bool fixed, double cutoff) : d_cutoff(cutoff * cutoff) { if (d_cutoff < (MIN_CUTOFF_VAL * MIN_CUTOFF_VAL)) { d_cutoff = CUTOFF * CUTOFF; } if (probeAtomType == "O") { d_DAprop = 'D'; d_probetype = O; } else if (probeAtomType == "OH") { d_DAprop = 'A'; d_probetype = O; } else if (probeAtomType == "N") { d_DAprop = 'D'; d_probetype = N; } else if (probeAtomType == "NH") { d_DAprop = 'A'; d_probetype = N; } else { const std::string msg = "Probe atom type not supported: " + probeAtomType; BOOST_LOG(rdErrorLog) << msg << std::endl; throw ValueErrorException(msg); } d_nInteract = mol.getNumAtoms(); // number of atoms = highest possible number // of interactions std::vector specialAtoms; findSpecials(mol, confId, fixed, specialAtoms); if (d_DAprop == 'A') { if (fixed) { findAcceptors(mol, confId, specialAtoms); } else { findAcceptorsUnfixed(mol, confId, specialAtoms); } } else if (d_DAprop == 'D') { if (fixed) { findDonors(mol, confId, specialAtoms); } else { findDonorsUnfixed(mol, confId, specialAtoms); } } else { // this should never be the case BOOST_LOG(rdErrorLog) << "HBond: unknown target property d_DAprop: " << d_DAprop << std::endl; } d_nInteract = d_targettypes.size(); // updated to number of interactions d_eneContrib.resize(d_nInteract, 0); d_vectTargetProbe.resize(d_nInteract * 3, 0); POSTCONDITION( d_nInteract * 3 == d_pos.size(), "Error in constructing H-Bond descriptor: Vector length mismatch (target atom types)."); POSTCONDITION( d_nInteract * 3 == d_direction.size(), "Error in constructing H-Bond descriptor: Vector length mismatch (bond directions)."); POSTCONDITION( d_nInteract == d_function.size(), "Error in constructing H-Bond descriptor: Vector length mismatch (angular functions)."); POSTCONDITION( d_nInteract * 3 == d_plane.size(), "Error in constructing H-Bond descriptor: Vector length mismatch (lone pair planes)."); POSTCONDITION( d_nInteract == d_lengths.size(), "Error in constructing H-Bond descriptor: Vector length mismatch (bondlengths)."); } unsigned int HBond::findSpecials(const RDKit::ROMol &mol, int confId, bool fixed, std::vector &specials) { using namespace HBondDetail; RDKit::MatchVectType matches; RDKit::ROMol::ADJ_ITER nbrIdx, endNbrs; RDGeom::Point3D pos, dir, hbonddir, plane, bondDirection[12]; unsigned int nbrs; unsigned int match = 0, nMatches = 0; const RDKit::Conformer &conf = mol.getConformer(confId); // RDKit::RWMol thr = *RDKit::SmilesToMol("C[C@H]([C@@H](C(=O))N)O"); // //threonine, serine itself is a substructure of this static const auto ser = RDKit::v2::SmilesParse::MolFromSmiles( "[CH2]([C@@H](C(=O))N)O"); // serine // RDKit::RWMol his = *RDKit::SmilesToMol("Cc1cnc[nH]1"); //imidazole residue, // is correctly taken into account in 'normal' treatment match = RDKit::SubstructMatch(mol, *ser, matches); nMatches += match; for (auto &matche : matches) { const auto *atom = mol.getAtomWithIdx(matche.second); if (atom->getAtomicNum() == 8) { boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); pos = conf.getAtomPos(matche.second); if (d_DAprop == 'A') { nbrs = 0; while (nbrIdx != endNbrs) { // loop over atoms if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { hbonddir = pos - conf.getAtomPos(*nbrIdx); hbonddir.normalize(); } else { bondDirection[nbrs] = pos - conf.getAtomPos(*nbrIdx); bondDirection[nbrs].normalize(); ++nbrs; } ++nbrIdx; } if (nbrs == 2) { dir = bondDirection[0] + hbonddir; plane = bondDirection[0].crossProduct(hbonddir); // X-O-Y plane vector plane = plane.crossProduct(dir); // lp plane vector if (fixed) { addVectElements(O, &cos_2_0, pos, dir, plane); } else { addVectElements(O, &cos_4_rot, pos, bondDirection[0]); } specials.push_back(matche.second); } } else if (d_DAprop == 'D') { if (fixed) { while (nbrIdx != endNbrs) { if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // hydrogen neighbor necessary for H bond donation dir = conf.getAtomPos(*nbrIdx) - pos; addVectElements(O, &cos_6, pos, dir); } ++nbrIdx; } } else { while (nbrIdx != endNbrs) { if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // search for non-hydrogen bond direction dir = pos - conf.getAtomPos(*nbrIdx); addVectElements(O, &cos_6_rot, pos, dir); } ++nbrIdx; } } specials.push_back(matche.second); } else { // this should never be the case BOOST_LOG(rdErrorLog) << "HBond::operator(): unknown target property d_DAprop: " << d_DAprop << std::endl; } } } return nMatches; } /* General structure of findAcceptors, findAcceptorsUnfixed, findDonors, * findDonorsUnfixed functions: loop over all atoms: * - check whether atom was already treated in specialAtoms * - switch ( atomicNum ): find atoms which are able to donate/accept * hydrogen bonds if able: * - switch ( number of neighbors ): find the right geometry * - check for charge or aromatic neighborhood * - calculate adequate hydrogen bond direction vector, * lone pair plane vector * - choose correct angular function * - add atomtype, angular function, position of atom, * hydrogen bond direction vector and lone pair plane vector to vectors for * calculating the interaction returns number of added interactions */ unsigned int HBond::findAcceptors(const RDKit::ROMol &mol, int confId, const std::vector &specials) { using namespace HBondDetail; const RDKit::Conformer &conf = mol.getConformer(confId); // get conformer of molecule RDKit::ROMol::ADJ_ITER nbrIdx, endNbrs; RDKit::ROMol::ADJ_ITER secnbrIdx, secendNbrs; RDGeom::Point3D pos, dir, plane, bondDirection[12]; unsigned int nbrs; // no of neigbors unsigned int interact = 0; // no of interactions bool aromaticnbr; // aromatic neighbor atom? for (unsigned int i = 0; i < d_nInteract; i++) { // loop over all atoms if (std::find(specials.begin(), specials.end(), i) != specials.end()) { // check whether atom was already treated specially interact++; continue; } const auto *atom = mol.getAtomWithIdx(i); // get ptr to atom switch (atom->getAtomicNum()) { // find atoms able to donate hydrogen bonds case 7: // Nitrogen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neighbors pos = conf.getAtomPos(i); // get position of atom nbrs = 0; while (nbrIdx != endNbrs) { // loop over all neigbors bondDirection[nbrs] = pos - conf.getAtomPos(*nbrIdx); // store bond vectors in an array bondDirection[nbrs].normalize(); ++nbrs; // count neigbors ++nbrIdx; } switch (nbrs) { // number of neigbors case 1: // sp, eg. nitriles if (atom->getFormalCharge() <= 0) { // no positively charged nitrogens addVectElements( N, &cos_2, pos, bondDirection[0]); // no differentiation between in-plane and // out-of-plane angle, plane not needed interact++; } break; case 2: // eg. imines, heterocycles if (atom->getFormalCharge() <= 0) { // no positively charged nitrogen dir = bondDirection[0] + bondDirection[1]; // get hydrogen bond direction plane = bondDirection[0].crossProduct( bondDirection[1]); // normal vector of lone pair plane = // plane of three atoms addVectElements(N, &cos_2, pos, dir, plane); interact++; } break; case 3: // amines, iminium ions if (atom->getFormalCharge() <= 0 && !(RDKit::MolOps::atomHasConjugatedBond( atom))) { // no positively charged nitrogen, no conjugated // nitrogen (amide bonds!) dir = bondDirection[0] + bondDirection[1] + bondDirection[2]; // get hydrogen bond direction addVectElements(N, &cos_2, pos, dir); // no differentiation between in-plane and // out-of-plane angle, plane not needed interact++; } break; case 0: // unbound nitrogen atoms, no hydrogen bonding case 4: // ammonium ions, no hydrogen bonding break; default: // more than four bonds: not possible with nitrogen BOOST_LOG(rdErrorLog) << "HBond: Nitrogen atom bound to more than 4 neighbor atoms: Atom: " << i << std::endl; } break; case 8: // Oxygen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neighbors pos = conf.getAtomPos(i); // get position of atom nbrs = 0; aromaticnbr = false; while (nbrIdx != endNbrs) { // loop over neighbors bondDirection[nbrs] = pos - conf.getAtomPos(*nbrIdx); // store bond vectors in an array bondDirection[nbrs].normalize(); // normalization if (mol.getAtomWithIdx(*nbrIdx) ->getIsAromatic()) { // check whether neighbor is aromatic // (phenolic oxygens) aromaticnbr = true; } ++nbrs; // count neighbors ++nbrIdx; } switch (nbrs) { // no of neighbors case 1: // carbonyl, carboxyl C=O, X=O (X=S,P,...), anions // (alcoholates, carboxylates) --nbrIdx; boost::tie(secnbrIdx, secendNbrs) = mol.getAtomNeighbors( mol.getAtomWithIdx(*nbrIdx)); // get neighbors of neighbor atom while (secnbrIdx != secendNbrs) { // loop over neighbors of neighbor atom if ((*secnbrIdx) != i) { // second neighbor should not be the oxygen itself // bond direction of neighbor atom to second neighbor atom dir = conf.getAtomPos(*secnbrIdx) - conf.getAtomPos(*nbrIdx); break; // we only need one bond vector } ++secnbrIdx; } plane = bondDirection[0].crossProduct(dir); // lp plane vector if (atom->getFormalCharge() == 0) { // carbonyl, carboxyl C=O, X=O addVectElements(O, &cos_acc, pos, bondDirection[0], plane); interact++; } else if (atom->getFormalCharge() == -1) { // anion, eg. phenolate, carboxylic acid if (RDKit::MolOps::atomHasConjugatedBond(atom)) { // charged oxygen in conjungated system, eg phenolates or // carboxylates addVectElements(O, &cos_acc, pos, bondDirection[0], plane); } else { // non-conjugated anion, eg sp3-alcoholate addVectElements(O, &cos_2, pos, bondDirection[0], plane); } interact++; } break; case 2: // alcohols, ethers, carboxyl OH dir = bondDirection[0] + bondDirection[1]; // get bond direction plane = bondDirection[0].crossProduct( bondDirection[1]); // X-O-Y plane vector plane = plane.crossProduct(dir); // lp plane vector if (aromaticnbr) { // if oxygen bound to aromatic system, eg. // phenol addVectElements(O, &cos_2, pos, dir, plane); } else { // all other addVectElements(O, &cos_2_0, pos, dir, plane); } interact++; break; case 0: // oxygen atoms, no hydrogen bonding case 3: // only with positively charged atom possible, no hydrogen // bonding break; default: // more than 3 neighbors: not possible with oxygen BOOST_LOG(rdErrorLog) << "HBond: Oxygen atom bound to more than 3 neighbor atoms: Atom: " << i << std::endl; } break; // Halogens case 9: // F case 17: // Cl case 35: // Br case 53: // I pos = conf.getAtomPos(i); // get position of atom dir = RDGeom::Point3D(1.0, 1.0, 1.0); // no directionality needed addVectElements( N, &no_dep, pos, dir); // type of halogens ~ nitrogen; no lp plane needed interact++; break; default: break; } } return interact; } unsigned int HBond::findAcceptorsUnfixed( const RDKit::ROMol &mol, int confId, const std::vector &specials) { using namespace HBondDetail; const RDKit::Conformer &conf = mol.getConformer(confId); // get conformer of molecule RDKit::ROMol::ADJ_ITER nbrIdx, endNbrs; RDKit::ROMol::ADJ_ITER secnbrIdx, secendNbrs; RDGeom::Point3D pos, dir, plane, bondDirection[12]; unsigned int nbrs, nonhnbrs; // no of neighbors, no of nonhydrogen - neighbors unsigned int interact = 0; // no of interactions bool aromaticnbr; // aromatic neighbor atom? for (unsigned int i = 0; i < d_nInteract; i++) { // loop over all atoms if (std::find(specials.begin(), specials.end(), i) != specials.end()) { // check whether atom was already treated specially interact++; continue; } const auto *atom = mol.getAtomWithIdx(i); // get pointer to atom switch (atom->getAtomicNum()) { // find atoms able to accept hydrogen bonds // (O, N, halogens) case 7: // Nitrogen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neighbors pos = conf.getAtomPos(i); // get position of atom nbrs = 0; nonhnbrs = 0; while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // hydrogen bond directions are not included bondDirection[nonhnbrs] = pos - conf.getAtomPos(*nbrIdx); // store bond direction in an array bondDirection[nonhnbrs].normalize(); ++nonhnbrs; // count non hydrogen neighbors } ++nbrs; // count neighbors ++nbrIdx; } switch (nbrs) { // number of neigbors case 1: // sp, eg. nitriles, no difference to fixed bonds if (atom->getFormalCharge() <= 0) { // no positively charged nitrogens addVectElements( N, &cos_2, pos, bondDirection[0]); // no differentiation between in-plane and // out-of-plane angle, plane not needed interact++; } break; case 2: // eg. imines, heterocycles if (atom->getFormalCharge() <= 0) { // no positively charged nitrogen if (nonhnbrs == 2) { // secondary imines, heterocycles dir = bondDirection[0] + bondDirection[1]; // get hydrogen bond direction plane = bondDirection[0].crossProduct( bondDirection[1]); // normal vector of lone pair plane = // plane of three atoms addVectElements(N, &cos_2, pos, dir, plane); } else if (nonhnbrs == 1) { // primary imine, hydrogen is allowed // to swap places nbrIdx -= 2; nbrs = nonhnbrs; while (nbrIdx != endNbrs) { if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // only bond directions to hydrogens are included bondDirection[nbrs] = pos - conf.getAtomPos(*nbrIdx); bondDirection[nbrs].normalize(); ++nbrs; } ++nbrIdx; } plane = bondDirection[0].crossProduct( bondDirection[1]); // normal vector of lone pair plane = // plane of three atoms addVectElements(N, &cos_acc, pos, bondDirection[0], plane); } else { //[N-]H2 rotating addVectElements(N, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); } interact++; } break; case 3: // amines, iminium ions if (atom->getFormalCharge() <= 0) { // no iminium ions, no positively charged nitrogen if (nonhnbrs == 0) { // ammonia addVectElements(N, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); } else if (nonhnbrs == 1) { // primary amines, rotation addVectElements( N, &cos_2_rot, pos, bondDirection[0]); // no differentiation between in-plane // and out-of-plane angle, plane not // needed } else { // secondary amines (not flexible) and tertiary amines, // same as fixed hydrogen bonds if (atom->getFormalCharge() <= 0 && !(RDKit::MolOps::atomHasConjugatedBond( atom))) { // positively charged nitrogen, no conjugated // nitrogen (amide bonds!) nbrIdx -= 3; nbrs = nonhnbrs; while (nbrIdx != endNbrs) { if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // only bond directions to hydrogens are included bondDirection[nbrs] = pos - conf.getAtomPos(*nbrIdx); bondDirection[nbrs].normalize(); ++nbrs; } ++nbrIdx; } dir = bondDirection[0] + bondDirection[1] + bondDirection[2]; // hydrogen bond direction addVectElements( N, &cos_2, pos, dir); // no differentiation between in-plane and // out-of-plane angle, plane not needed } } interact++; } break; case 0: // unbound nitrogen atoms, no hydrogen bonding case 4: // ammonium ions, no hydrogen bonding break; default: // more than four bonds: not possible with nitrogen BOOST_LOG(rdErrorLog) << "HBond: Nitrogen atom bound to more than 4 neighbor atoms: Atom: " << i << std::endl; } break; case 8: // Oxygen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neighbors pos = conf.getAtomPos(i); // get atom position nbrs = 0; nonhnbrs = 0; aromaticnbr = false; while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // bond directions to hydrogen are not included bondDirection[nonhnbrs] = pos - conf.getAtomPos(*nbrIdx); bondDirection[nonhnbrs].normalize(); ++nonhnbrs; // count non hydrogen neighbors } if (mol.getAtomWithIdx(*nbrIdx) ->getIsAromatic()) { // check whether aromatic neighbor aromaticnbr = true; } ++nbrs; // count neighbors ++nbrIdx; } switch (nbrs) { // no of neighbors case 1: // carbonyl, carboxyl C=O, X=O (X=S,P,...), anions // (alcoholates, carboxylates) --nbrIdx; boost::tie(secnbrIdx, secendNbrs) = mol.getAtomNeighbors( mol.getAtomWithIdx(*nbrIdx)); // get neighbors of neighbor atom while (secnbrIdx != secendNbrs) { // loop over neighbors of neighbor atom if ((*secnbrIdx) != i) { // second neighbor should not be the oxygen itself // bond direction of neighbor atom to second neighbor atom dir = conf.getAtomPos(*secnbrIdx) - conf.getAtomPos(*nbrIdx); break; // we only need one vector } ++secnbrIdx; } plane = bondDirection[0].crossProduct(dir); // lp plane vector if (atom->getFormalCharge() == 0) { // carbonyl, carboxyl C=O, X=O addVectElements(O, &cos_acc, pos, bondDirection[0], plane); interact++; } else if (atom->getFormalCharge() == -1) { // anion, eg. phenolate, carboxylic acid if (RDKit::MolOps::atomHasConjugatedBond(atom)) { // charged oxygen in conjungated system, eg phenolates or // carboxylates addVectElements(O, &cos_acc, pos, bondDirection[0], plane); } else { // all other addVectElements(O, &cos_2, pos, bondDirection[0], plane); } interact++; } break; case 2: // alcohols, ethers, carboxyl OH if (nonhnbrs == 0) { // water addVectElements(O, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); } else if (nonhnbrs == 1) { // hydroxy groups if (aromaticnbr) { // phenol nbrIdx -= 2; nbrs = nonhnbrs; while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // only bond directions to hydrogens are included bondDirection[nbrs] = pos - conf.getAtomPos( *nbrIdx); // get O-H bond direction vector bondDirection[nbrs].normalize(); ++nbrs; } ++nbrIdx; } plane = bondDirection[0].crossProduct( bondDirection[1]); // X-O-Y plane vector addVectElements(O, &cos_acc, pos, bondDirection[0], plane); } else { addVectElements( O, &cos_2_rot, pos, bondDirection[0]); // no plane information needed } } else { // ethers, same as fixed hydrogen bonds dir = bondDirection[0] + bondDirection[1]; // get hydrogen bond direction plane = bondDirection[0].crossProduct( bondDirection[1]); // X-O-Y plane vector plane = plane.crossProduct(dir); // lp plane vector addVectElements(O, &cos_2_0, pos, dir, plane); } interact++; break; case 0: // oxygen atoms, no hydrogen bonding case 3: // only with positively charged atom possible, no hydrogen // bonding break; default: // more than 3 neighbors: not possible with oxygen BOOST_LOG(rdErrorLog) << "HBond: Oxygen atom bound to more than 3 neighbor atoms: Atom: " << i << std::endl; } break; // Halogens case 9: // F case 17: // Cl case 35: // Br case 53: // I pos = conf.getAtomPos(i); // get atoms position dir = RDGeom::Point3D(1.0, 1.0, 1.0); // no directionality needed addVectElements( N, &no_dep, pos, dir); // type of halogens ~ nitrogen; no lp plane needed interact++; break; default: break; } } return interact; } unsigned int HBond::findDonors(const RDKit::ROMol &mol, int confId, const std::vector &specials) { using namespace HBondDetail; const RDKit::Conformer &conf = mol.getConformer(confId); // get conformer of molecule RDKit::ROMol::ADJ_ITER nbrIdx, endNbrs; RDGeom::Point3D pos, dir; unsigned int interact = 0; for (unsigned int i = 0; i < d_nInteract; i++) { // loop over all atoms if (std::find(specials.begin(), specials.end(), i) != specials.end()) { // check whether atom was already treated specially interact++; continue; } const auto *atom = mol.getAtomWithIdx(i); // get ptr to atom switch ( atom->getAtomicNum()) { // find atoms able to donate hydrogen bonds (O, // N, of course only with attached hydrogen) case 7: // Nitrogen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neigbors pos = conf.getAtomPos(i); // get position while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // hydrogen neighbor necessary for H bond donation dir = conf.getAtomPos(*nbrIdx) - pos; // bond direction vector, IMPORTANT: no normalization, // because operator() needs length of vector in case of // donors addVectElements(N, &cos_2, pos, dir); interact++; } ++nbrIdx; } break; case 8: // Oxygen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neigbors pos = conf.getAtomPos(i); // get position while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // hydrogen neighbor necessary for H bond donation dir = conf.getAtomPos(*nbrIdx) - pos; // bond direction vector, IMPORTANT: no normalization, // because operator() needs length of vector in case of // donors addVectElements(O, &cos_4, pos, dir); interact++; } ++nbrIdx; } break; default: break; } } return interact; } unsigned int HBond::findDonorsUnfixed( const RDKit::ROMol &mol, int confId, const std::vector &specials) { using namespace HBondDetail; const auto &conf = mol.getConformer(confId); // get conformer of molecule RDKit::ROMol::ADJ_ITER nbrIdx, endNbrs; RDGeom::Point3D pos, hbonddir, dir, plane; unsigned int nbrs, nonhnbrs; // no of neighbors, no of non hydrogen neighbors unsigned int interact = 0; bool aromaticnbr; for (unsigned int i = 0; i < d_nInteract; i++) { // loop over all atoms if (std::find(specials.begin(), specials.end(), i) != specials.end()) { // check whether atom was already treated specially interact++; continue; // skip loop for this atom } const auto *atom = mol.getAtomWithIdx(i); // get ptr to atom switch ( atom->getAtomicNum()) { // find atoms able to donate hydrogen bonds (O, // N, of course only with attached hydrogen) case 7: // Nitrogen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neighbors pos = conf.getAtomPos(i); // get position nbrs = 0; nonhnbrs = 0; while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { ++nonhnbrs; // count non-hydrogen neighbors } ++nbrs; // count neighbors ++nbrIdx; } nbrIdx -= nbrs; if (nonhnbrs != nbrs) { // otherwise no hydrogens, no donation possible switch (nbrs) { // number of neigbors case 2: // eg. imines, heterocycles if (nonhnbrs == 0) { //[N-]H2 addVectElements(N, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); interact++; } else { // primary imine, swapping of hydrogen is allowed while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { dir = pos - conf.getAtomPos(*nbrIdx); // the other bond dir.normalize(); } else { hbonddir = conf.getAtomPos(*nbrIdx) - pos; // hydrogen bond } ++nbrIdx; } addVectElements(N, &cos_2, pos, hbonddir); // first hbond interact++; // let's swap hydrogen and lp: plane = dir.crossProduct(hbonddir); // lp plane vector plane = plane.crossProduct(dir); // plane through other bond // perpendicular to X-N-H plane dir = plane * hbonddir.dotProduct(plane); // projection of vector // hbonddir on plane vector hbonddir -= dir * 2; // mirroring of dir vector on plane addVectElements( N, &cos_2, pos, hbonddir); // second hbond, hydrogen at other place interact++; } break; case 3: // amines, iminium ions if (nonhnbrs == 2) { // sec amines, no rotation, same as fixed bonds while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // hydrogen neighbor necessary for H bond donation hbonddir = conf.getAtomPos(*nbrIdx) - pos; // hbond direction } ++nbrIdx; } addVectElements(N, &cos_2, pos, hbonddir); interact++; } else if (nonhnbrs == 1) { // primary amines, rotation while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // search for X-N bond dir = pos - conf.getAtomPos(*nbrIdx); // X-N bond direction dir.normalize(); } ++nbrIdx; } addVectElements( N, &cos_2_rot, pos, dir * (-bondlength[N])); // vector dir has typcial N-H // bondlength, for approximate / // average calculation of angle p // (see operator()) interact++; } else { // ammonia addVectElements(N, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); interact++; } break; case 4: if (nonhnbrs == 0) { // ammonium ion addVectElements(N, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); interact++; } else if (nonhnbrs == 1) { // primary ammonium, rotation while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // search for X-N bond dir = pos - conf.getAtomPos(*nbrIdx); // X-N bond direction } ++nbrIdx; } addVectElements( N, &cos_2_rot, pos, dir * (-bondlength[N])); // vector dir has typcial N-H // bondlength, for approximate / // average calculation of angle p // (see operator()) interact++; } else { // secondary or tertiary ammonium, no rotation, same as // fixed bonds while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // hydrogen neighbor necessary for H bond donation dir = pos - conf.getAtomPos(*nbrIdx); // hydrogen bond direction addVectElements(N, &cos_2_rot, pos, dir); interact++; } ++nbrIdx; } } break; default: // more than four bonds: not possible with nitrogen BOOST_LOG(rdErrorLog) << "HBond: Nitrogen atom bound to more than 4 neighbor atoms: Atom: " << i << std::endl; } } break; case 8: // Oxygen boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom); // get neighbors pos = conf.getAtomPos(i); // get position nbrs = 0; nonhnbrs = 0; aromaticnbr = false; while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { ++nonhnbrs; // count non-hydrogen neighbors } if (mol.getAtomWithIdx(*nbrIdx) ->getIsAromatic()) { // check whether oxygen is bound to // aromatic system aromaticnbr = true; } ++nbrs; // count neighbors ++nbrIdx; } nbrIdx -= nbrs; if (nonhnbrs != nbrs) { // otherwise no hydrogen, no hydrogen bond // donation possible switch (nbrs) { // no of neighbors case 1: // hydroxyl addVectElements(O, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); interact++; break; case 2: if (nonhnbrs == 0) { // water addVectElements(O, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); interact++; } else { // OH groups while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // search for X-O bond dir = pos - conf.getAtomPos(*nbrIdx); // O-X bond direction dir.normalize(); } else { hbonddir = conf.getAtomPos(*nbrIdx) - pos; // O-H bond direction } ++nbrIdx; } if (aromaticnbr) { // phenolic oxygen, allows h to swap places // in aromatic plane addVectElements(O, &cos_4, pos, hbonddir); // first hbond interact++; // let's swap hydrogen and lp: plane = dir.crossProduct(hbonddir); // lp plane vector plane = plane.crossProduct(dir); // plane through other bond // perpendicular to X-N-H plane dir = plane * hbonddir.dotProduct( plane); // projection of vector dir on plane vector hbonddir -= dir * 2; // mirroring of dir vector on plane addVectElements( O, &cos_4, pos, hbonddir); // second hbond, hydrogen at other place interact++; } else { // all other oxygens, flexible addVectElements( O, &cos_4_rot, pos, dir * (-bondlength[O])); // vector dir has typcial O-H // bondlength, for approximate / // average calculation of angle // p (see operator()) interact++; } } break; case 3: // positively charged oxygen if (nonhnbrs == 0) { // oxonium addVectElements(O, &no_dep, pos, RDGeom::Point3D(0.0, 0.0, 0.0)); interact++; } else if (nonhnbrs == 1) { // R[O+]H2 while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() != 1) { // search for X-O bond dir = pos - conf.getAtomPos(*nbrIdx); // X-O bond direction dir.normalize(); addVectElements( O, &cos_4_rot, pos, dir * (-bondlength[O])); // vector dir has typcial O-H // bondlength, for approximate // / average calculation of // angle p (see operator()) interact++; } ++nbrIdx; } } else { // R1R2[O+]H, non-flexible while (nbrIdx != endNbrs) { // loop over neighbors if (mol.getAtomWithIdx(*nbrIdx)->getAtomicNum() == 1) { // search for O-H bond dir = conf.getAtomPos(*nbrIdx) - pos; // O-H bond direction addVectElements(O, &cos_4, pos, dir); interact++; } ++nbrIdx; } } break; case 0: // oxygen atom gas, no hydrogen bonding break; default: BOOST_LOG(rdErrorLog) << "HBond: Oxygen atom bound to more than 3 neighbor atoms: Atom: " << i << std::endl; } } break; default: break; } } return interact; } double HBond::operator()(double x, double y, double z, double thres) const { using namespace HBondDetail; if (d_nInteract < 1) { // no interactions return 0.0; // return 0.0 } double res = 0.0; if (d_DAprop == 'A') { unsigned int minId = 0; double minEne = std::numeric_limits::max(); // minimal energy double probeDirection[3]; // direction of probe for (unsigned int i = 0, j = 0; i < d_nInteract; i++, j += 3) { // calculation of energy contributions and searching // for the favored probe direction ( direction of // lowest energy contribution ) d_vectTargetProbe[j] = x - d_pos[j]; // vector of interaction d_vectTargetProbe[j + 1] = y - d_pos[j + 1]; d_vectTargetProbe[j + 2] = z - d_pos[j + 2]; double dist2 = d_vectTargetProbe[j] * d_vectTargetProbe[j] + d_vectTargetProbe[j + 1] * d_vectTargetProbe[j + 1] + d_vectTargetProbe[j + 2] * d_vectTargetProbe[j + 2]; // calc of squared length of interaction // std::cout << dist2 << std::endl; if (dist2 < thres) { double dis = sqrt(dist2); double distN[3] = {d_vectTargetProbe[j] / dis, d_vectTargetProbe[j + 1] / dis, d_vectTargetProbe[j + 2] / dis}; dist2 = std::max(dist2, d_cutoff); double t = angle( distN[0], distN[1], distN[2], d_direction[j], d_direction[j + 1], d_direction[j + 2]); // calc of angle between direction of hbond // and target-probe direction double eneTerm1 = rm[d_probetype][d_targettypes[i]]; eneTerm1 *= eneTerm1; // squared rm eneTerm1 /= dist2; // division by squared distance double eneTerm2 = eneTerm1 * eneTerm1; // fourth power of rm/distance eneTerm1 = eneTerm2 * eneTerm1; // sixth power of rm/distance eneTerm2 *= eneTerm2; // eigth power of rm/distance eneTerm2 *= (4.0 * eneTerm1 - 3.0 * eneTerm2); eneTerm2 *= em[d_probetype][d_targettypes[i]]; // multiplication with em d_eneContrib[i] = eneTerm2; double t0, ti; if (d_function[i] == &cos_acc || d_function[i] == &cos_2_0) { // only if dependent of ti and t0 double dotProd = d_vectTargetProbe[j] * d_plane[j] + d_vectTargetProbe[j + 1] * d_plane[j + 1] + d_vectTargetProbe[j + 2] * d_plane[j + 2]; double vectTargetProbeInPlane[3] = { d_vectTargetProbe[j] - d_plane[j] * dotProd, // projection of targetProbe vector on lp plane d_vectTargetProbe[j + 1] - d_plane[j + 1] * dotProd, // projection of targetProbe vector on lp plane d_vectTargetProbe[j + 2] - d_plane[j + 2] * dotProd}; // projection of targetProbe vector on lp plane normalize(vectTargetProbeInPlane[0], vectTargetProbeInPlane[1], vectTargetProbeInPlane[2]); // angles t_0 (out-of-lonepair-plane angle), t_i (in-lonepair-plane // angle), t0 = angle(distN[0], distN[1], distN[2], vectTargetProbeInPlane[0], vectTargetProbeInPlane[1], vectTargetProbeInPlane[2]); // out of plane angle ti = angle(d_direction[j], d_direction[j + 1], d_direction[j + 2], vectTargetProbeInPlane[0], vectTargetProbeInPlane[1], vectTargetProbeInPlane[2]); // in plane angle } else { t0 = 0.0; ti = 1.0; } d_eneContrib[i] *= (*(d_function[i]))(t, t0, ti); // scaling of energy contribution if (d_eneContrib[i] < minEne) { // check whether most favored interaction minEne = d_eneContrib[i]; minId = i; } } else { d_eneContrib[i] = 0.0; } } minId *= 3; probeDirection[0] = -d_vectTargetProbe[minId]; // probe is directed to most // favored interaction probeDirection[1] = -d_vectTargetProbe[++minId]; probeDirection[2] = -d_vectTargetProbe[++minId]; normalize(probeDirection[0], probeDirection[1], probeDirection[2]); for (unsigned int i = 0, j = 0; i < d_nInteract; i++, j += 3) { // scaling to take probe direction into account and adding up double vectHydrogenTarget[3] = { probeDirection[0] * bondlength[d_probetype] - d_vectTargetProbe[j], probeDirection[1] * bondlength[d_probetype] - d_vectTargetProbe[j + 1], probeDirection[2] * bondlength[d_probetype] - d_vectTargetProbe[j + 2]}; normalize(vectHydrogenTarget[0], vectHydrogenTarget[1], vectHydrogenTarget[2]); // p (angle between best hydrogen bond direction of probe and // hydrogen-acceptor vector) double p = angle(probeDirection[0], probeDirection[1], probeDirection[2], vectHydrogenTarget[0], vectHydrogenTarget[1], vectHydrogenTarget[2]); res += d_eneContrib[i] * cos_2(p, 0.0, 1.0); // scaling of energy contribution and adding up } } else { // d_DAprop='D' unsigned int minId = 0; double minEne = std::numeric_limits::max(); // minimal energy double probeDirection[3]; // direction of prope for (unsigned int i = 0, j = 0; i < d_nInteract; i++, j += 3) { // calculation of energy contributions and searching // for the favored probe direction ( direction of // lowest energy contribution ) d_vectTargetProbe[j] = x - d_pos[j]; // vector of interaction d_vectTargetProbe[j + 1] = y - d_pos[j + 1]; d_vectTargetProbe[j + 2] = z - d_pos[j + 2]; double dist2 = d_vectTargetProbe[j] * d_vectTargetProbe[j] + d_vectTargetProbe[j + 1] * d_vectTargetProbe[j + 1] + d_vectTargetProbe[j + 2] * d_vectTargetProbe[j + 2]; // calc of squared length of interaction // std::cout << dist2 << std::endl; if (dist2 < thres) { double dis = sqrt(dist2); double distN[3] = {d_vectTargetProbe[j] / dis, d_vectTargetProbe[j + 1] / dis, d_vectTargetProbe[j + 2] / dis}; dist2 = std::max(dist2, d_cutoff); double t = angle( distN[0], distN[1], distN[2], d_direction[j], d_direction[j + 1], d_direction[j + 2]); // calc of angle between direction of hbond // and target-probe direction double eneTerm1 = rm[d_probetype][d_targettypes[i]]; eneTerm1 *= eneTerm1; // squared rm eneTerm1 /= dist2; // division by squared distance double eneTerm2 = eneTerm1 * eneTerm1; // fourth power of rm/distance eneTerm1 = eneTerm2 * eneTerm1; // sixth power of rm/distance eneTerm2 *= eneTerm2; // eigth power of rm/distance eneTerm2 *= (4.0 * eneTerm1 - 3.0 * eneTerm2); eneTerm2 *= em[d_probetype][d_targettypes[i]]; // multiplication with em d_eneContrib[i] = eneTerm2; d_eneContrib[i] *= (*(d_function[i]))(t, 0.0, 1.0); // scaling of energy contribution if (d_eneContrib[i] < minEne) { // check whether most favored interaction minEne = d_eneContrib[i]; minId = i; } } else { d_eneContrib[i] = 0; } } minId *= 3; probeDirection[0] = -d_vectTargetProbe[minId]; // probe is directed to most // favored interaction probeDirection[1] = -d_vectTargetProbe[++minId]; probeDirection[2] = -d_vectTargetProbe[++minId]; normalize(probeDirection[0], probeDirection[1], probeDirection[2]); for (unsigned int i = 0, j = 0; i < d_nInteract; i++, j += 3) { // scaling to take probe direction into account and adding up double vectProbeHydrogen[3] = { d_direction[j] * d_lengths[i] - d_vectTargetProbe[j], d_direction[j + 1] * d_lengths[i] - d_vectTargetProbe[j + 1], d_direction[j + 2] * d_lengths[i] - d_vectTargetProbe[j + 2]}; normalize(vectProbeHydrogen[0], vectProbeHydrogen[1], vectProbeHydrogen[2]); // p (angle between best hydrogen bond direction of probe and // hydrogen-acceptor vector) double p = angle(vectProbeHydrogen[0], vectProbeHydrogen[1], vectProbeHydrogen[2], probeDirection[0], probeDirection[1], probeDirection[2]); res += d_eneContrib[i] * cos_2(p, 0.0, 1.0); // scaling of energy contribution and adding up } } return res; } void HBond::addVectElements(atomtype type, double (*funct)(double, double, double), const RDGeom::Point3D &pos, const RDGeom::Point3D &dir, const RDGeom::Point3D &plane) { d_targettypes.push_back(type); d_function.push_back(funct); d_pos.push_back(pos.x); d_pos.push_back(pos.y); d_pos.push_back(pos.z); double len = dir.length(); d_lengths.push_back(len); d_direction.push_back(dir.x / len); d_direction.push_back(dir.y / len); d_direction.push_back(dir.z / len); len = plane.length(); if (len > 1.e-16) { d_plane.push_back(plane.x / len); d_plane.push_back(plane.y / len); d_plane.push_back(plane.z / len); } else { d_plane.push_back(0.0); d_plane.push_back(0.0); d_plane.push_back(0.0); } } void HBond::normalize(double &x, double &y, double &z) const { double temp = x * x + y * y + z * z; temp = sqrt(temp); x /= temp; y /= temp; z /= temp; } double HBond::angle(double x1, double y1, double z1, double x2, double y2, double z2) const { double dotProd = x1 * x2 + y1 * y2 + z1 * z2; if (dotProd < -1.0) { dotProd = -1.0; } else if (dotProd > 1.0) { dotProd = 1.0; } return acos(dotProd); } Hydrophilic::Hydrophilic(const RDKit::ROMol &mol, int confId, bool fixed, double cutoff) { d_hbondOH = HBond(mol, confId, "OH", fixed, cutoff); d_hbondO = HBond(mol, confId, "O", fixed, cutoff); } double Hydrophilic::operator()(double x, double y, double z, double thres) const { double hbondO, hbondOH; hbondO = d_hbondO(x, y, z, thres); hbondOH = d_hbondOH(x, y, z, thres); return std::min(hbondO, hbondOH); } void writeToCubeStream(const RDGeom::UniformRealValueGrid3D &grd, std::ostream &outStrm, const RDKit::ROMol *mol, int confid) { PRECONDITION(outStrm, "bad stream"); const double bohr = 0.529177249; int dimX = grd.getNumX(); //+2; int dimY = grd.getNumY(); //+2; int dimZ = grd.getNumZ(); //+2; auto spacing = grd.getSpacing() / bohr; auto offSet = grd.getOffset() / bohr; auto nAtoms = mol ? mol->getNumAtoms() : 0u; outStrm.setf(std::ios::left); outStrm << "Gaussian cube format generated by RDKit\n*************************\n"; outStrm << std::setw(20) << std::setprecision(6) << nAtoms << std::setw(20) << std::setprecision(6) << offSet.x << std::setw(20) << std::setprecision(6) << offSet.y << std::setw(20) << std::setprecision(6) << offSet.z << std::endl; outStrm << std::setw(20) << std::setprecision(6) << dimX << std::setw(20) << std::setprecision(6) << spacing << std::setw(20) << std::setprecision(6) << 0 << std::setw(20) << std::setprecision(6) << 0 << std::endl << std::setw(20) << std::setprecision(6) << dimY << std::setw(20) << std::setprecision(6) << 0 << std::setw(20) << std::setprecision(6) << spacing << std::setw(20) << std::setprecision(6) << 0 << std::endl << std::setw(20) << std::setprecision(6) << dimZ << std::setw(20) << std::setprecision(6) << std::setw(20) << std::setprecision(6) << 0 << std::setw(20) << std::setprecision(6) << 0 << std::setw(20) << std::setprecision(6) << spacing << std::endl; if (mol) { // this will throw a ConformerException if confId does not exist const auto &conf = mol->getConformer(confid); for (const auto &atom : mol->atoms()) { const auto &pt = conf.getAtomPos(atom->getIdx()) / bohr; outStrm << std::setw(20) << std::setprecision(6) << std::left << atom->getAtomicNum() << std::setw(20) << std::setprecision(6) << atom->getAtomicNum() << std::setw(20) << std::setprecision(6) << pt.x << std::setw(20) << std::setprecision(6) << std::setw(20) << std::setprecision(6) << pt.y << std::setw(20) << std::setprecision(6) << pt.z << std::endl; } } for (auto xi = 0u; xi < grd.getNumX(); ++xi) { for (auto yi = 0u; yi < grd.getNumY(); ++yi) { for (auto zi = 0u; zi < grd.getNumZ(); ++zi) { outStrm << std::setw(20) << std::setprecision(6) << std::left << static_cast( grd.getVal(grd.getGridIndex(xi, yi, zi))); // grd->d_numX-xi-1, grd->d_numY-yi-1, grd->d_numZ-zi-1 if ((zi + 1) % 8 == 0) { outStrm << std::endl; } } outStrm << std::endl; } outStrm << std::endl; } } void writeToCubeFile(const RDGeom::UniformRealValueGrid3D &grd, const std::string &filename, const RDKit::ROMol *mol, int confid) { std::ofstream ofStrm(filename.c_str()); writeToCubeStream(grd, ofStrm, mol, confid); } std::unique_ptr readFromCubeStream( RDGeom::UniformRealValueGrid3D &grd, std::istream &inStrm) { PRECONDITION(inStrm, "bad stream"); constexpr double bohr = 0.529177249; constexpr double spacingThreshold = 0.0001; if (inStrm.eof()) { return nullptr; } std::string string; int nAtoms; std::getline(inStrm, string); std::getline(inStrm, string); inStrm >> nAtoms; double x, y, z; inStrm >> x >> y >> z; const RDGeom::Point3D offSet(x * bohr, y * bohr, z * bohr); int dimX, dimY, dimZ; double spacingX, spacingY, spacingZ, temp1, temp2; inStrm >> dimX >> spacingX >> temp1 >> temp2; inStrm >> dimY >> temp1 >> spacingY >> temp2; inStrm >> dimZ >> temp1 >> temp2 >> spacingZ; if ((fabs(spacingX - spacingY) > spacingThreshold) || (fabs(spacingX - spacingZ) > spacingThreshold)) { std::ostringstream errout; errout << "Same spacing in all directions needed"; throw RDKit::FileParseException(errout.str()); } else { spacingX *= bohr; grd = RDGeom::UniformRealValueGrid3D(spacingX * static_cast(dimX), spacingX * static_cast(dimY), spacingX * static_cast(dimZ), spacingX, &offSet); } std::unique_ptr molecule; if (nAtoms) { molecule.reset(new RDKit::RWMol()); std::unique_ptr conf(new RDKit::Conformer(nAtoms)); int atomNum; for (auto i = 0; i < nAtoms; ++i) { inStrm >> atomNum >> temp1 >> x >> y >> z; RDKit::Atom atom(atomNum); molecule->addAtom(&atom, true, false); RDGeom::Point3D pos(x * bohr, y * bohr, z * bohr); conf->setAtomPos(i, pos); } molecule->addConformer(conf.release(), false); } for (auto xi = 0; xi < dimX; ++xi) { for (auto yi = 0; yi < dimY; ++yi) { for (auto zi = 0; zi < dimZ; ++zi) { double tempVal; inStrm >> tempVal; grd.setVal(grd.getGridIndex(xi, yi, zi), tempVal); } } } return molecule; } std::unique_ptr readFromCubeFile( RDGeom::UniformRealValueGrid3D &grd, const std::string &filename) { std::ifstream ifStrm(filename.c_str()); if (ifStrm.bad()) { std::ostringstream errout; errout << "Bad input file " << filename; throw RDKit::BadFileException(errout.str()); }; return readFromCubeStream(grd, ifStrm); } } // namespace RDMIF