#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pubchem-align3d/shape_functions.hpp" #include "PubChemShape.hpp" constexpr auto pubchemFeatureName = "PUBCHEM_PHARMACOPHORE_FEATURES"; // #define DEBUG_MSG(msg_stream) cout << msg_stream << '\n' #define DEBUG_MSG(msg_stream) using namespace RDKit; // Bondi radii // can find more of these in Table 12 of this publication: // https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3658832/ // The dummy atom radius (atomic number 0) is set to // 2.16 in ShapeInputOptions and may be varied there, as // may all the other radii if required, including the // addition of atoms not covered here. const std::map vdw_radii = { {1, 1.10}, // H {2, 1.40}, // He {3, 1.81}, // Li {4, 1.53}, // Be {5, 1.92}, // B {6, 1.70}, // C {7, 1.55}, // N {8, 1.52}, // O {9, 1.47}, // F {10, 1.54}, // Ne {11, 2.27}, // Na {12, 1.73}, // Mg {13, 1.84}, // Al {14, 2.10}, // Si {15, 1.80}, // P {16, 1.80}, // S {17, 1.75}, // Cl {18, 1.88}, // Ar {19, 2.75}, // K {20, 2.31}, // Ca {31, 1.87}, // Ga {32, 2.11}, // Ge {33, 1.85}, // As {34, 1.90}, // Se {35, 1.83}, // Br {36, 2.02}, // Kr {37, 3.03}, // Rb {38, 2.49}, // Sr {49, 1.93}, // In {50, 2.17}, // Sn {51, 2.06}, // Sb {52, 2.06}, // Te {53, 1.98}, // I {54, 2.16}, // Xe {55, 3.43}, // Cs {56, 2.68}, // Ba {81, 1.96}, // Tl {82, 2.02}, // Pb {83, 2.07}, // Bi {84, 1.97}, // Po {85, 2.02}, // At {86, 2.20}, // Rn {87, 3.48}, // Fr {88, 2.83}, // Ra }; constexpr double radius_color = 1.08265; // same radius for all feature/color "atoms" namespace { class ss_matcher { public: ss_matcher(const std::string &pattern) : m_pattern(pattern) { m_needCopies = (pattern.find_first_of("$") != std::string::npos); RDKit::RWMol *p = RDKit::SmartsToMol(pattern); m_matcher = p; POSTCONDITION(m_matcher, "no matcher"); }; const RDKit::ROMol *getMatcher() const { return m_matcher; }; unsigned int countMatches(const RDKit::ROMol &mol) const { PRECONDITION(m_matcher, "no matcher"); std::vector matches; // This is an ugly one. Recursive queries aren't thread safe. // Unfortunately we have to take a performance hit here in order // to guarantee thread safety if (m_needCopies) { const RDKit::ROMol nm(*(m_matcher), true); RDKit::SubstructMatch(mol, nm, matches); } else { const RDKit::ROMol &nm = *m_matcher; RDKit::SubstructMatch(mol, nm, matches); } return matches.size(); } ~ss_matcher() { delete m_matcher; }; private: ss_matcher() : m_pattern("") {}; std::string m_pattern; bool m_needCopies{false}; const RDKit::ROMol *m_matcher{nullptr}; }; } // namespace typedef boost::flyweight, boost::flyweights::no_tracking> pattern_flyweight; // Definitions for feature points adapted from: // Gobbi and Poppinger, Biotech. Bioeng. _61_ 47-54 (1998) const std::vector> smartsPatterns = { {"[$([N;!H0;v3,v4&+1]),\ $([O,S;H1;+0]),\ n&H1&+0]"}, // Donor {"[$([O,S;H1;v2;!$(*-*=[O,N,P,S])]),\ $([O,S;H0;v2]),\ $([O,S;-]),\ $([N;v3;!$(N-*=[O,N,P,S])]),\ n&H0&+0,\ $([o,s;+0;!$([o,s]:n);!$([o,s]:c:n)])]"}, // Acceptor { "[r]1[r][r]1", "[r]1[r][r][r]1", "[r]1[r][r][r][r]1", "[r]1[r][r][r][r][r]1", "[r]1[r][r][r][r][r][r]1", }, // rings // "[a]", // // Aromatic // "[F,Cl,Br,I]", // Halogen {"[#7;+,\ $([N;H2&+0][$([C,a]);!$([C,a](=O))]),\ $([N;H1&+0]([$([C,a]);!$([C,a](=O))])[$([C,a]);!$([C,a](=O))]),\ $([N;H0&+0]([C;!$(C(=O))])([C;!$(C(=O))])[C;!$(C(=O))])]"}, // Basic {"[$([C,S](=[O,S,P])-[O;H1,-1])]"} // Acidic }; std::vector> *getPh4Patterns() { static std::unique_ptr>> patterns; if (!patterns) { patterns.reset(new std::vector>()); for (const auto &smartsV : smartsPatterns) { std::vector v; for (const auto &smarts : smartsV) { const ROMol *matcher = pattern_flyweight(smarts).get().getMatcher(); CHECK_INVARIANT(matcher, "bad smarts"); v.push_back(matcher); } patterns->push_back(std::move(v)); } } return patterns.get(); } namespace { std::vector, unsigned int>> extractFeatures( const ROMol &mol, const ShapeInputOptions &shapeOpts) { // unpack features (PubChem-specific property from SDF) // NOTE: this unpacking assumes that RWMol-atom-index = SDF-atom-number - 1 // e.g. RWMol uses [0..N-1] and SDF uses [1..N], with atoms in the same // order // If there are no PubChem features, falls back on RDKit pphore types. std::vector, unsigned int>> feature_idx_type; if (shapeOpts.useColors) { std::string features; if (mol.getPropIfPresent(pubchemFeatureName, features)) { // regular atoms have type 0; feature "atoms" (features represented by a // single point+radius) must have type > 0 static const std::map atomTypes = { {"acceptor", 1}, {"anion", 2}, {"cation", 3}, {"donor", 4}, {"hydrophobe", 5}, {"rings", 6}, }; std::istringstream iss(features); std::string line; unsigned int n = 0; while (std::getline(iss, line)) { if (n == 0) { feature_idx_type.resize(stoul(line)); } else { unsigned int f = n - 1; if (f >= feature_idx_type.size()) { throw ValueErrorException("Too many features"); } std::istringstream iss2(line); std::string token; unsigned int t = 0; while (std::getline(iss2, token, ' ')) { if (t == 0) { feature_idx_type[f].first.resize(stoul(token)); } else if (t <= feature_idx_type[f].first.size()) { feature_idx_type[f].first[t - 1] = stoul(token) - 1; } else { auto type = atomTypes.find(token); if (type == atomTypes.end()) { throw ValueErrorException("Invalid feature type: " + token); } feature_idx_type[f].second = type->second; } ++t; } if (t != (feature_idx_type[f].first.size() + 2)) { throw ValueErrorException("Wrong number of tokens in feature"); } } ++n; } if (n != (feature_idx_type.size() + 1)) { throw ValueErrorException("Wrong number of features"); } DEBUG_MSG("# features: " << feature_idx_type.size()); } else { const auto pattVects = getPh4Patterns(); feature_idx_type.clear(); unsigned pattIdx = 1; for (const auto &patts : *pattVects) { for (const auto patt : patts) { auto matches = SubstructMatch(mol, *patt); for (auto match : matches) { std::vector ats; for (const auto &pr : match) { ats.push_back(pr.second); } feature_idx_type.emplace_back(ats, pattIdx); } } ++pattIdx; } } } return feature_idx_type; } bool atomInSubset(unsigned int atomIdx, const ShapeInputOptions &shapeOpts) { if (shapeOpts.atomSubset.empty()) { return true; } return std::ranges::find(shapeOpts.atomSubset, atomIdx) != shapeOpts.atomSubset.end(); } bool atomAllowedInColor(unsigned int atomIdx, const ShapeInputOptions &shapeOpts) { return std::ranges::find(shapeOpts.notColorAtoms, atomIdx) == shapeOpts.notColorAtoms.end(); } double getAtomRadius(unsigned int atomIdx, const ShapeInputOptions &shapeOpts) { auto it = std::ranges::find_if( shapeOpts.atomRadii, [atomIdx](const auto &p) -> bool { return p.first == atomIdx; }); return it == shapeOpts.atomRadii.end() ? -1.0 : it->second; } // Get the atom radii. rad_vector is expected to be big enough to hold them // all. Also computes the average coordinates of the selected atoms. void extractAtomRadii(const Conformer &conformer, unsigned int nAtoms, const ShapeInputOptions &shapeOpts, RDGeom::Point3D &ave, unsigned int &nSelectedAtoms, std::vector &rad_vector) { nSelectedAtoms = 0; for (unsigned int i = 0u; i < nAtoms; ++i) { if (!atomInSubset(i, shapeOpts)) { continue; } double rad = getAtomRadius(i, shapeOpts); if (rad > 0.0) { rad_vector[nSelectedAtoms++] = rad; ave += conformer.getAtomPos(i); } else { unsigned int Z = conformer.getOwningMol().getAtomWithIdx(i)->getAtomicNum(); if (Z > 1) { ave += conformer.getAtomPos(i); if (auto rad = vdw_radii.find(Z); rad != vdw_radii.end()) { rad_vector[nSelectedAtoms++] = rad->second; } else { throw ValueErrorException("No VdW radius for atom with Z=" + std::to_string(Z)); } } else if (shapeOpts.includeDummies && Z == 0) { ave += conformer.getAtomPos(i); rad_vector[nSelectedAtoms++] = shapeOpts.dummyRadius; } } } ave /= nSelectedAtoms; } void extractAtomCoords(const Conformer &conformer, const unsigned int nAtoms, const ShapeInputOptions &shapeOpts, const RDGeom::Point3D &ave, std::vector &coords) { for (unsigned i = 0, j = 0; i < nAtoms; ++i) { if (!atomInSubset(i, shapeOpts)) { continue; } // use only non-H for alignment, optionally with dummy atoms. unsigned int Z = conformer.getOwningMol().getAtomWithIdx(i)->getAtomicNum(); if (Z > 1 || (shapeOpts.includeDummies && Z == 0)) { RDGeom::Point3D pos = conformer.getAtomPos(i); if (shapeOpts.normalize) { pos -= ave; } coords[j * 3] = pos.x; coords[(j * 3) + 1] = pos.y; coords[(j * 3) + 2] = pos.z; ++j; } } } void extractFeatureCoords( const Conformer &conformer, const unsigned int nAtoms, const unsigned int nSelectedAtoms, const std::vector, unsigned int>> &feature_idx_type, const ShapeInputOptions &shapeOpts, const RDGeom::Point3D &ave, unsigned int &numFeatures, ShapeInput &res, std::vector &rad_vector) { // get feature coordinates - simply the average of coords of all atoms in the // feature for (unsigned i = 0; i < feature_idx_type.size(); ++i) { RDGeom::Point3D floc; unsigned int nSel = 0; for (unsigned int j = 0; j < feature_idx_type[i].first.size(); ++j) { unsigned int idx = feature_idx_type[i].first[j]; if (!atomInSubset(idx, shapeOpts) || !atomAllowedInColor(idx, shapeOpts)) { continue; } if (idx >= nAtoms || conformer.getOwningMol().getAtomWithIdx(idx)->getAtomicNum() <= 1) { throw ValueErrorException("Invalid feature atom index"); } floc += conformer.getAtomPos(idx); ++nSel; } if (nSel == feature_idx_type[i].first.size()) { floc /= nSel; if (shapeOpts.normalize) { floc -= ave; } DEBUG_MSG("feature type " << feature_idx_type[i].second << " (" << floc << ")"); auto array_idx = nSelectedAtoms + numFeatures; res.coord[array_idx * 3] = floc.x; res.coord[(array_idx * 3) + 1] = floc.y; res.coord[(array_idx * 3) + 2] = floc.z; rad_vector[array_idx] = radius_color; res.atom_type_vector[array_idx] = feature_idx_type[i].second; ++numFeatures; } } } void extractCustomFeatureCoords( const unsigned int nSelectedAtoms, const ShapeInputOptions &shapeOpts, const RDGeom::Point3D &ave, unsigned int &numFeatures, ShapeInput &res, std::vector &rad_vector, const std::unique_ptr &trans) { for (const auto &feature : shapeOpts.customFeatures) { unsigned int feature_type = std::get<0>(feature); RDGeom::Point3D floc = std::get<1>(feature); if (trans) { trans->TransformPoint(floc); } double radius = std::get<2>(feature); floc -= ave; DEBUG_MSG("custom feature type " << feature_type << " (" << floc << ")"); auto array_idx = nSelectedAtoms + numFeatures; res.coord[array_idx * 3] = floc.x; res.coord[(array_idx * 3) + 1] = floc.y; res.coord[(array_idx * 3) + 2] = floc.z; rad_vector[array_idx] = radius; res.atom_type_vector[array_idx] = feature_type; ++numFeatures; } } } // namespace // The conformer is left where it is, the shape is translated to the origin // and aligned to the principal axes. ShapeInput PrepareConformer(const ROMol &mol, int confId, const ShapeInputOptions &shapeOpts) { Align3D::setUseCutOff(true); ShapeInput res; auto feature_idx_type = extractFeatures(mol, shapeOpts); auto &conformer = mol.getConformer(confId); if (!conformer.is3D()) { throw ValueErrorException("Conformer must be 3D"); } unsigned int nAtoms = mol.getNumAtoms(); // DEBUG_MSG("num atoms: " << nAtoms); // Start with the arrays as large as they will possibly have to be. // They will be re-sized later. unsigned int nAlignmentAtoms = nAtoms; if (shapeOpts.customFeatures.empty()) { nAlignmentAtoms += feature_idx_type.size(); } else { nAlignmentAtoms += shapeOpts.customFeatures.size(); } std::vector rad_vector(nAlignmentAtoms); res.atom_type_vector.resize(nAlignmentAtoms, 0); Conformer confCp(conformer); std::unique_ptr trans; RDGeom::Point3D initialCentroid; if (shapeOpts.normalize) { // This ignores hydrogens by default, which is what is needed here. trans.reset(MolTransforms::computeCanonicalTransform(confCp)); MolTransforms::transformConformer(confCp, *trans); // Save the rotation part of the transformation. The coords will be centred // below and left that way so we don't need to keep the translation. for (unsigned int i = 0, k = 0; i < 3; ++i) { for (unsigned int j = 0; j < 3; ++j, ++k) { res.inertialRot[k] = trans->getValUnchecked(i, j); } } // Work back from the canonical transformation to get the initial // centroid by the inverse rotation of the current translation // but not taking its negative so it can be used directly as the shift. initialCentroid = RDGeom::Point3D{trans->getValUnchecked(0, 3), trans->getValUnchecked(1, 3), trans->getValUnchecked(2, 3)}; RDGeom::Transform3D invTrans; invTrans.setValUnchecked(0, 0, trans->getValUnchecked(0, 0)); invTrans.setValUnchecked(0, 1, trans->getValUnchecked(1, 0)); invTrans.setValUnchecked(0, 2, trans->getValUnchecked(2, 0)); invTrans.setValUnchecked(1, 0, trans->getValUnchecked(0, 1)); invTrans.setValUnchecked(1, 1, trans->getValUnchecked(1, 1)); invTrans.setValUnchecked(1, 2, trans->getValUnchecked(2, 1)); invTrans.setValUnchecked(2, 0, trans->getValUnchecked(0, 2)); invTrans.setValUnchecked(2, 1, trans->getValUnchecked(1, 2)); invTrans.setValUnchecked(2, 2, trans->getValUnchecked(2, 2)); invTrans.TransformPoint(initialCentroid); } RWMol molCp(mol); molCp.removeConformer(0); molCp.addConformer(new Conformer(confCp), true); RDGeom::Point3D ave; unsigned int nSelectedAtoms = 0; // Calculates the average coord as by-product extractAtomRadii(confCp, nAtoms, shapeOpts, ave, nSelectedAtoms, rad_vector); // translate steric center to origin DEBUG_MSG("steric center: (" << ave << ")"); res.shift = {-ave.x, -ave.y, -ave.z}; res.coord.resize(nAlignmentAtoms * 3); extractAtomCoords(confCp, nAtoms, shapeOpts, ave, res.coord); unsigned int numFeatures = 0; if (shapeOpts.customFeatures.empty()) { extractFeatureCoords(confCp, nAtoms, nSelectedAtoms, feature_idx_type, shapeOpts, ave, numFeatures, res, rad_vector); } else if (shapeOpts.useColors) { extractCustomFeatureCoords(nSelectedAtoms, shapeOpts, ave, numFeatures, res, rad_vector, trans); } // Now cut the final vectors down to the actual number of atoms and // features used. nAlignmentAtoms = nSelectedAtoms + numFeatures; res.coord.resize(nAlignmentAtoms * 3); rad_vector.resize(nAlignmentAtoms); res.atom_type_vector.resize(nAlignmentAtoms); Align3D::setAlpha(rad_vector.data(), rad_vector.size(), res.alpha_vector); // regular atom self overlap Align3D::getVolumeAtomIndexVector(res.atom_type_vector.data(), res.atom_type_vector.size(), res.volumeAtomIndexVector); res.sov = Align3D::ComputeShapeOverlap( res.coord.data(), res.alpha_vector, res.volumeAtomIndexVector, res.coord.data(), res.alpha_vector, res.volumeAtomIndexVector); DEBUG_MSG("sov: " << res.sov); // feature self overlap if (feature_idx_type.size() > 0) { Align3D::getColorAtomType2IndexVectorMap(res.atom_type_vector.data(), res.atom_type_vector.size(), res.colorAtomType2IndexVectorMap); res.sof = Align3D::ComputeFeatureOverlap( res.coord.data(), res.alpha_vector, res.colorAtomType2IndexVectorMap, res.coord.data(), res.alpha_vector, res.colorAtomType2IndexVectorMap); DEBUG_MSG("sof: " << res.sof); } // Finally set the shift to the negative of the original average coords. res.shift = {initialCentroid.x, initialCentroid.y, initialCentroid.z}; return res; } std::pair AlignShape(const ShapeInput &refShape, ShapeInput &fitShape, std::vector &matrix, double opt_param, unsigned int max_preiters, unsigned int max_postiters) { std::set jointColorAtomTypeSet; Align3D::getJointColorTypeSet( refShape.atom_type_vector.data(), refShape.atom_type_vector.size(), fitShape.atom_type_vector.data(), fitShape.atom_type_vector.size(), jointColorAtomTypeSet); auto mapCp = refShape.colorAtomType2IndexVectorMap; Align3D::restrictColorAtomType2IndexVectorMap(mapCp, jointColorAtomTypeSet); // Take copy of the color atom mappings so as not to alter the input shape // which might be re-used. auto fitMapCp = fitShape.colorAtomType2IndexVectorMap; Align3D::restrictColorAtomType2IndexVectorMap(fitMapCp, jointColorAtomTypeSet); DEBUG_MSG("Running alignment..."); double nbr_st = 0.0; double nbr_ct = 0.0; Align3D::Neighbor_Conformers( refShape.coord.data(), refShape.alpha_vector, refShape.volumeAtomIndexVector, mapCp, refShape.sov, refShape.sof, fitShape.coord.data(), fitShape.alpha_vector, fitShape.volumeAtomIndexVector, fitMapCp, fitShape.sov, fitShape.sof, !jointColorAtomTypeSet.empty(), max_preiters, max_postiters, opt_param, matrix.data(), nbr_st, nbr_ct); DEBUG_MSG("Done!"); DEBUG_MSG("nbr_st: " << nbr_st); DEBUG_MSG("nbr_ct: " << nbr_ct); std::vector transformed(fitShape.coord.size()); Align3D::VApplyRotTransMatrix(transformed.data(), fitShape.coord.data(), fitShape.coord.size() / 3, matrix.data()); fitShape.coord = transformed; return std::make_pair(nbr_st, nbr_ct); } void TransformConformer(const std::vector &finalTrans, const std::vector &finalRot, const std::vector &matrix, const ShapeInput &fitShape, Conformer &fitConf) { // Move fitConf to fitShape's initial centroid and principal axes RDGeom::Transform3D transform0; transform0.SetTranslation( RDGeom::Point3D{fitShape.shift[0], fitShape.shift[1], fitShape.shift[2]}); RDGeom::Transform3D transform1; transform1.setValUnchecked(0, 0, fitShape.inertialRot[0]); transform1.setValUnchecked(0, 1, fitShape.inertialRot[1]); transform1.setValUnchecked(0, 2, fitShape.inertialRot[2]); transform1.setValUnchecked(1, 0, fitShape.inertialRot[3]); transform1.setValUnchecked(1, 1, fitShape.inertialRot[4]); transform1.setValUnchecked(1, 2, fitShape.inertialRot[5]); transform1.setValUnchecked(2, 0, fitShape.inertialRot[6]); transform1.setValUnchecked(2, 1, fitShape.inertialRot[7]); transform1.setValUnchecked(2, 2, fitShape.inertialRot[8]); transform1.setValUnchecked(2, 3, 0.0); transform1.setValUnchecked(3, 0, 0.0); transform1.setValUnchecked(3, 1, 0.0); transform1.setValUnchecked(3, 2, 0.0); transform1.setValUnchecked(3, 3, 1.0); // Apply the overlay matrix. // The matrix comes in as a linear form of a 3x3 rotation matrix and a // 3x1 translation. Convert that into a RDGeom::Trans3D RDGeom::Transform3D transform2; transform2.setValUnchecked(0, 0, matrix[0]); transform2.setValUnchecked(0, 1, matrix[1]); transform2.setValUnchecked(0, 2, matrix[2]); transform2.setValUnchecked(0, 3, matrix[9]); transform2.setValUnchecked(1, 0, matrix[3]); transform2.setValUnchecked(1, 1, matrix[4]); transform2.setValUnchecked(1, 2, matrix[5]); transform2.setValUnchecked(1, 3, matrix[10]); transform2.setValUnchecked(2, 0, matrix[6]); transform2.setValUnchecked(2, 1, matrix[7]); transform2.setValUnchecked(2, 2, matrix[8]); transform2.setValUnchecked(2, 3, matrix[11]); transform2.setValUnchecked(3, 0, 0.0); transform2.setValUnchecked(3, 1, 0.0); transform2.setValUnchecked(3, 2, 0.0); transform2.setValUnchecked(3, 3, 1.0); // Now apply the final rotation and final translation, which // is to put it into the reference shape's frame of reference. RDGeom::Transform3D transform3; transform3.setValUnchecked(0, 0, finalRot[0]); transform3.setValUnchecked(0, 1, finalRot[1]); transform3.setValUnchecked(0, 2, finalRot[2]); transform3.setValUnchecked(0, 3, finalTrans[0]); transform3.setValUnchecked(1, 0, finalRot[3]); transform3.setValUnchecked(1, 1, finalRot[4]); transform3.setValUnchecked(1, 2, finalRot[5]); transform3.setValUnchecked(1, 3, finalTrans[1]); transform3.setValUnchecked(2, 0, finalRot[6]); transform3.setValUnchecked(2, 1, finalRot[7]); transform3.setValUnchecked(2, 2, finalRot[8]); transform3.setValUnchecked(2, 3, finalTrans[2]); transform3.setValUnchecked(3, 0, 0.0); transform3.setValUnchecked(3, 1, 0.0); transform3.setValUnchecked(3, 2, 0.0); transform3.setValUnchecked(3, 3, 1.0); auto finalTransform = transform3 * transform2 * transform1 * transform0; MolTransforms::transformConformer(fitConf, finalTransform); } std::pair AlignMolecule( const ShapeInput &refShape, ROMol &fit, std::vector &matrix, const ShapeInputOptions &shapeOpts, int fitConfId, double opt_param, unsigned int max_preiters, unsigned int max_postiters, bool applyRefShift) { PRECONDITION(matrix.size() == 12, "bad matrix size"); Align3D::setUseCutOff(true); DEBUG_MSG("Fit details:"); auto fitShape = PrepareConformer(fit, fitConfId, shapeOpts); auto tanis = AlignShape(refShape, fitShape, matrix, opt_param, max_preiters, max_postiters); // transform fit coords Conformer &fit_conformer = fit.getConformer(fitConfId); std::vector finalTrans{0.0, 0.0, 0.0}; std::vector finalRot{1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; if (applyRefShift) { finalTrans = std::vector{-refShape.shift[0], -refShape.shift[1], -refShape.shift[2]}; // set the final rotation to the inverse of the original inertialRot // of the reference. finalRot = std::vector{refShape.inertialRot[0], refShape.inertialRot[3], refShape.inertialRot[6], refShape.inertialRot[1], refShape.inertialRot[4], refShape.inertialRot[7], refShape.inertialRot[2], refShape.inertialRot[5], refShape.inertialRot[8]}; } TransformConformer(finalTrans, finalRot, matrix, fitShape, fit_conformer); fit.setProp("shape_align_shape_tanimoto", tanis.first); fit.setProp("shape_align_color_tanimoto", tanis.second); return tanis; } std::pair AlignMolecule( const ShapeInput &refShape, ROMol &fit, std::vector &matrix, int fitConfId, bool useColors, double opt_param, unsigned int max_preiters, unsigned int max_postiters, bool applyRefShift) { ShapeInputOptions shapeOpts; shapeOpts.useColors = useColors; return AlignMolecule(refShape, fit, matrix, shapeOpts, fitConfId, opt_param, max_preiters, max_postiters, applyRefShift); } std::pair AlignMolecule( const ROMol &ref, ROMol &fit, std::vector &matrix, const ShapeInputOptions &refShapeOpts, const ShapeInputOptions &fitShapeOpts, int refConfId, int fitConfId, double opt_param, unsigned int max_preiters, unsigned int max_postiters) { Align3D::setUseCutOff(true); if (refShapeOpts.useColors != fitShapeOpts.useColors) { BOOST_LOG(rdWarningLog) << "useColor values inconsistent between the reference and fit molecules. Colors will not be used in the alignment." << std::endl; } DEBUG_MSG("Reference details:"); auto refShape = PrepareConformer(ref, refConfId, refShapeOpts); bool applyRefShift = true; auto scores = AlignMolecule(refShape, fit, matrix, fitShapeOpts, fitConfId, opt_param, max_preiters, max_postiters, applyRefShift); return scores; } std::pair AlignMolecule(const ROMol &ref, ROMol &fit, std::vector &matrix, int refConfId, int fitConfId, bool useColors, double opt_param, unsigned int max_preiters, unsigned int max_postiters) { Align3D::setUseCutOff(true); DEBUG_MSG("Reference details:"); ShapeInputOptions shapeOpts; shapeOpts.useColors = useColors; return AlignMolecule(ref, fit, matrix, shapeOpts, shapeOpts, refConfId, fitConfId, opt_param, max_preiters, max_postiters); } std::pair ScoreShape(const ShapeInput &shape1, ShapeInput &shape2, bool useColors) { double shapeOverlap = Align3D::ComputeShapeOverlap( shape1.coord.data(), shape1.alpha_vector, shape1.volumeAtomIndexVector, shape2.coord.data(), shape2.alpha_vector, shape2.volumeAtomIndexVector); double shape = shapeOverlap / (shape1.sov + shape2.sov - shapeOverlap); std::set jointColorAtomTypeSet; Align3D::getJointColorTypeSet( shape1.atom_type_vector.data(), shape1.atom_type_vector.size(), shape2.atom_type_vector.data(), shape2.atom_type_vector.size(), jointColorAtomTypeSet); // Take copy of the color atom mappings so as not to alter the input shape // which might be re-used. double color = 0.0; if (useColors) { auto shape1MapCp = shape1.colorAtomType2IndexVectorMap; Align3D::restrictColorAtomType2IndexVectorMap(shape1MapCp, jointColorAtomTypeSet); auto shape2MapCp = shape2.colorAtomType2IndexVectorMap; Align3D::restrictColorAtomType2IndexVectorMap(shape2MapCp, jointColorAtomTypeSet); double featureOverlap = Align3D::ComputeFeatureOverlap( shape1.coord.data(), shape1.alpha_vector, shape1MapCp, shape2.coord.data(), shape2.alpha_vector, shape2MapCp, nullptr); color = featureOverlap / (shape1.sof + shape2.sof - featureOverlap); } return std::make_pair(shape, color); } std::pair ScoreMolecule(const ShapeInput &shape, RDKit::ROMol &mol, const ShapeInputOptions &molShapeOpts, int molConfId) { ShapeInputOptions shapeOpts = molShapeOpts; shapeOpts.normalize = false; auto shape2 = PrepareConformer(mol, molConfId, shapeOpts); return ScoreShape(shape, shape2, shapeOpts.useColors); } std::pair ScoreMolecule(const RDKit::ROMol &mol1, RDKit::ROMol &mol2, const ShapeInputOptions &mol1ShapeOpts, const ShapeInputOptions &mol2ShapeOpts, int mol1ConfId, int mol2ConfId) { ShapeInputOptions shapeOpts = mol1ShapeOpts; shapeOpts.normalize = false; auto shape1 = PrepareConformer(mol1, mol1ConfId, shapeOpts); shapeOpts = mol2ShapeOpts; shapeOpts.normalize = false; auto shape2 = PrepareConformer(mol2, mol2ConfId, shapeOpts); // If either shapeOpts has useColors of false, we have to go with that. bool useColors = mol1ShapeOpts.useColors && mol2ShapeOpts.useColors; return ScoreShape(shape1, shape2, useColors); }