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,339 @@
#pragma once
#include <array>
#include <cstddef>
#include <string_view>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mfem.hpp>
#include <stroid/stroid.h>
#include "serif/discretization/domain/ids/boundary.hpp"
#include "serif/discretization/domain/ids/domain.hpp"
#include "serif/discretization/domain/ids/lists/lists.hpp"
#include "serif/discretization/domain/physical_domains.hpp"
#include "serif/discretization/domain/relation/lists/relation_list.hpp"
#include "serif/discretization/domain/relation/relations.hpp"
#include "serif/discretization/domain/schema/domain_schema.hpp"
#include "serif/discretization/domain/schema/validation/all.hpp"
#include "serif/discretization/domain/types.hpp"
namespace domain_test_utils {
namespace domain = serif::discretization::domain;
namespace ids = domain::ids;
namespace relation = domain::relation;
namespace schema = domain::schema;
namespace validation = schema::validation;
struct UnregisteredDomain final : public domain::Domain {
static constexpr std::string_view name = "unregistered_domain";
};
struct UnregisteredBoundary final : public domain::Boundary {
static constexpr std::string_view name = "unregistered_boundary";
};
struct BoundaryEdge {
int firstVertexId{-1};
int secondVertexId{-1};
int attribute{0};
};
struct StroidCase {
std::string_view name;
int refinementLevels{0};
int order{1};
double flattening{0.0};
};
template <typename... DomainIDTs>
concept CanFormDomainIDList = requires { typename ids::lists::DomainIDList<DomainIDTs...>; };
template <typename... BoundaryIDTs>
concept CanFormBoundaryIDList = requires { typename ids::lists::BoundaryIDList<BoundaryIDTs...>; };
template <typename BoundaryT, typename... DomainTs>
concept CanFormDomainBoundary = requires { typename relation::DomainBoundary<BoundaryT, DomainTs...>; };
template <typename DomainIDsT, typename BoundaryIDsT, typename RelationsT>
concept CanFormSchema = requires { typename schema::DomainSchema<DomainIDsT, BoundaryIDsT, RelationsT>; };
[[nodiscard]] inline int vertex_id(const int xElementCount, const int x, const int y) {
return y * (xElementCount + 1) + x;
}
[[nodiscard]] inline int cell_index(const int xElementCount, const int x, const int y) {
return y * xElementCount + x;
}
[[nodiscard]]inline int cell_attribute(const std::vector<int> &attributes, const int xElementCount, const int x, const int y) {
return attributes.at(static_cast<std::size_t>(cell_index(xElementCount, x, y)));
}
template <typename FirstPredicateT, typename SecondPredicateT>
void append_interface_boundaries(
std::vector<BoundaryEdge> &boundaries,
const std::vector<int> &attributes,
const int xElementCount,
const int yElementCount,
FirstPredicateT firstPredicate,
SecondPredicateT secondPredicate,
const int boundaryAttribute
) {
/*
* Vertical internal faces.
*/
for (int y = 0; y < yElementCount; ++y) {
for (int x = 1; x < xElementCount; ++x) {
const int leftAttribute = cell_attribute(attributes, xElementCount, x - 1, y);
const int rightAttribute = cell_attribute(attributes, xElementCount, x, y);
const bool matches = (firstPredicate(leftAttribute) && secondPredicate(rightAttribute)) ||
(secondPredicate(leftAttribute) && firstPredicate(rightAttribute));
if (!matches) {
continue;
}
boundaries.push_back(
{.firstVertexId = vertex_id(xElementCount, x, y),
.secondVertexId = vertex_id(xElementCount, x, y + 1),
.attribute = boundaryAttribute}
);
}
}
/*
* Horizontal internal faces.
*/
for (int y = 1; y < yElementCount; ++y) {
for (int x = 0; x < xElementCount; ++x) {
const int lowerAttribute = cell_attribute(attributes, xElementCount, x, y - 1);
const int upperAttribute = cell_attribute(attributes, xElementCount, x, y);
const bool matches = (firstPredicate(lowerAttribute) && secondPredicate(upperAttribute)) ||
(secondPredicate(lowerAttribute) && firstPredicate(upperAttribute));
if (!matches) {
continue;
}
boundaries.push_back(
{.firstVertexId = vertex_id(xElementCount, x, y),
.secondVertexId = vertex_id(xElementCount, x + 1, y),
.attribute = boundaryAttribute}
);
}
}
}
template <typename PredicateT>
void append_exterior_boundaries(
std::vector<BoundaryEdge> &boundaries,
const std::vector<int> &attributes,
const int xElementCount,
const int yElementCount,
PredicateT predicate,
const int boundaryAttribute
) {
/*
* Bottom.
*/
for (int x = 0; x < xElementCount; ++x) {
if (predicate(cell_attribute(attributes, xElementCount, x, 0))) {
boundaries.push_back(
{.firstVertexId = vertex_id(xElementCount, x, 0),
.secondVertexId = vertex_id(xElementCount, x + 1, 0),
.attribute = boundaryAttribute}
);
}
}
/*
* Top.
*/
for (int x = 0; x < xElementCount; ++x) {
if (predicate(cell_attribute(attributes, xElementCount, x, yElementCount - 1))) {
boundaries.push_back(
{.firstVertexId = vertex_id(xElementCount, x, yElementCount),
.secondVertexId = vertex_id(xElementCount, x + 1, yElementCount),
.attribute = boundaryAttribute}
);
}
}
/*
* Left.
*/
for (int y = 0; y < yElementCount; ++y) {
if (predicate(cell_attribute(attributes, xElementCount, 0, y))) {
boundaries.push_back(
{.firstVertexId = vertex_id(xElementCount, 0, y),
.secondVertexId = vertex_id(xElementCount, 0, y + 1),
.attribute = boundaryAttribute}
);
}
}
/*
* Right.
*/
for (int y = 0; y < yElementCount; ++y) {
if (predicate(cell_attribute(attributes, xElementCount, xElementCount - 1, y))) {
boundaries.push_back(
{.firstVertexId = vertex_id(xElementCount, xElementCount, y),
.secondVertexId = vertex_id(xElementCount, xElementCount, y + 1),
.attribute = boundaryAttribute}
);
}
}
}
[[nodiscard]] inline mfem::Mesh make_grid_mesh(
const int xElementCount,
const int yElementCount,
const std::vector<int> &attributes,
const std::vector<BoundaryEdge> &boundaryEdges
) {
REQUIRE(static_cast<int>(attributes.size()) == xElementCount * yElementCount);
mfem::Mesh mesh(
2, (xElementCount + 1) * (yElementCount + 1), xElementCount * yElementCount,
static_cast<int>(boundaryEdges.size()), 2
);
for (int y = 0; y <= yElementCount; ++y) {
for (int x = 0; x <= xElementCount; ++x) {
mesh.AddVertex(static_cast<double>(x), static_cast<double>(y));
}
}
for (int y = 0; y < yElementCount; ++y) {
for (int x = 0; x < xElementCount; ++x) {
const int lowerLeft = vertex_id(xElementCount, x, y);
const int lowerRight = vertex_id(xElementCount, x + 1, y);
const int upperRight = vertex_id(xElementCount, x + 1, y + 1);
const int upperLeft = vertex_id(xElementCount, x, y + 1);
mesh.AddQuad(
lowerLeft, lowerRight, upperRight, upperLeft, cell_attribute(attributes, xElementCount, x, y)
);
}
}
for (const BoundaryEdge &boundary : boundaryEdges) {
mesh.AddBdrSegment(boundary.firstVertexId, boundary.secondVertexId, boundary.attribute);
}
mesh.FinalizeTopology(false);
mesh.Finalize(false, false);
REQUIRE(mesh.GetNBE() == static_cast<int>(boundaryEdges.size()));
return mesh;
}
[[nodiscard]] inline std::vector<int> make_layered_attributes() {
constexpr int xElementCount = 5;
constexpr int yElementCount = 5;
std::vector<int> attributes(xElementCount * yElementCount, 3);
for (int y = 1; y <= 3; ++y) {
for (int x = 1; x <= 3; ++x) {
attributes[static_cast<std::size_t>(cell_index(xElementCount, x, y))] = 2;
}
}
attributes[static_cast<std::size_t>(cell_index(xElementCount, 2, 2))] = 1;
return attributes;
}
[[nodiscard]] inline mfem::Mesh make_layered_mesh(
const bool includeStellarSurface = true,
const bool includeInfinitySurface = true,
const int stellarSurfaceAttribute = 1,
const int infinitySurfaceAttribute = 2
) {
constexpr int xElementCount = 5;
constexpr int yElementCount = 5;
const std::vector<int> attributes = make_layered_attributes();
std::vector<BoundaryEdge> boundaries;
const auto isStellar = [](const int materialId) { return materialId == 1 || materialId == 2; };
const auto isVacuum = [](const int materialId) { return materialId == 3; };
if (includeStellarSurface) {
append_interface_boundaries(
boundaries, attributes, xElementCount, yElementCount, isStellar, isVacuum, stellarSurfaceAttribute
);
}
if (includeInfinitySurface) {
append_exterior_boundaries(
boundaries, attributes, xElementCount, yElementCount, isVacuum, infinitySurfaceAttribute
);
}
return make_grid_mesh(xElementCount, yElementCount, attributes, boundaries);
}
template <typename SchemaT>
void check_schema_is_valid(const mfem::Mesh &mesh) {
const auto validation = domain::schema::validation::validate_schema<SchemaT>(mesh);
CHECK(validation.relationResults.size() == SchemaT::relation_count);
for (const auto &relationResult : validation.relationResults) {
INFO("Relation index = " << relationResult.relationIndex);
INFO("Relation name = " << relationResult.relationName);
INFO("Failure enum = " << static_cast<int>(relationResult.result.failure));
CHECK(relationResult.valid());
}
CHECK(validation.valid());
}
using AlternateIdSchema = schema::DomainSchema<
ids::lists::DomainIDList<
ids::DomainID<domain::CoreDomain, 11>,
ids::DomainID<domain::EnvelopeDomain, 17>,
ids::DomainID<domain::VacuumDomain, 29>>,
ids::lists::BoundaryIDList<
ids::BoundaryID<domain::StellarSurfaceBoundary, 101>,
ids::BoundaryID<domain::InfinitySurfaceBoundary, 203>>,
relation::lists::RelationList<
relation::FullyConnected<domain::CoreDomain>,
relation::FullyConnected<domain::EnvelopeDomain>,
relation::FullyConnected<domain::VacuumDomain>,
relation::Inscribed<domain::CoreDomain, domain::EnvelopeDomain>,
relation::Inscribed<domain::StellarDomains, domain::VacuumDomain>,
relation::DomainBoundary<
domain::StellarSurfaceBoundary,
domain::StellarDomains,
domain::VacuumDomain>,
relation::DomainBoundary<domain::InfinitySurfaceBoundary, domain::VacuumDomain>>>;
[[nodiscard]] inline stroid::config::MeshConfig make_stroid_config(
const int refinementLevels,
const int order,
const double flattening
) {
stroid::config::MeshConfig config;
config.refinement_levels = refinementLevels;
config.order = order;
config.include_external_domain = true;
config.r_core = 0.25;
config.r_star = 1.0;
config.r_infinity = 4.0;
config.flattening = flattening;
config.core_id = 1;
config.envelope_id = 2;
config.vacuum_id = 3;
config.surface_bdr_id = 1;
config.inf_bdr_id = 2;
config.optimization_methods = stroid::config::OptimizationMethods{.tmop = false, .smoothstep = true};
return config;
}
} // namespace domain_test_utils

View File

@@ -0,0 +1,71 @@
#pragma once
/*
* Header form of the tag machinery that currently lives in
* test_helpers.cppm. Only the tags used by the discretization/domain
* tests are carried over; once test_helpers is itself de-moduled this
* file should be deleted and the project wide header included instead.
*/
#include <algorithm>
#include <array>
#include <cstddef>
#include <catch2/internal/catch_stringref.hpp>
template <std::size_t N> struct Tag {
std::array<char, N> chars{};
// ReSharper disable once CppNonExplicitConvertingConstructor
consteval Tag(
std::array<
char,
N> arr
)
: chars(arr) {
}
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator const char *() const {
return chars.data();
}
// ReSharper disable once CppNonExplicitConversionOperator
constexpr operator Catch::StringRef() const {
return Catch::StringRef(chars.data(), N - 1);
}
template <std::size_t M> consteval Tag<N + M - 1> operator&(const Tag<M> &other) const {
std::array<char, N + M - 1> res{};
std::ranges::copy(chars.begin(), chars.end() - 1, res.begin());
std::ranges::copy(other.chars, res.begin() + (N - 1));
return {res};
}
};
template <std::size_t N> consteval auto make_tag(const char (&str)[N]) {
std::array<char, N + 2> res{};
res[0] = '[';
std::ranges::copy(str, str + N - 1, res.begin() + 1);
res[N] = ']';
res[N + 1] = '\0';
return Tag<N + 2>{res};
}
template <
std::size_t N,
std::size_t M>
consteval auto sub_tag(
const Tag<N> &parent,
const char (&str)[M]
) {
return parent & make_tag(str);
}
namespace tags {
inline constexpr auto unit = make_tag("unit");
inline constexpr auto mesh = make_tag("mesh");
inline constexpr auto integration = make_tag("integration");
inline constexpr auto utils = make_tag("utils");
inline constexpr auto domain = sub_tag(mesh, "domain");
} // namespace tags