Files
rdkit/Code/MinimalLib/common.h
Greg Landrum 45681a1c04 Switch from using RapidJSON to Boost::JSON for MolInterchange (#8859)
* First pass at port

Mostly auto-converted using claude sonnet 4

Things are a bit slower in this initial port. Here's some timing data for molecules from SMILES (no coords) and from SDF (with coords)

# MASTER
## smiles
read: 50000 mols.
 9.260000s wall, 8.650000s user + 0.600000s system = 9.250000s CPU (99.9%)
serialize
 3.060000s wall, 2.400000s user + 0.660000s system = 3.060000s CPU (100.0%)
deserialize
 1.350000s wall, 1.250000s user + 0.090000s system = 1.340000s CPU (99.3%)

## SDF
read: 50000 mols.
 9.340000s wall, 8.930000s user + 0.400000s system = 9.330000s CPU (99.9%)
serialize
 6.630000s wall, 5.960000s user + 0.680000s system = 6.640000s CPU (100.2%)
deserialize
 1.450000s wall, 1.450000s user + 0.000000s system = 1.450000s CPU (100.0%)

# Boost::JSON
## smiles
read: 50000 mols.
 9.250000s wall, 8.830000s user + 0.420000s system = 9.250000s CPU (100.0%)
serialize
 4.770000s wall, 4.410000s user + 0.350000s system = 4.760000s CPU (99.8%)
deserialize
 2.320000s wall, 2.100000s user + 0.230000s system = 2.330000s CPU (100.4%)

## SDF
read: 50000 mols.
 9.500000s wall, 9.100000s user + 0.400000s system = 9.500000s CPU (100.0%)
serialize
 8.760000s wall, 8.330000s user + 0.420000s system = 8.750000s CPU (99.9%)
deserialize
 2.540000s wall, 2.330000s user + 0.210000s system = 2.540000s CPU (100.0%)

* some json parser optimization

* around the edges

* optimizations for the writer

* hopefully get things compiling

* convert the MinimalLib stuff to use boost::json

Again, a lot of the lifting here was done using Claude Sonnet 4 in VS Code Copilot

* fix Windows DLL build

* response to review

Co-authored-by: Paolo Tosco <paolo.tosco.mail@gmail.com>

* better not to blindly accept suggestions

* fix the problems in MinimalLib

---------

Co-authored-by: Paolo Tosco <paolo.tosco.mail@gmail.com>
Co-authored-by: = <=>
2025-11-11 11:54:44 +01:00

1520 lines
50 KiB
C++

//
// 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 <string>
#include <RDGeneral/versions.h>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/MolPickler.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/FileParsers/MolFileStereochem.h>
#include <GraphMol/FileParsers/PNGParser.h>
#include <RDGeneral/FileParseException.h>
#include <GraphMol/MolDraw2D/MolDraw2DSVG.h>
#include <GraphMol/Substruct/SubstructMatch.h>
#include <GraphMol/MolInterchange/MolInterchange.h>
#include <GraphMol/Descriptors/Property.h>
#include <GraphMol/Descriptors/MolDescriptors.h>
#include <GraphMol/Fingerprints/Fingerprints.h>
#include <GraphMol/Fingerprints/MorganFingerprints.h>
#include <GraphMol/Fingerprints/AtomPairGenerator.h>
#include <GraphMol/Fingerprints/TopologicalTorsionGenerator.h>
#include <GraphMol/Fingerprints/MorganGenerator.h>
#include <GraphMol/Fingerprints/RDKitFPGenerator.h>
#include <GraphMol/Fingerprints/MACCS.h>
#ifdef RDK_BUILD_AVALON_SUPPORT
#include <External/AvalonTools/AvalonTools.h>
#endif
#include <GraphMol/Depictor/RDDepictor.h>
#include <GraphMol/Depictor/DepictUtils.h>
#include <GraphMol/Conformer.h>
#include <GraphMol/MolAlign/AlignMolecules.h>
#include <GraphMol/Substruct/SubstructUtils.h>
#include <GraphMol/MolTransforms/MolTransforms.h>
#include <GraphMol/CIPLabeler/CIPLabeler.h>
#include <GraphMol/Abbreviations/Abbreviations.h>
#include <DataStructs/BitOps.h>
#include <GraphMol/MolStandardize/MolStandardize.h>
#include <GraphMol/MolStandardize/Charge.h>
#include <GraphMol/MolStandardize/Tautomer.h>
#include <GraphMol/ChemReactions/Reaction.h>
#include <GraphMol/ChemReactions/ReactionParser.h>
#include <GraphMol/ChemReactions/SanitizeRxn.h>
#include <GraphMol/ChemTransforms/ChemTransforms.h>
#include <GraphMol/RGroupDecomposition/RGroupUtils.h>
#include <RDGeneral/RDLog.h>
#include "common_defs.h"
#include "JSONParsers.h"
#include <sstream>
#include <RDGeneral/BoostStartInclude.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <RDGeneral/BoostEndInclude.h>
#include <boost/json.hpp>
#ifdef RDK_BUILD_THREADSAFE_SSS
#include <mutex>
#include <atomic>
#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<bool>();
} 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<int> &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<int>(item.as_int64()));
}
}
return "";
}
std::string parse_double_array(const bj::object &doc,
std::vector<double> &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<double>());
}
}
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<double> 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<double>();
}
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<int, std::vector<DrawColour>> &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<int, DrawColour> &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<int> &atomIds, std::vector<int> &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<int>(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<double>();
}
}
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<int> &atomIds, std::vector<int> &bondIds,
std::map<int, DrawColour> &atomMap,
std::map<int, DrawColour> &bondMap,
std::map<int, double> &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<int> &atomIds, std::vector<int> &bondIds,
bool &kekulize, bool &highlightByReactant,
std::vector<DrawColour> &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<RWMol> 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<int> 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<MolDraw2DSVG> 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<std::string> dns = props.getPropertyNames();
std::vector<double> dvs = props.computeProperties(m);
for (size_t i = 0; i < dns.size(); ++i) {
doc[dns[i]] = dvs[i];
}
return bj::serialize(doc);
}
namespace {
template <typename T, typename U>
std::unique_ptr<RWMol> 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<RWMol>(static_cast<RWMol *>(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<RWMol> do_cleanup(RWMol &mol, const std::string &details_json) {
auto molp = &mol;
return standardize_func(
molp, details_json,
static_cast<RWMol *(*)(const RWMol *,
const MolStandardize::CleanupParameters &)>(
MolStandardize::cleanup));
}
std::unique_ptr<RWMol> do_normalize(RWMol &mol,
const std::string &details_json) {
auto molp = &mol;
return standardize_func(molp, details_json, MolStandardize::normalize);
}
std::unique_ptr<RWMol> do_reionize(RWMol &mol,
const std::string &details_json) {
auto molp = &mol;
return standardize_func(molp, details_json, MolStandardize::reionize);
}
std::unique_ptr<RWMol> 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<RWMol> res(static_cast<RWMol *>(te.canonicalize(mol)));
return res;
}
std::unique_ptr<RWMol> 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<RWMol> res(static_cast<RWMol *>(uncharger.uncharge(mol)));
return res;
}
std::unique_ptr<RWMol> 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<RWMol> res(
MolStandardize::chargeParent(mol, ps, skipStandardize));
return res;
}
std::unique_ptr<RWMol> 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<RWMol> res(
MolStandardize::fragmentParent(mol, ps, skipStandardize));
return res;
}
std::unique_ptr<ExplicitBitVect> 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<FingerprintGenerator<std::uint32_t>> morganFPGen{
RDKit::MorganFingerprint::getMorganGenerator<std::uint32_t>(
radius, useCountSimulation, useChirality, useBondTypes,
onlyNonzeroInvariants, includeRedundantEnvironments, nullptr, nullptr,
nBits)};
return std::unique_ptr<ExplicitBitVect>(morganFPGen->getFingerprint(mol));
}
std::unique_ptr<ExplicitBitVect> rdkit_fp_as_bitvect(const RWMol &mol,
const char *details_json) {
static const std::vector<std::uint32_t> 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<FingerprintGenerator<std::uint32_t>> rdkFPGen{
RDKit::RDKitFP::getRDKitFPGenerator<std::uint32_t>(
minPath, maxPath, useHs, branchedPaths, useBondOrder, nullptr,
useCountSimulation, countBounds, nBits, nBitsPerHash)};
return std::unique_ptr<ExplicitBitVect>(rdkFPGen->getFingerprint(mol));
}
std::unique_ptr<ExplicitBitVect> 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<ExplicitBitVect>{fp};
}
std::unique_ptr<ExplicitBitVect> 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<FingerprintGenerator<std::uint64_t>> topoTorsFPGen{
RDKit::TopologicalTorsion::getTopologicalTorsionGenerator<std::uint64_t>(
useChirality, torsionAtomCount, nullptr, useCountSimulation, nBits)};
return std::unique_ptr<ExplicitBitVect>(topoTorsFPGen->getFingerprint(mol));
}
std::unique_ptr<ExplicitBitVect> 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<FingerprintGenerator<std::uint32_t>> atomPairFPGen{
RDKit::AtomPair::getAtomPairGenerator<std::uint32_t>(
minLength, maxLength, useChirality, use2D, nullptr,
useCountSimulation, nBits)};
return std::unique_ptr<ExplicitBitVect>(atomPairFPGen->getFingerprint(mol));
}
std::unique_ptr<ExplicitBitVect> maccs_fp_as_bitvect(const RWMol &mol) {
auto fp = MACCSFingerprints::getFingerprintAsBitVect(mol);
return std::unique_ptr<ExplicitBitVect>{fp};
}
#ifdef RDK_BUILD_AVALON_SUPPORT
std::unique_ptr<ExplicitBitVect> 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<ExplicitBitVect> 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<ROMol> 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 &copyConformers) {
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<int> &frags,
const std::vector<std::vector<int>> &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<bool> d_lock;
#else
bool d_lock;
#endif
};
typedef std::vector<LoggerState *> 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<std::mutex> 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<std::pair<std::string, RDLogger>>{
{"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<std::unique_ptr<LoggerState>> d_singletonLoggerStates;
std::map<std::string, LoggerStatePtrVector> d_map;
static std::unique_ptr<LoggerStateSingletons> d_instance;
};
} // end anonymous namespace
class LogHandle {
public:
~LogHandle() {
#ifdef RDK_BUILD_THREADSAFE_SSS
std::lock_guard<std::mutex> 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<std::mutex> 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<ROMOL_SPTR> get_mols_from_png_blob_internal(
const std::string &pngString, bool singleMol = false,
const char *details = nullptr) {
std::vector<ROMOL_SPTR> 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<RWMol> 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<ROMol> &combinedMol,
const char *details_json = nullptr) {
std::vector<double> 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