Files
MeanField/tests/utils/blocks.cpp
Emily Boudreaux 0f3ca8050b feat(field-support): added field support system, mid migration
currently the barotope and the pressure force operator are migrated to the new support system
2026-08-23 10:13:53 -04:00

626 lines
30 KiB
C++

#include <array>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <type_traits>
import mean_field;
import test_helpers;
using namespace mean_field;
namespace {
namespace blocks = utils::blocks;
template <typename Query, typename List> struct contains_type;
template <typename Query> struct contains_type<Query, blocks::type_list<>> : std::false_type { };
template <typename Query, typename Head, typename... Tail>
struct contains_type<Query, blocks::type_list<Head, Tail...>>
: std::conditional_t<
std::is_same_v<Query, Head>,
std::true_type,
contains_type<Query, blocks::type_list<Tail...>>> { };
template <typename Query, typename List> inline constexpr bool contains_type_v = contains_type<Query, List>::value;
template <int index, typename List> struct type_at;
template <typename Head, typename... Tail> struct type_at<0, blocks::type_list<Head, Tail...>> {
using type = Head;
};
template <int index, typename Head, typename... Tail> struct type_at<index, blocks::type_list<Head, Tail...>> {
static_assert(index > 0);
using type = typename type_at<index - 1, blocks::type_list<Tail...>>::type;
};
template <int index, typename List> using type_at_t = typename type_at<index, List>::type;
template <typename Row> struct block_row_traits;
template <typename Residual, typename... Values> struct block_row_traits<blocks::block_row<Residual, Values...>> {
using residual = Residual;
using values = blocks::type_list<Values...>;
static constexpr int value_count = sizeof...(Values);
};
template <typename Row, typename... ExpectedValues>
inline constexpr bool row_has_exact_values_v =
block_row_traits<Row>::value_count == sizeof...(ExpectedValues) &&
(contains_type_v<ExpectedValues, typename block_row_traits<Row>::values> && ...);
struct foreign_value final : blocks::value_block_base { };
struct foreign_residual final : blocks::residual_block_base { };
} // namespace
TEST_CASE(
"Block Types Preserve Their Semantic Hierarchy",
tags::unit &tags::solver &tags::utils
) {
STATIC_REQUIRE(std::is_base_of_v<blocks::block, blocks::residual_block_base>);
STATIC_REQUIRE(std::is_base_of_v<blocks::block, blocks::value_block_base>);
STATIC_REQUIRE(std::is_base_of_v<blocks::residual_block_base, blocks::residual_block<0>>);
STATIC_REQUIRE(std::is_base_of_v<blocks::value_block_base, blocks::value_block<0>>);
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::density>);
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::displacement>);
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::gravity>);
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::enthalpy>);
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::density::mass>);
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::displacement::geometry>);
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::gravity::gradient>);
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::gravity::poisson>);
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::enthalpy::specific>);
STATIC_REQUIRE(std::is_base_of_v<blocks::value_block_base, blocks::density::mass::value>);
STATIC_REQUIRE(std::is_base_of_v<blocks::residual_block_base, blocks::density::mass::residual>);
STATIC_REQUIRE(std::is_base_of_v<blocks::value_block_base, blocks::gravity::gradient::value>);
STATIC_REQUIRE(std::is_base_of_v<blocks::residual_block_base, blocks::gravity::gradient::residual>);
STATIC_REQUIRE(std::is_base_of_v<blocks::value_block_base, blocks::gravity::poisson::value>);
STATIC_REQUIRE(std::is_base_of_v<blocks::residual_block_base, blocks::gravity::poisson::residual>);
STATIC_REQUIRE(std::is_base_of_v<blocks::value_block_base, blocks::enthalpy::specific::value>);
STATIC_REQUIRE(std::is_base_of_v<blocks::residual_block_base, blocks::enthalpy::specific::residual>);
STATIC_REQUIRE(std::is_empty_v<blocks::density>);
STATIC_REQUIRE(std::is_empty_v<blocks::displacement>);
STATIC_REQUIRE(std::is_empty_v<blocks::gravity>);
STATIC_REQUIRE(std::is_empty_v<blocks::enthalpy>);
STATIC_REQUIRE(std::is_empty_v<blocks::density::mass>);
STATIC_REQUIRE(std::is_empty_v<blocks::gravity::gradient>);
STATIC_REQUIRE(std::is_empty_v<blocks::gravity::poisson>);
STATIC_REQUIRE(std::is_empty_v<blocks::enthalpy::specific>);
STATIC_REQUIRE(std::is_base_of_v<blocks::field, blocks::barotropic_constant>);
STATIC_REQUIRE(std::is_base_of_v<blocks::term, blocks::barotropic_constant::mass_normalization>);
STATIC_REQUIRE(std::is_base_of_v<blocks::value_block_base, blocks::barotropic_constant::mass_normalization::value>);
STATIC_REQUIRE(
std::is_base_of_v<blocks::residual_block_base, blocks::barotropic_constant::mass_normalization::residual>
);
STATIC_REQUIRE(blocks::barotropic_constant::mass_normalization::value::static_block_size == 1);
STATIC_REQUIRE(blocks::barotropic_constant::mass_normalization::residual::static_block_size == 1);
STATIC_REQUIRE(std::is_empty_v<blocks::barotropic_constant>);
STATIC_REQUIRE(std::is_empty_v<blocks::barotropic_constant::mass_normalization>);
CHECK(true);
}
TEST_CASE(
"Type Lists Resolve Their Types At Compile Time",
tags::unit &tags::solver &tags::utils
) {
using list = blocks::type_list<
blocks::density::mass::value, blocks::displacement::geometry::value, blocks::gravity::gradient::value,
blocks::gravity::poisson::value>;
STATIC_REQUIRE(list::size == 4);
STATIC_REQUIRE(blocks::type_index_v<blocks::density::mass::value, list> == 0);
STATIC_REQUIRE(blocks::type_index_v<blocks::displacement::geometry::value, list> == 1);
STATIC_REQUIRE(blocks::type_index_v<blocks::gravity::gradient::value, list> == 2);
STATIC_REQUIRE(blocks::type_index_v<blocks::gravity::poisson::value, list> == 3);
STATIC_REQUIRE(std::is_same_v<type_at_t<0, list>, blocks::density::mass::value>);
STATIC_REQUIRE(std::is_same_v<type_at_t<1, list>, blocks::displacement::geometry::value>);
STATIC_REQUIRE(std::is_same_v<type_at_t<2, list>, blocks::gravity::gradient::value>);
STATIC_REQUIRE(std::is_same_v<type_at_t<3, list>, blocks::gravity::poisson::value>);
CHECK(true);
}
TEST_CASE(
"Gravity Field Form Resolves Value And Residual Blocks",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::gravity_field_form;
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto displacement_value = blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
constexpr auto gravity_gradient_value = blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_value = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
STATIC_REQUIRE(form::value_block_count == 4);
STATIC_REQUIRE(form::residual_block_count == 2);
STATIC_REQUIRE(std::is_same_v<std::remove_cv_t<decltype(density_value)>, blocks::value_block<0>>);
STATIC_REQUIRE(std::is_same_v<std::remove_cv_t<decltype(displacement_value)>, blocks::value_block<1>>);
STATIC_REQUIRE(std::is_same_v<std::remove_cv_t<decltype(gravity_gradient_value)>, blocks::value_block<2>>);
STATIC_REQUIRE(std::is_same_v<std::remove_cv_t<decltype(gravity_potential_value)>, blocks::value_block<3>>);
STATIC_REQUIRE(std::is_same_v<std::remove_cv_t<decltype(gravity_gradient_residual)>, blocks::residual_block<0>>);
STATIC_REQUIRE(std::is_same_v<std::remove_cv_t<decltype(gravity_poisson_residual)>, blocks::residual_block<1>>);
CHECK(static_cast<int>(density_value) == 0);
CHECK(static_cast<int>(displacement_value) == 1);
CHECK(static_cast<int>(gravity_gradient_value) == 2);
CHECK(static_cast<int>(gravity_potential_value) == 3);
CHECK(static_cast<int>(gravity_gradient_residual) == 0);
CHECK(static_cast<int>(gravity_poisson_residual) == 1);
}
TEST_CASE(
"Resolved Blocks Implicitly Convert For MFEM Interfaces",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::gravity_field_form;
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto gravity_potential_value = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
auto consume_block_index = [](const int block_index) { return block_index; };
CHECK(consume_block_index(density_value) == 0);
CHECK(consume_block_index(gravity_potential_value) == 3);
CHECK(consume_block_index(gravity_gradient_residual) == 0);
CHECK(consume_block_index(gravity_poisson_residual) == 1);
}
TEST_CASE(
"Block Indices Belong To Their Form",
tags::unit &tags::solver &tags::utils
) {
using reordered_form = blocks::block_form<
blocks::type_list<
blocks::gravity::poisson::value, blocks::gravity::gradient::value, blocks::density::mass::value,
blocks::displacement::geometry::value>,
blocks::type_list<blocks::gravity::poisson::residual, blocks::gravity::gradient::residual>>;
constexpr auto gravity_potential_value =
blocks::get_value_block<reordered_form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_value =
blocks::get_value_block<reordered_form>(blocks::gravity_field.gradient_term);
constexpr auto density_value = blocks::get_value_block<reordered_form>(blocks::density_field.mass_term);
constexpr auto displacement_value =
blocks::get_value_block<reordered_form>(blocks::displacement_field.geometry_term);
constexpr auto gravity_poisson_residual =
blocks::get_residual_block<reordered_form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual =
blocks::get_residual_block<reordered_form>(blocks::gravity_field.gradient_term);
STATIC_REQUIRE(static_cast<int>(gravity_potential_value) == 0);
STATIC_REQUIRE(static_cast<int>(gravity_gradient_value) == 1);
STATIC_REQUIRE(static_cast<int>(density_value) == 2);
STATIC_REQUIRE(static_cast<int>(displacement_value) == 3);
STATIC_REQUIRE(static_cast<int>(gravity_poisson_residual) == 0);
STATIC_REQUIRE(static_cast<int>(gravity_gradient_residual) == 1);
CHECK(true);
}
TEST_CASE(
"Form Layout Constructs Runtime Offsets From Block Sizes",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::gravity_field_form;
const std::array<int, form::value_block_count> value_sizes{11, 13, 17, 19};
const std::array<int, form::residual_block_count> residual_sizes{23, 29};
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto displacement_value = blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
constexpr auto gravity_gradient_value = blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_value = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
CHECK(layout.size(density_value) == 11);
CHECK(layout.size(displacement_value) == 13);
CHECK(layout.size(gravity_gradient_value) == 17);
CHECK(layout.size(gravity_potential_value) == 19);
CHECK(layout.size(gravity_gradient_residual) == 23);
CHECK(layout.size(gravity_poisson_residual) == 29);
CHECK(layout.offset(density_value) == 0);
CHECK(layout.offset(displacement_value) == 11);
CHECK(layout.offset(gravity_gradient_value) == 24);
CHECK(layout.offset(gravity_potential_value) == 41);
CHECK(layout.offset(gravity_gradient_residual) == 0);
CHECK(layout.offset(gravity_poisson_residual) == 23);
REQUIRE(layout.value_offsets().Size() == 5);
CHECK(layout.value_offsets()[0] == 0);
CHECK(layout.value_offsets()[1] == 11);
CHECK(layout.value_offsets()[2] == 24);
CHECK(layout.value_offsets()[3] == 41);
CHECK(layout.value_offsets()[4] == 60);
REQUIRE(layout.residual_offsets().Size() == 3);
CHECK(layout.residual_offsets()[0] == 0);
CHECK(layout.residual_offsets()[1] == 23);
CHECK(layout.residual_offsets()[2] == 52);
}
TEST_CASE(
"Form Layout Supports Empty Runtime Blocks",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::gravity_field_form;
const std::array<int, form::value_block_count> value_sizes{3, 0, 5, 0};
const std::array<int, form::residual_block_count> residual_sizes{0, 7};
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto displacement_value = blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
constexpr auto gravity_gradient_value = blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_value = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
CHECK(layout.size(density_value) == 3);
CHECK(layout.size(displacement_value) == 0);
CHECK(layout.size(gravity_gradient_value) == 5);
CHECK(layout.size(gravity_potential_value) == 0);
CHECK(layout.size(gravity_gradient_residual) == 0);
CHECK(layout.size(gravity_poisson_residual) == 7);
CHECK(layout.offset(density_value) == 0);
CHECK(layout.offset(displacement_value) == 3);
CHECK(layout.offset(gravity_gradient_value) == 3);
CHECK(layout.offset(gravity_potential_value) == 8);
CHECK(layout.offset(gravity_gradient_residual) == 0);
CHECK(layout.offset(gravity_poisson_residual) == 0);
CHECK(layout.value_offsets().Last() == 8);
CHECK(layout.residual_offsets().Last() == 7);
}
TEST_CASE(
"Form Layout Integrates With MFEM Block Vectors",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::gravity_field_form;
const std::array<int, form::value_block_count> value_sizes{3, 5, 7, 11};
const std::array<int, form::residual_block_count> residual_sizes{13, 17};
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto displacement_value = blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
constexpr auto gravity_gradient_value = blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_value = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
mfem::BlockVector values(layout.value_offsets());
mfem::BlockVector residuals(layout.residual_offsets());
values = 0.0;
residuals = 0.0;
values.GetBlock(density_value) = 1.0;
values.GetBlock(displacement_value) = 2.0;
values.GetBlock(gravity_gradient_value) = 3.0;
values.GetBlock(gravity_potential_value) = 4.0;
residuals.GetBlock(gravity_gradient_residual) = 5.0;
residuals.GetBlock(gravity_poisson_residual) = 6.0;
CHECK(values.GetBlock(density_value).Size() == 3);
CHECK(values.GetBlock(displacement_value).Size() == 5);
CHECK(values.GetBlock(gravity_gradient_value).Size() == 7);
CHECK(values.GetBlock(gravity_potential_value).Size() == 11);
CHECK(residuals.GetBlock(gravity_gradient_residual).Size() == 13);
CHECK(residuals.GetBlock(gravity_poisson_residual).Size() == 17);
CHECK(values.GetBlock(density_value).Min() == 1.0);
CHECK(values.GetBlock(density_value).Max() == 1.0);
CHECK(values.GetBlock(displacement_value).Min() == 2.0);
CHECK(values.GetBlock(displacement_value).Max() == 2.0);
CHECK(values.GetBlock(gravity_gradient_value).Min() == 3.0);
CHECK(values.GetBlock(gravity_gradient_value).Max() == 3.0);
CHECK(values.GetBlock(gravity_potential_value).Min() == 4.0);
CHECK(values.GetBlock(gravity_potential_value).Max() == 4.0);
CHECK(residuals.GetBlock(gravity_gradient_residual).Min() == 5.0);
CHECK(residuals.GetBlock(gravity_gradient_residual).Max() == 5.0);
CHECK(residuals.GetBlock(gravity_poisson_residual).Min() == 6.0);
CHECK(residuals.GetBlock(gravity_poisson_residual).Max() == 6.0);
}
TEST_CASE(
"Compile Time Blocks Configure An MFEM Block Operator",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::gravity_field_form;
const std::array<int, form::value_block_count> value_sizes{3, 5, 7, 11};
const std::array<int, form::residual_block_count> residual_sizes{13, 17};
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
mfem::DenseMatrix source_block(layout.size(gravity_poisson_residual), layout.size(density_value));
source_block = 1.0;
mfem::BlockOperator block_operator(layout.residual_offsets(), layout.value_offsets());
block_operator.SetBlock(gravity_poisson_residual, density_value, &source_block);
mfem::BlockVector values(layout.value_offsets());
mfem::BlockVector residuals(layout.residual_offsets());
values = 0.0;
residuals = 0.0;
values.GetBlock(density_value) = 2.0;
block_operator.Mult(values, residuals);
CHECK(residuals.GetBlock(gravity_gradient_residual).Norml2() == 0.0);
const mfem::Vector &poisson_residual = residuals.GetBlock(gravity_poisson_residual);
REQUIRE(poisson_residual.Size() == residual_sizes[1]);
for (int i = 0; i < poisson_residual.Size(); ++i) {
CHECK(poisson_residual(i) == 2.0 * value_sizes[0]);
}
}
TEST_CASE(
"Gravity Jacobian Form Describes The Expected Couplings",
tags::unit &tags::solver &tags::utils
) {
using gradient_row = type_at_t<0, blocks::gravity_jacobian_form>;
using poisson_row = type_at_t<1, blocks::gravity_jacobian_form>;
using gradient_traits = block_row_traits<gradient_row>;
using poisson_traits = block_row_traits<poisson_row>;
STATIC_REQUIRE(blocks::gravity_jacobian_form::size == 2);
STATIC_REQUIRE(std::is_same_v<typename gradient_traits::residual, blocks::gravity::gradient::residual>);
STATIC_REQUIRE(gradient_traits::value_count == 3);
STATIC_REQUIRE(contains_type_v<blocks::gravity::gradient::value, typename gradient_traits::values>);
STATIC_REQUIRE(contains_type_v<blocks::gravity::poisson::value, typename gradient_traits::values>);
STATIC_REQUIRE(contains_type_v<blocks::displacement::geometry::value, typename gradient_traits::values>);
STATIC_REQUIRE_FALSE(contains_type_v<blocks::density::mass::value, typename gradient_traits::values>);
STATIC_REQUIRE(std::is_same_v<typename poisson_traits::residual, blocks::gravity::poisson::residual>);
STATIC_REQUIRE(poisson_traits::value_count == 3);
STATIC_REQUIRE(contains_type_v<blocks::gravity::gradient::value, typename poisson_traits::values>);
STATIC_REQUIRE(contains_type_v<blocks::density::mass::value, typename poisson_traits::values>);
STATIC_REQUIRE(contains_type_v<blocks::displacement::geometry::value, typename poisson_traits::values>);
STATIC_REQUIRE_FALSE(contains_type_v<blocks::gravity::poisson::value, typename poisson_traits::values>);
CHECK(true);
}
TEST_CASE(
"Barotropic Equilibrium Form Encodes The Agreed Row And Column Layouts",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::barotropic_equilibrium_form;
constexpr auto density_value = blocks::get_value_block<form>(blocks::density_field.mass_term);
constexpr auto displacement_value = blocks::get_value_block<form>(blocks::displacement_field.geometry_term);
constexpr auto gravity_gradient_value = blocks::get_value_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_potential_value = blocks::get_value_block<form>(blocks::gravity_field.poisson_term);
constexpr auto enthalpy_value = blocks::get_value_block<form>(blocks::enthalpy_field.specific_term);
constexpr auto barotropic_constant_value =
blocks::get_value_block<form>(blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto gravity_gradient_residual = blocks::get_residual_block<form>(blocks::gravity_field.gradient_term);
constexpr auto gravity_poisson_residual = blocks::get_residual_block<form>(blocks::gravity_field.poisson_term);
constexpr auto density_residual = blocks::get_residual_block<form>(blocks::density_field.mass_term);
constexpr auto displacement_residual = blocks::get_residual_block<form>(blocks::displacement_field.geometry_term);
constexpr auto enthalpy_residual = blocks::get_residual_block<form>(blocks::enthalpy_field.specific_term);
constexpr auto mass_residual =
blocks::get_residual_block<form>(blocks::barotropic_constant_field.mass_normalization_term);
STATIC_REQUIRE(form::value_block_count == 6);
STATIC_REQUIRE(form::residual_block_count == 6);
STATIC_REQUIRE(static_cast<int>(density_value) == 0);
STATIC_REQUIRE(static_cast<int>(displacement_value) == 1);
STATIC_REQUIRE(static_cast<int>(gravity_gradient_value) == 2);
STATIC_REQUIRE(static_cast<int>(gravity_potential_value) == 3);
STATIC_REQUIRE(static_cast<int>(enthalpy_value) == 4);
STATIC_REQUIRE(static_cast<int>(barotropic_constant_value) == 5);
STATIC_REQUIRE(static_cast<int>(gravity_gradient_residual) == 0);
STATIC_REQUIRE(static_cast<int>(gravity_poisson_residual) == 1);
STATIC_REQUIRE(static_cast<int>(density_residual) == 2);
STATIC_REQUIRE(static_cast<int>(displacement_residual) == 3);
STATIC_REQUIRE(static_cast<int>(enthalpy_residual) == 4);
STATIC_REQUIRE(static_cast<int>(mass_residual) == 5);
CHECK(true);
}
TEST_CASE(
"Barotropic Jacobian Form Encodes The Exact Direct Coupling Graph",
tags::unit &tags::solver &tags::utils
) {
using jacobian = blocks::barotropic_equilibrium_jacobian_form;
using gradient_row = type_at_t<0, jacobian>;
using poisson_row = type_at_t<1, jacobian>;
using density_row = type_at_t<2, jacobian>;
using displacement_row = type_at_t<3, jacobian>;
using enthalpy_row = type_at_t<4, jacobian>;
using mass_row = type_at_t<5, jacobian>;
STATIC_REQUIRE(jacobian::size == 6);
STATIC_REQUIRE(
row_has_exact_values_v<
gradient_row, blocks::gravity::gradient::value, blocks::gravity::poisson::value,
blocks::displacement::geometry::value>
);
STATIC_REQUIRE(
row_has_exact_values_v<
poisson_row, blocks::gravity::gradient::value, blocks::density::mass::value,
blocks::displacement::geometry::value>
);
STATIC_REQUIRE(
row_has_exact_values_v<
density_row, blocks::density::mass::value, blocks::enthalpy::specific::value,
blocks::displacement::geometry::value>
);
STATIC_REQUIRE(
row_has_exact_values_v<
displacement_row, blocks::density::mass::value, blocks::displacement::geometry::value,
blocks::gravity::gradient::value, blocks::enthalpy::specific::value>
);
STATIC_REQUIRE(
row_has_exact_values_v<
enthalpy_row, blocks::enthalpy::specific::value, blocks::gravity::poisson::value,
blocks::displacement::geometry::value, blocks::barotropic_constant::mass_normalization::value>
);
STATIC_REQUIRE(
row_has_exact_values_v<mass_row, blocks::density::mass::value, blocks::displacement::geometry::value>
);
STATIC_REQUIRE_FALSE(
blocks::has_jacobian_coupling_v<
blocks::displacement::geometry::residual, blocks::gravity::poisson::value, jacobian>
);
STATIC_REQUIRE_FALSE(
blocks::has_jacobian_coupling_v<
blocks::barotropic_constant::mass_normalization::residual,
blocks::barotropic_constant::mass_normalization::value, jacobian>
);
STATIC_REQUIRE_FALSE(
blocks::has_jacobian_coupling_v<
blocks::barotropic_constant::mass_normalization::residual, blocks::enthalpy::specific::value, jacobian>
);
CHECK(true);
}
TEST_CASE(
"Block Validators Reject Structurally Malformed Forms",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::barotropic_equilibrium_form;
using jacobian = blocks::barotropic_equilibrium_jacobian_form;
using gradient_row = type_at_t<0, jacobian>;
using poisson_row = type_at_t<1, jacobian>;
using density_row = type_at_t<2, jacobian>;
using displacement_row = type_at_t<3, jacobian>;
using enthalpy_row = type_at_t<4, jacobian>;
using mass_row = type_at_t<5, jacobian>;
using missing_row = blocks::type_list<gradient_row, poisson_row, density_row, displacement_row, enthalpy_row>;
using duplicate_row =
blocks::type_list<gradient_row, poisson_row, density_row, displacement_row, enthalpy_row, enthalpy_row>;
using reordered_rows =
blocks::type_list<poisson_row, gradient_row, density_row, displacement_row, enthalpy_row, mass_row>;
using foreign_value_row = blocks::block_row<
blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value, blocks::gravity::poisson::value,
blocks::displacement::geometry::value, foreign_value>;
using foreign_value_jacobian =
blocks::type_list<gradient_row, poisson_row, density_row, displacement_row, foreign_value_row, mass_row>;
using duplicate_value_row = blocks::block_row<
blocks::enthalpy::specific::residual, blocks::enthalpy::specific::value, blocks::gravity::poisson::value,
blocks::displacement::geometry::value, blocks::barotropic_constant::mass_normalization::value,
blocks::barotropic_constant::mass_normalization::value>;
using duplicate_value_jacobian =
blocks::type_list<gradient_row, poisson_row, density_row, displacement_row, duplicate_value_row, mass_row>;
using unknown_residual_row = blocks::block_row<foreign_residual, blocks::density::mass::value>;
using unknown_residual_jacobian =
blocks::type_list<gradient_row, poisson_row, density_row, displacement_row, enthalpy_row, unknown_residual_row>;
using duplicate_value_form = blocks::block_form<
blocks::type_list<blocks::density::mass::value, blocks::density::mass::value>,
blocks::type_list<blocks::density::mass::residual>>;
STATIC_REQUIRE(blocks::block_form_is_valid_v<form>);
STATIC_REQUIRE((blocks::valid_jacobian_form<form, jacobian>));
STATIC_REQUIRE_FALSE(blocks::block_form_is_valid_v<duplicate_value_form>);
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, missing_row>));
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, duplicate_row>));
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, reordered_rows>));
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, foreign_value_jacobian>));
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, duplicate_value_jacobian>));
STATIC_REQUIRE_FALSE((blocks::valid_jacobian_form<form, unknown_residual_jacobian>));
CHECK(true);
}
TEST_CASE(
"Barotropic Layout Preserves Distinct Row And Column Offsets",
tags::unit &tags::solver &tags::utils
) {
using form = blocks::barotropic_equilibrium_form;
// Columns: [rho, d, g, Phi, h, C]
const std::array<int, form::value_block_count> value_sizes{11, 13, 17, 19, 23, 1};
// Rows: [R_g, R_Phi, R_rho, R_d, R_h, R_M]
const std::array<int, form::residual_block_count> residual_sizes{17, 19, 11, 13, 23, 1};
const blocks::form_layout<form> layout(value_sizes, residual_sizes);
constexpr auto constant_value =
blocks::get_value_block<form>(blocks::barotropic_constant_field.mass_normalization_term);
constexpr auto mass_residual =
blocks::get_residual_block<form>(blocks::barotropic_constant_field.mass_normalization_term);
CHECK(layout.size(constant_value) == 1);
CHECK(layout.size(mass_residual) == 1);
CHECK(layout.offset(constant_value) == 83);
CHECK(layout.offset(mass_residual) == 83);
CHECK(layout.value_offsets().Last() == 84);
CHECK(layout.residual_offsets().Last() == 84);
const std::array<int, form::value_block_count> invalid_value_sizes{11, 13, 17, 19, 23, 2};
CHECK_THROWS(blocks::form_layout<form>(invalid_value_sizes, residual_sizes));
}