#include #include #include #include #include #include #include #include #include #include #include import mean_field; import test_helpers; using namespace mean_field; namespace { template concept HasRuntimeSpaceOrders = requires(T value) { value.space_orders; }; static_assert(mean_field::field::FieldTag); static_assert(mean_field::field::FieldTag); static_assert(mean_field::field::FieldTag); static_assert(mean_field::field::FieldTag); static_assert(mean_field::field::FieldTag); static_assert(field::Gravity::constraintsAreValid); static_assert(std::same_as< field::RelationTargetT, field::Gravity::Potential>); static_assert(!HasRuntimeSpaceOrders); std::string_view get_term_name(const quadrature::Term term) { switch (term) { case quadrature::Term::gravity_hdiv_mass: return "gravity_hdiv_mass"; case quadrature::Term::gravity_divergence: return "gravity_divergence"; case quadrature::Term::gravity_source: return "gravity_source"; case quadrature::Term::gravity_force: return "gravity_force"; case quadrature::Term::gravity_boundary: return "gravity_boundary"; case quadrature::Term::centrifugal: return "centrifugal"; case quadrature::Term::density_projection: return "density_projection"; case quadrature::Term::eos_closure: return "eos_closure"; case quadrature::Term::hydrostatic_equilibrium: return "hydrostatic_equilibrium"; case quadrature::Term::isobaric_surface: return "isobaric_surface"; case quadrature::Term::mesh_extension: return "mesh_extension"; case quadrature::Term::mass_conservation: return "mass_conservation"; case quadrature::Term::mass_normalization: return "mass_normalization"; case quadrature::Term::center_of_mass: return "center_of_mass"; case quadrature::Term::quadrupole: return "quadrupole"; case quadrature::Term::gravitational_energy: return "gravitational_energy"; case quadrature::Term::pressure_integral: return "pressure_integral"; case quadrature::Term::virial: return "virial"; case quadrature::Term::error_norm: return "error_norm"; case quadrature::Term::pressure_force: return "pressure_force"; } return "unknown"; } quadrature::Query make_query( const quadrature::Term term, const int base_order ) { return {.term = term, .base_order = base_order}; } template concept CanMakeFec = requires { FieldT::template make_fec(3); }; } // namespace TEST_CASE( "Quadrature Policy Computes Base Orders", tags::unit &tags::quadrature ) { const quadrature::Policy policy(quadrature::make_rule_set(quadrature::Mode::production)); quadrature::Query generic_query = { .term = quadrature::Term::gravitational_energy, .trial_order = 3, .test_order = 4, .coefficient_order = 2, .geometry_weight_order = 5 }; const quadrature::Resolution generic_resolution = policy.resolve(generic_query); CHECK(generic_resolution.base_order == 14); CHECK(generic_resolution.boost == 0); CHECK(generic_resolution.order == 14); CHECK_FALSE(generic_resolution.used_fixed_order); quadrature::Query divergence_query = { .term = quadrature::Term::gravity_divergence, .trial_order = 3, .test_order = 2, .coefficient_order = 1, .geometry_weight_order = 4 }; CHECK(policy.resolve(divergence_query).base_order == 9); divergence_query.trial_order = 0; CHECK(policy.resolve(divergence_query).base_order == 7); quadrature::Query explicit_query = { .term = quadrature::Term::gravity_hdiv_mass, .trial_order = 20, .test_order = 20, .coefficient_order = 20, .geometry_weight_order = 20, .base_order = 11 }; CHECK(policy.resolve(explicit_query).base_order == 11); CHECK(policy.resolve(explicit_query).order == 11); } TEST_CASE( "Quadrature Policy Composes Global and Term Boosts", tags::unit &tags::quadrature ) { quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 3); rule_set.gravity_hdiv_mass.boost = 5; rule_set.error_norm.boost = 2; const quadrature::Policy policy(rule_set); const quadrature::Resolution mass_resolution = policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 7)); CHECK(mass_resolution.base_order == 7); CHECK(mass_resolution.boost == 8); CHECK(mass_resolution.order == 15); CHECK_FALSE(mass_resolution.used_fixed_order); const quadrature::Resolution error_resolution = policy.resolve(make_query(quadrature::Term::error_norm, 7)); CHECK(error_resolution.boost == 5); CHECK(error_resolution.order == 12); const quadrature::Resolution source_resolution = policy.resolve(make_query(quadrature::Term::gravity_source, 7)); CHECK(source_resolution.boost == 3); CHECK(source_resolution.order == 10); } TEST_CASE( "Quadrature Fixed Orders Have Defined Precedence", tags::unit &tags::quadrature ) { quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 4); rule_set.fallback.fixed_order = 17; rule_set.gravity_hdiv_mass.fixed_order = 23; rule_set.gravity_hdiv_mass.boost = 100; rule_set.gravity_source.boost = 100; const quadrature::Policy policy(rule_set); const quadrature::Resolution term_resolution = policy.resolve(make_query(quadrature::Term::gravity_hdiv_mass, 8)); CHECK(term_resolution.base_order == 8); CHECK(term_resolution.boost == 0); CHECK(term_resolution.order == 23); CHECK(term_resolution.used_fixed_order); const quadrature::Resolution fallback_resolution = policy.resolve(make_query(quadrature::Term::gravity_source, 8)); CHECK(fallback_resolution.base_order == 8); CHECK(fallback_resolution.boost == 0); CHECK(fallback_resolution.order == 17); CHECK(fallback_resolution.used_fixed_order); } TEST_CASE( "Quadrature Modes Apply Their Expected Baseline Boosts", tags::unit &tags::quadrature ) { constexpr int base_order = 6; constexpr int global_boost = 3; for (const quadrature::Mode mode : {quadrature::Mode::fast, quadrature::Mode::production, quadrature::Mode::convergence}) { const quadrature::Policy policy(quadrature::make_rule_set(mode, global_boost)); const quadrature::Resolution resolution = policy.resolve(make_query(quadrature::Term::error_norm, base_order)); CHECK(resolution.boost == global_boost); CHECK(resolution.order == base_order + global_boost); } const quadrature::Policy reference_policy(quadrature::make_rule_set(quadrature::Mode::reference, global_boost)); const quadrature::Resolution reference_resolution = reference_policy.resolve(make_query(quadrature::Term::error_norm, base_order)); CHECK(reference_resolution.boost == global_boost + 8); CHECK(reference_resolution.order == base_order + global_boost + 8); } TEST_CASE( "Quadrature Policy Routes Every Term to Its Control", tags::unit &tags::quadrature ) { quadrature::RuleSet rule_set; rule_set.gravity_hdiv_mass.boost = 1; rule_set.gravity_divergence.boost = 2; rule_set.gravity_source.boost = 3; rule_set.gravity_force.boost = 19; rule_set.gravity_boundary.boost = 4; rule_set.centrifugal.boost = 18; rule_set.density_projection.boost = 5; rule_set.eos_closure.boost = 6; rule_set.hydrostatic_equilibrium.boost = 7; rule_set.isobaric_surface.boost = 8; rule_set.mesh_extension.boost = 9; rule_set.mass_conservation.boost = 10; rule_set.mass_normalization.boost = 11; rule_set.center_of_mass.boost = 12; rule_set.quadrupole.boost = 13; rule_set.gravitational_energy.boost = 14; rule_set.pressure_integral.boost = 15; rule_set.virial.boost = 16; rule_set.error_norm.boost = 17; const quadrature::Policy policy(rule_set); const std::array, 19> cases = { {{quadrature::Term::gravity_hdiv_mass, 1}, {quadrature::Term::gravity_divergence, 2}, {quadrature::Term::gravity_source, 3}, {quadrature::Term::gravity_force, 19}, {quadrature::Term::gravity_boundary, 4}, {quadrature::Term::centrifugal, 18}, {quadrature::Term::density_projection, 5}, {quadrature::Term::eos_closure, 6}, {quadrature::Term::hydrostatic_equilibrium, 7}, {quadrature::Term::isobaric_surface, 8}, {quadrature::Term::mesh_extension, 9}, {quadrature::Term::mass_conservation, 10}, {quadrature::Term::mass_normalization, 11}, {quadrature::Term::center_of_mass, 12}, {quadrature::Term::quadrupole, 13}, {quadrature::Term::gravitational_energy, 14}, {quadrature::Term::pressure_integral, 15}, {quadrature::Term::virial, 16}, {quadrature::Term::error_norm, 17}} }; for (const auto &[term, expected_boost] : cases) { DYNAMIC_SECTION(get_term_name(term)) { const quadrature::Resolution resolution = policy.resolve(make_query(term, 20)); CHECK(resolution.boost == expected_boost); CHECK(resolution.order == 20 + expected_boost); } } } TEST_CASE( "Quadrature Policy Rejects Invalid Orders", tags::unit &tags::quadrature ) { const quadrature::Policy policy(quadrature::make_rule_set(quadrature::Mode::production)); quadrature::Query negative_component_query = {.term = quadrature::Term::error_norm, .trial_order = -1}; REQUIRE_THROWS_AS(policy.resolve(negative_component_query), std::invalid_argument); quadrature::Query negative_base_query = {.term = quadrature::Term::error_norm, .base_order = -1}; REQUIRE_THROWS_AS(policy.resolve(negative_base_query), std::invalid_argument); quadrature::RuleSet negative_fixed_rule_set; negative_fixed_rule_set.error_norm.fixed_order = -1; const quadrature::Policy negative_fixed_policy(negative_fixed_rule_set); REQUIRE_THROWS_AS( negative_fixed_policy.resolve(make_query(quadrature::Term::error_norm, 3)), std::invalid_argument ); quadrature::RuleSet negative_resolved_rule_set; negative_resolved_rule_set.fallback.boost = -4; const quadrature::Policy negative_resolved_policy(negative_resolved_rule_set); REQUIRE_THROWS_AS( negative_resolved_policy.resolve(make_query(quadrature::Term::error_norm, 3)), std::invalid_argument ); } TEST_CASE( "MFEM Rule Factory Returns the Resolved Rule", tags::unit &tags::quadrature ) { quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production, 2); rule_set.error_norm.boost = 3; const quadrature::RuleFactory factory{quadrature::Policy(rule_set)}; const quadrature::MfemRule selected_rule = factory.get(make_query(quadrature::Term::error_norm, 4), mfem::Geometry::CUBE); const mfem::IntegrationRule &expected_rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 9); REQUIRE(selected_rule.integration_rule != nullptr); CHECK(selected_rule.resolution.base_order == 4); CHECK(selected_rule.resolution.boost == 5); CHECK(selected_rule.resolution.order == 9); CHECK(selected_rule.integration_rule == &expected_rule); CHECK(selected_rule.integration_rule->GetNPoints() > 0); } TEST_CASE( "MFEM Quadrature Rule Integrates Tensor Polynomial Exactly", tags::unit &tags::quadrature ) { constexpr int polynomial_degree = 7; const quadrature::RuleFactory factory{quadrature::Policy(quadrature::make_rule_set(quadrature::Mode::production))}; const quadrature::MfemRule selected_rule = factory.get(make_query(quadrature::Term::error_norm, polynomial_degree), mfem::Geometry::CUBE); double numerical_integral = 0.0; for (int i = 0; i < selected_rule.integration_rule->GetNPoints(); ++i) { const mfem::IntegrationPoint &integration_point = selected_rule.integration_rule->IntPoint(i); numerical_integral += integration_point.weight * std::pow(integration_point.x, polynomial_degree) * std::pow(integration_point.y, polynomial_degree) * std::pow(integration_point.z, polynomial_degree); } const double one_dimensional_integral = 1.0 / static_cast(polynomial_degree + 1); const double analytic_integral = one_dimensional_integral * one_dimensional_integral * one_dimensional_integral; CHECK_THAT(numerical_integral, Catch::Matchers::WithinAbs(analytic_integral, 5.0e-14)); } TEST_CASE( "Policy Controlled Hdiv Mass Assembly Matches Overintegrated Reference", tags::quadrature &tags::solver &tags::integration ) { mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::RT_FECollection rt_collection(2, 3); mfem::FiniteElementSpace rt_space(&mesh, &rt_collection); const mfem::FiniteElement *rt_element = rt_space.GetTypicalFE(); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); const int base_order = 2 * rt_element->GetOrder() + transformation->OrderW(); const quadrature::RuleFactory production_factory{ quadrature::Policy(quadrature::make_rule_set(quadrature::Mode::production)) }; const quadrature::MfemRule production_rule = production_factory.get(make_query(quadrature::Term::gravity_hdiv_mass, base_order), rt_element->GetGeomType()); quadrature::RuleSet reference_rule_set = quadrature::make_rule_set(quadrature::Mode::production); reference_rule_set.gravity_hdiv_mass.boost = 8; const quadrature::RuleFactory reference_factory{quadrature::Policy(reference_rule_set)}; const quadrature::MfemRule reference_rule = reference_factory.get(make_query(quadrature::Term::gravity_hdiv_mass, base_order), rt_element->GetGeomType()); mfem::BilinearForm production_mass(&rt_space); auto *production_integrator = new mfem::VectorFEMassIntegrator(); production_integrator->SetIntegrationRule(*production_rule.integration_rule); production_mass.AddDomainIntegrator(production_integrator); production_mass.Assemble(); production_mass.Finalize(); mfem::BilinearForm reference_mass(&rt_space); auto *reference_integrator = new mfem::VectorFEMassIntegrator(); reference_integrator->SetIntegrationRule(*reference_rule.integration_rule); reference_mass.AddDomainIntegrator(reference_integrator); reference_mass.Assemble(); reference_mass.Finalize(); mfem::Vector input(rt_space.GetVSize()); mfem::Vector production_output(rt_space.GetVSize()); mfem::Vector reference_output(rt_space.GetVSize()); for (int i = 0; i < input.Size(); ++i) { input(i) = std::sin(0.37 * static_cast(i + 1)); } production_mass.Mult(input, production_output); reference_mass.Mult(input, reference_output); mfem::Vector difference(production_output); difference -= reference_output; const double relative_difference = difference.Norml2() / reference_output.Norml2(); INFO("Production quadrature order = " << production_rule.resolution.order); INFO("Reference quadrature order = " << reference_rule.resolution.order); INFO("Relative operator difference = " << relative_difference); CHECK_THAT(relative_difference, Catch::Matchers::WithinAbs(0.0, 1.0e-12)); } TEST_CASE( "HDiv Mass Helper Resolves the MFEM Baseline", tags::unit &tags::quadrature &tags::solver ) { mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON); mfem::RT_FECollection rt_collection(2, 3); mfem::FiniteElementSpace rt_space(&mesh, &rt_collection); quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); rule_set.gravity_hdiv_mass.boost = 3; quadrature::RuleFactory factory{quadrature::Policy(std::move(rule_set))}; const mfem::FiniteElement &element = *rt_space.GetTypicalFE(); const mfem::ElementTransformation &transformation = *mesh.GetElementTransformation(0); mfem::VectorFEMassIntegrator integrator; const quadrature::Resolution resolution = factory.configure_gravity_hdiv_mass( integrator, quadrature::QuadratureRole::discretization, element, transformation ); const int expected_base_order = 2 * element.GetOrder() + transformation.OrderW(); CHECK(resolution.base_order == expected_base_order); CHECK(resolution.boost == 3); CHECK(resolution.order == expected_base_order + 3); } TEST_CASE( "Gravity Divergence Helper Resolves Preconditioner Rule", tags::unit &tags::quadrature &tags::solver ) { mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON); mfem::RT_FECollection rt_collection(2, 3); mfem::L2_FECollection l2_collection(2, 3); mfem::FiniteElementSpace rt_space(&mesh, &rt_collection); mfem::FiniteElementSpace l2_space(&mesh, &l2_collection); quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); rule_set.gravity_divergence.boost = 2; rule_set.roles.preconditioner.boost = 3; quadrature::RuleFactory factory{quadrature::Policy(std::move(rule_set))}; const mfem::FiniteElement &trial_element = *rt_space.GetTypicalFE(); const mfem::FiniteElement &test_element = *l2_space.GetTypicalFE(); const mfem::ElementTransformation &transformation = *mesh.GetElementTransformation(0); mfem::VectorFEDivergenceIntegrator integrator; const quadrature::Resolution resolution = factory.configure_gravity_divergence( integrator, quadrature::QuadratureRole::preconditioner, trial_element, test_element, transformation ); const int expected_base_order = std::max(0, trial_element.GetOrder() - 1) + test_element.GetOrder() + transformation.OrderW(); CHECK(resolution.base_order == expected_base_order); CHECK(resolution.boost == 5); CHECK(resolution.order == expected_base_order + 5); } TEST_CASE( "Field Registry Encodes Spaces Orders And Stable Pair Constraints", tags::unit &tags::quadrature &tags::initialization ) { CHECK(mean_field::field::Density::Scalar::familyOrder == 2); CHECK(mean_field::field::Gravity::Potential::familyOrder == 2); CHECK(mean_field::field::Gravity::Flux::familyOrder == 2); CHECK(mean_field::field::Displacement::Vector::familyOrder == 3); CHECK(mean_field::field::Enthalpy::Scalar::familyOrder == 3); CHECK((std::same_as)); CHECK((std::same_as)); CHECK((std::same_as)); CHECK((std::same_as)); CHECK((std::same_as)); CHECK(mean_field::field::Density::Scalar::rankValue == 0); CHECK(mean_field::field::Gravity::Potential::rankValue == 0); CHECK(mean_field::field::Gravity::Flux::rankValue == 1); CHECK(mean_field::field::Displacement::Vector::rankValue == 1); CHECK(mean_field::field::Enthalpy::Scalar::rankValue == 0); CHECK(mean_field::field::Gravity::Flux::familyOrder == mean_field::field::Gravity::Potential::familyOrder); CHECK(std::is_empty_v>); CHECK(std::is_empty_v>); CHECK(std::is_empty_v>); CHECK(std::is_empty_v>); STATIC_CHECK(field::RegisteredQuantity); STATIC_CHECK(field::GlobalScalarQuantity); STATIC_CHECK_FALSE(field::FieldQuantity); STATIC_CHECK(field::BarotropicConstant::Scalar::storageKind == field::StorageKind::global_scalar); STATIC_CHECK(field::BarotropicConstant::Scalar::staticBlockSize == 1); STATIC_CHECK(field::Enthalpy::Scalar::storageKind == field::StorageKind::finite_element); STATIC_CHECK(field::Enthalpy::Scalar::staticBlockSize == field::dynamicBlockSize); STATIC_CHECK_FALSE(CanMakeFec, field::BarotropicConstant::Scalar>); } TEST_CASE( "Field Forms Produce Typed Quadrature Queries", tags::unit &tags::quadrature ) { using GravityField = field::Field; using DensityField = field::Field; using EnthalpyField = field::Field; constexpr quadrature::Query hdiv_query = GravityField::make_query( quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::ALL, quadrature::MappingKind::general ); constexpr quadrature::Query divergence_query = GravityField::make_query( quadrature::QuadratureRole::preconditioner, 2 ); constexpr quadrature::Query source_query = GravityField::make_query( quadrature::QuadratureRole::projection, 2, {}, utils::DOMAINS::STELLAR ); constexpr quadrature::Query center_of_mass_query = DensityField::make_query( quadrature::QuadratureRole::diagnostic, 2, std::array{1}, utils::DOMAINS::STELLAR ); constexpr quadrature::Query eos_closure_query = EnthalpyField::make_query( quadrature::QuadratureRole::discretization, 2, std::array{6}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query equilibrium_gravity_query = EnthalpyField::make_query( quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query equilibrium_constant_query = EnthalpyField::make_query( quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query rotation_query = EnthalpyField::make_query( quadrature::QuadratureRole::discretization, 2, std::array{2}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query isobaric_surface_query = EnthalpyField::make_query( quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query mesh_extension_query = field::Field::make_query( quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::ALL, quadrature::MappingKind::general ); constexpr quadrature::Query mass_normalization_query = DensityField::make_query( quadrature::QuadratureRole::discretization, 2, {}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); constexpr quadrature::Query pressure_integral_query = EnthalpyField::make_query( quadrature::QuadratureRole::diagnostic, 2, std::array{9}, utils::DOMAINS::STELLAR, quadrature::MappingKind::general ); STATIC_CHECK(mean_field::field::Gravity::Form::SourceProjection::dynamicOrderCount == 0); STATIC_CHECK(hdiv_query.base_order.has_value()); STATIC_CHECK(*hdiv_query.base_order == 8); STATIC_CHECK(*divergence_query.base_order == 6); STATIC_CHECK(*source_query.base_order == 6); STATIC_CHECK(*center_of_mass_query.base_order == 5); STATIC_CHECK(*eos_closure_query.base_order == 13); STATIC_CHECK(*equilibrium_gravity_query.base_order == 7); STATIC_CHECK(*rotation_query.base_order == 7); STATIC_CHECK(*isobaric_surface_query.base_order == 8); STATIC_CHECK(*mesh_extension_query.base_order == 6); STATIC_CHECK(*mass_normalization_query.base_order == 4); STATIC_CHECK(*pressure_integral_query.base_order == 14); STATIC_CHECK(*equilibrium_constant_query.base_order == 5); CHECK(equilibrium_constant_query.term == quadrature::Term::hydrostatic_equilibrium); CHECK(hdiv_query.term == mean_field::quadrature::Term::gravity_hdiv_mass); CHECK(hdiv_query.role == mean_field::quadrature::QuadratureRole::discretization); CHECK(hdiv_query.domain == mean_field::utils::DOMAINS::ALL); CHECK(hdiv_query.mapping == mean_field::quadrature::MappingKind::general); CHECK(source_query.term == mean_field::quadrature::Term::gravity_source); CHECK(center_of_mass_query.term == mean_field::quadrature::Term::center_of_mass); CHECK(eos_closure_query.term == mean_field::quadrature::Term::eos_closure); CHECK(equilibrium_gravity_query.term == mean_field::quadrature::Term::hydrostatic_equilibrium); CHECK(rotation_query.term == mean_field::quadrature::Term::hydrostatic_equilibrium); CHECK(isobaric_surface_query.term == mean_field::quadrature::Term::isobaric_surface); CHECK(mesh_extension_query.term == mean_field::quadrature::Term::mesh_extension); CHECK(mass_normalization_query.term == mean_field::quadrature::Term::mass_normalization); CHECK(pressure_integral_query.term == mean_field::quadrature::Term::pressure_integral); CHECK_THROWS_AS( (GravityField::make_query( mean_field::quadrature::QuadratureRole::discretization, -1 )), std::invalid_argument ); CHECK_THROWS_AS( (DensityField::make_query( mean_field::quadrature::QuadratureRole::diagnostic, 2, std::array{-1} )), std::invalid_argument ); } TEST_CASE( "Field Factories Construct The Registered MFEM Collections", tags::unit &tags::quadrature &tags::initialization ) { using GravityField = field::Field; using DisplacementField = field::Field; using DensityField = field::Field; using EnthalpyField = field::Field; std::unique_ptr density_collection = DensityField::make_fec(3); std::unique_ptr potential_collection = GravityField::make_fec(3); std::unique_ptr flux_collection = GravityField::make_fec(3); std::unique_ptr displacement_collection = DisplacementField::make_fec(3); std::unique_ptr enthalpy_collection = EnthalpyField::make_fec(3); CHECK(dynamic_cast(density_collection.get()) != nullptr); CHECK(dynamic_cast(potential_collection.get()) != nullptr); CHECK(dynamic_cast(flux_collection.get()) != nullptr); CHECK(dynamic_cast(displacement_collection.get()) != nullptr); CHECK(dynamic_cast(enthalpy_collection.get()) != nullptr); CHECK_THROWS_AS((DensityField::make_fec(0)), std::invalid_argument); } TEST_CASE( "FEM Setup Realizes Every Registered Field Independently", tags::integration &tags::initialization &tags::solver ) { const utils::Args args = test_utils::setup_args(); const fem::FEM fem = fem::setup_fem(args.mesh_file, args, 0); REQUIRE(fem.okay()); REQUIRE(fem.mesh != nullptr); REQUIRE(fem.densityFes != nullptr); REQUIRE(fem.gravityPotentialFes != nullptr); REQUIRE(fem.gravityFluxFes != nullptr); REQUIRE(fem.displacementFes != nullptr); REQUIRE(fem.enthalpyFes != nullptr); CHECK(fem.densityFes.get() != fem.gravityPotentialFes.get()); CHECK(fem.densityFec.get() != fem.gravityPotentialFec.get()); CHECK(fem.densityFes->GetMaxElementOrder() == mean_field::field::Density::Scalar::familyOrder); CHECK(fem.gravityPotentialFes->GetMaxElementOrder() == mean_field::field::Gravity::Potential::familyOrder); CHECK(fem.gravityFluxFes->GetMaxElementOrder() == mean_field::field::Gravity::Flux::familyOrder + 1); CHECK(fem.displacementFes->GetMaxElementOrder() == mean_field::field::Displacement::Vector::familyOrder); CHECK(fem.enthalpyFes->GetMaxElementOrder() == mean_field::field::Enthalpy::Scalar::familyOrder); CHECK(fem.densityFes->GetVDim() == 1); CHECK(fem.gravityPotentialFes->GetVDim() == 1); CHECK(fem.gravityFluxFes->GetVDim() == 1); CHECK(fem.displacementFes->GetVDim() == fem.mesh->SpaceDimension()); CHECK(fem.displacementFes->GetOrdering() == mfem::Ordering::byNODES); CHECK(fem.enthalpyFes->GetVDim() == 1); using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema; const mean_field::field::FieldDofMap densityMap = mean_field::field::make_field_dof_map(*fem.densityFes); const mean_field::field::FieldDofMap displacementMap = mean_field::field::make_field_dof_map(*fem.displacementFes); CHECK(densityMap.full_size() == fem.densityFes->GetTrueVSize()); CHECK(densityMap.reduced_size() < densityMap.full_size()); CHECK(displacementMap.full_size() == fem.displacementFes->GetTrueVSize()); CHECK(displacementMap.reduced_size() == displacementMap.full_size()); }