From e9de7afdec4f8b1d3333ff1454dd09a7a7efe6f5 Mon Sep 17 00:00:00 2001 From: Owen Arnold <owen.arnold@stfc.ac.uk> Date: Thu, 17 Jan 2019 12:36:41 +0000 Subject: [PATCH] refs #24322. ComponentInfo iterator Also some improvements to detector info iterator. Note that I added an index property. I see this as being particularly useful for users to get away from the old instrument python API. i.e. for item : component_info if item.index == component_info.source: break --- .../Instrument/ComponentInfoItem.h | 3 +- .../Instrument/DetectorInfoItem.h | 2 + .../api/ComponentInfoPythonIterator.h | 57 +++++++++++++++++ .../PythonInterface/mantid/api/CMakeLists.txt | 1 + .../mantid/geometry/CMakeLists.txt | 2 + .../geometry/src/Exports/ComponentInfo.cpp | 10 +++ .../src/Exports/ComponentInfoItem.cpp | 50 +++++++++++++++ .../Exports/ComponentInfoPythonIterator.cpp | 41 ++++++++++++ .../geometry/src/Exports/DetectorInfoItem.cpp | 1 + .../mantid/geometry/ComponentInfoTest.py | 62 +++++++++++++++++++ .../mantid/geometry/DetectorInfoTest.py | 3 + 11 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h create mode 100644 Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp create mode 100644 Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h index ef59ad69c51..c3241376470 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoItem.h @@ -42,7 +42,8 @@ public: Kernel::V3D scaleFactor() const { return m_componentInfo->scaleFactor(m_index); } - const std::string &name() const { return m_componentInfo->name(m_index); } + std::string name() const { return m_componentInfo->name(m_index); } + size_t index() const { return m_index; } // Non-owning pointer. A reference makes the class unable to define an // assignment operator that we need. diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h index f288d1564d0..0719fd38344 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h @@ -57,6 +57,8 @@ public: size_t infoSize() const { return m_detectorInfo->size(); } + size_t index() const { return m_index; } + DetectorInfoItem(T &detectorInfo, const size_t index) : m_detectorInfo(&detectorInfo), m_index(index) {} diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h new file mode 100644 index 00000000000..204524f4642 --- /dev/null +++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/ComponentInfoPythonIterator.h @@ -0,0 +1,57 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_PYTHONINTERFACE_COMPONENTINFOPYTHONITERATOR_H_ +#define MANTID_PYTHONINTERFACE_COMPONENTINFOPYTHONITERATOR_H_ + +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/ComponentInfoItem.h" +#include "MantidGeometry/Instrument/ComponentInfoIterator.h" + +#include <boost/python/iterator.hpp> + +using Mantid::Geometry::ComponentInfo; +using Mantid::Geometry::ComponentInfoItem; +using Mantid::Geometry::ComponentInfoIterator; +using namespace boost::python; + +namespace Mantid { +namespace PythonInterface { + +/** ComponentInfoPythonIterator + +ComponentInfoPythonIterator is used to expose ComponentInfoIterator to the Python +side. +*/ + +class ComponentInfoPythonIterator { +public: + explicit ComponentInfoPythonIterator(ComponentInfo &detectorInfo) + : m_begin(detectorInfo.begin()), m_end(detectorInfo.end()), + m_firstOrDone(true) {} + + const ComponentInfoItem<ComponentInfo> &next() { + if (!m_firstOrDone) + ++m_begin; + else + m_firstOrDone = false; + if (m_begin == m_end) { + m_firstOrDone = true; + objects::stop_iteration_error(); + } + return *m_begin; + } + +private: + ComponentInfoIterator<ComponentInfo> m_begin; + ComponentInfoIterator<ComponentInfo> m_end; + bool m_firstOrDone; +}; + +} // namespace PythonInterface +} // namespace Mantid + +#endif /* MANTID_PYTHONINTERFACE_COMPONENTINFOPYTHONITERATOR_H_ */ diff --git a/Framework/PythonInterface/mantid/api/CMakeLists.txt b/Framework/PythonInterface/mantid/api/CMakeLists.txt index fb4911e15d1..b2f3dc15750 100644 --- a/Framework/PythonInterface/mantid/api/CMakeLists.txt +++ b/Framework/PythonInterface/mantid/api/CMakeLists.txt @@ -118,6 +118,7 @@ set ( INC_FILES ${HEADER_DIR}/api/WorkspacePropertyExporter.h ${HEADER_DIR}/api/SpectrumInfoPythonIterator.h ${HEADER_DIR}/api/DetectorInfoPythonIterator.h + ${HEADER_DIR}/api/ComponentInfoPythonIterator.h ) set ( PY_FILES diff --git a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt index b96ce5a042a..8a1e4f0422e 100644 --- a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt +++ b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt @@ -44,6 +44,8 @@ set ( EXPORT_FILES src/Exports/DetectorInfoIterator.cpp src/Exports/DetectorInfoPythonIterator.cpp src/Exports/ComponentInfo.cpp + src/Exports/ComponentInfoItem.cpp + src/Exports/ComponentInfoPythonIterator.cpp ) ############################################################################################# diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp index 47fbca2275f..9ecf5d49229 100644 --- a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp @@ -8,6 +8,7 @@ #include "MantidGeometry/Objects/IObject.h" #include "MantidKernel/Quat.h" #include "MantidKernel/V3D.h" +#include "MantidPythonInterface/api/ComponentInfoPythonIterator.h" #include "MantidPythonInterface/core/Converters/WrapWithNDArray.h" #include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" @@ -19,10 +20,17 @@ using Mantid::Geometry::ComponentInfo; using Mantid::Kernel::Quat; using Mantid::Kernel::V3D; +using Mantid::PythonInterface::ComponentInfoPythonIterator; using namespace Mantid::PythonInterface::Converters; using namespace Mantid::PythonInterface::Policies; using namespace boost::python; +namespace { +ComponentInfoPythonIterator make_pyiterator(ComponentInfo &componentInfo) { + return ComponentInfoPythonIterator(componentInfo); +} +} // namespace + // Function pointers to help resolve ambiguity Mantid::Kernel::V3D (ComponentInfo::*position)(const size_t) const = &ComponentInfo::position; @@ -40,6 +48,8 @@ void (ComponentInfo::*setRotation)(const size_t, const Mantid::Kernel::Quat &) = void export_ComponentInfo() { class_<ComponentInfo, boost::noncopyable>("ComponentInfo", no_init) + .def("__iter__", make_pyiterator) + .def("__len__", &ComponentInfo::size, arg("self"), "Returns the number of components.") diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp new file mode 100644 index 00000000000..7e9feba6251 --- /dev/null +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoItem.cpp @@ -0,0 +1,50 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidGeometry/Instrument/ComponentInfoItem.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidKernel/Quat.h" +#include "MantidKernel/V3D.h" +#include "MantidPythonInterface/core/Converters/WrapWithNDArray.h" +#include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" +#include <boost/python/class.hpp> +#include <boost/python/make_function.hpp> +#include <boost/python/module.hpp> + +using Mantid::Geometry::ComponentInfo; +using Mantid::Geometry::ComponentInfoItem; +using Mantid::Kernel::V3D; +using namespace boost::python; +using namespace Mantid::PythonInterface::Converters; +using namespace Mantid::PythonInterface::Policies; + +// Export DetectorInfoItem +void export_ComponentInfoItem() { + + // Export to Python + class_<ComponentInfoItem<ComponentInfo>>("ComponentInfoItem", no_init) + .add_property("isDetector", &ComponentInfoItem<ComponentInfo>::isDetector) + .add_property( + "componentsInSubtree", + make_function(&ComponentInfoItem<ComponentInfo>::componentsInSubtree, + return_value_policy<VectorToNumpy>())) + .add_property( + "detectorsInSubtree", + make_function(&ComponentInfoItem<ComponentInfo>::detectorsInSubtree, + return_value_policy<VectorToNumpy>())) + .add_property("position", &ComponentInfoItem<ComponentInfo>::position) + .add_property("rotation", &ComponentInfoItem<ComponentInfo>::rotation) + .add_property("parent", &ComponentInfoItem<ComponentInfo>::parent) + .add_property("hasParent", &ComponentInfoItem<ComponentInfo>::hasParent) + .add_property("scaleFactor", + &ComponentInfoItem<ComponentInfo>::scaleFactor) + .add_property("name", &ComponentInfoItem<ComponentInfo>::name) + .add_property( + "children", + make_function(&ComponentInfoItem<ComponentInfo>::children, + return_value_policy<VectorRefToNumpy<WrapReadOnly>>())) + .add_property("index", &ComponentInfoItem<ComponentInfo>::index); +} diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp new file mode 100644 index 00000000000..83fdd03e797 --- /dev/null +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfoPythonIterator.cpp @@ -0,0 +1,41 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidPythonInterface/api/ComponentInfoPythonIterator.h" + +#include <boost/python/class.hpp> +#include <boost/python/copy_const_reference.hpp> +#include <boost/python/iterator.hpp> +#include <boost/python/module.hpp> + +using Mantid::PythonInterface::ComponentInfoPythonIterator; +using namespace boost::python; + +// Export ComponentInfoPythonIterator +void export_ComponentInfoPythonIterator() { + + // Export to Python + class_<ComponentInfoPythonIterator>("ComponentInfoPythonIterator", no_init) + .def("__iter__", objects::identity_function()) + .def( +#if PY_VERSION_HEX >= 0x03000000 + "__next__" +#else + "next" +#endif + , + &ComponentInfoPythonIterator::next, + return_value_policy<copy_const_reference>()); + /* + Return value policy for next is to copy the const reference. Copy by value is + essential for python 2.0 compatibility because items (DetectorInfoItem) will + outlive their iterators if declared as part of for loops. There is no good + way to deal with this other than to force a copy so that internals of the + item are not also corrupted. Future developers may wish to choose a separte + policy for python 3.0 where this is not a concern, and const ref returns + would be faster. + */ +} diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp index 3149768436e..e9b2a2d3461 100644 --- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp @@ -28,6 +28,7 @@ void export_DetectorInfoItem() { .add_property("position", &DetectorInfoItem<DetectorInfo>::position) .add_property("rotation", &DetectorInfoItem<DetectorInfo>::rotation) .add_property("l2", &DetectorInfoItem<DetectorInfo>::l2) + .add_property("index", &DetectorInfoItem<DetectorInfo>::index) .def("setMasked", &DetectorInfoItem<DetectorInfo>::setMasked, (arg("self"), arg("masked")), "Set the mask flag for the detector"); } diff --git a/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py b/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py index f122b1a3a35..6ac48ff2ca9 100644 --- a/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py +++ b/Framework/PythonInterface/test/python/mantid/geometry/ComponentInfoTest.py @@ -14,6 +14,7 @@ from mantid.kernel import V3D from mantid.kernel import Quat from mantid.geometry import CSGObject from mantid.simpleapi import * +from itertools import islice class ComponentInfoTest(unittest.TestCase): @@ -483,5 +484,66 @@ class ComponentInfoTest(unittest.TestCase): with self.assertRaises(TypeError): info.shape(11.32) + def test_basic_iterator(self): + info = self._ws.componentInfo() + expected_iterations = len(info) + actual_iterations = len(list(iter(info))) + self.assertEquals(expected_iterations, actual_iterations) + it = iter(info) + self.assertEquals(next(it).index, 0) + self.assertEquals(next(it).index, 1) + + def test_isDetector_via_iterator(self): + comp_info = self._ws.componentInfo() + n_detectors = len(self._ws.detectorInfo()) + it = iter(comp_info) + self.assertEquals(next(it).isDetector, True) + self.assertEquals(next(it).isDetector, True) + self.assertEquals(next(it).isDetector, False) + self.assertEquals(next(it).isDetector, False) + + def test_position_via_iterator(self): + comp_info = self._ws.componentInfo() + source_pos = comp_info.sourcePosition() + it = iter(comp_info) + # basic check on first detector position + self.assertTrue(next(it).position.distance(source_pos) > 0) + + def test_children_via_iterator(self): + info = self._ws.componentInfo() + it = iter(info) + first_det = next(it) + self.assertEquals(type(first_det.children), np.ndarray) + self.assertEquals(len(first_det.children), 0) + root = next(it) + for root in it: + continue + self.assertEquals(root.index, info.root()) # sanity check + self.assertTrue(np.array_equal(root.children, np.array([0,1,2,3,4], dtype='uint64'))) + + def test_detectorsInSubtree_via_iterator(self): + info = self._ws.componentInfo() + it = iter(info) + first_det = next(it) + self.assertEquals(type(first_det.detectorsInSubtree), np.ndarray) + # For detectors, only contain own index + self.assertTrue(np.array_equal(first_det.detectorsInSubtree,np.array([0], dtype='uint64'))) + root = next(it) + for root in it: + continue + self.assertTrue(np.array_equal(root.detectorsInSubtree, np.array([0,1], dtype='uint64'))) + + def test_componentsInSubtree_via_iterator(self): + info = self._ws.componentInfo() + it = iter(info) + first_det = next(it) + self.assertEquals(type(first_det.detectorsInSubtree), np.ndarray) + # For detectors, only contain own index + self.assertTrue(np.array_equal(first_det.componentsInSubtree,np.array([0], dtype='uint64'))) + root = next(it) + for root in it: + continue + # All component indices expected including self + self.assertTrue(np.array_equal(root.componentsInSubtree, np.array([0,1,2,3,4,5], dtype='uint64'))) if __name__ == '__main__': unittest.main() diff --git a/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py b/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py index 8006396e1bc..2419c714d0e 100644 --- a/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py +++ b/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py @@ -133,6 +133,9 @@ class DetectorInfoTest(unittest.TestCase): expected_iterations = len(info) actual_iterations = len(list(iter(info))) self.assertEquals(expected_iterations, actual_iterations) + it = iter(info) + self.assertEquals(next(it).index, 0) + self.assertEquals(next(it).index, 1) def test_iterator_for_monitors(self): info = self._ws.detectorInfo() -- GitLab