From 7d56741852e5f4d511cfff22724f99f1f5f64620 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Thu, 1 Apr 2021 12:54:00 +0200
Subject: [PATCH] Add ItemArray class

It consists in a collection of arrays of the SAME size associated to items.
The implementation return a SubArray associate with each item.

It mimics the API of ItemValue's.

The array size itself is dynamic.
---
 src/mesh/ItemArray.hpp   | 219 +++++++++++++++++++++++++++++++++
 tests/CMakeLists.txt     |   1 +
 tests/test_ItemArray.cpp | 256 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 476 insertions(+)
 create mode 100644 src/mesh/ItemArray.hpp
 create mode 100644 tests/test_ItemArray.cpp

diff --git a/src/mesh/ItemArray.hpp b/src/mesh/ItemArray.hpp
new file mode 100644
index 000000000..f47835b7c
--- /dev/null
+++ b/src/mesh/ItemArray.hpp
@@ -0,0 +1,219 @@
+#ifndef ITEM_ARRAY_HPP
+#define ITEM_ARRAY_HPP
+
+#include <mesh/IConnectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <mesh/ItemType.hpp>
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/SubArray.hpp>
+
+#include <memory>
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
+class ItemArray
+{
+ public:
+  static constexpr ItemType item_t{item_type};
+  using data_type = DataType;
+
+  using ItemId     = ItemIdT<item_type>;
+  using index_type = ItemId;
+
+ private:
+  using ConnectivitySharedPtr = std::shared_ptr<const IConnectivity>;
+  using ConnectivityWeakPtr   = std::weak_ptr<const IConnectivity>;
+
+  static_assert(std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> or
+                std::is_same_v<ConnectivityPtr, ConnectivityWeakPtr>);
+
+  ConnectivityPtr m_connectivity_ptr;
+
+  Array<DataType> m_arrays_values;
+
+  size_t m_size_of_arrays;
+
+ public:
+  // Allow const std:shared_ptr version to access our data
+  friend ItemArray<std::add_const_t<DataType>, item_type, ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend ItemArray<std::add_const_t<DataType>, item_type, ConnectivityWeakPtr>;
+
+  friend PUGS_INLINE ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr>
+  copy(const ItemArray<DataType, item_type, ConnectivityPtr>& source)
+  {
+    ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr> image;
+
+    image.m_connectivity_ptr = source.m_connectivity_ptr;
+    image.m_arrays_values    = copy(source.m_arrays_values);
+    image.m_size_of_arrays   = source.m_size_of_arrays;
+    return image;
+  }
+
+  PUGS_INLINE
+  bool
+  isBuilt() const noexcept
+  {
+    return m_connectivity_ptr.use_count() != 0;
+  }
+
+  PUGS_INLINE
+  std::shared_ptr<const IConnectivity>
+  connectivity_ptr() const noexcept
+  {
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr>) {
+      return m_connectivity_ptr;
+    } else {
+      return m_connectivity_ptr.lock();
+    }
+  }
+
+  PUGS_INLINE
+  size_t
+  numberOfValues() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_arrays_values.size();
+  }
+
+  PUGS_INLINE
+  void
+  fill(const DataType& data) const noexcept
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot modify ItemArray of const");
+    m_arrays_values.fill(data);
+  }
+
+  // Following Kokkos logic, these classes are view and const view does allow
+  // changes in data
+  PUGS_FORCEINLINE
+  SubArray<DataType>
+  operator[](const ItemId& i) const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return SubArray{m_arrays_values, i * m_size_of_arrays, m_size_of_arrays};
+  }
+
+  template <typename IndexType>
+  SubArray<DataType>
+  operator[](const IndexType&) const noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_same_v<IndexType, ItemId>, "ItemArray must be indexed by ItemId");
+  }
+
+  PUGS_INLINE
+  size_t
+  numberOfItems() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_connectivity_ptr->template numberOf<item_type>();
+  }
+
+  PUGS_INLINE
+  size_t
+  sizeOfArrays() const
+  {
+    Assert(this->isBuilt());
+    return m_size_of_arrays;
+  }
+
+  template <typename DataType2>
+  PUGS_INLINE ItemArray&
+  operator=(const Array<DataType2>& arrays) noexcept(NO_ASSERT)
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
+                  "Cannot assign ItemArray of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>) or not std::is_const_v<DataType2>),
+                  "Cannot assign ItemArray of const to ItemArray of non-const");
+
+    Assert((arrays.size() == 0) or this->isBuilt(), "Cannot assign array of arrays to a non-built ItemArray\n");
+
+    Assert(m_arrays_values.size() == arrays.size(), "Cannot assign an array of arrays of a different size\n");
+
+    m_arrays_values = arrays;
+
+    return *this;
+  }
+
+  template <typename DataType2, typename ConnectivityPtr2>
+  PUGS_INLINE ItemArray&
+  operator=(const ItemArray<DataType2, item_type, ConnectivityPtr2>& array_per_item) noexcept
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
+                  "Cannot assign ItemArray of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>) or not std::is_const_v<DataType2>),
+                  "Cannot assign ItemArray of const to ItemArray of non-const");
+
+    m_arrays_values  = array_per_item.m_arrays_values;
+    m_size_of_arrays = array_per_item.m_size_of_arrays;
+
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
+                  std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
+      m_connectivity_ptr = array_per_item.m_connectivity_ptr.lock();
+    } else {
+      m_connectivity_ptr = array_per_item.m_connectivity_ptr;
+    }
+
+    return *this;
+  }
+
+  template <typename DataType2, typename ConnectivityPtr2>
+  PUGS_INLINE
+  ItemArray(const ItemArray<DataType2, item_type, ConnectivityPtr2>& array_per_item) noexcept
+  {
+    this->operator=(array_per_item);
+  }
+
+  PUGS_INLINE
+  ItemArray() = default;
+
+  PUGS_INLINE
+  ItemArray(const IConnectivity& connectivity, size_t size_of_array) noexcept
+    : m_connectivity_ptr{connectivity.shared_ptr()},
+      m_arrays_values{connectivity.numberOf<item_type>() * size_of_array},
+      m_size_of_arrays{size_of_array}
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot allocate ItemArray of const data: only view is "
+                                                 "supported");
+    ;
+  }
+
+  PUGS_INLINE
+  ~ItemArray() = default;
+};
+
+template <typename DataType>
+using NodeArray = ItemArray<DataType, ItemType::node>;
+
+template <typename DataType>
+using EdgeArray = ItemArray<DataType, ItemType::edge>;
+
+template <typename DataType>
+using FaceArray = ItemArray<DataType, ItemType::face>;
+
+template <typename DataType>
+using CellArray = ItemArray<DataType, ItemType::cell>;
+
+// Weak versions: should not be used outside of Connectivity
+
+template <typename DataType, ItemType item_type>
+using WeakItemArray = ItemArray<DataType, item_type, std::weak_ptr<const IConnectivity>>;
+
+template <typename DataType>
+using WeakNodeArray = WeakItemArray<DataType, ItemType::node>;
+
+template <typename DataType>
+using WeakEdgeArray = WeakItemArray<DataType, ItemType::edge>;
+
+template <typename DataType>
+using WeakFaceArray = WeakItemArray<DataType, ItemType::face>;
+
+template <typename DataType>
+using WeakCellArray = WeakItemArray<DataType, ItemType::cell>;
+
+#endif   // ITEM_ARRAY_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d6fff1695..976258f59 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -100,6 +100,7 @@ add_executable (mpi_unit_tests
   mpi_test_main.cpp
   test_Messenger.cpp
   test_Partitioner.cpp
+  test_ItemArray.cpp
   test_ItemValue.cpp
   test_ItemValueUtils.cpp
   test_SubItemValuePerItem.cpp
diff --git a/tests/test_ItemArray.cpp b/tests/test_ItemArray.cpp
new file mode 100644
index 000000000..2177d83c2
--- /dev/null
+++ b/tests/test_ItemArray.cpp
@@ -0,0 +1,256 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemArray.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+template class ItemArray<int, ItemType::node>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ItemArray", "[mesh]")
+{
+  SECTION("default constructors")
+  {
+    REQUIRE_NOTHROW(NodeArray<int>{});
+    REQUIRE_NOTHROW(EdgeArray<int>{});
+    REQUIRE_NOTHROW(FaceArray<int>{});
+    REQUIRE_NOTHROW(CellArray<int>{});
+
+    REQUIRE(not NodeArray<int>{}.isBuilt());
+    REQUIRE(not EdgeArray<int>{}.isBuilt());
+    REQUIRE(not FaceArray<int>{}.isBuilt());
+    REQUIRE(not CellArray<int>{}.isBuilt());
+  }
+
+  SECTION("1D")
+  {
+    const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+    const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
+
+    REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
+
+    NodeArray<int> node_value{connectivity, 3};
+    EdgeArray<int> edge_value{connectivity, 3};
+    FaceArray<int> face_value{connectivity, 3};
+    CellArray<int> cell_value{connectivity, 3};
+
+    REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
+    REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
+    REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
+
+    REQUIRE(node_value.numberOfValues() == 3 * node_value.numberOfItems());
+    REQUIRE(edge_value.numberOfValues() == 3 * edge_value.numberOfItems());
+    REQUIRE(face_value.numberOfValues() == 3 * face_value.numberOfItems());
+    REQUIRE(cell_value.numberOfValues() == 3 * cell_value.numberOfItems());
+
+    REQUIRE(node_value.sizeOfArrays() == 3);
+    REQUIRE(edge_value.sizeOfArrays() == 3);
+    REQUIRE(face_value.sizeOfArrays() == 3);
+    REQUIRE(cell_value.sizeOfArrays() == 3);
+  }
+
+  SECTION("2D")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 2});
+    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 2});
+    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 2});
+    REQUIRE_NOTHROW(CellArray<int>{connectivity, 2});
+
+    REQUIRE(NodeArray<int>{connectivity, 2}.isBuilt());
+    REQUIRE(EdgeArray<int>{connectivity, 2}.isBuilt());
+    REQUIRE(FaceArray<int>{connectivity, 2}.isBuilt());
+    REQUIRE(CellArray<int>{connectivity, 2}.isBuilt());
+
+    NodeArray<int> node_value{connectivity, 2};
+    EdgeArray<int> edge_value{connectivity, 2};
+    FaceArray<int> face_value{connectivity, 2};
+    CellArray<int> cell_value{connectivity, 2};
+
+    REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
+
+    REQUIRE(node_value.numberOfValues() == 2 * node_value.numberOfItems());
+    REQUIRE(edge_value.numberOfValues() == 2 * edge_value.numberOfItems());
+    REQUIRE(face_value.numberOfValues() == 2 * face_value.numberOfItems());
+    REQUIRE(cell_value.numberOfValues() == 2 * cell_value.numberOfItems());
+
+    REQUIRE(node_value.sizeOfArrays() == 2);
+    REQUIRE(edge_value.sizeOfArrays() == 2);
+    REQUIRE(face_value.sizeOfArrays() == 2);
+    REQUIRE(cell_value.sizeOfArrays() == 2);
+  }
+
+  SECTION("3D")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
+
+    REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
+
+    NodeArray<int> node_value{connectivity, 3};
+    EdgeArray<int> edge_value{connectivity, 3};
+    FaceArray<int> face_value{connectivity, 3};
+    CellArray<int> cell_value{connectivity, 3};
+
+    REQUIRE(node_value.numberOfValues() == 3 * node_value.numberOfItems());
+    REQUIRE(edge_value.numberOfValues() == 3 * edge_value.numberOfItems());
+    REQUIRE(face_value.numberOfValues() == 3 * face_value.numberOfItems());
+    REQUIRE(cell_value.numberOfValues() == 3 * cell_value.numberOfItems());
+
+    REQUIRE(node_value.sizeOfArrays() == 3);
+    REQUIRE(edge_value.sizeOfArrays() == 3);
+    REQUIRE(face_value.sizeOfArrays() == 3);
+    REQUIRE(cell_value.sizeOfArrays() == 3);
+  }
+
+  SECTION("set values from array")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    CellArray<size_t> cell_array{connectivity, 3};
+
+    Array<size_t> array{cell_array.numberOfValues()};
+    for (size_t i = 0; i < array.size(); ++i) {
+      array[i] = i;
+    }
+
+    cell_array = array;
+
+    auto is_same = [](const CellArray<size_t>& cell_array, const Array<size_t>& array) {
+      bool is_same = true;
+      size_t k     = 0;
+      for (CellId cell_id = 0; cell_id < cell_array.numberOfItems(); ++cell_id) {
+        SubArray sub_array = cell_array[cell_id];
+        for (size_t i = 0; i < sub_array.size(); ++i, ++k) {
+          is_same &= (sub_array[i] == array[k]);
+        }
+      }
+      return is_same;
+    };
+
+    REQUIRE(is_same(cell_array, array));
+  }
+
+  SECTION("copy")
+  {
+    auto is_same = [](const auto& cell_array, int value) {
+      bool is_same = true;
+      for (CellId cell_id = 0; cell_id < cell_array.numberOfItems(); ++cell_id) {
+        SubArray sub_array = cell_array[cell_id];
+        for (size_t i = 0; i < sub_array.size(); ++i) {
+          is_same &= (sub_array[i] == value);
+        }
+      }
+      return is_same;
+    };
+
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    CellArray<int> cell_array{connectivity, 4};
+    cell_array.fill(parallel::rank());
+
+    CellArray<const int> cell_array_const_view{cell_array};
+    REQUIRE(cell_array.numberOfValues() == cell_array_const_view.numberOfValues());
+    REQUIRE(is_same(cell_array_const_view, static_cast<std::int64_t>(parallel::rank())));
+
+    CellArray<const int> const_cell_array;
+    const_cell_array = copy(cell_array);
+
+    cell_array.fill(0);
+
+    REQUIRE(is_same(cell_array, 0));
+    REQUIRE(is_same(cell_array_const_view, 0));
+    REQUIRE(is_same(const_cell_array, static_cast<std::int64_t>(parallel::rank())));
+  }
+
+  SECTION("WeakItemArray")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    WeakFaceArray<int> weak_face_array{connectivity, 5};
+
+    weak_face_array.fill(parallel::rank());
+
+    FaceArray<const int> face_array{weak_face_array};
+
+    REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("checking for build ItemArray")
+    {
+      CellArray<int> cell_array;
+      REQUIRE_THROWS_AS(cell_array[CellId{0}], AssertError);
+
+      FaceArray<int> face_array;
+      REQUIRE_THROWS_AS(face_array[FaceId{0}], AssertError);
+
+      EdgeArray<int> edge_array;
+      REQUIRE_THROWS_AS(edge_array[EdgeId{0}], AssertError);
+
+      NodeArray<int> node_array;
+      REQUIRE_THROWS_AS(node_array[NodeId{0}], AssertError);
+    }
+
+    SECTION("checking for bounds violation")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellArray<int> cell_array{connectivity, 1};
+      CellId invalid_cell_id = connectivity.numberOfCells();
+      REQUIRE_THROWS_AS(cell_array[invalid_cell_id], AssertError);
+
+      FaceArray<int> face_array{connectivity, 2};
+      FaceId invalid_face_id = connectivity.numberOfFaces();
+      REQUIRE_THROWS_AS(face_array[invalid_face_id], AssertError);
+
+      EdgeArray<int> edge_array{connectivity, 1};
+      EdgeId invalid_edge_id = connectivity.numberOfEdges();
+      REQUIRE_THROWS_AS(edge_array[invalid_edge_id], AssertError);
+
+      NodeArray<int> node_array{connectivity, 0};
+      NodeId invalid_node_id = connectivity.numberOfNodes();
+      REQUIRE_THROWS_AS(node_array[invalid_node_id], AssertError);
+    }
+
+    SECTION("set values from invalid array size")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellArray<size_t> cell_array{connectivity, 2};
+
+      Array<size_t> values{3 + cell_array.numberOfValues()};
+      REQUIRE_THROWS_AS(cell_array = values, AssertError);
+    }
+  }
+#endif   // NDEBUG
+}
-- 
GitLab