diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt index 78c1b6bb98747d4cfc6e3da3e3d1a4ad770ab00c..11b0abf40fc2d535e6639b27d138c0805f6c7831 100644 --- a/Framework/Kernel/CMakeLists.txt +++ b/Framework/Kernel/CMakeLists.txt @@ -166,7 +166,7 @@ set ( INC_FILES inc/MantidKernel/ConfigService.h inc/MantidKernel/ConfigObserver.h inc/MantidKernel/ConfigPropertyObserver.h - inc/MantidKernel/DateAndTime.h + inc/MantidKernel/DateAndTime.h inc/MantidKernel/DataItem.h inc/MantidKernel/DataService.h inc/MantidKernel/DateAndTimeHelpers.h @@ -246,7 +246,7 @@ set ( INC_FILES inc/MantidKernel/NetworkProxy.h inc/MantidKernel/NeutronAtom.h inc/MantidKernel/NexusDescriptor.h - inc/MantidKernel/normal_distribution.h + inc/MantidKernel/normal_distribution.h inc/MantidKernel/NullValidator.h inc/MantidKernel/OptionalBool.h inc/MantidKernel/ParaViewVersion.h diff --git a/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h b/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h index 1f39d3d73a937a23b28dc59a6091d6c7f4b5022e..3b1e4701ef5abc19f748b28b7d297588d449bc4d 100644 --- a/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h +++ b/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h @@ -120,6 +120,7 @@ class DLLExport TimeSeriesProperty : public Property, public: /// Constructor explicit TimeSeriesProperty(const std::string &name); + /// Virtual destructor ~TimeSeriesProperty() override; /// "Virtual" copy constructor diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/ContainerDtype.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/ContainerDtype.h new file mode 100644 index 0000000000000000000000000000000000000000..ba5428819b01be68143c591c6cb625844f89cbd9 --- /dev/null +++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/ContainerDtype.h @@ -0,0 +1,76 @@ +#ifndef MANTID_PYTHONINTERFACE_CONVERTERS_CONTAINERDTYPE_H_ +#define MANTID_PYTHONINTERFACE_CONVERTERS_CONTAINERDTYPE_H_ + +#include <string> +#include <type_traits> + +/** + ContainerDtype Header File + + A helper free function to allow identification of data type being used by + providing a numpy friendly string. + + @author Lamar Moore STFC, Bhuvan Bezawada STFC + @date 21/06/2018 + + Copyright © 2018 ISIS Rutherford Appleton Laboratory, NScD Oak + Ridge National Laboratory & European Spallation Source + + 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> +*/ + +namespace Mantid { +namespace PythonInterface { +namespace Converters { + +// Free function to determine data type being stored in container +template <template <class> class Container, typename HeldType> +std::string dtype(const Container<HeldType> &) { + if (std::is_same<HeldType, bool>::value) { + return "bool_"; + } else if (std::is_same<HeldType, short>::value) { + return "int16"; + } else if (std::is_same<HeldType, std::int8_t>::value) { + return "int8"; + } else if (std::is_same<HeldType, std::int16_t>::value) { + return "int16"; + } else if (std::is_same<HeldType, std::int32_t>::value) { + return "int32"; + } else if (std::is_same<HeldType, std::int64_t>::value) { + return "int64"; + } else if (std::is_same<HeldType, long>::value) { + return "int_"; + } else if (std::is_same<HeldType, long long>::value) { + return "int64"; + } else if (std::is_same<HeldType, float>::value) { + return "float32"; + } else if (std::is_same<HeldType, double>::value) { + return "float64"; + } else if (std::is_same<HeldType, std::string>::value) { + return "string_"; + } else { + return "object_"; + } +} + +} // namespace Converters +} // namespace PythonInterface +} // namespace Mantid + +#endif /*MANTID_PYTHONINTERFACE_CONVERTERS_CONTAINERDTYPE_H_*/ diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/PropertyWithValueExporter.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/PropertyWithValueExporter.h index 8cc6f84d4fe6bc9e86d1c5d6ccca2c3579159c7f..7e29893342e1a550c80742ba21f8d25b81fd5d06 100644 --- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/PropertyWithValueExporter.h +++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/PropertyWithValueExporter.h @@ -25,6 +25,7 @@ */ #include "MantidKernel/PropertyWithValue.h" +#include "MantidPythonInterface/kernel/Converters/ContainerDtype.h" #ifndef Q_MOC_RUN #include <boost/python/class.hpp> @@ -33,8 +34,15 @@ #include <boost/python/return_by_value.hpp> #endif +// Call the dtype helper function +template <typename HeldType> +std::string dtype(Mantid::Kernel::PropertyWithValue<HeldType> &self) { + return Mantid::PythonInterface::Converters::dtype(self); +} + namespace Mantid { namespace PythonInterface { + /** * A helper struct to export PropertyWithValue<> types to Python. */ @@ -49,7 +57,8 @@ struct PropertyWithValueExporter { pythonClassName, no_init) .add_property("value", make_function(&PropertyWithValue<HeldType>::operator(), - return_value_policy<ValueReturnPolicy>())); + return_value_policy<ValueReturnPolicy>())) + .def("dtype", &dtype<HeldType>, arg("self")); } }; } diff --git a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt index 4fa38967a53bded8bff9ee46f72f3e23812af5a7..b8ce08e9069086e3ec66795771b1c7875e46226e 100644 --- a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt +++ b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt @@ -95,6 +95,7 @@ set ( INC_FILES ${HEADER_DIR}/kernel/NdArray.h ${HEADER_DIR}/kernel/Converters/CArrayToNDArray.h ${HEADER_DIR}/kernel/Converters/CloneToNumpy.h + ${HEADER_DIR}/kernel/Converters/ContainerDtype.h ${HEADER_DIR}/kernel/Converters/DateAndTime.h ${HEADER_DIR}/kernel/Converters/MapToPyDictionary.h ${HEADER_DIR}/kernel/Converters/MatrixToNDArray.h diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/ArrayProperty.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/ArrayProperty.cpp index f6610cc09ee0ddfe61537f250d4e891d5f218cc1..32166550d26414b20b70aee9a49f0ce38511b51b 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Exports/ArrayProperty.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/ArrayProperty.cpp @@ -1,22 +1,23 @@ #include "MantidKernel/ArrayProperty.h" #include "MantidKernel/NullValidator.h" -#include "MantidPythonInterface/kernel/NdArray.h" -#include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" #include "MantidPythonInterface/kernel/Converters/NDArrayToVector.h" #include "MantidPythonInterface/kernel/Converters/PySequenceToVector.h" +#include "MantidPythonInterface/kernel/Converters/ContainerDtype.h" +#include "MantidPythonInterface/kernel/NdArray.h" +#include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" #include <boost/python/class.hpp> #include <boost/python/list.hpp> -#include <boost/python/make_constructor.hpp> #include <boost/python/default_call_policies.hpp> +#include <boost/python/make_constructor.hpp> using Mantid::Kernel::ArrayProperty; -using Mantid::Kernel::PropertyWithValue; using Mantid::Kernel::Direction; using Mantid::Kernel::IValidator_sptr; using Mantid::Kernel::NullValidator; +using Mantid::Kernel::PropertyWithValue; namespace Converters = Mantid::PythonInterface::Converters; namespace NumPy = Mantid::PythonInterface::NumPy; namespace Policies = Mantid::PythonInterface::Policies; @@ -27,6 +28,11 @@ namespace { using return_cloned_numpy = return_value_policy<Policies::VectorRefToNumpy<Converters::Clone>>; +// Call the dtype helper function +template <typename type> std::string dtype(ArrayProperty<type> &self) { + return Converters::dtype(self); +} + #define EXPORT_ARRAY_PROP(type, prefix) \ class_<ArrayProperty<type>, bases<PropertyWithValue<std::vector<type>>>, \ boost::noncopyable>(#prefix "ArrayProperty", no_init) \ @@ -58,6 +64,7 @@ using return_cloned_numpy = (arg("name"), arg("values"), \ arg("validator") = IValidator_sptr(new NullValidator), \ arg("direction") = Direction::Input))) \ + .def("dtype", &dtype<type>, arg("self")) \ .add_property("value", make_function(&ArrayProperty<type>::operator(), \ return_cloned_numpy())); @@ -79,14 +86,14 @@ ArrayProperty<T> *createArrayPropertyFromList(const std::string &name, } /** - * Factory function to allow the initial values to be specified as a numpy + * Factory function to allow the initial values to be specified as a numpy * array - * @param name :: The name of the property - * @param vec :: A boost python array of initial values - * @param validator :: A validator object - * @param direction :: A direction - * @return - */ + * @param name :: The name of the property + * @param vec :: A boost python array of initial values + * @param validator :: A validator object + * @param direction :: A direction + * @return + */ template <typename T> ArrayProperty<T> *createArrayPropertyFromNDArray(const std::string &name, const NumPy::NdArray &values, @@ -95,7 +102,7 @@ ArrayProperty<T> *createArrayPropertyFromNDArray(const std::string &name, return new ArrayProperty<T>(name, Converters::NDArrayToVector<T>(values)(), validator, direction); } -} +} // namespace void export_ArrayProperty() { // Match the python names to their C types @@ -107,4 +114,4 @@ void export_ArrayProperty() { // Python can be seen also. Users shouldn't need this EXPORT_ARRAY_PROP(int, CInt); EXPORT_ARRAY_PROP(size_t, UnsignedInt); -} +} \ No newline at end of file diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp index a6f2f38ccae46736f58d80192fae1a0311d828a3..fc132c5a1705ca9fcc9406eb46f280e28c405e4a 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/TimeSeriesProperty.cpp @@ -1,5 +1,6 @@ #include "MantidKernel/TimeSeriesProperty.h" #include "MantidPythonInterface/kernel/Converters/DateAndTime.h" +#include "MantidPythonInterface/kernel/Converters/ContainerDtype.h" #include "MantidPythonInterface/kernel/GetPointer.h" #include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" @@ -7,12 +8,12 @@ #include <boost/python/implicit.hpp> #include <boost/python/init.hpp> #include <boost/python/make_function.hpp> -#include <boost/python/return_value_policy.hpp> #include <boost/python/register_ptr_to_python.hpp> +#include <boost/python/return_value_policy.hpp> -using Mantid::Types::Core::DateAndTime; -using Mantid::Kernel::TimeSeriesProperty; using Mantid::Kernel::Property; +using Mantid::Kernel::TimeSeriesProperty; +using Mantid::Types::Core::DateAndTime; using namespace boost::python; using boost::python::arg; @@ -35,6 +36,11 @@ void addPyTimeValue(TimeSeriesProperty<TYPE> &self, self.addValue(*dateandtime, value); } +// Call the dtype helper function +template <typename TYPE> std::string dtype(TimeSeriesProperty<TYPE> &self) { + return Mantid::PythonInterface::Converters::dtype(self); +} + // Macro to reduce copy-and-paste #define EXPORT_TIMESERIES_PROP(TYPE, Prefix) \ register_ptr_to_python<TimeSeriesProperty<TYPE> *>(); \ @@ -80,8 +86,10 @@ void addPyTimeValue(TimeSeriesProperty<TYPE> &self, arg("self"), \ "returns :class:`mantid.kernel.TimeSeriesPropertyStatistics`") \ .def("timeAverageValue", &TimeSeriesProperty<TYPE>::timeAverageValue, \ - arg("self")); -} + arg("self")) \ + .def("dtype", &dtype<TYPE>, arg("self")); + +} // namespace void export_TimeSeriesProperty_Double() { EXPORT_TIMESERIES_PROP(double, Float); diff --git a/Framework/PythonInterface/test/python/mantid/kernel/ArrayPropertyTest.py b/Framework/PythonInterface/test/python/mantid/kernel/ArrayPropertyTest.py index 8a5d09ec6512e8920c0133f4004c3bbf91cb78f9..db5f08d0f2783b2a600c8b6c576f9a78edc110f4 100644 --- a/Framework/PythonInterface/test/python/mantid/kernel/ArrayPropertyTest.py +++ b/Framework/PythonInterface/test/python/mantid/kernel/ArrayPropertyTest.py @@ -129,10 +129,86 @@ class ArrayPropertyTest(unittest.TestCase): FloatArrayProperty("Input", Direction.Input), "Float array") def PyExec(self): self._input_values = self.getProperty("Input").value - + input_values = range(1, 5) self._do_algorithm_test(AlgWithFloatArrayProperty, input_values) + def test_dtype_function_calls(self): + """ + Tests the dtype() function call for the data types stored in the array. + """ + # Set up + direc = Direction.Output + validator = NullValidator() + + # Create float array + float_input_values = [1.1, 2.5, 5.6, 4.6, 9.0, 6.0] + float_arr = FloatArrayProperty("floats", float_input_values, validator, direc) + + # Create int array + int_input_values = [1, 2, 5, 4, 9, 6] + int_arr = IntArrayProperty("integers", int_input_values, validator, direc) + + # Create string array + str_input_values = ["a", "b", "c", "d", "e"] + str_arr = StringArrayProperty("letters", str_input_values, validator, direc) + + # Test + self.assertEquals(float_arr.dtype(), "float64") + self.assertEquals(str_arr.dtype(), "string_") + + # Implementation of IntArrayProperty is based on long + # (which is itself implementation defined, so a special case is needed here) + if sys.platform == 'win32' or sys.platform == 'darwin': + # Windows and macOS should return "int_" + self.assertEquals(int_arr.dtype(), "int_") + else: + # Linux based systems should return "int64" + self.assertEquals(int_arr.dtype(), "int64") + + def test_construct_numpy_array_with_given_dtype_float(self): + # Set up + direc = Direction.Output + validator = NullValidator() + + # Create float array + float_input_values = [1.1, 2.5, 5.6, 4.6, 9.0, 6.0] + float_arr = FloatArrayProperty("floats", float_input_values, validator, direc) + + # Use the returned dtype() to check it works with numpy arrays + x = np.arange(1, 10, dtype=float_arr.dtype()) + self.assertIsInstance(x, np.ndarray) + self.assertEquals(x.dtype, float_arr.dtype()) + + def test_construct_numpy_array_with_given_dtype_int(self): + # Set up + direc = Direction.Output + validator = NullValidator() + + # Create int array + int_input_values = [1, 2, 5, 4, 9, 6] + int_arr = IntArrayProperty("integers", int_input_values, validator, direc) + + # Use the returned dtype() to check it works with numpy arrays + x = np.arange(1, 10, dtype=int_arr.dtype()) + self.assertIsInstance(x, np.ndarray) + self.assertEquals(x.dtype, int_arr.dtype()) + + def test_construct_numpy_array_with_given_dtype_string(self): + # Set up + direc = Direction.Output + validator = NullValidator() + + # Create string array + str_input_values = ["hello", "testing", "word", "another word", "word"] + str_arr = StringArrayProperty("letters", str_input_values, validator, direc) + + # Use the returned dtype() to check it works with numpy arrays + x = np.array(str_input_values, dtype=str_arr.dtype()) + self.assertIsInstance(x, np.ndarray) + # Expect longest string to be returned + self.assertEquals(x.dtype, "S12") + def test_PythonAlgorithm_setProperty_With_Ranges_String(self): """ Test ArrayProperty within a python algorithm can diff --git a/Framework/PythonInterface/test/python/mantid/kernel/PropertyWithValueTest.py b/Framework/PythonInterface/test/python/mantid/kernel/PropertyWithValueTest.py index 773ec7871a59f5bf98d4de753c5b28297295ff42..56908e8161f3b6e592b3ba4dd07e1a8e2c46d88a 100644 --- a/Framework/PythonInterface/test/python/mantid/kernel/PropertyWithValueTest.py +++ b/Framework/PythonInterface/test/python/mantid/kernel/PropertyWithValueTest.py @@ -127,6 +127,9 @@ class PropertyWithValueTest(unittest.TestCase): self.assertEquals(result.startswith("1,2,3,"), True) self.assertEquals(result.endswith("98,99"), True) + # Check the dtype return value + self.assertEquals(det_list_prop.dtype(), "int32") + def _do_vector_double_numpy_test(self, int_type=False): create_ws = AlgorithmManager.createUnmanaged('CreateWorkspace') create_ws.initialize() diff --git a/Framework/PythonInterface/test/python/mantid/kernel/TimeSeriesPropertyTest.py b/Framework/PythonInterface/test/python/mantid/kernel/TimeSeriesPropertyTest.py index 9e9c2410e43c21c262f3ef982b72a4470b5cca10..d162fc3f5b44c8d0ab9ef1ce8b3fbb03183d38a5 100644 --- a/Framework/PythonInterface/test/python/mantid/kernel/TimeSeriesPropertyTest.py +++ b/Framework/PythonInterface/test/python/mantid/kernel/TimeSeriesPropertyTest.py @@ -60,23 +60,31 @@ class TimeSeriesPropertyTest(unittest.TestCase): self._check_has_time_series_attributes(log_series) self.assertEquals(log_series.size(), self._ntemp) self.assertAlmostEqual(log_series.nthValue(0), -0.00161) + # Check the dtype return value + self.assertEquals(log_series.dtype(), "float64") def test_time_series_int_can_be_extracted(self): log_series = self._test_ws.getRun()["raw_frames"] self._check_has_time_series_attributes(log_series) self.assertEquals(log_series.size(), self._nframes) self.assertEquals(log_series.nthValue(1), 1436) + # Check the dtype return value + self.assertEquals(log_series.dtype(), "int64") def test_time_series_string_can_be_extracted(self): log_series = self._test_ws.getRun()["icp_event"] self._check_has_time_series_attributes(log_series, list) self.assertEquals(log_series.size(), 4) self.assertEquals(log_series.nthValue(0).strip(), 'CHANGE_PERIOD 1') + # Check the dtype return value + self.assertEquals(log_series.dtype(), "string_") def test_time_series_bool_can_be_extracted(self): log_series = self._test_ws.getRun()["period 1"] self._check_has_time_series_attributes(log_series) self.assertEquals(log_series.size(), 1) + # Check the dtype return value + self.assertEquals(log_series.dtype(), "bool_") def _check_has_time_series_attributes(self, log, values_type=np.ndarray): self.assertTrue(hasattr(log, "value")) @@ -87,6 +95,5 @@ class TimeSeriesPropertyTest(unittest.TestCase): self.assertTrue(isinstance(values, values_type)) self.assertEquals(log.size(), len(values)) - if __name__ == '__main__': unittest.main()