diff --git a/Code/Mantid/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp b/Code/Mantid/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp index 5cb9242db99b8440f922a0dd0b4521123dde4d60..f6abb808c98f4ab955e237d2471375fca2df129a 100644 --- a/Code/Mantid/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp +++ b/Code/Mantid/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp @@ -12,6 +12,7 @@ #include <boost/python/overloads.hpp> #include <boost/python/copy_const_reference.hpp> #include <boost/python/implicit.hpp> +#include <boost/python/numeric.hpp> using namespace Mantid::API; using Mantid::Geometry::IDetector_sptr; @@ -25,6 +26,7 @@ namespace { /// Typedef for data access, i.e. dataX,Y,E members typedef Mantid::MantidVec&(MatrixWorkspace::*data_modifier)(const std::size_t); + /// return_value_policy for read-only numpy array typedef return_value_policy<Policies::VectorRefToNumpy<Converters::WrapReadOnly> > return_readonly_numpy; /// return_value_policy for read-write numpy array @@ -34,6 +36,72 @@ namespace // Overloads for binIndexOf function which has 1 optional argument BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(MatrixWorkspace_binIndexOfOverloads, MatrixWorkspace::binIndexOf, 1, 2) + + /** + * Set the values from an python array-style object into the given spectrum in the workspace + * @param self :: A reference to the calling object + * @param accessor :: A member-function pointer to the data{X,Y,E} member that will extract the writable values. + * @param wsIndex :: The workspace index for the spectrum to set + * @param values :: A numpy array. The length must match the size of the + */ + void setSpectrumFromPyObject(MatrixWorkspace & self, data_modifier accessor, + const size_t wsIndex, numeric::array values) + { + boost::python::tuple shape(values.attr("shape")); + if( boost::python::len(shape) != 1 ) + { + throw std::invalid_argument("Invalid shape for setting 1D spectrum array, array is " + + boost::lexical_cast<std::string>(boost::python::len(shape)) + "D"); + } + const size_t pyArrayLength = boost::python::extract<size_t>(shape[0]); + Mantid::MantidVec & wsArrayRef = (self.*accessor)(wsIndex); + const size_t wsArrayLength = wsArrayRef.size(); + + if(pyArrayLength != wsArrayLength) + { + throw std::invalid_argument("Length mismatch between workspace array & python array. ws=" + + boost::lexical_cast<std::string>(wsArrayLength) + ", python=" + boost::lexical_cast<std::string>(pyArrayLength)); + } + for(size_t i = 0; i < wsArrayLength; ++i) + { + wsArrayRef[i] = extract<double>(values[i]); + } + } + + + /** + * Set the X values from an python array-style object + * @param self :: A reference to the calling object + * @param wsIndex :: The workspace index for the spectrum to set + * @param values :: A numpy array. The length must match the size of the + */ + void setXFromPyObject(MatrixWorkspace & self, const size_t wsIndex, numeric::array values) + { + setSpectrumFromPyObject(self, &MatrixWorkspace::dataX, wsIndex, values); + } + + /** + * Set the Y values from an python array-style object + * @param self :: A reference to the calling object + * @param wsIndex :: The workspace index for the spectrum to set + * @param values :: A numpy array. The length must match the size of the + */ + void setYFromPyObject(MatrixWorkspace & self, const size_t wsIndex, numeric::array values) + { + setSpectrumFromPyObject(self, &MatrixWorkspace::dataY, wsIndex, values); + } + + /** + * Set the E values from an python array-style object + * @param self :: A reference to the calling object + * @param wsIndex :: The workspace index for the spectrum to set + * @param values :: A numpy array. The length must match the size of the + */ + void setEFromPyObject(MatrixWorkspace & self, const size_t wsIndex, numeric::array values) + { + setSpectrumFromPyObject(self, &MatrixWorkspace::dataE, wsIndex, values); + } + } void export_MatrixWorkspace() @@ -61,13 +129,15 @@ void export_MatrixWorkspace() return_value_policy<copy_const_reference>(), "Returns the status of the distribution flag") .def("YUnit", &MatrixWorkspace::YUnit, "Returns the current Y unit for the data (Y axis) in the workspace") .def("YUnitLabel", &MatrixWorkspace::YUnitLabel, "Returns the caption for the Y axis") + //--------------------------------------- Setters ------------------------------------------------------------------------------- .def("setYUnitLabel", &MatrixWorkspace::setYUnitLabel, "Sets a new caption for the data (Y axis) in the workspace") .def("setYUnit", &MatrixWorkspace::setYUnit, "Sets a new unit for the data (Y axis) in the workspace") .def("setDistribution", (bool& (MatrixWorkspace::*)(const bool))&MatrixWorkspace::isDistribution, return_value_policy<return_by_value>(), "Set distribution flag. If True the workspace has been divided by the bin-width.") .def("replaceAxis", &MatrixWorkspace::replaceAxis) - //--------------------------------------- Data access --------------------------------------------------------------------------- + + //--------------------------------------- Read spectrum data --------------------------------------------------------------------------- .def("readX", &MatrixWorkspace::readX, return_readonly_numpy(), "Creates a read-only numpy wrapper around the original X data at the given index") .def("readY", &MatrixWorkspace::readY, return_readonly_numpy(), @@ -76,6 +146,8 @@ void export_MatrixWorkspace() "Creates a read-only numpy wrapper around the original E data at the given index") .def("readDx", &MatrixWorkspace::readDx, return_readonly_numpy(), "Creates a read-only numpy wrapper around the original Dx data at the given index") + + //--------------------------------------- Write spectrum data --------------------------------------------------------------------------- .def("dataX", (data_modifier)&MatrixWorkspace::dataX, return_readwrite_numpy(), "Creates a writable numpy wrapper around the original X data at the given index") .def("dataY", (data_modifier)&MatrixWorkspace::dataY, return_readwrite_numpy(), @@ -84,6 +156,11 @@ void export_MatrixWorkspace() "Creates a writable numpy wrapper around the original E data at the given index") .def("dataDx", (data_modifier)&MatrixWorkspace::dataDx, return_readwrite_numpy(), "Creates a writable numpy wrapper around the original Dx data at the given index") + .def("setX", &setXFromPyObject, "Set X values from a python list or numpy array. It performs a simple copy into the array.") + .def("setY", &setYFromPyObject, "Set Y values from a python list or numpy array. It performs a simple copy into the array.") + .def("setE", &setEFromPyObject, "Set E values from a python list or numpy array. It performs a simple copy into the array.") + + // --------------------------------------- Extract data --------------------------------------------------------------------------------- .def("extractX", Mantid::PythonInterface::cloneX, "Extracts (copies) the X data from the workspace into a 2D numpy array. " "Note: This can fail for large workspaces as numpy will require a block " diff --git a/Code/Mantid/Framework/PythonInterface/test/python/MatrixWorkspaceTest.py b/Code/Mantid/Framework/PythonInterface/test/python/MatrixWorkspaceTest.py index 5345713eabd63959a1b7f5896ad2e7a25b59d22b..a76c69a3da7354056385425f60c8524a447a0f84 100644 --- a/Code/Mantid/Framework/PythonInterface/test/python/MatrixWorkspaceTest.py +++ b/Code/Mantid/Framework/PythonInterface/test/python/MatrixWorkspaceTest.py @@ -3,7 +3,7 @@ import sys import math from testhelpers import run_algorithm, can_be_instantiated from mantid.api import (MatrixWorkspace, WorkspaceProperty, Workspace, - ExperimentInfo, AnalysisDataService) + ExperimentInfo, AnalysisDataService, WorkspaceFactory) from mantid.geometry import Detector from mantid.kernel import V3D @@ -106,6 +106,61 @@ class MatrixWorkspaceTest(unittest.TestCase): for attr in [x,y,e,dx]: do_numpy_test(attr) + def test_setting_spectra_from_array_of_incorrect_length_raises_error(self): + nvectors = 2 + xlength = 11 + ylength = 10 + test_ws = WorkspaceFactory.create("Workspace2D", nvectors, xlength, ylength) + + values = np.arange(xlength + 1) + self.assertRaises(ValueError, test_ws.setX, 0, values) + self.assertRaises(ValueError, test_ws.setY, 0, values) + self.assertRaises(ValueError, test_ws.setE, 0, values) + + def test_setting_spectra_from_array_of_incorrect_shape_raises_error(self): + nvectors = 2 + xlength = 11 + ylength = 10 + test_ws = WorkspaceFactory.create("Workspace2D", nvectors, xlength, ylength) + + values = np.linspace(0,1,num=xlength-1) + values = values.reshape(5,2) + self.assertRaises(ValueError, test_ws.setX, 0, values) + self.assertRaises(ValueError, test_ws.setY, 0, values) + self.assertRaises(ValueError, test_ws.setE, 0, values) + + def test_setting_spectra_from_array_using_incorrect_index_raises_error(self): + nvectors = 2 + xlength = 11 + ylength = 10 + + test_ws = WorkspaceFactory.create("Workspace2D", nvectors, xlength, ylength) + xvalues = np.arange(xlength) + self.assertRaises(RuntimeError, test_ws.setX, 3, xvalues) + + def test_setting_spectra_from_array_sets_expected_values(self): + nvectors = 2 + xlength = 11 + ylength = 10 + + test_ws = WorkspaceFactory.create("Workspace2D", nvectors, xlength, ylength) + ws_index = 1 + + values = np.linspace(0,1,xlength) + test_ws.setX(ws_index, values) + ws_values = test_ws.readX(ws_index) + self.assertTrue(np.array_equal(values, ws_values)) + + values = np.ones(ylength) + test_ws.setY(ws_index, values) + ws_values = test_ws.readY(ws_index) + self.assertTrue(np.array_equal(values, ws_values)) + + values = np.sqrt(values) + test_ws.setE(ws_index, values) + ws_values = test_ws.readE(ws_index) + self.assertTrue(np.array_equal(values, ws_values)) + def test_data_can_be_extracted_to_numpy_successfully(self): x = self._test_ws.extractX() y = self._test_ws.extractY()