Files
MeanField/tests/field/field_dof_map.cpp
Emily Boudreaux 36adfa1174 feat(FieldDofMap): Completed FieldDofMap migration
also removed legacy BarotropicPolytrope implementation
2026-08-29 08:56:36 -04:00

985 lines
30 KiB
C++

#include <algorithm>
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <cmath>
#include <cstddef>
#include <mfem.hpp>
#include <mpi.h>
#include <optional>
#include <stdexcept>
#include <vector>
import mean_field;
import test_helpers;
namespace field_dof_map_test_utils {
namespace field = mean_field::field;
namespace domain = mean_field::utils::domain;
using Schema = domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]]
mfem::Array<int> make_array(const std::initializer_list<int> values) {
mfem::Array<int> result(static_cast<int>(values.size()));
int index = 0;
for (const int value : values) {
result[index++] = value;
}
return result;
}
[[nodiscard]]
mfem::Mesh make_split_mesh(
const int stellarAttribute = 2,
const int vacuumAttribute = 3
) {
int communicatorSize = 1;
MPI_Comm_size(MPI_COMM_WORLD, &communicatorSize);
/*
* Ensure there are enough cells that every reasonable MPI test
* configuration has useful work available.
*/
const int xElementCount = std::max(4, 2 * communicatorSize);
constexpr int yElementCount = 2;
mfem::Mesh mesh = mfem::Mesh::MakeCartesian2D(
xElementCount, yElementCount, mfem::Element::QUADRILATERAL, true, static_cast<double>(xElementCount),
static_cast<double>(yElementCount)
);
for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) {
const int xIndex = elementId % xElementCount;
mesh.GetElement(elementId)->SetAttribute(xIndex < xElementCount / 2 ? stellarAttribute : vacuumAttribute);
}
mesh.SetAttributes();
return mesh;
}
[[nodiscard]]
long long global_sum(const int localValue) {
const long long local = static_cast<long long>(localValue);
long long global = 0;
MPI_Allreduce(&local, &global, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD);
return global;
}
template <typename FieldT>
concept CanMakeFieldDofMap =
requires(const mfem::ParFiniteElementSpace &space) { field::make_field_dof_map<FieldT, Schema>(space); };
template <typename FieldT>
concept CanMakeFieldDofGridFunctionAdapter = requires(const mfem::ParFiniteElementSpace &space) {
field::make_field_dof_grid_function_adapter<FieldT, Schema>(space);
};
using AlternateSchema = domain::DomainSchema<
domain::MaterialList<
domain::Material<domain::Core, 11>,
domain::Material<domain::Envelope, 17>,
domain::Material<domain::Vacuum, 29>>,
domain::BoundaryList<>,
domain::RelationList<>>;
} // namespace field_dof_map_test_utils
TEST_CASE(
"Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const mfem::Array<int> active = field_dof_map_test_utils::make_array({0, 2, 5, 7});
const field::FieldDofMap map(9, active);
CHECK(map.full_size() == 9);
CHECK(map.reduced_size() == 4);
CHECK(map.inactive_size() == 5);
CHECK_FALSE(map.is_identity());
CHECK(map.true_dof(0) == 0);
CHECK(map.true_dof(1) == 2);
CHECK(map.true_dof(2) == 5);
CHECK(map.true_dof(3) == 7);
REQUIRE(map.reduced_dof(0).has_value());
REQUIRE(map.reduced_dof(2).has_value());
REQUIRE(map.reduced_dof(5).has_value());
REQUIRE(map.reduced_dof(7).has_value());
CHECK(*map.reduced_dof(0) == 0);
CHECK(*map.reduced_dof(2) == 1);
CHECK(*map.reduced_dof(5) == 2);
CHECK(*map.reduced_dof(7) == 3);
CHECK_FALSE(map.reduced_dof(1).has_value());
CHECK_FALSE(map.reduced_dof(3).has_value());
CHECK(map.contains_true_dof(0));
CHECK(map.contains_true_dof(2));
CHECK_FALSE(map.contains_true_dof(1));
const mfem::Array<int> &forward = map.reduced_to_true();
const mfem::Array<int> &inverse = map.true_to_reduced();
REQUIRE(forward.Size() == 4);
REQUIRE(inverse.Size() == 9);
CHECK(forward[0] == 0);
CHECK(forward[1] == 2);
CHECK(forward[2] == 5);
CHECK(forward[3] == 7);
CHECK(inverse[0] == 0);
CHECK(inverse[1] == -1);
CHECK(inverse[2] == 1);
CHECK(inverse[3] == -1);
CHECK(inverse[4] == -1);
CHECK(inverse[5] == 2);
CHECK(inverse[6] == -1);
CHECK(inverse[7] == 3);
CHECK(inverse[8] == -1);
}
TEST_CASE(
"Field DOF Map Rejects Invalid Canonical Mappings",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const mfem::Array<int> empty;
CHECK_THROWS_AS((field::FieldDofMap(-1, empty)), std::invalid_argument);
CHECK_THROWS_AS((field::FieldDofMap(4, field_dof_map_test_utils::make_array({-1, 2}))), std::invalid_argument);
CHECK_THROWS_AS((field::FieldDofMap(4, field_dof_map_test_utils::make_array({1, 4}))), std::invalid_argument);
/*
* Duplicate true DOF.
*/
CHECK_THROWS_AS((field::FieldDofMap(5, field_dof_map_test_utils::make_array({1, 1, 3}))), std::invalid_argument);
/*
* Non-canonical unsorted ordering.
*/
CHECK_THROWS_AS((field::FieldDofMap(5, field_dof_map_test_utils::make_array({1, 3, 2}))), std::invalid_argument);
}
TEST_CASE(
"Field DOF Map Rejects Out Of Range Index Queries",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({1, 3}));
CHECK_THROWS_AS(map.true_dof(-1), std::out_of_range);
CHECK_THROWS_AS(map.true_dof(2), std::out_of_range);
CHECK_THROWS_AS(map.reduced_dof(-1), std::out_of_range);
CHECK_THROWS_AS(map.reduced_dof(5), std::out_of_range);
CHECK_THROWS_AS(map.contains_true_dof(-1), std::out_of_range);
CHECK_THROWS_AS(map.contains_true_dof(5), std::out_of_range);
}
TEST_CASE(
"Field DOF Map Gather Selects Exactly The Active True DOFs",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(6, field_dof_map_test_utils::make_array({1, 3, 5}));
mfem::Vector full(6);
for (int trueDof = 0; trueDof < full.Size(); ++trueDof) {
full(trueDof) = 10.0 + static_cast<double>(trueDof);
}
const mfem::Vector reduced = map.gather(full);
REQUIRE(reduced.Size() == 3);
CHECK(reduced(0) == 11.0);
CHECK(reduced(1) == 13.0);
CHECK(reduced(2) == 15.0);
mfem::Vector output(3);
map.gather(full, output);
CHECK(output(0) == 11.0);
CHECK(output(1) == 13.0);
CHECK(output(2) == 15.0);
}
TEST_CASE(
"Field DOF Map Scatter Produces The Canonical Supported Projection",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(6, field_dof_map_test_utils::make_array({1, 3, 5}));
mfem::Vector reduced(3);
reduced(0) = 2.0;
reduced(1) = 4.0;
reduced(2) = 6.0;
const mfem::Vector full = map.scatter(reduced);
REQUIRE(full.Size() == 6);
CHECK(full(0) == 0.0);
CHECK(full(1) == 2.0);
CHECK(full(2) == 0.0);
CHECK(full(3) == 4.0);
CHECK(full(4) == 0.0);
CHECK(full(5) == 6.0);
const mfem::Vector roundTrip = map.gather(full);
REQUIRE(roundTrip.Size() == reduced.Size());
for (int index = 0; index < reduced.Size(); ++index) {
CHECK(roundTrip(index) == reduced(index));
}
}
TEST_CASE(
"Field DOF Map Gather Scatter Projects A Full Vector Onto Field Support",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(7, field_dof_map_test_utils::make_array({0, 2, 3, 6}));
mfem::Vector original(7);
for (int index = 0; index < original.Size(); ++index) {
original(index) = 0.25 + static_cast<double>(index);
}
const mfem::Vector reduced = map.gather(original);
const mfem::Vector projected = map.scatter(reduced);
for (int trueDof = 0; trueDof < original.Size(); ++trueDof) {
CAPTURE(trueDof);
if (map.contains_true_dof(trueDof)) {
CHECK(projected(trueDof) == original(trueDof));
} else {
CHECK(projected(trueDof) == 0.0);
}
}
}
TEST_CASE(
"Field DOF Map Scatter Into Preserves Unsupported True DOFs",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(6, field_dof_map_test_utils::make_array({1, 4}));
mfem::Vector reduced(2);
reduced(0) = 7.0;
reduced(1) = 9.0;
mfem::Vector full(6);
full = -3.0;
map.scatter_into(reduced, full);
CHECK(full(0) == -3.0);
CHECK(full(1) == 7.0);
CHECK(full(2) == -3.0);
CHECK(full(3) == -3.0);
CHECK(full(4) == 9.0);
CHECK(full(5) == -3.0);
}
TEST_CASE(
"Field DOF Map Scatter Add Accumulates Only Onto Active True DOFs",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({0, 2, 4}));
mfem::Vector reduced(3);
reduced(0) = 1.0;
reduced(1) = 2.0;
reduced(2) = 3.0;
mfem::Vector full(5);
full = 10.0;
map.scatter_add(reduced, full, 2.0);
CHECK(full(0) == 12.0);
CHECK(full(1) == 10.0);
CHECK(full(2) == 14.0);
CHECK(full(3) == 10.0);
CHECK(full(4) == 16.0);
}
TEST_CASE(
"Field DOF Map Operations Support MFEM Vector Views Without Resizing",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({1, 3}));
mfem::Vector storage(9);
storage = -8.0;
/*
* View [2, 7) of the parent vector.
*/
mfem::Vector fullView(storage.GetData() + 2, 5);
mfem::Vector reduced(2);
reduced(0) = 4.0;
reduced(1) = 6.0;
map.scatter_into(reduced, fullView);
/*
* Storage outside the view must remain untouched.
*/
CHECK(storage(0) == -8.0);
CHECK(storage(1) == -8.0);
CHECK(storage(7) == -8.0);
CHECK(storage(8) == -8.0);
/*
* Within the view, only active true DOFs change.
*/
CHECK(storage(2) == -8.0);
CHECK(storage(3) == 4.0);
CHECK(storage(4) == -8.0);
CHECK(storage(5) == 6.0);
CHECK(storage(6) == -8.0);
}
TEST_CASE(
"Field DOF Map Operations Reject Incompatible Vector Sizes",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(5, field_dof_map_test_utils::make_array({1, 3}));
mfem::Vector correctFull(5);
mfem::Vector wrongFull(4);
mfem::Vector correctReduced(2);
mfem::Vector wrongReduced(3);
CHECK_THROWS_AS(map.gather(wrongFull), std::invalid_argument);
CHECK_THROWS_AS(map.gather(correctFull, wrongReduced), std::invalid_argument);
CHECK_THROWS_AS(map.scatter(wrongReduced), std::invalid_argument);
CHECK_THROWS_AS(map.scatter(correctReduced, wrongFull), std::invalid_argument);
CHECK_THROWS_AS(map.scatter_into(wrongReduced, correctFull), std::invalid_argument);
CHECK_THROWS_AS(map.scatter_add(correctReduced, wrongFull), std::invalid_argument);
}
TEST_CASE(
"Field DOF Map Identity Mapping Is An Exact Vector Identity",
tags::field_dof_unit
) {
namespace field = mean_field::field;
const field::FieldDofMap map(4, field_dof_map_test_utils::make_array({0, 1, 2, 3}));
REQUIRE(map.is_identity());
REQUIRE(map.inactive_size() == 0);
mfem::Vector full(4);
full(0) = 0.1;
full(1) = -0.2;
full(2) = 3.7;
full(3) = 8.1;
const mfem::Vector reduced = map.gather(full);
const mfem::Vector restored = map.scatter(reduced);
for (int index = 0; index < full.Size(); ++index) {
CHECK(reduced(index) == full(index));
CHECK(restored(index) == full(index));
}
}
TEST_CASE(
"Field DOF Map Validates Field DOF Support Consistency",
tags::field_dof_unit
) {
namespace field = mean_field::field;
field::FieldDofSupport support;
support.activeTrueDofMarker.SetSize(5);
support.activeTrueDofMarker = 0;
support.activeTrueDofMarker[1] = 1;
support.activeTrueDofMarker[3] = 1;
support.activeTrueDofs = field_dof_map_test_utils::make_array({1, 3});
const field::FieldDofMap validMap(support);
CHECK(validMap.full_size() == 5);
CHECK(validMap.reduced_size() == 2);
/*
* Make the marker disagree with the list.
*/
support.activeTrueDofMarker[3] = 0;
CHECK_THROWS_AS((field::FieldDofMap(support)), std::invalid_argument);
}
TEST_CASE(
"Field DOF Map Factory Is Available Only For Spatial Registered Fields",
tags::field_dof_unit
) {
namespace field = mean_field::field;
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap<field::Density>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap<field::Enthalpy>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap<field::Gravity>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofMap<field::Displacement>);
STATIC_REQUIRE_FALSE(field_dof_map_test_utils::CanMakeFieldDofMap<field::BarotropicConstant>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Density>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Enthalpy>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Gravity>);
STATIC_REQUIRE(field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::Displacement>);
STATIC_REQUIRE_FALSE(
field_dof_map_test_utils::CanMakeFieldDofGridFunctionAdapter<field::BarotropicConstant>
);
CHECK(true);
}
TEST_CASE(
"Field DOF Map Factory Exactly Preserves Density Support",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofSupport support =
field::resolve_field_dof_support<field::Density, field_dof_map_test_utils::Schema>(*finiteElementSpace);
const field::FieldDofMap map =
field::make_field_dof_map<field::Density, field_dof_map_test_utils::Schema>(*finiteElementSpace);
REQUIRE(map.full_size() == finiteElementSpace->GetTrueVSize());
REQUIRE(map.reduced_size() == support.activeTrueDofs.Size());
REQUIRE(map.full_size() == support.activeTrueDofMarker.Size());
for (int reducedDof = 0; reducedDof < map.reduced_size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(map.true_dof(reducedDof) == support.activeTrueDofs[reducedDof]);
}
for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(map.contains_true_dof(trueDof) == (support.activeTrueDofMarker[trueDof] != 0));
}
const long long globalFullSize = field_dof_map_test_utils::global_sum(map.full_size());
const long long globalReducedSize = field_dof_map_test_utils::global_sum(map.reduced_size());
/*
* L2 density has independent vacuum element DOFs, so removing vacuum
* support must genuinely reduce the global nonlinear block.
*/
CHECK(globalReducedSize > 0);
CHECK(globalReducedSize < globalFullSize);
}
TEST_CASE(
"Field DOF Map Factory Exactly Preserves H1 Enthalpy Support",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace = field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofSupport support =
field::resolve_field_dof_support<field::Enthalpy, field_dof_map_test_utils::Schema>(*finiteElementSpace);
const field::FieldDofMap map =
field::make_field_dof_map<field::Enthalpy, field_dof_map_test_utils::Schema>(*finiteElementSpace);
REQUIRE(map.reduced_size() == support.activeTrueDofs.Size());
for (int reducedDof = 0; reducedDof < map.reduced_size(); ++reducedDof) {
CHECK(map.true_dof(reducedDof) == support.activeTrueDofs[reducedDof]);
}
/*
* The separate field_mfem support tests already establish that shared
* Stellar/Vacuum H1 trace DOFs are active. This test establishes that
* FieldDofMap preserves that active set exactly, rather than applying
* a second reduction or reinterpretation.
*/
for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) {
CHECK(map.contains_true_dof(trueDof) == (support.activeTrueDofMarker[trueDof] != 0));
}
const long long globalFullSize = field_dof_map_test_utils::global_sum(map.full_size());
const long long globalReducedSize = field_dof_map_test_utils::global_sum(map.reduced_size());
CHECK(globalReducedSize > 0);
CHECK(globalReducedSize < globalFullSize);
}
TEST_CASE(
"Field DOF Map Factory Produces Identity Maps For All Supported Fields",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
auto finiteElementSpace = field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofMap map =
field::make_field_dof_map<field::Displacement, field_dof_map_test_utils::Schema>(*finiteElementSpace);
CHECK(map.is_identity());
CHECK(map.full_size() == finiteElementSpace->GetTrueVSize());
CHECK(map.reduced_size() == finiteElementSpace->GetTrueVSize());
CHECK(map.inactive_size() == 0);
for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) {
CHECK(map.true_dof(trueDof) == trueDof);
CHECK(map.contains_true_dof(trueDof));
}
}
TEST_CASE(
"Field DOF Map Factory Uses Schema Material Bindings Rather Than Numeric Conventions",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh(17, 29);
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace = field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofMap map =
field::make_field_dof_map<field::Density, field_dof_map_test_utils::AlternateSchema>(*finiteElementSpace);
const field::FieldDofSupport support =
field::resolve_field_dof_support<field::Density, field_dof_map_test_utils::AlternateSchema>(
*finiteElementSpace
);
CHECK(map.full_size() == support.activeTrueDofMarker.Size());
CHECK(map.reduced_size() == support.activeTrueDofs.Size());
for (int trueDof = 0; trueDof < map.full_size(); ++trueDof) {
CHECK(map.contains_true_dof(trueDof) == (support.activeTrueDofMarker[trueDof] != 0));
}
}
TEST_CASE(
"Field DOF Map Reduced Vectors Round Trip Through Real Field Support",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace = field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofMap map =
field::make_field_dof_map<field::Enthalpy, field_dof_map_test_utils::Schema>(*finiteElementSpace);
mfem::Vector reduced(map.reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = 0.125 + 0.031 * static_cast<double>(reducedDof + 1);
}
const mfem::Vector full = map.scatter(reduced);
const mfem::Vector recovered = map.gather(full);
REQUIRE(recovered.Size() == reduced.Size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(recovered(reducedDof) == reduced(reducedDof));
}
for (int trueDof = 0; trueDof < full.Size(); ++trueDof) {
if (!map.contains_true_dof(trueDof)) {
CHECK(full(trueDof) == 0.0);
}
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Gathers Exactly The Supported True DOFs",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector full(adapter.dof_map().full_size());
for (int trueDof = 0; trueDof < full.Size(); ++trueDof) {
full(trueDof) = 1.25 + 0.375 * static_cast<double>(trueDof + 1);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction.SetFromTrueDofs(full);
const mfem::Vector expected = adapter.dof_map().gather(full);
const mfem::Vector actual = adapter.gather(gridFunction);
REQUIRE(actual.Size() == expected.Size());
for (int reducedDof = 0; reducedDof < actual.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(actual(reducedDof) == expected(reducedDof));
}
mfem::Vector output(adapter.dof_map().reduced_size());
adapter.gather(gridFunction, output);
for (int reducedDof = 0; reducedDof < output.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(output(reducedDof) == expected(reducedDof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Scatter Projects And Round Trips Reduced Fields",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Enthalpy>::make_fec<field::Enthalpy::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Enthalpy>::make_fespace<field::Enthalpy::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Enthalpy, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = -0.75 + 0.0625 * static_cast<double>(reducedDof + 1);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction = 91.0;
adapter.scatter(reduced, gridFunction);
mfem::Vector actualFull;
gridFunction.GetTrueDofs(actualFull);
const mfem::Vector expectedFull = adapter.dof_map().scatter(reduced);
REQUIRE(actualFull.Size() == expectedFull.Size());
for (int trueDof = 0; trueDof < actualFull.Size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(actualFull(trueDof) == expectedFull(trueDof));
if (!adapter.dof_map().contains_true_dof(trueDof)) {
CHECK(actualFull(trueDof) == 0.0);
}
}
const mfem::Vector recovered = adapter.gather(gridFunction);
REQUIRE(recovered.Size() == reduced.Size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
CAPTURE(reducedDof);
CHECK(recovered(reducedDof) == reduced(reducedDof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Scatter Into Preserves Unsupported True DOFs",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
mfem::Vector initialFull(adapter.dof_map().full_size());
for (int trueDof = 0; trueDof < initialFull.Size(); ++trueDof) {
initialFull(trueDof) = 40.0 + static_cast<double>(trueDof);
}
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int reducedDof = 0; reducedDof < reduced.Size(); ++reducedDof) {
reduced(reducedDof) = -10.0 - static_cast<double>(reducedDof);
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
gridFunction.SetFromTrueDofs(initialFull);
adapter.scatter_into(reduced, gridFunction);
mfem::Vector actualFull;
gridFunction.GetTrueDofs(actualFull);
mfem::Vector expectedFull(initialFull);
adapter.dof_map().scatter_into(reduced, expectedFull);
REQUIRE(actualFull.Size() == expectedFull.Size());
for (int trueDof = 0; trueDof < actualFull.Size(); ++trueDof) {
CAPTURE(trueDof);
CHECK(actualFull(trueDof) == expectedFull(trueDof));
if (!adapter.dof_map().contains_true_dof(trueDof)) {
CHECK(actualFull(trueDof) == initialFull(trueDof));
}
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Is Exact For Identity Vector Field Maps",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Displacement>::make_fec<field::Displacement::Vector>(2);
auto finiteElementSpace =
field::Field<field::Displacement>::make_fespace<field::Displacement::Vector>(mesh, *fec);
REQUIRE(finiteElementSpace != nullptr);
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Displacement, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
REQUIRE(adapter.dof_map().is_identity());
mfem::Vector reduced(adapter.dof_map().reduced_size());
for (int dof = 0; dof < reduced.Size(); ++dof) {
reduced(dof) = std::sin(0.23 * static_cast<double>(dof + 1));
}
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
adapter.scatter(reduced, gridFunction);
const mfem::Vector recovered = adapter.gather(gridFunction);
REQUIRE(recovered.Size() == reduced.Size());
for (int dof = 0; dof < reduced.Size(); ++dof) {
CAPTURE(dof);
CHECK(recovered(dof) == reduced(dof));
}
}
TEST_CASE(
"Field DOF Grid Function Adapter Rejects Incompatible Maps Spaces And Vectors",
tags::field_dof_integration
) {
namespace field = mean_field::field;
mfem::Mesh serialMesh = field_dof_map_test_utils::make_split_mesh();
mfem::ParMesh mesh(MPI_COMM_WORLD, serialMesh);
auto fec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto finiteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *fec);
auto otherFec = field::Field<field::Density>::make_fec<field::Density::Scalar>(2);
auto otherFiniteElementSpace =
field::Field<field::Density>::make_fespace<field::Density::Scalar>(mesh, *otherFec);
REQUIRE(finiteElementSpace != nullptr);
REQUIRE(otherFiniteElementSpace != nullptr);
REQUIRE(finiteElementSpace->GetTrueVSize() == otherFiniteElementSpace->GetTrueVSize());
const field::FieldDofGridFunctionAdapter adapter =
field::make_field_dof_grid_function_adapter<field::Density, field_dof_map_test_utils::Schema>(
*finiteElementSpace
);
const mfem::Array<int> empty;
CHECK_THROWS_AS(
(field::FieldDofGridFunctionAdapter(
field::FieldDofMap(finiteElementSpace->GetTrueVSize() + 1, empty),
*finiteElementSpace
)),
std::invalid_argument
);
mfem::ParGridFunction gridFunction(finiteElementSpace.get());
mfem::ParGridFunction otherGridFunction(otherFiniteElementSpace.get());
mfem::Vector reduced(adapter.dof_map().reduced_size());
reduced = 1.0;
mfem::Vector wrongReduced(adapter.dof_map().reduced_size() + 1);
mfem::Vector wrongOutput(adapter.dof_map().reduced_size() + 1);
CHECK_THROWS_AS(adapter.gather(otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter(reduced, otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter_into(reduced, otherGridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.gather(gridFunction, wrongOutput), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter(wrongReduced, gridFunction), std::invalid_argument);
CHECK_THROWS_AS(adapter.scatter_into(wrongReduced, gridFunction), std::invalid_argument);
}