// // Copyright (C) 2021 Greg Landrum // // @@ All Rights Reserved @@ // This file is part of the RDKit. // The contents are covered by the terms of the BSD license // which is included in the file license.txt, found at the root // of the RDKit source tree. // #pragma once #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef RDK_BUILD_AVALON_SUPPORT #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common_defs.h" #include "JSONParsers.h" #include #include #include #include #include #include #ifdef RDK_BUILD_THREADSAFE_SSS #include #include #endif #define GET_JSON_VALUE(doc, drawingDetails, key, type) \ if (doc.contains(#key)) { \ const auto &key##Val = doc.at(#key); \ if (!key##Val.is_##type()) { \ return "JSON contains '" #key "' field, but its type is not '" #type \ "'"; \ } \ drawingDetails.key = key##Val.as_##type(); \ } namespace bj = boost::json; namespace RDKit { namespace MinimalLib { static constexpr int d_defaultWidth = 250; static constexpr int d_defaultHeight = 200; #define LPT_OPT_GET(opt) opt = pt.get(#opt, opt); #define LPT_OPT_GET2(holder, opt) holder.opt = pt.get(#opt, holder.opt); RWMol *mol_from_input(const std::string &input, const std::string &details_json = "") { auto haveMolBlock = false; auto haveRDKitJson = false; if (input.find("M END") != std::string::npos) { haveMolBlock = true; } else if (input.find("commonchem") != std::string::npos || input.find("rdkitjson") != std::string::npos) { haveRDKitJson = true; } auto haveSmiles = (!haveMolBlock && !haveRDKitJson); bool sanitize = true; bool kekulize = true; bool removeHs = haveSmiles; bool mergeQueryHs = false; bool setAromaticity = true; bool fastFindRings = true; bool assignChiralTypesFromMolParity = false; bool assignStereo = true; bool assignCIPLabels = false; bool mappedDummiesAreRGroups = false; bool makeDummiesQueries = false; RWMol *res = nullptr; boost::property_tree::ptree pt; unsigned int sanitizeOps = MolOps::SanitizeFlags::SANITIZE_ALL; if (!details_json.empty()) { std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); auto sanitizeIt = pt.find("sanitize"); if (sanitizeIt != pt.not_found()) { // Does the "sanitize" key correspond to a terminal value? if (sanitizeIt->second.empty()) { sanitize = sanitizeIt->second.get_value(); } else { std::stringstream ss; boost::property_tree::json_parser::write_json(ss, sanitizeIt->second); updateSanitizeFlagsFromJSON(sanitizeOps, ss.str().c_str()); } } LPT_OPT_GET(kekulize); LPT_OPT_GET(removeHs); LPT_OPT_GET(mergeQueryHs); LPT_OPT_GET(setAromaticity); LPT_OPT_GET(fastFindRings); LPT_OPT_GET(assignChiralTypesFromMolParity); LPT_OPT_GET(assignStereo); LPT_OPT_GET(assignCIPLabels); LPT_OPT_GET(mappedDummiesAreRGroups); LPT_OPT_GET(makeDummiesQueries); } try { // We set default sanitization to false // as we want to enable partial sanitization // if required by the user through JSON details if (haveMolBlock) { bool strictParsing = false; LPT_OPT_GET(strictParsing); res = MolBlockToMol(input, false, false, strictParsing); } else if (haveRDKitJson) { auto ps = MolInterchange::defaultJSONParseParameters; LPT_OPT_GET2(ps, setAromaticBonds); LPT_OPT_GET2(ps, strictValenceCheck); LPT_OPT_GET2(ps, parseProperties); LPT_OPT_GET2(ps, parseConformers); auto molVect = MolInterchange::JSONDataToMols(input, ps); if (!molVect.empty()) { res = new RWMol(*molVect[0]); } } else { SmilesParserParams ps; ps.sanitize = false; ps.removeHs = removeHs; LPT_OPT_GET2(ps, strictCXSMILES); res = SmilesToMol(input, ps); } } catch (...) { // we really don't want exceptions to be thrown in here res = nullptr; } if (res) { try { if (removeHs && !haveSmiles) { MolOps::RemoveHsParameters removeHsParams; MolOps::removeHs(*res, removeHsParams, false); } if (sanitize) { unsigned int failedOp; if (!kekulize) { sanitizeOps ^= MolOps::SANITIZE_KEKULIZE; } if (!setAromaticity) { sanitizeOps ^= MolOps::SANITIZE_SETAROMATICITY; } MolOps::sanitizeMol(*res, failedOp, sanitizeOps); } else { res->updatePropertyCache(false); if (fastFindRings) { MolOps::fastFindRings(*res); } } if (assignChiralTypesFromMolParity) { MolOps::assignChiralTypesFromMolParity(*res); } if (assignStereo) { MolOps::assignStereochemistry(*res, true, true, true); } if (assignCIPLabels) { CIPLabeler::assignCIPLabels(*res); } if (mergeQueryHs) { MolOps::mergeQueryHs(*res); } if (mappedDummiesAreRGroups) { relabelMappedDummies(*res); } if (makeDummiesQueries) { auto ps = MolOps::AdjustQueryParameters::noAdjustments(); ps.makeDummiesQueries = true; MolOps::adjustQueryProperties(*res, &ps); } } catch (...) { delete res; res = nullptr; } } return res; } RWMol *mol_from_input(const std::string &input, const char *details_json) { std::string json; if (details_json) { json = details_json; } return mol_from_input(input, json); } RWMol *qmol_from_input(const std::string &input, const std::string &details_json = "") { RWMol *res = nullptr; bool removeHs = true; boost::property_tree::ptree pt; if (!details_json.empty()) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(removeHs); } if (input.find("M END") != std::string::npos) { bool strictParsing = false; LPT_OPT_GET(strictParsing); res = MolBlockToMol(input, false, removeHs, strictParsing); } else if (input.find("commonchem") != std::string::npos || input.find("rdkitjson") != std::string::npos) { auto ps = MolInterchange::defaultJSONParseParameters; LPT_OPT_GET2(ps, setAromaticBonds); LPT_OPT_GET2(ps, strictValenceCheck); LPT_OPT_GET2(ps, parseProperties); LPT_OPT_GET2(ps, parseConformers); auto molVect = MolInterchange::JSONDataToMols(input, ps); if (!molVect.empty()) { res = new RWMol(*molVect[0]); } } else { bool mergeHs = false; LPT_OPT_GET(mergeHs); res = SmartsToMol(input, 0, mergeHs); } return res; } RWMol *qmol_from_input(const std::string &input, const char *details_json) { std::string json; if (details_json) { json = details_json; } return qmol_from_input(input, json); } ChemicalReaction *rxn_from_input(const std::string &input, const std::string &details_json = "") { bool useSmiles = false; bool sanitize = false; ChemicalReaction *rxn = nullptr; boost::property_tree::ptree pt; if (!details_json.empty()) { std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(sanitize); LPT_OPT_GET(useSmiles); } try { if (input.find("$RXN") != std::string::npos) { bool removeHs = false; bool strictParsing = false; LPT_OPT_GET(removeHs); LPT_OPT_GET(strictParsing); rxn = RxnBlockToChemicalReaction(input, false, removeHs, strictParsing); } else { rxn = RxnSmartsToChemicalReaction(input, nullptr, useSmiles); } } catch (...) { // we really don't want exceptions to be thrown in here rxn = nullptr; } if (rxn) { try { if (sanitize) { unsigned int failedOp; unsigned int sanitizeOps = RxnOps::SANITIZE_ALL; bool adjustReactants = true; bool mergeQueryHs = true; LPT_OPT_GET(adjustReactants); LPT_OPT_GET(mergeQueryHs); if (!adjustReactants) { sanitizeOps ^= RxnOps::SANITIZE_ADJUST_REACTANTS; } if (!mergeQueryHs) { sanitizeOps ^= RxnOps::SANITIZE_MERGEHS; } RxnOps::sanitizeRxn(*rxn, failedOp, sanitizeOps); } } catch (...) { delete rxn; rxn = nullptr; } } return rxn; } ChemicalReaction *rxn_from_input(const std::string &input, const char *details_json) { std::string json; if (details_json) { json = details_json; } return rxn_from_input(input, json); } std::string parse_int_array(const bj::object &doc, std::vector &intVec, const std::string &keyName, const std::string &valueName) { if (doc.contains(keyName)) { const auto &val = doc.at(keyName); if (!val.is_array()) { return "JSON contains '" + keyName + "' field, but it is not an array"; } intVec.clear(); const auto &arr = val.as_array(); for (const auto &item : arr) { if (!item.is_int64()) { return valueName + " should be integers"; } intVec.push_back(static_cast(item.as_int64())); } } return ""; } std::string parse_double_array(const bj::object &doc, std::vector &doubleVec, const std::string &keyName, const std::string &valueName) { if (doc.contains(keyName)) { const auto &val = doc.at(keyName); if (!val.is_array()) { return "JSON contains '" + keyName + "' field, but it is not an array"; } doubleVec.clear(); const auto &arr = val.as_array(); for (const auto &item : arr) { if (!item.is_number()) { return valueName + " should be floats"; } doubleVec.push_back(item.to_number()); } } return ""; } std::string parse_rgba_array(const bj::value &val, DrawColour &color, const std::string &keyName) { if (!val.is_array()) { return "JSON contains '" + keyName + "' field, but the " "colors are not arrays"; } const auto &arr = val.as_array(); if (arr.size() < 3 || arr.size() > 4) { return "JSON contains '" + keyName + "' field, but the " "colors are not R,G,B[,A] arrays"; } std::vector rgba(4, 1.0); unsigned int i = 0; for (const auto &component : arr) { if (!component.is_number()) { return "JSON contains '" + keyName + "' field, but the " "R,G,B[,A] arrays contain non-float values"; } CHECK_INVARIANT(i < 4, ""); rgba[i++] = component.to_number(); } color.r = rgba[0]; color.g = rgba[1]; color.b = rgba[2]; color.a = rgba[3]; return ""; } std::string parse_highlight_multi_colors( const bj::object &doc, std::map> &colorMap, const std::string &keyName, bool &haveMultiColors) { haveMultiColors = doc.contains(keyName); if (!haveMultiColors) { return ""; } const auto &val = doc.at(keyName); if (!val.is_object()) { return "JSON contains '" + keyName + "' field, but it is not an object"; } const auto &obj = val.as_object(); for (const auto &entry : obj) { int idx = std::atoi(entry.key().data()); auto &drawColorVect = colorMap[idx]; if (entry.value().is_array()) { const auto &arr = entry.value().as_array(); bool allAreArrays = true; for (const auto &item : arr) { if (!item.is_array()) { allAreArrays = false; break; } } if (allAreArrays) { for (const auto &item : arr) { DrawColour color; auto problems = parse_rgba_array(item, color, keyName); if (!problems.empty()) { return problems; } drawColorVect.push_back(std::move(color)); } continue; } } return "JSON contains '" + keyName + "' field, but it is not an array of R,G,B[,A] arrays"; } return ""; } std::string parse_highlight_colors(const bj::object &doc, std::map &colorMap, const std::string &keyName) { if (doc.contains(keyName)) { const auto &val = doc.at(keyName); if (!val.is_object()) { return "JSON contains '" + keyName + "' field, but it is not an object"; } const auto &obj = val.as_object(); for (const auto &entry : obj) { DrawColour color; auto problems = parse_rgba_array(entry.value(), color, keyName); if (!problems.empty()) { return problems; } int idx = std::atoi(entry.key().data()); colorMap[idx] = std::move(color); } } return ""; } std::string process_details(bj::object &doc, const std::string &details, DrawingDetails &drawingDetails) { try { auto parsed = bj::parse(details); if (!parsed.is_object()) { return "Invalid JSON"; } doc = parsed.as_object(); } catch (...) { return "Invalid JSON"; } std::string problems; problems = parse_int_array(doc, drawingDetails.atomIds, "atoms", "Atom IDs"); if (!problems.empty()) { return problems; } problems = parse_int_array(doc, drawingDetails.bondIds, "bonds", "Bond IDs"); if (!problems.empty()) { return problems; } GET_JSON_VALUE(doc, drawingDetails, width, int64) GET_JSON_VALUE(doc, drawingDetails, height, int64) GET_JSON_VALUE(doc, drawingDetails, offsetx, int64) GET_JSON_VALUE(doc, drawingDetails, offsety, int64) GET_JSON_VALUE(doc, drawingDetails, panelWidth, int64) GET_JSON_VALUE(doc, drawingDetails, panelHeight, int64) GET_JSON_VALUE(doc, drawingDetails, noFreetype, bool) if (doc.contains("legend")) { const auto &legendVal = doc.at("legend"); if (!legendVal.is_string()) { return "JSON contains 'legend' field, but its type is not 'string'"; } drawingDetails.legend = std::string(legendVal.as_string()); } GET_JSON_VALUE(doc, drawingDetails, kekulize, bool) GET_JSON_VALUE(doc, drawingDetails, addChiralHs, bool) GET_JSON_VALUE(doc, drawingDetails, wedgeBonds, bool) GET_JSON_VALUE(doc, drawingDetails, forceCoords, bool) GET_JSON_VALUE(doc, drawingDetails, wavyBonds, bool) GET_JSON_VALUE(doc, drawingDetails, useMolBlockWedging, bool) GET_JSON_VALUE(doc, drawingDetails, returnDrawCoords, bool) return ""; } [[deprecated( "please use the overload taking DrawingDetails& as parameter")]] std::string process_details(bj::object &doc, const std::string &details, int &width, int &height, int &offsetx, int &offsety, std::string &legend, std::vector &atomIds, std::vector &bondIds, bool &kekulize, bool &addChiralHs, bool &wedgeBonds, bool &forceCoords, bool &wavyBonds) { DrawingDetails drawingDetails; auto problems = process_details(doc, details, drawingDetails); width = drawingDetails.width; height = drawingDetails.height; offsetx = drawingDetails.offsetx; offsety = drawingDetails.offsety; legend = drawingDetails.legend; atomIds = drawingDetails.atomIds; bondIds = drawingDetails.bondIds; kekulize = drawingDetails.kekulize; addChiralHs = drawingDetails.addChiralHs; wedgeBonds = drawingDetails.wedgeBonds; forceCoords = drawingDetails.forceCoords; wavyBonds = drawingDetails.wavyBonds; return problems; } std::string process_mol_details(const std::string &details, MolDrawingDetails &molDrawingDetails) { bj::object doc; auto problems = process_details(doc, details, molDrawingDetails); if (!problems.empty()) { return problems; } bool haveAtomMultiColors; problems = parse_highlight_multi_colors(doc, molDrawingDetails.atomMultiMap, "highlightAtomMultipleColors", haveAtomMultiColors); if (!problems.empty()) { return problems; } bool haveBondMultiColors; problems = parse_highlight_multi_colors(doc, molDrawingDetails.bondMultiMap, "highlightBondMultipleColors", haveBondMultiColors); if (!problems.empty()) { return problems; } if (haveAtomMultiColors || haveBondMultiColors) { if (const auto it = doc.find("highlightLineWidthMultipliers"); it != doc.end()) { const auto &val = it->value(); if (!val.is_object()) { return "JSON contains 'highlightLineWidthMultipliers' field, but it is not an object"; } const auto &obj = val.as_object(); for (const auto &entry : obj) { if (!entry.value().is_int64()) { return "JSON contains 'highlightLineWidthMultipliers' field, but the multipliers" "are not ints"; } int idx = std::atoi(entry.key().data()); molDrawingDetails.lineWidthMultiplierMap[idx] = static_cast(entry.value().as_int64()); } } } else { problems = parse_highlight_colors(doc, molDrawingDetails.atomMap, "highlightAtomColors"); if (!problems.empty()) { return problems; } problems = parse_highlight_colors(doc, molDrawingDetails.bondMap, "highlightBondColors"); if (!problems.empty()) { return problems; } } if (const auto it = doc.find("highlightAtomRadii"); it != doc.end()) { const auto &val = it->value(); if (!val.is_object()) { return "JSON contains 'highlightAtomRadii' field, but it is not an object"; } const auto &obj = val.as_object(); for (const auto &entry : obj) { if (!entry.value().is_number()) { return "JSON contains 'highlightAtomRadii' field, but the radii" "are not floats"; } int idx = std::atoi(entry.key().data()); molDrawingDetails.radiiMap[idx] = entry.value().to_number(); } } return ""; } [[deprecated( "please use the overload taking MolDrawingDetails& as parameter")]] std:: string process_mol_details(const std::string &details, int &width, int &height, int &offsetx, int &offsety, std::string &legend, std::vector &atomIds, std::vector &bondIds, std::map &atomMap, std::map &bondMap, std::map &radiiMap, bool &kekulize, bool &addChiralHs, bool &wedgeBonds, bool &forceCoords, bool &wavyBonds) { MolDrawingDetails molDrawingDetails; auto problems = process_mol_details(details, molDrawingDetails); width = molDrawingDetails.width; height = molDrawingDetails.height; offsetx = molDrawingDetails.offsetx; offsety = molDrawingDetails.offsety; legend = molDrawingDetails.legend; atomIds = molDrawingDetails.atomIds; bondIds = molDrawingDetails.bondIds; atomMap = molDrawingDetails.atomMap; bondMap = molDrawingDetails.bondMap; radiiMap = molDrawingDetails.radiiMap; kekulize = molDrawingDetails.kekulize; addChiralHs = molDrawingDetails.addChiralHs; wedgeBonds = molDrawingDetails.wedgeBonds; forceCoords = molDrawingDetails.forceCoords; wavyBonds = molDrawingDetails.wavyBonds; return problems; } std::string process_rxn_details(const std::string &details, RxnDrawingDetails &rxnDrawingDetails) { bj::object doc; auto problems = process_details(doc, details, rxnDrawingDetails); if (!problems.empty()) { return problems; } GET_JSON_VALUE(doc, rxnDrawingDetails, highlightByReactant, bool) if (const auto it = doc.find("highlightColorsReactants"); it != doc.end()) { const auto &val = it->value(); if (!val.is_array()) { return "JSON contains 'highlightColorsReactants' field, but it is not an " "array"; } const auto &arr = val.as_array(); for (const auto &rgbaArray : arr) { DrawColour color; problems = parse_rgba_array(rgbaArray, color, "highlightColorsReactants"); if (!problems.empty()) { return problems; } rxnDrawingDetails.highlightColorsReactants.push_back(std::move(color)); } } return ""; } [[deprecated( "please use the overload taking RxnDrawingDetails& as parameter")]] std:: string process_rxn_details(const std::string &details, int &width, int &height, int &offsetx, int &offsety, std::string &legend, std::vector &atomIds, std::vector &bondIds, bool &kekulize, bool &highlightByReactant, std::vector &highlightColorsReactants) { RxnDrawingDetails rxnDrawingDetails; auto problems = process_rxn_details(details, rxnDrawingDetails); width = rxnDrawingDetails.width; height = rxnDrawingDetails.height; offsetx = rxnDrawingDetails.offsetx; offsety = rxnDrawingDetails.offsety; legend = rxnDrawingDetails.legend; atomIds = rxnDrawingDetails.atomIds; bondIds = rxnDrawingDetails.bondIds; kekulize = rxnDrawingDetails.kekulize; highlightByReactant = rxnDrawingDetails.highlightByReactant; highlightColorsReactants = rxnDrawingDetails.highlightColorsReactants; return problems; } std::string molblock_helper(const RWMol &mol, const char *details_json, MDLVersion forceMDLVersionAsEnum) { bool includeStereo = true; bool kekulize = true; bool useMolBlockWedging = false; bool addChiralHs = false; if (details_json && strlen(details_json)) { boost::property_tree::ptree pt; std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(includeStereo); LPT_OPT_GET(kekulize); LPT_OPT_GET(useMolBlockWedging); LPT_OPT_GET(addChiralHs); if (forceMDLVersionAsEnum == MDLVersion::AUTO) { std::string forceMDLVersion; LPT_OPT_GET(forceMDLVersion); auto forceMDLVersionSize = forceMDLVersion.size(); size_t pos = 0; if (forceMDLVersionSize > 0 && std::tolower(forceMDLVersion[0]) == 'v') { pos = 1; } if (forceMDLVersionSize > pos) { if (forceMDLVersion[pos] == '2') { forceMDLVersionAsEnum = MDLVersion::V2000; } else if (forceMDLVersion[pos] == '3') { forceMDLVersionAsEnum = MDLVersion::V3000; } } } } const RWMol *molPtr = &mol; std::unique_ptr molCopy; if (useMolBlockWedging || addChiralHs) { molCopy.reset(new RWMol(mol)); molPtr = molCopy.get(); if (useMolBlockWedging) { RDKit::Chirality::reapplyMolBlockWedging(*molCopy); } if (addChiralHs) { MolDraw2DUtils::prepareMolForDrawing(*molCopy, false, true, false, false, false); } } MolWriterParams params{.includeStereo = includeStereo, .kekulize = kekulize}; std::string (*molToMolBlockFunc)(const ROMol &, const MolWriterParams &, int); switch (forceMDLVersionAsEnum) { case MDLVersion::V2000: molToMolBlockFunc = MolToV2KMolBlock; break; case MDLVersion::V3000: molToMolBlockFunc = MolToV3KMolBlock; break; default: molToMolBlockFunc = MolToMolBlock; break; } return molToMolBlockFunc(*molPtr, params, -1); } [[deprecated( "please use the overload taking the force MDLVersion enum parameter")]] std::string molblock_helper(const RWMol &mol, const char *details_json, bool forceV3000) { return molblock_helper(mol, details_json, forceV3000 ? MDLVersion::V3000 : MDLVersion::AUTO); } void get_sss_json(const ROMol &d_mol, const ROMol &q_mol, const MatchVectType &match, bj::object &obj) { bj::array bjAtoms; for (const auto &pr : match) { bjAtoms.push_back(pr.second); } obj["atoms"] = bjAtoms; bj::array bjBonds; std::vector invMatch(q_mol.getNumAtoms(), -1); for (const auto &pair : match) { invMatch[pair.first] = &pair - &match.front(); } for (const auto qbond : q_mol.bonds()) { auto beginIdx = invMatch.at(qbond->getBeginAtomIdx()); auto endIdx = invMatch.at(qbond->getEndAtomIdx()); if (beginIdx == -1 || endIdx == -1) { continue; } unsigned int idx1 = match.at(beginIdx).second; unsigned int idx2 = match.at(endIdx).second; const auto bond = d_mol.getBondBetweenAtoms(idx1, idx2); if (bond != nullptr) { bjBonds.push_back(bond->getIdx()); } } obj["bonds"] = bjBonds; } namespace { class SVGDrawerFromDetails : public DrawerFromDetails { public: SVGDrawerFromDetails(int w = -1, int h = -1, const std::string &details = std::string()) { init(w, h, details); } private: MolDraw2DSVG &drawer() const { CHECK_INVARIANT(d_drawer, "d_drawer must not be null"); return *d_drawer; }; void initDrawer(const DrawingDetails &drawingDetails) { d_drawer.reset(new MolDraw2DSVG( drawingDetails.width, drawingDetails.height, drawingDetails.panelWidth, drawingDetails.panelHeight, drawingDetails.noFreetype)); updateDrawerParamsFromJSON(); } std::string finalizeDrawing() { CHECK_INVARIANT(d_drawer, "d_drawer must not be null"); d_drawer->finishDrawing(); auto svg = d_drawer->getDrawingText(); return createDrawingResult(svg); } const char *getDrawingResultKey() { static const char *SVG_KEY = "svg"; return SVG_KEY; } std::unique_ptr d_drawer; }; } // end anonymous namespace std::string mol_to_svg(const ROMol &m, int w = -1, int h = -1, const std::string &details = "") { SVGDrawerFromDetails svgDrawer(w, h, details); return svgDrawer.draw_mol(m); } std::string rxn_to_svg(const ChemicalReaction &rxn, int w = -1, int h = -1, const std::string &details = "") { SVGDrawerFromDetails svgDrawer(w, h, details); return svgDrawer.draw_rxn(rxn); } std::string get_descriptors(const ROMol &m) { bj::object doc; Descriptors::Properties props; std::vector dns = props.getPropertyNames(); std::vector dvs = props.computeProperties(m); for (size_t i = 0; i < dns.size(); ++i) { doc[dns[i]] = dvs[i]; } return bj::serialize(doc); } namespace { template std::unique_ptr standardize_func(T &mol, const std::string &details_json, U func) { MolStandardize::CleanupParameters ps = MolStandardize::defaultCleanupParameters; if (!details_json.empty()) { MolStandardize::updateCleanupParamsFromJSON(ps, details_json); } return std::unique_ptr(static_cast(func(mol, ps))); } bool invertWedgingIfMolHasFlipped(ROMol &mol, const RDGeom::Transform3D &trans) { constexpr double FLIP_THRESHOLD = -0.99; auto zRot = trans.getVal(2, 2); bool shouldFlip = zRot < FLIP_THRESHOLD; if (shouldFlip) { RDKit::Chirality::invertMolBlockWedgingInfo(mol); } return shouldFlip; } } // namespace std::unique_ptr do_cleanup(RWMol &mol, const std::string &details_json) { auto molp = &mol; return standardize_func( molp, details_json, static_cast( MolStandardize::cleanup)); } std::unique_ptr do_normalize(RWMol &mol, const std::string &details_json) { auto molp = &mol; return standardize_func(molp, details_json, MolStandardize::normalize); } std::unique_ptr do_reionize(RWMol &mol, const std::string &details_json) { auto molp = &mol; return standardize_func(molp, details_json, MolStandardize::reionize); } std::unique_ptr do_canonical_tautomer(RWMol &mol, const std::string &details_json) { MolStandardize::CleanupParameters ps = MolStandardize::defaultCleanupParameters; if (!details_json.empty()) { MolStandardize::updateCleanupParamsFromJSON(ps, details_json); } MolStandardize::TautomerEnumerator te(ps); std::unique_ptr res(static_cast(te.canonicalize(mol))); return res; } std::unique_ptr do_neutralize(RWMol &mol, const std::string &details_json) { MolStandardize::CleanupParameters ps = MolStandardize::defaultCleanupParameters; if (!details_json.empty()) { MolStandardize::updateCleanupParamsFromJSON(ps, details_json); } MolStandardize::Uncharger uncharger(ps.doCanonical); std::unique_ptr res(static_cast(uncharger.uncharge(mol))); return res; } std::unique_ptr do_charge_parent(RWMol &mol, const std::string &details_json) { MolStandardize::CleanupParameters ps = MolStandardize::defaultCleanupParameters; bool skipStandardize = false; if (!details_json.empty()) { MolStandardize::updateCleanupParamsFromJSON(ps, details_json); boost::property_tree::ptree pt; std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(skipStandardize); } std::unique_ptr res( MolStandardize::chargeParent(mol, ps, skipStandardize)); return res; } std::unique_ptr do_fragment_parent(RWMol &mol, const std::string &details_json) { MolStandardize::CleanupParameters ps = MolStandardize::defaultCleanupParameters; bool skipStandardize = false; if (!details_json.empty()) { MolStandardize::updateCleanupParamsFromJSON(ps, details_json); boost::property_tree::ptree pt; std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(skipStandardize); } std::unique_ptr res( MolStandardize::fragmentParent(mol, ps, skipStandardize)); return res; } std::unique_ptr morgan_fp_as_bitvect( const RWMol &mol, const char *details_json) { unsigned int radius = 2; unsigned int nBits = 2048; bool useCountSimulation = false; bool useChirality = false; bool useBondTypes = true; bool includeRedundantEnvironments = false; bool onlyNonzeroInvariants = false; if (details_json && strlen(details_json)) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(radius); LPT_OPT_GET(nBits); LPT_OPT_GET(useCountSimulation); LPT_OPT_GET(useChirality); LPT_OPT_GET(useBondTypes); LPT_OPT_GET(includeRedundantEnvironments); LPT_OPT_GET(onlyNonzeroInvariants); } std::unique_ptr> morganFPGen{ RDKit::MorganFingerprint::getMorganGenerator( radius, useCountSimulation, useChirality, useBondTypes, onlyNonzeroInvariants, includeRedundantEnvironments, nullptr, nullptr, nBits)}; return std::unique_ptr(morganFPGen->getFingerprint(mol)); } std::unique_ptr rdkit_fp_as_bitvect(const RWMol &mol, const char *details_json) { static const std::vector countBounds{1, 2, 4, 8}; unsigned int minPath = 1; unsigned int maxPath = 7; unsigned int nBits = 2048; unsigned int nBitsPerHash = 2; bool useHs = true; bool branchedPaths = true; bool useBondOrder = true; bool useCountSimulation = false; if (details_json && strlen(details_json)) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(minPath); LPT_OPT_GET(maxPath); LPT_OPT_GET(nBits); LPT_OPT_GET(nBitsPerHash); LPT_OPT_GET(useHs); LPT_OPT_GET(branchedPaths); LPT_OPT_GET(useBondOrder); LPT_OPT_GET(useCountSimulation); } std::unique_ptr> rdkFPGen{ RDKit::RDKitFP::getRDKitFPGenerator( minPath, maxPath, useHs, branchedPaths, useBondOrder, nullptr, useCountSimulation, countBounds, nBits, nBitsPerHash)}; return std::unique_ptr(rdkFPGen->getFingerprint(mol)); } std::unique_ptr pattern_fp_as_bitvect( const RWMol &mol, const char *details_json) { unsigned int nBits = 2048; bool tautomericFingerprint = false; if (details_json && strlen(details_json)) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(nBits); LPT_OPT_GET(tautomericFingerprint); } auto fp = PatternFingerprintMol(mol, nBits, nullptr, nullptr, tautomericFingerprint); return std::unique_ptr{fp}; } std::unique_ptr topological_torsion_fp_as_bitvect( const RWMol &mol, const char *details_json) { unsigned int nBits = 2048; unsigned int torsionAtomCount = 4; bool useChirality = false; bool useCountSimulation = true; if (details_json && strlen(details_json)) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(nBits); LPT_OPT_GET(torsionAtomCount); LPT_OPT_GET(useChirality); LPT_OPT_GET(useCountSimulation); } std::unique_ptr> topoTorsFPGen{ RDKit::TopologicalTorsion::getTopologicalTorsionGenerator( useChirality, torsionAtomCount, nullptr, useCountSimulation, nBits)}; return std::unique_ptr(topoTorsFPGen->getFingerprint(mol)); } std::unique_ptr atom_pair_fp_as_bitvect( const RWMol &mol, const char *details_json) { unsigned int nBits = 2048; unsigned int minLength = 1; unsigned int maxLength = 30; bool useChirality = false; bool use2D = true; bool useCountSimulation = true; if (details_json && strlen(details_json)) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(nBits); LPT_OPT_GET(minLength); LPT_OPT_GET(maxLength); LPT_OPT_GET(useChirality); LPT_OPT_GET(use2D); LPT_OPT_GET(useCountSimulation); } std::unique_ptr> atomPairFPGen{ RDKit::AtomPair::getAtomPairGenerator( minLength, maxLength, useChirality, use2D, nullptr, useCountSimulation, nBits)}; return std::unique_ptr(atomPairFPGen->getFingerprint(mol)); } std::unique_ptr maccs_fp_as_bitvect(const RWMol &mol) { auto fp = MACCSFingerprints::getFingerprintAsBitVect(mol); return std::unique_ptr{fp}; } #ifdef RDK_BUILD_AVALON_SUPPORT std::unique_ptr avalon_fp_as_bitvect( const RWMol &mol, const char *details_json) { unsigned int nBits = 512; if (details_json && strlen(details_json)) { // FIX: this should eventually be moved somewhere else std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(nBits); } std::unique_ptr fp(new ExplicitBitVect(nBits)); AvalonTools::getAvalonFP(mol, *fp, nBits); return fp; } #endif // If alignOnly is set to true in details_json, original molblock wedging // information is preserved, and inverted if needed (in case the rigid-body // alignment required a flip around the Z axis). // If alignOnly is set to false in details_json or not specified, original // molblock wedging information is preserved if it only involves the invariant // core whose coordinates never change, and is cleared in case coordinates were // changed. If acceptFailure is set to true and no substructure match is found, // coordinates will be recomputed from scratch, hence molblock wedging // information will be cleared. std::string generate_aligned_coords(ROMol &mol, const ROMol &templateMol, const char *details_json) { std::string res; if (!templateMol.getNumConformers()) { return res; } RDDepict::ConstrainedDepictionParams p; bool useCoordGen = false; std::string referenceSmarts; if (details_json && strlen(details_json)) { std::istringstream ss; ss.str(details_json); boost::property_tree::ptree pt; boost::property_tree::read_json(ss, pt); LPT_OPT_GET(useCoordGen); LPT_OPT_GET(referenceSmarts); LPT_OPT_GET2(p, allowRGroups); LPT_OPT_GET2(p, acceptFailure); LPT_OPT_GET2(p, alignOnly); } int confId = -1; MatchVectType match; #ifdef RDK_BUILD_COORDGEN_SUPPORT bool oprefer = RDDepict::preferCoordGen; RDDepict::preferCoordGen = useCoordGen; #endif std::unique_ptr refPattern; if (!referenceSmarts.empty()) { try { refPattern.reset(SmartsToMol(referenceSmarts)); } catch (...) { } } try { match = RDDepict::generateDepictionMatching2DStructure( mol, templateMol, confId, refPattern.get(), p); } catch (...) { } #ifdef RDK_BUILD_COORDGEN_SUPPORT RDDepict::preferCoordGen = oprefer; #endif if (match.empty()) { res = (p.acceptFailure ? "{}" : ""); } else { bj::object doc; MinimalLib::get_sss_json(mol, templateMol, match, doc); res = bj::serialize(doc); } return res; } void get_mol_frags_details(const std::string &details_json, bool &sanitizeFrags, bool ©Conformers) { boost::property_tree::ptree pt; if (!details_json.empty()) { std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(sanitizeFrags); LPT_OPT_GET(copyConformers); } } std::string get_mol_frags_mappings( const std::vector &frags, const std::vector> &fragsMolAtomMapping) { bj::object doc; bj::array bjFrags; for (int fragIdx : frags) { bjFrags.push_back(fragIdx); } doc["frags"] = bjFrags; bj::array bjFragsMolAtomMapping; for (const auto &atomIdxVec : fragsMolAtomMapping) { bj::array bjAtomIndices; for (int atomIdx : atomIdxVec) { bjAtomIndices.push_back(atomIdx); } bjFragsMolAtomMapping.push_back(std::move(bjAtomIndices)); } doc["fragsMolAtomMapping"] = bjFragsMolAtomMapping; return bj::serialize(doc); } #ifdef RDK_BUILD_INCHI_SUPPORT std::string parse_inchi_options(const char *details_json) { std::string options; if (details_json && strlen(details_json)) { boost::property_tree::ptree pt; std::istringstream ss; ss.str(details_json); boost::property_tree::read_json(ss, pt); LPT_OPT_GET(options); } return options; } #endif namespace { #ifdef RDK_BUILD_THREADSAFE_SSS std::mutex &getLoggerMutex() { // create on demand static std::mutex _mutex; return _mutex; } #endif class LoggerState { public: // this runs only once under mutex lock LoggerState(const std::string &logName, RDLogger &logger) : d_logName(logName), d_logger(logger), d_lock(false) { CHECK_INVARIANT(d_logger, "d_logger must not be null"); // store original logger stream d_prevDest = d_logger->dp_dest; // store whether the logger was originally enabled d_enabled = d_logger->df_enabled; } LoggerState(const LoggerState &) = delete; LoggerState &operator=(const LoggerState &) = delete; ~LoggerState() {} void resetStream() { setStream(d_prevDest); } void setTee(std::ostream &ostream) { d_logger->SetTee(ostream); } void clearTee() { d_logger->ClearTee(); } void setStream(std::ostream *ostream) { if (d_logger->dp_dest) { d_logger->dp_dest->flush(); } d_logger->dp_dest = ostream; } void setEnabled(bool enabled, bool store = false) { if (store) { d_enabled = enabled; } if (enabled) { boost::logging::enable_logs(d_logName); } else if (!d_lock) { boost::logging::disable_logs(d_logName); } } bool hasLock() { return d_lock; } bool setLock() { if (!d_lock) { d_lock = true; return true; } return false; } void releaseLock() { d_lock = false; setEnabled(d_enabled); } private: bool d_enabled = false; std::string d_logName; RDLogger d_logger; std::ostream *d_prevDest; #ifdef RDK_BUILD_THREADSAFE_SSS std::atomic d_lock; #else bool d_lock; #endif }; typedef std::vector LoggerStatePtrVector; class LoggerStateSingletons { public: static LoggerStatePtrVector *get(const std::string &logName) { auto it = instance()->d_map.find(logName); if (it != instance()->d_map.end()) { return &it->second; } return nullptr; } static bool enable(const char *logNameCStr, bool enabled) { std::string inputLogName(logNameCStr ? logNameCStr : defaultLogName()); auto loggerStates = get(inputLogName); if (!loggerStates) { return false; } #ifdef RDK_BUILD_THREADSAFE_SSS std::lock_guard lock(getLoggerMutex()); #endif for (auto &loggerState : *loggerStates) { loggerState->setEnabled(enabled, true); } return true; } private: LoggerStateSingletons() { // this runs only once under mutex lock // and initializes LoggerState singletons RDLog::InitLogs(); for (auto &[logName, logger] : std::vector>{ {"rdApp.debug", rdDebugLog}, {"rdApp.info", rdInfoLog}, {"rdApp.warning", rdWarningLog}, {"rdApp.error", rdErrorLog}, }) { d_singletonLoggerStates.emplace_back(new LoggerState(logName, logger)); auto loggerState = d_singletonLoggerStates.back().get(); CHECK_INVARIANT(loggerState, "loggerState must not be nullptr"); d_map[logName].push_back(loggerState); d_map[defaultLogName()].push_back(loggerState); } } static const char *defaultLogName() { static const char *DEFAULT_LOG_NAME = "rdApp.*"; return DEFAULT_LOG_NAME; } static LoggerStateSingletons *instance() { // this is called under mutex lock if (!d_instance) { d_instance.reset(new LoggerStateSingletons()); } return d_instance.get(); } std::vector> d_singletonLoggerStates; std::map d_map; static std::unique_ptr d_instance; }; } // end anonymous namespace class LogHandle { public: ~LogHandle() { #ifdef RDK_BUILD_THREADSAFE_SSS std::lock_guard lock(getLoggerMutex()); #endif close(); } static bool enableLogging(const char *logNameCStr = nullptr) { return LoggerStateSingletons::enable(logNameCStr, true); } static bool disableLogging(const char *logNameCStr = nullptr) { return LoggerStateSingletons::enable(logNameCStr, false); } void clearBuffer() { d_stream.str({}); d_stream.clear(); } std::string getBuffer() { d_stream.flush(); return d_stream.str(); } static LogHandle *setLogTee(const char *logNameCStr) { return setLogCommon(logNameCStr, true); } static LogHandle *setLogCapture(const char *logNameCStr) { return setLogCommon(logNameCStr, false); } private: LogHandle(const std::string &logName, bool isTee) : d_logName(logName), d_isTee(isTee) { open(); } void open() { // this is called under mutex lock auto loggerStates = LoggerStateSingletons::get(d_logName); CHECK_INVARIANT(loggerStates, "loggerStates must not be nullptr"); for (auto &loggerState : *loggerStates) { CHECK_INVARIANT(loggerState->setLock(), "Failed to acquire lock"); if (d_isTee) { loggerState->setTee(d_stream); } else { loggerState->setStream(&d_stream); } loggerState->setEnabled(true); } } void close() { // this is called under mutex lock auto loggerStates = LoggerStateSingletons::get(d_logName); CHECK_INVARIANT(loggerStates, "loggerStates must not be nullptr"); for (const auto &loggerState : *loggerStates) { if (d_isTee) { loggerState->clearTee(); } else { loggerState->resetStream(); } loggerState->releaseLock(); } } // returns nullptr if the requested log does not exist or is already captured static LogHandle *setLogCommon(const char *logNameCStr, bool isTee) { if (!logNameCStr) { return nullptr; } std::string logName(logNameCStr); #ifdef RDK_BUILD_THREADSAFE_SSS std::lock_guard lock(getLoggerMutex()); #endif auto loggerStates = LoggerStateSingletons::get(logName); if (loggerStates && std::none_of(loggerStates->begin(), loggerStates->end(), [](const auto &loggerState) { return loggerState->hasLock(); })) { return new LogHandle(logName, isTee); } return nullptr; } std::string d_logName; bool d_isTee; std::stringstream d_stream; }; std::vector get_mols_from_png_blob_internal( const std::string &pngString, bool singleMol = false, const char *details = nullptr) { std::vector res; if (pngString.empty()) { return res; } PNGMetadataParams params; params.includePkl = singleMol; params.includeSmiles = singleMol; params.includeMol = singleMol; updatePNGMetadataParamsFromJSON(params, details); std::string tagToUse; unsigned int numTagsFound = 0; if (params.includePkl) { ++numTagsFound; tagToUse = PNGData::pklTag; } if (params.includeSmiles) { ++numTagsFound; tagToUse = PNGData::smilesTag; } if (params.includeMol) { ++numTagsFound; tagToUse = PNGData::molTag; } if (numTagsFound == 0 || (!singleMol && numTagsFound > 1)) { return res; } auto metadata = PNGStringToMetadata(pngString); for (const auto &[key, value] : metadata) { if (!singleMol && key.rfind(tagToUse, 0) == std::string::npos) { continue; } std::unique_ptr mol; if (params.includePkl && key.rfind(PNGData::pklTag, 0) == 0) { try { mol.reset(new RWMol(value)); } catch (...) { } } else if ((params.includeSmiles && key.rfind(PNGData::smilesTag, 0) == 0) || (params.includeMol && key.rfind(PNGData::molTag, 0) == 0)) { mol.reset(MinimalLib::mol_from_input(value, details)); } if (mol) { res.emplace_back(mol.release()); if (singleMol) { break; } } } return res; } std::string combine_mols_internal(const ROMol &mol1, const ROMol &mol2, std::unique_ptr &combinedMol, const char *details_json = nullptr) { std::vector offset(3, 0.0); combinedMol = nullptr; if (details_json) { try { auto parsed = bj::parse(details_json); if (!parsed.is_object()) { return "Invalid JSON"; } auto doc = parsed.as_object(); std::string problems; problems = parse_double_array(doc, offset, "offset", "offset coordinates"); if (!problems.empty()) { return problems; } } catch (...) { return "Invalid JSON"; } } try { combinedMol.reset(combineMols( mol1, mol2, RDGeom::Point3D(offset[0], offset[1], offset[2]))); } catch (...) { return "Failed to combine molecules"; } return ""; } } // namespace MinimalLib } // namespace RDKit #undef LPT_OPT_GET #undef LPT_OPT_GET2