Skip to content

Commit

Permalink
Add first cut of IFunction1D export that allows inheritance
Browse files Browse the repository at this point in the history
in Python. Refs #970
  • Loading branch information
martyngigg committed Apr 15, 2013
1 parent 27533ab commit 2977daf
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#ifndef MANTID_PYTHONINTERFACE_IFunction1DAdapter_H_
#define MANTID_PYTHONINTERFACE_IFunction1DAdapter_H_
/**
Copyright © 2011 ISIS Rutherford Appleton Laboratory & NScD Oak Ridge National Laboratory
This file is part of Mantid.
Mantid is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
Mantid is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
File change history is stored at: <https://github.com/mantidproject/mantid>.
Code Documentation is available at: <http://doxygen.mantidproject.org>
*/
//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include "MantidAPI/IFunction1D.h"
#include "MantidAPI/ParamFunction.h"
#include "MantidKernel/ClassMacros.h"

#include <boost/python/object.hpp>

namespace Mantid
{
namespace PythonInterface
{

/**
* Provides a layer class for boost::python to allow C++ virtual functions
* to be overridden in a Python object that is derived from IFunction1D.
*
* This is essentially a transparent layer that handles the function calls up into Python.
*/
class IFunction1DAdapter : public virtual API::ParamFunction, public virtual API::IFunction1D
{
public:
/// A constructor that looks like a Python __init__ method
IFunction1DAdapter(PyObject* self);

/** @name Virtual methods */
///@{
/// Returns the name of the algorithm
std::string name() const;
/// Base-class method
void function1D(double* out, const double* xValues, const size_t nData) const;
/// Python-type signature
void function1D(boost::python::object & out, const boost::python::object & xvals) const;
///@}

private:
/// The PyObject must be supplied to construct the object
DISABLE_DEFAULT_CONSTRUCT(IFunction1DAdapter);
DISABLE_COPY_AND_ASSIGN(IFunction1DAdapter);

/**
* Returns the PyObject that owns this wrapper, i.e. self
* @returns A pointer to self
*/
inline PyObject * getSelf() const { return m_self; }

/// The Python portion of the object
PyObject *m_self;
};
}
}


#endif /* MANTID_PYTHONINTERFACE_IFunction1DAdapter_H_ */
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ set ( EXPORT_FILES
src/Exports/Run.cpp
src/Exports/WorkspaceFactory.cpp
src/Exports/IFunction.cpp
src/Exports/IFunction1D.cpp
src/Exports/PropertyManagerDataService.cpp
src/Exports/FunctionFactory.cpp
src/Exports/Progress.cpp
Expand All @@ -61,12 +62,14 @@ set ( EXPORT_FILES

# Files containing additional helper code that are not related to exporting class/functions
set ( SRC_FILES
src/FitFunctions/IFunction1DAdapter.cpp
src/PythonAlgorithm/AlgorithmWrapper.cpp
src/PythonAlgorithm/PythonAlgorithm.cpp
src/CloneMatrixWorkspace.cpp
)

set ( INC_FILES
${HEADER_DIR}/api/FitFunctions/IFunction1DAdapter.h
${HEADER_DIR}/api/PythonAlgorithm/AlgorithmWrapper.h
${HEADER_DIR}/api/PythonAlgorithm/PythonAlgorithm.h
${HEADER_DIR}/api/BinaryOperations.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

using Mantid::API::FunctionFactoryImpl;
using Mantid::API::FunctionFactory;
using Mantid::API::IFunction;
using Mantid::PythonInterface::PythonObjectInstantiator;
using Mantid::Kernel::AbstractInstantiator;

using namespace boost::python;

namespace
Expand All @@ -37,6 +41,46 @@ namespace

return registered;
}

//--------------------------------------------- Function registration ------------------------------------------------

/// Python algorithm registration mutex in anonymous namespace (aka static)
Poco::Mutex FUNCTION_REGISTER_MUTEX;

/**
* A free function to register a fit function from Python
* @param obj :: A Python object that should either be a class type derived from IFunction
* or an instance of a class type derived from IFunction
*/
void subscribe(FunctionFactoryImpl & self, boost::python::object obj)
{
Poco::ScopedLock<Poco::Mutex> lock(FUNCTION_REGISTER_MUTEX);

static PyObject * const baseClass = (PyObject*)converter::registered<IFunction>::converters.to_python_target_type();
// obj could be or instance/class, check instance first
PyObject *classObject(NULL);
if( PyObject_IsInstance(obj.ptr(), baseClass) )
{
classObject = PyObject_GetAttrString(obj.ptr(), "__class__");
}
else if(PyObject_IsSubclass(obj.ptr(), baseClass))
{
classObject = obj.ptr(); // We need to ensure the type of lifetime management so grab the raw pointer
}
else
{
throw std::invalid_argument("Cannot register a function that does not derive from IFunction.");
}
//Instantiator will store a reference to the class object, so increase reference count with borrowed template
auto classHandle = handle<>(borrowed(classObject));
auto *creator = new PythonObjectInstantiator<IFunction>(object(classHandle));

// Find the function name
auto func = creator->createInstance();

// Takes ownership of instantiator
self.subscribe(func->name(), creator, FunctionFactoryImpl::OverwriteCurrent);
}
///@endcond
}

Expand All @@ -48,6 +92,9 @@ void export_FunctionFactory()
"Returns a list of the currently available functions")
.def("createFunction", &FunctionFactoryImpl::createFunction,
"Return a pointer to the requested function")
.def("subscribe", &subscribe, "Register a Python class derived from IFunction into the factory")
.def("unsubscribe", &FunctionFactoryImpl::unsubscribe, "Remove a type from the factory")

.def("Instance", &FunctionFactory::Instance, return_value_policy<reference_existing_object>(),
"Returns a reference to the FunctionFactory singleton")
.staticmethod("Instance")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "MantidAPI/IFunction1D.h"
#include "MantidPythonInterface/api/FitFunctions/IFunction1DAdapter.h"
#include <boost/python/class.hpp>

using Mantid::API::IFunction1D;
using Mantid::API::IFunction;
using Mantid::PythonInterface::IFunction1DAdapter;
using namespace boost::python;

void export_IFunction1D()
{
/**
* The Python held type, boost::shared_ptr<IFunction1DAdapter>, allows
* the class' virtual functions to be overridden in Python
*/
class_<IFunction1D,bases<IFunction>,boost::shared_ptr<IFunction1DAdapter>,
boost::noncopyable>("IFunction1D", "Base class for 1D Fit functions")
.def("function1D", (void (IFunction1DAdapter::*)(object&,const object &)const)&IFunction1DAdapter::function1D,
"Calculate the values of the function for the given x values. The output should be stored in the out array")
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "MantidPythonInterface/api/FitFunctions/IFunction1DAdapter.h"

#include "MantidPythonInterface/kernel/Converters/WrapWithNumpy.h"
#include "MantidPythonInterface/kernel/Environment/WrapperHelpers.h"

#include <boost/python/call_method.hpp>
#include <boost/python/class.hpp>

//-----------------------------------------------------------------------------
// IFunction1D definition
//-----------------------------------------------------------------------------
namespace Mantid
{
namespace PythonInterface
{
using namespace boost::python;


/**
* Construct the "wrapper" and stores the reference to the PyObject
* * @param self A reference to the calling Python object
*/
IFunction1DAdapter::IFunction1DAdapter(PyObject* self)
: IFunction1D(), m_self(self)
{
}

/**
* Returns the class name of the function. This cannot be overridden in Python.
*/
std::string IFunction1DAdapter::name() const
{
return std::string(getSelf()->ob_type->tp_name);
}

/**
* Translates between the C++ signature & the Python signature
* @param out The 1D data array of size nData that stores the output values
* @param xValues The input X values
* @param nData The size of the two arrays
*/
void IFunction1DAdapter::function1D(double* out, const double* xValues, const size_t nData) const
{
Py_intptr_t dims[1] = { static_cast<Py_intptr_t>(nData) } ;
object xvals = object(handle<>(Converters::WrapReadOnly::apply<double>::createFromArray(xValues, 1,dims)));
object outnp = object(handle<>(Converters::WrapReadWrite::apply<double>::createFromArray(out, 1,dims)));
function1D(outnp, xvals);
}

/**
* Python-type signature version of above
* @param out A read/write numpy array of doubles to store the results
* @param xvals The input X values in read-only numpy array
*/
void IFunction1DAdapter::function1D(boost::python::object & out, const boost::python::object & xvals) const
{
boost::python::call_method<void,object,object>(getSelf(), "function1D", out, xvals);
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set ( TEST_PY_FILES
FunctionFactoryTest.py
FunctionPropertyTest.py
IEventWorkspaceTest.py
IFunction1DTest.py
IPeaksWorkspaceTest.py
ITableWorkspaceTest.py
MatrixWorkspaceTest.py
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import unittest
from mantid.api import IFunction1D, IFunction, FunctionFactory

class PyLinear(IFunction1D):

def function1D(self, out, xvals):
pass

class IFunction1DTest(unittest.TestCase):

def test_instance_can_be_created_standalone(self):
func = PyLinear()
self.assertTrue(isinstance(func, IFunction1D))

def test_instance_can_be_created_from_factory(self):
FunctionFactory.subscribe(PyLinear)
func_name = PyLinear.__name__
func = FunctionFactory.createFunction(func_name)
self.assertTrue(isinstance(func, IFunction1D))
FunctionFactory.unsubscribe(func_name)


if __name__ == '__main__':
unittest.main()

0 comments on commit 2977daf

Please sign in to comment.