#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>

#include <MeshDataBaseForTests.hpp>
#include <scheme/DiscreteFunctionP0Vector.hpp>

// clazy:excludeall=non-pod-global-static

TEST_CASE("DiscreteFunctionP0Vector", "[scheme]")
{
  auto same_values = [](const auto& f, const auto& g) {
    const size_t number_of_cells = f.cellArrays().numberOfItems();
    const size_t size_of_arrays  = f.cellArrays().sizeOfArrays();
    for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
      for (size_t i = 0; i < size_of_arrays; ++i) {
        if (f[cell_id][i] != g[cell_id][i]) {
          return false;
        }
      }
    }
    return true;
  };

  SECTION("constructors")
  {
    SECTION("1D")
    {
      const size_t size = 3;

      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
      constexpr size_t Dimension = 1;

      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
      REQUIRE(f.size() == size);

      REQUIRE(f.mesh().get() == mesh.get());

      DiscreteFunctionP0Vector g{f};
      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
      REQUIRE(g.size() == size);

      CellArray<double> h_arrays{mesh->connectivity(), size};
      h_arrays.fill(0);

      DiscreteFunctionP0Vector zero{mesh, [&] {
                                      CellArray<double> cell_array{mesh->connectivity(), size};
                                      cell_array.fill(0);
                                      return cell_array;
                                    }()};

      DiscreteFunctionP0Vector h{mesh, h_arrays};
      REQUIRE(same_values(h, zero));
      REQUIRE(same_values(h, h_arrays));

      h_arrays.fill(1);

      REQUIRE(same_values(h, h_arrays));
      REQUIRE(not same_values(h, zero));

      DiscreteFunctionP0Vector moved_h{std::move(h)};
      REQUIRE(same_values(moved_h, h_arrays));
    }

    SECTION("2D")
    {
      const size_t size = 3;

      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
      constexpr size_t Dimension = 2;

      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
      REQUIRE(f.size() == size);

      REQUIRE(f.mesh().get() == mesh.get());

      DiscreteFunctionP0Vector g{f};
      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
      REQUIRE(g.size() == size);

      CellArray<double> h_arrays{mesh->connectivity(), size};
      h_arrays.fill(0);

      DiscreteFunctionP0Vector zero{mesh, [&] {
                                      CellArray<double> cell_array{mesh->connectivity(), size};
                                      cell_array.fill(0);
                                      return cell_array;
                                    }()};

      DiscreteFunctionP0Vector h{mesh, h_arrays};
      REQUIRE(same_values(h, zero));
      REQUIRE(same_values(h, h_arrays));

      h_arrays.fill(1);

      REQUIRE(same_values(h, h_arrays));
      REQUIRE(not same_values(h, zero));

      DiscreteFunctionP0Vector moved_h{std::move(h)};
      REQUIRE(same_values(moved_h, h_arrays));
    }

    SECTION("3D")
    {
      const size_t size = 2;

      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
      constexpr size_t Dimension = 3;

      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
      REQUIRE(f.dataType() == ASTNodeDataType::double_t);
      REQUIRE(f.descriptor().type() == DiscreteFunctionType::P0Vector);
      REQUIRE(f.size() == size);

      REQUIRE(f.mesh().get() == mesh.get());

      DiscreteFunctionP0Vector g{f};
      REQUIRE(g.dataType() == ASTNodeDataType::double_t);
      REQUIRE(g.descriptor().type() == DiscreteFunctionType::P0Vector);
      REQUIRE(g.size() == size);

      CellArray<double> h_arrays{mesh->connectivity(), size};
      h_arrays.fill(0);

      DiscreteFunctionP0Vector zero{mesh, [&] {
                                      CellArray<double> cell_array{mesh->connectivity(), size};
                                      cell_array.fill(0);
                                      return cell_array;
                                    }()};

      DiscreteFunctionP0Vector h{mesh, h_arrays};
      REQUIRE(same_values(h, zero));
      REQUIRE(same_values(h, h_arrays));

      h_arrays.fill(1);

      REQUIRE(same_values(h, h_arrays));
      REQUIRE(not same_values(h, zero));

      DiscreteFunctionP0Vector moved_h{std::move(h)};
      REQUIRE(same_values(moved_h, h_arrays));
    }
  }

  SECTION("fill")
  {
    auto all_values_equal = [](const auto& f, const auto& g) {
      const size_t number_of_cells = f.cellArrays().numberOfItems();
      size_t size_of_arrays        = f.cellArrays().sizeOfArrays();
      for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
        for (size_t i = 0; i < size_of_arrays; ++i) {
          if (f[cell_id][i] != g) {
            return false;
          }
        }
      }
      return true;
    };

    SECTION("1D")
    {
      const size_t size = 3;

      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh1D();
      constexpr size_t Dimension = 1;

      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
      f.fill(3);

      REQUIRE(all_values_equal(f, 3));
    }

    SECTION("2D")
    {
      const size_t size = 3;

      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh2D();
      constexpr size_t Dimension = 2;

      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
      f.fill(2.3);

      REQUIRE(all_values_equal(f, 2.3));
    }

    SECTION("3D")
    {
      const size_t size = 2;

      std::shared_ptr mesh       = MeshDataBaseForTests::get().cartesianMesh3D();
      constexpr size_t Dimension = 3;

      DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
      f.fill(3.2);

      REQUIRE(all_values_equal(f, 3.2));
    }
  }

  SECTION("copies")
  {
    auto all_values_equal = [](const auto& f, const auto& g) {
      const size_t number_of_cells = f.cellArrays().numberOfItems();
      const size_t size_of_arrays  = f.cellArrays().sizeOfArrays();

      for (CellId cell_id = 0; cell_id < number_of_cells; ++cell_id) {
        for (size_t i = 0; i < size_of_arrays; ++i) {
          if (f[cell_id][i] != g) {
            return false;
          }
        }
      }
      return true;
    };

    SECTION("1D")
    {
      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();

      constexpr size_t Dimension = 1;

      const size_t size  = 3;
      const size_t value = parallel::rank() + 1;
      const size_t zero  = 0;

      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
      f.fill(value);

      REQUIRE(all_values_equal(f, value));

      DiscreteFunctionP0Vector g = copy(f);
      f.fill(zero);

      REQUIRE(all_values_equal(f, zero));
      REQUIRE(all_values_equal(g, value));

      copy_to(g, f);
      g.fill(zero);

      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);

      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
      shallow_g = g;

      REQUIRE(all_values_equal(f, value));
      REQUIRE(all_values_equal(g, zero));
      REQUIRE(all_values_equal(shallow_g, zero));
      REQUIRE(all_values_equal(h, value));

      copy_to(h, g);

      REQUIRE(all_values_equal(g, value));
      REQUIRE(all_values_equal(shallow_g, value));
    }

    SECTION("2D")
    {
      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();

      constexpr size_t Dimension = 2;

      const size_t size  = 3;
      const size_t value = parallel::rank() + 1;
      const size_t zero  = 0;

      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
      f.fill(value);

      REQUIRE(all_values_equal(f, value));

      DiscreteFunctionP0Vector g = copy(f);
      f.fill(zero);

      REQUIRE(all_values_equal(f, zero));
      REQUIRE(all_values_equal(g, value));

      copy_to(g, f);
      g.fill(zero);

      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);

      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
      shallow_g = g;

      REQUIRE(all_values_equal(f, value));
      REQUIRE(all_values_equal(g, zero));
      REQUIRE(all_values_equal(shallow_g, zero));
      REQUIRE(all_values_equal(h, value));

      copy_to(h, g);

      REQUIRE(all_values_equal(g, value));
      REQUIRE(all_values_equal(shallow_g, value));
    }

    SECTION("3D")
    {
      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();

      constexpr size_t Dimension = 3;

      const size_t size  = 3;
      const size_t value = parallel::rank() + 1;
      const size_t zero  = 0;

      DiscreteFunctionP0Vector<Dimension, size_t> f{mesh, size};
      f.fill(value);

      REQUIRE(all_values_equal(f, value));

      DiscreteFunctionP0Vector g = copy(f);
      f.fill(zero);

      REQUIRE(all_values_equal(f, zero));
      REQUIRE(all_values_equal(g, value));

      copy_to(g, f);
      g.fill(zero);

      DiscreteFunctionP0Vector<Dimension, const size_t> h = copy(f);

      DiscreteFunctionP0Vector<Dimension, size_t> shallow_g{mesh, size};
      shallow_g = g;

      REQUIRE(all_values_equal(f, value));
      REQUIRE(all_values_equal(g, zero));
      REQUIRE(all_values_equal(h, value));

      copy_to(h, g);

      REQUIRE(all_values_equal(g, value));
      REQUIRE(all_values_equal(shallow_g, value));
    }
  }

  SECTION("unary operators")
  {
    SECTION("1D")
    {
      const size_t size    = 3;
      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();

      constexpr size_t Dimension = 1;

      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();

      SECTION("unary minus")
      {
        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            const double x = xj[cell_id][0];
            for (size_t i = 0; i < size; ++i) {
              f[cell_id][i] = 2 * x + i;
            }
          });

        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;

        Table<double> minus_values{mesh->numberOfCells(), size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            for (size_t i = 0; i < size; ++i) {
              minus_values[cell_id][i] = -f[cell_id][i];
            }
          });

        REQUIRE(same_values(-f, minus_values));
        REQUIRE(same_values(-const_f, minus_values));
      }
    }

    SECTION("2D")
    {
      const size_t size    = 3;
      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();

      constexpr size_t Dimension = 2;

      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();

      SECTION("unary minus")
      {
        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            const double x = xj[cell_id][0];
            const double y = xj[cell_id][1];
            for (size_t i = 0; i < size; ++i) {
              f[cell_id][i] = 2 * x + i * y;
            }
          });

        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;

        Table<double> minus_values{mesh->numberOfCells(), size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            for (size_t i = 0; i < size; ++i) {
              minus_values[cell_id][i] = -f[cell_id][i];
            }
          });

        REQUIRE(same_values(-f, minus_values));
        REQUIRE(same_values(-const_f, minus_values));
      }
    }

    SECTION("3D")
    {
      const size_t size    = 2;
      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();

      constexpr size_t Dimension = 3;

      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();

      SECTION("unary minus")
      {
        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            const double x = xj[cell_id][0];
            const double y = xj[cell_id][1];
            const double z = xj[cell_id][2];
            for (size_t i = 0; i < size; ++i) {
              f[cell_id][i] = 2 * x + i * y - z;
            }
          });

        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;

        Table<double> minus_values{mesh->numberOfCells(), size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            for (size_t i = 0; i < size; ++i) {
              minus_values[cell_id][i] = -f[cell_id][i];
            }
          });

        REQUIRE(same_values(-f, minus_values));
        REQUIRE(same_values(-const_f, minus_values));
      }
    }
  }

  SECTION("binary operators")
  {
    SECTION("1D")
    {
      const size_t size = 3;

      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh1D();

      constexpr size_t Dimension = 1;

      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();

      SECTION("inner operators")
      {
        SECTION("scalar functions")
        {
          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const double x = xj[cell_id][0];
              f[cell_id][0]  = 2 * x + 1;
              f[cell_id][1]  = x * x - 1;
              f[cell_id][2]  = 2 + x;
            });

          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const double x = xj[cell_id][0];
              g[cell_id][0]  = (x + 1) * (x - 2) + 1;
              g[cell_id][1]  = 3 * (x + 2) - 1;
              g[cell_id][2]  = (x + 3) * 5;
            });

          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};

          SECTION("sum")
          {
            Table<double> sum_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
                }
              });

            REQUIRE(same_values(f + g, sum_values));
            REQUIRE(same_values(const_f + g, sum_values));
            REQUIRE(same_values(f + const_g, sum_values));
            REQUIRE(same_values(const_f + const_g, sum_values));
          }

          SECTION("difference")
          {
            Table<double> difference_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
                }
              });

            REQUIRE(same_values(f - g, difference_values));
            REQUIRE(same_values(const_f - g, difference_values));
            REQUIRE(same_values(f - const_g, difference_values));
            REQUIRE(same_values(const_f - const_g, difference_values));
          }
        }
      }

      SECTION("external operators")
      {
        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            const double x = xj[cell_id][0];
            for (size_t i = 0; i < size; ++i) {
              f[cell_id][i] = std::abs(2 * x) + i;
            }
          });

        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;

        SECTION("product")
        {
          SECTION("scalar lhs")
          {
            const double a = 3.2;
            Table<double> product_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  product_values[cell_id][i] = a * f[cell_id][i];
                }
              });

            REQUIRE(same_values(a * f, product_values));
            REQUIRE(same_values(a * const_f, product_values));
          }

          SECTION("DiscreteFunctionP0 lhs")
          {
            DiscreteFunctionP0<Dimension, double> a{mesh};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                const double x = xj[cell_id][0];
                a[cell_id]     = 2 * x + 1;
              });

            Table<double> product_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
                }
              });

            REQUIRE(same_values(a * f, product_values));
            REQUIRE(same_values(a * const_f, product_values));

            DiscreteFunctionP0<Dimension, const double> const_a = a;
            REQUIRE(same_values(const_a * f, product_values));
            REQUIRE(same_values(const_a * const_f, product_values));
          }
        }
      }
    }

    SECTION("2D")
    {
      const size_t size = 3;

      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh2D();

      constexpr size_t Dimension = 2;

      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();

      SECTION("inner operators")
      {
        SECTION("scalar functions")
        {
          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const double x = xj[cell_id][0];
              const double y = xj[cell_id][1];
              f[cell_id][0]  = 2 * x + 1;
              f[cell_id][1]  = x * x - y;
              f[cell_id][2]  = 2 + x * y;
            });

          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const double x = xj[cell_id][0];
              const double y = xj[cell_id][1];
              g[cell_id][0]  = (x + 1) * (y - 2) + 1;
              g[cell_id][1]  = 3 * (x + 2) - y;
              g[cell_id][2]  = (x + 3) + 5 * y;
            });

          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};

          SECTION("sum")
          {
            Table<double> sum_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
                }
              });

            REQUIRE(same_values(f + g, sum_values));
            REQUIRE(same_values(const_f + g, sum_values));
            REQUIRE(same_values(f + const_g, sum_values));
            REQUIRE(same_values(const_f + const_g, sum_values));
          }

          SECTION("difference")
          {
            Table<double> difference_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
                }
              });

            REQUIRE(same_values(f - g, difference_values));
            REQUIRE(same_values(const_f - g, difference_values));
            REQUIRE(same_values(f - const_g, difference_values));
            REQUIRE(same_values(const_f - const_g, difference_values));
          }
        }
      }

      SECTION("external operators")
      {
        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            const double x = xj[cell_id][0];
            const double y = xj[cell_id][1];
            for (size_t i = 0; i < size; ++i) {
              f[cell_id][i] = std::abs(2 * x) + i * y;
            }
          });

        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;

        SECTION("product")
        {
          SECTION("scalar lhs")
          {
            const double a = 3.2;
            Table<double> product_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  product_values[cell_id][i] = a * f[cell_id][i];
                }
              });

            REQUIRE(same_values(a * f, product_values));
            REQUIRE(same_values(a * const_f, product_values));
          }

          SECTION("DiscreteFunctionP0 lhs")
          {
            DiscreteFunctionP0<Dimension, double> a{mesh};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                const double x = xj[cell_id][0];
                const double y = xj[cell_id][1];
                a[cell_id]     = 2 * x + 1 - y;
              });

            Table<double> product_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
                }
              });

            REQUIRE(same_values(a * f, product_values));
            REQUIRE(same_values(a * const_f, product_values));

            DiscreteFunctionP0<Dimension, const double> const_a = a;
            REQUIRE(same_values(const_a * f, product_values));
            REQUIRE(same_values(const_a * const_f, product_values));
          }
        }
      }
    }

    SECTION("3D")
    {
      const size_t size = 2;

      std::shared_ptr mesh = MeshDataBaseForTests::get().cartesianMesh3D();

      constexpr size_t Dimension = 3;

      auto xj = MeshDataManager::instance().getMeshData(*mesh).xj();

      SECTION("inner operators")
      {
        SECTION("scalar functions")
        {
          DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const double x = xj[cell_id][0];
              const double y = xj[cell_id][1];
              const double z = xj[cell_id][2];
              f[cell_id][0]  = 2 * x * z + 1;
              f[cell_id][1]  = x * z - y;
            });

          DiscreteFunctionP0Vector<Dimension, double> g{mesh, size};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const double x = xj[cell_id][0];
              const double y = xj[cell_id][1];
              const double z = xj[cell_id][2];
              g[cell_id][0]  = (x + 1) * (y - 2) + 1 - z;
              g[cell_id][1]  = 3 * (x + 2) - y * z;
            });

          DiscreteFunctionP0Vector<Dimension, const double> const_f = f;
          DiscreteFunctionP0Vector<Dimension, const double> const_g{g};

          SECTION("sum")
          {
            Table<double> sum_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  sum_values[cell_id][i] = f[cell_id][i] + g[cell_id][i];
                }
              });

            REQUIRE(same_values(f + g, sum_values));
            REQUIRE(same_values(const_f + g, sum_values));
            REQUIRE(same_values(f + const_g, sum_values));
            REQUIRE(same_values(const_f + const_g, sum_values));
          }

          SECTION("difference")
          {
            Table<double> difference_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  difference_values[cell_id][i] = f[cell_id][i] - g[cell_id][i];
                }
              });

            REQUIRE(same_values(f - g, difference_values));
            REQUIRE(same_values(const_f - g, difference_values));
            REQUIRE(same_values(f - const_g, difference_values));
            REQUIRE(same_values(const_f - const_g, difference_values));
          }
        }
      }

      SECTION("external operators")
      {
        DiscreteFunctionP0Vector<Dimension, double> f{mesh, size};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            const double x = xj[cell_id][0];
            const double y = xj[cell_id][1];
            const double z = xj[cell_id][2];
            for (size_t i = 0; i < size; ++i) {
              f[cell_id][i] = std::abs(2 * x) + i * y + z;
            }
          });

        DiscreteFunctionP0Vector<Dimension, const double> const_f = f;

        SECTION("product")
        {
          SECTION("scalar lhs")
          {
            const double a = 3.2;
            Table<double> product_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  product_values[cell_id][i] = a * f[cell_id][i];
                }
              });

            REQUIRE(same_values(a * f, product_values));
            REQUIRE(same_values(a * const_f, product_values));
          }

          SECTION("DiscreteFunctionP0 lhs")
          {
            DiscreteFunctionP0<Dimension, double> a{mesh};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                const double x = xj[cell_id][0];
                const double y = xj[cell_id][1];
                const double z = xj[cell_id][2];
                a[cell_id]     = 2 * x + 1 - y * z;
              });

            Table<double> product_values{mesh->numberOfCells(), size};
            parallel_for(
              mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
                for (size_t i = 0; i < size; ++i) {
                  product_values[cell_id][i] = a[cell_id] * f[cell_id][i];
                }
              });

            REQUIRE(same_values(a * f, product_values));
            REQUIRE(same_values(a * const_f, product_values));

            DiscreteFunctionP0<Dimension, const double> const_a = a;
            REQUIRE(same_values(const_a * f, product_values));
            REQUIRE(same_values(const_a * const_f, product_values));
          }
        }
      }
    }
  }
}