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 +
11
12#include <boost/python/class.hpp>
13#include <boost/python/list.hpp>
14#include <boost/variant/apply_visitor.hpp>
15#include <utility>
16
17#define PY_ARRAY_UNIQUE_SYMBOL API_ARRAY_API
18#define NO_IMPORT_ARRAY
19#include <numpy/arrayobject.h>
20
22using API::IFunction;
23using PythonInterface::callMethod;
24using PythonInterface::callMethodNoCheck;
25using PythonInterface::UndefinedAttributeError;
26using namespace boost::python;
27
28namespace {
29
31public:
32 AttrVisitor(IFunction::Attribute &attrToUpdate) : m_attr(attrToUpdate) {}
33
34 void operator()(bool value) const override { m_attr.setValue(value); }
35 void operator()(long value) const override { m_attr.setValue(static_cast<int>(value)); }
36 void operator()(double value) const override { m_attr.setValue(value); }
37 void operator()(std::string value) const override { m_attr.setValue(std::move(value)); }
38 void operator()(Mantid::API::Workspace_sptr) const override { throw std::invalid_argument(m_errorMsg); }
39
40 void operator()(std::vector<bool>) const override { throw std::invalid_argument(m_errorMsg); }
41 void operator()(std::vector<long> value) const override {
42 // Previous existing code blindly converted any list type into a list of doubles.
43 // We now have to preserve this behaviour to maintain API compatibility as
44 // setValue only takes std::vector<double>.
45 std::vector<double> doubleVals(value.cbegin(), value.cend());
46 m_attr.setValue(std::move(doubleVals));
47 }
48 void operator()(std::vector<double> value) const override { m_attr.setValue(std::move(value)); }
49 void operator()(std::vector<std::string>) const override { throw std::invalid_argument(m_errorMsg); }
50
51 using Mantid::PythonInterface::IPyTypeVisitor::operator();
52
53private:
54 IFunction::Attribute &m_attr;
55 const std::string m_errorMsg = "Invalid attribute. Allowed types=float,int,str,bool,list(float),list(int)";
56};
57
64IFunction::Attribute createAttributeFromPythonValue(IFunction::Attribute attrToUpdate, const object &value) {
65
67 auto variantObj = PyNativeTypeExtractor::convert(value);
68
69 boost::apply_visitor(AttrVisitor(attrToUpdate), variantObj);
70 return attrToUpdate;
71}
72
73} // namespace
74
81IFunctionAdapter::IFunctionAdapter(PyObject *self, std::string functionMethod, std::string derivMethod)
82 : m_self(self), m_functionName(std::move(functionMethod)), m_derivName(std::move(derivMethod)),
83 m_derivOveridden(typeHasAttribute(self, m_derivName.c_str())) {
84 if (!typeHasAttribute(self, "init"))
85 throw std::runtime_error("Function does not define an init method.");
86 if (!typeHasAttribute(self, m_functionName.c_str()))
87 throw std::runtime_error("Function does not define a " + m_functionName + " method.");
88}
89
93std::string IFunctionAdapter::name() const { return getSelf()->ob_type->tp_name; }
94
98const std::string IFunctionAdapter::category() const {
99 try {
100 return callMethod<std::string>(getSelf(), "category");
101 } catch (UndefinedAttributeError &) {
102 return IFunction::category();
103 }
104}
105
106void IFunctionAdapter::init() { callMethodNoCheck<void>(getSelf(), "init"); }
107
113void IFunctionAdapter::declareAttribute(const std::string &name, const boost::python::object &defaultValue) {
115 attr = createAttributeFromPythonValue(attr, defaultValue);
117 try {
118 callMethod<void, std::string, object>(getSelf(), "setAttributeValue", name, defaultValue);
119 } catch (UndefinedAttributeError &) {
120 // nothing to do
121 }
122}
123
130void IFunctionAdapter::declareAttribute(const std::string &name, const boost::python::object &defaultValue,
131 const boost::python::object &validator) {
133 Mantid::Kernel::IValidator_sptr c_validator = nullptr;
134
135 try {
136 c_validator = boost::python::extract<Mantid::Kernel::IValidator_sptr>(validator);
137 } catch (boost::python::error_already_set &) {
138 throw std::invalid_argument("Cannot extract Validator from object ");
139 }
140 attr = createAttributeFromPythonValue(attr, defaultValue);
141 IFunction::declareAttribute(name, attr, *c_validator);
142 try {
143 callMethod<void, std::string, object>(getSelf(), "setAttributeValue", name, defaultValue);
144 } catch (UndefinedAttributeError &) {
145 // nothing to do
146 }
147}
148
155PyObject *IFunctionAdapter::getAttributeValue(const IFunction &self, const std::string &name) {
156 auto attr = self.getAttribute(name);
157 return getAttributeValue(self, attr);
158}
159
167 UNUSED_ARG(self);
168 std::string type = attr.type();
169 PyObject *result(nullptr);
170 if (type == "int")
171 result = to_python_value<const int &>()(attr.asInt());
172 else if (type == "double")
173 result = to_python_value<const double &>()(attr.asDouble());
174 else if (type == "std::string")
175 result = to_python_value<const std::string &>()(attr.asString());
176 else if (type == "bool")
177 result = to_python_value<const bool &>()(attr.asBool());
178 else if (type == "std::vector<double>")
179 result = to_python_value<const std::vector<double> &>()(attr.asVector());
180 else
181 throw std::runtime_error("Unknown attribute type, cannot convert C++ type "
182 "to Python. Contact developement team.");
183 return result;
184}
185
192void IFunctionAdapter::setAttributePythonValue(IFunction &self, const std::string &name, const object &value) {
193 auto previousAttr = self.getAttribute(name);
194 self.setAttribute(name, createAttributeFromPythonValue(previousAttr, value));
195}
196
203void IFunctionAdapter::setAttribute(const std::string &attName, const Attribute &attr) {
204 try {
205 object value = object(handle<>(getAttributeValue(*this, attr)));
206 callMethod<void, std::string, object>(getSelf(), "setAttributeValue", attName, value);
207 storeAttributeValue(attName, attr);
208 } catch (UndefinedAttributeError &) {
209 IFunction::setAttribute(attName, attr);
210 }
211}
212
220 auto functions = self.createEquivalentFunctions();
221 boost::python::list list;
222 for (const auto &fun : functions) {
223 list.append(fun);
224 }
225 return list;
226}
227
234double IFunctionAdapter::activeParameter(size_t i) const {
235 try {
236 return callMethod<double, size_t>(getSelf(), "activeParameter", i);
237 } catch (UndefinedAttributeError &) {
239 }
240}
241
250 try {
251 callMethod<void, size_t, double>(getSelf(), "setActiveParameter", i, value);
252 } catch (UndefinedAttributeError &) {
254 }
255}
256
264void IFunctionAdapter::evaluateFunction(double *out, const double *xValues, const size_t nData) const {
265 using namespace Converters;
266 // GIL must be held while numpy wrappers are destroyed as they access Python
267 // state information
269
270 Py_intptr_t dims[1] = {static_cast<Py_intptr_t>(nData)};
271 PyObject *xvals = WrapReadOnly::apply<double>::createFromArray(xValues, 1, dims);
272
273 // Deliberately avoids using the CallMethod wrappers. They lock the GIL again
274 // and
275 // will check for each function call whether the wrapped method exists. It
276 // also avoid unnecessary construction of
277 // boost::python::objects whn using boost::python::call_method
278
279 PyObject *result =
280 PyObject_CallMethod(getSelf(), const_cast<char *>(m_functionName.c_str()), const_cast<char *>("(O)"), xvals);
281 Py_DECREF(xvals);
282 if (PyErr_Occurred()) {
283 Py_XDECREF(result);
284 throw PythonException();
285 }
286 if (PyArray_Check(result)) {
287 auto nparray = reinterpret_cast<PyArrayObject *>(result);
288 // dtype matches so use memcpy for speed
289 if (PyArray_TYPE(nparray) == NPY_DOUBLE) {
290 std::memcpy(static_cast<void *>(out), PyArray_DATA(nparray), nData * sizeof(npy_double));
291 Py_DECREF(result);
292 } else {
293 Py_DECREF(result);
294 PyArray_Descr *dtype = PyArray_DESCR(nparray);
295 std::string err("Unsupported numpy data type: '");
296 err.append(dtype->typeobj->tp_name).append("'. Currently only numpy.float64 is supported.");
297 throw std::runtime_error(err);
298 }
299 } else {
300 std::string err("Expected ");
301 err.append(m_functionName)
302 .append(" to return a numpy array, however an ")
303 .append(result->ob_type->tp_name)
304 .append(" was returned.");
305 throw std::runtime_error(err);
306 }
307}
308
316void IFunctionAdapter::evaluateDerivative(API::Jacobian *out, const double *xValues, const size_t nData) const {
317 using namespace Converters;
318 // GIL must be held while numpy wrappers are destroyed as they access Python
319 // state information
321
322 Py_intptr_t dims[1] = {static_cast<Py_intptr_t>(nData)};
323 PyObject *xvals = WrapReadOnly::apply<double>::createFromArray(xValues, 1, dims);
324 PyObject *jacobian = boost::python::to_python_value<API::Jacobian *>()(out);
325
326 // Deliberately avoids using the CallMethod wrappers. They lock the GIL
327 // again and
328 // will check for each function call whether the wrapped method exists. It
329 // also avoid unnecessary construction of
330 // boost::python::objects when using boost::python::call_method
331 PyObject_CallMethod(getSelf(), const_cast<char *>(m_derivName.c_str()), const_cast<char *>("(OO)"), xvals, jacobian);
332 if (PyErr_Occurred())
333 throw PythonException();
334}
335} // namespace Mantid::PythonInterface
double value
The value of the point.
Definition: FitMW.cpp:51
const std::string m_errorMsg
IFunction::Attribute & m_attr
tagPyArrayObject PyArrayObject
_PyArray_Descr PyArray_Descr
std::string dtype(Mantid::Kernel::PropertyWithValue< HeldType > &self)
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:64
Attribute is a non-fitting parameter.
Definition: IFunction.h:282
std::vector< double > asVector() const
Returns vector<double> if attribute is vector<double>, throws exception otherwise.
Definition: IFunction.cpp:765
int asInt() const
Returns int value if attribute is a int, throws exception otherwise.
Definition: IFunction.cpp:726
std::string asString() const
Returns string value if attribute is a string, throws exception otherwise.
Definition: IFunction.cpp:660
double asDouble() const
Returns double value if attribute is a double, throws exception otherwise.
Definition: IFunction.cpp:739
bool asBool() const
Returns bool value if attribute is a bool, throws exception otherwise.
Definition: IFunction.cpp:752
std::string type() const
Returns type of the attribute.
Definition: IFunction.cpp:608
This is an interface to a fitting function - a semi-abstarct class.
Definition: IFunction.h:163
virtual Attribute getAttribute(const std::string &name) const
Return a value of attribute attName.
Definition: IFunction.cpp:1394
void declareAttribute(const std::string &name, const API::IFunction::Attribute &defaultValue)
Declare a single attribute.
Definition: IFunction.cpp:1418
virtual const std::string category() const
The categories the Fit function belong to.
Definition: IFunction.h:440
virtual void setAttribute(const std::string &name, const Attribute &)
Set a value to attribute attName.
Definition: IFunction.cpp:1409
virtual double activeParameter(size_t i) const
Value of i-th active parameter.
Definition: IFunction.cpp:988
virtual bool hasAttribute(const std::string &name) const
Check if attribute attName exists.
Definition: IFunction.cpp:1339
virtual std::vector< std::shared_ptr< IFunction > > createEquivalentFunctions() const
Split this function (if needed) into a list of independent functions.
Definition: IFunction.cpp:1579
void storeAttributeValue(const std::string &name, const API::IFunction::Attribute &value)
Store an attribute's value.
Definition: IFunction.cpp:1464
virtual void setActiveParameter(size_t i, double value)
Set new value of i-th active parameter.
Definition: IFunction.cpp:997
Represents the Jacobian in IFitFunction::functionDeriv.
Definition: Jacobian.h:22
Defines a structure for acquiring/releasing the Python GIL using the RAII pattern.
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...
Definition: ErrorHandling.h:24
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
Definition: Workspace_fwd.h:20
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