The full rewrite of mean_field into something maintainable is progressing. dimensions is mostly done, discritization (domain, blocks, and fields) is done, and eos is progressing quickly
230 lines
10 KiB
C++
230 lines
10 KiB
C++
#pragma once
|
|
|
|
#include <optional>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include "serif/discretization/domain/concepts.hpp"
|
|
#include "serif/discretization/domain/mesh/topology.hpp"
|
|
#include "serif/discretization/domain/relation/relations.hpp"
|
|
#include "serif/discretization/domain/relation/validation/runtime.hpp"
|
|
#include "serif/discretization/domain/schema/concepts.hpp"
|
|
#include "serif/discretization/domain/schema/validation/validator.hpp"
|
|
|
|
namespace serif::discretization::domain::schema::validation {
|
|
using relation::DomainBoundary;
|
|
|
|
using relation::validation::RelationValidationResult;
|
|
using relation::validation::RelationValidationFailure;
|
|
|
|
template <IsBoundary BoundaryT, IsDomainOrSet... DomainTs>
|
|
struct RelationValidator<DomainBoundary<BoundaryT, DomainTs...>> {
|
|
template <IsSchema SchemaT>
|
|
[[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<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 expectedBoundaryID = SchemaT::template boundary_id<BoundaryT>();
|
|
|
|
using DomainsTuple = std::tuple<DomainTs...>;
|
|
|
|
const auto make_diagnostics = [&](const int faceID, const int boundaryElementID, const std::optional<int> actualBoundaryID) {
|
|
RelationValidationResult::DomainBoundaryDiagnostics diagnostics{
|
|
.faceID = faceID,
|
|
.boundaryElementID = boundaryElementID,
|
|
.expectedBoundaryID = expectedBoundaryID,
|
|
.actualBoundaryID = actualBoundaryID
|
|
};
|
|
|
|
if (faceID < 0 || faceID >= topology.face_count()) {
|
|
return diagnostics;
|
|
}
|
|
|
|
const mesh::FaceElements faceElements = topology.face_elements(faceID);
|
|
|
|
diagnostics.firstElementID = faceElements.firstElementID;
|
|
diagnostics.secondElementID = faceElements.secondElementID;
|
|
|
|
if (diagnostics.firstElementID >= 0) {
|
|
diagnostics.firstDomainID = topology.element_domain_id(diagnostics.firstElementID);
|
|
}
|
|
if (diagnostics.secondElementID >= 0) {
|
|
diagnostics.secondDomainID = topology.element_domain_id(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 = [](const int firstElementID, const int secondElementID) noexcept {
|
|
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 = [&topology](const int firstElementID, const int secondElementID) noexcept {
|
|
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 domainID = topology.element_domain_id(elementID);
|
|
|
|
return SchemaT::template domain_id_belongs_to<DomainT>(domainID);
|
|
} 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 firstDomainID = topology.element_domain_id(firstElementID);
|
|
const int secondDomainID = topology.element_domain_id(secondElementID);
|
|
|
|
const bool forwardMatch = SchemaT::template domain_id_belongs_to<FirstDomainT>(firstDomainID) &&
|
|
SchemaT::template domain_id_belongs_to<SecondDomainT>(secondDomainID);
|
|
|
|
const bool reverseMatch = SchemaT::template domain_id_belongs_to<SecondDomainT>(firstDomainID) &&
|
|
SchemaT::template domain_id_belongs_to<FirstDomainT>(secondDomainID);
|
|
|
|
return forwardMatch || reverseMatch;
|
|
}
|
|
};
|
|
|
|
bool foundTaggedBoundary = false;
|
|
|
|
// Forward validation: every boundary element carrying BoundaryT must
|
|
// lie on exactly the interface declared by DomainBoundary.
|
|
for (int boundaryElementID = 0; boundaryElementID < topology.boundary_element_count(); ++boundaryElementID) {
|
|
const int boundaryID = topology.boundary_element_boundary_id(boundaryElementID);
|
|
|
|
if (boundaryID != expectedBoundaryID) {
|
|
continue;
|
|
}
|
|
|
|
foundTaggedBoundary = true;
|
|
|
|
const int faceID = topology.boundary_element_face(boundaryElementID);
|
|
|
|
const auto [firstElementID, secondElementID] = topology.face_elements(faceID);
|
|
|
|
if (!has_required_topology(firstElementID, secondElementID)) {
|
|
return {
|
|
.failure = RelationValidationFailure::DomainBoundaryTaggedFaceHasWrongTopology,
|
|
.domainBoundaryDiagnostics =
|
|
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
|
|
make_diagnostics(faceID, boundaryElementID, boundaryID)
|
|
)
|
|
};
|
|
}
|
|
|
|
if (!face_matches_domains(firstElementID, secondElementID)) {
|
|
return {
|
|
.failure = RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedDomain,
|
|
.domainBoundaryDiagnostics =
|
|
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
|
|
make_diagnostics(faceID, boundaryElementID, boundaryID)
|
|
)
|
|
};
|
|
}
|
|
}
|
|
|
|
bool foundExpectedFace = false;
|
|
|
|
// Reverse validation: every face having the declared domain
|
|
// adjacency must carry BoundaryT.
|
|
for (int faceID = 0; faceID < topology.face_count(); ++faceID) {
|
|
const auto [firstElementID, secondElementID] = topology.face_elements(faceID);
|
|
|
|
if (!face_matches_domains(firstElementID, secondElementID)) {
|
|
continue;
|
|
}
|
|
|
|
foundExpectedFace = true;
|
|
|
|
const std::vector<int> &boundaryElementIDs = topology.boundary_elements_on_face(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 actualBoundaryID = topology.boundary_element_boundary_id(boundaryElementID);
|
|
|
|
if (actualBoundaryID == expectedBoundaryID) {
|
|
continue;
|
|
}
|
|
|
|
return {
|
|
.failure = RelationValidationFailure::DomainBoundaryExpectedFaceHasWrongID,
|
|
.domainBoundaryDiagnostics =
|
|
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
|
|
make_diagnostics(faceID, boundaryElementID, actualBoundaryID)
|
|
)
|
|
};
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
if (!foundTaggedBoundary || !foundExpectedFace) {
|
|
return {
|
|
.failure = RelationValidationFailure::DomainBoundaryAbsent,
|
|
.domainBoundaryDiagnostics =
|
|
std::make_optional<RelationValidationResult::DomainBoundaryDiagnostics>(
|
|
make_diagnostics(-1, -1, std::nullopt)
|
|
)
|
|
};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
};
|
|
}
|