From 43e9bbfd05c65df2308c8db47937414d8921165f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Thu, 17 Feb 2022 14:23:10 +0100
Subject: [PATCH] Add min, max and sum for ItemArray

- These function returns the min, max or the sum of all values (for
each array of each item)
- These values are computed in parallel: ghost item values are not
taken into account
---
 src/mesh/ItemArrayUtils.hpp   | 296 ++++++++++++++++++++++++++++++++++
 tests/test_ItemArrayUtils.cpp | 270 +++++++++++++++++++++++++++++++
 2 files changed, 566 insertions(+)

diff --git a/src/mesh/ItemArrayUtils.hpp b/src/mesh/ItemArrayUtils.hpp
index 6025bd0f3..ca339de9a 100644
--- a/src/mesh/ItemArrayUtils.hpp
+++ b/src/mesh/ItemArrayUtils.hpp
@@ -10,6 +10,302 @@
 
 #include <iostream>
 
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+min(const ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  using ItemArrayType   = ItemArray<DataType, item_type, ConnectivityPtr>;
+  using ItemIsOwnedType = ItemValue<const bool, item_type>;
+  using data_type       = std::remove_const_t<typename ItemArrayType::data_type>;
+  using index_type      = typename ItemArrayType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean data");
+
+  class ItemArrayMin
+  {
+   private:
+    const ItemArrayType& m_item_value;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i_item, data_type& data) const
+    {
+      if (m_is_owned[i_item]) {
+        Array array = m_item_value[i_item];
+        for (size_t i = 0; i < m_item_value.sizeOfArrays(); ++i) {
+          if (array[i] < data) {
+            data = array[i];
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src < dst) {
+        // cannot be reached if initial value is the min
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::max();
+    }
+
+    PUGS_INLINE
+    ItemArrayMin(const ItemArrayType& item_value)
+      : m_item_value(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ItemArrayMin() = default;
+  };
+
+  const DataType local_min = ItemArrayMin{item_value};
+  return parallel::allReduceMin(local_min);
+}
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+max(const ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  using ItemArrayType   = ItemArray<DataType, item_type, ConnectivityPtr>;
+  using ItemIsOwnedType = ItemValue<const bool, item_type>;
+  using data_type       = std::remove_const_t<typename ItemArrayType::data_type>;
+  using index_type      = typename ItemArrayType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "max cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "max cannot be called on boolean data");
+
+  class ItemArrayMax
+  {
+   private:
+    const ItemArrayType& m_item_value;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i_item, data_type& data) const
+    {
+      if (m_is_owned[i_item]) {
+        Array array = m_item_value[i_item];
+        for (size_t i = 0; i < m_item_value.sizeOfArrays(); ++i) {
+          if (array[i] > data) {
+            data = array[i];
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src > dst) {
+        // cannot be reached if initial value is the max
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::min();
+    }
+
+    PUGS_INLINE
+    ItemArrayMax(const ItemArrayType& item_value)
+      : m_item_value(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ItemArrayMax() = default;
+  };
+
+  const DataType local_max = ItemArrayMax{item_value};
+  return parallel::allReduceMax(local_max);
+}
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+sum(const ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  using ItemArrayType   = ItemArray<DataType, item_type, ConnectivityPtr>;
+  using ItemIsOwnedType = ItemValue<const bool, item_type>;
+  using data_type       = std::remove_const_t<typename ItemArrayType::data_type>;
+  using index_type      = typename ItemArrayType::index_type;
+
+  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean data");
+
+  class ItemArraySum
+  {
+   private:
+    const ItemArrayType& m_item_value;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i_item, data_type& data) const
+    {
+      if (m_is_owned[i_item]) {
+        Array array = m_item_value[i_item];
+        for (size_t i = 0; i < m_item_value.sizeOfArrays(); ++i) {
+          data += array[i];
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        value = 0;
+      } else {
+        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
+        value = zero;
+      }
+    }
+
+    PUGS_INLINE
+    ItemArraySum(const ItemArrayType& item_value)
+      : m_item_value(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ItemArraySum() = default;
+  };
+
+  const DataType local_sum = ItemArraySum{item_value};
+  return parallel::allReduceSum(local_sum);
+}
+
 template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 void
 synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_array)
diff --git a/tests/test_ItemArrayUtils.cpp b/tests/test_ItemArrayUtils.cpp
index 85e2e6e07..f4ab1f47d 100644
--- a/tests/test_ItemArrayUtils.cpp
+++ b/tests/test_ItemArrayUtils.cpp
@@ -839,4 +839,274 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
       }
     }
   }
+
+  SECTION("min")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellArray<int> cell_array{connectivity, 3};
+          cell_array.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_1d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = 10 + parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(min(cell_array) == 10);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          CellArray<int> cell_array{connectivity, 3};
+          cell_array.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_2d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = 10 + parallel::rank() - i;
+                }
+              }
+            });
+
+          REQUIRE(min(cell_array) == 8);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellArray<int> cell_array{connectivity, 3};
+          cell_array.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_3d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = 10 + parallel::rank() + 2 - i;
+                }
+              }
+            });
+
+          REQUIRE(min(cell_array) == 10);
+        }
+      }
+    }
+  }
+
+  SECTION("max")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 3};
+          cell_array.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_1d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = parallel::rank() + 1 + i;
+                }
+              }
+            });
+
+          REQUIRE(max(cell_array) == parallel::size() + 2);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 3};
+          cell_array.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_2d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(max(cell_array) == parallel::size() + 1);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 2};
+          cell_array.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_3d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = parallel::rank() + 1 + i;
+                }
+              }
+            });
+
+          REQUIRE(max(cell_array) == parallel::size() + 1);
+        }
+      }
+    }
+  }
+
+  SECTION("sum")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 3};
+          cell_array.fill(5);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          const size_t global_number_of_cells = [&] {
+            size_t number_of_cells = 0;
+            for (CellId cell_id = 0; cell_id < cell_is_owned.numberOfItems(); ++cell_id) {
+              number_of_cells += cell_is_owned[cell_id];
+            }
+            return parallel::allReduceSum(number_of_cells);
+          }();
+
+          REQUIRE(sum(cell_array) == 5 * cell_array.sizeOfArrays() * global_number_of_cells);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          FaceArray<size_t> face_array{connectivity, 3};
+          face_array.fill(2);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_faces = [&] {
+            size_t number_of_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_faces += face_is_owned[face_id];
+            }
+            return parallel::allReduceSum(number_of_faces);
+          }();
+
+          REQUIRE(sum(face_array) == 2 * face_array.sizeOfArrays() * global_number_of_faces);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          NodeArray<size_t> node_array{connectivity, 3};
+          node_array.fill(3);
+
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          const size_t global_number_of_nodes = [&] {
+            size_t number_of_nodes = 0;
+            for (NodeId node_id = 0; node_id < node_is_owned.numberOfItems(); ++node_id) {
+              number_of_nodes += node_is_owned[node_id];
+            }
+            return parallel::allReduceSum(number_of_nodes);
+          }();
+
+          REQUIRE(sum(node_array) == 3 * node_array.sizeOfArrays() * global_number_of_nodes);
+        }
+      }
+    }
+  }
 }
-- 
GitLab