Files
MeanField_SERiF/tests/include/serif/tests/equations/volume_equation_fixture.hpp
Emily Boudreaux cb20faeb3d test(tests): added test suite
The test suite is extensive. Note that LLMs were used to aid in the development of the test suite. All LLM generated code was manually reviewed.
2026-11-09 13:53:17 -05:00

194 lines
8.6 KiB
C++

#pragma once
#include "serif/utils/error/errors.hpp"
#include <optional>
#include <stdexcept>
#include <utility>
#include "serif/backend/mfem/operators/volume_computations.hpp"
#include "serif/backend/mfem/operators/volume_application.hpp"
namespace serif::tests::equations {
namespace backend = serif::operators::backend::mfem;
/// Derive the coefficient context used by an equation's unique input terms.
template <typename ColumnsT, typename SchemaT>
struct EquationCoefficients;
template <typename... Operands, typename SchemaT>
struct EquationCoefficients<utils::types::TypeList<Operands...>, SchemaT> {
using Type = decltype(backend::make_term_coefficients(
backend::bind(
std::declval<const discretization::backend::mfem::TermBinding<typename Operands::term_type, SchemaT>&>(),
std::declval<const ::mfem::Vector&>()
)...
));
};
/// Keep legacy numerical comparisons concise while production uses dependency caches directly.
template <operators::VolumeEquation EquationT, discretization::domain::schema::IsSchema SchemaT>
class VolumeEquationFixture {
public:
using Plan = operators::EquationPlan<EquationT>;
using Discretization = backend::VolumeDiscretization<typename Plan::Test, SchemaT>;
using RowBinding = discretization::backend::mfem::TermBinding<typename Plan::Test, SchemaT>;
using DisplacementBinding = discretization::backend::mfem::TermBinding<discretization::blocks::displacement::vector, SchemaT>;
using Coefficients = EquationCoefficients<typename Plan::Columns, SchemaT>::Type;
using Inputs = decltype(backend::make_volume_inputs<EquationT>(
std::declval<const Discretization&>(), std::declval<const Coefficients&>(), std::declval<const ::mfem::Vector&>()
));
using Cache = decltype(backend::make_volume_equation_cache(
std::declval<EquationT>(), std::declval<Discretization>(), std::declval<const Inputs&>(), backend::VolumeOptions{}
));
/// Retain the fixed test configuration until the first numerical input is supplied.
VolumeEquationFixture(
EquationT equation,
const RowBinding& residual_binding,
const DisplacementBinding& displacement_binding,
const ::mfem::GridFunction& compactification,
const mapping::DomainMapper& mapper,
discretization::quadrature::Policy quadrature_policy = discretization::quadrature::Policy{{}},
backend::ContractionMode contraction_mode = backend::ContractionMode::automatic
) :
m_equation(std::move(equation)),
m_discretization{
.residual_binding = residual_binding,
.displacement_binding = displacement_binding,
.compactification = compactification,
.mapper = mapper
},
m_options{
.quadrature_policy = std::move(quadrature_policy),
.contraction_mode = contraction_mode
} {}
/// Exercise the production validation and graph execution with the old test call shape.
template <typename CoefficientsT>
std::expected<void, backend::PreparationRejection> try_prepare(
const CoefficientsT& coefficients,
const ::mfem::Vector& displacement,
backend::Workspace&,
backend::PreparationMode mode = backend::PreparationMode::rebuild
) {
try {
if (!m_cache) {
const auto inputs = backend::make_volume_inputs<EquationT>(m_discretization, coefficients, displacement);
m_cache.emplace(backend::make_volume_equation_cache(std::move(m_equation), m_discretization, inputs, m_options));
}
} catch (const std::domain_error& error) {
return std::unexpected(backend::PreparationRejection{
.reason = backend::PreparationRejectionReason::NON_FINITE,
.message = error.what()
});
}
return backend::try_prepare_volume(*m_cache, m_discretization, coefficients, displacement, mode);
}
/// Turn a numerical rejection into the exception expected by the numerical fixtures.
template <typename CoefficientsT>
void prepare(
const CoefficientsT& coefficients,
const ::mfem::Vector& displacement,
backend::Workspace& workspace,
backend::PreparationMode mode = backend::PreparationMode::rebuild
) {
const auto result = try_prepare(coefficients, displacement, workspace, mode);
if (!result) {
serif::utils::errors::serif_error<std::domain_error, serif::utils::errors::SERiFErrorCode::INVALID_ARGUMENT>("{}", result.error().message);
}
}
/// Access the real cache for MFEM-adapter tests.
Cache& cache() {
if (!m_cache) {
serif::utils::errors::serif_error<std::logic_error, serif::utils::errors::SERiFErrorCode::INVALID_ARGUMENT>("The numerical fixture has not received its inputs.");
}
return *m_cache;
}
/// Read the same accepted result consumed by production functions.
const auto& data() const {
if (!m_cache) {
serif::utils::errors::serif_error<std::logic_error, serif::utils::errors::SERiFErrorCode::INVALID_ARGUMENT>("The numerical fixture has not received its inputs.");
}
return m_cache->template get<backend::volume_keys::Linearization>();
}
/// Report readiness directly from the dependency cache.
bool is_prepared() const {
return m_cache && m_cache->ready();
}
/// Count successful preparations, including explicitly requested rebuilds.
std::size_t preparation_count() const {
return m_cache ? m_cache->preparation_count() : 0;
}
/// Read derived numerical work without owning a second preparation report.
auto preparation_report() const {
return backend::volume_work_report(*m_cache);
}
/// Discard cached results through the dependency module.
void invalidate() {
if (m_cache) {
m_cache->invalidate();
}
}
/// Return the supported row size before preparation is needed.
int height() const {
return m_discretization.residual_binding.size();
}
/// Return the accepted Jacobian column count.
int width() const {
return backend::volume_width(data());
}
/// Inspect which numerical path the production preparation selected.
std::size_t tensor_element_count() const {
return backend::tensor_element_count(data());
}
/// Inspect the selected quadrature order for a supported element.
int integration_order(int element) const {
return backend::integration_order(data(), element);
}
/// Fetch the residual already assembled by the graph.
void residual(backend::Workspace&, ::mfem::Vector& result) const {
backend::volume_residual(data(), result);
}
/// Delegate every Jacobian call to the production numerical kernel.
template <typename... Args>
void apply_jacobian(Args&&... args) const {
backend::apply_jacobian(data(), std::forward<Args>(args)...);
}
/// Delegate transpose application to the production numerical kernel.
void apply_jacobian_transpose(const ::mfem::Vector& dual, backend::Workspace& workspace, ::mfem::Vector& result) const {
backend::apply_jacobian_transpose(data(), dual, workspace, result);
}
private:
EquationT m_equation;
Discretization m_discretization;
backend::VolumeOptions m_options;
std::optional<Cache> m_cache;
};
template <operators::VolumeEquation EquationT, discretization::domain::schema::IsSchema SchemaT>
VolumeEquationFixture(
EquationT,
const discretization::backend::mfem::TermBinding<typename EquationT::Test, SchemaT>&,
const discretization::backend::mfem::TermBinding<discretization::blocks::displacement::vector, SchemaT>&,
const ::mfem::GridFunction&,
const mapping::DomainMapper&,
discretization::quadrature::Policy = discretization::quadrature::Policy{{}},
backend::ContractionMode = backend::ContractionMode::automatic
) -> VolumeEquationFixture<EquationT, SchemaT>;
} // namespace serif::tests::equations