Repository: computationalgeography/lue (language: python)

Issue:
Verify arrays have the same shape and shape in partitions
I'm wondering it this should be possible
```
  e1 = lfr.create_array((400,400), (20,20), np.dtype(np.float32), fill_value=2.71)
  e2 = lfr.create_array((600,600), (20,20), np.dtype(np.float32), fill_value=2.71)
  array = lfr.pow(e1, e2)
  minimum = lfr.minimum(array)
  maximum = lfr.maximum(array)
  print(minimum.get(), maximum.get())
```
prints values

```
  e1 = lfr.create_array((400,400), (20,20), np.dtype(np.float32), fill_value=2.71)
  e2 = lfr.create_array((200,200), (20,20), np.dtype(np.float32), fill_value=2.71)
  array = lfr.pow(e1, e2)
  minimum = lfr.minimum(array)
  maximum = lfr.maximum(array)
  print(minimum.get(), maximum.get())
```
silently exits (prints nothing)

```
  e1 = lfr.create_array((400,400), (20,20), np.dtype(np.float32), fill_value=2.71)
  e2 = lfr.create_array((400,400), (20,40), np.dtype(np.float32), fill_value=2.71)
  array = lfr.pow(e1, e2)
  minimum = lfr.minimum(array)
  maximum = lfr.maximum(array)
  print(minimum.get(), maximum.get())
```
segfaults



Discussion / hints:
All arrays are assumed to have the same partitioning and all 'same' partitions from different arrays are assumed to be located on the same locality. This needs to be asserted and in case these assumptions are not met, like in your case, an error messages must pop up.

Fix patch:
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/accu3.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/accu3.hpp
index 19117cd5d..d97c70c24 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/accu3.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/accu3.hpp
@@ -1,6 +1,7 @@
 #pragma once
 #include "lue/framework/algorithm/accu3.hpp"
 #include "lue/framework/algorithm/definition/flow_accumulation3.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/routing_operation_export.hpp"
 #include "lue/macro.hpp"
 
@@ -581,6 +582,8 @@ namespace lue {
     {
         AnnotateFunction annotation{"accu_meh"};
 
+        detail::verify_compatible(flow_direction, external_inflow);
+
         using MaterialArray = PartitionedArray<MaterialElement, rank>;
         using MaterialPartitions = PartitionsT<MaterialArray>;
 
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/accu_threshold3.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/accu_threshold3.hpp
index 30b18c914..499627d92 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/accu_threshold3.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/accu_threshold3.hpp
@@ -1,6 +1,7 @@
 #pragma once
 #include "lue/framework/algorithm/accu_threshold3.hpp"
 #include "lue/framework/algorithm/definition/flow_accumulation3.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/routing_operation_export.hpp"
 #include "lue/macro.hpp"
 
@@ -669,6 +670,8 @@ namespace lue {
     {
         AnnotateFunction annotation{"accu_threshold_meh"};
 
+        detail::verify_compatible(flow_direction, external_inflow, threshold);
+
         // This function must:
         // - Return as soon as possible
         // - Create a task per partition, returning partitions per output
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/binary_local_operation.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/binary_local_operation.hpp
index 987df6313..b4e15a10f 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/binary_local_operation.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/binary_local_operation.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "lue/framework/algorithm/binary_local_operation.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/local_operation_export.hpp"
 #include "lue/framework/core/annotate.hpp"
 #include "lue/framework/core/component.hpp"
@@ -481,11 +482,10 @@ namespace lue {
 
         using Shape = ShapeT<OutputArray>;
 
+        detail::verify_compatible(input_array1, input_array2);
+
         lue_hpx_assert(all_are_valid(input_array1.partitions()));
         lue_hpx_assert(all_are_valid(input_array2.partitions()));
-        lue_hpx_assert(nr_partitions(input_array1) == nr_partitions(input_array2));
-        lue_hpx_assert(input_array1.shape() == input_array2.shape());
-        lue_hpx_assert(shape_in_partitions(input_array1) == shape_in_partitions(input_array2));
 
         detail::BinaryLocalOperationPartitionAction<
             Policies,
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/decreasing_order.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/decreasing_order.hpp
index 98a2ca872..c06b2ba2f 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/decreasing_order.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/decreasing_order.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "lue/framework/algorithm/decreasing_order.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/routing_operation_export.hpp"
 #include "lue/framework/core/component.hpp"
 #include "lue/macro.hpp"
@@ -1225,7 +1226,7 @@ namespace lue {
         static_assert(
             std::is_same_v<policy::OutputElementT<Policies, 0>, policy::InputElementT<Policies, 0>>);
 
-        lue_hpx_assert(zone.shape() == value.shape());
+        detail::verify_compatible(zone, value);
 
         using Zone = policy::InputElementT<Policies, 0>;
         using ZoneArray = PartitionedArray<Zone, rank>;
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/flow_accumulation.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/flow_accumulation.hpp
index e2e39c9ee..f86030d12 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/flow_accumulation.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/flow_accumulation.hpp
@@ -5,6 +5,7 @@
 #include "lue/framework/algorithm/detail/partition.hpp"
 #include "lue/framework/algorithm/detail/promise.hpp"
 #include "lue/framework/algorithm/detail/spawn.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/detail/when_all_get.hpp"
 #include "lue/framework/algorithm/inflow_count.hpp"
 #include "lue/framework/algorithm/type_traits.hpp"
@@ -1498,6 +1499,8 @@ namespace lue {
         [[maybe_unused]] Accumulator&& accumulator,
         PartitionedArray<CriterionElement, rank> const& criterion)
     {
+        detail::verify_compatible(flow_direction, material, criterion);
+
         lue::Localities<rank> const& localities{flow_direction.localities()};
 
         using FlowDirectionArray = PartitionedArray<FlowDirectionElement, rank>;
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/focal_operation.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/focal_operation.hpp
index 6837717dc..5a9733ce7 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/focal_operation.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/focal_operation.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "lue/framework/algorithm/detail/halo_partition.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/detail/when_all_get.hpp"
 #include "lue/framework/algorithm/export.hpp"
 #include "lue/framework/algorithm/policy.hpp"
@@ -854,7 +855,8 @@ namespace lue {
 
                             HPX_UNUSED(input_partitions);
 
-                            auto const& first_partition_data{std::get<0>(std::forward_as_tuple(partition_data...))};
+                            auto const& first_partition_data{
+                                std::get<0>(std::forward_as_tuple(partition_data...))};
                             auto const& partition_shape{first_partition_data.shape()};
 
                             auto const [nr_elements0, nr_elements1] = partition_shape;
@@ -1958,6 +1960,8 @@ namespace lue {
             Functor functor,
             PartitionedArray<InputElements, rank<Kernel>> const&... input_arrays)
         {
+            verify_compatible(input_arrays...);
+
             // Replace the input arrays by a type that will help in the
             // implementation of this operation
             return focal_operation_2d(
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/kinematic_wave.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/kinematic_wave.hpp
index e93f9c58e..c287c5a08 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/kinematic_wave.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/kinematic_wave.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "lue/framework/algorithm/definition/flow_accumulation3.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/kinematic_wave.hpp"
 #include "lue/framework/algorithm/routing_operation_export.hpp"
 #include "lue/macro.hpp"
@@ -851,6 +852,8 @@ namespace lue {
         using MaterialArray = PartitionedArray<Material, rank>;
         using MaterialPartitions = PartitionsT<MaterialArray>;
 
+        detail::verify_compatible(flow_direction, current_discharge, inflow, channel_length);
+
         auto const& shape_in_partitions{flow_direction.partitions().shape()};
         Localities<rank> const& localities{flow_direction.localities()};
 
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/where.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/where.hpp
index 097c274bb..3bc5ccdec 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/where.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/where.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "lue/framework/algorithm/definition/binary_local_operation.hpp"
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/local_operation_export.hpp"
 #include "lue/framework/algorithm/where.hpp"
 #include "lue/macro.hpp"
@@ -522,10 +523,7 @@ namespace lue {
         using Partitions = PartitionsT<Array>;
         using Partition = PartitionT<Array>;
 
-        lue_hpx_assert(nr_partitions(condition) == nr_partitions(true_array));
-        lue_hpx_assert(nr_partitions(condition) == nr_partitions(false_array));
-        lue_hpx_assert(shape_in_partitions(condition) == shape_in_partitions(true_array));
-        lue_hpx_assert(shape_in_partitions(condition) == shape_in_partitions(false_array));
+        detail::verify_compatible(condition, true_array, false_array);
 
         detail::where::WherePartitionAction<Policies, ConditionPartition, Partition, Partition> action;
 
@@ -575,8 +573,7 @@ namespace lue {
         using Partitions = PartitionsT<Array>;
         using Partition = PartitionT<Array>;
 
-        lue_hpx_assert(nr_partitions(condition) == nr_partitions(true_array));
-        lue_hpx_assert(shape_in_partitions(condition) == shape_in_partitions(true_array));
+        detail::verify_compatible(condition, true_array);
 
         detail::where::WherePartitionAction<Policies, ConditionPartition, Partition, Element> action;
 
@@ -625,8 +622,7 @@ namespace lue {
         using Partitions = PartitionsT<Array>;
         using Partition = PartitionT<Array>;
 
-        lue_hpx_assert(nr_partitions(condition) == nr_partitions(false_array));
-        lue_hpx_assert(shape_in_partitions(condition) == shape_in_partitions(false_array));
+        detail::verify_compatible(condition, false_array);
 
         detail::where::WherePartitionAction<Policies, ConditionPartition, Element, Partition> action;
 
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/definition/zonal_operation.hpp b/source/framework/algorithm/include/lue/framework/algorithm/definition/zonal_operation.hpp
index d6aa96268..c8e07d1d1 100644
--- a/source/framework/algorithm/include/lue/framework/algorithm/definition/zonal_operation.hpp
+++ b/source/framework/algorithm/include/lue/framework/algorithm/definition/zonal_operation.hpp
@@ -1,4 +1,5 @@
 #pragma once
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
 #include "lue/framework/algorithm/functor_traits.hpp"
 #include "lue/framework/core/annotate.hpp"
 #include "lue/framework/partitioned_array.hpp"
@@ -389,8 +390,9 @@ namespace lue {
         using Aggregator = AggregatorT<Functor>;
         using Aggregators = std::vector<hpx::future<Aggregator>>;
 
+        detail::verify_compatible(input_array, zones_array);
+
         Count const nr_partitions{lue::nr_partitions(zones_array)};
-        lue_hpx_assert(lue::nr_partitions(input_array) == nr_partitions);
 
         Localities<rank> const& localities{zones_array.localities()};
         ZonesPartitions const& zones_partitions{zones_array.partitions()};
diff --git a/source/framework/algorithm/include/lue/framework/algorithm/detail/verify_compatible.hpp b/source/framework/algorithm/include/lue/framework/algorithm/detail/verify_compatible.hpp
new file mode 100644
index 000000000..d5e74ec27
--- /dev/null
+++ b/source/framework/algorithm/include/lue/framework/algorithm/detail/verify_compatible.hpp
@@ -0,0 +1,63 @@
+#pragma once
+#include "lue/framework/partitioned_array.hpp"
+#include <fmt/ranges.h>
+#include <stdexcept>
+
+
+namespace lue::detail {
+
+    /*!
+        @overload
+    */
+    template<typename Element, Rank rank>
+    void verify_compatible([[maybe_unused]] PartitionedArray<Element, rank> const& array)
+    {
+    }
+
+
+    /*!
+        @overload
+    */
+    template<typename Element1, typename Element2, Rank rank>
+    void verify_compatible(
+        PartitionedArray<Element1, rank> const& array1, PartitionedArray<Element2, rank> const& array2)
+    {
+        if (array1.shape() != array2.shape())
+        {
+            throw std::runtime_error(fmt::format("Shapes differ: {} vs {}", array1.shape(), array2.shape()));
+        }
+
+        if (array1.partitions().shape() != array2.partitions().shape())
+        {
+            throw std::runtime_error(fmt::format(
+                "Shapes in partitions differ: {} vs {}",
+                array1.partitions().shape(),
+                array2.partitions().shape()));
+        }
+    }
+
+
+    /*!
+        @brief      Verify whether the arrays passed in are compatible with the first one
+        @tparam     Element Type of array elements
+        @tparam     rank Rank of the array
+        @return     .
+        @exception  std::runtime_error When one of the arrays is not compatible
+
+        Compatible arrays have the same domain decomposition:
+
+        - Shape is the same
+        - Shape in partitions is the same
+        - Localities of the partitions are the same
+    */
+    template<typename Element1, typename Element2, typename... Elements, Rank rank>
+    void verify_compatible(
+        PartitionedArray<Element1, rank> const& array1,
+        PartitionedArray<Element2, rank> const& array2,
+        PartitionedArray<Elements, rank> const&... arrays)
+    {
+        verify_compatible(array1, array2);
+        verify_compatible(array2, arrays...);
+    }
+
+}  // namespace lue::detail


Test patch:
diff --git a/source/framework/algorithm/test/CMakeLists.txt b/source/framework/algorithm/test/CMakeLists.txt
index bbbf4fb7d..b39ed708a 100644
--- a/source/framework/algorithm/test/CMakeLists.txt
+++ b/source/framework/algorithm/test/CMakeLists.txt
@@ -123,6 +123,7 @@ set(names
     sum
     timestamp
     upstream
+    verify_compatible
 )
 
 foreach(name ${names})
diff --git a/source/framework/algorithm/test/verify_compatible_test.cpp b/source/framework/algorithm/test/verify_compatible_test.cpp
new file mode 100644
index 000000000..8abe6d614
--- /dev/null
+++ b/source/framework/algorithm/test/verify_compatible_test.cpp
@@ -0,0 +1,65 @@
+#define BOOST_TEST_MODULE lue framework algorithm verify_compatible
+#include "lue/framework/algorithm/detail/verify_compatible.hpp"
+#include "lue/framework/test/hpx_unit_test.hpp"
+
+
+BOOST_AUTO_TEST_CASE(compatible)
+{
+    using Element = std::int32_t;
+    constexpr lue::Rank rank = 2;
+    using Shape = lue::Shape<lue::Count, rank>;
+
+    Shape const array_shape{{60, 40}};
+    Shape const partition_shape{{10, 10}};
+
+    auto array1 = lue::create_partitioned_array<Element>(array_shape, partition_shape, 5);
+    auto array2 = lue::create_partitioned_array<Element>(array_shape, partition_shape, 6);
+    auto array3 = lue::create_partitioned_array<Element>(array_shape, partition_shape, 7);
+
+    BOOST_CHECK_NO_THROW(lue::detail::verify_compatible(array1));
+    BOOST_CHECK_NO_THROW(lue::detail::verify_compatible(array1, array2));
+    BOOST_CHECK_NO_THROW(lue::detail::verify_compatible(array1, array2, array3));
+
+    BOOST_CHECK_NO_THROW(lue::detail::verify_compatible(array1, array1));
+    BOOST_CHECK_NO_THROW(lue::detail::verify_compatible(array1, array1, array1));
+}
+
+
+BOOST_AUTO_TEST_CASE(not_compatible_shape_in_elements)
+{
+    using Element = std::int32_t;
+    constexpr lue::Rank rank = 2;
+    using Shape = lue::Shape<lue::Count, rank>;
+
+    Shape const partition_shape{{10, 10}};
+
+    auto array1 = lue::create_partitioned_array<Element>({{60, 40}}, partition_shape, 5);
+    auto array2 = lue::create_partitioned_array<Element>({{40, 60}}, partition_shape, 6);
+    auto array3 = lue::create_partitioned_array<Element>({{30, 30}}, partition_shape, 7);
+
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array1, array2), std::runtime_error);
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array1, array2, array3), std::runtime_error);
+
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array1, array1, array2), std::runtime_error);
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array2, array1, array1), std::runtime_error);
+}
+
+
+BOOST_AUTO_TEST_CASE(not_compatible_shape_in_partitions)
+{
+    using Element = std::int32_t;
+    constexpr lue::Rank rank = 2;
+    using Shape = lue::Shape<lue::Count, rank>;
+
+    Shape const array_shape{{60, 40}};
+
+    auto array1 = lue::create_partitioned_array<Element>(array_shape, {{10, 10}}, 5);
+    auto array2 = lue::create_partitioned_array<Element>(array_shape, {{5, 10}}, 6);
+    auto array3 = lue::create_partitioned_array<Element>(array_shape, {{10, 5}}, 7);
+
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array1, array2), std::runtime_error);
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array1, array2, array3), std::runtime_error);
+
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array1, array1, array2), std::runtime_error);
+    BOOST_CHECK_THROW(lue::detail::verify_compatible(array2, array1, array1), std::runtime_error);
+}