Commit 4d4fa5f6 authored by Nick Draper's avatar Nick Draper
Browse files

Merge remote-tracking branch 'origin/feature/4218_algs_as_python_methods'

parents 8d326f49 36d0e2f3
......@@ -180,6 +180,11 @@ public:
virtual const std::string categorySeparator() const {return ";";}
/// function to return any aliases to the algorithm; A default implementation is provided
virtual const std::string alias() const {return "";}
const std::string workspaceMethodName() const;
const std::vector<std::string> workspaceMethodOn() const;
const std::string workspaceMethodInputProperty() const;
/// Algorithm ID. Unmanaged algorithms return 0 (or NULL?) values. Managed ones have non-zero.
AlgorithmID getAlgorithmID()const{return m_algorithmID;}
......@@ -267,6 +272,9 @@ protected:
/// Method defining summary, optional
virtual void initDocs() {};
/// Returns a semi-colon separated list of workspace types to attach this algorithm
virtual const std::string workspaceMethodOnTypes() const { return ""; }
void cacheWorkspaceProperties();
friend class AlgorithmProxy;
......
......@@ -101,6 +101,10 @@ namespace Mantid
void enableHistoryRecordingForChild(const bool) {};
void setRethrows(const bool rethrow);
const std::string workspaceMethodName() const;
const std::vector<std::string> workspaceMethodOn() const;
const std::string workspaceMethodInputProperty() const;
/** @name PropertyManager methods */
//@{
/// Set the property value
......
......@@ -79,6 +79,16 @@ public:
/// function to return any aliases of the algorithm.
virtual const std::string alias() const = 0;
/** @name Algorithms As Methods */
///@{
/// Returns a name that will be used when attached as a workspace method. Empty string indicates do not attach
virtual const std::string workspaceMethodName() const = 0;
/// Returns a set of class names that will have the method attached. Empty list indicates all types
virtual const std::vector<std::string> workspaceMethodOn() const = 0;
/// Returns the name of the input workspace property used by the calling object
virtual const std::string workspaceMethodInputProperty() const = 0;
///@}
/// Algorithm ID. Unmanaged algorithms return 0 (or NULL?) values. Managed ones have non-zero.
virtual AlgorithmID getAlgorithmID()const = 0;
......
......@@ -36,6 +36,12 @@ namespace Mantid
{
namespace API
{
namespace
{
/// Separator for workspace types in workspaceMethodOnTypes member
const std::string WORKSPACE_TYPES_SEPARATOR = ";";
}
// Doxygen can't handle member specialization at the moment: https://bugzilla.gnome.org/show_bug.cgi?id=406027
// so we have to ignore them
///@cond
......@@ -213,7 +219,7 @@ namespace Mantid
Poco::StringTokenizer tokenizer(category(), categorySeparator(),
Poco::StringTokenizer::TOK_TRIM | Poco::StringTokenizer::TOK_IGNORE_EMPTY);
Poco::StringTokenizer::Iterator h = tokenizer.begin();
for (; h != tokenizer.end(); ++h)
{
res.push_back(*h);
......@@ -227,6 +233,40 @@ namespace Mantid
return res;
}
/**
* @return A string giving the method name that should be attached to a workspace
*/
const std::string Algorithm::workspaceMethodName() const
{
return "";
}
/**
*
* @return A list of workspace class names that should have the workspaceMethodName attached
*/
const std::vector<std::string> Algorithm::workspaceMethodOn() const
{
Poco::StringTokenizer tokenizer(this->workspaceMethodOnTypes(), WORKSPACE_TYPES_SEPARATOR,
Poco::StringTokenizer::TOK_TRIM | Poco::StringTokenizer::TOK_IGNORE_EMPTY);
std::vector<std::string> res;
res.reserve(tokenizer.count());
for( auto iter = tokenizer.begin(); iter != tokenizer.end(); ++iter )
{
res.push_back(*iter);
}
return res;
}
/**
* @return The name of the property that the calling object will be passed to.
*/
const std::string Algorithm::workspaceMethodInputProperty() const
{
return "";
}
//=============================================================================================
//================================== Initialization ===========================================
......
......@@ -183,6 +183,33 @@ namespace Mantid
if(m_alg) m_alg->setRethrows(rethrow);
}
/**
* @return A string giving the method name that should be attached to a workspace
*/
const std::string AlgorithmProxy::workspaceMethodName() const
{
if(m_alg) return m_alg->workspaceMethodName();
else return "";
}
/**
* @return A set of workspace class names that should have the workspaceMethodName attached
*/
const std::vector<std::string> AlgorithmProxy::workspaceMethodOn() const
{
if(m_alg) return m_alg->workspaceMethodOn();
else return std::vector<std::string>();
}
/**
* @return The name of the property that the calling object will be passed to
*/
const std::string AlgorithmProxy::workspaceMethodInputProperty() const
{
if(m_alg) return m_alg->workspaceMethodInputProperty();
else return "";
}
/**
* Override setPropertyValue
* @param name The name of the property
......
......@@ -21,6 +21,9 @@ public:
int version() const { return 1;} ///< Algorithm's version for identification
const std::string category() const { return "ProxyCat";} ///< Algorithm's category for identification
const std::string alias() const { return "Dog";} ///< Algorithm's alias
const std::string workspaceMethodName() const { return "toyalgorithm"; }
const std::string workspaceMethodOnTypes() const { return "MatrixWorkspace;ITableWorkspace"; }
const std::string workspaceMethodInputProperty() const { return "InputWorkspace"; }
void init()
{
......@@ -192,6 +195,23 @@ public:
TS_ASSERT( obs.progress );
TS_ASSERT( obs.finish );
}
void test_WorkspaceMethodFunctionsReturnProxiedContent()
{
IAlgorithm_sptr alg = AlgorithmManager::Instance().create("ToyAlgorithmProxy");
TS_ASSERT_EQUALS("toyalgorithm", alg->workspaceMethodName());
auto types = alg->workspaceMethodOn();
TS_ASSERT_EQUALS(2, types.size());
if(types.size() == 2)
{
TS_ASSERT_EQUALS("MatrixWorkspace", types[0]);
TS_ASSERT_EQUALS("ITableWorkspace", types[1]);
}
TS_ASSERT_EQUALS("InputWorkspace", alg->workspaceMethodInputProperty());
}
};
#endif /*ALGORITHMPROXYTEST_H_*/
......@@ -86,6 +86,10 @@ public:
const std::string name() const { return "StubbedWorkspaceAlgorithm2";}
int version() const { return 1;}
const std::string category() const { return "Cat;Leopard;Mink";}
const std::string workspaceMethodName() const { return "methodname"; }
const std::string workspaceMethodOnTypes() const { return "MatrixWorkspace;ITableWorkspace"; }
const std::string workspaceMethodInputProperty() const { return "InputWorkspace"; }
void init()
{
declareProperty("PropertyA", 12);
......@@ -242,6 +246,30 @@ public:
TS_ASSERT( alg.isExecuted() );
}
void test_WorkspaceMethodFunctionsReturnEmptyByDefault()
{
StubbedWorkspaceAlgorithm alg;
TS_ASSERT_EQUALS("", alg.workspaceMethodName());
TS_ASSERT_EQUALS(std::vector<std::string>(), alg.workspaceMethodOn());
TS_ASSERT_EQUALS("", alg.workspaceMethodInputProperty());
}
void test_WorkspaceMethodsReturnTypesCorrectly()
{
AlgorithmWithValidateInputs alg;
TS_ASSERT_EQUALS("methodname", alg.workspaceMethodName());
auto types = alg.workspaceMethodOn();
TS_ASSERT_EQUALS(2, types.size());
if(types.size() == 2)
{
TS_ASSERT_EQUALS("MatrixWorkspace", types[0]);
TS_ASSERT_EQUALS("ITableWorkspace", types[1]);
}
TS_ASSERT_EQUALS("InputWorkspace", alg.workspaceMethodInputProperty());
}
void testStringization()
{
//Set the properties so that we know what they are
......
......@@ -57,6 +57,9 @@ public:
virtual const std::string category() const { return "Utility\\Workspaces"; }
private:
const std::string workspaceMethodName() const { return "clone"; }
const std::string workspaceMethodInputProperty() const { return "InputWorkspace"; }
/// Sets documentation strings for this algorithm
virtual void initDocs();
/// Initialisation code
......
......@@ -73,6 +73,9 @@ public:
virtual const std::string category() const { return "Transforms\\Units";}
private:
const std::string workspaceMethodName() const { return "convertUnits"; }
const std::string workspaceMethodInputProperty() const { return "InputWorkspace"; }
/// Sets documentation strings for this algorithm
virtual void initDocs();
// Overridden Algorithm methods
......
......@@ -55,6 +55,8 @@ namespace Mantid
/// Overridden exec
void exec();
const std::string workspaceMethodName() const { return "delete"; }
const std::string workspaceMethodInputProperty() const { return "Workspace"; }
};
} // namespace Algorithm
......
......@@ -76,6 +76,8 @@ public:
virtual const std::string alias() const { return ""; }
protected:
const std::string workspaceMethodName() const { return ""; } // Override the one from Rebin to ignore us
/// Sets documentation strings for this algorithm
virtual void initDocs();
// Overridden Algorithm methods
......
......@@ -64,11 +64,17 @@ public:
virtual const std::string alias() const { return "rebin"; }
protected:
const std::string workspaceMethodName() const { return "rebin"; }
const std::string workspaceMethodOnTypes() const { return "MatrixWorkspace"; }
const std::string workspaceMethodInputProperty() const { return "InputWorkspace"; }
/// Sets documentation strings for this algorithm
virtual void initDocs();
// Overridden Algorithm methods
void init();
virtual void exec();
void propagateMasks(API::MatrixWorkspace_const_sptr inputW, API::MatrixWorkspace_sptr outputW, int hist);
};
......
......@@ -55,6 +55,9 @@ public:
virtual const std::string category() const { return "Utility\\Workspaces";}
private:
const std::string workspaceMethodName() const { return "rename"; }
const std::string workspaceMethodInputProperty() const { return "InputWorkspace"; }
/// Sets documentation strings for this algorithm
virtual void initDocs();
// Overridden Algorithm methods
......
......@@ -49,9 +49,12 @@ namespace Algorithms
void setOptions(const int numBins, const bool useLogBins, const bool isDist);
private:
const std::string workspaceMethodName() const { return ""; } // Override the one from Rebin to ignore us
virtual void initDocs();
void init();
void exec();
std::map<std::string, std::string> validateInputs();
bool m_useLogBinning;
bool m_preserveEvents;
......
......@@ -61,6 +61,8 @@ public:
virtual const std::string category() const { return "Transforms\\Masking";}
private:
const std::string workspaceMethodName() const { return "maskDetectors"; }
const std::string workspaceMethodInputProperty() const { return "Workspace"; }
/// Sets documentation strings for this algorithm
virtual void initDocs();
// Implement abstract Algorithm methods
......
......@@ -9,7 +9,7 @@ Defines Python objects that wrap the C++ API namespace.
###############################################################################
# The _api C extension depends on exports defined in the _kernel extension
###############################################################################
# The fully-qualified package path allows it to be found with path manipulation
# The fully-qualified package path allows it to be found without path manipulation
from mantid.kernel import dlopen as _pydlopen
import os as _os
clib = _os.path.join(_os.path.dirname(__file__), '_api.so')
......@@ -21,10 +21,11 @@ _pydlopen.restore_flags(flags)
###############################################################################
###############################################################################
# Start the framework (if embedded in other application)
# Start the framework (if not embedded in other application)
###############################################################################
FrameworkManagerImpl.Instance()
_api._declareCPPAlgorithms()
# Declare any additional C++ algorithms defined in this package
_api._declareCPPAlgorithms()
###############################################################################
# Make aliases accessible in this namespace
......@@ -37,6 +38,9 @@ from _aliases import *
import _adsimports
###############################################################################
# Attach operators to workspaces
# Attach additional operators to workspaces
###############################################################################
import _workspaceops
_workspaceops.attach_binary_operators_to_workspace()
_workspaceops.attach_unary_operators_to_workspace()
_workspaceops.attach_tableworkspaceiterator()
\ No newline at end of file
"""
This module adds functions to the Workspace classe
This module adds functions to the Workspace classes
so that Python operators, i.e +-*/, can be used on them
It is intended for internal use.
"""
from mantid.api import (AnalysisDataService, FrameworkManager, ITableWorkspace,
Workspace, WorkspaceGroup)
from mantid.api import performBinaryOp as _performBinaryOp
from mantid.kernel.funcreturns import lhs_info
import _api
import inspect as _inspect
#------------------------------------------------------------------------------
# Binary Ops
......@@ -26,7 +26,7 @@ def attach_binary_operators_to_workspace():
return _do_binary_operation(algorithm, self, other, result_info,
inplace, reverse)
op_wrapper.__name__ = attr
setattr(Workspace, attr, op_wrapper)
setattr(_api.Workspace, attr, op_wrapper)
# Binary operations that workspaces are aware of
operations = {
"Plus":("__add__", "__radd__","__iadd__"),
......@@ -54,12 +54,12 @@ def _do_binary_operation(op, self, rhs, lhs_vars, inplace, reverse):
"""
Perform the given binary operation
@param op A string containing the Mantid algorithm name
@param self The object that was the self argument when object.__op__(other) was called
@param rhs The object that was the other argument when object.__op__(other) was called
@param lhs_vars A tuple containing details of the lhs of the assignment, i.e a = b + c, lhs_vars = (1, 'a')
@param inplace True if the operation should be performed inplace
@param reverse True if the reverse operator was called, i.e. 3 + a calls __radd__
:param op: A string containing the Mantid algorithm name
:param self: The object that was the self argument when object.__op__(other) was called
:param rhs: The object that was the other argument when object.__op__(other) was called
:param lhs_vars: A tuple containing details of the lhs of the assignment, i.e a = b + c, lhs_vars = (1, 'a')
:param inplace: True if the operation should be performed inplace
:param reverse: True if the reverse operator was called, i.e. 3 + a calls __radd__
"""
global _workspace_op_tmps
......@@ -78,16 +78,17 @@ def _do_binary_operation(op, self, rhs, lhs_vars, inplace, reverse):
output_name = _workspace_op_prefix + str(len(_workspace_op_tmps))
# Do the operation
resultws = _performBinaryOp(self,rhs, op, output_name, inplace, reverse)
resultws = _api.performBinaryOp(self,rhs, op, output_name, inplace, reverse)
# Do we need to clean up
if clear_tmps:
ads = _api.AnalysisDataServiceImpl.Instance()
for name in _workspace_op_tmps:
if name in AnalysisDataService and output_name != name:
del AnalysisDataService[name]
if name in ads and output_name != name:
del ads[name]
_workspace_op_tmps = []
else:
if type(resultws) == WorkspaceGroup:
if type(resultws) == _api.WorkspaceGroup:
# Ensure the members are removed aswell
members = resultws.getNames()
for member in members:
......@@ -113,7 +114,7 @@ def attach_unary_operators_to_workspace():
# Pass off to helper
return _do_unary_operation(algorithm, self, result_info)
op_wrapper.__name__ = attr
setattr(Workspace, attr, op_wrapper)
setattr(_api.Workspace, attr, op_wrapper)
# Binary operations that workspaces are aware of
operations = {
'NotMD':'__invert__'
......@@ -129,9 +130,9 @@ def _do_unary_operation(op, self, lhs_vars):
"""
Perform the unary operation
@param op :: name of the algorithm to run
@param self :: The object that this operation was called on
@param lhs_vars :: is expected to be a tuple containing the number of lhs variables and
:param op: name of the algorithm to run
:param self: The object that this operation was called on
:param lhs_vars: is expected to be a tuple containing the number of lhs variables and
their names as the first and second element respectively
"""
global _workspace_op_tmps
......@@ -148,16 +149,19 @@ def _do_unary_operation(op, self, lhs_vars):
_workspace_op_tmps.append(output_name)
# Do the operation
alg = FrameworkManager.createAlgorithm(op)
ads = _api.AnalysisDataServiceImpl.Instance()
fmgr = _api.FrameworkManagerImpl.Instance()
alg = fmgr.createAlgorithm(op)
alg.setPropertyValue("InputWorkspace", self.name())
alg.setPropertyValue("OutputWorkspace", output_name)
alg.execute()
resultws = AnalysisDataService[output_name]
resultws = ads[output_name]
if clear_tmps:
for name in _workspace_op_tmps:
if name in AnalysisDataService and output_name != name:
AnalysisDataService.remove(name)
if name in ads and output_name != name:
ads.remove(name)
_workspace_op_tmps = []
return resultws
......@@ -180,11 +184,45 @@ def attach_tableworkspaceiterator():
return self.__wksp.row(self.__pos-1)
return ITableWorkspaceIter(self)
setattr(ITableWorkspace, "__iter__", __iter_method)
setattr(_api.ITableWorkspace, "__iter__", __iter_method)
#------------------------------------------------------------------------------
# Attach the operators
# Algorithms as workspace methods
#------------------------------------------------------------------------------
attach_binary_operators_to_workspace()
attach_unary_operators_to_workspace()
attach_tableworkspaceiterator()
def attach_func_as_method(name, func_obj, self_param_name, workspace_types=None):
"""
Adds a method to the given type that calls an algorithm
using the calling object as the input workspace
:param name: The name of the new method as it should appear on the type
:param func_obj: A free function object that defines the implementation of the call
:param self_param_name: The name of the parameter in the free function that the method's self maps to
:param workspace_types: A list of string names of a workspace types. If None, then it is attached
to the general Workspace type. Default=None
"""
def _method_impl(self, *args, **kwargs):
# Map the calling object to the requested parameter
kwargs[self_param_name] = self
# Define the frame containing the final variable assignment
# used to figure out the workspace name
kwargs["__LHS_FRAME_OBJECT__"] = _inspect.currentframe().f_back
# Call main function
return func_obj(*args, **kwargs)
#------------------------------------------------------------------
# Add correct meta-properties for the method
_method_impl.__name__ = func_obj.__name__
_method_impl.__doc__ = func_obj.__doc__
f = _method_impl.func_code
signature = ['self']
signature.extend(func_obj.func_code.co_varnames)
c = f.__new__(f.__class__, f.co_argcount, f.co_nlocals, f.co_stacksize, f.co_flags, f.co_code, f.co_consts, f.co_names,
tuple(signature), f.co_filename, f.co_name, f.co_firstlineno, f.co_lnotab, f.co_freevars)
# Replace the code object of the wrapper function
_method_impl.func_code = c
if workspace_types or len(workspace_types) > 0:
for typename in workspace_types:
cls = getattr(_api, typename)
setattr(cls, name, _method_impl)
else:
setattr(_api.Workspace, name, _method_impl)
\ No newline at end of file
......@@ -7,6 +7,7 @@
#endif
#include "MantidKernel/Strings.h"
#include "MantidPythonInterface/kernel/SharedPtrToPythonMacro.h"
#include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h"
#include <Poco/Thread.h>
......@@ -19,6 +20,7 @@ using Mantid::Kernel::Property;
using Mantid::Kernel::Direction;
using Mantid::API::IAlgorithm;
using Mantid::API::IAlgorithm_sptr;
using Mantid::PythonInterface::Policies::VectorToNumpy;
using namespace boost::python;
namespace
......@@ -219,6 +221,12 @@ void export_ialgorithm()
.def("version", &IAlgorithm::version, "Returns the version number of the algorithm")
.def("category", &IAlgorithm::category, "Returns the category containing the algorithm")
.def("categories", &IAlgorithm::categories, "Returns the list of categories this algorithm belongs to")
.def("workspaceMethodName",&IAlgorithm::workspaceMethodName,
"Returns a name that will be used when attached as a workspace method. Empty string indicates do not attach")
.def("workspaceMethodOn", &IAlgorithm::workspaceMethodOn, return_value_policy<VectorToNumpy>(), // creates a list for strings
"Returns a set of class names that will have the method attached. Empty list indicates all types")
.def("workspaceMethodInputProperty", &IAlgorithm::workspaceMethodInputProperty,
"Returns the name of the input workspace property used by the calling object")
.def("getOptionalMessage", &IAlgorithm::getOptionalMessage, "Returns the optional user message attached to the algorithm")
.def("getWikiSummary", &IAlgorithm::getWikiSummary, "Returns the summary found on the wiki page")
.def("getWikiDescription", &IAlgorithm::getWikiDescription, "Returns the description found on the wiki page using wiki markup")
......
......@@ -192,7 +192,7 @@ def process_frame(frame):
#-------------------------------------------------------------------------------
def lhs_info(output_type='both'):
def lhs_info(output_type='both', frame=None):
"""Returns the number of arguments on the left of assignment along
with the names of the variables.
......@@ -213,18 +213,21 @@ def lhs_info(output_type='both'):
variable names
output_type = 'both' : A tuple containing both of
the above
frame A frame object that points to the frame containing a variable assignment.
Default = inspect.currentframe().f_back.f_back
Outputs:
=========
Depends on the value of the argument. See above.
"""
# Two frames back so that we get the callers' caller, i.e. this should only
# be called from within a function
try:
frame = inspect.currentframe().f_back.f_back
except AttributeError:
raise RuntimeError("lhs_info cannot be used on the command line, only within a function")
if not frame:
try:
# Two frames back so that we get the callers' caller, i.e. this should only
# be called from within a function
frame = inspect.currentframe().f_back.f_back
except AttributeError:
raise RuntimeError("lhs_info cannot be used on the command line, only within a function")
# Process the frame noting the advice here:
# http://docs.python.org/library/inspect.html#the-interpreter-stack
......
......@@ -24,6 +24,7 @@ import kernel as _kernel
from kernel import funcreturns as _funcreturns
from api import AnalysisDataService as _ads
from api import FrameworkManager as _framework
from api import _workspaceops