diff --git a/Code/GraphMol/MolStandardize/Fragment.cpp b/Code/GraphMol/MolStandardize/Fragment.cpp index 1e8db8d52..a8aca0847 100644 --- a/Code/GraphMol/MolStandardize/Fragment.cpp +++ b/Code/GraphMol/MolStandardize/Fragment.cpp @@ -161,10 +161,10 @@ void FragmentRemover::removeInPlace(RWMol &mol) { mol.commitBatchEdit(); } -bool isOrganic(const ROMol &frag) { +bool isOrganic(const ROMol &mol, const std::vector &indices) { // Returns true if fragment contains at least one carbon atom. - for (const auto at : frag.atoms()) { - if (at->getAtomicNum() == 6) { + for (auto idx : indices) { + if (mol.getAtomWithIdx(idx)->getAtomicNum() == 6) { return true; } } @@ -179,57 +179,84 @@ LargestFragmentChooser::LargestFragmentChooser( countHeavyAtomsOnly = other.countHeavyAtomsOnly; } -ROMol *LargestFragmentChooser::choose(const ROMol &mol) { +ROMol *LargestFragmentChooser::choose(const ROMol &mol) const { + auto res = new RWMol(mol); + chooseInPlace(*res); + // resanitize the molecule + MolOps::sanitizeMol(*res); + + return static_cast(res); +} + +void LargestFragmentChooser::chooseInPlace(RWMol &mol) const { BOOST_LOG(rdInfoLog) << "Running LargestFragmentChooser\n"; if (!mol.getNumAtoms()) { - return new ROMol(mol); + return; } - std::vector> frags = MolOps::getMolFrags(mol); + + std::vector> frags; + MolOps::getMolFrags(mol, frags); + if (frags.size() == 1) { + // nothing to do + return; + } + LargestFragmentChooser::Largest l; - for (const auto &frag : frags) { - std::string smiles = MolToSmiles(*frag); + SmilesWriteParams ps; + int bestFragment = -1; + for (auto fidx = 0u; fidx < frags.size(); ++fidx) { + const auto &frag = frags[fidx]; + std::string smiles = MolFragmentToSmiles(mol, ps, frag); BOOST_LOG(rdInfoLog) << "Fragment: " << smiles << "\n"; - bool organic = isOrganic(*frag); + bool organic = isOrganic(mol, frag); if (this->preferOrganic) { // Skip this fragment if not organic and we already have an organic // fragment as the largest so far - if (l.Fragment != nullptr && l.Organic && !organic) { + if (bestFragment >= 0 && l.Organic && !organic) { continue; } // Reset largest if it wasn't organic and this fragment is organic // if largest and organic and not largest['organic']: - if (l.Fragment != nullptr && organic && !l.Organic) { - l.Fragment = nullptr; + if (bestFragment >= 0 && organic && !l.Organic) { + bestFragment = -1; } } unsigned int numatoms = 0; if (this->useAtomCount) { - for (const auto at : frag->atoms()) { + for (const auto idx : frag) { ++numatoms; if (!this->countHeavyAtomsOnly) { - numatoms += at->getTotalNumHs(); + numatoms += mol.getAtomWithIdx(idx)->getTotalNumHs(); } } // Skip this fragment if fewer atoms than the largest - if (l.Fragment != nullptr && (numatoms < l.NumAtoms)) { + if (bestFragment >= 0 && (numatoms < l.NumAtoms)) { continue; } } // Skip this fragment if equal number of atoms but weight is lower - double weight = Descriptors::calcExactMW(*frag); - if (l.Fragment != nullptr && - (!this->useAtomCount || numatoms == l.NumAtoms) && + double weight = 0.0; + for (auto idx : frag) { + const auto atom = mol.getAtomWithIdx(idx); + // it's not important to be perfect here + weight += 100 * atom->getAtomicNum() + atom->getIsotope() - + atom->getFormalCharge() * .1; + if (!this->countHeavyAtomsOnly) { + weight += atom->getTotalNumHs(); + } + } + + if (bestFragment >= 0 && (!this->useAtomCount || numatoms == l.NumAtoms) && (weight < l.Weight)) { continue; } // Skip this fragment if equal number of atoms and equal weight but smiles // comes last alphabetically - if (l.Fragment != nullptr && - (!this->useAtomCount || numatoms == l.NumAtoms) && + if (bestFragment >= 0 && (!this->useAtomCount || numatoms == l.NumAtoms) && (weight == l.Weight) && (smiles > l.Smiles)) { continue; } @@ -238,13 +265,21 @@ ROMol *LargestFragmentChooser::choose(const ROMol &mol) { << numatoms << ")\n"; // Otherwise this is the largest so far l.Smiles = smiles; - l.Fragment = frag; + bestFragment = fidx; l.NumAtoms = numatoms; l.Weight = weight; l.Organic = organic; } - - return new ROMol(*(l.Fragment)); + mol.beginBatchEdit(); + for (auto fi = 0; fi < static_cast(frags.size()); ++fi) { + if (fi == bestFragment) { + continue; + } + for (auto i : frags[fi]) { + mol.removeAtom(i); + } + } + mol.commitBatchEdit(); } LargestFragmentChooser::Largest::Largest() : Smiles(""), Fragment(nullptr) {} diff --git a/Code/GraphMol/MolStandardize/Fragment.h b/Code/GraphMol/MolStandardize/Fragment.h index 8f08fa76b..e7d465df9 100644 --- a/Code/GraphMol/MolStandardize/Fragment.h +++ b/Code/GraphMol/MolStandardize/Fragment.h @@ -83,7 +83,8 @@ class RDKIT_MOLSTANDARDIZE_EXPORT LargestFragmentChooser { LargestFragmentChooser(const LargestFragmentChooser &other); ~LargestFragmentChooser() = default; - ROMol *choose(const ROMol &mol); + ROMol *choose(const ROMol &mol) const; + void chooseInPlace(RWMol &mol) const; struct Largest { Largest(); Largest(std::string &smiles, boost::shared_ptr fragment, diff --git a/Code/GraphMol/MolStandardize/MolStandardize.cpp b/Code/GraphMol/MolStandardize/MolStandardize.cpp index 102ac1b61..425365fa9 100644 --- a/Code/GraphMol/MolStandardize/MolStandardize.cpp +++ b/Code/GraphMol/MolStandardize/MolStandardize.cpp @@ -182,16 +182,47 @@ void cleanupInPlace(std::vector &mols, int numThreads, mols, numThreads, params); } +void tautomerParentInPlace(RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { + if (!skip_standardize) { + cleanupInPlace(mol, params); + } + + canonicalTautomerInPlace(mol, params); + cleanupInPlace(mol, params); +} + +void tautomerParentInPlace(std::vector &mols, int numThreads, + const CleanupParameters ¶ms, + bool skip_standardize) { + auto sfunc = [skip_standardize](RWMol &m, const CleanupParameters &ps) { + tautomerParentInPlace(m, ps, skip_standardize); + }; + standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); +} + RWMol *tautomerParent(const RWMol &mol, const CleanupParameters ¶ms, bool skip_standardize) { std::unique_ptr res{new RWMol(mol)}; - if (!skip_standardize) { - cleanupInPlace(*res, params); - } + tautomerParentInPlace(*res, params, skip_standardize); + return res.release(); +} - std::unique_ptr ct{canonicalTautomer(res.get(), params)}; - cleanupInPlace(*ct, params); - return ct.release(); +void fragmentParentInPlace(std::vector &mols, int numThreads, + const CleanupParameters ¶ms, + bool skip_standardize) { + auto sfunc = [skip_standardize](RWMol &m, const CleanupParameters &ps) { + fragmentParentInPlace(m, ps, skip_standardize); + }; + standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); +} +void fragmentParentInPlace(RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { + if (!skip_standardize) { + cleanupInPlace(mol, params); + } + LargestFragmentChooser lfragchooser(params.preferOrganic); + lfragchooser.chooseInPlace(mol); } // Return the fragment parent of a given molecule. @@ -200,65 +231,109 @@ RWMol *tautomerParent(const RWMol &mol, const CleanupParameters ¶ms, RWMol *fragmentParent(const RWMol &mol, const CleanupParameters ¶ms, bool skip_standardize) { std::unique_ptr res{new RWMol(mol)}; - if (!skip_standardize) { - cleanupInPlace(*res, params); - } - LargestFragmentChooser lfragchooser(params.preferOrganic); - return static_cast(lfragchooser.choose(*res)); + fragmentParentInPlace(*res, params, skip_standardize); + return res.release(); } +void stereoParentInPlace(std::vector &mols, int numThreads, + const CleanupParameters ¶ms, + bool skip_standardize) { + auto sfunc = [skip_standardize](RWMol &m, const CleanupParameters &ps) { + stereoParentInPlace(m, ps, skip_standardize); + }; + standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); +} +void stereoParentInPlace(RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { + if (!skip_standardize) { + cleanupInPlace(mol, params); + } + + MolOps::removeStereochemistry(mol); +} RWMol *stereoParent(const RWMol &mol, const CleanupParameters ¶ms, bool skip_standardize) { - RWMol *res = new RWMol(mol); - if (!skip_standardize) { - cleanupInPlace(*res, params); - } - - MolOps::removeStereochemistry(*res); - return res; + std::unique_ptr res{new RWMol(mol)}; + stereoParentInPlace(*res, params, skip_standardize); + return res.release(); +} +void isotopeParentInPlace(std::vector &mols, int numThreads, + const CleanupParameters ¶ms, + bool skip_standardize) { + auto sfunc = [skip_standardize](RWMol &m, const CleanupParameters &ps) { + isotopeParentInPlace(m, ps, skip_standardize); + }; + standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); } -RWMol *isotopeParent(const RWMol &mol, const CleanupParameters ¶ms, - bool skip_standardize) { - RWMol *res = new RWMol(mol); +void isotopeParentInPlace(RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { if (!skip_standardize) { - cleanupInPlace(*res, params); + cleanupInPlace(mol, params); } - for (auto atom : res->atoms()) { + for (auto atom : mol.atoms()) { atom->setIsotope(0); } - return res; +} +RWMol *isotopeParent(const RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { + std::unique_ptr res{new RWMol(mol)}; + isotopeParentInPlace(*res, params, skip_standardize); + return res.release(); } +void chargeParentInPlace(std::vector &mols, int numThreads, + const CleanupParameters ¶ms, + bool skip_standardize) { + auto sfunc = [skip_standardize](RWMol &m, const CleanupParameters &ps) { + chargeParentInPlace(m, ps, skip_standardize); + }; + standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); +} +void chargeParentInPlace(RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { + fragmentParentInPlace(mol, params, skip_standardize); + Uncharger uncharger(params.doCanonical); + uncharger.unchargeInPlace(mol); + cleanupInPlace(mol, params); +} RWMol *chargeParent(const RWMol &mol, const CleanupParameters ¶ms, bool skip_standardize) { // Return the charge parent of a given molecule. // The charge parent is the uncharged version of the fragment parent. + std::unique_ptr res{new RWMol(mol)}; + chargeParentInPlace(*res, params, skip_standardize); + return res.release(); +} - std::unique_ptr fragparent{ - fragmentParent(mol, params, skip_standardize)}; +void superParentInPlace(RWMol &mol, const CleanupParameters ¶ms, + bool skip_standardize) { + if (!skip_standardize) { + cleanupInPlace(mol, params); + } + // we can skip fragmentParent since the chargeParent takes care of that + chargeParentInPlace(mol, params, true); + isotopeParentInPlace(mol, params, true); + stereoParentInPlace(mol, params, true); + tautomerParentInPlace(mol, params, true); + cleanupInPlace(mol, params); +} - Uncharger uncharger(params.doCanonical); - uncharger.unchargeInPlace(*fragparent); - cleanupInPlace(*fragparent, params); - return fragparent.release(); +void superParentInPlace(std::vector &mols, int numThreads, + const CleanupParameters ¶ms, + bool skip_standardize) { + auto sfunc = [skip_standardize](RWMol &m, const CleanupParameters &ps) { + superParentInPlace(m, ps, skip_standardize); + }; + standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); } RWMol *superParent(const RWMol &mol, const CleanupParameters ¶ms, bool skip_standardize) { - std::unique_ptr res; - if (!skip_standardize) { - res.reset(cleanup(mol, params)); - } else { - res.reset(new RWMol(mol)); - } - // we can skip fragmentParent since the chargeParent takes care of that - res.reset(chargeParent(*res, params, true)); - res.reset(isotopeParent(*res, params, true)); - res.reset(stereoParent(*res, params, true)); - res.reset(tautomerParent(*res, params, true)); - return cleanup(*res, params); + std::unique_ptr res{new RWMol(mol)}; + superParentInPlace(*res, params, skip_standardize); + return res.release(); } RWMol *normalize(const RWMol *mol, const CleanupParameters ¶ms) { @@ -281,7 +356,7 @@ void normalizeInPlace(RWMol &mol, const CleanupParameters ¶ms) { void normalizeInPlace(std::vector &mols, int numThreads, const CleanupParameters ¶ms) { std::unique_ptr normalizer{normalizerFromParams(params)}; - auto sfunc = [&](RWMol &m, const CleanupParameters &) { + auto sfunc = [&normalizer](RWMol &m, const CleanupParameters &) { normalizer->normalizeInPlace(m); }; standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); @@ -291,10 +366,10 @@ void reionizeInPlace(RWMol &mol, const CleanupParameters ¶ms) { std::unique_ptr reionizer{reionizerFromParams(params)}; reionizer->reionizeInPlace(mol); } -void reionizeInPlace(std::vector &mols,int numThreads, +void reionizeInPlace(std::vector &mols, int numThreads, const CleanupParameters ¶ms) { std::unique_ptr reionizer{reionizerFromParams(params)}; - auto sfunc = [&](RWMol &m, const CleanupParameters &) { + auto sfunc = [&reionizer](RWMol &m, const CleanupParameters &) { reionizer->reionizeInPlace(m); }; standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); @@ -311,10 +386,10 @@ void removeFragmentsInPlace(RWMol &mol, const CleanupParameters ¶ms) { remover->removeInPlace(mol); } -void removeFragmentsInPlace(std::vector &mols,int numThreads, +void removeFragmentsInPlace(std::vector &mols, int numThreads, const CleanupParameters ¶ms) { std::unique_ptr remover{fragmentRemoverFromParams(params)}; - auto sfunc = [&](RWMol &m, const CleanupParameters &) { + auto sfunc = [&remover](RWMol &m, const CleanupParameters &) { remover->removeInPlace(m); }; standardizeMultipleMolsInPlace(sfunc, mols, numThreads, params); @@ -325,6 +400,10 @@ RWMol *canonicalTautomer(const RWMol *mol, const CleanupParameters ¶ms) { std::unique_ptr te{tautomerEnumeratorFromParams(params)}; return static_cast(te->canonicalize(*mol)); } +void canonicalTautomerInPlace(RWMol &mol, const CleanupParameters ¶ms) { + std::unique_ptr te{tautomerEnumeratorFromParams(params)}; + te->canonicalizeInPlace(mol); +} std::string standardizeSmiles(const std::string &smiles) { std::unique_ptr mol{SmilesToMol(smiles, 0, false)}; diff --git a/Code/GraphMol/MolStandardize/MolStandardize.h b/Code/GraphMol/MolStandardize/MolStandardize.h index f49b05b13..fb523bc70 100644 --- a/Code/GraphMol/MolStandardize/MolStandardize.h +++ b/Code/GraphMol/MolStandardize/MolStandardize.h @@ -151,6 +151,8 @@ RDKIT_MOLSTANDARDIZE_EXPORT void removeFragmentsInPlace( RDKIT_MOLSTANDARDIZE_EXPORT RWMol *canonicalTautomer( const RWMol *mol, const CleanupParameters ¶ms = defaultCleanupParameters); +RDKIT_MOLSTANDARDIZE_EXPORT void canonicalTautomerInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters); //! Returns the tautomer parent of a given molecule. The fragment parent is the /// standardized canonical tautomer of the molecule @@ -158,6 +160,13 @@ RDKIT_MOLSTANDARDIZE_EXPORT RWMol *tautomerParent( const RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, bool skipStandardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void tautomerParentInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, + bool skipStandardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void tautomerParentInPlace( + std::vector &mols, int numThreads = 1, + const CleanupParameters ¶ms = defaultCleanupParameters, + bool skipStandardize = false); //! Returns the fragment parent of a given molecule. The fragment parent is the /// largest organic covalent unit in the molecule. @@ -165,18 +174,39 @@ RDKIT_MOLSTANDARDIZE_EXPORT RWMol *fragmentParent( const RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void fragmentParentInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void fragmentParentInPlace( + std::vector &mols, int numThreads = 1, + const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); //! calls removeStereochemistry() on the given molecule RDKIT_MOLSTANDARDIZE_EXPORT RWMol *stereoParent( const RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void stereoParentInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void stereoParentInPlace( + std::vector &mols, int numThreads = 1, + const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); //! removes all isotopes specifications from the given molecule RDKIT_MOLSTANDARDIZE_EXPORT RWMol *isotopeParent( const RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void isotopeParentInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void isotopeParentInPlace( + std::vector &mols, int numThreads = 1, + const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); //! Returns the charge parent of a given molecule. The charge parent is the //! uncharged version of the fragment parent. @@ -184,13 +214,28 @@ RDKIT_MOLSTANDARDIZE_EXPORT RWMol *chargeParent( const RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void chargeParentInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); +//! operates on multiple molecules +RDKIT_MOLSTANDARDIZE_EXPORT void chargeParentInPlace( + std::vector &mols, int numThreads = 1, + const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); -//! Returns the super parent. The super parent is the fragment, charge, isotope, -//! stereo, and tautomer parent of the molecule. +//! Returns the super parent. The super parent is the fragment, charge, +//! isotope, stereo, and tautomer parent of the molecule. RDKIT_MOLSTANDARDIZE_EXPORT RWMol *superParent( const RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void superParentInPlace( + RWMol &mol, const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); +RDKIT_MOLSTANDARDIZE_EXPORT void superParentInPlace( + std::vector &mols, int numThreads = 1, + const CleanupParameters ¶ms = defaultCleanupParameters, + bool skip_standardize = false); //! Convenience function for quickly standardizing a single SMILES string. /// Returns a standardized canonical SMILES string given a SMILES string. diff --git a/Code/GraphMol/MolStandardize/Tautomer.cpp b/Code/GraphMol/MolStandardize/Tautomer.cpp index c3396d51f..e1067174d 100644 --- a/Code/GraphMol/MolStandardize/Tautomer.cpp +++ b/Code/GraphMol/MolStandardize/Tautomer.cpp @@ -594,5 +594,47 @@ ROMol *TautomerEnumerator::canonicalize( return pickCanonical(res, scoreFunc); } +void TautomerEnumerator::canonicalizeInPlace( + RWMol &mol, boost::function scoreFunc) const { + auto thisCopy = TautomerEnumerator(*this); + thisCopy.setReassignStereo(false); + auto res = thisCopy.enumerate(mol); + if (res.empty()) { + BOOST_LOG(rdWarningLog) + << "no tautomers found, molecule unchanged" << std::endl; + return; + } + std::unique_ptr tmp{pickCanonical(res, scoreFunc)}; + + TEST_ASSERT(tmp->getNumAtoms() == mol.getNumAtoms()); + TEST_ASSERT(tmp->getNumBonds() == mol.getNumBonds()); + // now copy the info from the canonical tautomer over to the input molecule + for (const auto tmpAtom : tmp->atoms()) { + auto atom = mol.getAtomWithIdx(tmpAtom->getIdx()); + TEST_ASSERT(tmpAtom->getAtomicNum() == atom->getAtomicNum()); + atom->setFormalCharge(tmpAtom->getFormalCharge()); + atom->setNoImplicit(tmpAtom->getNoImplicit()); + atom->setIsAromatic(tmpAtom->getIsAromatic()); + atom->setNumExplicitHs(tmpAtom->getNumExplicitHs()); + atom->setNumRadicalElectrons(tmpAtom->getNumRadicalElectrons()); + atom->setChiralTag(tmpAtom->getChiralTag()); + } + for (const auto tmpBond : tmp->bonds()) { + auto bond = mol.getBondWithIdx(tmpBond->getIdx()); + TEST_ASSERT(tmpBond->getBeginAtomIdx() == bond->getBeginAtomIdx()); + TEST_ASSERT(tmpBond->getEndAtomIdx() == bond->getEndAtomIdx()); + bond->setBondType(tmpBond->getBondType()); + bond->setBondDir(tmpBond->getBondDir()); + bond->setIsAromatic(tmpBond->getIsAromatic()); + bond->setIsConjugated(tmpBond->getIsConjugated()); + if (tmpBond->getStereoAtoms().size() == 2) { + bond->setStereoAtoms(tmpBond->getStereoAtoms()[0], + tmpBond->getStereoAtoms()[1]); + } + bond->setStereo(tmpBond->getStereo()); + } + mol.updatePropertyCache(false); +} + } // namespace MolStandardize } // namespace RDKit diff --git a/Code/GraphMol/MolStandardize/Tautomer.h b/Code/GraphMol/MolStandardize/Tautomer.h index ec968b9f6..f5da8635a 100644 --- a/Code/GraphMol/MolStandardize/Tautomer.h +++ b/Code/GraphMol/MolStandardize/Tautomer.h @@ -396,6 +396,9 @@ class RDKIT_MOLSTANDARDIZE_EXPORT TautomerEnumerator { ROMol *canonicalize(const ROMol &mol, boost::function scoreFunc = TautomerScoringFunctions::scoreTautomer) const; + void canonicalizeInPlace(RWMol &mol, + boost::function scoreFunc = + TautomerScoringFunctions::scoreTautomer) const; private: bool setTautomerStereoAndIsoHs(const ROMol &mol, ROMol &taut, diff --git a/Code/GraphMol/MolStandardize/Wrap/Fragment.cpp b/Code/GraphMol/MolStandardize/Wrap/Fragment.cpp index 6423fb7ba..c4c1831d0 100644 --- a/Code/GraphMol/MolStandardize/Wrap/Fragment.cpp +++ b/Code/GraphMol/MolStandardize/Wrap/Fragment.cpp @@ -29,6 +29,12 @@ ROMol *chooseHelper(MolStandardize::LargestFragmentChooser &self, const ROMol &mol) { return self.choose(mol); } + +void chooseInPlaceHelper(MolStandardize::LargestFragmentChooser &self, + ROMol &mol) { + self.chooseInPlace(static_cast(mol)); +} + MolStandardize::FragmentRemover *removerFromParams(const std::string &data, bool leave_last, bool skip_if_all_match) { @@ -73,7 +79,11 @@ struct fragment_wrapper { .def(python::init( (python::arg("self"), python::arg("params")))) .def("choose", &chooseHelper, (python::arg("self"), python::arg("mol")), - "", python::return_value_policy()); + "", python::return_value_policy()) + .def("chooseInPlace", &chooseInPlaceHelper, + (python::arg("self"), python::arg("mol")), "", + python::return_value_policy()); + ; } }; diff --git a/Code/GraphMol/MolStandardize/Wrap/rdMolStandardize.cpp b/Code/GraphMol/MolStandardize/Wrap/rdMolStandardize.cpp index 6af85d8d1..d0f80c533 100644 --- a/Code/GraphMol/MolStandardize/Wrap/rdMolStandardize.cpp +++ b/Code/GraphMol/MolStandardize/Wrap/rdMolStandardize.cpp @@ -72,6 +72,19 @@ void inPlaceHelper(RDKit::ROMol *mol, python::object params, FUNCTYPE func) { func(*static_cast(mol), *ps); } +template +void inPlaceHelper2(RDKit::ROMol *mol, python::object params, + bool skip_standardize, FUNCTYPE func) { + if (!mol) { + throw_value_error("Molecule is None"); + } + const RDKit::MolStandardize::CleanupParameters *ps = + &RDKit::MolStandardize::defaultCleanupParameters; + if (params) { + ps = python::extract(params); + } + func(*static_cast(mol), *ps, skip_standardize); +} void cleanupInPlaceHelper(RDKit::ROMol *mol, python::object params) { inPlaceHelper( mol, params, @@ -104,6 +117,60 @@ void removeFragmentsInPlaceHelper(RDKit::ROMol *mol, python::object params) { RDKit::MolStandardize::removeFragmentsInPlace)); } +void fragmentParentInPlaceHelper(RDKit::ROMol *mol, python::object params, + bool skip_standardize) { + inPlaceHelper2( + mol, params, skip_standardize, + static_cast(RDKit::MolStandardize::fragmentParentInPlace)); +} + +void stereoParentInPlaceHelper(RDKit::ROMol *mol, python::object params, + bool skip_standardize) { + inPlaceHelper2( + mol, params, skip_standardize, + static_cast(RDKit::MolStandardize::stereoParentInPlace)); +} + +void isotopeParentInPlaceHelper(RDKit::ROMol *mol, python::object params, + bool skip_standardize) { + inPlaceHelper2( + mol, params, skip_standardize, + static_cast(RDKit::MolStandardize::isotopeParentInPlace)); +} + +void chargeParentInPlaceHelper(RDKit::ROMol *mol, python::object params, + bool skip_standardize) { + inPlaceHelper2( + mol, params, skip_standardize, + static_cast(RDKit::MolStandardize::chargeParentInPlace)); +} + +void superParentInPlaceHelper(RDKit::ROMol *mol, python::object params, + bool skip_standardize) { + inPlaceHelper2( + mol, params, skip_standardize, + static_cast(RDKit::MolStandardize::superParentInPlace)); +} + +void tautomerParentInPlaceHelper(RDKit::ROMol *mol, python::object params, + bool skip_standardize) { + inPlaceHelper2( + mol, params, skip_standardize, + static_cast(RDKit::MolStandardize::tautomerParentInPlace)); +} + template void mtinPlaceHelper(python::object pymols, int numThreads, python::object params, FUNCTYPE func) { @@ -115,7 +182,7 @@ void mtinPlaceHelper(python::object pymols, int numThreads, unsigned int nmols = python::extract(pymols.attr("__len__")()); std::vector mols(nmols); for (auto i = 0u; i < nmols; ++i) { - RDKit::RWMol *mol = static_cast( + auto mol = static_cast( python::extract(pymols[i])()); mols[i] = mol; } @@ -124,6 +191,28 @@ void mtinPlaceHelper(python::object pymols, int numThreads, func(mols, numThreads, *ps); } } +template +void mtinPlaceHelper2(python::object pymols, int numThreads, + python::object params, bool skip_standardize, + FUNCTYPE func) { + const auto *ps = + &RDKit::MolStandardize::defaultCleanupParameters; + if (params) { + ps = python::extract(params); + } + unsigned int nmols = python::extract(pymols.attr("__len__")()); + std::vector mols(nmols); + for (auto i = 0u; i < nmols; ++i) { + auto mol = static_cast( + python::extract(pymols[i])()); + mols[i] = mol; + } + { + NOGIL gil; + func(mols, numThreads, *ps, skip_standardize); + } +} + void mtcleanupInPlaceHelper(python::object mols, int numThreads, python::object params) { mtinPlaceHelper( @@ -160,6 +249,63 @@ void mtremoveFragmentsInPlaceHelper(python::object mols, int numThreads, RDKit::MolStandardize::removeFragmentsInPlace)); } +void mtfragmentParentInPlaceHelper(python::object mols, int numThreads, + python::object params, + bool skip_standardize) { + mtinPlaceHelper2(mols, numThreads, params, skip_standardize, + static_cast &, int, + const RDKit::MolStandardize::CleanupParameters &, bool)>( + RDKit::MolStandardize::fragmentParentInPlace)); +} + +void mtstereoParentInPlaceHelper(python::object mols, int numThreads, + python::object params, bool skip_standardize) { + mtinPlaceHelper2( + mols, numThreads, params, skip_standardize, + static_cast &, int, + const RDKit::MolStandardize::CleanupParameters &, + bool)>(RDKit::MolStandardize::stereoParentInPlace)); +} + +void mtisotopeParentInPlaceHelper(python::object mols, int numThreads, + python::object params, + bool skip_standardize) { + mtinPlaceHelper2( + mols, numThreads, params, skip_standardize, + static_cast &, int, + const RDKit::MolStandardize::CleanupParameters &, + bool)>(RDKit::MolStandardize::isotopeParentInPlace)); +} + +void mtchargeParentInPlaceHelper(python::object mols, int numThreads, + python::object params, bool skip_standardize) { + mtinPlaceHelper2( + mols, numThreads, params, skip_standardize, + static_cast &, int, + const RDKit::MolStandardize::CleanupParameters &, + bool)>(RDKit::MolStandardize::chargeParentInPlace)); +} + +void mtsuperParentInPlaceHelper(python::object mols, int numThreads, + python::object params, bool skip_standardize) { + mtinPlaceHelper2( + mols, numThreads, params, skip_standardize, + static_cast &, int, + const RDKit::MolStandardize::CleanupParameters &, + bool)>(RDKit::MolStandardize::superParentInPlace)); +} + +void mttautomerParentInPlaceHelper(python::object mols, int numThreads, + python::object params, + bool skip_standardize) { + mtinPlaceHelper2(mols, numThreads, params, skip_standardize, + static_cast &, int, + const RDKit::MolStandardize::CleanupParameters &, bool)>( + RDKit::MolStandardize::tautomerParentInPlace)); +} + template RDKit::ROMol *parentHelper(const RDKit::ROMol *mol, python::object params, bool skip_standardize, FUNCTYPE func) { @@ -346,30 +492,87 @@ BOOST_PYTHON_MODULE(rdMolStandardize) { python::arg("skipStandardize") = false), docString.c_str(), python::return_value_policy()); + docString = "Generates the tautomer parent in place"; + python::def("TautomerParentInPlace", tautomerParentInPlaceHelper, + (python::arg("mol"), python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Generates the tautomer parent in place for multiple molecules"; + python::def("TautomerParentInPlace", mttautomerParentInPlaceHelper, + (python::arg("mols"), python::arg("numThreads"), + python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Returns the largest fragment after doing a cleanup"; python::def("FragmentParent", fragmentParentHelper, (python::arg("mol"), python::arg("params") = python::object(), python::arg("skipStandardize") = false), docString.c_str(), python::return_value_policy()); - docString = "calls removeStereochemistry() on the given molecule"; + docString = "Generates the largest fragment in place"; + python::def("FragmentParentInPlace", fragmentParentInPlaceHelper, + (python::arg("mol"), python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Generates the largest fragment in place for multiple molecules"; + python::def("FragmentParentInPlace", mtfragmentParentInPlaceHelper, + (python::arg("mols"), python::arg("numThreads"), + python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + python::def("StereoParent", stereoParentHelper, (python::arg("mol"), python::arg("params") = python::object(), python::arg("skipStandardize") = false), docString.c_str(), python::return_value_policy()); + docString = "Generates the stereo parent in place"; + python::def("StereoParentInPlace", stereoParentInPlaceHelper, + (python::arg("mol"), python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Generates the stereo parent in place for multiple molecules"; + python::def("StereoParentInPlace", mtstereoParentInPlaceHelper, + (python::arg("mols"), python::arg("numThreads"), + python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); docString = "removes all isotopes specifications from the given molecule"; python::def("IsotopeParent", isotopeParentHelper, (python::arg("mol"), python::arg("params") = python::object(), python::arg("skipStandardize") = false), docString.c_str(), python::return_value_policy()); + docString = "Generates the isotope parent in place"; + python::def("IsotopeParentInPlace", isotopeParentInPlaceHelper, + (python::arg("mol"), python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Generates the isotope parent in place for multiple molecules"; + python::def("IsotopeParentInPlace", mtisotopeParentInPlaceHelper, + (python::arg("mols"), python::arg("numThreads"), + python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); docString = "Returns the uncharged version of the largest fragment"; python::def("ChargeParent", chargeParentHelper, (python::arg("mol"), python::arg("params") = python::object(), python::arg("skipStandardize") = false), docString.c_str(), python::return_value_policy()); + docString = "Generates the charge parent in place"; + python::def("ChargeParentInPlace", chargeParentInPlaceHelper, + (python::arg("mol"), python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Generates the chargeparent in place for multiple molecules"; + python::def("ChargeParentInPlace", mtchargeParentInPlaceHelper, + (python::arg("mols"), python::arg("numThreads"), + python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Returns the super parent. The super parent is the fragment, charge, " "isotope, stereo, and tautomer parent of the molecule."; @@ -378,6 +581,18 @@ BOOST_PYTHON_MODULE(rdMolStandardize) { python::arg("skipStandardize") = false), docString.c_str(), python::return_value_policy()); + docString = "Generates the super parent in place"; + python::def("SuperParentInPlace", superParentInPlaceHelper, + (python::arg("mol"), python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Generates the super parent in place for multiple molecules"; + python::def("SuperParentInPlace", mtsuperParentInPlaceHelper, + (python::arg("mols"), python::arg("numThreads"), + python::arg("params") = python::object(), + python::arg("skipStandardize") = false), + docString.c_str()); + docString = "Applies a series of standard transformations to correct functional " "groups and recombine charges"; @@ -431,6 +646,7 @@ BOOST_PYTHON_MODULE(rdMolStandardize) { (python::arg("mol"), python::arg("params") = python::object()), docString.c_str(), python::return_value_policy()); + docString = "Returns the molecule disconnected using the organometallics" " rules."; diff --git a/Code/GraphMol/MolStandardize/Wrap/testMolStandardize.py b/Code/GraphMol/MolStandardize/Wrap/testMolStandardize.py index 1836645dc..b80d1fed9 100644 --- a/Code/GraphMol/MolStandardize/Wrap/testMolStandardize.py +++ b/Code/GraphMol/MolStandardize/Wrap/testMolStandardize.py @@ -77,7 +77,6 @@ class TestCase(unittest.TestCase): md.DisconnectInPlace(nm) self.assertEqual(Chem.MolToSmiles(nm), "[Br-].[CH-]1CCCCC1.[Zn+2]") - # test user defined metal_nof md.SetMetalNof( Chem.MolFromSmarts( @@ -130,7 +129,6 @@ class TestCase(unittest.TestCase): reionizer.reionizeInPlace(nm) self.assertEqual(Chem.MolToSmiles(nm), "O=S(O)c1ccc(S(=O)(=O)[O-])cc1") - # try reionize with another acid base pair library without the right # pairs abfile = os.path.join(RDConfig.RDBaseDir, 'Code', 'GraphMol', 'MolStandardize', 'test_data', @@ -157,7 +155,6 @@ class TestCase(unittest.TestCase): uncharger.unchargeInPlace(nm3) self.assertEqual(Chem.MolToSmiles(nm3), "C[N+](C)(C)CC(CC(=O)[O-])C(=O)O") - uncharger = rdMolStandardize.Uncharger(canonicalOrder=True) nm3 = uncharger.uncharge(mol3) self.assertEqual(Chem.MolToSmiles(nm3), "C[N+](C)(C)CC(CC(=O)O)C(=O)[O-]") @@ -179,10 +176,16 @@ class TestCase(unittest.TestCase): mol2 = Chem.MolFromSmiles("[N+](=O)([O-])[O-].[CH3+]") nm2 = lfragchooser.choose(mol2) self.assertEqual(Chem.MolToSmiles(nm2), "O=[N+]([O-])[O-]") + nm2 = Chem.Mol(mol2) + lfragchooser.chooseInPlace(nm2) + self.assertEqual(Chem.MolToSmiles(nm2), "O=[N+]([O-])[O-]") lfragchooser2 = rdMolStandardize.LargestFragmentChooser(preferOrganic=True) nm3 = lfragchooser2.choose(mol2) self.assertEqual(Chem.MolToSmiles(nm3), "[CH3+]") + nm3 = Chem.Mol(mol2) + lfragchooser2.chooseInPlace(nm3) + self.assertEqual(Chem.MolToSmiles(nm3), "[CH3+]") fragremover = rdMolStandardize.FragmentRemover(skip_if_all_match=True) mol = Chem.MolFromSmiles("[Na+].Cl.Cl.Br") @@ -261,7 +264,6 @@ class TestCase(unittest.TestCase): normalizer.normalizeInPlace(nm) self.assertEqual(Chem.MolToSmiles(nm), "Cn1ccccc1=O") - def test9Validate(self): vm = rdMolStandardize.RDKitValidation() mol = Chem.MolFromSmiles("CO(C)C", sanitize=False) @@ -991,7 +993,7 @@ chlorine [Cl] def test22StandardizeInPlace(self): m = Chem.MolFromSmiles("O=N(=O)-C(O[Fe])C(C(=O)O)C-N(=O)=O") rdMolStandardize.CleanupInPlace(m) - self.assertEqual(Chem.MolToSmiles(m),"O=C([O-])C(C[N+](=O)[O-])C(O)[N+](=O)[O-].[Fe+]") + self.assertEqual(Chem.MolToSmiles(m), "O=C([O-])C(C[N+](=O)[O-])C(O)[N+](=O)[O-].[Fe+]") m = Chem.MolFromSmiles('[F-].[Cl-].[Br-].CC') rdMolStandardize.RemoveFragmentsInPlace(m) @@ -1011,51 +1013,139 @@ chlorine [Cl] def test23CleanupInPlaceMT(self): ind = (("O=N(=O)-C(O[Fe])C(C(=O)O)C-N(=O)=O", - "O=C([O-])C(C[N+](=O)[O-])C(O)[N+](=O)[O-].[Fe+]"), - ("O=N(=O)-CC(O[Fe])C(C(=O)O)C-N(=O)=O", - "O=C([O-])C(C[N+](=O)[O-])C(O)C[N+](=O)[O-].[Fe+]"), - ("O=N(=O)-CCC(O[Fe])C(C(=O)O)C-N(=O)=O", - "O=C([O-])C(C[N+](=O)[O-])C(O)CC[N+](=O)[O-].[Fe+]")) + "O=C([O-])C(C[N+](=O)[O-])C(O)[N+](=O)[O-].[Fe+]"), + ("O=N(=O)-CC(O[Fe])C(C(=O)O)C-N(=O)=O", + "O=C([O-])C(C[N+](=O)[O-])C(O)C[N+](=O)[O-].[Fe+]"), + ("O=N(=O)-CCC(O[Fe])C(C(=O)O)C-N(=O)=O", + "O=C([O-])C(C[N+](=O)[O-])C(O)CC[N+](=O)[O-].[Fe+]")) for i in range(4): ind = ind + ind - ms = [Chem.MolFromSmiles(x) for x,y in ind] - rdMolStandardize.CleanupInPlace(ms,4) - self.assertEqual([Chem.MolToSmiles(m) for m in ms], - [y for x,y in ind]) + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.CleanupInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) def test24NormalizeInPlaceMT(self): ind = (("O=N(=O)-CC-N(=O)=O", "O=[N+]([O-])CC[N+](=O)[O-]"), - ("O=N(=O)-CCC-N(=O)=O", "O=[N+]([O-])CCC[N+](=O)[O-]"), - ("O=N(=O)-CCCC-N(=O)=O", "O=[N+]([O-])CCCC[N+](=O)[O-]")) + ("O=N(=O)-CCC-N(=O)=O", "O=[N+]([O-])CCC[N+](=O)[O-]"), ("O=N(=O)-CCCC-N(=O)=O", + "O=[N+]([O-])CCCC[N+](=O)[O-]")) for i in range(4): ind = ind + ind - ms = [Chem.MolFromSmiles(x) for x,y in ind] - rdMolStandardize.NormalizeInPlace(ms,4) - self.assertEqual([Chem.MolToSmiles(m) for m in ms], - [y for x,y in ind]) + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.NormalizeInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) def test25ReionizeInPlaceMT(self): ind = (("c1cc([O-])cc(C(=O)O)c1", "O=C([O-])c1cccc(O)c1"), - ("c1cc(C[O-])cc(C(=O)O)c1", "O=C([O-])c1cccc(CO)c1"), - ("c1cc(CC[O-])cc(C(=O)O)c1", "O=C([O-])c1cccc(CCO)c1")) + ("c1cc(C[O-])cc(C(=O)O)c1", "O=C([O-])c1cccc(CO)c1"), ("c1cc(CC[O-])cc(C(=O)O)c1", + "O=C([O-])c1cccc(CCO)c1")) for i in range(4): ind = ind + ind - ms = [Chem.MolFromSmiles(x) for x,y in ind] - rdMolStandardize.ReionizeInPlace(ms,4) - self.assertEqual([Chem.MolToSmiles(m) for m in ms], - [y for x,y in ind]) + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.ReionizeInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) def test26RemoveFragmentsInPlaceMT(self): - ind = (("CCCC.Cl.[Na]", "CCCC"), - ("CCCCO.Cl.[Na]", "CCCCO"), - ("CCOC.Cl.[Na]", "CCOC")) + ind = (("CCCC.Cl.[Na]", "CCCC"), ("CCCCO.Cl.[Na]", "CCCCO"), ("CCOC.Cl.[Na]", "CCOC")) for i in range(4): ind = ind + ind - ms = [Chem.MolFromSmiles(x) for x,y in ind] - rdMolStandardize.RemoveFragmentsInPlace(ms,4) - self.assertEqual([Chem.MolToSmiles(m) for m in ms], - [y for x,y in ind]) + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.RemoveFragmentsInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) + def test27ChargeParentInPlaceMT(self): + ind = (("O=C([O-])c1ccccc1", "O=C(O)c1ccccc1"), ("CCCCO.Cl.[Na]", "CCCCO"), + ("[N+](=O)([O-])[O-].[CH2]", "[CH2]")) + lfParams = rdMolStandardize.CleanupParameters() + lfParams.preferOrganic = True + for x, y in ind: + m2 = Chem.MolFromSmiles(x) + rdMolStandardize.ChargeParentInPlace(m2, lfParams) + self.assertEqual(Chem.MolToSmiles(m2), y) + + ms = [Chem.MolFromSmiles(x) for x, y in ind] + for i in range(4): + ind = ind + ind + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.ChargeParentInPlace(ms, 4, lfParams) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) + + def test28TautomerParentInPlaceMT(self): + ind = (("[O-]c1ccc(C(=O)O)cc1CC=CO", "O=CCCc1cc(C(=O)[O-])ccc1O"), + ("[O-]c1ccc(C(=O)O)cc1CC=CO.[Na+]", "O=CCCc1cc(C(=O)[O-])ccc1O.[Na+]"), + ("[O-]c1ccc(C(=O)O)cc1C[13CH]=CO", "O=C[13CH2]Cc1cc(C(=O)[O-])ccc1O")) + for x, y in ind: + m2 = Chem.MolFromSmiles(x) + rdMolStandardize.TautomerParentInPlace(m2) + self.assertEqual(Chem.MolToSmiles(m2), y) + + ms = [Chem.MolFromSmiles(x) for x, y in ind] + for i in range(4): + ind = ind + ind + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.TautomerParentInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) + + def test29StereoParentInPlaceMT(self): + ind = (("F[C@H](O)Cl", "OC(F)Cl"), ("F[C@H](CCO)Cl", "OCCC(F)Cl"), ("F[C@H](CCO)Cl.F[C@H](O)Cl", + "OC(F)Cl.OCCC(F)Cl")) + for x, y in ind: + m2 = Chem.MolFromSmiles(x) + rdMolStandardize.StereoParentInPlace(m2) + self.assertEqual(Chem.MolToSmiles(m2), y) + + ms = [Chem.MolFromSmiles(x) for x, y in ind] + for i in range(4): + ind = ind + ind + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.StereoParentInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) + + def test30FragmentParentInPlaceMT(self): + ind = (("O=C([O-])c1ccccc1", "O=C([O-])c1ccccc1"), ("CCCCO.Cl.[Na]", "CCCC[O-]"), + ("[N+](=O)([O-])[O-].[CH2]", "[CH2]")) + lfParams = rdMolStandardize.CleanupParameters() + lfParams.preferOrganic = True + for x, y in ind: + m2 = Chem.MolFromSmiles(x) + rdMolStandardize.FragmentParentInPlace(m2, lfParams) + self.assertEqual(Chem.MolToSmiles(m2), y) + + ms = [Chem.MolFromSmiles(x) for x, y in ind] + for i in range(4): + ind = ind + ind + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.FragmentParentInPlace(ms, 4, lfParams) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) + + def test31IsotopeParentInPlaceMT(self): + ind = (("[13CH3]C", "CC"), ("[13CH3]C.C", "C.CC"), ("[13CH3][12CH3]", "CC")) + for x, y in ind: + m2 = Chem.MolFromSmiles(x) + rdMolStandardize.IsotopeParentInPlace(m2) + self.assertEqual(Chem.MolToSmiles(m2), y) + + ms = [Chem.MolFromSmiles(x) for x, y in ind] + for i in range(4): + ind = ind + ind + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.IsotopeParentInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) + + def test32SuperParentInPlaceMT(self): + ind = (("[O-]c1ccc(C(=O)O)cc1CC=CO", "O=CCCc1cc(C(=O)O)ccc1O"), + ("[O-]c1ccc(C(=O)O)cc1CC=CO.[Na+]", + "O=CCCc1cc(C(=O)O)ccc1O"), ("[O-]c1ccc(C(=O)O)cc1C[13CH]=CO", "O=CCCc1cc(C(=O)O)ccc1O")) + for x, y in ind: + m2 = Chem.MolFromSmiles(x) + rdMolStandardize.SuperParentInPlace(m2) + self.assertEqual(Chem.MolToSmiles(m2), y) + + ms = [Chem.MolFromSmiles(x) for x, y in ind] + for i in range(4): + ind = ind + ind + ms = [Chem.MolFromSmiles(x) for x, y in ind] + rdMolStandardize.SuperParentInPlace(ms, 4) + self.assertEqual([Chem.MolToSmiles(m) for m in ms], [y for x, y in ind]) if __name__ == "__main__": diff --git a/Code/GraphMol/MolStandardize/catch_tests.cpp b/Code/GraphMol/MolStandardize/catch_tests.cpp index d3fec6734..48cc5ba16 100644 --- a/Code/GraphMol/MolStandardize/catch_tests.cpp +++ b/Code/GraphMol/MolStandardize/catch_tests.cpp @@ -794,6 +794,8 @@ TEST_CASE("tautomer parent") { REQUIRE(m); std::unique_ptr nm{MolStandardize::tautomerParent(*m)}; CHECK(MolToSmiles(*nm) == "O=CCCc1cc(C(=O)[O-])ccc1O"); + MolStandardize::tautomerParentInPlace(*m); + CHECK(MolToSmiles(*m) == "O=CCCc1cc(C(=O)[O-])ccc1O"); } } @@ -830,6 +832,8 @@ TEST_CASE("super parent") { REQUIRE(m); std::unique_ptr nm{MolStandardize::superParent(*m)}; CHECK(MolToSmiles(*nm) == "O=CCCc1cc(C(=O)O)c(O)c(C(F)Cl)c1O"); + MolStandardize::superParentInPlace(*m); + CHECK(MolToSmiles(*m) == "O=CCCc1cc(C(=O)O)c(O)c(C(F)Cl)c1O"); } } @@ -957,6 +961,9 @@ TEST_CASE("Github 5318: standardizing unsanitized molecules should work") { std::unique_ptr res{MolStandardize::canonicalTautomer(m2.get())}; REQUIRE(res); CHECK(MolToSmiles(*res) == "Cc1cc[nH]n1.Cl"); + RWMol cp(*m2); + MolStandardize::canonicalTautomerInPlace(cp); + CHECK(MolToSmiles(cp) == "Cc1cc[nH]n1.Cl"); } SECTION("fragments") { std::unique_ptr res{MolStandardize::removeFragments(m2.get())}; @@ -1096,6 +1103,36 @@ TEST_CASE("in place operations") { MolStandardize::removeFragmentsInPlace(cp2); CHECK(MolToSmiles(cp2) == "CCCC"); } + SECTION("FragmentParent") { + auto m = "CCCC.Cl.[Na]"_smiles; + REQUIRE(m); + RWMol cp1(*m); + MolStandardize::fragmentParentInPlace(cp1); + // note: this isn't a nice answer, and it should be + // fixed, but it is what the code currently generates + CHECK(MolToSmiles(cp1) == "[CH2-]CCC"); + } + SECTION("ChargeParent") { + auto m = "[O-]C(=O)CCC.[Na+]"_smiles; + REQUIRE(m); + RWMol cp1(*m); + MolStandardize::chargeParentInPlace(cp1); + CHECK(MolToSmiles(cp1) == "CCCC(=O)O"); + } + SECTION("IsotopeParent") { + auto m = "[13CH3]C"_smiles; + REQUIRE(m); + RWMol cp1(*m); + MolStandardize::isotopeParentInPlace(cp1); + CHECK(MolToSmiles(cp1) == "CC"); + } + SECTION("StereoParent") { + auto m = "F[C@H](O)Cl"_smiles; + REQUIRE(m); + RWMol cp1(*m); + MolStandardize::stereoParentInPlace(cp1); + CHECK(MolToSmiles(cp1) == "OC(F)Cl"); + } SECTION("cleanup") { SmilesParserParams ps; ps.sanitize = false; @@ -1282,4 +1319,258 @@ TEST_CASE("RemoveFragments with multiple mols") { } } #endif +} + +TEST_CASE("charge with multiple mols") { + auto params = MolStandardize::defaultCleanupParameters; + params.preferOrganic = true; + + std::vector> data = { + {"O=C([O-])c1ccccc1", "O=C(O)c1ccccc1"}, + {"C[NH+](C)(C).[Cl-]", "CN(C)C"}, + {"[N+](=O)([O-])[O-].[CH2]", "[CH2]"}, + }; + // bulk that up a bit + for (auto iter = 0u; iter < 8; ++iter) { + auto sz = data.size(); + for (auto i = 0u; i < sz; ++i) { + data.push_back(data[i]); + } + } + std::vector> mols; + std::vector molPtrs; + for (const auto &[insmi, outsmi] : data) { + mols.emplace_back(SmilesToMol(insmi)); + REQUIRE(mols.back()); + molPtrs.push_back(mols.back().get()); + } + SECTION("basics") { + int numThreads = 1; + MolStandardize::chargeParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#ifdef RDK_BUILD_THREADSAFE_SSS + SECTION("multithreaded") { + int numThreads = 4; + MolStandardize::chargeParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#endif +} + +TEST_CASE("isotope with multiple mols") { + auto params = MolStandardize::defaultCleanupParameters; + + std::vector> data = { + {"[13CH3]C", "CC"}, + {"[13CH3]C.C", "C.CC"}, + {"[13CH3][12CH3]", "CC"}, + }; + // bulk that up a bit + for (auto iter = 0u; iter < 8; ++iter) { + auto sz = data.size(); + for (auto i = 0u; i < sz; ++i) { + data.push_back(data[i]); + } + } + std::vector> mols; + std::vector molPtrs; + for (const auto &[insmi, outsmi] : data) { + mols.emplace_back(SmilesToMol(insmi)); + REQUIRE(mols.back()); + molPtrs.push_back(mols.back().get()); + } + SECTION("basics") { + int numThreads = 1; + MolStandardize::isotopeParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#ifdef RDK_BUILD_THREADSAFE_SSS + SECTION("multithreaded") { + int numThreads = 4; + MolStandardize::isotopeParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#endif +} + +TEST_CASE("fragments with multiple mols") { + auto params = MolStandardize::defaultCleanupParameters; + params.preferOrganic = true; + + std::vector> data = { + {"O=C([O-])c1ccccc1", "O=C([O-])c1ccccc1"}, + {"C[NH+](C)(C).[Cl-]", "C[NH+](C)C"}, + {"[N+](=O)([O-])[O-].CC", "CC"}, + }; + // bulk that up a bit + for (auto iter = 0u; iter < 8; ++iter) { + auto sz = data.size(); + for (auto i = 0u; i < sz; ++i) { + data.push_back(data[i]); + } + } + std::vector> mols; + std::vector molPtrs; + for (const auto &[insmi, outsmi] : data) { + mols.emplace_back(SmilesToMol(insmi)); + REQUIRE(mols.back()); + molPtrs.push_back(mols.back().get()); + } + SECTION("basics") { + int numThreads = 1; + MolStandardize::fragmentParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#ifdef RDK_BUILD_THREADSAFE_SSS + SECTION("multithreaded") { + int numThreads = 4; + MolStandardize::fragmentParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#endif +} + +TEST_CASE("stereo with multiple mols") { + auto params = MolStandardize::defaultCleanupParameters; + + std::vector> data = { + {"F[C@H](O)Cl", "OC(F)Cl"}, + {"F[C@H](CCO)Cl", "OCCC(F)Cl"}, + {"F[C@H](CCO)Cl.F[C@H](O)Cl", "OC(F)Cl.OCCC(F)Cl"}, + }; + // bulk that up a bit + for (auto iter = 0u; iter < 8; ++iter) { + auto sz = data.size(); + for (auto i = 0u; i < sz; ++i) { + data.push_back(data[i]); + } + } + std::vector> mols; + std::vector molPtrs; + for (const auto &[insmi, outsmi] : data) { + mols.emplace_back(SmilesToMol(insmi)); + REQUIRE(mols.back()); + molPtrs.push_back(mols.back().get()); + } + SECTION("basics") { + int numThreads = 1; + MolStandardize::stereoParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#ifdef RDK_BUILD_THREADSAFE_SSS + SECTION("multithreaded") { + int numThreads = 4; + MolStandardize::stereoParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#endif +} + +TEST_CASE("tautomerParent with multiple mols") { + auto params = MolStandardize::defaultCleanupParameters; + + std::vector> data = { + {"[O-]c1ccc(C(=O)O)cc1CC=CO", "O=CCCc1cc(C(=O)[O-])ccc1O"}, + {"[O-]c1ccc(C(=O)O)cc1CC=CO.[Na+]", "O=CCCc1cc(C(=O)[O-])ccc1O.[Na+]"}, + {"[O-]c1ccc(C(=O)O)cc1C[13CH]=CO", "O=C[13CH2]Cc1cc(C(=O)[O-])ccc1O"}, + }; + // bulk that up a bit + for (auto iter = 0u; iter < 5; ++iter) { + auto sz = data.size(); + for (auto i = 0u; i < sz; ++i) { + data.push_back(data[i]); + } + } + std::vector> mols; + std::vector molPtrs; + for (const auto &[insmi, outsmi] : data) { + mols.emplace_back(SmilesToMol(insmi)); + REQUIRE(mols.back()); + molPtrs.push_back(mols.back().get()); + } + SECTION("basics") { + int numThreads = 1; + MolStandardize::tautomerParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#ifdef RDK_BUILD_THREADSAFE_SSS + SECTION("multithreaded") { + int numThreads = 4; + MolStandardize::tautomerParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#endif +} + +TEST_CASE("superParent with multiple mols") { + auto params = MolStandardize::defaultCleanupParameters; + + std::vector> data = { + {"[O-]c1ccc(C(=O)O)cc1CC=CO", "O=CCCc1cc(C(=O)O)ccc1O"}, + {"[O-]c1ccc(C(=O)O)cc1CC=CO.[Na+]", "O=CCCc1cc(C(=O)O)ccc1O"}, + {"[O-]c1ccc(C(=O)O)cc1C[13CH]=CO", "O=CCCc1cc(C(=O)O)ccc1O"}, + }; + // bulk that up a bit + for (auto iter = 0u; iter < 5; ++iter) { + auto sz = data.size(); + for (auto i = 0u; i < sz; ++i) { + data.push_back(data[i]); + } + } + std::vector> mols; + std::vector molPtrs; + for (const auto &[insmi, outsmi] : data) { + mols.emplace_back(SmilesToMol(insmi)); + REQUIRE(mols.back()); + molPtrs.push_back(mols.back().get()); + } + SECTION("basics") { + int numThreads = 1; + MolStandardize::superParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#ifdef RDK_BUILD_THREADSAFE_SSS + SECTION("multithreaded") { + int numThreads = 4; + MolStandardize::superParentInPlace(molPtrs, numThreads, params); + for (auto i = 0u; i < mols.size(); ++i) { + REQUIRE(mols[i]); + CHECK(MolToSmiles(*mols[i]) == data[i].second); + } + } +#endif } \ No newline at end of file diff --git a/Code/GraphMol/MolStandardize/testFragment.cpp b/Code/GraphMol/MolStandardize/testFragment.cpp index 9e529c3d7..9a04faf40 100644 --- a/Code/GraphMol/MolStandardize/testFragment.cpp +++ b/Code/GraphMol/MolStandardize/testFragment.cpp @@ -129,30 +129,41 @@ void test_largest_fragment() { std::shared_ptr m2(SmilesToMol(smi2)); std::shared_ptr res2(MolStandardize::fragmentParent(*m2, params)); TEST_ASSERT(MolToSmiles(*res2) == "O=C(O)c1ccccc1"); + lfragchooser.chooseInPlace(*m2); + TEST_ASSERT(MolToSmiles(*m2) == "O=C(O)c1ccccc1"); // No organic fragments smi3 = "[N+](=O)([O-])[O-]"; - std::shared_ptr m3(SmilesToMol(smi3)); + std::shared_ptr m3(SmilesToMol(smi3)); std::shared_ptr lfrag3(lfragchooser.choose(*m3)); TEST_ASSERT(MolToSmiles(*lfrag3) == "O=[N+]([O-])[O-]"); + lfragchooser.chooseInPlace(*m3); + TEST_ASSERT(MolToSmiles(*m3) == "O=[N+]([O-])[O-]"); // Larger inorganic should be chosen smi4 = "[N+](=O)([O-])[O-].[CH3+]"; - std::shared_ptr m4(SmilesToMol(smi4)); + std::shared_ptr m4(SmilesToMol(smi4)); std::shared_ptr lfrag4(lfragchooser.choose(*m4)); TEST_ASSERT(MolToSmiles(*lfrag4) == "O=[N+]([O-])[O-]"); + lfragchooser.chooseInPlace(*m4); + TEST_ASSERT(MolToSmiles(*m4) == "O=[N+]([O-])[O-]"); // Smaller organic fragment should be chosen over larger inorganic fragment. smi5 = "[N+](=O)([O-])[O-].[CH3+]"; - std::shared_ptr m5(SmilesToMol(smi5)); + std::shared_ptr m5(SmilesToMol(smi5)); std::shared_ptr lfrag5(lfrag_preferOrg.choose(*m5)); TEST_ASSERT(MolToSmiles(*lfrag5) == "[CH3+]"); + lfrag_preferOrg.chooseInPlace(*m5); + TEST_ASSERT(MolToSmiles(*m5) == "[CH3+]"); // Salt without charges. smi1 = "[Na].O=C(O)c1ccccc1"; std::shared_ptr m1(SmilesToMol(smi1)); std::shared_ptr res1(MolStandardize::fragmentParent(*m1, params)); - // TEST_ASSERT(MolToSmiles(*lfrag) == "CN(C)C"); + // std::cerr << MolToSmiles(*res1) << std::endl; + // TEST_ASSERT(MolToSmiles(*res1) == "O=C([O-])c1ccccc1"); + lfrag_preferOrg.chooseInPlace(*m1); + TEST_ASSERT(MolToSmiles(*m1) == "O=C(O)c1ccccc1"); smi6 = "[Na]OC(=O)c1ccccc1"; std::shared_ptr m6(SmilesToMol(smi6)); @@ -190,71 +201,87 @@ void test_largest_fragment() { { CleanupParameters lfParams; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m11(SmilesToMol(smi11)); + std::shared_ptr m11(SmilesToMol(smi11)); std::shared_ptr lfrag6(lfrag_params.choose(*m11)); TEST_ASSERT(MolToSmiles(*lfrag6) == "CNC[C@H](O)[C@@H](O)[C@H](O)[C@H](O)CO"); + lfrag_params.chooseInPlace(*m11); + TEST_ASSERT(MolToSmiles(*m11) == "CNC[C@H](O)[C@@H](O)[C@H](O)[C@H](O)CO"); } { CleanupParameters lfParams; lfParams.largestFragmentChooserCountHeavyAtomsOnly = true; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m11(SmilesToMol(smi11)); + std::shared_ptr m11(SmilesToMol(smi11)); std::shared_ptr lfrag6(lfrag_params.choose(*m11)); TEST_ASSERT(MolToSmiles(*lfrag6) == "O=C(O)c1ccc2nc(-c3cc(Cl)cc(Cl)c3)oc2c1"); + lfrag_params.chooseInPlace(*m11); + TEST_ASSERT(MolToSmiles(*m11) == "O=C(O)c1ccc2nc(-c3cc(Cl)cc(Cl)c3)oc2c1"); } { CleanupParameters lfParams; lfParams.largestFragmentChooserUseAtomCount = false; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m11(SmilesToMol(smi11)); + std::shared_ptr m11(SmilesToMol(smi11)); std::shared_ptr lfrag6(lfrag_params.choose(*m11)); TEST_ASSERT(MolToSmiles(*lfrag6) == "O=C(O)c1ccc2nc(-c3cc(Cl)cc(Cl)c3)oc2c1"); + lfrag_params.chooseInPlace(*m11); + TEST_ASSERT(MolToSmiles(*m11) == "O=C(O)c1ccc2nc(-c3cc(Cl)cc(Cl)c3)oc2c1"); } smi12 = "CC.O=[Pb]=O"; { CleanupParameters lfParams; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m12(SmilesToMol(smi12)); + std::shared_ptr m12(SmilesToMol(smi12)); std::shared_ptr lfrag7(lfrag_params.choose(*m12)); TEST_ASSERT(MolToSmiles(*lfrag7) == "CC"); + lfrag_params.chooseInPlace(*m12); + TEST_ASSERT(MolToSmiles(*m12) == "CC"); } { CleanupParameters lfParams; lfParams.largestFragmentChooserCountHeavyAtomsOnly = true; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m12(SmilesToMol(smi12)); + std::shared_ptr m12(SmilesToMol(smi12)); std::shared_ptr lfrag7(lfrag_params.choose(*m12)); TEST_ASSERT(MolToSmiles(*lfrag7) == "O=[Pb]=O"); + lfrag_params.chooseInPlace(*m12); + TEST_ASSERT(MolToSmiles(*m12) == "O=[Pb]=O"); } { CleanupParameters lfParams; lfParams.largestFragmentChooserUseAtomCount = false; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m12(SmilesToMol(smi12)); + std::shared_ptr m12(SmilesToMol(smi12)); std::shared_ptr lfrag7(lfrag_params.choose(*m12)); TEST_ASSERT(MolToSmiles(*lfrag7) == "O=[Pb]=O"); + lfrag_params.chooseInPlace(*m12); + TEST_ASSERT(MolToSmiles(*m12) == "O=[Pb]=O"); } { CleanupParameters lfParams; lfParams.largestFragmentChooserCountHeavyAtomsOnly = true; lfParams.preferOrganic = true; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m12(SmilesToMol(smi12)); + std::shared_ptr m12(SmilesToMol(smi12)); std::shared_ptr lfrag7(lfrag_params.choose(*m12)); TEST_ASSERT(MolToSmiles(*lfrag7) == "CC"); + lfrag_params.chooseInPlace(*m12); + TEST_ASSERT(MolToSmiles(*m12) == "CC"); } { CleanupParameters lfParams; lfParams.largestFragmentChooserUseAtomCount = false; lfParams.preferOrganic = true; LargestFragmentChooser lfrag_params(lfParams); - std::shared_ptr m12(SmilesToMol(smi12)); + std::shared_ptr m12(SmilesToMol(smi12)); std::shared_ptr lfrag7(lfrag_params.choose(*m12)); TEST_ASSERT(MolToSmiles(*lfrag7) == "CC"); + lfrag_params.chooseInPlace(*m12); + TEST_ASSERT(MolToSmiles(*m12) == "CC"); } BOOST_LOG(rdDebugLog) << "Finished" << std::endl; diff --git a/Code/GraphMol/MolStandardize/testTautomer.cpp b/Code/GraphMol/MolStandardize/testTautomer.cpp index b596d148b..a5a68ab86 100644 --- a/Code/GraphMol/MolStandardize/testTautomer.cpp +++ b/Code/GraphMol/MolStandardize/testTautomer.cpp @@ -717,11 +717,13 @@ void testCanonicalize() { TautomerEnumerator te(new TautomerCatalog(tautparams.get())); for (const auto &itm : canonTautomerData) { - std::unique_ptr mol{SmilesToMol(itm.first)}; + std::unique_ptr mol{SmilesToMol(itm.first)}; TEST_ASSERT(mol); std::unique_ptr res{te.canonicalize(*mol)}; TEST_ASSERT(res); TEST_ASSERT(MolToSmiles(*res) == itm.second); + te.canonicalizeInPlace(*mol); + TEST_ASSERT(MolToSmiles(*mol) == itm.second); } BOOST_LOG(rdInfoLog) << "Finished" << std::endl; } @@ -1374,10 +1376,12 @@ void testGithub3755() { {"NC(=N)C(N)CO", "N=C(N)C(N)CO"}, {"NC(=N)NC(N)CO", "N=C(N)NC(N)CO"}}; TautomerEnumerator te; for (const auto &pair : orig_vs_expected) { - ROMOL_SPTR orig(SmilesToMol(pair.first)); + std::unique_ptr orig{SmilesToMol(pair.first)}; TEST_ASSERT(orig); ROMOL_SPTR canonical(te.canonicalize(*orig)); TEST_ASSERT(MolToSmiles(*canonical) == pair.second); + te.canonicalizeInPlace(*orig); + TEST_ASSERT(MolToSmiles(*orig) == pair.second); } } diff --git a/Code/RDGeneral/CMakeLists.txt b/Code/RDGeneral/CMakeLists.txt index ecdf8d8e5..332da9ae2 100644 --- a/Code/RDGeneral/CMakeLists.txt +++ b/Code/RDGeneral/CMakeLists.txt @@ -12,7 +12,8 @@ rdkit_library(RDGeneral target_compile_definitions(RDGeneral PRIVATE RDKIT_RDGENERAL_BUILD) if(RDK_USE_BOOST_STACKTRACE AND UNIX AND NOT APPLE) -set(EXTRA_STACKTRACE_LIBS dl) + target_compile_definitions(RDGeneral PRIVATE RDK_USE_BOOST_STACKTRACE) + set(EXTRA_STACKTRACE_LIBS dl) endif() target_link_libraries(RDGeneral PUBLIC ${RDKit_THREAD_LIBS} ${EXTRA_STACKTRACE_LIBS})