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:
BIN
tests/discritization/domain/.DS_Store
vendored
Normal file
BIN
tests/discritization/domain/.DS_Store
vendored
Normal file
Binary file not shown.
25
tests/discritization/domain/concepts.cpp
Normal file
25
tests/discritization/domain/concepts.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "serif/discretization/domain/concepts.hpp"
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Types And Composite Domains Preserve Their Semantic Categories",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
STATIC_REQUIRE(domain::IsDomain<domain::CoreDomain>);
|
||||
STATIC_REQUIRE(domain::IsDomain<domain::EnvelopeDomain>);
|
||||
STATIC_REQUIRE(domain::IsDomain<domain::VacuumDomain>);
|
||||
STATIC_REQUIRE(domain::IsDomainSet<domain::StellarDomains>);
|
||||
STATIC_REQUIRE(domain::IsDomainSet<domain::AllDomains>);
|
||||
STATIC_REQUIRE_FALSE(domain::IsDomain<domain::StellarDomains>);
|
||||
STATIC_REQUIRE(domain::IsDomainOrSet<domain::StellarDomains>);
|
||||
STATIC_REQUIRE(domain::IsBoundary<domain::StellarSurfaceBoundary>);
|
||||
STATIC_REQUIRE(domain::IsBoundary<domain::InfinitySurfaceBoundary>);
|
||||
CHECK(true);
|
||||
}
|
||||
79
tests/discritization/domain/ids/lists/lists.cpp
Normal file
79
tests/discritization/domain/ids/lists/lists.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace ids = domain::ids;
|
||||
|
||||
TEST_CASE(
|
||||
"Material Lists Reject Duplicate Ids And Duplicate Semantic Domains",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
STATIC_REQUIRE(
|
||||
domain_test_utils::CanFormDomainIDList<
|
||||
ids::DomainID<domain::CoreDomain, 1>,
|
||||
ids::DomainID<domain::EnvelopeDomain, 2>>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormDomainIDList<
|
||||
ids::DomainID<domain::CoreDomain, 1>,
|
||||
ids::DomainID<domain::EnvelopeDomain, 1>>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormDomainIDList<
|
||||
ids::DomainID<domain::CoreDomain, 1>,
|
||||
ids::DomainID<domain::CoreDomain, 2>>
|
||||
);
|
||||
|
||||
/*
|
||||
* The schema intentionally imposes no convention on the
|
||||
* numerical range or indexing scheme used by a mesh producer.
|
||||
*/
|
||||
STATIC_REQUIRE(
|
||||
domain_test_utils::CanFormDomainIDList<
|
||||
ids::DomainID<domain::CoreDomain, 0>,
|
||||
ids::DomainID<domain::EnvelopeDomain, -7>,
|
||||
ids::DomainID<domain::VacuumDomain, 42>>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Boundary Lists Reject Duplicate Ids And Duplicate Semantic Boundaries",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
STATIC_REQUIRE(
|
||||
domain_test_utils::CanFormBoundaryIDList<
|
||||
ids::BoundaryID<domain::StellarSurfaceBoundary, 1>,
|
||||
ids::BoundaryID<domain::InfinitySurfaceBoundary, 2>>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormBoundaryIDList<
|
||||
ids::BoundaryID<domain::StellarSurfaceBoundary, 1>,
|
||||
ids::BoundaryID<domain::InfinitySurfaceBoundary, 1>>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormBoundaryIDList<
|
||||
ids::BoundaryID<domain::StellarSurfaceBoundary, 1>,
|
||||
ids::BoundaryID<domain::StellarSurfaceBoundary, 2>>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
domain_test_utils::CanFormBoundaryIDList<
|
||||
ids::BoundaryID<domain::StellarSurfaceBoundary, 0>,
|
||||
ids::BoundaryID<domain::InfinitySurfaceBoundary, -13>>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
36
tests/discritization/domain/relation/relations.cpp
Normal file
36
tests/discritization/domain/relation/relations.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/relation/relations.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Relations Accept Exactly One Or Two Domains",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
STATIC_REQUIRE(
|
||||
domain_test_utils::CanFormDomainBoundary<
|
||||
domain::InfinitySurfaceBoundary, domain::VacuumDomain>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE(
|
||||
domain_test_utils::CanFormDomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(domain_test_utils::CanFormDomainBoundary<domain::StellarSurfaceBoundary>);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormDomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::CoreDomain,
|
||||
domain::EnvelopeDomain, domain::VacuumDomain>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
BIN
tests/discritization/domain/schema/.DS_Store
vendored
Normal file
BIN
tests/discritization/domain/schema/.DS_Store
vendored
Normal file
Binary file not shown.
55
tests/discritization/domain/schema/domain_schema.cpp
Normal file
55
tests/discritization/domain/schema/domain_schema.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace ids = domain::ids;
|
||||
namespace relation = domain::relation;
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Schemas Reject Relations That Reference Unregistered Entities",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
using IncompleteDomainIDs = ids::lists::DomainIDList<
|
||||
ids::DomainID<domain::CoreDomain, 1>,
|
||||
ids::DomainID<domain::VacuumDomain, 3>>;
|
||||
|
||||
using CompleteDomainIDs = ids::lists::DomainIDList<
|
||||
ids::DomainID<domain::CoreDomain, 1>,
|
||||
ids::DomainID<domain::EnvelopeDomain, 2>,
|
||||
ids::DomainID<domain::VacuumDomain, 3>>;
|
||||
|
||||
using CompleteBoundaryIDs = ids::lists::BoundaryIDList<
|
||||
ids::BoundaryID<domain::StellarSurfaceBoundary, 1>,
|
||||
ids::BoundaryID<domain::InfinitySurfaceBoundary, 2>>;
|
||||
|
||||
using InfinityOnlyBoundaryIDs = ids::lists::BoundaryIDList<
|
||||
ids::BoundaryID<domain::InfinitySurfaceBoundary, 2>>;
|
||||
|
||||
using MissingEnvelopeRelation = relation::lists::RelationList<
|
||||
relation::FullyConnected<domain::EnvelopeDomain>>;
|
||||
|
||||
using MissingBoundaryRelation = relation::lists::RelationList<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>>;
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormSchema<IncompleteDomainIDs, CompleteBoundaryIDs, MissingEnvelopeRelation>
|
||||
);
|
||||
|
||||
STATIC_REQUIRE_FALSE(
|
||||
domain_test_utils::CanFormSchema<CompleteDomainIDs, InfinityOnlyBoundaryIDs, MissingBoundaryRelation>
|
||||
);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
64
tests/discritization/domain/schema/schemas.cpp
Normal file
64
tests/discritization/domain/schema/schemas.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <string_view>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/schema/concepts.hpp"
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace schema = domain::schema;
|
||||
|
||||
TEST_CASE(
|
||||
"Core Envelope Vacuum Schema Exposes Exact Compile Time And Runtime "
|
||||
"Metadata",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
using SchemaT = schema::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
STATIC_REQUIRE(schema::IsSchema<SchemaT>);
|
||||
STATIC_REQUIRE(SchemaT::domain_id_count == 3);
|
||||
STATIC_REQUIRE(SchemaT::boundary_id_count == 2);
|
||||
STATIC_REQUIRE(SchemaT::relation_count == 7);
|
||||
|
||||
constexpr auto domainDescriptors = SchemaT::domain_descriptors();
|
||||
constexpr auto boundaryDescriptors = SchemaT::boundary_descriptors();
|
||||
|
||||
STATIC_REQUIRE(domainDescriptors[0].name == std::string_view{"core"});
|
||||
STATIC_REQUIRE(domainDescriptors[0].ID == 1);
|
||||
|
||||
STATIC_REQUIRE(domainDescriptors[1].name == std::string_view{"envelope"});
|
||||
STATIC_REQUIRE(domainDescriptors[1].ID == 2);
|
||||
|
||||
STATIC_REQUIRE(domainDescriptors[2].name == std::string_view{"vacuum"});
|
||||
STATIC_REQUIRE(domainDescriptors[2].ID == 3);
|
||||
|
||||
STATIC_REQUIRE(boundaryDescriptors[0].name == std::string_view{"stellar_surface"});
|
||||
STATIC_REQUIRE(boundaryDescriptors[0].ID == 1);
|
||||
|
||||
STATIC_REQUIRE(boundaryDescriptors[1].name == std::string_view{"infinity_surface"});
|
||||
STATIC_REQUIRE(boundaryDescriptors[1].ID == 2);
|
||||
|
||||
STATIC_REQUIRE(SchemaT::template contains_domain<domain::CoreDomain>());
|
||||
STATIC_REQUIRE(SchemaT::template contains_domain<domain::StellarDomains>());
|
||||
STATIC_REQUIRE(SchemaT::template contains_domain<domain::AllDomains>());
|
||||
STATIC_REQUIRE(SchemaT::template domain_id_belongs_to<domain::StellarDomains>(1));
|
||||
STATIC_REQUIRE(SchemaT::template domain_id_belongs_to<domain::StellarDomains>(2));
|
||||
STATIC_REQUIRE_FALSE(SchemaT::template domain_id_belongs_to<domain::StellarDomains>(3));
|
||||
STATIC_REQUIRE(SchemaT::template domain_id_belongs_to<domain::AllDomains>(1));
|
||||
STATIC_REQUIRE(SchemaT::template domain_id_belongs_to<domain::AllDomains>(2));
|
||||
STATIC_REQUIRE(SchemaT::template domain_id_belongs_to<domain::AllDomains>(3));
|
||||
STATIC_REQUIRE(SchemaT::template domain_id<domain::CoreDomain>() == 1);
|
||||
STATIC_REQUIRE(SchemaT::template domain_id<domain::EnvelopeDomain>() == 2);
|
||||
STATIC_REQUIRE(SchemaT::template domain_id<domain::VacuumDomain>() == 3);
|
||||
|
||||
STATIC_REQUIRE(SchemaT::template contains_boundary<domain::StellarSurfaceBoundary>());
|
||||
STATIC_REQUIRE(SchemaT::template contains_boundary<domain::InfinitySurfaceBoundary>());
|
||||
STATIC_REQUIRE(SchemaT::template boundary_id<domain::StellarSurfaceBoundary>() == 1);
|
||||
STATIC_REQUIRE(SchemaT::template boundary_id<domain::InfinitySurfaceBoundary>() == 2);
|
||||
|
||||
CHECK(true);
|
||||
}
|
||||
42
tests/discritization/domain/schema/utils.cpp
Normal file
42
tests/discritization/domain/schema/utils.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "serif/discretization/domain/schema/utils.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace schema = domain::schema;
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Schema Builds Exact MFEM Attribute Markers",
|
||||
tags::domain &tags::utils &tags::unit
|
||||
) {
|
||||
using Schema = schema::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
const mfem::Mesh mesh = domain_test_utils::make_layered_mesh();
|
||||
|
||||
const mfem::Array<int> stellarMarker = schema::make_attribute_marker<domain::StellarDomains, Schema>(mesh);
|
||||
const mfem::Array<int> vacuumMarker = schema::make_attribute_marker<domain::VacuumDomain, Schema>(mesh);
|
||||
const mfem::Array<int> allMarker = schema::make_attribute_marker<domain::AllDomains, Schema>(mesh);
|
||||
|
||||
REQUIRE(stellarMarker.Size() == 3);
|
||||
REQUIRE(vacuumMarker.Size() == 3);
|
||||
REQUIRE(allMarker.Size() == 3);
|
||||
|
||||
CHECK(stellarMarker[0] == 1);
|
||||
CHECK(stellarMarker[1] == 1);
|
||||
CHECK(stellarMarker[2] == 0);
|
||||
|
||||
CHECK(vacuumMarker[0] == 0);
|
||||
CHECK(vacuumMarker[1] == 0);
|
||||
CHECK(vacuumMarker[2] == 1);
|
||||
|
||||
CHECK(allMarker[0] == 1);
|
||||
CHECK(allMarker[1] == 1);
|
||||
CHECK(allMarker[2] == 1);
|
||||
}
|
||||
182
tests/discritization/domain/schema/validation/all.cpp
Normal file
182
tests/discritization/domain/schema/validation/all.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
#include <stroid/stroid.h>
|
||||
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "serif/discretization/domain/schema/validation/all.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace schema = domain::schema;
|
||||
namespace schema_validation = schema::validation;
|
||||
|
||||
TEST_CASE(
|
||||
"Complete Schema Validation Accepts A Synthetic Core Envelope Vacuum Mesh",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_layered_mesh();
|
||||
|
||||
const auto validation =
|
||||
schema_validation::validate_schema<schema::CoreEnvelopeVacuumDomainSchema>(mesh);
|
||||
|
||||
REQUIRE(validation.valid());
|
||||
REQUIRE(validation.relationResults.size() == 7);
|
||||
CHECK(validation.failed_relation_count() == 0);
|
||||
CHECK(validation.passed_relation_count() == 7);
|
||||
CHECK_FALSE(validation.first_failed_relation_index().has_value());
|
||||
|
||||
constexpr std::array<std::string_view, 7> expectedRelationNames{"fully_connected", "fully_connected",
|
||||
"fully_connected", "inscribed",
|
||||
"inscribed", "domain_boundary",
|
||||
"domain_boundary"};
|
||||
|
||||
for (std::size_t relationIndex = 0; relationIndex < expectedRelationNames.size(); ++relationIndex) {
|
||||
CHECK(validation.relationResults[relationIndex].relationIndex == relationIndex);
|
||||
CHECK(validation.relationResults[relationIndex].relationName == expectedRelationNames[relationIndex]);
|
||||
CHECK(validation.relationResults[relationIndex].valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Complete Schema Validation Evaluates Every Relation After A Failure",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
/*
|
||||
* All material topology and the outer vacuum boundary are valid.
|
||||
* Only the Stellar/Vacuum boundary tagging is intentionally absent.
|
||||
*/
|
||||
const mfem::Mesh mesh = domain_test_utils::make_layered_mesh(false, true);
|
||||
const auto validation =
|
||||
schema_validation::validate_schema<schema::CoreEnvelopeVacuumDomainSchema>(mesh);
|
||||
CHECK_FALSE(validation.valid());
|
||||
REQUIRE(validation.relationResults.size() == 7);
|
||||
CHECK(validation.failed_relation_count() == 1);
|
||||
CHECK(validation.passed_relation_count() == 6);
|
||||
REQUIRE(validation.first_failed_relation_index().has_value());
|
||||
CHECK(*validation.first_failed_relation_index() == 5);
|
||||
|
||||
for (std::size_t relationIndex = 0; relationIndex < 7; ++relationIndex) {
|
||||
CAPTURE(relationIndex);
|
||||
|
||||
if (relationIndex == 5) {
|
||||
CHECK_FALSE(validation.relationResults[relationIndex].valid());
|
||||
CHECK(
|
||||
validation.relationResults[relationIndex].result.failure ==
|
||||
schema_validation::RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK(validation.relationResults[relationIndex].valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"STROID Meshes Satisfy The Core Envelope Vacuum Domain Schema",
|
||||
tags::integration &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
constexpr std::array<domain_test_utils::StroidCase, 3> testCases{
|
||||
domain_test_utils::StroidCase{
|
||||
.name = "spherical_low_order", .refinementLevels = 0, .order = 1, .flattening = 0.0
|
||||
},
|
||||
domain_test_utils::StroidCase{.name = "oblate", .refinementLevels = 0, .order = 2, .flattening = 0.15},
|
||||
domain_test_utils::StroidCase{.name = "refined_oblate", .refinementLevels = 1, .order = 2, .flattening = 0.10}
|
||||
};
|
||||
|
||||
for (const auto &[name, refinementLevels, order, flattening] : testCases) {
|
||||
INFO("STROID case = " << name);
|
||||
INFO("Refinement levels = " << refinementLevels);
|
||||
INFO("Order = " << order);
|
||||
INFO("Flattening = " << flattening);
|
||||
|
||||
const stroid::config::MeshConfig config = domain_test_utils::make_stroid_config(refinementLevels, order, flattening);
|
||||
stroid::StroidMesh stroidMesh = stroid::GenerateMesh(config);
|
||||
|
||||
REQUIRE(stroidMesh.reference_mesh != nullptr);
|
||||
REQUIRE(stroidMesh.mesh != nullptr);
|
||||
|
||||
/*
|
||||
* Validate both the reference topology and the projected
|
||||
* physical mesh. The mapping/projection must not alter
|
||||
* material or boundary semantics.
|
||||
*/
|
||||
domain_test_utils::check_schema_is_valid<schema::CoreEnvelopeVacuumDomainSchema>(
|
||||
*stroidMesh.reference_mesh
|
||||
);
|
||||
|
||||
domain_test_utils::check_schema_is_valid<schema::CoreEnvelopeVacuumDomainSchema>(
|
||||
*stroidMesh.mesh
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"STROID Material And Boundary Id Conventions Are Fully Schema Driven",
|
||||
tags::integration &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
stroid::config::MeshConfig config = domain_test_utils::make_stroid_config(0, 1, 0.0);
|
||||
|
||||
config.core_id = 11;
|
||||
config.envelope_id = 17;
|
||||
config.vacuum_id = 29;
|
||||
config.surface_bdr_id = 101;
|
||||
config.inf_bdr_id = 203;
|
||||
|
||||
stroid::StroidMesh stroidMesh = stroid::GenerateMesh(config);
|
||||
|
||||
REQUIRE(stroidMesh.reference_mesh != nullptr);
|
||||
REQUIRE(stroidMesh.mesh != nullptr);
|
||||
|
||||
/*
|
||||
* The same semantic topology must validate when a mesh generator
|
||||
* uses an entirely different attribute numbering convention.
|
||||
*/
|
||||
domain_test_utils::check_schema_is_valid<domain_test_utils::AlternateIdSchema>(*stroidMesh.reference_mesh);
|
||||
domain_test_utils::check_schema_is_valid<domain_test_utils::AlternateIdSchema>(*stroidMesh.mesh);
|
||||
|
||||
/*
|
||||
* Conversely, the production 1/2/3 + 1/2 schema must not silently
|
||||
* accept a mesh generated under another numbering convention.
|
||||
*/
|
||||
const auto productionValidation =
|
||||
schema_validation::validate_schema<schema::CoreEnvelopeVacuumDomainSchema>(*stroidMesh.mesh);
|
||||
|
||||
CHECK_FALSE(productionValidation.valid());
|
||||
CHECK(productionValidation.failed_relation_count() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Complete Schema Validation Rejects A Mesh Without Vacuum",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(3, 3, {2, 2, 2, 2, 1, 2, 2, 2, 2}, {});
|
||||
|
||||
const auto validation = schema_validation::validate_schema<schema::CoreEnvelopeVacuumDomainSchema>(mesh);
|
||||
|
||||
CHECK_FALSE(validation.valid());
|
||||
REQUIRE(validation.relationResults.size() == 7);
|
||||
|
||||
/*
|
||||
* FullyConnected<VacuumDomain>
|
||||
*/
|
||||
CHECK_FALSE(validation.relationResults[2].valid());
|
||||
CHECK(
|
||||
validation.relationResults[2].result.failure ==
|
||||
schema_validation::RelationValidationFailure::DomainAbsent
|
||||
);
|
||||
|
||||
/*
|
||||
* Inscribed<StellarDomains, VacuumDomain>
|
||||
*/
|
||||
CHECK_FALSE(validation.relationResults[4].valid());
|
||||
CHECK(
|
||||
validation.relationResults[4].result.failure ==
|
||||
schema_validation::RelationValidationFailure::OuterDomainAbsent
|
||||
);
|
||||
}
|
||||
179
tests/discritization/domain/schema/validation/boundary.cpp
Normal file
179
tests/discritization/domain/schema/validation/boundary.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
#include "serif/discretization/domain/mesh/topology.hpp"
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/relation/relations.hpp"
|
||||
#include "serif/discretization/domain/relation/validation/runtime.hpp"
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "serif/discretization/domain/schema/validation/boundary.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace relation = domain::relation;
|
||||
namespace schema = domain::schema;
|
||||
namespace validation = schema::validation;
|
||||
|
||||
using domain::mesh::MeshTopology;
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Accepts A Complete Internal Stellar Vacuum Interface",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
std::vector<domain_test_utils::BoundaryEdge> boundaries{{.firstVertexId = 1, .secondVertexId = 4, .attribute = 1}};
|
||||
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 3}, boundaries);
|
||||
const MeshTopology topology{mesh};
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(topology);
|
||||
|
||||
CHECK(result);
|
||||
|
||||
/*
|
||||
* Interface ordering is intentionally semantic rather
|
||||
* than oriented.
|
||||
*/
|
||||
const auto reversedResult = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::VacuumDomain,
|
||||
domain::StellarDomains>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(topology);
|
||||
|
||||
CHECK(reversedResult);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Accepts A Complete Exterior Vacuum Boundary",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const std::vector<int> attributes{3};
|
||||
std::vector<domain_test_utils::BoundaryEdge> boundaries;
|
||||
|
||||
domain_test_utils::append_exterior_boundaries(
|
||||
boundaries, attributes, 1, 1, [](const int materialId) { return materialId == 3; }, 2
|
||||
);
|
||||
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(1, 1, attributes, boundaries);
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::InfinitySurfaceBoundary, domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK(result);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Rejects A Tagged Internal Face For An Exterior Boundary",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {3, 3}, {{.firstVertexId = 1, .secondVertexId = 4, .attribute = 2}});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::InfinitySurfaceBoundary, domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(
|
||||
result.failure == validation::RelationValidationFailure::DomainBoundaryTaggedFaceHasWrongTopology
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Rejects A Tagged Exterior Face Of The Wrong Material",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(1, 1, {2}, {{.firstVertexId = 0, .secondVertexId = 1, .attribute = 2}});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::InfinitySurfaceBoundary, domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(
|
||||
result.failure ==
|
||||
validation::RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedDomain
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Rejects A Tagged Internal Interface With Unexpected "
|
||||
"Materials",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh =
|
||||
domain_test_utils::make_grid_mesh(2, 1, {1, 2}, {{.firstVertexId = 1, .secondVertexId = 4, .attribute = 1}});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
|
||||
CHECK(
|
||||
result.failure ==
|
||||
validation::RelationValidationFailure::DomainBoundaryTaggedFaceTouchesUnexpectedDomain
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Rejects An Untagged Expected Interface",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 3}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::DomainBoundaryExpectedFaceIsUntagged);
|
||||
REQUIRE(result.domainBoundaryDiagnostics.has_value());
|
||||
CHECK(result.domainBoundaryDiagnostics->faceID >= 0);
|
||||
CHECK(result.domainBoundaryDiagnostics->boundaryElementID == -1);
|
||||
CHECK_FALSE(result.domainBoundaryDiagnostics->actualBoundaryID.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Rejects An Expected Interface With The Wrong Attribute",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 3}, {{.firstVertexId = 1, .secondVertexId = 4, .attribute = 9}});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(
|
||||
result.failure ==
|
||||
validation::RelationValidationFailure::DomainBoundaryExpectedFaceHasWrongID
|
||||
);
|
||||
REQUIRE(result.domainBoundaryDiagnostics.has_value());
|
||||
REQUIRE(result.domainBoundaryDiagnostics->actualBoundaryID.has_value());
|
||||
CHECK(*result.domainBoundaryDiagnostics->actualBoundaryID == 9);
|
||||
CHECK(result.domainBoundaryDiagnostics->expectedBoundaryID == 1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Domain Boundary Rejects A Relation That Is Not Realized Anywhere",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 2}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::DomainBoundary<
|
||||
domain::StellarSurfaceBoundary, domain::StellarDomains,
|
||||
domain::VacuumDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::DomainBoundaryAbsent);
|
||||
}
|
||||
73
tests/discritization/domain/schema/validation/connected.cpp
Normal file
73
tests/discritization/domain/schema/validation/connected.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
#include "serif/discretization/domain/mesh/topology.hpp"
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/relation/relations.hpp"
|
||||
#include "serif/discretization/domain/relation/validation/runtime.hpp"
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "serif/discretization/domain/schema/validation/connected.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace relation = domain::relation;
|
||||
namespace schema = domain::schema;
|
||||
namespace validation = schema::validation;
|
||||
|
||||
using domain::mesh::MeshTopology;
|
||||
|
||||
TEST_CASE(
|
||||
"Connected Accepts Face Connected Atomic And Composite Domains",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_layered_mesh();
|
||||
const MeshTopology topology{mesh};
|
||||
|
||||
const auto coreResult = validation::RelationValidator<relation::FullyConnected<domain::CoreDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(topology);
|
||||
|
||||
REQUIRE(coreResult);
|
||||
REQUIRE(coreResult.connectedDiagnostics.has_value());
|
||||
CHECK(coreResult.connectedDiagnostics->domainElementCount == 1);
|
||||
CHECK(coreResult.connectedDiagnostics->visitedElementCount == 1);
|
||||
|
||||
const auto stellarResult = validation::RelationValidator<relation::FullyConnected<domain::StellarDomains>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(topology);
|
||||
|
||||
REQUIRE(stellarResult);
|
||||
REQUIRE(stellarResult.connectedDiagnostics.has_value());
|
||||
CHECK(stellarResult.connectedDiagnostics->domainElementCount == 9);
|
||||
CHECK(stellarResult.connectedDiagnostics->visitedElementCount == 9);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Connected Rejects An Absent Domain",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 1, {2, 2}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::FullyConnected<domain::CoreDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::DomainAbsent);
|
||||
REQUIRE(result.connectedDiagnostics.has_value());
|
||||
CHECK(result.connectedDiagnostics->domainElementCount == 0);
|
||||
CHECK(result.connectedDiagnostics->visitedElementCount == 0);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Connected Rejects Multiple Face Disconnected Components",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(3, 1, {1, 2, 1}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::FullyConnected<domain::CoreDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::DomainDisconnected);
|
||||
REQUIRE(result.connectedDiagnostics.has_value());
|
||||
CHECK(result.connectedDiagnostics->domainElementCount == 2);
|
||||
CHECK(result.connectedDiagnostics->visitedElementCount == 1);
|
||||
CHECK(result.connectedDiagnostics->elementID >= 0);
|
||||
}
|
||||
96
tests/discritization/domain/schema/validation/inscribed.cpp
Normal file
96
tests/discritization/domain/schema/validation/inscribed.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <vector>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mfem.hpp>
|
||||
|
||||
#include "serif/discretization/domain/mesh/topology.hpp"
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/domain/relation/relations.hpp"
|
||||
#include "serif/discretization/domain/relation/validation/runtime.hpp"
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "serif/discretization/domain/schema/validation/inscribed.hpp"
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
|
||||
#include "serif/tests/test_tags.hpp"
|
||||
#include "serif/tests/discritization/domain/domain_test_utils.hpp"
|
||||
|
||||
namespace domain = serif::discretization::domain;
|
||||
namespace relation = domain::relation;
|
||||
namespace schema = domain::schema;
|
||||
namespace validation = schema::validation;
|
||||
|
||||
using domain::mesh::MeshTopology;
|
||||
|
||||
TEST_CASE(
|
||||
"Inscribed Accepts Nested Atomic And Composite Domains",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_layered_mesh();
|
||||
const MeshTopology topology{mesh};
|
||||
|
||||
const auto coreResult = validation::RelationValidator<relation::Inscribed<domain::CoreDomain, domain::EnvelopeDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(topology);
|
||||
|
||||
CHECK(coreResult);
|
||||
|
||||
const auto stellarResult = validation::RelationValidator<relation::Inscribed<domain::StellarDomains, domain::VacuumDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(topology);
|
||||
|
||||
CHECK(stellarResult);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Inscribed Rejects An Absent Inner Domain",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 2, {2, 2, 2, 2}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::Inscribed<domain::CoreDomain, domain::EnvelopeDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::InnerDomainAbsent);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Inscribed Rejects An Absent Outer Domain",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(1, 1, {1}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::Inscribed<domain::CoreDomain, domain::EnvelopeDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::OuterDomainAbsent);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Inscribed Rejects An Inner Domain Touching The Computational Boundary",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(2, 2, {1, 2, 2, 2}, {});
|
||||
|
||||
const auto result = validation::RelationValidator<relation::Inscribed<domain::CoreDomain, domain::EnvelopeDomain>>::template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::InnerDomainTouchesMeshBoundary);
|
||||
REQUIRE(result.inscribedDiagnostics.has_value());
|
||||
CHECK(result.inscribedDiagnostics->faceID >= 0);
|
||||
CHECK(result.inscribedDiagnostics->innerElementID >= 0);
|
||||
CHECK(result.inscribedDiagnostics->adjacentElementID == -1);
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Inscribed Rejects An Inner Domain Touching An Unexpected Material",
|
||||
tags::unit &tags::mesh &tags::utils &tags::domain
|
||||
) {
|
||||
std::vector<int> attributes{2, 2, 2, 2, 1, 3, 2, 2, 2};
|
||||
|
||||
const mfem::Mesh mesh = domain_test_utils::make_grid_mesh(3, 3, attributes, {});
|
||||
|
||||
const auto result = validation::RelationValidator<
|
||||
relation::Inscribed<domain::CoreDomain, domain::EnvelopeDomain>>::
|
||||
template validate<schema::CoreEnvelopeVacuumDomainSchema>(MeshTopology{mesh});
|
||||
|
||||
CHECK_FALSE(result);
|
||||
CHECK(result.failure == validation::RelationValidationFailure::InnerDomainTouchesUnexpectedDomain);
|
||||
REQUIRE(result.inscribedDiagnostics.has_value());
|
||||
CHECK(result.inscribedDiagnostics->adjacentDomainID == 3);
|
||||
}
|
||||
12
tests/discritization/meson.build
Normal file
12
tests/discritization/meson.build
Normal file
@@ -0,0 +1,12 @@
|
||||
discretization_test_sources = files(
|
||||
'domain/concepts.cpp',
|
||||
# 'domain/ids/lists/lists.cpp',
|
||||
# 'domain/relation/relations.cpp',
|
||||
# 'domain/schema/domain_schema.cpp',
|
||||
# 'domain/schema/schemas.cpp',
|
||||
# 'domain/schema/utils.cpp',
|
||||
# 'domain/schema/validation/all.cpp',
|
||||
# 'domain/schema/validation/boundary.cpp',
|
||||
# 'domain/schema/validation/connected.cpp',
|
||||
# 'domain/schema/validation/inscribed.cpp',
|
||||
)
|
||||
@@ -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
|
||||
71
tests/include/serif/tests/test_tags.hpp
Normal file
71
tests/include/serif/tests/test_tags.hpp
Normal 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
|
||||
@@ -1,169 +1,13 @@
|
||||
if get_option('build_tests') and get_option('build_examples')
|
||||
runtime_environment = environment()
|
||||
if (
|
||||
mfem_runtime_prefix != '' and dependency_prefix != '' and
|
||||
not wheel_carries_native_bundle
|
||||
)
|
||||
runtime_environment.prepend('PATH', dependency_prefix / 'bin')
|
||||
if host_machine.system() == 'darwin'
|
||||
runtime_environment.prepend('DYLD_LIBRARY_PATH', dependency_prefix / 'lib64')
|
||||
runtime_environment.prepend('DYLD_LIBRARY_PATH', dependency_prefix / 'lib')
|
||||
elif host_machine.system() != 'windows' and not is_wasm
|
||||
runtime_environment.prepend('LD_LIBRARY_PATH', dependency_prefix / 'lib64')
|
||||
runtime_environment.prepend('LD_LIBRARY_PATH', dependency_prefix / 'lib')
|
||||
endif
|
||||
endif
|
||||
if mfem_runtime_prefix != ''
|
||||
runtime_environment.prepend('PATH', mfem_runtime_prefix / 'bin')
|
||||
if host_machine.system() == 'darwin'
|
||||
runtime_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
|
||||
runtime_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
|
||||
elif host_machine.system() != 'windows' and not is_wasm
|
||||
runtime_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
|
||||
runtime_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
|
||||
endif
|
||||
endif
|
||||
|
||||
test(
|
||||
'serial-poisson',
|
||||
serial_example,
|
||||
env: runtime_environment,
|
||||
timeout: 120,
|
||||
)
|
||||
|
||||
if mfem_has_cuda
|
||||
test(
|
||||
'serial-poisson-cuda',
|
||||
serial_example,
|
||||
args: ['cuda'],
|
||||
env: runtime_environment,
|
||||
timeout: 120,
|
||||
)
|
||||
endif
|
||||
|
||||
if mfem_has_mpi
|
||||
if mpi_launcher_from_dependency and not wheel_carries_native_bundle
|
||||
mpi_launcher_program = find_program(
|
||||
dependency_prefix / 'bin' / 'mpiexec',
|
||||
dependency_prefix / 'bin' / 'mpirun',
|
||||
required: true,
|
||||
)
|
||||
mpi_launcher = mpi_launcher_program.full_path()
|
||||
elif mfem_runtime_prefix != ''
|
||||
mpi_launcher = mfem_runtime_prefix / 'bin' / 'mpiexec'
|
||||
else
|
||||
mpi_launcher_program = find_program('mpiexec', 'mpirun', required: true)
|
||||
mpi_launcher = mpi_launcher_program.full_path()
|
||||
endif
|
||||
test(
|
||||
'parallel-hypre-boomeramg',
|
||||
python_build,
|
||||
args: [
|
||||
files('../tools/run_mpi_test.py'),
|
||||
'--launcher', mpi_launcher,
|
||||
'--processes', '2',
|
||||
parallel_example,
|
||||
],
|
||||
env: runtime_environment,
|
||||
timeout: 180,
|
||||
)
|
||||
if mfem_has_cuda
|
||||
test(
|
||||
'parallel-hypre-boomeramg-cuda',
|
||||
python_build,
|
||||
args: [
|
||||
files('../tools/run_mpi_test.py'),
|
||||
'--launcher', mpi_launcher,
|
||||
'--processes', '2',
|
||||
parallel_example,
|
||||
'cuda',
|
||||
],
|
||||
env: runtime_environment,
|
||||
timeout: 180,
|
||||
)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
if get_option('build_tests') and get_option('build_benchmarks')
|
||||
benchmark_smoke_environment = environment()
|
||||
benchmark_smoke_environment.set('OMP_NUM_THREADS', '1')
|
||||
benchmark_smoke_environment.set('OMP_DYNAMIC', 'FALSE')
|
||||
if (
|
||||
mfem_runtime_prefix != '' and dependency_prefix != '' and
|
||||
not wheel_carries_native_bundle
|
||||
)
|
||||
benchmark_smoke_environment.prepend('PATH', dependency_prefix / 'bin')
|
||||
if host_machine.system() == 'darwin'
|
||||
benchmark_smoke_environment.prepend('DYLD_LIBRARY_PATH', dependency_prefix / 'lib64')
|
||||
benchmark_smoke_environment.prepend('DYLD_LIBRARY_PATH', dependency_prefix / 'lib')
|
||||
elif host_machine.system() != 'windows' and not is_wasm
|
||||
benchmark_smoke_environment.prepend('LD_LIBRARY_PATH', dependency_prefix / 'lib64')
|
||||
benchmark_smoke_environment.prepend('LD_LIBRARY_PATH', dependency_prefix / 'lib')
|
||||
endif
|
||||
endif
|
||||
if mfem_runtime_prefix != ''
|
||||
benchmark_smoke_environment.prepend('PATH', mfem_runtime_prefix / 'bin')
|
||||
if host_machine.system() == 'darwin'
|
||||
benchmark_smoke_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
|
||||
benchmark_smoke_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
|
||||
elif host_machine.system() != 'windows'
|
||||
benchmark_smoke_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
|
||||
benchmark_smoke_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
|
||||
endif
|
||||
endif
|
||||
test(
|
||||
'backend-benchmark-smoke',
|
||||
python_build,
|
||||
args: [
|
||||
files('../tools/run_mpi_test.py'),
|
||||
'--launcher', benchmark_mpi_launcher,
|
||||
'--processes', '1',
|
||||
'--',
|
||||
benchmark_executable,
|
||||
'--device', 'cpu',
|
||||
'--mesh-n', '4',
|
||||
'--order', '2',
|
||||
'--applications', '2',
|
||||
'--minimum-apply-seconds', '0',
|
||||
'--warmup-applications', '1',
|
||||
'--relative-tolerance', '1e-6',
|
||||
'--max-iterations', '200',
|
||||
'--solve',
|
||||
],
|
||||
depends: benchmark_executable,
|
||||
env: benchmark_smoke_environment,
|
||||
timeout: 180,
|
||||
)
|
||||
endif
|
||||
|
||||
if get_option('build_tests') and get_option('build_python')
|
||||
python_test_environment = environment()
|
||||
python_test_environment.prepend('PYTHONPATH', python_extension_dir)
|
||||
if mfem_runtime_prefix != ''
|
||||
if host_machine.system() == 'darwin'
|
||||
python_test_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
|
||||
python_test_environment.prepend('DYLD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
|
||||
elif host_machine.system() != 'windows'
|
||||
python_test_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib64')
|
||||
python_test_environment.prepend('LD_LIBRARY_PATH', mfem_runtime_prefix / 'lib')
|
||||
endif
|
||||
endif
|
||||
test(
|
||||
'python-nanobind',
|
||||
python_build,
|
||||
args: [
|
||||
'-c',
|
||||
'import _core; assert _core.serial_poisson_dofs(3, 1) > 0; assert _core.capabilities()["hypre"] == _core.capabilities()["mpi"]',
|
||||
],
|
||||
depends: python_extension,
|
||||
env: python_test_environment,
|
||||
timeout: 120,
|
||||
)
|
||||
endif
|
||||
|
||||
if get_option('build_tests')
|
||||
test('cuda-toolchain-diagnostics', python_build,
|
||||
args: files('test_cuda_toolchain.py'),
|
||||
)
|
||||
endif
|
||||
# subdir('discritization')
|
||||
#
|
||||
# test_include_dir = include_directories('include')
|
||||
#
|
||||
# catch2_dep = dependency('Catch2', required:true)
|
||||
stroid_dep = dependency('stroid', required:true)
|
||||
#
|
||||
# test_sources = discretization_test_sources
|
||||
# executable('serif_tests', test_sources, dependencies: [serif_dep, catch2_dep, stroid_dep], include_directories: test_include_dir)
|
||||
|
||||
subdir('sandbox')
|
||||
endif
|
||||
24
tests/sandbox/discritization_sandbox.cpp
Normal file
24
tests/sandbox/discritization_sandbox.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <print>
|
||||
|
||||
#include "serif/discretization/domain/schema/schemas.hpp"
|
||||
#include "mfem.hpp"
|
||||
#include "serif/discretization/domain/schema/validation/all.hpp"
|
||||
#include "serif/discretization/domain/schema/validation/results.hpp"
|
||||
#include "stroid/stroid.h"
|
||||
#include "serif/discretization/blocks/base.hpp"
|
||||
|
||||
int main() {
|
||||
using serif::discretization::domain::schema::validation::SchemaValidationResult;
|
||||
using serif::discretization::domain::schema::validation::validate_schema;
|
||||
using serif::discretization::domain::schema::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
const auto smesh_result = stroid::IO::LoadStroidMesh("sandbox.smesh");
|
||||
if (not smesh_result.has_value()) {
|
||||
throw std::runtime_error("Failed to load sandbox.smesh");
|
||||
}
|
||||
|
||||
const stroid::StroidMesh& smesh = smesh_result.value();
|
||||
|
||||
SchemaValidationResult result = validate_schema<CoreEnvelopeVacuumDomainSchema>(*smesh.mesh);
|
||||
std::println("{}", result);
|
||||
}
|
||||
11
tests/sandbox/eos_sandbox.cpp
Normal file
11
tests/sandbox/eos_sandbox.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <print>
|
||||
|
||||
#include "serif/eos/models/polytropic.hpp"
|
||||
#include "serif/eos/evaluation.hpp"
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
int main() {
|
||||
const serif::eos::models::Polytrope polytrope(1, 0.6);
|
||||
std::println("Density: {}", serif::eos::evaluate<serif::dimensions::Density>(polytrope, serif::dimensions::SpecificEnthalpyValue{1.0}).value());
|
||||
|
||||
|
||||
}
|
||||
26
tests/sandbox/finite_sandbox.cpp
Normal file
26
tests/sandbox/finite_sandbox.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "serif/utils/misc/finite.hpp"
|
||||
#include <limits>
|
||||
|
||||
enum class [[maybe_unused]] TestErrorCode {
|
||||
NotFinite
|
||||
};
|
||||
|
||||
namespace {
|
||||
class test_exception : public std::runtime_error {
|
||||
public:
|
||||
test_exception(TestErrorCode error_code, const std::string& message)
|
||||
: std::runtime_error(message), m_error_code(error_code) {}
|
||||
|
||||
TestErrorCode error_code() const noexcept {
|
||||
return m_error_code;
|
||||
}
|
||||
private:
|
||||
TestErrorCode m_error_code;
|
||||
};
|
||||
}
|
||||
|
||||
int main() {
|
||||
double a = std::numeric_limits<double>::infinity();
|
||||
|
||||
serif::utils::misc::validate_finite<test_exception>(a, TestErrorCode::NotFinite);
|
||||
}
|
||||
14
tests/sandbox/meson.build
Normal file
14
tests/sandbox/meson.build
Normal file
@@ -0,0 +1,14 @@
|
||||
sandboxes = [
|
||||
'discritization_sandbox',
|
||||
'eos_sandbox',
|
||||
'finite_sandbox',
|
||||
]
|
||||
|
||||
foreach sandbox: sandboxes
|
||||
executable(
|
||||
sandbox,
|
||||
sandbox + '.cpp',
|
||||
dependencies: [serif_dep, stroid_dep],
|
||||
build_rpath: mfem_runtime_prefix == '' ? '' : mfem_runtime_prefix / 'lib',
|
||||
)
|
||||
endforeach
|
||||
653
tests/serif_tests.cpp
Normal file
653
tests/serif_tests.cpp
Normal file
@@ -0,0 +1,653 @@
|
||||
#include <algorithm>
|
||||
#include <catch2/catch_session.hpp>
|
||||
#include <catch2/catch_test_case_info.hpp>
|
||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
||||
#include <catch2/reporters/catch_reporter_streaming_base.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <mfem.hpp>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <fourdst/config/config.h>
|
||||
|
||||
|
||||
std::string escapeHtml(const std::string &data) {
|
||||
std::string buffer;
|
||||
buffer.reserve(data.size());
|
||||
for (size_t pos = 0; pos != data.size(); ++pos) {
|
||||
switch (data[pos]) {
|
||||
case '&':
|
||||
buffer.append("&");
|
||||
break;
|
||||
case '\"':
|
||||
buffer.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
buffer.append("'");
|
||||
break;
|
||||
case '<':
|
||||
buffer.append("<");
|
||||
break;
|
||||
case '>':
|
||||
buffer.append(">");
|
||||
break;
|
||||
default:
|
||||
buffer.append(&data[pos], 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string ansiToHtml(const std::string &text) {
|
||||
// Convert text to HTML-safe first
|
||||
std::string htmlEscaped = escapeHtml(text);
|
||||
|
||||
std::ostringstream oss;
|
||||
size_t i = 0;
|
||||
size_t len = htmlEscaped.length();
|
||||
int openSpans = 0;
|
||||
|
||||
auto closeSpans = [&oss, &openSpans]() {
|
||||
while (openSpans > 0) {
|
||||
oss << "</span>";
|
||||
--openSpans;
|
||||
}
|
||||
};
|
||||
|
||||
while (i < len) {
|
||||
// Look for ANSI CSI sequence '\033[' or '\x1b['
|
||||
if ((htmlEscaped[i] == '\033' || htmlEscaped[i] == '\x1b') && i + 1 < len && htmlEscaped[i + 1] == '[') {
|
||||
size_t seqStart = i + 2;
|
||||
size_t seqEnd = htmlEscaped.find('m', seqStart);
|
||||
|
||||
if (seqEnd != std::string::npos) {
|
||||
std::string codeStr = htmlEscaped.substr(seqStart, seqEnd - seqStart);
|
||||
i = seqEnd + 1;
|
||||
|
||||
std::istringstream codeStream(codeStr);
|
||||
std::string codeVal;
|
||||
|
||||
// Defaults if sequence is just \033[m (Reset)
|
||||
if (codeStr.empty()) {
|
||||
closeSpans();
|
||||
continue;
|
||||
}
|
||||
|
||||
while (std::getline(codeStream, codeVal, ';')) {
|
||||
int code = 0;
|
||||
try {
|
||||
code = std::stoi(codeVal);
|
||||
} catch (...) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 0: // Reset
|
||||
closeSpans();
|
||||
break;
|
||||
case 1: // Bold
|
||||
oss << "<span style='font-weight:bold;'>";
|
||||
openSpans++;
|
||||
break;
|
||||
case 2: // Dim
|
||||
oss << "<span style='opacity:0.7;'>";
|
||||
openSpans++;
|
||||
break;
|
||||
// Standard Foreground Colors
|
||||
case 30:
|
||||
oss << "<span style='color:#2c3e50;'>";
|
||||
openSpans++;
|
||||
break; // Black
|
||||
case 31:
|
||||
oss << "<span style='color:#e74c3c;'>";
|
||||
openSpans++;
|
||||
break; // Red
|
||||
case 32:
|
||||
oss << "<span style='color:#27ae60;'>";
|
||||
openSpans++;
|
||||
break; // Green
|
||||
case 33:
|
||||
oss << "<span style='color:#f39c12;'>";
|
||||
openSpans++;
|
||||
break; // Yellow
|
||||
case 34:
|
||||
oss << "<span style='color:#2980b9;'>";
|
||||
openSpans++;
|
||||
break; // Blue
|
||||
case 35:
|
||||
oss << "<span style='color:#8e44ad;'>";
|
||||
openSpans++;
|
||||
break; // Magenta
|
||||
case 36:
|
||||
oss << "<span style='color:#16a085;'>";
|
||||
openSpans++;
|
||||
break; // Cyan
|
||||
case 37:
|
||||
oss << "<span style='color:#bdc3c7;'>";
|
||||
openSpans++;
|
||||
break; // Light Gray
|
||||
// Bright Foreground Colors
|
||||
case 90:
|
||||
oss << "<span style='color:#7f8c8d;'>";
|
||||
openSpans++;
|
||||
break; // Dark Gray
|
||||
case 91:
|
||||
oss << "<span style='color:#ff6b6b;'>";
|
||||
openSpans++;
|
||||
break; // Bright Red
|
||||
case 92:
|
||||
oss << "<span style='color:#51cf66;'>";
|
||||
openSpans++;
|
||||
break; // Bright Green
|
||||
case 93:
|
||||
oss << "<span style='color:#fcc419;'>";
|
||||
openSpans++;
|
||||
break; // Bright Yellow
|
||||
case 94:
|
||||
oss << "<span style='color:#339af0;'>";
|
||||
openSpans++;
|
||||
break; // Bright Blue
|
||||
case 95:
|
||||
oss << "<span style='color:#cc5de8;'>";
|
||||
openSpans++;
|
||||
break; // Bright Magenta
|
||||
case 96:
|
||||
oss << "<span style='color:#22b8cf;'>";
|
||||
openSpans++;
|
||||
break; // Bright Cyan
|
||||
case 97:
|
||||
oss << "<span style='color:#ffffff;'>";
|
||||
openSpans++;
|
||||
break; // White
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
oss << htmlEscaped[i];
|
||||
++i;
|
||||
}
|
||||
|
||||
closeSpans();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> wrapText(
|
||||
const std::string &text,
|
||||
size_t width
|
||||
) {
|
||||
std::vector<std::string> lines;
|
||||
std::istringstream words(text);
|
||||
std::string word, line;
|
||||
|
||||
while (words >> word) {
|
||||
if (line.length() + word.length() + 1 > width) {
|
||||
if (!line.empty()) {
|
||||
lines.push_back(line);
|
||||
line.clear();
|
||||
}
|
||||
if (word.length() > width) {
|
||||
lines.push_back(word.substr(0, width - 3) + "...");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!line.empty())
|
||||
line += " ";
|
||||
line += word;
|
||||
}
|
||||
if (!line.empty())
|
||||
lines.push_back(line);
|
||||
if (lines.empty())
|
||||
lines.push_back("");
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
class CheckReporter : public Catch::StreamingReporterBase {
|
||||
struct TestCaseData {
|
||||
std::string name;
|
||||
std::string tags;
|
||||
bool passed;
|
||||
std::size_t assertionsPassed;
|
||||
std::size_t assertionsFailed;
|
||||
double durationSeconds;
|
||||
std::vector<std::string> failureMessages;
|
||||
std::vector<std::string> infoMessages;
|
||||
};
|
||||
|
||||
std::vector<std::string> m_currentFailures;
|
||||
std::vector<std::string> m_currentInfos;
|
||||
std::unordered_set<unsigned int> m_currentInfoSequences;
|
||||
std::vector<TestCaseData> m_testRunData;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_testStartTime;
|
||||
|
||||
static bool isRootProcess() {
|
||||
int initialized = 0;
|
||||
int finalized = 0;
|
||||
MPI_Initialized(&initialized);
|
||||
if (initialized == 0) {
|
||||
return true;
|
||||
}
|
||||
MPI_Finalized(&finalized);
|
||||
if (finalized != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int rank = 0;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
return rank == 0;
|
||||
}
|
||||
|
||||
void captureInfoMessages(Catch::AssertionStats const &assertionStats) {
|
||||
for (auto const &message : assertionStats.infoMessages) {
|
||||
if (m_currentInfoSequences.insert(message.sequence).second) {
|
||||
m_currentInfos.push_back(message.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit CheckReporter(Catch::ReporterConfig &&config) : Catch::StreamingReporterBase(std::move(config)) {
|
||||
// INFO messages are delivered through assertionEnded. Request passing
|
||||
// assertions as well so HTML logging does not depend on Catch2's -s
|
||||
// flag.
|
||||
m_preferences.shouldReportAllAssertions = true;
|
||||
|
||||
// This reporter does not use assertionStarting events. Disabling them
|
||||
// preserves Catch2's successful-assertion fast path where possible.
|
||||
m_preferences.shouldReportAllAssertionStarts = false;
|
||||
}
|
||||
|
||||
static std::string getDescription() {
|
||||
return "Console reporter with wrapping, tags, live test progress, and collapsible HTML "
|
||||
"export with ANSI color rendering.";
|
||||
}
|
||||
|
||||
void testRunStarting(Catch::TestRunInfo const &_testRunInfo) override {
|
||||
StreamingReporterBase::testRunStarting(_testRunInfo);
|
||||
|
||||
if (!isRootProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << std::left << std::setw(85) << "Test Case Name"
|
||||
<< "Status " << std::right << std::setw(8) << "Passed" << std::setw(8) << "Failed" << std::setw(12)
|
||||
<< "Time (s)" << '\n';
|
||||
std::cout << std::string(133, '-') << '\n';
|
||||
}
|
||||
|
||||
void testCaseStarting(Catch::TestCaseInfo const &testInfo) override {
|
||||
StreamingReporterBase::testCaseStarting(testInfo);
|
||||
|
||||
m_testStartTime = std::chrono::steady_clock::now();
|
||||
if (!isRootProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = testInfo.name;
|
||||
auto wrappedName = wrapText(name, 83);
|
||||
|
||||
// Print progress line, \r to overwrite later, \033[K to clear till end of line
|
||||
std::cout << "\r\033[K" << std::left << std::setw(85) << (wrappedName[0] + " ...") << std::flush;
|
||||
}
|
||||
|
||||
void assertionEnded(Catch::AssertionStats const &assertionStats) override {
|
||||
StreamingReporterBase::assertionEnded(assertionStats);
|
||||
|
||||
// Capture every INFO message encountered by either a passing or failing
|
||||
// assertion. Message sequence IDs prevent a scoped INFO from being
|
||||
// repeated once for every assertion that occurs while it remains
|
||||
// active.
|
||||
captureInfoMessages(assertionStats);
|
||||
|
||||
if (!assertionStats.assertionResult.isOk()) {
|
||||
auto const &result = assertionStats.assertionResult;
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << " \033[31m-> FAILED:\033[0m " << result.getSourceInfo().file << ":" << result.getSourceInfo().line
|
||||
<< '\n';
|
||||
oss << " " << result.getTestMacroName() << "( " << result.getExpression() << " )\n";
|
||||
|
||||
if (result.hasExpandedExpression()) {
|
||||
oss << " with expansion:\n"
|
||||
<< " " << result.getExpandedExpression() << '\n';
|
||||
}
|
||||
|
||||
for (auto const &msg : assertionStats.infoMessages) {
|
||||
oss << " \033[36m[INFO]\033[0m " << msg.message << '\n';
|
||||
}
|
||||
|
||||
m_currentFailures.push_back(oss.str());
|
||||
}
|
||||
}
|
||||
|
||||
void testCaseEnded(Catch::TestCaseStats const &stats) override {
|
||||
StreamingReporterBase::testCaseEnded(stats);
|
||||
|
||||
auto endTime = std::chrono::steady_clock::now();
|
||||
std::chrono::duration<double> elapsed = endTime - m_testStartTime;
|
||||
double duration_s = elapsed.count();
|
||||
|
||||
bool passed = stats.totals.assertions.allPassed();
|
||||
std::string mark = passed ? "\033[32m✓\033[0m" : "\033[31m✗\033[0m";
|
||||
|
||||
std::string name = stats.testInfo->name;
|
||||
auto wrappedName = wrapText(name, 83);
|
||||
|
||||
if (isRootProcess()) {
|
||||
// Overwrite the loading line with the actual result
|
||||
std::cout << "\r\033[K" << std::left << std::setw(85) << wrappedName[0] << mark << " " << std::right
|
||||
<< std::setw(8) << stats.totals.assertions.passed << std::setw(8)
|
||||
<< stats.totals.assertions.failed << std::setw(11) << std::fixed << std::setprecision(3)
|
||||
<< duration_s << "s\n";
|
||||
|
||||
for (size_t i = 1; i < wrappedName.size(); ++i) {
|
||||
std::cout << " \033[90m↳ \033[0m" // Dim indent arrow
|
||||
<< std::left << std::setw(81) << wrappedName[i] << '\n';
|
||||
}
|
||||
|
||||
std::string tagsStr = stats.testInfo->tagsAsString();
|
||||
if (!tagsStr.empty()) {
|
||||
auto wrappedTags = wrapText("Tags: " + tagsStr, 83);
|
||||
for (const auto &line : wrappedTags) {
|
||||
std::cout << " \033[36m" << line << "\033[0m\n"; // Cyan
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_currentFailures.empty()) {
|
||||
std::cout << '\n';
|
||||
for (auto const &failure : m_currentFailures) {
|
||||
std::cout << failure << '\n';
|
||||
}
|
||||
std::cout << std::string(133, '-') << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
std::string tagsStr = stats.testInfo->tagsAsString();
|
||||
|
||||
m_testRunData.push_back(
|
||||
{name, tagsStr, passed, stats.totals.assertions.passed, stats.totals.assertions.failed, duration_s,
|
||||
m_currentFailures, m_currentInfos}
|
||||
);
|
||||
|
||||
m_currentFailures.clear();
|
||||
m_currentInfos.clear();
|
||||
m_currentInfoSequences.clear();
|
||||
}
|
||||
|
||||
void testRunEnded(Catch::TestRunStats const &_testRunStats) override {
|
||||
StreamingReporterBase::testRunEnded(_testRunStats);
|
||||
|
||||
if (!isRootProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << std::string(133, '=') << '\n';
|
||||
|
||||
auto const &tc = _testRunStats.totals.testCases;
|
||||
auto const &as = _testRunStats.totals.assertions;
|
||||
|
||||
std::string tc_passed_str =
|
||||
tc.passed > 0 ? "\033[32m" + std::to_string(tc.passed) + " passed\033[0m" : "0 passed";
|
||||
std::string tc_failed_str =
|
||||
tc.failed > 0 ? "\033[31m" + std::to_string(tc.failed) + " failed\033[0m" : "0 failed";
|
||||
|
||||
std::string as_passed_str =
|
||||
as.passed > 0 ? "\033[32m" + std::to_string(as.passed) + " passed\033[0m" : "0 passed";
|
||||
std::string as_failed_str =
|
||||
as.failed > 0 ? "\033[31m" + std::to_string(as.failed) + " failed\033[0m" : "0 failed";
|
||||
|
||||
std::cout << "Test Cases: " << tc_passed_str << ", " << tc_failed_str << ", " << tc.total() << " total\n";
|
||||
std::cout << "Assertions: " << as_passed_str << ", " << as_failed_str << ", " << as.total() << " total\n\n";
|
||||
|
||||
generateHtmlReport(_testRunStats);
|
||||
}
|
||||
|
||||
private:
|
||||
void generateHtmlReport(Catch::TestRunStats const &stats) {
|
||||
std::ofstream html("test_summary.html");
|
||||
if (!html)
|
||||
return;
|
||||
|
||||
html << "<!DOCTYPE html>\n<html lang='en'>\n<head>\n"
|
||||
<< "<meta charset='UTF-8'>\n"
|
||||
<< "<meta name='viewport' content='width=device-width, "
|
||||
"initial-scale=1.0'>\n"
|
||||
<< "<title>Test Run Summary</title>\n"
|
||||
<< "<style>\n"
|
||||
<< "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe "
|
||||
"UI', "
|
||||
"Roboto, Helvetica, Arial, sans-serif; "
|
||||
"background: #f4f6f8; color: #333; margin: 0; padding: 2rem; }\n"
|
||||
<< "h1 { color: #2c3e50; border-bottom: 2px solid #e0e0e0; "
|
||||
"padding-bottom: 0.5rem; }\n"
|
||||
<< ".summary-cards { display: flex; gap: 1rem; margin-bottom: "
|
||||
"2rem; }\n"
|
||||
<< ".card { background: white; padding: 1rem 1.5rem; "
|
||||
"border-radius: "
|
||||
"8px; box-shadow: 0 2px 4px "
|
||||
"rgba(0,0,0,0.05); flex: 1; }\n"
|
||||
<< ".card h3 { margin-top: 0; font-size: 0.9rem; color: #7f8c8d; "
|
||||
"text-transform: uppercase; }\n"
|
||||
<< ".card p { font-size: 1.5rem; font-weight: bold; margin: 0; }\n"
|
||||
<< ".text-green { color: #27ae60; }\n"
|
||||
<< ".text-red { color: #e74c3c; }\n"
|
||||
<< ".test-item { background: white; border-radius: 8px; padding: "
|
||||
"1rem; "
|
||||
"margin-bottom: 1rem; box-shadow: 0 2px "
|
||||
"4px rgba(0,0,0,0.05); border-left: 5px solid #bdc3c7; }\n"
|
||||
<< ".test-item.passed { border-left-color: #27ae60; }\n"
|
||||
<< ".test-item.failed { border-left-color: #e74c3c; }\n"
|
||||
<< ".test-header { display: flex; justify-content: space-between; "
|
||||
"align-items: flex-start; }\n"
|
||||
<< ".test-name { font-size: 1.1rem; font-weight: 600; margin: 0 0 "
|
||||
"0.5rem 0; word-break: break-word; }\n"
|
||||
<< ".tags { font-size: 0.8rem; color: #2980b9; background: "
|
||||
"#ebf5fb; "
|
||||
"padding: 2px 6px; border-radius: 4px; "
|
||||
"display: inline-block; margin-top: 4px; }\n"
|
||||
<< ".stats { font-size: 0.9rem; color: #7f8c8d; }\n"
|
||||
<< "details { margin-top: 0.8rem; background: #f8f9fa; border: 1px "
|
||||
"solid #e9ecef; border-radius: 6px; "
|
||||
"padding: 0.5rem 0.8rem; }\n"
|
||||
<< "summary { cursor: pointer; font-weight: 600; color: #34495e; "
|
||||
"user-select: none; font-size: 0.9rem; }\n"
|
||||
<< "summary:hover { color: #2980b9; }\n"
|
||||
<< "pre { background: #1e293b; color: #f8fafc; padding: 1rem; "
|
||||
"border-radius: 4px; overflow-x: auto; "
|
||||
"font-size: 0.85rem; line-height: 1.4; margin-top: 0.5rem; }\n"
|
||||
<< "pre.info-block { background: #0f172a; border-left: 4px solid "
|
||||
"#0284c7; }\n"
|
||||
<< "</style>\n</head>\n<body>\n";
|
||||
|
||||
html << "<h1>Test Run Summary</h1>\n";
|
||||
|
||||
// Summary Cards
|
||||
html << "<div class='summary-cards'>\n";
|
||||
html << "<div class='card'><h3>Total Cases</h3><p>" << stats.totals.testCases.total() << "</p></div>\n";
|
||||
html << "<div class='card'><h3>Cases Passed</h3><p class='text-green'>" << stats.totals.testCases.passed
|
||||
<< "</p></div>\n";
|
||||
html << "<div class='card'><h3>Cases Failed</h3><p class='text-red'>" << stats.totals.testCases.failed
|
||||
<< "</p></div>\n";
|
||||
html << "</div>\n";
|
||||
|
||||
for (const auto &test : m_testRunData) {
|
||||
std::string statusClass = test.passed ? "passed" : "failed";
|
||||
html << "<div class='test-item " << statusClass << "'>\n";
|
||||
html << " <div class='test-header'>\n";
|
||||
html << " <div>\n";
|
||||
html << " <h3 class='test-name'>" << escapeHtml(test.name) << "</h3>\n";
|
||||
if (!test.tags.empty()) {
|
||||
html << " <div class='tags'>" << escapeHtml(test.tags) << "</div>\n";
|
||||
}
|
||||
html << " </div>\n";
|
||||
html << " <div class='stats'>\n";
|
||||
html << " <span class='text-green'>✓ " << test.assertionsPassed << "</span> | ";
|
||||
html << " <span class='text-red'>✗ " << test.assertionsFailed << "</span> | ";
|
||||
html << " <span style='color: #34495e;'>⌛ " << std::fixed << std::setprecision(3)
|
||||
<< test.durationSeconds << "s</span>\n";
|
||||
html << " </div>\n";
|
||||
html << " </div>\n";
|
||||
|
||||
// Collapsible INFO Messages section with ANSI color rendering
|
||||
if (!test.infoMessages.empty()) {
|
||||
html << " <details>\n";
|
||||
html << " <summary>Info Logs (" << test.infoMessages.size() << ")</summary>\n";
|
||||
html << " <pre class='info-block'>";
|
||||
for (const auto &info : test.infoMessages) {
|
||||
html << "[INFO] " << ansiToHtml(info) << "\n";
|
||||
}
|
||||
html << "</pre>\n";
|
||||
html << " </details>\n";
|
||||
}
|
||||
|
||||
// Collapsible Failures section with ANSI color rendering
|
||||
if (!test.failureMessages.empty()) {
|
||||
html << " <details open>\n";
|
||||
html << " <summary class='text-red'>Failure Details (" << test.failureMessages.size()
|
||||
<< ")</summary>\n";
|
||||
html << " <pre>";
|
||||
for (const auto &msg : test.failureMessages) {
|
||||
html << ansiToHtml(msg) << "\n";
|
||||
}
|
||||
html << " </pre>\n";
|
||||
html << " </details>\n";
|
||||
}
|
||||
|
||||
html << "</div>\n";
|
||||
}
|
||||
|
||||
html << "</body>\n</html>\n";
|
||||
}
|
||||
};
|
||||
|
||||
CATCH_REGISTER_REPORTER(
|
||||
"check",
|
||||
CheckReporter
|
||||
)
|
||||
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
fourdst::config::Config<mean_field::utils::Args> cfg;
|
||||
CLI::App app{"Mean Field Tests"};
|
||||
|
||||
app.allow_extras();
|
||||
app.set_help_flag("--config-help", "Show mean-field configuration options");
|
||||
fourdst::config::register_as_cli(cfg, app);
|
||||
|
||||
std::vector<std::string> config_arguments;
|
||||
std::vector<std::string> forced_catch_arguments;
|
||||
config_arguments.emplace_back(argv[0]);
|
||||
|
||||
bool parsing_catch_arguments = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (std::string_view(argv[i]) == "--catch2") {
|
||||
parsing_catch_arguments = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parsing_catch_arguments) {
|
||||
forced_catch_arguments.emplace_back(argv[i]);
|
||||
} else {
|
||||
config_arguments.emplace_back(argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char *> config_argv;
|
||||
config_argv.reserve(config_arguments.size());
|
||||
|
||||
for (const std::string &argument : config_arguments) {
|
||||
config_argv.push_back(argument.c_str());
|
||||
}
|
||||
|
||||
try {
|
||||
app.parse(static_cast<int>(config_argv.size()), config_argv.data());
|
||||
} catch (const CLI::ParseError &error) {
|
||||
return app.exit(error);
|
||||
}
|
||||
|
||||
std::vector<std::string> catch_arguments;
|
||||
catch_arguments.emplace_back(argv[0]);
|
||||
|
||||
for (const std::string &argument : app.remaining()) {
|
||||
catch_arguments.push_back(argument);
|
||||
}
|
||||
|
||||
for (const std::string &argument : forced_catch_arguments) {
|
||||
catch_arguments.push_back(argument);
|
||||
}
|
||||
|
||||
const auto is_reporter_option = [](const std::string &argument) {
|
||||
return argument == "-r" || argument == "--reporter" || argument.starts_with("-r=") ||
|
||||
argument.starts_with("--reporter=");
|
||||
};
|
||||
|
||||
if (const bool has_reporter = std::ranges::any_of(catch_arguments, is_reporter_option); !has_reporter) {
|
||||
catch_arguments.emplace_back("--reporter");
|
||||
catch_arguments.emplace_back("check");
|
||||
}
|
||||
|
||||
std::vector<const char *> catch_argv;
|
||||
catch_argv.reserve(catch_arguments.size());
|
||||
|
||||
for (const std::string &argument : catch_arguments) {
|
||||
catch_argv.push_back(argument.c_str());
|
||||
}
|
||||
|
||||
Catch::Session session;
|
||||
|
||||
if (const int catch_parse_result = session.applyCommandLine(static_cast<int>(catch_argv.size()), catch_argv.data());
|
||||
catch_parse_result != 0) {
|
||||
return catch_parse_result;
|
||||
}
|
||||
|
||||
mfem::Mpi::Init(argc, argv);
|
||||
|
||||
std::uint32_t synchronized_seed = session.configData().rngSeed;
|
||||
MPI_Bcast(&synchronized_seed, 1, MPI_UINT32_T, 0, MPI_COMM_WORLD);
|
||||
session.configData().rngSeed = synchronized_seed;
|
||||
|
||||
constexpr std::string device_config = "cpu";
|
||||
mfem::Device device(device_config);
|
||||
|
||||
const int hdiv_max_q1d = mfem::DeviceDofQuadLimits::Get().HDIV_MAX_Q1D;
|
||||
if (mfem::Mpi::Root()) {
|
||||
std::cout << "H(div) maximum Q1D = " << hdiv_max_q1d << '\n';
|
||||
std::cout << "Approximate maximum safe integration order = " << 2 * hdiv_max_q1d - 1 << '\n';
|
||||
}
|
||||
|
||||
mean_field::utils::Args test_args = cfg.main();
|
||||
|
||||
if (app.count("--mesh_file") == 0) {
|
||||
test_args.mesh_file = "sandbox.smesh";
|
||||
}
|
||||
|
||||
if (app.count("--p.rtol") == 0) {
|
||||
test_args.p.rtol = 1.0e-12;
|
||||
}
|
||||
|
||||
if (app.count("--p.atol") == 0) {
|
||||
test_args.p.atol = 1.0e-12;
|
||||
}
|
||||
|
||||
test_utils::set_args(std::move(test_args));
|
||||
|
||||
return session.run();
|
||||
}
|
||||
Reference in New Issue
Block a user