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 &copy; 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 &copy; 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 &copy; 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