Files
MeanField/libmeanfield/interface/utils/domain.cppm

1129 lines
43 KiB
C++

module;
#include <array>
#include <mfem.hpp>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
export module mean_field:utils.domain;
export namespace mean_field::utils::domain {
struct Domain { };
struct Core final : public Domain {
static constexpr std::string_view name = "core";
};
struct Envelope final : public Domain {
static constexpr std::string_view name = "envelope";
};
struct Vacuum final : public Domain {
static constexpr std::string_view name = "vacuum";
};
struct Boundary { };
struct StellarSurface final : public Boundary {
static constexpr std::string_view name = "stellar_surface";
};
struct InfinitySurface final : public Boundary {
static constexpr std::string_view name = "infinity_surface";
};
template <typename T>
concept IsDomain = std::is_base_of_v<Domain, T>;
template <typename T>
concept IsBoundary = std::is_base_of_v<Boundary, T>;
template <IsDomain... DomainTs> struct DomainSet { };
template <typename T> constexpr bool is_domain_set_v = false;
template <IsDomain... DomainTs> constexpr bool is_domain_set_v<DomainSet<DomainTs...>> = true;
template <typename T>
concept IsDomainSet = is_domain_set_v<T>;
template <typename T>
concept IsDomainOrSet = IsDomain<T> || IsDomainSet<T>;
using Stellar = DomainSet<Core, Envelope>;
using All = DomainSet<Core, Envelope, Vacuum>;
struct DomainRelation { };
template <IsDomainOrSet A, IsDomainOrSet B> struct Inscribed final : public DomainRelation {
using inner_type = A;
using outer_type = B;
static constexpr std::string_view name = "inscribed";
};
template <IsDomainOrSet A> struct Connected final : public DomainRelation {
using domain_type = A;
static constexpr std::string_view name = "connected";
};
template <typename R>
concept IsRelation = std::is_base_of_v<DomainRelation, R>;
template <IsDomain D, int Id> struct Material {
using domain_type = D;
static constexpr int id = Id;
};
template <IsBoundary B, int Id> struct BoundaryAttribute {
using boundary_type = B;
static constexpr int id = Id;
};
template <typename T> constexpr bool is_material_v = false;
template <IsDomain D, int Id> constexpr bool is_material_v<Material<D, Id>> = true;
template <typename T>
concept IsMaterial = is_material_v<T>;
template <typename T> constexpr bool is_boundary_attr_v = false;
template <IsBoundary B, int Id> constexpr bool is_boundary_attr_v<BoundaryAttribute<B, Id>> = true;
template <typename T>
concept IsBoundaryAttr = is_boundary_attr_v<T>;
struct MaterialDescriptor {
std::string_view name;
int id;
};
struct BoundaryDescriptor {
std::string_view name;
int id;
};
template <IsMaterial... MaterialTs>
[[nodiscard]]
consteval bool material_ids_are_unique() noexcept {
constexpr std::array<int, sizeof...(MaterialTs)> materialIds{MaterialTs::id...};
for (std::size_t firstIndex = 0; firstIndex < materialIds.size(); ++firstIndex) {
for (std::size_t secondIndex = firstIndex + 1; secondIndex < materialIds.size(); ++secondIndex) {
if (materialIds[firstIndex] == materialIds[secondIndex]) {
return false;
}
}
}
return true;
}
template <IsBoundaryAttr... BoundaryTs>
[[nodiscard]]
consteval bool boundary_ids_are_unique() noexcept {
constexpr std::array<int, sizeof...(BoundaryTs)> boundaryIds{BoundaryTs::id...};
for (std::size_t firstIndex = 0; firstIndex < boundaryIds.size(); ++firstIndex) {
for (std::size_t secondIndex = firstIndex + 1; secondIndex < boundaryIds.size(); ++secondIndex) {
if (boundaryIds[firstIndex] == boundaryIds[secondIndex]) {
return false;
}
}
}
return true;
}
template <typename... MaterialTs> struct MaterialDomainsAreUnique;
template <> struct MaterialDomainsAreUnique<> : std::true_type { };
template <typename MaterialT> struct MaterialDomainsAreUnique<MaterialT> : std::true_type { };
template <typename FirstMaterialT, typename... RemainingMaterialTs>
struct MaterialDomainsAreUnique<FirstMaterialT, RemainingMaterialTs...>
: std::bool_constant<
(!std::is_same_v<typename FirstMaterialT::domain_type, typename RemainingMaterialTs::domain_type> &&
...) &&
MaterialDomainsAreUnique<RemainingMaterialTs...>::value> { };
template <typename... BoundaryTs> struct BoundaryTypesAreUnique;
template <> struct BoundaryTypesAreUnique<> : std::true_type { };
template <typename BoundaryT> struct BoundaryTypesAreUnique<BoundaryT> : std::true_type { };
template <typename FirstBoundaryT, typename... RemainingBoundaryTs>
struct BoundaryTypesAreUnique<FirstBoundaryT, RemainingBoundaryTs...>
: std::bool_constant<
(!std::is_same_v<typename FirstBoundaryT::boundary_type, typename RemainingBoundaryTs::boundary_type> &&
...) &&
BoundaryTypesAreUnique<RemainingBoundaryTs...>::value> { };
template <typename... MaterialTs>
concept HaveUniqueMaterialIds = material_ids_are_unique<MaterialTs...>();
template <typename... MaterialTs>
concept HaveUniqueMaterialDomains = MaterialDomainsAreUnique<MaterialTs...>::value;
template <typename... BoundaryTs>
concept HaveUniqueBoundaryIds = boundary_ids_are_unique<BoundaryTs...>();
template <typename... BoundaryTs>
concept HaveUniqueBoundaryTypes = BoundaryTypesAreUnique<BoundaryTs...>::value;
template <IsMaterial... MaterialTs>
requires(HaveUniqueMaterialIds<MaterialTs...> && HaveUniqueMaterialDomains<MaterialTs...>)
struct MaterialList {
static constexpr std::size_t count = sizeof...(MaterialTs);
[[nodiscard]]
static constexpr std::array<
MaterialDescriptor,
count> descriptors() noexcept {
return {MaterialDescriptor{.name = MaterialTs::domain_type::name, .id = MaterialTs::id}...};
}
};
template <IsBoundaryAttr... BoundaryTs>
requires(HaveUniqueBoundaryIds<BoundaryTs...> && HaveUniqueBoundaryTypes<BoundaryTs...>)
struct BoundaryList {
static constexpr std::size_t count = sizeof...(BoundaryTs);
[[nodiscard]]
static constexpr std::array<
BoundaryDescriptor,
count> descriptors() noexcept {
return {BoundaryDescriptor{.name = BoundaryTs::boundary_type::name, .id = BoundaryTs::id}...};
}
};
template <typename DomainT, typename MaterialListT> struct DomainMaterialResolver;
template <IsDomain DomainT, IsMaterial... MaterialTs>
struct DomainMaterialResolver<DomainT, MaterialList<MaterialTs...>> {
static constexpr bool registered = (std::is_same_v<DomainT, typename MaterialTs::domain_type> || ...);
[[nodiscard]]
static constexpr bool contains_attribute(int materialId) noexcept {
return ((std::is_same_v<DomainT, typename MaterialTs::domain_type> && MaterialTs::id == materialId) || ...);
}
[[nodiscard]]
static consteval int attribute() {
static_assert(registered, "Requested domain is not registered in this schema.");
int result = 0;
((std::is_same_v<DomainT, typename MaterialTs::domain_type> ? result = MaterialTs::id : result), ...);
return result;
}
};
template <IsDomain... DomainTs, IsMaterial... MaterialTs>
struct DomainMaterialResolver<DomainSet<DomainTs...>, MaterialList<MaterialTs...>> {
static constexpr bool registered =
(DomainMaterialResolver<DomainTs, MaterialList<MaterialTs...>>::registered && ...);
[[nodiscard]]
static constexpr bool contains_attribute(int materialId) noexcept {
return (
DomainMaterialResolver<DomainTs, MaterialList<MaterialTs...>>::contains_attribute(materialId) || ...
);
}
};
template <typename BoundaryT, typename BoundaryListT> struct BoundaryAttributeResolver;
template <IsBoundary BoundaryT, IsBoundaryAttr... BoundaryTs>
struct BoundaryAttributeResolver<BoundaryT, BoundaryList<BoundaryTs...>> {
static constexpr bool registered = (std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> || ...);
[[nodiscard]]
static constexpr bool matches_attribute(int boundaryId) noexcept {
return (
(std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> && BoundaryTs::id == boundaryId) || ...
);
}
[[nodiscard]]
static consteval int attribute() {
static_assert(
registered, "Requested boundary is not registered "
"in this schema."
);
int result = 0;
((std::is_same_v<BoundaryT, typename BoundaryTs::boundary_type> ? result = BoundaryTs::id : result), ...);
return result;
}
};
template <typename T> constexpr bool is_material_list_v = false;
template <IsMaterial... MaterialTs> constexpr bool is_material_list_v<MaterialList<MaterialTs...>> = true;
template <typename T>
concept IsMaterialList = is_material_list_v<T>;
template <typename T> constexpr bool is_boundary_list_v = false;
template <IsBoundaryAttr... BoundaryTs> constexpr bool is_boundary_list_v<BoundaryList<BoundaryTs...>> = true;
template <typename T>
concept IsBoundaryList = is_boundary_list_v<T>;
template <IsDomainOrSet... DomainTs> struct DomainOperandList {
static constexpr std::size_t count = sizeof...(DomainTs);
};
template <IsBoundary BoundaryT, IsDomainOrSet... DomainTs>
requires(sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2)
struct DomainBoundary final : public DomainRelation {
using boundary_type = BoundaryT;
using domains_type = DomainOperandList<DomainTs...>;
static constexpr std::size_t domainCount = sizeof...(DomainTs);
static constexpr std::string_view name = "domain_boundary";
};
template <IsRelation... RelationTs> struct RelationList {
static constexpr std::size_t count = sizeof...(RelationTs);
};
template <typename T> constexpr bool is_relation_list_v = false;
template <IsRelation... RelationTs> constexpr bool is_relation_list_v<RelationList<RelationTs...>> = true;
template <typename T>
concept IsRelationList = is_relation_list_v<T>;
/*
* Compile-time validation that every semantic entity
* referenced by a relation is registered by the schema.
*
* Connected and Inscribed only reference domains.
* DomainBoundary references both a boundary and one or
* two domains.
*/
template <IsRelation RelationT, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities;
template <IsDomainOrSet DomainT, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<Connected<DomainT>, MaterialsT, BoundariesT>
: std::bool_constant<DomainMaterialResolver<DomainT, MaterialsT>::registered> { };
template <IsDomainOrSet InnerT, IsDomainOrSet OuterT, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<Inscribed<InnerT, OuterT>, MaterialsT, BoundariesT>
: std::bool_constant<
DomainMaterialResolver<InnerT, MaterialsT>::registered &&
DomainMaterialResolver<OuterT, MaterialsT>::registered> { };
template <IsBoundary BoundaryT, IsDomainOrSet... DomainTs, IsMaterialList MaterialsT, IsBoundaryList BoundariesT>
struct RelationUsesRegisteredEntities<DomainBoundary<BoundaryT, DomainTs...>, MaterialsT, BoundariesT>
: std::bool_constant<
BoundaryAttributeResolver<BoundaryT, BoundariesT>::registered &&
(DomainMaterialResolver<DomainTs, MaterialsT>::registered && ...)> { };
template <IsMaterialList MaterialsT, IsBoundaryList BoundariesT, IsRelationList RelationsT>
struct RelationsUseRegisteredEntities;
template <IsMaterialList MaterialsT, IsBoundaryList BoundariesT, IsRelation... RelationTs>
struct RelationsUseRegisteredEntities<MaterialsT, BoundariesT, RelationList<RelationTs...>>
: std::bool_constant<(RelationUsesRegisteredEntities<RelationTs, MaterialsT, BoundariesT>::value && ...)> { };
template <typename MaterialsT, typename BoundariesT, typename RelationsT>
concept HaveValidRelationEntities = RelationsUseRegisteredEntities<MaterialsT, BoundariesT, RelationsT>::value;
template <IsMaterialList Materials, IsBoundaryList Boundaries, IsRelationList Relations>
requires HaveValidRelationEntities<Materials, Boundaries, Relations>
struct DomainSchema {
using materials_type = Materials;
using boundaries_type = Boundaries;
using relations_type = Relations;
static constexpr std::size_t materialCount = Materials::count;
static constexpr std::size_t boundaryCount = Boundaries::count;
static constexpr std::size_t relationCount = Relations::count;
[[nodiscard]]
static constexpr auto materials() noexcept {
return Materials::descriptors();
}
[[nodiscard]]
static constexpr auto boundaries() noexcept {
return Boundaries::descriptors();
}
template <IsDomainOrSet DomainT>
[[nodiscard]]
static consteval bool contains_domain() noexcept {
return DomainMaterialResolver<DomainT, Materials>::registered;
}
template <IsDomainOrSet DomainT>
[[nodiscard]]
static constexpr bool attribute_belongs_to(int materialId) noexcept {
static_assert(
contains_domain<DomainT>(), "Requested domain is not completely "
"registered in this schema."
);
return DomainMaterialResolver<DomainT, Materials>::contains_attribute(materialId);
}
template <IsDomain DomainT>
[[nodiscard]]
static consteval int material_attribute() noexcept {
static_assert(contains_domain<DomainT>(), "Requested domain is not registered in this schema.");
return DomainMaterialResolver<DomainT, Materials>::attribute();
}
template <IsBoundary BoundaryT>
[[nodiscard]]
static consteval bool contains_boundary() noexcept {
return BoundaryAttributeResolver<BoundaryT, Boundaries>::registered;
}
template <IsBoundary BoundaryT>
[[nodiscard]]
static consteval int boundary_attribute() noexcept {
return BoundaryAttributeResolver<BoundaryT, Boundaries>::attribute();
}
template <IsBoundary BoundaryT>
[[nodiscard]]
static constexpr bool boundary_attribute_matches(int boundaryId) noexcept {
static_assert(
contains_boundary<BoundaryT>(), "Requested boundary is not registered "
"in this schema."
);
return BoundaryAttributeResolver<BoundaryT, Boundaries>::matches_attribute(boundaryId);
}
};
template <typename S> constexpr bool is_schema_v = false;
template <IsMaterialList Materials, IsBoundaryList Boundaries, IsRelationList Relations>
constexpr bool is_schema_v<DomainSchema<Materials, Boundaries, Relations>> = true;
template <typename T>
concept IsSchema = is_schema_v<T>;
enum class RelationValidationFailure {
None,
// Connected<DomainT>
DomainAbsent,
DomainDisconnected,
// Inscribed<InnerT, OuterT>
InnerDomainAbsent,
OuterDomainAbsent,
InnerDomainHasNoBoundary,
InnerDomainTouchesMeshBoundary,
InnerDomainTouchesUnexpectedMaterial,
// DomainBoundary<BoundaryT, DomainTs...>
DomainBoundaryAbsent,
DomainBoundaryTaggedFaceHasWrongTopology,
DomainBoundaryTaggedFaceTouchesUnexpectedMaterial,
DomainBoundaryExpectedFaceIsUntagged,
DomainBoundaryExpectedFaceHasWrongAttribute
};
struct RelationValidationResult {
RelationValidationFailure failure{RelationValidationFailure::None};
struct InscribedDiagnostics {
int faceId{-1};
int innerElementId{-1};
int adjacentElementId{-1};
int adjacentMaterialId{-1};
};
std::optional<InscribedDiagnostics> inscribedDiagnostics = std::nullopt;
struct ConnectedDiagnostics {
int elementId{-1};
int domainElementCount{0};
int visitedElementCount{0};
};
std::optional<ConnectedDiagnostics> connectedDiagnostics = std::nullopt;
struct DomainBoundaryDiagnostics {
int faceId{-1};
int boundaryElementId{-1};
int expectedBoundaryAttribute{0};
std::optional<int> actualBoundaryAttribute = std::nullopt;
int firstElementId{-1};
int secondElementId{-1};
std::optional<int> firstMaterialId = std::nullopt;
std::optional<int> secondMaterialId = std::nullopt;
};
std::optional<DomainBoundaryDiagnostics> domainBoundaryDiagnostics = std::nullopt;
[[nodiscard]]
bool valid() const noexcept {
return failure == RelationValidationFailure::None;
}
[[nodiscard]]
explicit operator bool() const noexcept {
return valid();
}
};
template <IsRelation RelationT> struct RelationValidator;
template <IsDomainOrSet InnerT, IsDomainOrSet OuterT> struct RelationValidator<Inscribed<InnerT, OuterT>> {
template <IsSchema SchemaT>
[[nodiscard]]
static RelationValidationResult validate(const mfem::Mesh &mesh) {
static_assert(
SchemaT::template contains_domain<InnerT>(), "The inner domain of Inscribed is not "
"registered in the supplied schema."
);
static_assert(
SchemaT::template contains_domain<OuterT>(), "The outer domain of Inscribed is not "
"registered in the supplied schema."
);
bool foundInnerElement = false;
bool foundOuterElement = false;
bool foundInnerBoundary = false;
for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) {
const int materialId = mesh.GetAttribute(elementId);
foundInnerElement = foundInnerElement || SchemaT::template attribute_belongs_to<InnerT>(materialId);
foundOuterElement = foundOuterElement || SchemaT::template attribute_belongs_to<OuterT>(materialId);
}
if (!foundInnerElement) {
return {.failure = RelationValidationFailure::InnerDomainAbsent};
}
if (!foundOuterElement) {
return {.failure = RelationValidationFailure::OuterDomainAbsent};
}
for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) {
int firstElementId = -1;
int secondElementId = -1;
mesh.GetFaceElements(faceId, &firstElementId, &secondElementId);
const bool firstIsInner =
firstElementId >= 0 &&
SchemaT::template attribute_belongs_to<InnerT>(mesh.GetAttribute(firstElementId));
const bool secondIsInner =
secondElementId >= 0 &&
SchemaT::template attribute_belongs_to<InnerT>(mesh.GetAttribute(secondElementId));
if (firstIsInner == secondIsInner) {
continue;
}
foundInnerBoundary = true;
const int innerElementId = firstIsInner ? firstElementId : secondElementId;
const int adjacentElementId = firstIsInner ? secondElementId : firstElementId;
if (adjacentElementId < 0) {
return {
.failure = RelationValidationFailure::InnerDomainTouchesMeshBoundary,
.inscribedDiagnostics = std::make_optional<RelationValidationResult::InscribedDiagnostics>(
{.faceId = faceId, .innerElementId = innerElementId}
)
};
}
const int adjacentMaterialId = mesh.GetAttribute(adjacentElementId);
if (!SchemaT::template attribute_belongs_to<OuterT>(adjacentMaterialId)) {
return {
.failure = RelationValidationFailure::InnerDomainTouchesUnexpectedMaterial,
.inscribedDiagnostics = std::make_optional<RelationValidationResult::InscribedDiagnostics>(
{.faceId = faceId,
.innerElementId = innerElementId,
.adjacentElementId = adjacentElementId,
.adjacentMaterialId = adjacentMaterialId}
)
};
}
}
if (!foundInnerBoundary) {
return {.failure = RelationValidationFailure::InnerDomainHasNoBoundary};
}
return {};
}
};
template <IsDomainOrSet DomainT> struct RelationValidator<Connected<DomainT>> {
template <IsSchema SchemaT>
[[nodiscard]]
static RelationValidationResult validate(const mfem::Mesh &mesh) {
static_assert(
SchemaT::template contains_domain<DomainT>(), "Connected refers to a domain which is "
"not completely registered in the "
"supplied DomainSchema."
);
std::vector<bool> belongsToDomain(static_cast<std::size_t>(mesh.GetNE()), false);
int domainElementCount = 0;
int firstDomainElement = -1;
for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) {
const int materialId = mesh.GetAttribute(elementId);
const bool belongs = SchemaT::template attribute_belongs_to<DomainT>(materialId);
belongsToDomain[static_cast<std::size_t>(elementId)] = belongs;
if (!belongs) {
continue;
}
++domainElementCount;
if (firstDomainElement < 0) {
firstDomainElement = elementId;
}
}
if (domainElementCount == 0) {
return {
.failure = RelationValidationFailure::DomainAbsent,
.connectedDiagnostics = std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
{.domainElementCount = 0, .visitedElementCount = 0}
)
};
}
std::vector<std::vector<int>> adjacency(static_cast<std::size_t>(mesh.GetNE()));
for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) {
int firstElementId = -1;
int secondElementId = -1;
mesh.GetFaceElements(faceId, &firstElementId, &secondElementId);
if (firstElementId < 0 || secondElementId < 0) {
continue;
}
const bool firstBelongs = belongsToDomain[static_cast<std::size_t>(firstElementId)];
const bool secondBelongs = belongsToDomain[static_cast<std::size_t>(secondElementId)];
if (!(firstBelongs && secondBelongs)) {
continue;
}
adjacency[static_cast<std::size_t>(firstElementId)].push_back(secondElementId);
adjacency[static_cast<std::size_t>(secondElementId)].push_back(firstElementId);
}
std::vector<bool> visited(static_cast<std::size_t>(mesh.GetNE()), false);
std::vector<int> pending;
pending.reserve(static_cast<std::size_t>(domainElementCount));
pending.push_back(firstDomainElement);
int visitedElementCount = 0;
while (!pending.empty()) {
const int elementId = pending.back();
pending.pop_back();
if (visited[static_cast<std::size_t>(elementId)]) {
continue;
}
visited[static_cast<std::size_t>(elementId)] = true;
++visitedElementCount;
for (const int neighborElementId : adjacency[static_cast<std::size_t>(elementId)]) {
if (!visited[static_cast<std::size_t>(neighborElementId)]) {
pending.push_back(neighborElementId);
}
}
}
if (visitedElementCount == domainElementCount) {
return {
.connectedDiagnostics = std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
{.domainElementCount = domainElementCount, .visitedElementCount = visitedElementCount}
)
};
}
int disconnectedElementId = -1;
for (int elementId = 0; elementId < mesh.GetNE(); ++elementId) {
const std::size_t index = static_cast<std::size_t>(elementId);
if (belongsToDomain[index] && !visited[index]) {
disconnectedElementId = elementId;
break;
}
}
return {
.failure = RelationValidationFailure::DomainDisconnected,
.connectedDiagnostics = std::make_optional<RelationValidationResult::ConnectedDiagnostics>(
{.elementId = disconnectedElementId,
.domainElementCount = domainElementCount,
.visitedElementCount = visitedElementCount}
)
};
}
};
template <IsBoundary BoundaryT, IsDomainOrSet... DomainTs>
struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
template <IsSchema SchemaT>
[[nodiscard]]
static RelationValidationResult validate(const mfem::Mesh &mesh) {
static_assert(
sizeof...(DomainTs) == 1 || sizeof...(DomainTs) == 2,
"DomainBoundary requires exactly one or two domains."
);
static_assert(
SchemaT::template contains_boundary<BoundaryT>(), "DomainBoundary refers to a boundary which is not "
"registered in the supplied DomainSchema."
);
static_assert(
(SchemaT::template contains_domain<DomainTs>() && ...),
"DomainBoundary refers to a domain which is not "
"completely registered in the supplied DomainSchema."
);
constexpr int expectedBoundaryAttribute = SchemaT::template boundary_attribute<BoundaryT>();
using DomainsTuple = std::tuple<DomainTs...>;
/*
* Record all MFEM boundary elements associated with each
* mesh face.
*
* A face can in principle have more than one boundary
* element associated with it. We do not require exactly
* one here; instead, every boundary element on an expected
* face must carry the expected semantic boundary attribute.
*/
std::vector<std::vector<int>> boundaryElementsByFace(static_cast<std::size_t>(mesh.GetNumFaces()));
for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE(); ++boundaryElementId) {
const int faceId = mesh.GetBdrElementFaceIndex(boundaryElementId);
if (faceId >= 0 && faceId < mesh.GetNumFaces()) {
boundaryElementsByFace[static_cast<std::size_t>(faceId)].push_back(boundaryElementId);
}
}
/*
* Build detailed diagnostics for one face.
*/
const auto make_diagnostics = [&mesh, expectedBoundaryAttribute](
int faceId, int boundaryElementId,
std::optional<int> actualBoundaryAttribute
) {
RelationValidationResult::DomainBoundaryDiagnostics diagnostics{
.faceId = faceId,
.boundaryElementId = boundaryElementId,
.expectedBoundaryAttribute = expectedBoundaryAttribute,
.actualBoundaryAttribute = actualBoundaryAttribute
};
if (faceId < 0 || faceId >= mesh.GetNumFaces()) {
return diagnostics;
}
mesh.GetFaceElements(faceId, &diagnostics.firstElementId, &diagnostics.secondElementId);
if (diagnostics.firstElementId >= 0) {
diagnostics.firstMaterialId = mesh.GetAttribute(diagnostics.firstElementId);
}
if (diagnostics.secondElementId >= 0) {
diagnostics.secondMaterialId = mesh.GetAttribute(diagnostics.secondElementId);
}
return diagnostics;
};
/*
* Check only the cardinality/topological shape required by
* the relation.
*
* One-domain form:
*
* Domain | computational exterior
*
* Exactly one adjacent volume element must exist.
*
* Two-domain form:
*
* DomainA | DomainB
*
* Both adjacent volume elements must exist.
*/
const auto has_required_topology = [](int firstElementId, int secondElementId) {
if constexpr (sizeof...(DomainTs) == 1) {
const bool firstExists = firstElementId >= 0;
const bool secondExists = secondElementId >= 0;
return firstExists != secondExists;
} else {
return firstElementId >= 0 && secondElementId >= 0;
}
};
/*
* Determine whether a face is exactly one of the faces
* described by DomainBoundary<BoundaryT, DomainTs...>.
*
* For two domains, ordering is intentionally irrelevant.
*/
const auto face_matches_domains = [&mesh](int firstElementId, int secondElementId) {
if constexpr (sizeof...(DomainTs) == 1) {
using DomainT = std::tuple_element_t<0, DomainsTuple>;
const bool firstExists = firstElementId >= 0;
const bool secondExists = secondElementId >= 0;
if (firstExists == secondExists) {
return false;
}
const int elementId = firstExists ? firstElementId : secondElementId;
const int materialId = mesh.GetAttribute(elementId);
return SchemaT::template attribute_belongs_to<DomainT>(materialId);
} else {
using FirstDomainT = std::tuple_element_t<0, DomainsTuple>;
using SecondDomainT = std::tuple_element_t<1, DomainsTuple>;
if (firstElementId < 0 || secondElementId < 0) {
return false;
}
const int firstMaterialId = mesh.GetAttribute(firstElementId);
const int secondMaterialId = mesh.GetAttribute(secondElementId);
const bool forwardMatch = SchemaT::template attribute_belongs_to<FirstDomainT>(firstMaterialId) &&
SchemaT::template attribute_belongs_to<SecondDomainT>(secondMaterialId);
const bool reverseMatch = SchemaT::template attribute_belongs_to<SecondDomainT>(firstMaterialId) &&
SchemaT::template attribute_belongs_to<FirstDomainT>(secondMaterialId);
return forwardMatch || reverseMatch;
}
};
bool foundTaggedBoundary = false;
/*
* Forward validation:
*
* Every boundary element carrying BoundaryT must lie on
* exactly the topology/material interface declared by
* DomainBoundary.
*/
for (int boundaryElementId = 0; boundaryElementId < mesh.GetNBE(); ++boundaryElementId) {
const int boundaryAttribute = mesh.GetBdrAttribute(boundaryElementId);
if (boundaryAttribute != expectedBoundaryAttribute) {
continue;
}
foundTaggedBoundary = true;
const int faceId = mesh.GetBdrElementFaceIndex(boundaryElementId);
int firstElementId = -1;
int secondElementId = -1;
mesh.GetFaceElements(faceId, &firstElementId, &secondElementId);
if (!has_required_topology(firstElementId, secondElementId)) {
return {
.failure = RelationValidationFailure::DomainBoundaryTaggedFaceHasWrongTopology,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId, boundaryAttribute)
)
};
}
if (!face_matches_domains(firstElementId, secondElementId)) {
return {
.failure = RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedMaterial,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId, boundaryAttribute)
)
};
}
}
bool foundExpectedFace = false;
/*
* Reverse validation:
*
* Every face having the declared domain adjacency must
* carry BoundaryT.
*
* This is important for physical constraints: a partially
* tagged Stellar/Vacuum interface must fail rather than
* silently leaving part of the stellar surface unconstrained.
*/
for (int faceId = 0; faceId < mesh.GetNumFaces(); ++faceId) {
int firstElementId = -1;
int secondElementId = -1;
mesh.GetFaceElements(faceId, &firstElementId, &secondElementId);
if (!face_matches_domains(firstElementId, secondElementId)) {
continue;
}
foundExpectedFace = true;
const auto &boundaryElementIds = boundaryElementsByFace[static_cast<std::size_t>(faceId)];
if (boundaryElementIds.empty()) {
return {
.failure = RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, -1, std::nullopt)
)
};
}
for (const int boundaryElementId : boundaryElementIds) {
const int actualBoundaryAttribute = mesh.GetBdrAttribute(boundaryElementId);
if (actualBoundaryAttribute == expectedBoundaryAttribute) {
continue;
}
return {
.failure = RelationValidationFailure::DomainBoundaryExpectedFaceHasWrongAttribute,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(faceId, boundaryElementId, actualBoundaryAttribute)
)
};
}
}
/*
* If neither a correctly tagged boundary nor a face having
* the required semantic topology exists, the declared
* DomainBoundary simply is not realized by this mesh.
*
* In the usual partial-failure cases above we will already
* have returned a more specific diagnostic.
*/
if (!foundTaggedBoundary || !foundExpectedFace) {
return {
.failure = RelationValidationFailure::DomainBoundaryAbsent,
.domainBoundaryDiagnostics =
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
make_diagnostics(-1, -1, std::nullopt)
)
};
}
return {};
}
};
struct SchemaRelationValidationResult {
std::size_t relationIndex{0};
std::string_view relationName;
RelationValidationResult result;
[[nodiscard]]
bool valid() const noexcept {
return result.valid();
}
[[nodiscard]]
explicit operator bool() const noexcept {
return valid();
}
};
struct SchemaValidationResult {
std::vector<SchemaRelationValidationResult> relationResults;
[[nodiscard]]
bool valid() const noexcept {
for (const auto &relationResult : relationResults) {
if (!relationResult.valid()) {
return false;
}
}
return true;
}
[[nodiscard]]
explicit operator bool() const noexcept {
return valid();
}
[[nodiscard]]
std::size_t relation_count() const noexcept {
return relationResults.size();
}
[[nodiscard]]
std::size_t failed_relation_count() const noexcept {
std::size_t failureCount = 0;
for (const auto &relationResult : relationResults) {
if (!relationResult.valid()) {
++failureCount;
}
}
return failureCount;
}
[[nodiscard]]
std::size_t passed_relation_count() const noexcept {
return relationResults.size() - failed_relation_count();
}
[[nodiscard]]
std::optional<std::size_t> first_failed_relation_index() const noexcept {
for (std::size_t relationIndex = 0; relationIndex < relationResults.size(); ++relationIndex) {
if (!relationResults[relationIndex].valid()) {
return relationIndex;
}
}
return std::nullopt;
}
};
template <IsSchema SchemaT, typename RelationListT> struct SchemaRelationValidator;
template <IsSchema SchemaT, IsRelation... RelationTs>
struct SchemaRelationValidator<SchemaT, RelationList<RelationTs...>> {
[[nodiscard]]
static SchemaValidationResult validate(const mfem::Mesh &mesh) {
SchemaValidationResult schemaResult;
schemaResult.relationResults.reserve(sizeof...(RelationTs));
std::size_t relationIndex = 0;
(schemaResult.relationResults.push_back(
SchemaRelationValidationResult{
.relationIndex = relationIndex++,
.relationName = RelationTs::name,
.result = RelationValidator<RelationTs>::template validate<SchemaT>(mesh)
}
),
...);
return schemaResult;
}
};
template <IsSchema SchemaT>
[[nodiscard]]
SchemaValidationResult validate_schema(const mfem::Mesh &mesh) {
using RelationsT = typename SchemaT::relations_type;
return SchemaRelationValidator<SchemaT, RelationsT>::validate(mesh);
}
template <
IsDomainOrSet DomainT,
IsSchema SchemaT>
[[nodiscard]]
mfem::Array<int> make_attribute_marker(const mfem::Mesh &mesh) {
static_assert(
SchemaT::template contains_domain<DomainT>(), "Requested marker domain is not completely registered in the "
"supplied DomainSchema."
);
mfem::Array<int> marker(mesh.attributes.Max());
for (int attribute = 1; attribute <= marker.Size(); ++attribute) {
marker[attribute - 1] = SchemaT::template attribute_belongs_to<DomainT>(attribute) ? 1 : 0;
}
return marker;
}
using CoreEnvelopeVacuumDomainSchema = DomainSchema<
MaterialList<Material<Core, 1>, Material<Envelope, 2>, Material<Vacuum, 3>>,
BoundaryList<BoundaryAttribute<StellarSurface, 1>, BoundaryAttribute<InfinitySurface, 2>>,
RelationList<
// All Domains must be fully connected
Connected<Core>,
Connected<Envelope>,
Connected<Vacuum>,
// Describe the topology of the mesh (core must be within envelope and
// the stellar domain (core + envelope) must be inscribed within vacuum
// region
Inscribed<Core, Envelope>,
Inscribed<Stellar, Vacuum>,
// The stellar surface sits between the stellar and vacuum domain and
// the infinity surface sits at the outside of the vacuum domain
DomainBoundary<StellarSurface, Stellar, Vacuum>,
DomainBoundary<InfinitySurface, Vacuum>>>;
} // namespace mean_field::utils::domain