Mantid
Loading...
Searching...
No Matches
IFunctionAdapter.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
13
14#include <boost/python/class.hpp>
15#include <boost/python/list.hpp>
16#include <boost/variant/apply_visitor.hpp>
17#include <utility>
18
19#define PY_ARRAY_UNIQUE_SYMBOL API_ARRAY_API
20#define NO_IMPORT_ARRAY
21#include <numpy/arrayobject.h>
22
24using API::IFunction;
25using PythonInterface::callMethod;
26using PythonInterface::callMethodNoCheck;
27using PythonInterface::UndefinedAttributeError;
28using namespace boost::python;
29
30namespace {
31
33public:
34 AttrVisitor(IFunction::Attribute &attrToUpdate) : m_attr(attrToUpdate) {}
35
36 void operator()(bool value) const override { m_attr.setValue(value); }
37 void operator()(int value) const override { m_attr.setValue(value); }
38 void operator()(double value) const override { m_attr.setValue(value); }
39 void operator()(std::string value) const override { m_attr.setValue(std::move(value)); }
40 void operator()(Mantid::API::Workspace_sptr) const override { throw std::invalid_argument(m_errorMsg); }
41
42 void operator()(std::vector<bool>) const override { throw std::invalid_argument(m_errorMsg); }
43 void operator()(std::vector<int> value) const override {
44 // Previous existing code blindly converted any list type into a list of doubles.
45 // We now have to preserve this behaviour to maintain API compatibility as
46 // setValue only takes std::vector<double>.
47 std::vector<double> doubleVals(value.cbegin(), value.cend());
48 m_attr.setValue(std::move(doubleVals));
49 }
50 void operator()(std::vector<double> value) const override { m_attr.setValue(std::move(value)); }
51 void operator()(std::vector<std::string>) const override { throw std::invalid_argument(m_errorMsg); }
52
53 using Mantid::PythonInterface::IPyTypeVisitor::operator();
54
55private:
56 IFunction::Attribute &m_attr;
57 const std::string m_errorMsg = "Invalid attribute. Allowed types=float,int,str,bool,list(float),list(int)";
58};
59
66IFunction::Attribute createAttributeFromPythonValue(IFunction::Attribute attrToUpdate, const object &value) {
67
69 auto variantObj = PyNativeTypeExtractor::convert(value);
70
71 boost::apply_visitor(AttrVisitor(attrToUpdate), variantObj);
72 return attrToUpdate;
73}
74
75} // namespace
76
83IFunctionAdapter::IFunctionAdapter(PyObject *self, std::string functionMethod, std::string derivMethod)
84 : m_self(self), m_functionName(std::move(functionMethod)), m_derivName(std::move(derivMethod)),
85 m_derivOveridden(typeHasAttribute(self, m_derivName.c_str())) {
86 if (!typeHasAttribute(self, "init"))
87 throw std::runtime_error("Function does not define an init method.");
88 if (!typeHasAttribute(self, m_functionName.c_str()))
89 throw std::runtime_error("Function does not define a " + m_functionName + " method.");
90}
91
95std::string IFunctionAdapter::name() const { return getSelf()->ob_type->tp_name; }
96
100const std::string IFunctionAdapter::category() const {
101 try {
102 return callMethod<std::string>(getSelf(), "category");
103 } catch (UndefinedAttributeError &) {
104 return IFunction::category();
105 }
106}
107
108void IFunctionAdapter::init() { callMethodNoCheck<void>(getSelf(), "init"); }
109
115void IFunctionAdapter::declareAttribute(const std::string &name, const boost::python::object &defaultValue) {
117 attr = createAttributeFromPythonValue(attr, defaultValue);
119 try {
120 callMethod<void, std::string, object>(getSelf(), "setAttributeValue", name, defaultValue);
121 } catch (UndefinedAttributeError &) {
122 // nothing to do
123 }
124}
125
126GNU_DIAG_OFF("maybe-uninitialized")
133void IFunctionAdapter::declareAttribute(const std::string &name, const boost::python::object &defaultValue,
134 const boost::python::object &validator) {
136 Mantid::Kernel::IValidator_sptr c_validator = nullptr;
137
138 try {
139 c_validator = boost::python::extract<Mantid::Kernel::IValidator_sptr>(validator);
140 } catch (boost::python::error_already_set &) {
141 throw std::invalid_argument("Cannot extract Validator from object ");
142 }
143 attr = createAttributeFromPythonValue(attr, defaultValue);
144 IFunction::declareAttribute(name, attr, *c_validator);
145 try {
146 callMethod<void, std::string, object>(getSelf(), "setAttributeValue", name, defaultValue);
147 } catch (UndefinedAttributeError &) {
148 // nothing to do
149 }
150}
151GNU_DIAG_ON("maybe-uninitialized")
152
153
159PyObject *IFunctionAdapter::getAttributeValue(const IFunction &self, const std::string &name) {
160 auto attr = self.getAttribute(name);
161 return getAttributeValue(self, attr);
162}
163
171 UNUSED_ARG(self);
172 std::string type = attr.type();
173 PyObject *result(nullptr);
174 GlobalInterpreterLock gilLock;
175 if (type == "int")
176 result = to_python_value<const int &>()(attr.asInt());
177 else if (type == "double")
178 result = to_python_value<const double &>()(attr.asDouble());
179 else if (type == "std::string")
180 result = to_python_value<const std::string &>()(attr.asString());
181 else if (type == "bool")
182 result = to_python_value<const bool &>()(attr.asBool());
183 else if (type == "std::vector<double>")
184 result = to_python_value<const std::vector<double> &>()(attr.asVector());
185 else
186 throw std::runtime_error("Unknown attribute type, cannot convert C++ type "
187 "to Python. Contact developement team.");
188 return result;
189}
190
197void IFunctionAdapter::setAttributePythonValue(IFunction &self, const std::string &name, const object &value) {
198 auto previousAttr = self.getAttribute(name);
199 self.setAttribute(name, createAttributeFromPythonValue(previousAttr, value));
200}
201
208void IFunctionAdapter::setAttribute(const std::string &attName, const Attribute &attr) {
209 auto self = getSelf();
210 if (typeHasAttribute(self, "setAttributeValue")) {
211 object value = object(handle<>(getAttributeValue(*this, attr)));
212 callMethod<void, std::string, object>(self, "setAttributeValue", attName, value);
213 storeAttributeValue(attName, attr);
214 } else {
215 IFunction::setAttribute(attName, attr);
216 }
217}
218
226 auto functions = self.createEquivalentFunctions();
227 boost::python::list list;
228 for (const auto &fun : functions) {
229 list.append(fun);
230 }
231 return list;
232}
233
240double IFunctionAdapter::activeParameter(size_t i) const {
241 try {
242 return callMethod<double, size_t>(getSelf(), "activeParameter", i);
243 } catch (UndefinedAttributeError &) {
245 }
246}
247
256 try {
257 callMethod<void, size_t, double>(getSelf(), "setActiveParameter", i, value);
258 } catch (UndefinedAttributeError &) {
260 }
261}
262
270void IFunctionAdapter::evaluateFunction(double *out, const double *xValues, const size_t nData) const {
271 using namespace Converters;
272 // GIL must be held while numpy wrappers are destroyed as they access Python
273 // state information
275
276 Py_intptr_t dims[1] = {static_cast<Py_intptr_t>(nData)};
277 PyObject *xvals = WrapReadOnly::apply<double>::createFromArray(xValues, 1, dims);
278
279 // Deliberately avoids using the CallMethod wrappers. They lock the GIL again
280 // and
281 // will check for each function call whether the wrapped method exists. It
282 // also avoid unnecessary construction of
283 // boost::python::objects whn using boost::python::call_method
284
285 PyObject *result =
286 PyObject_CallMethod(getSelf(), const_cast<char *>(m_functionName.c_str()), const_cast<char *>("(O)"), xvals);
287 Py_DECREF(xvals);
288 if (PyErr_Occurred()) {
289 Py_XDECREF(result);
290 throw PythonException();
291 }
292 if (PyArray_Check(result)) {
293 auto nparray = reinterpret_cast<PyArrayObject *>(result);
294 // dtype matches so use memcpy for speed
295 if (PyArray_TYPE(nparray) == NPY_DOUBLE) {
296 std::memcpy(static_cast<void *>(out), PyArray_DATA(nparray), nData * sizeof(npy_double));
297 Py_DECREF(result);
298 } else {
299 Py_DECREF(result);
300 PyArray_Descr *dtype = PyArray_DESCR(nparray);
301 std::string err("Unsupported numpy data type: '");
302 err.append(dtype->typeobj->tp_name).append("'. Currently only numpy.float64 is supported.");
303 throw std::runtime_error(err);
304 }
305 } else {
306 std::string err("Expected ");
307 err.append(m_functionName)
308 .append(" to return a numpy array, however an ")
309 .append(result->ob_type->tp_name)
310 .append(" was returned.");
311 throw std::runtime_error(err);
312 }
313}
314
322void IFunctionAdapter::evaluateDerivative(API::Jacobian *out, const double *xValues, const size_t nData) const {
323 using namespace Converters;
324 // GIL must be held while numpy wrappers are destroyed as they access Python
325 // state information
327
328 Py_intptr_t dims[1] = {static_cast<Py_intptr_t>(nData)};
329 PyObject *xvals = WrapReadOnly::apply<double>::createFromArray(xValues, 1, dims);
330 PyObject *jacobian = boost::python::to_python_value<API::Jacobian *>()(out);
331
332 // Deliberately avoids using the CallMethod wrappers. They lock the GIL
333 // again and
334 // will check for each function call whether the wrapped method exists. It
335 // also avoid unnecessary construction of
336 // boost::python::objects when using boost::python::call_method
337 PyObject_CallMethod(getSelf(), const_cast<char *>(m_derivName.c_str()), const_cast<char *>("(OO)"), xvals, jacobian);
338 if (PyErr_Occurred())
339 throw PythonException();
340}
341} // namespace Mantid::PythonInterface
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
const std::string m_errorMsg
IFunction::Attribute & m_attr
Mantid::API::IFunction::Attribute Attribute
tagPyArrayObject PyArrayObject
_PyArray_Descr PyArray_Descr
std::unique_ptr< ConceptT > m_self
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:48
#define GNU_DIAG_ON(x)
#define GNU_DIAG_OFF(x)
This is a collection of macros for turning compiler warnings off in a controlled manner.
Attribute is a non-fitting parameter.
Definition IFunction.h:285
std::vector< double > asVector() const
Returns vector<double> if attribute is vector<double>, throws exception otherwise.
int asInt() const
Returns int value if attribute is a int, throws exception otherwise.
std::string asString() const
Returns string value if attribute is a string, throws exception otherwise.
double asDouble() const
Returns double value if attribute is a double, throws exception otherwise.
bool asBool() const
Returns bool value if attribute is a bool, throws exception otherwise.
std::string type() const
Returns type of the attribute.
This is an interface to a fitting function - a semi-abstarct class.
Definition IFunction.h:166
virtual Attribute getAttribute(const std::string &name) const
Return a value of attribute attName.
void declareAttribute(const std::string &name, const API::IFunction::Attribute &defaultValue)
Declare a single attribute.
virtual const std::string category() const
The categories the Fit function belong to.
Definition IFunction.h:443
virtual void setAttribute(const std::string &name, const Attribute &)
Set a value to attribute attName.
virtual double activeParameter(size_t i) const
Value of i-th active parameter.
virtual bool hasAttribute(const std::string &name) const
Check if attribute attName exists.
virtual std::vector< std::shared_ptr< IFunction > > createEquivalentFunctions() const
Split this function (if needed) into a list of independent functions.
void storeAttributeValue(const std::string &name, const API::IFunction::Attribute &value)
Store an attribute's value.
virtual void setActiveParameter(size_t i, double value)
Set new value of i-th active parameter.
Represents the Jacobian in IFitFunction::functionDeriv.
Definition Jacobian.h:22
Defines a structure for acquiring/releasing the Python GIL using the RAII pattern.
Provides a layer to hook into the protected functions of IFunction.
void init() override
Declare all attributes & parameters.
IFunctionAdapter(PyObject *self, std::string functionMethod, std::string derivMethod)
A constructor that looks like a Python init method.
static void setAttributePythonValue(IFunction &self, const std::string &name, const boost::python::object &value)
Set the attribute's value.
static boost::python::list createPythonEquivalentFunctions(const IFunction &self)
Split this function (if needed) into a list of independent functions.
std::string name() const override
Returns the name of the function.
void evaluateFunction(double *out, const double *xValues, const size_t nData) const
Evaluate the function by calling the overridden method.
void evaluateDerivative(API::Jacobian *out, const double *xValues, const size_t nData) const
Evaluate the derivative by calling the overridden method.
const std::string category() const override
Specify a category for the function.
void setAttribute(const std::string &attName, const API::IFunction::Attribute &attr) override
Called by the framework when an attribute has been set.
std::string m_functionName
The name of the method to evaluate the function.
std::string m_derivName
The name of the method to evaluate the derivative.
void setActiveParameter(size_t i, double value) override
Override this method to make fitted parameters different from the declared.
void declareAttribute(const std::string &name, const boost::python::object &defaultValue)
Declare an attribute with an initial value.
double activeParameter(size_t i) const override
Override this method to make fitted parameters different from the declared.
static PyObject * getAttributeValue(const IFunction &self, const std::string &name)
Get a named attribute value.
Exception type that captures the current Python error state as a generic C++ exception for any genera...
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
std::shared_ptr< IValidator > IValidator_sptr
A shared_ptr to an IValidator.
Definition IValidator.h:26
bool MANTID_PYTHONINTERFACE_CORE_DLL typeHasAttribute(PyObject *obj, const char *attr)
This namespace contains helper functions for classes that are overridden in Python.
STL namespace.
static PythonOutputT convert(const boost::python::object &obj)
Defines an exception for an undefined attribute.
Definition CallMethod.h:20