docs(blocks): added doxygen comments

This commit is contained in:
2026-10-01 15:12:07 -04:00
parent c1ddf6d534
commit f94f4ee8d6
6 changed files with 422 additions and 70 deletions

View File

@@ -1,30 +1,62 @@
#pragma once
/**
* @file base.hpp
* @author Emily M. Boudreaux
* @brief This file provides the base classes for residual and value blocks in the discretization framework.
*/
namespace serif::discretization::blocks {
inline constexpr int DYNAMIC_BLOCK_SIZE = -1;
/**
* @struct block
* @brief Base class for all block types in the discretization framework.
*/
struct block {};
/**
* @struct residual_block_base
* @brief Base class for residual blocks, derived from block.
*/
struct residual_block_base : block {
static constexpr int static_block_size = DYNAMIC_BLOCK_SIZE;
};
/**
* @struct value_block_base
* @brief Base class for value blocks, derived from block.
*/
struct value_block_base : block {
static constexpr int static_block_size = DYNAMIC_BLOCK_SIZE;
};
/**
* @struct generated_value_block
* @brief Final class for generated value blocks, derived from value_block_base. These are blocks that are generated at runtime
* @todo This may be removable at this point
*/
template <typename GeneratedValue>
struct generated_value_block final : value_block_base {
using GeneratedType = GeneratedValue;
static constexpr int static_block_size = static_cast<int>(GeneratedValue::scalarArity);
};
/**
* @struct generated_residual_block
* @brief Final class for generated residual blocks, derived from residual_block_base. These are blocks that are generated at runtime
* @todo This may be removable at this point
*/
template <typename GeneratedResidual>
struct generated_residual_block final : residual_block_base {
using GeneratedType = GeneratedResidual;
static constexpr int static_block_size = static_cast<int>(GeneratedResidual::scalarArity);
};
/**
* @struct residual_block
* @brief Final class for residual blocks with a fixed index, derived from residual_block_base.
*/
template <int index_value>
struct residual_block final : residual_block_base {
static constexpr int index = index_value;
@@ -35,6 +67,10 @@ namespace serif::discretization::blocks {
}
};
/**
* @struct value_block
* @brief Final class for value blocks with a fixed index, derived from value_block_base.
*/
template <int index_value>
struct value_block final : value_block_base {
static constexpr int index = index_value;

View File

@@ -0,0 +1,7 @@
#pragma once
#include "serif/discretization/blocks/base.hpp"
#include "serif/discretization/blocks/concepts.hpp"
#include "serif/discretization/blocks/fields.hpp"
#include "serif/discretization/blocks/runtime.hpp"
#include "serif/discretization/blocks/traits.hpp"

View File

@@ -1,16 +1,31 @@
#pragma once
/**
* @file concepts.hpp
* @author Emily M. Boudreaux
* @brief This file defines concepts for blocks in the discretization framework.
*/
#include "serif/discretization/blocks/fields.hpp"
namespace serif::discretization::blocks {
/**
* @brief Concept for field types in the discretization framework. To be a field a type must derive from the field base class.
*/
template <typename FieldCandidate>
concept Field = std::derived_from<FieldCandidate, field>;
/**
* @brief Concept for term types in the discretization framework. To be a term a type must derive from term_base, must have a field_type member, and that field_type member must be a valid field.
*/
template <typename T>
concept Term = std::derived_from<std::remove_cvref_t<T>, term_base> && // a term must derive from term_base
requires {typename std::remove_cvref_t<T>::field_type;} && // a term must have a field_type member
Field<typename std::remove_cvref_t<T>::field_type>; // That field type member must be a valid field
/**
* @brief Concept for term types that have a functional space. To have a functional space a type must first be a valid @ref serif::discretization::blocks::Term "Term" and must have a FunctionalSpace member type.
*/
template <typename T>
concept HasFunctionalSpace =
Term<T> &&
@@ -18,6 +33,9 @@ namespace serif::discretization::blocks {
typename std::remove_cvref_t<T>::FunctionalSpace;
};
/**
* @brief Concept for term types that have a domain over which they are supported. To have support a type must first be a valid @ref serif::discretization::blocks::Term "Term" and must have a Support member type.
*/
template <typename T>
concept HasSupport =
Term<T> &&
@@ -25,6 +43,9 @@ namespace serif::discretization::blocks {
typename std::remove_cvref_t<T>::Support;
};
/**
* @brief Concept for term types that have a physical quantity associated with them. To have a quantity a type must first be a valid @ref serif::discretization::blocks::Term "Term" and must have a Quantity member type.
*/
template <typename T>
concept HasQuantity =
Term<T> &&
@@ -32,6 +53,9 @@ namespace serif::discretization::blocks {
typename std::remove_cvref_t<T>::Quantity;
};
/**
* @brief Concept for term types that have a value block associated with them. To have a value block a type must first be a valid @ref serif::discretization::blocks::Term "Term" and must have a value member type.
*/
template <typename T>
concept HasValueBlock =
Term<T> &&
@@ -39,6 +63,9 @@ namespace serif::discretization::blocks {
typename std::remove_cvref_t<T>::value;
};
/**
* @brief Concept for term types that have a residual block associated with them. To have a residual block a type must first be a valid @ref serif::discretization::blocks::Term "Term" and must have a residual member type.
*/
template <typename T>
concept HasResidualBlock =
Term<T> &&
@@ -46,20 +73,47 @@ namespace serif::discretization::blocks {
typename std::remove_cvref_t<T>::residual;
};
/**
* @brief Concept for term types that are spatial terms. A spatial term is a term that has a functional space (@ref serif::discretization::blocks::HasFunctionalSpace "HasFunctionalSpace") and a support (@ref serif::discretization::blocks::HasSupport "HasSupport").
*/
template <typename T>
concept SpatialTerm =
Term<T> &&
HasFunctionalSpace<T> &&
HasSupport<T>;
/**
* @brief Concept for term types that are global scalar terms. A global scalar term is a term that has support (@ref serif::discretization::blocks::HasSupport "HasSupport") but does not have a functional space (@ref serif::discretization::blocks::HasFunctionalSpace "HasFunctionalSpace") and is derived from the global_scalar class.
*/
template <typename T>
concept GlobalScalarTerm = Term<T> && HasSupport<T> && !HasFunctionalSpace<T> && std::derived_from<std::remove_cvref_t<T>, global_scalar>;
/**
* @brief Concept for a physical spatial term. A physical spatial term is a spatial term (@ref serif::discretization::blocks::SpatialTerm "SpatialTerm") that has a physical quantity associated with it (@ref serif::discretization::blocks::HasQuantity "HasQuantity").
*/
template <typename T>
concept PhysicalSpatialTerm =
SpatialTerm<T> &&
HasQuantity<T>;
/**
* @brief Helper struct to determine if a type is a spatial term (@ref serif::discretization::blocks::SpatialTerm "SpatialTerm").
* @tparam T The type to check.
*/
template <typename T>
struct IsSpatialTerm : std::bool_constant<SpatialTerm<T>> {};
/**
* @brief Helper struct to determine if a type is a global scalar term (@ref serif::discretization::blocks::GlobalScalarTerm "GlobalScalarTerm").
* @tparam T The type to check.
*/
template <typename T>
struct IsGlobalScalarTerm : std::bool_constant<GlobalScalarTerm<T>> {};
/**
* @brief Helper struct to determine if a type is a physical spatial term (@ref serif::discretization::blocks::PhysicalSpatialTerm "PhysicalSpatialTerm").
* @tparam T The type to check.
*/
template <typename T>
struct IsPhysicalSpatialTerm : std::bool_constant<PhysicalSpatialTerm<T>> {};
}

View File

@@ -1,4 +1,20 @@
#pragma once
/**
* @file fields.hpp
* @author Emily M. Boudreaux
* @brief Define the fields and their associated terms for the discretization. This is a key file to understand if you are to understand SERiF's numerics.
* Each of these field types should contain all the information needed for the compiler to generate the correct
* - blocks
* - functional spaces
* - support domains
* - quantities
* - symbols
* - and any other information needed to generate the correct weak form for the field
* Broadly you can think of these as where we define the "physics" that we want to solve for or the unknowns of the system.
* If you find yourself adding any sort of unknown to the system and you are not adding it to this file you should step back and reconsider\
* if that belongs here instead.
*/
#include <string_view>
#include "serif/dimensions/dimensions.hpp"
@@ -11,120 +27,278 @@
#include "serif/meta/names.hpp"
namespace serif::discretization::blocks {
/**
* @brief The field struct is a tag type that is used to identify a field in the discretization. It is used to group together terms that belong to the same field. Each field should have a unique name and a list of terms that belong to it.
*/
struct field {};
/**
* @brief The term_base struct is a tag type that is used to identify a term in the discretization. It is used to group together terms that belong to the same field. Each term should have a unique name and a list of blocks that belong to it.
* Generally you should always use term rather than term_base as term associates to a particular field at compile time.
*/
struct term_base {};
/**
* @brief The term struct is a tag type that is used to identify a term in the discretization. It is used to group together terms that belong to the same field. Each term should have a unique name and a list of blocks that belong to it.
* Note that generally you should always use term rather than term_base as term associates to a particular field at compile time.
*/
template <typename FieldT>
struct term : term_base {
using field_type = FieldT;
};
struct density final : field {
static constexpr std::string_view name = meta::type_name<density>();
/**
* @brief A global scalar is a term that has a single value over the entire domain (think angular momentum or total mass). Broadly we need to separate
* these from the other terms since they require different (often less enriched) information at compile time.
*/
struct global_scalar {
/**
* @brief The value block is a block that contains the value of the global scalar. It is a single value over the entire domain and is not associated with any particular functional space.
* For something like total mass this would be the actual value of the mass of the system
*/
struct value final : value_block_base {
static constexpr int static_block_size = 1; ///< Because this is a global scalar unknown we can be sure at compile time that the block size is 1 and we can use this to optimize the linear algebra operations on this block.
};
struct mass final : term<density> { // Note the reading here is mass density (that is this is not in reference to the common FEM usage of mass as a term in a weak form)
/**
* @brief The residual block is a block that contains the residual of the global scalar. It is a single value over the entire domain and is not associated with any particular functional space.
* For something like total mass this would be the residual of the mass of the system (i.e. the difference between the current mass and the target mass).
*/
struct residual final : residual_block_base {
static constexpr int static_block_size = 1; ///< Because this is a global scalar unknown we can be sure at compile time that the block size is 1 and we can use this to optimize the linear algebra operations on this block.
};
};
/**
* @brief The barotropic_constant field is a field that represents the barotropic constant of the system. It is a global scalar that is associated with the mass normalization term. This is used to enforce the mass normalization condition in the weak form when
* we are in the barotropic case. This field will not generalize as useful to the non-barotropic case. Note that we use compile time reflection to get the name of the field and the terms.
*/
struct barotropic_constant final : field {
static constexpr std::string_view name = meta::type_name<barotropic_constant>(); ///< Compile time reflected name of the field
/**
* @brief the mass_normalization term represents the mass normalization condition of the system. That is that it enforces that the total mass of the system is equal to the target mass in a weak sense.
* This is both a term and a global scalar.
*/
struct mass_normalization final : term<barotropic_constant>, global_scalar {
static constexpr std::string_view name = meta::type_name<mass_normalization>(); ///< Compile time reflected name of the term
using Support = domain::StellarDomains; ///< The support of the mass normalization term is the stellar domains since we only want to enforce the mass normalization condition in the stellar domains.
using Quantity = dimensions::SpecificEnthalpy; ///< As we are in teh barotropic case the mass normalization term is associated with the specific enthalpy. This will nto generalize to the non-barotropic case.
static constexpr std::string_view symbol = "C"; ///< Compile time symbol used for the term
};
static constexpr mass_normalization mass_normalization_term{}; ///< Static constexpr instance of the mass_normalization term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<mass_normalization>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
/**
* @brief The rotational field, represented as the angular velocity term, given that we currently only model rigid rotation this is a global scalar. A future extension which models differential rotation will
* likely need to modify this to some vector field or other more complex representation of the rotation.
*/
struct rotation final : field {
static constexpr std::string_view name = meta::type_name<rotation>(); ///< Compile time reflected name of the field
/**
* @brief The angular_velocity term represents the angular velocity of the system. It is a global scalar that is associated with the rotation of the system. This is used to enforce the rotation condition in the weak form when we are in the rigid rotation case.
* Note that downstream the way that this currently works is to calculate the expected angular velocity given the current mass, geometry, density, and angular momentum and then prescribe, given those, what the angular velocity should be.
* Therefore, this does not enforce that the angular velocity must be constant over the entire solver run, but rather that the angular velocity is consistent with the current state of the system.
*/
struct angular_velocity final : term<rotation>, global_scalar {
static constexpr std::string_view name = meta::type_name<angular_velocity>(); ///< Compile time reflected name of the term
using Support = domain::StellarDomains; ///< The support of the angular velocity term is the stellar domains since we only want to enforce the rotation condition in the stellar domains.
using Quantity = dimensions::AngularVelocity; ///< As we are in the rigid rotation case the angular velocity term is associated with the angular velocity. This will not generalize to the differential rotation case.
static constexpr std::string_view symbol = "Ω"; ///< Compile time symbol used for the term
};
static constexpr angular_velocity angular_velocity_term{}; ///< Static constexpr instance of the angular_velocity term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<angular_velocity>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
/**
* @brief field used to inject a border row used to enforce a central density constraint
*/
struct central_density final : field {
static constexpr std::string_view name = meta::type_name<central_density>(); ///< Compile time reflected name of the field
/**
* @brief The border term represents the border row used to enforce a central density constraint. This is a global scalar as it is a single value over the entire domain.
*/
struct border final : term<central_density>, global_scalar {
static constexpr std::string_view name = meta::type_name<border>(); ///< Compile time reflected name of the term
using Support = domain::StellarDomains; ///< We only enforce this over the stellar domain as there is only material to "slosh" around in the stellar domain
using Quantity = dimensions::SpecificEnthalpy; ///< As we are in the barotropic case this border term is associate with the specific enthalpy.
static constexpr std::string_view symbol = "ρ_c"; ///< Compile time symbol used for the term
};
static constexpr border border_term{}; ///< Static constexpr instance of the border term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<border>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
/**
* @brief The density field used to represent the distribution of mass in the system. This is a scalar field that is associated with the mass density of the system. The density field is used to enforce the mass conservation condition in the weak form.
*/
struct density final : field {
static constexpr std::string_view name = meta::type_name<density>(); ///< Compile time reflected name of the field
/**
* @brief The mass density term for the system. Note that the naming here could be a bit confusing as FEM also uses the term "mass" to represent the canonical mass matrix. This is a more standard physics reading of what is going on here.
* We define that this space is a second order scalar L2 space. This allows for the possibility of discontinuous density fields.
*/
struct mass final : term<density> {
static constexpr std::string_view name = meta::type_name<mass>();
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::L2, 2, 0>; // second order scalar L2 space
using Support = domain::StellarDomains;
using Quantity = dimensions::MassDensity;
static constexpr std::string_view symbol = "ρ";
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::L2, 2, 0>; ///< second order scalar L2 space. Read the template parameters as follows: 1) L2 space, 2) second order, 3) scalar field (rank 0)
using Support = domain::StellarDomains; ///< The support of the mass density term is the stellar domains since we do not allow material to exist in the vacuum domain.
using Quantity = dimensions::MassDensity; ///< The quantity of the mass density term is the mass density.
static constexpr std::string_view symbol = "ρ"; ///< Compile time symbol used for the term. Note that this is a UTF-8 character and may not render correctly in all environments. (TODO: Should we introduce an ASCII fallback?, something like make this a tuple of form <main, fallback>?)
struct value final : value_block_base {};
struct residual final : residual_block_base {};
struct value final : value_block_base {}; ///< The value block is a block that contains the value of the mass density term. It is a scalar field that is associated with the mass density of the system.
struct residual final : residual_block_base {}; ///< The residual block is a block that contains the residual of the mass density term. It is a scalar field that is associated with the mass density of the system.
};
static inline constexpr mass mass_density_term{};
using Terms = utils::types::TypeList<mass>;
static constexpr mass mass_density_term{}; ///< Static constexpr instance of the mass term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<mass>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
/**
* @brief The displacement field is a 3 vector H1 field (C0 continuous) that is used to represent continuous nodal displacement over the entire domain.
*/
struct displacement final : field {
static constexpr std::string_view name = meta::type_name<displacement>();
static constexpr std::string_view name = meta::type_name<displacement>(); ///< Compile time reflected name of the field
struct vector final : term<displacement> { // Similarly this is the displacement vector
static constexpr std::string_view name = meta::type_name<vector>();
/**
* @brief the displacement vector term is a 3 vector H1 field (C0 continuous) that is used to represent continuous nodal displacement over the entire domain.
* This is a vector field that is associated with the mapping of the domain from the reference to physical spaces. E.g. if there is some domain
* O_ref which n nodal positions x^i_ref then there is some other domain O_phys with n nodel positions such that x^i_phys = x^i_ref + d^i where d^i is the displacement vector at node i.
*
* @note Generally a particular model will not actually model its geometry with the full displacement field as an unknown. Rather, the displacement
* system will generally report some surface representation and prescription (say Isobaric with a spherical harmonic expansion) to the solver. So there may be
* say 25 spherical harmonic coefficients for the solver to actually solver for. Those are then analytically mapped to the full displacement field
* using some interior and exterior extension. For more information see the surface and deformation systems.
*/
struct vector final : term<displacement> {
static constexpr std::string_view name = meta::type_name<vector>(); ///< Compile time reflected name of the term
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::H1, 3, 1>; // third order vector H1 space
using Support = domain::AllDomains;
using Quantity = dimensions::Length;
static constexpr std::string_view symbol = "d";
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::H1, 3, 1>; ///< 3 vector H1 space (C0 continuous). Read the template parameters as follows: 1) H1 space, 2) 3 vector, 3) rank 1 tensor field
using Support = domain::AllDomains; ///< We support displacement over the entire domain since we want to be able to map the reference domain to the physical domain over the entire domain. This enables both kelvin compactification but also the smooth transition from the stellar domain to the vacuum domain.
using Quantity = dimensions::Length; ///< We represent displacement as a length quantity
static constexpr std::string_view symbol = "d"; ///< Compile time symbol used for the term.
struct value final : value_block_base {};
struct residual final : residual_block_base {};
struct value final : value_block_base {}; ///< The value block is a block that contains the value of the displacement vector term. It is a vector field that is associated with the mapping of the domain from the reference to physical spaces.
struct residual final : residual_block_base {}; ///< The residual block is a block that contains the residual of the displacement vector term. It is a vector field that is associated with the mapping of the domain from the reference to physical spaces.
};
static inline constexpr vector vector_term{};
using Terms = utils::types::TypeList<vector>;
static constexpr vector vector_term{}; ///< Static constexpr instance of the vector term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<vector>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
struct surface_deformation final : field { // Surface deformation is a more general field that the others, it is not pointwise defined over the domain. Use concepts to constrain this as a non spatial field.
static constexpr std::string_view name = meta::type_name<surface_deformation>();
/**
* @brief The surface corollary field to the full displacement field. This is in some ways more general than the others in so far as it is not pointwise defined over the domain. Rather,
* it is some general field of numbers, divorced from the underlying discrimination, that is used to represent the surface deformation of the system. This may be used, for example
* to represent spherical harmonic coefficients of the surface deformation. For more information see the surface and deformation systems.
*/
struct surface_deformation final : field {
static constexpr std::string_view name = meta::type_name<surface_deformation>(); ///< Compile time reflected name of the field
/**
* @brief The coefficients term represents some set of coefficients associated with whatever surface representation is being used to represent the surface deformation.
* Note how we do not have a functional space, support, or a quantity. This is also where we associate the values with.
*/
struct coefficients final : term<surface_deformation> {
static constexpr std::string_view name = meta::type_name<coefficients>();
struct value final : value_block_base {};
static constexpr std::string_view name = meta::type_name<coefficients>(); ///< Compile time reflected name of the term
struct value final : value_block_base {}; ///< The actual values of the coefficients for whatever surface representation is being used
};
/**
* @brief The shape equilibrium term represents the shape equilibrium condition of the system. This is a term that is associated with the surface deformation field and is used to enforce the shape equilibrium condition in the weak form.
* This is where we associate the residuals with. Note how we do not have a functional space, support, or a quantity. This is because the shape equilibrium condition is a global condition that is not associated with any particular functional space, support, or quantity.
*/
struct shape_equilibrium final : term<surface_deformation> {
static constexpr std::string_view name = meta::type_name<shape_equilibrium>();
struct residual final : residual_block_base {};
static constexpr std::string_view name = meta::type_name<shape_equilibrium>(); ///< Compile time reflected name of the term
struct residual final : residual_block_base {}; ///< The residuals of the shape equilibrium condition.
};
static inline constexpr coefficients coefficients_term{};
static inline constexpr shape_equilibrium shape_equilibrium_term{};
using Terms = utils::types::TypeList<coefficients, shape_equilibrium>;
static constexpr coefficients coefficients_term{}; ///< Static constexpr instance of the coefficients term. This is used to access the term in the discretization.
static constexpr shape_equilibrium shape_equilibrium_term{}; ///< Static constexpr instance of the shape_equilibrium term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<coefficients, shape_equilibrium>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
/**
* @brief The gravity field is represented as a mixed system, in many ways this field is the motivation for the entire design of this file. Since it is a
* mixed system we have two separate terms that make it up, the potential and the gradient. These are not derived from one and other, rather they are
* weakly enforced to agree. This buys us an additional order of convergence in the potential which in tern allows us to use a lower resolution mesh.
*/
struct gravity final : field {
static constexpr std::string_view name = meta::type_name<gravity>();
static constexpr std::string_view name = meta::type_name<gravity>(); ///< Compile time reflected name of the field
/**
* @brief The gravitational gradient term is a 2nd order Hdiv vector field that is used to represent the negative gravitational acceleration of the system.
* Note that when I started developing this code I made the frankly wacky choice to let this be the true gradient of the potential, which is the negative
* acceleration. Broadly this may make sense mathematically; however, it means that developers must be extra careful to remember the sign convention.
*/
struct gradient final : term<gravity> {
static constexpr std::string_view name = meta::type_name<gradient>();
static constexpr std::string_view name = meta::type_name<gradient>(); ///< Compile time reflected name of the term
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::Hdiv, 2, 1>;
using Support = domain::AllDomains;
using Quantity = dimensions::Acceleration;
static constexpr std::string_view symbol = "∇φ";
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::Hdiv, 2, 1>; ///< 2nd order Hdiv vector field. Read the template parameters as follows: 1) Hdiv space, 2) 2nd order, 3) rank 1 tensor field
using Support = domain::AllDomains; ///< We support gravity on all domains, primarily since the only real boundary condition we have in the general case is that the potential goes to zero at infinity, which is in the vacuum domain.
using Quantity = dimensions::Acceleration; ///< This is a dimension of acceleration, despite the wacky sign convention I adopted.
static constexpr std::string_view symbol = "∇φ"; ///< Compile time symbol used for the term. Note that this is a UTF-8 character and may not render correctly in all environments. (TODO: Should we introduce an ASCII fallback?, something like make this a tuple of form <main, fallback>?)
struct value final : value_block_base {};
struct residual final : residual_block_base {};
struct value final : value_block_base {}; ///< The value block is a block that contains the value of the gravitational gradient term. It is a vector field that is associated with the negative gravitational acceleration of the system.
struct residual final : residual_block_base {}; ///< The residual block is a block that contains the residual of the gravitational gradient term. It is a vector field that is associated with the negative gravitational acceleration of the system.
};
/**
* @brief The gravitational potential term is a 2nd order L2 scalar field that is used to represent the gravitational potential of the system. Note that
* this is not exactly the same as the if you were to take the scalar potential of the gravitational gradient term. These are solved for by the code separately and
* weakly enforced to agree. Generally if you need the most accurate gradient you should use the gradient term and if you need the most accurate
* potential you should use this term (this is because differentiating this term loses an order of accuracy in the differentiation).
*/
struct potential final : term<gravity> {
static constexpr std::string_view name = meta::type_name<potential>();
static constexpr std::string_view name = meta::type_name<potential>(); ///< Compile time reflected name of the term
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::L2, 2, 0>;
using Support = domain::AllDomains;
using Quantity = dimensions::GravitationalPotential;
static constexpr std::string_view symbol = "φ";
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::L2, 2, 0>; ///< 2nd order L2 scalar field. Read the template parameters as follows: 1) L2 space, 2) 2nd order, 3) rank 0 tensor field
using Support = domain::AllDomains; ///< We support gravity on all domains, primarily since the only real boundary condition we have in the general case is that the potential goes to zero at infinity, which is in the vacuum domain.
using Quantity = dimensions::GravitationalPotential; ///< This is a dimension of gravitational potential, which is a scalar field that is associated with the gravitational potential of the system.
static constexpr std::string_view symbol = "φ"; ///< Compile time symbol used for the term. Note that this is a UTF-8 character and may not render correctly in all environments. (TODO: Should we introduce an ASCII fallback?, something like make this a tuple of form <main, fallback>?)
struct value final : value_block_base {};
struct residual final : residual_block_base {};
struct value final : value_block_base {}; ///< The value block is a block that contains the value of the gravitational potential term. It is a scalar field that is associated with the gravitational potential of the system.
struct residual final : residual_block_base {}; ///< The residual block is a block that contains the residual of the gravitational potential term. It is a scalar field that is associated with the gravitational potential of the system.
};
static inline constexpr potential potential_term{};
static inline constexpr gradient gradient_term{};
static constexpr potential potential_term{}; ///< Static constexpr instance of the potential term. This is used to access the term in the discretization.
static constexpr gradient gradient_term{}; ///< Static constexpr instance of the gradient term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<potential, gradient>;
using Terms = utils::types::TypeList<potential, gradient>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
/**
* @brief The enthalpy field is a scalar field is a C0 continuous scalar field. Note that this field likely will not survive the transition to the more general
* non-barotropic case. In the barotropic case the gradient of enthalpy is parallel to the gradient of the pressure. This make enthalpy a particularly
* useful quantity as we can represent the 0 pressure surface condition as the 0 enthalpy condition and we can break down mass constraint into
* a game of optimizing the Bernoulli constant.
*/
struct enthalpy final : field {
static constexpr std::string_view name = meta::type_name<enthalpy>();
static constexpr std::string_view name = meta::type_name<enthalpy>(); ///< Compile time reflected name of the field
/**
* @brief the specific enthalpy is the term we specifically work with rather than the total enthalpy of dof. In fact heuristically I would
* say that generally we should always work in specific quantities rather than total quantities as there is not a well defined amount of material
* at some dof.
*/
struct specific final : term<enthalpy> {
static constexpr std::string_view name = meta::type_name<specific>();
static constexpr std::string_view name = meta::type_name<specific>(); ///< Compile time reflected name of the term
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::H1, 3, 0>;
using Support = domain::StellarDomains;
using Quantity = dimensions::SpecificEnthalpy;
static constexpr std::string_view symbol = "h";
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::H1, 3, 0>; //< 3 vector H1 space (C0 continuous). Read the template parameters as follows: 1) H1 space, 2) 3rd order, 3) rank 0 tensor field
using Support = domain::StellarDomains; ///< There is only material in the stellar domain therefore we only support the enthalpy field in the stellar domain.
using Quantity = dimensions::SpecificEnthalpy; ///< This has the dimension of specific enthalpy,
static constexpr std::string_view symbol = "h"; ///< Compile time symbol used for the term.
struct value final : value_block_base { };
struct residual final : residual_block_base { };
struct value final : value_block_base { }; ///< The value block is a block that contains the value of the specific enthalpy term. It is a scalar field that is associated with the specific enthalpy of the system.
struct residual final : residual_block_base { }; ///< The residual block is a block that contains the residual of the specific enthalpy term. It is a scalar field that is associated with the specific enthalpy of the system.
};
static inline constexpr specific specific_term{};
static constexpr specific specific_term{}; ///< Static constexpr instance of the specific term. This is used to access the term in the discretization.
using Terms = utils::types::TypeList<specific>;
using Terms = utils::types::TypeList<specific>; ///< Type list of terms for compile time reflection and iteration over the terms of the field.
};
}

View File

@@ -1,24 +1,61 @@
#pragma once
/**
* @file runtime.hpp
* @author Emily M. Boudreaux
*
* @brief Runtime descriptors for fields and terms, used to project compile time types into runtime values.
*/
#include <string_view>
#include "serif/discretization/blocks/concepts.hpp"
namespace serif::discretization::blocks {
struct FieldDescriptor final {
std::string_view name;
/**
* @struct FieldDescriptor
* @brief Runtime descriptor for a field, containing its name. Two FieldDescriptors are equal if they have identical names.
*/
struct FieldDescriptor final {
std::string_view name; ///< The name of the field, used for runtime identification.
/**
* @brief Compare two FieldDescriptors for equality based on their names.
* @param lhs The left-hand side FieldDescriptor.
* @param rhs The right-hand side FieldDescriptor.
* @return True if the names of both FieldDescriptors are equal, false otherwise.
*/
[[nodiscard]] friend constexpr bool operator==(const FieldDescriptor&, const FieldDescriptor&) noexcept = default;
};
/**
* @struct TermDescriptor
* @brief Runtime descriptor for a term, containing its associated field and name. Two TermDescriptors are equal if they have identical fields and names.
*/
struct TermDescriptor final {
FieldDescriptor field;
std::string_view name;
FieldDescriptor field; ///< Each term belongs to a field, and this descriptor identifies that field.
std::string_view name; ///< The name of the term, used for runtime identification.
/**
* @brief Compare two TermDescriptors for equality based on their fields and names.
* @param lhs The left-hand side TermDescriptor.
* @param rhs The right-hand side TermDescriptor.
* @return True if both the fields and names of the TermDescriptors are equal, false otherwise.
*/
[[nodiscard]] friend constexpr bool operator==(const TermDescriptor&, const TermDescriptor&) noexcept = default;
};
/**
* @brief Create a FieldDescriptor for a given field type.
* @tparam FieldT The field type for which to create the descriptor.
*/
template <Field FieldT>
inline constexpr FieldDescriptor field_descriptor{.name = FieldT::name};
/**
* @brief Create a TermDescriptor for a given term type.
* @tparam TermT The term type for which to create the descriptor.
*/
template <Term TermT>
inline constexpr TermDescriptor term_descriptor{
.field = field_descriptor<typename TermT::field_type>,
@@ -26,6 +63,9 @@ namespace serif::discretization::blocks {
};
}
/**
* @brief Specialization of std::formatter for FieldDescriptor and TermDescriptor to enable formatting with std::format.
*/
template <>
struct std::formatter<serif::discretization::blocks::FieldDescriptor> : std::formatter<std::string_view> {
template <typename FormatContext>
@@ -34,6 +74,9 @@ struct std::formatter<serif::discretization::blocks::FieldDescriptor> : std::for
}
};
/**
* @brief Specialization of std::formatter for TermDescriptor to enable formatting with std::format.
*/
template <>
struct std::formatter<serif::discretization::blocks::TermDescriptor> : std::formatter<std::string> {
template <typename FormatContext>

View File

@@ -1,32 +1,70 @@
#pragma once
/**
* @file traits.hpp
* @author Emily M. Boudreaux
* @brief Compile-time traits for fields and terms, used to extract types and properties from template parameters.
*/
#include <type_traits>
#include "serif/discretization/blocks/concepts.hpp"
namespace serif::discretization::blocks {
/**
* @brief Extract the field type associated with a given term type. Will have a type of whatever TermT::field_type is.
* @tparam TermT The term type from which to extract the field type.
*/
template <Term TermT>
using FieldOf = std::remove_cvref_t<TermT>::field_type;
/**
* @brief Extract the functional space type associated with a given term type. Will have a type of whatever TermT::FunctionalSpace is.
* @tparam TermT The term type from which to extract the functional space type.
*
* @todo For consistency with other types consider renaming TermT::FunctionalSpace to TermT::functional_space. This would require updating all term definitions to use the new naming convention. CLion or some other IDE should be able to handle this refactor easily.
*/
template <HasFunctionalSpace TermT>
using FunctionalSpaceOf = std::remove_cvref_t<TermT>::FunctionalSpace;
/**
* @brief Extract the support type associated with a given term type. Will have a type of whatever TermT::Support is.
* @tparam TermT The term type from which to extract the support type.
*
* @todo For consistency with other types consider renaming TermT::Support to TermT::support. This would require updating all term definitions to use the new naming convention. CLion or some other IDE should be able to handle this refactor easily.
*/
template <HasSupport TermT>
using SupportOf = std::remove_cvref_t<TermT>::Support;
/**
* @brief Extract the quantity type associated with a given term type. Will have a type of whatever TermT::Quantity is.
* @tparam TermT The term type from which to extract the quantity type.
*
* @todo For consistency with other types consider renaming TermT::Quantity to TermT::quantity. This would require updating all term definitions to use the new naming convention. CLion or some other IDE should be able to handle this refactor easily.
*/
template <HasQuantity TermT>
using QuantityOf = std::remove_cvref_t<TermT>::Quantity;
/**
* @brief Extract the terms associated with a given field type. Will have a type of whatever FieldT::Terms is.
* @tparam FieldT The field type from which to extract the terms.
*
* @todo For consistency with other types consider renaming FieldT::Terms to FieldT::terms. This would require updating all field definitions to use the new naming convention. CLion or some other IDE should be able to handle this refactor easily.
*/
template <typename FieldT>
using TermsOf = FieldT::Terms;
/**
* @brief Helper struct representing the traits of some spatial term type, including its functional space, support, family, order, and rank.
* @tparam TermT The spatial term type from which to extract the functional space and support types.
*/
template <SpatialTerm TermT>
struct SpatialTermTraits {
using FunctionalSpace = TermT::FunctionalSpace;
using Support = TermT::Support;
using FunctionalSpace = TermT::FunctionalSpace; ///< The functional space type associated with the term. e.g. 2nd order scalar H1 space
using Support = TermT::Support; ///< The support type associated with the term. e.g. stellar domain
static constexpr auto family = FunctionalSpace::functionalSpace.family;
static constexpr int order = FunctionalSpace::order;
static constexpr std::size_t rank = FunctionalSpace::rank;
static constexpr auto family = FunctionalSpace::functional_space.family; ///< The family of the functional space associated with the term. e.g. H1, L2, etc.
static constexpr int order = FunctionalSpace::order; ///< The order of the functional space associated with the term. e.g. 1st order, 2nd order, etc.
static constexpr std::size_t rank = FunctionalSpace::rank::value; ///< The rank of the functional space associated with the term.
};
}