From 65761a36895e7ddbfc34f62290c83c71c738cf6c Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Mon, 9 Nov 2026 13:54:34 -0500 Subject: [PATCH] test(compile_time_checks): added robust compile time checking library. Note that the compile time checks were developed with the assistance of a large language model. All LLM generated code has been manually reviewed --- .../quadrature/mfem_resolver.cpp | 22 + .../quadrature/mfem_static_order.cpp | 11 + .../serif/backend_independent.cpp | 13 + .../serif/dimensions/catalogue_meanfield.cpp | 59 +++ .../serif/dimensions/quantities.cpp | 22 + .../serif/discretization/blocks/fields.cpp | 13 +- .../serif/discretization/fields_meanfield.cpp | 93 ++++ .../serif/discretization/forms/forms.cpp | 16 +- .../serif/discretization/forms/operands.cpp | 16 +- .../quadrature/backend/mfem/mfem_resolver.cpp | 26 -- .../serif/eos/consumers_meanfield.cpp | 41 ++ .../serif/eos/evaluation.cpp | 21 + .../serif/eos/models/traits.cpp | 23 + .../serif/geometry/deformation.cpp | 136 ++++++ .../serif/geometry/deformation_meanfield.cpp | 90 ++++ .../serif/geometry/surface.cpp | 188 ++++++++ .../serif/geometry/surface_boundary.cpp | 273 +++++++++++ .../geometry/surface_storage_meanfield.cpp | 14 + .../serif/model/barotropic_density.cpp | 67 +++ .../serif/model/density_primal.cpp | 43 ++ .../serif/model/description.cpp | 157 +++++++ .../serif/model/execution_validation.cpp | 169 +++++++ .../serif/model/finite_elements.cpp | 24 + .../serif/model/normalization.cpp | 148 ++++++ .../serif/model/scalar_row.cpp | 25 + src/compile_time_checks/serif/model/state.cpp | 161 +++++++ .../serif/model/validation.cpp | 177 ++++++++ .../serif/operators/computation_owners.cpp | 72 +++ .../coordinate_identity_meanfield.cpp | 46 ++ .../serif/operators/plan.cpp | 174 +++++++ .../serif/solver/continuation.cpp | 76 ++++ .../serif/solver/normalization/algebra.cpp | 49 ++ .../solver/normalization/dimensionless.cpp | 68 +++ .../serif/solver/normalization/plan.cpp | 426 ++++++++++++++++++ .../serif/solver/preconditioner/coupling.cpp | 26 ++ .../serif/solver/preconditioner/inverses.cpp | 13 + .../serif/solver/preconditioner/layout.cpp | 150 ++++++ .../serif/solver/public_contracts.cpp | 47 ++ .../serif/utils/dependencies/binding.cpp | 120 +++++ .../utils/dependencies/computation_graph.cpp | 321 +++++++++++++ .../utils/dependencies/dependency_context.cpp | 131 ++++++ .../serif/utils/dependencies/key_list.cpp | 157 +++++++ .../serif/utils/dependencies/mixed_errors.cpp | 81 ++++ .../serif/utils/laneemden.cpp | 32 ++ .../serif/utils/projection.cpp | 41 ++ 45 files changed, 4034 insertions(+), 44 deletions(-) create mode 100644 src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_resolver.cpp create mode 100644 src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_static_order.cpp create mode 100644 src/compile_time_checks/serif/backend_independent.cpp create mode 100644 src/compile_time_checks/serif/dimensions/catalogue_meanfield.cpp create mode 100644 src/compile_time_checks/serif/dimensions/quantities.cpp create mode 100644 src/compile_time_checks/serif/discretization/fields_meanfield.cpp delete mode 100644 src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp create mode 100644 src/compile_time_checks/serif/eos/consumers_meanfield.cpp create mode 100644 src/compile_time_checks/serif/eos/evaluation.cpp create mode 100644 src/compile_time_checks/serif/eos/models/traits.cpp create mode 100644 src/compile_time_checks/serif/geometry/deformation.cpp create mode 100644 src/compile_time_checks/serif/geometry/deformation_meanfield.cpp create mode 100644 src/compile_time_checks/serif/geometry/surface.cpp create mode 100644 src/compile_time_checks/serif/geometry/surface_boundary.cpp create mode 100644 src/compile_time_checks/serif/geometry/surface_storage_meanfield.cpp create mode 100644 src/compile_time_checks/serif/model/barotropic_density.cpp create mode 100644 src/compile_time_checks/serif/model/density_primal.cpp create mode 100644 src/compile_time_checks/serif/model/description.cpp create mode 100644 src/compile_time_checks/serif/model/execution_validation.cpp create mode 100644 src/compile_time_checks/serif/model/finite_elements.cpp create mode 100644 src/compile_time_checks/serif/model/normalization.cpp create mode 100644 src/compile_time_checks/serif/model/scalar_row.cpp create mode 100644 src/compile_time_checks/serif/model/state.cpp create mode 100644 src/compile_time_checks/serif/model/validation.cpp create mode 100644 src/compile_time_checks/serif/operators/computation_owners.cpp create mode 100644 src/compile_time_checks/serif/operators/coordinate_identity_meanfield.cpp create mode 100644 src/compile_time_checks/serif/operators/plan.cpp create mode 100644 src/compile_time_checks/serif/solver/continuation.cpp create mode 100644 src/compile_time_checks/serif/solver/normalization/algebra.cpp create mode 100644 src/compile_time_checks/serif/solver/normalization/dimensionless.cpp create mode 100644 src/compile_time_checks/serif/solver/normalization/plan.cpp create mode 100644 src/compile_time_checks/serif/solver/preconditioner/coupling.cpp create mode 100644 src/compile_time_checks/serif/solver/preconditioner/inverses.cpp create mode 100644 src/compile_time_checks/serif/solver/preconditioner/layout.cpp create mode 100644 src/compile_time_checks/serif/solver/public_contracts.cpp create mode 100644 src/compile_time_checks/serif/utils/dependencies/binding.cpp create mode 100644 src/compile_time_checks/serif/utils/dependencies/computation_graph.cpp create mode 100644 src/compile_time_checks/serif/utils/dependencies/dependency_context.cpp create mode 100644 src/compile_time_checks/serif/utils/dependencies/key_list.cpp create mode 100644 src/compile_time_checks/serif/utils/dependencies/mixed_errors.cpp create mode 100644 src/compile_time_checks/serif/utils/laneemden.cpp create mode 100644 src/compile_time_checks/serif/utils/projection.cpp diff --git a/src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_resolver.cpp b/src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_resolver.cpp new file mode 100644 index 00000000..a92ec4cd --- /dev/null +++ b/src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_resolver.cpp @@ -0,0 +1,22 @@ +#include "serif/backend/mfem/discretization/quadrature/mfem_resolver.hpp" +#include "serif/backend/mfem/discretization/quadrature/mfem_static_order.hpp" + +#include "serif/utils/error/errors.hpp" +#include "serif/utils/types/strings.hpp" + +namespace { + using namespace serif::discretization; + using namespace serif::utils::errors; + using namespace quadrature::backend::mfem; + using namespace serif::utils::types; + + using DensityProjectionForm = forms::projection; + + + // Note when using serif_assert (since it is a consteval function) it must be within + // a function (C++ does not allow arbitrary calls to consteval functions outside of a function context). This is why we have this function here. It is not called, but it is used to force the compiler to evaluate the consteval function and thus trigger the compile time checks. + consteval void compile_checks() { + constexpr auto density_projection_error_msg = ct_format<"Density Projection Form should have a static, compile time, order of 6, instead got {}">(mfem_form_static_order); + serif::utils::errors::serif_ensure == 6, density_projection_error_msg>(); + } +} diff --git a/src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_static_order.cpp b/src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_static_order.cpp new file mode 100644 index 00000000..a268f74c --- /dev/null +++ b/src/compile_time_checks/serif/backend/mfem/discretization/quadrature/mfem_static_order.cpp @@ -0,0 +1,11 @@ +#include "serif/backend/mfem/discretization/quadrature/mfem_static_order.hpp" +#include "serif/discretization/forms/forms.hpp" + +using namespace serif::discretization; +using namespace serif::discretization::quadrature::backend::mfem; + +static_assert(resolve_operand_order>() == 3); +static_assert(resolve_operand_order>() == 2); +static_assert(resolve_operand_order>() == 2); +static_assert(resolve_operand_order>() == 0, "Non-spatial values contribute no polynomial order"); +static_assert(mfem_form_static_order> == 6); diff --git a/src/compile_time_checks/serif/backend_independent.cpp b/src/compile_time_checks/serif/backend_independent.cpp new file mode 100644 index 00000000..b7ebcfd7 --- /dev/null +++ b/src/compile_time_checks/serif/backend_independent.cpp @@ -0,0 +1,13 @@ +#include "serif/model/model.hpp" +#include "serif/model/configured_discretization.hpp" +#include "serif/solver/nonlinear/newton.hpp" +#include "serif/solver/continuation/continuation.hpp" +#include "serif/solver/preconditioner/default_preconditioners.hpp" +#include "serif/discretization/domain/schema/validation/all.hpp" +#include "serif/utils/projection/radial.hpp" +#include "serif/utils/checks/finite.hpp" +#include "serif/utils/serialize/serialize.hpp" + +static_assert(serif::discretization::blocks::ScalarTraceTerm); +static_assert(std::same_as{})), serif::utils::serialize::Pack>); +static_assert(std::same_as>(std::declval())), serif::utils::serialize::Result>>); diff --git a/src/compile_time_checks/serif/dimensions/catalogue_meanfield.cpp b/src/compile_time_checks/serif/dimensions/catalogue_meanfield.cpp new file mode 100644 index 00000000..b7a83cfb --- /dev/null +++ b/src/compile_time_checks/serif/dimensions/catalogue_meanfield.cpp @@ -0,0 +1,59 @@ +#include + +#include "serif/dimensions/type_alias.hpp" + +namespace serif::compile_time_checks::dimensions_meanfield +{ + template + concept Addable = requires(Left left, Right right) + { + left + right; + }; + + template + concept EqualityComparable = requires(Left left, Right right) + { + { left == right } -> std::convertible_to; + }; + + static_assert(!Addable); + static_assert(!EqualityComparable); + static_assert(dimensions::MassValue{2.0} > dimensions::MassValue{0.5}); + + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); + static_assert(dimensions::QuantityValueType); +} // namespace serif::compile_time_checks::dimensions_meanfield diff --git a/src/compile_time_checks/serif/dimensions/quantities.cpp b/src/compile_time_checks/serif/dimensions/quantities.cpp new file mode 100644 index 00000000..64cc421b --- /dev/null +++ b/src/compile_time_checks/serif/dimensions/quantities.cpp @@ -0,0 +1,22 @@ +#include "serif/dimensions/quantities.hpp" +#include "serif/dimensions/type_alias.hpp" +#include "serif/dimensions/partial.hpp" + +#include + +using namespace serif::dimensions; + +static_assert(PhysicalQuantityType); +static_assert(ThermodynamicQuantityType); +static_assert(!ThermodynamicQuantityType); +static_assert(QuantityValueType); +static_assert(std::same_as, Mass>); +static_assert(std::constructible_from); +static_assert(!std::convertible_to); +static_assert(!std::convertible_to); +static_assert(!std::constructible_from); +static_assert((MassValue{2.0} + MassValue{0.5}).value() == 2.5); +static_assert((MassValue{2.0} - MassValue{0.5}).value() == 1.5); +static_assert((3.0 * MassValue{2.0}).value() == 6.0); +static_assert((MassValue{2.0} / 4.0).value() == 0.5); +static_assert(!std::convertible_to>); diff --git a/src/compile_time_checks/serif/discretization/blocks/fields.cpp b/src/compile_time_checks/serif/discretization/blocks/fields.cpp index d00804ea..37c3d5bd 100644 --- a/src/compile_time_checks/serif/discretization/blocks/fields.cpp +++ b/src/compile_time_checks/serif/discretization/blocks/fields.cpp @@ -19,10 +19,19 @@ namespace { using DensitySpace = blocks::FunctionalSpaceOf; - static_assert(DensitySpace::functional_space.family == functional_space::Family::l2, "Density should be in an L2 space"); + static_assert(DensitySpace::functional_space.family == functional_space::Family::h1, "Density should be in an H1 space"); - static_assert(DensitySpace::order == 2, "Density should be in a second order space"); + static_assert(DensitySpace::order == 3, "Density should be in a third order space"); static_assert(DensitySpace::rank::value == 0, "Density should be a scalar field"); static_assert(std::same_as, domain::StellarDomains>, "Density should be supported on stellar domains"); static_assert(std::same_as, serif::dimensions::MassDensity>, "Density should have a quantity of type Density"); + static_assert(blocks::SpatialTermTraits::family == functional_space::Family::h1, "Density should be in an H1 space"); + static_assert(blocks::SpatialTermTraits::order == 3, "Density should be in a third order space"); + static_assert(blocks::SpatialTermTraits::rank == 0, "Density should be a scalar field"); + static_assert(blocks::SpatialTermTraits::family == functional_space::Family::h1, "Displacement should be in an H1 space"); + static_assert(blocks::SpatialTermTraits::order == 3, "Displacement should be in a third order space"); + static_assert(blocks::SpatialTermTraits::rank == 1, "Displacement should be a vector field"); + static_assert(blocks::SpatialTermTraits::family == functional_space::Family::h1, "Gravity potential should be in an H1 space"); + static_assert(blocks::SpatialTermTraits::order == 4, "Gravity potential should be in a fourth order space"); + static_assert(blocks::SpatialTermTraits::rank == 0, "Gravity potential should be a scalar field"); } diff --git a/src/compile_time_checks/serif/discretization/fields_meanfield.cpp b/src/compile_time_checks/serif/discretization/fields_meanfield.cpp new file mode 100644 index 00000000..b5bb7859 --- /dev/null +++ b/src/compile_time_checks/serif/discretization/fields_meanfield.cpp @@ -0,0 +1,93 @@ +// MeanField field/base/registry/block contracts expressed through SERiF's production descriptors. +#include +#include +#include + +#include "serif/discretization/blocks/blocks.hpp" +#include "serif/discretization/forms/runtime.hpp" +#include "serif/backend/mfem/discretization/quadrature/order.hpp" +#include "serif/equations/point.hpp" +#include "serif/utils/dependencies/dependency_context.hpp" + +namespace serif::compile_time_checks::fields_meanfield +{ + namespace blocks = discretization::blocks; + namespace spaces = discretization::functional_space; + namespace forms = discretization::forms; + namespace domain = discretization::domain; + namespace dependencies = utils::dependencies; + + using Density = blocks::density::mass; + using Potential = blocks::gravity::potential; + using Displacement = blocks::displacement::vector; + using Constant = blocks::barotropic_constant::mass_normalization; + using AngularVelocity = blocks::rotation::angular_velocity; + + static_assert(blocks::Field && blocks::Field && blocks::Field && blocks::Field && blocks::Field); + static_assert(blocks::SpatialTerm && blocks::SpatialTerm && blocks::SpatialTerm); + static_assert(blocks::GlobalScalarTerm && blocks::GlobalScalarTerm); + static_assert(!blocks::SpatialTerm && !blocks::HasFunctionalSpace); + static_assert(!blocks::SpatialTerm && !blocks::HasFunctionalSpace); + static_assert(!blocks::Term && !blocks::SpatialTerm && !blocks::GlobalScalarTerm); + static_assert(std::same_as, domain::StellarDomains>); + static_assert(std::same_as, domain::AllDomains>); + static_assert(std::same_as, domain::AllDomains>); + static_assert(Constant::value::static_block_size == 1 && Constant::residual::static_block_size == 1); + static_assert(AngularVelocity::value::static_block_size == 1 && AngularVelocity::residual::static_block_size == 1); + static_assert(Density::value::static_block_size == blocks::DYNAMIC_BLOCK_SIZE); + static_assert(std::derived_from && std::derived_from); + static_assert(!std::derived_from); + static_assert(std::derived_from && std::derived_from); + static_assert(static_cast(blocks::value_block<2>{}) == 2); + static_assert(static_cast(blocks::residual_block<3>{}) == 3); + + using ScalarL2 = spaces::DiscreteFunctionalSpace; + using VectorH1 = spaces::DiscreteFunctionalSpace; + using VectorRT = spaces::DiscreteFunctionalSpace; + using VectorND = spaces::DiscreteFunctionalSpace; + static_assert(ScalarL2::rank::value == 0 && ScalarL2::order == 2 && ScalarL2::functional_space.family == spaces::Family::l2); + static_assert(VectorH1::rank::value == 1 && VectorH1::order == 3 && VectorH1::functional_space.trace == spaces::Trace::value); + static_assert(VectorRT::rank::value == 1 && VectorRT::functional_space.mapping == spaces::Mapping::contravariant_piola); + static_assert(VectorND::rank::value == 1 && VectorND::functional_space.mapping == spaces::Mapping::covariant_piola); + static_assert(blocks::SpatialTermTraits::order == 3 && blocks::SpatialTermTraits::order == 4); + static_assert(blocks::SpatialTermTraits::family == spaces::Family::h1 && blocks::SpatialTermTraits::family == spaces::Family::h1); + + using GradientOperand = forms::Operand; + using DivergenceOperand = forms::Operand; + static_assert(forms::IsOperation && forms::IsOperation && forms::IsOperation && forms::IsOperation && forms::IsOperation); + static_assert(!forms::IsOperation && !forms::IsOperand); + static_assert(std::same_as && std::same_as); + static_assert(std::same_as); + static_assert(discretization::quadrature::backend::mfem::resolve_operand_order>() == 3); + static_assert(discretization::quadrature::backend::mfem::resolve_operand_order() == 2); + static_assert(discretization::quadrature::backend::mfem::resolve_operand_order() == 2); + static_assert(discretization::quadrature::backend::mfem::resolve_operand_order>() == 0); + static_assert(forms::form_descriptor> != forms::form_descriptor>); + static_assert(forms::form_descriptor>.trial == blocks::term_descriptor); + static_assert(forms::form_descriptor>.test == blocks::term_descriptor); + + using Keys = dependencies::Bindings, dependencies::Binding, dependencies::Binding>; + static_assert(dependencies::SatisfiesKeyList>); + static_assert(!dependencies::SatisfiesKeyList>); + static_assert(!dependencies::SatisfiesKeyList, dependencies::KeyList>); + static_assert(std::same_as, double>); + + // Ordered physical operands retain positional identity without reintroducing a general typelist indexing API. + using DensityOperand = forms::Operand; + using DisplacementOperand = forms::Operand; + using DivergenceSample = forms::Operand; + using PotentialOperand = forms::Operand; + using OrderedSamples = equations::OperandValues>; + static_assert(OrderedSamples::count == 4); + static_assert(OrderedSamples::index() == 0); + static_assert(OrderedSamples::index() == 1); + static_assert(OrderedSamples::index() == 2); + static_assert(OrderedSamples::index() == 3); + static_assert(OrderedSamples::index() == 0 && OrderedSamples::index() == 3); + static_assert(std::same_as().get()), double&>); + static_assert(std::same_as().get()), const forms::VectorValue&>); + static_assert(std::same_as, forms::VectorValue>); + static_assert(std::same_as, double>); + static_assert(std::same_as, double>); + +} // namespace serif::compile_time_checks::fields_meanfield diff --git a/src/compile_time_checks/serif/discretization/forms/forms.cpp b/src/compile_time_checks/serif/discretization/forms/forms.cpp index c939fb3d..0f6fdea8 100644 --- a/src/compile_time_checks/serif/discretization/forms/forms.cpp +++ b/src/compile_time_checks/serif/discretization/forms/forms.cpp @@ -12,15 +12,15 @@ namespace { // Note we define custom versions of forms that likely exist in the codebase. We are not here testing that the // forms have a particular structure; rather, we are testing that given some known structure // the form system behaves as we expect it to. - using HypotheticalTestingGravityMass = mass; - using HypotheticalTestingGravityDivergence = divergence; + using HypotheticalTestingDisplacementMass = mass; + using HypotheticalTestingDisplacementDivergence = divergence; - static_assert(Form, "GravityMass should satisfy the Form concept. A failure here indicates that the mass form is not properly defined for the gravity field. Please check out the forms submodule"); - static_assert(Form, "GravityDivergence should satisfy the Form concept. A failure here indicates that the divergence form is not properly defined for the gravity field. Please check out the forms submodule"); + static_assert(Form, "DisplacementMass should satisfy the Form concept. A failure here indicates that the mass form is not properly defined for a vector field. Please check out the forms submodule"); + static_assert(Form, "DisplacementDivergence should satisfy the Form concept. A failure here indicates that the divergence form is not properly defined for a vector field. Please check out the forms submodule"); - static_assert(std::same_as, "GravityMass::test_type should be gravity::gradient. A failure here indicates that the mass form is not properly defined for the gravity field. Please check out the forms submodule"); - static_assert(std::same_as, "GravityMass::trial_type should be gravity::gradient. A failure here indicates that the mass form is not properly defined for the gravity field. Please check out the forms submodule"); + static_assert(std::same_as, "DisplacementMass::test_type should be displacement::vector. A failure here indicates that the mass form is not properly defined for a vector field. Please check out the forms submodule"); + static_assert(std::same_as, "DisplacementMass::trial_type should be displacement::vector. A failure here indicates that the mass form is not properly defined for a vector field. Please check out the forms submodule"); - static_assert(std::same_as, "GravityDivergence::trial_type should be gravity::gradient. A failure here indicates that the divergence form is not properly defined for the gravity field. Please check out the forms submodule"); - static_assert(std::same_as, "GravityDivergence::test_type should be gravity::potential. A failure here indicates that the divergence form is not properly defined for the gravity field. Please check out the forms submodule"); + static_assert(std::same_as, "DisplacementDivergence::trial_type should be displacement::vector. A failure here indicates that the divergence form is not properly defined for a vector field. Please check out the forms submodule"); + static_assert(std::same_as, "DisplacementDivergence::test_type should be gravity::potential. A failure here indicates that the divergence form is not properly defined for a vector field. Please check out the forms submodule"); } \ No newline at end of file diff --git a/src/compile_time_checks/serif/discretization/forms/operands.cpp b/src/compile_time_checks/serif/discretization/forms/operands.cpp index 7f23d5bb..3e5f2a73 100644 --- a/src/compile_time_checks/serif/discretization/forms/operands.cpp +++ b/src/compile_time_checks/serif/discretization/forms/operands.cpp @@ -7,17 +7,17 @@ namespace { using namespace serif::discretization; using DensityValue = forms::Operand; - using GravityValue = forms::Operand; - using GravityDivergence = forms::Operand; - using GravityNormalTrace = forms::Operand; + using DisplacementValue = forms::Operand; + using DisplacementDivergence = forms::Operand; + using DisplacementNormalTrace = forms::Operand; using DisplacementGradient = forms::Operand; static_assert(forms::IsOperand, "DensityValue should be a valid operand"); - static_assert(forms::IsOperand, "GravityValue should be a valid operand"); - static_assert(forms::IsOperand, "GravityDivergence should be a valid operand"); - static_assert(forms::IsOperand, "GravityNormalTrace should be a valid operand"); + static_assert(forms::IsOperand, "DisplacementValue should be a valid operand"); + static_assert(forms::IsOperand, "DisplacementDivergence should be a valid operand"); + static_assert(forms::IsOperand, "DisplacementNormalTrace should be a valid operand"); static_assert(forms::IsOperand, "DisplacementGradient should be a valid operand"); - static_assert(std::same_as, "GravityDivergence should have the correct term type"); - static_assert(std::same_as, "GravityDivergence should have the correct operation type"); + static_assert(std::same_as, "DisplacementDivergence should have the correct term type"); + static_assert(std::same_as, "DisplacementDivergence should have the correct operation type"); } \ No newline at end of file diff --git a/src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp b/src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp deleted file mode 100644 index 36473e7a..00000000 --- a/src/compile_time_checks/serif/discretization/quadrature/backend/mfem/mfem_resolver.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "serif/discretization/quadrature/backend/mfem/mfem_resolver.hpp" -#include "serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp" - -#include "serif/utils/error/errors.hpp" - -namespace { - using namespace serif::discretization; - using namespace serif::utils::errors; - using namespace quadrature::backend::mfem; - - using GravityMassForm = forms::mass; - using GravityDivergenceForm = forms::divergence; - using GravityBoundaryForm = forms::boundary_flux; - - using DensityProjectionForm = forms::projection; - - - // Note when using serif_assert (since it is a consteval function) it must be within - // a function (C++ does not allow arbitrary calls to consteval functions outside of a function context). This is why we have this function here. It is not called, but it is used to force the compiler to evaluate the consteval function and thus trigger the compile time checks. - consteval void compile_checks() { - serif::utils::errors::serif_assert == 6>(); - serif::utils::errors::serif_assert == 4>(); - serif::utils::errors::serif_assert == 4>(); - serif::utils::errors::serif_assert == 4>(); - } -} \ No newline at end of file diff --git a/src/compile_time_checks/serif/eos/consumers_meanfield.cpp b/src/compile_time_checks/serif/eos/consumers_meanfield.cpp new file mode 100644 index 00000000..a72db8de --- /dev/null +++ b/src/compile_time_checks/serif/eos/consumers_meanfield.cpp @@ -0,0 +1,41 @@ +#include "serif/eos/models/concepts.hpp" +#include "serif/eos/models/polytropic.hpp" + +namespace serif::compile_time_checks::eos_meanfield::consumer_contracts +{ + /** @brief Describe exactly one EOS relation; unrelated consumer capabilities remain absent. */ + template + struct SingleRelationEOS + { + using Relation = eos::relations::Relation; + using Relations = eos::relations::RelationCatalog; + + /** @brief Preserve strong physical input/output types in the minimal consumer probe. */ + [[nodiscard]] constexpr dimensions::QuantityValue evaluate(Relation, dimensions::QuantityValue value) const + { + return dimensions::QuantityValue{value.value()}; + } + + /** @brief Make derivative availability independent of value evaluation. */ + [[nodiscard]] constexpr dimensions::PartialDerivative partial_derivative(Relation, dimensions::WithRespectTo, dimensions::QuantityValue) const requires HasDerivative + { + return dimensions::PartialDerivative{1.0}; + } + }; + + using DensityClosure = SingleRelationEOS; + using DensityWithoutDerivative = SingleRelationEOS; + using EnthalpyPressure = SingleRelationEOS; + using DensitySeed = SingleRelationEOS; + using GeneralEOS = SingleRelationEOS; + + using DensityFromEnthalpy = eos::relations::DensityFromSpecificEnthalpy; + static_assert(eos::models::EOSSupportingPartialDerivative); + static_assert(eos::models::EOSModel && eos::models::EOSSupportingRelation && + !eos::models::EOSSupportingPartialDerivative); + static_assert(!eos::models::EOSSupportingRelation); + static_assert(eos::models::StructureSeedEOS && eos::models::StructureSeedEOS); + static_assert(!eos::models::StructureSeedEOS); + static_assert(eos::models::EOSModel); + static_assert(!eos::models::StructureSeedEOS && !eos::models::EOSSupportingRelation); +} // namespace serif::compile_time_checks::eos_meanfield::consumer_contracts diff --git a/src/compile_time_checks/serif/eos/evaluation.cpp b/src/compile_time_checks/serif/eos/evaluation.cpp new file mode 100644 index 00000000..33238392 --- /dev/null +++ b/src/compile_time_checks/serif/eos/evaluation.cpp @@ -0,0 +1,21 @@ +#include "serif/eos/evaluation.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/model/eos_reference.hpp" + +#include +#include + +using namespace serif::eos; +using namespace serif::dimensions; + +static_assert(std::same_as(std::declval(), DensityValue{1.0})), PressureValue>); +static_assert(std::same_as(std::declval(), SpecificEnthalpyValue{1.0})), PartialDerivative>); +static_assert(std::same_as(std::declval(), DensityValue{1.0})), PartialDerivative>); +static_assert(!noexcept(evaluate(std::declval(), DensityValue{1.0})), "Invalid thermodynamic inputs must be allowed to throw"); +static_assert(std::same_as(std::declval(), PressureValue{1.0})), DensityValue>); +static_assert(std::same_as( + std::declval(), DensityValue{1.0})), SecondPartialDerivative>); +static_assert(models::EOSSupportingSecondPartialDerivative, relations::PressureFromDensity, MassDensity, MassDensity>); +static_assert(!models::EOSSupportingSecondPartialDerivative); +static_assert(!models::EOSSupportingSecondPartialDerivative); +static_assert(!models::EOSSupportingSecondPartialDerivative); diff --git a/src/compile_time_checks/serif/eos/models/traits.cpp b/src/compile_time_checks/serif/eos/models/traits.cpp new file mode 100644 index 00000000..5bd8daa5 --- /dev/null +++ b/src/compile_time_checks/serif/eos/models/traits.cpp @@ -0,0 +1,23 @@ +#include "serif/eos/models/traits.hpp" +#include "serif/eos/models/concepts.hpp" +#include "serif/eos/models/polytropic.hpp" + +using namespace serif::eos; +using namespace serif::dimensions; + +static_assert(models::EOSModel, "Polytrope should satisfy the EOS model contract"); +static_assert(models::StructureSeedEOS, "Polytrope should support structure seeds"); +static_assert(models::EOSSupportingPartialDerivative, "Polytrope should support density from enthalpy and its derivative"); + +static_assert(models::EOSSupportingRelation, "Polytrope should support pressure from density"); +static_assert(models::EOSSupportingRelation, "Polytrope should support pressure from specific enthalpy"); +static_assert(models::EOSSupportingRelation, "Polytrope should support specific enthalpy from density"); +static_assert(models::EOSSupportingRelation, "Polytrope should support specific enthalpy from pressure"); +static_assert(models::EOSSupportingRelation, "Polytrope should support density from specific enthalpy"); + +static_assert(models::EOSSupportingPartialDerivative, "Polytrope should support partial derivative of pressure with respect to density"); +static_assert(models::EOSSupportingPartialDerivative, "Polytrope should support partial derivative of pressure with respect to specific enthalpy"); +static_assert(models::EOSSupportingPartialDerivative, "Polytrope should support partial derivative of density with respect to specific enthalpy"); +static_assert(!models::EOSSupportingPartialDerivative, "Polytrope should not support partial derivative of specific enthalpy with respect to density"); +static_assert(!models::EOSSupportingPartialDerivative, "Polytrope should not support partial derivative of pressure with respect to temperature"); +static_assert(!models::EOSModel, "double should not satisfy the EOS model contract"); diff --git a/src/compile_time_checks/serif/geometry/deformation.cpp b/src/compile_time_checks/serif/geometry/deformation.cpp new file mode 100644 index 00000000..b57c71bd --- /dev/null +++ b/src/compile_time_checks/serif/geometry/deformation.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include + +#include "serif/geometry/deformation/actions.hpp" +#include "serif/geometry/deformation/composition.hpp" +#include "serif/geometry/deformation/linearization.hpp" +#include "serif/geometry/deformation/radial_extension.hpp" +#include "serif/geometry/deformation/surface_embedding.hpp" + +namespace +{ + namespace deformation = serif::geometry::deformation; + namespace spaces = deformation::spaces; + + /** @brief Check nonlinear capabilities without linking or running numerical implementations. */ + struct NonlinearSurface + { + using InputSpace = spaces::ShapeParameters; + using OutputSpace = spaces::SurfaceDisplacement; + + struct Workspace + { + }; + + /// Declare the shape coordinate count needed by action validation. + [[nodiscard]] std::size_t input_size() const; + /// Declare the output trace count needed by composition validation. + [[nodiscard]] std::size_t output_size() const; + /// Require explicit caller-owned scratch construction. + [[nodiscard]] Workspace make_workspace() const; + /// Publish the primal map capability. + [[nodiscard]] deformation::DeformationStatus build_displacement(std::span, std::span, Workspace&) const; + /// Publish the exact tangent capability. + [[nodiscard]] deformation::DeformationStatus apply_jacobian(std::span, std::span, std::span, Workspace&) const; + /// Publish the work-conjugate transpose capability. + [[nodiscard]] deformation::DeformationStatus apply_jacobian_transpose(std::span, std::span, std::span, Workspace&) const; + /// Require the geometric second-derivative term instead of assuming that it vanishes. + [[nodiscard]] deformation::DeformationStatus apply_pullback_derivative(std::span, std::span, std::span, std::span, Workspace&) const; + }; + + /** @brief Linear declarations may retain the full differentiable interface. */ + struct DeclaredLinearSurface : NonlinearSurface + { + static constexpr bool is_linear = true; + }; + + /** @brief Forward actions cannot stand in for the required work-conjugate transpose. */ + struct IncompleteLinearSurface + { + using InputSpace = spaces::ShapeParameters; + using OutputSpace = spaces::SurfaceDisplacement; + using Workspace = deformation::EmptyDeformationWorkspace; + static constexpr bool is_linear = true; + + /// Declare the native shape parameter count. + [[nodiscard]] std::size_t input_size() const; + /// Declare the surface displacement count. + [[nodiscard]] std::size_t output_size() const; + /// Deliberately omit the transpose contract. + [[nodiscard]] deformation::DeformationStatus apply(std::span, std::span, Workspace&) const; + }; + + struct MissingPullback : NonlinearSurface + { + /// Hide the inherited capability to model an incomplete nonlinear interface. + deformation::DeformationStatus apply_pullback_derivative(std::span, std::span, std::span, std::span, Workspace&) const = delete; + }; + + struct MissingTranspose : NonlinearSurface + { + /// A forward tangent alone cannot establish virtual-work preservation. + deformation::DeformationStatus apply_jacobian_transpose(std::span, std::span, std::span, Workspace&) const = delete; + }; + + struct WrongTrace : NonlinearSurface + { + using OutputSpace = spaces::VolumeDisplacement; + }; + + struct MissingSemanticSpace : NonlinearSurface + { + private: + using OutputSpace = spaces::SurfaceDisplacement; + }; + + /** @brief Equal vector lengths cannot substitute for matching physical meanings at a composition boundary. */ + template + concept CanCompose = requires(std::shared_ptr surface, std::shared_ptr extension) + { + deformation::compose_deformation(surface, extension); + }; + + static_assert(deformation::LinearDeformationMap); + static_assert(deformation::LinearDeformationMap); + static_assert(!std::default_initializable); + static_assert(!std::default_initializable); + static_assert(deformation::DeformationMap); + static_assert(deformation::DifferentiableDeformationMap); + static_assert(!deformation::LinearDeformationMap); + static_assert(!deformation::is_linear_deformation); + static_assert(!deformation::DeformationMap); + static_assert(!deformation::DeformationMap); + static_assert(!deformation::DeformationMap); + static_assert(!deformation::DeformationMap); + static_assert(!deformation::DeformationMap); + static_assert(!std::default_initializable); + static_assert(!std::is_aggregate_v); + static_assert(!std::constructible_from); + static_assert(std::same_as().data()), const deformation::RadialInterpolationData&>); + static_assert(CanCompose); + static_assert(CanCompose); + static_assert(!CanCompose); + static_assert(!CanCompose); + + using LinearDomain = deformation::ComposedDeformation; + using NonlinearDomain = deformation::ComposedDeformation; + using DeclaredLinearDomain = deformation::ComposedDeformation; + static_assert(deformation::LinearDeformationMap); + static_assert(!deformation::DifferentiableDeformationMap); + static_assert(std::same_as); + static_assert(deformation::DeformationMap); + static_assert(deformation::is_linear_deformation); + static_assert(!deformation::LinearDeformationMap); + static_assert(!std::default_initializable); + static_assert(deformation::DeformationMap); + static_assert(deformation::is_linear_deformation); + static_assert(!deformation::is_linear_deformation); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as, deformation::EmptyDeformationWorkspace>); + static_assert(std::same_as, NonlinearSurface::Workspace>); + static_assert(std::same_as, LinearDomain::Workspace>); +} // namespace diff --git a/src/compile_time_checks/serif/geometry/deformation_meanfield.cpp b/src/compile_time_checks/serif/geometry/deformation_meanfield.cpp new file mode 100644 index 00000000..a84b8494 --- /dev/null +++ b/src/compile_time_checks/serif/geometry/deformation_meanfield.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include + +#include "serif/geometry/deformation/actions.hpp" +#include "serif/backend/mfem/geometry/deformation/inputs.hpp" + +namespace +{ + namespace deformation = serif::geometry::deformation; + namespace spaces = deformation::spaces; + namespace backend = deformation::backend::mfem; + namespace domain = serif::discretization::domain; + using Schema = domain::schema::CoreEnvelopeVacuumDomainSchema; + using AlternateBoundarySchema = domain::schema::DomainSchema, domain::ids::BoundaryID>, domain::relation::lists::RelationList<>>; + using MissingSurfaceSchema = domain::schema::DomainSchema>, domain::relation::lists::RelationList<>>; + + /** @brief Missing physical boundary names must fail the public discretization factory's constraints. */ + template + concept CanResolveDeformation = requires(const mfem::FiniteElementSpace& scalar, const mfem::FiniteElementSpace& vector, mfem::Mesh& logical) + { + backend::deformation_discretization(scalar, vector, logical); + }; + + static_assert(CanResolveDeformation); + static_assert(CanResolveDeformation); + static_assert(!CanResolveDeformation); + + + /** @brief Input views may be temporary, but their owning containers may not be. */ + template + concept CanBindShape = requires(const deformation::DomainDeformation& model, + const backend::ReferenceDiscretization& reference, Coefficients&& coefficients) { + backend::make_deformation_inputs(model, reference, std::forward(coefficients)); + }; + + static_assert(CanBindShape>); + static_assert(CanBindShape>); + static_assert(CanBindShape>); + static_assert(CanBindShape&>); + static_assert(CanBindShape&>); + static_assert(CanBindShape&>); + static_assert(!CanBindShape>); + static_assert(!CanBindShape>); + static_assert(!CanBindShape>); + static_assert(!CanBindShape>); + + /** @brief Remove one capability at a time without providing any numerical implementation. */ + template + struct MapCapabilities + { + using InputSpace = Input; + using OutputSpace = Output; + using Workspace = deformation::EmptyDeformationWorkspace; + + /** @brief Advertise the native physical input count. */ + [[nodiscard]] std::size_t input_size() const; + /** @brief Advertise the native physical output count. */ + [[nodiscard]] std::size_t output_size() const; + /** @brief Require an explicit scratch construction contract even for an empty workspace. */ + [[nodiscard]] Workspace make_workspace() const; + /** @brief Conditionally advertise the forward deformation. */ + [[nodiscard]] deformation::DeformationStatus build_displacement(std::span, std::span, Workspace&) const requires Build; + /** @brief Conditionally advertise its tangent. */ + [[nodiscard]] deformation::DeformationStatus apply_jacobian(std::span, std::span, std::span, Workspace&) const requires Jacobian; + /** @brief Conditionally advertise the work-conjugate transpose. */ + [[nodiscard]] deformation::DeformationStatus apply_jacobian_transpose(std::span, std::span, std::span, Workspace&) const requires Transpose; + /** @brief Conditionally advertise the geometric part of the Newton force derivative. */ + [[nodiscard]] deformation::DeformationStatus apply_pullback_derivative(std::span, std::span, std::span, std::span, Workspace&) const requires Curvature; + }; + + /** @brief Enforce the old complete contract through the common surface, extension and composed-map concept. */ + template + consteval bool complete_contract_is_required() + { + static_assert(deformation::DeformationMap>); + static_assert(!deformation::DeformationMap>); + static_assert(!deformation::DeformationMap>); + static_assert(!deformation::DeformationMap>); + static_assert(!deformation::DeformationMap>); + return true; + } + static_assert(complete_contract_is_required()); + // Interior and vacuum behavior now share one extension map with the same complete derivative contract. + static_assert(complete_contract_is_required()); + static_assert(complete_contract_is_required()); +} // namespace diff --git a/src/compile_time_checks/serif/geometry/surface.cpp b/src/compile_time_checks/serif/geometry/surface.cpp new file mode 100644 index 00000000..f7259d0c --- /dev/null +++ b/src/compile_time_checks/serif/geometry/surface.cpp @@ -0,0 +1,188 @@ +#include + +#include "serif/eos/models/polytropic.hpp" +#include "serif/geometry/surface/surface.hpp" +#include "serif/utils/dependencies/computation_cache.hpp" + +namespace { + namespace surface = serif::geometry::surface; + namespace dependencies = serif::utils::dependencies; + namespace quantities = serif::dimensions; + namespace eos = serif::eos; + namespace blocks = serif::discretization::blocks; + + /** @brief A scalar H1 enthalpy-like carrier; surface selection depends only on its declared quantity and space. */ + struct Enthalpy : blocks::term { + using FunctionalSpace = serif::discretization::functional_space::DiscreteFunctionalSpace; + using Support = serif::discretization::domain::StellarDomains; + using Quantity = quantities::SpecificEnthalpy; + struct value final : blocks::value_block_base {}; + struct residual final : blocks::residual_block_base {}; + }; + using Density = blocks::density::mass; + using Barotropic = dependencies::Bindings>; + using DensityState = dependencies::Bindings>; + using BothCarriers = dependencies::Bindings, dependencies::Binding>; + + using DensityFromPressure = eos::relations::Relation; + using DensityFromPressureAndEnthalpy = eos::relations::Relation; + + struct TwoCarriers { + using Relations = eos::relations::RelationCatalog; + /// Only declarations are needed to prove EOS capabilities; this check library has no runtime implementation. + quantities::DensityValue evaluate(DensityFromPressure, quantities::PressureValue) const; + /// The second candidate prevents accidental selection by catalog order. + quantities::SpecificEnthalpyValue evaluate(eos::relations::SpecificEnthalpyFromPressure, quantities::PressureValue) const; + }; + + struct ReversedCarriers : TwoCarriers { + using Relations = eos::relations::RelationCatalog; + }; + + struct GeneralEOS { + using Relations = eos::relations::RelationCatalog; + /// Pressure deliberately appears after the state input in the relation. + quantities::DensityValue evaluate(DensityFromPressureAndEnthalpy, quantities::SpecificEnthalpyValue, quantities::PressureValue) const; + /// Only state partials are required; no pressure partial is declared. + quantities::PartialDerivative partial_derivative(DensityFromPressureAndEnthalpy, quantities::WithRespectTo, quantities::SpecificEnthalpyValue, quantities::PressureValue) const; + }; + + struct MissingPartial { + using Relations = eos::relations::RelationCatalog; + /// Relation support alone is insufficient for a Newton linearization. + quantities::DensityValue evaluate(DensityFromPressureAndEnthalpy, quantities::SpecificEnthalpyValue, quantities::PressureValue) const; + }; + + struct AmbiguousEOS : GeneralEOS { + using Relations = eos::relations::RelationCatalog; + using GeneralEOS::evaluate; + /// Even explicitly choosing density leaves these two relations ambiguous. + quantities::DensityValue evaluate(DensityFromPressure, quantities::PressureValue) const; + }; + + struct ForwardOnly { + using Relations = eos::relations::RelationCatalog; + /// A forward relation must not be silently inverted. + quantities::PressureValue evaluate(eos::relations::PressureFromDensity, quantities::DensityValue) const; + /// The direct-pressure formulation requires this derivative instead. + quantities::PartialDerivative partial_derivative(eos::relations::PressureFromDensity, quantities::WithRespectTo, quantities::DensityValue) const; + }; + + using TwoStateRelation = eos::relations::Relation; + using TwoStateBindings = dependencies::Bindings, dependencies::Binding, dependencies::Binding>; + + struct MissingSecondPartial { + using Relations = eos::relations::RelationCatalog; + /// MeanField's general EOS contract requires every additional state input, not just one of them. + quantities::SpecificEnthalpyValue evaluate(TwoStateRelation, quantities::PressureValue, quantities::EntropyValue, quantities::TemperatureValue) const; + /// Supplying the entropy partial does not compensate for the missing temperature partial. + quantities::PartialDerivative partial_derivative(TwoStateRelation, quantities::WithRespectTo, quantities::PressureValue, quantities::EntropyValue, quantities::TemperatureValue) const; + }; + + struct CompletePartials : MissingSecondPartial { + using MissingSecondPartial::partial_derivative; + /// Adding precisely the missing partial makes this relation compilable. + quantities::PartialDerivative partial_derivative(TwoStateRelation, quantities::WithRespectTo, quantities::PressureValue, quantities::EntropyValue, quantities::TemperatureValue) const; + }; + + struct SampleOnlyTerm : blocks::term { + using FunctionalSpace = Enthalpy::FunctionalSpace; + using Support = Enthalpy::Support; + }; + + template + concept SelectsCarrier = requires { + typename Candidate::CarrierQuantity; + }; + + template + concept HasSurfaceRelation = requires { + typename surface::pressure_relation_t; + }; + + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!SelectsCarrier); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + + using DuplicateQuantities = dependencies::Bindings, dependencies::Binding>; + using SharedTarget = dependencies::Bindings, dependencies::Binding>; + using VectorCarrier = dependencies::Bindings>; + using InvalidQuantity = dependencies::Bindings>; + + static_assert(surface::SurfaceStateBindings); + static_assert(surface::SurfaceStateBindings); + static_assert(surface::SurfaceStateBindings); + static_assert(!surface::SurfaceStateBindings); + static_assert(!surface::SurfaceStateBindings); + static_assert(!surface::SurfaceStateBindings); + static_assert(!surface::SurfaceStateBindings>); + static_assert(!surface::SurfaceStateBindings); + static_assert(surface::SurfaceScalarTerm); + static_assert(!surface::PressureSurfaceCompilable>>); + + static_assert(surface::PressureSurfaceCompilable); + static_assert(surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!HasSurfaceRelation); + static_assert(!HasSurfaceRelation); + static_assert(!HasSurfaceRelation); + + static_assert(surface::PressureSurfaceCompilable); + static_assert(surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(surface::PressureSurfaceCompilable); + static_assert(surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + static_assert(surface::PressureSurfaceCompilable); + static_assert(!surface::PressureSurfaceCompilable); + + using General = surface::PressureSurfaceFormulation; + using Shared = surface::PressureSurfaceFormulation; + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(std::same_as); + static_assert(std::same_as>); + + static_assert(surface::DirectPressureSurfaceCompilable); + static_assert(!surface::DirectPressureSurfaceCompilable); + static_assert(!surface::DirectPressureSurfaceCompilable); + static_assert(!surface::DirectPressureSurfaceCompilable); + static_assert(!surface::DirectPressureSurfaceCompilable); + + using Direct = surface::DirectPressureSurfaceFormulation; + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(!Direct::resolves_carrier); + static_assert(surface::PressureSurfaceFormulationType); + static_assert(!surface::PressureSurfaceFormulationType); + + using Graph = decltype(surface::make_pressure_surface_graph()); + using EOSContext = decltype(dependencies::make_dependency_context(dependencies::borrow(std::declval()))); + static_assert(!dependencies::CanPrepare); + static_assert(!std::copy_constructible( + surface::make_pressure_surface_graph(), + dependencies::make_dependency_context( + dependencies::borrow(std::declval(), dependencies::track_by([](const auto& model) { + return std::pair{model.polytropic_index(), model.polytropic_constant()}; + })), + surface::pressure_condition(std::declval()), + surface::boundary_values({0.4}) + )))>); +} diff --git a/src/compile_time_checks/serif/geometry/surface_boundary.cpp b/src/compile_time_checks/serif/geometry/surface_boundary.cpp new file mode 100644 index 00000000..919e0010 --- /dev/null +++ b/src/compile_time_checks/serif/geometry/surface_boundary.cpp @@ -0,0 +1,273 @@ +#include +#include +#include + +#include "serif/discretization/domain/schema/schemas.hpp" +#include "serif/backend/mfem/geometry/surface/boundary_computations.hpp" +#include "serif/geometry/surface/automatic.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/utils/dependencies/computation_cache.hpp" + +namespace serif::compile_time_checks::surface_boundary +{ + namespace surface = serif::geometry::surface; + namespace backend = surface::backend::mfem; + namespace discretization = serif::discretization; + namespace finite_elements = discretization::backend::mfem; + namespace coefficients = serif::operators::backend::mfem; + namespace blocks = discretization::blocks; + namespace spaces = discretization::functional_space; + namespace quantities = serif::dimensions; + namespace dependencies = serif::utils::dependencies; + namespace eos = serif::eos; + + template + struct ScalarTerm : blocks::term + { + using FunctionalSpace = Space; + using Support = discretization::domain::StellarDomains; + using Quantity = QuantityT; + + struct value : blocks::value_block_base + { + }; + + struct residual : blocks::residual_block_base + { + }; + }; + + struct UniformTemperature : blocks::term, blocks::global_scalar + { + using Support = discretization::domain::StellarDomains; + using Quantity = quantities::Temperature; + static constexpr bool thermodynamic_state = true; + }; + + using Density = ScalarTerm>; + using DiscontinuousDensity = ScalarTerm>; + using Temperature = ScalarTerm>; + using DiscontinuousTemperature = ScalarTerm>; + using DensityRelation = eos::relations::Relation; + using PressureRelation = eos::relations::Relation; + + struct Gas + { + using Relations = eos::relations::RelationCatalog; + + /** @brief Declare a density-carried pressure condition without enthalpy. */ + quantities::DensityValue evaluate(DensityRelation, quantities::PressureValue, quantities::TemperatureValue) const; + /** @brief Declare the independent forward-pressure relation. */ + quantities::PressureValue evaluate(PressureRelation, quantities::DensityValue, quantities::TemperatureValue) const; + /** @brief Require the non-carrier derivative in the inverse relation. */ + quantities::PartialDerivative partial_derivative(DensityRelation, quantities::WithRespectTo, quantities::PressureValue, quantities::TemperatureValue) const; + /** @brief Require the density column in the forward relation. */ + quantities::PartialDerivative partial_derivative(PressureRelation, quantities::WithRespectTo, quantities::DensityValue, quantities::TemperatureValue) const; + /** @brief Require the temperature column in the forward relation. */ + quantities::PartialDerivative partial_derivative(PressureRelation, quantities::WithRespectTo, quantities::DensityValue, quantities::TemperatureValue) const; + /** @brief This declaration-only EOS has no mutable configuration. */ + bool operator==(const Gas&) const = default; + }; + + template + using State = dependencies::Bindings, dependencies::Binding>; + + using Formulation = surface::PressureSurfaceFormulation>; + using GlobalInput = surface::PressureSurfaceFormulation>; + using DiscontinuousInput = surface::PressureSurfaceFormulation>; + using DiscontinuousRow = surface::PressureSurfaceFormulation>; + using Direct = surface::DirectPressureSurfaceFormulation, quantities::Temperature, PressureRelation>; + using GlobalRow = surface::DirectPressureSurfaceFormulation, quantities::Temperature, PressureRelation>; + + static_assert(finite_elements::BoundaryTraceTerm); + static_assert(finite_elements::BoundaryTraceTerm); + static_assert(finite_elements::BoundaryTraceTerm); + static_assert(!finite_elements::BoundaryTraceTerm); + static_assert(!finite_elements::BoundaryTraceTerm); + static_assert(!finite_elements::BoundaryTraceTerm); + static_assert(backend::PressureBoundaryFormulation); + static_assert(backend::PressureBoundaryFormulation); + static_assert(backend::PressureBoundaryFormulation); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(!backend::PressureBoundaryFormulation); + static_assert(!backend::PressureBoundaryFormulation); + static_assert(!backend::PressureBoundaryFormulation); + static_assert(!backend::PressureBoundaryFormulation); + + using Schema = discretization::domain::schema::CoreEnvelopeVacuumDomainSchema; + template + using View = coefficients::CoefficientView>; + + template + concept BuildsTrace = requires(std::shared_ptr rows, const finite_elements::TermBinding& binding) + { + finite_elements::make_boundary_trace(rows, binding); + }; + + static_assert(BuildsTrace); + static_assert(BuildsTrace); + static_assert(BuildsTrace); + static_assert(!BuildsTrace); + static_assert(!BuildsTrace); + + template + concept BuildsTemporaryTrace = requires(std::shared_ptr rows) + { + finite_elements::make_boundary_trace(rows, std::declval()); + }; + + static_assert(!BuildsTemporaryTrace>); + static_assert(!BuildsTemporaryTrace>); + static_assert(!BuildsTemporaryTrace>); + + using Coefficients = decltype(coefficients::make_term_coefficients(std::declval>(), std::declval>())); + using MissingColumn = decltype(coefficients::make_term_coefficients(std::declval>())); + + template + concept BuildsBoundary = requires(const Context& context) + { + backend::make_pressure_boundary(context); + }; + + static_assert(BuildsBoundary); + static_assert(!BuildsBoundary); + using Boundary = decltype(backend::make_pressure_boundary(std::declval())); + using EOSResource = decltype(dependencies::own(Gas{})); + using WrongEOS = decltype(dependencies::own(0)); + using WrongEOSKey = decltype(dependencies::own(Gas{})); + + template + concept BuildsInputs = requires(Boundary boundary, Resource equation_of_state, surface::IsobaricSurface condition, const Context& context) + { + backend::make_pressure_boundary_inputs(boundary, equation_of_state, condition, context); + }; + + static_assert(BuildsInputs); + static_assert(!BuildsInputs); + static_assert(!BuildsInputs); + static_assert(!BuildsInputs); + + using Inputs = decltype(backend::make_pressure_boundary_inputs(std::declval(), std::declval(), std::declval(), std::declval())); + using Graph = decltype(backend::make_pressure_boundary_graph()); + using MissingEOS = decltype(std::declval().select(dependencies::KeyList{})); + using MissingState = decltype(std::declval().select(dependencies::KeyList{})); + + static_assert(dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + static_assert(std::same_as, View>); + static_assert(std::same_as, View>); + + using GlobalCoefficients = decltype(coefficients::make_term_coefficients(std::declval>(), std::declval>())); + using GlobalBoundary = decltype(backend::make_pressure_boundary(std::declval())); + using GlobalInputs = decltype(backend::make_pressure_boundary_inputs(std::declval(), std::declval(), std::declval(), std::declval())); + using DirectBoundary = decltype(backend::make_pressure_boundary(std::declval())); + using DirectInputs = decltype(backend::make_pressure_boundary_inputs(std::declval(), std::declval(), std::declval(), std::declval())); + static_assert(dependencies::CanPrepare()), GlobalInputs, surface::SurfaceFailure>); + static_assert(dependencies::CanPrepare()), DirectInputs, surface::SurfaceFailure>); + + using DensitySampling = Graph::ComputationAt>()>; + using TemperatureSampling = Graph::ComputationAt>()>; + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(!std::same_as, Density>); + static_assert(Graph::size == 4); + + /** @brief An enthalpy-dimensioned global multiplier must not be mistaken for the enthalpy carrier. */ + struct CentralMultiplier : blocks::term, blocks::global_scalar + { + using Support = discretization::domain::StellarDomains; + using Quantity = quantities::SpecificEnthalpy; + }; + + using Enthalpy = ScalarTerm>; + using Pressure = ScalarTerm>; + using BernoulliConstant = blocks::barotropic_constant::mass_normalization; + using GasFields = dependencies::KeyList; + using GasRows = dependencies::KeyList; + using GasSelection = surface::AutomaticPressureSurface; + static_assert(GasSelection::status == surface::PressureSurfaceSelection::supported); + static_assert(GasSelection::Formulation::resolves_carrier); + static_assert(std::same_as); + static_assert(!surface::AutomaticPressureSurfaceCompilable>); + static_assert(!surface::AutomaticPressureSurfaceCompilable>); + static_assert(!surface::AutomaticPressureSurfaceCompilable, GasRows>); + static_assert(!surface::AutomaticPressureSurfaceCompilable, GasRows>); + static_assert(surface::AutomaticPressureSurfaceCompilable, GasRows>); + + struct ForwardOnly : Gas + { + using Relations = eos::relations::RelationCatalog; + }; + + struct MissingPartials + { + using Relations = eos::relations::RelationCatalog; + /** @brief Value evaluation alone cannot supply an analytic boundary Jacobian. */ + quantities::PressureValue evaluate(PressureRelation, quantities::DensityValue, quantities::TemperatureValue) const; + }; + + struct MissingInversePartial : Gas + { + using Gas::partial_derivative; + + /** @brief An unusable inverse must not hide the fully differentiable forward relation. */ + quantities::PartialDerivative partial_derivative(DensityRelation, quantities::WithRespectTo, quantities::PressureValue, quantities::TemperatureValue) const = delete; + }; + + using ForwardFallback = surface::AutomaticPressureSurface; + static_assert(ForwardFallback::status == surface::PressureSurfaceSelection::supported); + static_assert(!ForwardFallback::Formulation::resolves_carrier); + + struct OtherTemperature : Temperature + { + }; + + struct OtherDensity : Density + { + }; + + using DensityFromPressure = eos::relations::Relation; + + struct TwoInverseRelations : Gas + { + using Relations = eos::relations::RelationCatalog; + using Gas::evaluate; + + /** @brief A second admissible inverse must not silently win by catalog order. */ + quantities::DensityValue evaluate(DensityFromPressure, quantities::PressureValue) const; + }; + + struct ReversedInverseRelations : TwoInverseRelations + { + using Relations = eos::relations::RelationCatalog; + }; + + using DirectSelection = surface::AutomaticPressureSurface; + static_assert(!DirectSelection::Formulation::resolves_carrier); + static_assert(!surface::AutomaticPressureSurfaceCompilable); + static_assert(surface::AutomaticPressureSurface::status == surface::PressureSurfaceSelection::ambiguous); + static_assert(surface::AutomaticPressureSurfaceCompilable); + static_assert(surface::AutomaticPressureSurface::status == surface::PressureSurfaceSelection::ambiguous); + static_assert(surface::AutomaticPressureSurface::status == surface::PressureSurfaceSelection::ambiguous); + static_assert(!surface::AutomaticPressureSurfaceCompilable, GasRows>); + static_assert(surface::AutomaticPressureSurface, dependencies::KeyList>::status == surface::PressureSurfaceSelection::ambiguous); + + using PolytropicFields = dependencies::KeyList; + using PolytropicSelection = surface::AutomaticPressureSurface>; + static_assert(std::same_as); + static_assert(!surface::AutomaticPressureSurfaceCompilable, dependencies::KeyList>); + static_assert(surface::AutomaticPressureSurfaceCompilable, dependencies::KeyList>); + + struct SymbolicEOS + { + }; + + using PressureFields = dependencies::KeyList; + using PressureSelection = surface::AutomaticPressureSurface>; + static_assert(!PressureSelection::Formulation::uses_eos); + static_assert(std::same_as::Formulation::CarrierTerm, Pressure>); + static_assert(backend::PressureBoundaryFormulation); +} diff --git a/src/compile_time_checks/serif/geometry/surface_storage_meanfield.cpp b/src/compile_time_checks/serif/geometry/surface_storage_meanfield.cpp new file mode 100644 index 00000000..6e3aba00 --- /dev/null +++ b/src/compile_time_checks/serif/geometry/surface_storage_meanfield.cpp @@ -0,0 +1,14 @@ +#include + +#include "serif/geometry/deformation/models.hpp" + +namespace serif::geometry::deformation +{ + // MeanField's move tests guarded heap-owned center vectors. The current prescriptions store their coordinates inline. + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); +} diff --git a/src/compile_time_checks/serif/model/barotropic_density.cpp b/src/compile_time_checks/serif/model/barotropic_density.cpp new file mode 100644 index 00000000..82022e4e --- /dev/null +++ b/src/compile_time_checks/serif/model/barotropic_density.cpp @@ -0,0 +1,67 @@ +#include "serif/equations/angular_momentum.hpp" +#include "serif/equations/barotropic_density.hpp" +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/equations/surface_potential.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/model/eos_reference.hpp" +#include "serif/model/execution_validation.hpp" +#include "serif/model/normalization/plan.hpp" +#include "serif/solver/normalization/computations.hpp" + +namespace serif::compile_time_checks::barotropic_density { + namespace blocks = discretization::blocks; + namespace dependencies = utils::dependencies; + namespace normalization = solver::normalization; + + /** @brief Declare only the mechanical EOS capabilities; no enthalpy, polytropic parameters, or curvature is available. */ + struct MechanicalBarotrope final { + using Relations = eos::relations::RelationCatalog; + /** @brief Declare pressure evaluation for capability checking without a runtime implementation. */ + dimensions::PressureValue evaluate(eos::relations::PressureFromDensity, dimensions::DensityValue) const; + /** @brief Declare the prescribed-pressure surface density. */ + dimensions::DensityValue evaluate(eos::relations::DensityFromPressure, dimensions::PressureValue) const; + /** @brief Declare the mechanical primitive independently of thermodynamic enthalpy. */ + dimensions::BarotropicPressurePotentialValue evaluate(eos::relations::PressurePotentialFromDensity, dimensions::DensityValue) const; + /** @brief Declare the sole pressure derivative required by the local balance. */ + dimensions::PartialDerivative partial_derivative( + eos::relations::PressureFromDensity, dimensions::WithRespectTo, dimensions::DensityValue) const; + }; + + using Balance = equations::BarotropicDensityBalance; + using BorrowedBalance = equations::BarotropicDensityBalance>; + using Registry = equations::EquationRegistry; + using Description = model::EquilibriumEquations; + struct BoundDescription { + using Description = barotropic_density::Description; + using Plan = model::EquilibriumPlan; + }; + using Plan = BoundDescription::Plan; + using Rules = model::normalization::PlanFor; + + static_assert(dimensions::ThermodynamicQuantityType); + static_assert(!std::same_as); + static_assert(eos::models::EOSSupportingRelation); + static_assert(eos::models::EOSModel); + static_assert(!eos::models::EOSSupportingRelation); + static_assert(!eos::models::EOSSupportingSecondPartialDerivative); + static_assert(operators::VolumeEquation); + static_assert(operators::VolumeEquation); + static_assert(std::same_as>); + static_assert(std::same_as); + static_assert(model::ExecutableEquilibriumDescription); + static_assert(Plan::has_shape && Plan::has_primal_gravity); + static_assert(dependencies::key_count_v == 5); + static_assert(dependencies::key_count_v == 5); + static_assert(std::same_as); + static_assert(normalization::NormalizationPreparable); + static_assert(normalization::CompleteNormalizationFor); + static_assert(std::same_as, + normalization::WithPointRows>, dimensions::MassDensity, model::normalization::SurfaceRows>>); + static_assert(std::same_as, + normalization::DimensionlessRieszCoordinate>>); +} diff --git a/src/compile_time_checks/serif/model/density_primal.cpp b/src/compile_time_checks/serif/model/density_primal.cpp new file mode 100644 index 00000000..bb80de8e --- /dev/null +++ b/src/compile_time_checks/serif/model/density_primal.cpp @@ -0,0 +1,43 @@ +#include "serif/equations/barotropic_density.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/equations/surface_potential.hpp" +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/angular_momentum.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/model/execution_validation.hpp" +#include "serif/model/normalization/plan.hpp" +#include "serif/solver/normalization/computations.hpp" + +namespace serif::compile_time_checks::density_primal { + namespace blocks = discretization::blocks; + namespace dependencies = utils::dependencies; + namespace normalization = solver::normalization; + using EOS = eos::models::Polytrope; + using Balance = equations::BarotropicDensityBalance; + using Registry = equations::EquationRegistry; + using Description = model::EquilibriumEquations; + struct BoundDescription { + using Description = density_primal::Description; + using Plan = model::EquilibriumPlan; + }; + using Plan = BoundDescription::Plan; + using Rules = model::normalization::PlanFor; + static_assert(operators::VolumeEquation); + static_assert(operators::VolumeEquation); + static_assert(model::ExecutableEquilibriumDescription); + static_assert(Plan::has_shape && Plan::has_primal_gravity); + static_assert(std::same_as>); + static_assert(dependencies::key_count_v == 5); + static_assert(dependencies::key_count_v == 5); + static_assert(std::same_as); + static_assert(normalization::NormalizationPreparable); + static_assert(normalization::CompleteNormalizationFor); + static_assert(std::same_as, + normalization::DimensionlessRieszCoordinate>); + static_assert(std::same_as, + normalization::WithPointRows>, dimensions::GravitationalPotential, + model::normalization::InfinityRows>>); +} diff --git a/src/compile_time_checks/serif/model/description.cpp b/src/compile_time_checks/serif/model/description.cpp new file mode 100644 index 00000000..49ca42e8 --- /dev/null +++ b/src/compile_time_checks/serif/model/description.cpp @@ -0,0 +1,157 @@ +#include +#include +#include + +#include "serif/model/description.hpp" +#include "serif/model/eos_reference.hpp" + +namespace +{ + namespace model = serif::model; + namespace dimensions = serif::dimensions; + + struct SymbolicEOS {}; + struct OtherSymbolicEOS {}; + using Equations = model::EquilibriumEquations; + + struct Boundary + { + using ModelDefinition = model::SpecificationDefinition; + }; + + struct OtherBoundary + { + using ModelDefinition = model::SpecificationDefinition; + }; + + struct SemanticCollision + { + using ModelDefinition = model::SpecificationDefinition; + }; + + struct SameNameDifferentRole + { + using ModelDefinition = model::SpecificationDefinition; + }; + + template + struct RoleChoice + { + using ModelDefinition = model::SpecificationDefinition; + }; + + /** @brief Retain the historical role/state admission matrix through the public specification concept. */ + template + consteval std::array admitted_states() + { + using Kind = model::GeneratedStateKind; + return {model::ModelSpecification>, model::ModelSpecification>, + model::ModelSpecification>, model::ModelSpecification>}; + } + + struct InvalidRole + { + using ModelDefinition = model::SpecificationDefinition; + }; + + struct WrongOwner + { + using ModelDefinition = model::SpecificationDefinition; + }; + + struct Malformed + { + using ModelDefinition = int; + }; + + struct IndependentUnits + { + using ScalarDescription = model::ScalarConstraint; + using ModelDefinition = model::SpecificationDefinition; + /** @brief Supply the target units independently of generated coordinate and residual units. */ + ScalarDescription::TargetValue target() const; + }; + + struct IncorrectTarget + { + using ModelDefinition = model::SpecificationDefinition; + /** @brief Deliberately mismatch the typed target to exercise detection. */ + dimensions::MassValue target() const; + }; + + using First = decltype(model::StellarModel(std::declval(), std::declval(), std::declval(), std::declval())); + using Reordered = decltype(model::StellarModel(std::declval(), std::declval(), std::declval(), std::declval())); + using Qualified = decltype(model::StellarModel(std::declval(), std::declval(), std::declval(), std::declval())); + using TwoBoundaries = decltype(model::StellarModel(SymbolicEOS{}, Equations{1}, Boundary{}, OtherBoundary{})); + using NoBoundary = decltype(model::StellarModel(SymbolicEOS{}, Equations{1})); + + template + concept CanResolveTemporary = requires { model::resolve_equilibrium_equations(std::declval()); }; + + template + concept CanResolveDescription = requires(const Candidate& candidate) { model::resolve_equilibrium_equations(candidate); }; + + template + concept CanReadUniqueBoundary = requires(const Candidate& candidate) { candidate.template specification_for_role(); }; + + static_assert(!CanResolveTemporary); + static_assert(!CanResolveDescription); + using NotEquations = decltype(model::StellarModel(SymbolicEOS{}, 5)); + static_assert(!CanResolveDescription); + + static_assert(std::same_as && std::same_as); + static_assert(model::StellarModelType && model::StellarModelType); + static_assert(model::specification_role_count == 2); + static_assert(!model::HasUniqueSpecificationForRole); + static_assert(!CanReadUniqueBoundary && !CanReadUniqueBoundary); + static_assert(CanReadUniqueBoundary); + static_assert(std::same_as().specifications_for_role()), std::tuple>); + static_assert(model::specification_role_count == 0); + static_assert(!model::StellarModelType>); + static_assert(!model::ModelSpecification && !model::ModelSpecification && !model::ModelSpecification); + static_assert(!model::ModelSpecification); + static_assert(!model::ValidSpecificationPack); + static_assert(model::ValidSpecificationPack); + static_assert(!model::ValidSpecificationPack); + static_assert(!model::ValidSpecificationPack); + static_assert(model::DimensionalScalarSpecification); + static_assert(!model::DimensionalScalarSpecification); + static_assert(std::same_as, dimensions::Dimensionless>); + static_assert(std::same_as, dimensions::Mass>); + static_assert(std::same_as, dimensions::AngularVelocity>); + static_assert(!std::same_as, model::GeneratedCoordinateOf>); + static_assert(std::same_as().specification()), const model::FixedTotalMass&>); + static_assert(std::same_as, serif::utils::types::TypeList>); + + using Role = model::SpecificationRole; + static_assert(admitted_states() == std::array{true, false, false, false}); + static_assert(admitted_states() == std::array{true, false, false, false}); + static_assert(admitted_states() == std::array{false, true, true, false}); + static_assert(admitted_states() == std::array{false, false, false, true}); + static_assert(admitted_states() == std::array{false, false, false, true}); + static_assert(admitted_states() == std::array{true, false, false, false}); + static_assert(!model::ModelSpecification(1000), model::GeneratedStateKind::none>>); + static_assert(!model::ModelSpecification(1000)>>); + + /** @brief Exercise 2 EOS x 2 boundaries x 4 constraint packs without a numerical EOS. */ + template + consteval bool independent_constraint_choices() + { + using Definition = model::EquilibriumEquations; + using Mass = model::FixedTotalMass; + using Momentum = model::FixedAngularMomentum; + return model::StellarModelType>> && + model::StellarModelType>> && + model::StellarModelType>> && + model::StellarModelType>>; + } + + static_assert(independent_constraint_choices()); + static_assert(independent_constraint_choices()); + static_assert(independent_constraint_choices()); + static_assert(independent_constraint_choices()); +} diff --git a/src/compile_time_checks/serif/model/execution_validation.cpp b/src/compile_time_checks/serif/model/execution_validation.cpp new file mode 100644 index 00000000..58909528 --- /dev/null +++ b/src/compile_time_checks/serif/model/execution_validation.cpp @@ -0,0 +1,169 @@ +#include + +#include "serif/equations/barycenter.hpp" +#include "serif/equations/angular_momentum.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/equations/barotropic_density.hpp" +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/equations/surface_potential.hpp" +#include "serif/model/execution_validation.hpp" + +namespace +{ + namespace model = serif::model; + namespace blocks = serif::discretization::blocks; + namespace dependencies = serif::utils::dependencies; + namespace equations = serif::equations; + using EOS = serif::eos::models::Polytrope; + using Registry = equations::EquationRegistry, equations::PrimalPoissonBalance, + equations::MassNormalization, equations::AngularMomentumConstraint>; + using Description = model::EquilibriumEquations; + using Plan = model::EquilibriumPlan; + using Model = model::StellarModel>; + static_assert(model::ExecutableEquilibriumDescription); + static_assert(model::executable_model_topology()); + static_assert(!model::ExecutableEquilibriumDescription); + static_assert(!model::ExecutableEquilibriumDescription>); + static_assert(!model::ExecutableEquilibriumDescription>); + static_assert(!model::ExecutableEquilibriumDescription>); + static_assert(!model::ExecutableEquilibriumDescription>); + static_assert(!model::executable_model_topology()); + static_assert(!model::executable_model_topology()); + static_assert(!model::executable_model_topology()); + + using IsobaricDescription = model::EquilibriumEquations; + using IsobaricPlan = model::EquilibriumPlan; + static_assert(model::ExecutableEquilibriumDescription); + static_assert(model::executable_model_topology()); + static_assert(IsobaricPlan::Surface::Formulation::resolves_carrier); + static_assert(std::same_as); + using PrescribedCarrier = model::EquilibriumPlan>; + static_assert(!PrescribedCarrier::Surface::supported); + static_assert(!model::executable_model_topology()); + + struct FirstSurfaceHint + { + using IsobaricSurfaceField = blocks::gravity::potential; + }; + + struct SecondSurfaceHint + { + using IsobaricSurfaceField = blocks::density::mass; + }; + + // Surface hints are irrelevant when the physical model has no surface condition. + using UnusedSurfaceHints = model::detail::SurfacePlan, dependencies::KeyList<>, + dependencies::KeyList<>, serif::utils::types::TypeList>; + static_assert(UnusedSurfaceHints::supported); + + using MissingMassClosure = model::EquilibriumEquations; + static_assert(!model::executable_model_topology>()); + + // Fixed geometry has its own mass outcome; the remaining rotation prescription still requires a mechanical row. + using FixedRegistry = equations::EquationRegistry, equations::PrimalPoissonBalance, + equations::AngularMomentumConstraint>; + using WithoutShape = model::EquilibriumEquations; + using FixedModel = model::StellarModel>; + using FixedGeometry = model::EquilibriumPlan; + static_assert(model::executable_model_topology()); + static_assert(dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); + using PrescribedShape = model::EquilibriumPlan>; + static_assert(model::executable_model_topology()); + using PrescribedDensity = model::EquilibriumPlan>; + static_assert(model::executable_model_topology()); + static_assert(!dependencies::ContainsKey); + using PrescribedOmega = model::EquilibriumPlan>; + static_assert(!model::executable_model_topology()); + + // Fixed geometry removes only its own equation; every remaining physical force must still be authored. + using WithoutHydrostatic = model::EquilibriumEquations, equations::SurfacePotentialBalance>; + using GlobalConstraints = serif::utils::types::TypeList; + using GlobalModel = model::StellarModel; + static_assert(!model::executable_model_topology>()); + using WithoutAngularMomentum = model::EquilibriumEquations, + equations::PrimalPoissonBalance, equations::MassNormalization>, equations::SurfacePotentialBalance>; + using RotationModel = model::StellarModel; + static_assert(!model::executable_model_topology>()); + + struct NonrotatingHydrostatic + { + static constexpr std::string_view name = "nonrotating hydrostatic equilibrium"; + using Test = blocks::density::mass; + using Operands = serif::utils::types::TypeList, serif::discretization::forms::Operand>; + /** @brief Declare a closed row deliberately lacking an angular-velocity effect. */ + equations::PointLinearization evaluate(const equations::EquationPoint&) const; + /** @brief Admit scalar-value integration independently of the omitted rotation operand. */ + template + int additional_order(Operand, int) const; + }; + using WithoutRotationForce = model::EquilibriumEquations, equations::SurfacePotentialBalance>; + using NoRotationForcePlan = model::EquilibriumPlan; + static_assert(!model::executable_model_topology()); + using ExplicitRotationZero = dependencies::KeyList>; + static_assert(model::executable_model_topology()); + using Actual = model::ActualEquilibriumCouplings; + static_assert(dependencies::ContainsKey, Actual>); + static_assert(dependencies::ContainsKey, Actual>); + static_assert(dependencies::ContainsKey, Actual>); + + struct CustomConstraint + { + using ScalarDescription = model::FixedTotalMass::ScalarDescription; + using ModelDefinition = model::SpecificationDefinition, model::Changes>; + /** @brief Describe a custom target without pretending that metadata implements its force. */ + serif::dimensions::MassValue target() const; + }; + + using CustomModel = model::StellarModel>; + using Coordinate = blocks::frame_acceleration::component<1>; + using Bindings = model::SpecificationBindings, Coordinate>, + dependencies::Binding, blocks::barotropic_constant::mass_normalization>>; + // The complete field layout can name lambda while the equation list supplies no lambda force. + using CustomPlan = model::ModelPlan, dependencies::KeyList>; + using InteriorOnly = model::EquilibriumEquations; + static_assert(!model::executable_model_topology()); + using ExplicitForceZero = dependencies::KeyList>; + static_assert(model::executable_model_topology()); + + using AliasedModel = model::StellarModel>; + using AliasedBindings = model::SpecificationBindings, blocks::barotropic_constant::mass_normalization>, + dependencies::Binding, blocks::barotropic_constant::mass_normalization>>; + static_assert(!model::executable_model_topology()); + + struct InvalidScalarTarget + { + using ModelDefinition = model::SpecificationDefinition, model::Changes>; + /** @brief Deliberately violate the declared mass target to exercise clean metadata rejection. */ + serif::dimensions::DensityValue target() const; + }; + using InvalidScalarModel = model::StellarModel>; + static_assert(model::StellarModelType); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::executable_model_topology()); + + struct InvalidUngeneratedReads + { + using ModelDefinition = model::SpecificationDefinition>; + }; + using InvalidUngeneratedModel = model::StellarModel>; + static_assert(model::StellarModelType); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::executable_model_topology()); +} + +namespace { + using IncompatibleEndpoint = dependencies::Bindings, blocks::frame_acceleration::component<1>>>; + static_assert(!model::executable_model_topology()); + + template + constexpr bool barycenter_admitted = serif::operators::VolumeEquation>; + static_assert(barycenter_admitted<0> && barycenter_admitted<1> && barycenter_admitted<2>); +} diff --git a/src/compile_time_checks/serif/model/finite_elements.cpp b/src/compile_time_checks/serif/model/finite_elements.cpp new file mode 100644 index 00000000..e3ac4219 --- /dev/null +++ b/src/compile_time_checks/serif/model/finite_elements.cpp @@ -0,0 +1,24 @@ +#include +#include + +#include "serif/model/finite_elements.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/equations/primal_gravity.hpp" + +namespace { + namespace model = serif::model; + namespace equations = serif::equations; + namespace blocks = serif::discretization::blocks; + namespace dependencies = serif::utils::dependencies; + using Physics = decltype(model::make_stellar_model(serif::eos::models::Polytrope{1.0, 1.0}, + model::EquilibriumEquations{.interior_equations = equations::EquationRegistry{ + equations::PrimalPoissonBalance{1.0}}})); + using FiniteElements = decltype(model::make_stellar_discretization(std::declval(), {}, + serif::geometry::deformation::DomainDeformation{})); + using Keys = typename std::remove_cvref_t().term_bindings)>::Keys; + + static_assert(dependencies::ContainsKey); + static_assert(dependencies::ContainsKey); + static_assert(dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); +} diff --git a/src/compile_time_checks/serif/model/normalization.cpp b/src/compile_time_checks/serif/model/normalization.cpp new file mode 100644 index 00000000..ab2a8f7e --- /dev/null +++ b/src/compile_time_checks/serif/model/normalization.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#include "serif/equations/angular_momentum.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/equations/barotropic_density.hpp" +#include "serif/equations/surface_potential.hpp" +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/geometry/surface/isobaric.hpp" +#include "serif/model/configured_discretization.hpp" +#include "serif/backend/mfem/model/computations/solver_layout.hpp" +#include "serif/model/normalization/plan.hpp" +#include "serif/solver/normalization/computations.hpp" +#include "serif/solver/preconditioner/recipes/block.hpp" +#include "serif/solver/preconditioner/strategies/inverse/diagonal.hpp" + +namespace { + namespace model = serif::model; + namespace normalization = serif::solver::normalization; + namespace dimensions = serif::dimensions; + namespace dependencies = serif::utils::dependencies; + namespace blocks = serif::discretization::blocks; + namespace equations = serif::equations; + using EOS = serif::eos::models::Polytrope; + using Registry = equations::EquationRegistry, equations::PrimalPoissonBalance, + equations::MassNormalization, equations::AngularMomentumConstraint>; + using Definition = model::EquilibriumEquations; + using Model = model::StellarModel>; + using WithoutMass = model::StellarModel>; + using ExplicitScheme = normalization::LaneEmdenApproximation; + using ExplicitOptions = normalization::NormalizationOptions; + // No model prescribes central density, so the reference always names it; the mass may still come from the model. + using ModelMassScheme = normalization::LaneEmdenApproximation; + using ModelMassOptions = normalization::NormalizationOptions; + + static_assert(!model::normalization::ReferenceResolvable, Model>); + static_assert(model::normalization::ReferenceResolvable); + static_assert(!model::normalization::ReferenceResolvable); + static_assert(model::normalization::ReferenceResolvable); + static_assert(model::normalization::ReferenceResolvable); + static_assert(std::same_as>); + static_assert(std::same_as(), std::declval())), ExplicitScheme>); + + /** @brief Make invalid factory configurations detectable without entering numerical preparation. */ + template + concept CanConfigureDiscretization = requires(Options options) { + model::make_discretization(std::move(options)); + }; + static_assert(CanConfigureDiscretization>); + static_assert(!CanConfigureDiscretization>); + static_assert(!CanConfigureDiscretization>); + static_assert(CanConfigureDiscretization>); + using ResolvedConfiguration = decltype(model::make_discretization( + std::declval>())); + static_assert(std::same_as>); + + using SelectedInverse = decltype(serif::solver::preconditioner::make_preconditioner( + serif::solver::preconditioner::strategies::inverse::Diagonal{})); + using PreconditionedConfiguration = model::DiscretizationOptions; + using ResolvedPreconditionedConfiguration = decltype(model::make_discretization(std::declval())); + static_assert(CanConfigureDiscretization); + static_assert(std::same_as>); + static_assert(std::same_as().preconditioner), SelectedInverse>); + + + /** @brief Keep unsupported coordinate policies and wrongly typed reference values out of user configurations. */ + template + concept CoordinatePolicy = requires { + typename normalization::NormalizationOptions; + }; + template + concept LaneEmdenInputs = requires { + typename normalization::LaneEmdenApproximation; + }; + static_assert(CoordinatePolicy); + static_assert(!CoordinatePolicy); + static_assert(!LaneEmdenInputs); + static_assert(!LaneEmdenInputs); + + struct CustomReference final {}; + + /** @brief Future strategies share the reference-scale result through ordinary argument-dependent lookup. */ + [[nodiscard]] std::expected prepare_reference_scales( + const CustomReference&, dimensions::QuantityValue gravity) { + return normalization::prepare_stellar_reference_scales(dimensions::MassValue{2.0}, dimensions::LengthValue{3.0}, gravity); + } + + static_assert(normalization::ReferenceScheme); + static_assert(!normalization::ReferenceScheme); + using CustomOptions = normalization::NormalizationOptions; + static_assert(model::normalization::ReferenceResolvable); + static_assert(CanConfigureDiscretization>); + static_assert(std::same_as())), CustomReference>); + + /** @brief Use the real equilibrium plan without requiring any runtime mesh resources. */ + struct BoundDescription { + using Description = Definition; + using Plan = model::EquilibriumPlan>; + }; + using Plan = model::normalization::PlanFor; + using Layout = BoundDescription::Plan::Layout; + using LayoutComputations = decltype(model::computations::make_solver_layout_computations(std::declval())); + static_assert(std::tuple_size_v == 1); + static_assert(std::same_as::Key, blocks::keys::Layout>); + static_assert(normalization::NormalizationPreparable); + static_assert(normalization::CompleteNormalizationFor); + + using Density = blocks::density::mass; + using Potential = blocks::gravity::potential; + using MassConstant = blocks::barotropic_constant::mass_normalization; + using Shape = blocks::surface_deformation::coefficients; + using SurfaceMetric = serif::geometry::deformation::keys::SurfaceMetric; + using DensityMethod = normalization::DimensionlessRieszCoordinate>; + static_assert(std::same_as, + normalization::WithPointRows>); + static_assert(std::same_as, + normalization::WithPointRows>, dimensions::GravitationalPotential, model::normalization::InfinityRows>>); + static_assert(std::same_as::Quantity, dimensions::GravitationalPotential>); + static_assert(std::same_as::Quantity, dimensions::Mass>); + static_assert(std::same_as, normalization::DimensionlessRieszCoordinate>); + static_assert(dependencies::ContainsKey>); + static_assert(std::same_as, Plan>); + + /** @brief A surface EOS with only a forward pressure relation must retain pressure residual units. */ + struct ForwardPressureEOS { + using Relations = serif::eos::relations::RelationCatalog; + /** @brief Declare the physical relation needed for surface selection without an inverse density relation. */ + dimensions::PressureValue evaluate(serif::eos::relations::PressureFromDensity, dimensions::DensityValue) const; + /** @brief Declare its field derivative, as required by the direct-pressure surface formulation. */ + dimensions::PartialDerivative partial_derivative( + serif::eos::relations::PressureFromDensity, dimensions::WithRespectTo, dimensions::DensityValue) const; + }; + struct DirectPressureDescription { + using Description = Definition; + using Plan = model::EquilibriumPlan>; + }; + using DirectPressurePlan = model::normalization::PlanFor; + static_assert(normalization::NormalizationPreparable); + static_assert(std::same_as, + normalization::WithPointRows>); + +} diff --git a/src/compile_time_checks/serif/model/scalar_row.cpp b/src/compile_time_checks/serif/model/scalar_row.cpp new file mode 100644 index 00000000..ba05090d --- /dev/null +++ b/src/compile_time_checks/serif/model/scalar_row.cpp @@ -0,0 +1,25 @@ +#include "serif/equations/mass_normalization.hpp" +#include "serif/backend/mfem/model/computations/scalar_row.hpp" + +namespace { + namespace blocks = serif::discretization::blocks; + namespace computations = serif::model::computations; + namespace dependencies = serif::utils::dependencies; + using Density = blocks::density::mass; + using MassRow = blocks::barotropic_constant::mass_normalization; + using Shape = blocks::surface_deformation::coefficients; + using Volume = serif::operators::backend::mfem::volume_keys::Scoped::Linearization; + + /** @brief Spatial equations must retain their ordinary volume representation. */ + template + concept CacheableRow = requires { typename computations::ScalarResidualRow; }; + + static_assert(CacheableRow); + static_assert(!CacheableRow); + using FixedLayout = blocks::SystemLayout, dependencies::KeyList>; + using MovingLayout = blocks::SystemLayout, dependencies::KeyList>; + using Fixed = decltype(computations::scalar_residual_row(std::declval())); + using Moving = decltype(computations::scalar_residual_row(std::declval())); + static_assert(std::same_as>); + static_assert(std::same_as>); +} diff --git a/src/compile_time_checks/serif/model/state.cpp b/src/compile_time_checks/serif/model/state.cpp new file mode 100644 index 00000000..f8da6238 --- /dev/null +++ b/src/compile_time_checks/serif/model/state.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include + +#include "serif/backend/mfem/model/equilibrium.hpp" +#include "serif/model/state.hpp" + +namespace serif::model::checks { + namespace blocks = discretization::blocks; + namespace dependencies = utils::dependencies; + namespace execution = backend::mfem; + using Density = blocks::density::mass; + using Rotation = blocks::rotation::angular_velocity; + using Shape = blocks::surface_deformation::coefficients; + using ShapeRow = blocks::surface_deformation::shape_equilibrium; + using Values = dependencies::KeyList; + using Rows = dependencies::KeyList; + using Layout = blocks::SystemLayout; + using State = StellarState; + using EmptyInputs = dependencies::DependencyContext<>; + + /** @brief Check field availability and owner lifetime without instantiating a getter body. */ + template + concept ReadsField = requires(Owner&& state) { + std::forward(state).template get(); + }; + + /** @brief Packed views must remain tied to an lvalue owning state. */ + template + concept ReadsValues = requires(Owner&& state) { + std::forward(state).values(); + }; + + /** @brief Layout references cannot survive destruction of a temporary owning state. */ + template + concept ReadsLayout = requires(Owner&& state) { + std::forward(state).layout(); + }; + + static_assert(std::copyable); + static_assert(std::same_as::Layout, Layout>); + static_assert(std::constructible_from, Layout>); + static_assert(std::same_as())), State>); + static_assert(std::same_as(), std::declval>())), State>); + static_assert(std::same_as().get()), std::span>); + static_assert(std::same_as().get()), std::span>); + static_assert(std::same_as().values()), std::span>); + static_assert(std::same_as().values()), std::span>); + static_assert(std::same_as().layout()), const Layout&>); + static_assert(ReadsField && ReadsField); + static_assert(!ReadsField && !ReadsField); + static_assert(!ReadsField && !ReadsField); + static_assert(ReadsValues && ReadsValues); + static_assert(!ReadsValues && !ReadsValues); + static_assert(ReadsLayout && ReadsLayout); + static_assert(!ReadsLayout && !ReadsLayout); + + /** @brief Only the binding's physical identity is needed to check public overload constraints. */ + template + struct Binding { + using Term = TermT; + }; + + template + using CoefficientSource = decltype(dependencies::own(std::declval>>())); + using RotationInputs = dependencies::DependencyContext>; + using WrongIdentity = dependencies::DependencyContext>; + using WrongValue = decltype(dependencies::make_dependency_context(dependencies::own(1.0))); + using Overlap = dependencies::DependencyContext, CoefficientSource>; + using ShapeOverlap = dependencies::DependencyContext, CoefficientSource>; + using CompleteFields = dependencies::DependencyContext, CoefficientSource>; + + struct MovingPlan { + using Values = checks::Values; + using Coefficients = dependencies::KeyList; + using Layout = checks::Layout; + struct Surface { + using Formulation = void; + }; + static constexpr bool has_shape = true; + }; + + struct FixedPlan : MovingPlan { + using Values = dependencies::KeyList; + using Layout = blocks::SystemLayout; + static constexpr bool has_shape = false; + }; + + template + struct Bound { + using Plan = PlanT; + }; + + // A constant graph checks overload participation without constructing finite-element resources. + using Graph = decltype(dependencies::make_computation_graph( + dependencies::make_computation>([]() { + return 0; + }))); + using MovingEvaluator = execution::EquilibriumEvaluator, Graph, EmptyInputs>; + using FixedEvaluator = execution::EquilibriumEvaluator, Graph, EmptyInputs>; + using PreparedGraph = decltype(dependencies::make_computation_graph( + dependencies::make_computation>([]() { + return 0; + }), dependencies::make_computation>([](int value) { + return value; + }))); + using MovingPreparedEvaluator = execution::EquilibriumEvaluator, PreparedGraph, EmptyInputs>; + using FixedPreparedEvaluator = execution::EquilibriumEvaluator, PreparedGraph, EmptyInputs>; + using FixedState = StellarState; + using OtherState = StellarState, Rows>>; + + /** @brief Unsupported state and prescribed-field combinations must fail during overload selection. */ + template + concept CanEvaluate = requires(Evaluator& evaluator, const Arguments&... arguments) { + evaluator.evaluate(arguments...); + }; + + /** @brief Linear preparation needs both a selected recipe and a valid candidate binding. */ + template + concept CanPrepareLinearSystem = requires(Evaluator& evaluator, const Arguments&... arguments) { + evaluator.prepare_linear_system(arguments...); + }; + + static_assert(execution::detail::PrescribedInputsFor); + static_assert(!execution::detail::PrescribedInputsFor); + static_assert(!execution::detail::PrescribedInputsFor); + static_assert(!execution::detail::PrescribedInputsFor); + static_assert(!execution::detail::PrescribedInputsFor); + static_assert(!execution::detail::PrescribedInputsFor); + static_assert(CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(CanEvaluate>); + static_assert(!CanEvaluate>); + static_assert(CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanEvaluate); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(CanPrepareLinearSystem); + static_assert(CanPrepareLinearSystem); + static_assert(CanPrepareLinearSystem>); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem>); + static_assert(CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); + static_assert(!CanPrepareLinearSystem); +} diff --git a/src/compile_time_checks/serif/model/validation.cpp b/src/compile_time_checks/serif/model/validation.cpp new file mode 100644 index 00000000..2b166bd3 --- /dev/null +++ b/src/compile_time_checks/serif/model/validation.cpp @@ -0,0 +1,177 @@ +#include "serif/eos/models/polytropic.hpp" +#include "serif/equations/barotropic_density.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/model/validation.hpp" + +namespace +{ + namespace model = serif::model; + namespace dimensions = serif::dimensions; + namespace blocks = serif::discretization::blocks; + namespace dependencies = serif::utils::dependencies; + + struct SymbolicEOS + { + }; + struct CustomCoordinate + { + }; + struct CustomResidual + { + }; + struct CustomState + { + }; + struct CustomBalance + { + }; + + struct CustomConstraint + { + using ScalarDescription = model::ScalarConstraint; + using ModelDefinition = model::SpecificationDefinition, model::Changes>; + /** @brief Declare independently typed target metadata without requiring a numerical provider. */ + dimensions::MassValue target() const; + }; + + struct ForeignConstraint + { + using ModelDefinition = model::SpecificationDefinition>, model::Changes>; + /** @brief Supply correct units while deliberately depending on another owning prescription. */ + dimensions::MassValue target() const; + }; + + // Any spatial unknown can carry a custom physical state and its balance; potential is merely a convenient registered term. + using Field = blocks::gravity::potential; + using Description = model::EquilibriumEquations; + using CustomModel = model::StellarModel>; + using Values = dependencies::KeyList; + using Rows = dependencies::KeyList; + using Bindings = model::SpecificationBindings, CustomCoordinate>, + dependencies::Binding, CustomResidual>, + dependencies::Binding, dependencies::Binding>; + using ConstraintDerivative = model::Derivative; + using MultiplierDerivative = model::Derivative; + using StateDerivative = model::Derivative; + using Declared = dependencies::KeyList; + using MissingStateDerivative = dependencies::KeyList; + + static_assert(model::StellarModelType); + static_assert(model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid, Rows, Declared, Bindings>); + static_assert(!model::SpecificationTopologyValid, Declared, Bindings>); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + static_assert(model::SpecificationEndpointsValid, Bindings>); + static_assert(model::SpecificationEndpointsValid, Values, Rows, dependencies::KeyList<>, Bindings>); + static_assert(!model::SpecificationEndpointsValid, dependencies::KeyList, Rows, dependencies::KeyList<>, Bindings>); + static_assert(!model::SpecificationBackendAvailable); + // Registering an explicit zero is an implementation decision, never inferred from an absent method. + using WithExplicitStateZero = dependencies::CombinedKeyList>; + static_assert(model::SpecificationBackendAvailable); + + using ForeignModel = model::StellarModel>; + using ForeignBindings = model::SpecificationBindings, CustomCoordinate>, + dependencies::Binding, CustomResidual>, dependencies::Binding, Field>>; + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationEndpointsValid, ForeignBindings>); + + using MassModel = model::StellarModel>; + using MassCoordinate = blocks::barotropic_constant::mass_normalization; + using MassValues = dependencies::KeyList; + using MassRows = dependencies::KeyList; + using MassCouplings = dependencies::KeyList, model::Derivative, model::Derivative>; + static_assert(!model::SpecificationTopologyValid); + + using MassShape = blocks::surface_deformation::coefficients; + using MassShapeRow = blocks::surface_deformation::shape_equilibrium; + using SurfaceMassValues = dependencies::KeyList; + using SurfaceMassRows = dependencies::KeyList; + using SurfaceMassCouplings = dependencies::KeyList< + model::Derivative, model::Derivative, + model::Derivative, model::Derivative, + model::Derivative>; + static_assert(model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + static_assert(std::same_as, dimensions::GravitationalPotential>); + static_assert(std::same_as, dimensions::Mass>); + static_assert(model::FixedTotalMass::ScalarDescription::coordinate_id == "fixed_total_mass.multiplier"); + static_assert(model::FixedTotalMass::ScalarDescription::coordinate_symbol == "Us"); + + using Balance = serif::equations::BarotropicDensityBalance; + using Registry = serif::equations::EquationRegistry; + using Derived = model::DeclaredEquationCouplings; + static_assert(dependencies::ContainsKey, Derived>); + static_assert(dependencies::ContainsKey, Derived>); + static_assert(!dependencies::ContainsKey, Derived>); + + struct CrossAlpha; + struct CrossBeta; + using AlphaCoordinate = model::GeneratedCoordinateOf; + using BetaCoordinate = model::GeneratedCoordinateOf; + using AlphaResidual = model::ConstraintOf; + using BetaResidual = model::ConstraintOf; + + // Identity tags must be usable before either owner is complete, including as binding keys. + static_assert(AlphaCoordinate::global_scalar && BetaResidual::global_scalar); + using CrossBindings = model::SpecificationBindings, dependencies::Binding, + dependencies::Binding, dependencies::Binding>; + + struct CrossAlpha + { + using ModelDefinition = model::SpecificationDefinition, model::Changes>; + /** @brief Preserve separate owner identity even when the scalar units and descriptive IDs match. */ + dimensions::MassValue target() const; + }; + + struct CrossBeta + { + using ModelDefinition = model::SpecificationDefinition, model::Changes>; + /** @brief Declare a second independently owned constraint with the same scalar dimensions. */ + dimensions::MassValue target() const; + }; + + using CrossModel = decltype(model::StellarModel(SymbolicEOS{}, Description{1}, CrossAlpha{}, CrossBeta{})); + using PermutedCrossModel = decltype(model::StellarModel(SymbolicEOS{}, Description{1}, CrossBeta{}, CrossAlpha{})); + using ShapeRow = blocks::surface_deformation::shape_equilibrium; + using CrossValues = dependencies::KeyList; + using CrossRows = dependencies::KeyList; + using CrossBaseCouplings = dependencies::KeyList, model::Derivative, + model::Derivative, model::Derivative, model::Derivative>; + using CrossCouplings = dependencies::CombinedKeyList>>; + static_assert(std::same_as); + static_assert(!std::same_as && !std::same_as); + static_assert(std::same_as, model::GeneratedCoordinateQuantity>); + static_assert(model::SpecificationTopologyValid); + static_assert(!model::SpecificationTopologyValid); + + // Supplying row/column identities cannot stand in for their absent owning physical prescription. + using MissingCrossOwner = decltype(model::StellarModel(SymbolicEOS{}, Description{1}, CrossAlpha{})); + static_assert(!model::SpecificationTopologyValid); + + struct UngeneratedSpecification + { + using ModelDefinition = model::SpecificationDefinition; + }; + + struct InvalidGeneratedReference + { + using ModelDefinition = model::SpecificationDefinition>, model::Changes>; + /** @brief Exercise a syntactically valid identity whose owner does not generate a scalar. */ + dimensions::MassValue target() const; + }; + using InvalidReferenceModel = decltype(model::StellarModel(SymbolicEOS{}, Description{1}, UngeneratedSpecification{}, InvalidGeneratedReference{})); + using InvalidReferenceBindings = model::SpecificationBindings, CustomCoordinate>, + dependencies::Binding, CustomResidual>, dependencies::Binding, Field>>; + static_assert(!model::SpecificationTopologyValid); + static_assert(!model::SpecificationEndpointsValid, InvalidReferenceBindings>); +} diff --git a/src/compile_time_checks/serif/operators/computation_owners.cpp b/src/compile_time_checks/serif/operators/computation_owners.cpp new file mode 100644 index 00000000..221fae97 --- /dev/null +++ b/src/compile_time_checks/serif/operators/computation_owners.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +#include "serif/discretization/domain/schema/schemas.hpp" +#include "serif/equations/contributions.hpp" +#include "serif/equations/point.hpp" +#include "serif/backend/mfem/operators/jacobian_operator.hpp" +#include "serif/backend/mfem/operators/volume_computations.hpp" + +namespace serif::operators::backend::mfem { + using Schema = discretization::domain::schema::CoreEnvelopeVacuumDomainSchema; + /** @brief A minimal density-valued volume equation, sufficient to instantiate a volume cache. */ + struct Closure final : equations::Contribution> { + using Operands = utils::types::TypeList; + using ResidualQuantity = dimensions::MassDensity; + static constexpr std::string_view name = "density value"; + /** @brief Declare the point residual; only the cache types are inspected. */ + static equations::PointLinearization evaluate(const equations::EquationPoint&); + /** @brief Add no quadrature order beyond the operand degree. */ + static int additional_order(TestOperand, int); + }; + + using Density = discretization::forms::Operand; + using DensityBinding = discretization::backend::mfem::TermBinding; + using Vector = ::mfem::Vector; + + using VolumeCoefficients = decltype(make_term_coefficients( + bind(std::declval(), std::declval()) + )); + using VolumeMesh = VolumeDiscretization; + using VolumeInputs = decltype(make_volume_inputs( + std::declval(), std::declval(), std::declval() + )); + using VolumeCache = decltype(make_volume_equation_cache( + std::declval(), std::declval(), std::declval() + )); + + template + concept ClosureResult = requires(const VolumeCache& cache) { + cache.template get(); + }; + + template + concept ClosureInputsAccept = requires( + const VolumeMesh& discretization, + const VolumeCoefficients& coefficients, + DisplacementT&& displacement + ) { + make_volume_inputs(discretization, coefficients, std::forward(displacement)); + }; + + static_assert(ClosureInputsAccept); + static_assert(ClosureInputsAccept); + static_assert(!ClosureInputsAccept); + static_assert(!ClosureInputsAccept); + + // Caches own their graphs and are movable; assignment cannot invalidate borrowed adapter addresses. + static_assert(std::move_constructible && !std::copy_constructible); + static_assert(!std::is_move_assignable_v); + static_assert(std::constructible_from, VolumeCache&>); + static_assert(!std::constructible_from, VolumeCache&&>); + + static_assert(ClosureResult>); + static_assert(ClosureResult>); + static_assert(ClosureResult>); + static_assert(ClosureResult>); + static_assert(ClosureResult); + static_assert(!ClosureResult>); + static_assert(!ClosureResult>); +} diff --git a/src/compile_time_checks/serif/operators/coordinate_identity_meanfield.cpp b/src/compile_time_checks/serif/operators/coordinate_identity_meanfield.cpp new file mode 100644 index 00000000..5e584563 --- /dev/null +++ b/src/compile_time_checks/serif/operators/coordinate_identity_meanfield.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include "serif/eos/models/polytropic.hpp" +#include "serif/equations/barotropic_density.hpp" +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/operators/plan.hpp" + +namespace serif::operators +{ + // MeanField contribution coordinates and generated mass couplings. + using Density = discretization::forms::Operand; + using Potential = discretization::forms::Operand; + using Constant = discretization::forms::Operand; + using FirstOrder = equations::OperandValues>; + using PermutedOrder = equations::OperandValues>; + + static_assert(FirstOrder::index() == 0); + static_assert(PermutedOrder::index() == 1); + static_assert(FirstOrder::index() == 1); + static_assert(PermutedOrder::index() == 2); + static_assert(FirstOrder::index() == 2); + static_assert(PermutedOrder::index() == 0); + static_assert(std::same_as().get()), const double&>); + static_assert(std::same_as().get()), double&>); + + using MassPlan = EquationPlan; + using BalancePlan = EquationPlan>; + static_assert(std::same_as); + static_assert(std::same_as>); + static_assert(BalancePlan::column_index() < BalancePlan::column_count); + static_assert(std::same_as); + + // A pointwise contribution receives exactly its declared fields by const reference. + using MassPoint = equations::EquationPoint; + static_assert(decltype(MassPoint::fields)::count == 1); + static_assert(std::same_as().fields.get()), const double&>); + + // Gravity assembly names its physical contributions by their declared order. + using PotentialContributions = equations::ContributionsOf; + static_assert(std::same_as, equations::PrimalPoissonBalance::PotentialFlux>); + static_assert(std::same_as, equations::PrimalPoissonBalance::DensitySource>); +} diff --git a/src/compile_time_checks/serif/operators/plan.cpp b/src/compile_time_checks/serif/operators/plan.cpp new file mode 100644 index 00000000..0e9e1d65 --- /dev/null +++ b/src/compile_time_checks/serif/operators/plan.cpp @@ -0,0 +1,174 @@ +#include + +#include "serif/eos/models/polytropic.hpp" +#include "serif/operators/plan.hpp" +#include "serif/equations/registry.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/equations/barotropic_density.hpp" +#include "serif/backend/mfem/operators/operand_evaluation.hpp" +#include "serif/backend/mfem/discretization/quadrature/order.hpp" +#include "serif/discretization/domain/schema/schemas.hpp" + +namespace serif::operators { + using equations::EquationRegistry; + using equations::OperandValues; + using Balance = equations::BarotropicDensityBalance; + using Density = discretization::blocks::density::mass; + using Potential = discretization::blocks::gravity::potential; + using Displacement = discretization::blocks::displacement::vector; + using Schema = discretization::domain::schema::CoreEnvelopeVacuumDomainSchema; + + // Keep discontinuous-space rejection independent of the production density representation. + struct DiscontinuousScalar : discretization::blocks::term { + using FunctionalSpace = discretization::functional_space::DiscreteFunctionalSpace; + using Support = discretization::domain::StellarDomains; + struct residual : discretization::blocks::residual_block_base {}; + }; + + template + struct NodalRequest : equations::Contribution, discretization::domain::StellarSurfaceBoundary> { + using Operands = utils::types::TypeList>; + using Result = discretization::forms::OperandValue>; + static constexpr std::string_view name = "nodal capability test"; + static constexpr bool position_independent = PositionIndependent; + /** @brief Supply a point linearization so capability checks depend only on the declared operand and support. */ + equations::PointLinearization evaluate(const equations::EquationPoint&) const { + return {}; + } + }; + using BoundaryValue = NodalRequest; + static_assert(equations::Equation && equations::PointEquation); + static_assert(!VolumeEquation && !ScalarVolumeEquation); + static_assert(std::same_as, discretization::domain::StellarSurfaceBoundary>); + + static_assert(ScalarVolumeEquation, "Barotropic density balance is a scalar volume equation."); + static_assert(!ScalarVolumeEquation, "Primal Poisson balance samples a potential gradient."); + static_assert(!ScalarVolumeEquation, "Polytrope is not a scalar volume equation."); + static_assert(!ScalarValueTerm, "A vector field is not a scalar value term."); + static_assert(!ScalarValueOperand>, "Gradient operand is not a scalar value operand."); + static_assert(std::same_as::Test, Density>, "Barotropic density balance test is density."); + static_assert(std::same_as::Support, discretization::domain::StellarDomains>, "Barotropic density balance support is stellar domains."); + static_assert(EquationPlan::operand_count == 4, "Barotropic density balance has four operands."); + static_assert(OperandValues::index() == 0, "Barotropic density balance density operand is first."); + static_assert(OperandValues::index() == 1, "Barotropic density balance potential operand is second."); + static_assert(std::same_as< + EquationPlan::Form>, + discretization::forms::mass>, "Barotropic density balance density form is mass."); + static_assert(std::same_as< + EquationPlan::Form>, + discretization::forms::source>, "Barotropic density balance potential form is source."); + static_assert(std::same_as< + EquationRegistry::Equations, + utils::types::TypeList>, "Equation registry contains density balance and Poisson balance."); + + using DensityValue = discretization::forms::Operand; + using PotentialValue = discretization::forms::Operand; + using PotentialGradient = discretization::forms::Operand; + static_assert(backend::mfem::EvaluableOperand); + static_assert(backend::mfem::EvaluableOperand); + static_assert(backend::mfem::EvaluableOperand); + static_assert(backend::mfem::EvaluableOperand>); + static_assert(!backend::mfem::EvaluableOperand>); + static_assert(!backend::mfem::EvaluableOperand); + + using DisplacementDivergence = discretization::forms::Operand; + static_assert(backend::mfem::EvaluableOperand); + static_assert(discretization::quadrature::backend::mfem::resolve_operand_order() == 2); + static_assert(std::same_as, double>); + + // Describing an equation does not imply a particular backend can execute it. + struct GradientRequest { + using Test = Density; + using Operands = utils::types::TypeList; + static constexpr std::string_view name = "scalar gradient request"; + /// Supply a syntactically valid authoring interface for the capability check. + equations::PointLinearization evaluate(const equations::EquationPoint&) const { return {}; } + /// No extra polynomial factor accompanies this gradient sample. + int additional_order(PotentialGradient, int) const { return 0; } + }; + static_assert(equations::Equation); + static_assert(!ScalarVolumeEquation); + static_assert(VolumeEquation); + static_assert(VolumeEquation); + static_assert(EquationPlan::column_count == 2); + static_assert(std::same_as, discretization::forms::VectorValue>); + using DisplacementValue = discretization::forms::Operand; + static_assert(backend::mfem::EvaluableOperand); + static_assert(backend::mfem::EvaluableOperand>); + using DisplacementGradient = discretization::forms::Operand; + using VectorTensor = discretization::forms::MatrixValue; + using CartesianVector = discretization::forms::VectorValue; + static_assert(backend::mfem::EvaluableOperand); + static_assert(std::same_as, VectorTensor>); + static_assert(discretization::forms::component_count == 9); + static_assert(discretization::forms::component_count> == 81); + static_assert(discretization::forms::component_count> == 27); + static_assert(discretization::forms::component_count> == 27); + static_assert(std::same_as, VectorTensor>); + static_assert(std::same_as, VectorTensor>); + static_assert(discretization::quadrature::backend::mfem::resolve_operand_order() == 2); + static_assert(std::same_as, discretization::forms::VectorValue>); + static_assert(!backend::mfem::EvaluableOperand< + discretization::forms::Operand>); + static_assert(!backend::mfem::EvaluableOperand>); + using MixedSamples = utils::types::TypeList; + static_assert(OperandValues::index() == 0); + static_assert(OperandValues::index() == 1); + static_assert(std::same_as, + discretization::forms::VectorValue>); + static_assert( + std::same_as, + discretization::forms::MatrixValue>); + struct BadContributionRow { + using Test = Density; + static constexpr std::string_view name = "malformed contribution"; + /// Bad user descriptions should fail the concept rather than instantiate invalid aliases. + auto contributions() const { + return std::tuple{1}; + } + }; + static_assert(!equations::Equation); + + struct BadContributionCollection { + using Test = Displacement; + static constexpr std::string_view name = "non-tuple contributions"; + + auto contributions() const { + return equations::PrimalPoissonBalance{1.0}; + } + }; + + struct NestedBadCollection { + using Test = Displacement; + static constexpr std::string_view name = "nested non-tuple contributions"; + + auto contributions() const { + return std::tuple{equations::PrimalPoissonBalance{1.0}, BadContributionCollection{}}; + } + }; + + static_assert(!equations::Equation); + static_assert(!equations::Equation); +} // namespace serif::operators + +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/angular_momentum.hpp" + +namespace serif::operators { + using Mass = equations::MassNormalization; + using Constant = discretization::blocks::barotropic_constant::mass_normalization; + using Omega = discretization::blocks::rotation::angular_velocity; + using Momentum = equations::AngularMomentumConstraint; + static_assert(ScalarVolumeEquation && VolumeEquation); + static_assert(std::same_as::Test, Omega>); + static_assert(std::same_as::Columns, utils::types::TypeList>>); + static_assert(ScalarVolumeEquation && VolumeEquation); + static_assert(EquationPlan::column_count == 1); + static_assert(EquationPlan::column_count == 4); + static_assert(backend::mfem::EvaluableOperand>); + static_assert(backend::mfem::EvaluableOperand>); + static_assert(!MappedOperand>); + static_assert(!discretization::blocks::GlobalScalarTerm); +} + diff --git a/src/compile_time_checks/serif/solver/continuation.cpp b/src/compile_time_checks/serif/solver/continuation.cpp new file mode 100644 index 00000000..1078312c --- /dev/null +++ b/src/compile_time_checks/serif/solver/continuation.cpp @@ -0,0 +1,76 @@ +#include + +#include "serif/solver/continuation/polytropic.hpp" +#include "serif/solver/continuation/identified_equations.hpp" +#include "serif/solver/continuation/options.hpp" + +namespace { + namespace family = serif::solver::continuation; + using Index = family::PolytropicIndex; + using Constant = family::PolytropicConstant; + using Momentum = family::AngularMomentum; + using Definition = decltype([]() {}); + using Model = decltype(serif::model::make_stellar_model(std::declval(), Definition{}, + std::declval())); + using NoRotation = decltype(serif::model::make_stellar_model(std::declval(), Definition{})); + + /** @brief Selected coordinates must be unique typed physical parameters. */ + template + concept Selectable = requires(Parameters... parameters) { family::in(parameters...); }; + /** @brief Dependent laws must read selected inputs, have distinct outputs, and avoid independent outputs. */ + template + concept Attachable = requires(Selection selection, Law law) { std::move(selection).with(std::move(law)); }; + /** @brief A family must account for parameter ownership, required dependent laws, and physical identity. */ + template + concept Bindable = requires(Selection selection, Target target) { std::move(selection).bind(std::move(target)); }; + /** @brief Laws must preserve physical units and callable signatures. */ + template + concept ConstantLaw = requires(Function function) { family::law(Constant{}, family::depends_on(Index{}), std::move(function)); }; + /** @brief Starting values have the selected parameter's units. */ + template + concept StartingValue = requires(Parameter parameter, Value value) { family::starting_at(parameter, value); }; + /** @brief Strict stage settings must cover each bound coordinate, not only a subset. */ + template + concept Applicable = requires(Setting setting, serif::solver::continuation::Options& options) { setting.apply(options); }; + + using IndexSelection = decltype(family::in(Index{})); + using MomentumSelection = decltype(family::in(Momentum{})); + using KLaw = decltype(family::law(Constant{}, family::depends_on(Index{}), [](double value) { return 1.0 / value; })); + using Complete = decltype(std::declval().with(std::declval())); + using Both = decltype(family::in(Index{}, Momentum{}).with(std::declval())); + using Bound = decltype(std::declval().bind(std::declval())); + using SelfLaw = decltype(family::law(Constant{}, family::depends_on(Constant{}), [](double value) { return value; })); + struct UnidentifiedEOS {}; + using Unidentified = decltype(serif::model::make_stellar_model(UnidentifiedEOS{}, Definition{}, std::declval())); + struct OpaqueEquations { double pressure; }; + using Opaque = decltype(serif::model::make_stellar_model(std::declval(), OpaqueEquations{}, std::declval())); + using Identified = decltype(family::identified_equations([](const auto&) { return 1; }, serif::dimensions::PressureValue{0.1})); + using ExplicitIdentity = decltype(serif::model::make_stellar_model(std::declval(), + std::declval(), std::declval())); + + static_assert(family::PhysicalParameter && family::PhysicalParameter && family::PhysicalParameter); + static_assert(!Selectable<> && !Selectable && !Selectable); + static_assert(Attachable); + static_assert(!Attachable); + static_assert(!Attachable); + static_assert(!Attachable); + static_assert(!Attachable); + static_assert(!ConstantLaw); + static_assert(!ConstantLaw); + static_assert(!Bindable); + static_assert(Bindable); + static_assert(!Bindable); + static_assert(!Bindable); + static_assert(!Bindable); + static_assert(Bindable); + static_assert(std::same_as().identity)>, serif::dimensions::PressureValue>); + static_assert(StartingValue); + static_assert(StartingValue); + static_assert(!StartingValue); + static_assert(Bound::coordinate_count == 2); + static_assert(Bound::coordinate_index(Index{}) == 0 && Bound::coordinate_index(Momentum{}) == 1); + static_assert(!Bound::has_coordinate); + static_assert(Applicable); + static_assert(!Applicable); + static_assert(Applicable); +} diff --git a/src/compile_time_checks/serif/solver/normalization/algebra.cpp b/src/compile_time_checks/serif/solver/normalization/algebra.cpp new file mode 100644 index 00000000..0cdaff1a --- /dev/null +++ b/src/compile_time_checks/serif/solver/normalization/algebra.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +#include + +#include "serif/solver/normalization/scales.hpp" +#include "serif/backend/mfem/solver/normalization/operators.hpp" + +namespace +{ + namespace dimensions = serif::dimensions; + namespace normalization = serif::solver::normalization; + namespace backend = normalization::backend::mfem; + using Map = normalization::DiagonalNormalization; + + template + concept HasStellarScale = requires(const normalization::StellarReferenceScales &scales) + { + { normalization::characteristic_scale(scales, Quantity{}) } -> std::same_as>; + }; + + static_assert(HasStellarScale); + static_assert(HasStellarScale); + static_assert(HasStellarScale); + static_assert(HasStellarScale); + static_assert(!HasStellarScale); + static_assert(!HasStellarScale); + static_assert(!std::convertible_to>); + static_assert(std::equality_comparable); + static_assert(!std::default_initializable); + static_assert(std::same_as().state_factors()), std::span>); + static_assert(std::same_as().residual_factors()), std::span>); + + static_assert(std::constructible_from); + static_assert(std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::is_copy_assignable_v); + static_assert(!std::is_move_assignable_v); + static_assert(std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); +} diff --git a/src/compile_time_checks/serif/solver/normalization/dimensionless.cpp b/src/compile_time_checks/serif/solver/normalization/dimensionless.cpp new file mode 100644 index 00000000..5541b789 --- /dev/null +++ b/src/compile_time_checks/serif/solver/normalization/dimensionless.cpp @@ -0,0 +1,68 @@ +#include + +#include "serif/solver/normalization/computations.hpp" + +namespace serif::solver::normalization::checks { + namespace dependencies = utils::dependencies; + using Density = discretization::blocks::density::mass; + using Potential = discretization::blocks::gravity::potential; + using Shape = discretization::blocks::surface_deformation::coefficients; + using ShapeRow = discretization::blocks::surface_deformation::shape_equilibrium; + using MassConstant = discretization::blocks::barotropic_constant::mass_normalization; + using SurfaceMetric = geometry::deformation::keys::SurfaceMetric; + using DensityMethod = DimensionlessRieszCoordinate>; + + static_assert(DensityMethod::geometry == ReferenceGeometry::volume); + static_assert(DimensionlessRieszCoordinate::geometry == ReferenceGeometry::surface); + static_assert(DimensionlessRieszCoordinate::geometry == ReferenceGeometry::algebraic); + static_assert(std::same_as); + static_assert(std::same_as>); + + using DensityLayout = CoordinateLayout, dependencies::KeyList>; + using DensityPlan = NormalizationPlan>, + dependencies::Bindings>>; + static_assert(NormalizationPreparable); + static_assert(!NormalizationPlanType>, dependencies::Bindings<>>>); + static_assert(!NormalizationPlanType>>>, dependencies::Bindings<>>>); + static_assert(!NormalizationPlanType, static_cast(99)>>>, dependencies::Bindings<>>>); + + struct PressureRow { + using Test = Density; + using ResidualQuantity = dimensions::Pressure; + }; + using PressurePlan = NormalizationPlan, dependencies::Bindings>>>>; + static_assert(NormalizationPreparable, dependencies::KeyList>>); + static_assert(!NormalizationPlanType, dependencies::Bindings>>>); + + struct Selection {}; + using SelectedMethod = WithPointRows; + static_assert(NormalizationPreparable, dependencies::Bindings>>, + CoordinateLayout, dependencies::KeyList>>); + static_assert(!NormalizationPlanType>, dependencies::Bindings<>>>); + + using SurfacePlan = NormalizationPlan>>, + dependencies::Bindings>>>; + static_assert(NormalizationPreparable, dependencies::KeyList>>); + using ScalarPlan = NormalizationPlan>>, + dependencies::Bindings>>>; + static_assert(NormalizationPreparable, dependencies::KeyList>>); + static_assert(!NormalizationPlanType>>, dependencies::Bindings<>>>); + + struct CustomMetric { + using Coordinates = dependencies::KeyList; + }; + + /** @brief Custom metrics must state their coordinate convention instead of silently assuming volume weighting. */ + template + concept HasDefaultDimensionlessGeometry = requires { + typename DimensionlessRieszCoordinate; + }; + static_assert(!HasDefaultDimensionlessGeometry); + using CustomPlan = NormalizationPlan>>, dependencies::Bindings<>>; + static_assert(NormalizationPreparable, dependencies::KeyList<>>>); +} diff --git a/src/compile_time_checks/serif/solver/normalization/plan.cpp b/src/compile_time_checks/serif/solver/normalization/plan.cpp new file mode 100644 index 00000000..2d378ec4 --- /dev/null +++ b/src/compile_time_checks/serif/solver/normalization/plan.cpp @@ -0,0 +1,426 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "serif/solver/normalization/computations.hpp" +#include "serif/solver/normalization/physical_coordinates.hpp" +#include "serif/equations/angular_momentum.hpp" +#include "serif/equations/mass_normalization.hpp" +#include "serif/equations/primal_gravity.hpp" +#include "serif/eos/models/polytropic.hpp" +#include "serif/utils/dependencies/computation_cache.hpp" + +namespace serif::solver::normalization::checks +{ + namespace blocks = discretization::blocks; + namespace dependencies = utils::dependencies; + using dependencies::Binding; + using dependencies::Bindings; + using dependencies::KeyList; + using Density = blocks::density::mass; + using Potential = blocks::gravity::potential; + using Displacement = blocks::displacement::vector; + using MassConstant = blocks::barotropic_constant::mass_normalization; + using AngularVelocity = blocks::rotation::angular_velocity; + using CentralBorder = blocks::frame_acceleration::component<0>; + using SurfaceCoefficients = blocks::surface_deformation::coefficients; + using SurfaceEquilibrium = blocks::surface_deformation::shape_equilibrium; + using SurfaceMetric = geometry::deformation::keys::SurfaceMetric; + + struct FirstValue + { + }; + + struct SecondValue + { + }; + + struct FirstResidual + { + }; + + struct SecondResidual + { + }; + + struct ForeignCoordinate + { + }; + + using Values = KeyList; + using Residuals = KeyList; + using ValueRules = Bindings, Binding>; + using ResidualRules = Bindings, Binding>; + using CompletePlan = NormalizationPlan; + using ReorderedPlan = NormalizationPlan< + Bindings, Binding>, + Bindings, Binding>>; + + // MeanField plan.cpp: both roles prove exact ownership independently of declaration order. + static_assert(NormalizationPlanType); + static_assert(NormalizationPlanType); + static_assert(CompleteNormalizationFor); + static_assert(CompleteNormalizationFor); + static_assert(CompleteNormalizationFor); + static_assert(CompleteNormalizationFor, KeyList>); + static_assert(CompleteNormalizationFor, Bindings<>>, KeyList<>, KeyList<>>); + static_assert(CompleteNormalizationFor>, Bindings>>, KeyList, KeyList>); + + using MissingValue = NormalizationPlan>, ResidualRules>; + using MissingResidual = NormalizationPlan>>; + using DuplicateValue = NormalizationPlan, Binding>, ResidualRules>; + using DuplicateResidual = NormalizationPlan, Binding>>; + using ForeignValue = NormalizationPlan, Binding>, ResidualRules>; + using ForeignResidual = NormalizationPlan, Binding>>; + static_assert(!CompleteNormalizationFor); + static_assert(!CompleteNormalizationFor); + static_assert(!NormalizationPlanType); + static_assert(!NormalizationPlanType); + static_assert(!CompleteNormalizationFor); + static_assert(!CompleteNormalizationFor); + static_assert(!CompleteNormalizationFor, Residuals>); + static_assert(!CompleteNormalizationFor>); + static_assert(!CompleteNormalizationFor); + static_assert(!CompleteNormalizationFor); + static_assert(!CompleteNormalizationFor); + static_assert(!NormalizationPlanType>); + static_assert(!NormalizationPlanType>); + + using Layout = CoordinateLayout; + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(std::is_aggregate_v); + + /** @brief Keep invalid layout declarations safely testable without instantiating their storage. */ + template + concept CanDeclareLayout = requires + { + typename CoordinateLayout; + }; + + static_assert(!CanDeclareLayout); + static_assert(!CanDeclareLayout); + static_assert(!CanDeclareLayout, Residuals>); + + using DensityRiesz = PhysicalRieszCoordinate>; + using DisplacementRiesz = PhysicalRieszCoordinate>; + using PhysicalValues = NormalizationPlan, Binding>, Bindings<>>; + static_assert(CompleteNormalizationFor, KeyList<>>); + static_assert(!MetricFor, Density>); + static_assert(!MetricFor, Potential>); + static_assert(!MetricFor); + static_assert(!MetricFor); + static_assert(!MetricFor); + static_assert(!NormalizationPlanType>>>, Bindings<>>>); + static_assert(!NormalizationPlanType>>>, Bindings<>>>); + + struct PressureRow + { + using Test = Density; + using Quantity = dimensions::MassDensity; + using ResidualQuantity = dimensions::Pressure; + }; + + struct MassRow + { + using Test = MassConstant; + using ResidualQuantity = dimensions::Mass; + }; + + struct CentralRow + { + using Test = Density; + using Border = CentralBorder; + using ResidualQuantity = dimensions::MassDensity; + }; + + // Row physics overrides the sampled field's quantity; a central point row uses its scalar border metric. + using PressureRiesz = PhysicalRieszCoordinate>; + using PressurePlan = NormalizationPlan, Bindings>>; + static_assert(CompleteNormalizationFor, KeyList>); + static_assert(!NormalizationPlanType, Bindings>>>); + static_assert(NormalizationPlanType, Bindings>>>); + static_assert(MetricFor); + static_assert(MetricFor); + static_assert(!MetricFor, CentralRow>); + static_assert(NormalizationPlanType, Bindings>>>>); + static_assert(!NormalizationPlanType, Bindings>>>>); + + struct SelectedSurfaceRows + { + }; + + using PotentialRiesz = PhysicalRieszCoordinate>; + using HybridResidual = WithPointRows; + static_assert(NormalizationPlanType, Bindings>>>); + static_assert(!NormalizationPlanType>, Bindings<>>>); + static_assert(!NormalizationPlanType, Bindings>>>>); + + // Inherited scalar block aliases coincide; physical coordinate identities must never do so. + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(!std::same_as); + using ScalarValues = KeyList; + using ScalarPlan = NormalizationPlan>, + Binding>, + Binding>>, Bindings<>>; + static_assert(CompleteNormalizationFor>); + static_assert(!CompleteNormalizationFor, KeyList<>>); + static_assert(!MetricFor); + + struct CustomDensityMetric + { + using Coordinates = KeyList; + }; + + struct SharedFieldMetric + { + using Coordinates = KeyList; + }; + + struct MalformedMetric + { + using Coordinates = int; + }; + + struct MissingMetricMetadata + { + }; + + struct DuplicateMetricCoordinates + { + using Coordinates = KeyList; + }; + + struct PretendRieszMethod + { + using Quantity = dimensions::MassDensity; + using Metric = CustomDensityMetric; + }; + + static_assert(MetricFor); + static_assert(MetricFor); + static_assert(!MetricFor); + static_assert(MetricFor); + static_assert(MetricFor); + static_assert(!MetricFor); + static_assert(!MetricFor); + static_assert(!MetricFor); + static_assert(NormalizationPlanType>>, Bindings<>>>); + static_assert(!NormalizationPlanType>, Bindings<>>>); + static_assert(!NormalizationPlanType>, Bindings<>>>); + static_assert(!NormalizationPlanType>, Bindings<>>>); + + // Native shape coordinates share deformation's trace metric, independently of the volume displacement basis. + static_assert(MetricFor); + static_assert(MetricFor); + static_assert(!MetricFor); + static_assert(!MetricFor); + using SurfacePlan = NormalizationPlan< + Bindings>>, + Bindings>>>; + static_assert(CompleteNormalizationFor, KeyList>); + + struct MoveOnlyPrescription final : NormalizationPrescriptionTag + { + /** @brief Retain runtime policy ownership without requiring a copyable prescription. */ + MoveOnlyPrescription() = default; + /** @brief Prevent accidental duplication of the method's owned state. */ + MoveOnlyPrescription(const MoveOnlyPrescription&) = delete; + /** @brief Prevent replacing an owned method through a copy. */ + MoveOnlyPrescription& operator=(const MoveOnlyPrescription&) = delete; + /** @brief Transfer the custom prescription into its dependency resource. */ + MoveOnlyPrescription(MoveOnlyPrescription&&) = default; + /** @brief Transfer replacement policy state explicitly. */ + MoveOnlyPrescription& operator=(MoveOnlyPrescription&&) = default; + }; + + /** @brief Require explicit policy identity rather than accepting unrelated preparation-like objects. */ + template + concept CanDeclareRuntimeMethod = requires + { + typename RuntimePreparedCoordinate; + }; + + using CustomMethod = RuntimePreparedCoordinate; + using CustomPlan = NormalizationPlan>, Bindings>>; + static_assert(!std::copy_constructible); + static_assert(std::move_constructible); + static_assert(NormalizationPrescription); + static_assert(std::same_as); + static_assert(CompleteNormalizationFor, KeyList>); + static_assert(!CanDeclareRuntimeMethod); + static_assert(!NormalizationPrescription); + + struct PreparedPrescription : NormalizationPrescriptionTag + { + /** @brief Supply owning factors for the precise role and coordinate requested by the graph. */ + template + [[nodiscard]] std::expected, std::string> prepare_factors(keys::Factors, std::size_t size) const + { + return std::vector(size, 0.5); + } + + /** @brief The stateless prescription is suitable for the dependency value snapshot policy. */ + [[nodiscard]] bool operator==(const PreparedPrescription&) const noexcept + { + return true; + } + }; + + struct DerivedPrescription final : PreparedPrescription + { + }; + + struct ConvertiblePrescription final + { + PreparedPrescription prescription; + + /** @brief Implicit conversion must not substitute for the exact declared prescription source. */ + operator const PreparedPrescription&() const noexcept + { + return prescription; + } + + /** @brief Keep snapshot compatibility independent of the intentionally wrong source type. */ + [[nodiscard]] bool operator==(const ConvertiblePrescription&) const = default; + }; + + struct WrongPreparationResult final : NormalizationPrescriptionTag + { + /** @brief Deliberately violate the preparation result contract. */ + template + [[nodiscard]] int prepare_factors(keys::Factors, std::size_t) const; + }; + + struct BorrowedPreparationResult final : NormalizationPrescriptionTag + { + /** @brief A borrowed result cannot become the graph's independently owned frozen factors. */ + template + [[nodiscard]] const std::expected, std::string>& prepare_factors(keys::Factors, std::size_t) const; + }; + + struct StateOnlyPrescription final : NormalizationPrescriptionTag + { + /** @brief A state method does not implicitly supply the inverse residual coordinate transform. */ + [[nodiscard]] std::expected, std::string> prepare_factors(keys::Factors, std::size_t) const; + }; + + /** @brief Apply one declared policy independently to state and residual coordinates. */ + template + using PolicyPlan = NormalizationPlan< + Bindings>>, + Bindings>>>; + + using PolicyLayout = CoordinateLayout, KeyList>; + static_assert(NormalizationPreparable); + static_assert(NormalizationPreparable); + static_assert(NormalizationPreparable, PolicyLayout>); + static_assert(!NormalizationPreparable, PolicyLayout>); + static_assert(!NormalizationPreparable, PolicyLayout>); + static_assert(!NormalizationPreparable, PolicyLayout>); + static_assert(!NormalizationPreparable, PolicyLayout>); + static_assert(!NormalizationPreparable); + static_assert(!NormalizationPreparable); + static_assert(!NormalizationPreparable); + static_assert(!NormalizationPreparable, KeyList<>>, int>); + + struct AliasesOnlyLayout + { + using Values = checks::Values; + using Residuals = checks::Residuals; + }; + + static_assert(!NormalizationPreparable); + + using QualifiedPolicyComputation = decltype(detail::coordinate_computation(RuntimePreparedCoordinate{})); + static_assert(std::same_as>>); + + // The generated rules read residual physics from equations, never from the scalar unknown they close. + static_assert(std::same_as::Target, PhysicalRieszCoordinate>); + static_assert(std::same_as::Target, PhysicalRieszCoordinate>); + static_assert(std::same_as::Target, PhysicalRieszCoordinate>); + static_assert(std::same_as::Target, PhysicalRieszCoordinate>>); + + struct TemperatureCoordinate + { + using Quantity = dimensions::Temperature; + }; + + struct TemperatureMetric + { + using Coordinates = KeyList; + }; + + struct TemperatureScales + { + }; + + /** @brief External thermodynamic scales participate through their own typed ADL provider. */ + [[nodiscard]] dimensions::QuantityValue characteristic_scale(const TemperatureScales&, dimensions::Temperature); + + struct UntypedTemperatureScales + { + }; + + /** @brief A raw number cannot silently stand in for a scale carrying the requested quantity. */ + [[nodiscard]] double characteristic_scale(const UntypedTemperatureScales&, dimensions::Temperature); + + using TemperaturePlan = NormalizationPlan>>, Bindings<>>; + using TemperatureLayout = CoordinateLayout, KeyList<>>; + static_assert(NormalizationPlanType); + static_assert(!NormalizationPreparable); + static_assert(NormalizationPreparable); + static_assert(NormalizationPreparable); + static_assert(!NormalizationPreparable); + + /** @brief Inspect the public graph's type without constructing or preparing numerical objects. */ + template + auto graph_type(std::tuple) -> decltype(dependencies::make_computation_graph(std::declval()...)); + + /** @brief Describe an explicitly owned source using the same factory as runtime clients. */ + template + using OwnedSource = decltype(dependencies::own(std::declval())); + + using MetricLayout = CoordinateLayout, KeyList<>>; + using MetricComputations = decltype(make_normalization_computations(std::declval())); + using MetricGraph = decltype(graph_type(std::declval())); + using LayoutSource = OwnedSource; + using ScaleSource = OwnedSource; + using DensityMetricSource = OwnedSource, std::vector>; + using DisplacementMetricSource = OwnedSource, std::vector>; + using CompleteMetricContext = dependencies::DependencyContext; + using MissingMetricContext = dependencies::DependencyContext; + using WrongMetricContext = dependencies::DependencyContext, std::vector>>; + static_assert(dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + + using PolicyComputations = decltype(make_normalization_computations>(std::declval())); + using PolicyGraph = decltype(graph_type(std::declval())); + + /** @brief Vary the supplied resource type while keeping the plan's prescription key unchanged. */ + template + using PolicyContext = dependencies::DependencyContext, OwnedSource, SuppliedPrescription>>; + + static_assert(std::equality_comparable); + static_assert(std::equality_comparable); + static_assert(dependencies::CanPrepare, std::string>); + static_assert(!dependencies::CanPrepare, std::string>); + static_assert(!dependencies::CanPrepare, std::string>); +} + + +namespace serif::solver::normalization::checks { + static_assert(DimensionlessMetricGeometry::value == ReferenceGeometry::surface); +} diff --git a/src/compile_time_checks/serif/solver/preconditioner/coupling.cpp b/src/compile_time_checks/serif/solver/preconditioner/coupling.cpp new file mode 100644 index 00000000..608282a3 --- /dev/null +++ b/src/compile_time_checks/serif/solver/preconditioner/coupling.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +#include "serif/backend/mfem/solver/preconditioner/border.hpp" + +namespace serif::solver::preconditioner::checks +{ + namespace backend = preconditioner::backend::mfem; + + /** @brief A borrowed raw operator cannot satisfy factories which retain their numerical prerequisites. */ + template + concept Restrictable = requires(Candidate candidate, std::span indices) + { + backend::try_restrict_operator(candidate, indices, indices); + }; + + static_assert(Restrictable); + static_assert(!Restrictable<::mfem::Operator&>); + static_assert(!Restrictable); + static_assert(std::same_as); + static_assert(std::same_as>); + static_assert(std::same_as>); +} // namespace serif::solver::preconditioner::checks diff --git a/src/compile_time_checks/serif/solver/preconditioner/inverses.cpp b/src/compile_time_checks/serif/solver/preconditioner/inverses.cpp new file mode 100644 index 00000000..2d88b64d --- /dev/null +++ b/src/compile_time_checks/serif/solver/preconditioner/inverses.cpp @@ -0,0 +1,13 @@ +#include +#include "serif/backend/mfem/solver/preconditioner/inverse/amg.hpp" +#include "serif/backend/mfem/solver/preconditioner/inverse/dense.hpp" +#include "serif/solver/preconditioner/strategies/inverse/diagonal.hpp" + +namespace serif::solver::preconditioner +{ + namespace inverse = strategies::inverse; + + static_assert(inverse::Diagonal::application_contract == ApplicationContract::fixed_linear); + static_assert(inverse::DenseDirect::application_contract == ApplicationContract::fixed_linear); + static_assert(inverse::FixedAMG::application_contract == ApplicationContract::fixed_linear); +} // namespace serif::solver::preconditioner diff --git a/src/compile_time_checks/serif/solver/preconditioner/layout.cpp b/src/compile_time_checks/serif/solver/preconditioner/layout.cpp new file mode 100644 index 00000000..c1690765 --- /dev/null +++ b/src/compile_time_checks/serif/solver/preconditioner/layout.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include + +#include "serif/discretization/blocks/fields.hpp" +#include "serif/discretization/blocks/vector_view.hpp" +#include "serif/operators/linearized_system_view.hpp" +#include "serif/solver/normalization/layout.hpp" +#include "serif/solver/normalization/keys.hpp" +#include "serif/backend/mfem/solver/preconditioner/computations/ordering.hpp" +#include "serif/solver/preconditioner/default_preconditioners.hpp" +#include "serif/utils/dependencies/dependencies.hpp" + +namespace serif::solver::preconditioner::checks +{ + namespace blocks = discretization::blocks; + namespace dependencies = utils::dependencies; + using Kind = blocks::RepresentedValueKind; + using Mass = blocks::barotropic_constant::mass_normalization; + using Reaction = blocks::frame_acceleration::component<0>; + using Density = blocks::density::mass; + using Keys = dependencies::KeyList; + using Layout = blocks::SystemLayout; + using State = blocks::StateView; + using ConstantState = blocks::StateView; + using Physical = operators::PhysicalBlock; + using Sources = decltype(dependencies::make_dependency_context(dependencies::own(1))); + using System = operators::LinearizedSystemView; + + /** @brief Detection of an absent coordinate must fail in constraints instead of instantiating a function body. */ + template + concept ReadsCoordinate = requires(const View& view) + { + view.template get(); + }; + + /** @brief Only declared equation/unknown pairs are exposed by a borrowed linearization. */ + template + concept ReadsBlock = requires(const View& view) + { + view.template block(); + }; + + /** @brief Malformed coordinate requests remain detection-safe on the owning metadata as well. */ + template + concept HasOffset = requires(const Candidate& layout) + { + layout.template offset(); + }; + + static_assert(std::same_as); + static_assert(Layout::index() == 0); + static_assert(Layout::index() == 1); + static_assert(std::same_as, Layout>); + static_assert(std::same_as); + static_assert(std::same_as); + + using ReorderedLayout = blocks::SystemLayout>; + constexpr ReorderedLayout resolved_layout = blocks::make_system_layout([]() { + if constexpr (std::same_as) { + return 3; + } else { + return 1; + } + }); + static_assert(resolved_layout.state_sizes == std::array{3, 1}); + static_assert(resolved_layout.residual_sizes == std::array{1, 3}); + + static_assert(blocks::SystemLayoutType); + static_assert(std::same_as::Values, Keys>); + static_assert(ReadsCoordinate); + static_assert(!ReadsCoordinate); + static_assert(!ReadsCoordinate); + static_assert(HasOffset); + static_assert(!HasOffset); + static_assert(std::same_as().get()), std::span>); + static_assert(std::same_as().get()), std::span>); + static_assert(std::constructible_from>); + static_assert(!std::constructible_from>); + static_assert(!std::constructible_from>); + static_assert(!std::constructible_from>); + static_assert(!std::constructible_from&&>); + static_assert(!std::constructible_from&&>); + static_assert(!std::constructible_from&&>); + static_assert(std::constructible_from&>); + static_assert(std::constructible_from>); + static_assert(std::constructible_from, const Layout&, std::span>); + static_assert(!std::constructible_from, Layout&&, std::span>); + static_assert(ReadsBlock); + static_assert(!ReadsBlock); + static_assert(std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(!std::constructible_from); + static_assert(std::constructible_from, const Layout&, const Sources&>); +} // namespace serif::solver::preconditioner::checks + + +namespace serif::solver::preconditioner::checks { + using Potential = blocks::gravity::potential; + using Rotation = blocks::rotation::angular_velocity; + using Shape = blocks::surface_deformation::coefficients; + using ShapeRow = blocks::surface_deformation::shape_equilibrium; + + // Extra generated scalars need no new entry in the recipe registry, even when equation and value identities differ. + struct ExtraField : blocks::field {}; + struct ExtraValue : blocks::term, blocks::global_scalar { + using Support = discretization::domain::StellarDomains; + }; + struct ExtraRow : blocks::term, blocks::global_scalar { + using Support = discretization::domain::StellarDomains; + }; + using PrimalFields = dependencies::KeyList; + using PrimalFixedLayout = blocks::SystemLayout; + using PrimalMovingLayout = blocks::SystemLayout, + dependencies::KeyList>; + using PrimalRecipe = decltype(make_primal_preconditioner(std::declval())); + using PrimalExtraLayout = blocks::SystemLayout>, + dependencies::CombinedKeyList>>; + using PrimalExtraRecipe = decltype(make_primal_preconditioner(std::declval())); + + /** @brief Keep unsupported coordinate sets out of the primal recipe before its body is instantiated. */ + template + concept HasPrimalPreconditioner = requires(const Candidate& layout) { + make_primal_preconditioner(layout); + }; + + static_assert(HasPrimalPreconditioner); + static_assert(HasPrimalPreconditioner); + static_assert(HasPrimalPreconditioner); + static_assert(!HasPrimalPreconditioner); + static_assert(!HasPrimalPreconditioner, dependencies::KeyList>>); + static_assert(!HasPrimalPreconditioner>, PrimalFields>>); + static_assert(!HasPrimalPreconditioner>, PrimalFields>>); + static_assert(!HasPrimalPreconditioner>, + dependencies::CombinedKeyList>>>); + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(std::same_as>); + static_assert(std::same_as().fields.coupling), strategies::coupling::SecondThenFirst>); + static_assert(std::same_as().fields.first.inverse), strategies::inverse::Diagonal>); + static_assert(std::same_as().fields.second.inverse), strategies::inverse::FixedAMG>); +} diff --git a/src/compile_time_checks/serif/solver/public_contracts.cpp b/src/compile_time_checks/serif/solver/public_contracts.cpp new file mode 100644 index 00000000..b75b81f0 --- /dev/null +++ b/src/compile_time_checks/serif/solver/public_contracts.cpp @@ -0,0 +1,47 @@ +#include +#include +#include "serif/types.hpp" + +#include "serif/solver/nonlinear/result.hpp" +#include "serif/solver/nonlinear/newton.hpp" +#include "serif/solver/seed/polytrope.hpp" + +namespace { + namespace solver = serif::solver; + + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + + struct EquationDefinition {}; + using MassModel = decltype(serif::model::make_stellar_model(std::declval(), EquationDefinition{}, + std::declval())); + using UnspecifiedMass = decltype(serif::model::make_stellar_model(std::declval(), EquationDefinition{})); + template + concept DefaultRadialSeed = requires(const Model& model) { solver::seed::make_polytropic_seed(model); }; + static_assert(DefaultRadialSeed); + static_assert(!DefaultRadialSeed); + static_assert(!DefaultRadialSeed); + + struct State {}; + struct System {}; + + struct Problem { + using Vector = serif::Vector; + std::expected, std::string> prepare(const State&); + }; + struct InvalidSolve : solver::StandardNewton { + solver::linear::Report solve(int); + }; + struct DirectionOnly : solver::StandardNewton { + solver::DirectionAssessment direction(auto&, const auto&, const auto&, const auto&, auto&, const auto&) { return {}; } + }; + static_assert(solver::detail::NewtonStrategyFor>); + static_assert(solver::detail::NewtonStrategyFor>); + static_assert(!solver::detail::NewtonStrategyFor>); + using Result = solver::SolveResult; + static_assert(std::same_as().state()), const State&>); + static_assert(std::same_as().system()), const std::shared_ptr&>); + static_assert(std::same_as().diagnostics()), const solver::SolveDiagnostics&>); +} diff --git a/src/compile_time_checks/serif/utils/dependencies/binding.cpp b/src/compile_time_checks/serif/utils/dependencies/binding.cpp new file mode 100644 index 00000000..fbe56482 --- /dev/null +++ b/src/compile_time_checks/serif/utils/dependencies/binding.cpp @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "serif/utils/dependencies/binding.hpp" + +namespace { + namespace dependencies = serif::utils::dependencies; + + struct FirstKey {}; + struct SecondKey {}; + struct MissingKey {}; + + struct UnrelatedBinding { + using Key = FirstKey; + using Target = int; + }; + + using Empty = dependencies::Bindings<>; + using Collection = dependencies::Bindings, dependencies::Binding>; + using RepeatedTargets = dependencies::Bindings, dependencies::Binding>; + using DuplicateKeys = dependencies::Bindings, dependencies::Binding>; + using QualifiedKeys = dependencies::Bindings< + dependencies::Binding, + dependencies::Binding + >; + using QualifiedTarget = dependencies::Bindings>; + + template + concept HasBoundType = requires { + typename dependencies::bound_type_t; + }; + + template + concept HasBoundTypeMember = requires { + typename dependencies::BoundType::target_type; + }; + + static_assert(dependencies::IsBinding>); + static_assert(!dependencies::IsBinding); + static_assert(!dependencies::IsBinding); + static_assert(dependencies::IsBindings); + static_assert(dependencies::IsBindings); + static_assert(dependencies::IsBindings); + static_assert(!dependencies::IsBindings); + static_assert(!dependencies::IsBindings); + + static_assert(dependencies::HasBinding::value); + static_assert(dependencies::has_binding_v); + static_assert(!dependencies::has_binding_v); + static_assert(!dependencies::has_binding_v); + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, double>); + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, const int&>); + + // Every public lookup accepts the qualified collection types recognized by IsBindings. + static_assert(dependencies::IsBindings); + static_assert(dependencies::HasBinding::value); + static_assert(dependencies::has_binding_v); + static_assert(dependencies::BindingsWithBinding); + static_assert(std::is_same_v::target_type, int>); + static_assert(std::is_same_v, double>); + + // Qualified keys are distinct associations; queries must agree with their exact stored types. + static_assert(dependencies::IsBindings); + static_assert(dependencies::has_binding_v); + static_assert(dependencies::BindingsWithBinding); + static_assert(dependencies::BindingsWithBinding); + static_assert(!dependencies::has_binding_v); + static_assert(!dependencies::BindingsWithBinding); + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, double>); + + // Missing keys and invalid collections must fail these requirements without a hard compiler error. + static_assert(HasBoundType); + static_assert(HasBoundTypeMember); + static_assert(!HasBoundType); + static_assert(!HasBoundTypeMember); + static_assert(!HasBoundType); + static_assert(!HasBoundTypeMember); + static_assert(!HasBoundType); + static_assert(!HasBoundType); + static_assert(!HasBoundTypeMember); + static_assert(!dependencies::BindingsWithBinding); + static_assert(!dependencies::BindingsWithBinding); + static_assert(!HasBoundType); + static_assert(!HasBoundTypeMember); + + template + struct IndexedKey {}; + + template + struct IndexedTarget { + IndexedTarget() = delete; + }; + + /** @brief Resolve a stellar-graph-sized collection without constructing its targets. */ + template + consteval bool every_binding_resolves(std::index_sequence) { + using LargeCollection = dependencies::Bindings, IndexedTarget>...>; + constexpr std::array resolved{ + std::is_same_v>, IndexedTarget>...}; + for (const bool correct : resolved) { + if (!correct) { return false; } + } + return !dependencies::has_binding_v; + } + + static_assert(every_binding_resolves(std::make_index_sequence<300>{})); + /** @brief Invalid members remain a failed collection requirement, even when their aliases are missing. */ + template + concept CanMakeBindings = requires { typename dependencies::Bindings; }; + static_assert(CanMakeBindings<>); + static_assert(CanMakeBindings>); + static_assert(!CanMakeBindings>); + static_assert(!CanMakeBindings, int>); + static_assert(!CanMakeBindings); + +} diff --git a/src/compile_time_checks/serif/utils/dependencies/computation_graph.cpp b/src/compile_time_checks/serif/utils/dependencies/computation_graph.cpp new file mode 100644 index 00000000..2d58f05e --- /dev/null +++ b/src/compile_time_checks/serif/utils/dependencies/computation_graph.cpp @@ -0,0 +1,321 @@ +#include "serif/utils/dependencies/computation_cache.hpp" +#include "serif/utils/dependencies/timing.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + namespace dependencies = serif::utils::dependencies; + + struct Source {}; + struct First {}; + struct Second {}; + struct Missing {}; + struct Unused {}; + struct Untrackable {}; + + using DependencyContext = decltype(dependencies::make_dependency_context(dependencies::own(1), dependencies::own(Untrackable{}))); + using EmptyContext = dependencies::DependencyContext<>; + using FirstCalculation = decltype(dependencies::make_computation>([](int value) { return value + 1; })); + using SecondCalculation = decltype(dependencies::make_computation>([](int value) { return 2 * value; })); + using ComputationGraph = dependencies::ComputationGraph; + + static_assert(dependencies::CompatibleComputations); + static_assert(!dependencies::CompatibleComputations); + static_assert(!dependencies::CompatibleComputations); + static_assert(dependencies::ComputationGraphFor); + static_assert(dependencies::ComputationGraphFor); + static_assert(!dependencies::ComputationGraphFor); + static_assert(!dependencies::ComputationGraphFor && !dependencies::ComputationGraphFor); + static_assert(dependencies::ComputationGraphFor, EmptyContext>); + static_assert(ComputationGraph::Dependencies::order == std::array{1, 0}); + static_assert(ComputationGraph::Dependencies::required(dependencies::KeyList{}) == std::array{true, true}); + static_assert(ComputationGraph::Dependencies::required(dependencies::KeyList{}) == std::array{false, true}); + static_assert(ComputationGraph::Dependencies::required(dependencies::KeyList<>{}) == std::array{false, false}); + + using CyclicFirst = decltype(dependencies::make_computation>([](int value) { return value; })); + using SelfCycle = decltype(dependencies::make_computation>([](int value) { return value; })); + static_assert(!dependencies::CompatibleComputations); + static_assert(!dependencies::CompatibleComputations); + + using DuplicateProducer = decltype(dependencies::make_dependency_context(dependencies::own(1), dependencies::own(2))); + static_assert(!dependencies::ComputationGraphFor); + + template > + using SingleGraph = dependencies::ComputationGraph>; + + using MutableInput = decltype([](int& value) { return ++value; }); + using MutableCallable = decltype([value = 0](int) mutable { return ++value; }); + using WrongArity = decltype([](int, int) { return 0; }); + using WrongType = decltype([](const std::string&) { return 0; }); + using VoidResult = decltype([](int) {}); + using ReferenceResult = decltype([](const int& value) -> const int& { return value; }); + using MoveOnlyResult = decltype([](int value) { return std::make_unique(value); }); + using VoidExpected = decltype([](int) -> std::expected { return {}; }); + using Unary = decltype([](int value) { return value; }); + + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor, DependencyContext>); + static_assert(!dependencies::ComputationGraphFor>, DependencyContext>); + static_assert(dependencies::ComputationGraphFor, DependencyContext>); + + using Fallible = decltype([](int value) -> std::expected { return value; }); + using ConstFallible = decltype([](int value) -> const std::expected { return value; }); + static_assert(dependencies::CanPrepare); // Unused resources need no tracking policy. + static_assert(dependencies::CanPrepare, DependencyContext, int>); + static_assert(!dependencies::CanPrepare, DependencyContext, std::string>); + static_assert(dependencies::CanPrepare, DependencyContext, int>); + static_assert(!dependencies::CanPrepare, DependencyContext, std::string>); + static_assert(dependencies::CanPrepare, DependencyContext, std::string>); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + + using ReadUntrackable = decltype([](const Untrackable&) { return 1; }); + using UntrackableGraph = SingleGraph>; + static_assert(dependencies::ComputationGraphFor); + static_assert(!dependencies::CanPrepare); + + using ComputationCache = dependencies::ComputationCache; + template + concept CanRequest = requires(ComputationCache& cache, const DependencyContext& context, RequestedT request) { + cache.try_prepare(context, request); + }; + static_assert(CanRequest>); + static_assert(CanRequest>); + static_assert(CanRequest>); + static_assert(!CanRequest>); + static_assert(!CanRequest>); + static_assert(!CanRequest>); + static_assert(!CanRequest); + static_assert(!std::copy_constructible); + static_assert(std::move_constructible); + static_assert(std::same_as().get()), const int&>); + + using Inputs = dependencies::InputList; + + struct WrongIdentity : dependencies::ValueSnapshot { + std::string identity(const int&) const; + }; + static_assert(!dependencies::SnapshotPolicyFor); + + template > + concept CanMakeComputation = requires(FunctionT&& function) { + dependencies::make_computation(std::forward(function)); + }; + + template + concept CanMakeCache = requires(const ContextT& context, Computations&&... computations) { + dependencies::make_computation_cache(context, std::forward(computations)...); + }; + + template + concept CanMakeFallibleCache = requires(const DependencyContext& context, Computations&&... computations) { + dependencies::make_computation_cache(context, std::forward(computations)...); + }; + + static_assert(CanMakeComputation); + static_assert(!CanMakeComputation); + static_assert(!CanMakeComputation>); + static_assert(CanMakeCache); + static_assert(CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache>); + static_assert(CanMakeFallibleCache>); + static_assert(!CanMakeFallibleCache>); + + using MoveOnlyCallable = decltype([value = std::make_unique(2)](int input) { return input + *value; }); + using MoveOnlyComputation = dependencies::Computation; + static_assert(CanMakeComputation); + static_assert(!CanMakeComputation); + static_assert(CanMakeCache); + static_assert(!CanMakeCache); + + using OwningCalculation = decltype(dependencies::make_owning_computation>([](std::shared_ptr value) + { + return value; + })); + using OwningGraph = dependencies::ComputationGraph; + static_assert(dependencies::CanPrepare); + static_assert(CanMakeCache); + static_assert(!dependencies::CompatibleComputations); + static_assert(!CanMakeCache); + static_assert(!CanMakeCache); + + using OwnsSource = decltype(dependencies::make_owning_computation>([](std::shared_ptr value) + { + return value; + })); + static_assert(!CanMakeCache); + + using MutableOwnedInput = decltype(dependencies::make_owning_computation>([](std::shared_ptr value) + { + return *value; + })); + using MutableOwnedGraph = dependencies::ComputationGraph; + static_assert(!dependencies::CanPrepare); + + using EmptyOwningCalculation = decltype(dependencies::make_owning_computation>([]() + { + return 3; + })); + static_assert(CanMakeCache); +} + + +namespace { + template + struct ChainKey {}; + + template + struct ReverseChainNode { + using Key = ChainKey; + using Inputs = dependencies::KeyList>; + }; + + /** @brief Exercise the graph ordering directly without unrelated callable and storage template instantiations. */ + template + [[nodiscard]] consteval auto reverse_chain(std::index_sequence) { + return dependencies::detail::Graph...>{}; + } + + using LongChain = decltype(reverse_chain(std::make_index_sequence<144>{})); + + /** @brief A reverse-declared chain must compile within the toolchain's ordinary constexpr evaluation budget. */ + [[nodiscard]] consteval bool long_chain_is_ordered() { + for (std::size_t position = 0; position < LongChain::size; ++position) { + if (LongChain::order[position] != LongChain::size - position - 1) { + return false; + } + } + return true; + } + + template + struct OrderingNode { + using Key = ChainKey; + using Inputs = dependencies::KeyList...>; + }; + + // Nodes 1 and 2 become ready together. After 1, newly ready node 0 must precede already-ready node 2. + using StableDiamond = dependencies::detail::Graph, OrderingNode<1, 3>, OrderingNode<2, 3>, + OrderingNode<3>, OrderingNode<4, 0, 2>>; + using PartialCycle = dependencies::detail::Graph, OrderingNode<1, 0>, OrderingNode<2>>; + static_assert(LongChain::acyclic()); + static_assert(long_chain_is_ordered()); + static_assert(StableDiamond::order == std::array{3, 1, 0, 2, 4}); + static_assert(StableDiamond::acyclic()); + static_assert(PartialCycle::order == std::array{2, 3, 3}); + static_assert(!PartialCycle::acyclic()); +} + +namespace { + using NodeOutcome = dependencies::NodeOutcome; + using ObserverBefore = decltype([] { return std::make_unique(0); }); + using ObserverAfter = decltype([](std::unique_ptr&, NodeOutcome) noexcept {}); + + /** @brief Observer lifetime callbacks must agree on an owned state and cannot fail while closing it. */ + template + concept CanObserveNode = requires(BeforeT&& before, AfterT&& after) { + dependencies::observe_node(std::forward(before), std::forward(after)); + }; + + using VoidBefore = decltype([] {}); + using ReferenceBefore = decltype([]() -> int& { static int state = 0; return state; }); + using WrongStateAfter = decltype([](int&, NodeOutcome) noexcept {}); + using ThrowingAfter = decltype([](std::unique_ptr&, NodeOutcome) {}); + using NonvoidAfter = decltype([](std::unique_ptr&, NodeOutcome) noexcept { return 1; }); + static_assert(CanObserveNode); + static_assert(!CanObserveNode); + static_assert(!CanObserveNode); + static_assert(!CanObserveNode); + static_assert(!CanObserveNode); + static_assert(!CanObserveNode); + + using NodeObserver = decltype(dependencies::observe_node(ObserverBefore{}, ObserverAfter{})); + using SourceObserver = decltype(dependencies::observe_node(ObserverBefore{}, ObserverAfter{})); + using MissingObserver = decltype(dependencies::observe_node(ObserverBefore{}, ObserverAfter{})); + using InputObserver = decltype([](const int&) {}); + using SourceEdge = decltype(dependencies::observe_edge(InputObserver{})); + using InternalEdge = decltype(dependencies::observe_edge(InputObserver{})); + using MissingEdge = decltype(dependencies::observe_edge(InputObserver{})); + using ReversedEdge = decltype(dependencies::observe_edge(InputObserver{})); + using IndirectEdge = decltype(dependencies::observe_edge(InputObserver{})); + using MissingConsumer = decltype(dependencies::observe_edge(InputObserver{})); + using MutableEdge = decltype(dependencies::observe_edge([](int&) {})); + using WrongTypeEdge = decltype(dependencies::observe_edge([](const std::string&) {})); + using NonvoidEdge = decltype(dependencies::observe_edge([](const int&) { return 1; })); + + template + using ObserverSet = decltype(dependencies::make_observers(std::declval()...)); + + static_assert(dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(dependencies::ObserversFor, OwningGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor); + static_assert(!dependencies::ObserversFor, int, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, int>); + + /** @brief Observer validation must also constrain the public cache factory. */ + template + concept CanMakeObservedCache = requires(const ComputationGraph& graph, const DependencyContext& context, ObserversT&& observers) { + dependencies::make_computation_cache(graph, context, std::forward(observers)); + }; + static_assert(CanMakeObservedCache>); + static_assert(!CanMakeObservedCache>); + static_assert(!CanMakeObservedCache>); + + using MoveOnlyBefore = decltype([state = std::make_unique(0)] { return ++*state; }); + using CountAfter = decltype([](int&, NodeOutcome) noexcept {}); + using MoveOnlyObserver = decltype(dependencies::observe_node(std::declval(), CountAfter{})); + static_assert(CanObserveNode); + static_assert(!CanObserveNode); + static_assert(CanMakeObservedCache>); + static_assert(!CanMakeObservedCache&>); +} + +namespace { + using AllBefore = decltype([](std::type_identity) { return 0; }); + using AllAfter = decltype([](int&, NodeOutcome) noexcept {}); + using AllObserver = decltype(dependencies::observe_nodes(AllBefore{}, AllAfter{})); + using SomeBefore = decltype([](std::type_identity) { return 0; }); + using SomeObserver = decltype(dependencies::observe_nodes(SomeBefore{}, AllAfter{})); + using ThrowingAllAfter = decltype([](int&, NodeOutcome) {}); + using UnsafeAllObserver = decltype(dependencies::observe_nodes(AllBefore{}, ThrowingAllAfter{})); + using ReferenceAllBefore = decltype([](std::type_identity) -> int& { static int state = 0; return state; }); + using ReferenceAllObserver = decltype(dependencies::observe_nodes(ReferenceAllBefore{}, AllAfter{})); + static_assert(dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(!dependencies::ObserversFor, ComputationGraph, DependencyContext>); + static_assert(CanMakeObservedCache>); + static_assert(!CanMakeObservedCache>); + static_assert(dependencies::NodeTimingClock); + static_assert(!dependencies::NodeTimingClock); + static_assert(!dependencies::NodeTimingClock); +} diff --git a/src/compile_time_checks/serif/utils/dependencies/dependency_context.cpp b/src/compile_time_checks/serif/utils/dependencies/dependency_context.cpp new file mode 100644 index 00000000..3f43e01f --- /dev/null +++ b/src/compile_time_checks/serif/utils/dependencies/dependency_context.cpp @@ -0,0 +1,131 @@ +#include "serif/utils/dependencies/dependency_context.hpp" +#include "serif/utils/dependencies/key_list_operations.hpp" +#include "serif/utils/dependencies/keys.hpp" + +#include +#include +#include +#include + +namespace { + namespace dependencies = serif::utils::dependencies; + + struct First {}; + struct Second {}; + struct Missing {}; + + using FirstResource = decltype(dependencies::own(1)); + using SecondResource = decltype(dependencies::own(2)); + using DependencyContext = dependencies::DependencyContext; + using Selection = decltype(std::declval().select(dependencies::KeyList{})); + + template + concept CanMakeContext = requires(Resources&&... resources) { + dependencies::make_dependency_context(std::forward(resources)...); + }; + + template + concept CanRead = requires(const ContextT& context) { context.template get(); }; + + template + concept CanSelect = requires(ContextT&& context) { + std::forward(context).select(dependencies::KeyList{}); + }; + + template + concept CanBorrow = requires(ValueT&& value) { dependencies::borrow(std::forward(value)); }; + + static_assert(dependencies::IsDependencyContext); + static_assert(dependencies::IsDependencyContext); + static_assert(dependencies::IsDependencyContext); + static_assert(!dependencies::IsDependencyContext); + static_assert(CanMakeContext<>); + static_assert(CanMakeContext); + static_assert(!CanMakeContext); + static_assert(!CanMakeContext); + static_assert(CanRead && CanRead); + static_assert(!CanRead); + static_assert(CanRead && !CanRead); + static_assert(std::same_as().get()), const int&>); + static_assert(std::same_as, int>); + static_assert(CanSelect && CanSelect); + static_assert(!CanSelect && !CanSelect); + static_assert(!std::constructible_from); + static_assert(CanBorrow && CanBorrow); + static_assert(!CanBorrow && !CanBorrow); + + using QualifiedContext = decltype(dependencies::make_dependency_context( + dependencies::own(1), dependencies::own(2), dependencies::own(3) + )); + static_assert(CanRead && CanRead && CanRead); + + using Combined = dependencies::CombinedKeyList< + dependencies::KeyList, dependencies::KeyList, const dependencies::KeyList& + >; + static_assert(std::same_as>); + static_assert(std::same_as, dependencies::KeyList<>>); + + using GeneralKeys = dependencies::KeyList; + static_assert(dependencies::IsKeyList); + static_assert(std::same_as, dependencies::KeyList>); + static_assert(std::same_as, dependencies::KeyList>); + + // Keys describe roles; even a nonconstructible descriptor with data can name one. + struct CustomTerm { + int member; + CustomTerm() = delete; + }; + using CustomContext = decltype(dependencies::make_dependency_context(dependencies::own(1))); + static_assert(CanRead); + + struct NonComparable {}; + struct ReferenceSnapshot { + const int& capture(const int&) const; + bool matches(const int&, const int&) const; + }; + struct RvalueOnlySnapshot { + int capture(const int&) const; + bool matches(int&&, const int&) const; + }; + struct MoveOnlySnapshot { + std::unique_ptr capture(const int&) const; + bool matches(const std::unique_ptr&, const int&) const; + }; + + static_assert(dependencies::SnapshotPolicyFor); + static_assert(!dependencies::SnapshotPolicyFor); + static_assert(!dependencies::SnapshotPolicyFor>); + static_assert(!dependencies::SnapshotPolicyFor); + static_assert(!dependencies::SnapshotPolicyFor); + static_assert(dependencies::SnapshotPolicyFor); + using Projection = decltype(dependencies::track_by([](const int& value) { return value; })); + using InvalidProjection = decltype(dependencies::track_by([](const int&) { return NonComparable{}; })); + static_assert(dependencies::SnapshotPolicyFor); + static_assert(!dependencies::SnapshotPolicyFor); + + template + concept CanCombineContexts = requires(const Contexts&... contexts) + { + dependencies::combine_dependency_contexts(contexts...); + }; + using FirstContext = dependencies::DependencyContext; + using SecondContext = dependencies::DependencyContext; + struct DefaultInputs { + dependencies::DependencyContext<> inputs; + }; + static_assert(requires { DefaultInputs{}; }); + static_assert(std::constructible_from); + static_assert(!std::convertible_to); + static_assert(!std::default_initializable); + using MoveOnlyContext = decltype(dependencies::make_dependency_context(dependencies::own(std::make_unique(3)))); + static_assert(CanCombineContexts<>); + static_assert(CanCombineContexts); + static_assert(CanCombineContexts); + static_assert(!CanCombineContexts); + static_assert(!CanCombineContexts); + static_assert(!CanCombineContexts); + static_assert(!CanCombineContexts); + static_assert(std::same_as>); + static_assert(std::same_as>); +} diff --git a/src/compile_time_checks/serif/utils/dependencies/key_list.cpp b/src/compile_time_checks/serif/utils/dependencies/key_list.cpp new file mode 100644 index 00000000..e01fe77b --- /dev/null +++ b/src/compile_time_checks/serif/utils/dependencies/key_list.cpp @@ -0,0 +1,157 @@ +#include "serif/utils/dependencies/key_list.hpp" +#include "serif/utils/dependencies/key_list_operations.hpp" + +#include +#include + +namespace { + namespace dependencies = serif::utils::dependencies; + namespace types = serif::utils::types; + + struct Enthalpy {}; + struct Entropy {}; + struct ElectronFraction {}; + struct Density {}; + struct MissingKey {}; + + struct EnthalpyTerm {}; + struct MaterialTerm {}; + struct DensityTerm {}; + + using Empty = dependencies::KeyList<>; + using SurfaceInputs = dependencies::KeyList; + using DuplicateInputs = dependencies::KeyList; + using MissingInput = dependencies::KeyList; + + /** @brief Invalid indexing remains a failed requirement rather than an out-of-range constant. */ + template + concept CanIndex = requires + { + dependencies::key_index(); + }; + + static_assert(dependencies::ContainsKey); + static_assert(dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); + static_assert(!dependencies::ContainsKey); + static_assert(dependencies::key_count_v == 0); + static_assert(dependencies::key_count_v == 3); + static_assert(dependencies::key_index() == 0); + static_assert(dependencies::key_index() == 2); + static_assert(!CanIndex); + static_assert(!CanIndex); + static_assert(!CanIndex); + static_assert(!CanIndex); + + // Binding order differs from declaration order, and density is available but not required. + using Available = dependencies::Bindings< + dependencies::Binding, + dependencies::Binding, + dependencies::Binding, + dependencies::Binding + >; + using Incomplete = dependencies::Bindings>; + using DuplicateBindings = dependencies::Bindings< + dependencies::Binding, + dependencies::Binding + >; + + template + concept CanResolve = requires { + typename dependencies::ResolvedTargets; + }; + + static_assert(dependencies::IsKeyList); + static_assert(dependencies::IsKeyList); + static_assert(dependencies::IsKeyList); + static_assert(!dependencies::IsKeyList); + static_assert(!dependencies::IsKeyList); + static_assert(std::is_same_v>); + + static_assert(dependencies::SatisfiesKeyList); + static_assert(dependencies::SatisfiesKeyList); + static_assert(dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + + // Distinct physical inputs remain distinct entries even when they share a target type. + using SurfaceTargets = types::TypeList; + static_assert(std::is_same_v, SurfaceTargets>); + static_assert(std::is_same_v, SurfaceTargets>); + static_assert(std::is_same_v< + dependencies::ResolvedTargets>, + types::TypeList + >); + + static_assert(dependencies::SatisfiesKeyList, Empty>); + static_assert(dependencies::SatisfiesKeyList); + static_assert(std::is_same_v, Empty>, types::TypeList<>>); + static_assert(std::is_same_v, types::TypeList<>>); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + + // Normalize the containers, while preserving the exact key and target types they contain. + using QualifiedInputs = dependencies::KeyList; + using QualifiedBindings = dependencies::Bindings< + dependencies::Binding, + dependencies::Binding + >; + static_assert(dependencies::IsKeyList); + static_assert(dependencies::key_index() == 0); + static_assert(dependencies::key_index() == 1); + static_assert(!CanIndex); + static_assert(dependencies::IsKeyList>); + static_assert(dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList); + static_assert(!dependencies::SatisfiesKeyList>); + static_assert(std::is_same_v, types::TypeList>); + + // Unsupported resolution must fail a requirement without producing a hard compiler error. + static_assert(CanResolve); + static_assert(CanResolve, Empty>); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + static_assert(!CanResolve); + // Full model graphs may contain more keys than the compiler's default fold-expression depth. + template + struct LargeKey {}; + + /** @brief Name a large key pack without constructing any resources or computations. */ + template + auto large_keys(std::index_sequence) -> dependencies::KeyList...>; + + /** @brief Keep a duplicate at the end of a large public key list to test validation rather than union deduplication. */ + template + auto duplicate_large_keys(std::index_sequence) -> dependencies::KeyList..., LargeKey<299>>; + + using Large = decltype(large_keys(std::make_index_sequence<300>{})); + using DuplicateLarge = decltype(duplicate_large_keys(std::make_index_sequence<300>{})); + static_assert(!dependencies::IsKeyList); + static_assert(dependencies::IsKeyList, const LargeKey<0>, LargeKey<0>&>>); + static_assert(!dependencies::IsKeyList, LargeKey<0>&, const LargeKey<0>>>); + using Extended = dependencies::CombinedKeyList, MissingKey>>; + static_assert(dependencies::ContainsKey, Large>); + static_assert(!dependencies::ContainsKey); + static_assert(dependencies::key_index, Large>() == 299); + static_assert(dependencies::detail::key_list_subset_v); + static_assert(dependencies::detail::key_list_subset_v); + static_assert(!dependencies::detail::key_list_subset_v); + static_assert(!dependencies::detail::key_list_subset_v, Large>); + static_assert(dependencies::key_count_v == 301); + static_assert(dependencies::key_index, Extended>() == 0); + static_assert(dependencies::key_index, Extended>() == 299); + static_assert(dependencies::key_index() == 300); + +} diff --git a/src/compile_time_checks/serif/utils/dependencies/mixed_errors.cpp b/src/compile_time_checks/serif/utils/dependencies/mixed_errors.cpp new file mode 100644 index 00000000..f3c063b6 --- /dev/null +++ b/src/compile_time_checks/serif/utils/dependencies/mixed_errors.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +#include "serif/utils/dependencies/computation_cache.hpp" + +namespace { + namespace dependencies = serif::utils::dependencies; + + struct Source {}; + struct First {}; + struct Second {}; + struct Output {}; + struct InputFailure {}; + struct PhysicsFailure {}; + struct OwnedFailure { + std::unique_ptr value; + }; + + template + struct Fallible { + /// Exercise the expected-result type without a runtime library dependency. + [[nodiscard]] constexpr std::expected operator()(int value) const { + return value; + } + }; + + struct Infallible { + /// Value-returning stages must remain compatible with every valid chosen error type. + [[nodiscard]] constexpr int operator()(int value) const { + return value; + } + }; + + using Context = decltype(dependencies::make_dependency_context(dependencies::own(1))); + + template + using FirstCalculation = dependencies::Computation, Fallible>; + + template + using SecondCalculation = dependencies::Computation, Fallible>; + + using OutputCalculation = dependencies::Computation, Infallible>; + using MixedGraph = dependencies::ComputationGraph, SecondCalculation, OutputCalculation>; + using FailureUnion = std::variant; + + static_assert(dependencies::CanPrepare); + static_assert(dependencies::CanPrepare); + static_assert(dependencies::CanPrepare>); + static_assert(dependencies::CanPrepare>); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare>); + static_assert(!dependencies::CanPrepare>); + static_assert(!dependencies::CanPrepare>); + static_assert(!dependencies::CanPrepare>); + static_assert(!dependencies::CanPrepare>); + static_assert(!dependencies::CanPrepare); + + using SameErrorGraph = dependencies::ComputationGraph, SecondCalculation, OutputCalculation>; + static_assert(dependencies::CanPrepare); + static_assert(dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare); + + using ExistingUnionGraph = dependencies::ComputationGraph>; + static_assert(dependencies::CanPrepare); + + using TextErrorGraph = dependencies::ComputationGraph>; + static_assert(std::constructible_from); + static_assert(std::constructible_from, const char*>); + static_assert(!dependencies::CanPrepare); + static_assert(!dependencies::CanPrepare>); + static_assert(dependencies::CanPrepare>); + + using OwnedErrorGraph = dependencies::ComputationGraph, SecondCalculation, OutputCalculation>; + using OwnedErrorUnion = std::variant; + static_assert(std::move_constructible); + static_assert(!std::copy_constructible); + static_assert(dependencies::CanPrepare); +} diff --git a/src/compile_time_checks/serif/utils/laneemden.cpp b/src/compile_time_checks/serif/utils/laneemden.cpp new file mode 100644 index 00000000..e684d6df --- /dev/null +++ b/src/compile_time_checks/serif/utils/laneemden.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#include "serif/utils/laneemden/laneemden.hpp" + +namespace { + namespace laneemden = serif::utils::laneemden; + namespace dimensions = serif::dimensions; + + /** @brief Physical reference inputs must carry their distinct dimensions. */ + template + concept CanScaleMass = requires(const laneemden::Solution &solution, Mass mass, Density density, Gravity gravity) { + { laneemden::scale_from_mass(solution, mass, density, gravity) } -> std::same_as>; + }; + + static_assert(CanScaleMass, dimensions::QuantityValue, dimensions::QuantityValue>); + static_assert(!CanScaleMass); + static_assert(!CanScaleMass, dimensions::QuantityValue, dimensions::QuantityValue>); + static_assert(!CanScaleMass, dimensions::QuantityValue, dimensions::QuantityValue>); + static_assert(std::same_as>); + static_assert(std::equality_comparable); + static_assert(std::formattable); + static_assert(std::formattable); + + /** @brief Literal formatting must parse selectors at compile time for both levels of the profile. */ + constexpr std::format_string + sample_formats{"{} {:all} {:xi} {:theta} {:derivative}"}; + constexpr std::format_string + solution_formats{"{} {:all} {:xi} {:theta} {:derivative}"}; +} diff --git a/src/compile_time_checks/serif/utils/projection.cpp b/src/compile_time_checks/serif/utils/projection.cpp new file mode 100644 index 00000000..c24b2071 --- /dev/null +++ b/src/compile_time_checks/serif/utils/projection.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "serif/backend/mfem/projection/radial.hpp" + +namespace { + namespace projection = serif::utils::projection; + namespace backend = projection::backend::mfem; + + struct ScalarProfile { + /** @brief Declare a valid infallible radial evaluator. */ + double operator()(double) const; + }; + + struct CheckedProfile { + /** @brief Declare a valid fallible radial evaluator. */ + projection::RadialValue operator()(double) const; + }; + + struct VectorProfile { + /** @brief Vectors are not scalar radial amplitudes. */ + ::mfem::Vector operator()(double) const; + }; + + struct WrongFailure { + /** @brief Reject unadapted errors rather than silently erasing their meaning. */ + std::expected operator()(double) const; + }; + + static_assert(projection::RadialFunction); + static_assert(projection::RadialFunction); + static_assert(!projection::RadialFunction); + static_assert(!projection::RadialFunction); + static_assert(!projection::RadialFunction); + static_assert(std::same_as(), ScalarProfile{} + )), backend::ProjectionResult>); + static_assert(std::same_as(), CheckedProfile{} + )), backend::ProjectionResult>); +}