From 36585a9682480a009deef3d8bc48f873e28a762e Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Mon, 5 Oct 2026 13:22:51 -0400 Subject: [PATCH] docs(discretization/domain): added doxygen comments throughout --- .../serif/discretization/domain/concepts.hpp | 93 +++++++++++++--- .../domain/physical_domains.hpp | 20 +++- .../serif/discretization/domain/resolver.hpp | 82 +++++++++++--- .../discretization/domain/schema/concepts.hpp | 11 ++ .../domain/schema/domain_schema.hpp | 103 ++++++++++++++++-- .../discretization/domain/schema/schemas.hpp | 51 ++++++++- .../discretization/domain/schema/traits.hpp | 17 +++ .../discretization/domain/schema/utils.hpp | 22 +++- .../domain/schema/validation/all.hpp | 27 ++++- .../domain/schema/validation/boundary.hpp | 37 +++++-- .../domain/schema/validation/connected.hpp | 14 +++ .../domain/schema/validation/inscribed.hpp | 15 +++ .../domain/schema/validation/results.hpp | 47 +++++--- .../domain/schema/validation/validator.hpp | 46 +++++--- .../serif/discretization/domain/types.hpp | 42 +++++++ 15 files changed, 537 insertions(+), 90 deletions(-) diff --git a/src/include/serif/discretization/domain/concepts.hpp b/src/include/serif/discretization/domain/concepts.hpp index afe8107d..0cfd8956 100644 --- a/src/include/serif/discretization/domain/concepts.hpp +++ b/src/include/serif/discretization/domain/concepts.hpp @@ -1,4 +1,10 @@ #pragma once +/** + * @file concepts.hpp + * @author Emily M. Boudreaux + * + * @brief File containing concepts for the domain module. These concepts are used to constrain template parameters in the domain module. + */ #include #include @@ -12,76 +18,133 @@ #include "serif/utils/misc/std_helper/variadic_cleaning.hpp" namespace serif::discretization::domain { - using utils::misc::std_helper::AIsBaseClassOfB; - using utils::misc::std_helper::CountVariadicArguments; + using utils::misc::std_helper::AIsBaseClassOfB; ///< @todo remove this and just use the raw std::is_base_of_v + using utils::misc::std_helper::CountVariadicArguments; ///< @todo remove this and just use the raw sizeof...(Args) + /** + * @brief A Domain is a volume in the mesh. It is a base class for all domain types. + */ template concept IsDomain = AIsBaseClassOfB; + /** + * @brief A Boundary is a surface in the mesh. It is a base class for all boundary types. + */ template concept IsBoundary = AIsBaseClassOfB; - // A DomainSet is several volumes treated as one. StellarDomains - // (core + envelope) is a motivating example. + /** + * @brief A DomainSet is a collection of domains. It is used to group multiple domains together for convenience. + * @tparam DomainTs The domain types that make up the set. + */ template struct DomainSet { - static constexpr std::size_t count = CountVariadicArguments(); - - static inline const std::array names{ - DomainTs::name... - }; + static constexpr std::size_t count = CountVariadicArguments(); ///< The number of domains in the set. + static inline const std::array names{DomainTs::name...}; ///< The names of the domains in the set. }; - // Do not make the constructors of this class explicit. We want to be able to pass a single domain or a domain set to functions that take a DomainOrSet. + /** + * @brief A DomainOrSet is a type that can represent either a single domain or a set of domains. + * + * @note Do not make the constructors of this class explicit. We want to be able to pass a single domain or a domain set to functions that take a DomainOrSet. + */ class DomainOrSet final { public: + /** + * @brief Construct a DomainOrSet from a single domain type. + * @tparam DomainT The domain type to construct from. + */ template // ReSharper disable once CppNonExplicitConvertingConstructor constexpr DomainOrSet(DomainT) noexcept : m_domains(&DomainT::name, 1) {} + /** + * @brief Construct a DomainOrSet from a DomainSet type. + * @tparam DomainTs The domain types that make up the set. + */ template // ReSharper disable once CppNonExplicitConvertingConstructor constexpr DomainOrSet(DomainSet) noexcept : m_domains(DomainSet::names) {} + /** + * @brief Get the names of the domains in this DomainOrSet. + * @return A span of string_views containing the names of the domains. + */ [[nodiscard]] constexpr std::span domains() const noexcept { return m_domains; } private: - std::span m_domains; + std::span m_domains; ///< The names of the domains in this DomainOrSet. }; + /** + * @brief A BoundarySet is a collection of boundaries. It is used to group multiple boundaries together for convenience. + * @tparam BoundaryTs The boundary types that make up the set. + */ template struct BoundarySet { - static constexpr std::size_t count = CountVariadicArguments(); + static constexpr std::size_t count = CountVariadicArguments(); ///< The number of boundaries in the set. }; + /** + * @brief A trait to determine if a type is a DomainSet. This is the default false specialization + * @tparam T Candidate type to check. + */ template constexpr bool is_domain_set_v = false; + /** + * @brief A trait to determine if a type is a DomainSet. This is the true specialization for DomainSet types. + * @tparam DomainTs The domain types that make up the set. + */ template constexpr bool is_domain_set_v> = true; + /** + * @brief A concept to determine if a type is a DomainSet. A type is a DomainSet if it satisfies the @ref is_domain_set_v trait. + * @tparam T Candidate type to check. + */ template concept IsDomainSet = is_domain_set_v; + /** + * @brief A concept to determine if a type is a DomainOrSet. A type is a DomainOrSet if it is either a Domain or a DomainSet. + * @tparam T Candidate type to check. + */ template concept IsDomainOrSet = IsDomain || IsDomainSet; + /** + * @brief A list of DomainOrSet types. + * @tparam DomainTs The DomainOrSet types that make up the list. + */ template struct DomainOrSetList { - static constexpr std::size_t count = CountVariadicArguments(); + static constexpr std::size_t count = CountVariadicArguments(); ///< The number of DomainOrSet types in the list. }; - // "This volume is the one tagged by that DomainID" / "this surface is the one - // tagged by that BoundaryID". Used by the resolvers in resolver.hpp. + + /** + * @brief A concept to determine if a Domain type is identified by a DomainID type. A Domain type is identified by a DomainID type if the DomainID's domain_type is the same as the Domain type. + * @tparam DomainT The Domain type to check. + * @tparam DomainIDT The DomainID type to check. + */ template concept DomainIsIdentifiedBy = utils::misc::std_helper::SameType; + /** + * @brief A concept to determine if a Boundary type is identified by a BoundaryID type. A Boundary type is identified by a BoundaryID type if the BoundaryID's boundary_type is the same as the Boundary type. + * @tparam BoundaryT The Boundary type to check. + * @tparam BoundaryIDT The BoundaryID type to check. + */ template concept BoundaryIsIdentifiedBy = utils::misc::std_helper::SameType; } +/** + * @brief Specialization of std::formatter for DomainOrSet. This allows us to use std::format to format DomainOrSet objects. + */ template <> struct std::formatter : std::formatter { template diff --git a/src/include/serif/discretization/domain/physical_domains.hpp b/src/include/serif/discretization/domain/physical_domains.hpp index ab6354a2..41611fd3 100644 --- a/src/include/serif/discretization/domain/physical_domains.hpp +++ b/src/include/serif/discretization/domain/physical_domains.hpp @@ -1,11 +1,23 @@ #pragma once +/** + * @file physical_domains.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the physical domain types for SERiF. These are domain sets that are more useful physically since they group + * together computationally separate domains that may have no physically meaningful distinction. For example, the core and envelope domains are computationally distinct but physically they are both stellar domains. + * + * @note the core and envelope domains do not necessarily correspond to the physical core and envelope of a star. Rather, this is terminology used + * to describe the two regions where we orient the mesh differently. Fundamentally we cannot reasonably continue a spherical mesh to + * the center of a stellar model due to the r=0 singularity. Therefore, the mesh generation program we use (stroid) generates + * a cartisan mesh embedded within a spherical mesh (there is a bit more to the topology to ensure we have a nicely structured mesh, see + * stroid for more details). It is this inner cartesian mesh (along with the transition region) that we call the core. The user may need to make choices about + * where that transition region occurs in their stellar model. + */ #include "serif/discretization/domain/types.hpp" #include "serif/discretization/domain/concepts.hpp" namespace serif::discretization::domain { - // Sets are named in the plural to distinguish them from the single volumes - // in types.hpp. - using StellarDomains = DomainSet; - using AllDomains = DomainSet; + using StellarDomains = DomainSet; ///< The set of domains that make up the stellar region of the mesh. + using AllDomains = DomainSet; ///< The set of all domains in the mesh. } diff --git a/src/include/serif/discretization/domain/resolver.hpp b/src/include/serif/discretization/domain/resolver.hpp index 118d20ed..e8762678 100644 --- a/src/include/serif/discretization/domain/resolver.hpp +++ b/src/include/serif/discretization/domain/resolver.hpp @@ -1,5 +1,12 @@ #pragma once +/** + * @file resolver.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the DomainIDResolver and BoundaryIDResolver structs. These structs are used to resolve the domain and boundary IDs for a given domain or boundary type. + */ + #include "serif/discretization/domain/concepts.hpp" #include "serif/discretization/domain/ids/lists/concepts.hpp" #include "serif/discretization/domain/ids/lists/lists.hpp" @@ -25,15 +32,28 @@ namespace serif::discretization::domain { // 2. registered therefore asks if all of the domains in the set are registered with the ID list by folding over all domains and then asking the single domain resolver if that domain is registered with the ID list. // 3. contains_id does basically the same thing as registered, but instead of asking if the domain is registered it asks if the domain contains the ID that is passed in. - // A specialization to resolve the element attribute for a single Domain + /** + * @brief A specialization of the DomainIDResolver struct for a single Domain type. This struct is used to resolve the domain ID for a given Domain type. + * @tparam DomainT The Domain type to resolve the ID for. + * @tparam DomainIDTs The list of DomainID types that are expected in the mesh. + */ template struct DomainIDResolver> { - static constexpr bool registered = (DomainIsIdentifiedBy || ...); + static constexpr bool registered = (DomainIsIdentifiedBy || ...); ///< True if the Domain type is registered with at least one of the DomainID types in the list, false otherwise. + /** + * @brief Check if the specified domain ID belongs to the given Domain type. + * @param domainID The domain ID to check. + * @return True if the domain ID belongs to the specified Domain type, false otherwise. + */ [[nodiscard]] static constexpr bool contains_id(const int domainID) noexcept { return ((DomainIsIdentifiedBy && DomainIDTs::ID == domainID) || ...); } + /** + * @brief Get the ID for the specified Domain type. + * @return The ID for the specified Domain type. + */ [[nodiscard]] static consteval int id() { static_assert(registered, "Requested domain is not registered in this schema."); return ((DomainIsIdentifiedBy ? DomainIDTs::ID : 0) + ...); @@ -41,52 +61,88 @@ namespace serif::discretization::domain { }; // This specialization resolves the element attributes for a set of Domains + + /** + * @brief A specialization of the DomainIDResolver struct for a DomainSet type. This struct is used to resolve the domain IDs for a given DomainSet type. + * @tparam DomainTs The Domain types that make up the set. + * @tparam DomainIDTs The list of DomainID types that are expected in the mesh. + */ template struct DomainIDResolver, ids::lists::DomainIDList> { template - using ResolverFor = DomainIDResolver>; + using ResolverFor = DomainIDResolver>; ///< A helper alias to resolve the domain ID for a single Domain type in the set. - static constexpr bool registered = (ResolverFor::registered && ...); + static constexpr bool registered = (ResolverFor::registered && ...); ///< True if all of the Domain types in the set are registered with at least one of the DomainID types in the list, false otherwise. + /** + * @brief Check if the specified domain ID belongs to the given DomainSet type. + * @param domainID The domain ID to check. + * @return True if the domain ID belongs to any of the Domain types in the set, false otherwise. + */ [[nodiscard]] static constexpr bool contains_id(const int domainID) noexcept { return (ResolverFor::contains_id(domainID) || ...); } }; + /** + * @brief A specialization of the DomainIDResolver struct for a DomainOrSet type. This struct is used to resolve the domain IDs for a given DomainOrSet type. + * @tparam DomainT The DomainOrSet type to resolve the IDs for. + * @tparam DomainIDTs The list of DomainID types that are expected in the mesh. + */ template struct BoundaryIDResolver; - // MFEM has no pleasant and clear way to - // distinguish the *idea* of a boundary from the integer attribute tagging it. - // We elect to use the vocabulary Boundary for the idea and BoundaryID for the MFEM integer (and - // Domain / DomainID on the volume side), so the two sides read symmetrically. Generally in MFEM these would be called Boundary and BoundaryAttribute. - // Aside from that these follow the same logic as the DomainIDResolver - // specializations above, so I will not repeat the comments here. + /** + * @brief A specialization of the BoundaryIDResolver struct for a single Boundary type. This struct is used to resolve the boundary ID for a given Boundary type. + * @tparam BoundaryT The Boundary type to resolve the ID for. + * @tparam BoundaryIDTs The list of BoundaryID types that are expected in the mesh. + * + * @note MFEM has no pleasant and clear way to distinguish the *idea* of a boundary from the integer attribute tagging it. We elect to use the vocabulary Boundary for the idea and BoundaryID for the MFEM integer (and Domain / DomainID on the volume side), so the two sides read symmetrically. Generally in MFEM these would be called Boundary and BoundaryAttribute. Aside from that these follow the same logic as the DomainIDResolver specializations above, so I will not repeat the comments here. + */ template struct BoundaryIDResolver> { - static constexpr bool registered = (BoundaryIsIdentifiedBy || ...); + static constexpr bool registered = (BoundaryIsIdentifiedBy || ...); ///< True if the Boundary type is registered with at least one of the BoundaryID types in the list, false otherwise. + /** + * @brief Check if the specified boundary ID belongs to the given Boundary type. + * @param boundaryID The boundary ID to check. + * @return True if the boundary ID belongs to the specified Boundary type, false otherwise. + */ [[nodiscard]] static constexpr bool contains_id(const int boundaryID) noexcept { return ((BoundaryIsIdentifiedBy && BoundaryIDTs::ID == boundaryID) || ...); } + /** + * @brief Get the ID for the specified Boundary type. + * @return The ID for the specified Boundary type. + */ [[nodiscard]] static consteval int id() { static_assert(registered, "Requested boundary is not registered in this schema."); return ((BoundaryIsIdentifiedBy ? BoundaryIDTs::ID : 0) + ...); } }; + /** + * @brief A specialization of the BoundaryIDResolver struct for a BoundarySet type. This struct is used to resolve the boundary IDs for a given BoundarySet type. + * @tparam BoundaryTs The Boundary types that make up the set. + * @tparam BoundaryIDTs The list of BoundaryID types that are expected in the mesh. + */ template struct BoundaryIDResolver, ids::lists::BoundaryIDList> { template - using ResolverFor = BoundaryIDResolver>; + using ResolverFor = BoundaryIDResolver>; ///< A helper alias to resolve the boundary ID for a single Boundary type in the set. - static constexpr bool registered = (ResolverFor::registered && ...); + static constexpr bool registered = (ResolverFor::registered && ...); ///< True if all of the Boundary types in the set are registered with at least one of the BoundaryID types in the list, false otherwise. + /** + * @brief Check if the specified boundary ID belongs to the given BoundarySet type. + * @param boundaryID The boundary ID to check. + * @return True if the boundary ID belongs to any of the Boundary types in the set, false otherwise. + */ [[nodiscard]] static constexpr bool contains_id(const int boundaryID) noexcept { return (ResolverFor::contains_id(boundaryID) || ...); } diff --git a/src/include/serif/discretization/domain/schema/concepts.hpp b/src/include/serif/discretization/domain/schema/concepts.hpp index 444f2591..05059097 100644 --- a/src/include/serif/discretization/domain/schema/concepts.hpp +++ b/src/include/serif/discretization/domain/schema/concepts.hpp @@ -1,8 +1,19 @@ #pragma once +/** + * @file concepts.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the concept for a schema, which is a collection of relations that describe the relationships between domains and boundaries in a mesh. + */ + #include "serif/discretization/domain/schema/traits.hpp" namespace serif::discretization::domain::schema { + /** + * @brief Concept for a schema, which is a collection of relations that describe the relationships between domains and boundaries in a mesh. + * Something is a schema if it satisfies the is_schema_v trait. + */ template concept IsSchema = is_schema_v; } diff --git a/src/include/serif/discretization/domain/schema/domain_schema.hpp b/src/include/serif/discretization/domain/schema/domain_schema.hpp index 1c460840..4a7b97ae 100644 --- a/src/include/serif/discretization/domain/schema/domain_schema.hpp +++ b/src/include/serif/discretization/domain/schema/domain_schema.hpp @@ -1,5 +1,19 @@ #pragma once +/** + * @file domain_schema.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the DomainSchema class. The idea here is to prevent a class of bugs wherein the user or developer + * things that their mesh is valid but there is some subtle difference between their mesh and what we expect. DomainSchema lets + * us define the particular traits a mesh must have to be valid (e.g. must have a particular set of domain and boundary IDs, + * those must correspond to a particular set of topological relations). Then the first things that happens when a mesh + * is loaded is that it is validated against this schema. + * + * If you wish to extend the set of relations that can be validated or introduce a new more complex kind of mesh that SERiF + * can use you will likely want to start here or in adjacent files. + */ + #include #include "serif/discretization/domain/concepts.hpp" @@ -16,31 +30,83 @@ namespace serif::discretization::domain::schema { using relation::validation::HaveValidRelationEntities; + /** + * @brief A DomainSchema is a collection of relations that describe the relationships between domains and boundaries in a mesh. + * It is used to validate that a mesh has the expected set of domains and boundaries, and that those domains and boundaries + * have the expected topological relations. + * + * @tparam DomainIDs The list of domain IDs that are expected in the mesh. + * @tparam BoundaryIDs The list of boundary IDs that are expected in the mesh. + * @tparam Relations The list of relations that describe the relationships between the domains and boundaries. + * + * There is an additional requirement on instantiating a DomainSchema, that the relations defined in the third template + * argument only require domain and boundary IDs that are present in the first two template arguments. + * + * + * /** + * @brief A DomainSchema is a collection of relations that describe the relationships between domains and boundaries in a mesh. + * It is used to validate that a mesh has the expected set of domains and boundaries, and that those domains and boundaries + * have the expected topological relations. This is important for ensuring that the mesh is suitable for the intended + * simulation or analysis. There is one primary schema we define for SERiF, that is + * three domains (core, envelope, and vacuum) and two boundaries (stellar surface and infinity surface). See @ref schemas.hpp for + * more information on this schema. + * + * + * @tparam DomainIDs The list of domain IDs that are expected in the mesh. + * @tparam BoundaryIDs The list of boundary IDs that are expected in the mesh. + * @tparam Relations The list of relations that describe the relationships between the domains and boundaries. + */ template requires HaveValidRelationEntities struct DomainSchema { - using domain_ids_type = DomainIDs; - using boundary_ids_type = BoundaryIDs; - using relations_type = Relations; + using domain_ids_type = DomainIDs; ///< The list of domain IDs that are expected in the mesh. + using boundary_ids_type = BoundaryIDs; ///< The list of boundary IDs that are expected in the mesh. + using relations_type = Relations; ///< The list of relations that describe the relationships between the domains and boundaries. - static constexpr std::size_t domain_id_count = DomainIDs::count; - static constexpr std::size_t boundary_id_count = BoundaryIDs::count; - static constexpr std::size_t relation_count = Relations::count; + static constexpr std::size_t domain_id_count = DomainIDs::count; ///< The number of domain IDs that are expected in the mesh. + static constexpr std::size_t boundary_id_count = BoundaryIDs::count; ///< The number of boundary IDs that are expected in the mesh. + static constexpr std::size_t relation_count = Relations::count; ///< The number of relations that describe the relationships between the domains and boundaries. + /** + * @brief Get the descriptors for the domain IDs in this schema. + * @return A constexpr array of DomainIDDescriptor objects describing the domain IDs in this schema. + */ [[nodiscard]] static constexpr auto domain_descriptors() noexcept { return DomainIDs::descriptors(); } + /** + * @brief Get the descriptors for the boundary IDs in this schema. + * @return A constexpr array of BoundaryIDDescriptor objects describing the boundary IDs in this schema. + */ [[nodiscard]] static constexpr auto boundary_descriptors() noexcept { return BoundaryIDs::descriptors(); } + /** + * @brief Get the descriptors for the relations in this schema. + * @return A constexpr array of RelationDescriptor objects describing the relations in this schema. + */ + [[nodiscard]] static constexpr auto relation_descriptors() noexcept { + return Relations::descriptors(); + } + + /** + * @brief Check if the schema contains the specified domain. + * @tparam DomainT The domain type to check. + * @return True if the domain is registered in this schema, false otherwise. + */ template [[nodiscard]] static consteval bool contains_domain() noexcept { return DomainIDResolver::registered; } - // Does an element attribute read from a mesh belong to DomainT? + /** + * @brief Check if the specified domain ID belongs to the given domain type. + * @tparam DomainT The domain type to check. + * @param domainID The domain ID to check. + * @return True if the domain ID belongs to the specified domain type, false otherwise. + */ template [[nodiscard]] static constexpr bool domain_id_belongs_to(const int domainID) noexcept { static_assert(contains_domain(), "DomainT is not registered in this schema."); @@ -48,25 +114,44 @@ namespace serif::discretization::domain::schema { return DomainIDResolver::contains_id(domainID); } - // The single element attribute of a single volume. + /** + * @brief Get the ID for the specified domain type. + * @tparam DomainT The domain type to get the ID for. + * @return The ID for the specified domain type. + */ template [[nodiscard]] static consteval int domain_id() noexcept { static_assert(contains_domain(), "DomainT is not registered in this schema."); return DomainIDResolver::id(); } + /** + * @brief Check if the schema contains the specified boundary. + * @tparam BoundaryT The boundary type to check. + * @return True if the boundary is registered in this schema, false otherwise. + */ template [[nodiscard]] static consteval bool contains_boundary() noexcept { return BoundaryIDResolver::registered; } + /** + * @brief Get the ID for the specified boundary type. + * @tparam BoundaryT The boundary type to get the ID for. + * @return The ID for the specified boundary type. + */ template [[nodiscard]] static consteval int boundary_id() noexcept { static_assert(contains_boundary(), "Requested boundary is not registered in this schema."); return BoundaryIDResolver::id(); } - // Does a boundary attribute read from a mesh belong to BoundaryT? + /** + * @brief Check if the specified boundary ID belongs to the given boundary type. + * @tparam BoundaryT The boundary type to check. + * @param boundaryID The boundary ID to check. + * @return True if the boundary ID belongs to the specified boundary type, false otherwise. + */ template [[nodiscard]] static constexpr bool boundary_id_matches(const int boundaryID) noexcept { static_assert(contains_boundary(), "Requested boundary is not registered in this schema."); diff --git a/src/include/serif/discretization/domain/schema/schemas.hpp b/src/include/serif/discretization/domain/schema/schemas.hpp index fb1d580b..f85b12d8 100644 --- a/src/include/serif/discretization/domain/schema/schemas.hpp +++ b/src/include/serif/discretization/domain/schema/schemas.hpp @@ -1,5 +1,13 @@ #pragma once +/** + * @file schemas.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the primary domain schema for SERiF. This is a three-domain, two-boundary schema that is used to validate + * meshes that are loaded into SERiF. + */ + #include "serif/discretization/domain/ids/boundary.hpp" #include "serif/discretization/domain/ids/domain.hpp" #include "serif/discretization/domain/ids/lists/lists.hpp" @@ -18,8 +26,47 @@ namespace serif::discretization::domain::schema { using relation::lists::RelationList; - // A star (core inside envelope) surrounded by a vacuum region, with a - // tagged stellar surface and a tagged outer surface. + /** + * @brief The primary domain schema for SERiF. This is a three-domain, two-boundary schema that is used to validate + * meshes that are loaded into SERiF. + * + * The three domains are: + * - CoreDomain: The central, cartesian, region of the star. + * - EnvelopeDomain: The outer, spherical, region of the star + * - VacuumDomain: The vacuum region surrounding the star. + * + * The two boundaries are: + * - StellarSurfaceBoundary: The surface of the star, which is the interface between the stellar domains and the vacuum domain. + * - InfinitySurfaceBoundary: The outer edge of the vacuum region, which is the interface between the vacuum domain and the exterior of the mesh. + * + * @note The core and envelope domains do not necessarily correspond to the physical core and envelope of a star. Rather, this is terminology used + * to describe the two regions where we orient the mesh differently. Fundamentally we cannot reasonably continue a spherical mesh to + * the center of a stellar model due to the r=0 singularity. Therefore, the mesh generation program we use (stroid) generates + * a cartisan mesh embedded within a spherical mesh (there is a bit more to the topology to ensure we have a nicely structured mesh, see stroid for more details). + * It is this inner cartesian mesh (along with the transition region) that we call the core. The user may need to make choices about + * where that transition region occurs in their stellar model. + * + * In addition to the three domains and two boundaries, we also impose a series of topological relations between the domains and boundaries. These relations are: + * - FullyConnected: The core domain must be fully connected (i.e there are no disconnected blobs of core domain). + * - FullyConnected: The envelope domain must be fully connected (i.e there are no disconnected blobs of envelope domain). + * - FullyConnected: The vacuum domain must be fully connected (i.e there are no disconnected blobs of vacuum domain). + * - Inscribed: The core domain must be inscribed within the envelope domain (i.e the core domain must be fully contained within the envelope domain). + * - Inscribed: The stellar domains (core and envelope) must be inscribed within the vacuum domain (i.e the stellar domains must be fully contained within the vacuum domain). + * - DomainBoundary: The stellar surface boundary must be the interface between the stellar domains and the vacuum domain. + * - DomainBoundary: The infinity surface boundary must be the interface between the vacuum domain and the exterior of the mesh. + * + * We also specify what domain and boundary IDs are expected in the mesh. The expected domain IDs are: + * - CoreDomain: 1 + * - EnvelopeDomain: 2 + * - VacuumDomain: 3 + * + * The expected boundary IDs are: + * - StellarSurfaceBoundary: 1 + * - InfinitySurfaceBoundary: 2 + * + * This lets us say at compile time that a mesh must have this structure; however, we can only validate that a mesh actually has + * this structure at runtime. + */ using CoreEnvelopeVacuumDomainSchema = DomainSchema< DomainIDList< DomainID, diff --git a/src/include/serif/discretization/domain/schema/traits.hpp b/src/include/serif/discretization/domain/schema/traits.hpp index 1962affc..5a4d6554 100644 --- a/src/include/serif/discretization/domain/schema/traits.hpp +++ b/src/include/serif/discretization/domain/schema/traits.hpp @@ -1,11 +1,28 @@ #pragma once +/** + * @file traits.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the is_schema_v trait. This trait is used to determine if a type is a DomainSchema. + */ + #include "serif/discretization/domain/schema/domain_schema.hpp" namespace serif::discretization::domain::schema { + /** + * @brief Trait to determine if a type is a DomainSchema. This is the default false specialization + * @tparam S The type to check. + */ template constexpr bool is_schema_v = false; + /** + * @brief Trait to determine if a type is a DomainSchema. This is the true specialization for DomainSchema types. + * @tparam DomainIDs The list of domain IDs that are expected in the mesh. + * @tparam BoundaryIDs The list of boundary IDs that are expected in the mesh. + * @tparam Relations The list of relations that describe the relationships between the domains and boundaries. + */ template constexpr bool is_schema_v> = true; } diff --git a/src/include/serif/discretization/domain/schema/utils.hpp b/src/include/serif/discretization/domain/schema/utils.hpp index 91484427..667fccaa 100644 --- a/src/include/serif/discretization/domain/schema/utils.hpp +++ b/src/include/serif/discretization/domain/schema/utils.hpp @@ -1,19 +1,29 @@ #pragma once +/** + * @file utils.hpp + * @author Emily M. Boudreaux + * + * @brief File containing utility functions for working with DomainSchemas. + */ + #include #include "serif/discretization/domain/concepts.hpp" #include "serif/discretization/domain/schema/concepts.hpp" namespace serif::discretization::domain::schema { - // Build the 0/1 marker array MFEM wants when an integrator or an essential - // BC should apply only on part of the mesh. + /** + * @brief Build the 0/1 marker array MFEM wants when an integrator or an essential + * BC should apply only on part of the mesh. + * @tparam DomainT The domain or set type. + * @tparam SchemaT The schema type. + * @param mesh The MFEM mesh. + * @return An MFEM Array with 0/1 markers. + */ template [[nodiscard]] mfem::Array make_attribute_marker(const mfem::Mesh &mesh) { - static_assert( - SchemaT::template contains_domain(), - "Requested marker domain is not completely registered in the supplied DomainSchema." - ); + static_assert(SchemaT::template contains_domain(), "Requested marker domain is not completely registered in the supplied DomainSchema."); mfem::Array marker(mesh.attributes.Max()); for (int domainID = 1; domainID <= marker.Size(); ++domainID) { diff --git a/src/include/serif/discretization/domain/schema/validation/all.hpp b/src/include/serif/discretization/domain/schema/validation/all.hpp index cb21dbd0..81d82852 100644 --- a/src/include/serif/discretization/domain/schema/validation/all.hpp +++ b/src/include/serif/discretization/domain/schema/validation/all.hpp @@ -1,5 +1,12 @@ #pragma once +/** + * @file all.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the overloads of validate_schema which automatically distributes the mesh to the correct validation function based on the schema type + */ + #include #include "serif/discretization/domain/mesh/topology.hpp" @@ -16,15 +23,29 @@ #include "serif/discretization/domain/schema/validation/inscribed.hpp" namespace serif::discretization::domain::schema::validation { - // Validate against connectivity that has already been extracted. Prefer - // this overload when validating several schemas against the same mesh. + /** + * @brief Validate a mesh against a schema. + * + * @tparam SchemaT The schema type to validate against. + * @param topology The mesh topology to validate. + * @return A SchemaValidationResult containing the validation results. + */ template [[nodiscard]] SchemaValidationResult validate_schema(const mesh::MeshTopology &topology) { using RelationsT = typename SchemaT::relations_type; return SchemaRelationValidator::validate(topology); } - // Extract the mesh connectivity once, then check every relation against it. + /** + * @brief Validate a mesh against a schema. + * + * @tparam SchemaT The schema type to validate against. + * @param mfemMesh The MFEM mesh to validate. + * @return A SchemaValidationResult containing the validation results. + * + * @note This overload is provided for convenience. It constructs a MeshTopology from the given MFEM mesh and then calls the other overload of validate_schema. It + * may be more efficient to construct a MeshTopology once and reuse it for multiple validations, rather than constructing it each time. + */ template [[nodiscard]] SchemaValidationResult validate_schema(const mfem::Mesh &mfemMesh) { const mesh::MeshTopology topology{mfemMesh}; diff --git a/src/include/serif/discretization/domain/schema/validation/boundary.hpp b/src/include/serif/discretization/domain/schema/validation/boundary.hpp index bf686b1c..6745e0f8 100644 --- a/src/include/serif/discretization/domain/schema/validation/boundary.hpp +++ b/src/include/serif/discretization/domain/schema/validation/boundary.hpp @@ -1,5 +1,13 @@ #pragma once +/** + * @file boundary.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the specialization of RelationValidator for DomainBoundary relations. This relation + * enforces that a boundary element is adjacent to exactly one or two volume elements. + */ + #include #include #include @@ -11,30 +19,35 @@ #include "serif/discretization/domain/schema/concepts.hpp" #include "serif/discretization/domain/schema/validation/validator.hpp" +#include "serif/utils/error/errors.hpp" + namespace serif::discretization::domain::schema::validation { using relation::DomainBoundary; using relation::validation::RelationValidationResult; using relation::validation::RelationValidationFailure; + /** + * @brief Specialization of RelationValidator for DomainBoundary relations. This relation + * enforces that a boundary element is adjacent to exactly one or two volume elements. + * + * @tparam BoundaryT The boundary type to validate. (e.g. Which boundary to use for this relation) + * @tparam DomainTs The domain types to validate. (e.g. which domains are adjacent to the boundary, this can be a pack of length either 1 or 2) + */ template struct RelationValidator> { template [[nodiscard]] static RelationValidationResult validate(const mesh::MeshTopology &topology) { - static_assert( - sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2, - "DomainBoundary requires exactly one or two domains." - ); - static_assert( - SchemaT::template contains_boundary(), - "DomainBoundary refers to a boundary which is not registered in the supplied DomainSchema." - ); + constexpr auto invalidDomainTSizeEMSG = utils::types::ct_format<"DomainBoundary requires exactly one or two domains, got {}">(sizeof...(DomainTs)); + utils::errors::serif_ensure(); + + constexpr auto invalidBoundaryTEMSG = utils::types::ct_format<"DomainBoundary requires the boundary to be registered in the schema, it is not">(); + utils::errors::serif_ensure(), invalidBoundaryTEMSG>(); + + constexpr auto invalidDomainTEMSG = utils::types::ct_format<"DomainBoundary requires the domain to be registered in the schema, at least one is not">(); + utils::errors::serif_ensure() && ...), invalidDomainTEMSG>(); - static_assert( - (SchemaT::template contains_domain() && ...), - "DomainBoundary refers to a domain which is not completely registered in the supplied DomainSchema." - ); constexpr int expectedBoundaryID = SchemaT::template boundary_id(); diff --git a/src/include/serif/discretization/domain/schema/validation/connected.hpp b/src/include/serif/discretization/domain/schema/validation/connected.hpp index 5ebb068d..6401bb41 100644 --- a/src/include/serif/discretization/domain/schema/validation/connected.hpp +++ b/src/include/serif/discretization/domain/schema/validation/connected.hpp @@ -1,5 +1,13 @@ #pragma once +/** + * @file connected.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the specialization of RelationValidator for FullyConnected relations. This relation + * enforces that a domain is fully connected, meaning that all elements of the domain are reachable from any other element of the domain. + */ + #include #include #include @@ -19,6 +27,12 @@ namespace serif::discretization::domain::schema::validation { using relation::validation::RelationValidationResult; using relation::validation::RelationValidationFailure; + /** + * @brief Specialization of RelationValidator for FullyConnected relations. This relation + * enforces that a domain is fully connected, meaning that all elements of the domain are reachable from any other element of the domain. + * + * @tparam DomainT The domain type to validate. (e.g. Which domain to check for connectivity) + */ template struct RelationValidator> { template diff --git a/src/include/serif/discretization/domain/schema/validation/inscribed.hpp b/src/include/serif/discretization/domain/schema/validation/inscribed.hpp index ab3eebce..bbced2f9 100644 --- a/src/include/serif/discretization/domain/schema/validation/inscribed.hpp +++ b/src/include/serif/discretization/domain/schema/validation/inscribed.hpp @@ -1,5 +1,13 @@ #pragma once +/** + * @file inscribed.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the specialization of RelationValidator for Inscribed relations. This relation + * enforces that an inner domain is inscribed within an outer domain, meaning that every face where the inner domain stops must have the outer domain on the far side. + */ + #include #include "serif/discretization/domain/concepts.hpp" @@ -17,6 +25,13 @@ namespace serif::discretization::domain::schema::validation { using relation::validation::RelationValidationResult; using relation::validation::RelationValidationFailure; + /** + * @brief Specialization of RelationValidator for Inscribed relations. This relation + * enforces that an inner domain is inscribed within an outer domain, meaning that every face where the inner domain stops must have the outer domain on the far side. + * + * @tparam InnerT The inner domain type to validate. + * @tparam OuterT The outer domain type to validate. + */ template struct RelationValidator> { template diff --git a/src/include/serif/discretization/domain/schema/validation/results.hpp b/src/include/serif/discretization/domain/schema/validation/results.hpp index 50725145..062d2d0c 100644 --- a/src/include/serif/discretization/domain/schema/validation/results.hpp +++ b/src/include/serif/discretization/domain/schema/validation/results.hpp @@ -1,5 +1,12 @@ #pragma once +/** + * @file results.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the data structures used for representing the results of validating a schema against a mesh. + */ + #include #include #include @@ -11,28 +18,42 @@ // The aggregate result of validating a whole schema against a mesh namespace serif::discretization::domain::schema::validation { - struct SchemaRelationValidationResult { - std::size_t relationIndex{0}; - std::string_view relationName; - relation::validation::RelationValidationResult result; - [[nodiscard]] bool valid() const noexcept; - [[nodiscard]] explicit operator bool() const noexcept; + /** + * @struct SchemaRelationValidationResult + * + * @brief The result of validating a single relation against a mesh. + */ + struct SchemaRelationValidationResult { + std::size_t relationIndex{0}; ///< The index of the relation in the schema's relation list. + std::string_view relationName; ///< The name of the relation being validated. + relation::validation::RelationValidationResult result; ///< The result of validating the relation against the mesh. + + [[nodiscard]] bool valid() const noexcept; ///< Check if the relation validation was successful. + [[nodiscard]] explicit operator bool() const noexcept; ///< Check if the relation validation was successful (explicit conversion to bool). }; + /** + * @struct SchemaValidationResult + * + * @brief The result of validating a schema against a mesh. + */ struct SchemaValidationResult { - std::vector relationResults; + std::vector relationResults; ///< The results of validating each relation in the schema. - [[nodiscard]] bool valid() const noexcept; - [[nodiscard]] explicit operator bool() const noexcept; - [[nodiscard]] std::size_t relation_count() const noexcept; - [[nodiscard]] std::size_t failed_relation_count() const noexcept; - [[nodiscard]] std::size_t passed_relation_count() const noexcept; - [[nodiscard]] std::optional first_failed_relation_index() const noexcept; + [[nodiscard]] bool valid() const noexcept; ///< Check if all relations in the schema validation were successful. + [[nodiscard]] explicit operator bool() const noexcept; ///< Check if all relations in the schema validation were successful (explicit conversion to bool). + [[nodiscard]] std::size_t relation_count() const noexcept; ///< Get the total number of relations in the schema. + [[nodiscard]] std::size_t failed_relation_count() const noexcept; ///< Get the number of relations that failed validation. + [[nodiscard]] std::size_t passed_relation_count() const noexcept; ///< Get the number of relations that passed validation. + [[nodiscard]] std::optional first_failed_relation_index() const noexcept; ///< Get the index of the first failed relation, if any. }; } +/** + * @brief Specialization of std::formatter for SchemaValidationResult to enable formatting with std::format. + */ template <> struct std::formatter { std::formatter string_formatter; diff --git a/src/include/serif/discretization/domain/schema/validation/validator.hpp b/src/include/serif/discretization/domain/schema/validation/validator.hpp index c957ecf6..c6960470 100644 --- a/src/include/serif/discretization/domain/schema/validation/validator.hpp +++ b/src/include/serif/discretization/domain/schema/validation/validator.hpp @@ -1,5 +1,14 @@ #pragma once +/** + * @file validator.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the primary RelationValidator template, plus the fold that runs one validator per declared relation. + * + * @note Every relation kind is validated by an explicit specialization of RelationValidator, and those specializations live in sibling headers (inscribed.hpp, connected.hpp, boundary.hpp). Under headers, instantiating SchemaRelationValidator in a translation unit that has not yet seen all of those specializations is ill-formed with no diagnostic required. That is why validate_schema lives in all.hpp, which includes this header and every specialization. Include all.hpp, not this header, to validate a mesh. + */ + #include #include "serif/discretization/domain/mesh/topology.hpp" @@ -9,28 +18,39 @@ #include "serif/discretization/domain/schema/concepts.hpp" #include "serif/discretization/domain/schema/validation/results.hpp" -// The primary RelationValidator template, plus the fold that runs one validator -// per declared relation. -// -// IMPORTANT: every relation kind is validated by an explicit specialization of -// RelationValidator, and those specializations live in sibling headers -// (inscribed.hpp, connected.hpp, boundary.hpp). Under headers, instantiating -// SchemaRelationValidator in a translation unit that has not yet seen all of -// those specializations is ill-formed with no diagnostic required. That is why -// validate_schema lives in all.hpp, which includes this header and every -// specialization. Include all.hpp, not this header, to validate a mesh. namespace serif::discretization::domain::schema::validation { using relation::IsRelation; + /** + * @brief Primary template for RelationValidator. Each relation kind is validated by an explicit specialization of this template, which lives in a sibling header (inscribed.hpp, connected.hpp, boundary.hpp). + * + * @tparam RelationT The relation type to validate. + */ template struct RelationValidator; + /** + * @brief Fold that runs one validator per declared relation in a schema. + * + * @tparam SchemaT The schema type to validate. + * @tparam RelationListT The list of relations to validate. + */ template struct SchemaRelationValidator; + /** + * @brief Fold that runs one validator per declared relation in a schema. This specialization is for a RelationList of arbitrary length. + * + * @tparam SchemaT The schema type to validate. + * @tparam RelationTs The list of relations to validate. + */ template - struct SchemaRelationValidator> { - + struct SchemaRelationValidator> { + /** + * @brief Validate a mesh against a schema, running one validator per declared relation in the schema. + * @param topology The mesh topology to validate against the schema. + * @return The result of validating the schema against the mesh topology. + */ [[nodiscard]] static SchemaValidationResult validate(const mesh::MeshTopology &topology) { SchemaValidationResult schemaResult; @@ -41,7 +61,7 @@ namespace serif::discretization::domain::schema::validation { .relationIndex = relationIndex++, .relationName = RelationTs::name, .result = RelationValidator::template validate(topology) - }), ...); + }), ...); // Fold over the parameter pack of relations, running one validator per relation. return schemaResult; } diff --git a/src/include/serif/discretization/domain/types.hpp b/src/include/serif/discretization/domain/types.hpp index 00610fea..55cd5020 100644 --- a/src/include/serif/discretization/domain/types.hpp +++ b/src/include/serif/discretization/domain/types.hpp @@ -1,4 +1,12 @@ #pragma once + +/** + * @file types.hpp + * @author Emily M. Boudreaux + * + * @brief File containing the Domain and Boundary types for SERiF. These types are used to represent the different domains and boundaries in a mesh. + */ + #include // A domain is a volume while a boundary is @@ -6,26 +14,60 @@ // the integer attributes stored in a mesh file lives in ids/ (DomainID and // BoundaryID) and defined by a schema. namespace serif::discretization::domain { + /** + * @brief A Domain is a volume in the mesh. It is a base class for all domain types. + */ struct Domain { }; + /** + * @brief The CoreDomain is the central, cartesian, region of the star. It is a derived class of Domain. + * + * @note This need not, and in fact generally will not, correspond to the physical core of a star. Rather, this is terminology used to describe the two regions where + * we orient the mesh differently. Fundamentally we cannot reasonably continue a spherical mesh to the center of a stellar model due to the r=0 singularity. + * Therefore, the mesh generation program we use (stroid) generates a cartisan mesh embedded within a spherical mesh (there is a bit more to the topology to ensure we + * have a nicely structured mesh, see stroid for more details). It is this inner cartesian mesh (along with the transition region) that we call the core. The user may + * need to make choices about where that transition region occurs in their stellar model. + */ struct CoreDomain final : Domain { static constexpr std::string_view name = "core"; }; + /** + * @brief The EnvelopeDomain is the outer, spherical, region of the star. It is a derived class of Domain. + * + * @note This need not, and in fact generally will not, correspond to the physical envelope of a star. Rather, this is terminology used to describe the two regions where + * we orient the mesh differently. Fundamentally we cannot reasonably continue a spherical mesh to the center of a stellar model due to the r=0 singularity. + * Therefore, the mesh generation program we use (stroid) generates a cartisan mesh embedded within a spherical mesh (there is a bit more to the topology to ensure we + * have a nicely structured mesh, see stroid for more details). It is this inner cartesian mesh (along with the transition region) that we call the core. The user may + * need to make choices about where that transition region occurs in their stellar model. + */ struct EnvelopeDomain final : Domain { static constexpr std::string_view name = "envelope"; }; + /** + * @brief The VacuumDomain is the vacuum region surrounding the star. It is a derived class of Domain. + */ struct VacuumDomain final : Domain { static constexpr std::string_view name = "vacuum"; }; + /** + * @brief A Boundary is a surface in the mesh. It is a base class for all boundary types. + */ struct Boundary { }; + /** + * @brief The StellarSurfaceBoundary is the surface of the star. It is a derived class of Boundary. + */ struct StellarSurfaceBoundary final : Boundary { static constexpr std::string_view name = "stellar_surface"; }; + /** + * @brief The InfinitySurfaceBoundary is the surface at infinity. It is a derived class of Boundary. More generally this is the outer boundary of the vacuum region; + * however, since we really need the boundary conditions to be applied at infinity, we call it the infinity surface. + */ struct InfinitySurfaceBoundary final : Boundary { static constexpr std::string_view name = "infinity_surface"; };