feat(FieldDofMap): Completed FieldDofMap migration

also removed legacy BarotropicPolytrope implementation
This commit is contained in:
2026-08-29 08:56:36 -04:00
parent 177ae8b38a
commit 36adfa1174
104 changed files with 26967 additions and 26916 deletions

View File

@@ -1,6 +1,7 @@
#include <algorithm>
#include <array>
#include <catch2/catch_test_macros.hpp>
#include <cmath>
#include <cstddef>
#include <mfem.hpp>
#include <mpi.h>
@@ -79,6 +80,11 @@ namespace field_dof_map_test_utils {
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>,
@@ -90,7 +96,7 @@ namespace field_dof_map_test_utils {
TEST_CASE(
"Field DOF Map Preserves Canonical Bidirectional Indexing",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -166,7 +172,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Rejects Invalid Canonical Mappings",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -191,7 +197,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Rejects Out Of Range Index Queries",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -212,7 +218,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Gather Selects Exactly The Active True DOFs",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -247,7 +253,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Scatter Produces The Canonical Supported Projection",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -281,7 +287,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Gather Scatter Projects A Full Vector Onto Field Support",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -310,7 +316,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Scatter Into Preserves Unsupported True DOFs",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -337,7 +343,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Scatter Add Accumulates Only Onto Active True DOFs",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -364,7 +370,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Operations Support MFEM Vector Views Without Resizing",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -406,7 +412,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Operations Reject Incompatible Vector Sizes",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -433,7 +439,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Identity Mapping Is An Exact Vector Identity",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -463,7 +469,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Validates Field DOF Support Consistency",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -495,7 +501,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Is Available Only For Spatial Registered Fields",
tags::unit &tags::field
tags::field_dof_unit
) {
namespace field = mean_field::field;
@@ -509,12 +515,24 @@ TEST_CASE(
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::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -567,7 +585,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Exactly Preserves H1 Enthalpy Support",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -614,7 +632,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Produces Identity Maps For All Supported Fields",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -648,7 +666,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Factory Uses Schema Material Bindings Rather Than Numeric Conventions",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -681,7 +699,7 @@ TEST_CASE(
TEST_CASE(
"Field DOF Map Reduced Vectors Round Trip Through Real Field Support",
tags::integration &tags::field
tags::field_dof_integration
) {
namespace field = mean_field::field;
@@ -721,4 +739,246 @@ TEST_CASE(
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);
}