feat(mean_field): added dimensions, discritization, and start of eos

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
This commit is contained in:
2026-09-15 10:42:00 -04:00
parent 7c99debf2f
commit d1f59d6d70
88 changed files with 324020 additions and 268 deletions

View File

@@ -0,0 +1,229 @@
#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 {};
}
};
}