1 // 2 // Copyright (C) 2017-2020 Greg Landrum 3 // 4 // @@ All Rights Reserved @@ 5 // This file is part of the RDKit. 6 // The contents are covered by the terms of the BSD license 7 // which is included in the file license.txt, found at the root 8 // of the RDKit source tree. 9 // 10 11 #include <RDGeneral/RDLog.h> 12 #include <GraphMol/RDKitBase.h> 13 #include <GraphMol/Substruct/SubstructMatch.h> 14 #include <GraphMol/MolTransforms/MolTransforms.h> 15 #include <Geometry/Transform3D.h> 16 #include <cstdlib> 17 18 #include "coordgen/sketcherMinimizer.h" 19 #include "coordgen/CoordgenFragmenter.h" 20 21 namespace RDKit { 22 namespace CoordGen { 23 24 struct CoordGenParams { 25 const float sketcherCoarsePrecision = 0.01; 26 const float sketcherStandardPrecision = SKETCHER_STANDARD_PRECISION; 27 const float sketcherBestPrecision = SKETCHER_BEST_PRECISION; 28 const float sketcherQuickPrecision = SKETCHER_QUICK_PRECISION; 29 RDGeom::INT_POINT2D_MAP 30 coordMap; // coordinates for fixing particular atoms of a template 31 const ROMol* templateMol = nullptr; // a molecule to use as a template 32 double coordgenScaling = 50.0; // at the time this was written, coordgen 33 // returned coordinates with a single bond 34 // length of 50. 35 std::string templateFileDir = ""; 36 float minimizerPrecision = 37 sketcherCoarsePrecision; // controls sketch precision 38 bool dbg_useConstrained = true; // debugging 39 bool dbg_useFixed = false; // debugging 40 bool minimizeOnly = false; // don't actually generate full coords 41 bool treatNonterminalBondsToMetalAsZeroOrder = 42 false; // set non-terminal bonds to metal atoms to be zero-order bonds 43 }; 44 45 static CoordGenParams defaultParams; 46 47 //! Generates a 2D conformer for a molecule and replaces the existing conformers 48 /*! 49 This call uses the CoordGen library from Schroedinger 50 51 \param mol the molecule we are working with 52 \param params a pointer to a parameter object 53 54 */ 55 template <typename T> 56 unsigned int addCoords(T& mol, const CoordGenParams* params = nullptr) { 57 if (!params) params = &defaultParams; 58 // FIX: the default value of this should be handled once in a threadsafe way 59 std::string templateFileDir; 60 if (params->templateFileDir != "") { 61 templateFileDir = params->templateFileDir; 62 } else { 63 auto rdbase = std::getenv("RDBASE"); 64 if (rdbase != nullptr) { 65 templateFileDir += rdbase; 66 templateFileDir += "/Data/"; 67 } 68 } 69 70 if (params->minimizeOnly && !mol.getNumConformers()) { 71 throw ValueErrorException( 72 "minimizeOnly set but molecule has no conformers"); 73 } 74 75 double scaleFactor = params->coordgenScaling; 76 77 sketcherMinimizer minimizer(params->minimizerPrecision); 78 auto min_mol = new sketcherMinimizerMolecule(); 79 80 minimizer.setTreatNonterminalBondsToMetalAsZOBs( 81 params->treatNonterminalBondsToMetalAsZeroOrder); 82 83 // FIX: only do this check once. 84 // std::cerr << " TEMPLATES: " << templateFileDir << std::endl; 85 if (templateFileDir != "") { 86 minimizer.setTemplateFileDir(templateFileDir); 87 } 88 bool hasTemplateMatch = false; 89 MatchVectType mv; 90 if (!params->minimizeOnly && params->templateMol && 91 params->templateMol->getNumConformers() == 1) { 92 if (SubstructMatch(mol, *(params->templateMol), mv)) { 93 hasTemplateMatch = true; 94 } 95 } 96 97 // if we're doing coordinate minimization it makes our life easier to 98 // start by translating to the origin 99 RDGeom::Point3D centroid{0.0, 0.0, 0.0}; 100 if (params->minimizeOnly) { 101 auto conf = mol.getConformer(); 102 centroid = MolTransforms::computeCentroid(conf); 103 } 104 105 std::vector<sketcherMinimizerAtom*> ats(mol.getNumAtoms()); 106 for (auto atit = mol.beginAtoms(); atit != mol.endAtoms(); ++atit) { 107 auto oatom = *atit; 108 auto atom = min_mol->addNewAtom(); 109 atom->molecule = min_mol; // seems like this should be in addNewAtom() 110 atom->atomicNumber = oatom->getAtomicNum(); 111 atom->charge = oatom->getFormalCharge(); 112 if (params->minimizeOnly) { 113 auto coords = mol.getConformer().getAtomPos(oatom->getIdx()); 114 coords -= centroid; 115 atom->coordinates = sketcherMinimizerPointF(coords.x * scaleFactor, 116 coords.y * scaleFactor); 117 atom->fixed = false; 118 atom->constrained = false; 119 } else if (hasTemplateMatch || params->coordMap.find(oatom->getIdx()) != 120 params->coordMap.end()) { 121 atom->constrained = params->dbg_useConstrained; 122 atom->fixed = params->dbg_useFixed; 123 if (hasTemplateMatch) { 124 for (auto& pr : mv) { 125 if (pr.second == static_cast<int>(oatom->getIdx())) { 126 const RDGeom::Point3D& coords = 127 params->templateMol->getConformer().getAtomPos(pr.first); 128 atom->templateCoordinates = sketcherMinimizerPointF( 129 coords.x * scaleFactor, coords.y * scaleFactor); 130 break; 131 } 132 } 133 } else { 134 const RDGeom::Point2D& coords = 135 params->coordMap.find(oatom->getIdx())->second; 136 atom->templateCoordinates = sketcherMinimizerPointF( 137 coords.x * scaleFactor, coords.y * scaleFactor); 138 } 139 } 140 ats[oatom->getIdx()] = atom; 141 } 142 143 // coordgen has its own ideas about what bond lengths should be 144 // if we are doing minimizeOnly we want to do a rigid scaling after the 145 // minimization in order to try and get as close to where we started as 146 // possible 147 double singleBondLenAccum = 0.0; 148 double bondLenAccum = 0.0; 149 unsigned nSingle = 0; 150 151 std::vector<sketcherMinimizerBond*> bnds(mol.getNumBonds()); 152 for (auto bndit = mol.beginBonds(); bndit != mol.endBonds(); ++bndit) { 153 auto obnd = *bndit; 154 auto bnd = min_mol->addNewBond(ats[obnd->getBeginAtomIdx()], 155 ats[obnd->getEndAtomIdx()]); 156 // FIX: This is no doubt wrong 157 switch (obnd->getBondType()) { 158 case Bond::SINGLE: 159 bnd->bondOrder = 1; 160 if (params->minimizeOnly) { 161 ++nSingle; 162 singleBondLenAccum += 163 (mol.getConformer().getAtomPos(obnd->getBeginAtomIdx()) - 164 mol.getConformer().getAtomPos(obnd->getEndAtomIdx())) 165 .length(); 166 } 167 break; 168 case Bond::DOUBLE: 169 bnd->bondOrder = 2; 170 break; 171 case Bond::TRIPLE: 172 bnd->bondOrder = 3; 173 break; 174 case Bond::AROMATIC: 175 bnd->bondOrder = 1; 176 break; 177 default: 178 BOOST_LOG(rdWarningLog) << "unrecognized bond type"; 179 } 180 bnds[obnd->getIdx()] = bnd; 181 if (params->minimizeOnly) { 182 bondLenAccum += (mol.getConformer().getAtomPos(obnd->getBeginAtomIdx()) - 183 mol.getConformer().getAtomPos(obnd->getEndAtomIdx())) 184 .length(); 185 } 186 } 187 double avgBondLen = 1.0; 188 if (params->minimizeOnly) { 189 if (nSingle) { 190 avgBondLen = singleBondLenAccum / nSingle; 191 } else if (mol.getNumBonds()) { 192 avgBondLen = bondLenAccum / mol.getNumBonds(); 193 } 194 } 195 196 // setup double bond stereo 197 min_mol->assignBondsAndNeighbors(ats, bnds); 198 for (auto bndit = mol.beginBonds(); bndit != mol.endBonds(); ++bndit) { 199 auto obnd = *bndit; 200 if (obnd->getBondType() != Bond::DOUBLE || 201 obnd->getStereo() <= Bond::STEREOANY || 202 obnd->getStereo() > Bond::STEREOTRANS) 203 continue; 204 205 sketcherMinimizerBondStereoInfo sinfo; 206 sinfo.atom1 = ats[obnd->getStereoAtoms()[0]]; 207 sinfo.atom2 = ats[obnd->getStereoAtoms()[1]]; 208 sinfo.stereo = (obnd->getStereo() == Bond::STEREOZ || 209 obnd->getStereo() == Bond::STEREOCIS) 210 ? sketcherMinimizerBondStereoInfo::cis 211 : sketcherMinimizerBondStereoInfo::trans; 212 auto bnd = bnds[obnd->getIdx()]; 213 bnd->setStereoChemistry(sinfo); 214 bnd->setAbsoluteStereoFromStereoInfo(); 215 } 216 217 minimizer.initialize(min_mol); 218 if (!params->minimizeOnly) { 219 minimizer.runGenerateCoordinates(); 220 } else { 221 CoordgenFragmenter::splitIntoFragments(min_mol); 222 minimizer.m_minimizer.minimizeMolecule(min_mol); 223 224 // Hook the fragments into the minimizer so that they 225 // get cleaned up when the minimizer is terminated 226 minimizer._fragments = min_mol->getFragments(); 227 } 228 auto conf = new Conformer(mol.getNumAtoms()); 229 for (size_t i = 0; i < mol.getNumAtoms(); ++i) { 230 auto coords = RDGeom::Point3D(ats[i]->coordinates.x() / scaleFactor, 231 ats[i]->coordinates.y() / scaleFactor, 0.0); 232 if (params->minimizeOnly) { 233 // readjust the average bond length from 1 (coordgen's target) 234 // to whatever we started with 235 coords *= avgBondLen; 236 coords += centroid; 237 } 238 conf->setAtomPos(i, coords); 239 // std::cerr << atom->coordinates << std::endl; 240 } 241 conf->set3D(false); 242 mol.clearConformers(); 243 auto res = mol.addConformer(conf, true); 244 if (!params->minimizeOnly && params->coordMap.empty() && 245 !params->templateMol) { 246 // center the coordinates 247 RDGeom::Transform3D tf; 248 auto centroid = MolTransforms::computeCentroid(*conf); 249 centroid *= -1; 250 tf.SetTranslation(centroid); 251 MolTransforms::transformConformer(*conf, tf); 252 } 253 return res; 254 } 255 } // end of namespace CoordGen 256 } // namespace RDKit 257