Files
MeanField/tests/utils/blocks.cpp

861 lines
32 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::displacement::geometry::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)
);
}