mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-03 21:44:30 +08:00
- Switched from dynamic to static allocation for an instance of `MCSParameters` - Switched to using `auto` where possible - Added a few `CHECK_INVARIANT` where appropriate before dereferencing pointers - Moved some inline comments to the previous line to improve readability - Added a early check for `CompleteRingsOnly` in `checkBondRingMatch()` to improve computational efficiency - Removed `RingMatchTableSet` entirely as 1) it is unnecessary since its functionality is already provided by `RingInfo` 2) it abused the `userData` pointer. This allows cleaning up and simplifying the code, particularly the Python wrappers which had a significant amount of added complexity to support it - Removed all the code that was deprecated several releases ago - Reimplemented ringFusionCheck() from scratch to address several bug reports; also switched from std::set to boost::dynamic_bitset for better efficiency - Replaced boost::tie with boost::make_iterator_range - Modernized `for` loops where possible - Removed entirely the QueryRings structure as its functionality is already available in RingInfo - Removed entirely the _DFS() function since the same algorithm can be implemented in a simpler and more efficient way using RingInfo (from 2m28.441s to 2m9.859s for the same task) - Replaced std::vector<bool> with boost::dynamic_bitset - Replaced C-style casts with C++ casts - Replaced some size_t with unsigned int - Refactored checkIfRingsAreClosed() such that checkNoLoneRingAtoms() is not needed anymore - Added a test for slow runtimes with CompleteRingsOnly - Setting Timeout to 0 means no timeout, as it should be - Removed unused `steps` variable from `MaximumCommonSubgraph::growSeeds` - Storing both Atom and Bond pointers and their indices on Seed and MCS data structures is time-consuming and a potential source of incons istencies; storing pointers is sufficient - Promoted `MaximumCommonSubgraph::match` from `private` to `public` - `NewBonds` was declared `mutable`, but `Seed::fillNewBonds()` was incorrectly declared as `non-const`, which caused the need for an ugly (and unnecessary) `const_cast`. I have now removed the `const_cast` and correctly declared functions that alter `NewBonds` as `const`, since `NewBonds` is explicitly `mut able` - Removed some useless random scoping that was peppering the MCS code - Removed a significant amount of duplicate code from the Python wrappers by inheriting from a base `PyMCSWrapper` class - Fixed #6082 - Fixed #5510 - Fixed #5457 - Fixed #5440 - Fixed #5411 - Fixed #3965 - Fixed #6578 Co-authored-by: ptosco <paolo.tosco@novartis.com>
908 lines
37 KiB
C++
908 lines
37 KiB
C++
//
|
|
// Copyright (C) 2014 Novartis Institutes for BioMedical Research
|
|
//
|
|
// @@ 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.
|
|
//
|
|
#include <RDBoost/python.h>
|
|
#include <GraphMol/ROMol.h>
|
|
#include <GraphMol/Wrap/substructmethods.h>
|
|
#include <RDBoost/Wrap.h>
|
|
#include <GraphMol/FMCS/FMCS.h>
|
|
|
|
#define COMPARE_FUNC_NAME "__call__"
|
|
#define CALLBACK_FUNC_NAME "__call__"
|
|
|
|
namespace python = boost::python;
|
|
|
|
namespace RDKit {
|
|
|
|
struct PyMCSWrapper : public boost::python::wrapper<PyMCSWrapper> {
|
|
public:
|
|
PyMCSWrapper() {}
|
|
PyMCSWrapper(PyObject *obj) {
|
|
PRECONDITION(obj, "PyObject* must not be NULL");
|
|
d_pyObject.reset(
|
|
new python::object(python::handle<>(python::borrowed(obj))));
|
|
}
|
|
virtual ~PyMCSWrapper() {}
|
|
virtual const char *subclassName() const {
|
|
throw std::invalid_argument(
|
|
"subclassName() must be overridden in the "
|
|
"derived class");
|
|
}
|
|
const python::object &pyObject() const { return *d_pyObject; }
|
|
|
|
protected:
|
|
void failedToExtractPyObject() const {
|
|
std::stringstream ss;
|
|
ss << "Failed to extract object from " << subclassName() << " subclass";
|
|
PyErr_SetString(PyExc_RuntimeError, ss.str().c_str());
|
|
python::throw_error_already_set();
|
|
}
|
|
void errorNotOverridden() const {
|
|
std::stringstream ss;
|
|
ss << "The " COMPARE_FUNC_NAME "() method must be overridden in the rdFMCS."
|
|
<< subclassName() << " subclass";
|
|
PyErr_SetString(PyExc_AttributeError, ss.str().c_str());
|
|
python::throw_error_already_set();
|
|
}
|
|
void errorNotDefined() const {
|
|
// should never happen as the method is virtual but not pure in the C++
|
|
// class
|
|
std::stringstream ss;
|
|
ss << "The " CALLBACK_FUNC_NAME
|
|
"() method must be defined "
|
|
"in the "
|
|
<< subclassName() << " subclass";
|
|
PyErr_SetString(PyExc_AttributeError, ss.str().c_str());
|
|
python::throw_error_already_set();
|
|
}
|
|
void errorNotCallable() const {
|
|
std::stringstream ss;
|
|
ss << "The " COMPARE_FUNC_NAME " attribute in the " << subclassName()
|
|
<< " subclass is not a callable method";
|
|
PyErr_SetString(PyExc_TypeError, ss.str().c_str());
|
|
python::throw_error_already_set();
|
|
}
|
|
virtual bool hasPythonOverride(const char *attrName) const {
|
|
auto obj = get_override(attrName);
|
|
return PyCallable_Check(obj.ptr());
|
|
}
|
|
void extractPyMCSWrapper() {
|
|
d_pyObjectExtractor.reset(new python::extract<PyMCSWrapper *>(*d_pyObject));
|
|
if (d_pyObjectExtractor->check()) {
|
|
PyObject *callable =
|
|
PyObject_GetAttrString(d_pyObject->ptr(), CALLBACK_FUNC_NAME);
|
|
if (!callable) {
|
|
errorNotDefined();
|
|
}
|
|
if (!PyCallable_Check(callable)) {
|
|
errorNotCallable();
|
|
}
|
|
if (!pyObjectExtract()->hasPythonOverride(CALLBACK_FUNC_NAME)) {
|
|
errorNotOverridden();
|
|
}
|
|
} else {
|
|
std::stringstream ss;
|
|
ss << "expected an instance of the rdFMCS." << subclassName()
|
|
<< " subclass";
|
|
PyErr_SetString(PyExc_TypeError, ss.str().c_str());
|
|
python::throw_error_already_set();
|
|
}
|
|
}
|
|
PyMCSWrapper *pyObjectExtract() const { return (*d_pyObjectExtractor)(); }
|
|
|
|
private:
|
|
std::unique_ptr<python::object> d_pyObject;
|
|
std::unique_ptr<python::extract<PyMCSWrapper *>> d_pyObjectExtractor;
|
|
};
|
|
|
|
struct PyMCSAtomCompare : public PyMCSWrapper {
|
|
public:
|
|
PyMCSAtomCompare() {}
|
|
PyMCSAtomCompare(PyObject *obj) : PyMCSWrapper(obj) {}
|
|
~PyMCSAtomCompare() {}
|
|
bool extractAtomComparator(AtomComparator &ac) {
|
|
bool res = false;
|
|
python::extract<AtomComparator> predefinedAtomComparator(pyObject());
|
|
if (predefinedAtomComparator.check()) {
|
|
ac = predefinedAtomComparator();
|
|
res = true;
|
|
} else {
|
|
extractPyMCSWrapper();
|
|
}
|
|
return res;
|
|
}
|
|
PyMCSAtomCompare *extractPyObject() const {
|
|
auto res = dynamic_cast<PyMCSAtomCompare *>(pyObjectExtract());
|
|
if (!res) {
|
|
failedToExtractPyObject();
|
|
}
|
|
return res;
|
|
}
|
|
inline bool checkAtomRingMatch(const MCSAtomCompareParameters &p,
|
|
const ROMol &mol1, unsigned int atom1,
|
|
const ROMol &mol2, unsigned int atom2) const {
|
|
return RDKit::checkAtomRingMatch(p, mol1, atom1, mol2, atom2);
|
|
}
|
|
inline bool checkAtomCharge(const MCSAtomCompareParameters &p,
|
|
const ROMol &mol1, unsigned int atom1,
|
|
const ROMol &mol2, unsigned int atom2) const {
|
|
return RDKit::checkAtomCharge(p, mol1, atom1, mol2, atom2);
|
|
}
|
|
inline bool checkAtomChirality(const MCSAtomCompareParameters &p,
|
|
const ROMol &mol1, unsigned int atom1,
|
|
const ROMol &mol2, unsigned int atom2) const {
|
|
return RDKit::checkAtomChirality(p, mol1, atom1, mol2, atom2);
|
|
}
|
|
inline const char *subclassName() const { return "MCSAtomCompare"; }
|
|
virtual bool operator()(const MCSAtomCompareParameters &, const ROMol &,
|
|
unsigned int, const ROMol &, unsigned int) const {
|
|
errorNotOverridden();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct PyMCSBondCompare : public PyMCSWrapper {
|
|
PyMCSBondCompare() {}
|
|
PyMCSBondCompare(PyObject *obj) : PyMCSWrapper(obj) {}
|
|
~PyMCSBondCompare() {}
|
|
bool extractBondComparator(BondComparator &bc) {
|
|
bool res = false;
|
|
python::extract<BondComparator> predefinedBondComparator(pyObject());
|
|
if (predefinedBondComparator.check()) {
|
|
bc = predefinedBondComparator();
|
|
res = true;
|
|
} else {
|
|
extractPyMCSWrapper();
|
|
}
|
|
return res;
|
|
}
|
|
PyMCSBondCompare *extractPyObject() const {
|
|
auto res = dynamic_cast<PyMCSBondCompare *>(pyObjectExtract());
|
|
if (!res) {
|
|
failedToExtractPyObject();
|
|
}
|
|
return res;
|
|
}
|
|
inline bool checkBondStereo(const MCSBondCompareParameters &p,
|
|
const ROMol &mol1, unsigned int bond1,
|
|
const ROMol &mol2, unsigned int bond2) const {
|
|
return RDKit::checkBondStereo(p, mol1, bond1, mol2, bond2);
|
|
}
|
|
inline bool checkBondRingMatch(const MCSBondCompareParameters &p,
|
|
const ROMol &mol1, unsigned int bond1,
|
|
const ROMol &mol2, unsigned int bond2) {
|
|
return RDKit::checkBondRingMatch(p, mol1, bond1, mol2, bond2);
|
|
}
|
|
inline const char *subclassName() const { return "MCSBondCompare"; }
|
|
virtual bool operator()(const MCSBondCompareParameters &, const ROMol &,
|
|
unsigned int, const ROMol &, unsigned int) const {
|
|
errorNotOverridden();
|
|
return false;
|
|
}
|
|
const MCSParameters *mcsParameters;
|
|
};
|
|
|
|
struct PyAtomBondCompData {
|
|
python::object pyAtomComp;
|
|
python::object pyBondComp;
|
|
MCSBondCompareFunction standardBondTyperFunc;
|
|
};
|
|
|
|
struct PyBaseUserData {
|
|
PyAtomBondCompData pyAtomBondCompData;
|
|
};
|
|
|
|
struct PyCompareFunctionUserData : public PyBaseUserData {
|
|
const MCSParameters *mcsParameters;
|
|
};
|
|
|
|
struct PyProgressCallbackUserData : public PyBaseUserData {
|
|
const MCSProgressData *mcsProgressData;
|
|
python::object pyMCSProgress;
|
|
};
|
|
|
|
struct PyMCSFinalMatchCheckFunctionUserData : public PyBaseUserData {
|
|
python::object pyMCSFinalMatchCheck;
|
|
};
|
|
|
|
struct PyMCSAcceptanceFunctionUserData : public PyBaseUserData {
|
|
python::object pyMCSAcceptance;
|
|
};
|
|
|
|
struct PyMCSProgress : public PyMCSWrapper {
|
|
PyMCSProgress() {}
|
|
PyMCSProgress(PyObject *obj) : PyMCSWrapper(obj) { extractPyMCSWrapper(); }
|
|
~PyMCSProgress() {}
|
|
PyMCSProgress *extractPyObject() const {
|
|
auto res = dynamic_cast<PyMCSProgress *>(pyObjectExtract());
|
|
if (!res) {
|
|
failedToExtractPyObject();
|
|
}
|
|
return res;
|
|
}
|
|
inline const char *subclassName() const { return "MCSProgress"; }
|
|
virtual bool operator()(const MCSProgressData &,
|
|
const MCSParameters &) const {
|
|
errorNotOverridden();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class PyMCSProgressData {
|
|
public:
|
|
PyMCSProgressData()
|
|
: pd(new MCSProgressData()), pcud(new PyProgressCallbackUserData()) {
|
|
pcud->mcsProgressData = pd.get();
|
|
}
|
|
PyMCSProgressData(const MCSProgressData &other) : PyMCSProgressData() {
|
|
*pd = other;
|
|
}
|
|
unsigned int getNumAtoms() const { return pd->NumAtoms; }
|
|
unsigned int getNumBonds() const { return pd->NumBonds; }
|
|
unsigned int getSeedProcessed() const { return pd->SeedProcessed; }
|
|
|
|
private:
|
|
std::unique_ptr<MCSProgressData> pd;
|
|
std::unique_ptr<PyProgressCallbackUserData> pcud;
|
|
};
|
|
|
|
struct PyMCSFinalMatchCheck : public PyMCSWrapper {
|
|
PyMCSFinalMatchCheck() {}
|
|
PyMCSFinalMatchCheck(PyObject *obj) : PyMCSWrapper(obj) {
|
|
extractPyMCSWrapper();
|
|
}
|
|
~PyMCSFinalMatchCheck() {}
|
|
PyMCSFinalMatchCheck *extractPyObject() const {
|
|
auto res = dynamic_cast<PyMCSFinalMatchCheck *>(pyObjectExtract());
|
|
if (!res) {
|
|
failedToExtractPyObject();
|
|
}
|
|
return res;
|
|
}
|
|
inline const char *subclassName() const { return "MCSFinalMatchCheck"; }
|
|
virtual bool operator()() const {
|
|
errorNotOverridden();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct PyMCSAcceptance : public PyMCSWrapper {
|
|
PyMCSAcceptance() {}
|
|
PyMCSAcceptance(PyObject *obj) : PyMCSWrapper(obj) { extractPyMCSWrapper(); }
|
|
~PyMCSAcceptance() {}
|
|
PyMCSAcceptance *extractPyObject() const {
|
|
auto res = dynamic_cast<PyMCSAcceptance *>(pyObjectExtract());
|
|
if (!res) {
|
|
failedToExtractPyObject();
|
|
}
|
|
return res;
|
|
}
|
|
virtual bool operator()() const {
|
|
errorNotOverridden();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class PyMCSParameters {
|
|
public:
|
|
PyMCSParameters() : p(new MCSParameters()) {
|
|
cfud.mcsParameters = p.get();
|
|
pcud.mcsProgressData = nullptr;
|
|
}
|
|
PyMCSParameters(const MCSParameters &other) : PyMCSParameters() {
|
|
*p = other;
|
|
}
|
|
PyMCSParameters(const MCSParameters &other,
|
|
const PyProgressCallbackUserData &pcudOther)
|
|
: PyMCSParameters(other) {
|
|
pcud.pyMCSProgress = pcudOther.pyMCSProgress;
|
|
cfud.pyAtomBondCompData = pcudOther.pyAtomBondCompData;
|
|
}
|
|
PyMCSParameters(const MCSParameters &other,
|
|
const PyMCSFinalMatchCheckFunctionUserData &fmudOther)
|
|
: PyMCSParameters(other) {
|
|
fmud.pyMCSFinalMatchCheck = fmudOther.pyMCSFinalMatchCheck;
|
|
cfud.pyAtomBondCompData = fmudOther.pyAtomBondCompData;
|
|
}
|
|
PyMCSParameters(const MCSParameters &other,
|
|
const PyMCSAcceptanceFunctionUserData &afudOther)
|
|
: PyMCSParameters(other) {
|
|
afud.pyMCSAcceptance = afudOther.pyMCSAcceptance;
|
|
cfud.pyAtomBondCompData = afudOther.pyAtomBondCompData;
|
|
}
|
|
const MCSParameters *get() const { return p.get(); }
|
|
bool getMaximizeBonds() const { return p->MaximizeBonds; }
|
|
void setMaximizeBonds(bool value) { p->MaximizeBonds = value; }
|
|
double getThreshold() const { return p->Threshold; }
|
|
void setThreshold(double value) { p->Threshold = value; }
|
|
unsigned int getTimeout() const { return p->Timeout; }
|
|
void setTimeout(unsigned int value) { p->Timeout = value; }
|
|
bool getVerbose() const { return p->Verbose; }
|
|
void setVerbose(bool value) { p->Verbose = value; }
|
|
const MCSAtomCompareParameters &getAtomCompareParameters() const {
|
|
return p->AtomCompareParameters;
|
|
}
|
|
void setAtomCompareParameters(const MCSAtomCompareParameters &value) {
|
|
p->AtomCompareParameters = value;
|
|
}
|
|
const MCSBondCompareParameters &getBondCompareParameters() const {
|
|
return p->BondCompareParameters;
|
|
}
|
|
void setBondCompareParameters(const MCSBondCompareParameters &value) {
|
|
p->BondCompareParameters = value;
|
|
}
|
|
std::string getInitialSeed() const { return p->InitialSeed; }
|
|
void setInitialSeed(const std::string &value) { p->InitialSeed = value; }
|
|
bool getStoreAll() const { return p->StoreAll; }
|
|
void setStoreAll(bool value) { p->StoreAll = value; }
|
|
void setMCSAtomTyper(PyObject *atomComp) {
|
|
PyMCSAtomCompare pyMCSAtomCompare(atomComp);
|
|
AtomComparator ac;
|
|
if (pyMCSAtomCompare.extractAtomComparator(ac)) {
|
|
p->setMCSAtomTyperFromEnum(ac);
|
|
} else {
|
|
p->CompareFunctionsUserData = &cfud;
|
|
p->AtomTyper = MCSAtomComparePyFunc;
|
|
cfud.pyAtomBondCompData.pyAtomComp = pyMCSAtomCompare.pyObject();
|
|
cfud.mcsParameters = p.get();
|
|
}
|
|
}
|
|
python::object getMCSAtomTyper() const {
|
|
static const std::map<RDKit::MCSAtomCompareFunction, RDKit::AtomComparator>
|
|
atomTyperToComp = {
|
|
{MCSAtomCompareAny, AtomCompareAny},
|
|
{MCSAtomCompareElements, AtomCompareElements},
|
|
{MCSAtomCompareIsotopes, AtomCompareIsotopes},
|
|
{MCSAtomCompareAnyHeavyAtom, AtomCompareAnyHeavyAtom}};
|
|
if (!cfud.pyAtomBondCompData.pyAtomComp.is_none()) {
|
|
return cfud.pyAtomBondCompData.pyAtomComp;
|
|
}
|
|
python::object res;
|
|
try {
|
|
res = python::object(atomTyperToComp.at(p->AtomTyper));
|
|
} catch (const std::out_of_range &) {
|
|
PyErr_SetString(PyExc_TypeError, "Unknown AtomTyper");
|
|
python::throw_error_already_set();
|
|
}
|
|
return res;
|
|
}
|
|
void setMCSBondTyper(PyObject *bondComp) {
|
|
PyMCSBondCompare pyMCSBondCompare(bondComp);
|
|
BondComparator bc;
|
|
if (pyMCSBondCompare.extractBondComparator(bc)) {
|
|
p->setMCSBondTyperFromEnum(bc);
|
|
} else {
|
|
p->CompareFunctionsUserData = &cfud;
|
|
p->BondTyper = MCSBondComparePyFunc;
|
|
cfud.pyAtomBondCompData.pyBondComp = pyMCSBondCompare.pyObject();
|
|
auto bc = pyMCSBondCompare.extractPyObject();
|
|
bc->mcsParameters = p.get();
|
|
cfud.mcsParameters = p.get();
|
|
}
|
|
}
|
|
python::object getMCSBondTyper() const {
|
|
static const std::map<RDKit::MCSBondCompareFunction, RDKit::BondComparator>
|
|
bondTyperToComp = {{MCSBondCompareAny, BondCompareAny},
|
|
{MCSBondCompareOrder, BondCompareOrder},
|
|
{MCSBondCompareOrderExact, BondCompareOrderExact}};
|
|
if (!cfud.pyAtomBondCompData.pyBondComp.is_none()) {
|
|
return cfud.pyAtomBondCompData.pyBondComp;
|
|
}
|
|
python::object res;
|
|
try {
|
|
res = python::object(bondTyperToComp.at(p->BondTyper));
|
|
} catch (const std::out_of_range &) {
|
|
PyErr_SetString(PyExc_TypeError, "Unknown BondTyper");
|
|
python::throw_error_already_set();
|
|
}
|
|
return res;
|
|
}
|
|
void setMCSProgressCallback(PyObject *progress) {
|
|
PyMCSProgress pyMCSProgress(progress);
|
|
p->ProgressCallbackUserData = &pcud;
|
|
p->ProgressCallback = MCSProgressCallbackPyFunc;
|
|
pcud.pyMCSProgress = pyMCSProgress.pyObject();
|
|
pcud.pyAtomBondCompData = cfud.pyAtomBondCompData;
|
|
}
|
|
python::object getMCSProgressCallback() {
|
|
if (!pcud.pyMCSProgress.is_none()) {
|
|
return pcud.pyMCSProgress;
|
|
}
|
|
return python::object();
|
|
}
|
|
void setFinalMatchCheck(PyObject *finalMatchCheck) {
|
|
PyMCSFinalMatchCheck pyMCSFinalMatchCheck(finalMatchCheck);
|
|
p->FinalMatchChecker = MCSFinalMatchCheckPyFunc;
|
|
p->FinalMatchCheckerUserData = &fmud;
|
|
fmud.pyMCSFinalMatchCheck = pyMCSFinalMatchCheck.pyObject();
|
|
fmud.pyAtomBondCompData = cfud.pyAtomBondCompData;
|
|
}
|
|
python::object getFinalMatchCheck() const {
|
|
if (!fmud.pyMCSFinalMatchCheck.is_none()) {
|
|
return fmud.pyMCSFinalMatchCheck;
|
|
}
|
|
return python::object();
|
|
}
|
|
void setShouldAcceptMCS(PyObject *mcsAcceptance) {
|
|
PyMCSAcceptance pyMCSAcceptance(mcsAcceptance);
|
|
p->ShouldAcceptMCS = MCSAcceptancePyFunc;
|
|
p->ShouldAcceptMCSUserData = &afud;
|
|
afud.pyMCSAcceptance = pyMCSAcceptance.pyObject();
|
|
afud.pyAtomBondCompData = cfud.pyAtomBondCompData;
|
|
}
|
|
python::object getShouldAcceptMCS() const {
|
|
if (!afud.pyMCSAcceptance.is_none()) {
|
|
return afud.pyMCSAcceptance;
|
|
}
|
|
return python::object();
|
|
}
|
|
|
|
private:
|
|
static bool MCSAtomComparePyFunc(const MCSAtomCompareParameters &p,
|
|
const ROMol &mol1, unsigned int atom1,
|
|
const ROMol &mol2, unsigned int atom2,
|
|
void *userData) {
|
|
PRECONDITION(userData, "userData must not be NULL");
|
|
PyCompareFunctionUserData *cfud =
|
|
static_cast<PyCompareFunctionUserData *>(userData);
|
|
CHECK_INVARIANT(cfud, "");
|
|
bool res = false;
|
|
{
|
|
PyGILStateHolder h;
|
|
res = python::call_method<bool>(
|
|
cfud->pyAtomBondCompData.pyAtomComp.ptr(), COMPARE_FUNC_NAME,
|
|
boost::ref(p), boost::ref(mol1), atom1, boost::ref(mol2), atom2);
|
|
}
|
|
return res;
|
|
}
|
|
static bool MCSBondComparePyFunc(const MCSBondCompareParameters &p,
|
|
const ROMol &mol1, unsigned int bond1,
|
|
const ROMol &mol2, unsigned int bond2,
|
|
void *userData) {
|
|
PRECONDITION(userData, "userData must not be NULL");
|
|
PyCompareFunctionUserData *cfud =
|
|
static_cast<PyCompareFunctionUserData *>(userData);
|
|
CHECK_INVARIANT(cfud, "");
|
|
bool res = false;
|
|
{
|
|
PyGILStateHolder h;
|
|
res = python::call_method<bool>(
|
|
cfud->pyAtomBondCompData.pyBondComp.ptr(), COMPARE_FUNC_NAME,
|
|
boost::ref(p), boost::ref(mol1), bond1, boost::ref(mol2), bond2);
|
|
}
|
|
return res;
|
|
}
|
|
static bool MCSProgressCallbackPyFunc(const MCSProgressData &stat,
|
|
const MCSParameters ¶ms,
|
|
void *userData) {
|
|
PRECONDITION(userData, "userData must not be NULL");
|
|
PyProgressCallbackUserData *pcud =
|
|
static_cast<PyProgressCallbackUserData *>(userData);
|
|
bool res = false;
|
|
CHECK_INVARIANT(pcud, "");
|
|
MCSParameters paramsCopy(params);
|
|
if (pcud->pyAtomBondCompData.standardBondTyperFunc) {
|
|
paramsCopy.BondTyper = pcud->pyAtomBondCompData.standardBondTyperFunc;
|
|
}
|
|
{
|
|
PyGILStateHolder h;
|
|
PyMCSParameters ps(paramsCopy, *pcud);
|
|
PyMCSProgressData pd(stat);
|
|
res = python::call_method<bool>(pcud->pyMCSProgress.ptr(),
|
|
CALLBACK_FUNC_NAME, boost::ref(pd),
|
|
boost::ref(ps));
|
|
}
|
|
return res;
|
|
}
|
|
static bool MCSFinalMatchCheckPyFunc(
|
|
const std::uint32_t c1[], const std::uint32_t c2[], const ROMol &mol1,
|
|
const FMCS::Graph &query, const ROMol &mol2, const FMCS::Graph &target,
|
|
const MCSParameters *params) {
|
|
PRECONDITION(params, "params must not be NULL");
|
|
PyMCSFinalMatchCheckFunctionUserData *fmud =
|
|
static_cast<PyMCSFinalMatchCheckFunctionUserData *>(
|
|
params->FinalMatchCheckerUserData);
|
|
CHECK_INVARIANT(fmud, "");
|
|
bool res = false;
|
|
{
|
|
PyGILStateHolder h;
|
|
PyMCSParameters ps(*params, *fmud);
|
|
auto numMcsAtoms = boost::num_vertices(query);
|
|
PyObject *pyAtomIdxMatch = PyTuple_New(numMcsAtoms);
|
|
for (unsigned int i = 0; i < numMcsAtoms; ++i) {
|
|
PyObject *pyPair = PyTuple_New(2);
|
|
PyTuple_SetItem(pyPair, 0,
|
|
PyInt_FromLong(query[c1[boost::vertex(i, query)]]));
|
|
PyTuple_SetItem(pyPair, 1,
|
|
PyInt_FromLong(target[c2[boost::vertex(i, query)]]));
|
|
PyTuple_SetItem(pyAtomIdxMatch, i, pyPair);
|
|
}
|
|
auto numMcsBonds = boost::num_edges(query);
|
|
auto queryBondIt = boost::edges(query).first;
|
|
PyObject *pyBondIdxMatch = PyTuple_New(numMcsBonds);
|
|
for (unsigned int i = 0; i < numMcsBonds; ++i, ++queryBondIt) {
|
|
const auto queryBond = mol1.getBondBetweenAtoms(
|
|
query[c1[boost::source(*queryBondIt, query)]],
|
|
query[c1[boost::target(*queryBondIt, query)]]);
|
|
CHECK_INVARIANT(queryBond, "");
|
|
const auto targetBond = mol2.getBondBetweenAtoms(
|
|
target[c2[boost::source(*queryBondIt, query)]],
|
|
target[c2[boost::target(*queryBondIt, query)]]);
|
|
CHECK_INVARIANT(targetBond, "");
|
|
PyObject *pyPair = PyTuple_New(2);
|
|
PyTuple_SetItem(pyPair, 0, PyInt_FromLong(queryBond->getIdx()));
|
|
PyTuple_SetItem(pyPair, 1, PyInt_FromLong(targetBond->getIdx()));
|
|
PyTuple_SetItem(pyBondIdxMatch, i, pyPair);
|
|
}
|
|
res = python::call_method<bool>(
|
|
fmud->pyMCSFinalMatchCheck.ptr(), CALLBACK_FUNC_NAME,
|
|
boost::ref(mol1), boost::ref(mol2), python::handle<>(pyAtomIdxMatch),
|
|
python::handle<>(pyBondIdxMatch), boost::ref(ps));
|
|
}
|
|
return res;
|
|
}
|
|
static bool MCSAcceptancePyFunc(
|
|
const ROMol &query, const ROMol &target,
|
|
const std::vector<std::pair<int, int>> &atomIdxMatch,
|
|
const std::vector<std::pair<int, int>> &bondIdxMatch,
|
|
const MCSParameters *params) {
|
|
PRECONDITION(params, "params must not be NULL");
|
|
PyMCSAcceptanceFunctionUserData *afud =
|
|
static_cast<PyMCSAcceptanceFunctionUserData *>(
|
|
params->ShouldAcceptMCSUserData);
|
|
CHECK_INVARIANT(afud, "");
|
|
bool res = false;
|
|
{
|
|
PyGILStateHolder h;
|
|
PyMCSParameters ps(*params, *afud);
|
|
auto pyAtomIdxMatch = convertMatchesToTupleOfPairs(atomIdxMatch);
|
|
auto pyBondIdxMatch = convertMatchesToTupleOfPairs(bondIdxMatch);
|
|
res = python::call_method<bool>(
|
|
afud->pyMCSAcceptance.ptr(), CALLBACK_FUNC_NAME, boost::ref(query),
|
|
boost::ref(target), python::handle<>(pyAtomIdxMatch),
|
|
python::handle<>(pyBondIdxMatch), boost::ref(ps));
|
|
}
|
|
return res;
|
|
}
|
|
std::unique_ptr<MCSParameters> p;
|
|
PyCompareFunctionUserData cfud;
|
|
PyProgressCallbackUserData pcud;
|
|
PyMCSFinalMatchCheckFunctionUserData fmud;
|
|
PyMCSAcceptanceFunctionUserData afud;
|
|
};
|
|
|
|
MCSResult *FindMCSWrapper(python::object mols, bool maximizeBonds,
|
|
double threshold, unsigned int timeout, bool verbose,
|
|
bool matchValences, bool ringMatchesRingOnly,
|
|
bool completeRingsOnly, bool matchChiralTag,
|
|
AtomComparator atomComp, BondComparator bondComp,
|
|
RingComparator ringComp, std::string seedSmarts) {
|
|
std::vector<ROMOL_SPTR> ms;
|
|
unsigned int nElems = python::extract<unsigned int>(mols.attr("__len__")());
|
|
ms.resize(nElems);
|
|
for (unsigned int i = 0; i < nElems; ++i) {
|
|
if (!mols[i]) {
|
|
throw_value_error("molecule is None");
|
|
}
|
|
ms[i] = python::extract<ROMOL_SPTR>(mols[i]);
|
|
}
|
|
MCSParameters p;
|
|
p.Threshold = threshold;
|
|
p.MaximizeBonds = maximizeBonds;
|
|
p.Timeout = timeout;
|
|
p.Verbose = verbose;
|
|
p.InitialSeed = seedSmarts;
|
|
p.AtomCompareParameters.MatchValences = matchValences;
|
|
p.AtomCompareParameters.MatchChiralTag = matchChiralTag;
|
|
p.AtomCompareParameters.RingMatchesRingOnly = ringMatchesRingOnly;
|
|
p.setMCSAtomTyperFromEnum(atomComp);
|
|
p.setMCSBondTyperFromEnum(bondComp);
|
|
p.BondCompareParameters.RingMatchesRingOnly = ringMatchesRingOnly;
|
|
p.BondCompareParameters.CompleteRingsOnly = completeRingsOnly;
|
|
p.BondCompareParameters.MatchFusedRings = (ringComp != IgnoreRingFusion);
|
|
p.BondCompareParameters.MatchFusedRingsStrict =
|
|
(ringComp == StrictRingFusion);
|
|
|
|
MCSResult *res = nullptr;
|
|
{
|
|
NOGIL gil;
|
|
res = new MCSResult(findMCS(ms, &p));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
MCSResult *FindMCSWrapper2(python::object mols, PyMCSParameters &pyMcsParams) {
|
|
std::vector<ROMOL_SPTR> ms;
|
|
unsigned int nElems = python::extract<unsigned int>(mols.attr("__len__")());
|
|
ms.resize(nElems);
|
|
for (unsigned int i = 0; i < nElems; ++i) {
|
|
if (!mols[i]) {
|
|
throw_value_error("molecule is None");
|
|
}
|
|
ms[i] = python::extract<ROMOL_SPTR>(mols[i]);
|
|
}
|
|
|
|
MCSResult *res = nullptr;
|
|
{
|
|
NOGIL gil;
|
|
res = new MCSResult(findMCS(ms, pyMcsParams.get()));
|
|
}
|
|
return res;
|
|
}
|
|
} // namespace RDKit
|
|
namespace {
|
|
python::object degenerateSmartsQueryMolDictHelper(
|
|
const RDKit::MCSResult &self) {
|
|
python::dict res;
|
|
for (const auto &pair : self.DegenerateSmartsQueryMolDict) {
|
|
res[pair.first] = pair.second;
|
|
}
|
|
return std::move(res);
|
|
}
|
|
struct mcsresult_wrapper {
|
|
static void wrap() {
|
|
python::class_<RDKit::MCSResult>("MCSResult", "used to return MCS results",
|
|
python::no_init)
|
|
.def_readonly("numAtoms", &RDKit::MCSResult::NumAtoms,
|
|
"number of atoms in MCS")
|
|
.def_readonly("numBonds", &RDKit::MCSResult::NumBonds,
|
|
"number of bonds in MCS")
|
|
.def_readonly("queryMol", &RDKit::MCSResult::QueryMol,
|
|
"query molecule for the MCS")
|
|
.def_readonly("smartsString", &RDKit::MCSResult::SmartsString,
|
|
"SMARTS string for the MCS")
|
|
.def_readonly("canceled", &RDKit::MCSResult::Canceled,
|
|
"if True, the MCS calculation did not finish")
|
|
.add_property(
|
|
"degenerateSmartsQueryMolDict", degenerateSmartsQueryMolDictHelper,
|
|
"Dictionary collecting all degenerate (SMARTS, queryMol) pairs "
|
|
"(empty if MCSParameters.StoreAll is False)");
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
BOOST_PYTHON_MODULE(rdFMCS) {
|
|
python::scope().attr("__doc__") =
|
|
"Module containing a C++ implementation of the FMCS algorithm";
|
|
mcsresult_wrapper::wrap();
|
|
|
|
python::enum_<RDKit::AtomComparator>("AtomCompare")
|
|
.value("CompareAny", RDKit::AtomCompareAny)
|
|
.value("CompareElements", RDKit::AtomCompareElements)
|
|
.value("CompareIsotopes", RDKit::AtomCompareIsotopes)
|
|
.value("CompareAnyHeavyAtom", RDKit::AtomCompareAnyHeavyAtom);
|
|
python::enum_<RDKit::BondComparator>("BondCompare")
|
|
.value("CompareAny", RDKit::BondCompareAny)
|
|
.value("CompareOrder", RDKit::BondCompareOrder)
|
|
.value("CompareOrderExact", RDKit::BondCompareOrderExact);
|
|
python::enum_<RDKit::RingComparator>("RingCompare")
|
|
.value("IgnoreRingFusion", RDKit::IgnoreRingFusion)
|
|
.value("PermissiveRingFusion", RDKit::PermissiveRingFusion)
|
|
.value("StrictRingFusion", RDKit::StrictRingFusion);
|
|
|
|
std::string docString = "Find the MCS for a set of molecules";
|
|
python::def(
|
|
"FindMCS", RDKit::FindMCSWrapper,
|
|
(python::arg("mols"), python::arg("maximizeBonds") = true,
|
|
python::arg("threshold") = 1.0, python::arg("timeout") = 3600,
|
|
python::arg("verbose") = false, python::arg("matchValences") = false,
|
|
python::arg("ringMatchesRingOnly") = false,
|
|
python::arg("completeRingsOnly") = false,
|
|
python::arg("matchChiralTag") = false,
|
|
python::arg("atomCompare") = RDKit::AtomCompareElements,
|
|
python::arg("bondCompare") = RDKit::BondCompareOrder,
|
|
python::arg("ringCompare") = RDKit::IgnoreRingFusion,
|
|
python::arg("seedSmarts") = ""),
|
|
python::return_value_policy<python::manage_new_object>(),
|
|
docString.c_str());
|
|
|
|
python::class_<RDKit::PyMCSParameters, boost::noncopyable>(
|
|
"MCSParameters", "Parameters controlling how the MCS is constructed")
|
|
.add_property("MaximizeBonds", &RDKit::PyMCSParameters::getMaximizeBonds,
|
|
&RDKit::PyMCSParameters::setMaximizeBonds,
|
|
"toggles maximizing the number of bonds (instead of the "
|
|
"number of atoms)")
|
|
.add_property("Threshold", &RDKit::PyMCSParameters::getThreshold,
|
|
&RDKit::PyMCSParameters::setThreshold,
|
|
"fraction of the dataset that must contain the MCS")
|
|
.add_property("Timeout", &RDKit::PyMCSParameters::getTimeout,
|
|
&RDKit::PyMCSParameters::setTimeout,
|
|
"timeout (in seconds) for the calculation")
|
|
.add_property("Verbose", &RDKit::PyMCSParameters::getVerbose,
|
|
&RDKit::PyMCSParameters::setVerbose, "toggles verbose mode")
|
|
.add_property("AtomCompareParameters",
|
|
python::make_function(
|
|
&RDKit::PyMCSParameters::getAtomCompareParameters,
|
|
python::return_internal_reference<>()),
|
|
&RDKit::PyMCSParameters::setAtomCompareParameters,
|
|
"parameters for comparing atoms")
|
|
.add_property("BondCompareParameters",
|
|
python::make_function(
|
|
&RDKit::PyMCSParameters::getBondCompareParameters,
|
|
python::return_internal_reference<>()),
|
|
&RDKit::PyMCSParameters::setBondCompareParameters,
|
|
"parameters for comparing bonds")
|
|
.add_property("AtomTyper", &RDKit::PyMCSParameters::getMCSAtomTyper,
|
|
&RDKit::PyMCSParameters::setMCSAtomTyper,
|
|
"atom typer to be used. Must be one of the "
|
|
"members of the rdFMCS.AtomCompare class or "
|
|
"an instance of a user-defined subclass of "
|
|
"rdFMCS.MCSAtomCompare")
|
|
.add_property("BondTyper", &RDKit::PyMCSParameters::getMCSBondTyper,
|
|
&RDKit::PyMCSParameters::setMCSBondTyper,
|
|
"bond typer to be used. Must be one of the "
|
|
"members of the rdFMCS.BondCompare class or "
|
|
"an instance of a user-defined subclass of "
|
|
"rdFMCS.MCSBondCompare")
|
|
.add_property("ProgressCallback",
|
|
&RDKit::PyMCSParameters::getMCSProgressCallback,
|
|
&RDKit::PyMCSParameters::setMCSProgressCallback,
|
|
"progress callback class. Must be a "
|
|
"user-defined subclass of rdFMCS.Progress")
|
|
.add_property("FinalMatchChecker",
|
|
&RDKit::PyMCSParameters::getFinalMatchCheck,
|
|
&RDKit::PyMCSParameters::setFinalMatchCheck,
|
|
"seed final match checker callback class. Must be a "
|
|
"user-defined subclass of rdFMCS.MCSFinalMatchCheck")
|
|
.add_property("ShouldAcceptMCS",
|
|
&RDKit::PyMCSParameters::getShouldAcceptMCS,
|
|
&RDKit::PyMCSParameters::setShouldAcceptMCS,
|
|
"MCS acceptance callback class. Must be a "
|
|
"user-defined subclass of rdFMCS.MCSAcceptance")
|
|
.add_property("InitialSeed", &RDKit::PyMCSParameters::getInitialSeed,
|
|
&RDKit::PyMCSParameters::setInitialSeed,
|
|
"SMILES string to be used as the seed of the MCS")
|
|
.add_property("StoreAll", &RDKit::PyMCSParameters::getStoreAll,
|
|
&RDKit::PyMCSParameters::setStoreAll,
|
|
"toggles storage of degenerate MCSs");
|
|
|
|
python::class_<RDKit::MCSAtomCompareParameters, boost::noncopyable>(
|
|
"MCSAtomCompareParameters",
|
|
"Parameters controlling how atom-atom matching is done")
|
|
.def_readwrite("MatchValences",
|
|
&RDKit::MCSAtomCompareParameters::MatchValences,
|
|
"include atom valences in the match")
|
|
.def_readwrite("MatchChiralTag",
|
|
&RDKit::MCSAtomCompareParameters::MatchChiralTag,
|
|
"include atom chirality in the match")
|
|
.def_readwrite("MaxDistance",
|
|
&RDKit::MCSAtomCompareParameters::MaxDistance,
|
|
"Require atoms to be within this many angstroms in 3D")
|
|
.def_readwrite("MatchFormalCharge",
|
|
&RDKit::MCSAtomCompareParameters::MatchFormalCharge,
|
|
"include formal charge in the match")
|
|
.def_readwrite("RingMatchesRingOnly",
|
|
&RDKit::MCSAtomCompareParameters::RingMatchesRingOnly,
|
|
"ring atoms are only allowed to match other ring atoms")
|
|
.def_readwrite("CompleteRingsOnly",
|
|
&RDKit::MCSAtomCompareParameters::CompleteRingsOnly,
|
|
"results cannot include lone ring atoms")
|
|
.def_readwrite("MatchIsotope",
|
|
&RDKit::MCSAtomCompareParameters::MatchIsotope,
|
|
"use isotope atom queries in MCSResults");
|
|
|
|
python::class_<RDKit::MCSBondCompareParameters, boost::noncopyable>(
|
|
"MCSBondCompareParameters",
|
|
"Parameters controlling how bond-bond matching is done")
|
|
.def_readwrite("RingMatchesRingOnly",
|
|
&RDKit::MCSBondCompareParameters::RingMatchesRingOnly,
|
|
"ring bonds are only allowed to match other ring bonds")
|
|
.def_readwrite("CompleteRingsOnly",
|
|
&RDKit::MCSBondCompareParameters::CompleteRingsOnly,
|
|
"results cannot include partial rings")
|
|
.def_readwrite(
|
|
"MatchFusedRings", &RDKit::MCSBondCompareParameters::MatchFusedRings,
|
|
"enforce check on ring fusion, i.e. alpha-methylnaphthalene "
|
|
"won't match beta-methylnaphtalene, but decalin "
|
|
"will match cyclodecane unless MatchFusedRingsStrict is True")
|
|
.def_readwrite(
|
|
"MatchFusedRingsStrict",
|
|
&RDKit::MCSBondCompareParameters::MatchFusedRingsStrict,
|
|
"only enforced if MatchFusedRings is True; the ring fusion "
|
|
"must be the same in both query and target, i.e. decalin "
|
|
"won't match cyclodecane")
|
|
.def_readwrite("MatchStereo",
|
|
&RDKit::MCSBondCompareParameters::MatchStereo,
|
|
"include bond stereo in the comparison");
|
|
|
|
python::class_<RDKit::PyMCSProgress, boost::noncopyable>(
|
|
"MCSProgress",
|
|
"Base class. Subclass and override "
|
|
"MCSProgress." CALLBACK_FUNC_NAME
|
|
"() "
|
|
"to define a custom callback function")
|
|
.def(
|
|
CALLBACK_FUNC_NAME, &RDKit::PyMCSProgress::operator(),
|
|
(python::arg("self"), python::arg("stat"), python::arg("parameters")),
|
|
"override to implement a custom progress callback");
|
|
|
|
python::class_<RDKit::PyMCSProgressData, boost::noncopyable>(
|
|
"MCSProgressData", "Information about the MCS progress")
|
|
.add_property("numAtoms", &RDKit::PyMCSProgressData::getNumAtoms,
|
|
"number of atoms in MCS")
|
|
.add_property("numBonds", &RDKit::PyMCSProgressData::getNumBonds,
|
|
"number of bonds in MCS")
|
|
.add_property("seedProcessed",
|
|
&RDKit::PyMCSProgressData::getSeedProcessed,
|
|
"number of processed seeds");
|
|
|
|
python::class_<RDKit::PyMCSAtomCompare, boost::noncopyable>(
|
|
"MCSAtomCompare",
|
|
"Base class. Subclass and override "
|
|
"MCSAtomCompare." COMPARE_FUNC_NAME
|
|
"() to define custom "
|
|
"atom compare functions, then set MCSParameters.AtomTyper "
|
|
"to an instance of the subclass")
|
|
.def("CheckAtomRingMatch", &RDKit::PyMCSAtomCompare::checkAtomRingMatch,
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("atom1"), python::arg("mol2"), python::arg("atom2")),
|
|
"Return True if both atoms are, or are not, in a ring")
|
|
.def("CheckAtomCharge", &RDKit::PyMCSAtomCompare::checkAtomCharge,
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("atom1"), python::arg("mol2"), python::arg("atom2")),
|
|
"Return True if both atoms have the same formal charge")
|
|
.def("CheckAtomChirality", &RDKit::PyMCSAtomCompare::checkAtomChirality,
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("atom1"), python::arg("mol2"), python::arg("atom2")),
|
|
"Return True if both atoms have, or have not, a chiral tag")
|
|
.def(COMPARE_FUNC_NAME, &RDKit::PyMCSAtomCompare::operator(),
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("atom1"), python::arg("mol2"), python::arg("atom2")),
|
|
"override to implement custom atom comparison");
|
|
|
|
python::class_<RDKit::PyMCSBondCompare, boost::noncopyable>(
|
|
"MCSBondCompare",
|
|
"Base class. Subclass and override "
|
|
"MCSBondCompare." COMPARE_FUNC_NAME
|
|
"() to define custom "
|
|
"bond compare functions, then set MCSParameters.BondTyper "
|
|
"to an instance of the subclass")
|
|
.def("CheckBondStereo", &RDKit::PyMCSBondCompare::checkBondStereo,
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("bond1"), python::arg("mol2"), python::arg("bond2")),
|
|
"Return True if both bonds have, or have not, a stereo descriptor")
|
|
.def("CheckBondRingMatch", &RDKit::PyMCSBondCompare::checkBondRingMatch,
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("bond1"), python::arg("mol2"), python::arg("bond2")),
|
|
"Return True if both bonds are, or are not, part of a ring")
|
|
.def(COMPARE_FUNC_NAME, &RDKit::PyMCSBondCompare::operator(),
|
|
(python::arg("self"), python::arg("parameters"), python::arg("mol1"),
|
|
python::arg("bond1"), python::arg("mol2"), python::arg("bond2")),
|
|
"override to implement custom bond comparison");
|
|
|
|
python::class_<RDKit::PyMCSFinalMatchCheck, boost::noncopyable>(
|
|
"MCSFinalMatchCheck",
|
|
"Base class. Subclass and override "
|
|
"MCSFinalMatchCheck." CALLBACK_FUNC_NAME
|
|
"() "
|
|
"to define a custom boolean callback function. "
|
|
"Returning True will cause the growing seed to be accepted, "
|
|
"False to be rejected")
|
|
.def(CALLBACK_FUNC_NAME, &RDKit::PyMCSFinalMatchCheck::operator(),
|
|
(python::arg("self")),
|
|
"override to implement a custom seed final match checker callback");
|
|
|
|
python::class_<RDKit::PyMCSAcceptance, boost::noncopyable>(
|
|
"MCSAcceptance",
|
|
"Base class. Subclass and override "
|
|
"MCSAcceptance." CALLBACK_FUNC_NAME
|
|
"() "
|
|
"to define a custom boolean callback function. "
|
|
"Returning True will cause the MCS candidate to be accepted, "
|
|
"False to be rejected")
|
|
.def(CALLBACK_FUNC_NAME, &RDKit::PyMCSAcceptance::operator(),
|
|
(python::arg("self")),
|
|
"override to implement a custom MCS acceptance callback");
|
|
|
|
python::def("FindMCS", RDKit::FindMCSWrapper2,
|
|
(python::arg("mols"), python::arg("parameters")),
|
|
python::return_value_policy<python::manage_new_object>(),
|
|
docString.c_str());
|
|
}
|