From ddb2af5017dc20616d2b61455aacd4b5a4ef58c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Tue, 25 Feb 2025 17:02:45 +0100
Subject: [PATCH] Add tests for load balancer

---
 src/scheme/LoadBalancer.cpp          |   3 +
 tests/CMakeLists.txt                 |   2 +
 tests/test_LoadBalancer_parallel.cpp | 271 +++++++++++++++++++++++++++
 tests/test_LoadBalancer_serial.cpp   | 235 +++++++++++++++++++++++
 4 files changed, 511 insertions(+)
 create mode 100644 tests/test_LoadBalancer_parallel.cpp
 create mode 100644 tests/test_LoadBalancer_serial.cpp

diff --git a/src/scheme/LoadBalancer.cpp b/src/scheme/LoadBalancer.cpp
index 0eefbeff2..edee4f980 100644
--- a/src/scheme/LoadBalancer.cpp
+++ b/src/scheme/LoadBalancer.cpp
@@ -44,6 +44,8 @@ LoadBalancer::balance(const std::vector<std::shared_ptr<const DiscreteFunctionVa
       },
       p_balanced_mesh_v->variant());
   } else {
+    // This macro test is used to avoid none reachable code for serial builds of pugs
+#ifdef PUGS_HAS_MPI
     std::visit(
       [&mesh_balancer, &balanced_discrete_function_list, &discrete_function_list](auto&& p_balanced_mesh) {
         using MeshType             = mesh_type_t<decltype(p_balanced_mesh)>;
@@ -69,6 +71,7 @@ LoadBalancer::balance(const std::vector<std::shared_ptr<const DiscreteFunctionVa
         }
       },
       p_balanced_mesh_v->variant());
+#endif   // PUGS_HAS_MPI
   }
 
   return balanced_discrete_function_list;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e0449ebcf..169d37553 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -134,6 +134,7 @@ add_executable (unit_tests
   test_LinearSolverOptions.cpp
   test_LineTransformation.cpp
   test_ListAffectationProcessor.cpp
+  test_LoadBalancer_serial.cpp
   test_MathModule.cpp
   test_MedianDualConnectivityBuilder.cpp
   test_MedianDualMeshBuilder.cpp
@@ -255,6 +256,7 @@ add_executable (mpi_unit_tests
   test_ItemValueUtils.cpp
   test_ItemValueVariant.cpp
   test_ItemValueVariantFunctionInterpoler.cpp
+  test_LoadBalancer_parallel.cpp
   test_MeshEdgeBoundary.cpp
   test_MeshEdgeInterface.cpp
   test_MeshFaceBoundary.cpp
diff --git a/tests/test_LoadBalancer_parallel.cpp b/tests/test_LoadBalancer_parallel.cpp
new file mode 100644
index 000000000..a252c74e0
--- /dev/null
+++ b/tests/test_LoadBalancer_parallel.cpp
@@ -0,0 +1,271 @@
+#include <catch2/catch_all.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <scheme/LoadBalancer.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <utils/PartitionerOptions.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("LoadBalancer parallel", "[scheme]")
+{
+  auto pugs_has_partitioner = [&](const PartitionerLibrary& partitioner_library) {
+    switch (partitioner_library) {
+    case PartitionerLibrary::parmetis: {
+#ifdef PUGS_HAS_PARMETIS
+      return true;
+#else    // PUGS_HAS_PARMETIS
+      return false;
+#endif   // PUGS_HAS_PARMETIS
+    }
+    case PartitionerLibrary::ptscotch: {
+#ifdef PUGS_HAS_PARMETIS
+      return true;
+#else    // PUGS_HAS_PARMETIS
+      return false;
+#endif   // PUGS_HAS_PARMETIS
+    }
+    default: {
+      return false;
+    }
+    }
+  };
+
+  constexpr std::array partitioner_libs = {PartitionerLibrary::parmetis, PartitionerLibrary::ptscotch};
+
+  const auto initial_library = PartitionerOptions::default_options.library();
+
+  for (auto lib : partitioner_libs) {
+    if (pugs_has_partitioner(lib)) {
+      SECTION(name(lib))
+      {
+        SECTION("1D")
+        {
+          for (auto&& named_mesh : MeshDataBaseForTests::get().all1DMeshes()) {
+            std::shared_ptr<const Mesh<1>> initial_mesh_1d = named_mesh.mesh()->get<Mesh<1>>();
+            SECTION(named_mesh.name())
+            {
+              DiscreteFunctionP0<double> initial_fh{initial_mesh_1d};
+              DiscreteFunctionP0Vector<double> initial_vh{initial_mesh_1d, 3};
+
+              {
+                auto initial_xj = MeshDataManager::instance().getMeshData(*initial_mesh_1d).xj();
+
+                for (CellId cell_id = 0; cell_id < initial_mesh_1d->numberOfCells(); ++cell_id) {
+                  initial_fh[cell_id] = 2 * dot(initial_xj[cell_id], initial_xj[cell_id]);
+
+                  initial_vh[cell_id][0] = 3 * initial_xj[cell_id][0] - 1;
+                  initial_vh[cell_id][1] = std::sin(initial_xj[cell_id][0]);
+                  initial_vh[cell_id][2] = -2 * initial_xj[cell_id][0] + 4;
+                }
+              }
+
+              LoadBalancer load_balancer;
+              auto balanced_discrete_function_list =
+                load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                       std::make_shared<DiscreteFunctionVariant>(initial_vh)});
+
+              REQUIRE(balanced_discrete_function_list.size() == 2);
+
+              DiscreteFunctionP0<const double> final_fh =
+                balanced_discrete_function_list[0]->get<DiscreteFunctionP0<const double>>();
+              DiscreteFunctionP0Vector<const double> final_vh =
+                balanced_discrete_function_list[1]->get<DiscreteFunctionP0Vector<const double>>();
+
+              REQUIRE(final_vh.size() == 3);
+              {
+                auto final_mesh_1d_v = final_fh.meshVariant();
+                auto final_mesh_1d   = final_mesh_1d_v->get<Mesh<1>>();
+
+                REQUIRE(final_mesh_1d->id() > initial_mesh_1d->id());
+
+                REQUIRE_THROWS_WITH(load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                                           std::make_shared<DiscreteFunctionVariant>(final_fh)}),
+                                    "error: discrete functions are not defined on the same mesh");
+
+                {
+                  auto final_xj = MeshDataManager::instance().getMeshData(*final_mesh_1d).xj();
+
+                  bool final_fh_is_same = true;
+                  bool final_vh_is_same = true;
+                  for (CellId cell_id = 0; cell_id < final_mesh_1d->numberOfCells(); ++cell_id) {
+                    if (final_fh[cell_id] != 2 * dot(final_xj[cell_id], final_xj[cell_id])) {
+                      final_fh_is_same = false;
+                    }
+
+                    if (final_vh[cell_id][0] != 3 * final_xj[cell_id][0] - 1) {
+                      final_vh_is_same = false;
+                    }
+                    if (final_vh[cell_id][1] != std::sin(final_xj[cell_id][0])) {
+                      final_vh_is_same = false;
+                    }
+                    if (final_vh[cell_id][2] != -2 * final_xj[cell_id][0] + 4) {
+                      final_vh_is_same = false;
+                    }
+                  }
+
+                  REQUIRE(parallel::allReduceAnd(final_fh_is_same));
+                  REQUIRE(parallel::allReduceAnd(final_vh_is_same));
+                }
+              }
+            }
+          }
+        }
+
+        SECTION("2D")
+        {
+          for (auto&& named_mesh : MeshDataBaseForTests::get().all2DMeshes()) {
+            std::shared_ptr<const Mesh<2>> initial_mesh_2d = named_mesh.mesh()->get<Mesh<2>>();
+            SECTION(named_mesh.name())
+            {
+              DiscreteFunctionP0<double> initial_fh{initial_mesh_2d};
+              DiscreteFunctionP0Vector<double> initial_vh{initial_mesh_2d, 3};
+
+              {
+                auto initial_xj = MeshDataManager::instance().getMeshData(*initial_mesh_2d).xj();
+
+                for (CellId cell_id = 0; cell_id < initial_mesh_2d->numberOfCells(); ++cell_id) {
+                  initial_fh[cell_id] = 2 * dot(initial_xj[cell_id], initial_xj[cell_id]);
+
+                  initial_vh[cell_id][0] = 3 * initial_xj[cell_id][0] - 1;
+                  initial_vh[cell_id][1] = std::sin(initial_xj[cell_id][0] + initial_xj[cell_id][1]);
+                  initial_vh[cell_id][2] = -2 * initial_xj[cell_id][0] * initial_xj[cell_id][1] + 4;
+                }
+              }
+
+              LoadBalancer load_balancer;
+              auto balanced_discrete_function_list =
+                load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                       std::make_shared<DiscreteFunctionVariant>(initial_vh)});
+
+              REQUIRE(balanced_discrete_function_list.size() == 2);
+
+              DiscreteFunctionP0<const double> final_fh =
+                balanced_discrete_function_list[0]->get<DiscreteFunctionP0<const double>>();
+              DiscreteFunctionP0Vector<const double> final_vh =
+                balanced_discrete_function_list[1]->get<DiscreteFunctionP0Vector<const double>>();
+
+              REQUIRE(final_vh.size() == 3);
+              {
+                auto final_mesh_2d_v = final_fh.meshVariant();
+                auto final_mesh_2d   = final_mesh_2d_v->get<Mesh<2>>();
+
+                REQUIRE(final_mesh_2d->id() > initial_mesh_2d->id());
+
+                REQUIRE_THROWS_WITH(load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                                           std::make_shared<DiscreteFunctionVariant>(final_fh)}),
+                                    "error: discrete functions are not defined on the same mesh");
+
+                {
+                  auto final_xj = MeshDataManager::instance().getMeshData(*final_mesh_2d).xj();
+
+                  bool final_fh_is_same = true;
+                  bool final_vh_is_same = true;
+                  for (CellId cell_id = 0; cell_id < final_mesh_2d->numberOfCells(); ++cell_id) {
+                    if (final_fh[cell_id] != 2 * dot(final_xj[cell_id], final_xj[cell_id])) {
+                      final_fh_is_same = false;
+                    }
+
+                    if (final_vh[cell_id][0] != 3 * final_xj[cell_id][0] - 1) {
+                      final_vh_is_same = false;
+                    }
+                    if (final_vh[cell_id][1] != std::sin(final_xj[cell_id][0] + final_xj[cell_id][1])) {
+                      final_vh_is_same = false;
+                    }
+                    if (final_vh[cell_id][2] != -2 * final_xj[cell_id][0] * final_xj[cell_id][1] + 4) {
+                      final_vh_is_same = false;
+                    }
+                  }
+
+                  REQUIRE(parallel::allReduceAnd(final_fh_is_same));
+                  REQUIRE(parallel::allReduceAnd(final_vh_is_same));
+                }
+              }
+            }
+          }
+        }
+
+        SECTION("3D")
+        {
+          for (auto&& named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+            std::shared_ptr<const Mesh<3>> initial_mesh_3d = named_mesh.mesh()->get<Mesh<3>>();
+            SECTION(named_mesh.name())
+            {
+              DiscreteFunctionP0<double> initial_fh{initial_mesh_3d};
+              DiscreteFunctionP0Vector<double> initial_vh{initial_mesh_3d, 3};
+
+              {
+                auto initial_xj = MeshDataManager::instance().getMeshData(*initial_mesh_3d).xj();
+
+                for (CellId cell_id = 0; cell_id < initial_mesh_3d->numberOfCells(); ++cell_id) {
+                  initial_fh[cell_id] = 2 * dot(initial_xj[cell_id], initial_xj[cell_id]);
+
+                  initial_vh[cell_id][0] = 3 * initial_xj[cell_id][0] - 1;
+                  initial_vh[cell_id][1] = std::sin(initial_xj[cell_id][1] + initial_xj[cell_id][2]);
+                  initial_vh[cell_id][2] = -2 * initial_xj[cell_id][0] * initial_xj[cell_id][1] + 4;
+                }
+              }
+
+              LoadBalancer load_balancer;
+              auto balanced_discrete_function_list =
+                load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                       std::make_shared<DiscreteFunctionVariant>(initial_vh)});
+
+              REQUIRE(balanced_discrete_function_list.size() == 2);
+
+              DiscreteFunctionP0<const double> final_fh =
+                balanced_discrete_function_list[0]->get<DiscreteFunctionP0<const double>>();
+              DiscreteFunctionP0Vector<const double> final_vh =
+                balanced_discrete_function_list[1]->get<DiscreteFunctionP0Vector<const double>>();
+
+              REQUIRE(final_vh.size() == 3);
+              {
+                auto final_mesh_3d_v = final_fh.meshVariant();
+                auto final_mesh_3d   = final_mesh_3d_v->get<Mesh<3>>();
+
+                REQUIRE(final_mesh_3d->id() > initial_mesh_3d->id());
+
+                REQUIRE_THROWS_WITH(load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                                           std::make_shared<DiscreteFunctionVariant>(final_fh)}),
+                                    "error: discrete functions are not defined on the same mesh");
+
+                {
+                  auto final_xj = MeshDataManager::instance().getMeshData(*final_mesh_3d).xj();
+
+                  bool final_fh_is_same = true;
+                  bool final_vh_is_same = true;
+                  for (CellId cell_id = 0; cell_id < final_mesh_3d->numberOfCells(); ++cell_id) {
+                    if (final_fh[cell_id] != 2 * dot(final_xj[cell_id], final_xj[cell_id])) {
+                      final_fh_is_same = false;
+                    }
+
+                    if (final_vh[cell_id][0] != 3 * final_xj[cell_id][0] - 1) {
+                      final_vh_is_same = false;
+                    }
+                    if (final_vh[cell_id][1] != std::sin(final_xj[cell_id][1] + final_xj[cell_id][2])) {
+                      final_vh_is_same = false;
+                    }
+                    if (final_vh[cell_id][2] != -2 * final_xj[cell_id][0] * final_xj[cell_id][1] + 4) {
+                      final_vh_is_same = false;
+                    }
+                  }
+
+                  REQUIRE(parallel::allReduceAnd(final_fh_is_same));
+                  REQUIRE(parallel::allReduceAnd(final_vh_is_same));
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  PartitionerOptions::default_options.library() = initial_library;
+}
diff --git a/tests/test_LoadBalancer_serial.cpp b/tests/test_LoadBalancer_serial.cpp
new file mode 100644
index 000000000..63f627177
--- /dev/null
+++ b/tests/test_LoadBalancer_serial.cpp
@@ -0,0 +1,235 @@
+#include <catch2/catch_all.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <scheme/LoadBalancer.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionP0Vector.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("LoadBalancer serial", "[scheme]")
+{
+  SECTION("1D")
+  {
+    for (auto&& named_mesh : MeshDataBaseForTests::get().all1DMeshes()) {
+      std::shared_ptr<const Mesh<1>> initial_mesh_1d = named_mesh.mesh()->get<Mesh<1>>();
+      SECTION(named_mesh.name())
+      {
+        DiscreteFunctionP0<double> initial_fh{initial_mesh_1d};
+        DiscreteFunctionP0Vector<double> initial_vh{initial_mesh_1d, 3};
+
+        {
+          auto initial_xj = MeshDataManager::instance().getMeshData(*initial_mesh_1d).xj();
+
+          for (CellId cell_id = 0; cell_id < initial_mesh_1d->numberOfCells(); ++cell_id) {
+            initial_fh[cell_id] = 2 * dot(initial_xj[cell_id], initial_xj[cell_id]);
+
+            initial_vh[cell_id][0] = 3 * initial_xj[cell_id][0] - 1;
+            initial_vh[cell_id][1] = std::sin(initial_xj[cell_id][0]);
+            initial_vh[cell_id][2] = -2 * initial_xj[cell_id][0] + 4;
+          }
+        }
+
+        LoadBalancer load_balancer;
+        auto balanced_discrete_function_list =
+          load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                 std::make_shared<DiscreteFunctionVariant>(initial_vh)});
+
+        REQUIRE(balanced_discrete_function_list.size() == 2);
+
+        DiscreteFunctionP0<const double> final_fh =
+          balanced_discrete_function_list[0]->get<DiscreteFunctionP0<const double>>();
+        DiscreteFunctionP0Vector<const double> final_vh =
+          balanced_discrete_function_list[1]->get<DiscreteFunctionP0Vector<const double>>();
+
+        REQUIRE(final_vh.size() == 3);
+        {
+          auto final_mesh_1d_v = final_fh.meshVariant();
+          auto final_mesh_1d   = final_mesh_1d_v->get<Mesh<1>>();
+
+          REQUIRE(final_mesh_1d->id() > initial_mesh_1d->id());
+
+          REQUIRE_THROWS_WITH(load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                                     std::make_shared<DiscreteFunctionVariant>(final_fh)}),
+                              "error: discrete functions are not defined on the same mesh");
+
+          {
+            auto final_xj = MeshDataManager::instance().getMeshData(*final_mesh_1d).xj();
+
+            bool final_fh_is_same = true;
+            bool final_vh_is_same = true;
+            for (CellId cell_id = 0; cell_id < final_mesh_1d->numberOfCells(); ++cell_id) {
+              if (final_fh[cell_id] != 2 * dot(final_xj[cell_id], final_xj[cell_id])) {
+                final_fh_is_same = false;
+              }
+
+              if (final_vh[cell_id][0] != 3 * final_xj[cell_id][0] - 1) {
+                final_vh_is_same = false;
+              }
+              if (final_vh[cell_id][1] != std::sin(final_xj[cell_id][0])) {
+                final_vh_is_same = false;
+              }
+              if (final_vh[cell_id][2] != -2 * final_xj[cell_id][0] + 4) {
+                final_vh_is_same = false;
+              }
+            }
+
+            REQUIRE(final_fh_is_same);
+            REQUIRE(final_vh_is_same);
+          }
+        }
+      }
+    }
+  }
+
+  SECTION("2D")
+  {
+    for (auto&& named_mesh : MeshDataBaseForTests::get().all2DMeshes()) {
+      std::shared_ptr<const Mesh<2>> initial_mesh_2d = named_mesh.mesh()->get<Mesh<2>>();
+      SECTION(named_mesh.name())
+      {
+        DiscreteFunctionP0<double> initial_fh{initial_mesh_2d};
+        DiscreteFunctionP0Vector<double> initial_vh{initial_mesh_2d, 3};
+
+        {
+          auto initial_xj = MeshDataManager::instance().getMeshData(*initial_mesh_2d).xj();
+
+          for (CellId cell_id = 0; cell_id < initial_mesh_2d->numberOfCells(); ++cell_id) {
+            initial_fh[cell_id] = 2 * dot(initial_xj[cell_id], initial_xj[cell_id]);
+
+            initial_vh[cell_id][0] = 3 * initial_xj[cell_id][0] - 1;
+            initial_vh[cell_id][1] = std::sin(initial_xj[cell_id][0] + initial_xj[cell_id][1]);
+            initial_vh[cell_id][2] = -2 * initial_xj[cell_id][0] * initial_xj[cell_id][1] + 4;
+          }
+        }
+
+        LoadBalancer load_balancer;
+        auto balanced_discrete_function_list =
+          load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                 std::make_shared<DiscreteFunctionVariant>(initial_vh)});
+
+        REQUIRE(balanced_discrete_function_list.size() == 2);
+
+        DiscreteFunctionP0<const double> final_fh =
+          balanced_discrete_function_list[0]->get<DiscreteFunctionP0<const double>>();
+        DiscreteFunctionP0Vector<const double> final_vh =
+          balanced_discrete_function_list[1]->get<DiscreteFunctionP0Vector<const double>>();
+
+        REQUIRE(final_vh.size() == 3);
+        {
+          auto final_mesh_2d_v = final_fh.meshVariant();
+          auto final_mesh_2d   = final_mesh_2d_v->get<Mesh<2>>();
+
+          REQUIRE(final_mesh_2d->id() > initial_mesh_2d->id());
+
+          REQUIRE_THROWS_WITH(load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                                     std::make_shared<DiscreteFunctionVariant>(final_fh)}),
+                              "error: discrete functions are not defined on the same mesh");
+
+          {
+            auto final_xj = MeshDataManager::instance().getMeshData(*final_mesh_2d).xj();
+
+            bool final_fh_is_same = true;
+            bool final_vh_is_same = true;
+            for (CellId cell_id = 0; cell_id < final_mesh_2d->numberOfCells(); ++cell_id) {
+              if (final_fh[cell_id] != 2 * dot(final_xj[cell_id], final_xj[cell_id])) {
+                final_fh_is_same = false;
+              }
+
+              if (final_vh[cell_id][0] != 3 * final_xj[cell_id][0] - 1) {
+                final_vh_is_same = false;
+              }
+              if (final_vh[cell_id][1] != std::sin(final_xj[cell_id][0] + final_xj[cell_id][1])) {
+                final_vh_is_same = false;
+              }
+              if (final_vh[cell_id][2] != -2 * final_xj[cell_id][0] * final_xj[cell_id][1] + 4) {
+                final_vh_is_same = false;
+              }
+            }
+
+            REQUIRE(final_fh_is_same);
+            REQUIRE(final_vh_is_same);
+          }
+        }
+      }
+    }
+  }
+
+  SECTION("3D")
+  {
+    for (auto&& named_mesh : MeshDataBaseForTests::get().all3DMeshes()) {
+      std::shared_ptr<const Mesh<3>> initial_mesh_3d = named_mesh.mesh()->get<Mesh<3>>();
+      SECTION(named_mesh.name())
+      {
+        DiscreteFunctionP0<double> initial_fh{initial_mesh_3d};
+        DiscreteFunctionP0Vector<double> initial_vh{initial_mesh_3d, 3};
+
+        {
+          auto initial_xj = MeshDataManager::instance().getMeshData(*initial_mesh_3d).xj();
+
+          for (CellId cell_id = 0; cell_id < initial_mesh_3d->numberOfCells(); ++cell_id) {
+            initial_fh[cell_id] = 2 * dot(initial_xj[cell_id], initial_xj[cell_id]);
+
+            initial_vh[cell_id][0] = 3 * initial_xj[cell_id][0] - 1;
+            initial_vh[cell_id][1] = std::sin(initial_xj[cell_id][1] + initial_xj[cell_id][2]);
+            initial_vh[cell_id][2] = -2 * initial_xj[cell_id][0] * initial_xj[cell_id][1] + 4;
+          }
+        }
+
+        LoadBalancer load_balancer;
+        auto balanced_discrete_function_list =
+          load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                 std::make_shared<DiscreteFunctionVariant>(initial_vh)});
+
+        REQUIRE(balanced_discrete_function_list.size() == 2);
+
+        DiscreteFunctionP0<const double> final_fh =
+          balanced_discrete_function_list[0]->get<DiscreteFunctionP0<const double>>();
+        DiscreteFunctionP0Vector<const double> final_vh =
+          balanced_discrete_function_list[1]->get<DiscreteFunctionP0Vector<const double>>();
+
+        REQUIRE(final_vh.size() == 3);
+        {
+          auto final_mesh_3d_v = final_fh.meshVariant();
+          auto final_mesh_3d   = final_mesh_3d_v->get<Mesh<3>>();
+
+          REQUIRE(final_mesh_3d->id() > initial_mesh_3d->id());
+
+          REQUIRE_THROWS_WITH(load_balancer.balance({std::make_shared<DiscreteFunctionVariant>(initial_fh),
+                                                     std::make_shared<DiscreteFunctionVariant>(final_fh)}),
+                              "error: discrete functions are not defined on the same mesh");
+
+          {
+            auto final_xj = MeshDataManager::instance().getMeshData(*final_mesh_3d).xj();
+
+            bool final_fh_is_same = true;
+            bool final_vh_is_same = true;
+            for (CellId cell_id = 0; cell_id < final_mesh_3d->numberOfCells(); ++cell_id) {
+              if (final_fh[cell_id] != 2 * dot(final_xj[cell_id], final_xj[cell_id])) {
+                final_fh_is_same = false;
+              }
+
+              if (final_vh[cell_id][0] != 3 * final_xj[cell_id][0] - 1) {
+                final_vh_is_same = false;
+              }
+              if (final_vh[cell_id][1] != std::sin(final_xj[cell_id][1] + final_xj[cell_id][2])) {
+                final_vh_is_same = false;
+              }
+              if (final_vh[cell_id][2] != -2 * final_xj[cell_id][0] * final_xj[cell_id][1] + 4) {
+                final_vh_is_same = false;
+              }
+            }
+
+            REQUIRE(final_fh_is_same);
+            REQUIRE(final_vh_is_same);
+          }
+        }
+      }
+    }
+  }
+}
-- 
GitLab