currently the barotope and the pressure force operator are migrated to the new support system
1092 lines
41 KiB
C++
1092 lines
41 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) || ...);
|
|
}
|
|
};
|
|
|
|
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 <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);
|
|
}
|
|
|
|
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
|