feat(libmeanfield): variadic refactor
also added normaliztion operator
This commit is contained in:
@@ -103,7 +103,7 @@ target_sources(mean_field
|
|||||||
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
|
libmeanfield/impl/solver/preconditioning_diagnostics.cpp
|
||||||
libmeanfield/impl/preconditioning/gravity_field.cpp
|
libmeanfield/impl/preconditioning/gravity_field.cpp
|
||||||
libmeanfield/impl/operators/prepared_mass_normalization.cpp
|
libmeanfield/impl/operators/prepared_mass_normalization.cpp
|
||||||
libmeanfield/impl/operators/prepared_central_density_stellar_equilibrium.cpp
|
libmeanfield/impl/operators/prepared_angular_momentum.cpp
|
||||||
libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp
|
libmeanfield/impl/operators/prepared_stellar_equilibrium.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -156,6 +156,11 @@ target_sources(mean_field
|
|||||||
libmeanfield/interface/preconditioning/specification_border.cppm
|
libmeanfield/interface/preconditioning/specification_border.cppm
|
||||||
libmeanfield/interface/preconditioning/equilibrium_coordinates.cppm
|
libmeanfield/interface/preconditioning/equilibrium_coordinates.cppm
|
||||||
libmeanfield/interface/preconditioning/preconditioning.cppm
|
libmeanfield/interface/preconditioning/preconditioning.cppm
|
||||||
|
libmeanfield/interface/normalization/plan.cppm
|
||||||
|
libmeanfield/interface/normalization/physical_riesz.cppm
|
||||||
|
libmeanfield/interface/normalization/operators.cppm
|
||||||
|
libmeanfield/interface/normalization/stellar_equilibrium.cppm
|
||||||
|
libmeanfield/interface/normalization/normalization.cppm
|
||||||
libmeanfield/interface/operators/gravity_field.cppm
|
libmeanfield/interface/operators/gravity_field.cppm
|
||||||
libmeanfield/interface/operators/gravity_field_jacobian.cppm
|
libmeanfield/interface/operators/gravity_field_jacobian.cppm
|
||||||
libmeanfield/interface/operators/kernels/gravity_kernels.cppm
|
libmeanfield/interface/operators/kernels/gravity_kernels.cppm
|
||||||
@@ -197,6 +202,7 @@ target_sources(mean_field
|
|||||||
libmeanfield/interface/models/specifications.cppm
|
libmeanfield/interface/models/specifications.cppm
|
||||||
libmeanfield/interface/models/typed_stellar_model.cppm
|
libmeanfield/interface/models/typed_stellar_model.cppm
|
||||||
libmeanfield/interface/models/compiled_fixed_mass.cppm
|
libmeanfield/interface/models/compiled_fixed_mass.cppm
|
||||||
|
libmeanfield/interface/models/compiled_fixed_angular_momentum.cppm
|
||||||
libmeanfield/interface/models/compiled_fixed_central_density.cppm
|
libmeanfield/interface/models/compiled_fixed_central_density.cppm
|
||||||
libmeanfield/interface/surface/constant.cppm
|
libmeanfield/interface/surface/constant.cppm
|
||||||
libmeanfield/interface/surface/dependencies.cppm
|
libmeanfield/interface/surface/dependencies.cppm
|
||||||
@@ -214,11 +220,13 @@ target_sources(mean_field
|
|||||||
libmeanfield/interface/operators/root_manifest.cppm
|
libmeanfield/interface/operators/root_manifest.cppm
|
||||||
libmeanfield/interface/operators/prepared_constraint.cppm
|
libmeanfield/interface/operators/prepared_constraint.cppm
|
||||||
libmeanfield/interface/operators/prepared_mass_normalization.cppm
|
libmeanfield/interface/operators/prepared_mass_normalization.cppm
|
||||||
|
libmeanfield/interface/operators/prepared_angular_momentum.cppm
|
||||||
libmeanfield/interface/operators/prepared_central_density.cppm
|
libmeanfield/interface/operators/prepared_central_density.cppm
|
||||||
libmeanfield/interface/operators/prepared_centering_constraint.cppm
|
libmeanfield/interface/operators/prepared_centering_constraint.cppm
|
||||||
libmeanfield/interface/operators/prepared_surface_constraint.cppm
|
libmeanfield/interface/operators/prepared_surface_constraint.cppm
|
||||||
libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm
|
libmeanfield/interface/operators/prepared_stellar_equilibrium.cppm
|
||||||
libmeanfield/interface/operators/prepared_central_density_stellar_equilibrium.cppm
|
libmeanfield/interface/operators/stellar_equilibrium_compiler.cppm
|
||||||
|
libmeanfield/interface/operators/prepared_variadic_stellar_equilibrium.cppm
|
||||||
libmeanfield/interface/equilibrium/stellar_discretization.cppm
|
libmeanfield/interface/equilibrium/stellar_discretization.cppm
|
||||||
libmeanfield/interface/operators/stellar_equilibrium_problem.cppm
|
libmeanfield/interface/operators/stellar_equilibrium_problem.cppm
|
||||||
libmeanfield/interface/seed/stellar_equilibrium_projection.cppm
|
libmeanfield/interface/seed/stellar_equilibrium_projection.cppm
|
||||||
@@ -303,10 +311,12 @@ add_executable(tests
|
|||||||
tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp
|
tests/operators/prepared_rotation_displacement_force_affine_deformation.cpp
|
||||||
tests/operators/prepared_displacement_operator.cpp
|
tests/operators/prepared_displacement_operator.cpp
|
||||||
tests/operators/root_manifest.cpp
|
tests/operators/root_manifest.cpp
|
||||||
|
tests/operators/stellar_equilibrium_compiler.cpp
|
||||||
tests/operators/prepared_central_density.cpp
|
tests/operators/prepared_central_density.cpp
|
||||||
tests/operators/prepared_central_density_stellar_equilibrium.cpp
|
tests/operators/prepared_central_density_stellar_equilibrium.cpp
|
||||||
tests/models/model_specifications.cpp
|
tests/models/model_specifications.cpp
|
||||||
tests/models/typed_stellar_model.cpp
|
tests/models/typed_stellar_model.cpp
|
||||||
|
tests/models/physics_specification_frontend.cpp
|
||||||
tests/models/stellar_model.cpp
|
tests/models/stellar_model.cpp
|
||||||
tests/operators/stellar_equilibrium_system.cpp
|
tests/operators/stellar_equilibrium_system.cpp
|
||||||
tests/deformation/contracts.cpp
|
tests/deformation/contracts.cpp
|
||||||
@@ -315,6 +325,7 @@ add_executable(tests
|
|||||||
tests/deformation/radial_extensions.cpp
|
tests/deformation/radial_extensions.cpp
|
||||||
tests/deformation/domain_deformation.cpp
|
tests/deformation/domain_deformation.cpp
|
||||||
tests/operators/prepared_mass_normalization.cpp
|
tests/operators/prepared_mass_normalization.cpp
|
||||||
|
tests/operators/prepared_angular_momentum.cpp
|
||||||
tests/operators/prepared_stellar_equilibrium.cpp
|
tests/operators/prepared_stellar_equilibrium.cpp
|
||||||
tests/utils/domain.cpp
|
tests/utils/domain.cpp
|
||||||
tests/field/field_base.cpp
|
tests/field/field_base.cpp
|
||||||
@@ -327,8 +338,12 @@ add_executable(tests
|
|||||||
tests/preconditioning/material_surface.cpp
|
tests/preconditioning/material_surface.cpp
|
||||||
tests/preconditioning/stellar_structure.cpp
|
tests/preconditioning/stellar_structure.cpp
|
||||||
tests/preconditioning/specification_border.cpp
|
tests/preconditioning/specification_border.cpp
|
||||||
|
tests/extensions/fixed_magnetic_specific_energy.cpp
|
||||||
tests/preconditioning/equilibrium_coordinates.cpp
|
tests/preconditioning/equilibrium_coordinates.cpp
|
||||||
tests/preconditioning/stellar_equilibrium.cpp
|
tests/preconditioning/stellar_equilibrium.cpp
|
||||||
|
tests/normalization/plan.cpp
|
||||||
|
tests/normalization/physical_riesz.cpp
|
||||||
|
tests/normalization/stellar_equilibrium.cpp
|
||||||
tests/user-api/stellar_equilibrium.cpp
|
tests/user-api/stellar_equilibrium.cpp
|
||||||
tests/solver/preconditioning_diagnostics.cpp
|
tests/solver/preconditioning_diagnostics.cpp
|
||||||
)
|
)
|
||||||
@@ -429,3 +444,8 @@ add_custom_target(
|
|||||||
DEPENDS mpi_tests
|
DEPENDS mpi_tests
|
||||||
USES_TERMINAL
|
USES_TERMINAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# A deliberately separate, physics-developer-facing example. Its targets
|
||||||
|
# depend on MeanField, but none of its sources are part of the mean_field
|
||||||
|
# library or the main regression-test executable.
|
||||||
|
add_subdirectory(extension_example)
|
||||||
|
|||||||
@@ -228,8 +228,7 @@ TEST_CASE(
|
|||||||
announce(communicator, "P0 extended baseline: preparing the complete equilibrium operator");
|
announce(communicator, "P0 extended baseline: preparing the complete equilibrium operator");
|
||||||
|
|
||||||
const Clock::time_point operatorPreparationStart = Clock::now();
|
const Clock::time_point operatorPreparationStart = Clock::now();
|
||||||
const operators::PreparedCentralDensityStellarEquilibriumReport preparation =
|
const auto preparation = problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
|
||||||
problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
|
|
||||||
REQUIRE(preparation.assembledResidual);
|
REQUIRE(preparation.assembledResidual);
|
||||||
const double operatorPreparationSeconds = maximum_rank_seconds(operatorPreparationStart, communicator);
|
const double operatorPreparationSeconds = maximum_rank_seconds(operatorPreparationStart, communicator);
|
||||||
|
|
||||||
|
|||||||
50
extension_example/CMakeLists.txt
Normal file
50
extension_example/CMakeLists.txt
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
add_library(mean_field_extension_example)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
mean_field_extension_example
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET CXX_MODULES FILES
|
||||||
|
ideal_gas_radiation.cppm
|
||||||
|
rotating_stellar_model.cppm
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(mean_field_extension_example PUBLIC mean_field)
|
||||||
|
|
||||||
|
add_executable(extension_example_demo demo.cpp)
|
||||||
|
target_link_libraries(extension_example_demo PRIVATE mean_field_extension_example)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
extension_example_tests
|
||||||
|
tests/ideal_gas_radiation.cpp
|
||||||
|
tests/rotating_stellar_model.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
extension_example_tests
|
||||||
|
PRIVATE
|
||||||
|
mean_field_extension_example
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
|
||||||
|
catch_discover_tests(
|
||||||
|
extension_example_tests
|
||||||
|
TEST_PREFIX "extension_example::"
|
||||||
|
PROPERTIES LABELS "extension-example"
|
||||||
|
)
|
||||||
|
|
||||||
|
find_program(LATEXMK_EXECUTABLE latexmk)
|
||||||
|
if (LATEXMK_EXECUTABLE)
|
||||||
|
add_custom_target(
|
||||||
|
extension_example_manual
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/manual"
|
||||||
|
COMMAND
|
||||||
|
${LATEXMK_EXECUTABLE}
|
||||||
|
-pdf
|
||||||
|
-interaction=nonstopmode
|
||||||
|
-halt-on-error
|
||||||
|
-outdir=${CMAKE_CURRENT_BINARY_DIR}/manual
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/manual/physics_developer_manual.tex"
|
||||||
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/manual"
|
||||||
|
COMMENT "Compiling the MeanField physics developer manual"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
55
extension_example/README.md
Normal file
55
extension_example/README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# MeanField physics extension example
|
||||||
|
|
||||||
|
This directory is a small, isolated example for physicists who want to extend
|
||||||
|
MeanField without first learning its internal block-matrix machinery.
|
||||||
|
|
||||||
|
Start in this order:
|
||||||
|
|
||||||
|
1. Read `ideal_gas_radiation.cppm`. It implements a monatomic ideal gas plus
|
||||||
|
equilibrium radiation using the public EOS relation protocol.
|
||||||
|
2. Read `rotating_stellar_model.cppm`. It composes that EOS with the existing
|
||||||
|
isobaric surface, fixed-total-mass invariant, and fixed-angular-momentum
|
||||||
|
invariant.
|
||||||
|
3. Read and run `demo.cpp`.
|
||||||
|
4. Read the tests. They show which claims should be compile-time contracts and
|
||||||
|
which claims require physical or numerical checks.
|
||||||
|
5. Use `manual/physics_developer_manual.pdf` as the detailed guide. Its LaTeX
|
||||||
|
source is beside it.
|
||||||
|
|
||||||
|
## The important boundary
|
||||||
|
|
||||||
|
`makeRotatingStellarModel(...)` produces a valid, strongly typed stellar-model
|
||||||
|
specification. The current equilibrium numerical core is still barotropic: it
|
||||||
|
expects density to be closed by specific enthalpy alone. An ideal-gas plus
|
||||||
|
radiation EOS depends independently on density and temperature, so a complete
|
||||||
|
thermal equilibrium solve also needs a temperature or entropy field and its
|
||||||
|
governing equation.
|
||||||
|
|
||||||
|
The example therefore proves at compile time that model composition succeeds
|
||||||
|
and that the present discretizer rejects this model. It does not disguise the
|
||||||
|
thermal EOS as a polytrope or claim that a missing energy equation exists.
|
||||||
|
|
||||||
|
## Build only this example
|
||||||
|
|
||||||
|
From the repository root, configure as usual, then build only these targets:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --build cmake-build-profile-homebrew-llvm \
|
||||||
|
--target extension_example_demo extension_example_tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Run only the extension tests:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./cmake-build-profile-homebrew-llvm/extension_example/extension_example_tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile a fresh manual into the build directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --build cmake-build-profile-homebrew-llvm \
|
||||||
|
--target extension_example_manual
|
||||||
|
```
|
||||||
|
|
||||||
|
No source under `libmeanfield/` belongs to this example, and the extension test
|
||||||
|
executable is separate from the main MeanField regression suite.
|
||||||
43
extension_example/demo.cpp
Normal file
43
extension_example/demo.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
import mean_field_extension_example.rotating_stellar_model;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace mean_field;
|
||||||
|
using namespace mean_field::extension_example;
|
||||||
|
|
||||||
|
const IdealGasRadiation equationOfState({
|
||||||
|
.meanMolecularWeight = 0.61,
|
||||||
|
.boltzmannConstant = 1.380649e-16,
|
||||||
|
.atomicMassUnit = 1.66053906660e-24,
|
||||||
|
.radiationConstant = 7.5657e-15
|
||||||
|
});
|
||||||
|
|
||||||
|
const dimensions::DensityValue density{10.0}; // g cm^-3
|
||||||
|
const dimensions::TemperatureValue temperature{1.5e7}; // K
|
||||||
|
const auto pressure = eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
temperature
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto model = makeRotatingStellarModel({
|
||||||
|
.equationOfState = equationOfState.parameters(),
|
||||||
|
.surfacePressure = dimensions::PressureValue{0.0},
|
||||||
|
.totalMass = dimensions::MassValue{1.0},
|
||||||
|
.totalAngularMomentum = dimensions::AngularMomentumValue{0.2}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << std::scientific
|
||||||
|
<< "P(rho = 10 g cm^-3, T = 1.5e7 K) = "
|
||||||
|
<< pressure.value() << " dyn cm^-2\n"
|
||||||
|
<< "Compiled specification count = "
|
||||||
|
<< model.specificationCount << '\n'
|
||||||
|
<< "Current barotropic backend accepts this thermal model = "
|
||||||
|
<< std::boolalpha
|
||||||
|
<< currentEquilibriumBackendSupportsIdealGasRadiation << '\n';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
403
extension_example/ideal_gas_radiation.cppm
Normal file
403
extension_example/ideal_gas_radiation.cppm
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
export module mean_field_extension_example.ideal_gas_radiation;
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is intended to be read from top to bottom by a physicist who is
|
||||||
|
* adding an equation of state (EOS). The comments explain the small amount
|
||||||
|
* of type-system vocabulary required by MeanField; the thermodynamics remain
|
||||||
|
* visible as ordinary equations.
|
||||||
|
*/
|
||||||
|
export namespace mean_field::extension_example {
|
||||||
|
namespace eos_quantity = mean_field::dimensions::quantity;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A relation is only a compile-time sentence:
|
||||||
|
*
|
||||||
|
* output = f(input 1, input 2, ...).
|
||||||
|
*
|
||||||
|
* Input order is significant. These declarations say that density is
|
||||||
|
* the first argument and temperature is the second argument. They do not
|
||||||
|
* allocate data and have no runtime cost.
|
||||||
|
*/
|
||||||
|
using PressureFromDensityAndTemperature = mean_field::eos::Relation<
|
||||||
|
eos_quantity::Pressure,
|
||||||
|
eos_quantity::Density,
|
||||||
|
eos_quantity::Temperature>;
|
||||||
|
|
||||||
|
using SpecificInternalEnergyFromDensityAndTemperature = mean_field::eos::Relation<
|
||||||
|
eos_quantity::SpecificInternalEnergy,
|
||||||
|
eos_quantity::Density,
|
||||||
|
eos_quantity::Temperature>;
|
||||||
|
|
||||||
|
using SpecificEnthalpyFromDensityAndTemperature = mean_field::eos::Relation<
|
||||||
|
eos_quantity::SpecificEnthalpy,
|
||||||
|
eos_quantity::Density,
|
||||||
|
eos_quantity::Temperature>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A monatomic ideal gas plus equilibrium radiation:
|
||||||
|
*
|
||||||
|
* R = k_B / (mu m_u)
|
||||||
|
* P_gas = rho R T
|
||||||
|
* P_rad = a T^4 / 3
|
||||||
|
* u = (3/2) R T + a T^4 / rho
|
||||||
|
* h = u + P/rho
|
||||||
|
* = (5/2) R T + 4 a T^4 / (3 rho)
|
||||||
|
*
|
||||||
|
* The scalar QuantityValue wrappers identify what a number means. They
|
||||||
|
* intentionally do not perform unit conversion. Every number supplied
|
||||||
|
* here must therefore use one coherent unit system.
|
||||||
|
*/
|
||||||
|
class IdealGasRadiation final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
/* Mean particle mass in atomic-mass units. */
|
||||||
|
double meanMolecularWeight{0.61};
|
||||||
|
|
||||||
|
/* CGS defaults: erg K^-1, g, and erg cm^-3 K^-4. */
|
||||||
|
double boltzmannConstant{1.380649e-16};
|
||||||
|
double atomicMassUnit{1.66053906660e-24};
|
||||||
|
double radiationConstant{7.5657e-15};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This one alias makes the EOS a constitutive-law specification that
|
||||||
|
* can be placed directly in model::StellarModel(...). There is no
|
||||||
|
* registry edit and no central list of EOS combinations to maintain.
|
||||||
|
*/
|
||||||
|
using ModelDefinition = mean_field::eos::ConstitutiveLaw<IdealGasRadiation,"IdealGasRadiation">;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The catalog is the complete public claim made by this EOS. If an
|
||||||
|
* evaluate overload below is missing or has the wrong argument order,
|
||||||
|
* eos::EquationOfStateModel<IdealGasRadiation> becomes false at
|
||||||
|
* compile time.
|
||||||
|
*/
|
||||||
|
using Relations = mean_field::eos::RelationCatalog<
|
||||||
|
PressureFromDensityAndTemperature,
|
||||||
|
SpecificInternalEnergyFromDensityAndTemperature,
|
||||||
|
SpecificEnthalpyFromDensityAndTemperature
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct PressureContributions final {
|
||||||
|
mean_field::dimensions::PressureValue gas;
|
||||||
|
mean_field::dimensions::PressureValue radiation;
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::PressureValue total() const noexcept {
|
||||||
|
return gas + radiation;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit IdealGasRadiation(const Parameters parameters)
|
||||||
|
: m_parameters(validatedParameters(parameters)),
|
||||||
|
m_specificGasConstant(
|
||||||
|
m_parameters.boltzmannConstant /(m_parameters.meanMolecularWeight * m_parameters.atomicMassUnit)
|
||||||
|
) {}
|
||||||
|
|
||||||
|
[[nodiscard]] const Parameters ¶meters() const noexcept {
|
||||||
|
return m_parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double specificGasConstant() const noexcept {
|
||||||
|
return m_specificGasConstant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Named component functions are not required by the EOS protocol.
|
||||||
|
* They are provided because they make diagnostics and physics tests
|
||||||
|
* easier to read than repeated algebra in client code.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] PressureContributions pressureContributions(
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
|
||||||
|
const double rho = density.value();
|
||||||
|
const double T = temperature.value();
|
||||||
|
return PressureContributions{
|
||||||
|
.gas = mean_field::dimensions::PressureValue{rho * m_specificGasConstant * T},
|
||||||
|
.radiation = mean_field::dimensions::PressureValue{
|
||||||
|
m_parameters.radiationConstant * fourthPower(T) / 3.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::SpecificInternalEnergyValue gasSpecificInternalEnergy(
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateTemperature(temperature);
|
||||||
|
return mean_field::dimensions::SpecificInternalEnergyValue{
|
||||||
|
1.5 * m_specificGasConstant * temperature.value()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::SpecificInternalEnergyValue radiationSpecificInternalEnergy(
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
return mean_field::dimensions::SpecificInternalEnergyValue{
|
||||||
|
m_parameters.radiationConstant * fourthPower(temperature.value()) / density.value()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The evaluate overloads implement the three declared relations. */
|
||||||
|
[[nodiscard]] mean_field::dimensions::PressureValue evaluate(
|
||||||
|
PressureFromDensityAndTemperature,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
return pressureContributions(density, temperature).total();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::SpecificInternalEnergyValue evaluate(
|
||||||
|
SpecificInternalEnergyFromDensityAndTemperature,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
const auto gas = gasSpecificInternalEnergy(temperature);
|
||||||
|
const auto radiation = radiationSpecificInternalEnergy(density, temperature);
|
||||||
|
return gas + radiation;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
|
||||||
|
SpecificEnthalpyFromDensityAndTemperature,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
|
||||||
|
const double rho = density.value();
|
||||||
|
const double T = temperature.value();
|
||||||
|
return mean_field::dimensions::SpecificEnthalpyValue{
|
||||||
|
2.5 * m_specificGasConstant * T +
|
||||||
|
4.0 * m_parameters.radiationConstant * fourthPower(T) / (3.0 * rho)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Jacobian entries are ordinary analytic partial derivatives. The
|
||||||
|
* WithRespectTo tag prevents accidentally returning dP/dT from the
|
||||||
|
* overload that promised dP/drho.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::Pressure,
|
||||||
|
eos_quantity::Density>
|
||||||
|
partialDerivative(
|
||||||
|
PressureFromDensityAndTemperature,
|
||||||
|
mean_field::eos::WithRespectTo<eos_quantity::Density>,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
return mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::Pressure,
|
||||||
|
eos_quantity::Density>{m_specificGasConstant * temperature.value()};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::Pressure,
|
||||||
|
eos_quantity::Temperature>
|
||||||
|
partialDerivative(
|
||||||
|
PressureFromDensityAndTemperature,
|
||||||
|
mean_field::eos::WithRespectTo<eos_quantity::Temperature>,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
const double T = temperature.value();
|
||||||
|
return mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::Pressure,
|
||||||
|
eos_quantity::Temperature>{
|
||||||
|
density.value() * m_specificGasConstant +
|
||||||
|
4.0 * m_parameters.radiationConstant * cube(T) / 3.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificInternalEnergy,
|
||||||
|
eos_quantity::Density>
|
||||||
|
partialDerivative(
|
||||||
|
SpecificInternalEnergyFromDensityAndTemperature,
|
||||||
|
mean_field::eos::WithRespectTo<eos_quantity::Density>,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
return mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificInternalEnergy,
|
||||||
|
eos_quantity::Density>{
|
||||||
|
-m_parameters.radiationConstant * fourthPower(temperature.value()) /
|
||||||
|
square(density.value())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificInternalEnergy,
|
||||||
|
eos_quantity::Temperature>
|
||||||
|
partialDerivative(
|
||||||
|
SpecificInternalEnergyFromDensityAndTemperature,
|
||||||
|
mean_field::eos::WithRespectTo<eos_quantity::Temperature>,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
return mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificInternalEnergy,
|
||||||
|
eos_quantity::Temperature>{
|
||||||
|
1.5 * m_specificGasConstant +
|
||||||
|
4.0 * m_parameters.radiationConstant * cube(temperature.value()) / density.value()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificEnthalpy,
|
||||||
|
eos_quantity::Density>
|
||||||
|
partialDerivative(
|
||||||
|
SpecificEnthalpyFromDensityAndTemperature,
|
||||||
|
mean_field::eos::WithRespectTo<eos_quantity::Density>,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
return mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificEnthalpy,
|
||||||
|
eos_quantity::Density>{
|
||||||
|
-4.0 * m_parameters.radiationConstant * fourthPower(temperature.value()) /
|
||||||
|
(3.0 * square(density.value()))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificEnthalpy,
|
||||||
|
eos_quantity::Temperature>
|
||||||
|
partialDerivative(
|
||||||
|
SpecificEnthalpyFromDensityAndTemperature,
|
||||||
|
mean_field::eos::WithRespectTo<eos_quantity::Temperature>,
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) const {
|
||||||
|
validateMaterialState(density, temperature);
|
||||||
|
return mean_field::eos::PartialDerivative<
|
||||||
|
eos_quantity::SpecificEnthalpy,
|
||||||
|
eos_quantity::Temperature>{
|
||||||
|
2.5 * m_specificGasConstant +
|
||||||
|
16.0 * m_parameters.radiationConstant * cube(temperature.value()) /
|
||||||
|
(3.0 * density.value())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] static Parameters validatedParameters(const Parameters parameters) {
|
||||||
|
requirePositiveFinite(parameters.meanMolecularWeight, "mean molecular weight");
|
||||||
|
requirePositiveFinite(parameters.boltzmannConstant, "Boltzmann constant");
|
||||||
|
requirePositiveFinite(parameters.atomicMassUnit, "atomic mass unit");
|
||||||
|
requireNonnegativeFinite(parameters.radiationConstant, "radiation constant");
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void requirePositiveFinite(const double value, const char *name) {
|
||||||
|
if (!std::isfinite(value) || value <= 0.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string{"IdealGasRadiation requires a finite, positive "} + name + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void requireNonnegativeFinite(const double value, const char *name) {
|
||||||
|
if (!std::isfinite(value) || value < 0.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string{"IdealGasRadiation requires a finite, nonnegative "} + name + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateMaterialState(
|
||||||
|
const mean_field::dimensions::DensityValue density,
|
||||||
|
const mean_field::dimensions::TemperatureValue temperature
|
||||||
|
) {
|
||||||
|
if (!std::isfinite(density.value()) || !std::isfinite(temperature.value())) {
|
||||||
|
throw mean_field::eos::EvaluationError{
|
||||||
|
mean_field::eos::EvaluationErrorCode::nonfinite_input,
|
||||||
|
"IdealGasRadiation requires finite density and temperature."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (density.value() <= 0.0 || temperature.value() < 0.0) {
|
||||||
|
throw mean_field::eos::EvaluationError{
|
||||||
|
mean_field::eos::EvaluationErrorCode::outside_domain,
|
||||||
|
"IdealGasRadiation requires rho > 0 and T >= 0."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateTemperature(const mean_field::dimensions::TemperatureValue temperature) {
|
||||||
|
if (!std::isfinite(temperature.value())) {
|
||||||
|
throw mean_field::eos::EvaluationError{
|
||||||
|
mean_field::eos::EvaluationErrorCode::nonfinite_input,
|
||||||
|
"IdealGasRadiation requires finite temperature."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (temperature.value() < 0.0) {
|
||||||
|
throw mean_field::eos::EvaluationError{
|
||||||
|
mean_field::eos::EvaluationErrorCode::outside_domain,
|
||||||
|
"IdealGasRadiation requires T >= 0."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static double square(const double value) noexcept {
|
||||||
|
return value * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static double cube(const double value) noexcept {
|
||||||
|
return value * value * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static double fourthPower(const double value) noexcept {
|
||||||
|
const double squared = square(value);
|
||||||
|
return squared * squared;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parameters m_parameters;
|
||||||
|
double m_specificGasConstant;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These assertions are executable documentation. They prove that the
|
||||||
|
* class and every derivative satisfy the public extension protocol.
|
||||||
|
*/
|
||||||
|
static_assert(mean_field::models::SelfDescribingModelSpecification<IdealGasRadiation>);
|
||||||
|
static_assert(mean_field::eos::EquationOfStateModel<IdealGasRadiation>);
|
||||||
|
static_assert(mean_field::eos::SupportsPartialDerivative<
|
||||||
|
IdealGasRadiation,
|
||||||
|
PressureFromDensityAndTemperature,
|
||||||
|
eos_quantity::Density>);
|
||||||
|
static_assert(mean_field::eos::SupportsPartialDerivative<
|
||||||
|
IdealGasRadiation,
|
||||||
|
PressureFromDensityAndTemperature,
|
||||||
|
eos_quantity::Temperature>);
|
||||||
|
static_assert(mean_field::eos::SupportsPartialDerivative<
|
||||||
|
IdealGasRadiation,
|
||||||
|
SpecificInternalEnergyFromDensityAndTemperature,
|
||||||
|
eos_quantity::Density>);
|
||||||
|
static_assert(mean_field::eos::SupportsPartialDerivative<
|
||||||
|
IdealGasRadiation,
|
||||||
|
SpecificInternalEnergyFromDensityAndTemperature,
|
||||||
|
eos_quantity::Temperature>);
|
||||||
|
static_assert(mean_field::eos::SupportsPartialDerivative<
|
||||||
|
IdealGasRadiation,
|
||||||
|
SpecificEnthalpyFromDensityAndTemperature,
|
||||||
|
eos_quantity::Density>);
|
||||||
|
static_assert(mean_field::eos::SupportsPartialDerivative<
|
||||||
|
IdealGasRadiation,
|
||||||
|
SpecificEnthalpyFromDensityAndTemperature,
|
||||||
|
eos_quantity::Temperature>);
|
||||||
|
} // namespace mean_field::extension_example
|
||||||
BIN
extension_example/manual/physics_developer_manual.pdf
Normal file
BIN
extension_example/manual/physics_developer_manual.pdf
Normal file
Binary file not shown.
1058
extension_example/manual/physics_developer_manual.tex
Normal file
1058
extension_example/manual/physics_developer_manual.tex
Normal file
File diff suppressed because it is too large
Load Diff
61
extension_example/rotating_stellar_model.cppm
Normal file
61
extension_example/rotating_stellar_model.cppm
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
export module mean_field_extension_example.rotating_stellar_model;
|
||||||
|
|
||||||
|
export import mean_field_extension_example.ideal_gas_radiation;
|
||||||
|
import mean_field;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is the physics-facing composition layer. It contains no block
|
||||||
|
* matrices, generated residual types, Jacobian indices, or preconditioner
|
||||||
|
* plumbing. StellarModel infers those structural types from the four
|
||||||
|
* physical specifications passed to it.
|
||||||
|
*/
|
||||||
|
export namespace mean_field::extension_example {
|
||||||
|
struct RotatingStellarModelParameters final {
|
||||||
|
IdealGasRadiation::Parameters equationOfState;
|
||||||
|
mean_field::dimensions::PressureValue surfacePressure;
|
||||||
|
mean_field::dimensions::MassValue totalMass;
|
||||||
|
mean_field::dimensions::AngularMomentumValue totalAngularMomentum;
|
||||||
|
std::array<double, 3> rotationAxis{0.0, 0.0, 1.0};
|
||||||
|
std::array<double, 3> rotationCenter{0.0, 0.0, 0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] auto makeRotatingStellarModel(const RotatingStellarModelParameters ¶meters) {
|
||||||
|
return mean_field::model::StellarModel(
|
||||||
|
IdealGasRadiation(parameters.equationOfState),
|
||||||
|
mean_field::surface::Isobaric({.Psurf = parameters.surfacePressure}),
|
||||||
|
mean_field::integral::FixedTotalMass({.Mtotal = parameters.totalMass}),
|
||||||
|
mean_field::integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = parameters.totalAngularMomentum,
|
||||||
|
.axis = parameters.rotationAxis,
|
||||||
|
.center = parameters.rotationCenter
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
using RotatingStellarModel = decltype(
|
||||||
|
makeRotatingStellarModel(std::declval<const RotatingStellarModelParameters &>())
|
||||||
|
);
|
||||||
|
|
||||||
|
static_assert(mean_field::model::StellarModelType<RotatingStellarModel>);
|
||||||
|
static_assert(RotatingStellarModel::symbolicallySquare);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Deliberate capability boundary:
|
||||||
|
*
|
||||||
|
* The specification above is a valid, strongly typed stellar model. The
|
||||||
|
* current numerical equilibrium core, however, closes density through a
|
||||||
|
* barotropic relation rho(h). This EOS instead needs an independent
|
||||||
|
* temperature or entropy field and its governing equation. Keeping this
|
||||||
|
* assertion false prevents an example from suggesting that discretize()
|
||||||
|
* already implements thermal equilibrium when it does not.
|
||||||
|
*/
|
||||||
|
inline constexpr bool currentEquilibriumBackendSupportsIdealGasRadiation =
|
||||||
|
mean_field::equilibrium::StellarEquilibriumModel<RotatingStellarModel>;
|
||||||
|
|
||||||
|
static_assert(!currentEquilibriumBackendSupportsIdealGasRadiation);
|
||||||
|
} // namespace mean_field::extension_example
|
||||||
292
extension_example/tests/ideal_gas_radiation.cpp
Normal file
292
extension_example/tests/ideal_gas_radiation.cpp
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <concepts>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <catch2/catch_approx.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
import mean_field_extension_example.ideal_gas_radiation;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
namespace dimensions = mean_field::dimensions;
|
||||||
|
namespace eos = mean_field::eos;
|
||||||
|
namespace example = mean_field::extension_example;
|
||||||
|
|
||||||
|
[[nodiscard]] example::IdealGasRadiation makeSimpleEquationOfState() {
|
||||||
|
/* R = k_B / (mu m_u) = 12 / (2 * 3) = 2. */
|
||||||
|
return example::IdealGasRadiation({
|
||||||
|
.meanMolecularWeight = 2.0,
|
||||||
|
.boltzmannConstant = 12.0,
|
||||||
|
.atomicMassUnit = 3.0,
|
||||||
|
.radiationConstant = 9.0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Function>
|
||||||
|
[[nodiscard]] double centeredDifference(
|
||||||
|
Function function,
|
||||||
|
const double point
|
||||||
|
) {
|
||||||
|
const double step = std::cbrt(std::numeric_limits<double>::epsilon()) *
|
||||||
|
std::max(1.0, std::abs(point));
|
||||||
|
return (function(point + step) - function(point - step)) / (2.0 * step);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename EquationOfState>
|
||||||
|
concept CanEvaluatePressureWithReversedInputs = requires(
|
||||||
|
const EquationOfState &equationOfState,
|
||||||
|
const dimensions::TemperatureValue temperature,
|
||||||
|
const dimensions::DensityValue density
|
||||||
|
) {
|
||||||
|
eos::evaluate<dimensions::quantity::Pressure>(equationOfState, temperature, density);
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("The extension satisfies the EOS protocol at compile time", "[extension-example][eos][type]") {
|
||||||
|
using EquationOfState = example::IdealGasRadiation;
|
||||||
|
|
||||||
|
STATIC_CHECK(mean_field::models::SelfDescribingModelSpecification<EquationOfState>);
|
||||||
|
STATIC_CHECK(eos::EquationOfStateModel<EquationOfState>);
|
||||||
|
STATIC_CHECK(eos::SupportsRelation<EquationOfState, example::PressureFromDensityAndTemperature>);
|
||||||
|
STATIC_CHECK(eos::SupportsRelation<EquationOfState, example::SpecificInternalEnergyFromDensityAndTemperature>);
|
||||||
|
STATIC_CHECK(eos::SupportsRelation<EquationOfState, example::SpecificEnthalpyFromDensityAndTemperature>);
|
||||||
|
STATIC_CHECK_FALSE(eos::BarotropicClosureEquationOfState<EquationOfState>);
|
||||||
|
STATIC_CHECK_FALSE(CanEvaluatePressureWithReversedInputs<EquationOfState>);
|
||||||
|
|
||||||
|
using PressureResult = decltype(eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
std::declval<const EquationOfState &>(),
|
||||||
|
dimensions::DensityValue{1.0},
|
||||||
|
dimensions::TemperatureValue{1.0}
|
||||||
|
));
|
||||||
|
STATIC_CHECK(std::same_as<PressureResult, dimensions::PressureValue>);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Gas and radiation terms reproduce the defining thermodynamics", "[extension-example][eos][physics]") {
|
||||||
|
const auto equationOfState = makeSimpleEquationOfState();
|
||||||
|
const dimensions::DensityValue density{4.0};
|
||||||
|
const dimensions::TemperatureValue temperature{2.0};
|
||||||
|
|
||||||
|
const auto pressureContributions = equationOfState.pressureContributions(density, temperature);
|
||||||
|
const auto pressure = eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
temperature
|
||||||
|
);
|
||||||
|
const auto internalEnergy = eos::evaluate<dimensions::quantity::SpecificInternalEnergy>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
temperature
|
||||||
|
);
|
||||||
|
const auto enthalpy = eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
temperature
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(equationOfState.specificGasConstant() == Catch::Approx(2.0));
|
||||||
|
CHECK(pressureContributions.gas.value() == Catch::Approx(16.0));
|
||||||
|
CHECK(pressureContributions.radiation.value() == Catch::Approx(48.0));
|
||||||
|
CHECK(pressure.value() == Catch::Approx(64.0));
|
||||||
|
CHECK(internalEnergy.value() == Catch::Approx(42.0));
|
||||||
|
CHECK(enthalpy.value() == Catch::Approx(58.0));
|
||||||
|
|
||||||
|
/* This is the thermodynamic identity h = u + P/rho. */
|
||||||
|
CHECK(enthalpy.value() == Catch::Approx(internalEnergy.value() + pressure.value() / density.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The gas and photon terms have their expected scaling laws", "[extension-example][eos][physics]") {
|
||||||
|
const auto equationOfState = makeSimpleEquationOfState();
|
||||||
|
const dimensions::DensityValue density{3.5};
|
||||||
|
const dimensions::TemperatureValue temperature{1.25};
|
||||||
|
|
||||||
|
const auto baseline = equationOfState.pressureContributions(density, temperature);
|
||||||
|
const auto doubledDensity = equationOfState.pressureContributions(
|
||||||
|
dimensions::DensityValue{2.0 * density.value()},
|
||||||
|
temperature
|
||||||
|
);
|
||||||
|
const auto doubledTemperature = equationOfState.pressureContributions(
|
||||||
|
density,
|
||||||
|
dimensions::TemperatureValue{2.0 * temperature.value()}
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(doubledDensity.gas.value() == Catch::Approx(2.0 * baseline.gas.value()));
|
||||||
|
CHECK(doubledDensity.radiation.value() == Catch::Approx(baseline.radiation.value()));
|
||||||
|
CHECK(doubledTemperature.gas.value() == Catch::Approx(2.0 * baseline.gas.value()));
|
||||||
|
CHECK(doubledTemperature.radiation.value() == Catch::Approx(16.0 * baseline.radiation.value()));
|
||||||
|
|
||||||
|
const double crossoverTemperature = std::cbrt(
|
||||||
|
3.0 * density.value() * equationOfState.specificGasConstant() /
|
||||||
|
equationOfState.parameters().radiationConstant
|
||||||
|
);
|
||||||
|
const auto crossover = equationOfState.pressureContributions(
|
||||||
|
density,
|
||||||
|
dimensions::TemperatureValue{crossoverTemperature}
|
||||||
|
);
|
||||||
|
CHECK(crossover.gas.value() == Catch::Approx(crossover.radiation.value()).epsilon(2.0e-14));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("All declared Jacobian entries match centered numerical derivatives",
|
||||||
|
"[extension-example][eos][derivative][numerical]") {
|
||||||
|
const auto equationOfState = example::IdealGasRadiation({
|
||||||
|
.meanMolecularWeight = 1.25,
|
||||||
|
.boltzmannConstant = 2.75,
|
||||||
|
.atomicMassUnit = 0.8,
|
||||||
|
.radiationConstant = 0.35
|
||||||
|
});
|
||||||
|
|
||||||
|
struct State final {
|
||||||
|
double density;
|
||||||
|
double temperature;
|
||||||
|
};
|
||||||
|
const std::array states{
|
||||||
|
State{.density = 0.4, .temperature = 0.7},
|
||||||
|
State{.density = 2.0, .temperature = 1.5},
|
||||||
|
State{.density = 11.0, .temperature = 3.0}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const State state : states) {
|
||||||
|
const dimensions::DensityValue density{state.density};
|
||||||
|
const dimensions::TemperatureValue temperature{state.temperature};
|
||||||
|
|
||||||
|
const auto pressureDensity = eos::partialDerivative<
|
||||||
|
dimensions::quantity::Pressure,
|
||||||
|
dimensions::quantity::Density>(equationOfState, density, temperature);
|
||||||
|
const auto pressureTemperature = eos::partialDerivative<
|
||||||
|
dimensions::quantity::Pressure,
|
||||||
|
dimensions::quantity::Temperature>(equationOfState, density, temperature);
|
||||||
|
const auto energyDensity = eos::partialDerivative<
|
||||||
|
dimensions::quantity::SpecificInternalEnergy,
|
||||||
|
dimensions::quantity::Density>(equationOfState, density, temperature);
|
||||||
|
const auto energyTemperature = eos::partialDerivative<
|
||||||
|
dimensions::quantity::SpecificInternalEnergy,
|
||||||
|
dimensions::quantity::Temperature>(equationOfState, density, temperature);
|
||||||
|
const auto enthalpyDensity = eos::partialDerivative<
|
||||||
|
dimensions::quantity::SpecificEnthalpy,
|
||||||
|
dimensions::quantity::Density>(equationOfState, density, temperature);
|
||||||
|
const auto enthalpyTemperature = eos::partialDerivative<
|
||||||
|
dimensions::quantity::SpecificEnthalpy,
|
||||||
|
dimensions::quantity::Temperature>(equationOfState, density, temperature);
|
||||||
|
|
||||||
|
const double numericalPressureDensity = centeredDifference(
|
||||||
|
[&](const double rho) {
|
||||||
|
return eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
dimensions::DensityValue{rho},
|
||||||
|
temperature
|
||||||
|
).value();
|
||||||
|
},
|
||||||
|
state.density
|
||||||
|
);
|
||||||
|
const double numericalPressureTemperature = centeredDifference(
|
||||||
|
[&](const double T) {
|
||||||
|
return eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
dimensions::TemperatureValue{T}
|
||||||
|
).value();
|
||||||
|
},
|
||||||
|
state.temperature
|
||||||
|
);
|
||||||
|
const double numericalEnergyDensity = centeredDifference(
|
||||||
|
[&](const double rho) {
|
||||||
|
return eos::evaluate<dimensions::quantity::SpecificInternalEnergy>(
|
||||||
|
equationOfState,
|
||||||
|
dimensions::DensityValue{rho},
|
||||||
|
temperature
|
||||||
|
).value();
|
||||||
|
},
|
||||||
|
state.density
|
||||||
|
);
|
||||||
|
const double numericalEnergyTemperature = centeredDifference(
|
||||||
|
[&](const double T) {
|
||||||
|
return eos::evaluate<dimensions::quantity::SpecificInternalEnergy>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
dimensions::TemperatureValue{T}
|
||||||
|
).value();
|
||||||
|
},
|
||||||
|
state.temperature
|
||||||
|
);
|
||||||
|
const double numericalEnthalpyDensity = centeredDifference(
|
||||||
|
[&](const double rho) {
|
||||||
|
return eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
||||||
|
equationOfState,
|
||||||
|
dimensions::DensityValue{rho},
|
||||||
|
temperature
|
||||||
|
).value();
|
||||||
|
},
|
||||||
|
state.density
|
||||||
|
);
|
||||||
|
const double numericalEnthalpyTemperature = centeredDifference(
|
||||||
|
[&](const double T) {
|
||||||
|
return eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
||||||
|
equationOfState,
|
||||||
|
density,
|
||||||
|
dimensions::TemperatureValue{T}
|
||||||
|
).value();
|
||||||
|
},
|
||||||
|
state.temperature
|
||||||
|
);
|
||||||
|
|
||||||
|
constexpr double tolerance = 3.0e-9;
|
||||||
|
CHECK(pressureDensity.value() == Catch::Approx(numericalPressureDensity).epsilon(tolerance));
|
||||||
|
CHECK(pressureTemperature.value() == Catch::Approx(numericalPressureTemperature).epsilon(tolerance));
|
||||||
|
CHECK(energyDensity.value() == Catch::Approx(numericalEnergyDensity).epsilon(tolerance));
|
||||||
|
CHECK(energyTemperature.value() == Catch::Approx(numericalEnergyTemperature).epsilon(tolerance));
|
||||||
|
CHECK(enthalpyDensity.value() == Catch::Approx(numericalEnthalpyDensity).epsilon(tolerance));
|
||||||
|
CHECK(enthalpyTemperature.value() == Catch::Approx(numericalEnthalpyTemperature).epsilon(tolerance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The physical domain is checked at the EOS boundary", "[extension-example][eos][domain]") {
|
||||||
|
const auto equationOfState = makeSimpleEquationOfState();
|
||||||
|
const double nan = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
example::IdealGasRadiation({
|
||||||
|
.meanMolecularWeight = 0.0,
|
||||||
|
.boltzmannConstant = 1.0,
|
||||||
|
.atomicMassUnit = 1.0,
|
||||||
|
.radiationConstant = 1.0
|
||||||
|
}),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
example::IdealGasRadiation({
|
||||||
|
.meanMolecularWeight = 1.0,
|
||||||
|
.boltzmannConstant = 1.0,
|
||||||
|
.atomicMassUnit = 1.0,
|
||||||
|
.radiationConstant = -1.0
|
||||||
|
}),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
dimensions::DensityValue{0.0},
|
||||||
|
dimensions::TemperatureValue{1.0}
|
||||||
|
),
|
||||||
|
eos::EvaluationError
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
dimensions::DensityValue{1.0},
|
||||||
|
dimensions::TemperatureValue{-1.0}
|
||||||
|
),
|
||||||
|
eos::EvaluationError
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
eos::evaluate<dimensions::quantity::Pressure>(
|
||||||
|
equationOfState,
|
||||||
|
dimensions::DensityValue{nan},
|
||||||
|
dimensions::TemperatureValue{1.0}
|
||||||
|
),
|
||||||
|
eos::EvaluationError
|
||||||
|
);
|
||||||
|
}
|
||||||
67
extension_example/tests/rotating_stellar_model.cpp
Normal file
67
extension_example/tests/rotating_stellar_model.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include <concepts>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
import mean_field_extension_example.rotating_stellar_model;
|
||||||
|
|
||||||
|
TEST_CASE("The example EOS composes with existing stellar specifications",
|
||||||
|
"[extension-example][model][type]") {
|
||||||
|
using namespace mean_field;
|
||||||
|
namespace example = mean_field::extension_example;
|
||||||
|
|
||||||
|
const auto stellarModel = example::makeRotatingStellarModel({
|
||||||
|
.equationOfState = {
|
||||||
|
.meanMolecularWeight = 0.62,
|
||||||
|
.boltzmannConstant = 1.380649e-16,
|
||||||
|
.atomicMassUnit = 1.66053906660e-24,
|
||||||
|
.radiationConstant = 7.5657e-15
|
||||||
|
},
|
||||||
|
.surfacePressure = dimensions::PressureValue{0.0},
|
||||||
|
.totalMass = dimensions::MassValue{1.75},
|
||||||
|
.totalAngularMomentum = dimensions::AngularMomentumValue{0.3},
|
||||||
|
.rotationAxis = {0.0, 0.0, 4.0},
|
||||||
|
.rotationCenter = {0.1, -0.2, 0.3}
|
||||||
|
});
|
||||||
|
using Model = std::remove_cvref_t<decltype(stellarModel)>;
|
||||||
|
|
||||||
|
STATIC_CHECK(std::same_as<Model, example::RotatingStellarModel>);
|
||||||
|
STATIC_CHECK(model::StellarModelType<Model>);
|
||||||
|
STATIC_CHECK(Model::symbolicallySquare);
|
||||||
|
STATIC_CHECK(Model::specificationCount == 4);
|
||||||
|
STATIC_CHECK(std::same_as<model::EquationOfStateType<Model>, example::IdealGasRadiation>);
|
||||||
|
STATIC_CHECK(Model::template containsSpecification<integral::FixedTotalMass>);
|
||||||
|
STATIC_CHECK(Model::template containsSpecification<integral::FixedAngularMomentum>);
|
||||||
|
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::constitutive_law> == 1);
|
||||||
|
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::boundary_condition> == 1);
|
||||||
|
STATIC_CHECK(Model::template specificationRoleCount<models::SpecificationRole::invariant> == 2);
|
||||||
|
|
||||||
|
CHECK(stellarModel.equationOfState().parameters().meanMolecularWeight == 0.62);
|
||||||
|
CHECK(stellarModel.surfaceCondition().targetPressure() == dimensions::PressureValue{0.0});
|
||||||
|
CHECK(stellarModel.specification<integral::FixedTotalMass>().targetMass() == dimensions::MassValue{1.75});
|
||||||
|
|
||||||
|
const auto &angularMomentum = stellarModel.specification<integral::FixedAngularMomentum>();
|
||||||
|
CHECK(angularMomentum.targetAngularMomentum() == dimensions::AngularMomentumValue{0.3});
|
||||||
|
CHECK(angularMomentum.axis()[0] == 0.0);
|
||||||
|
CHECK(angularMomentum.axis()[1] == 0.0);
|
||||||
|
CHECK(angularMomentum.axis()[2] == 1.0);
|
||||||
|
CHECK(angularMomentum.center()[0] == 0.1);
|
||||||
|
CHECK(angularMomentum.center()[1] == -0.2);
|
||||||
|
CHECK(angularMomentum.center()[2] == 0.3);
|
||||||
|
CHECK(stellarModel.runtimeSpecificationDescriptors().size() == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The example states the current thermal-runtime boundary explicitly",
|
||||||
|
"[extension-example][model][capability]") {
|
||||||
|
using Model = mean_field::extension_example::RotatingStellarModel;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is not a failure of model composition. It is the intended
|
||||||
|
* compile-time rejection of a thermal EOS by a currently barotropic
|
||||||
|
* numerical core. See the manual section 'What compiles today'.
|
||||||
|
*/
|
||||||
|
STATIC_CHECK(mean_field::model::StellarModelType<Model>);
|
||||||
|
STATIC_CHECK_FALSE(mean_field::extension_example::currentEquilibriumBackendSupportsIdealGasRadiation);
|
||||||
|
STATIC_CHECK_FALSE(mean_field::equilibrium::StellarEquilibriumModel<Model>);
|
||||||
|
}
|
||||||
590
libmeanfield/impl/operators/prepared_angular_momentum.cpp
Normal file
590
libmeanfield/impl/operators/prepared_angular_momentum.cpp
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
module mean_field;
|
||||||
|
|
||||||
|
import :operators.prepared_angular_momentum;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
|
||||||
|
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
|
||||||
|
for (int index = 0; index < vector.Size(); ++index) {
|
||||||
|
MFEM_VERIFY(std::isfinite(vector(index)), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void true_to_local(
|
||||||
|
const mfem::ParFiniteElementSpace &finiteElementSpace,
|
||||||
|
const mfem::Vector &trueVector,
|
||||||
|
mfem::Vector &localVector
|
||||||
|
) {
|
||||||
|
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size.");
|
||||||
|
localVector.SetSize(finiteElementSpace.GetVSize());
|
||||||
|
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
|
||||||
|
if (prolongation != nullptr) {
|
||||||
|
prolongation->Mult(trueVector, localVector);
|
||||||
|
} else {
|
||||||
|
localVector = trueVector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::IntegrationRule &get_moment_of_inertia_rule(
|
||||||
|
const mean_field::fem::FEM &f,
|
||||||
|
const mfem::FiniteElement &densityElement,
|
||||||
|
const mfem::ElementTransformation &transformation
|
||||||
|
) {
|
||||||
|
using DensityField = mean_field::field::Field<mean_field::field::Density>;
|
||||||
|
MFEM_VERIFY(
|
||||||
|
densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
|
||||||
|
"The angular-momentum element does not match the registered density field."
|
||||||
|
);
|
||||||
|
const mean_field::quadrature::Query query =
|
||||||
|
DensityField::make_query<mean_field::field::Density::Form::Quadrupole>(
|
||||||
|
mean_field::quadrature::QuadratureRole::discretization,
|
||||||
|
transformation.OrderW(),
|
||||||
|
std::array<int, 1>{2},
|
||||||
|
mean_field::utils::DOMAINS::STELLAR,
|
||||||
|
mean_field::quadrature::MappingKind::general
|
||||||
|
);
|
||||||
|
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
|
||||||
|
MFEM_VERIFY(
|
||||||
|
resolution.integration_rule != nullptr,
|
||||||
|
"The quadrature policy did not return an angular-momentum integration rule."
|
||||||
|
);
|
||||||
|
return *resolution.integration_rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate_shared_gravity_revisions(
|
||||||
|
const mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &gravityContext,
|
||||||
|
const mean_field::operators::AngularMomentumDependencies &dependencies
|
||||||
|
) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
gravityContext.IsPrepared(),
|
||||||
|
"PreparedAngularMomentumOperator requires the shared gravity context to be prepared first."
|
||||||
|
);
|
||||||
|
const auto &revisions = gravityContext.GetRevisions();
|
||||||
|
MFEM_VERIFY(
|
||||||
|
revisions.discretization.value == dependencies.discretization.revision &&
|
||||||
|
revisions.density.value == dependencies.density.revision &&
|
||||||
|
revisions.displacement.value == dependencies.displacement.revision,
|
||||||
|
"PreparedAngularMomentumOperator received revisions that do not match the shared gravity context."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate_identity_transition(
|
||||||
|
const mean_field::operators::AngularMomentumDependencyStamp &prepared,
|
||||||
|
const mean_field::operators::AngularMomentumDependencyStamp &requested,
|
||||||
|
const char *message
|
||||||
|
) {
|
||||||
|
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace mean_field::operators {
|
||||||
|
PreparedAngularMomentumOperator::PreparedAngularMomentumOperator(
|
||||||
|
const fem::FEM &f,
|
||||||
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
const context::gravity_field::GravityFieldLinearizationContext &gravityContext,
|
||||||
|
models::CompiledFixedAngularMomentum constraint
|
||||||
|
)
|
||||||
|
: m_fem(f),
|
||||||
|
m_domainMapper(domainMapper),
|
||||||
|
m_gravityContext(gravityContext),
|
||||||
|
m_constraint(std::move(constraint)) {
|
||||||
|
MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedAngularMomentumOperator requires a mesh.");
|
||||||
|
MFEM_VERIFY(
|
||||||
|
m_fem.mesh->Dimension() == 3 && m_domainMapper.GetDimension() == 3,
|
||||||
|
"PreparedAngularMomentumOperator currently requires a three-dimensional mapped domain."
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr &&
|
||||||
|
m_fem.compactificationFes != nullptr && m_fem.compactificationCoordinate != nullptr &&
|
||||||
|
m_fem.quadratureFactory != nullptr,
|
||||||
|
"PreparedAngularMomentumOperator requires density, displacement, compactification, and quadrature data."
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
m_gravityContext.GetDensityMap().full_size() == m_fem.densityFes->GetTrueVSize() &&
|
||||||
|
m_gravityContext.GetDisplacementMap().full_size() == m_fem.displacementFes->GetTrueVSize(),
|
||||||
|
"PreparedAngularMomentumOperator received incompatible shared FieldDof maps."
|
||||||
|
);
|
||||||
|
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
|
||||||
|
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedAngularMomentumReport PreparedAngularMomentumOperator::Prepare(
|
||||||
|
const double angularVelocity,
|
||||||
|
const AngularMomentumDependencies &dependencies
|
||||||
|
) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
std::isfinite(angularVelocity),
|
||||||
|
"PreparedAngularMomentumOperator requires a finite angular-velocity coordinate."
|
||||||
|
);
|
||||||
|
validate_shared_gravity_revisions(m_gravityContext, dependencies);
|
||||||
|
|
||||||
|
if (m_isPrepared) {
|
||||||
|
validate_identity_transition(
|
||||||
|
m_preparedDependencies.discretization,
|
||||||
|
dependencies.discretization,
|
||||||
|
"A new angular-momentum discretization identity must change its revision."
|
||||||
|
);
|
||||||
|
validate_identity_transition(
|
||||||
|
m_preparedDependencies.density,
|
||||||
|
dependencies.density,
|
||||||
|
"A new angular-momentum density identity must change its revision."
|
||||||
|
);
|
||||||
|
validate_identity_transition(
|
||||||
|
m_preparedDependencies.displacement,
|
||||||
|
dependencies.displacement,
|
||||||
|
"A new angular-momentum displacement identity must change its revision."
|
||||||
|
);
|
||||||
|
validate_identity_transition(
|
||||||
|
m_preparedDependencies.rotation,
|
||||||
|
dependencies.rotation,
|
||||||
|
"A new angular-momentum rotation identity must change its revision."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool rebuildStaticPlan =
|
||||||
|
!m_isPrepared || dependencies.discretization != m_preparedDependencies.discretization;
|
||||||
|
const bool refreshGeometry =
|
||||||
|
rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement;
|
||||||
|
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
|
||||||
|
const bool updateAngularVelocity =
|
||||||
|
!m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
|
||||||
|
angularVelocity != m_angularVelocity;
|
||||||
|
|
||||||
|
m_isPrepared = false;
|
||||||
|
PreparedAngularMomentumReport report;
|
||||||
|
if (rebuildStaticPlan) {
|
||||||
|
BuildStaticPlan();
|
||||||
|
report.rebuiltStaticPlan = true;
|
||||||
|
}
|
||||||
|
if (refreshGeometry) {
|
||||||
|
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
|
||||||
|
report.refreshedGeometry = true;
|
||||||
|
}
|
||||||
|
if (refreshDensity) {
|
||||||
|
RefreshDensity(m_gravityContext.GetDensityTrue());
|
||||||
|
report.refreshedDensity = true;
|
||||||
|
}
|
||||||
|
if (updateAngularVelocity) {
|
||||||
|
m_angularVelocity = angularVelocity;
|
||||||
|
report.updatedAngularVelocity = true;
|
||||||
|
}
|
||||||
|
if (refreshGeometry || refreshDensity || updateAngularVelocity) {
|
||||||
|
AssembleResidual();
|
||||||
|
report.assembledResidual = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_preparedDependencies = dependencies;
|
||||||
|
m_isPrepared = true;
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::BuildStaticPlan() {
|
||||||
|
m_elements.clear();
|
||||||
|
m_elements.reserve(m_fem.mesh->GetNE());
|
||||||
|
int localStellarElementCount = 0;
|
||||||
|
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
|
||||||
|
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
|
||||||
|
MFEM_VERIFY(transformation != nullptr, "Angular-momentum preparation received a null transformation.");
|
||||||
|
if (is_vacuum_attribute(transformation->Attribute)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++localStellarElementCount;
|
||||||
|
m_elements.emplace_back();
|
||||||
|
ElementPAData &data = m_elements.back();
|
||||||
|
data.elementId = elementId;
|
||||||
|
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
|
||||||
|
data.displacementDofTransformation =
|
||||||
|
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
|
||||||
|
data.compactificationDofTransformation =
|
||||||
|
m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs);
|
||||||
|
|
||||||
|
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
|
||||||
|
const mfem::IntegrationRule &integrationRule =
|
||||||
|
get_moment_of_inertia_rule(m_fem, densityElement, *transformation);
|
||||||
|
data.quadraturePoints.resize(integrationRule.GetNPoints());
|
||||||
|
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
|
||||||
|
QuadraturePointData &point = data.quadraturePoints[quadraturePoint];
|
||||||
|
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
|
||||||
|
point.densityShape.SetSize(densityElement.GetDof());
|
||||||
|
densityElement.CalcShape(point.integrationPoint, point.densityShape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int globalStellarElementCount = 0;
|
||||||
|
MPI_Allreduce(
|
||||||
|
&localStellarElementCount,
|
||||||
|
&globalStellarElementCount,
|
||||||
|
1,
|
||||||
|
MPI_INT,
|
||||||
|
MPI_SUM,
|
||||||
|
m_fem.mesh->GetComm()
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedAngularMomentumOperator found no stellar elements.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||||
|
"Angular-momentum geometry has the wrong displacement size."
|
||||||
|
);
|
||||||
|
validate_finite_vector(displacement, "Angular-momentum geometry contains a non-finite displacement.");
|
||||||
|
mfem::Vector displacementLocal;
|
||||||
|
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
|
||||||
|
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
||||||
|
|
||||||
|
for (ElementPAData &data : m_elements) {
|
||||||
|
displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement);
|
||||||
|
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, data.compactification);
|
||||||
|
if (data.displacementDofTransformation != nullptr) {
|
||||||
|
data.displacementDofTransformation->InvTransformPrimal(data.baseDisplacement);
|
||||||
|
}
|
||||||
|
if (data.compactificationDofTransformation != nullptr) {
|
||||||
|
data.compactificationDofTransformation->InvTransformPrimal(data.compactification);
|
||||||
|
}
|
||||||
|
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||||
|
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
|
||||||
|
const mapping::ElementDisplacementData displacementData =
|
||||||
|
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
||||||
|
const mapping::ElementCompactificationData compactificationData(
|
||||||
|
compactificationElement,
|
||||||
|
data.compactification
|
||||||
|
);
|
||||||
|
const mapping::ElementMappingData mappingData{
|
||||||
|
.displacement = displacementData,
|
||||||
|
.compactification = compactificationData
|
||||||
|
};
|
||||||
|
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
||||||
|
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||||
|
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
|
||||||
|
mappingData,
|
||||||
|
*transformation,
|
||||||
|
point.integrationPoint,
|
||||||
|
workspace,
|
||||||
|
point.mappingContext
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status == mapping::MappingStatus::valid && !point.mappingContext.mapping.compactified,
|
||||||
|
"Mapped angular-momentum geometry is invalid. Element: " << data.elementId
|
||||||
|
);
|
||||||
|
point.cylindricalRadiusSquared =
|
||||||
|
CylindricalRadiusSquared(point.mappingContext.mapping.physical_position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::RefreshDensity(const mfem::Vector &density) {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
density.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||||
|
"Angular-momentum density has the wrong size."
|
||||||
|
);
|
||||||
|
validate_finite_vector(density, "Angular-momentum density contains a non-finite value.");
|
||||||
|
mfem::Vector densityLocal;
|
||||||
|
true_to_local(*m_fem.densityFes, density, densityLocal);
|
||||||
|
mfem::Vector elementDensity;
|
||||||
|
for (ElementPAData &data : m_elements) {
|
||||||
|
densityLocal.GetSubVector(data.densityDofs, elementDensity);
|
||||||
|
if (data.densityDofTransformation != nullptr) {
|
||||||
|
data.densityDofTransformation->InvTransformPrimal(elementDensity);
|
||||||
|
}
|
||||||
|
for (QuadraturePointData &point : data.quadraturePoints) {
|
||||||
|
point.density = elementDensity * point.densityShape;
|
||||||
|
MFEM_VERIFY(std::isfinite(point.density), "Angular-momentum quadrature density is non-finite.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::AssembleResidual() {
|
||||||
|
double localMomentOfInertia = 0.0;
|
||||||
|
for (const ElementPAData &data : m_elements) {
|
||||||
|
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||||
|
localMomentOfInertia += point.density * point.cylindricalRadiusSquared *
|
||||||
|
point.mappingContext.quadrature.weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_momentOfInertia = GlobalSum(localMomentOfInertia);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
std::isfinite(m_momentOfInertia) && m_momentOfInertia >= 0.0,
|
||||||
|
"PreparedAngularMomentumOperator assembled an invalid moment of inertia."
|
||||||
|
);
|
||||||
|
m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia;
|
||||||
|
m_cachedResidual.SetSize(1);
|
||||||
|
m_cachedResidual(0) = m_currentAngularMomentum - m_constraint.targetAngularMomentum().value();
|
||||||
|
++m_preparationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
residual = m_cachedResidual;
|
||||||
|
++m_residualApplicationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(
|
||||||
|
const mfem::Vector &densityVariation
|
||||||
|
) const {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
|
||||||
|
"Angular-momentum density action has the wrong true-vector size."
|
||||||
|
);
|
||||||
|
true_to_local(*m_fem.densityFes, densityVariation, m_densityVariationLocal);
|
||||||
|
double localAction = 0.0;
|
||||||
|
for (const ElementPAData &data : m_elements) {
|
||||||
|
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
|
||||||
|
if (data.densityDofTransformation != nullptr) {
|
||||||
|
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
|
||||||
|
}
|
||||||
|
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||||
|
localAction += (m_elementDensityVariation * point.densityShape) *
|
||||||
|
point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::EvaluateDisplacementMomentActionLocal(
|
||||||
|
const mfem::Vector &displacementVariation
|
||||||
|
) const {
|
||||||
|
MFEM_VERIFY(
|
||||||
|
displacementVariation.Size() == m_fem.displacementFes->GetTrueVSize(),
|
||||||
|
"Angular-momentum displacement action has the wrong true-vector size."
|
||||||
|
);
|
||||||
|
true_to_local(*m_fem.displacementFes, displacementVariation, m_displacementVariationLocal);
|
||||||
|
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
|
||||||
|
mapping::VolumeMappingVariation variation;
|
||||||
|
double localAction = 0.0;
|
||||||
|
for (const ElementPAData &data : m_elements) {
|
||||||
|
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
|
||||||
|
if (data.displacementDofTransformation != nullptr) {
|
||||||
|
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
|
||||||
|
}
|
||||||
|
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
|
||||||
|
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
|
||||||
|
const mapping::ElementDisplacementData baseDisplacementData =
|
||||||
|
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
|
||||||
|
const mapping::ElementDisplacementData directionData =
|
||||||
|
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
|
||||||
|
const mapping::ElementCompactificationData compactificationData(
|
||||||
|
compactificationElement,
|
||||||
|
data.compactification
|
||||||
|
);
|
||||||
|
const mapping::ElementMappingData mappingData{
|
||||||
|
.displacement = baseDisplacementData,
|
||||||
|
.compactification = compactificationData
|
||||||
|
};
|
||||||
|
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
|
||||||
|
for (const QuadraturePointData &point : data.quadraturePoints) {
|
||||||
|
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
|
||||||
|
mappingData,
|
||||||
|
directionData,
|
||||||
|
*transformation,
|
||||||
|
point.integrationPoint,
|
||||||
|
point.mappingContext,
|
||||||
|
workspace,
|
||||||
|
variation
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
status == mapping::MappingStatus::valid,
|
||||||
|
"Mapped angular-momentum variation is invalid. Element: " << data.elementId
|
||||||
|
);
|
||||||
|
const double radiusSquaredVariation = CylindricalRadiusSquaredVariation(
|
||||||
|
point.mappingContext.mapping.physical_position,
|
||||||
|
variation.mapping.physical_position_variation
|
||||||
|
);
|
||||||
|
localAction += point.density *
|
||||||
|
(radiusSquaredVariation * point.mappingContext.quadrature.weight +
|
||||||
|
point.cylindricalRadiusSquared * variation.weight_variation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::ApplyDensityJacobianAction(
|
||||||
|
const mfem::Vector &densityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
MFEM_VERIFY(
|
||||||
|
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
|
||||||
|
"Angular-momentum density action has the wrong reduced size."
|
||||||
|
);
|
||||||
|
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||||
|
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
||||||
|
action.SetSize(1);
|
||||||
|
action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue));
|
||||||
|
++m_actionStatistics.densityApplications;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::ApplyDisplacementJacobianAction(
|
||||||
|
const mfem::Vector &displacementVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
MFEM_VERIFY(
|
||||||
|
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
||||||
|
"Angular-momentum displacement action has the wrong reduced size."
|
||||||
|
);
|
||||||
|
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
|
||||||
|
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
||||||
|
action.SetSize(1);
|
||||||
|
action(0) = m_angularVelocity *
|
||||||
|
GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
|
||||||
|
++m_actionStatistics.displacementApplications;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::ApplyAngularVelocityJacobianAction(
|
||||||
|
const double angularVelocityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
|
||||||
|
action.SetSize(1);
|
||||||
|
action(0) = m_momentOfInertia * angularVelocityVariation;
|
||||||
|
++m_actionStatistics.angularVelocityApplications;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::ApplyCompleteJacobianAction(
|
||||||
|
const mfem::Vector &densityVariation,
|
||||||
|
const mfem::Vector &displacementVariation,
|
||||||
|
const double angularVelocityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
MFEM_VERIFY(
|
||||||
|
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size() &&
|
||||||
|
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
|
||||||
|
"Angular-momentum complete action has incompatible reduced coordinates."
|
||||||
|
);
|
||||||
|
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
|
||||||
|
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
|
||||||
|
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
|
||||||
|
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
|
||||||
|
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
|
||||||
|
const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) +
|
||||||
|
EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue);
|
||||||
|
action.SetSize(1);
|
||||||
|
action(0) = m_angularVelocity * GlobalSum(localMomentAction) +
|
||||||
|
m_momentOfInertia * angularVelocityVariation;
|
||||||
|
++m_actionStatistics.completeApplications;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::CylindricalRadiusSquared(
|
||||||
|
const mfem::Vector &physicalPosition
|
||||||
|
) const noexcept {
|
||||||
|
const auto &axis = m_constraint.specification().axis();
|
||||||
|
const auto ¢er = m_constraint.specification().center();
|
||||||
|
double radiusSquared = 0.0;
|
||||||
|
double axialPosition = 0.0;
|
||||||
|
for (int component = 0; component < 3; ++component) {
|
||||||
|
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
|
||||||
|
radiusSquared += relative * relative;
|
||||||
|
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
|
||||||
|
}
|
||||||
|
return std::max(0.0, radiusSquared - axialPosition * axialPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation(
|
||||||
|
const mfem::Vector &physicalPosition,
|
||||||
|
const mfem::Vector &physicalPositionVariation
|
||||||
|
) const noexcept {
|
||||||
|
const auto &axis = m_constraint.specification().axis();
|
||||||
|
const auto ¢er = m_constraint.specification().center();
|
||||||
|
double relativeDotVariation = 0.0;
|
||||||
|
double axialPosition = 0.0;
|
||||||
|
double axialVariation = 0.0;
|
||||||
|
for (int component = 0; component < 3; ++component) {
|
||||||
|
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
|
||||||
|
relativeDotVariation += relative * physicalPositionVariation(component);
|
||||||
|
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
|
||||||
|
axialVariation += axis[static_cast<std::size_t>(component)] * physicalPositionVariation(component);
|
||||||
|
}
|
||||||
|
return 2.0 * (relativeDotVariation - axialPosition * axialVariation);
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::GlobalSum(const double localValue) const {
|
||||||
|
double globalValue = 0.0;
|
||||||
|
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm());
|
||||||
|
return globalValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreparedAngularMomentumOperator::IsPrepared() const noexcept {
|
||||||
|
if (!m_isPrepared || !m_gravityContext.IsPrepared()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto &revisions = m_gravityContext.GetRevisions();
|
||||||
|
return revisions.discretization.value == m_preparedDependencies.discretization.revision &&
|
||||||
|
revisions.density.value == m_preparedDependencies.density.revision &&
|
||||||
|
revisions.displacement.value == m_preparedDependencies.displacement.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::GetMomentOfInertia() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return m_momentOfInertia;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::GetAngularVelocity() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return m_angularVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::GetCurrentAngularMomentum() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return m_currentAngularMomentum;
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedAngularMomentumOperator::GetTargetAngularMomentum() const noexcept {
|
||||||
|
return m_constraint.targetAngularMomentum().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
physics::RigidRotation PreparedAngularMomentumOperator::GetRotation() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return m_constraint.makeRotation(m_angularVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
AngularMomentumConstraintReport PreparedAngularMomentumOperator::GetConstraintReport() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
const double target = GetTargetAngularMomentum();
|
||||||
|
const double residual = m_currentAngularMomentum - target;
|
||||||
|
return {
|
||||||
|
.targetAngularMomentum = target,
|
||||||
|
.achievedAngularMomentum = m_currentAngularMomentum,
|
||||||
|
.momentOfInertia = m_momentOfInertia,
|
||||||
|
.angularVelocity = m_angularVelocity,
|
||||||
|
.dimensionalResidual = residual,
|
||||||
|
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t PreparedAngularMomentumOperator::GetPreparationCount() const noexcept {
|
||||||
|
return m_preparationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t PreparedAngularMomentumOperator::GetResidualApplicationCount() const noexcept {
|
||||||
|
return m_residualApplicationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PreparedAngularMomentumActionStatistics &
|
||||||
|
PreparedAngularMomentumOperator::GetActionStatistics() const noexcept {
|
||||||
|
return m_actionStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
const models::CompiledFixedAngularMomentum &
|
||||||
|
PreparedAngularMomentumOperator::GetCompiledConstraint() const noexcept {
|
||||||
|
return m_constraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedAngularMomentumOperator::VerifyPrepared() const {
|
||||||
|
MFEM_VERIFY(IsPrepared(), "The angular-momentum invariant must be prepared before application.");
|
||||||
|
}
|
||||||
|
} // namespace mean_field::operators
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
module;
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <mfem.hpp>
|
|
||||||
|
|
||||||
module mean_field;
|
|
||||||
|
|
||||||
import :operators.prepared_central_density_stellar_equilibrium;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
||||||
using PhysicalForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
|
|
||||||
using BorderedForm = mean_field::operators::CentralDensityStellarEquilibriumForm;
|
|
||||||
|
|
||||||
[[nodiscard]] std::array<
|
|
||||||
int,
|
|
||||||
BorderedForm::value_block_count>
|
|
||||||
make_value_sizes(const mean_field::operators::StellarEquilibriumLayout &physicalLayout) {
|
|
||||||
std::array<int, BorderedForm::value_block_count> sizes{};
|
|
||||||
for (int block = 0; block < PhysicalForm::value_block_count; ++block) {
|
|
||||||
sizes[block] = physicalLayout.value_offsets()[block + 1] - physicalLayout.value_offsets()[block];
|
|
||||||
}
|
|
||||||
sizes[PhysicalForm::value_block_count] = 1;
|
|
||||||
return sizes;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::array<
|
|
||||||
int,
|
|
||||||
BorderedForm::residual_block_count>
|
|
||||||
make_residual_sizes(const mean_field::operators::StellarEquilibriumLayout &physicalLayout) {
|
|
||||||
std::array<int, BorderedForm::residual_block_count> sizes{};
|
|
||||||
for (int block = 0; block < PhysicalForm::residual_block_count; ++block) {
|
|
||||||
sizes[block] = physicalLayout.residual_offsets()[block + 1] - physicalLayout.residual_offsets()[block];
|
|
||||||
}
|
|
||||||
sizes[PhysicalForm::residual_block_count] = 1;
|
|
||||||
return sizes;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] mean_field::operators::CentralDensityDependencies
|
|
||||||
make_phase_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) {
|
|
||||||
return {.enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}};
|
|
||||||
}
|
|
||||||
|
|
||||||
void validate_finite_scalar(
|
|
||||||
const double value,
|
|
||||||
const char *message
|
|
||||||
) {
|
|
||||||
MFEM_VERIFY(std::isfinite(value), message);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace mean_field::operators {
|
|
||||||
field::FieldPointDofMap PreparedCentralDensityStellarEquilibriumOperator::MakeCenterDofMap(const fem::FEM &f) {
|
|
||||||
MFEM_VERIFY(
|
|
||||||
f.mesh != nullptr && f.enthalpyFes != nullptr,
|
|
||||||
"The central-density phase requires the mesh and enthalpy finite-element space."
|
|
||||||
);
|
|
||||||
const field::FieldDofMap enthalpyMap = field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes);
|
|
||||||
mfem::Vector origin(f.mesh->SpaceDimension());
|
|
||||||
origin = 0.0;
|
|
||||||
return field::make_field_point_dof_map<field::Enthalpy>(*f.enthalpyFes, enthalpyMap, origin, 1.0e-12);
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator::PreparedCentralDensityStellarEquilibriumOperator(
|
|
||||||
fem::FEM &f,
|
|
||||||
std::unique_ptr<PreparedStellarEquilibriumOperator> physicalOperator,
|
|
||||||
models::CompiledFixedCentralDensity centralDensity,
|
|
||||||
field::FieldPointDofMap centerDof
|
|
||||||
)
|
|
||||||
: mfem::Operator(
|
|
||||||
physicalOperator->Height() + 1,
|
|
||||||
physicalOperator->Width() + 1
|
|
||||||
),
|
|
||||||
m_physicalOperator(std::move(physicalOperator)),
|
|
||||||
m_centralDensity(std::move(centralDensity)),
|
|
||||||
m_phaseConstraint(
|
|
||||||
std::move(centerDof),
|
|
||||||
f.mesh->GetComm()
|
|
||||||
),
|
|
||||||
m_rootManifest(
|
|
||||||
make_value_sizes(m_physicalOperator->GetLayout()),
|
|
||||||
make_residual_sizes(m_physicalOperator->GetLayout()),
|
|
||||||
m_physicalOperator->GetTargetMass(),
|
|
||||||
m_physicalOperator->GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure,
|
|
||||||
m_physicalOperator->GetSurfaceConstraintOperator().GetSurfaceRows().size(),
|
|
||||||
CentralDensityManifestInput{
|
|
||||||
.targetDensity = m_centralDensity.targetDensity().value(),
|
|
||||||
.targetEnthalpy = m_centralDensity.targetEnthalpy().value(),
|
|
||||||
.centerDofCount = 1
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
MFEM_VERIFY(
|
|
||||||
Width() == m_rootManifest.layout().value_offsets().Last() &&
|
|
||||||
Height() == m_rootManifest.layout().residual_offsets().Last(),
|
|
||||||
"The central-density bordered root has inconsistent dimensions."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedCentralDensityStellarEquilibriumReport PreparedCentralDensityStellarEquilibriumOperator::Prepare(
|
|
||||||
const mfem::Vector &state,
|
|
||||||
const StellarEquilibriumDependencies &dependencies,
|
|
||||||
const physics::RigidRotation &rotation
|
|
||||||
) {
|
|
||||||
MFEM_VERIFY(state.Size() == Width(), "The central-density bordered root received a state with the wrong size.");
|
|
||||||
const auto stateView = m_rootManifest.stateView(state);
|
|
||||||
const mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
|
|
||||||
const mfem::Vector border = stateView.block(utils::blocks::fixed_central_density_phase.central_value_term);
|
|
||||||
validate_finite_scalar(border(0), "The central-density bordered root received a non-finite border value.");
|
|
||||||
|
|
||||||
mfem::Vector physicalState(const_cast<mfem::real_t *>(state.GetData()), m_physicalOperator->Width());
|
|
||||||
|
|
||||||
m_isPrepared = false;
|
|
||||||
PreparedCentralDensityStellarEquilibriumReport report;
|
|
||||||
report.physical = m_physicalOperator->Prepare(physicalState, dependencies, rotation);
|
|
||||||
report.phase =
|
|
||||||
m_phaseConstraint.Prepare(m_centralDensity, enthalpy, border(0), make_phase_dependencies(dependencies));
|
|
||||||
|
|
||||||
if (report.physical.assembledResidual || report.phase.DidAnyWork() || m_cachedResidual.Size() != Height()) {
|
|
||||||
AssembleResidual();
|
|
||||||
report.assembledResidual = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_isPrepared = true;
|
|
||||||
return report;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PreparedCentralDensityStellarEquilibriumOperator::AssembleResidual() {
|
|
||||||
mfem::Vector physicalResidual;
|
|
||||||
m_physicalOperator->BuildResidual(physicalResidual);
|
|
||||||
|
|
||||||
m_cachedResidual.SetSize(Height());
|
|
||||||
m_cachedResidual = 0.0;
|
|
||||||
mfem::Vector physicalDestination(m_cachedResidual.GetData(), physicalResidual.Size());
|
|
||||||
physicalDestination = physicalResidual;
|
|
||||||
|
|
||||||
const auto residualView = m_rootManifest.residualView(m_cachedResidual);
|
|
||||||
mfem::Vector enthalpyResidual = residualView.block(utils::blocks::enthalpy_field.specific_term);
|
|
||||||
mfem::Vector phaseResidual = residualView.block(utils::blocks::fixed_central_density_phase.central_value_term);
|
|
||||||
m_phaseConstraint.AddResidual(enthalpyResidual, phaseResidual);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PreparedCentralDensityStellarEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
|
|
||||||
VerifyPrepared();
|
|
||||||
residual = m_cachedResidual;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PreparedCentralDensityStellarEquilibriumOperator::Mult(
|
|
||||||
const mfem::Vector &direction,
|
|
||||||
mfem::Vector &action
|
|
||||||
) const {
|
|
||||||
VerifyPrepared();
|
|
||||||
MFEM_VERIFY(
|
|
||||||
direction.Size() == Width(), "The central-density bordered root received a direction with the wrong size."
|
|
||||||
);
|
|
||||||
const auto directionView = m_rootManifest.directionView(direction);
|
|
||||||
const mfem::Vector enthalpyDirection = directionView.block(utils::blocks::enthalpy_field.specific_term);
|
|
||||||
const mfem::Vector borderDirection =
|
|
||||||
directionView.block(utils::blocks::fixed_central_density_phase.central_value_term);
|
|
||||||
validate_finite_scalar(
|
|
||||||
borderDirection(0), "The central-density bordered root received a non-finite border direction."
|
|
||||||
);
|
|
||||||
|
|
||||||
mfem::Vector physicalDirection(const_cast<mfem::real_t *>(direction.GetData()), m_physicalOperator->Width());
|
|
||||||
mfem::Vector physicalAction;
|
|
||||||
m_physicalOperator->Mult(physicalDirection, physicalAction);
|
|
||||||
|
|
||||||
action.SetSize(Height());
|
|
||||||
action = 0.0;
|
|
||||||
mfem::Vector physicalDestination(action.GetData(), physicalAction.Size());
|
|
||||||
physicalDestination = physicalAction;
|
|
||||||
|
|
||||||
const auto actionView = m_rootManifest.residualView(action);
|
|
||||||
mfem::Vector enthalpyAction = actionView.block(utils::blocks::enthalpy_field.specific_term);
|
|
||||||
mfem::Vector phaseAction = actionView.block(utils::blocks::fixed_central_density_phase.central_value_term);
|
|
||||||
m_phaseConstraint.ApplyJacobian(
|
|
||||||
{.enthalpyVariation = enthalpyDirection, .borderVariation = borderDirection(0)},
|
|
||||||
{.enthalpyAction = enthalpyAction, .phaseAction = phaseAction}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreparedCentralDensityStellarEquilibriumOperator::IsPrepared() const noexcept {
|
|
||||||
return m_isPrepared && m_physicalOperator->IsPrepared() && m_phaseConstraint.IsPrepared();
|
|
||||||
}
|
|
||||||
|
|
||||||
const CentralDensityStellarEquilibriumLayout &
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator::GetLayout() const noexcept {
|
|
||||||
return m_rootManifest.layout();
|
|
||||||
}
|
|
||||||
|
|
||||||
const CentralDensityStellarEquilibriumRootManifest &
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator::GetRootManifest() const noexcept {
|
|
||||||
return m_rootManifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreparedStellarEquilibriumOperator &
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator::GetPhysicalOperator() const noexcept {
|
|
||||||
return *m_physicalOperator;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreparedCentralDensityConstraint &
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator::GetCentralDensityConstraint() const noexcept {
|
|
||||||
return m_phaseConstraint;
|
|
||||||
}
|
|
||||||
|
|
||||||
RootConstraintReport PreparedCentralDensityStellarEquilibriumOperator::GetFixedMassReport() const {
|
|
||||||
VerifyPrepared();
|
|
||||||
return m_physicalOperator->GetFixedMassReport();
|
|
||||||
}
|
|
||||||
|
|
||||||
CentralDensityConstraintReport PreparedCentralDensityStellarEquilibriumOperator::GetCentralDensityReport() const {
|
|
||||||
VerifyPrepared();
|
|
||||||
return m_phaseConstraint.GetConstraintReport();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PreparedCentralDensityStellarEquilibriumOperator::VerifyPrepared() const {
|
|
||||||
MFEM_VERIFY(IsPrepared(), "The central-density bordered root must be prepared before application.");
|
|
||||||
}
|
|
||||||
} // namespace mean_field::operators
|
|
||||||
@@ -903,6 +903,47 @@ namespace mean_field::operators {
|
|||||||
++m_algebraicJacobianStatistics.bernoulliConstantApplications;
|
++m_algebraicJacobianStatistics.bernoulliConstantApplications;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PreparedHydrostaticEquilibriumOperator::ApplyRotationAmplitudeJacobianAction(
|
||||||
|
const double fractionalAngularVelocityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
MFEM_VERIFY(
|
||||||
|
std::isfinite(fractionalAngularVelocityVariation),
|
||||||
|
"Prepared hydrostatic rotation-amplitude Jacobian received a non-finite variation."
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector localAction(m_fem.enthalpyFes->GetVSize());
|
||||||
|
localAction = 0.0;
|
||||||
|
mfem::Vector weightedVariation;
|
||||||
|
mfem::Vector elementAction;
|
||||||
|
|
||||||
|
for (const ElementPAData &data : m_elements) {
|
||||||
|
const int quadraturePointCount = data.quadratureWeights.Size();
|
||||||
|
MFEM_VERIFY(
|
||||||
|
data.rotationPotential.Size() == quadraturePointCount,
|
||||||
|
"Prepared hydrostatic rotation-amplitude Jacobian has stale rotation data."
|
||||||
|
);
|
||||||
|
weightedVariation.SetSize(quadraturePointCount);
|
||||||
|
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
|
||||||
|
weightedVariation(quadraturePoint) =
|
||||||
|
-2.0 * fractionalAngularVelocityVariation * data.quadratureWeights(quadraturePoint) *
|
||||||
|
data.rotationPotential(quadraturePoint);
|
||||||
|
}
|
||||||
|
elementAction.SetSize(data.enthalpyDofs.Size());
|
||||||
|
data.enthalpyBasis.MultTranspose(weightedVariation, elementAction);
|
||||||
|
if (data.enthalpyDofTransformation != nullptr) {
|
||||||
|
data.enthalpyDofTransformation->TransformDual(elementAction);
|
||||||
|
}
|
||||||
|
localAction.AddElementVector(data.enthalpyDofs, elementAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
local_to_true(*m_fem.enthalpyFes, localAction, m_fullEnthalpyAction);
|
||||||
|
action.SetSize(m_context.GetEnthalpyMap().reduced_size());
|
||||||
|
m_context.GetEnthalpyMap().gather(m_fullEnthalpyAction, action);
|
||||||
|
++m_algebraicJacobianStatistics.rotationAmplitudeApplications;
|
||||||
|
}
|
||||||
|
|
||||||
void PreparedHydrostaticEquilibriumOperator::ApplyAlgebraicJacobianAction(
|
void PreparedHydrostaticEquilibriumOperator::ApplyAlgebraicJacobianAction(
|
||||||
const mfem::Vector &enthalpyVariation,
|
const mfem::Vector &enthalpyVariation,
|
||||||
const mfem::Vector &gravityPotentialVariation,
|
const mfem::Vector &gravityPotentialVariation,
|
||||||
|
|||||||
@@ -356,8 +356,11 @@ namespace mean_field::operators {
|
|||||||
m_rootManifest(
|
m_rootManifest(
|
||||||
constructionData.valueSizes,
|
constructionData.valueSizes,
|
||||||
constructionData.residualSizes,
|
constructionData.residualSizes,
|
||||||
fixedMassConstraint.targetMass().value(),
|
StellarEquilibriumSpecificationModel{
|
||||||
surfaceConstraint.descriptor().targetPressure,
|
equationOfState,
|
||||||
|
surface::Isobaric{
|
||||||
|
dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
|
||||||
|
fixedMassConstraint.specification()},
|
||||||
constructionData.pressureSurfaceRows.size()
|
constructionData.pressureSurfaceRows.size()
|
||||||
),
|
),
|
||||||
m_gravityStateOffsets(constructionData.gravityStateOffsets),
|
m_gravityStateOffsets(constructionData.gravityStateOffsets),
|
||||||
@@ -431,6 +434,7 @@ namespace mean_field::operators {
|
|||||||
m_fullMechanicalAction.SetSize(m_domainDeformation.volumeDisplacementSize());
|
m_fullMechanicalAction.SetSize(m_domainDeformation.volumeDisplacementSize());
|
||||||
m_surfaceShapeAction.SetSize(m_domainDeformation.parameterCount());
|
m_surfaceShapeAction.SetSize(m_domainDeformation.parameterCount());
|
||||||
m_pullbackDerivativeAction.SetSize(m_domainDeformation.parameterCount());
|
m_pullbackDerivativeAction.SetSize(m_domainDeformation.parameterCount());
|
||||||
|
m_densityVolumeIntegralAction.SetSize(1);
|
||||||
|
|
||||||
m_gravityState = 0.0;
|
m_gravityState = 0.0;
|
||||||
m_gravityDirection = 0.0;
|
m_gravityDirection = 0.0;
|
||||||
@@ -441,6 +445,7 @@ namespace mean_field::operators {
|
|||||||
m_fullMechanicalAction = 0.0;
|
m_fullMechanicalAction = 0.0;
|
||||||
m_surfaceShapeAction = 0.0;
|
m_surfaceShapeAction = 0.0;
|
||||||
m_pullbackDerivativeAction = 0.0;
|
m_pullbackDerivativeAction = 0.0;
|
||||||
|
m_densityVolumeIntegralAction = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedStellarEquilibriumReport PreparedStellarEquilibriumOperator::Prepare(
|
PreparedStellarEquilibriumReport PreparedStellarEquilibriumOperator::Prepare(
|
||||||
@@ -494,13 +499,13 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
const auto rootState = m_rootManifest.stateView(state);
|
const auto rootState = m_rootManifest.stateView(state);
|
||||||
|
|
||||||
const mfem::Vector reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
|
const auto reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
|
||||||
const mfem::Vector surfaceDeformationParameters =
|
const auto surfaceDeformationParameters =
|
||||||
rootState.block(utils::blocks::surface_deformation_field.parameters_term);
|
rootState.block(utils::blocks::surface_deformation_field.parameters_term);
|
||||||
const mfem::Vector gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
|
const auto gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
|
||||||
const mfem::Vector gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
|
const auto gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
|
||||||
const mfem::Vector reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
|
const auto reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
|
||||||
const mfem::Vector bernoulli =
|
const auto bernoulli =
|
||||||
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
||||||
|
|
||||||
const bool generatedGeometryChanged =
|
const bool generatedGeometryChanged =
|
||||||
@@ -633,13 +638,13 @@ namespace mean_field::operators {
|
|||||||
|
|
||||||
const auto rootDirection = m_rootManifest.directionView(direction);
|
const auto rootDirection = m_rootManifest.directionView(direction);
|
||||||
|
|
||||||
const mfem::Vector reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
|
const auto reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
|
||||||
const mfem::Vector surfaceDeformationDirection =
|
const auto surfaceDeformationDirection =
|
||||||
rootDirection.block(utils::blocks::surface_deformation_field.parameters_term);
|
rootDirection.block(utils::blocks::surface_deformation_field.parameters_term);
|
||||||
const mfem::Vector gravityGradientDirection = rootDirection.block(utils::blocks::gravity_field.gradient_term);
|
const auto gravityGradientDirection = rootDirection.block(utils::blocks::gravity_field.gradient_term);
|
||||||
const mfem::Vector gravityPotentialDirection = rootDirection.block(utils::blocks::gravity_field.poisson_term);
|
const auto gravityPotentialDirection = rootDirection.block(utils::blocks::gravity_field.poisson_term);
|
||||||
const mfem::Vector reducedEnthalpyDirection = rootDirection.block(utils::blocks::enthalpy_field.specific_term);
|
const auto reducedEnthalpyDirection = rootDirection.block(utils::blocks::enthalpy_field.specific_term);
|
||||||
const mfem::Vector bernoulliDirection =
|
const auto bernoulliDirection =
|
||||||
rootDirection.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
rootDirection.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
||||||
|
|
||||||
m_domainDeformation.applyJacobian(
|
m_domainDeformation.applyJacobian(
|
||||||
@@ -794,6 +799,41 @@ namespace mean_field::operators {
|
|||||||
return m_massNormalizationOperator;
|
return m_massNormalizationOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double PreparedStellarEquilibriumOperator::ApplyDensityVolumeIntegralDensityAction(
|
||||||
|
const mfem::Vector &densityDirection
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
m_massNormalizationOperator.ApplyDensityJacobianAction(
|
||||||
|
densityDirection,
|
||||||
|
m_densityVolumeIntegralAction
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
m_densityVolumeIntegralAction.Size() == 1,
|
||||||
|
"The density-volume integral must produce one global scalar."
|
||||||
|
);
|
||||||
|
return m_densityVolumeIntegralAction(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double PreparedStellarEquilibriumOperator::ApplyDensityVolumeIntegralSurfaceShapeAction(
|
||||||
|
const mfem::Vector &surfaceShapeDirection
|
||||||
|
) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
m_domainDeformation.applyJacobian(
|
||||||
|
m_surfaceDeformationParameters,
|
||||||
|
surfaceShapeDirection,
|
||||||
|
m_volumeDisplacementDirection
|
||||||
|
);
|
||||||
|
m_massNormalizationOperator.ApplyDisplacementJacobianAction(
|
||||||
|
m_volumeDisplacementDirection,
|
||||||
|
m_densityVolumeIntegralAction
|
||||||
|
);
|
||||||
|
MFEM_VERIFY(
|
||||||
|
m_densityVolumeIntegralAction.Size() == 1,
|
||||||
|
"The density-volume shape derivative must produce one global scalar."
|
||||||
|
);
|
||||||
|
return m_densityVolumeIntegralAction(0);
|
||||||
|
}
|
||||||
|
|
||||||
const PreparedPressureSurfaceConstraint &
|
const PreparedPressureSurfaceConstraint &
|
||||||
PreparedStellarEquilibriumOperator::GetSurfaceConstraintOperator() const noexcept {
|
PreparedStellarEquilibriumOperator::GetSurfaceConstraintOperator() const noexcept {
|
||||||
return m_surfaceConstraintOperator;
|
return m_surfaceConstraintOperator;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module;
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <numbers>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -133,21 +134,15 @@ namespace {
|
|||||||
|
|
||||||
namespace mean_field::seed::detail {
|
namespace mean_field::seed::detail {
|
||||||
ProjectedRadialFields projectRadialFields(
|
ProjectedRadialFields projectRadialFields(
|
||||||
const equilibrium::StellarDiscretization &discretization,
|
fem::FEM &finiteElementModel,
|
||||||
const RadialProfile &profile,
|
const RadialProfile &profile,
|
||||||
const dimensions::MassValue targetMass,
|
const dimensions::MassValue targetMass,
|
||||||
const dimensions::PressureValue targetSurfacePressure,
|
|
||||||
const StellarEquilibriumProjectionOptions &options
|
const StellarEquilibriumProjectionOptions &options
|
||||||
) {
|
) {
|
||||||
validate_profile(profile);
|
validate_profile(profile);
|
||||||
if (!std::isfinite(options.surfaceRadiusRelativeTolerance) || options.surfaceRadiusRelativeTolerance < 0.0) {
|
if (!std::isfinite(options.surfaceRadiusRelativeTolerance) || options.surfaceRadiusRelativeTolerance < 0.0) {
|
||||||
throw std::invalid_argument("The surface-radius projection tolerance must be finite and nonnegative.");
|
throw std::invalid_argument("The surface-radius projection tolerance must be finite and nonnegative.");
|
||||||
}
|
}
|
||||||
if (targetSurfacePressure.value() != 0.0) {
|
|
||||||
throw std::invalid_argument("A Lane-Emden radial seed requires a zero-pressure isobaric surface.");
|
|
||||||
}
|
|
||||||
|
|
||||||
fem::FEM &finiteElementModel = discretization.finiteElementModel();
|
|
||||||
const SurfaceRadiusRange surfaceRadius = measure_surface_radius(finiteElementModel);
|
const SurfaceRadiusRange surfaceRadius = measure_surface_radius(finiteElementModel);
|
||||||
const double targetRadius = profile.stellarRadius.value();
|
const double targetRadius = profile.stellarRadius.value();
|
||||||
const double comparisonScale = std::max({targetRadius, surfaceRadius.maximum, 1.0e-300});
|
const double comparisonScale = std::max({targetRadius, surfaceRadius.maximum, 1.0e-300});
|
||||||
@@ -185,6 +180,20 @@ namespace mean_field::seed::detail {
|
|||||||
const physics::GravitySolution gravitySolution =
|
const physics::GravitySolution gravitySolution =
|
||||||
physics::solve_gravity_field(finiteElementModel, options.gravity, densityField, displacementField);
|
physics::solve_gravity_field(finiteElementModel, options.gravity, densityField, displacementField);
|
||||||
|
|
||||||
|
double radialMomentIntegral = 0.0;
|
||||||
|
for (int index = 0; index + 1 < profile.radius.Size(); ++index) {
|
||||||
|
const double leftRadius = profile.radius(index);
|
||||||
|
const double rightRadius = profile.radius(index + 1);
|
||||||
|
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
|
||||||
|
const double rightIntegrand = profile.density(index + 1) * std::pow(rightRadius, 4);
|
||||||
|
radialMomentIntegral +=
|
||||||
|
0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
|
||||||
|
}
|
||||||
|
const double sphericalMomentOfInertia = (8.0 * std::numbers::pi / 3.0) * radialMomentIntegral;
|
||||||
|
if (!std::isfinite(sphericalMomentOfInertia) || sphericalMomentOfInertia <= 0.0) {
|
||||||
|
throw std::runtime_error("The radial profile has no finite, positive moment of inertia.");
|
||||||
|
}
|
||||||
|
|
||||||
const field::FieldDofGridFunctionAdapter densityAdapter =
|
const field::FieldDofGridFunctionAdapter densityAdapter =
|
||||||
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*finiteElementModel.densityFes);
|
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*finiteElementModel.densityFes);
|
||||||
const field::FieldDofGridFunctionAdapter enthalpyAdapter =
|
const field::FieldDofGridFunctionAdapter enthalpyAdapter =
|
||||||
@@ -203,7 +212,8 @@ namespace mean_field::seed::detail {
|
|||||||
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
|
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
|
||||||
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
|
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
|
||||||
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
|
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
|
||||||
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius
|
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
|
||||||
|
.sphericalMomentOfInertia = sphericalMomentOfInertia
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} // namespace mean_field::seed::detail
|
} // namespace mean_field::seed::detail
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
export module mean_field:equilibrium.stellar_discretization;
|
export module mean_field:equilibrium.stellar_discretization;
|
||||||
|
|
||||||
export import :fem;
|
export import :fem;
|
||||||
export import :mapping.domain_mapper;
|
export import :mapping.domain_mapper;
|
||||||
|
export import :normalization.physical_riesz;
|
||||||
|
|
||||||
export namespace mean_field::equilibrium {
|
export namespace mean_field::equilibrium {
|
||||||
/*
|
/*
|
||||||
@@ -18,26 +22,78 @@ export namespace mean_field::equilibrium {
|
|||||||
* mutable field workspaces. Separating those workspaces is a prerequisite
|
* mutable field workspaces. Separating those workspaces is a prerequisite
|
||||||
* for shared discretization ownership by solved Structure objects.
|
* for shared discretization ownership by solved Structure objects.
|
||||||
*/
|
*/
|
||||||
class StellarDiscretization final {
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
class StellarDiscretizationFor final {
|
||||||
public:
|
public:
|
||||||
explicit StellarDiscretization(fem::FEM &finiteElementModel)
|
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
|
||||||
: StellarDiscretization(
|
|
||||||
|
explicit StellarDiscretizationFor(fem::FEM &finiteElementModel)
|
||||||
|
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
|
||||||
|
: StellarDiscretizationFor(
|
||||||
finiteElementModel,
|
finiteElementModel,
|
||||||
RequireDomainMapper(finiteElementModel)
|
RequireDomainMapper(finiteElementModel),
|
||||||
|
normalization::Unnormalized{}
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
StellarDiscretization(
|
StellarDiscretizationFor(
|
||||||
fem::FEM &finiteElementModel,
|
fem::FEM &finiteElementModel,
|
||||||
const mapping::DomainMapper &domainMapper
|
const mapping::DomainMapper &domainMapper
|
||||||
|
)
|
||||||
|
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
|
||||||
|
: StellarDiscretizationFor(
|
||||||
|
finiteElementModel,
|
||||||
|
domainMapper,
|
||||||
|
normalization::Unnormalized{}
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
StellarDiscretizationFor(
|
||||||
|
fem::FEM &,
|
||||||
|
mapping::DomainMapper &&
|
||||||
|
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
|
||||||
|
|
||||||
|
StellarDiscretizationFor(
|
||||||
|
fem::FEM &,
|
||||||
|
const mapping::DomainMapper &&
|
||||||
|
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
|
||||||
|
|
||||||
|
StellarDiscretizationFor(
|
||||||
|
fem::FEM &finiteElementModel,
|
||||||
|
NormalizationPrescriptionType normalizationPrescription
|
||||||
|
)
|
||||||
|
: StellarDiscretizationFor(
|
||||||
|
finiteElementModel,
|
||||||
|
RequireDomainMapper(finiteElementModel),
|
||||||
|
std::move(normalizationPrescription)
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
StellarDiscretizationFor(
|
||||||
|
fem::FEM &finiteElementModel,
|
||||||
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
NormalizationPrescriptionType normalizationPrescription
|
||||||
)
|
)
|
||||||
: m_finiteElementModel(std::addressof(finiteElementModel)),
|
: m_finiteElementModel(std::addressof(finiteElementModel)),
|
||||||
m_domainMapper(std::addressof(domainMapper)) {
|
m_domainMapper(std::addressof(domainMapper)),
|
||||||
|
m_normalizationPrescription(std::move(normalizationPrescription)) {
|
||||||
if (!finiteElementModel.okay()) {
|
if (!finiteElementModel.okay()) {
|
||||||
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
|
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StellarDiscretizationFor(
|
||||||
|
fem::FEM &,
|
||||||
|
mapping::DomainMapper &&,
|
||||||
|
NormalizationPrescriptionType
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
StellarDiscretizationFor(
|
||||||
|
fem::FEM &,
|
||||||
|
const mapping::DomainMapper &&,
|
||||||
|
NormalizationPrescriptionType
|
||||||
|
) = delete;
|
||||||
|
|
||||||
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
|
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
|
||||||
return *m_finiteElementModel;
|
return *m_finiteElementModel;
|
||||||
}
|
}
|
||||||
@@ -46,6 +102,10 @@ export namespace mean_field::equilibrium {
|
|||||||
return *m_domainMapper;
|
return *m_domainMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const noexcept {
|
||||||
|
return m_normalizationPrescription;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool isCurrent() const noexcept {
|
[[nodiscard]] bool isCurrent() const noexcept {
|
||||||
return m_finiteElementModel != nullptr && m_domainMapper != nullptr && m_finiteElementModel->okay();
|
return m_finiteElementModel != nullptr && m_domainMapper != nullptr && m_finiteElementModel->okay();
|
||||||
}
|
}
|
||||||
@@ -60,5 +120,64 @@ export namespace mean_field::equilibrium {
|
|||||||
|
|
||||||
fem::FEM *m_finiteElementModel;
|
fem::FEM *m_finiteElementModel;
|
||||||
const mapping::DomainMapper *m_domainMapper;
|
const mapping::DomainMapper *m_domainMapper;
|
||||||
|
NormalizationPrescriptionType m_normalizationPrescription;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
StellarDiscretizationFor(fem::FEM &, Normalization)
|
||||||
|
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
StellarDiscretizationFor(fem::FEM &, const mapping::DomainMapper &, Normalization)
|
||||||
|
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
|
||||||
|
|
||||||
|
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsStellarDiscretization : std::false_type { };
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
struct IsStellarDiscretization<StellarDiscretizationFor<Normalization>> : std::true_type { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StellarDiscretizationType = IsStellarDiscretization<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
[[nodiscard]] auto makeStellarDiscretization(
|
||||||
|
fem::FEM &finiteElementModel,
|
||||||
|
Normalization normalizationPrescription
|
||||||
|
) {
|
||||||
|
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
|
||||||
|
finiteElementModel,
|
||||||
|
std::move(normalizationPrescription)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
[[nodiscard]] auto makeStellarDiscretization(
|
||||||
|
fem::FEM &finiteElementModel,
|
||||||
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
Normalization normalizationPrescription
|
||||||
|
) {
|
||||||
|
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
|
||||||
|
finiteElementModel,
|
||||||
|
domainMapper,
|
||||||
|
std::move(normalizationPrescription)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
|
||||||
|
makeStellarDiscretization(
|
||||||
|
fem::FEM &,
|
||||||
|
mapping::DomainMapper &&,
|
||||||
|
Normalization
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
template <normalization::NormalizationPrescription Normalization>
|
||||||
|
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
|
||||||
|
makeStellarDiscretization(
|
||||||
|
fem::FEM &,
|
||||||
|
const mapping::DomainMapper &&,
|
||||||
|
Normalization
|
||||||
|
) = delete;
|
||||||
} // namespace mean_field::equilibrium
|
} // namespace mean_field::equilibrium
|
||||||
|
|||||||
@@ -231,6 +231,28 @@ export namespace mean_field::field {
|
|||||||
static_assert(constraintsAreValid);
|
static_assert(constraintsAreValid);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Scalar angular speed generated by FixedAngularMomentum. The axis and
|
||||||
|
// center belong to the compiled invariant, so the nonlinear coordinate
|
||||||
|
// contains only the signed speed along that fixed unit axis.
|
||||||
|
struct AngularVelocity {
|
||||||
|
static constexpr std::string_view name = "angular_velocity";
|
||||||
|
|
||||||
|
using PhysicalQuantity = dimensions::quantity::AngularVelocity;
|
||||||
|
using Support = NonSpatialSupport;
|
||||||
|
|
||||||
|
struct Scalar final : GlobalScalarQ {
|
||||||
|
static constexpr std::string_view symbol = "Omega";
|
||||||
|
};
|
||||||
|
|
||||||
|
using Quantities = TypeList<Scalar>;
|
||||||
|
using Constraints = TypeList<>;
|
||||||
|
using FormList = TypeList<>;
|
||||||
|
|
||||||
|
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
|
||||||
|
|
||||||
|
static_assert(constraintsAreValid);
|
||||||
|
};
|
||||||
|
|
||||||
// Solver border generated by FixedCentralDensity. This is deliberately a
|
// Solver border generated by FixedCentralDensity. This is deliberately a
|
||||||
// non-spatial numerical coordinate rather than a physical stellar field.
|
// non-spatial numerical coordinate rather than a physical stellar field.
|
||||||
struct CentralDensityBorder {
|
struct CentralDensityBorder {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export import :quadrature.mfem;
|
|||||||
export import :solver.fields;
|
export import :solver.fields;
|
||||||
export import :solver.preconditioning_diagnostics;
|
export import :solver.preconditioning_diagnostics;
|
||||||
export import :preconditioning;
|
export import :preconditioning;
|
||||||
|
export import :normalization;
|
||||||
export import :utils.blocks;
|
export import :utils.blocks;
|
||||||
export import :operators.gravity_field;
|
export import :operators.gravity_field;
|
||||||
export import :operators.gravity_field_jacobian;
|
export import :operators.gravity_field_jacobian;
|
||||||
@@ -60,6 +61,7 @@ export import :model.structure.polytropic;
|
|||||||
export import :model.specifications;
|
export import :model.specifications;
|
||||||
export import :model.typed_stellar;
|
export import :model.typed_stellar;
|
||||||
export import :model.compiled_fixed_mass;
|
export import :model.compiled_fixed_mass;
|
||||||
|
export import :model.compiled_fixed_angular_momentum;
|
||||||
export import :model.compiled_fixed_central_density;
|
export import :model.compiled_fixed_central_density;
|
||||||
export import :eos.quantities;
|
export import :eos.quantities;
|
||||||
export import :eos.relations;
|
export import :eos.relations;
|
||||||
@@ -85,11 +87,13 @@ export import :model.stellar;
|
|||||||
export import :operators.root_manifest;
|
export import :operators.root_manifest;
|
||||||
export import :operators.prepared_constraint;
|
export import :operators.prepared_constraint;
|
||||||
export import :operators.prepared_mass_normalization;
|
export import :operators.prepared_mass_normalization;
|
||||||
|
export import :operators.prepared_angular_momentum;
|
||||||
export import :operators.prepared_central_density;
|
export import :operators.prepared_central_density;
|
||||||
export import :operators.prepared_centering_constraint;
|
export import :operators.prepared_centering_constraint;
|
||||||
export import :operators.prepared_surface_constraint;
|
export import :operators.prepared_surface_constraint;
|
||||||
export import :operators.prepared_stellar_equilibrium;
|
export import :operators.prepared_stellar_equilibrium;
|
||||||
export import :operators.prepared_central_density_stellar_equilibrium;
|
export import :operators.stellar_equilibrium_compiler;
|
||||||
|
export import :operators.prepared_variadic_stellar_equilibrium;
|
||||||
export import :equilibrium.stellar_discretization;
|
export import :equilibrium.stellar_discretization;
|
||||||
export import :operators.stellar_equilibrium_problem;
|
export import :operators.stellar_equilibrium_problem;
|
||||||
export import :seed.stellar_equilibrium_projection;
|
export import :seed.stellar_equilibrium_projection;
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
export module mean_field:model.compiled_fixed_angular_momentum;
|
||||||
|
|
||||||
|
export import :field.registry;
|
||||||
|
export import :model.compiled_fixed_mass;
|
||||||
|
export import :physics.rigid_rotation;
|
||||||
|
export import :utils.blocks;
|
||||||
|
|
||||||
|
export namespace mean_field::models {
|
||||||
|
using FixedAngularMomentumLayoutRequest = ConstraintLayoutRequest<
|
||||||
|
FixedAngularMomentum,
|
||||||
|
PhysicalCoordinateFor<FixedAngularMomentum>,
|
||||||
|
ResidualFor<FixedAngularMomentum>,
|
||||||
|
utils::blocks::fixed_angular_momentum::angular_velocity::value,
|
||||||
|
utils::blocks::fixed_angular_momentum::angular_velocity::residual,
|
||||||
|
utils::blocks::fixed_angular_momentum::angular_velocity,
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value,
|
||||||
|
utils::blocks::fixed_angular_momentum::angular_velocity::value>;
|
||||||
|
|
||||||
|
class CompiledFixedAngularMomentum final {
|
||||||
|
public:
|
||||||
|
using SpecificationType = FixedAngularMomentum;
|
||||||
|
using LayoutRequest = FixedAngularMomentumLayoutRequest;
|
||||||
|
using AngularVelocityType = typename LayoutRequest::GeneratedValueType;
|
||||||
|
using ResidualType = typename LayoutRequest::GeneratedResidualType;
|
||||||
|
using AngularVelocityField = field::AngularVelocity;
|
||||||
|
|
||||||
|
explicit CompiledFixedAngularMomentum(const FixedAngularMomentum specification) noexcept
|
||||||
|
: m_specification(specification) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const FixedAngularMomentum &specification() const noexcept {
|
||||||
|
return m_specification;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] dimensions::AngularMomentumValue targetAngularMomentum() const noexcept {
|
||||||
|
return m_specification.targetAngularMomentum();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] physics::RigidRotation makeRotation(const double angularVelocity) const {
|
||||||
|
mfem::Vector velocity(3);
|
||||||
|
mfem::Vector center(3);
|
||||||
|
for (int component = 0; component < 3; ++component) {
|
||||||
|
velocity(component) = angularVelocity * m_specification.axis()[static_cast<std::size_t>(component)];
|
||||||
|
center(component) = m_specification.center()[static_cast<std::size_t>(component)];
|
||||||
|
}
|
||||||
|
return {velocity, center};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static consteval LayoutRequest layoutRequest() noexcept {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FixedAngularMomentum m_specification;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline CompiledFixedAngularMomentum compileConstraint(
|
||||||
|
const FixedAngularMomentum specification
|
||||||
|
) noexcept {
|
||||||
|
return CompiledFixedAngularMomentum{specification};
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(ConstraintLayoutRequestType<FixedAngularMomentumLayoutRequest>);
|
||||||
|
static_assert(CompiledConstraint<CompiledFixedAngularMomentum>);
|
||||||
|
} // namespace mean_field::models
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -14,21 +14,41 @@ export namespace mean_field::model {
|
|||||||
template <typename SpecificationSet> class StellarModel;
|
template <typename SpecificationSet> class StellarModel;
|
||||||
|
|
||||||
template <models::ModelSpecification... CanonicalSpecifications>
|
template <models::ModelSpecification... CanonicalSpecifications>
|
||||||
|
requires models::ValidModelSpecificationPack<CanonicalSpecifications...> &&
|
||||||
|
models::SpecificationOperatorSignature<
|
||||||
|
models::detail::SpecificationSetStorage<CanonicalSpecifications...>>::symbolicallySquare
|
||||||
class StellarModel<models::detail::SpecificationSetStorage<CanonicalSpecifications...>> final {
|
class StellarModel<models::detail::SpecificationSetStorage<CanonicalSpecifications...>> final {
|
||||||
public:
|
public:
|
||||||
using SpecificationTypes = models::detail::SpecificationSetStorage<CanonicalSpecifications...>;
|
using SpecificationTypes = models::detail::SpecificationSetStorage<CanonicalSpecifications...>;
|
||||||
using OperatorSignature = models::SpecificationOperatorSignature<SpecificationTypes>;
|
using OperatorSignature = models::SpecificationOperatorSignature<SpecificationTypes>;
|
||||||
using Storage = models::Model<CanonicalSpecifications...>;
|
using Storage = models::Model<CanonicalSpecifications...>;
|
||||||
|
using EquationOfStateType =
|
||||||
|
models::SpecificationForRoleT<models::SpecificationRole::constitutive_law, SpecificationTypes>;
|
||||||
|
|
||||||
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
|
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
|
||||||
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
|
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
|
||||||
static constexpr bool hasCompleteEquilibriumCompiler = Storage::hasCompleteRootCompiler;
|
static constexpr bool hasCompleteEquilibriumDeclaration =
|
||||||
static constexpr models::EquilibriumSystemCompilation compilationClass = Storage::compilationClass;
|
Storage::hasCompleteEquilibriumDeclaration;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role>
|
||||||
|
using SpecificationsForRole = models::SpecificationsForRoleT<Role, SpecificationTypes>;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role>
|
||||||
|
requires models::HasUniqueSpecificationForRole<Role, SpecificationTypes>
|
||||||
|
using SpecificationForRole = models::SpecificationForRoleT<Role, SpecificationTypes>;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role>
|
||||||
|
static constexpr std::size_t specificationRoleCount = models::specificationRoleCount<Role, SpecificationTypes>;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role>
|
||||||
|
static constexpr bool hasSpecificationsForRole = models::HasSpecificationsForRole<Role, SpecificationTypes>;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role>
|
||||||
|
static constexpr bool hasUniqueSpecificationForRole =
|
||||||
|
models::HasUniqueSpecificationForRole<Role, SpecificationTypes>;
|
||||||
|
|
||||||
template <typename... Arguments>
|
template <typename... Arguments>
|
||||||
requires std::constructible_from<
|
requires std::constructible_from<Storage, Arguments...>
|
||||||
Storage,
|
|
||||||
Arguments...>
|
|
||||||
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
|
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +61,25 @@ export namespace mean_field::model {
|
|||||||
template <models::ModelSpecification Specification>
|
template <models::ModelSpecification Specification>
|
||||||
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
|
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role>
|
||||||
|
requires models::HasUniqueSpecificationForRole<Role, SpecificationTypes>
|
||||||
|
[[nodiscard]] const models::SpecificationForRoleT<Role, SpecificationTypes> &
|
||||||
|
specificationForRole() const noexcept {
|
||||||
|
using Specification = models::SpecificationForRoleT<Role, SpecificationTypes>;
|
||||||
|
return specification<Specification>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const EquationOfStateType &equationOfState() const noexcept {
|
||||||
|
return specificationForRole<models::SpecificationRole::constitutive_law>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename = void>
|
||||||
|
requires models::HasUniqueSpecificationForRole<models::SpecificationRole::boundary_condition,
|
||||||
|
SpecificationTypes>
|
||||||
|
[[nodiscard]] const auto &surfaceCondition() const noexcept {
|
||||||
|
return specificationForRole<models::SpecificationRole::boundary_condition>();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
|
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
|
||||||
runtimeSpecificationDescriptors() noexcept {
|
runtimeSpecificationDescriptors() noexcept {
|
||||||
return Storage::runtimeSpecificationDescriptors();
|
return Storage::runtimeSpecificationDescriptors();
|
||||||
@@ -56,11 +95,69 @@ export namespace mean_field::model {
|
|||||||
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
|
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <typename Candidate> struct IsStellarModel : std::false_type { };
|
template <typename Candidate, typename = void> struct IsStellarModel : std::false_type {};
|
||||||
|
|
||||||
template <typename SpecificationSet> struct IsStellarModel<StellarModel<SpecificationSet>> : std::true_type { };
|
template <typename SpecificationSet>
|
||||||
|
struct IsStellarModel<
|
||||||
|
StellarModel<SpecificationSet>,
|
||||||
|
std::void_t<typename StellarModel<SpecificationSet>::SpecificationTypes,
|
||||||
|
typename StellarModel<SpecificationSet>::OperatorSignature,
|
||||||
|
decltype(StellarModel<SpecificationSet>::specificationCount),
|
||||||
|
decltype(StellarModel<SpecificationSet>::hasCompleteEquilibriumDeclaration)>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate, bool = IsStellarModel<Candidate>::value>
|
||||||
|
struct StellarModelRoleSelection {
|
||||||
|
using Types = models::ModelTypeList<>;
|
||||||
|
|
||||||
|
static constexpr std::size_t count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate>
|
||||||
|
struct StellarModelRoleSelection<Role, Candidate, true> {
|
||||||
|
using Types = models::SpecificationsForRoleT<Role, typename Candidate::SpecificationTypes>;
|
||||||
|
|
||||||
|
static constexpr std::size_t count =
|
||||||
|
models::specificationRoleCount<Role, typename Candidate::SpecificationTypes>;
|
||||||
|
};
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept StellarModelType = detail::IsStellarModel<std::remove_cvref_t<Candidate>>::value;
|
concept StellarModelType = detail::IsStellarModel<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate>
|
||||||
|
inline constexpr std::size_t specificationRoleCount =
|
||||||
|
detail::StellarModelRoleSelection<Role, std::remove_cvref_t<Candidate>>::count;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate>
|
||||||
|
concept HasSpecificationsForRole = StellarModelType<Candidate> && specificationRoleCount<Role, Candidate> > 0;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate>
|
||||||
|
concept HasUniqueSpecificationForRole = StellarModelType<Candidate> && specificationRoleCount<Role, Candidate> == 1;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate>
|
||||||
|
requires StellarModelType<Candidate>
|
||||||
|
using SpecificationsForRoleT =
|
||||||
|
typename detail::StellarModelRoleSelection<Role, std::remove_cvref_t<Candidate>>::Types;
|
||||||
|
|
||||||
|
template <models::SpecificationRole Role, typename Candidate>
|
||||||
|
requires HasUniqueSpecificationForRole<Role, Candidate>
|
||||||
|
using SpecificationForRoleT =
|
||||||
|
models::SpecificationForRoleT<Role, typename std::remove_cvref_t<Candidate>::SpecificationTypes>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept HasEquationOfState = HasUniqueSpecificationForRole<models::SpecificationRole::constitutive_law, Candidate>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept HasSurfaceCondition = HasSpecificationsForRole<models::SpecificationRole::boundary_condition, Candidate>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept HasUniqueSurfaceCondition =
|
||||||
|
HasUniqueSpecificationForRole<models::SpecificationRole::boundary_condition, Candidate>;
|
||||||
|
|
||||||
|
template <HasEquationOfState Candidate>
|
||||||
|
using EquationOfStateType = SpecificationForRoleT<models::SpecificationRole::constitutive_law, Candidate>;
|
||||||
|
|
||||||
|
template <HasUniqueSurfaceCondition Candidate>
|
||||||
|
using SurfaceConditionType = SpecificationForRoleT<models::SpecificationRole::boundary_condition, Candidate>;
|
||||||
} // namespace mean_field::model
|
} // namespace mean_field::model
|
||||||
|
|||||||
6
libmeanfield/interface/normalization/normalization.cppm
Normal file
6
libmeanfield/interface/normalization/normalization.cppm
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export module mean_field:normalization;
|
||||||
|
|
||||||
|
export import :normalization.plan;
|
||||||
|
export import :normalization.physical_riesz;
|
||||||
|
export import :normalization.operators;
|
||||||
|
export import :normalization.stellar_equilibrium;
|
||||||
621
libmeanfield/interface/normalization/operators.cppm
Normal file
621
libmeanfield/interface/normalization/operators.cppm
Normal file
@@ -0,0 +1,621 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
export module mean_field:normalization.operators;
|
||||||
|
|
||||||
|
export import :normalization.physical_riesz;
|
||||||
|
|
||||||
|
export namespace mean_field::normalization {
|
||||||
|
class DiagonalNormalization final {
|
||||||
|
public:
|
||||||
|
DiagonalNormalization(
|
||||||
|
mfem::Vector stateToNormalized,
|
||||||
|
mfem::Vector residualToNormalized
|
||||||
|
)
|
||||||
|
: m_stateToNormalized(std::move(stateToNormalized)),
|
||||||
|
m_residualToNormalized(std::move(residualToNormalized)) {
|
||||||
|
ValidateFactors(m_stateToNormalized, "state");
|
||||||
|
ValidateFactors(m_residualToNormalized, "residual");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static DiagonalNormalization Identity(
|
||||||
|
const int stateSize,
|
||||||
|
const int residualSize
|
||||||
|
) {
|
||||||
|
if (stateSize < 0 || residualSize < 0) {
|
||||||
|
throw std::invalid_argument("Normalization dimensions cannot be negative.");
|
||||||
|
}
|
||||||
|
mfem::Vector state(stateSize);
|
||||||
|
mfem::Vector residual(residualSize);
|
||||||
|
state = 1.0;
|
||||||
|
residual = 1.0;
|
||||||
|
return {std::move(state), std::move(residual)};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int StateSize() const noexcept {
|
||||||
|
return m_stateToNormalized.Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int ResidualSize() const noexcept {
|
||||||
|
return m_residualToNormalized.Size();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Vector &StateFactors() const noexcept {
|
||||||
|
return m_stateToNormalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Vector &ResidualFactors() const noexcept {
|
||||||
|
return m_residualToNormalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NormalizeState(
|
||||||
|
const mfem::Vector &physical,
|
||||||
|
mfem::Vector &normalized
|
||||||
|
) const {
|
||||||
|
Apply(m_stateToNormalized, physical, normalized, false, "state");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DenormalizeState(
|
||||||
|
const mfem::Vector &normalized,
|
||||||
|
mfem::Vector &physical
|
||||||
|
) const {
|
||||||
|
Apply(m_stateToNormalized, normalized, physical, true, "state");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NormalizeResidual(
|
||||||
|
const mfem::Vector &physical,
|
||||||
|
mfem::Vector &normalized
|
||||||
|
) const {
|
||||||
|
Apply(m_residualToNormalized, physical, normalized, false, "residual");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DenormalizeResidual(
|
||||||
|
const mfem::Vector &normalized,
|
||||||
|
mfem::Vector &physical
|
||||||
|
) const {
|
||||||
|
Apply(m_residualToNormalized, normalized, physical, true, "residual");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double LocalStateNormSquared(const mfem::Vector &physical) const {
|
||||||
|
return LocalNormSquared(m_stateToNormalized, physical, "state");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double LocalResidualNormSquared(const mfem::Vector &physical) const {
|
||||||
|
return LocalNormSquared(m_residualToNormalized, physical, "residual");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void ValidateFactors(
|
||||||
|
const mfem::Vector &factors,
|
||||||
|
const char *role
|
||||||
|
) {
|
||||||
|
for (int index = 0; index < factors.Size(); ++index) {
|
||||||
|
if (!std::isfinite(factors(index)) || factors(index) <= 0.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string("The ") + role + " normalization factors must be finite and positive."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Apply(
|
||||||
|
const mfem::Vector &factors,
|
||||||
|
const mfem::Vector &input,
|
||||||
|
mfem::Vector &output,
|
||||||
|
const bool inverse,
|
||||||
|
const char *role
|
||||||
|
) {
|
||||||
|
if (input.Size() != factors.Size()) {
|
||||||
|
throw std::invalid_argument(std::string("The ") + role + " vector has the wrong size.");
|
||||||
|
}
|
||||||
|
const bool exactAlias = input.GetData() == output.GetData() && input.Size() == output.Size();
|
||||||
|
if (!exactAlias) {
|
||||||
|
output.SetSize(input.Size());
|
||||||
|
}
|
||||||
|
for (int index = 0; index < input.Size(); ++index) {
|
||||||
|
const double value = input(index);
|
||||||
|
output(index) = inverse ? value / factors(index) : factors(index) * value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static double LocalNormSquared(
|
||||||
|
const mfem::Vector &factors,
|
||||||
|
const mfem::Vector &physical,
|
||||||
|
const char *role
|
||||||
|
) {
|
||||||
|
if (physical.Size() != factors.Size()) {
|
||||||
|
throw std::invalid_argument(std::string("The ") + role + " vector has the wrong size.");
|
||||||
|
}
|
||||||
|
double normSquared = 0.0;
|
||||||
|
for (int index = 0; index < physical.Size(); ++index) {
|
||||||
|
const double normalized = factors(index) * physical(index);
|
||||||
|
normSquared += normalized * normalized;
|
||||||
|
}
|
||||||
|
return normSquared;
|
||||||
|
}
|
||||||
|
|
||||||
|
mfem::Vector m_stateToNormalized;
|
||||||
|
mfem::Vector m_residualToNormalized;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Detection-safe public operation for an ordinary third-party runtime
|
||||||
|
* policy. The exact policy is recovered from the problem type and must own
|
||||||
|
* every method in its compiled plan. Its implementation remains beside
|
||||||
|
* the policy and is found by ADL, so adding a normalization family does
|
||||||
|
* not edit a library registry or switch. */
|
||||||
|
template <typename Problem>
|
||||||
|
concept RuntimePreparedNormalizationOperation =
|
||||||
|
requires(const std::remove_cvref_t<Problem> &problem) {
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType;
|
||||||
|
requires RuntimePreparedNormalizationFor<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType>;
|
||||||
|
{
|
||||||
|
problem.GetNormalizationPrescription()
|
||||||
|
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
|
||||||
|
{
|
||||||
|
prepareStellarNormalization(
|
||||||
|
problem.GetNormalizationPrescription(),
|
||||||
|
problem)
|
||||||
|
} -> std::same_as<DiagonalNormalization>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
class DiagonalNormalizationBuilder final {
|
||||||
|
public:
|
||||||
|
explicit DiagonalNormalizationBuilder(const utils::blocks::form_layout<Form> &layout)
|
||||||
|
: m_layout(&layout),
|
||||||
|
m_stateFactors(layout.value_offsets().Last()),
|
||||||
|
m_residualFactors(layout.residual_offsets().Last()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit DiagonalNormalizationBuilder(
|
||||||
|
utils::blocks::form_layout<Form> &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
explicit DiagonalNormalizationBuilder(
|
||||||
|
const utils::blocks::form_layout<Form> &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
|
||||||
|
void SetValueBlock(
|
||||||
|
const double physicalScale,
|
||||||
|
const mfem::Vector &primalGramDiagonal
|
||||||
|
) {
|
||||||
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
||||||
|
RequireUnassigned(m_valueAssigned[block], "value");
|
||||||
|
AssignBlock(
|
||||||
|
m_stateFactors,
|
||||||
|
m_layout->value_offsets()[block],
|
||||||
|
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block],
|
||||||
|
physicalScale,
|
||||||
|
primalGramDiagonal,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
m_valueAssigned[block] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
||||||
|
void SetResidualBlock(
|
||||||
|
const double physicalScale,
|
||||||
|
const mfem::Vector &primalGramDiagonal
|
||||||
|
) {
|
||||||
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||||
|
RequireUnassigned(m_residualAssigned[block], "residual");
|
||||||
|
AssignBlock(
|
||||||
|
m_residualFactors,
|
||||||
|
m_layout->residual_offsets()[block],
|
||||||
|
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block],
|
||||||
|
physicalScale,
|
||||||
|
primalGramDiagonal,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
m_residualAssigned[block] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
|
||||||
|
void SetValueGlobal(const double physicalScale) {
|
||||||
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
|
||||||
|
SetConstantMetricValueBlock<Block>(physicalScale, BlockSize(m_layout->value_offsets(), block));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
||||||
|
void SetResidualGlobal(const double physicalScale) {
|
||||||
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||||
|
mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block));
|
||||||
|
metric = 1.0;
|
||||||
|
SetResidualBlock<Block>(physicalScale, metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
|
||||||
|
void SetHybridResidualBlock(
|
||||||
|
const double physicalScale,
|
||||||
|
const mfem::Vector &bulkPrimalGramDiagonal,
|
||||||
|
const std::span<const int> pointRows,
|
||||||
|
const double pointMetric = 1.0
|
||||||
|
) {
|
||||||
|
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
|
||||||
|
const int size = BlockSize(m_layout->residual_offsets(), block);
|
||||||
|
if (bulkPrimalGramDiagonal.Size() != size) {
|
||||||
|
throw std::invalid_argument("The hybrid residual Gram diagonal has the wrong size.");
|
||||||
|
}
|
||||||
|
ValidateMetric(pointMetric);
|
||||||
|
|
||||||
|
std::vector<bool> isPointRow(static_cast<std::size_t>(size), false);
|
||||||
|
for (const int row : pointRows) {
|
||||||
|
if (row < 0 || row >= size) {
|
||||||
|
throw std::out_of_range("A hybrid point row lies outside its residual block.");
|
||||||
|
}
|
||||||
|
if (isPointRow[static_cast<std::size_t>(row)]) {
|
||||||
|
throw std::invalid_argument("A hybrid point row was supplied more than once.");
|
||||||
|
}
|
||||||
|
isPointRow[static_cast<std::size_t>(row)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mfem::Vector metric(size);
|
||||||
|
for (int row = 0; row < size; ++row) {
|
||||||
|
metric(row) = isPointRow[static_cast<std::size_t>(row)]
|
||||||
|
? pointMetric
|
||||||
|
: bulkPrimalGramDiagonal(row);
|
||||||
|
}
|
||||||
|
SetResidualBlock<Block>(physicalScale, metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] DiagonalNormalization Build() && {
|
||||||
|
for (const bool assigned : m_valueAssigned) {
|
||||||
|
if (!assigned) {
|
||||||
|
throw std::logic_error("The normalization is missing a value block.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const bool assigned : m_residualAssigned) {
|
||||||
|
if (!assigned) {
|
||||||
|
throw std::logic_error("The normalization is missing a residual block.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {std::move(m_stateFactors), std::move(m_residualFactors)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename Block>
|
||||||
|
void SetConstantMetricValueBlock(
|
||||||
|
const double physicalScale,
|
||||||
|
const int size
|
||||||
|
) {
|
||||||
|
mfem::Vector metric(size);
|
||||||
|
metric = 1.0;
|
||||||
|
SetValueBlock<Block>(physicalScale, metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static int BlockSize(
|
||||||
|
const mfem::Array<int> &offsets,
|
||||||
|
const int block
|
||||||
|
) noexcept {
|
||||||
|
return offsets[block + 1] - offsets[block];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RequireUnassigned(
|
||||||
|
const bool assigned,
|
||||||
|
const char *role
|
||||||
|
) {
|
||||||
|
if (assigned) {
|
||||||
|
throw std::logic_error(std::string("The ") + role + " block normalization was assigned twice.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ValidateMetric(const double metric) {
|
||||||
|
if (!std::isfinite(metric) || metric <= 0.0) {
|
||||||
|
throw std::invalid_argument("Every Riesz Gram diagonal entry must be finite and positive.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AssignBlock(
|
||||||
|
mfem::Vector &factors,
|
||||||
|
const int offset,
|
||||||
|
const int size,
|
||||||
|
const double physicalScale,
|
||||||
|
const mfem::Vector &primalGramDiagonal,
|
||||||
|
const bool dual
|
||||||
|
) {
|
||||||
|
if (!std::isfinite(physicalScale) || physicalScale <= 0.0) {
|
||||||
|
throw std::invalid_argument("A physical normalization scale must be finite and positive.");
|
||||||
|
}
|
||||||
|
if (primalGramDiagonal.Size() != size) {
|
||||||
|
throw std::invalid_argument("A Riesz Gram diagonal has the wrong block size.");
|
||||||
|
}
|
||||||
|
for (int index = 0; index < size; ++index) {
|
||||||
|
const double metric = primalGramDiagonal(index);
|
||||||
|
ValidateMetric(metric);
|
||||||
|
const double rieszFactor = std::sqrt(metric);
|
||||||
|
const double factor = dual
|
||||||
|
? 1.0 / (physicalScale * rieszFactor)
|
||||||
|
: rieszFactor / physicalScale;
|
||||||
|
if (!std::isfinite(factor) || factor <= 0.0) {
|
||||||
|
throw std::overflow_error("A normalization factor is not finite and positive.");
|
||||||
|
}
|
||||||
|
factors(offset + index) = factor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const utils::blocks::form_layout<Form> *m_layout;
|
||||||
|
mfem::Vector m_stateFactors;
|
||||||
|
mfem::Vector m_residualFactors;
|
||||||
|
std::array<bool, Form::value_block_count> m_valueAssigned{};
|
||||||
|
std::array<bool, Form::residual_block_count> m_residualAssigned{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScaledJacobianOperator final : public mfem::Operator {
|
||||||
|
public:
|
||||||
|
ScaledJacobianOperator(
|
||||||
|
const mfem::Operator &physicalJacobian,
|
||||||
|
const DiagonalNormalization &normalization
|
||||||
|
)
|
||||||
|
: mfem::Operator(normalization.ResidualSize(), normalization.StateSize()),
|
||||||
|
m_physicalJacobian(&physicalJacobian),
|
||||||
|
m_normalization(&normalization),
|
||||||
|
m_physicalDirection(normalization.StateSize()),
|
||||||
|
m_physicalAction(normalization.ResidualSize()) {
|
||||||
|
if (physicalJacobian.Width() != normalization.StateSize() ||
|
||||||
|
physicalJacobian.Height() != normalization.ResidualSize()) {
|
||||||
|
throw std::invalid_argument("The physical Jacobian and normalization dimensions do not agree.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaledJacobianOperator(
|
||||||
|
mfem::Operator &&,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledJacobianOperator(
|
||||||
|
const mfem::Operator &&,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledJacobianOperator(
|
||||||
|
const mfem::Operator &,
|
||||||
|
DiagonalNormalization &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledJacobianOperator(
|
||||||
|
const mfem::Operator &,
|
||||||
|
const DiagonalNormalization &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &normalizedDirection,
|
||||||
|
mfem::Vector &normalizedAction
|
||||||
|
) const override {
|
||||||
|
m_normalization->DenormalizeState(normalizedDirection, m_physicalDirection);
|
||||||
|
m_physicalJacobian->Mult(m_physicalDirection, m_physicalAction);
|
||||||
|
m_normalization->NormalizeResidual(m_physicalAction, normalizedAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const mfem::Operator *m_physicalJacobian;
|
||||||
|
const DiagonalNormalization *m_normalization;
|
||||||
|
mutable mfem::Vector m_physicalDirection;
|
||||||
|
mutable mfem::Vector m_physicalAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScaledInverseOperator final : public mfem::Operator {
|
||||||
|
public:
|
||||||
|
ScaledInverseOperator(
|
||||||
|
const mfem::Operator &physicalInverse,
|
||||||
|
const DiagonalNormalization &normalization
|
||||||
|
)
|
||||||
|
: mfem::Operator(normalization.StateSize(), normalization.ResidualSize()),
|
||||||
|
m_physicalInverse(&physicalInverse),
|
||||||
|
m_normalization(&normalization),
|
||||||
|
m_physicalResidual(normalization.ResidualSize()),
|
||||||
|
m_physicalCorrection(normalization.StateSize()) {
|
||||||
|
if (physicalInverse.Width() != normalization.ResidualSize() ||
|
||||||
|
physicalInverse.Height() != normalization.StateSize()) {
|
||||||
|
throw std::invalid_argument("The physical inverse and normalization dimensions do not agree.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaledInverseOperator(
|
||||||
|
mfem::Operator &&,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledInverseOperator(
|
||||||
|
const mfem::Operator &&,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledInverseOperator(
|
||||||
|
const mfem::Operator &,
|
||||||
|
DiagonalNormalization &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledInverseOperator(
|
||||||
|
const mfem::Operator &,
|
||||||
|
const DiagonalNormalization &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &normalizedResidual,
|
||||||
|
mfem::Vector &normalizedCorrection
|
||||||
|
) const override {
|
||||||
|
m_normalization->DenormalizeResidual(normalizedResidual, m_physicalResidual);
|
||||||
|
m_physicalInverse->Mult(m_physicalResidual, m_physicalCorrection);
|
||||||
|
m_normalization->NormalizeState(m_physicalCorrection, normalizedCorrection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const mfem::Operator *m_physicalInverse;
|
||||||
|
const DiagonalNormalization *m_normalization;
|
||||||
|
mutable mfem::Vector m_physicalResidual;
|
||||||
|
mutable mfem::Vector m_physicalCorrection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScaledPreconditionerStatistics final {
|
||||||
|
std::uint64_t operatorBindings{0};
|
||||||
|
std::uint64_t applications{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solver-compatible realization of R^{-1} M^{-1} L^{-1}. The wrapped
|
||||||
|
* inverse always sees the dimensional Jacobian, even when an MFEM Krylov
|
||||||
|
* solver binds this object to the normalized Jacobian L J R.
|
||||||
|
*/
|
||||||
|
class ScaledPreconditioner final : public mfem::Solver {
|
||||||
|
public:
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &physicalInverse,
|
||||||
|
const mfem::Operator &physicalJacobian,
|
||||||
|
const mfem::Operator &normalizedJacobian,
|
||||||
|
const DiagonalNormalization &normalization
|
||||||
|
)
|
||||||
|
: mfem::Solver(
|
||||||
|
normalization.StateSize(),
|
||||||
|
normalization.ResidualSize(),
|
||||||
|
physicalInverse.iterative_mode
|
||||||
|
),
|
||||||
|
m_physicalInverse(&physicalInverse),
|
||||||
|
m_physicalJacobian(&physicalJacobian),
|
||||||
|
m_expectedNormalizedJacobian(&normalizedJacobian),
|
||||||
|
m_normalization(&normalization),
|
||||||
|
m_physicalResidual(normalization.ResidualSize()),
|
||||||
|
m_physicalCorrection(normalization.StateSize()) {
|
||||||
|
if (physicalInverse.Width() != normalization.ResidualSize() ||
|
||||||
|
physicalInverse.Height() != normalization.StateSize() ||
|
||||||
|
physicalJacobian.Width() != normalization.StateSize() ||
|
||||||
|
physicalJacobian.Height() != normalization.ResidualSize()) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"The physical preconditioner, Jacobian, and normalization dimensions do not agree."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SetOperator(normalizedJacobian);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &,
|
||||||
|
mfem::Operator &&,
|
||||||
|
const mfem::Operator &,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &,
|
||||||
|
const mfem::Operator &&,
|
||||||
|
const mfem::Operator &,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &,
|
||||||
|
const mfem::Operator &,
|
||||||
|
mfem::Operator &&,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &,
|
||||||
|
const mfem::Operator &,
|
||||||
|
const mfem::Operator &&,
|
||||||
|
const DiagonalNormalization &
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &,
|
||||||
|
const mfem::Operator &,
|
||||||
|
const mfem::Operator &,
|
||||||
|
DiagonalNormalization &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledPreconditioner(
|
||||||
|
mfem::Solver &,
|
||||||
|
const mfem::Operator &,
|
||||||
|
const mfem::Operator &,
|
||||||
|
const DiagonalNormalization &&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScaledPreconditioner(const ScaledPreconditioner &) = delete;
|
||||||
|
ScaledPreconditioner &operator=(const ScaledPreconditioner &) = delete;
|
||||||
|
ScaledPreconditioner(ScaledPreconditioner &&) = delete;
|
||||||
|
ScaledPreconditioner &operator=(ScaledPreconditioner &&) = delete;
|
||||||
|
|
||||||
|
void SetOperator(const mfem::Operator &normalizedJacobian) override {
|
||||||
|
if (normalizedJacobian.Width() != Width() || normalizedJacobian.Height() != Height()) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"The scaled preconditioner received an incompatible normalized Jacobian."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (&normalizedJacobian != m_expectedNormalizedJacobian) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"The scaled preconditioner cannot be rebound to a different normalized Jacobian."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
m_physicalInverse->SetOperator(*m_physicalJacobian);
|
||||||
|
m_normalizedJacobian = &normalizedJacobian;
|
||||||
|
++m_statistics.operatorBindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &normalizedResidual,
|
||||||
|
mfem::Vector &normalizedCorrection
|
||||||
|
) const override {
|
||||||
|
if (m_normalizedJacobian == nullptr) {
|
||||||
|
throw std::logic_error("The scaled preconditioner has not been bound to a normalized Jacobian.");
|
||||||
|
}
|
||||||
|
if (normalizedResidual.Size() != Width() || normalizedCorrection.Size() != Height()) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"The scaled preconditioner requires compatible, preallocated normalized vectors."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
m_normalization->DenormalizeResidual(normalizedResidual, m_physicalResidual);
|
||||||
|
m_physicalInverse->Mult(m_physicalResidual, m_physicalCorrection);
|
||||||
|
m_normalization->NormalizeState(m_physicalCorrection, normalizedCorrection);
|
||||||
|
++m_statistics.applications;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Solver &GetPhysicalInverse() const noexcept {
|
||||||
|
return *m_physicalInverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
|
||||||
|
return *m_physicalJacobian;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Operator &GetNormalizedJacobian() const {
|
||||||
|
if (m_normalizedJacobian == nullptr) {
|
||||||
|
throw std::logic_error("The scaled preconditioner has not been bound to a normalized Jacobian.");
|
||||||
|
}
|
||||||
|
return *m_normalizedJacobian;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ScaledPreconditionerStatistics &GetStatistics() const noexcept {
|
||||||
|
return m_statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mfem::Solver *m_physicalInverse;
|
||||||
|
const mfem::Operator *m_physicalJacobian;
|
||||||
|
const mfem::Operator *m_expectedNormalizedJacobian;
|
||||||
|
const mfem::Operator *m_normalizedJacobian{nullptr};
|
||||||
|
const DiagonalNormalization *m_normalization;
|
||||||
|
mutable mfem::Vector m_physicalResidual;
|
||||||
|
mutable mfem::Vector m_physicalCorrection;
|
||||||
|
mutable ScaledPreconditionerStatistics m_statistics;
|
||||||
|
};
|
||||||
|
} // namespace mean_field::normalization
|
||||||
728
libmeanfield/interface/normalization/physical_riesz.cppm
Normal file
728
libmeanfield/interface/normalization/physical_riesz.cppm
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <concepts>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
export module mean_field:normalization.physical_riesz;
|
||||||
|
|
||||||
|
export import :dimensions.quantities;
|
||||||
|
export import :field.mfem;
|
||||||
|
export import :model.specifications;
|
||||||
|
export import :normalization.plan;
|
||||||
|
|
||||||
|
export namespace mean_field::normalization {
|
||||||
|
struct Unnormalized final : NormalizationPrescriptionTag { };
|
||||||
|
|
||||||
|
struct ReferenceGeometry final { };
|
||||||
|
|
||||||
|
struct FixedMassBranchReference final { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept RieszGeometryPolicy = std::same_as<std::remove_cvref_t<Candidate>, ReferenceGeometry>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept ReferenceScalePolicy = std::same_as<std::remove_cvref_t<Candidate>, FixedMassBranchReference>;
|
||||||
|
|
||||||
|
template <
|
||||||
|
RieszGeometryPolicy GeometryPolicy = ReferenceGeometry,
|
||||||
|
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
|
||||||
|
class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag {
|
||||||
|
public:
|
||||||
|
using Geometry = GeometryPolicy;
|
||||||
|
using ScaleSource = ScalePolicy;
|
||||||
|
|
||||||
|
explicit PhysicalRieszDiagonal(
|
||||||
|
const dimensions::LengthValue referenceRadius,
|
||||||
|
const double gravitationalConstant = 1.0
|
||||||
|
)
|
||||||
|
: m_referenceRadius(referenceRadius),
|
||||||
|
m_gravitationalConstant(gravitationalConstant) {
|
||||||
|
if (!std::isfinite(referenceRadius.value()) || referenceRadius.value() <= 0.0) {
|
||||||
|
throw std::invalid_argument("Physical Riesz normalization requires a finite, positive branch radius.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Physical Riesz normalization requires a finite, positive gravitational constant."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] dimensions::LengthValue referenceRadius() const noexcept {
|
||||||
|
return m_referenceRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double gravitationalConstant() const noexcept {
|
||||||
|
return m_gravitationalConstant;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
dimensions::LengthValue m_referenceRadius;
|
||||||
|
double m_gravitationalConstant;
|
||||||
|
};
|
||||||
|
|
||||||
|
PhysicalRieszDiagonal(dimensions::LengthValue, double = 1.0)
|
||||||
|
-> PhysicalRieszDiagonal<ReferenceGeometry, FixedMassBranchReference>;
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsPhysicalRieszDiagonal : std::false_type { };
|
||||||
|
|
||||||
|
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource>
|
||||||
|
struct IsPhysicalRieszDiagonal<PhysicalRieszDiagonal<Geometry, ScaleSource>> : std::true_type { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept PhysicalRieszDiagonalPrescription =
|
||||||
|
IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
struct StellarCharacteristicScales final {
|
||||||
|
dimensions::MassValue mass;
|
||||||
|
dimensions::LengthValue radius;
|
||||||
|
double gravitationalConstant;
|
||||||
|
double density;
|
||||||
|
double acceleration;
|
||||||
|
double inverseTimeSquared;
|
||||||
|
double specificEnergy;
|
||||||
|
double pressure;
|
||||||
|
double angularVelocity;
|
||||||
|
double angularMomentum;
|
||||||
|
double force;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] inline StellarCharacteristicScales deriveStellarCharacteristicScales(
|
||||||
|
const dimensions::MassValue mass,
|
||||||
|
const dimensions::LengthValue radius,
|
||||||
|
const double gravitationalConstant = 1.0
|
||||||
|
) {
|
||||||
|
const double massValue = mass.value();
|
||||||
|
const double radiusValue = radius.value();
|
||||||
|
if (!std::isfinite(massValue) || massValue <= 0.0) {
|
||||||
|
throw std::invalid_argument("Characteristic stellar scales require a finite, positive mass.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(radiusValue) || radiusValue <= 0.0) {
|
||||||
|
throw std::invalid_argument("Characteristic stellar scales require a finite, positive radius.");
|
||||||
|
}
|
||||||
|
if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Characteristic stellar scales require a finite, positive gravitational constant."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const double radiusSquared = radiusValue * radiusValue;
|
||||||
|
const double radiusCubed = radiusSquared * radiusValue;
|
||||||
|
const double density = massValue / radiusCubed;
|
||||||
|
const double acceleration = gravitationalConstant * massValue / radiusSquared;
|
||||||
|
const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed;
|
||||||
|
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
|
||||||
|
const double pressure = gravitationalConstant * massValue * massValue /
|
||||||
|
(radiusSquared * radiusSquared);
|
||||||
|
const double angularVelocity = std::sqrt(inverseTimeSquared);
|
||||||
|
const double angularMomentum = massValue * std::sqrt(gravitationalConstant * massValue * radiusValue);
|
||||||
|
const double force = gravitationalConstant * massValue * massValue / radiusSquared;
|
||||||
|
|
||||||
|
const double derived[] = {
|
||||||
|
density,
|
||||||
|
acceleration,
|
||||||
|
inverseTimeSquared,
|
||||||
|
specificEnergy,
|
||||||
|
pressure,
|
||||||
|
angularVelocity,
|
||||||
|
angularMomentum,
|
||||||
|
force
|
||||||
|
};
|
||||||
|
for (const double value : derived) {
|
||||||
|
if (!std::isfinite(value) || value <= 0.0) {
|
||||||
|
throw std::overflow_error("A derived characteristic stellar scale is not finite and positive.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
.mass = mass,
|
||||||
|
.radius = radius,
|
||||||
|
.gravitationalConstant = gravitationalConstant,
|
||||||
|
.density = density,
|
||||||
|
.acceleration = acceleration,
|
||||||
|
.inverseTimeSquared = inverseTimeSquared,
|
||||||
|
.specificEnergy = specificEnergy,
|
||||||
|
.pressure = pressure,
|
||||||
|
.angularVelocity = angularVelocity,
|
||||||
|
.angularMomentum = angularMomentum,
|
||||||
|
.force = force
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Model>
|
||||||
|
requires requires(const Model &model) {
|
||||||
|
{
|
||||||
|
model.template specification<models::FixedTotalMass>()
|
||||||
|
} -> std::same_as<const models::FixedTotalMass &>;
|
||||||
|
{
|
||||||
|
model.template specification<models::FixedTotalMass>().targetMass()
|
||||||
|
} -> std::same_as<dimensions::MassValue>;
|
||||||
|
}
|
||||||
|
[[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales(
|
||||||
|
const PhysicalRieszDiagonal<Geometry, ScaleSource> &prescription,
|
||||||
|
const Model &model
|
||||||
|
) {
|
||||||
|
return deriveStellarCharacteristicScales(
|
||||||
|
model.template specification<models::FixedTotalMass>().targetMass(),
|
||||||
|
prescription.referenceRadius(),
|
||||||
|
prescription.gravitationalConstant()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
/*
|
||||||
|
* Model definitions live below the numerical normalization layer so
|
||||||
|
* that a physics component can describe its generated coordinates
|
||||||
|
* without importing solver machinery. These two translations are the
|
||||||
|
* deliberately small boundary between that neutral declaration and the
|
||||||
|
* normalization plan used by the discretization.
|
||||||
|
*/
|
||||||
|
template <models::RieszTopology Topology> struct DeclaredRieszTopology {
|
||||||
|
static constexpr bool available = false;
|
||||||
|
static constexpr RieszTopology value = RieszTopology::identity;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
|
||||||
|
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
|
||||||
|
static constexpr bool available = true; \
|
||||||
|
static constexpr RieszTopology value = RieszTopology::Name; \
|
||||||
|
}
|
||||||
|
|
||||||
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity);
|
||||||
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_volume_l2);
|
||||||
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(vector_volume_l2);
|
||||||
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_boundary_l2);
|
||||||
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(hybrid_scalar_volume_point_rows);
|
||||||
|
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(global_scalar);
|
||||||
|
|
||||||
|
#undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY
|
||||||
|
|
||||||
|
template <models::PhysicalScaleLaw Scale> struct DeclaredPhysicalScale {
|
||||||
|
static constexpr bool available = false;
|
||||||
|
static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
|
||||||
|
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
|
||||||
|
static constexpr bool available = true; \
|
||||||
|
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
|
||||||
|
}
|
||||||
|
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(density);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(length);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(acceleration);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(inverse_time_squared);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(specific_energy);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(pressure);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(mass);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(force);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_velocity);
|
||||||
|
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_momentum);
|
||||||
|
|
||||||
|
#undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE
|
||||||
|
|
||||||
|
template <typename Declaration, typename = void>
|
||||||
|
struct CompileDeclaredPhysicalRieszCoordinate {
|
||||||
|
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Declaration>
|
||||||
|
struct CompileDeclaredPhysicalRieszCoordinate<
|
||||||
|
Declaration,
|
||||||
|
std::void_t<
|
||||||
|
decltype(std::integral_constant<
|
||||||
|
models::RieszTopology,
|
||||||
|
static_cast<models::RieszTopology>(Declaration::topology)>{}),
|
||||||
|
decltype(std::integral_constant<
|
||||||
|
models::PhysicalScaleLaw,
|
||||||
|
static_cast<models::PhysicalScaleLaw>(Declaration::scale)>{}),
|
||||||
|
decltype(std::bool_constant<static_cast<bool>(Declaration::available)>{})>> {
|
||||||
|
private:
|
||||||
|
static constexpr models::RieszTopology declaredTopology =
|
||||||
|
static_cast<models::RieszTopology>(Declaration::topology);
|
||||||
|
static constexpr models::PhysicalScaleLaw declaredScale =
|
||||||
|
static_cast<models::PhysicalScaleLaw>(Declaration::scale);
|
||||||
|
using Topology = DeclaredRieszTopology<declaredTopology>;
|
||||||
|
using Scale = DeclaredPhysicalScale<declaredScale>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool registered = static_cast<bool>(Declaration::available) &&
|
||||||
|
Topology::available && Scale::available;
|
||||||
|
using Method = std::conditional_t<
|
||||||
|
registered,
|
||||||
|
PhysicalRieszCoordinate<Topology::value, Scale::value>,
|
||||||
|
UnsupportedPhysicalRieszCoordinate>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Generated, CoordinateKind Kind, typename = void>
|
||||||
|
struct DeclaredGeneratedPhysicalRieszCoordinate {
|
||||||
|
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct DeclaredGeneratedPhysicalRieszCoordinate<
|
||||||
|
Generated,
|
||||||
|
CoordinateKind::value,
|
||||||
|
std::void_t<
|
||||||
|
typename Generated::SpecificationType,
|
||||||
|
typename models::SpecificationContribution<
|
||||||
|
typename Generated::SpecificationType>::Normalization::Value>>
|
||||||
|
: CompileDeclaredPhysicalRieszCoordinate<
|
||||||
|
typename models::SpecificationContribution<
|
||||||
|
typename Generated::SpecificationType>::Normalization::Value> { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct DeclaredGeneratedPhysicalRieszCoordinate<
|
||||||
|
Generated,
|
||||||
|
CoordinateKind::residual,
|
||||||
|
std::void_t<
|
||||||
|
typename Generated::SpecificationType,
|
||||||
|
typename models::SpecificationContribution<
|
||||||
|
typename Generated::SpecificationType>::Normalization::Residual>>
|
||||||
|
: CompileDeclaredPhysicalRieszCoordinate<
|
||||||
|
typename models::SpecificationContribution<
|
||||||
|
typename Generated::SpecificationType>::Normalization::Residual> { };
|
||||||
|
|
||||||
|
template <typename GeneratedValues, typename GeneratedResiduals>
|
||||||
|
struct GeneratedPhysicalRieszCoverage {
|
||||||
|
static constexpr bool complete = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... GeneratedValues, typename... GeneratedResiduals>
|
||||||
|
struct GeneratedPhysicalRieszCoverage<
|
||||||
|
models::ModelTypeList<GeneratedValues...>,
|
||||||
|
models::ModelTypeList<GeneratedResiduals...>> {
|
||||||
|
static constexpr bool complete =
|
||||||
|
(DeclaredGeneratedPhysicalRieszCoordinate<
|
||||||
|
GeneratedValues,
|
||||||
|
CoordinateKind::value>::registered && ...) &&
|
||||||
|
(DeclaredGeneratedPhysicalRieszCoordinate<
|
||||||
|
GeneratedResiduals,
|
||||||
|
CoordinateKind::residual>::registered && ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification, typename = void>
|
||||||
|
struct SpecificationPhysicalRieszCoverage {
|
||||||
|
static constexpr bool complete = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct SpecificationPhysicalRieszCoverage<
|
||||||
|
Specification,
|
||||||
|
std::void_t<
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedResiduals>>
|
||||||
|
: GeneratedPhysicalRieszCoverage<
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedResiduals> { };
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All generated blocks are normalized from their generating physics
|
||||||
|
* specification. Adding another constraint therefore does not add a
|
||||||
|
* normalization specialization: its public ModelDefinition is the single
|
||||||
|
* source of both the value and residual Riesz laws.
|
||||||
|
*/
|
||||||
|
template <typename Generated>
|
||||||
|
struct PhysicalRieszBlockTraits<utils::blocks::generated_value_block<Generated>>
|
||||||
|
: detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::value> { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct PhysicalRieszBlockTraits<utils::blocks::generated_residual_block<Generated>>
|
||||||
|
: detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::residual> { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
concept GeneratedValuePhysicalRieszNormalizable =
|
||||||
|
detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::value>::registered;
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
concept GeneratedResidualPhysicalRieszNormalizable =
|
||||||
|
detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::residual>::registered;
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
concept CompleteGeneratedPhysicalRieszNormalizationFor =
|
||||||
|
detail::SpecificationPhysicalRieszCoverage<std::remove_cvref_t<Specification>>::complete;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Runtime Physical Riesz assembly needs more than a symbolically complete
|
||||||
|
* plan: it must be able to recover the finite-element maps owned by the
|
||||||
|
* selected physical core. Keep that structural capability in this low
|
||||||
|
* normalization module so both problem formation and the solver-facing
|
||||||
|
* adapter can consult the same authority without importing one another.
|
||||||
|
*/
|
||||||
|
template <typename Candidate>
|
||||||
|
concept PhysicalRieszCoreRuntime =
|
||||||
|
requires(const std::remove_cvref_t<Candidate> &core) {
|
||||||
|
{
|
||||||
|
core.GetGravityContext().GetDensityMap()
|
||||||
|
} -> std::same_as<const field::FieldDofMap &>;
|
||||||
|
{
|
||||||
|
core.GetGravityContext().GetGravityGradientMap()
|
||||||
|
} -> std::same_as<const field::FieldDofMap &>;
|
||||||
|
{
|
||||||
|
core.GetGravityContext().GetGravityPotentialMap()
|
||||||
|
} -> std::same_as<const field::FieldDofMap &>;
|
||||||
|
{
|
||||||
|
core.GetHydrostaticOperator().GetEnthalpyMap()
|
||||||
|
} -> std::same_as<const field::FieldDofMap &>;
|
||||||
|
{
|
||||||
|
core.GetDomainDeformation().parameterCount()
|
||||||
|
} -> std::same_as<int>;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Generated, CoordinateKind Kind>
|
||||||
|
using GeneratedPhysicalRieszMethod =
|
||||||
|
typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
|
||||||
|
|
||||||
|
template <typename Generated, CoordinateKind Kind, typename = void>
|
||||||
|
struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated, CoordinateKind Kind>
|
||||||
|
struct GeneratedPhysicalRieszRuntimeCoordinate<
|
||||||
|
Generated,
|
||||||
|
Kind,
|
||||||
|
std::void_t<decltype(GeneratedPhysicalRieszMethod<Generated, Kind>::topology)>>
|
||||||
|
: std::bool_constant<
|
||||||
|
DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::registered &&
|
||||||
|
GeneratedPhysicalRieszMethod<Generated, Kind>::topology ==
|
||||||
|
RieszTopology::global_scalar> { };
|
||||||
|
|
||||||
|
template <typename Specification, typename = void>
|
||||||
|
struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Values, typename Residuals>
|
||||||
|
struct GeneratedPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||||
|
|
||||||
|
template <typename... Values, typename... Residuals>
|
||||||
|
struct GeneratedPhysicalRieszRuntimeCoverage<
|
||||||
|
models::ModelTypeList<Values...>,
|
||||||
|
models::ModelTypeList<Residuals...>>
|
||||||
|
: std::bool_constant<
|
||||||
|
(GeneratedPhysicalRieszRuntimeCoordinate<Values, CoordinateKind::value>::value && ...) &&
|
||||||
|
(GeneratedPhysicalRieszRuntimeCoordinate<Residuals, CoordinateKind::residual>::value && ...)> { };
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct SpecificationPhysicalRieszRuntimeCoverage<
|
||||||
|
Specification,
|
||||||
|
std::void_t<
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedResiduals>>
|
||||||
|
: GeneratedPhysicalRieszRuntimeCoverage<
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedValues,
|
||||||
|
typename models::SpecificationContribution<Specification>::GeneratedResiduals> { };
|
||||||
|
|
||||||
|
template <typename SpecificationTypes>
|
||||||
|
struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { };
|
||||||
|
|
||||||
|
template <models::ModelSpecification... Specifications>
|
||||||
|
struct SpecificationSetPhysicalRieszRuntimeCoverage<
|
||||||
|
models::detail::SpecificationSetStorage<Specifications...>>
|
||||||
|
: std::bool_constant<
|
||||||
|
(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor =
|
||||||
|
detail::SpecificationPhysicalRieszRuntimeCoverage<
|
||||||
|
std::remove_cvref_t<Specification>>::value;
|
||||||
|
|
||||||
|
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
|
||||||
|
template <> struct PhysicalRieszBlockTraits<BlockType> { \
|
||||||
|
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
|
||||||
|
static constexpr bool registered = true; \
|
||||||
|
}
|
||||||
|
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
scalar_volume_l2,
|
||||||
|
density
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::surface_deformation::parameters::value,
|
||||||
|
scalar_boundary_l2,
|
||||||
|
length
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::gravity::gradient::value,
|
||||||
|
vector_volume_l2,
|
||||||
|
acceleration
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::gravity::poisson::value,
|
||||||
|
scalar_volume_l2,
|
||||||
|
specific_energy
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::enthalpy::specific::value,
|
||||||
|
scalar_volume_l2,
|
||||||
|
specific_energy
|
||||||
|
);
|
||||||
|
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::gravity::gradient::residual,
|
||||||
|
vector_volume_l2,
|
||||||
|
acceleration
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::gravity::poisson::residual,
|
||||||
|
scalar_volume_l2,
|
||||||
|
inverse_time_squared
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::density::mass::residual,
|
||||||
|
scalar_volume_l2,
|
||||||
|
density
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||||
|
scalar_boundary_l2,
|
||||||
|
force
|
||||||
|
);
|
||||||
|
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
hybrid_scalar_volume_point_rows,
|
||||||
|
specific_energy
|
||||||
|
);
|
||||||
|
#undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
[[nodiscard]] double physicalScale(
|
||||||
|
const StellarCharacteristicScales &scales
|
||||||
|
) {
|
||||||
|
static_assert(PhysicalRieszBlockTraits<Block>::registered, "The block has no Physical Riesz normalization.");
|
||||||
|
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
|
||||||
|
constexpr PhysicalScaleKind scale = Method::scale;
|
||||||
|
if constexpr (scale == PhysicalScaleKind::dimensionless) {
|
||||||
|
return 1.0;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::density) {
|
||||||
|
return scales.density;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::length) {
|
||||||
|
return scales.radius.value();
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::acceleration) {
|
||||||
|
return scales.acceleration;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::inverse_time_squared) {
|
||||||
|
return scales.inverseTimeSquared;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::specific_energy) {
|
||||||
|
return scales.specificEnergy;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::pressure) {
|
||||||
|
return scales.pressure;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::mass) {
|
||||||
|
return scales.mass.value();
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::force) {
|
||||||
|
return scales.force;
|
||||||
|
} else if constexpr (scale == PhysicalScaleKind::angular_velocity) {
|
||||||
|
return scales.angularVelocity;
|
||||||
|
} else {
|
||||||
|
static_assert(scale == PhysicalScaleKind::angular_momentum);
|
||||||
|
return scales.angularMomentum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Values, typename Residuals> struct MakePhysicalRieszPlan;
|
||||||
|
|
||||||
|
template <typename... Values, typename... Residuals>
|
||||||
|
struct MakePhysicalRieszPlan<
|
||||||
|
utils::blocks::type_list<Values...>,
|
||||||
|
utils::blocks::type_list<Residuals...>> {
|
||||||
|
using Type = NormalizationPlan<
|
||||||
|
CoordinateComponent<
|
||||||
|
CoordinateKind::value,
|
||||||
|
utils::blocks::type_list<Values>,
|
||||||
|
typename PhysicalRieszBlockTraits<Values>::Method>...,
|
||||||
|
CoordinateComponent<
|
||||||
|
CoordinateKind::residual,
|
||||||
|
utils::blocks::type_list<Residuals>,
|
||||||
|
typename PhysicalRieszBlockTraits<Residuals>::Method>...>;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan<
|
||||||
|
typename Form::value_blocks,
|
||||||
|
typename Form::residual_blocks>::Type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public compile-time extension point for a normalization prescription.
|
||||||
|
* A specialization owns both the complete coordinate plan and the
|
||||||
|
* low-level runtime compatibility predicate used before a discretized
|
||||||
|
* problem type is formed. Keeping those declarations together prevents a
|
||||||
|
* policy from compiling a plan which the selected stellar core cannot
|
||||||
|
* actually prepare.
|
||||||
|
*/
|
||||||
|
template <typename Prescription, typename Form> struct NormalizationCompilation {
|
||||||
|
using Plan = NormalizationPlan<>;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
|
static constexpr bool runtimeAvailableFor = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Astronomy/numerics-facing package for a policy which prepares one
|
||||||
|
* runtime diagonal over the complete inferred form and needs no private
|
||||||
|
* facility of a particular stellar core. The generated plan truthfully
|
||||||
|
* labels every coordinate as runtime-prepared by this exact policy. */
|
||||||
|
template <NormalizationPrescription Prescription, typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
struct RuntimePreparedNormalizationCompilation {
|
||||||
|
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
|
||||||
|
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||||
|
|
||||||
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
|
static constexpr bool runtimeAvailableFor = registered;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
struct NormalizationCompilation<Unnormalized, Form> {
|
||||||
|
using Plan = IdentityNormalizationPlanFor<Form>;
|
||||||
|
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||||
|
|
||||||
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
|
static constexpr bool runtimeAvailableFor = registered;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
struct NormalizationCompilation<PhysicalRieszDiagonal<Geometry, ScaleSource>, Form> {
|
||||||
|
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
|
||||||
|
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
|
||||||
|
|
||||||
|
template <typename PhysicalCore, typename SpecificationTypes>
|
||||||
|
static constexpr bool runtimeAvailableFor =
|
||||||
|
registered &&
|
||||||
|
PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
|
||||||
|
detail::SpecificationSetPhysicalRieszRuntimeCoverage<
|
||||||
|
std::remove_cvref_t<SpecificationTypes>>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Prescription, typename Form, typename = void>
|
||||||
|
struct NormalizationCompilationAudit {
|
||||||
|
using Plan = NormalizationPlan<>;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Prescription, typename Form>
|
||||||
|
requires NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
|
||||||
|
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>>
|
||||||
|
struct NormalizationCompilationAudit<
|
||||||
|
Prescription,
|
||||||
|
Form,
|
||||||
|
std::void_t<
|
||||||
|
typename NormalizationCompilation<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>>::Plan,
|
||||||
|
decltype(std::bool_constant<static_cast<bool>(
|
||||||
|
NormalizationCompilation<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>>::registered)>{})>> {
|
||||||
|
using Compilation = NormalizationCompilation<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>>;
|
||||||
|
using Plan = typename Compilation::Plan;
|
||||||
|
|
||||||
|
static constexpr bool registered =
|
||||||
|
static_cast<bool>(Compilation::registered) &&
|
||||||
|
CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <NormalizationPrescription Prescription, typename Form>
|
||||||
|
using NormalizationPlanFor = typename detail::NormalizationCompilationAudit<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
Form>::Plan;
|
||||||
|
|
||||||
|
template <typename Prescription, typename Form>
|
||||||
|
concept CompilableNormalizationFor =
|
||||||
|
detail::NormalizationCompilationAudit<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>>::registered;
|
||||||
|
|
||||||
|
/* The public runtime-preparation adapter is intentionally narrower than
|
||||||
|
* an arbitrary complete plan: every coordinate must name the exact policy
|
||||||
|
* which supplies its runtime factor. This prevents a custom policy from
|
||||||
|
* advertising IdentityCoordinate (or another policy's method) while
|
||||||
|
* silently installing a different diagonal at runtime. */
|
||||||
|
template <typename Prescription, typename Form>
|
||||||
|
concept RuntimePreparedNormalizationFor =
|
||||||
|
NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
|
||||||
|
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>> &&
|
||||||
|
CompilableNormalizationFor<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>> &&
|
||||||
|
std::same_as<
|
||||||
|
NormalizationPlanFor<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>>,
|
||||||
|
RuntimePreparedNormalizationPlanFor<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>>>;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <
|
||||||
|
typename Prescription,
|
||||||
|
typename Form,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename SpecificationTypes,
|
||||||
|
typename = void>
|
||||||
|
struct StellarNormalizationRuntimeAudit : std::false_type { };
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Prescription,
|
||||||
|
typename Form,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename SpecificationTypes>
|
||||||
|
struct StellarNormalizationRuntimeAudit<
|
||||||
|
Prescription,
|
||||||
|
Form,
|
||||||
|
PhysicalCore,
|
||||||
|
SpecificationTypes,
|
||||||
|
std::void_t<
|
||||||
|
std::enable_if_t<NormalizationCompilationAudit<
|
||||||
|
Prescription,
|
||||||
|
Form>::registered>,
|
||||||
|
decltype(std::bool_constant<static_cast<bool>(
|
||||||
|
NormalizationCompilation<
|
||||||
|
Prescription,
|
||||||
|
Form>::template runtimeAvailableFor<
|
||||||
|
PhysicalCore,
|
||||||
|
SpecificationTypes>)>{})>>
|
||||||
|
: std::bool_constant<
|
||||||
|
(std::same_as<Prescription, Unnormalized> ||
|
||||||
|
PhysicalRieszDiagonalPrescription<Prescription> ||
|
||||||
|
RuntimePreparedNormalizationFor<Prescription, Form>) &&
|
||||||
|
static_cast<bool>(NormalizationCompilation<
|
||||||
|
Prescription,
|
||||||
|
Form>::template runtimeAvailableFor<
|
||||||
|
PhysicalCore,
|
||||||
|
SpecificationTypes>)> { };
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Single detection-safe authority for pairing a compiled stellar form,
|
||||||
|
* its selected physical core, and a runtime normalization prescription.
|
||||||
|
* Each public NormalizationCompilation specialization declares this
|
||||||
|
* compatibility alongside its plan. The identity policy needs only a
|
||||||
|
* complete plan. Physical Riesz also requires every map consumed during
|
||||||
|
* assembly and global-scalar runtime preparation for every generated
|
||||||
|
* coordinate in the specification pack.
|
||||||
|
*/
|
||||||
|
template <
|
||||||
|
typename Prescription,
|
||||||
|
typename Form,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename SpecificationTypes>
|
||||||
|
concept StellarNormalizationRuntimeAvailableFor =
|
||||||
|
detail::StellarNormalizationRuntimeAudit<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
std::remove_cvref_t<Form>,
|
||||||
|
std::remove_cvref_t<PhysicalCore>,
|
||||||
|
std::remove_cvref_t<SpecificationTypes>>::value;
|
||||||
|
} // namespace mean_field::normalization
|
||||||
376
libmeanfield/interface/normalization/plan.cppm
Normal file
376
libmeanfield/interface/normalization/plan.cppm
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
export module mean_field:normalization.plan;
|
||||||
|
|
||||||
|
export import :utils.blocks;
|
||||||
|
|
||||||
|
export namespace mean_field::normalization {
|
||||||
|
struct NormalizationPrescriptionTag { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept NormalizationPrescription =
|
||||||
|
std::derived_from<
|
||||||
|
std::remove_cvref_t<Candidate>,
|
||||||
|
NormalizationPrescriptionTag>;
|
||||||
|
|
||||||
|
enum class CoordinateKind { value, residual };
|
||||||
|
|
||||||
|
enum class RieszTopology {
|
||||||
|
identity,
|
||||||
|
scalar_volume_l2,
|
||||||
|
vector_volume_l2,
|
||||||
|
scalar_boundary_l2,
|
||||||
|
hybrid_scalar_volume_point_rows,
|
||||||
|
global_scalar
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PhysicalScaleKind {
|
||||||
|
dimensionless,
|
||||||
|
density,
|
||||||
|
length,
|
||||||
|
acceleration,
|
||||||
|
inverse_time_squared,
|
||||||
|
specific_energy,
|
||||||
|
pressure,
|
||||||
|
mass,
|
||||||
|
force,
|
||||||
|
angular_velocity,
|
||||||
|
angular_momentum
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IdentityCoordinate final { };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Honest compile-time method for a coordinate whose positive diagonal
|
||||||
|
* factor is supplied at runtime by one exact normalization prescription.
|
||||||
|
* Unlike IdentityCoordinate, this category makes no claim about the
|
||||||
|
* numerical value of that factor. The owner type prevents one policy from
|
||||||
|
* silently presenting another policy's runtime map as its own plan.
|
||||||
|
*/
|
||||||
|
template <NormalizationPrescription Prescription>
|
||||||
|
struct RuntimePreparedCoordinate final {
|
||||||
|
using PrescriptionType = std::remove_cvref_t<Prescription>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <RieszTopology Topology, PhysicalScaleKind Scale> struct PhysicalRieszCoordinate final {
|
||||||
|
static constexpr RieszTopology topology = Topology;
|
||||||
|
static constexpr PhysicalScaleKind scale = Scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnsupportedPhysicalRieszCoordinate final { };
|
||||||
|
|
||||||
|
template <typename Block> struct PhysicalRieszBlockTraits {
|
||||||
|
using Method = UnsupportedPhysicalRieszCoordinate;
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <CoordinateKind Kind, typename BlockList, typename MethodType>
|
||||||
|
struct CoordinateComponent final {
|
||||||
|
using Blocks = BlockList;
|
||||||
|
using Method = MethodType;
|
||||||
|
static constexpr CoordinateKind kind = Kind;
|
||||||
|
|
||||||
|
using ValueBlocks = std::conditional_t<
|
||||||
|
Kind == CoordinateKind::value,
|
||||||
|
BlockList,
|
||||||
|
utils::blocks::type_list<>>;
|
||||||
|
using ResidualBlocks = std::conditional_t<
|
||||||
|
Kind == CoordinateKind::residual,
|
||||||
|
BlockList,
|
||||||
|
utils::blocks::type_list<>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Candidate> struct IsTypeList : std::false_type { };
|
||||||
|
|
||||||
|
template <typename... Types>
|
||||||
|
struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
|
||||||
|
|
||||||
|
template <typename List, typename Base> struct IsUniqueDerivedBlockList : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Base, typename... Blocks>
|
||||||
|
struct IsUniqueDerivedBlockList<utils::blocks::type_list<Blocks...>, Base>
|
||||||
|
: std::bool_constant<
|
||||||
|
(std::derived_from<Blocks, Base> && ...) &&
|
||||||
|
utils::blocks::types_are_unique_v<utils::blocks::type_list<Blocks...>>> { };
|
||||||
|
|
||||||
|
template <typename Method> struct IsCoordinateMethod : std::false_type { };
|
||||||
|
|
||||||
|
template <> struct IsCoordinateMethod<IdentityCoordinate> : std::true_type { };
|
||||||
|
|
||||||
|
template <NormalizationPrescription Prescription>
|
||||||
|
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>>
|
||||||
|
: std::true_type { };
|
||||||
|
|
||||||
|
template <RieszTopology Topology, PhysicalScaleKind Scale>
|
||||||
|
struct IsCoordinateMethod<PhysicalRieszCoordinate<Topology, Scale>> : std::true_type { };
|
||||||
|
|
||||||
|
template <typename Method, typename Block> struct MethodSupportsBlock : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
struct MethodSupportsBlock<IdentityCoordinate, Block>
|
||||||
|
: std::bool_constant<std::derived_from<Block, utils::blocks::block>> { };
|
||||||
|
|
||||||
|
template <NormalizationPrescription Prescription, typename Block>
|
||||||
|
struct MethodSupportsBlock<RuntimePreparedCoordinate<Prescription>, Block>
|
||||||
|
: std::bool_constant<std::derived_from<Block, utils::blocks::block>> { };
|
||||||
|
|
||||||
|
template <RieszTopology Topology, PhysicalScaleKind Scale, typename Block>
|
||||||
|
struct MethodSupportsBlock<PhysicalRieszCoordinate<Topology, Scale>, Block>
|
||||||
|
: std::bool_constant<
|
||||||
|
PhysicalRieszBlockTraits<Block>::registered &&
|
||||||
|
std::same_as<
|
||||||
|
typename PhysicalRieszBlockTraits<Block>::Method,
|
||||||
|
PhysicalRieszCoordinate<Topology, Scale>>> { };
|
||||||
|
|
||||||
|
template <typename Method, typename List> struct MethodSupportsEveryBlock : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Method, typename... Blocks>
|
||||||
|
struct MethodSupportsEveryBlock<Method, utils::blocks::type_list<Blocks...>>
|
||||||
|
: std::bool_constant<(MethodSupportsBlock<Method, Blocks>::value && ...)> { };
|
||||||
|
|
||||||
|
template <typename Candidate, typename = void> struct ComponentTraits {
|
||||||
|
static constexpr bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct ComponentTraits<
|
||||||
|
Candidate,
|
||||||
|
std::void_t<
|
||||||
|
typename Candidate::Blocks,
|
||||||
|
typename Candidate::Method,
|
||||||
|
typename Candidate::ValueBlocks,
|
||||||
|
typename Candidate::ResidualBlocks,
|
||||||
|
decltype(Candidate::kind)>> {
|
||||||
|
using Blocks = typename Candidate::Blocks;
|
||||||
|
using Method = typename Candidate::Method;
|
||||||
|
using ValueBlocks = typename Candidate::ValueBlocks;
|
||||||
|
using ResidualBlocks = typename Candidate::ResidualBlocks;
|
||||||
|
|
||||||
|
static constexpr bool hasValidKind =
|
||||||
|
std::same_as<std::remove_cv_t<decltype(Candidate::kind)>, CoordinateKind>;
|
||||||
|
|
||||||
|
static constexpr bool hasValidBlockList = [] {
|
||||||
|
if constexpr (!hasValidKind || !IsTypeList<Blocks>::value) {
|
||||||
|
return false;
|
||||||
|
} else if constexpr (Candidate::kind == CoordinateKind::value) {
|
||||||
|
return IsUniqueDerivedBlockList<Blocks, utils::blocks::value_block_base>::value;
|
||||||
|
} else if constexpr (Candidate::kind == CoordinateKind::residual) {
|
||||||
|
return IsUniqueDerivedBlockList<Blocks, utils::blocks::residual_block_base>::value;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
static constexpr bool hasCoherentCoordinateLists = [] {
|
||||||
|
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value ||
|
||||||
|
!IsTypeList<ResidualBlocks>::value) {
|
||||||
|
return false;
|
||||||
|
} else if constexpr (Candidate::kind == CoordinateKind::value) {
|
||||||
|
return std::same_as<ValueBlocks, Blocks> &&
|
||||||
|
std::same_as<ResidualBlocks, utils::blocks::type_list<>>;
|
||||||
|
} else if constexpr (Candidate::kind == CoordinateKind::residual) {
|
||||||
|
return std::same_as<ValueBlocks, utils::blocks::type_list<>> &&
|
||||||
|
std::same_as<ResidualBlocks, Blocks>;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
static constexpr bool valid = hasValidKind && IsTypeList<Blocks>::value &&
|
||||||
|
IsCoordinateMethod<Method>::value && hasValidBlockList &&
|
||||||
|
hasCoherentCoordinateLists &&
|
||||||
|
MethodSupportsEveryBlock<Method, Blocks>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Lists> struct Concatenate;
|
||||||
|
|
||||||
|
template <> struct Concatenate<> {
|
||||||
|
using Type = utils::blocks::type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Types> struct Concatenate<utils::blocks::type_list<Types...>> {
|
||||||
|
using Type = utils::blocks::type_list<Types...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Left, typename... Right, typename... Remaining>
|
||||||
|
struct Concatenate<utils::blocks::type_list<Left...>, utils::blocks::type_list<Right...>, Remaining...> {
|
||||||
|
using Type = typename Concatenate<utils::blocks::type_list<Left..., Right...>, Remaining...>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Lists> using ConcatenateT = typename Concatenate<Lists...>::Type;
|
||||||
|
|
||||||
|
template <typename List, typename Type> struct Append;
|
||||||
|
|
||||||
|
template <typename... Types, typename Appended>
|
||||||
|
struct Append<utils::blocks::type_list<Types...>, Appended> {
|
||||||
|
using Type = utils::blocks::type_list<Types..., Appended>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Type;
|
||||||
|
|
||||||
|
template <typename List, typename Type>
|
||||||
|
using AppendUniqueT = std::conditional_t<
|
||||||
|
utils::blocks::contains_type_v<Type, List>,
|
||||||
|
List,
|
||||||
|
AppendT<List, Type>>;
|
||||||
|
|
||||||
|
template <typename Source, typename Excluded> struct ListDifference;
|
||||||
|
|
||||||
|
template <typename Excluded>
|
||||||
|
struct ListDifference<utils::blocks::type_list<>, Excluded> {
|
||||||
|
using Type = utils::blocks::type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Head, typename... Tail, typename Excluded>
|
||||||
|
struct ListDifference<utils::blocks::type_list<Head, Tail...>, Excluded> {
|
||||||
|
private:
|
||||||
|
using Remaining = typename ListDifference<utils::blocks::type_list<Tail...>, Excluded>::Type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = std::conditional_t<
|
||||||
|
utils::blocks::contains_type_v<Head, Excluded>,
|
||||||
|
Remaining,
|
||||||
|
ConcatenateT<utils::blocks::type_list<Head>, Remaining>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Source, typename Excluded>
|
||||||
|
using ListDifferenceT = typename ListDifference<Source, Excluded>::Type;
|
||||||
|
|
||||||
|
template <typename Remaining, typename Original, typename Repeated> struct CollectRepeatedTypes;
|
||||||
|
|
||||||
|
template <typename Original, typename Repeated>
|
||||||
|
struct CollectRepeatedTypes<utils::blocks::type_list<>, Original, Repeated> {
|
||||||
|
using Type = Repeated;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Head, typename... Tail, typename Original, typename Repeated>
|
||||||
|
struct CollectRepeatedTypes<utils::blocks::type_list<Head, Tail...>, Original, Repeated> {
|
||||||
|
private:
|
||||||
|
using Next = std::conditional_t<
|
||||||
|
(utils::blocks::type_count_v<Head, Original> > 1),
|
||||||
|
AppendUniqueT<Repeated, Head>,
|
||||||
|
Repeated>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = typename CollectRepeatedTypes<utils::blocks::type_list<Tail...>, Original, Next>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename List>
|
||||||
|
using RepeatedTypesT = typename CollectRepeatedTypes<
|
||||||
|
List,
|
||||||
|
List,
|
||||||
|
utils::blocks::type_list<>>::Type;
|
||||||
|
|
||||||
|
template <typename Candidate, typename = void> struct PlanTraits {
|
||||||
|
static constexpr bool valid = false;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept NormalizationComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||||
|
|
||||||
|
template <typename... Components> struct NormalizationPlan final {
|
||||||
|
using ComponentTypes = utils::blocks::type_list<Components...>;
|
||||||
|
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
|
||||||
|
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename... Components>
|
||||||
|
struct PlanTraits<NormalizationPlan<Components...>> {
|
||||||
|
static constexpr bool valid = (ComponentTraits<Components>::valid && ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Values, typename Residuals> struct MakeIdentityPlan;
|
||||||
|
|
||||||
|
template <typename... Values, typename... Residuals>
|
||||||
|
struct MakeIdentityPlan<
|
||||||
|
utils::blocks::type_list<Values...>,
|
||||||
|
utils::blocks::type_list<Residuals...>> {
|
||||||
|
using Type = NormalizationPlan<
|
||||||
|
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, IdentityCoordinate>...,
|
||||||
|
CoordinateComponent<
|
||||||
|
CoordinateKind::residual,
|
||||||
|
utils::blocks::type_list<Residuals>,
|
||||||
|
IdentityCoordinate>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
NormalizationPrescription Prescription,
|
||||||
|
typename Values,
|
||||||
|
typename Residuals>
|
||||||
|
struct MakeRuntimePreparedPlan;
|
||||||
|
|
||||||
|
template <
|
||||||
|
NormalizationPrescription Prescription,
|
||||||
|
typename... Values,
|
||||||
|
typename... Residuals>
|
||||||
|
struct MakeRuntimePreparedPlan<
|
||||||
|
Prescription,
|
||||||
|
utils::blocks::type_list<Values...>,
|
||||||
|
utils::blocks::type_list<Residuals...>> {
|
||||||
|
using Method = RuntimePreparedCoordinate<Prescription>;
|
||||||
|
using Type = NormalizationPlan<
|
||||||
|
CoordinateComponent<
|
||||||
|
CoordinateKind::value,
|
||||||
|
utils::blocks::type_list<Values>,
|
||||||
|
Method>...,
|
||||||
|
CoordinateComponent<
|
||||||
|
CoordinateKind::residual,
|
||||||
|
utils::blocks::type_list<Residuals>,
|
||||||
|
Method>...>;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept NormalizationPlanType = detail::PlanTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
using IdentityNormalizationPlanFor = typename detail::MakeIdentityPlan<
|
||||||
|
typename Form::value_blocks,
|
||||||
|
typename Form::residual_blocks>::Type;
|
||||||
|
|
||||||
|
template <NormalizationPrescription Prescription, typename Form>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
using RuntimePreparedNormalizationPlanFor =
|
||||||
|
typename detail::MakeRuntimePreparedPlan<
|
||||||
|
std::remove_cvref_t<Prescription>,
|
||||||
|
typename Form::value_blocks,
|
||||||
|
typename Form::residual_blocks>::Type;
|
||||||
|
|
||||||
|
template <typename Form, typename Plan>
|
||||||
|
requires utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
struct NormalizationCoverage final {
|
||||||
|
using DeclaredValueBlocks = typename Plan::ValueBlocks;
|
||||||
|
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
|
||||||
|
|
||||||
|
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
|
||||||
|
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
|
||||||
|
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
|
||||||
|
|
||||||
|
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
|
||||||
|
using UnexpectedResidualBlocks = detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
|
||||||
|
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
|
||||||
|
|
||||||
|
static constexpr bool hasEveryValueBlock = MissingValueBlocks::size == 0;
|
||||||
|
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
|
||||||
|
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
|
||||||
|
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
|
||||||
|
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
|
||||||
|
static constexpr bool hasUniqueResidualOwners = RepeatedResidualBlocks::size == 0;
|
||||||
|
|
||||||
|
static constexpr bool complete = hasEveryValueBlock && hasOnlyValueBlocks && hasUniqueValueOwners &&
|
||||||
|
hasEveryResidualBlock && hasOnlyResidualBlocks &&
|
||||||
|
hasUniqueResidualOwners;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Plan, typename Form>
|
||||||
|
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> &&
|
||||||
|
NormalizationPlanType<Plan> &&
|
||||||
|
NormalizationCoverage<Form, std::remove_cvref_t<Plan>>::complete;
|
||||||
|
} // namespace mean_field::normalization
|
||||||
921
libmeanfield/interface/normalization/stellar_equilibrium.cppm
Normal file
921
libmeanfield/interface/normalization/stellar_equilibrium.cppm
Normal file
@@ -0,0 +1,921 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
export module mean_field:normalization.stellar_equilibrium;
|
||||||
|
|
||||||
|
export import :normalization.operators;
|
||||||
|
export import :operators.stellar_equilibrium_compiler;
|
||||||
|
export import :operators.stellar_equilibrium_problem;
|
||||||
|
export import :utils.domain;
|
||||||
|
|
||||||
|
namespace mean_field::normalization::detail {
|
||||||
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||||
|
|
||||||
|
[[nodiscard]] inline mfem::Vector AssembleScalarMassDiagonal(
|
||||||
|
mfem::ParFiniteElementSpace &space,
|
||||||
|
mfem::Array<int> *domainMarker = nullptr
|
||||||
|
) {
|
||||||
|
mfem::ParBilinearForm mass(&space);
|
||||||
|
if (domainMarker == nullptr) {
|
||||||
|
mass.AddDomainIntegrator(new mfem::MassIntegrator());
|
||||||
|
} else {
|
||||||
|
mass.AddDomainIntegrator(new mfem::MassIntegrator(), *domainMarker);
|
||||||
|
}
|
||||||
|
mass.Assemble();
|
||||||
|
mass.Finalize();
|
||||||
|
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||||
|
if (matrix == nullptr) {
|
||||||
|
throw std::runtime_error("Reference scalar Riesz mass assembly failed.");
|
||||||
|
}
|
||||||
|
mfem::Vector diagonal;
|
||||||
|
matrix->GetDiag(diagonal);
|
||||||
|
return diagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(mfem::ParFiniteElementSpace &space) {
|
||||||
|
mfem::ParBilinearForm mass(&space);
|
||||||
|
mass.AddDomainIntegrator(new mfem::VectorFEMassIntegrator());
|
||||||
|
mass.Assemble();
|
||||||
|
mass.Finalize();
|
||||||
|
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||||
|
if (matrix == nullptr) {
|
||||||
|
throw std::runtime_error("Reference H(div) Riesz mass assembly failed.");
|
||||||
|
}
|
||||||
|
mfem::Vector diagonal;
|
||||||
|
matrix->GetDiag(diagonal);
|
||||||
|
return diagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline mfem::Vector AssembleSurfaceMassDiagonal(
|
||||||
|
const fem::FEM &finiteElements,
|
||||||
|
const field::ScalarBoundaryDofMap &surfaceMap
|
||||||
|
) {
|
||||||
|
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
|
||||||
|
marker = 0;
|
||||||
|
constexpr int attribute = DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
|
||||||
|
if (attribute <= 0 || attribute > marker.Size()) {
|
||||||
|
throw std::invalid_argument("The reference mesh does not contain the stellar-surface boundary.");
|
||||||
|
}
|
||||||
|
marker[attribute - 1] = 1;
|
||||||
|
|
||||||
|
mfem::ParBilinearForm mass(finiteElements.surfaceDeformationFes.get());
|
||||||
|
mass.AddBoundaryIntegrator(new mfem::MassIntegrator(), marker);
|
||||||
|
mass.Assemble();
|
||||||
|
mass.Finalize();
|
||||||
|
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||||
|
if (matrix == nullptr) {
|
||||||
|
throw std::runtime_error("Reference surface Riesz mass assembly failed.");
|
||||||
|
}
|
||||||
|
mfem::Vector ambientDiagonal;
|
||||||
|
matrix->GetDiag(ambientDiagonal);
|
||||||
|
return surfaceMap.gather(ambientDiagonal);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
[[nodiscard]] const auto &PhysicalOperator(const Problem &problem) {
|
||||||
|
return problem.GetPhysicalOperator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline mfem::Vector GatherDiagonal(
|
||||||
|
const mfem::Vector &fullDiagonal,
|
||||||
|
const field::FieldDofMap &map,
|
||||||
|
const char *role
|
||||||
|
) {
|
||||||
|
if (fullDiagonal.Size() != map.full_size()) {
|
||||||
|
throw std::logic_error(std::string("The reference ") + role + " Gram diagonal has an incompatible map.");
|
||||||
|
}
|
||||||
|
return map.gather(fullDiagonal);
|
||||||
|
}
|
||||||
|
} // namespace mean_field::normalization::detail
|
||||||
|
|
||||||
|
export namespace mean_field::normalization {
|
||||||
|
/*
|
||||||
|
* Runtime preparation paired with the compile-time normalization plan.
|
||||||
|
* The operator compiler is the authority for which blocks a specification
|
||||||
|
* generated, and PhysicalRieszBlockTraits is the authority for their
|
||||||
|
* declared physical laws. Keeping those responsibilities separate means
|
||||||
|
* this layer never names a concrete integral or phase constraint.
|
||||||
|
*/
|
||||||
|
namespace detail {
|
||||||
|
template <typename Block>
|
||||||
|
using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
|
||||||
|
|
||||||
|
template <typename Block, typename = void>
|
||||||
|
struct IsGlobalGeneratedValueNormalization : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct IsGlobalGeneratedValueNormalization<
|
||||||
|
utils::blocks::generated_value_block<Generated>,
|
||||||
|
std::void_t<
|
||||||
|
decltype(PhysicalRieszMethodFor<
|
||||||
|
utils::blocks::generated_value_block<Generated>>::topology),
|
||||||
|
decltype(PhysicalRieszMethodFor<
|
||||||
|
utils::blocks::generated_value_block<Generated>>::scale)>>
|
||||||
|
: std::bool_constant<
|
||||||
|
PhysicalRieszBlockTraits<
|
||||||
|
utils::blocks::generated_value_block<Generated>>::registered &&
|
||||||
|
PhysicalRieszMethodFor<
|
||||||
|
utils::blocks::generated_value_block<Generated>>::topology ==
|
||||||
|
RieszTopology::global_scalar> { };
|
||||||
|
|
||||||
|
template <typename Blocks, typename Specification>
|
||||||
|
struct GeneratedValueBlocksBelongToSpecification : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated, typename Specification, typename = void>
|
||||||
|
struct GeneratedCoordinateBelongsToSpecification : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated, typename Specification>
|
||||||
|
struct GeneratedCoordinateBelongsToSpecification<
|
||||||
|
Generated,
|
||||||
|
Specification,
|
||||||
|
std::void_t<typename Generated::SpecificationType>>
|
||||||
|
: std::bool_constant<
|
||||||
|
std::same_as<typename Generated::SpecificationType, Specification>> { };
|
||||||
|
|
||||||
|
template <typename Specification, typename... Generated>
|
||||||
|
struct GeneratedValueBlocksBelongToSpecification<
|
||||||
|
utils::blocks::type_list<utils::blocks::generated_value_block<Generated>...>,
|
||||||
|
Specification>
|
||||||
|
: std::bool_constant<
|
||||||
|
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
|
||||||
|
|
||||||
|
template <typename Block, typename = void>
|
||||||
|
struct IsGlobalGeneratedResidualNormalization : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct IsGlobalGeneratedResidualNormalization<
|
||||||
|
utils::blocks::generated_residual_block<Generated>,
|
||||||
|
std::void_t<
|
||||||
|
decltype(PhysicalRieszMethodFor<
|
||||||
|
utils::blocks::generated_residual_block<Generated>>::topology),
|
||||||
|
decltype(PhysicalRieszMethodFor<
|
||||||
|
utils::blocks::generated_residual_block<Generated>>::scale)>>
|
||||||
|
: std::bool_constant<
|
||||||
|
PhysicalRieszBlockTraits<
|
||||||
|
utils::blocks::generated_residual_block<Generated>>::registered &&
|
||||||
|
PhysicalRieszMethodFor<
|
||||||
|
utils::blocks::generated_residual_block<Generated>>::topology ==
|
||||||
|
RieszTopology::global_scalar> { };
|
||||||
|
|
||||||
|
template <typename Blocks, typename Specification>
|
||||||
|
struct GeneratedResidualBlocksBelongToSpecification : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Specification, typename... Generated>
|
||||||
|
struct GeneratedResidualBlocksBelongToSpecification<
|
||||||
|
utils::blocks::type_list<utils::blocks::generated_residual_block<Generated>...>,
|
||||||
|
Specification>
|
||||||
|
: std::bool_constant<
|
||||||
|
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
|
||||||
|
|
||||||
|
template <typename Blocks> struct PrepareGeneratedValueNormalizations {
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor = false;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &,
|
||||||
|
const StellarCharacteristicScales &
|
||||||
|
) {
|
||||||
|
static_assert(registered, "Generated value-block normalization metadata is malformed.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Blocks>
|
||||||
|
struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
|
||||||
|
static constexpr bool registered =
|
||||||
|
(IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor = registered &&
|
||||||
|
utils::blocks::block_form_is_valid_v<Form> &&
|
||||||
|
(utils::blocks::contains_type_v<Blocks, typename Form::value_blocks> && ...);
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &builder,
|
||||||
|
const StellarCharacteristicScales &scales
|
||||||
|
) {
|
||||||
|
if constexpr (completeFor<Form>) {
|
||||||
|
(builder.template SetValueGlobal<Blocks>(physicalScale<Blocks>(scales)), ...);
|
||||||
|
} else {
|
||||||
|
static_assert(
|
||||||
|
completeFor<Form>,
|
||||||
|
"Every generated value block must have a declared global-scalar Physical Riesz law "
|
||||||
|
"and belong to the compiled equilibrium form."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Blocks> struct PrepareGeneratedResidualNormalizations {
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor = false;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &,
|
||||||
|
const StellarCharacteristicScales &
|
||||||
|
) {
|
||||||
|
static_assert(registered, "Generated residual-block normalization metadata is malformed.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Blocks>
|
||||||
|
struct PrepareGeneratedResidualNormalizations<utils::blocks::type_list<Blocks...>> {
|
||||||
|
static constexpr bool registered =
|
||||||
|
(IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor = registered &&
|
||||||
|
utils::blocks::block_form_is_valid_v<Form> &&
|
||||||
|
(utils::blocks::contains_type_v<Blocks, typename Form::residual_blocks> && ...);
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &builder,
|
||||||
|
const StellarCharacteristicScales &scales
|
||||||
|
) {
|
||||||
|
if constexpr (completeFor<Form>) {
|
||||||
|
(builder.template SetResidualGlobal<Blocks>(physicalScale<Blocks>(scales)), ...);
|
||||||
|
} else {
|
||||||
|
static_assert(
|
||||||
|
completeFor<Form>,
|
||||||
|
"Every generated residual block must have a declared global-scalar Physical Riesz law "
|
||||||
|
"and belong to the compiled equilibrium form."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification, typename = void>
|
||||||
|
struct CompileStellarSpecificationNormalization {
|
||||||
|
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
|
||||||
|
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
|
||||||
|
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor = false;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &,
|
||||||
|
const StellarCharacteristicScales &
|
||||||
|
) {
|
||||||
|
static_assert(
|
||||||
|
completeFor<Form>,
|
||||||
|
"The specification has no complete generated-coordinate normalization."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct CompileStellarSpecificationNormalization<
|
||||||
|
Specification,
|
||||||
|
std::void_t<
|
||||||
|
typename operators::StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::GeneratedValueBlocks,
|
||||||
|
typename operators::StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::GeneratedResidualBlocks>> {
|
||||||
|
using OperatorCompilation =
|
||||||
|
operators::StellarEquilibriumSpecificationCompilation<Specification>;
|
||||||
|
using ValuePreparation = PrepareGeneratedValueNormalizations<
|
||||||
|
typename OperatorCompilation::GeneratedValueBlocks>;
|
||||||
|
using ResidualPreparation = PrepareGeneratedResidualNormalizations<
|
||||||
|
typename OperatorCompilation::GeneratedResidualBlocks>;
|
||||||
|
|
||||||
|
static constexpr bool registered = OperatorCompilation::complete &&
|
||||||
|
models::CompleteGeneratedNormalizationFor<
|
||||||
|
Specification> &&
|
||||||
|
GeneratedValueBlocksBelongToSpecification<
|
||||||
|
typename OperatorCompilation::GeneratedValueBlocks,
|
||||||
|
Specification>::value &&
|
||||||
|
GeneratedResidualBlocksBelongToSpecification<
|
||||||
|
typename OperatorCompilation::GeneratedResidualBlocks,
|
||||||
|
Specification>::value &&
|
||||||
|
ValuePreparation::registered &&
|
||||||
|
ResidualPreparation::registered;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor = registered &&
|
||||||
|
ValuePreparation::template completeFor<Form> &&
|
||||||
|
ResidualPreparation::template completeFor<Form>;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &builder,
|
||||||
|
const StellarCharacteristicScales &scales
|
||||||
|
) {
|
||||||
|
if constexpr (completeFor<Form>) {
|
||||||
|
ValuePreparation::template Apply<Form>(builder, scales);
|
||||||
|
ResidualPreparation::template Apply<Form>(builder, scales);
|
||||||
|
} else {
|
||||||
|
static_assert(
|
||||||
|
completeFor<Form>,
|
||||||
|
"The specification's generated blocks do not have a complete runtime normalization."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename SpecificationSet> struct PrepareSpecificationNormalizations;
|
||||||
|
|
||||||
|
template <models::ModelSpecification... Specifications>
|
||||||
|
struct PrepareSpecificationNormalizations<models::detail::SpecificationSetStorage<Specifications...>> {
|
||||||
|
static constexpr bool registered =
|
||||||
|
(CompileStellarSpecificationNormalization<Specifications>::registered && ...);
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static constexpr bool completeFor =
|
||||||
|
(CompileStellarSpecificationNormalization<Specifications>::template completeFor<Form> && ...);
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &builder,
|
||||||
|
const StellarCharacteristicScales &scales
|
||||||
|
) {
|
||||||
|
static_assert(
|
||||||
|
completeFor<Form>,
|
||||||
|
"Every generated stellar-equilibrium coordinate requires a declared global-scalar "
|
||||||
|
"Physical Riesz normalization and compiler-owned root block."
|
||||||
|
);
|
||||||
|
(CompileStellarSpecificationNormalization<Specifications>::template Apply<Form>(builder, scales), ...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Model, typename Form, typename = void>
|
||||||
|
struct StellarModelNormalizationCoverage : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Model, typename Form>
|
||||||
|
requires model::StellarModelType<Model> && utils::blocks::block_form_is_valid_v<Form>
|
||||||
|
struct StellarModelNormalizationCoverage<
|
||||||
|
Model,
|
||||||
|
Form,
|
||||||
|
std::void_t<typename std::remove_cvref_t<Model>::SpecificationTypes>>
|
||||||
|
: std::bool_constant<
|
||||||
|
PrepareSpecificationNormalizations<
|
||||||
|
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
struct StellarSpecificationNormalizationContribution
|
||||||
|
: detail::CompileStellarSpecificationNormalization<std::remove_cvref_t<Specification>> {
|
||||||
|
using Base = detail::CompileStellarSpecificationNormalization<std::remove_cvref_t<Specification>>;
|
||||||
|
|
||||||
|
template <typename Form>
|
||||||
|
static void Apply(
|
||||||
|
DiagonalNormalizationBuilder<Form> &builder,
|
||||||
|
const StellarCharacteristicScales &scales
|
||||||
|
) {
|
||||||
|
static_assert(
|
||||||
|
Base::template completeFor<Form>,
|
||||||
|
"The specification's generated blocks do not have a complete runtime normalization."
|
||||||
|
);
|
||||||
|
Base::template Apply<Form>(builder, scales);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
concept RegisteredStellarSpecificationNormalization =
|
||||||
|
StellarSpecificationNormalizationContribution<Specification>::registered;
|
||||||
|
|
||||||
|
template <typename Specification, typename Form>
|
||||||
|
concept CompleteStellarSpecificationNormalizationFor =
|
||||||
|
utils::blocks::block_form_is_valid_v<Form> &&
|
||||||
|
StellarSpecificationNormalizationContribution<Specification>::template completeFor<Form>;
|
||||||
|
|
||||||
|
template <typename Model, typename Form>
|
||||||
|
concept CompleteStellarNormalizationFor =
|
||||||
|
detail::StellarModelNormalizationCoverage<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
std::remove_cvref_t<Form>>::value;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Physical Riesz preparation is an optional capability of a physical
|
||||||
|
* core, not part of the protocol needed by the variadic equilibrium root.
|
||||||
|
* Keeping this boundary structural lets a new EOS core opt in by exposing
|
||||||
|
* the same discretization maps without inheriting from, or otherwise
|
||||||
|
* naming, the Polytrope implementation.
|
||||||
|
*/
|
||||||
|
template <typename Candidate>
|
||||||
|
concept PhysicalRieszStellarEquilibriumCore =
|
||||||
|
operators::PreparedStellarEquilibriumPhysicalCore<std::remove_cvref_t<Candidate>> &&
|
||||||
|
PhysicalRieszCoreRuntime<std::remove_cvref_t<Candidate>>;
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept PhysicalRieszStellarEquilibriumProblem =
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
requires {
|
||||||
|
typename std::remove_cvref_t<Problem>::ModelType;
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType;
|
||||||
|
typename std::remove_cvref_t<Problem>::PhysicalCoreType;
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
|
||||||
|
requires PhysicalRieszDiagonalPrescription<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType>;
|
||||||
|
requires CompilableNormalizationFor<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType>;
|
||||||
|
requires CompleteStellarNormalizationFor<
|
||||||
|
typename std::remove_cvref_t<Problem>::ModelType,
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType>;
|
||||||
|
requires StellarNormalizationRuntimeAvailableFor<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType,
|
||||||
|
typename std::remove_cvref_t<Problem>::PhysicalCoreType,
|
||||||
|
typename std::remove_cvref_t<Problem>::ModelType::SpecificationTypes>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
requires std::same_as<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
|
Unnormalized>
|
||||||
|
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
||||||
|
return DiagonalNormalization::Identity(problem.StateSize(), problem.EquationSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <PhysicalRieszStellarEquilibriumProblem Problem>
|
||||||
|
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
using Form = typename ProblemType::FormType;
|
||||||
|
|
||||||
|
const fem::FEM &finiteElements = problem.GetDiscretization().finiteElementModel();
|
||||||
|
if (!finiteElements.okay()) {
|
||||||
|
throw std::invalid_argument("Physical Riesz preparation requires a current finite-element model.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &physical = detail::PhysicalOperator(problem);
|
||||||
|
const auto &gravityContext = physical.GetGravityContext();
|
||||||
|
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
|
||||||
|
const auto scales = deriveStellarCharacteristicScales(
|
||||||
|
problem.GetNormalizationPrescription(),
|
||||||
|
problem.GetStellarModel()
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Array<int> stellarMarker =
|
||||||
|
utils::domain::make_attribute_marker<utils::domain::Stellar, detail::DomainSchema>(*finiteElements.mesh);
|
||||||
|
const mfem::Vector densityDiagonal = detail::GatherDiagonal(
|
||||||
|
detail::AssembleScalarMassDiagonal(*finiteElements.densityFes, &stellarMarker),
|
||||||
|
gravityContext.GetDensityMap(),
|
||||||
|
"density"
|
||||||
|
);
|
||||||
|
const mfem::Vector enthalpyDiagonal = detail::GatherDiagonal(
|
||||||
|
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker),
|
||||||
|
enthalpyMap,
|
||||||
|
"enthalpy"
|
||||||
|
);
|
||||||
|
const mfem::Vector gravityGradientDiagonal = detail::GatherDiagonal(
|
||||||
|
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes),
|
||||||
|
gravityContext.GetGravityGradientMap(),
|
||||||
|
"gravity-gradient"
|
||||||
|
);
|
||||||
|
const mfem::Vector gravityPotentialDiagonal = detail::GatherDiagonal(
|
||||||
|
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
|
||||||
|
gravityContext.GetGravityPotentialMap(),
|
||||||
|
"gravity-potential"
|
||||||
|
);
|
||||||
|
const field::ScalarBoundaryDofMap surfaceMap =
|
||||||
|
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
|
||||||
|
const mfem::Vector surfaceDiagonal = detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap);
|
||||||
|
if (surfaceDiagonal.Size() != physical.GetDomainDeformation().parameterCount()) {
|
||||||
|
throw std::logic_error("The reference surface Gram diagonal does not match the root surface block.");
|
||||||
|
}
|
||||||
|
|
||||||
|
DiagonalNormalizationBuilder<Form> builder(problem.GetManifest().layout());
|
||||||
|
builder.template SetValueBlock<utils::blocks::density::mass::value>(
|
||||||
|
physicalScale<utils::blocks::density::mass::value>(scales), densityDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetValueBlock<utils::blocks::surface_deformation::parameters::value>(
|
||||||
|
physicalScale<utils::blocks::surface_deformation::parameters::value>(scales), surfaceDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetValueBlock<utils::blocks::gravity::gradient::value>(
|
||||||
|
physicalScale<utils::blocks::gravity::gradient::value>(scales), gravityGradientDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetValueBlock<utils::blocks::gravity::poisson::value>(
|
||||||
|
physicalScale<utils::blocks::gravity::poisson::value>(scales), gravityPotentialDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetValueBlock<utils::blocks::enthalpy::specific::value>(
|
||||||
|
physicalScale<utils::blocks::enthalpy::specific::value>(scales), enthalpyDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetResidualBlock<utils::blocks::gravity::gradient::residual>(
|
||||||
|
physicalScale<utils::blocks::gravity::gradient::residual>(scales), gravityGradientDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetResidualBlock<utils::blocks::gravity::poisson::residual>(
|
||||||
|
physicalScale<utils::blocks::gravity::poisson::residual>(scales), gravityPotentialDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetResidualBlock<utils::blocks::density::mass::residual>(
|
||||||
|
physicalScale<utils::blocks::density::mass::residual>(scales), densityDiagonal
|
||||||
|
);
|
||||||
|
builder.template SetResidualBlock<utils::blocks::surface_deformation::shape_equilibrium::residual>(
|
||||||
|
physicalScale<utils::blocks::surface_deformation::shape_equilibrium::residual>(scales), surfaceDiagonal
|
||||||
|
);
|
||||||
|
const mfem::Array<int> &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
||||||
|
builder.template SetHybridResidualBlock<utils::blocks::enthalpy::specific::residual>(
|
||||||
|
physicalScale<utils::blocks::enthalpy::specific::residual>(scales),
|
||||||
|
enthalpyDiagonal,
|
||||||
|
std::span<const int>{surfaceRows.GetData(), static_cast<std::size_t>(surfaceRows.Size())}
|
||||||
|
);
|
||||||
|
detail::PrepareSpecificationNormalizations<typename ProblemType::ModelType::SpecificationTypes>::Apply(
|
||||||
|
builder,
|
||||||
|
scales
|
||||||
|
);
|
||||||
|
|
||||||
|
return std::move(builder).Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public adapter for a third-party prescription. The implementation stays
|
||||||
|
* beside the policy and has the readable signature
|
||||||
|
*
|
||||||
|
* prepareStellarNormalization(policy, problem)
|
||||||
|
*
|
||||||
|
* while every solver-facing caller continues to use the uniform
|
||||||
|
* prepareNormalization(problem) operation. */
|
||||||
|
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||||
|
requires(
|
||||||
|
!std::same_as<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
|
||||||
|
Unnormalized> &&
|
||||||
|
!PhysicalRieszDiagonalPrescription<
|
||||||
|
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
|
||||||
|
RuntimePreparedNormalizationOperation<Problem>)
|
||||||
|
[[nodiscard]] DiagonalNormalization prepareNormalization(
|
||||||
|
const Problem &problem
|
||||||
|
) {
|
||||||
|
return prepareStellarNormalization(
|
||||||
|
problem.GetNormalizationPrescription(),
|
||||||
|
problem
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solver-facing normalization exists exactly when runtime preparation for
|
||||||
|
* the problem's compile-time prescription is a valid operation. This
|
||||||
|
* folds future policy-owned preparation hooks into the same public contract and
|
||||||
|
* turns unsupported core/prescription pairs into ordinary constraint
|
||||||
|
* failure instead of an error in a constructor body.
|
||||||
|
*/
|
||||||
|
template <typename Problem>
|
||||||
|
concept NormalizableStellarEquilibriumProblem =
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
requires(const std::remove_cvref_t<Problem> &problem) {
|
||||||
|
{
|
||||||
|
prepareNormalization(problem)
|
||||||
|
} -> std::same_as<DiagonalNormalization>;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NormalizedStellarEquilibriumStatistics final {
|
||||||
|
std::uint64_t normalizationPreparations{0};
|
||||||
|
std::uint64_t physicalPreparations{0};
|
||||||
|
std::uint64_t residualRetrievals{0};
|
||||||
|
std::uint64_t jacobianApplications{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The high-level stellar adapter retains a pointer to a prepared inverse.
|
||||||
|
* Consequently that inverse must identify the exact physical problem and
|
||||||
|
* expose its lifecycle state. Generic MFEM solvers remain valid inputs to
|
||||||
|
* the lower-level ScaledPreconditioner, where no stellar association is
|
||||||
|
* implied.
|
||||||
|
*/
|
||||||
|
template <typename Candidate, typename Problem>
|
||||||
|
concept ProblemBoundStellarInverseFor =
|
||||||
|
NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
|
||||||
|
requires(const std::remove_cvref_t<Candidate> &inverse) {
|
||||||
|
{
|
||||||
|
inverse.GetProblem()
|
||||||
|
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
|
||||||
|
{
|
||||||
|
inverse.IsCurrent()
|
||||||
|
} -> std::same_as<bool>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
|
||||||
|
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||||
|
class NormalizedStellarPreconditioner;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solver-facing coordinates for a dimensional stellar problem. The
|
||||||
|
* physical problem remains the sole source of residual and Jacobian
|
||||||
|
* physics; this adapter performs only the coordinate maps
|
||||||
|
*
|
||||||
|
* x = R x_hat, F_hat = L F, J_hat = L J R.
|
||||||
|
*
|
||||||
|
* Its normalization is immutable during Prepare/BuildResidual/Mult and is
|
||||||
|
* changed only by an explicit RefreshNormalization call.
|
||||||
|
*/
|
||||||
|
template <NormalizableStellarEquilibriumProblem Problem>
|
||||||
|
class NormalizedStellarEquilibriumOperator final : public mfem::Operator {
|
||||||
|
private:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NormalizedStellarEquilibriumOperator(ProblemType &problem)
|
||||||
|
: mfem::Operator(problem.EquationSize(), problem.StateSize()),
|
||||||
|
m_problem(&problem),
|
||||||
|
m_normalization(prepareNormalization(problem)),
|
||||||
|
m_scaledJacobian(problem.GetLinearizationOperator(), m_normalization),
|
||||||
|
m_physicalState(problem.StateSize()),
|
||||||
|
m_physicalResidual(problem.EquationSize()),
|
||||||
|
m_normalizedResidual(problem.EquationSize()) {
|
||||||
|
if (Width() != Height()) {
|
||||||
|
throw std::invalid_argument("A normalized stellar-equilibrium operator must be square.");
|
||||||
|
}
|
||||||
|
m_statistics.normalizationPreparations = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalizedStellarEquilibriumOperator(const NormalizedStellarEquilibriumOperator &) = delete;
|
||||||
|
NormalizedStellarEquilibriumOperator &operator=(const NormalizedStellarEquilibriumOperator &) = delete;
|
||||||
|
NormalizedStellarEquilibriumOperator(NormalizedStellarEquilibriumOperator &&) = delete;
|
||||||
|
NormalizedStellarEquilibriumOperator &operator=(NormalizedStellarEquilibriumOperator &&) = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] auto Prepare(
|
||||||
|
const mfem::Vector &normalizedState,
|
||||||
|
const operators::StellarEquilibriumDependencies &dependencies,
|
||||||
|
const physics::RigidRotation &rotation
|
||||||
|
) requires(ProblemType::generatedRotationProviderCount == 0) {
|
||||||
|
if (normalizedState.Size() != Width()) {
|
||||||
|
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isPrepared = false;
|
||||||
|
m_normalization.DenormalizeState(normalizedState, m_physicalState);
|
||||||
|
auto report = m_problem->Prepare(m_physicalState, dependencies, rotation);
|
||||||
|
m_problem->BuildResidual(m_physicalResidual);
|
||||||
|
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
|
||||||
|
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
|
||||||
|
m_isPrepared = true;
|
||||||
|
++m_statistics.physicalPreparations;
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto Prepare(
|
||||||
|
const mfem::Vector &normalizedState,
|
||||||
|
const operators::StellarEquilibriumDependencies &dependencies
|
||||||
|
) requires(ProblemType::generatedRotationProviderCount == 1) {
|
||||||
|
if (normalizedState.Size() != Width()) {
|
||||||
|
throw std::invalid_argument("The normalized stellar state has the wrong size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isPrepared = false;
|
||||||
|
m_normalization.DenormalizeState(normalizedState, m_physicalState);
|
||||||
|
auto report = m_problem->Prepare(m_physicalState, dependencies);
|
||||||
|
m_problem->BuildResidual(m_physicalResidual);
|
||||||
|
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
|
||||||
|
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
|
||||||
|
m_isPrepared = true;
|
||||||
|
++m_statistics.physicalPreparations;
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildResidual(mfem::Vector &normalizedResidual) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
normalizedResidual = m_normalizedResidual;
|
||||||
|
++m_statistics.residualRetrievals;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &normalizedDirection,
|
||||||
|
mfem::Vector &normalizedAction
|
||||||
|
) const override {
|
||||||
|
VerifyPrepared();
|
||||||
|
if (normalizedDirection.Size() != Width()) {
|
||||||
|
throw std::invalid_argument("The normalized stellar direction has the wrong size.");
|
||||||
|
}
|
||||||
|
m_scaledJacobian.Mult(normalizedDirection, normalizedAction);
|
||||||
|
++m_statistics.jacobianApplications;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RefreshNormalization() {
|
||||||
|
DiagonalNormalization refreshed = prepareNormalization(*m_problem);
|
||||||
|
m_normalization = std::move(refreshed);
|
||||||
|
m_isPrepared = false;
|
||||||
|
++m_statistics.normalizationPreparations;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NormalizeState(
|
||||||
|
const mfem::Vector &physicalState,
|
||||||
|
mfem::Vector &normalizedState
|
||||||
|
) const {
|
||||||
|
m_normalization.NormalizeState(physicalState, normalizedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DenormalizeState(
|
||||||
|
const mfem::Vector &normalizedState,
|
||||||
|
mfem::Vector &physicalState
|
||||||
|
) const {
|
||||||
|
m_normalization.DenormalizeState(normalizedState, physicalState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NormalizeResidual(
|
||||||
|
const mfem::Vector &physicalResidual,
|
||||||
|
mfem::Vector &normalizedResidual
|
||||||
|
) const {
|
||||||
|
m_normalization.NormalizeResidual(physicalResidual, normalizedResidual);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DenormalizeResidual(
|
||||||
|
const mfem::Vector &normalizedResidual,
|
||||||
|
mfem::Vector &physicalResidual
|
||||||
|
) const {
|
||||||
|
m_normalization.DenormalizeResidual(normalizedResidual, physicalResidual);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename PhysicalInverse>
|
||||||
|
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||||
|
[[nodiscard]] NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
|
||||||
|
MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||||
|
return m_isPrepared && m_problem->IsPrepared() &&
|
||||||
|
m_physicalPreparationGeneration == m_problem->GetPreparationGeneration();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ProblemType &GetPhysicalProblem() noexcept {
|
||||||
|
return *m_problem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ProblemType &GetPhysicalProblem() const noexcept {
|
||||||
|
return *m_problem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
|
||||||
|
return m_problem->GetLinearizationOperator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
|
||||||
|
return *m_problem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const DiagonalNormalization &GetNormalization() const noexcept {
|
||||||
|
return m_normalization;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Vector &GetPhysicalState() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return m_physicalState;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Vector &GetPhysicalResidual() const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return m_physicalResidual;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const NormalizedStellarEquilibriumStatistics &GetStatistics() const noexcept {
|
||||||
|
return m_statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void VerifyPrepared() const {
|
||||||
|
if (!IsPrepared()) {
|
||||||
|
throw std::logic_error(
|
||||||
|
"The normalized stellar-equilibrium operator must be prepared and current before application."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProblemType *m_problem;
|
||||||
|
DiagonalNormalization m_normalization;
|
||||||
|
ScaledJacobianOperator m_scaledJacobian;
|
||||||
|
mfem::Vector m_physicalState;
|
||||||
|
mfem::Vector m_physicalResidual;
|
||||||
|
mfem::Vector m_normalizedResidual;
|
||||||
|
std::uint64_t m_physicalPreparationGeneration{0};
|
||||||
|
mutable NormalizedStellarEquilibriumStatistics m_statistics;
|
||||||
|
bool m_isPrepared{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
|
||||||
|
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||||
|
class NormalizedStellarPreconditioner final : public mfem::Solver {
|
||||||
|
private:
|
||||||
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
|
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
|
||||||
|
using PhysicalInverseType = std::remove_cvref_t<PhysicalInverse>;
|
||||||
|
|
||||||
|
[[nodiscard]] static PhysicalInverseType &RequireAssociatedPhysicalInverse(
|
||||||
|
const NormalizedOperator &normalizedOperator,
|
||||||
|
PhysicalInverseType &physicalInverse
|
||||||
|
) {
|
||||||
|
if (std::addressof(physicalInverse.GetProblem()) !=
|
||||||
|
std::addressof(normalizedOperator.GetProblem())) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"A normalized stellar preconditioner and its physical inverse must belong to the same problem."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return physicalInverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
NormalizedStellarPreconditioner(
|
||||||
|
const NormalizedOperator &normalizedOperator,
|
||||||
|
PhysicalInverseType &physicalInverse
|
||||||
|
)
|
||||||
|
: mfem::Solver(
|
||||||
|
normalizedOperator.Width(),
|
||||||
|
normalizedOperator.Height(),
|
||||||
|
physicalInverse.iterative_mode
|
||||||
|
),
|
||||||
|
m_normalizedOperator(&normalizedOperator),
|
||||||
|
m_physicalInverse(&physicalInverse),
|
||||||
|
m_scaled(
|
||||||
|
RequireAssociatedPhysicalInverse(normalizedOperator, physicalInverse),
|
||||||
|
normalizedOperator.GetPhysicalJacobian(),
|
||||||
|
normalizedOperator,
|
||||||
|
normalizedOperator.GetNormalization()
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalizedStellarPreconditioner(const NormalizedStellarPreconditioner &) = delete;
|
||||||
|
NormalizedStellarPreconditioner &operator=(const NormalizedStellarPreconditioner &) = delete;
|
||||||
|
NormalizedStellarPreconditioner(NormalizedStellarPreconditioner &&) = delete;
|
||||||
|
NormalizedStellarPreconditioner &operator=(NormalizedStellarPreconditioner &&) = delete;
|
||||||
|
|
||||||
|
void SetOperator(const mfem::Operator &normalizedJacobian) override {
|
||||||
|
VerifyCurrent();
|
||||||
|
if (&normalizedJacobian != m_normalizedOperator) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"The normalized stellar preconditioner cannot be rebound to a different Jacobian."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
m_scaled.SetOperator(normalizedJacobian);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &normalizedResidual,
|
||||||
|
mfem::Vector &normalizedCorrection
|
||||||
|
) const override {
|
||||||
|
VerifyCurrent();
|
||||||
|
m_scaled.Mult(normalizedResidual, normalizedCorrection);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsCurrent() const {
|
||||||
|
return m_normalizedOperator->IsPrepared() &&
|
||||||
|
m_physicalInverse->IsCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] PhysicalInverseType &GetPhysicalInverse() noexcept {
|
||||||
|
return *m_physicalInverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const PhysicalInverseType &GetPhysicalInverse() const noexcept {
|
||||||
|
return *m_physicalInverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
|
||||||
|
return m_scaled.GetPhysicalJacobian();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Operator &GetNormalizedJacobian() const {
|
||||||
|
return m_scaled.GetNormalizedJacobian();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ScaledPreconditionerStatistics &GetStatistics() const noexcept {
|
||||||
|
return m_scaled.GetStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void VerifyCurrent() const {
|
||||||
|
if (!IsCurrent()) {
|
||||||
|
throw std::logic_error(
|
||||||
|
"The normalized stellar preconditioner cannot be used while its normalized operator or physical "
|
||||||
|
"inverse is stale."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NormalizedOperator *m_normalizedOperator;
|
||||||
|
PhysicalInverseType *m_physicalInverse;
|
||||||
|
ScaledPreconditioner m_scaled;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <NormalizableStellarEquilibriumProblem Problem>
|
||||||
|
template <typename PhysicalInverse>
|
||||||
|
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
|
||||||
|
NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
|
||||||
|
NormalizedStellarEquilibriumOperator<Problem>::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const {
|
||||||
|
VerifyPrepared();
|
||||||
|
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{
|
||||||
|
*this,
|
||||||
|
physicalInverse
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <NormalizableStellarEquilibriumProblem Problem>
|
||||||
|
[[nodiscard]] auto makeNormalizedStellarEquilibriumOperator(Problem &problem) {
|
||||||
|
return NormalizedStellarEquilibriumOperator<Problem>{problem};
|
||||||
|
}
|
||||||
|
} // namespace mean_field::normalization
|
||||||
196
libmeanfield/interface/operators/prepared_angular_momentum.cppm
Normal file
196
libmeanfield/interface/operators/prepared_angular_momentum.cppm
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <compare>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
export module mean_field:operators.prepared_angular_momentum;
|
||||||
|
|
||||||
|
export import :fem;
|
||||||
|
export import :mapping.domain_mapper;
|
||||||
|
export import :model.compiled_fixed_angular_momentum;
|
||||||
|
export import :operators.context.gravity_field;
|
||||||
|
|
||||||
|
export namespace mean_field::operators {
|
||||||
|
struct AngularMomentumDependencyStamp final {
|
||||||
|
std::uint64_t identity{0};
|
||||||
|
std::uint64_t revision{0};
|
||||||
|
|
||||||
|
constexpr auto operator<=>(const AngularMomentumDependencyStamp &) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AngularMomentumDependencies final {
|
||||||
|
AngularMomentumDependencyStamp discretization;
|
||||||
|
AngularMomentumDependencyStamp density;
|
||||||
|
AngularMomentumDependencyStamp displacement;
|
||||||
|
AngularMomentumDependencyStamp rotation;
|
||||||
|
|
||||||
|
constexpr auto operator<=>(const AngularMomentumDependencies &) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparedAngularMomentumReport final {
|
||||||
|
bool rebuiltStaticPlan{false};
|
||||||
|
bool refreshedGeometry{false};
|
||||||
|
bool refreshedDensity{false};
|
||||||
|
bool updatedAngularVelocity{false};
|
||||||
|
bool assembledResidual{false};
|
||||||
|
|
||||||
|
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||||
|
return rebuiltStaticPlan || refreshedGeometry || refreshedDensity || updatedAngularVelocity ||
|
||||||
|
assembledResidual;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto operator<=>(const PreparedAngularMomentumReport &) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AngularMomentumConstraintReport final {
|
||||||
|
double targetAngularMomentum;
|
||||||
|
double achievedAngularMomentum;
|
||||||
|
double momentOfInertia;
|
||||||
|
double angularVelocity;
|
||||||
|
double dimensionalResidual;
|
||||||
|
double scaledResidual;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparedAngularMomentumActionStatistics final {
|
||||||
|
std::uint64_t densityApplications{0};
|
||||||
|
std::uint64_t displacementApplications{0};
|
||||||
|
std::uint64_t angularVelocityApplications{0};
|
||||||
|
std::uint64_t completeApplications{0};
|
||||||
|
|
||||||
|
constexpr auto operator<=>(const PreparedAngularMomentumActionStatistics &) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepared scalar invariant
|
||||||
|
*
|
||||||
|
* R_J(rho, d, Omega) = Omega I_axis(rho, d) - J_target,
|
||||||
|
* I_axis = integral rho |(x-x_0)_perp|^2 dV.
|
||||||
|
*
|
||||||
|
* The axis is normalized by CompiledFixedAngularMomentum. Density and
|
||||||
|
* geometry are borrowed from the shared gravity context, so this row is
|
||||||
|
* linearized at exactly the same mapped state as every physical equation.
|
||||||
|
*/
|
||||||
|
class PreparedAngularMomentumOperator final {
|
||||||
|
public:
|
||||||
|
using SpecificationType = models::FixedAngularMomentum;
|
||||||
|
using CompiledConstraintType = models::CompiledFixedAngularMomentum;
|
||||||
|
using Dependencies = AngularMomentumDependencies;
|
||||||
|
using Report = PreparedAngularMomentumReport;
|
||||||
|
|
||||||
|
PreparedAngularMomentumOperator(
|
||||||
|
const fem::FEM &f,
|
||||||
|
const mapping::DomainMapper &domainMapper,
|
||||||
|
const context::gravity_field::GravityFieldLinearizationContext &gravityContext,
|
||||||
|
models::CompiledFixedAngularMomentum constraint
|
||||||
|
);
|
||||||
|
|
||||||
|
PreparedAngularMomentumOperator(const PreparedAngularMomentumOperator &) = delete;
|
||||||
|
PreparedAngularMomentumOperator &operator=(const PreparedAngularMomentumOperator &) = delete;
|
||||||
|
PreparedAngularMomentumOperator(PreparedAngularMomentumOperator &&) = delete;
|
||||||
|
PreparedAngularMomentumOperator &operator=(PreparedAngularMomentumOperator &&) = delete;
|
||||||
|
|
||||||
|
PreparedAngularMomentumReport Prepare(
|
||||||
|
double angularVelocity,
|
||||||
|
const AngularMomentumDependencies &dependencies
|
||||||
|
);
|
||||||
|
|
||||||
|
void BuildResidual(mfem::Vector &residual) const;
|
||||||
|
|
||||||
|
void ApplyDensityJacobianAction(
|
||||||
|
const mfem::Vector &densityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const;
|
||||||
|
|
||||||
|
void ApplyDisplacementJacobianAction(
|
||||||
|
const mfem::Vector &displacementVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const;
|
||||||
|
|
||||||
|
void ApplyAngularVelocityJacobianAction(
|
||||||
|
double angularVelocityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const;
|
||||||
|
|
||||||
|
void ApplyCompleteJacobianAction(
|
||||||
|
const mfem::Vector &densityVariation,
|
||||||
|
const mfem::Vector &displacementVariation,
|
||||||
|
double angularVelocityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||||
|
[[nodiscard]] double GetMomentOfInertia() const;
|
||||||
|
[[nodiscard]] double GetAngularVelocity() const;
|
||||||
|
[[nodiscard]] double GetCurrentAngularMomentum() const;
|
||||||
|
[[nodiscard]] double GetTargetAngularMomentum() const noexcept;
|
||||||
|
[[nodiscard]] physics::RigidRotation GetRotation() const;
|
||||||
|
[[nodiscard]] AngularMomentumConstraintReport GetConstraintReport() const;
|
||||||
|
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||||
|
[[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept;
|
||||||
|
[[nodiscard]] const PreparedAngularMomentumActionStatistics &GetActionStatistics() const noexcept;
|
||||||
|
[[nodiscard]] const models::CompiledFixedAngularMomentum &GetCompiledConstraint() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct QuadraturePointData final {
|
||||||
|
mfem::IntegrationPoint integrationPoint;
|
||||||
|
mfem::Vector densityShape;
|
||||||
|
mapping::VolumeMappingContext mappingContext;
|
||||||
|
double density{0.0};
|
||||||
|
double cylindricalRadiusSquared{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ElementPAData final {
|
||||||
|
int elementId{-1};
|
||||||
|
mfem::Array<int> densityDofs;
|
||||||
|
mfem::Array<int> displacementDofs;
|
||||||
|
mfem::Array<int> compactificationDofs;
|
||||||
|
mfem::DofTransformation *densityDofTransformation{nullptr};
|
||||||
|
mfem::DofTransformation *displacementDofTransformation{nullptr};
|
||||||
|
mfem::DofTransformation *compactificationDofTransformation{nullptr};
|
||||||
|
mfem::Vector baseDisplacement;
|
||||||
|
mfem::Vector compactification;
|
||||||
|
std::vector<QuadraturePointData> quadraturePoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
void BuildStaticPlan();
|
||||||
|
void RefreshGeometry(const mfem::Vector &displacement);
|
||||||
|
void RefreshDensity(const mfem::Vector &density);
|
||||||
|
void AssembleResidual();
|
||||||
|
void VerifyPrepared() const;
|
||||||
|
|
||||||
|
[[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const;
|
||||||
|
[[nodiscard]] double EvaluateDisplacementMomentActionLocal(const mfem::Vector &displacementVariation) const;
|
||||||
|
[[nodiscard]] double CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept;
|
||||||
|
[[nodiscard]] double CylindricalRadiusSquaredVariation(
|
||||||
|
const mfem::Vector &physicalPosition,
|
||||||
|
const mfem::Vector &physicalPositionVariation
|
||||||
|
) const noexcept;
|
||||||
|
[[nodiscard]] double GlobalSum(double localValue) const;
|
||||||
|
|
||||||
|
const fem::FEM &m_fem;
|
||||||
|
const mapping::DomainMapper &m_domainMapper;
|
||||||
|
const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext;
|
||||||
|
models::CompiledFixedAngularMomentum m_constraint;
|
||||||
|
|
||||||
|
std::vector<ElementPAData> m_elements;
|
||||||
|
AngularMomentumDependencies m_preparedDependencies;
|
||||||
|
mfem::Vector m_cachedResidual;
|
||||||
|
mutable mfem::Vector m_densityVariationTrue;
|
||||||
|
mutable mfem::Vector m_displacementVariationTrue;
|
||||||
|
mutable mfem::Vector m_densityVariationLocal;
|
||||||
|
mutable mfem::Vector m_displacementVariationLocal;
|
||||||
|
mutable mfem::Vector m_elementDensityVariation;
|
||||||
|
mutable mfem::Vector m_elementDisplacementVariation;
|
||||||
|
|
||||||
|
double m_momentOfInertia{0.0};
|
||||||
|
double m_angularVelocity{0.0};
|
||||||
|
double m_currentAngularMomentum{0.0};
|
||||||
|
std::uint64_t m_preparationCount{0};
|
||||||
|
mutable std::uint64_t m_residualApplicationCount{0};
|
||||||
|
mutable PreparedAngularMomentumActionStatistics m_actionStatistics;
|
||||||
|
bool m_isPrepared{false};
|
||||||
|
};
|
||||||
|
} // namespace mean_field::operators
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
module;
|
|
||||||
|
|
||||||
#include <concepts>
|
|
||||||
#include <memory>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <mfem.hpp>
|
|
||||||
|
|
||||||
export module mean_field:operators.prepared_central_density_stellar_equilibrium;
|
|
||||||
|
|
||||||
export import :model.compiled_fixed_central_density;
|
|
||||||
export import :operators.prepared_central_density;
|
|
||||||
export import :operators.prepared_stellar_equilibrium;
|
|
||||||
|
|
||||||
export namespace mean_field::operators {
|
|
||||||
using CentralDensityStellarEquilibriumSpecificationModel = model::StellarModel<
|
|
||||||
models::
|
|
||||||
SpecificationSet<eos::Polytrope, models::FixedTotalMass, surface::Isobaric, models::FixedCentralDensity>>;
|
|
||||||
|
|
||||||
using CentralDensityStellarEquilibriumForm = utils::blocks::central_density_bordered_stellar_equilibrium_form;
|
|
||||||
using CentralDensityStellarEquilibriumJacobianForm =
|
|
||||||
utils::blocks::central_density_bordered_stellar_equilibrium_jacobian_form;
|
|
||||||
using CentralDensityStellarEquilibriumLayout = utils::blocks::form_layout<CentralDensityStellarEquilibriumForm>;
|
|
||||||
using CentralDensityStellarEquilibriumSystemManifest = EquilibriumSystemManifest<
|
|
||||||
CentralDensityStellarEquilibriumSpecificationModel,
|
|
||||||
CentralDensityStellarEquilibriumForm,
|
|
||||||
CentralDensityStellarEquilibriumJacobianForm>;
|
|
||||||
|
|
||||||
using CentralDensityStellarEquilibriumRootManifest = CentralDensityStellarEquilibriumSystemManifest;
|
|
||||||
|
|
||||||
struct PreparedCentralDensityStellarEquilibriumReport final {
|
|
||||||
PreparedStellarEquilibriumReport physical;
|
|
||||||
PreparedCentralDensityReport phase;
|
|
||||||
bool assembledResidual{false};
|
|
||||||
|
|
||||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
|
||||||
return physical.DidAnyWork() || phase.DidAnyWork() || assembledResidual;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class PreparedCentralDensityStellarEquilibriumOperator final : public mfem::Operator {
|
|
||||||
public:
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator(
|
|
||||||
fem::FEM &f,
|
|
||||||
const mapping::DomainMapper &domainMapper,
|
|
||||||
const eos::Polytrope &equationOfState,
|
|
||||||
models::CompiledFixedMass fixedMassConstraint,
|
|
||||||
PressureSurfaceConstraintView surfaceConstraint,
|
|
||||||
deformation::PreparedDomainDeformationRuntime domainDeformation,
|
|
||||||
models::CompiledFixedCentralDensity centralDensity
|
|
||||||
)
|
|
||||||
: PreparedCentralDensityStellarEquilibriumOperator(
|
|
||||||
f,
|
|
||||||
std::make_unique<PreparedStellarEquilibriumOperator>(
|
|
||||||
f,
|
|
||||||
domainMapper,
|
|
||||||
equationOfState,
|
|
||||||
std::move(fixedMassConstraint),
|
|
||||||
surfaceConstraint,
|
|
||||||
std::move(domainDeformation)
|
|
||||||
),
|
|
||||||
std::move(centralDensity),
|
|
||||||
MakeCenterDofMap(f)
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator(const PreparedCentralDensityStellarEquilibriumOperator &) =
|
|
||||||
delete;
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator &
|
|
||||||
operator=(const PreparedCentralDensityStellarEquilibriumOperator &) = delete;
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator(PreparedCentralDensityStellarEquilibriumOperator &&) = delete;
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator &
|
|
||||||
operator=(PreparedCentralDensityStellarEquilibriumOperator &&) = delete;
|
|
||||||
|
|
||||||
PreparedCentralDensityStellarEquilibriumReport Prepare(
|
|
||||||
const mfem::Vector &state,
|
|
||||||
const StellarEquilibriumDependencies &dependencies,
|
|
||||||
const physics::RigidRotation &rotation
|
|
||||||
);
|
|
||||||
|
|
||||||
void BuildResidual(mfem::Vector &residual) const;
|
|
||||||
|
|
||||||
void Mult(
|
|
||||||
const mfem::Vector &direction,
|
|
||||||
mfem::Vector &action
|
|
||||||
) const override;
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
|
||||||
[[nodiscard]] const CentralDensityStellarEquilibriumLayout &GetLayout() const noexcept;
|
|
||||||
[[nodiscard]] const CentralDensityStellarEquilibriumRootManifest &GetRootManifest() const noexcept;
|
|
||||||
[[nodiscard]] const PreparedStellarEquilibriumOperator &GetPhysicalOperator() const noexcept;
|
|
||||||
[[nodiscard]] const PreparedCentralDensityConstraint &GetCentralDensityConstraint() const noexcept;
|
|
||||||
[[nodiscard]] RootConstraintReport GetFixedMassReport() const;
|
|
||||||
[[nodiscard]] CentralDensityConstraintReport GetCentralDensityReport() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static field::FieldPointDofMap MakeCenterDofMap(const fem::FEM &f);
|
|
||||||
|
|
||||||
PreparedCentralDensityStellarEquilibriumOperator(
|
|
||||||
fem::FEM &f,
|
|
||||||
std::unique_ptr<PreparedStellarEquilibriumOperator> physicalOperator,
|
|
||||||
models::CompiledFixedCentralDensity centralDensity,
|
|
||||||
field::FieldPointDofMap centerDof
|
|
||||||
);
|
|
||||||
|
|
||||||
void AssembleResidual();
|
|
||||||
void VerifyPrepared() const;
|
|
||||||
|
|
||||||
std::unique_ptr<PreparedStellarEquilibriumOperator> m_physicalOperator;
|
|
||||||
models::CompiledFixedCentralDensity m_centralDensity;
|
|
||||||
PreparedCentralDensityConstraint m_phaseConstraint;
|
|
||||||
CentralDensityStellarEquilibriumRootManifest m_rootManifest;
|
|
||||||
mfem::Vector m_cachedResidual;
|
|
||||||
bool m_isPrepared{false};
|
|
||||||
};
|
|
||||||
} // namespace mean_field::operators
|
|
||||||
@@ -35,6 +35,7 @@ export namespace mean_field::operators {
|
|||||||
std::uint64_t enthalpyApplications{0};
|
std::uint64_t enthalpyApplications{0};
|
||||||
std::uint64_t gravityPotentialApplications{0};
|
std::uint64_t gravityPotentialApplications{0};
|
||||||
std::uint64_t bernoulliConstantApplications{0};
|
std::uint64_t bernoulliConstantApplications{0};
|
||||||
|
std::uint64_t rotationAmplitudeApplications{0};
|
||||||
std::uint64_t combinedApplications{0};
|
std::uint64_t combinedApplications{0};
|
||||||
|
|
||||||
constexpr auto operator<=>(const PreparedHydrostaticAlgebraicJacobianStatistics &) const = default;
|
constexpr auto operator<=>(const PreparedHydrostaticAlgebraicJacobianStatistics &) const = default;
|
||||||
@@ -118,6 +119,14 @@ export namespace mean_field::operators {
|
|||||||
mfem::Vector &action
|
mfem::Vector &action
|
||||||
) const;
|
) const;
|
||||||
|
|
||||||
|
// Differentiates a multiplicative change Omega -> (1 + alpha) Omega
|
||||||
|
// at the frozen rigid rotation. Since Psi_rotation is quadratic in
|
||||||
|
// Omega, this contributes -2 alpha Psi_rotation to the hydrostatic row.
|
||||||
|
void ApplyRotationAmplitudeJacobianAction(
|
||||||
|
double fractionalAngularVelocityVariation,
|
||||||
|
mfem::Vector &action
|
||||||
|
) const;
|
||||||
|
|
||||||
void ApplyAlgebraicJacobianAction(
|
void ApplyAlgebraicJacobianAction(
|
||||||
const mfem::Vector &enthalpyVariation,
|
const mfem::Vector &enthalpyVariation,
|
||||||
const mfem::Vector &gravityPotentialVariation,
|
const mfem::Vector &gravityPotentialVariation,
|
||||||
|
|||||||
@@ -96,6 +96,20 @@ export namespace mean_field::operators {
|
|||||||
|
|
||||||
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
|
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
|
||||||
public:
|
public:
|
||||||
|
/*
|
||||||
|
* Privileged aggregate runtimes can inspect this complete numerical
|
||||||
|
* core. Keep their allow-list on the concrete core itself: a custom
|
||||||
|
* EOS may reuse this class, but it cannot extend the class's backend
|
||||||
|
* privileges. Ordinary specifications use restricted nested physics
|
||||||
|
* and never interact with this list.
|
||||||
|
*/
|
||||||
|
using BackendSpecifications = models::ModelTypeList<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
models::FixedTotalMass,
|
||||||
|
models::FixedAngularMomentum,
|
||||||
|
models::FixedCentralDensity>;
|
||||||
|
|
||||||
template <models::StellarModelType Model>
|
template <models::StellarModelType Model>
|
||||||
requires std::same_as<
|
requires std::same_as<
|
||||||
typename std::remove_cvref_t<Model>::EquationOfStateType,
|
typename std::remove_cvref_t<Model>::EquationOfStateType,
|
||||||
@@ -174,6 +188,12 @@ export namespace mean_field::operators {
|
|||||||
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
|
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
|
||||||
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
|
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
|
||||||
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
|
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
|
||||||
|
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
|
||||||
|
const mfem::Vector &densityDirection
|
||||||
|
) const;
|
||||||
|
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
|
||||||
|
const mfem::Vector &surfaceShapeDirection
|
||||||
|
) const;
|
||||||
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
|
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
|
||||||
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
|
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
|
||||||
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
|
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
|
||||||
@@ -234,5 +254,6 @@ export namespace mean_field::operators {
|
|||||||
mutable mfem::Vector m_fullMechanicalAction;
|
mutable mfem::Vector m_fullMechanicalAction;
|
||||||
mutable mfem::Vector m_surfaceShapeAction;
|
mutable mfem::Vector m_surfaceShapeAction;
|
||||||
mutable mfem::Vector m_pullbackDerivativeAction;
|
mutable mfem::Vector m_pullbackDerivativeAction;
|
||||||
|
mutable mfem::Vector m_densityVolumeIntegralAction;
|
||||||
};
|
};
|
||||||
} // namespace mean_field::operators
|
} // namespace mean_field::operators
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,815 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
export module mean_field:operators.stellar_equilibrium_compiler;
|
||||||
|
|
||||||
|
export import :model.compiled_fixed_angular_momentum;
|
||||||
|
export import :model.compiled_fixed_central_density;
|
||||||
|
export import :model.typed_stellar;
|
||||||
|
export import :utils.blocks;
|
||||||
|
|
||||||
|
export namespace mean_field::operators {
|
||||||
|
/*
|
||||||
|
* A coupling is the symbolic statement that one Jacobian block may be
|
||||||
|
* nonzero. Specifications contribute these statements independently of
|
||||||
|
* the final row and column layout.
|
||||||
|
*/
|
||||||
|
template <typename ResidualBlock, typename ValueBlock>
|
||||||
|
struct StellarEquilibriumJacobianCoupling final {
|
||||||
|
using Residual = ResidualBlock;
|
||||||
|
using Value = ValueBlock;
|
||||||
|
|
||||||
|
using ResidualBlockType = ResidualBlock;
|
||||||
|
using ValueBlockType = ValueBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ResidualBlock, typename ValueBlock>
|
||||||
|
using EquilibriumJacobianCoupling =
|
||||||
|
StellarEquilibriumJacobianCoupling<ResidualBlock, ValueBlock>;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename... Lists> struct ConcatenateBlockLists;
|
||||||
|
|
||||||
|
template <> struct ConcatenateBlockLists<> {
|
||||||
|
using Type = utils::blocks::type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Types>
|
||||||
|
struct ConcatenateBlockLists<utils::blocks::type_list<Types...>> {
|
||||||
|
using Type = utils::blocks::type_list<Types...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... First, typename... Second, typename... Remaining>
|
||||||
|
struct ConcatenateBlockLists<utils::blocks::type_list<First...>,
|
||||||
|
utils::blocks::type_list<Second...>,
|
||||||
|
Remaining...> {
|
||||||
|
using Type = typename ConcatenateBlockLists<
|
||||||
|
utils::blocks::type_list<First..., Second...>, Remaining...>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Lists>
|
||||||
|
using ConcatenateBlockListsT = typename ConcatenateBlockLists<Lists...>::Type;
|
||||||
|
|
||||||
|
template <typename List, typename Type> struct AppendUniqueBlockType;
|
||||||
|
|
||||||
|
template <typename... Types, typename Type>
|
||||||
|
struct AppendUniqueBlockType<utils::blocks::type_list<Types...>, Type> {
|
||||||
|
using TypeValue = std::conditional_t<
|
||||||
|
utils::blocks::contains_type_v<Type, utils::blocks::type_list<Types...>>,
|
||||||
|
utils::blocks::type_list<Types...>,
|
||||||
|
utils::blocks::type_list<Types..., Type>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Accumulated, typename Remaining> struct UniqueBlockListImpl;
|
||||||
|
|
||||||
|
template <typename Accumulated>
|
||||||
|
struct UniqueBlockListImpl<Accumulated, utils::blocks::type_list<>> {
|
||||||
|
using Type = Accumulated;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Accumulated, typename Head, typename... Tail>
|
||||||
|
struct UniqueBlockListImpl<Accumulated,
|
||||||
|
utils::blocks::type_list<Head, Tail...>> {
|
||||||
|
using Type = typename UniqueBlockListImpl<
|
||||||
|
typename AppendUniqueBlockType<Accumulated, Head>::TypeValue,
|
||||||
|
utils::blocks::type_list<Tail...>>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename List>
|
||||||
|
using UniqueBlockListT =
|
||||||
|
typename UniqueBlockListImpl<utils::blocks::type_list<>, List>::Type;
|
||||||
|
|
||||||
|
template <typename... Lists>
|
||||||
|
using UniqueConcatenateBlockListsT =
|
||||||
|
UniqueBlockListT<ConcatenateBlockListsT<Lists...>>;
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsValueBlockList : std::false_type {};
|
||||||
|
|
||||||
|
template <typename... Blocks>
|
||||||
|
struct IsValueBlockList<utils::blocks::type_list<Blocks...>>
|
||||||
|
: std::bool_constant<
|
||||||
|
(std::derived_from<Blocks, utils::blocks::value_block_base> && ...) &&
|
||||||
|
utils::blocks::types_are_unique_v<
|
||||||
|
utils::blocks::type_list<Blocks...>>> {};
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsResidualBlockList : std::false_type {};
|
||||||
|
|
||||||
|
template <typename... Blocks>
|
||||||
|
struct IsResidualBlockList<utils::blocks::type_list<Blocks...>>
|
||||||
|
: std::bool_constant<
|
||||||
|
(std::derived_from<Blocks, utils::blocks::residual_block_base> &&
|
||||||
|
...) &&
|
||||||
|
utils::blocks::types_are_unique_v<
|
||||||
|
utils::blocks::type_list<Blocks...>>> {};
|
||||||
|
|
||||||
|
template <typename GeneratedValues> struct GeneratedValueBlocksFor;
|
||||||
|
|
||||||
|
template <typename... GeneratedValues>
|
||||||
|
struct GeneratedValueBlocksFor<models::ModelTypeList<GeneratedValues...>> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
utils::blocks::generated_value_block<GeneratedValues>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename GeneratedResiduals> struct GeneratedResidualBlocksFor;
|
||||||
|
|
||||||
|
template <typename... GeneratedResiduals>
|
||||||
|
struct GeneratedResidualBlocksFor<
|
||||||
|
models::ModelTypeList<GeneratedResiduals...>> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
utils::blocks::generated_residual_block<GeneratedResiduals>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One translation boundary turns physics-facing stellar names into backend
|
||||||
|
* blocks. Existing backend block types pass through unchanged, which keeps
|
||||||
|
* the advanced extension API open without making built-in physics declarations
|
||||||
|
* depend on utils.blocks.
|
||||||
|
*/
|
||||||
|
template <typename DeclaredDependency>
|
||||||
|
struct UnmappedStellarDependency final {};
|
||||||
|
|
||||||
|
template <typename Blocks, typename DeclaredDependency>
|
||||||
|
struct SingleGeneratedBlock {
|
||||||
|
using Type = UnmappedStellarDependency<DeclaredDependency>;
|
||||||
|
static constexpr bool available = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Block, typename DeclaredDependency>
|
||||||
|
struct SingleGeneratedBlock<utils::blocks::type_list<Block>,
|
||||||
|
DeclaredDependency> {
|
||||||
|
using Type = Block;
|
||||||
|
static constexpr bool available = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification, typename Dependency>
|
||||||
|
struct StellarDependencyBlock {
|
||||||
|
using Type = UnmappedStellarDependency<Dependency>;
|
||||||
|
static constexpr bool mapped = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification, typename Block>
|
||||||
|
requires(std::derived_from<Block, utils::blocks::value_block_base> ||
|
||||||
|
std::derived_from<Block, utils::blocks::residual_block_base>)
|
||||||
|
struct StellarDependencyBlock<Specification, Block> {
|
||||||
|
using Type = Block;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification, models::stellar::state::Density> {
|
||||||
|
using Type = utils::blocks::density::mass::value;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification,
|
||||||
|
models::stellar::state::SurfaceShape> {
|
||||||
|
using Type = utils::blocks::surface_deformation::parameters::value;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification,
|
||||||
|
models::stellar::state::GravityGradient> {
|
||||||
|
using Type = utils::blocks::gravity::gradient::value;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::state::GravitationalPotential> {
|
||||||
|
using Type = utils::blocks::gravity::poisson::value;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification,
|
||||||
|
models::stellar::state::SpecificEnthalpy> {
|
||||||
|
using Type = utils::blocks::enthalpy::specific::value;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::state::OwnGeneratedCoordinate> {
|
||||||
|
private:
|
||||||
|
using GeneratedBlocks = typename GeneratedValueBlocksFor<
|
||||||
|
typename models::SpecificationContribution<
|
||||||
|
Specification>::GeneratedValues>::Type;
|
||||||
|
using Selection = SingleGeneratedBlock<
|
||||||
|
GeneratedBlocks, models::stellar::state::OwnGeneratedCoordinate>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = typename Selection::Type;
|
||||||
|
static constexpr bool mapped = Selection::available;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification,
|
||||||
|
models::ModelSpecification Owner>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::state::GeneratedCoordinateOf<Owner>> {
|
||||||
|
private:
|
||||||
|
using GeneratedBlocks = typename GeneratedValueBlocksFor<
|
||||||
|
typename models::SpecificationContribution<Owner>::GeneratedValues>::Type;
|
||||||
|
using Dependency = models::stellar::state::GeneratedCoordinateOf<Owner>;
|
||||||
|
using Selection = SingleGeneratedBlock<GeneratedBlocks, Dependency>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = typename Selection::Type;
|
||||||
|
static constexpr bool mapped = Selection::available;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::equation::GravityGradientDefinition> {
|
||||||
|
using Type = utils::blocks::gravity::gradient::residual;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification,
|
||||||
|
models::stellar::equation::PoissonEquation> {
|
||||||
|
using Type = utils::blocks::gravity::poisson::residual;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification,
|
||||||
|
models::stellar::equation::DensityClosure> {
|
||||||
|
using Type = utils::blocks::density::mass::residual;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::equation::SurfaceShapeBalance> {
|
||||||
|
using Type =
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::equation::HydrostaticBalance> {
|
||||||
|
using Type = utils::blocks::enthalpy::specific::residual;
|
||||||
|
static constexpr bool mapped = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarDependencyBlock<Specification,
|
||||||
|
models::stellar::equation::OwnConstraint> {
|
||||||
|
private:
|
||||||
|
using GeneratedBlocks = typename GeneratedResidualBlocksFor<
|
||||||
|
typename models::SpecificationContribution<
|
||||||
|
Specification>::GeneratedResiduals>::Type;
|
||||||
|
using Selection = SingleGeneratedBlock<
|
||||||
|
GeneratedBlocks, models::stellar::equation::OwnConstraint>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = typename Selection::Type;
|
||||||
|
static constexpr bool mapped = Selection::available;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification,
|
||||||
|
models::ModelSpecification Owner>
|
||||||
|
struct StellarDependencyBlock<
|
||||||
|
Specification, models::stellar::equation::ConstraintOf<Owner>> {
|
||||||
|
private:
|
||||||
|
using GeneratedBlocks = typename GeneratedResidualBlocksFor<
|
||||||
|
typename models::SpecificationContribution<Owner>::GeneratedResiduals>::Type;
|
||||||
|
using Dependency = models::stellar::equation::ConstraintOf<Owner>;
|
||||||
|
using Selection = SingleGeneratedBlock<GeneratedBlocks, Dependency>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = typename Selection::Type;
|
||||||
|
static constexpr bool mapped = Selection::available;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification, typename Dependencies>
|
||||||
|
struct CompileStellarDependencies {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
UnmappedStellarDependency<Dependencies>>;
|
||||||
|
static constexpr bool complete = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification, typename... Dependencies>
|
||||||
|
struct CompileStellarDependencies<Specification,
|
||||||
|
models::ModelTypeList<Dependencies...>> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
typename StellarDependencyBlock<Specification, Dependencies>::Type...>;
|
||||||
|
static constexpr bool complete =
|
||||||
|
(StellarDependencyBlock<Specification, Dependencies>::mapped && ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Residual, typename Values> struct CoupleResidualToValues;
|
||||||
|
|
||||||
|
template <typename Residual, typename... Values>
|
||||||
|
struct CoupleResidualToValues<Residual, utils::blocks::type_list<Values...>> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
StellarEquilibriumJacobianCoupling<Residual, Values>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Residuals, typename Values>
|
||||||
|
struct CartesianJacobianCouplings;
|
||||||
|
|
||||||
|
template <typename... Residuals, typename Values>
|
||||||
|
struct CartesianJacobianCouplings<utils::blocks::type_list<Residuals...>,
|
||||||
|
Values> {
|
||||||
|
using Type = ConcatenateBlockListsT<
|
||||||
|
typename CoupleResidualToValues<Residuals, Values>::Type...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsJacobianCoupling : std::false_type {};
|
||||||
|
|
||||||
|
template <typename Residual, typename Value>
|
||||||
|
struct IsJacobianCoupling<StellarEquilibriumJacobianCoupling<Residual, Value>>
|
||||||
|
: std::bool_constant<
|
||||||
|
std::derived_from<Residual, utils::blocks::residual_block_base> &&
|
||||||
|
std::derived_from<Value, utils::blocks::value_block_base>> {};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct IsJacobianCouplingList : std::false_type {};
|
||||||
|
|
||||||
|
template <typename... Couplings>
|
||||||
|
struct IsJacobianCouplingList<utils::blocks::type_list<Couplings...>>
|
||||||
|
: std::bool_constant<(IsJacobianCoupling<Couplings>::value && ...) &&
|
||||||
|
utils::blocks::types_are_unique_v<
|
||||||
|
utils::blocks::type_list<Couplings...>>> {};
|
||||||
|
|
||||||
|
template <typename Candidate> struct IsGeneratedValueBlock : std::false_type {};
|
||||||
|
|
||||||
|
template <typename Owner>
|
||||||
|
struct IsGeneratedValueBlock<utils::blocks::generated_value_block<Owner>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct IsGeneratedResidualBlock : std::false_type {};
|
||||||
|
|
||||||
|
template <typename Owner>
|
||||||
|
struct IsGeneratedResidualBlock<utils::blocks::generated_residual_block<Owner>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename Coupling>
|
||||||
|
inline constexpr bool isGeneratedBorderIncidentCoupling =
|
||||||
|
IsGeneratedValueBlock<typename Coupling::Value>::value ||
|
||||||
|
IsGeneratedResidualBlock<typename Coupling::Residual>::value;
|
||||||
|
|
||||||
|
template <typename Couplings> struct GeneratedBorderIncidentCouplings;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct GeneratedBorderIncidentCouplings<utils::blocks::type_list<>> {
|
||||||
|
using Type = utils::blocks::type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Head, typename... Tail>
|
||||||
|
struct GeneratedBorderIncidentCouplings<
|
||||||
|
utils::blocks::type_list<Head, Tail...>> {
|
||||||
|
private:
|
||||||
|
using Remaining = typename GeneratedBorderIncidentCouplings<
|
||||||
|
utils::blocks::type_list<Tail...>>::Type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = std::conditional_t<
|
||||||
|
isGeneratedBorderIncidentCoupling<Head>,
|
||||||
|
ConcatenateBlockListsT<utils::blocks::type_list<Head>, Remaining>,
|
||||||
|
Remaining>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <bool Registered, typename GeneratedValues,
|
||||||
|
typename GeneratedResiduals, typename DependsOn, typename Affects>
|
||||||
|
struct DeclarativeStellarEquilibriumSpecificationCompilation {
|
||||||
|
using GeneratedValueBlocks = GeneratedValues;
|
||||||
|
using GeneratedResidualBlocks = GeneratedResiduals;
|
||||||
|
using DependsOnValueBlocks = DependsOn;
|
||||||
|
using AffectedResidualBlocks = Affects;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Preserve the two physical meanings in the declaration instead of
|
||||||
|
* flattening their endpoints into independent unions:
|
||||||
|
*
|
||||||
|
* constraint equation <- everything named in Reads
|
||||||
|
* changed equations <- Reads plus the generated coordinate
|
||||||
|
*
|
||||||
|
* The second group deliberately includes Affects x Reads. Nonlinear
|
||||||
|
* constraints and multiplier forces generally contribute Hessian-like
|
||||||
|
* state derivatives there. Linear contributions simply assemble zero on
|
||||||
|
* those structurally permitted edges.
|
||||||
|
*/
|
||||||
|
using ConstraintInputValueBlocks = DependsOnValueBlocks;
|
||||||
|
using ConstraintOutputResidualBlocks = GeneratedResidualBlocks;
|
||||||
|
using ChangedEquationInputValueBlocks = UniqueConcatenateBlockListsT<
|
||||||
|
DependsOnValueBlocks, GeneratedValueBlocks>;
|
||||||
|
using ChangedEquationOutputResidualBlocks = AffectedResidualBlocks;
|
||||||
|
|
||||||
|
using ConstraintJacobianCouplings =
|
||||||
|
typename CartesianJacobianCouplings<GeneratedResidualBlocks,
|
||||||
|
DependsOnValueBlocks>::Type;
|
||||||
|
using ChangedEquationJacobianCouplings =
|
||||||
|
typename CartesianJacobianCouplings<AffectedResidualBlocks,
|
||||||
|
ChangedEquationInputValueBlocks>::Type;
|
||||||
|
|
||||||
|
// Compatibility names retained for backend code that distinguishes the
|
||||||
|
// generated row from the generated-coordinate column.
|
||||||
|
using GeneratedRowJacobianCouplings = ConstraintJacobianCouplings;
|
||||||
|
using AffectedRowJacobianCouplings =
|
||||||
|
typename CartesianJacobianCouplings<AffectedResidualBlocks,
|
||||||
|
GeneratedValueBlocks>::Type;
|
||||||
|
using AffectedStateJacobianCouplings =
|
||||||
|
typename CartesianJacobianCouplings<AffectedResidualBlocks,
|
||||||
|
DependsOnValueBlocks>::Type;
|
||||||
|
|
||||||
|
using JacobianCouplings = UniqueConcatenateBlockListsT<
|
||||||
|
ConstraintJacobianCouplings, ChangedEquationJacobianCouplings>;
|
||||||
|
using IncidentJacobianCouplings =
|
||||||
|
typename GeneratedBorderIncidentCouplings<JacobianCouplings>::Type;
|
||||||
|
|
||||||
|
// Correction is the Newton-facing name for a value coordinate.
|
||||||
|
using GeneratedCorrectionBlocks = GeneratedValueBlocks;
|
||||||
|
|
||||||
|
static constexpr bool registered = Registered;
|
||||||
|
static constexpr bool complete =
|
||||||
|
registered && IsValueBlockList<GeneratedValueBlocks>::value &&
|
||||||
|
IsResidualBlockList<GeneratedResidualBlocks>::value &&
|
||||||
|
IsValueBlockList<DependsOnValueBlocks>::value &&
|
||||||
|
IsResidualBlockList<AffectedResidualBlocks>::value &&
|
||||||
|
IsJacobianCouplingList<JacobianCouplings>::value &&
|
||||||
|
(GeneratedValueBlocks::size == GeneratedResidualBlocks::size) &&
|
||||||
|
((GeneratedValueBlocks::size == 0 && DependsOnValueBlocks::size == 0 &&
|
||||||
|
AffectedResidualBlocks::size == 0) ||
|
||||||
|
(GeneratedValueBlocks::size > 0 && DependsOnValueBlocks::size > 0 &&
|
||||||
|
AffectedResidualBlocks::size > 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
using EmptySpecificationCompilation =
|
||||||
|
DeclarativeStellarEquilibriumSpecificationCompilation<
|
||||||
|
false, utils::blocks::type_list<>, utils::blocks::type_list<>,
|
||||||
|
utils::blocks::type_list<>, utils::blocks::type_list<>>;
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct SelfDescribingSpecificationCompilationInputs {
|
||||||
|
using Contribution = models::SpecificationContribution<Specification>;
|
||||||
|
using DependsOn =
|
||||||
|
CompileStellarDependencies<Specification, typename Contribution::DependsOn>;
|
||||||
|
using Affects =
|
||||||
|
CompileStellarDependencies<Specification, typename Contribution::Affects>;
|
||||||
|
|
||||||
|
using GeneratedValueBlocks = typename GeneratedValueBlocksFor<
|
||||||
|
typename Contribution::GeneratedValues>::Type;
|
||||||
|
using GeneratedResidualBlocks = typename GeneratedResidualBlocksFor<
|
||||||
|
typename Contribution::GeneratedResiduals>::Type;
|
||||||
|
using DependsOnValueBlocks = typename DependsOn::Type;
|
||||||
|
using AffectedResidualBlocks = typename Affects::Type;
|
||||||
|
|
||||||
|
static constexpr bool registered =
|
||||||
|
Contribution::hasDeclarativeDefinition && DependsOn::complete &&
|
||||||
|
Affects::complete;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct SelfDescribingSpecificationCompilation
|
||||||
|
: DeclarativeStellarEquilibriumSpecificationCompilation<
|
||||||
|
SelfDescribingSpecificationCompilationInputs<Specification>::registered,
|
||||||
|
typename SelfDescribingSpecificationCompilationInputs<
|
||||||
|
Specification>::GeneratedValueBlocks,
|
||||||
|
typename SelfDescribingSpecificationCompilationInputs<
|
||||||
|
Specification>::GeneratedResidualBlocks,
|
||||||
|
typename SelfDescribingSpecificationCompilationInputs<
|
||||||
|
Specification>::DependsOnValueBlocks,
|
||||||
|
typename SelfDescribingSpecificationCompilationInputs<
|
||||||
|
Specification>::AffectedResidualBlocks> {};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public, inspectable per-specification compilation metadata. The primary
|
||||||
|
* is deliberately well formed and incomplete, so testing an arbitrary type
|
||||||
|
* in a requires-expression never triggers a diagnostic.
|
||||||
|
*/
|
||||||
|
template <typename Specification>
|
||||||
|
struct StellarEquilibriumSpecificationCompilation
|
||||||
|
: detail::EmptySpecificationCompilation {};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct StellarEquilibriumSpecificationCompilation<Specification>
|
||||||
|
: detail::SelfDescribingSpecificationCompilation<Specification> {};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Specification, typename = void>
|
||||||
|
struct SpecificationCompilationIsComplete : std::false_type {};
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
struct SpecificationCompilationIsComplete<
|
||||||
|
Specification,
|
||||||
|
std::void_t<typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::GeneratedValueBlocks,
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::GeneratedResidualBlocks,
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::DependsOnValueBlocks,
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::AffectedResidualBlocks,
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::JacobianCouplings,
|
||||||
|
std::bool_constant<StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::registered>,
|
||||||
|
std::bool_constant<StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::complete>>>
|
||||||
|
: std::bool_constant<
|
||||||
|
StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::registered &&
|
||||||
|
StellarEquilibriumSpecificationCompilation<Specification>::complete &&
|
||||||
|
IsValueBlockList<typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::GeneratedValueBlocks>::value &&
|
||||||
|
IsResidualBlockList<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::GeneratedResidualBlocks>::value &&
|
||||||
|
IsValueBlockList<typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::DependsOnValueBlocks>::value &&
|
||||||
|
IsResidualBlockList<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::AffectedResidualBlocks>::value &&
|
||||||
|
IsJacobianCouplingList<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::JacobianCouplings>::value> {};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
inline constexpr bool stellarEquilibriumSpecificationCompilationComplete =
|
||||||
|
detail::SpecificationCompilationIsComplete<
|
||||||
|
std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StellarEquilibriumSpecificationCompilable =
|
||||||
|
stellarEquilibriumSpecificationCompilationComplete<Candidate>;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
/*
|
||||||
|
* This five-by-five physical core is independent of global constraints.
|
||||||
|
* Even FixedTotalMass is compiled as a contribution, keeping C and R_M
|
||||||
|
* visible in that specification's metadata.
|
||||||
|
*/
|
||||||
|
using StellarPhysicsValueBlocks = utils::blocks::type_list<
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value,
|
||||||
|
utils::blocks::gravity::gradient::value,
|
||||||
|
utils::blocks::gravity::poisson::value,
|
||||||
|
utils::blocks::enthalpy::specific::value>;
|
||||||
|
|
||||||
|
using StellarPhysicsResidualBlocks = utils::blocks::type_list<
|
||||||
|
utils::blocks::gravity::gradient::residual,
|
||||||
|
utils::blocks::gravity::poisson::residual,
|
||||||
|
utils::blocks::density::mass::residual,
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||||
|
utils::blocks::enthalpy::specific::residual>;
|
||||||
|
|
||||||
|
using StellarPhysicsJacobianRows = utils::blocks::type_list<
|
||||||
|
utils::blocks::block_row<
|
||||||
|
utils::blocks::gravity::gradient::residual,
|
||||||
|
utils::blocks::gravity::gradient::value,
|
||||||
|
utils::blocks::gravity::poisson::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>,
|
||||||
|
utils::blocks::block_row<
|
||||||
|
utils::blocks::gravity::poisson::residual,
|
||||||
|
utils::blocks::gravity::gradient::value,
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>,
|
||||||
|
utils::blocks::block_row<
|
||||||
|
utils::blocks::density::mass::residual,
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
utils::blocks::enthalpy::specific::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>,
|
||||||
|
utils::blocks::block_row<
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value,
|
||||||
|
utils::blocks::gravity::gradient::value,
|
||||||
|
utils::blocks::enthalpy::specific::value>,
|
||||||
|
utils::blocks::block_row<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
utils::blocks::enthalpy::specific::value,
|
||||||
|
utils::blocks::gravity::poisson::value,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>>;
|
||||||
|
|
||||||
|
template <typename Row> struct JacobianRowCouplings;
|
||||||
|
|
||||||
|
template <typename Residual, typename... Values>
|
||||||
|
struct JacobianRowCouplings<utils::blocks::block_row<Residual, Values...>> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
StellarEquilibriumJacobianCoupling<Residual, Values>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Rows> struct FlattenJacobianRows;
|
||||||
|
|
||||||
|
template <typename... Rows>
|
||||||
|
struct FlattenJacobianRows<utils::blocks::type_list<Rows...>> {
|
||||||
|
using Type =
|
||||||
|
ConcatenateBlockListsT<typename JacobianRowCouplings<Rows>::Type...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
using StellarPhysicsJacobianCouplings =
|
||||||
|
typename FlattenJacobianRows<StellarPhysicsJacobianRows>::Type;
|
||||||
|
|
||||||
|
template <typename SpecificationSet>
|
||||||
|
struct SpecificationSetCompilationsAreComplete;
|
||||||
|
|
||||||
|
template <models::ModelSpecification... Specifications>
|
||||||
|
struct SpecificationSetCompilationsAreComplete<
|
||||||
|
models::detail::SpecificationSetStorage<Specifications...>>
|
||||||
|
: std::bool_constant<(
|
||||||
|
stellarEquilibriumSpecificationCompilationComplete<Specifications> &&
|
||||||
|
...)> {};
|
||||||
|
|
||||||
|
template <typename SpecificationSet, bool Complete>
|
||||||
|
struct CollectStellarEquilibriumContributionsImpl {
|
||||||
|
using GeneratedValueBlocks = utils::blocks::type_list<>;
|
||||||
|
using GeneratedResidualBlocks = utils::blocks::type_list<>;
|
||||||
|
using ContributionJacobianCouplings = utils::blocks::type_list<>;
|
||||||
|
using IncidentJacobianCouplings = ContributionJacobianCouplings;
|
||||||
|
|
||||||
|
static constexpr bool complete = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification... Specifications>
|
||||||
|
struct CollectStellarEquilibriumContributionsImpl<
|
||||||
|
models::detail::SpecificationSetStorage<Specifications...>, true> {
|
||||||
|
using GeneratedValueBlocks = ConcatenateBlockListsT<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specifications>::GeneratedValueBlocks...>;
|
||||||
|
using GeneratedResidualBlocks = ConcatenateBlockListsT<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specifications>::GeneratedResidualBlocks...>;
|
||||||
|
using ContributionJacobianCouplings = UniqueConcatenateBlockListsT<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specifications>::JacobianCouplings...>;
|
||||||
|
using IncidentJacobianCouplings = UniqueConcatenateBlockListsT<
|
||||||
|
typename StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specifications>::IncidentJacobianCouplings...>;
|
||||||
|
|
||||||
|
static constexpr bool complete = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename SpecificationSet>
|
||||||
|
using CollectStellarEquilibriumContributions =
|
||||||
|
CollectStellarEquilibriumContributionsImpl<
|
||||||
|
SpecificationSet,
|
||||||
|
SpecificationSetCompilationsAreComplete<SpecificationSet>::value>;
|
||||||
|
|
||||||
|
template <typename Residual, typename Couplings> struct ValuesCoupledToResidual;
|
||||||
|
|
||||||
|
template <typename Residual>
|
||||||
|
struct ValuesCoupledToResidual<Residual, utils::blocks::type_list<>> {
|
||||||
|
using Type = utils::blocks::type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Residual, typename HeadResidual, typename HeadValue,
|
||||||
|
typename... Tail>
|
||||||
|
struct ValuesCoupledToResidual<
|
||||||
|
Residual,
|
||||||
|
utils::blocks::type_list<
|
||||||
|
StellarEquilibriumJacobianCoupling<HeadResidual, HeadValue>, Tail...>> {
|
||||||
|
private:
|
||||||
|
using Remaining =
|
||||||
|
typename ValuesCoupledToResidual<Residual,
|
||||||
|
utils::blocks::type_list<Tail...>>::Type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = std::conditional_t<
|
||||||
|
std::same_as<Residual, HeadResidual>,
|
||||||
|
ConcatenateBlockListsT<utils::blocks::type_list<HeadValue>, Remaining>,
|
||||||
|
Remaining>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Residual, typename Values> struct MakeJacobianRow;
|
||||||
|
|
||||||
|
template <typename Residual, typename... Values>
|
||||||
|
struct MakeJacobianRow<Residual, utils::blocks::type_list<Values...>> {
|
||||||
|
using Type = utils::blocks::block_row<Residual, Values...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Residuals, typename Couplings> struct SynthesizeJacobianRows;
|
||||||
|
|
||||||
|
template <typename... Residuals, typename Couplings>
|
||||||
|
struct SynthesizeJacobianRows<utils::blocks::type_list<Residuals...>,
|
||||||
|
Couplings> {
|
||||||
|
using Type = utils::blocks::type_list<typename MakeJacobianRow<
|
||||||
|
Residuals,
|
||||||
|
typename ValuesCoupledToResidual<Residuals, Couplings>::Type>::Type...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Couplings, typename ValueBlocks, typename ResidualBlocks>
|
||||||
|
struct CouplingEndpointsBelongToForm : std::false_type {};
|
||||||
|
|
||||||
|
template <typename ValueBlocks, typename ResidualBlocks, typename... Couplings>
|
||||||
|
struct CouplingEndpointsBelongToForm<utils::blocks::type_list<Couplings...>,
|
||||||
|
ValueBlocks, ResidualBlocks>
|
||||||
|
: std::bool_constant<((utils::blocks::contains_type_v<
|
||||||
|
typename Couplings::Value, ValueBlocks> &&
|
||||||
|
utils::blocks::contains_type_v<
|
||||||
|
typename Couplings::Residual, ResidualBlocks>) &&
|
||||||
|
...)> {};
|
||||||
|
|
||||||
|
template <typename Candidate> struct CompileStellarEquilibriumSystem {
|
||||||
|
using GeneratedValueBlocks = utils::blocks::type_list<>;
|
||||||
|
using GeneratedCorrectionBlocks = GeneratedValueBlocks;
|
||||||
|
using GeneratedResidualBlocks = utils::blocks::type_list<>;
|
||||||
|
using BaseJacobianCouplings = utils::blocks::type_list<>;
|
||||||
|
using ContributionJacobianCouplings = utils::blocks::type_list<>;
|
||||||
|
using IncidentJacobianCouplings = ContributionJacobianCouplings;
|
||||||
|
using JacobianCouplings = utils::blocks::type_list<>;
|
||||||
|
|
||||||
|
static constexpr bool compilable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
struct CompileStellarEquilibriumSystem<Model> {
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using Contributions = CollectStellarEquilibriumContributions<
|
||||||
|
typename ModelType::SpecificationTypes>;
|
||||||
|
|
||||||
|
using GeneratedValueBlocks = typename Contributions::GeneratedValueBlocks;
|
||||||
|
using GeneratedCorrectionBlocks = GeneratedValueBlocks;
|
||||||
|
using GeneratedResidualBlocks =
|
||||||
|
typename Contributions::GeneratedResidualBlocks;
|
||||||
|
|
||||||
|
using ValueBlocks =
|
||||||
|
ConcatenateBlockListsT<StellarPhysicsValueBlocks, GeneratedValueBlocks>;
|
||||||
|
using ResidualBlocks = ConcatenateBlockListsT<StellarPhysicsResidualBlocks,
|
||||||
|
GeneratedResidualBlocks>;
|
||||||
|
using FormType = utils::blocks::block_form<ValueBlocks, ResidualBlocks>;
|
||||||
|
|
||||||
|
using BaseJacobianCouplings = StellarPhysicsJacobianCouplings;
|
||||||
|
using ContributionJacobianCouplings =
|
||||||
|
typename Contributions::ContributionJacobianCouplings;
|
||||||
|
using IncidentJacobianCouplings = ContributionJacobianCouplings;
|
||||||
|
using JacobianCouplings =
|
||||||
|
UniqueConcatenateBlockListsT<BaseJacobianCouplings,
|
||||||
|
ContributionJacobianCouplings>;
|
||||||
|
|
||||||
|
// Pass two: materialize rows only after all contributed blocks are
|
||||||
|
// present in the final form.
|
||||||
|
using JacobianType =
|
||||||
|
typename SynthesizeJacobianRows<ResidualBlocks, JacobianCouplings>::Type;
|
||||||
|
|
||||||
|
static constexpr bool compilable =
|
||||||
|
Contributions::complete &&
|
||||||
|
utils::blocks::block_form_is_valid_v<FormType> &&
|
||||||
|
IsJacobianCouplingList<JacobianCouplings>::value &&
|
||||||
|
CouplingEndpointsBelongToForm<JacobianCouplings, ValueBlocks,
|
||||||
|
ResidualBlocks>::value &&
|
||||||
|
utils::blocks::jacobian_form_is_valid_v<FormType, JacobianType>;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
inline constexpr bool stellarEquilibriumSystemIsCompilable =
|
||||||
|
detail::CompileStellarEquilibriumSystem<
|
||||||
|
std::remove_cvref_t<Candidate>>::compilable;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This compiler proves the symbolic block topology only. Keep the explicit
|
||||||
|
* name available to extension authors and tests so that success here is not
|
||||||
|
* mistaken for an assembled numerical runtime. The established spelling is
|
||||||
|
* retained below as a compatibility alias.
|
||||||
|
*/
|
||||||
|
template <typename Candidate>
|
||||||
|
inline constexpr bool stellarEquilibriumIsSymbolicallyCompilable =
|
||||||
|
stellarEquilibriumSystemIsCompilable<Candidate>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StellarEquilibriumSymbolicallyCompilable =
|
||||||
|
stellarEquilibriumIsSymbolicallyCompilable<Candidate>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StellarEquilibriumSystemCompilable =
|
||||||
|
StellarEquilibriumSymbolicallyCompilable<Candidate>;
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
requires StellarEquilibriumSystemCompilable<Model>
|
||||||
|
struct CompiledStellarEquilibriumSystem final
|
||||||
|
: detail::CompileStellarEquilibriumSystem<std::remove_cvref_t<Model>> {
|
||||||
|
using Base =
|
||||||
|
detail::CompileStellarEquilibriumSystem<std::remove_cvref_t<Model>>;
|
||||||
|
|
||||||
|
using FormType = typename Base::FormType;
|
||||||
|
using JacobianType = typename Base::JacobianType;
|
||||||
|
|
||||||
|
// This classification is exposed only after the complete compiler concept
|
||||||
|
// has succeeded; model declarations intentionally do not predict it.
|
||||||
|
static constexpr models::EquilibriumSystemCompilation compilationClass =
|
||||||
|
models::EquilibriumSystemCompilation::complete_equilibrium_system;
|
||||||
|
|
||||||
|
static_assert(utils::blocks::block_form_is_valid_v<FormType>);
|
||||||
|
static_assert(utils::blocks::valid_jacobian_form<FormType, JacobianType>);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
requires StellarEquilibriumSystemCompilable<Model>
|
||||||
|
using CompiledStellarEquilibriumForm =
|
||||||
|
typename CompiledStellarEquilibriumSystem<Model>::FormType;
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
requires StellarEquilibriumSystemCompilable<Model>
|
||||||
|
using CompiledStellarEquilibriumJacobianForm =
|
||||||
|
typename CompiledStellarEquilibriumSystem<Model>::JacobianType;
|
||||||
|
} // namespace mean_field::operators
|
||||||
@@ -2,6 +2,8 @@ module;
|
|||||||
|
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -13,46 +15,126 @@ export import :deformation.domain_deformation;
|
|||||||
export import :equilibrium.stellar_discretization;
|
export import :equilibrium.stellar_discretization;
|
||||||
export import :material.thermodynamic_equations;
|
export import :material.thermodynamic_equations;
|
||||||
export import :model.typed_stellar;
|
export import :model.typed_stellar;
|
||||||
export import :operators.prepared_central_density_stellar_equilibrium;
|
export import :normalization.operators;
|
||||||
|
export import :operators.prepared_variadic_stellar_equilibrium;
|
||||||
export import :surface.compiler;
|
export import :surface.compiler;
|
||||||
|
|
||||||
export namespace mean_field::equilibrium {
|
export namespace mean_field::equilibrium {
|
||||||
|
namespace detail {
|
||||||
|
template <
|
||||||
|
model::StellarModelType Model,
|
||||||
|
bool SymbolicallyCompilable = operators::StellarEquilibriumSystemCompilable<Model>>
|
||||||
|
struct StellarSurfaceCompilationAudit {
|
||||||
|
static constexpr bool complete = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
struct StellarSurfaceCompilationAudit<Model, true> {
|
||||||
|
private:
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using EquationOfState = typename ModelType::EquationOfStateType;
|
||||||
|
using Form = operators::CompiledStellarEquilibriumForm<ModelType>;
|
||||||
|
using AvailableEquations = material::StellarEquilibriumThermodynamicEquations;
|
||||||
|
|
||||||
|
static constexpr bool thermodynamicsCompilable =
|
||||||
|
material::ThermodynamicEquationsCompilable<EquationOfState, Form, AvailableEquations>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool complete = [] {
|
||||||
|
if constexpr (!thermodynamicsCompilable) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
using ThermodynamicEquations =
|
||||||
|
material::CompiledThermodynamicEquationsT<EquationOfState, Form, AvailableEquations>;
|
||||||
|
using Formulation = typename ThermodynamicEquations::PressureSurfaceFormulation;
|
||||||
|
using CompiledSurface =
|
||||||
|
surface::CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>;
|
||||||
|
return requires(const ModelType &model) {
|
||||||
|
{
|
||||||
|
surface::compilePressureSurfaceConstraint<Formulation>(
|
||||||
|
model.surfaceCondition(),
|
||||||
|
model.equationOfState()
|
||||||
|
)
|
||||||
|
} -> std::same_as<CompiledSurface>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
inline constexpr bool hasStellarEquilibriumSurfaceCompilation =
|
||||||
|
detail::StellarSurfaceCompilationAudit<std::remove_cvref_t<Model>>::complete;
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept StellarEquilibriumModel = model::StellarModelType<Candidate> && requires {
|
concept StellarEquilibriumModel = model::StellarModelType<Candidate> && requires {
|
||||||
requires std::remove_cvref_t<Candidate>::template containsSpecification<eos::Polytrope>;
|
typename std::remove_cvref_t<Candidate>::EquationOfStateType;
|
||||||
requires std::remove_cvref_t<Candidate>::template containsSpecification<surface::Isobaric>;
|
requires(
|
||||||
|
std::remove_cvref_t<Candidate>::template specificationRoleCount<
|
||||||
|
models::SpecificationRole::boundary_condition> == 1
|
||||||
|
);
|
||||||
requires std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedTotalMass>;
|
requires std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedTotalMass>;
|
||||||
requires std::remove_cvref_t<Candidate>::specificationCount ==
|
requires operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Candidate>>;
|
||||||
3 + static_cast<std::size_t>(
|
requires hasStellarEquilibriumSurfaceCompilation<std::remove_cvref_t<Candidate>>;
|
||||||
std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedCentralDensity>
|
requires operators::CompilableRootManifestFor<
|
||||||
);
|
std::remove_cvref_t<Candidate>,
|
||||||
|
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Candidate>>>;
|
||||||
|
requires operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Candidate>>;
|
||||||
|
requires operators::hasCompleteStellarEquilibriumRuntime<std::remove_cvref_t<Candidate>>;
|
||||||
|
requires operators::stellarEquilibriumRotationProviderCount<std::remove_cvref_t<Candidate>> <= 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <StellarEquilibriumModel Model> class StellarEquilibriumProblem final {
|
namespace detail {
|
||||||
|
template <typename Model, typename Discretization, typename = void>
|
||||||
|
struct StellarEquilibriumModelDiscretizationStructureAudit : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Model, typename Discretization>
|
||||||
|
requires StellarEquilibriumModel<std::remove_cvref_t<Model>> &&
|
||||||
|
StellarDiscretizationType<std::remove_cvref_t<Discretization>>
|
||||||
|
struct StellarEquilibriumModelDiscretizationStructureAudit<
|
||||||
|
Model,
|
||||||
|
Discretization,
|
||||||
|
std::void_t<
|
||||||
|
typename std::remove_cvref_t<Discretization>::NormalizationPrescriptionType,
|
||||||
|
typename std::remove_cvref_t<Model>::SpecificationTypes,
|
||||||
|
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Model>>,
|
||||||
|
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>>>
|
||||||
|
: std::bool_constant<normalization::StellarNormalizationRuntimeAvailableFor<
|
||||||
|
typename std::remove_cvref_t<Discretization>::NormalizationPrescriptionType,
|
||||||
|
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Model>>,
|
||||||
|
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>,
|
||||||
|
typename std::remove_cvref_t<Model>::SpecificationTypes>> { };
|
||||||
|
|
||||||
|
struct StellarEquilibriumProblemFactory;
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <
|
||||||
|
StellarEquilibriumModel Model,
|
||||||
|
StellarDiscretizationType Discretization = StellarDiscretization>
|
||||||
|
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
std::remove_cvref_t<Discretization>>::value
|
||||||
|
class StellarEquilibriumProblem final {
|
||||||
public:
|
public:
|
||||||
using ModelType = std::remove_cvref_t<Model>;
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
|
using NormalizationPrescriptionType = typename DiscretizationType::NormalizationPrescriptionType;
|
||||||
|
|
||||||
static constexpr bool hasFixedCentralDensity =
|
static constexpr bool hasFixedCentralDensity =
|
||||||
ModelType::template containsSpecification<models::FixedCentralDensity>;
|
ModelType::template containsSpecification<models::FixedCentralDensity>;
|
||||||
|
static constexpr bool hasFixedAngularMomentum =
|
||||||
|
ModelType::template containsSpecification<models::FixedAngularMomentum>;
|
||||||
|
static constexpr std::size_t generatedRotationProviderCount =
|
||||||
|
operators::stellarEquilibriumRotationProviderCount<ModelType>;
|
||||||
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
|
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
|
||||||
|
|
||||||
using PreparedOperatorType = std::conditional_t<
|
using PreparedOperatorType = operators::PreparedVariadicStellarEquilibriumOperator<ModelType>;
|
||||||
hasFixedCentralDensity,
|
using PhysicalCoreType = typename PreparedOperatorType::PhysicalCoreType;
|
||||||
operators::PreparedCentralDensityStellarEquilibriumOperator,
|
using FormType = operators::CompiledStellarEquilibriumForm<ModelType>;
|
||||||
operators::PreparedStellarEquilibriumOperator>;
|
using JacobianFormType = operators::CompiledStellarEquilibriumJacobianForm<ModelType>;
|
||||||
using FormType = std::conditional_t<
|
using ManifestType = operators::EquilibriumSystemManifest<ModelType, FormType, JacobianFormType>;
|
||||||
hasFixedCentralDensity,
|
using EquationOfStateType = model::EquationOfStateType<ModelType>;
|
||||||
operators::CentralDensityStellarEquilibriumForm,
|
using SurfaceConditionType = model::SurfaceConditionType<ModelType>;
|
||||||
utils::blocks::surface_deformed_stellar_equilibrium_form>;
|
|
||||||
using JacobianFormType = std::conditional_t<
|
|
||||||
hasFixedCentralDensity,
|
|
||||||
operators::CentralDensityStellarEquilibriumJacobianForm,
|
|
||||||
utils::blocks::surface_deformed_stellar_equilibrium_jacobian_form>;
|
|
||||||
using ManifestType = std::conditional_t<
|
|
||||||
hasFixedCentralDensity,
|
|
||||||
operators::CentralDensityStellarEquilibriumSystemManifest,
|
|
||||||
operators::StellarEquilibriumSystemManifest>;
|
|
||||||
using EquationOfStateType = eos::Polytrope;
|
|
||||||
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
|
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
|
||||||
using ThermodynamicEquationsType =
|
using ThermodynamicEquationsType =
|
||||||
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
|
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
|
||||||
@@ -60,44 +142,22 @@ export namespace mean_field::equilibrium {
|
|||||||
typename ThermodynamicEquationsType::PressureSurfaceFormulation,
|
typename ThermodynamicEquationsType::PressureSurfaceFormulation,
|
||||||
EquationOfStateType>;
|
EquationOfStateType>;
|
||||||
|
|
||||||
StellarEquilibriumProblem(
|
private:
|
||||||
ModelType stellarModel,
|
friend struct detail::StellarEquilibriumProblemFactory;
|
||||||
const StellarDiscretization discretization
|
|
||||||
)
|
|
||||||
requires(!hasFixedCentralDensity)
|
|
||||||
: m_stellarModel(std::move(stellarModel)),
|
|
||||||
m_discretization(discretization),
|
|
||||||
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
|
|
||||||
m_preparedOperator(
|
|
||||||
m_discretization.finiteElementModel(),
|
|
||||||
m_discretization.domainMapper(),
|
|
||||||
m_stellarModel.template specification<eos::Polytrope>(),
|
|
||||||
models::compileConstraint(m_stellarModel.template specification<models::FixedTotalMass>()),
|
|
||||||
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
|
|
||||||
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
|
|
||||||
) {
|
|
||||||
VerifyProblem();
|
|
||||||
}
|
|
||||||
|
|
||||||
StellarEquilibriumProblem(
|
StellarEquilibriumProblem(
|
||||||
ModelType stellarModel,
|
ModelType stellarModel,
|
||||||
const StellarDiscretization discretization
|
DiscretizationType discretization
|
||||||
)
|
)
|
||||||
requires hasFixedCentralDensity
|
: m_stellarModel(std::make_shared<ModelType>(std::move(stellarModel))),
|
||||||
: m_stellarModel(std::move(stellarModel)),
|
m_discretization(std::move(discretization)),
|
||||||
m_discretization(discretization),
|
m_compiledSurfaceConstraint(CompileSurfaceConstraint(*m_stellarModel)),
|
||||||
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
|
|
||||||
m_preparedOperator(
|
m_preparedOperator(
|
||||||
m_discretization.finiteElementModel(),
|
m_discretization.finiteElementModel(),
|
||||||
m_discretization.domainMapper(),
|
m_discretization.domainMapper(),
|
||||||
m_stellarModel.template specification<eos::Polytrope>(),
|
m_stellarModel,
|
||||||
models::compileConstraint(m_stellarModel.template specification<models::FixedTotalMass>()),
|
|
||||||
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
|
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
|
||||||
CompileDefaultDomainDeformation(m_discretization.finiteElementModel()),
|
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
|
||||||
models::compileConstraint(
|
|
||||||
m_stellarModel.template specification<models::FixedCentralDensity>(),
|
|
||||||
m_stellarModel.template specification<eos::Polytrope>()
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
VerifyProblem();
|
VerifyProblem();
|
||||||
}
|
}
|
||||||
@@ -107,14 +167,19 @@ export namespace mean_field::equilibrium {
|
|||||||
StellarEquilibriumProblem(StellarEquilibriumProblem &&) = delete;
|
StellarEquilibriumProblem(StellarEquilibriumProblem &&) = delete;
|
||||||
StellarEquilibriumProblem &operator=(StellarEquilibriumProblem &&) = delete;
|
StellarEquilibriumProblem &operator=(StellarEquilibriumProblem &&) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
[[nodiscard]] const ModelType &GetStellarModel() const noexcept {
|
[[nodiscard]] const ModelType &GetStellarModel() const noexcept {
|
||||||
return m_stellarModel;
|
return *m_stellarModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const StellarDiscretization &GetDiscretization() const noexcept {
|
[[nodiscard]] const DiscretizationType &GetDiscretization() const noexcept {
|
||||||
return m_discretization;
|
return m_discretization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const NormalizationPrescriptionType &GetNormalizationPrescription() const noexcept {
|
||||||
|
return m_discretization.normalizationPrescription();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const CompiledSurfaceConstraintType &GetCompiledSurfaceConstraint() const noexcept {
|
[[nodiscard]] const CompiledSurfaceConstraintType &GetCompiledSurfaceConstraint() const noexcept {
|
||||||
return m_compiledSurfaceConstraint;
|
return m_compiledSurfaceConstraint;
|
||||||
}
|
}
|
||||||
@@ -127,6 +192,10 @@ export namespace mean_field::equilibrium {
|
|||||||
return m_preparedOperator;
|
return m_preparedOperator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const PhysicalCoreType &GetPhysicalOperator() const noexcept {
|
||||||
|
return m_preparedOperator.GetPhysicalOperator();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const auto &GetManifest() const noexcept {
|
[[nodiscard]] const auto &GetManifest() const noexcept {
|
||||||
return m_preparedOperator.GetRootManifest();
|
return m_preparedOperator.GetRootManifest();
|
||||||
}
|
}
|
||||||
@@ -135,28 +204,20 @@ export namespace mean_field::equilibrium {
|
|||||||
return m_preparedOperator.IsPrepared();
|
return m_preparedOperator.IsPrepared();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint64_t GetPreparationGeneration() const noexcept {
|
||||||
|
return m_preparationGeneration;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const operators::StellarEquilibriumDependencies &GetLinearizationDependencies() const {
|
[[nodiscard]] const operators::StellarEquilibriumDependencies &GetLinearizationDependencies() const {
|
||||||
if constexpr (hasFixedCentralDensity) {
|
return GetPhysicalOperator().GetDependencies();
|
||||||
return m_preparedOperator.GetPhysicalOperator().GetDependencies();
|
|
||||||
} else {
|
|
||||||
return m_preparedOperator.GetDependencies();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const operators::StellarEquilibriumDependencyStamp &GetGeometryDependency() const {
|
[[nodiscard]] const operators::StellarEquilibriumDependencyStamp &GetGeometryDependency() const {
|
||||||
if constexpr (hasFixedCentralDensity) {
|
return GetPhysicalOperator().GetGeneratedDisplacementDependency();
|
||||||
return m_preparedOperator.GetPhysicalOperator().GetGeneratedDisplacementDependency();
|
|
||||||
} else {
|
|
||||||
return m_preparedOperator.GetGeneratedDisplacementDependency();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
|
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
|
||||||
if constexpr (hasFixedCentralDensity) {
|
return GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
|
||||||
return m_preparedOperator.GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
|
|
||||||
} else {
|
|
||||||
return m_preparedOperator.GetSurfaceConstraintOperator().GetSurfaceRows();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] int StateSize() const noexcept {
|
[[nodiscard]] int StateSize() const noexcept {
|
||||||
@@ -175,8 +236,19 @@ export namespace mean_field::equilibrium {
|
|||||||
const mfem::Vector &state,
|
const mfem::Vector &state,
|
||||||
const operators::StellarEquilibriumDependencies &dependencies,
|
const operators::StellarEquilibriumDependencies &dependencies,
|
||||||
const physics::RigidRotation &rotation
|
const physics::RigidRotation &rotation
|
||||||
) {
|
) requires(generatedRotationProviderCount == 0) {
|
||||||
return m_preparedOperator.Prepare(state, dependencies, rotation);
|
auto report = m_preparedOperator.Prepare(state, dependencies, rotation);
|
||||||
|
++m_preparationGeneration;
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto Prepare(
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const operators::StellarEquilibriumDependencies &dependencies
|
||||||
|
) requires(generatedRotationProviderCount == 1) {
|
||||||
|
auto report = m_preparedOperator.Prepare(state, dependencies);
|
||||||
|
++m_preparationGeneration;
|
||||||
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildResidual(mfem::Vector &residual) const {
|
void BuildResidual(mfem::Vector &residual) const {
|
||||||
@@ -194,8 +266,8 @@ export namespace mean_field::equilibrium {
|
|||||||
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
|
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
|
||||||
return surface::compilePressureSurfaceConstraint<
|
return surface::compilePressureSurfaceConstraint<
|
||||||
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
|
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
|
||||||
stellarModel.template specification<surface::Isobaric>(),
|
stellarModel.surfaceCondition(),
|
||||||
stellarModel.template specification<EquationOfStateType>()
|
stellarModel.equationOfState()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,34 +296,112 @@ export namespace mean_field::equilibrium {
|
|||||||
MFEM_VERIFY(m_discretization.isCurrent(), "The stellar equilibrium problem has a stale discretization.");
|
MFEM_VERIFY(m_discretization.isCurrent(), "The stellar equilibrium problem has a stale discretization.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelType m_stellarModel;
|
std::shared_ptr<const ModelType> m_stellarModel;
|
||||||
StellarDiscretization m_discretization;
|
DiscretizationType m_discretization;
|
||||||
CompiledSurfaceConstraintType m_compiledSurfaceConstraint;
|
CompiledSurfaceConstraintType m_compiledSurfaceConstraint;
|
||||||
PreparedOperatorType m_preparedOperator;
|
PreparedOperatorType m_preparedOperator;
|
||||||
|
std::uint64_t m_preparationGeneration{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <StellarEquilibriumModel Model>
|
template <typename Candidate> struct IsStellarEquilibriumProblem : std::false_type { };
|
||||||
|
|
||||||
|
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
|
||||||
|
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
std::remove_cvref_t<Discretization>>::value
|
||||||
|
struct IsStellarEquilibriumProblem<StellarEquilibriumProblem<Model, Discretization>> : std::true_type { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept DiscretizedStellarEquilibriumProblem = IsStellarEquilibriumProblem<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <
|
||||||
|
typename Model,
|
||||||
|
typename Discretization,
|
||||||
|
bool StructurallyCompatible =
|
||||||
|
StellarEquilibriumModelDiscretizationStructureAudit<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
std::remove_cvref_t<Discretization>>::value>
|
||||||
|
struct StellarEquilibriumModelDiscretizationOperationAudit : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Model, typename Discretization>
|
||||||
|
struct StellarEquilibriumModelDiscretizationOperationAudit<
|
||||||
|
Model,
|
||||||
|
Discretization,
|
||||||
|
true> {
|
||||||
|
private:
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
|
using Problem = StellarEquilibriumProblem<ModelType, DiscretizationType>;
|
||||||
|
using Prescription = typename DiscretizationType::NormalizationPrescriptionType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool value = [] {
|
||||||
|
if constexpr (
|
||||||
|
std::same_as<Prescription, normalization::Unnormalized> ||
|
||||||
|
normalization::PhysicalRieszDiagonalPrescription<Prescription>) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return normalization::RuntimePreparedNormalizationOperation<Problem>;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A model and a discretization are separate compile-time choices. Their
|
||||||
|
* pairing is valid only when the normalization plan covers the inferred
|
||||||
|
* form, the selected physical runtime supports it, and a third-party
|
||||||
|
* runtime policy provides its exact preparation operation. Keeping this
|
||||||
|
* as a detection-safe public factory boundary rejects incomplete policies
|
||||||
|
* at discretize(), before a solver-facing problem can be constructed.
|
||||||
|
*/
|
||||||
|
template <typename Model, typename Discretization>
|
||||||
|
concept StellarEquilibriumModelDiscretizationCompatible =
|
||||||
|
detail::StellarEquilibriumModelDiscretizationOperationAudit<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
std::remove_cvref_t<Discretization>>::value;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
/* The structurally formed problem type is needed to probe the ADL
|
||||||
|
* operation without a recursive concept. Its constructor remains
|
||||||
|
* private, and this factory is the single construction authority after
|
||||||
|
* the complete public compatibility contract has succeeded. */
|
||||||
|
struct StellarEquilibriumProblemFactory final {
|
||||||
|
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
|
||||||
|
[[nodiscard]] static auto Create(
|
||||||
|
Model &&stellarModel,
|
||||||
|
Discretization discretization
|
||||||
|
) {
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
using DiscretizationType = std::remove_cvref_t<Discretization>;
|
||||||
|
return StellarEquilibriumProblem<ModelType, DiscretizationType>{
|
||||||
|
std::forward<Model>(stellarModel),
|
||||||
|
std::move(discretization)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
|
||||||
[[nodiscard]] auto discretize(
|
[[nodiscard]] auto discretize(
|
||||||
Model &&stellarModel,
|
Model &&stellarModel,
|
||||||
const StellarDiscretization discretization
|
Discretization discretization
|
||||||
) {
|
) {
|
||||||
using ModelType = std::remove_cvref_t<Model>;
|
return detail::StellarEquilibriumProblemFactory::Create(
|
||||||
return StellarEquilibriumProblem<ModelType>{std::forward<Model>(stellarModel), discretization};
|
std::forward<Model>(stellarModel),
|
||||||
|
std::move(discretization)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <StellarEquilibriumModel Model>
|
template <StellarEquilibriumModel Model>
|
||||||
|
requires StellarEquilibriumModelDiscretizationCompatible<Model, StellarDiscretization>
|
||||||
[[nodiscard]] auto discretize(
|
[[nodiscard]] auto discretize(
|
||||||
Model &&stellarModel,
|
Model &&stellarModel,
|
||||||
fem::FEM &finiteElementModel
|
fem::FEM &finiteElementModel
|
||||||
) {
|
) {
|
||||||
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
|
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Candidate> struct IsStellarEquilibriumProblem : std::false_type { };
|
|
||||||
|
|
||||||
template <StellarEquilibriumModel Model>
|
|
||||||
struct IsStellarEquilibriumProblem<StellarEquilibriumProblem<Model>> : std::true_type { };
|
|
||||||
|
|
||||||
template <typename Candidate>
|
|
||||||
concept DiscretizedStellarEquilibriumProblem = IsStellarEquilibriumProblem<std::remove_cvref_t<Candidate>>::value;
|
|
||||||
} // namespace mean_field::equilibrium
|
} // namespace mean_field::equilibrium
|
||||||
|
|||||||
@@ -292,7 +292,8 @@ export namespace mean_field::preconditioning {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem, SpecificationBorderBlockType Block>
|
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem, SpecificationBorderBlockType Block>
|
||||||
requires EquilibriumCoordinateComponentFor<Block, typename std::remove_cvref_t<Problem>::FormType>
|
requires EquilibriumCoordinateComponentFor<Block, typename std::remove_cvref_t<Problem>::FormType> &&
|
||||||
|
SpecificationBorderPreparableFor<Problem, Block>
|
||||||
class PreparedStellarPreconditioner final : public mfem::Solver {
|
class PreparedStellarPreconditioner final : public mfem::Solver {
|
||||||
private:
|
private:
|
||||||
using ProblemType = std::remove_cvref_t<Problem>;
|
using ProblemType = std::remove_cvref_t<Problem>;
|
||||||
@@ -324,6 +325,9 @@ export namespace mean_field::preconditioning {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreparedStellarPreconditioner(ProblemType &&, BlockType) = delete;
|
||||||
|
PreparedStellarPreconditioner(const ProblemType &&, BlockType) = delete;
|
||||||
|
|
||||||
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
|
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
|
||||||
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
|
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
|
||||||
PreparedStellarPreconditioner(PreparedStellarPreconditioner &&) = delete;
|
PreparedStellarPreconditioner(PreparedStellarPreconditioner &&) = delete;
|
||||||
@@ -367,6 +371,10 @@ export namespace mean_field::preconditioning {
|
|||||||
return m_grouped.GetBlock();
|
return m_grouped.GetBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
|
||||||
|
return m_grouped.GetProblem();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] const GroupedPreconditioner &GetGroupedPreconditioner() const noexcept {
|
[[nodiscard]] const GroupedPreconditioner &GetGroupedPreconditioner() const noexcept {
|
||||||
return m_grouped;
|
return m_grouped;
|
||||||
}
|
}
|
||||||
@@ -392,11 +400,26 @@ export namespace mean_field::preconditioning {
|
|||||||
SpecificationBorderBlockType Block>
|
SpecificationBorderBlockType Block>
|
||||||
requires EquilibriumCoordinateComponentFor<
|
requires EquilibriumCoordinateComponentFor<
|
||||||
Block,
|
Block,
|
||||||
typename std::remove_cvref_t<Problem>::FormType>
|
typename std::remove_cvref_t<Problem>::FormType> &&
|
||||||
|
SpecificationBorderPreparableFor<Problem, Block>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const Problem &problem,
|
const Problem &problem,
|
||||||
Block block
|
Block block
|
||||||
) {
|
) {
|
||||||
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
|
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Problem, SpecificationBorderBlockType Block>
|
||||||
|
requires (!std::is_lvalue_reference_v<Problem>) &&
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
EquilibriumCoordinateComponentFor<
|
||||||
|
Block,
|
||||||
|
typename std::remove_cvref_t<Problem>::FormType> &&
|
||||||
|
SpecificationBorderPreparableFor<std::remove_cvref_t<Problem>, Block>
|
||||||
|
[[nodiscard]] auto prepare(
|
||||||
|
Problem &&,
|
||||||
|
Block
|
||||||
|
) -> PreparedStellarPreconditioner<
|
||||||
|
std::remove_cvref_t<Problem>,
|
||||||
|
std::remove_cvref_t<Block>> = delete;
|
||||||
} // namespace mean_field::preconditioning
|
} // namespace mean_field::preconditioning
|
||||||
|
|||||||
@@ -231,10 +231,90 @@ export namespace mean_field::preconditioning {
|
|||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept MaterialSurfaceDescriptor = detail::IsMaterialSurfaceDescriptor<std::remove_cvref_t<Candidate>>::value;
|
concept MaterialSurfaceDescriptor = detail::IsMaterialSurfaceDescriptor<std::remove_cvref_t<Candidate>>::value;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Capability boundary for EOS-specific material/surface surrogate
|
||||||
|
* assembly. The current kernels remain polytropic, but selection no
|
||||||
|
* longer embeds that closed-world type test in the descriptor concept.
|
||||||
|
*/
|
||||||
|
template <typename EquationOfState>
|
||||||
|
struct MaterialSurfaceEquationOfStateBackend {
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
using CoreType = operators::PreparedStellarEquilibriumOperator;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename EquationOfState>
|
||||||
|
concept ImplementedMaterialSurfaceEquationOfState = requires {
|
||||||
|
{
|
||||||
|
MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered
|
||||||
|
} -> std::convertible_to<bool>;
|
||||||
|
requires MaterialSurfaceEquationOfStateBackend<
|
||||||
|
std::remove_cvref_t<EquationOfState>>::registered;
|
||||||
|
typename MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::CoreType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registering an EOS-to-core association is intentionally not enough to
|
||||||
|
* claim that the material/surface preconditioner can execute it. Every
|
||||||
|
* implementation listed here must have matching prepared operators and
|
||||||
|
* prepare(...) overloads below. A future backend should add its pair only
|
||||||
|
* after those executable pieces exist; this keeps capability queries
|
||||||
|
* truthful while the current kernels still consume the legacy physical
|
||||||
|
* core directly.
|
||||||
|
*/
|
||||||
|
template <typename EquationOfState, typename PhysicalCore>
|
||||||
|
struct MaterialSurfaceExecutableRuntime {
|
||||||
|
static constexpr bool available = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
|
||||||
|
static constexpr bool available = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename EquationOfState, typename PhysicalCore>
|
||||||
|
concept ExecutableMaterialSurfaceRuntimeFor = requires {
|
||||||
|
{
|
||||||
|
MaterialSurfaceExecutableRuntime<
|
||||||
|
std::remove_cvref_t<EquationOfState>,
|
||||||
|
std::remove_cvref_t<PhysicalCore>>::available
|
||||||
|
} -> std::convertible_to<bool>;
|
||||||
|
requires MaterialSurfaceExecutableRuntime<
|
||||||
|
std::remove_cvref_t<EquationOfState>,
|
||||||
|
std::remove_cvref_t<PhysicalCore>>::available;
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Descriptor>
|
template <typename Descriptor>
|
||||||
concept ImplementedMaterialSurfaceDescriptor =
|
concept ImplementedMaterialSurfaceDescriptor =
|
||||||
MaterialSurfaceDescriptor<Descriptor> &&
|
MaterialSurfaceDescriptor<Descriptor> &&
|
||||||
std::same_as<typename Descriptor::ThermodynamicEquations::EquationOfStateType, eos::Polytrope>;
|
ImplementedMaterialSurfaceEquationOfState<
|
||||||
|
typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
|
||||||
|
|
||||||
|
template <typename Descriptor, typename PhysicalCore>
|
||||||
|
concept MaterialSurfaceRuntimeFor =
|
||||||
|
ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
|
||||||
|
typename MaterialSurfaceEquationOfStateBackend<
|
||||||
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
|
||||||
|
requires std::same_as<
|
||||||
|
std::remove_cvref_t<PhysicalCore>,
|
||||||
|
typename MaterialSurfaceEquationOfStateBackend<
|
||||||
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
|
||||||
|
requires ExecutableMaterialSurfaceRuntimeFor<
|
||||||
|
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType,
|
||||||
|
PhysicalCore>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept MaterialSurfacePreconditionerProblem =
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> && requires {
|
||||||
|
requires MaterialSurfaceRuntimeFor<
|
||||||
|
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
|
||||||
|
typename std::remove_cvref_t<Candidate>::PhysicalCoreType>;
|
||||||
|
};
|
||||||
|
|
||||||
using DensityMassDiagonalCharacteristics = OperatorCharacteristics<
|
using DensityMassDiagonalCharacteristics = OperatorCharacteristics<
|
||||||
OperatorCategory::mass_like,
|
OperatorCategory::mass_like,
|
||||||
@@ -605,7 +685,7 @@ export namespace mean_field::preconditioning {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
MaterialSurfacePreconditionerProblem Problem,
|
||||||
backend::Registered MaterialBackend = backend::Diagonal,
|
backend::Registered MaterialBackend = backend::Diagonal,
|
||||||
backend::Registered SurfaceBackend = backend::Diagonal,
|
backend::Registered SurfaceBackend = backend::Diagonal,
|
||||||
MaterialSurfaceFactorizationPolicy Policy = SurfaceThenMaterialTriangular>
|
MaterialSurfaceFactorizationPolicy Policy = SurfaceThenMaterialTriangular>
|
||||||
@@ -623,7 +703,7 @@ export namespace mean_field::preconditioning {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
MaterialSurfacePreconditionerProblem Problem,
|
||||||
backend::Registered MaterialBackend,
|
backend::Registered MaterialBackend,
|
||||||
backend::Registered SurfaceBackend,
|
backend::Registered SurfaceBackend,
|
||||||
MaterialSurfaceFactorizationPolicy Policy,
|
MaterialSurfaceFactorizationPolicy Policy,
|
||||||
@@ -688,7 +768,7 @@ export namespace mean_field::preconditioning {
|
|||||||
// direct coupling actions and does not pay for a full Jacobian
|
// direct coupling actions and does not pay for a full Jacobian
|
||||||
// application.
|
// application.
|
||||||
m_fullDirection = 0.0;
|
m_fullDirection = 0.0;
|
||||||
const auto fullDirectionView = m_operation->GetRootManifest().directionView(m_fullDirection);
|
const auto fullDirectionView = m_operation->GetRootManifest().stateView(m_fullDirection);
|
||||||
mfem::Vector fullDensityDirection = fullDirectionView.block(utils::blocks::density_field.mass_term);
|
mfem::Vector fullDensityDirection = fullDirectionView.block(utils::blocks::density_field.mass_term);
|
||||||
mfem::Vector fullSurfaceDirection =
|
mfem::Vector fullSurfaceDirection =
|
||||||
fullDirectionView.block(utils::blocks::surface_deformation_field.parameters_term);
|
fullDirectionView.block(utils::blocks::surface_deformation_field.parameters_term);
|
||||||
@@ -699,10 +779,10 @@ export namespace mean_field::preconditioning {
|
|||||||
|
|
||||||
m_operation->Mult(m_fullDirection, m_fullAction);
|
m_operation->Mult(m_fullDirection, m_fullAction);
|
||||||
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
|
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
|
||||||
const mfem::Vector fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
|
const auto fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
|
||||||
const mfem::Vector fullSurfaceAction =
|
const auto fullSurfaceAction =
|
||||||
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
||||||
const mfem::Vector fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
|
const auto fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
|
||||||
densityAction = fullDensityAction;
|
densityAction = fullDensityAction;
|
||||||
surfaceAction = fullSurfaceAction;
|
surfaceAction = fullSurfaceAction;
|
||||||
enthalpyAction = fullEnthalpyAction;
|
enthalpyAction = fullEnthalpyAction;
|
||||||
@@ -1108,7 +1188,8 @@ export namespace mean_field::preconditioning {
|
|||||||
std::uint64_t surfaceH1Assemblies{0};
|
std::uint64_t surfaceH1Assemblies{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <ImplementedMaterialSurfaceDescriptor Descriptor, MaterialSurfaceFactorizationPolicy Policy>
|
template <MaterialSurfaceDescriptor Descriptor, MaterialSurfaceFactorizationPolicy Policy>
|
||||||
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
||||||
class PreparedMaterialSurfaceBlock final : public mfem::Solver {
|
class PreparedMaterialSurfaceBlock final : public mfem::Solver {
|
||||||
public:
|
public:
|
||||||
using Block = MaterialSurfaceBlock<Descriptor, backend::Diagonal, backend::Diagonal, Policy>;
|
using Block = MaterialSurfaceBlock<Descriptor, backend::Diagonal, backend::Diagonal, Policy>;
|
||||||
@@ -1562,9 +1643,10 @@ export namespace mean_field::preconditioning {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
ImplementedMaterialSurfaceDescriptor Descriptor,
|
MaterialSurfaceDescriptor Descriptor,
|
||||||
MaterialSurfaceFactorizationPolicy Policy,
|
MaterialSurfaceFactorizationPolicy Policy,
|
||||||
backend::ApplicationMode Mode>
|
backend::ApplicationMode Mode>
|
||||||
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
||||||
class PreparedH1MaterialSurfaceBlock final : public mfem::Solver {
|
class PreparedH1MaterialSurfaceBlock final : public mfem::Solver {
|
||||||
public:
|
public:
|
||||||
using SurfaceBackend = backend::HypreBoomerAMG<Mode>;
|
using SurfaceBackend = backend::HypreBoomerAMG<Mode>;
|
||||||
@@ -2027,8 +2109,9 @@ export namespace mean_field::preconditioning {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
ImplementedMaterialSurfaceDescriptor Descriptor,
|
MaterialSurfaceDescriptor Descriptor,
|
||||||
MaterialSurfaceFactorizationPolicy Policy>
|
MaterialSurfaceFactorizationPolicy Policy>
|
||||||
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const operators::PreparedStellarEquilibriumOperator &operation,
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
@@ -2042,26 +2125,26 @@ export namespace mean_field::preconditioning {
|
|||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::StellarEquilibriumModel Model,
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
MaterialSurfaceFactorizationPolicy Policy>
|
MaterialSurfaceFactorizationPolicy Policy>
|
||||||
|
requires MaterialSurfacePreconditionerProblem<
|
||||||
|
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model> &problem,
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model>>,
|
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
Policy> block
|
Policy> block
|
||||||
) {
|
) {
|
||||||
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
|
return prepare(problem.GetPhysicalOperator(), std::move(block));
|
||||||
return prepare(problem.GetPreparedOperator().GetPhysicalOperator(), std::move(block));
|
|
||||||
} else {
|
|
||||||
return prepare(problem.GetPreparedOperator(), std::move(block));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
ImplementedMaterialSurfaceDescriptor Descriptor,
|
MaterialSurfaceDescriptor Descriptor,
|
||||||
MaterialSurfaceFactorizationPolicy Policy,
|
MaterialSurfaceFactorizationPolicy Policy,
|
||||||
backend::ApplicationMode Mode>
|
backend::ApplicationMode Mode>
|
||||||
|
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const operators::PreparedStellarEquilibriumOperator &operation,
|
const operators::PreparedStellarEquilibriumOperator &operation,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
@@ -2076,21 +2159,20 @@ export namespace mean_field::preconditioning {
|
|||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::StellarEquilibriumModel Model,
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
MaterialSurfaceFactorizationPolicy Policy,
|
MaterialSurfaceFactorizationPolicy Policy,
|
||||||
backend::ApplicationMode Mode>
|
backend::ApplicationMode Mode>
|
||||||
|
requires MaterialSurfacePreconditionerProblem<
|
||||||
|
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model> &problem,
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
||||||
MaterialSurfaceBlock<
|
MaterialSurfaceBlock<
|
||||||
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model>>,
|
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
backend::HypreBoomerAMG<Mode>,
|
backend::HypreBoomerAMG<Mode>,
|
||||||
Policy,
|
Policy,
|
||||||
SurfaceH1MassStiffness> block
|
SurfaceH1MassStiffness> block
|
||||||
) {
|
) {
|
||||||
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
|
return prepare(problem.GetPhysicalOperator(), std::move(block));
|
||||||
return prepare(problem.GetPreparedOperator().GetPhysicalOperator(), std::move(block));
|
|
||||||
} else {
|
|
||||||
return prepare(problem.GetPreparedOperator(), std::move(block));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // namespace mean_field::preconditioning
|
} // namespace mean_field::preconditioning
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ export namespace mean_field::preconditioning {
|
|||||||
operators::StellarEquilibriumDependencyStamp geometry;
|
operators::StellarEquilibriumDependencyStamp geometry;
|
||||||
const void *equationOfStateIdentity{nullptr};
|
const void *equationOfStateIdentity{nullptr};
|
||||||
operators::StellarEquilibriumDependencies linearization;
|
operators::StellarEquilibriumDependencies linearization;
|
||||||
|
std::uint64_t preparedOperatorGeneration{0};
|
||||||
|
|
||||||
constexpr bool operator==(const StellarPreconditionerLifecycleSnapshot &) const = default;
|
constexpr bool operator==(const StellarPreconditionerLifecycleSnapshot &) const = default;
|
||||||
};
|
};
|
||||||
@@ -61,7 +62,8 @@ export namespace mean_field::preconditioning {
|
|||||||
.discretization = prepared.discretization != current.discretization,
|
.discretization = prepared.discretization != current.discretization,
|
||||||
.geometry = prepared.geometry != current.geometry,
|
.geometry = prepared.geometry != current.geometry,
|
||||||
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
|
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
|
||||||
.linearization = prepared.linearization != current.linearization
|
.linearization = prepared.linearization != current.linearization ||
|
||||||
|
prepared.preparedOperatorGeneration != current.preparedOperatorGeneration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +97,11 @@ export namespace mean_field::preconditioning {
|
|||||||
static constexpr bool registered = false;
|
static constexpr bool registered = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <equilibrium::StellarEquilibriumModel Model>
|
template <
|
||||||
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model>> {
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
|
equilibrium::StellarDiscretizationType Discretization>
|
||||||
|
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model, Discretization>> {
|
||||||
|
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
||||||
using Form = typename Problem::FormType;
|
using Form = typename Problem::FormType;
|
||||||
using JacobianForm = typename Problem::JacobianFormType;
|
using JacobianForm = typename Problem::JacobianFormType;
|
||||||
using Manifest = typename Problem::ManifestType;
|
using Manifest = typename Problem::ManifestType;
|
||||||
@@ -130,8 +134,9 @@ export namespace mean_field::preconditioning {
|
|||||||
.discretization = dependencies.discretization,
|
.discretization = dependencies.discretization,
|
||||||
.geometry = problem.GetGeometryDependency(),
|
.geometry = problem.GetGeometryDependency(),
|
||||||
.equationOfStateIdentity =
|
.equationOfStateIdentity =
|
||||||
std::addressof(problem.GetStellarModel().template specification<eos::Polytrope>()),
|
std::addressof(problem.GetStellarModel().equationOfState()),
|
||||||
.linearization = dependencies
|
.linearization = dependencies,
|
||||||
|
.preparedOperatorGeneration = problem.GetPreparationGeneration()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -139,6 +144,243 @@ export namespace mean_field::preconditioning {
|
|||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
|
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Block>
|
||||||
|
struct IsGeneratedStellarValueBlock : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct IsGeneratedStellarValueBlock<utils::blocks::generated_value_block<Generated>>
|
||||||
|
: std::true_type { };
|
||||||
|
|
||||||
|
template <typename Block>
|
||||||
|
struct IsGeneratedStellarResidualBlock : std::false_type { };
|
||||||
|
|
||||||
|
template <typename Generated>
|
||||||
|
struct IsGeneratedStellarResidualBlock<utils::blocks::generated_residual_block<Generated>>
|
||||||
|
: std::true_type { };
|
||||||
|
|
||||||
|
template <typename Coupling>
|
||||||
|
inline constexpr bool isPurePhysicalStellarCoupling =
|
||||||
|
!IsGeneratedStellarValueBlock<
|
||||||
|
std::remove_cvref_t<typename Coupling::Value>>::value &&
|
||||||
|
!IsGeneratedStellarResidualBlock<
|
||||||
|
std::remove_cvref_t<typename Coupling::Residual>>::value;
|
||||||
|
|
||||||
|
/* Pure structure contributions owned by a trusted backend are exact
|
||||||
|
* (core, specification, coupling) capabilities. Future cores and new
|
||||||
|
* edges start with no privilege: changing a built-in declaration must
|
||||||
|
* be accompanied by an explicit preconditioner decision. Generated-
|
||||||
|
* border terms remain the responsibility of specification-border
|
||||||
|
* machinery. */
|
||||||
|
template <typename PhysicalCore, typename Specification>
|
||||||
|
struct StellarStructureBackendHandledCouplings {
|
||||||
|
using Type = utils::blocks::type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct StellarStructureBackendHandledCouplings<
|
||||||
|
operators::PreparedStellarEquilibriumOperator,
|
||||||
|
models::FixedTotalMass> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
utils::blocks::density::mass::value>,
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct StellarStructureBackendHandledCouplings<
|
||||||
|
operators::PreparedStellarEquilibriumOperator,
|
||||||
|
models::FixedAngularMomentum> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||||
|
utils::blocks::density::mass::value>,
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>,
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
utils::blocks::density::mass::value>,
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
utils::blocks::surface_deformation::parameters::value>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct StellarStructureBackendHandledCouplings<
|
||||||
|
operators::PreparedStellarEquilibriumOperator,
|
||||||
|
models::FixedCentralDensity> {
|
||||||
|
using Type = utils::blocks::type_list<
|
||||||
|
operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
utils::blocks::enthalpy::specific::value>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Coupling,
|
||||||
|
typename Model,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename ModelSpecifications>
|
||||||
|
struct EveryCouplingContributionHandled;
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Coupling,
|
||||||
|
model::StellarModelType Model,
|
||||||
|
typename PhysicalCore,
|
||||||
|
models::ModelSpecification... Specifications>
|
||||||
|
struct EveryCouplingContributionHandled<
|
||||||
|
Coupling,
|
||||||
|
Model,
|
||||||
|
PhysicalCore,
|
||||||
|
models::detail::SpecificationSetStorage<Specifications...>> final {
|
||||||
|
private:
|
||||||
|
template <typename Specification>
|
||||||
|
static constexpr bool handled =
|
||||||
|
!utils::blocks::contains_type_v<
|
||||||
|
Coupling,
|
||||||
|
typename operators::StellarEquilibriumSpecificationCompilation<
|
||||||
|
Specification>::JacobianCouplings> ||
|
||||||
|
(operators::stellarEquilibriumBackendRuntimeAuthorized<
|
||||||
|
Specification,
|
||||||
|
Model> &&
|
||||||
|
utils::blocks::contains_type_v<
|
||||||
|
Coupling,
|
||||||
|
typename StellarStructureBackendHandledCouplings<
|
||||||
|
PhysicalCore,
|
||||||
|
Specification>::Type>) ||
|
||||||
|
operators::stellarEquilibriumSpecificationCouplingIsStructuralZero<
|
||||||
|
Specification,
|
||||||
|
Model,
|
||||||
|
Coupling>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr bool value =
|
||||||
|
(handled<Specifications> && ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Remaining,
|
||||||
|
typename Model,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename ModelSpecifications,
|
||||||
|
typename Unsupported>
|
||||||
|
struct CollectUnsupportedStellarStructureCouplings;
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Model,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename ModelSpecifications,
|
||||||
|
typename Unsupported>
|
||||||
|
struct CollectUnsupportedStellarStructureCouplings<
|
||||||
|
utils::blocks::type_list<>,
|
||||||
|
Model,
|
||||||
|
PhysicalCore,
|
||||||
|
ModelSpecifications,
|
||||||
|
Unsupported> {
|
||||||
|
using Type = Unsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Head,
|
||||||
|
typename... Tail,
|
||||||
|
typename Model,
|
||||||
|
typename PhysicalCore,
|
||||||
|
typename ModelSpecifications,
|
||||||
|
typename... Unsupported>
|
||||||
|
struct CollectUnsupportedStellarStructureCouplings<
|
||||||
|
utils::blocks::type_list<Head, Tail...>,
|
||||||
|
Model,
|
||||||
|
PhysicalCore,
|
||||||
|
ModelSpecifications,
|
||||||
|
utils::blocks::type_list<Unsupported...>> {
|
||||||
|
private:
|
||||||
|
static constexpr bool supported =
|
||||||
|
!isPurePhysicalStellarCoupling<Head> ||
|
||||||
|
EveryCouplingContributionHandled<
|
||||||
|
Head,
|
||||||
|
Model,
|
||||||
|
PhysicalCore,
|
||||||
|
ModelSpecifications>::value;
|
||||||
|
using Next = std::conditional_t<
|
||||||
|
supported,
|
||||||
|
utils::blocks::type_list<Unsupported...>,
|
||||||
|
utils::blocks::type_list<Unsupported..., Head>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = typename CollectUnsupportedStellarStructureCouplings<
|
||||||
|
utils::blocks::type_list<Tail...>,
|
||||||
|
Model,
|
||||||
|
PhysicalCore,
|
||||||
|
ModelSpecifications,
|
||||||
|
Next>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate, typename = void>
|
||||||
|
struct DefaultStellarStructurePhysicalTopologyAudit {
|
||||||
|
using ContributionCouplings = utils::blocks::type_list<>;
|
||||||
|
using UnsupportedCouplings = utils::blocks::type_list<>;
|
||||||
|
|
||||||
|
static constexpr bool supported = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <model::StellarModelType Model>
|
||||||
|
requires(
|
||||||
|
operators::StellarEquilibriumSystemCompilable<
|
||||||
|
std::remove_cvref_t<Model>> &&
|
||||||
|
operators::hasStellarEquilibriumCoreRuntime<
|
||||||
|
std::remove_cvref_t<Model>>)
|
||||||
|
struct DefaultStellarStructurePhysicalTopologyAudit<
|
||||||
|
Model,
|
||||||
|
std::void_t<
|
||||||
|
typename operators::CompiledStellarEquilibriumSystem<
|
||||||
|
std::remove_cvref_t<Model>>::ContributionJacobianCouplings,
|
||||||
|
operators::StellarEquilibriumPhysicalCoreType<
|
||||||
|
std::remove_cvref_t<Model>>>> {
|
||||||
|
private:
|
||||||
|
using Compilation = operators::CompiledStellarEquilibriumSystem<
|
||||||
|
std::remove_cvref_t<Model>>;
|
||||||
|
using PhysicalCore = operators::StellarEquilibriumPhysicalCoreType<
|
||||||
|
std::remove_cvref_t<Model>>;
|
||||||
|
public:
|
||||||
|
using ContributionCouplings =
|
||||||
|
typename Compilation::ContributionJacobianCouplings;
|
||||||
|
using UnsupportedCouplings =
|
||||||
|
typename CollectUnsupportedStellarStructureCouplings<
|
||||||
|
ContributionCouplings,
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
PhysicalCore,
|
||||||
|
typename std::remove_cvref_t<Model>::SpecificationTypes,
|
||||||
|
utils::blocks::type_list<>>::Type;
|
||||||
|
|
||||||
|
static constexpr bool supported = UnsupportedCouplings::size == 0;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/* A generated-border edge is owned by specification-border machinery and
|
||||||
|
* is deliberately ignored here. Every pure physical edge contributed by a
|
||||||
|
* model must be owned by the selected numerical structure backend, or
|
||||||
|
* every non-backend provider of that edge must prove StructuralZero. In
|
||||||
|
* particular, merely overlapping an existing base-Jacobian edge is not
|
||||||
|
* sufficient: a custom nonzero coefficient on that edge would otherwise
|
||||||
|
* disappear silently from the default preconditioner. This audit is
|
||||||
|
* detection-safe and therefore suitable for constraining factories. */
|
||||||
|
template <typename Candidate>
|
||||||
|
struct DefaultStellarStructurePhysicalTopologySupport
|
||||||
|
: detail::DefaultStellarStructurePhysicalTopologyAudit<
|
||||||
|
std::remove_cvref_t<Candidate>> { };
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
inline constexpr bool defaultStellarStructurePhysicalTopologySupported =
|
||||||
|
DefaultStellarStructurePhysicalTopologySupport<
|
||||||
|
std::remove_cvref_t<Candidate>>::supported;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept DefaultStellarStructurePhysicalTopologySupportedFor =
|
||||||
|
defaultStellarStructurePhysicalTopologySupported<Candidate>;
|
||||||
|
|
||||||
namespace backend {
|
namespace backend {
|
||||||
template <typename Component, typename Problem, typename Backend = typename Component::BackendType>
|
template <typename Component, typename Problem, typename Backend = typename Component::BackendType>
|
||||||
class PreparedComponent;
|
class PreparedComponent;
|
||||||
@@ -174,56 +416,17 @@ export namespace mean_field::preconditioning {
|
|||||||
} // namespace backend
|
} // namespace backend
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
using DensityIdentity =
|
|
||||||
IdentityBlock<utils::blocks::density::mass::value, utils::blocks::density::mass::residual>;
|
|
||||||
using SurfaceIdentity = IdentityBlock<
|
|
||||||
utils::blocks::surface_deformation::parameters::value,
|
|
||||||
utils::blocks::surface_deformation::shape_equilibrium::residual>;
|
|
||||||
using GravityGradientIdentity =
|
|
||||||
IdentityBlock<utils::blocks::gravity::gradient::value, utils::blocks::gravity::gradient::residual>;
|
|
||||||
using GravityPotentialIdentity =
|
|
||||||
IdentityBlock<utils::blocks::gravity::poisson::value, utils::blocks::gravity::poisson::residual>;
|
|
||||||
using EnthalpyIdentity =
|
|
||||||
IdentityBlock<utils::blocks::enthalpy::specific::value, utils::blocks::enthalpy::specific::residual>;
|
|
||||||
using FixedMassIdentity = IdentityBlock<
|
|
||||||
utils::blocks::fixed_total_mass::mass_normalization::value,
|
|
||||||
utils::blocks::fixed_total_mass::mass_normalization::residual>;
|
|
||||||
using FixedCentralDensityIdentity = IdentityBlock<
|
|
||||||
utils::blocks::fixed_central_density::central_value::value,
|
|
||||||
utils::blocks::fixed_central_density::central_value::residual>;
|
|
||||||
|
|
||||||
template <typename Form> struct IdentityPlanForForm;
|
template <typename Form> struct IdentityPlanForForm;
|
||||||
|
|
||||||
template <> struct IdentityPlanForForm<utils::blocks::surface_deformed_stellar_equilibrium_form> {
|
template <typename... Values, typename... Residuals>
|
||||||
using Type = PreconditionerPlan<
|
struct IdentityPlanForForm<utils::blocks::block_form<
|
||||||
DensityIdentity,
|
utils::blocks::type_list<Values...>,
|
||||||
SurfaceIdentity,
|
utils::blocks::type_list<Residuals...>>> {
|
||||||
GravityGradientIdentity,
|
static_assert(sizeof...(Values) == sizeof...(Residuals));
|
||||||
GravityPotentialIdentity,
|
using Type = PreconditionerPlan<IdentityBlock<Values, Residuals>...>;
|
||||||
EnthalpyIdentity,
|
|
||||||
FixedMassIdentity>;
|
|
||||||
|
|
||||||
[[nodiscard]] static constexpr Type Make() {
|
[[nodiscard]] static constexpr Type Make() {
|
||||||
return Type{DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{},
|
return Type{IdentityBlock<Values, Residuals>{}...};
|
||||||
GravityPotentialIdentity{}, EnthalpyIdentity{}, FixedMassIdentity{}};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <> struct IdentityPlanForForm<utils::blocks::central_density_bordered_stellar_equilibrium_form> {
|
|
||||||
using Type = PreconditionerPlan<
|
|
||||||
DensityIdentity,
|
|
||||||
SurfaceIdentity,
|
|
||||||
GravityGradientIdentity,
|
|
||||||
GravityPotentialIdentity,
|
|
||||||
EnthalpyIdentity,
|
|
||||||
FixedMassIdentity,
|
|
||||||
FixedCentralDensityIdentity>;
|
|
||||||
|
|
||||||
[[nodiscard]] static constexpr Type Make() {
|
|
||||||
return Type{
|
|
||||||
DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{}, GravityPotentialIdentity{},
|
|
||||||
EnthalpyIdentity{}, FixedMassIdentity{}, FixedCentralDensityIdentity{}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export module mean_field:preconditioning.stellar_structure;
|
|||||||
|
|
||||||
export import :preconditioning.gravity_field;
|
export import :preconditioning.gravity_field;
|
||||||
export import :preconditioning.material_surface;
|
export import :preconditioning.material_surface;
|
||||||
|
export import :preconditioning.stellar_equilibrium;
|
||||||
|
|
||||||
export namespace mean_field::preconditioning {
|
export namespace mean_field::preconditioning {
|
||||||
struct IndependentStellarSubsystems final { };
|
struct IndependentStellarSubsystems final { };
|
||||||
@@ -626,28 +627,88 @@ export namespace mean_field::preconditioning {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Material/surface execution and stellar-structure execution are separate
|
||||||
|
* capabilities. The latter also owns the physical cross-Jacobian and
|
||||||
|
* gravity-context wiring, which currently target the legacy prepared core.
|
||||||
|
* Add future cores here only together with matching cross-coupling and
|
||||||
|
* preparation implementations.
|
||||||
|
*/
|
||||||
|
template <typename PhysicalCore>
|
||||||
|
struct StellarStructureExecutableRuntime {
|
||||||
|
static constexpr bool available = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct StellarStructureExecutableRuntime<operators::PreparedStellarEquilibriumOperator> {
|
||||||
|
static constexpr bool available = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename PhysicalCore>
|
||||||
|
concept ExecutableStellarStructureRuntimeFor = requires {
|
||||||
|
{
|
||||||
|
StellarStructureExecutableRuntime<std::remove_cvref_t<PhysicalCore>>::available
|
||||||
|
} -> std::convertible_to<bool>;
|
||||||
|
requires StellarStructureExecutableRuntime<std::remove_cvref_t<PhysicalCore>>::available;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Descriptor, typename PhysicalCore>
|
||||||
|
concept StellarStructureRuntimeFor =
|
||||||
|
MaterialSurfaceRuntimeFor<Descriptor, PhysicalCore> &&
|
||||||
|
ExecutableStellarStructureRuntimeFor<PhysicalCore>;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept StellarStructurePreconditionerProblem =
|
||||||
|
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> &&
|
||||||
|
DefaultStellarStructurePhysicalTopologySupportedFor<
|
||||||
|
typename std::remove_cvref_t<Candidate>::ModelType> &&
|
||||||
|
requires {
|
||||||
|
requires StellarStructureRuntimeFor<
|
||||||
|
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
|
||||||
|
typename std::remove_cvref_t<Candidate>::PhysicalCoreType>;
|
||||||
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <equilibrium::StellarEquilibriumModel Model>
|
template <StellarStructurePreconditionerProblem Problem>
|
||||||
[[nodiscard]] const operators::PreparedStellarEquilibriumOperator &
|
[[nodiscard]] const auto &physicalOperator(const Problem &problem) {
|
||||||
physicalOperator(const equilibrium::StellarEquilibriumProblem<Model> &problem) {
|
return problem.GetPhysicalOperator();
|
||||||
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
|
|
||||||
return problem.GetPreparedOperator().GetPhysicalOperator();
|
|
||||||
} else {
|
|
||||||
return problem.GetPreparedOperator();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename Problem, typename MaterialComponent, typename GravityComponent>
|
||||||
|
concept StellarStructurePreparableFor =
|
||||||
|
StellarStructurePreconditionerProblem<std::remove_cvref_t<Problem>> &&
|
||||||
|
PreconditionerComponent<std::remove_cvref_t<MaterialComponent>> &&
|
||||||
|
PreconditionerComponent<std::remove_cvref_t<GravityComponent>> &&
|
||||||
|
requires(
|
||||||
|
const std::remove_cvref_t<Problem> &problem,
|
||||||
|
std::remove_cvref_t<MaterialComponent> materialComponent,
|
||||||
|
std::remove_cvref_t<GravityComponent> gravityComponent
|
||||||
|
) {
|
||||||
|
preconditioning::prepare(problem, std::move(materialComponent));
|
||||||
|
preconditioning::prepare(
|
||||||
|
problem.GetPhysicalOperator().GetHydrostaticOperator().GetFEM(),
|
||||||
|
problem.GetPhysicalOperator().GetGravityContext().GetGeometryContext(),
|
||||||
|
std::move(gravityComponent)
|
||||||
|
);
|
||||||
|
StellarStructureCrossJacobianOperator{problem.GetPhysicalOperator()};
|
||||||
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::StellarEquilibriumModel Model,
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
typename MaterialComponent,
|
typename MaterialComponent,
|
||||||
backend::Registered GravityMassBackend,
|
backend::Registered GravityMassBackend,
|
||||||
backend::ApplicationMode Mode,
|
backend::ApplicationMode Mode,
|
||||||
GravityFactorizationPolicy GravityPolicy,
|
GravityFactorizationPolicy GravityPolicy,
|
||||||
StellarStructureFactorizationPolicy StructurePolicy>
|
StellarStructureFactorizationPolicy StructurePolicy>
|
||||||
|
requires StellarStructurePreparableFor<
|
||||||
|
equilibrium::StellarEquilibriumProblem<Model, Discretization>,
|
||||||
|
MaterialComponent,
|
||||||
|
GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>>
|
||||||
class PreparedStellarStructureBlock final : public mfem::Solver {
|
class PreparedStellarStructureBlock final : public mfem::Solver {
|
||||||
private:
|
private:
|
||||||
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
|
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
||||||
using GravityComponent = GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>;
|
using GravityComponent = GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>;
|
||||||
using Structure = StellarStructureBlock<
|
using Structure = StellarStructureBlock<
|
||||||
MaterialComponent,
|
MaterialComponent,
|
||||||
@@ -769,7 +830,7 @@ export namespace mean_field::preconditioning {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
StellarStructurePreconditionerProblem Problem,
|
||||||
typename MaterialComponent,
|
typename MaterialComponent,
|
||||||
backend::Registered GravityMassBackend,
|
backend::Registered GravityMassBackend,
|
||||||
backend::ApplicationMode Mode,
|
backend::ApplicationMode Mode,
|
||||||
@@ -792,7 +853,7 @@ export namespace mean_field::preconditioning {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
template <StellarStructurePreconditionerProblem Problem>
|
||||||
[[nodiscard]] constexpr auto stellarStructureBlock(const Problem &problem) {
|
[[nodiscard]] constexpr auto stellarStructureBlock(const Problem &problem) {
|
||||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||||
auto material = materialSurfaceBlock(problem);
|
auto material = materialSurfaceBlock(problem);
|
||||||
@@ -805,25 +866,30 @@ export namespace mean_field::preconditioning {
|
|||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::StellarEquilibriumModel Model,
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
typename MaterialComponent,
|
typename MaterialComponent,
|
||||||
backend::Registered GravityMassBackend,
|
backend::Registered GravityMassBackend,
|
||||||
backend::ApplicationMode Mode,
|
backend::ApplicationMode Mode,
|
||||||
GravityFactorizationPolicy GravityPolicy,
|
GravityFactorizationPolicy GravityPolicy,
|
||||||
StellarStructureFactorizationPolicy StructurePolicy>
|
StellarStructureFactorizationPolicy StructurePolicy>
|
||||||
|
requires StellarStructurePreparableFor<
|
||||||
|
equilibrium::StellarEquilibriumProblem<Model, Discretization>,
|
||||||
|
MaterialComponent,
|
||||||
|
GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>>
|
||||||
[[nodiscard]] auto prepare(
|
[[nodiscard]] auto prepare(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model> &problem,
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
||||||
StellarStructureBlock<
|
StellarStructureBlock<
|
||||||
MaterialComponent,
|
MaterialComponent,
|
||||||
GravityFieldBlock<
|
GravityFieldBlock<
|
||||||
GravityMassBackend,
|
GravityMassBackend,
|
||||||
backend::HypreBoomerAMG<Mode>,
|
backend::HypreBoomerAMG<Mode>,
|
||||||
GravityPolicy>,
|
GravityPolicy>,
|
||||||
typename equilibrium::StellarEquilibriumProblem<Model>::FormType,
|
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::FormType,
|
||||||
typename equilibrium::StellarEquilibriumProblem<Model>::JacobianFormType,
|
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::JacobianFormType,
|
||||||
StructurePolicy> structure
|
StructurePolicy> structure
|
||||||
) {
|
) {
|
||||||
return PreparedStellarStructureBlock<
|
return PreparedStellarStructureBlock<
|
||||||
Model, MaterialComponent, GravityMassBackend, Mode, GravityPolicy, StructurePolicy>{
|
Model, Discretization, MaterialComponent, GravityMassBackend, Mode, GravityPolicy, StructurePolicy>{
|
||||||
problem, std::move(structure)
|
problem, std::move(structure)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
module;
|
module;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -25,6 +27,65 @@ export namespace mean_field::seed {
|
|||||||
mfem::Vector values;
|
mfem::Vector values;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The public radial-projection extension boundary deliberately speaks in
|
||||||
|
* physical state names. A specification author supplies one small rule
|
||||||
|
* and opts in with
|
||||||
|
*
|
||||||
|
* using RadialProjection = seed::projection::Use<MyProjectionPhysics>;
|
||||||
|
*
|
||||||
|
* There is no registry ordinal and no model-combination specialization.
|
||||||
|
*/
|
||||||
|
struct RadialProjectionScales final {
|
||||||
|
dimensions::MassValue targetMass;
|
||||||
|
dimensions::LengthValue stellarRadius;
|
||||||
|
double bernoulliConstant;
|
||||||
|
double sphericalMomentOfInertia;
|
||||||
|
const mfem::Array<int> *surfaceCarrierRows;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RadialProjectionState final {
|
||||||
|
mfem::Vector density;
|
||||||
|
mfem::Vector surfaceShape;
|
||||||
|
mfem::Vector gravityGradient;
|
||||||
|
mfem::Vector gravityPotential;
|
||||||
|
mfem::Vector specificEnthalpy;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace projection {
|
||||||
|
template <typename Physics> struct Use final {
|
||||||
|
using PhysicsType = Physics;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* An explicit opt-in for a specification that leaves a radial seed unchanged. */
|
||||||
|
struct NoStateChange {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = true;
|
||||||
|
|
||||||
|
template <typename Specification, typename Model>
|
||||||
|
static void validate(
|
||||||
|
const Specification &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProfile &,
|
||||||
|
const StellarEquilibriumProjectionOptions &
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Specification, typename Model>
|
||||||
|
static void initialize(
|
||||||
|
const Specification &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProjectionScales &,
|
||||||
|
RadialProjectionState &,
|
||||||
|
mfem::Vector
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace projection
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct ProjectedRadialFields final {
|
struct ProjectedRadialFields final {
|
||||||
mfem::Vector density;
|
mfem::Vector density;
|
||||||
@@ -32,13 +93,13 @@ export namespace mean_field::seed {
|
|||||||
mfem::Vector gravityPotential;
|
mfem::Vector gravityPotential;
|
||||||
mfem::Vector specificEnthalpy;
|
mfem::Vector specificEnthalpy;
|
||||||
double bernoulliConstant;
|
double bernoulliConstant;
|
||||||
|
double sphericalMomentOfInertia;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] ProjectedRadialFields projectRadialFields(
|
[[nodiscard]] ProjectedRadialFields projectRadialFields(
|
||||||
const equilibrium::StellarDiscretization &discretization,
|
fem::FEM &finiteElementModel,
|
||||||
const RadialProfile &profile,
|
const RadialProfile &profile,
|
||||||
dimensions::MassValue targetMass,
|
dimensions::MassValue targetMass,
|
||||||
dimensions::PressureValue targetSurfacePressure,
|
|
||||||
const StellarEquilibriumProjectionOptions &options
|
const StellarEquilibriumProjectionOptions &options
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -52,18 +113,402 @@ export namespace mean_field::seed {
|
|||||||
}
|
}
|
||||||
destination = source;
|
destination = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UnavailableRadialProjectionPhysics final {
|
||||||
|
static constexpr bool registered = false;
|
||||||
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FixedTotalMassRadialProjectionPhysics final {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = true;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = true;
|
||||||
|
|
||||||
|
[[nodiscard]] static dimensions::MassValue targetMass(const models::FixedTotalMass &specification) {
|
||||||
|
return specification.targetMass();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void validate(
|
||||||
|
const models::FixedTotalMass &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProfile &,
|
||||||
|
const StellarEquilibriumProjectionOptions &
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void initialize(
|
||||||
|
const models::FixedTotalMass &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProjectionScales &scales,
|
||||||
|
RadialProjectionState &,
|
||||||
|
mfem::Vector coordinate
|
||||||
|
) {
|
||||||
|
if (coordinate.Size() != 1) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"A fixed-total-mass radial projection requires exactly one generated multiplier."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
coordinate(0) = scales.bernoulliConstant;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IsobaricRadialProjectionPhysics final {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = requires(
|
||||||
|
const Model &model,
|
||||||
|
const surface::Isobaric &condition
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
||||||
|
model.equationOfState(), condition.targetPressure()
|
||||||
|
)
|
||||||
|
} -> std::same_as<dimensions::SpecificEnthalpyValue>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void validate(
|
||||||
|
const surface::Isobaric &condition,
|
||||||
|
const Model &,
|
||||||
|
const RadialProfile &,
|
||||||
|
const StellarEquilibriumProjectionOptions &
|
||||||
|
) {
|
||||||
|
if (condition.targetPressure().value() != 0.0) {
|
||||||
|
throw std::invalid_argument("A Lane-Emden radial seed requires a zero-pressure isobaric surface.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void initialize(
|
||||||
|
const surface::Isobaric &condition,
|
||||||
|
const Model &model,
|
||||||
|
const RadialProjectionScales &scales,
|
||||||
|
RadialProjectionState &state,
|
||||||
|
mfem::Vector coordinate
|
||||||
|
) {
|
||||||
|
if (coordinate.Size() != 0) {
|
||||||
|
throw std::logic_error("An isobaric surface must not generate a radial-seed coordinate.");
|
||||||
|
}
|
||||||
|
if (scales.surfaceCarrierRows == nullptr) {
|
||||||
|
throw std::logic_error("An isobaric radial projection requires compiled surface-carrier rows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy =
|
||||||
|
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
||||||
|
model.equationOfState(), condition.targetPressure()
|
||||||
|
);
|
||||||
|
for (const int surfaceRow : *scales.surfaceCarrierRows) {
|
||||||
|
state.specificEnthalpy(surfaceRow) = requiredSurfaceEnthalpy.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FixedCentralDensityRadialProjectionPhysics final {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = true;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void validate(
|
||||||
|
const models::FixedCentralDensity &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProfile &,
|
||||||
|
const StellarEquilibriumProjectionOptions &
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void initialize(
|
||||||
|
const models::FixedCentralDensity &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProjectionScales &,
|
||||||
|
RadialProjectionState &,
|
||||||
|
mfem::Vector coordinate
|
||||||
|
) {
|
||||||
|
if (coordinate.Size() != 1) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"A fixed-central-density radial projection requires exactly one generated phase coordinate."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
coordinate(0) = 0.0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FixedAngularMomentumRadialProjectionPhysics final {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = true;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void validate(
|
||||||
|
const models::FixedAngularMomentum &,
|
||||||
|
const Model &,
|
||||||
|
const RadialProfile &,
|
||||||
|
const StellarEquilibriumProjectionOptions &
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static void initialize(
|
||||||
|
const models::FixedAngularMomentum &constraint,
|
||||||
|
const Model &,
|
||||||
|
const RadialProjectionScales &scales,
|
||||||
|
RadialProjectionState &,
|
||||||
|
mfem::Vector coordinate
|
||||||
|
) {
|
||||||
|
if (coordinate.Size() != 1) {
|
||||||
|
throw std::logic_error(
|
||||||
|
"A fixed-angular-momentum radial projection requires one angular-velocity coordinate."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double centerSquared = 0.0;
|
||||||
|
double centerAlongAxis = 0.0;
|
||||||
|
for (std::size_t component = 0; component < constraint.center().size(); ++component) {
|
||||||
|
centerSquared += constraint.center()[component] * constraint.center()[component];
|
||||||
|
centerAlongAxis += constraint.center()[component] * constraint.axis()[component];
|
||||||
|
}
|
||||||
|
const double parallelAxisCorrection =
|
||||||
|
scales.targetMass.value() * (centerSquared - centerAlongAxis * centerAlongAxis);
|
||||||
|
const double momentOfInertia = scales.sphericalMomentOfInertia + parallelAxisCorrection;
|
||||||
|
if (!std::isfinite(momentOfInertia) || momentOfInertia <= 0.0) {
|
||||||
|
throw std::runtime_error("The radial seed has no finite, positive axial moment of inertia.");
|
||||||
|
}
|
||||||
|
coordinate(0) = constraint.targetAngularMomentum().value() / momentOfInertia;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification> struct BuiltinRadialProjectionPhysics {
|
||||||
|
using Type = UnavailableRadialProjectionPhysics;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct BuiltinRadialProjectionPhysics<eos::Polytrope> {
|
||||||
|
using Type = projection::NoStateChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct BuiltinRadialProjectionPhysics<surface::Isobaric> {
|
||||||
|
using Type = IsobaricRadialProjectionPhysics;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct BuiltinRadialProjectionPhysics<models::FixedTotalMass> {
|
||||||
|
using Type = FixedTotalMassRadialProjectionPhysics;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct BuiltinRadialProjectionPhysics<models::FixedCentralDensity> {
|
||||||
|
using Type = FixedCentralDensityRadialProjectionPhysics;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct BuiltinRadialProjectionPhysics<models::FixedAngularMomentum> {
|
||||||
|
using Type = FixedAngularMomentumRadialProjectionPhysics;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate> struct UnwrapRadialProjectionPhysics {
|
||||||
|
using Type = UnavailableRadialProjectionPhysics;
|
||||||
|
static constexpr bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Physics> struct UnwrapRadialProjectionPhysics<projection::Use<Physics>> {
|
||||||
|
using Type = Physics;
|
||||||
|
static constexpr bool valid = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification, typename = void> struct SelectRadialProjectionPhysics {
|
||||||
|
using Type = typename BuiltinRadialProjectionPhysics<Specification>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
struct SelectRadialProjectionPhysics<Specification, std::void_t<typename Specification::RadialProjection>> {
|
||||||
|
private:
|
||||||
|
using Wrapped = UnwrapRadialProjectionPhysics<typename Specification::RadialProjection>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Type = std::conditional_t<Wrapped::valid, typename Wrapped::Type, UnavailableRadialProjectionPhysics>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Physics> [[nodiscard]] consteval bool radialProjectionPhysicsRegistered() {
|
||||||
|
if constexpr (requires {
|
||||||
|
{ Physics::registered } -> std::convertible_to<bool>;
|
||||||
|
}) {
|
||||||
|
return static_cast<bool>(Physics::registered);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Physics> [[nodiscard]] consteval bool radialProjectionPhysicsProvidesMass() {
|
||||||
|
if constexpr (requires {
|
||||||
|
{ Physics::providesRadialMass } -> std::convertible_to<bool>;
|
||||||
|
}) {
|
||||||
|
return static_cast<bool>(Physics::providesRadialMass);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Specification, typename Model>
|
||||||
|
[[nodiscard]] consteval bool radialProjectionPhysicsIsComplete() {
|
||||||
|
using Physics = typename SelectRadialProjectionPhysics<Specification>::Type;
|
||||||
|
if constexpr (!radialProjectionPhysicsRegistered<Physics>()) {
|
||||||
|
return false;
|
||||||
|
} else if constexpr (!requires {
|
||||||
|
{ Physics::template supports<Model> } -> std::convertible_to<bool>;
|
||||||
|
}) {
|
||||||
|
return false;
|
||||||
|
} else if constexpr (!static_cast<bool>(Physics::template supports<Model>)) {
|
||||||
|
return false;
|
||||||
|
} else if constexpr (!requires(
|
||||||
|
const Specification &specification,
|
||||||
|
const Model &model,
|
||||||
|
const RadialProfile &profile,
|
||||||
|
const StellarEquilibriumProjectionOptions &options,
|
||||||
|
const RadialProjectionScales &scales,
|
||||||
|
RadialProjectionState &state,
|
||||||
|
mfem::Vector coordinate
|
||||||
|
) {
|
||||||
|
Physics::validate(specification, model, profile, options);
|
||||||
|
Physics::initialize(specification, model, scales, state, coordinate);
|
||||||
|
}) {
|
||||||
|
return false;
|
||||||
|
} else if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
|
||||||
|
return requires(const Specification &specification) {
|
||||||
|
{ Physics::targetMass(specification) } -> std::same_as<dimensions::MassValue>;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification, models::GeneratedStateKind Kind>
|
||||||
|
struct RadialProjectionCoordinateTerm;
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct RadialProjectionCoordinateTerm<Specification, models::GeneratedStateKind::multiplier> final {
|
||||||
|
using value = utils::blocks::generated_value_block<models::MultiplierFor<Specification>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct RadialProjectionCoordinateTerm<Specification, models::GeneratedStateKind::physical_coordinate> final {
|
||||||
|
using value = utils::blocks::generated_value_block<models::PhysicalCoordinateFor<Specification>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <models::ModelSpecification Specification>
|
||||||
|
struct RadialProjectionCoordinateTerm<Specification, models::GeneratedStateKind::solver_border> final {
|
||||||
|
using value = utils::blocks::generated_value_block<models::BorderFor<Specification>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Model, typename SpecificationSet> struct CompileRadialProjection;
|
||||||
|
|
||||||
|
template <model::StellarModelType Model, models::ModelSpecification... Specifications>
|
||||||
|
struct CompileRadialProjection<Model, models::detail::SpecificationSetStorage<Specifications...>> {
|
||||||
|
using ModelType = std::remove_cvref_t<Model>;
|
||||||
|
|
||||||
|
static constexpr std::size_t radialMassProviderCount =
|
||||||
|
(std::size_t{0} + ... +
|
||||||
|
(radialProjectionPhysicsProvidesMass<
|
||||||
|
typename SelectRadialProjectionPhysics<Specifications>::Type>()
|
||||||
|
? std::size_t{1}
|
||||||
|
: std::size_t{0}));
|
||||||
|
static constexpr bool complete = radialMassProviderCount == 1 &&
|
||||||
|
(radialProjectionPhysicsIsComplete<Specifications, ModelType>() && ...);
|
||||||
|
|
||||||
|
[[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model) requires complete {
|
||||||
|
dimensions::MassValue result{0.0};
|
||||||
|
([&] {
|
||||||
|
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
||||||
|
if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
|
||||||
|
result = Physics::targetMass(model.template specification<Specifications>());
|
||||||
|
}
|
||||||
|
}(), ...);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validate(
|
||||||
|
const ModelType &model,
|
||||||
|
const RadialProfile &profile,
|
||||||
|
const StellarEquilibriumProjectionOptions &options
|
||||||
|
) requires complete {
|
||||||
|
([&] {
|
||||||
|
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
||||||
|
Physics::validate(model.template specification<Specifications>(), model, profile, options);
|
||||||
|
}(), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename StateView>
|
||||||
|
static void initialize(
|
||||||
|
const ModelType &model,
|
||||||
|
const RadialProjectionScales &scales,
|
||||||
|
RadialProjectionState &state,
|
||||||
|
const StateView &stateView
|
||||||
|
) requires complete {
|
||||||
|
([&] {
|
||||||
|
using Contribution = models::SpecificationContribution<Specifications>;
|
||||||
|
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
|
||||||
|
if constexpr (Contribution::generatedValueArity == 0) {
|
||||||
|
Physics::initialize(
|
||||||
|
model.template specification<Specifications>(), model, scales, state, mfem::Vector{}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
static_assert(
|
||||||
|
Contribution::generatedValueArity == 1,
|
||||||
|
"Radial projection currently requires each specification contribution to generate at "
|
||||||
|
"most one scalar coordinate."
|
||||||
|
);
|
||||||
|
using Term = RadialProjectionCoordinateTerm<Specifications, Contribution::generatedStateKind>;
|
||||||
|
Physics::initialize(
|
||||||
|
model.template specification<Specifications>(), model, scales, state,
|
||||||
|
stateView.block(Term{})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}(), ...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate, bool = model::StellarModelType<Candidate>>
|
||||||
|
struct RadialProjectionCompilationAudit {
|
||||||
|
static constexpr bool complete = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
struct RadialProjectionCompilationAudit<Candidate, true>
|
||||||
|
: CompileRadialProjection<Candidate, typename Candidate::SpecificationTypes> { };
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <equilibrium::StellarEquilibriumModel Model>
|
template <typename Candidate>
|
||||||
|
inline constexpr bool radialProjectionIsCompilable =
|
||||||
|
detail::RadialProjectionCompilationAudit<std::remove_cvref_t<Candidate>>::complete;
|
||||||
|
|
||||||
|
template <typename Candidate>
|
||||||
|
concept RadialProfileProjectableModel =
|
||||||
|
model::StellarModelType<Candidate> && radialProjectionIsCompilable<std::remove_cvref_t<Candidate>>;
|
||||||
|
|
||||||
|
template <equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization>
|
||||||
|
requires RadialProfileProjectableModel<Model>
|
||||||
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
|
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model> &problem,
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
||||||
const RadialProfile &profile,
|
const RadialProfile &profile,
|
||||||
const StellarEquilibriumProjectionOptions &options = {}
|
const StellarEquilibriumProjectionOptions &options = {}
|
||||||
) {
|
) {
|
||||||
|
using Projection = detail::CompileRadialProjection<
|
||||||
|
std::remove_cvref_t<Model>,
|
||||||
|
typename std::remove_cvref_t<Model>::SpecificationTypes>;
|
||||||
|
const auto &stellarModel = problem.GetStellarModel();
|
||||||
|
Projection::validate(stellarModel, profile, options);
|
||||||
|
const dimensions::MassValue targetMass = Projection::targetMass(stellarModel);
|
||||||
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
|
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
|
||||||
problem.GetDiscretization(), profile,
|
problem.GetDiscretization().finiteElementModel(), profile, targetMass, options
|
||||||
problem.GetStellarModel().template specification<models::FixedTotalMass>().targetMass(),
|
|
||||||
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure(), options
|
|
||||||
);
|
);
|
||||||
|
|
||||||
mfem::Vector values(problem.StateSize());
|
mfem::Vector values(problem.StateSize());
|
||||||
@@ -89,45 +534,40 @@ export namespace mean_field::seed {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Projection of a continuous spherical profile onto a faceted
|
* Each specification now initializes only its inferred contribution.
|
||||||
* reference mesh generally leaves a small trace error on the physical
|
* In particular, the surface rule imposes the exact carrier trace and
|
||||||
* surface. The pressure condition replaces these carrier rows in the
|
* generated constraints obtain their coordinate by type, not by a
|
||||||
* compiled equilibrium problem, so impose its required carrier value
|
* hard-coded whole-model layout.
|
||||||
* exactly after bulk projection instead of treating that geometric
|
|
||||||
* mismatch as part of the initial residual.
|
|
||||||
*/
|
*/
|
||||||
mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
|
RadialProjectionState projectedState{
|
||||||
const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy =
|
.density = stateView.block(utils::blocks::density_field.mass_term),
|
||||||
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
|
.surfaceShape = stateView.block(utils::blocks::surface_deformation_field.parameters_term),
|
||||||
problem.GetStellarModel().template specification<eos::Polytrope>(),
|
.gravityGradient = stateView.block(utils::blocks::gravity_field.gradient_term),
|
||||||
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure()
|
.gravityPotential = stateView.block(utils::blocks::gravity_field.poisson_term),
|
||||||
);
|
.specificEnthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term)
|
||||||
for (const int surfaceRow : problem.GetPressureSurfaceRows().reduced_dofs()) {
|
};
|
||||||
enthalpy(surfaceRow) = requiredSurfaceEnthalpy.value();
|
const RadialProjectionScales scales{
|
||||||
}
|
.targetMass = targetMass,
|
||||||
|
.stellarRadius = profile.stellarRadius,
|
||||||
mfem::Vector fixedMassCoordinate =
|
.bernoulliConstant = fields.bernoulliConstant,
|
||||||
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
|
.sphericalMomentOfInertia = fields.sphericalMomentOfInertia,
|
||||||
if (fixedMassCoordinate.Size() != 1) {
|
.surfaceCarrierRows = &problem.GetPressureSurfaceRows().reduced_dofs()
|
||||||
throw std::invalid_argument("FixedTotalMass must generate exactly one equilibrium-state coordinate.");
|
};
|
||||||
}
|
Projection::initialize(stellarModel, scales, projectedState, stateView);
|
||||||
fixedMassCoordinate(0) = fields.bernoulliConstant;
|
|
||||||
|
|
||||||
if constexpr (std::remove_cvref_t<Model>::template containsSpecification<models::FixedCentralDensity>) {
|
|
||||||
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {.values = std::move(values)};
|
return {.values = std::move(values)};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
equilibrium::StellarEquilibriumModel Model,
|
equilibrium::StellarEquilibriumModel Model,
|
||||||
|
equilibrium::StellarDiscretizationType Discretization,
|
||||||
typename Strategy>
|
typename Strategy>
|
||||||
requires RadialSeedStrategyFor<
|
requires RadialSeedStrategyFor<
|
||||||
Strategy,
|
Strategy,
|
||||||
typename equilibrium::StellarEquilibriumProblem<Model>::ModelType>
|
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::ModelType> &&
|
||||||
|
RadialProfileProjectableModel<Model>
|
||||||
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
|
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
|
||||||
const equilibrium::StellarEquilibriumProblem<Model> &problem,
|
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
|
||||||
const Strategy &strategy,
|
const Strategy &strategy,
|
||||||
const StellarEquilibriumProjectionOptions &options = {}
|
const StellarEquilibriumProjectionOptions &options = {}
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -136,6 +136,19 @@ export namespace mean_field::utils::blocks {
|
|||||||
static inline constexpr central_value central_value_term{};
|
static inline constexpr central_value central_value_term{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct fixed_angular_momentum final : field {
|
||||||
|
using SpecificationType = models::FixedAngularMomentum;
|
||||||
|
using CoordinateType = models::PhysicalCoordinateFor<SpecificationType>;
|
||||||
|
using ResidualType = models::ResidualFor<SpecificationType>;
|
||||||
|
|
||||||
|
struct angular_velocity final : term {
|
||||||
|
using value = generated_value_block<CoordinateType>;
|
||||||
|
using residual = generated_residual_block<ResidualType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline constexpr angular_velocity angular_velocity_term{};
|
||||||
|
};
|
||||||
|
|
||||||
// Compatibility name for the current barotropic formulation. The scalar
|
// Compatibility name for the current barotropic formulation. The scalar
|
||||||
// is generated by FixedTotalMass; its realization in this formulation is
|
// is generated by FixedTotalMass; its realization in this formulation is
|
||||||
// the historical C coordinate.
|
// the historical C coordinate.
|
||||||
@@ -148,6 +161,7 @@ export namespace mean_field::utils::blocks {
|
|||||||
inline constexpr enthalpy enthalpy_field{};
|
inline constexpr enthalpy enthalpy_field{};
|
||||||
inline constexpr fixed_total_mass fixed_total_mass_constraint{};
|
inline constexpr fixed_total_mass fixed_total_mass_constraint{};
|
||||||
inline constexpr fixed_central_density fixed_central_density_phase{};
|
inline constexpr fixed_central_density fixed_central_density_phase{};
|
||||||
|
inline constexpr fixed_angular_momentum fixed_angular_momentum_constraint{};
|
||||||
inline constexpr barotropic_constant barotropic_constant_field{};
|
inline constexpr barotropic_constant barotropic_constant_field{};
|
||||||
|
|
||||||
template <typename... Types> struct type_list {
|
template <typename... Types> struct type_list {
|
||||||
@@ -516,6 +530,7 @@ export namespace mean_field::utils::blocks {
|
|||||||
enthalpy::specific::value,
|
enthalpy::specific::value,
|
||||||
gravity::poisson::value,
|
gravity::poisson::value,
|
||||||
surface_deformation::parameters::value,
|
surface_deformation::parameters::value,
|
||||||
|
density::mass::value,
|
||||||
barotropic_constant::mass_normalization::value>,
|
barotropic_constant::mass_normalization::value>,
|
||||||
block_row<
|
block_row<
|
||||||
barotropic_constant::mass_normalization::residual,
|
barotropic_constant::mass_normalization::residual,
|
||||||
@@ -569,6 +584,7 @@ export namespace mean_field::utils::blocks {
|
|||||||
enthalpy::specific::value,
|
enthalpy::specific::value,
|
||||||
gravity::poisson::value,
|
gravity::poisson::value,
|
||||||
surface_deformation::parameters::value,
|
surface_deformation::parameters::value,
|
||||||
|
density::mass::value,
|
||||||
barotropic_constant::mass_normalization::value,
|
barotropic_constant::mass_normalization::value,
|
||||||
fixed_central_density::central_value::value>,
|
fixed_central_density::central_value::value>,
|
||||||
block_row<
|
block_row<
|
||||||
|
|||||||
2182
tests/extensions/fixed_magnetic_specific_energy.cpp
Normal file
2182
tests/extensions/fixed_magnetic_specific_energy.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,8 @@ TEST_CASE(
|
|||||||
|
|
||||||
STATIC_REQUIRE(field::FieldTag<field::BarotropicConstant>);
|
STATIC_REQUIRE(field::FieldTag<field::BarotropicConstant>);
|
||||||
|
|
||||||
|
STATIC_REQUIRE(field::FieldTag<field::AngularVelocity>);
|
||||||
|
|
||||||
STATIC_REQUIRE_FALSE(field::FieldTag<field_registry_test_utils::MissingSupportField>);
|
STATIC_REQUIRE_FALSE(field::FieldTag<field_registry_test_utils::MissingSupportField>);
|
||||||
|
|
||||||
STATIC_REQUIRE_FALSE(field::FieldTag<field_registry_test_utils::InvalidSupportField>);
|
STATIC_REQUIRE_FALSE(field::FieldTag<field_registry_test_utils::InvalidSupportField>);
|
||||||
@@ -113,6 +115,8 @@ TEST_CASE(
|
|||||||
|
|
||||||
STATIC_REQUIRE(field::NonSpatialField<field::BarotropicConstant>);
|
STATIC_REQUIRE(field::NonSpatialField<field::BarotropicConstant>);
|
||||||
|
|
||||||
|
STATIC_REQUIRE(field::NonSpatialField<field::AngularVelocity>);
|
||||||
|
|
||||||
STATIC_REQUIRE(std::same_as<field::FieldDomainT<field::Density>, domain::Stellar>);
|
STATIC_REQUIRE(std::same_as<field::FieldDomainT<field::Density>, domain::Stellar>);
|
||||||
|
|
||||||
STATIC_REQUIRE(std::same_as<field::FieldDomainT<field::Enthalpy>, domain::Stellar>);
|
STATIC_REQUIRE(std::same_as<field::FieldDomainT<field::Enthalpy>, domain::Stellar>);
|
||||||
@@ -123,6 +127,8 @@ TEST_CASE(
|
|||||||
|
|
||||||
STATIC_REQUIRE(std::same_as<field::FieldSupportT<field::BarotropicConstant>, field::NonSpatialSupport>);
|
STATIC_REQUIRE(std::same_as<field::FieldSupportT<field::BarotropicConstant>, field::NonSpatialSupport>);
|
||||||
|
|
||||||
|
STATIC_REQUIRE(std::same_as<field::FieldSupportT<field::AngularVelocity>, field::NonSpatialSupport>);
|
||||||
|
|
||||||
CHECK(true);
|
CHECK(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -23,10 +25,18 @@ namespace {
|
|||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::models::FixedTotalMass>;
|
mean_field::models::FixedTotalMass>;
|
||||||
|
|
||||||
|
using AngularMomentumPolytropicMassSpecifications = mean_field::models::SpecificationSet<
|
||||||
|
mean_field::models::FixedAngularMomentum,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::models::FixedTotalMass>;
|
||||||
|
|
||||||
using PolytropicMassModel = mean_field::model::StellarModel<PolytropicMassSpecifications>;
|
using PolytropicMassModel = mean_field::model::StellarModel<PolytropicMassSpecifications>;
|
||||||
using PermutedPolytropicMassModel = mean_field::model::StellarModel<PermutedPolytropicMassSpecifications>;
|
using PermutedPolytropicMassModel = mean_field::model::StellarModel<PermutedPolytropicMassSpecifications>;
|
||||||
using CentralDensityPolytropicMassModel =
|
using CentralDensityPolytropicMassModel =
|
||||||
mean_field::model::StellarModel<CentralDensityPolytropicMassSpecifications>;
|
mean_field::model::StellarModel<CentralDensityPolytropicMassSpecifications>;
|
||||||
|
using AngularMomentumPolytropicMassModel =
|
||||||
|
mean_field::model::StellarModel<AngularMomentumPolytropicMassSpecifications>;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -36,11 +46,13 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::eos::Polytrope>);
|
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::eos::Polytrope>);
|
||||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::surface::ConstantPressureSurface>);
|
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::surface::ConstantPressureSurface>);
|
||||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedTotalMass>);
|
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedTotalMass>);
|
||||||
|
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedAngularMomentum>);
|
||||||
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedCentralDensity>);
|
STATIC_CHECK(mean_field::models::ModelSpecification<mean_field::models::FixedCentralDensity>);
|
||||||
STATIC_CHECK_FALSE(mean_field::models::ModelSpecification<NotAModelSpecification>);
|
STATIC_CHECK_FALSE(mean_field::models::ModelSpecification<NotAModelSpecification>);
|
||||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::eos::Polytrope>);
|
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::eos::Polytrope>);
|
||||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::surface::ConstantPressureSurface>);
|
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::surface::ConstantPressureSurface>);
|
||||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedTotalMass>);
|
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedTotalMass>);
|
||||||
|
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedAngularMomentum>);
|
||||||
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedCentralDensity>);
|
STATIC_CHECK(mean_field::models::ResolvedModelSpecification<mean_field::models::FixedCentralDensity>);
|
||||||
|
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
@@ -61,8 +73,10 @@ TEST_CASE(
|
|||||||
|
|
||||||
STATIC_CHECK(std::same_as<PolytropicMassModel, PermutedPolytropicMassModel>);
|
STATIC_CHECK(std::same_as<PolytropicMassModel, PermutedPolytropicMassModel>);
|
||||||
STATIC_CHECK_FALSE(std::same_as<PolytropicMassModel, CentralDensityPolytropicMassModel>);
|
STATIC_CHECK_FALSE(std::same_as<PolytropicMassModel, CentralDensityPolytropicMassModel>);
|
||||||
|
STATIC_CHECK_FALSE(std::same_as<PolytropicMassModel, AngularMomentumPolytropicMassModel>);
|
||||||
STATIC_CHECK(mean_field::model::StellarModelType<PolytropicMassModel>);
|
STATIC_CHECK(mean_field::model::StellarModelType<PolytropicMassModel>);
|
||||||
STATIC_CHECK(mean_field::model::StellarModelType<CentralDensityPolytropicMassModel>);
|
STATIC_CHECK(mean_field::model::StellarModelType<CentralDensityPolytropicMassModel>);
|
||||||
|
STATIC_CHECK(mean_field::model::StellarModelType<AngularMomentumPolytropicMassModel>);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -81,6 +95,22 @@ TEST_CASE(
|
|||||||
typename MassSignature::GeneratedValues>
|
typename MassSignature::GeneratedValues>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
using AngularMomentumSignature = AngularMomentumPolytropicMassModel::OperatorSignature;
|
||||||
|
|
||||||
|
STATIC_CHECK(AngularMomentumSignature::generatedValueArity == 2);
|
||||||
|
STATIC_CHECK(AngularMomentumSignature::generatedResidualArity == 2);
|
||||||
|
STATIC_CHECK(AngularMomentumSignature::symbolicallySquare);
|
||||||
|
STATIC_CHECK(
|
||||||
|
mean_field::models::modelTypeListContains<
|
||||||
|
mean_field::models::PhysicalCoordinateFor<mean_field::models::FixedAngularMomentum>,
|
||||||
|
typename AngularMomentumSignature::GeneratedValues>
|
||||||
|
);
|
||||||
|
STATIC_CHECK(
|
||||||
|
mean_field::models::modelTypeListContains<
|
||||||
|
mean_field::models::ResidualFor<mean_field::models::FixedAngularMomentum>,
|
||||||
|
typename AngularMomentumSignature::GeneratedResiduals>
|
||||||
|
);
|
||||||
|
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
mean_field::models::modelTypeListContains<
|
mean_field::models::modelTypeListContains<
|
||||||
mean_field::models::ResidualFor<mean_field::models::FixedTotalMass>,
|
mean_field::models::ResidualFor<mean_field::models::FixedTotalMass>,
|
||||||
@@ -106,6 +136,76 @@ TEST_CASE(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Fixed Angular Momentum Compiles A Physical Angular Velocity And Invariant Row",
|
||||||
|
tags::model_specification_type_contract
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using Request = models::FixedAngularMomentumLayoutRequest;
|
||||||
|
using Form = operators::CompiledStellarEquilibriumForm<AngularMomentumPolytropicMassModel>;
|
||||||
|
using Jacobian = operators::CompiledStellarEquilibriumJacobianForm<AngularMomentumPolytropicMassModel>;
|
||||||
|
using AngularValue = utils::blocks::fixed_angular_momentum::angular_velocity::value;
|
||||||
|
using AngularResidual = utils::blocks::fixed_angular_momentum::angular_velocity::residual;
|
||||||
|
|
||||||
|
STATIC_CHECK(models::ConstraintLayoutRequestType<Request>);
|
||||||
|
STATIC_CHECK(models::CompiledConstraint<models::CompiledFixedAngularMomentum>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename Request::GeneratedValueType,
|
||||||
|
models::PhysicalCoordinateFor<models::FixedAngularMomentum>>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename AngularValue::GeneratedType,
|
||||||
|
models::PhysicalCoordinateFor<models::FixedAngularMomentum>>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename models::CompiledFixedAngularMomentum::AngularVelocityField,
|
||||||
|
field::AngularVelocity>);
|
||||||
|
STATIC_CHECK(Form::value_block_count == 7);
|
||||||
|
STATIC_CHECK(Form::residual_block_count == 7);
|
||||||
|
STATIC_CHECK(Request::valueBlock<Form>().index == 6);
|
||||||
|
STATIC_CHECK(Request::residualBlock<Form>().index == 6);
|
||||||
|
STATIC_CHECK(utils::blocks::valid_jacobian_form<Form, Jacobian>);
|
||||||
|
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
||||||
|
AngularResidual,
|
||||||
|
utils::blocks::density::mass::value,
|
||||||
|
Jacobian>);
|
||||||
|
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
||||||
|
AngularResidual,
|
||||||
|
utils::blocks::surface_deformation::parameters::value,
|
||||||
|
Jacobian>);
|
||||||
|
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<AngularResidual, AngularValue, Jacobian>);
|
||||||
|
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
||||||
|
utils::blocks::surface_deformation::shape_equilibrium::residual,
|
||||||
|
AngularValue,
|
||||||
|
Jacobian>);
|
||||||
|
STATIC_CHECK(utils::blocks::has_jacobian_coupling_v<
|
||||||
|
utils::blocks::enthalpy::specific::residual,
|
||||||
|
AngularValue,
|
||||||
|
Jacobian>);
|
||||||
|
STATIC_CHECK_FALSE(utils::blocks::has_jacobian_coupling_v<
|
||||||
|
utils::blocks::gravity::poisson::residual,
|
||||||
|
AngularValue,
|
||||||
|
Jacobian>);
|
||||||
|
|
||||||
|
const integral::FixedAngularMomentum specification({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{2.75},
|
||||||
|
.axis = {0.0, 3.0, 4.0},
|
||||||
|
.center = {0.25, -0.5, 0.75}
|
||||||
|
});
|
||||||
|
const models::CompiledFixedAngularMomentum compiled = models::compileConstraint(specification);
|
||||||
|
CHECK(compiled.targetAngularMomentum() == dimensions::AngularMomentumValue{2.75});
|
||||||
|
CHECK(std::abs(compiled.specification().axis()[0]) < 1.0e-15);
|
||||||
|
CHECK(std::abs(compiled.specification().axis()[1] - 0.6) < 1.0e-15);
|
||||||
|
CHECK(std::abs(compiled.specification().axis()[2] - 0.8) < 1.0e-15);
|
||||||
|
CHECK(compiled.specification().center() == std::array<double, 3>{0.25, -0.5, 0.75});
|
||||||
|
|
||||||
|
const physics::RigidRotation rotation = compiled.makeRotation(1.5);
|
||||||
|
CHECK(std::abs(rotation.angular_velocity()(0)) < 1.0e-15);
|
||||||
|
CHECK(std::abs(rotation.angular_velocity()(1) - 0.9) < 1.0e-15);
|
||||||
|
CHECK(std::abs(rotation.angular_velocity()(2) - 1.2) < 1.0e-15);
|
||||||
|
CHECK(rotation.center()(0) == 0.25);
|
||||||
|
CHECK(rotation.center()(1) == -0.5);
|
||||||
|
CHECK(rotation.center()(2) == 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Fixed Total Mass Compiles Its Generated Multiplier And Canonical Residual Row",
|
"Fixed Total Mass Compiles Its Generated Multiplier And Canonical Residual Row",
|
||||||
tags::model_specification_type_contract
|
tags::model_specification_type_contract
|
||||||
@@ -146,6 +246,8 @@ TEST_CASE(
|
|||||||
constexpr auto surface =
|
constexpr auto surface =
|
||||||
mean_field::models::specificationDescriptor<mean_field::surface::ConstantPressureSurface>();
|
mean_field::models::specificationDescriptor<mean_field::surface::ConstantPressureSurface>();
|
||||||
constexpr auto mass = mean_field::models::specificationDescriptor<mean_field::models::FixedTotalMass>();
|
constexpr auto mass = mean_field::models::specificationDescriptor<mean_field::models::FixedTotalMass>();
|
||||||
|
constexpr auto angularMomentum =
|
||||||
|
mean_field::models::specificationDescriptor<mean_field::models::FixedAngularMomentum>();
|
||||||
constexpr auto centralDensity =
|
constexpr auto centralDensity =
|
||||||
mean_field::models::specificationDescriptor<mean_field::models::FixedCentralDensity>();
|
mean_field::models::specificationDescriptor<mean_field::models::FixedCentralDensity>();
|
||||||
|
|
||||||
@@ -162,6 +264,11 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(mass.generatedValueArity == 1);
|
STATIC_CHECK(mass.generatedValueArity == 1);
|
||||||
STATIC_CHECK(mass.generatedResidualArity == 1);
|
STATIC_CHECK(mass.generatedResidualArity == 1);
|
||||||
|
|
||||||
|
STATIC_CHECK(angularMomentum.name == "FixedAngularMomentum");
|
||||||
|
STATIC_CHECK(angularMomentum.role == mean_field::models::SpecificationRole::invariant);
|
||||||
|
STATIC_CHECK(angularMomentum.generatedValueArity == 1);
|
||||||
|
STATIC_CHECK(angularMomentum.generatedResidualArity == 1);
|
||||||
|
|
||||||
STATIC_CHECK(centralDensity.name == "FixedCentralDensity");
|
STATIC_CHECK(centralDensity.name == "FixedCentralDensity");
|
||||||
STATIC_CHECK(centralDensity.role == mean_field::models::SpecificationRole::phase_condition);
|
STATIC_CHECK(centralDensity.role == mean_field::models::SpecificationRole::phase_condition);
|
||||||
STATIC_CHECK(centralDensity.generatedValueArity == 1);
|
STATIC_CHECK(centralDensity.generatedValueArity == 1);
|
||||||
@@ -174,9 +281,15 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
const mean_field::models::FixedTotalMass mass{mean_field::dimensions::MassValue{1.25}};
|
const mean_field::models::FixedTotalMass mass{mean_field::dimensions::MassValue{1.25}};
|
||||||
const mean_field::models::FixedCentralDensity centralDensity{mean_field::eos::DensityValue{2.5}};
|
const mean_field::models::FixedCentralDensity centralDensity{mean_field::eos::DensityValue{2.5}};
|
||||||
|
const mean_field::models::FixedAngularMomentum angularMomentum{
|
||||||
|
mean_field::dimensions::AngularMomentumValue{0.75}
|
||||||
|
};
|
||||||
|
|
||||||
CHECK(mass.targetMass() == mean_field::dimensions::MassValue{1.25});
|
CHECK(mass.targetMass() == mean_field::dimensions::MassValue{1.25});
|
||||||
CHECK(centralDensity.targetDensity() == mean_field::eos::DensityValue{2.5});
|
CHECK(centralDensity.targetDensity() == mean_field::eos::DensityValue{2.5});
|
||||||
|
CHECK(angularMomentum.targetAngularMomentum() == mean_field::dimensions::AngularMomentumValue{0.75});
|
||||||
|
CHECK(angularMomentum.axis() == std::array<double, 3>{0.0, 0.0, 1.0});
|
||||||
|
CHECK(angularMomentum.center() == std::array<double, 3>{0.0, 0.0, 0.0});
|
||||||
|
|
||||||
CHECK_THROWS_AS(mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{0.0}}, std::invalid_argument);
|
CHECK_THROWS_AS(mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{0.0}}, std::invalid_argument);
|
||||||
CHECK_THROWS_AS(mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{-1.0}}, std::invalid_argument);
|
CHECK_THROWS_AS(mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{-1.0}}, std::invalid_argument);
|
||||||
@@ -184,6 +297,31 @@ TEST_CASE(
|
|||||||
mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{std::numeric_limits<double>::infinity()}},
|
mean_field::models::FixedTotalMass{mean_field::dimensions::MassValue{std::numeric_limits<double>::infinity()}},
|
||||||
std::invalid_argument
|
std::invalid_argument
|
||||||
);
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
mean_field::models::FixedAngularMomentum{mean_field::dimensions::AngularMomentumValue{-1.0}},
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
mean_field::models::FixedAngularMomentum{
|
||||||
|
mean_field::dimensions::AngularMomentumValue{std::numeric_limits<double>::infinity()}
|
||||||
|
},
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
mean_field::models::FixedAngularMomentum({
|
||||||
|
.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
|
||||||
|
.axis = {0.0, 0.0, 0.0}
|
||||||
|
}),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
mean_field::models::FixedAngularMomentum({
|
||||||
|
.Jtotal = mean_field::dimensions::AngularMomentumValue{1.0},
|
||||||
|
.axis = {0.0, 0.0, 1.0},
|
||||||
|
.center = {0.0, std::numeric_limits<double>::quiet_NaN(), 0.0}
|
||||||
|
}),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
|
||||||
CHECK_THROWS_AS(mean_field::models::FixedCentralDensity{mean_field::eos::DensityValue{0.0}}, std::invalid_argument);
|
CHECK_THROWS_AS(mean_field::models::FixedCentralDensity{mean_field::eos::DensityValue{0.0}}, std::invalid_argument);
|
||||||
CHECK_THROWS_AS(
|
CHECK_THROWS_AS(
|
||||||
|
|||||||
1389
tests/models/physics_specification_frontend.cpp
Normal file
1389
tests/models/physics_specification_frontend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
@@ -19,6 +20,101 @@ namespace {
|
|||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
mean_field::surface::Isobaric,
|
mean_field::surface::Isobaric,
|
||||||
mean_field::integral::FixedTotalMass>>;
|
mean_field::integral::FixedTotalMass>>;
|
||||||
|
|
||||||
|
class MoveOnlyEquationOfState final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
int marker;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition =
|
||||||
|
mean_field::eos::ConstitutiveLaw<MoveOnlyEquationOfState, "MoveOnlyEquationOfState">;
|
||||||
|
|
||||||
|
explicit MoveOnlyEquationOfState(const Parameters parameters)
|
||||||
|
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveOnlyEquationOfState(const MoveOnlyEquationOfState &) = delete;
|
||||||
|
MoveOnlyEquationOfState &operator=(const MoveOnlyEquationOfState &) = delete;
|
||||||
|
MoveOnlyEquationOfState(MoveOnlyEquationOfState &&) noexcept = default;
|
||||||
|
MoveOnlyEquationOfState &operator=(MoveOnlyEquationOfState &&) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||||
|
return m_marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<int> m_marker;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoveOnlySurfaceCondition final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
int marker;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition =
|
||||||
|
mean_field::surface::BoundaryCondition<MoveOnlySurfaceCondition, "MoveOnlySurfaceCondition">;
|
||||||
|
|
||||||
|
explicit MoveOnlySurfaceCondition(const Parameters parameters)
|
||||||
|
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveOnlySurfaceCondition(const MoveOnlySurfaceCondition &) = delete;
|
||||||
|
MoveOnlySurfaceCondition &operator=(const MoveOnlySurfaceCondition &) = delete;
|
||||||
|
MoveOnlySurfaceCondition(MoveOnlySurfaceCondition &&) noexcept = default;
|
||||||
|
MoveOnlySurfaceCondition &operator=(MoveOnlySurfaceCondition &&) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||||
|
return m_marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<int> m_marker;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoveOnlyIntegralConstraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
int marker;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ScalarDescription = mean_field::stellar::ScalarConstraint<
|
||||||
|
mean_field::dimensions::quantity::Mass,
|
||||||
|
mean_field::dimensions::quantity::SpecificEnergy,
|
||||||
|
mean_field::dimensions::quantity::Mass,
|
||||||
|
"move_only_integral.multiplier",
|
||||||
|
"C_move",
|
||||||
|
"move_only_integral.residual",
|
||||||
|
"R_move">;
|
||||||
|
using TargetValue = typename ScalarDescription::TargetValue;
|
||||||
|
using ModelDefinition = mean_field::integral::FixedScalarWithMultiplier<
|
||||||
|
MoveOnlyIntegralConstraint,
|
||||||
|
"MoveOnlyIntegralConstraint",
|
||||||
|
mean_field::stellar::Reads<mean_field::stellar::state::Density>,
|
||||||
|
mean_field::stellar::Changes<mean_field::stellar::equation::HydrostaticBalance>,
|
||||||
|
ScalarDescription>;
|
||||||
|
|
||||||
|
explicit MoveOnlyIntegralConstraint(const Parameters parameters)
|
||||||
|
: m_marker(std::make_unique<int>(parameters.marker)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveOnlyIntegralConstraint(const MoveOnlyIntegralConstraint &) = delete;
|
||||||
|
MoveOnlyIntegralConstraint &operator=(const MoveOnlyIntegralConstraint &) = delete;
|
||||||
|
MoveOnlyIntegralConstraint(MoveOnlyIntegralConstraint &&) noexcept = default;
|
||||||
|
MoveOnlyIntegralConstraint &operator=(MoveOnlyIntegralConstraint &&) noexcept = default;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::unique_ptr<int> &marker() const noexcept {
|
||||||
|
return m_marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TargetValue target() const noexcept {
|
||||||
|
return TargetValue{static_cast<double>(*m_marker)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<int> m_marker;
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -38,8 +134,8 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(models::SpecifiedModelType<decltype(stellarModel)>);
|
STATIC_CHECK(models::SpecifiedModelType<decltype(stellarModel)>);
|
||||||
STATIC_CHECK(CanonicalModel::specificationCount == 4);
|
STATIC_CHECK(CanonicalModel::specificationCount == 4);
|
||||||
STATIC_CHECK(CanonicalModel::symbolicallySquare);
|
STATIC_CHECK(CanonicalModel::symbolicallySquare);
|
||||||
STATIC_CHECK(CanonicalModel::hasCompleteEquilibriumCompiler);
|
STATIC_CHECK(CanonicalModel::hasCompleteEquilibriumDeclaration);
|
||||||
STATIC_CHECK(CanonicalModel::compilationClass == models::EquilibriumSystemCompilation::complete_equilibrium_system);
|
STATIC_CHECK(operators::StellarEquilibriumSystemCompilable<CanonicalModel>);
|
||||||
|
|
||||||
CHECK(stellarModel.specification<eos::Polytrope>().polytropic_index() == 3.0);
|
CHECK(stellarModel.specification<eos::Polytrope>().polytropic_index() == 3.0);
|
||||||
CHECK(stellarModel.specification<eos::Polytrope>().polytropic_constant() == 0.25);
|
CHECK(stellarModel.specification<eos::Polytrope>().polytropic_constant() == 0.25);
|
||||||
@@ -50,6 +146,44 @@ TEST_CASE(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Incomplete Stellar Model Spellings Fail Capability Probes Without Diagnostics",
|
||||||
|
tags::stellar_model_specification_api
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using IncompleteModel = model::StellarModel<int>;
|
||||||
|
|
||||||
|
STATIC_CHECK_FALSE(model::StellarModelType<IncompleteModel>);
|
||||||
|
STATIC_CHECK_FALSE(models::SpecifiedModelType<IncompleteModel>);
|
||||||
|
STATIC_CHECK(model::specificationRoleCount<models::SpecificationRole::constitutive_law, IncompleteModel> == 0);
|
||||||
|
STATIC_CHECK_FALSE(model::HasEquationOfState<IncompleteModel>);
|
||||||
|
STATIC_CHECK_FALSE(model::HasSurfaceCondition<IncompleteModel>);
|
||||||
|
STATIC_CHECK_FALSE(operators::StellarEquilibriumSystemCompilable<IncompleteModel>);
|
||||||
|
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Specification Sets Canonicalize Cvref-Qualified Physics Types",
|
||||||
|
tags::stellar_model_specification_api
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using Qualified = models::SpecificationSet<
|
||||||
|
const eos::Polytrope &,
|
||||||
|
volatile surface::Isobaric &&,
|
||||||
|
const integral::FixedTotalMass>;
|
||||||
|
using Plain = models::SpecificationSet<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass>;
|
||||||
|
|
||||||
|
STATIC_CHECK(std::same_as<Qualified, Plain>);
|
||||||
|
STATIC_CHECK(models::ValidModelSpecificationPack<
|
||||||
|
const eos::Polytrope &,
|
||||||
|
volatile surface::Isobaric &&,
|
||||||
|
const integral::FixedTotalMass>);
|
||||||
|
STATIC_CHECK(model::StellarModelType<model::StellarModel<Qualified>>);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Stellar Model Deduction Canonicalizes Unordered Specifications",
|
"Stellar Model Deduction Canonicalizes Unordered Specifications",
|
||||||
tags::stellar_model_specification_api
|
tags::stellar_model_specification_api
|
||||||
@@ -85,6 +219,40 @@ TEST_CASE(
|
|||||||
CHECK(descriptors[3].specification.name == "FixedCentralDensity");
|
CHECK(descriptors[3].specification.name == "FixedCentralDensity");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Canonical Model Construction Preserves Every Move-Only Physics Specification",
|
||||||
|
tags::stellar_model_specification_api
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
auto stellarModel = model::StellarModel(
|
||||||
|
MoveOnlyIntegralConstraint({.marker = 307}),
|
||||||
|
MoveOnlySurfaceCondition({.marker = 211}),
|
||||||
|
MoveOnlyEquationOfState({.marker = 101})
|
||||||
|
);
|
||||||
|
using Model = std::remove_cvref_t<decltype(stellarModel)>;
|
||||||
|
using Expected = model::StellarModel<models::SpecificationSet<
|
||||||
|
MoveOnlyEquationOfState,
|
||||||
|
MoveOnlySurfaceCondition,
|
||||||
|
MoveOnlyIntegralConstraint>>;
|
||||||
|
|
||||||
|
STATIC_CHECK(std::same_as<Model, Expected>);
|
||||||
|
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyEquationOfState>);
|
||||||
|
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlySurfaceCondition>);
|
||||||
|
STATIC_CHECK_FALSE(std::copy_constructible<MoveOnlyIntegralConstraint>);
|
||||||
|
|
||||||
|
const auto &equationOfState = stellarModel.specification<MoveOnlyEquationOfState>();
|
||||||
|
const auto &surfaceCondition = stellarModel.specification<MoveOnlySurfaceCondition>();
|
||||||
|
const auto &integralConstraint = stellarModel.specification<MoveOnlyIntegralConstraint>();
|
||||||
|
|
||||||
|
REQUIRE(equationOfState.marker() != nullptr);
|
||||||
|
REQUIRE(surfaceCondition.marker() != nullptr);
|
||||||
|
REQUIRE(integralConstraint.marker() != nullptr);
|
||||||
|
CHECK(*equationOfState.marker() == 101);
|
||||||
|
CHECK(*surfaceCondition.marker() == 211);
|
||||||
|
CHECK(*integralConstraint.marker() == 307);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Stellar Specification Parameter Constructors Preserve Validation",
|
"Stellar Specification Parameter Constructors Preserve Validation",
|
||||||
tags::stellar_model_specification_api
|
tags::stellar_model_specification_api
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
|
#include <numbers>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
import mean_field;
|
import mean_field;
|
||||||
@@ -30,12 +34,41 @@ namespace {
|
|||||||
return vector;
|
return vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void require_all_ranks(
|
||||||
|
const bool localCondition,
|
||||||
|
const MPI_Comm communicator,
|
||||||
|
const char *description
|
||||||
|
) {
|
||||||
|
int rank = 0;
|
||||||
|
int size = 0;
|
||||||
|
MPI_Comm_rank(communicator, &rank);
|
||||||
|
MPI_Comm_size(communicator, &size);
|
||||||
|
|
||||||
|
const int localFailure = localCondition ? size : rank;
|
||||||
|
int firstFailure = size;
|
||||||
|
const int result = MPI_Allreduce(
|
||||||
|
&localFailure,
|
||||||
|
&firstFailure,
|
||||||
|
1,
|
||||||
|
MPI_INT,
|
||||||
|
MPI_MIN,
|
||||||
|
communicator
|
||||||
|
);
|
||||||
|
REQUIRE(result == MPI_SUCCESS);
|
||||||
|
CAPTURE(description, localCondition, firstFailure);
|
||||||
|
REQUIRE(firstFailure == size);
|
||||||
|
}
|
||||||
|
|
||||||
double global_dot(
|
double global_dot(
|
||||||
const mfem::Vector &left,
|
const mfem::Vector &left,
|
||||||
const mfem::Vector &right,
|
const mfem::Vector &right,
|
||||||
const MPI_Comm communicator
|
const MPI_Comm communicator
|
||||||
) {
|
) {
|
||||||
REQUIRE(left.Size() == right.Size());
|
require_all_ranks(
|
||||||
|
left.Size() == right.Size(),
|
||||||
|
communicator,
|
||||||
|
"global dot-product vector sizes"
|
||||||
|
);
|
||||||
const double local = left * right;
|
const double local = left * right;
|
||||||
double global = 0.0;
|
double global = 0.0;
|
||||||
REQUIRE(MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator) == MPI_SUCCESS);
|
REQUIRE(MPI_Allreduce(&local, &global, 1, MPI_DOUBLE, MPI_SUM, communicator) == MPI_SUCCESS);
|
||||||
@@ -48,6 +81,39 @@ namespace {
|
|||||||
) {
|
) {
|
||||||
return std::sqrt(global_dot(vector, vector, communicator));
|
return std::sqrt(global_dot(vector, vector, communicator));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||||
|
make_stellar_dependencies(const std::uint64_t revision = 1) {
|
||||||
|
return {
|
||||||
|
.discretization = {.identity = 16101, .revision = 1},
|
||||||
|
.density = {.identity = 16103, .revision = revision},
|
||||||
|
.surfaceDeformation = {.identity = 16111, .revision = revision},
|
||||||
|
.gravityGradient = {.identity = 16127, .revision = revision},
|
||||||
|
.gravityPotential = {.identity = 16139, .revision = revision},
|
||||||
|
.enthalpy = {.identity = 16141, .revision = revision},
|
||||||
|
.bernoulliConstant = {.identity = 16183, .revision = revision},
|
||||||
|
.rotation = {.identity = 16187, .revision = revision},
|
||||||
|
.targetMass = {.identity = 16189, .revision = 1}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_rank_consistent_scalar(
|
||||||
|
const double value,
|
||||||
|
const MPI_Comm communicator,
|
||||||
|
const double relativeTolerance = 2.0e-13
|
||||||
|
) {
|
||||||
|
double minimum = 0.0;
|
||||||
|
double maximum = 0.0;
|
||||||
|
REQUIRE(MPI_Allreduce(&value, &minimum, 1, MPI_DOUBLE, MPI_MIN, communicator) == MPI_SUCCESS);
|
||||||
|
REQUIRE(MPI_Allreduce(&value, &maximum, 1, MPI_DOUBLE, MPI_MAX, communicator) == MPI_SUCCESS);
|
||||||
|
CAPTURE(value, minimum, maximum);
|
||||||
|
CHECK(std::isfinite(minimum));
|
||||||
|
CHECK(std::isfinite(maximum));
|
||||||
|
CHECK(
|
||||||
|
std::abs(maximum - minimum) <=
|
||||||
|
relativeTolerance * std::max({1.0, std::abs(minimum), std::abs(maximum)})
|
||||||
|
);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -97,6 +163,750 @@ TEST_CASE(
|
|||||||
CHECK(f.logicalReferenceMesh->GetNE() == f.mesh->GetNE());
|
CHECK(f.logicalReferenceMesh->GetNE() == f.mesh->GetNE());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"MPI Fixed Angular Momentum Produces One Consistent Global Invariant Row",
|
||||||
|
"[mpi][distributed][fixed-angular-momentum][physics][jacobian]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
const auto args = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
require_all_ranks(
|
||||||
|
finiteElements.okay(),
|
||||||
|
MPI_COMM_WORLD,
|
||||||
|
"fixed-angular-momentum FEM setup"
|
||||||
|
);
|
||||||
|
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||||
|
|
||||||
|
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||||
|
mfem::ConstantCoefficient densityCoefficient(1.23);
|
||||||
|
densityField.ProjectCoefficient(densityCoefficient);
|
||||||
|
mfem::Vector densityTrue;
|
||||||
|
densityField.GetTrueDofs(densityTrue);
|
||||||
|
|
||||||
|
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||||
|
displacementTrue = 0.0;
|
||||||
|
finiteElements.displacement->SetFromTrueDofs(displacementTrue);
|
||||||
|
mfem::Vector gravityGradientTrue(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||||
|
mfem::Vector gravityPotentialTrue(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||||
|
gravityGradientTrue = 0.0;
|
||||||
|
gravityPotentialTrue = 0.0;
|
||||||
|
|
||||||
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless
|
||||||
|
);
|
||||||
|
gravityContext.Prepare(
|
||||||
|
{.density = gravityContext.GetDensityMap().gather(densityTrue),
|
||||||
|
.displacement = gravityContext.GetDisplacementMap().gather(displacementTrue),
|
||||||
|
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(gravityGradientTrue),
|
||||||
|
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(gravityPotentialTrue)},
|
||||||
|
{.discretization = {.value = 2},
|
||||||
|
.displacement = {.value = 3},
|
||||||
|
.density = {.value = 5},
|
||||||
|
.gravity_gradient = {.value = 7},
|
||||||
|
.gravity_potential = {.value = 11}}
|
||||||
|
);
|
||||||
|
|
||||||
|
constexpr double targetAngularMomentum = 0.37;
|
||||||
|
constexpr double angularVelocity = 0.61;
|
||||||
|
operators::PreparedAngularMomentumOperator invariant(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless,
|
||||||
|
gravityContext,
|
||||||
|
models::compileConstraint(
|
||||||
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum}})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const operators::AngularMomentumDependencies dependencies{
|
||||||
|
.discretization = {.identity = 101, .revision = 2},
|
||||||
|
.density = {.identity = 103, .revision = 5},
|
||||||
|
.displacement = {.identity = 107, .revision = 3},
|
||||||
|
.rotation = {.identity = 109, .revision = 13}
|
||||||
|
};
|
||||||
|
const auto preparation = invariant.Prepare(angularVelocity, dependencies);
|
||||||
|
CHECK(preparation.rebuiltStaticPlan);
|
||||||
|
CHECK(preparation.refreshedGeometry);
|
||||||
|
CHECK(preparation.refreshedDensity);
|
||||||
|
CHECK(preparation.updatedAngularVelocity);
|
||||||
|
CHECK(preparation.assembledResidual);
|
||||||
|
|
||||||
|
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
||||||
|
const double preparedMoment = invariant.GetMomentOfInertia();
|
||||||
|
const double comparisonScale = std::max({std::abs(independentMoment), std::abs(preparedMoment), 1.0e-300});
|
||||||
|
CHECK(std::abs(preparedMoment - independentMoment) / comparisonScale <= 3.0e-13);
|
||||||
|
|
||||||
|
double minimumMoment = 0.0;
|
||||||
|
double maximumMoment = 0.0;
|
||||||
|
MPI_Allreduce(&preparedMoment, &minimumMoment, 1, MPI_DOUBLE, MPI_MIN, finiteElements.mesh->GetComm());
|
||||||
|
MPI_Allreduce(&preparedMoment, &maximumMoment, 1, MPI_DOUBLE, MPI_MAX, finiteElements.mesh->GetComm());
|
||||||
|
CHECK(std::abs(maximumMoment - minimumMoment) / comparisonScale <= 2.0e-15);
|
||||||
|
|
||||||
|
mfem::Vector residual;
|
||||||
|
invariant.BuildResidual(residual);
|
||||||
|
require_all_ranks(
|
||||||
|
residual.Size() == 1,
|
||||||
|
communicator,
|
||||||
|
"fixed-angular-momentum residual size"
|
||||||
|
);
|
||||||
|
CHECK(
|
||||||
|
std::abs(residual(0) - (angularVelocity * independentMoment - targetAngularMomentum)) /
|
||||||
|
std::max({std::abs(residual(0)), std::abs(angularVelocity * independentMoment), 1.0}) <=
|
||||||
|
3.0e-13
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector densityAction;
|
||||||
|
invariant.ApplyDensityJacobianAction(gravityContext.GetDensityMap().gather(densityTrue), densityAction);
|
||||||
|
require_all_ranks(
|
||||||
|
densityAction.Size() == 1,
|
||||||
|
communicator,
|
||||||
|
"fixed-angular-momentum density action size"
|
||||||
|
);
|
||||||
|
CHECK(std::abs(densityAction(0) - angularVelocity * preparedMoment) / comparisonScale <= 3.0e-13);
|
||||||
|
|
||||||
|
constexpr double angularVelocityVariation = -0.29;
|
||||||
|
mfem::Vector angularVelocityAction;
|
||||||
|
invariant.ApplyAngularVelocityJacobianAction(angularVelocityVariation, angularVelocityAction);
|
||||||
|
require_all_ranks(
|
||||||
|
angularVelocityAction.Size() == 1,
|
||||||
|
communicator,
|
||||||
|
"fixed-angular-momentum rotation action size"
|
||||||
|
);
|
||||||
|
CHECK(
|
||||||
|
std::abs(angularVelocityAction(0) - angularVelocityVariation * preparedMoment) / comparisonScale <=
|
||||||
|
2.0e-15
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"MPI Density Volume Context Forwards The Prepared Global Mass Integral",
|
||||||
|
"[mpi][distributed][integral-context][physics-extension]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
const utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
require_all_ranks(
|
||||||
|
finiteElements.okay(),
|
||||||
|
MPI_COMM_WORLD,
|
||||||
|
"density-volume context FEM setup"
|
||||||
|
);
|
||||||
|
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||||
|
|
||||||
|
constexpr double densityValue = 1.23;
|
||||||
|
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||||
|
mfem::ConstantCoefficient densityCoefficient(densityValue);
|
||||||
|
densityField.ProjectCoefficient(densityCoefficient);
|
||||||
|
mfem::Vector densityTrue;
|
||||||
|
densityField.GetTrueDofs(densityTrue);
|
||||||
|
|
||||||
|
mfem::Vector displacementTrue(finiteElements.displacementFes->GetTrueVSize());
|
||||||
|
displacementTrue = 0.0;
|
||||||
|
finiteElements.displacement->SetFromTrueDofs(displacementTrue);
|
||||||
|
mfem::Vector gravityGradientTrue(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||||
|
mfem::Vector gravityPotentialTrue(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||||
|
gravityGradientTrue = 0.0;
|
||||||
|
gravityPotentialTrue = 0.0;
|
||||||
|
|
||||||
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless
|
||||||
|
);
|
||||||
|
const mfem::Vector reducedDensity = gravityContext.GetDensityMap().gather(
|
||||||
|
densityTrue
|
||||||
|
);
|
||||||
|
gravityContext.Prepare(
|
||||||
|
{.density = reducedDensity,
|
||||||
|
.displacement = gravityContext.GetDisplacementMap().gather(displacementTrue),
|
||||||
|
.gravity_gradient = gravityContext.GetGravityGradientMap().gather(gravityGradientTrue),
|
||||||
|
.gravity_potential = gravityContext.GetGravityPotentialMap().gather(gravityPotentialTrue)},
|
||||||
|
{.discretization = {.value = 2},
|
||||||
|
.displacement = {.value = 3},
|
||||||
|
.density = {.value = 5},
|
||||||
|
.gravity_gradient = {.value = 7},
|
||||||
|
.gravity_potential = {.value = 11}}
|
||||||
|
);
|
||||||
|
|
||||||
|
operators::PreparedMassNormalizationOperator massIntegral(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless,
|
||||||
|
gravityContext
|
||||||
|
);
|
||||||
|
massIntegral.Prepare(
|
||||||
|
models::compileConstraint(
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}})
|
||||||
|
),
|
||||||
|
{.discretization = {.identity = 101, .revision = 2},
|
||||||
|
.density = {.identity = 103, .revision = 5},
|
||||||
|
.displacement = {.identity = 107, .revision = 3},
|
||||||
|
.targetMass = {.identity = 109, .revision = 1}}
|
||||||
|
);
|
||||||
|
|
||||||
|
class DistributedMassIntegralCore final {
|
||||||
|
public:
|
||||||
|
explicit DistributedMassIntegralCore(
|
||||||
|
const operators::PreparedMassNormalizationOperator &mass
|
||||||
|
) noexcept
|
||||||
|
: m_mass(&mass) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
|
||||||
|
const mfem::Vector &direction
|
||||||
|
) const {
|
||||||
|
mfem::Vector action;
|
||||||
|
m_mass->ApplyDensityJacobianAction(direction, action);
|
||||||
|
return action(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
|
||||||
|
const mfem::Vector &
|
||||||
|
) const noexcept {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const operators::PreparedMassNormalizationOperator *m_mass;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DistributedMassIntegralCore testCore{massIntegral};
|
||||||
|
|
||||||
|
const stellar::DensityVolumeIntegralContext<integral::FixedTotalMass>
|
||||||
|
densityIntegral{testCore};
|
||||||
|
const double integratedMass =
|
||||||
|
densityIntegral.integrateDensity(reducedDensity).value();
|
||||||
|
const double linearizedDensityMass =
|
||||||
|
densityIntegral.linearizeDensityIntegral(reducedDensity).value();
|
||||||
|
const double preparedMass = massIntegral.GetCurrentMass();
|
||||||
|
const double independentMass =
|
||||||
|
densityValue * analysis::get_mesh_volume(finiteElements);
|
||||||
|
|
||||||
|
check_rank_consistent_scalar(integratedMass, communicator);
|
||||||
|
check_rank_consistent_scalar(linearizedDensityMass, communicator);
|
||||||
|
check_rank_consistent_scalar(preparedMass, communicator);
|
||||||
|
check_rank_consistent_scalar(independentMass, communicator);
|
||||||
|
const double massScale = std::max(
|
||||||
|
{1.0, std::abs(integratedMass), std::abs(independentMass)}
|
||||||
|
);
|
||||||
|
CHECK(std::abs(integratedMass - independentMass) <= 3.0e-12 * massScale);
|
||||||
|
CHECK(std::abs(integratedMass - preparedMass) <= 3.0e-13 * massScale);
|
||||||
|
CHECK(std::abs(linearizedDensityMass - integratedMass) <=
|
||||||
|
3.0e-13 * massScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"MPI Assembled Variadic Stellar Root Normalizes And Applies Its Inferred Preconditioner",
|
||||||
|
"[mpi][distributed][stellar-equilibrium][normalization][preconditioning][integration]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
const utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
require_all_ranks(
|
||||||
|
finiteElements.okay(),
|
||||||
|
MPI_COMM_WORLD,
|
||||||
|
"variadic stellar-root FEM setup"
|
||||||
|
);
|
||||||
|
const MPI_Comm communicator = finiteElements.mesh->GetComm();
|
||||||
|
|
||||||
|
constexpr double radius = utils::RADIUS;
|
||||||
|
constexpr double mass = utils::MASS;
|
||||||
|
const double polytropicConstant =
|
||||||
|
2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||||
|
const double centralDensity =
|
||||||
|
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||||
|
|
||||||
|
auto model = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
|
integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{0.05},
|
||||||
|
.axis = {0.0, 0.0, 1.0}
|
||||||
|
}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
||||||
|
);
|
||||||
|
auto problem = equilibrium::discretize(
|
||||||
|
model,
|
||||||
|
equilibrium::makeStellarDiscretization(
|
||||||
|
finiteElements,
|
||||||
|
normalization::PhysicalRieszDiagonal{
|
||||||
|
dimensions::LengthValue{radius},
|
||||||
|
utils::G
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
auto projected = seed::makeProjectedEquilibriumState(
|
||||||
|
problem,
|
||||||
|
seed::LaneEmden({
|
||||||
|
.centralDensity = dimensions::DensityValue{centralDensity},
|
||||||
|
.radialSampleCount = 512
|
||||||
|
})
|
||||||
|
);
|
||||||
|
auto normalized = normalization::makeNormalizedStellarEquilibriumOperator(problem);
|
||||||
|
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||||
|
using Form = typename Problem::FormType;
|
||||||
|
constexpr auto massResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||||
|
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||||
|
);
|
||||||
|
constexpr auto angularMomentumResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
);
|
||||||
|
constexpr auto centralDensityResidualBlock = utils::blocks::get_residual_block<Form>(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
problem.StateSize() == problem.EquationSize(),
|
||||||
|
communicator,
|
||||||
|
"variadic stellar-root square layout"
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector normalizedState;
|
||||||
|
normalized.NormalizeState(projected.values, normalizedState);
|
||||||
|
const auto preparation = normalized.Prepare(
|
||||||
|
normalizedState,
|
||||||
|
make_stellar_dependencies(1)
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
preparation.DidAnyWork() &&
|
||||||
|
preparation.generatedPhysicalControl &&
|
||||||
|
preparation.template specification<models::FixedAngularMomentum>().generatedRotation &&
|
||||||
|
problem.IsPrepared() &&
|
||||||
|
normalized.IsPrepared(),
|
||||||
|
communicator,
|
||||||
|
"initial variadic stellar-root preparation"
|
||||||
|
);
|
||||||
|
|
||||||
|
// The astronomy-facing integral handle must delegate to the same mapped
|
||||||
|
// physical-volume quadrature and collective reduction as the prepared
|
||||||
|
// mass equation, without exposing the FEM/core objects to extension
|
||||||
|
// physics. Linearity in density gives an independent check of both
|
||||||
|
// public operations on every rank.
|
||||||
|
const auto physicalState = problem.GetManifest().stateView(projected.values);
|
||||||
|
const auto physicalDensity = physicalState.block(
|
||||||
|
utils::blocks::density_field.mass_term
|
||||||
|
);
|
||||||
|
const stellar::DensityVolumeIntegralContext<integral::FixedTotalMass>
|
||||||
|
densityIntegral{problem.GetPhysicalOperator()};
|
||||||
|
const double integratedMass = densityIntegral.integrateDensity(
|
||||||
|
physicalDensity
|
||||||
|
).value();
|
||||||
|
const double linearizedMass = densityIntegral.linearizeDensityIntegral(
|
||||||
|
physicalDensity
|
||||||
|
).value();
|
||||||
|
const double preparedMass = problem.GetPhysicalOperator()
|
||||||
|
.GetFixedMassReport()
|
||||||
|
.achieved;
|
||||||
|
check_rank_consistent_scalar(integratedMass, communicator);
|
||||||
|
check_rank_consistent_scalar(linearizedMass, communicator);
|
||||||
|
const double massComparisonScale = std::max(
|
||||||
|
{std::abs(integratedMass), std::abs(preparedMass), 1.0e-300}
|
||||||
|
);
|
||||||
|
CHECK(std::abs(integratedMass - preparedMass) / massComparisonScale <=
|
||||||
|
3.0e-13);
|
||||||
|
CHECK(std::abs(linearizedMass - preparedMass) / massComparisonScale <=
|
||||||
|
3.0e-13);
|
||||||
|
|
||||||
|
mfem::Vector normalizedResidual;
|
||||||
|
normalized.BuildResidual(normalizedResidual);
|
||||||
|
require_all_ranks(
|
||||||
|
normalizedResidual.Size() == problem.EquationSize(),
|
||||||
|
communicator,
|
||||||
|
"normalized variadic residual size"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto residualView = problem.GetManifest().residualView(normalizedResidual);
|
||||||
|
const auto &layout = problem.GetManifest().layout();
|
||||||
|
const auto &residualFactors = normalized.GetNormalization().ResidualFactors();
|
||||||
|
const auto massReport = problem.GetPreparedOperator().GetFixedMassReport();
|
||||||
|
const auto angularMomentumReport =
|
||||||
|
problem.GetPreparedOperator().GetAngularMomentumReport();
|
||||||
|
const auto centralDensityReport =
|
||||||
|
problem.GetPreparedOperator().GetCentralDensityReport();
|
||||||
|
const auto checkReportedScalarResidual = [&](const mfem::Vector &block,
|
||||||
|
const double expected) {
|
||||||
|
require_all_ranks(
|
||||||
|
block.Size() == 1,
|
||||||
|
communicator,
|
||||||
|
"reported scalar residual block size"
|
||||||
|
);
|
||||||
|
const double actual = block(0);
|
||||||
|
CAPTURE(actual, expected);
|
||||||
|
CHECK(std::isfinite(expected));
|
||||||
|
CHECK(
|
||||||
|
std::abs(actual - expected) <=
|
||||||
|
5.0e-13 * std::max({1.0, std::abs(actual), std::abs(expected)})
|
||||||
|
);
|
||||||
|
check_rank_consistent_scalar(actual, finiteElements.mesh->GetComm());
|
||||||
|
};
|
||||||
|
checkReportedScalarResidual(
|
||||||
|
residualView.block(
|
||||||
|
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||||
|
),
|
||||||
|
massReport.dimensionalResidual *
|
||||||
|
residualFactors(layout.offset(massResidualBlock))
|
||||||
|
);
|
||||||
|
checkReportedScalarResidual(
|
||||||
|
residualView.block(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
),
|
||||||
|
angularMomentumReport.dimensionalResidual *
|
||||||
|
residualFactors(layout.offset(angularMomentumResidualBlock))
|
||||||
|
);
|
||||||
|
checkReportedScalarResidual(
|
||||||
|
residualView.block(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term
|
||||||
|
),
|
||||||
|
centralDensityReport.enthalpyResidual *
|
||||||
|
residualFactors(layout.offset(centralDensityResidualBlock))
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector normalizedDirection = make_deterministic_vector(problem.StateSize(), 0.37);
|
||||||
|
const auto directionState = problem.GetManifest().stateView(normalizedDirection);
|
||||||
|
mfem::Vector massDirection = directionState.block(
|
||||||
|
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||||
|
);
|
||||||
|
mfem::Vector angularVelocityDirection = directionState.block(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
);
|
||||||
|
mfem::Vector phaseDirection = directionState.block(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
massDirection.Size() == 1 &&
|
||||||
|
angularVelocityDirection.Size() == 1 &&
|
||||||
|
phaseDirection.Size() == 1,
|
||||||
|
communicator,
|
||||||
|
"generated scalar direction block sizes"
|
||||||
|
);
|
||||||
|
massDirection(0) = 0.17;
|
||||||
|
angularVelocityDirection(0) = -0.23;
|
||||||
|
phaseDirection(0) = 0.31;
|
||||||
|
massDirection.SyncAliasMemory(normalizedDirection);
|
||||||
|
angularVelocityDirection.SyncAliasMemory(normalizedDirection);
|
||||||
|
phaseDirection.SyncAliasMemory(normalizedDirection);
|
||||||
|
normalizedDirection /= global_norm(normalizedDirection, finiteElements.mesh->GetComm());
|
||||||
|
|
||||||
|
mfem::Vector normalizedAction;
|
||||||
|
normalized.Mult(normalizedDirection, normalizedAction);
|
||||||
|
require_all_ranks(
|
||||||
|
normalizedAction.Size() == problem.EquationSize(),
|
||||||
|
communicator,
|
||||||
|
"normalized variadic Jacobian-action size"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Different dependency revisions are intentional: the state changes in
|
||||||
|
* each difference evaluation, so the distributed physical contexts must
|
||||||
|
* be rebuilt even though all persistent identities remain the same. */
|
||||||
|
constexpr double differenceStep = 1.0e-5;
|
||||||
|
mfem::Vector plusState(normalizedState);
|
||||||
|
plusState.Add(differenceStep, normalizedDirection);
|
||||||
|
const auto plusPreparation = normalized.Prepare(
|
||||||
|
plusState,
|
||||||
|
make_stellar_dependencies(2)
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
plusPreparation.DidAnyWork(),
|
||||||
|
communicator,
|
||||||
|
"positive finite-difference preparation"
|
||||||
|
);
|
||||||
|
mfem::Vector plusResidual;
|
||||||
|
normalized.BuildResidual(plusResidual);
|
||||||
|
|
||||||
|
mfem::Vector minusState(normalizedState);
|
||||||
|
minusState.Add(-differenceStep, normalizedDirection);
|
||||||
|
const auto minusPreparation = normalized.Prepare(
|
||||||
|
minusState,
|
||||||
|
make_stellar_dependencies(3)
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
minusPreparation.DidAnyWork(),
|
||||||
|
communicator,
|
||||||
|
"negative finite-difference preparation"
|
||||||
|
);
|
||||||
|
mfem::Vector minusResidual;
|
||||||
|
normalized.BuildResidual(minusResidual);
|
||||||
|
|
||||||
|
mfem::Vector finiteDifference(plusResidual);
|
||||||
|
finiteDifference -= minusResidual;
|
||||||
|
finiteDifference /= 2.0 * differenceStep;
|
||||||
|
const auto restoredPreparation = normalized.Prepare(
|
||||||
|
normalizedState,
|
||||||
|
make_stellar_dependencies(4)
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
restoredPreparation.DidAnyWork() && normalized.IsPrepared(),
|
||||||
|
communicator,
|
||||||
|
"restored finite-difference preparation"
|
||||||
|
);
|
||||||
|
mfem::Vector restoredResidual;
|
||||||
|
normalized.BuildResidual(restoredResidual);
|
||||||
|
mfem::Vector restoredResidualDifference(restoredResidual);
|
||||||
|
restoredResidualDifference -= normalizedResidual;
|
||||||
|
const double restoredResidualError = global_norm(
|
||||||
|
restoredResidualDifference,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) / std::max({
|
||||||
|
global_norm(restoredResidual, finiteElements.mesh->GetComm()),
|
||||||
|
global_norm(normalizedResidual, finiteElements.mesh->GetComm()),
|
||||||
|
std::numeric_limits<double>::epsilon()
|
||||||
|
});
|
||||||
|
CAPTURE(restoredResidualError);
|
||||||
|
CHECK(restoredResidualError <= 2.0e-12);
|
||||||
|
|
||||||
|
mfem::Vector finiteDifferenceError(normalizedAction);
|
||||||
|
finiteDifferenceError -= finiteDifference;
|
||||||
|
const double actionNorm = global_norm(
|
||||||
|
normalizedAction,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
);
|
||||||
|
const double finiteDifferenceNorm = global_norm(
|
||||||
|
finiteDifference,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
);
|
||||||
|
const double completeDifferenceError = global_norm(
|
||||||
|
finiteDifferenceError,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) / std::max({
|
||||||
|
actionNorm,
|
||||||
|
finiteDifferenceNorm,
|
||||||
|
std::numeric_limits<double>::epsilon()
|
||||||
|
});
|
||||||
|
CAPTURE(actionNorm, finiteDifferenceNorm, completeDifferenceError);
|
||||||
|
CHECK(actionNorm > std::numeric_limits<double>::min());
|
||||||
|
CHECK(finiteDifferenceNorm > std::numeric_limits<double>::min());
|
||||||
|
CHECK(completeDifferenceError <= 8.0e-5);
|
||||||
|
|
||||||
|
const int locallyFinite =
|
||||||
|
vector_is_finite(normalizedResidual) &&
|
||||||
|
vector_is_finite(normalizedAction) &&
|
||||||
|
vector_is_finite(finiteDifference) ? 1 : 0;
|
||||||
|
int globallyFinite = 0;
|
||||||
|
REQUIRE(MPI_Allreduce(
|
||||||
|
&locallyFinite,
|
||||||
|
&globallyFinite,
|
||||||
|
1,
|
||||||
|
MPI_INT,
|
||||||
|
MPI_MIN,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) == MPI_SUCCESS);
|
||||||
|
CHECK(globallyFinite == 1);
|
||||||
|
CHECK(global_norm(normalizedResidual, finiteElements.mesh->GetComm()) > 0.0);
|
||||||
|
CHECK(global_norm(normalizedAction, finiteElements.mesh->GetComm()) > 0.0);
|
||||||
|
|
||||||
|
auto actionView = problem.GetManifest().residualView(normalizedAction);
|
||||||
|
auto finiteDifferenceView = problem.GetManifest().residualView(finiteDifference);
|
||||||
|
const auto checkGlobalRow = [&](const auto &term, const char *rowName) {
|
||||||
|
const mfem::Vector residualBlock = residualView.block(term);
|
||||||
|
const mfem::Vector actionBlock = actionView.block(term);
|
||||||
|
const mfem::Vector differenceBlock = finiteDifferenceView.block(term);
|
||||||
|
require_all_ranks(
|
||||||
|
residualBlock.Size() == 1 &&
|
||||||
|
actionBlock.Size() == 1 &&
|
||||||
|
differenceBlock.Size() == 1,
|
||||||
|
communicator,
|
||||||
|
"global scalar residual/Jacobian block sizes"
|
||||||
|
);
|
||||||
|
check_rank_consistent_scalar(residualBlock(0), finiteElements.mesh->GetComm());
|
||||||
|
check_rank_consistent_scalar(actionBlock(0), finiteElements.mesh->GetComm());
|
||||||
|
check_rank_consistent_scalar(differenceBlock(0), finiteElements.mesh->GetComm());
|
||||||
|
const double rowMagnitude = std::max(
|
||||||
|
std::abs(actionBlock(0)),
|
||||||
|
std::abs(differenceBlock(0))
|
||||||
|
);
|
||||||
|
const double rowDifferenceError =
|
||||||
|
std::abs(actionBlock(0) - differenceBlock(0)) /
|
||||||
|
std::max(rowMagnitude, std::numeric_limits<double>::epsilon());
|
||||||
|
CAPTURE(rowName, actionBlock(0), differenceBlock(0), rowMagnitude,
|
||||||
|
rowDifferenceError);
|
||||||
|
CHECK(rowMagnitude > 1.0e-10);
|
||||||
|
CHECK(rowDifferenceError <= 8.0e-5);
|
||||||
|
};
|
||||||
|
checkGlobalRow(
|
||||||
|
utils::blocks::fixed_total_mass_constraint.mass_normalization_term,
|
||||||
|
"fixed-total-mass"
|
||||||
|
);
|
||||||
|
checkGlobalRow(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term,
|
||||||
|
"fixed-angular-momentum"
|
||||||
|
);
|
||||||
|
checkGlobalRow(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term,
|
||||||
|
"fixed-central-density phase"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto component = preconditioning::makePreconditioner(problem);
|
||||||
|
STATIC_CHECK(decltype(component)::borderValueArity == 3);
|
||||||
|
STATIC_CHECK(decltype(component)::borderResidualArity == 3);
|
||||||
|
auto physicalInverse = preconditioning::prepare(problem, component);
|
||||||
|
require_all_ranks(
|
||||||
|
physicalInverse.IsCurrent(),
|
||||||
|
communicator,
|
||||||
|
"prepared physical preconditioner currentness"
|
||||||
|
);
|
||||||
|
auto scaledInverse = normalized.MakeScaledPreconditioner(physicalInverse);
|
||||||
|
require_all_ranks(
|
||||||
|
scaledInverse.IsCurrent(),
|
||||||
|
communicator,
|
||||||
|
"prepared normalized preconditioner currentness"
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector normalizedRightHandSide =
|
||||||
|
make_deterministic_vector(problem.EquationSize(), 0.73);
|
||||||
|
auto rightHandSideView = problem.GetManifest().residualView(normalizedRightHandSide);
|
||||||
|
mfem::Vector massRightHandSide = rightHandSideView.block(
|
||||||
|
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||||
|
);
|
||||||
|
mfem::Vector angularMomentumRightHandSide = rightHandSideView.block(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
);
|
||||||
|
mfem::Vector phaseRightHandSide = rightHandSideView.block(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term
|
||||||
|
);
|
||||||
|
massRightHandSide(0) = 0.11;
|
||||||
|
angularMomentumRightHandSide(0) = -0.19;
|
||||||
|
phaseRightHandSide(0) = 0.29;
|
||||||
|
massRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||||
|
angularMomentumRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||||
|
phaseRightHandSide.SyncAliasMemory(normalizedRightHandSide);
|
||||||
|
normalizedRightHandSide /= global_norm(
|
||||||
|
normalizedRightHandSide,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
);
|
||||||
|
|
||||||
|
mfem::Vector firstCorrection(scaledInverse.Height());
|
||||||
|
mfem::Vector repeatedCorrection(scaledInverse.Height());
|
||||||
|
firstCorrection = 0.0;
|
||||||
|
repeatedCorrection = 0.0;
|
||||||
|
scaledInverse.Mult(normalizedRightHandSide, firstCorrection);
|
||||||
|
scaledInverse.Mult(normalizedRightHandSide, repeatedCorrection);
|
||||||
|
|
||||||
|
mfem::Vector repeatDifference(repeatedCorrection);
|
||||||
|
repeatDifference -= firstCorrection;
|
||||||
|
const double correctionNorm = global_norm(firstCorrection, finiteElements.mesh->GetComm());
|
||||||
|
const double repeatError = global_norm(repeatDifference, finiteElements.mesh->GetComm()) /
|
||||||
|
std::max(correctionNorm, std::numeric_limits<double>::epsilon());
|
||||||
|
CAPTURE(correctionNorm, repeatError);
|
||||||
|
const int locallyFiniteCorrections =
|
||||||
|
vector_is_finite(firstCorrection) && vector_is_finite(repeatedCorrection) ? 1 : 0;
|
||||||
|
int globallyFiniteCorrections = 0;
|
||||||
|
REQUIRE(MPI_Allreduce(
|
||||||
|
&locallyFiniteCorrections,
|
||||||
|
&globallyFiniteCorrections,
|
||||||
|
1,
|
||||||
|
MPI_INT,
|
||||||
|
MPI_MIN,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) == MPI_SUCCESS);
|
||||||
|
CHECK(globallyFiniteCorrections == 1);
|
||||||
|
CHECK(std::isfinite(correctionNorm));
|
||||||
|
CHECK(correctionNorm > 0.0);
|
||||||
|
CHECK(repeatError <= 2.0e-12);
|
||||||
|
require_all_ranks(
|
||||||
|
physicalInverse.IsCurrent() && scaledInverse.IsCurrent(),
|
||||||
|
communicator,
|
||||||
|
"preconditioner currentness after repeated application"
|
||||||
|
);
|
||||||
|
|
||||||
|
const mfem::Vector &readOnlyCorrection = firstCorrection;
|
||||||
|
const auto correctionView = problem.GetManifest().stateView(readOnlyCorrection);
|
||||||
|
const double massCorrection = correctionView.block(
|
||||||
|
utils::blocks::fixed_total_mass_constraint.mass_normalization_term
|
||||||
|
)(0);
|
||||||
|
const double angularVelocityCorrection = correctionView.block(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
)(0);
|
||||||
|
const double phaseCorrection = correctionView.block(
|
||||||
|
utils::blocks::fixed_central_density_phase.central_value_term
|
||||||
|
)(0);
|
||||||
|
CAPTURE(massCorrection, angularVelocityCorrection, phaseCorrection);
|
||||||
|
check_rank_consistent_scalar(
|
||||||
|
massCorrection,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
);
|
||||||
|
check_rank_consistent_scalar(
|
||||||
|
angularVelocityCorrection,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
);
|
||||||
|
check_rank_consistent_scalar(
|
||||||
|
phaseCorrection,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
);
|
||||||
|
|
||||||
|
/* A Newton iteration reparses the same normalized state under fresh
|
||||||
|
* dependency revisions. Every rank must observe the stale inverse, and
|
||||||
|
* refresh must reconstruct the inferred border actions and Schur data. */
|
||||||
|
const auto secondPreparation = normalized.Prepare(
|
||||||
|
normalizedState,
|
||||||
|
make_stellar_dependencies(5)
|
||||||
|
);
|
||||||
|
require_all_ranks(
|
||||||
|
secondPreparation.DidAnyWork() && normalized.IsPrepared(),
|
||||||
|
communicator,
|
||||||
|
"second variadic stellar-root preparation"
|
||||||
|
);
|
||||||
|
mfem::Vector secondPreparedResidual;
|
||||||
|
normalized.BuildResidual(secondPreparedResidual);
|
||||||
|
mfem::Vector secondPreparedResidualDifference(secondPreparedResidual);
|
||||||
|
secondPreparedResidualDifference -= normalizedResidual;
|
||||||
|
const double secondPreparedResidualError = global_norm(
|
||||||
|
secondPreparedResidualDifference,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) / std::max({
|
||||||
|
global_norm(secondPreparedResidual, finiteElements.mesh->GetComm()),
|
||||||
|
global_norm(normalizedResidual, finiteElements.mesh->GetComm()),
|
||||||
|
std::numeric_limits<double>::epsilon()
|
||||||
|
});
|
||||||
|
CAPTURE(secondPreparedResidualError);
|
||||||
|
CHECK(secondPreparedResidualError <= 2.0e-12);
|
||||||
|
require_all_ranks(
|
||||||
|
!physicalInverse.IsCurrent() && !scaledInverse.IsCurrent(),
|
||||||
|
communicator,
|
||||||
|
"preconditioners become stale together"
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
scaledInverse.Mult(normalizedRightHandSide, repeatedCorrection),
|
||||||
|
std::logic_error
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto refresh = physicalInverse.Refresh();
|
||||||
|
CHECK(refresh.specificationActionsRefreshed);
|
||||||
|
CHECK(refresh.rebuiltSchurComplement);
|
||||||
|
CHECK(refresh.DidAnyWork());
|
||||||
|
require_all_ranks(
|
||||||
|
physicalInverse.IsCurrent() && scaledInverse.IsCurrent(),
|
||||||
|
communicator,
|
||||||
|
"refreshed preconditioner currentness"
|
||||||
|
);
|
||||||
|
const auto noOpRefresh = physicalInverse.Refresh();
|
||||||
|
CHECK_FALSE(noOpRefresh.DidAnyWork());
|
||||||
|
|
||||||
|
mfem::Vector refreshedCorrection(scaledInverse.Height());
|
||||||
|
refreshedCorrection = 0.0;
|
||||||
|
scaledInverse.Mult(normalizedRightHandSide, refreshedCorrection);
|
||||||
|
mfem::Vector refreshDifference(refreshedCorrection);
|
||||||
|
refreshDifference -= firstCorrection;
|
||||||
|
const double refreshError = global_norm(
|
||||||
|
refreshDifference,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) / std::max(
|
||||||
|
correctionNorm,
|
||||||
|
std::numeric_limits<double>::epsilon()
|
||||||
|
);
|
||||||
|
const int locallyFiniteRefresh = vector_is_finite(refreshedCorrection) ? 1 : 0;
|
||||||
|
int globallyFiniteRefresh = 0;
|
||||||
|
REQUIRE(MPI_Allreduce(
|
||||||
|
&locallyFiniteRefresh,
|
||||||
|
&globallyFiniteRefresh,
|
||||||
|
1,
|
||||||
|
MPI_INT,
|
||||||
|
MPI_MIN,
|
||||||
|
finiteElements.mesh->GetComm()
|
||||||
|
) == MPI_SUCCESS);
|
||||||
|
CAPTURE(refreshError);
|
||||||
|
CHECK(globallyFiniteRefresh == 1);
|
||||||
|
CHECK(refreshError <= 2.0e-10);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"MPI Prepared Gravity Operators Preserve Global Algebraic Identities",
|
"MPI Prepared Gravity Operators Preserve Global Algebraic Identities",
|
||||||
"[mpi][distributed][gravity][operators][unit]"
|
"[mpi][distributed][gravity][operators][unit]"
|
||||||
|
|||||||
828
tests/normalization/physical_riesz.cpp
Normal file
828
tests/normalization/physical_riesz.cpp
Normal file
@@ -0,0 +1,828 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <concepts>
|
||||||
|
#include <limits>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <catch2/catch_approx.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
import test_helpers;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
namespace blocks = mean_field::utils::blocks;
|
||||||
|
namespace normalization = mean_field::normalization;
|
||||||
|
namespace models = mean_field::models;
|
||||||
|
|
||||||
|
struct ModelWithoutFixedTotalMass final { };
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
concept SupportsModelDerivedStellarScales = requires(
|
||||||
|
const normalization::PhysicalRieszDiagonal<> &policy,
|
||||||
|
const Model &model
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
normalization::deriveStellarCharacteristicScales(policy, model)
|
||||||
|
} -> std::same_as<normalization::StellarCharacteristicScales>;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestValue final : blocks::value_block_base { };
|
||||||
|
struct TestResidual final : blocks::residual_block_base { };
|
||||||
|
using TestForm = blocks::block_form<
|
||||||
|
blocks::type_list<TestValue>,
|
||||||
|
blocks::type_list<TestResidual>>;
|
||||||
|
|
||||||
|
using GlobalSpecificEnergyNormalization = models::CoordinateNormalization<
|
||||||
|
models::RieszTopology::global_scalar,
|
||||||
|
models::PhysicalScaleLaw::specific_energy>;
|
||||||
|
using VolumeSpecificEnergyNormalization = models::CoordinateNormalization<
|
||||||
|
models::RieszTopology::scalar_volume_l2,
|
||||||
|
models::PhysicalScaleLaw::specific_energy>;
|
||||||
|
|
||||||
|
class SelfDescribingMagneticSpecificEnergy final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
double target;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition = mean_field::integral::FixedWithPhysicalCoordinate<
|
||||||
|
SelfDescribingMagneticSpecificEnergy,
|
||||||
|
"NormalizationMockMagneticSpecificEnergy",
|
||||||
|
models::DependsOn<blocks::density::mass::value>,
|
||||||
|
models::Affects<blocks::enthalpy::specific::residual>,
|
||||||
|
models::GlobalScalarNormalization<
|
||||||
|
models::PhysicalScaleLaw::dimensionless,
|
||||||
|
models::PhysicalScaleLaw::specific_energy>>;
|
||||||
|
|
||||||
|
explicit constexpr SelfDescribingMagneticSpecificEnergy(const Parameters parameters) noexcept
|
||||||
|
: m_target(parameters.target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MissingGeneratedNormalization final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
double target;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||||
|
MissingGeneratedNormalization,
|
||||||
|
"NormalizationMockMissing",
|
||||||
|
models::DependsOn<blocks::density::mass::value>,
|
||||||
|
models::Affects<blocks::enthalpy::specific::residual>>;
|
||||||
|
|
||||||
|
explicit constexpr MissingGeneratedNormalization(const Parameters parameters) noexcept
|
||||||
|
: m_target(parameters.target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GeneratedVolumeCoordinateWithoutMetricSource final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
double target;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition = mean_field::integral::FixedWithPhysicalCoordinate<
|
||||||
|
GeneratedVolumeCoordinateWithoutMetricSource,
|
||||||
|
"NormalizationMockVolumeCoordinate",
|
||||||
|
models::DependsOn<blocks::density::mass::value>,
|
||||||
|
models::Affects<blocks::enthalpy::specific::residual>,
|
||||||
|
models::GeneratedNormalization<
|
||||||
|
VolumeSpecificEnergyNormalization,
|
||||||
|
GlobalSpecificEnergyNormalization>>;
|
||||||
|
|
||||||
|
explicit constexpr GeneratedVolumeCoordinateWithoutMetricSource(const Parameters parameters) noexcept
|
||||||
|
: m_target(parameters.target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MalformedGeneratedNormalization final { };
|
||||||
|
|
||||||
|
class MalformedGeneratedNormalizationConstraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
double target;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||||
|
MalformedGeneratedNormalizationConstraint,
|
||||||
|
"NormalizationMockMalformed",
|
||||||
|
models::DependsOn<blocks::density::mass::value>,
|
||||||
|
models::Affects<blocks::enthalpy::specific::residual>,
|
||||||
|
MalformedGeneratedNormalization>;
|
||||||
|
|
||||||
|
explicit constexpr MalformedGeneratedNormalizationConstraint(const Parameters parameters) noexcept
|
||||||
|
: m_target(parameters.target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
using GeneratedValueBlock = blocks::generated_value_block<
|
||||||
|
models::PhysicalCoordinateFor<Specification>>;
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
using GeneratedMultiplierBlock = blocks::generated_value_block<
|
||||||
|
models::MultiplierFor<Specification>>;
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
using GeneratedResidualBlock = blocks::generated_residual_block<
|
||||||
|
models::ResidualFor<Specification>>;
|
||||||
|
|
||||||
|
using SelfDescribingValue = GeneratedValueBlock<SelfDescribingMagneticSpecificEnergy>;
|
||||||
|
using SelfDescribingResidual = GeneratedResidualBlock<SelfDescribingMagneticSpecificEnergy>;
|
||||||
|
using SelfDescribingForm = blocks::block_form<
|
||||||
|
blocks::type_list<SelfDescribingValue>,
|
||||||
|
blocks::type_list<SelfDescribingResidual>>;
|
||||||
|
|
||||||
|
using MissingValue = GeneratedMultiplierBlock<MissingGeneratedNormalization>;
|
||||||
|
using MissingResidual = GeneratedResidualBlock<MissingGeneratedNormalization>;
|
||||||
|
using MissingNormalizationForm = blocks::block_form<
|
||||||
|
blocks::type_list<MissingValue>,
|
||||||
|
blocks::type_list<MissingResidual>>;
|
||||||
|
|
||||||
|
using UnpreparedVolumeValue = GeneratedValueBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
||||||
|
using UnpreparedVolumeResidual = GeneratedResidualBlock<GeneratedVolumeCoordinateWithoutMetricSource>;
|
||||||
|
using UnpreparedVolumeForm = blocks::block_form<
|
||||||
|
blocks::type_list<UnpreparedVolumeValue>,
|
||||||
|
blocks::type_list<UnpreparedVolumeResidual>>;
|
||||||
|
|
||||||
|
class DenseOperator final : public mfem::Operator {
|
||||||
|
public:
|
||||||
|
explicit DenseOperator(const mfem::DenseMatrix &matrix)
|
||||||
|
: mfem::Operator(matrix.Height(), matrix.Width()),
|
||||||
|
m_matrix(matrix) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &input,
|
||||||
|
mfem::Vector &output
|
||||||
|
) const override {
|
||||||
|
m_matrix.Mult(input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mfem::DenseMatrix m_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DenseInverseSolver final : public mfem::Solver {
|
||||||
|
public:
|
||||||
|
explicit DenseInverseSolver(const mfem::DenseMatrix &inverse)
|
||||||
|
: mfem::Solver(inverse.Height(), inverse.Width()),
|
||||||
|
m_inverse(inverse) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetOperator(const mfem::Operator &operation) override {
|
||||||
|
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||||
|
throw std::invalid_argument("The dense inverse received an incompatible operator.");
|
||||||
|
}
|
||||||
|
m_boundOperator = &operation;
|
||||||
|
++m_bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mult(
|
||||||
|
const mfem::Vector &input,
|
||||||
|
mfem::Vector &output
|
||||||
|
) const override {
|
||||||
|
if (m_boundOperator == nullptr) {
|
||||||
|
throw std::logic_error("The dense inverse must be bound before application.");
|
||||||
|
}
|
||||||
|
m_inverse.Mult(input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const mfem::Operator *BoundOperator() const noexcept {
|
||||||
|
return m_boundOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int Bindings() const noexcept {
|
||||||
|
return m_bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mfem::DenseMatrix m_inverse;
|
||||||
|
const mfem::Operator *m_boundOperator{nullptr};
|
||||||
|
int m_bindings{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector vector(std::initializer_list<double> values) {
|
||||||
|
mfem::Vector result(static_cast<int>(values.size()));
|
||||||
|
int index = 0;
|
||||||
|
for (const double value : values) {
|
||||||
|
result(index++) = value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkVector(
|
||||||
|
const mfem::Vector &actual,
|
||||||
|
const mfem::Vector &expected,
|
||||||
|
const double epsilon = 2.0e-13
|
||||||
|
) {
|
||||||
|
REQUIRE(actual.Size() == expected.Size());
|
||||||
|
for (int index = 0; index < actual.Size(); ++index) {
|
||||||
|
CHECK(actual(index) == Catch::Approx(expected(index)).epsilon(epsilon).margin(1.0e-300));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Pointer-Retaining Normalization Operators Reject Temporary Dependencies",
|
||||||
|
"[normalization][type][lifetime]"
|
||||||
|
) {
|
||||||
|
using Map = normalization::DiagonalNormalization;
|
||||||
|
|
||||||
|
STATIC_CHECK(std::constructible_from<
|
||||||
|
normalization::ScaledJacobianOperator,
|
||||||
|
const DenseOperator &,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledJacobianOperator,
|
||||||
|
DenseOperator &&,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledJacobianOperator,
|
||||||
|
const DenseOperator &&,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledJacobianOperator,
|
||||||
|
const DenseOperator &,
|
||||||
|
Map &&>);
|
||||||
|
|
||||||
|
STATIC_CHECK(std::constructible_from<
|
||||||
|
normalization::ScaledInverseOperator,
|
||||||
|
const DenseOperator &,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledInverseOperator,
|
||||||
|
DenseOperator &&,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledInverseOperator,
|
||||||
|
const DenseOperator &,
|
||||||
|
Map &&>);
|
||||||
|
|
||||||
|
STATIC_CHECK(std::constructible_from<
|
||||||
|
normalization::ScaledPreconditioner,
|
||||||
|
DenseInverseSolver &,
|
||||||
|
const DenseOperator &,
|
||||||
|
const DenseOperator &,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledPreconditioner,
|
||||||
|
DenseInverseSolver &&,
|
||||||
|
const DenseOperator &,
|
||||||
|
const DenseOperator &,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledPreconditioner,
|
||||||
|
DenseInverseSolver &,
|
||||||
|
DenseOperator &&,
|
||||||
|
const DenseOperator &,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledPreconditioner,
|
||||||
|
DenseInverseSolver &,
|
||||||
|
const DenseOperator &,
|
||||||
|
DenseOperator &&,
|
||||||
|
const Map &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
normalization::ScaledPreconditioner,
|
||||||
|
DenseInverseSolver &,
|
||||||
|
const DenseOperator &,
|
||||||
|
const DenseOperator &,
|
||||||
|
Map &&>);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Characteristic Stellar Scales Satisfy Gravity Virial And Rotation Identities", "[normalization][physics]") {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
constexpr double mass = 7.0;
|
||||||
|
constexpr double radius = 3.0;
|
||||||
|
constexpr double gravity = 5.0;
|
||||||
|
const auto scales = normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{mass}, dimensions::LengthValue{radius}, gravity
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(scales.density == Catch::Approx(mass / std::pow(radius, 3)));
|
||||||
|
CHECK(scales.acceleration == Catch::Approx(gravity * mass / std::pow(radius, 2)));
|
||||||
|
CHECK(scales.specificEnergy == Catch::Approx(gravity * mass / radius));
|
||||||
|
CHECK(scales.pressure == Catch::Approx(gravity * mass * mass / std::pow(radius, 4)));
|
||||||
|
CHECK(scales.angularVelocity == Catch::Approx(std::sqrt(gravity * mass / std::pow(radius, 3))));
|
||||||
|
CHECK(scales.angularMomentum == Catch::Approx(mass * std::sqrt(gravity * mass * radius)));
|
||||||
|
|
||||||
|
// Hydrostatic/virial energy scales agree: P R^3 = M Phi = F R.
|
||||||
|
const double virial = scales.pressure * std::pow(radius, 3);
|
||||||
|
CHECK(virial == Catch::Approx(mass * scales.specificEnergy).epsilon(2.0e-15));
|
||||||
|
CHECK(virial == Catch::Approx(scales.force * radius).epsilon(2.0e-15));
|
||||||
|
|
||||||
|
// Omega_0 is the Kepler/break-up scale and J_0 = M R^2 Omega_0.
|
||||||
|
CHECK(scales.angularVelocity * scales.angularVelocity * radius ==
|
||||||
|
Catch::Approx(scales.acceleration).epsilon(2.0e-15));
|
||||||
|
CHECK(scales.angularMomentum ==
|
||||||
|
Catch::Approx(mass * radius * radius * scales.angularVelocity).epsilon(2.0e-15));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Characteristic Scales Obey The Expected Stellar Homology Exponents", "[normalization][physics]") {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
const auto reference = normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{2.5}, dimensions::LengthValue{4.0}, 3.0
|
||||||
|
);
|
||||||
|
constexpr double massFactor = 11.0;
|
||||||
|
constexpr double radiusFactor = 0.2;
|
||||||
|
constexpr double gravityFactor = 7.0;
|
||||||
|
const auto transformed = normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{2.5 * massFactor},
|
||||||
|
dimensions::LengthValue{4.0 * radiusFactor},
|
||||||
|
3.0 * gravityFactor
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(transformed.density / reference.density ==
|
||||||
|
Catch::Approx(massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15));
|
||||||
|
CHECK(transformed.acceleration / reference.acceleration ==
|
||||||
|
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 2)).epsilon(4.0e-15));
|
||||||
|
CHECK(transformed.inverseTimeSquared / reference.inverseTimeSquared ==
|
||||||
|
Catch::Approx(gravityFactor * massFactor / std::pow(radiusFactor, 3)).epsilon(4.0e-15));
|
||||||
|
CHECK(transformed.specificEnergy / reference.specificEnergy ==
|
||||||
|
Catch::Approx(gravityFactor * massFactor / radiusFactor).epsilon(4.0e-15));
|
||||||
|
CHECK(transformed.pressure / reference.pressure ==
|
||||||
|
Catch::Approx(gravityFactor * massFactor * massFactor / std::pow(radiusFactor, 4)).epsilon(4.0e-15));
|
||||||
|
CHECK(transformed.angularVelocity / reference.angularVelocity == Catch::Approx(
|
||||||
|
std::sqrt(gravityFactor * massFactor / std::pow(radiusFactor, 3))
|
||||||
|
).epsilon(4.0e-15));
|
||||||
|
CHECK(transformed.angularMomentum / reference.angularMomentum == Catch::Approx(
|
||||||
|
massFactor * std::sqrt(gravityFactor * massFactor * radiusFactor)
|
||||||
|
).epsilon(4.0e-15));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Physical Block Scales Distinguish Invariants From Numerical Phase Conditions", "[normalization][physics]") {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
const auto scales = normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0
|
||||||
|
);
|
||||||
|
CHECK(normalization::physicalScale<blocks::density::mass::value>(scales) == scales.density);
|
||||||
|
CHECK(normalization::physicalScale<blocks::gravity::gradient::value>(scales) == scales.acceleration);
|
||||||
|
CHECK(normalization::physicalScale<blocks::gravity::poisson::residual>(scales) == scales.inverseTimeSquared);
|
||||||
|
CHECK(normalization::physicalScale<blocks::fixed_total_mass::mass_normalization::residual>(scales) == 9.0);
|
||||||
|
CHECK(normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::value>(scales) ==
|
||||||
|
scales.angularVelocity);
|
||||||
|
CHECK(normalization::physicalScale<blocks::fixed_angular_momentum::angular_velocity::residual>(scales) ==
|
||||||
|
scales.angularMomentum);
|
||||||
|
|
||||||
|
// The central-density condition is implemented as h(0)-h_target, so its residual scale is energy/mass,
|
||||||
|
// despite the physical target being expressed as a density.
|
||||||
|
CHECK(normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) ==
|
||||||
|
scales.specificEnergy);
|
||||||
|
CHECK(normalization::physicalScale<blocks::fixed_central_density::central_value::residual>(scales) !=
|
||||||
|
scales.density);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Generated Physical Riesz Laws Come From A Self-Describing Physics Specification",
|
||||||
|
"[normalization][type][extension]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using ValueTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingValue>;
|
||||||
|
using ResidualTraits = normalization::PhysicalRieszBlockTraits<SelfDescribingResidual>;
|
||||||
|
|
||||||
|
STATIC_CHECK(models::SelfDescribingModelSpecification<SelfDescribingMagneticSpecificEnergy>);
|
||||||
|
STATIC_CHECK(models::CompleteGeneratedNormalizationFor<SelfDescribingMagneticSpecificEnergy>);
|
||||||
|
STATIC_CHECK(operators::StellarEquilibriumSpecificationCompilable<SelfDescribingMagneticSpecificEnergy>);
|
||||||
|
STATIC_CHECK(normalization::GeneratedValuePhysicalRieszNormalizable<
|
||||||
|
models::PhysicalCoordinateFor<SelfDescribingMagneticSpecificEnergy>>);
|
||||||
|
STATIC_CHECK(normalization::GeneratedResidualPhysicalRieszNormalizable<
|
||||||
|
models::ResidualFor<SelfDescribingMagneticSpecificEnergy>>);
|
||||||
|
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
||||||
|
SelfDescribingMagneticSpecificEnergy>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<
|
||||||
|
normalization::PhysicalRieszDiagonal<>,
|
||||||
|
SelfDescribingForm>);
|
||||||
|
STATIC_CHECK(normalization::RegisteredStellarSpecificationNormalization<
|
||||||
|
SelfDescribingMagneticSpecificEnergy>);
|
||||||
|
STATIC_CHECK(normalization::CompleteStellarSpecificationNormalizationFor<
|
||||||
|
SelfDescribingMagneticSpecificEnergy,
|
||||||
|
SelfDescribingForm>);
|
||||||
|
|
||||||
|
STATIC_CHECK(ValueTraits::Method::topology == normalization::RieszTopology::global_scalar);
|
||||||
|
STATIC_CHECK(ValueTraits::Method::scale == normalization::PhysicalScaleKind::dimensionless);
|
||||||
|
STATIC_CHECK(ResidualTraits::Method::topology == normalization::RieszTopology::global_scalar);
|
||||||
|
STATIC_CHECK(ResidualTraits::Method::scale == normalization::PhysicalScaleKind::specific_energy);
|
||||||
|
|
||||||
|
const auto scales = normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{9.0}, dimensions::LengthValue{2.0}, 4.0
|
||||||
|
);
|
||||||
|
const blocks::form_layout<SelfDescribingForm> layout({1}, {1});
|
||||||
|
normalization::DiagonalNormalizationBuilder<SelfDescribingForm> builder(layout);
|
||||||
|
normalization::StellarSpecificationNormalizationContribution<
|
||||||
|
SelfDescribingMagneticSpecificEnergy>::Apply(builder, scales);
|
||||||
|
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
||||||
|
|
||||||
|
REQUIRE(map.StateFactors().Size() == 1);
|
||||||
|
REQUIRE(map.ResidualFactors().Size() == 1);
|
||||||
|
CHECK(map.StateFactors()(0) == Catch::Approx(1.0).epsilon(2.0e-15));
|
||||||
|
CHECK(map.ResidualFactors()(0) == Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Generated Normalization Completeness Is SFINAE Safe And Rejects Missing Runtime Metrics",
|
||||||
|
"[normalization][type][validation]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<int>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<int>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteStellarNormalizationFor<int, SelfDescribingForm>);
|
||||||
|
|
||||||
|
STATIC_CHECK(models::ModelSpecification<MissingGeneratedNormalization>);
|
||||||
|
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<MissingGeneratedNormalization>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
||||||
|
MissingGeneratedNormalization>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<
|
||||||
|
normalization::PhysicalRieszDiagonal<>,
|
||||||
|
MissingNormalizationForm>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
|
||||||
|
MissingGeneratedNormalization>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteStellarSpecificationNormalizationFor<
|
||||||
|
MissingGeneratedNormalization,
|
||||||
|
MissingNormalizationForm>);
|
||||||
|
|
||||||
|
STATIC_CHECK(models::ModelSpecification<MalformedGeneratedNormalizationConstraint>);
|
||||||
|
STATIC_CHECK_FALSE(models::CompleteGeneratedNormalizationFor<
|
||||||
|
MalformedGeneratedNormalizationConstraint>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
||||||
|
MalformedGeneratedNormalizationConstraint>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
|
||||||
|
MalformedGeneratedNormalizationConstraint>);
|
||||||
|
|
||||||
|
// The declaration itself is a valid Riesz law, but runtime stellar
|
||||||
|
// preparation has no finite-element Gram source for a generated volume
|
||||||
|
// field. The stronger runtime concept must therefore reject it.
|
||||||
|
STATIC_CHECK(normalization::CompleteGeneratedPhysicalRieszNormalizationFor<
|
||||||
|
GeneratedVolumeCoordinateWithoutMetricSource>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<
|
||||||
|
normalization::PhysicalRieszDiagonal<>,
|
||||||
|
UnpreparedVolumeForm>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::RegisteredStellarSpecificationNormalization<
|
||||||
|
GeneratedVolumeCoordinateWithoutMetricSource>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteStellarSpecificationNormalizationFor<
|
||||||
|
GeneratedVolumeCoordinateWithoutMetricSource,
|
||||||
|
UnpreparedVolumeForm>);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Characteristic Scale Construction Rejects Invalid Or Overflowing References", "[normalization][validation]") {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{0.0}, dimensions::LengthValue{1.0}, 1.0
|
||||||
|
),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{1.0}, dimensions::LengthValue{-1.0}, 1.0
|
||||||
|
),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
(normalization::PhysicalRieszDiagonal{dimensions::LengthValue{1.0},
|
||||||
|
std::numeric_limits<double>::quiet_NaN()}),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
normalization::deriveStellarCharacteristicScales(
|
||||||
|
dimensions::MassValue{1.0e300}, dimensions::LengthValue{1.0e-200}, 1.0e100
|
||||||
|
),
|
||||||
|
std::overflow_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Diagonal Riesz Maps Reproduce Primal And Dual Norms Across Extreme Metrics", "[normalization][math]") {
|
||||||
|
const blocks::form_layout<TestForm> layout({3}, {3});
|
||||||
|
normalization::DiagonalNormalizationBuilder<TestForm> builder(layout);
|
||||||
|
const mfem::Vector gram = vector({1.0e-20, 4.0, 9.0e20});
|
||||||
|
constexpr double stateScale = 10.0;
|
||||||
|
constexpr double residualScale = 0.25;
|
||||||
|
builder.SetValueBlock<TestValue>(stateScale, gram);
|
||||||
|
builder.SetResidualBlock<TestResidual>(residualScale, gram);
|
||||||
|
const normalization::DiagonalNormalization map = std::move(builder).Build();
|
||||||
|
|
||||||
|
const mfem::Vector state = vector({3.0e10, -2.0, 4.0e-10});
|
||||||
|
const mfem::Vector residual = vector({2.0e-10, -3.0, 5.0e10});
|
||||||
|
double expectedPrimalNormSquared = 0.0;
|
||||||
|
double expectedDualNormSquared = 0.0;
|
||||||
|
for (int index = 0; index < gram.Size(); ++index) {
|
||||||
|
expectedPrimalNormSquared += gram(index) * state(index) * state(index) /
|
||||||
|
(stateScale * stateScale);
|
||||||
|
expectedDualNormSquared += residual(index) * residual(index) /
|
||||||
|
(gram(index) * residualScale * residualScale);
|
||||||
|
}
|
||||||
|
CHECK(map.LocalStateNormSquared(state) == Catch::Approx(expectedPrimalNormSquared).epsilon(3.0e-15));
|
||||||
|
CHECK(map.LocalResidualNormSquared(residual) == Catch::Approx(expectedDualNormSquared).epsilon(3.0e-15));
|
||||||
|
|
||||||
|
mfem::Vector normalizedState;
|
||||||
|
mfem::Vector recoveredState;
|
||||||
|
mfem::Vector normalizedResidual;
|
||||||
|
mfem::Vector recoveredResidual;
|
||||||
|
map.NormalizeState(state, normalizedState);
|
||||||
|
map.DenormalizeState(normalizedState, recoveredState);
|
||||||
|
map.NormalizeResidual(residual, normalizedResidual);
|
||||||
|
map.DenormalizeResidual(normalizedResidual, recoveredResidual);
|
||||||
|
checkVector(recoveredState, state, 3.0e-15);
|
||||||
|
checkVector(recoveredResidual, residual, 3.0e-15);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Hybrid Riesz Rows Replace Missing Volume Metrics With Point Metrics", "[normalization][math]") {
|
||||||
|
using HybridForm = blocks::block_form<
|
||||||
|
blocks::type_list<blocks::enthalpy::specific::value>,
|
||||||
|
blocks::type_list<blocks::enthalpy::specific::residual>>;
|
||||||
|
const blocks::form_layout<HybridForm> layout({4}, {4});
|
||||||
|
normalization::DiagonalNormalizationBuilder<HybridForm> builder(layout);
|
||||||
|
builder.SetValueBlock<blocks::enthalpy::specific::value>(2.0, vector({2.0, 3.0, 5.0, 7.0}));
|
||||||
|
|
||||||
|
// Replaced isobaric rows may have zero bulk mass because they are no longer volume weak rows.
|
||||||
|
const mfem::Vector bulkMetric = vector({4.0, 0.0, 16.0, 0.0});
|
||||||
|
const std::array<int, 2> pointRows{1, 3};
|
||||||
|
builder.SetHybridResidualBlock<blocks::enthalpy::specific::residual>(
|
||||||
|
5.0, bulkMetric, std::span<const int>{pointRows}, 1.0
|
||||||
|
);
|
||||||
|
const auto map = std::move(builder).Build();
|
||||||
|
CHECK(map.ResidualFactors()(0) == Catch::Approx(1.0 / 10.0));
|
||||||
|
CHECK(map.ResidualFactors()(1) == Catch::Approx(1.0 / 5.0));
|
||||||
|
CHECK(map.ResidualFactors()(2) == Catch::Approx(1.0 / 20.0));
|
||||||
|
CHECK(map.ResidualFactors()(3) == Catch::Approx(1.0 / 5.0));
|
||||||
|
|
||||||
|
normalization::DiagonalNormalizationBuilder<HybridForm> duplicateRows(layout);
|
||||||
|
duplicateRows.SetValueGlobal<blocks::enthalpy::specific::value>(1.0);
|
||||||
|
const std::array<int, 2> duplicates{1, 1};
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
duplicateRows.SetHybridResidualBlock<blocks::enthalpy::specific::residual>(
|
||||||
|
1.0, vector({1.0, 1.0, 1.0, 1.0}), std::span<const int>{duplicates}
|
||||||
|
),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Runtime Normalization Assembly Rejects Missing Duplicate And Invalid Data", "[normalization][validation]") {
|
||||||
|
const blocks::form_layout<TestForm> layout({2}, {2});
|
||||||
|
|
||||||
|
normalization::DiagonalNormalizationBuilder<TestForm> missing(layout);
|
||||||
|
missing.SetValueBlock<TestValue>(1.0, vector({1.0, 1.0}));
|
||||||
|
CHECK_THROWS_AS(std::move(missing).Build(), std::logic_error);
|
||||||
|
|
||||||
|
normalization::DiagonalNormalizationBuilder<TestForm> duplicate(layout);
|
||||||
|
duplicate.SetValueBlock<TestValue>(1.0, vector({1.0, 1.0}));
|
||||||
|
CHECK_THROWS_AS(duplicate.SetValueBlock<TestValue>(1.0, vector({1.0, 1.0})), std::logic_error);
|
||||||
|
|
||||||
|
normalization::DiagonalNormalizationBuilder<TestForm> zeroMetric(layout);
|
||||||
|
CHECK_THROWS_AS(zeroMetric.SetValueBlock<TestValue>(1.0, vector({1.0, 0.0})), std::invalid_argument);
|
||||||
|
|
||||||
|
normalization::DiagonalNormalizationBuilder<TestForm> wrongSize(layout);
|
||||||
|
CHECK_THROWS_AS(wrongSize.SetResidualBlock<TestResidual>(1.0, vector({1.0})), std::invalid_argument);
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
normalization::DiagonalNormalization(vector({1.0, std::numeric_limits<double>::infinity()}), vector({1.0})),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Scaled Jacobian And Inverse Implement The Exact Coordinate Change", "[normalization][linear-algebra]") {
|
||||||
|
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
|
||||||
|
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
||||||
|
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||||
|
|
||||||
|
// Start from a well-conditioned normalized Jacobian A_hat and form the dimensional
|
||||||
|
// J = L^{-1} A_hat R^{-1}. Its entries span the physical unit ranges, while L J R
|
||||||
|
// must recover A_hat rather than an artificially ill-conditioned dense matrix.
|
||||||
|
constexpr double normalizedMatrix[2][2]{{4.0, 1.0}, {2.0, 3.0}};
|
||||||
|
constexpr double normalizedInverse[2][2]{{0.3, -0.1}, {-0.2, 0.4}};
|
||||||
|
mfem::DenseMatrix matrix(2);
|
||||||
|
mfem::DenseMatrix inverse(2);
|
||||||
|
for (int row = 0; row < 2; ++row) {
|
||||||
|
for (int column = 0; column < 2; ++column) {
|
||||||
|
matrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) /
|
||||||
|
residualFactors(row);
|
||||||
|
inverse(row, column) = normalizedInverse[row][column] * residualFactors(column) /
|
||||||
|
stateFactors(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const DenseOperator physicalJacobian(matrix);
|
||||||
|
const DenseOperator physicalInverse(inverse);
|
||||||
|
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||||
|
const normalization::ScaledInverseOperator scaledInverse(physicalInverse, map);
|
||||||
|
|
||||||
|
const mfem::Vector direction = vector({0.75, -1.25});
|
||||||
|
mfem::Vector action;
|
||||||
|
scaledJacobian.Mult(direction, action);
|
||||||
|
mfem::Vector expected(2);
|
||||||
|
expected(0) = 4.0 * direction(0) + direction(1);
|
||||||
|
expected(1) = 2.0 * direction(0) + 3.0 * direction(1);
|
||||||
|
checkVector(action, expected, 4.0e-15);
|
||||||
|
|
||||||
|
mfem::Vector recovered;
|
||||||
|
scaledInverse.Mult(action, recovered);
|
||||||
|
checkVector(recovered, direction, 2.0e-13);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Scaled Preconditioning Routes An Exact Physical Inverse Through FGMRES", "[normalization][solver]") {
|
||||||
|
const mfem::Vector stateFactors = vector({1.0e-9, 2.0e7});
|
||||||
|
const mfem::Vector residualFactors = vector({5.0e8, 3.0e-6});
|
||||||
|
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||||
|
|
||||||
|
constexpr double normalizedMatrix[2][2]{{4.0, 1.0}, {2.0, 3.0}};
|
||||||
|
constexpr double normalizedInverse[2][2]{{0.3, -0.1}, {-0.2, 0.4}};
|
||||||
|
mfem::DenseMatrix physicalMatrix(2);
|
||||||
|
mfem::DenseMatrix physicalInverseMatrix(2);
|
||||||
|
for (int row = 0; row < 2; ++row) {
|
||||||
|
for (int column = 0; column < 2; ++column) {
|
||||||
|
physicalMatrix(row, column) = normalizedMatrix[row][column] * stateFactors(column) /
|
||||||
|
residualFactors(row);
|
||||||
|
physicalInverseMatrix(row, column) = normalizedInverse[row][column] * residualFactors(column) /
|
||||||
|
stateFactors(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DenseOperator physicalJacobian(physicalMatrix);
|
||||||
|
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||||
|
DenseInverseSolver physicalInverse(physicalInverseMatrix);
|
||||||
|
normalization::ScaledPreconditioner scaledPreconditioner(
|
||||||
|
physicalInverse, physicalJacobian, scaledJacobian, map
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
|
||||||
|
CHECK(&scaledPreconditioner.GetPhysicalJacobian() == &physicalJacobian);
|
||||||
|
CHECK(&scaledPreconditioner.GetNormalizedJacobian() == &scaledJacobian);
|
||||||
|
|
||||||
|
const mfem::Vector rightHandSide = vector({1.5, -0.75});
|
||||||
|
mfem::Vector directCorrection(2);
|
||||||
|
scaledPreconditioner.Mult(rightHandSide, directCorrection);
|
||||||
|
const mfem::Vector expected = vector({0.525, -0.6});
|
||||||
|
checkVector(directCorrection, expected, 3.0e-13);
|
||||||
|
|
||||||
|
mfem::FGMRESSolver krylov(MPI_COMM_WORLD);
|
||||||
|
krylov.SetRelTol(1.0e-13);
|
||||||
|
krylov.SetAbsTol(1.0e-15);
|
||||||
|
krylov.SetMaxIter(4);
|
||||||
|
krylov.SetKDim(2);
|
||||||
|
krylov.SetPrintLevel(0);
|
||||||
|
krylov.SetPreconditioner(scaledPreconditioner);
|
||||||
|
krylov.SetOperator(scaledJacobian);
|
||||||
|
|
||||||
|
mfem::Vector solution(2);
|
||||||
|
solution = 0.0;
|
||||||
|
krylov.Mult(rightHandSide, solution);
|
||||||
|
CHECK(krylov.GetConverged());
|
||||||
|
CHECK(krylov.GetNumIterations() <= 1);
|
||||||
|
checkVector(solution, expected, 3.0e-13);
|
||||||
|
CHECK(physicalInverse.BoundOperator() == &physicalJacobian);
|
||||||
|
CHECK(physicalInverse.Bindings() >= 2);
|
||||||
|
CHECK(scaledPreconditioner.GetStatistics().operatorBindings >= 2);
|
||||||
|
CHECK(scaledPreconditioner.GetStatistics().applications >= 2);
|
||||||
|
|
||||||
|
mfem::IdentityOperator differentNormalizedJacobian(2);
|
||||||
|
CHECK_THROWS_AS(
|
||||||
|
scaledPreconditioner.SetOperator(differentNormalizedJacobian),
|
||||||
|
std::invalid_argument
|
||||||
|
);
|
||||||
|
mfem::IdentityOperator wrongSize(3);
|
||||||
|
CHECK_THROWS_AS(scaledPreconditioner.SetOperator(wrongSize), std::invalid_argument);
|
||||||
|
mfem::Vector wrongCorrection(1);
|
||||||
|
CHECK_THROWS_AS(scaledPreconditioner.Mult(rightHandSide, wrongCorrection), std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Physical Riesz Scaling Collapses A Forty-Eight-Decade Diagonal Imbalance", "[normalization][numerics]") {
|
||||||
|
const mfem::Vector stateFactors = vector({1.0e-12, 1.0, 1.0e12});
|
||||||
|
const mfem::Vector residualFactors = vector({1.0e12, 1.0, 1.0e-12});
|
||||||
|
const normalization::DiagonalNormalization map(stateFactors, residualFactors);
|
||||||
|
|
||||||
|
mfem::DenseMatrix physicalMatrix(3);
|
||||||
|
physicalMatrix = 0.0;
|
||||||
|
for (int index = 0; index < 3; ++index) {
|
||||||
|
physicalMatrix(index, index) = stateFactors(index) / residualFactors(index);
|
||||||
|
}
|
||||||
|
CHECK(physicalMatrix(2, 2) / physicalMatrix(0, 0) == Catch::Approx(1.0e48));
|
||||||
|
|
||||||
|
const DenseOperator physicalJacobian(physicalMatrix);
|
||||||
|
const normalization::ScaledJacobianOperator scaledJacobian(physicalJacobian, map);
|
||||||
|
const mfem::Vector direction = vector({-2.0, 3.5, 0.125});
|
||||||
|
mfem::Vector action;
|
||||||
|
scaledJacobian.Mult(direction, action);
|
||||||
|
checkVector(action, direction, 4.0e-15);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A Compiled Stellar Problem Prepares Reference Physical Riesz Coordinates", "[normalization][integration]") {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
utils::Args args = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
constexpr double targetMass = 2.0;
|
||||||
|
constexpr double referenceRadius = 1.25;
|
||||||
|
constexpr double gravitationalConstant = 3.0;
|
||||||
|
const normalization::PhysicalRieszDiagonal policy{
|
||||||
|
dimensions::LengthValue{referenceRadius}, gravitationalConstant
|
||||||
|
};
|
||||||
|
const auto discretization = equilibrium::makeStellarDiscretization(finiteElements, policy);
|
||||||
|
auto problem = equilibrium::discretize(
|
||||||
|
model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 3.0, .K = 0.25}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
|
),
|
||||||
|
discretization
|
||||||
|
);
|
||||||
|
|
||||||
|
using ProblemType = std::remove_cvref_t<decltype(problem)>;
|
||||||
|
using Form = typename ProblemType::FormType;
|
||||||
|
using ModelType = std::remove_cvref_t<decltype(problem.GetStellarModel())>;
|
||||||
|
STATIC_CHECK(SupportsModelDerivedStellarScales<ModelType>);
|
||||||
|
STATIC_CHECK_FALSE(SupportsModelDerivedStellarScales<ModelWithoutFixedTotalMass>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename ProblemType::NormalizationPrescriptionType,
|
||||||
|
std::remove_cvref_t<decltype(policy)>>);
|
||||||
|
CHECK(problem.GetNormalizationPrescription().referenceRadius() == dimensions::LengthValue{referenceRadius});
|
||||||
|
|
||||||
|
const normalization::DiagonalNormalization map = normalization::prepareNormalization(problem);
|
||||||
|
REQUIRE(map.StateSize() == problem.StateSize());
|
||||||
|
REQUIRE(map.ResidualSize() == problem.EquationSize());
|
||||||
|
for (int index = 0; index < map.StateSize(); ++index) {
|
||||||
|
CHECK(std::isfinite(map.StateFactors()(index)));
|
||||||
|
CHECK(map.StateFactors()(index) > 0.0);
|
||||||
|
}
|
||||||
|
for (int index = 0; index < map.ResidualSize(); ++index) {
|
||||||
|
CHECK(std::isfinite(map.ResidualFactors()(index)));
|
||||||
|
CHECK(map.ResidualFactors()(index) > 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto scales = normalization::deriveStellarCharacteristicScales(policy, problem.GetStellarModel());
|
||||||
|
const auto &layout = problem.GetManifest().layout();
|
||||||
|
constexpr int massValueBlock = blocks::type_index_v<
|
||||||
|
blocks::fixed_total_mass::mass_normalization::value,
|
||||||
|
typename Form::value_blocks>;
|
||||||
|
constexpr int massResidualBlock = blocks::type_index_v<
|
||||||
|
blocks::fixed_total_mass::mass_normalization::residual,
|
||||||
|
typename Form::residual_blocks>;
|
||||||
|
constexpr int phaseValueBlock = blocks::type_index_v<
|
||||||
|
blocks::fixed_central_density::central_value::value,
|
||||||
|
typename Form::value_blocks>;
|
||||||
|
constexpr int phaseResidualBlock = blocks::type_index_v<
|
||||||
|
blocks::fixed_central_density::central_value::residual,
|
||||||
|
typename Form::residual_blocks>;
|
||||||
|
constexpr int enthalpyResidualBlock = blocks::type_index_v<
|
||||||
|
blocks::enthalpy::specific::residual,
|
||||||
|
typename Form::residual_blocks>;
|
||||||
|
|
||||||
|
CHECK(map.StateFactors()(layout.value_offsets()[massValueBlock]) ==
|
||||||
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||||
|
CHECK(map.ResidualFactors()(layout.residual_offsets()[massResidualBlock]) ==
|
||||||
|
Catch::Approx(1.0 / targetMass).epsilon(2.0e-15));
|
||||||
|
CHECK(map.StateFactors()(layout.value_offsets()[phaseValueBlock]) ==
|
||||||
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||||
|
CHECK(map.ResidualFactors()(layout.residual_offsets()[phaseResidualBlock]) ==
|
||||||
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||||
|
|
||||||
|
const auto &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
|
||||||
|
REQUIRE(surfaceRows.Size() > 0);
|
||||||
|
for (const int row : surfaceRows) {
|
||||||
|
const int rootRow = layout.residual_offsets()[enthalpyResidualBlock] + row;
|
||||||
|
CHECK(map.ResidualFactors()(rootRow) ==
|
||||||
|
Catch::Approx(1.0 / scales.specificEnergy).epsilon(2.0e-15));
|
||||||
|
}
|
||||||
|
|
||||||
|
mfem::Vector physicalState(problem.StateSize());
|
||||||
|
for (int index = 0; index < physicalState.Size(); ++index) {
|
||||||
|
physicalState(index) = std::sin(0.37 * static_cast<double>(index + 1));
|
||||||
|
}
|
||||||
|
mfem::Vector normalizedState;
|
||||||
|
mfem::Vector recoveredState;
|
||||||
|
map.NormalizeState(physicalState, normalizedState);
|
||||||
|
map.DenormalizeState(normalizedState, recoveredState);
|
||||||
|
checkVector(recoveredState, physicalState, 4.0e-15);
|
||||||
|
|
||||||
|
const normalization::ScaledJacobianOperator scaledJacobian(problem.GetLinearizationOperator(), map);
|
||||||
|
CHECK(scaledJacobian.Width() == problem.StateSize());
|
||||||
|
CHECK(scaledJacobian.Height() == problem.EquationSize());
|
||||||
|
|
||||||
|
// The existing provisional structure preconditioner remains available for this distinct problem type.
|
||||||
|
const auto structureBlock = preconditioning::stellarStructureBlock(problem);
|
||||||
|
STATIC_CHECK(preconditioning::PreconditionerComponent<std::remove_cvref_t<decltype(structureBlock)>>);
|
||||||
|
}
|
||||||
228
tests/normalization/plan.cpp
Normal file
228
tests/normalization/plan.cpp
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
#include <concepts>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
namespace blocks = mean_field::utils::blocks;
|
||||||
|
namespace normalization = mean_field::normalization;
|
||||||
|
|
||||||
|
using PhysicalForm = blocks::surface_deformed_stellar_equilibrium_form;
|
||||||
|
using PhaseForm = blocks::central_density_bordered_stellar_equilibrium_form;
|
||||||
|
using PhysicalPlan = normalization::PhysicalRieszNormalizationPlanFor<PhysicalForm>;
|
||||||
|
using PhasePlan = normalization::PhysicalRieszNormalizationPlanFor<PhaseForm>;
|
||||||
|
|
||||||
|
struct ValueA final : blocks::value_block_base { };
|
||||||
|
struct ValueB final : blocks::value_block_base { };
|
||||||
|
struct ResidualA final : blocks::residual_block_base { };
|
||||||
|
struct ResidualB final : blocks::residual_block_base { };
|
||||||
|
struct ForeignValue final : blocks::value_block_base { };
|
||||||
|
struct ForeignResidual final : blocks::residual_block_base { };
|
||||||
|
|
||||||
|
using SmallForm = blocks::block_form<
|
||||||
|
blocks::type_list<ValueA, ValueB>,
|
||||||
|
blocks::type_list<ResidualA, ResidualB>>;
|
||||||
|
using ValueAIdentity = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::value,
|
||||||
|
blocks::type_list<ValueA>,
|
||||||
|
normalization::IdentityCoordinate>;
|
||||||
|
using ValueBIdentity = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::value,
|
||||||
|
blocks::type_list<ValueB>,
|
||||||
|
normalization::IdentityCoordinate>;
|
||||||
|
using ResidualAIdentity = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::residual,
|
||||||
|
blocks::type_list<ResidualA>,
|
||||||
|
normalization::IdentityCoordinate>;
|
||||||
|
using ResidualBIdentity = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::residual,
|
||||||
|
blocks::type_list<ResidualB>,
|
||||||
|
normalization::IdentityCoordinate>;
|
||||||
|
using ForeignIdentity = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::value,
|
||||||
|
blocks::type_list<ForeignValue>,
|
||||||
|
normalization::IdentityCoordinate>;
|
||||||
|
using ForeignResidualIdentity = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::residual,
|
||||||
|
blocks::type_list<ForeignResidual>,
|
||||||
|
normalization::IdentityCoordinate>;
|
||||||
|
|
||||||
|
using CompleteSmallPlan = normalization::NormalizationPlan<
|
||||||
|
ValueAIdentity,
|
||||||
|
ValueBIdentity,
|
||||||
|
ResidualAIdentity,
|
||||||
|
ResidualBIdentity>;
|
||||||
|
using MissingSmallPlan = normalization::NormalizationPlan<
|
||||||
|
ValueAIdentity,
|
||||||
|
ResidualAIdentity,
|
||||||
|
ResidualBIdentity>;
|
||||||
|
using DuplicateSmallPlan = normalization::NormalizationPlan<
|
||||||
|
ValueAIdentity,
|
||||||
|
ValueAIdentity,
|
||||||
|
ValueBIdentity,
|
||||||
|
ResidualAIdentity,
|
||||||
|
ResidualBIdentity>;
|
||||||
|
using ForeignSmallPlan = normalization::NormalizationPlan<
|
||||||
|
ValueAIdentity,
|
||||||
|
ValueBIdentity,
|
||||||
|
ForeignIdentity,
|
||||||
|
ResidualAIdentity,
|
||||||
|
ResidualBIdentity,
|
||||||
|
ForeignResidualIdentity>;
|
||||||
|
|
||||||
|
struct MalformedComponent final {
|
||||||
|
using Blocks = blocks::type_list<ValueA>;
|
||||||
|
using Method = normalization::IdentityCoordinate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IncoherentComponent final {
|
||||||
|
using Blocks = blocks::type_list<ValueA>;
|
||||||
|
using Method = normalization::IdentityCoordinate;
|
||||||
|
using ValueBlocks = blocks::type_list<ValueB>;
|
||||||
|
using ResidualBlocks = blocks::type_list<>;
|
||||||
|
static constexpr auto kind = normalization::CoordinateKind::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
using WrongDensityTopology = normalization::CoordinateComponent<
|
||||||
|
normalization::CoordinateKind::value,
|
||||||
|
blocks::type_list<blocks::density::mass::value>,
|
||||||
|
normalization::PhysicalRieszCoordinate<
|
||||||
|
normalization::RieszTopology::vector_volume_l2,
|
||||||
|
normalization::PhysicalScaleKind::density>>;
|
||||||
|
|
||||||
|
struct FutureInvariantValue final : blocks::value_block_base { };
|
||||||
|
struct FutureInvariantResidual final : blocks::residual_block_base { };
|
||||||
|
using UnregisteredFutureForm = blocks::block_form<
|
||||||
|
blocks::type_list<FutureInvariantValue>,
|
||||||
|
blocks::type_list<FutureInvariantResidual>>;
|
||||||
|
|
||||||
|
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::integral::FixedTotalMass>>;
|
||||||
|
using RieszPolicy = normalization::PhysicalRieszDiagonal<>;
|
||||||
|
using RieszDiscretization = mean_field::equilibrium::StellarDiscretizationFor<RieszPolicy>;
|
||||||
|
using BaselineProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||||
|
using RieszProblem = mean_field::equilibrium::StellarEquilibriumProblem<BaseModel, RieszDiscretization>;
|
||||||
|
using AngularModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::integral::FixedTotalMass,
|
||||||
|
mean_field::integral::FixedAngularMomentum>>;
|
||||||
|
using AngularForm = mean_field::operators::CompiledStellarEquilibriumForm<AngularModel>;
|
||||||
|
|
||||||
|
template <typename Mapper>
|
||||||
|
concept CanMakeRieszDiscretization = requires(
|
||||||
|
mean_field::fem::FEM &finiteElements,
|
||||||
|
Mapper &&mapper,
|
||||||
|
RieszPolicy policy
|
||||||
|
) {
|
||||||
|
mean_field::equilibrium::makeStellarDiscretization(
|
||||||
|
finiteElements,
|
||||||
|
std::forward<Mapper>(mapper),
|
||||||
|
policy
|
||||||
|
);
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Normalization Plans Prove Exact Ownership Of Every Compiled Coordinate", "[normalization][type]") {
|
||||||
|
STATIC_CHECK(normalization::NormalizationPlanType<PhysicalPlan>);
|
||||||
|
STATIC_CHECK(normalization::CompleteNormalizationFor<PhysicalPlan, PhysicalForm>);
|
||||||
|
STATIC_CHECK(normalization::CompleteNormalizationFor<PhasePlan, PhaseForm>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::Unnormalized, PhysicalForm>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhysicalForm>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, PhaseForm>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<RieszPolicy, AngularForm>);
|
||||||
|
STATIC_CHECK(normalization::StellarSpecificationNormalizationContribution<
|
||||||
|
mean_field::integral::FixedAngularMomentum>::registered);
|
||||||
|
|
||||||
|
STATIC_CHECK(normalization::CompleteNormalizationFor<CompleteSmallPlan, SmallForm>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<MissingSmallPlan, SmallForm>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<DuplicateSmallPlan, SmallForm>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompleteNormalizationFor<ForeignSmallPlan, SmallForm>);
|
||||||
|
|
||||||
|
using Missing = normalization::NormalizationCoverage<SmallForm, MissingSmallPlan>;
|
||||||
|
using Duplicate = normalization::NormalizationCoverage<SmallForm, DuplicateSmallPlan>;
|
||||||
|
using Foreign = normalization::NormalizationCoverage<SmallForm, ForeignSmallPlan>;
|
||||||
|
STATIC_CHECK(Missing::MissingValueBlocks::size == 1);
|
||||||
|
STATIC_CHECK(blocks::contains_type_v<ValueB, typename Missing::MissingValueBlocks>);
|
||||||
|
STATIC_CHECK(Duplicate::RepeatedValueBlocks::size == 1);
|
||||||
|
STATIC_CHECK(blocks::contains_type_v<ValueA, typename Duplicate::RepeatedValueBlocks>);
|
||||||
|
STATIC_CHECK(Foreign::UnexpectedValueBlocks::size == 1);
|
||||||
|
STATIC_CHECK(Foreign::UnexpectedResidualBlocks::size == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Physical Riesz Methods Reject Incompatible Or Unregistered Field Topologies", "[normalization][type]") {
|
||||||
|
STATIC_CHECK_FALSE(normalization::NormalizationComponent<MalformedComponent>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::NormalizationComponent<IncoherentComponent>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::NormalizationComponent<WrongDensityTopology>);
|
||||||
|
STATIC_CHECK_FALSE(normalization::CompilableNormalizationFor<RieszPolicy, UnregisteredFutureForm>);
|
||||||
|
STATIC_CHECK(normalization::CompilableNormalizationFor<normalization::Unnormalized, UnregisteredFutureForm>);
|
||||||
|
|
||||||
|
using Density = normalization::PhysicalRieszBlockTraits<blocks::density::mass::value>;
|
||||||
|
using Gravity = normalization::PhysicalRieszBlockTraits<blocks::gravity::gradient::value>;
|
||||||
|
using Surface = normalization::PhysicalRieszBlockTraits<blocks::surface_deformation::parameters::value>;
|
||||||
|
using EnthalpyResidual = normalization::PhysicalRieszBlockTraits<blocks::enthalpy::specific::residual>;
|
||||||
|
using MassResidual = normalization::PhysicalRieszBlockTraits<
|
||||||
|
blocks::fixed_total_mass::mass_normalization::residual>;
|
||||||
|
|
||||||
|
STATIC_CHECK(Density::Method::topology == normalization::RieszTopology::scalar_volume_l2);
|
||||||
|
STATIC_CHECK(Gravity::Method::topology == normalization::RieszTopology::vector_volume_l2);
|
||||||
|
STATIC_CHECK(Surface::Method::topology == normalization::RieszTopology::scalar_boundary_l2);
|
||||||
|
STATIC_CHECK(
|
||||||
|
EnthalpyResidual::Method::topology == normalization::RieszTopology::hybrid_scalar_volume_point_rows
|
||||||
|
);
|
||||||
|
STATIC_CHECK(MassResidual::Method::topology == normalization::RieszTopology::global_scalar);
|
||||||
|
STATIC_CHECK(MassResidual::Method::scale == normalization::PhysicalScaleKind::mass);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Normalization Is Part Of The Compile-Time Discretization And Problem Type", "[normalization][type]") {
|
||||||
|
STATIC_CHECK(mean_field::equilibrium::StellarDiscretizationType<RieszDiscretization>);
|
||||||
|
STATIC_CHECK_FALSE(std::same_as<RieszDiscretization, mean_field::equilibrium::StellarDiscretization>);
|
||||||
|
STATIC_CHECK_FALSE(std::same_as<RieszProblem, BaselineProblem>);
|
||||||
|
STATIC_CHECK(std::same_as<typename BaselineProblem::NormalizationPrescriptionType, normalization::Unnormalized>);
|
||||||
|
STATIC_CHECK(std::same_as<typename RieszProblem::NormalizationPrescriptionType, RieszPolicy>);
|
||||||
|
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<RieszProblem>);
|
||||||
|
STATIC_CHECK(std::constructible_from<
|
||||||
|
RieszDiscretization,
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
const mean_field::mapping::DomainMapper &,
|
||||||
|
RieszPolicy>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
RieszDiscretization,
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
mean_field::mapping::DomainMapper &&,
|
||||||
|
RieszPolicy>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
RieszDiscretization,
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
const mean_field::mapping::DomainMapper &&,
|
||||||
|
RieszPolicy>);
|
||||||
|
STATIC_CHECK(std::constructible_from<
|
||||||
|
mean_field::equilibrium::StellarDiscretization,
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
const mean_field::mapping::DomainMapper &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
mean_field::equilibrium::StellarDiscretization,
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
mean_field::mapping::DomainMapper &&>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<
|
||||||
|
mean_field::equilibrium::StellarDiscretization,
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
const mean_field::mapping::DomainMapper &&>);
|
||||||
|
STATIC_CHECK(CanMakeRieszDiscretization<
|
||||||
|
mean_field::mapping::DomainMapper &>);
|
||||||
|
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<
|
||||||
|
mean_field::mapping::DomainMapper>);
|
||||||
|
STATIC_CHECK_FALSE(CanMakeRieszDiscretization<
|
||||||
|
const mean_field::mapping::DomainMapper>);
|
||||||
|
|
||||||
|
using SmallLayout = blocks::form_layout<SmallForm>;
|
||||||
|
using SmallBuilder = normalization::DiagonalNormalizationBuilder<SmallForm>;
|
||||||
|
STATIC_CHECK(std::constructible_from<SmallBuilder, const SmallLayout &>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<SmallBuilder, SmallLayout &&>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<SmallBuilder, const SmallLayout &&>);
|
||||||
|
}
|
||||||
1187
tests/normalization/stellar_equilibrium.cpp
Normal file
1187
tests/normalization/stellar_equilibrium.cpp
Normal file
File diff suppressed because it is too large
Load Diff
531
tests/operators/prepared_angular_momentum.cpp
Normal file
531
tests/operators/prepared_angular_momentum.cpp
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <catch2/catch_approx.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
|
import mean_field;
|
||||||
|
import test_helpers;
|
||||||
|
|
||||||
|
namespace angular_momentum_test_utils {
|
||||||
|
[[nodiscard]] mean_field::operators::AngularMomentumDependencies makeDependencies() {
|
||||||
|
return {
|
||||||
|
.discretization = {.identity = 15013, .revision = 3},
|
||||||
|
.density = {.identity = 15017, .revision = 5},
|
||||||
|
.displacement = {.identity = 15031, .revision = 7},
|
||||||
|
.rotation = {.identity = 15053, .revision = 11}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::operators::context::gravity_field::GravityFieldRevisions makeGravityRevisions(
|
||||||
|
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||||
|
const std::uint64_t gravityGradientRevision = 13,
|
||||||
|
const std::uint64_t gravityPotentialRevision = 17
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
.discretization = {.value = dependencies.discretization.revision},
|
||||||
|
.displacement = {.value = dependencies.displacement.revision},
|
||||||
|
.density = {.value = dependencies.density.revision},
|
||||||
|
.gravity_gradient = {.value = gravityGradientRevision},
|
||||||
|
.gravity_potential = {.value = gravityPotentialRevision}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void prepareGravityContext(
|
||||||
|
mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &context,
|
||||||
|
const mean_field::fem::FEM &finiteElements,
|
||||||
|
const mfem::Vector &density,
|
||||||
|
const mfem::Vector &displacement,
|
||||||
|
const mean_field::operators::AngularMomentumDependencies &dependencies,
|
||||||
|
const std::uint64_t gravityGradientRevision = 13,
|
||||||
|
const std::uint64_t gravityPotentialRevision = 17
|
||||||
|
) {
|
||||||
|
mfem::Vector gravityGradient(finiteElements.gravityFluxFes->GetTrueVSize());
|
||||||
|
mfem::Vector gravityPotential(finiteElements.gravityPotentialFes->GetTrueVSize());
|
||||||
|
gravityGradient = 0.0;
|
||||||
|
gravityPotential = 0.0;
|
||||||
|
context.Prepare(
|
||||||
|
{.density = context.GetDensityMap().gather(density),
|
||||||
|
.displacement = context.GetDisplacementMap().gather(displacement),
|
||||||
|
.gravity_gradient = context.GetGravityGradientMap().gather(gravityGradient),
|
||||||
|
.gravity_potential = context.GetGravityPotentialMap().gather(gravityPotential)},
|
||||||
|
makeGravityRevisions(dependencies, gravityGradientRevision, gravityPotentialRevision)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector projectDensity(
|
||||||
|
const mean_field::fem::FEM &finiteElements,
|
||||||
|
const double phase
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||||
|
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
||||||
|
return 0.94 + 0.08 * std::sin(0.71 * position(0) + phase) +
|
||||||
|
0.05 * std::cos(0.63 * position(1) - phase) + 0.03 * position(2) * position(2);
|
||||||
|
});
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector projectDensityDirection(
|
||||||
|
const mean_field::fem::FEM &finiteElements,
|
||||||
|
const double phase
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||||
|
mfem::FunctionCoefficient coefficient([phase](const mfem::Vector &position) {
|
||||||
|
return 0.17 * std::sin(0.83 * position(0) + phase) -
|
||||||
|
0.12 * std::cos(0.79 * position(1) - phase) + 0.06 * position(2);
|
||||||
|
});
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector projectConstantDensity(
|
||||||
|
const mean_field::fem::FEM &finiteElements,
|
||||||
|
const double value,
|
||||||
|
mfem::ParGridFunction *fieldOutput = nullptr
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(finiteElements.densityFes.get());
|
||||||
|
mfem::ConstantCoefficient coefficient(value);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
if (fieldOutput != nullptr) {
|
||||||
|
*fieldOutput = field;
|
||||||
|
}
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector projectAffineDisplacement(
|
||||||
|
const mean_field::fem::FEM &finiteElements,
|
||||||
|
const double scale
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
finiteElements.mesh->Dimension(),
|
||||||
|
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(position.Size());
|
||||||
|
for (int component = 0; component < position.Size(); ++component) {
|
||||||
|
value(component) = scale * position(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mfem::Vector projectDisplacementDirection(
|
||||||
|
const mean_field::fem::FEM &finiteElements,
|
||||||
|
const double scale
|
||||||
|
) {
|
||||||
|
mfem::ParGridFunction field(finiteElements.displacementFes.get());
|
||||||
|
mfem::VectorFunctionCoefficient coefficient(
|
||||||
|
finiteElements.mesh->Dimension(),
|
||||||
|
[scale](const mfem::Vector &position, mfem::Vector &value) {
|
||||||
|
value.SetSize(3);
|
||||||
|
value(0) = scale * (0.07 * position(0) + 0.018 * position(1) * position(2));
|
||||||
|
value(1) = scale * (-0.05 * position(1) + 0.013 * position(0) * position(2));
|
||||||
|
value(2) = scale * (0.04 * position(2) - 0.011 * position(0) * position(1));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
field.ProjectCoefficient(coefficient);
|
||||||
|
mfem::Vector result;
|
||||||
|
field.GetTrueDofs(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double residual(const mean_field::operators::PreparedAngularMomentumOperator &operation) {
|
||||||
|
mfem::Vector value;
|
||||||
|
operation.BuildResidual(value);
|
||||||
|
REQUIRE(value.Size() == 1);
|
||||||
|
return value(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double relativeError(const double actual, const double expected) {
|
||||||
|
return std::abs(actual - expected) /
|
||||||
|
std::max({std::abs(actual), std::abs(expected), 100.0 * std::numeric_limits<double>::epsilon()});
|
||||||
|
}
|
||||||
|
} // namespace angular_momentum_test_utils
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Angular Momentum Satisfies Moment Scaling And The Parallel Axis Theorem",
|
||||||
|
"[fixed-angular-momentum][physics][analytic]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using Catch::Approx;
|
||||||
|
using Operator = operators::PreparedAngularMomentumOperator;
|
||||||
|
|
||||||
|
STATIC_CHECK_FALSE(std::is_copy_constructible_v<Operator>);
|
||||||
|
STATIC_CHECK_FALSE(std::is_move_constructible_v<Operator>);
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
constexpr double densityValue = 1.37;
|
||||||
|
constexpr double angularVelocity = 0.73;
|
||||||
|
constexpr double targetAngularMomentum = 0.41;
|
||||||
|
mfem::ParGridFunction densityField(finiteElements.densityFes.get());
|
||||||
|
const mfem::Vector density = angular_momentum_test_utils::projectConstantDensity(
|
||||||
|
finiteElements,
|
||||||
|
densityValue,
|
||||||
|
&densityField
|
||||||
|
);
|
||||||
|
mfem::Vector displacement(finiteElements.displacementFes->GetTrueVSize());
|
||||||
|
displacement = 0.0;
|
||||||
|
finiteElements.displacement->SetFromTrueDofs(displacement);
|
||||||
|
|
||||||
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless
|
||||||
|
);
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
|
||||||
|
const models::CompiledFixedAngularMomentum originConstraint = models::compileConstraint(
|
||||||
|
integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||||
|
.axis = {0.0, 0.0, 4.0}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
Operator origin(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless,
|
||||||
|
gravityContext,
|
||||||
|
originConstraint
|
||||||
|
);
|
||||||
|
const auto initial = origin.Prepare(angularVelocity, dependencies);
|
||||||
|
CHECK(initial.rebuiltStaticPlan);
|
||||||
|
CHECK(initial.refreshedGeometry);
|
||||||
|
CHECK(initial.refreshedDensity);
|
||||||
|
CHECK(initial.updatedAngularVelocity);
|
||||||
|
CHECK(initial.assembledResidual);
|
||||||
|
|
||||||
|
const double independentMoment = analysis::get_moment_of_inertia(finiteElements, densityField);
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(origin.GetMomentOfInertia(), independentMoment) < 2.0e-13);
|
||||||
|
CHECK(origin.GetCurrentAngularMomentum() ==
|
||||||
|
Approx(angularVelocity * origin.GetMomentOfInertia()).epsilon(2.0e-15));
|
||||||
|
CHECK(angular_momentum_test_utils::residual(origin) ==
|
||||||
|
Approx(angularVelocity * origin.GetMomentOfInertia() - targetAngularMomentum).epsilon(2.0e-15));
|
||||||
|
|
||||||
|
const auto report = origin.GetConstraintReport();
|
||||||
|
CHECK(report.targetAngularMomentum == targetAngularMomentum);
|
||||||
|
CHECK(report.achievedAngularMomentum == origin.GetCurrentAngularMomentum());
|
||||||
|
CHECK(report.momentOfInertia == origin.GetMomentOfInertia());
|
||||||
|
CHECK(report.angularVelocity == angularVelocity);
|
||||||
|
const physics::RigidRotation rotation = origin.GetRotation();
|
||||||
|
CHECK(rotation.angular_velocity()(0) == 0.0);
|
||||||
|
CHECK(rotation.angular_velocity()(1) == 0.0);
|
||||||
|
CHECK(rotation.angular_velocity()(2) == angularVelocity);
|
||||||
|
|
||||||
|
constexpr double affineScale = 0.086;
|
||||||
|
const mfem::Vector affineDisplacement =
|
||||||
|
angular_momentum_test_utils::projectAffineDisplacement(finiteElements, affineScale);
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
affineDisplacement,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
const auto affine = origin.Prepare(angularVelocity, dependencies);
|
||||||
|
CHECK(affine.refreshedGeometry);
|
||||||
|
CHECK_FALSE(affine.refreshedDensity);
|
||||||
|
const double expectedAffineRatio = std::pow(1.0 + affineScale, 5);
|
||||||
|
const double measuredAffineRatio = origin.GetMomentOfInertia() / independentMoment;
|
||||||
|
INFO("Expected homothetic I ratio = " << expectedAffineRatio);
|
||||||
|
INFO("Measured homothetic I ratio = " << measuredAffineRatio);
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(measuredAffineRatio, expectedAffineRatio) < 7.0e-7);
|
||||||
|
|
||||||
|
displacement = 0.0;
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
origin.Prepare(angularVelocity, dependencies);
|
||||||
|
constexpr std::array<double, 3> shiftedCenter{0.27, -0.19, 0.31};
|
||||||
|
Operator shifted(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless,
|
||||||
|
gravityContext,
|
||||||
|
models::compileConstraint(integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||||
|
.axis = {0.0, 0.0, 1.0},
|
||||||
|
.center = shiftedCenter
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
shifted.Prepare(angularVelocity, dependencies);
|
||||||
|
|
||||||
|
const double mass = analysis::domain_integrate_grid_function(
|
||||||
|
finiteElements,
|
||||||
|
densityField,
|
||||||
|
utils::DOMAINS::STELLAR,
|
||||||
|
mapping::COORDINATE_SPACE::PHYSICAL
|
||||||
|
);
|
||||||
|
const mfem::Vector centerOfMass = analysis::get_com(finiteElements, densityField);
|
||||||
|
const double expectedShiftedMoment = origin.GetMomentOfInertia() +
|
||||||
|
mass * (shiftedCenter[0] * shiftedCenter[0] +
|
||||||
|
shiftedCenter[1] * shiftedCenter[1]) -
|
||||||
|
2.0 * mass * (shiftedCenter[0] * centerOfMass(0) +
|
||||||
|
shiftedCenter[1] * centerOfMass(1));
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(shifted.GetMomentOfInertia(), expectedShiftedMoment) < 3.0e-13);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Angular Momentum Jacobian Matches Density Geometry And Angular Velocity Differences",
|
||||||
|
"[fixed-angular-momentum][jacobian][accuracy]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
const mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.31);
|
||||||
|
const mfem::Vector densityDirection =
|
||||||
|
angular_momentum_test_utils::projectDensityDirection(finiteElements, 0.67);
|
||||||
|
const mfem::Vector displacement =
|
||||||
|
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.43);
|
||||||
|
const mfem::Vector displacementDirection =
|
||||||
|
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, -0.79);
|
||||||
|
constexpr double angularVelocity = 0.63;
|
||||||
|
constexpr double angularVelocityDirection = -0.37;
|
||||||
|
|
||||||
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless
|
||||||
|
);
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
operators::PreparedAngularMomentumOperator operation(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless,
|
||||||
|
gravityContext,
|
||||||
|
models::compileConstraint(
|
||||||
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.81}})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
|
|
||||||
|
const mfem::Vector reducedDensityDirection = gravityContext.GetDensityMap().gather(densityDirection);
|
||||||
|
const mfem::Vector reducedDisplacementDirection =
|
||||||
|
gravityContext.GetDisplacementMap().gather(displacementDirection);
|
||||||
|
mfem::Vector densityAction;
|
||||||
|
mfem::Vector geometryAction;
|
||||||
|
mfem::Vector angularVelocityAction;
|
||||||
|
mfem::Vector completeAction;
|
||||||
|
operation.ApplyDensityJacobianAction(reducedDensityDirection, densityAction);
|
||||||
|
operation.ApplyDisplacementJacobianAction(reducedDisplacementDirection, geometryAction);
|
||||||
|
operation.ApplyAngularVelocityJacobianAction(angularVelocityDirection, angularVelocityAction);
|
||||||
|
operation.ApplyCompleteJacobianAction(
|
||||||
|
reducedDensityDirection,
|
||||||
|
reducedDisplacementDirection,
|
||||||
|
angularVelocityDirection,
|
||||||
|
completeAction
|
||||||
|
);
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(
|
||||||
|
completeAction(0),
|
||||||
|
densityAction(0) + geometryAction(0) + angularVelocityAction(0)
|
||||||
|
) < 3.0e-15);
|
||||||
|
CHECK(angularVelocityAction(0) ==
|
||||||
|
Catch::Approx(operation.GetMomentOfInertia() * angularVelocityDirection).epsilon(2.0e-15));
|
||||||
|
|
||||||
|
constexpr double angularStep = 1.0e-6;
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
operation.Prepare(angularVelocity + angularStep * angularVelocityDirection, dependencies);
|
||||||
|
const double angularPlus = angular_momentum_test_utils::residual(operation);
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
operation.Prepare(angularVelocity - angularStep * angularVelocityDirection, dependencies);
|
||||||
|
const double angularMinus = angular_momentum_test_utils::residual(operation);
|
||||||
|
const double angularDifference = (angularPlus - angularMinus) / (2.0 * angularStep);
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(angularVelocityAction(0), angularDifference) < 2.0e-10);
|
||||||
|
|
||||||
|
constexpr double densityStep = 1.0e-3;
|
||||||
|
mfem::Vector densityPlus(density);
|
||||||
|
densityPlus.Add(densityStep, densityDirection);
|
||||||
|
++dependencies.density.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
densityPlus,
|
||||||
|
displacement,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
|
const double densityPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
|
mfem::Vector densityMinus(density);
|
||||||
|
densityMinus.Add(-densityStep, densityDirection);
|
||||||
|
++dependencies.density.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
densityMinus,
|
||||||
|
displacement,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
|
const double densityMinusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
|
const double densityDifference = (densityPlusResidual - densityMinusResidual) / (2.0 * densityStep);
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(densityAction(0), densityDifference) < 4.0e-8);
|
||||||
|
|
||||||
|
constexpr double geometryStep = 1.0e-6;
|
||||||
|
mfem::Vector displacementPlus(displacement);
|
||||||
|
displacementPlus.Add(geometryStep, displacementDirection);
|
||||||
|
++dependencies.density.revision;
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacementPlus,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
|
const double geometryPlusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
|
mfem::Vector displacementMinus(displacement);
|
||||||
|
displacementMinus.Add(-geometryStep, displacementDirection);
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacementMinus,
|
||||||
|
dependencies
|
||||||
|
);
|
||||||
|
operation.Prepare(angularVelocity, dependencies);
|
||||||
|
const double geometryMinusResidual = angular_momentum_test_utils::residual(operation);
|
||||||
|
const double geometryDifference = (geometryPlusResidual - geometryMinusResidual) / (2.0 * geometryStep);
|
||||||
|
INFO("Density angular-momentum derivative error = " <<
|
||||||
|
angular_momentum_test_utils::relativeError(densityAction(0), densityDifference));
|
||||||
|
INFO("Geometry angular-momentum derivative error = " <<
|
||||||
|
angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference));
|
||||||
|
CHECK(angular_momentum_test_utils::relativeError(geometryAction(0), geometryDifference) < 4.0e-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Prepared Angular Momentum Refreshes Only Changed Runtime Data",
|
||||||
|
"[fixed-angular-momentum][prepared][lifecycle]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
mfem::Vector density = angular_momentum_test_utils::projectDensity(finiteElements, 0.29);
|
||||||
|
mfem::Vector displacement =
|
||||||
|
angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.41);
|
||||||
|
auto dependencies = angular_momentum_test_utils::makeDependencies();
|
||||||
|
std::uint64_t gravityPotentialRevision = 17;
|
||||||
|
|
||||||
|
operators::context::gravity_field::GravityFieldLinearizationContext gravityContext(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless
|
||||||
|
);
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies,
|
||||||
|
13,
|
||||||
|
gravityPotentialRevision
|
||||||
|
);
|
||||||
|
operators::PreparedAngularMomentumOperator operation(
|
||||||
|
finiteElements,
|
||||||
|
*finiteElements.domainMapperStateless,
|
||||||
|
gravityContext,
|
||||||
|
models::compileConstraint(
|
||||||
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.71}})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
operation.Prepare(0.52, dependencies);
|
||||||
|
const auto preparationCount = operation.GetPreparationCount();
|
||||||
|
const double moment = operation.GetMomentOfInertia();
|
||||||
|
|
||||||
|
const auto repeated = operation.Prepare(0.52, dependencies);
|
||||||
|
CHECK_FALSE(repeated.DidAnyWork());
|
||||||
|
CHECK(operation.GetPreparationCount() == preparationCount);
|
||||||
|
|
||||||
|
++gravityPotentialRevision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies,
|
||||||
|
13,
|
||||||
|
gravityPotentialRevision
|
||||||
|
);
|
||||||
|
const auto unrelatedPotential = operation.Prepare(0.52, dependencies);
|
||||||
|
CHECK_FALSE(unrelatedPotential.DidAnyWork());
|
||||||
|
|
||||||
|
const double residualBeforeRotation = angular_momentum_test_utils::residual(operation);
|
||||||
|
++dependencies.rotation.revision;
|
||||||
|
const auto rotationOnly = operation.Prepare(0.81, dependencies);
|
||||||
|
CHECK(rotationOnly.updatedAngularVelocity);
|
||||||
|
CHECK(rotationOnly.assembledResidual);
|
||||||
|
CHECK_FALSE(rotationOnly.refreshedDensity);
|
||||||
|
CHECK_FALSE(rotationOnly.refreshedGeometry);
|
||||||
|
CHECK(operation.GetMomentOfInertia() == moment);
|
||||||
|
CHECK(angular_momentum_test_utils::residual(operation) - residualBeforeRotation ==
|
||||||
|
Catch::Approx((0.81 - 0.52) * moment).epsilon(3.0e-15));
|
||||||
|
|
||||||
|
density = angular_momentum_test_utils::projectDensity(finiteElements, 0.83);
|
||||||
|
++dependencies.density.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies,
|
||||||
|
13,
|
||||||
|
gravityPotentialRevision
|
||||||
|
);
|
||||||
|
const auto densityOnly = operation.Prepare(0.81, dependencies);
|
||||||
|
CHECK(densityOnly.refreshedDensity);
|
||||||
|
CHECK_FALSE(densityOnly.refreshedGeometry);
|
||||||
|
CHECK_FALSE(densityOnly.updatedAngularVelocity);
|
||||||
|
|
||||||
|
displacement = angular_momentum_test_utils::projectDisplacementDirection(finiteElements, 0.87);
|
||||||
|
++dependencies.displacement.revision;
|
||||||
|
angular_momentum_test_utils::prepareGravityContext(
|
||||||
|
gravityContext,
|
||||||
|
finiteElements,
|
||||||
|
density,
|
||||||
|
displacement,
|
||||||
|
dependencies,
|
||||||
|
13,
|
||||||
|
gravityPotentialRevision
|
||||||
|
);
|
||||||
|
const auto geometryOnly = operation.Prepare(0.81, dependencies);
|
||||||
|
CHECK(geometryOnly.refreshedGeometry);
|
||||||
|
CHECK_FALSE(geometryOnly.refreshedDensity);
|
||||||
|
CHECK_FALSE(geometryOnly.updatedAngularVelocity);
|
||||||
|
}
|
||||||
@@ -44,20 +44,11 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Central Density Bordered Root Preserves The Physical Operator Prefix",
|
"Central Density Contribution Composes Through The Variadic Root",
|
||||||
tags::central_density_phase_integration
|
tags::central_density_phase_integration
|
||||||
) {
|
) {
|
||||||
using namespace mean_field;
|
using namespace mean_field;
|
||||||
|
|
||||||
STATIC_CHECK_FALSE(
|
|
||||||
std::same_as<
|
|
||||||
operators::PreparedStellarEquilibriumOperator, operators::PreparedCentralDensityStellarEquilibriumOperator>
|
|
||||||
);
|
|
||||||
STATIC_CHECK(
|
|
||||||
operators::CentralDensityStellarEquilibriumSpecificationModel::compilationClass ==
|
|
||||||
models::ModelCompilationClass::isolated_root
|
|
||||||
);
|
|
||||||
|
|
||||||
utils::Args args = test_utils::setup_args();
|
utils::Args args = test_utils::setup_args();
|
||||||
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
fem::FEM f = fem::setup_fem(args.mesh_file, args, 0);
|
||||||
REQUIRE(f.okay());
|
REQUIRE(f.okay());
|
||||||
@@ -80,7 +71,8 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
std::same_as<
|
std::same_as<
|
||||||
typename std::remove_cvref_t<decltype(equilibriumProblem)>::PreparedOperatorType,
|
typename std::remove_cvref_t<decltype(equilibriumProblem)>::PreparedOperatorType,
|
||||||
operators::PreparedCentralDensityStellarEquilibriumOperator>
|
operators::PreparedVariadicStellarEquilibriumOperator<
|
||||||
|
typename std::remove_cvref_t<decltype(equilibriumProblem)>::ModelType>>
|
||||||
);
|
);
|
||||||
CHECK(
|
CHECK(
|
||||||
equilibriumProblem.GetStellarModel().specification<constraint::FixedCentralDensity>().targetDensity() ==
|
equilibriumProblem.GetStellarModel().specification<constraint::FixedCentralDensity>().targetDensity() ==
|
||||||
@@ -95,19 +87,21 @@ TEST_CASE(
|
|||||||
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
|
CHECK(borderedOperator.GetRootManifest().constraints().size() == 3);
|
||||||
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
|
CHECK(borderedOperator.GetRootManifest().specificationDescriptors().size() == 4);
|
||||||
|
|
||||||
const auto constraints = borderedOperator.GetRootManifest().constraints();
|
const auto ¢ralDescriptor =
|
||||||
CHECK(constraints[2].stableId == "FixedCentralDensity");
|
borderedOperator.GetRootManifest().specification<constraint::FixedCentralDensity>();
|
||||||
CHECK(constraints[2].role == models::SpecificationRole::phase_condition);
|
CHECK(centralDescriptor.stableId == "FixedCentralDensity");
|
||||||
CHECK(constraints[2].columnPolicy == operators::RootColumnPolicy::solver_border);
|
CHECK(centralDescriptor.role == models::SpecificationRole::phase_condition);
|
||||||
CHECK(constraints[2].target == 1.0);
|
CHECK(centralDescriptor.columnPolicy == operators::RootColumnPolicy::solver_border);
|
||||||
REQUIRE(constraints[2].carrierTarget.has_value());
|
CHECK(centralDescriptor.target == 1.0);
|
||||||
CHECK(*constraints[2].carrierTarget == 1.0);
|
REQUIRE(centralDescriptor.carrierTarget.has_value());
|
||||||
CHECK(constraints[2].targetUnits == "density");
|
CHECK(*centralDescriptor.carrierTarget == 1.0);
|
||||||
CHECK(constraints[2].residualUnits == "specific_enthalpy");
|
CHECK(centralDescriptor.targetUnits == "density");
|
||||||
|
CHECK(centralDescriptor.residualUnits == "specific_enthalpy");
|
||||||
|
|
||||||
mfem::Vector physicalState(physicalOperator.Width());
|
mfem::Vector physicalState(physicalOperator.Width());
|
||||||
physicalState = 0.0;
|
physicalState = 0.0;
|
||||||
const auto physicalStateView = physicalOperator.GetRootStateView(physicalState);
|
const auto physicalStateView =
|
||||||
|
physicalOperator.GetRootManifest().stateView(physicalState);
|
||||||
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
physicalStateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
physicalStateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
physicalStateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
|
||||||
@@ -118,10 +112,9 @@ TEST_CASE(
|
|||||||
const operators::StellarEquilibriumDependencies dependencies = make_dependencies();
|
const operators::StellarEquilibriumDependencies dependencies = make_dependencies();
|
||||||
const physics::RigidRotation rotation = make_zero_rotation();
|
const physics::RigidRotation rotation = make_zero_rotation();
|
||||||
physicalOperator.Prepare(physicalState, dependencies, rotation);
|
physicalOperator.Prepare(physicalState, dependencies, rotation);
|
||||||
const operators::PreparedCentralDensityStellarEquilibriumReport initialReport =
|
const auto initialReport = equilibriumProblem.Prepare(borderedState, dependencies, rotation);
|
||||||
equilibriumProblem.Prepare(borderedState, dependencies, rotation);
|
|
||||||
CHECK(initialReport.physical.assembledResidual);
|
CHECK(initialReport.physical.assembledResidual);
|
||||||
CHECK(initialReport.phase.assembledResidual);
|
CHECK(initialReport.specification<constraint::FixedCentralDensity>().constraint.assembledResidual);
|
||||||
CHECK(initialReport.assembledResidual);
|
CHECK(initialReport.assembledResidual);
|
||||||
|
|
||||||
mfem::Vector physicalResidual;
|
mfem::Vector physicalResidual;
|
||||||
@@ -164,13 +157,13 @@ TEST_CASE(
|
|||||||
|
|
||||||
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
const auto repeatedReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||||
CHECK_FALSE(repeatedReport.physical.DidAnyWork());
|
CHECK_FALSE(repeatedReport.physical.DidAnyWork());
|
||||||
CHECK_FALSE(repeatedReport.phase.DidAnyWork());
|
CHECK_FALSE(repeatedReport.specification<constraint::FixedCentralDensity>().DidAnyWork());
|
||||||
CHECK_FALSE(repeatedReport.assembledResidual);
|
CHECK(repeatedReport.assembledResidual);
|
||||||
|
|
||||||
borderedState(borderedState.Size() - 1) = 0.375;
|
borderedState(borderedState.Size() - 1) = 0.375;
|
||||||
const auto borderReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
const auto borderReport = borderedOperator.Prepare(borderedState, dependencies, rotation);
|
||||||
CHECK_FALSE(borderReport.physical.DidAnyWork());
|
CHECK_FALSE(borderReport.physical.DidAnyWork());
|
||||||
CHECK(borderReport.phase.refreshedBorder);
|
CHECK(borderReport.specification<constraint::FixedCentralDensity>().constraint.refreshedBorder);
|
||||||
CHECK(borderReport.assembledResidual);
|
CHECK(borderReport.assembledResidual);
|
||||||
|
|
||||||
mfem::Vector borderOnlyDirection(borderedOperator.Width());
|
mfem::Vector borderOnlyDirection(borderedOperator.Width());
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
2409
tests/operators/stellar_equilibrium_compiler.cpp
Normal file
2409
tests/operators/stellar_equilibrium_compiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
#include <numbers>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -10,6 +11,83 @@
|
|||||||
import mean_field;
|
import mean_field;
|
||||||
import test_helpers;
|
import test_helpers;
|
||||||
|
|
||||||
|
namespace outer_manifest_report_test {
|
||||||
|
template <mean_field::model::StellarModelType Model>
|
||||||
|
class PreparedEarlierMultiplier;
|
||||||
|
|
||||||
|
class EarlierMultiplier final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
mean_field::dimensions::SpecificEnergyValue target;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||||
|
EarlierMultiplier,
|
||||||
|
"AardvarkOuterManifestMultiplier",
|
||||||
|
mean_field::models::DependsOn<mean_field::models::stellar::state::Density>,
|
||||||
|
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>,
|
||||||
|
mean_field::models::GlobalScalarNormalization<
|
||||||
|
mean_field::models::PhysicalScaleLaw::specific_energy,
|
||||||
|
mean_field::models::PhysicalScaleLaw::specific_energy>,
|
||||||
|
mean_field::models::GeneratedManifest<
|
||||||
|
"aardvark_outer_manifest.value",
|
||||||
|
"a",
|
||||||
|
"aardvark_outer_manifest.residual",
|
||||||
|
"R_a",
|
||||||
|
"specific_energy",
|
||||||
|
"specific_energy">>;
|
||||||
|
using EquilibriumPhysics =
|
||||||
|
mean_field::operators::SpecificationEquilibriumPhysics<
|
||||||
|
PreparedEarlierMultiplier>;
|
||||||
|
|
||||||
|
explicit EarlierMultiplier(const Parameters parameters) noexcept
|
||||||
|
: m_target(parameters.target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::SpecificEnergyValue target() const noexcept {
|
||||||
|
return m_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mean_field::dimensions::SpecificEnergyValue m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <mean_field::model::StellarModelType Model>
|
||||||
|
class PreparedEarlierMultiplier final {
|
||||||
|
public:
|
||||||
|
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
||||||
|
|
||||||
|
explicit PreparedEarlierMultiplier(const EarlierMultiplier &) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename StateView>
|
||||||
|
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Equation, typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||||
|
Equation,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::structuralZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Equation, typename State, typename Direction, typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
|
mean_field::stellar::Derivative<Equation, State>,
|
||||||
|
const Direction &,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::zeroDerivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace outer_manifest_report_test
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
using BaseModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
mean_field::eos::Polytrope,
|
mean_field::eos::Polytrope,
|
||||||
@@ -22,9 +100,30 @@ namespace {
|
|||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::constraint::FixedCentralDensity>>;
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
|
|
||||||
|
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::integral::FixedTotalMass,
|
||||||
|
mean_field::integral::FixedAngularMomentum>>;
|
||||||
|
|
||||||
|
using AngularMomentumCentralDensityModel =
|
||||||
|
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::integral::FixedTotalMass,
|
||||||
|
mean_field::integral::FixedAngularMomentum,
|
||||||
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
|
|
||||||
using IncompleteModel =
|
using IncompleteModel =
|
||||||
mean_field::model::StellarModel<mean_field::models::SpecificationSet<mean_field::eos::Polytrope>>;
|
mean_field::model::StellarModel<mean_field::models::SpecificationSet<mean_field::eos::Polytrope>>;
|
||||||
|
|
||||||
|
using EarlierMultiplierModel =
|
||||||
|
mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
outer_manifest_report_test::EarlierMultiplier,
|
||||||
|
mean_field::integral::FixedTotalMass>>;
|
||||||
|
|
||||||
template <typename Candidate>
|
template <typename Candidate>
|
||||||
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
|
concept HasLegacyNumericalModelAdapter = requires { typename Candidate::NumericalModelAdapter; };
|
||||||
|
|
||||||
@@ -58,6 +157,7 @@ namespace {
|
|||||||
difference -= right;
|
difference -= right;
|
||||||
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
return difference.Norml2() / std::max({1.0, left.Norml2(), right.Norml2()});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -68,26 +168,65 @@ TEST_CASE(
|
|||||||
|
|
||||||
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
|
using BaseProblem = equilibrium::StellarEquilibriumProblem<BaseModel>;
|
||||||
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
using CentralDensityProblem = equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||||
|
using AngularMomentumProblem = equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
||||||
|
using AngularMomentumCentralDensityProblem =
|
||||||
|
equilibrium::StellarEquilibriumProblem<AngularMomentumCentralDensityModel>;
|
||||||
|
|
||||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<BaseModel>);
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<BaseModel>);
|
||||||
STATIC_CHECK(equilibrium::StellarEquilibriumModel<CentralDensityModel>);
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<CentralDensityModel>);
|
||||||
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumModel>);
|
||||||
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<AngularMomentumCentralDensityModel>);
|
||||||
|
STATIC_CHECK(equilibrium::StellarEquilibriumModel<EarlierMultiplierModel>);
|
||||||
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
STATIC_CHECK_FALSE(equilibrium::StellarEquilibriumModel<IncompleteModel>);
|
||||||
|
STATIC_CHECK_FALSE(operators::StellarEquilibriumRuntimeContribution<
|
||||||
|
outer_manifest_report_test::EarlierMultiplier>::registered);
|
||||||
|
STATIC_CHECK_FALSE(operators::stellarEquilibriumBackendRuntimeAuthorized<
|
||||||
|
outer_manifest_report_test::EarlierMultiplier,
|
||||||
|
EarlierMultiplierModel>);
|
||||||
|
STATIC_CHECK(operators::StellarEquilibriumPhysicsAvailableFor<
|
||||||
|
outer_manifest_report_test::EarlierMultiplier,
|
||||||
|
EarlierMultiplierModel>);
|
||||||
STATIC_CHECK_FALSE(std::same_as<BaseProblem, CentralDensityProblem>);
|
STATIC_CHECK_FALSE(std::same_as<BaseProblem, CentralDensityProblem>);
|
||||||
STATIC_CHECK(BaseProblem::symbolicallySquare);
|
STATIC_CHECK(BaseProblem::symbolicallySquare);
|
||||||
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
|
STATIC_CHECK(CentralDensityProblem::symbolicallySquare);
|
||||||
STATIC_CHECK_FALSE(BaseProblem::hasFixedCentralDensity);
|
STATIC_CHECK_FALSE(BaseProblem::hasFixedCentralDensity);
|
||||||
STATIC_CHECK(CentralDensityProblem::hasFixedCentralDensity);
|
STATIC_CHECK(CentralDensityProblem::hasFixedCentralDensity);
|
||||||
|
STATIC_CHECK(AngularMomentumProblem::hasFixedAngularMomentum);
|
||||||
|
STATIC_CHECK_FALSE(AngularMomentumProblem::hasFixedCentralDensity);
|
||||||
|
STATIC_CHECK(AngularMomentumCentralDensityProblem::hasFixedAngularMomentum);
|
||||||
|
STATIC_CHECK(AngularMomentumCentralDensityProblem::hasFixedCentralDensity);
|
||||||
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<BaseProblem>);
|
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<BaseProblem>);
|
||||||
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<CentralDensityProblem>);
|
STATIC_CHECK_FALSE(HasLegacyNumericalModelAdapter<CentralDensityProblem>);
|
||||||
STATIC_CHECK(std::same_as<BaseProblem, equilibrium::StellarEquilibriumSystem<BaseModel>>);
|
STATIC_CHECK(std::same_as<BaseProblem, equilibrium::StellarEquilibriumSystem<BaseModel>>);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
std::same_as<typename BaseProblem::PreparedOperatorType, operators::PreparedStellarEquilibriumOperator>
|
std::same_as<
|
||||||
|
typename BaseProblem::PreparedOperatorType,
|
||||||
|
operators::PreparedVariadicStellarEquilibriumOperator<BaseModel>>
|
||||||
);
|
);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
std::same_as<
|
std::same_as<
|
||||||
typename CentralDensityProblem::PreparedOperatorType,
|
typename CentralDensityProblem::PreparedOperatorType,
|
||||||
operators::PreparedCentralDensityStellarEquilibriumOperator>
|
operators::PreparedVariadicStellarEquilibriumOperator<CentralDensityModel>>
|
||||||
);
|
);
|
||||||
|
STATIC_CHECK(
|
||||||
|
std::same_as<
|
||||||
|
typename AngularMomentumProblem::PreparedOperatorType,
|
||||||
|
operators::PreparedVariadicStellarEquilibriumOperator<AngularMomentumModel>>
|
||||||
|
);
|
||||||
|
STATIC_CHECK_FALSE(std::same_as<
|
||||||
|
typename BaseProblem::PreparedOperatorType,
|
||||||
|
typename CentralDensityProblem::PreparedOperatorType>);
|
||||||
|
STATIC_CHECK_FALSE(std::same_as<
|
||||||
|
typename AngularMomentumProblem::PreparedOperatorType,
|
||||||
|
typename AngularMomentumCentralDensityProblem::PreparedOperatorType>);
|
||||||
|
STATIC_CHECK(AngularMomentumProblem::FormType::value_block_count == 7);
|
||||||
|
STATIC_CHECK(AngularMomentumCentralDensityProblem::FormType::value_block_count == 8);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename BaseProblem::FormType,
|
||||||
|
utils::blocks::surface_deformed_stellar_equilibrium_form>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename CentralDensityProblem::FormType,
|
||||||
|
utils::blocks::central_density_bordered_stellar_equilibrium_form>);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
std::same_as<
|
std::same_as<
|
||||||
typename BaseProblem::CompiledSurfaceConstraintType,
|
typename BaseProblem::CompiledSurfaceConstraintType,
|
||||||
@@ -97,6 +236,183 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(material::CompiledThermodynamicEquations<typename BaseProblem::ThermodynamicEquationsType>);
|
STATIC_CHECK(material::CompiledThermodynamicEquations<typename BaseProblem::ThermodynamicEquationsType>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Fixed Angular Momentum Root Uses Its Generated Angular Velocity In Every Physical Row",
|
||||||
|
"[fixed-angular-momentum][stellar-equilibrium][jacobian][integration]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
constexpr double radius = utils::RADIUS;
|
||||||
|
constexpr double mass = utils::MASS;
|
||||||
|
constexpr double targetAngularMomentum = 0.1;
|
||||||
|
const double polytropicConstant = 2.0 * utils::G * radius * radius / std::numbers::pi_v<double>;
|
||||||
|
const double seedCentralDensity =
|
||||||
|
std::numbers::pi_v<double> * mass / (4.0 * radius * radius * radius);
|
||||||
|
auto model = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
|
integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{targetAngularMomentum},
|
||||||
|
.axis = {0.0, 0.0, 3.0}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
auto problem = equilibrium::discretize(model, finiteElements);
|
||||||
|
auto projected = seed::makeProjectedEquilibriumState(
|
||||||
|
problem,
|
||||||
|
seed::LaneEmden({
|
||||||
|
.centralDensity = dimensions::DensityValue{seedCentralDensity},
|
||||||
|
.radialSampleCount = 1024
|
||||||
|
})
|
||||||
|
);
|
||||||
|
auto dependencies = make_dependencies();
|
||||||
|
|
||||||
|
const auto preparation = problem.Prepare(projected.values, dependencies);
|
||||||
|
CHECK(preparation.generatedPhysicalControl);
|
||||||
|
CHECK(preparation.physical.DidAnyWork());
|
||||||
|
CHECK(preparation.template specification<models::FixedAngularMomentum>().constraint.DidAnyWork());
|
||||||
|
CHECK(preparation.template specification<models::FixedAngularMomentum>().generatedRotation);
|
||||||
|
CHECK(problem.IsPrepared());
|
||||||
|
const auto angularReport = problem.GetPreparedOperator().GetAngularMomentumReport();
|
||||||
|
CHECK(angularReport.targetAngularMomentum == targetAngularMomentum);
|
||||||
|
CHECK(angularReport.angularVelocity > 0.0);
|
||||||
|
CHECK(angularReport.momentOfInertia > 0.0);
|
||||||
|
CHECK(std::abs(angularReport.scaledResidual) < 7.0e-4);
|
||||||
|
|
||||||
|
mfem::Vector direction(problem.StateSize());
|
||||||
|
direction = 0.0;
|
||||||
|
mfem::Vector angularVelocityDirection = problem.GetManifest().stateView(direction).block(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
);
|
||||||
|
REQUIRE(angularVelocityDirection.Size() == 1);
|
||||||
|
angularVelocityDirection(0) = -0.37;
|
||||||
|
angularVelocityDirection.SyncAliasMemory(direction);
|
||||||
|
|
||||||
|
mfem::Vector analyticAction;
|
||||||
|
problem.ApplyLinearization(direction, analyticAction);
|
||||||
|
|
||||||
|
constexpr double step = 1.0e-5;
|
||||||
|
mfem::Vector plusState(projected.values);
|
||||||
|
plusState.Add(step, direction);
|
||||||
|
problem.Prepare(plusState, dependencies);
|
||||||
|
mfem::Vector plusResidual;
|
||||||
|
problem.BuildResidual(plusResidual);
|
||||||
|
mfem::Vector minusState(projected.values);
|
||||||
|
minusState.Add(-step, direction);
|
||||||
|
problem.Prepare(minusState, dependencies);
|
||||||
|
mfem::Vector minusResidual;
|
||||||
|
problem.BuildResidual(minusResidual);
|
||||||
|
plusResidual -= minusResidual;
|
||||||
|
plusResidual /= 2.0 * step;
|
||||||
|
|
||||||
|
auto analyticView = problem.GetManifest().residualView(analyticAction);
|
||||||
|
auto differenceView = problem.GetManifest().residualView(plusResidual);
|
||||||
|
const auto blockError = [&](const auto &term) {
|
||||||
|
const mfem::Vector analytic = analyticView.block(term);
|
||||||
|
const mfem::Vector difference = differenceView.block(term);
|
||||||
|
return relative_difference(analytic, difference);
|
||||||
|
};
|
||||||
|
|
||||||
|
const double surfaceError = blockError(utils::blocks::surface_deformation_field.shape_equilibrium_term);
|
||||||
|
const double enthalpyError = blockError(utils::blocks::enthalpy_field.specific_term);
|
||||||
|
const double angularMomentumError =
|
||||||
|
blockError(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term);
|
||||||
|
INFO("Generated-Omega surface-row centered-difference error = " << surfaceError);
|
||||||
|
INFO("Generated-Omega hydrostatic-row centered-difference error = " << enthalpyError);
|
||||||
|
INFO("Generated-Omega invariant-row centered-difference error = " << angularMomentumError);
|
||||||
|
CHECK(surfaceError < 3.0e-7);
|
||||||
|
CHECK(enthalpyError < 3.0e-7);
|
||||||
|
CHECK(angularMomentumError < 3.0e-10);
|
||||||
|
CHECK(analyticView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term).Norml2() > 0.0);
|
||||||
|
CHECK(analyticView.block(utils::blocks::enthalpy_field.specific_term).Norml2() > 0.0);
|
||||||
|
CHECK(analyticView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term).Norml2() > 0.0);
|
||||||
|
CHECK(analyticView.block(utils::blocks::gravity_field.gradient_term).Norml2() == 0.0);
|
||||||
|
CHECK(analyticView.block(utils::blocks::gravity_field.poisson_term).Norml2() == 0.0);
|
||||||
|
CHECK(analyticView.block(utils::blocks::density_field.mass_term).Norml2() == 0.0);
|
||||||
|
CHECK(analyticView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term).Norml2() == 0.0);
|
||||||
|
|
||||||
|
auto zeroModel = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{mass}}),
|
||||||
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.0}})
|
||||||
|
);
|
||||||
|
auto zeroProblem = equilibrium::discretize(zeroModel, finiteElements);
|
||||||
|
mfem::Vector zeroState(projected.values);
|
||||||
|
zeroProblem.GetManifest().stateView(zeroState).block(
|
||||||
|
utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term
|
||||||
|
) = 0.0;
|
||||||
|
zeroProblem.Prepare(zeroState, dependencies);
|
||||||
|
mfem::Vector zeroAction;
|
||||||
|
zeroProblem.ApplyLinearization(direction, zeroAction);
|
||||||
|
auto zeroView = zeroProblem.GetManifest().residualView(zeroAction);
|
||||||
|
CHECK(zeroView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term).Norml2() == 0.0);
|
||||||
|
CHECK(zeroView.block(utils::blocks::enthalpy_field.specific_term).Norml2() == 0.0);
|
||||||
|
CHECK(zeroView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term).Norml2() > 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Fixed Mass Reports Use The Inferred Outer Manifest Indices",
|
||||||
|
"[stellar-equilibrium][manifest][runtime][ordering]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using Form = operators::CompiledStellarEquilibriumForm<EarlierMultiplierModel>;
|
||||||
|
using EarlierValue = utils::blocks::generated_value_block<
|
||||||
|
models::MultiplierFor<outer_manifest_report_test::EarlierMultiplier>>;
|
||||||
|
using MassValue = utils::blocks::fixed_total_mass::mass_normalization::value;
|
||||||
|
|
||||||
|
STATIC_CHECK(utils::blocks::type_index_v<
|
||||||
|
EarlierValue,
|
||||||
|
typename Form::value_blocks> == 5);
|
||||||
|
STATIC_CHECK(utils::blocks::type_index_v<
|
||||||
|
MassValue,
|
||||||
|
typename Form::value_blocks> == 6);
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
auto model = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
outer_manifest_report_test::EarlierMultiplier({
|
||||||
|
.target = dimensions::SpecificEnergyValue{0.75}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.25}})
|
||||||
|
);
|
||||||
|
auto problem = equilibrium::discretize(model, finiteElements);
|
||||||
|
|
||||||
|
mfem::Vector state(problem.StateSize());
|
||||||
|
state = 0.0;
|
||||||
|
const auto stateView = problem.GetManifest().stateView(state);
|
||||||
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
||||||
|
|
||||||
|
const auto preparation = problem.Prepare(
|
||||||
|
state,
|
||||||
|
make_dependencies(),
|
||||||
|
make_zero_rotation()
|
||||||
|
);
|
||||||
|
REQUIRE(preparation.physical.DidAnyWork());
|
||||||
|
|
||||||
|
const auto report = problem.GetPreparedOperator().GetFixedMassReport();
|
||||||
|
const auto &outerDescriptor =
|
||||||
|
problem.GetManifest().template specification<models::FixedTotalMass>();
|
||||||
|
CHECK(report.descriptor.stableId == outerDescriptor.stableId);
|
||||||
|
CHECK(report.descriptor.valueBlock == outerDescriptor.valueBlock);
|
||||||
|
CHECK(report.descriptor.residualBlock == outerDescriptor.residualBlock);
|
||||||
|
CHECK(report.descriptor.valueBlock == 6);
|
||||||
|
CHECK(report.descriptor.residualBlock == 6);
|
||||||
|
CHECK(report.descriptor.target == 1.25);
|
||||||
|
CHECK(report.dimensionalResidual == report.achieved - report.descriptor.target);
|
||||||
|
CHECK(report.scaledResidual ==
|
||||||
|
report.dimensionalResidual / report.descriptor.residualScale);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Discretized Stellar Equilibrium Problem Is Exactly Equivalent To The Legacy Construction Path",
|
"Discretized Stellar Equilibrium Problem Is Exactly Equivalent To The Legacy Construction Path",
|
||||||
tags::stellar_equilibrium_problem_integration
|
tags::stellar_equilibrium_problem_integration
|
||||||
@@ -122,6 +438,7 @@ TEST_CASE(
|
|||||||
discretization
|
discretization
|
||||||
);
|
);
|
||||||
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
|
auto &modelDrivenOperator = equilibriumProblem.GetPreparedOperator();
|
||||||
|
const auto &physicalOperator = equilibriumProblem.GetPhysicalOperator();
|
||||||
|
|
||||||
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
|
CHECK(equilibriumProblem.StateSize() == legacyOperator.Width());
|
||||||
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
|
CHECK(equilibriumProblem.EquationSize() == legacyOperator.Height());
|
||||||
@@ -129,16 +446,16 @@ TEST_CASE(
|
|||||||
CHECK(&equilibriumProblem.GetDiscretization().finiteElementModel() == &f);
|
CHECK(&equilibriumProblem.GetDiscretization().finiteElementModel() == &f);
|
||||||
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == f.domainMapperStateless.get());
|
CHECK(&equilibriumProblem.GetDiscretization().domainMapper() == f.domainMapperStateless.get());
|
||||||
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
|
CHECK(equilibriumProblem.GetDiscretization().isCurrent());
|
||||||
CHECK(modelDrivenOperator.GetTargetMass() == 1.25);
|
CHECK(physicalOperator.GetTargetMass() == 1.25);
|
||||||
CHECK(modelDrivenOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
CHECK(physicalOperator.GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure == 0.0);
|
||||||
CHECK(equilibriumProblem.GetCompiledSurfaceConstraint().targetPressure() == dimensions::PressureValue{0.0});
|
CHECK(equilibriumProblem.GetCompiledSurfaceConstraint().targetPressure() == dimensions::PressureValue{0.0});
|
||||||
CHECK(modelDrivenOperator.GetDomainDeformation().matchesCurrentDiscretization());
|
CHECK(physicalOperator.GetDomainDeformation().matchesCurrentDiscretization());
|
||||||
CHECK(&equilibriumProblem.GetLinearizationOperator() == &modelDrivenOperator);
|
CHECK(&equilibriumProblem.GetLinearizationOperator() == &modelDrivenOperator);
|
||||||
CHECK(equilibriumProblem.GetManifest().constraints()[0].target == 1.25);
|
CHECK(equilibriumProblem.GetManifest().template specification<models::FixedTotalMass>().target == 1.25);
|
||||||
|
|
||||||
mfem::Vector state(legacyOperator.Width());
|
mfem::Vector state(legacyOperator.Width());
|
||||||
state = 0.0;
|
state = 0.0;
|
||||||
const auto stateView = legacyOperator.GetRootStateView(state);
|
const auto stateView = legacyOperator.GetRootManifest().stateView(state);
|
||||||
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
|
||||||
@@ -163,3 +480,65 @@ TEST_CASE(
|
|||||||
equilibriumProblem.ApplyLinearization(direction, modelDrivenAction);
|
equilibriumProblem.ApplyLinearization(direction, modelDrivenAction);
|
||||||
CHECK(relative_difference(modelDrivenAction, legacyAction) < 2.0e-15);
|
CHECK(relative_difference(modelDrivenAction, legacyAction) < 2.0e-15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Fixed Angular Momentum Composes With The Optional Central Density Phase At Runtime",
|
||||||
|
"[fixed-angular-momentum][central-density][stellar-equilibrium][integration]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
utils::Args arguments = test_utils::setup_args();
|
||||||
|
fem::FEM finiteElements = fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
auto model = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.2}}),
|
||||||
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{1.0}})
|
||||||
|
);
|
||||||
|
auto problem = equilibrium::discretize(model, finiteElements);
|
||||||
|
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||||
|
STATIC_CHECK(Problem::FormType::value_block_count == 8);
|
||||||
|
STATIC_CHECK(Problem::FormType::residual_block_count == 8);
|
||||||
|
|
||||||
|
mfem::Vector state(problem.StateSize());
|
||||||
|
state = 0.0;
|
||||||
|
const auto stateView = problem.GetManifest().stateView(state);
|
||||||
|
stateView.block(utils::blocks::density_field.mass_term) = 1.0;
|
||||||
|
stateView.block(utils::blocks::enthalpy_field.specific_term) = 1.0;
|
||||||
|
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term) = 0.25;
|
||||||
|
stateView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term) = 0.4;
|
||||||
|
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.03;
|
||||||
|
|
||||||
|
const auto report = problem.Prepare(state, make_dependencies());
|
||||||
|
CHECK(report.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
|
||||||
|
CHECK(report.template specification<models::FixedAngularMomentum>().constraint.DidAnyWork());
|
||||||
|
CHECK(problem.IsPrepared());
|
||||||
|
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 2);
|
||||||
|
REQUIRE(problem.GetManifest().constraints().size() == 4);
|
||||||
|
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
|
||||||
|
"FixedAngularMomentum");
|
||||||
|
CHECK(problem.GetManifest().template specification<models::FixedCentralDensity>().stableId ==
|
||||||
|
"FixedCentralDensity");
|
||||||
|
|
||||||
|
mfem::Vector residual;
|
||||||
|
problem.BuildResidual(residual);
|
||||||
|
REQUIRE(residual.Size() == problem.EquationSize());
|
||||||
|
const auto residualView = problem.GetManifest().residualView(residual);
|
||||||
|
CHECK(std::isfinite(
|
||||||
|
residualView.block(utils::blocks::fixed_angular_momentum_constraint.angular_velocity_term)(0)
|
||||||
|
));
|
||||||
|
CHECK(std::isfinite(residualView.block(utils::blocks::fixed_central_density_phase.central_value_term)(0)));
|
||||||
|
|
||||||
|
mfem::Vector direction(problem.StateSize());
|
||||||
|
for (int index = 0; index < direction.Size(); ++index) {
|
||||||
|
direction(index) = 0.01 * std::sin(0.17 * static_cast<double>(index + 1));
|
||||||
|
}
|
||||||
|
mfem::Vector action;
|
||||||
|
problem.ApplyLinearization(direction, action);
|
||||||
|
REQUIRE(action.Size() == problem.EquationSize());
|
||||||
|
for (int index = 0; index < action.Size(); ++index) {
|
||||||
|
CHECK(std::isfinite(action(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <mfem.hpp>
|
#include <mfem.hpp>
|
||||||
@@ -56,6 +57,33 @@ namespace {
|
|||||||
blocks::type_list<>,
|
blocks::type_list<>,
|
||||||
preconditioning::IdentityOperatorCharacteristics,
|
preconditioning::IdentityOperatorCharacteristics,
|
||||||
preconditioning::backend::Identity>;
|
preconditioning::backend::Identity>;
|
||||||
|
using LifetimeModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::models::FixedTotalMass,
|
||||||
|
mean_field::models::FixedCentralDensity>>;
|
||||||
|
using LifetimeProblem = mean_field::equilibrium::StellarEquilibriumProblem<LifetimeModel>;
|
||||||
|
using LifetimeBlock = decltype(preconditioning::makePreconditioner(
|
||||||
|
std::declval<const LifetimeProblem &>()
|
||||||
|
));
|
||||||
|
using LifetimePrepared = preconditioning::PreparedStellarPreconditioner<
|
||||||
|
LifetimeProblem,
|
||||||
|
LifetimeBlock>;
|
||||||
|
|
||||||
|
template <typename Problem, typename Block>
|
||||||
|
concept CanPrepareStellarPreconditioner = requires(const Problem &problem, Block block) {
|
||||||
|
preconditioning::prepare(problem, std::move(block));
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Problem, typename Block>
|
||||||
|
concept CanPrepareStellarPreconditionerFromTemporary = requires(Block block) {
|
||||||
|
preconditioning::prepare(std::declval<Problem &&>(), std::move(block));
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Problem, typename Block>
|
||||||
|
concept CanPrepareStellarPreconditionerFromConstTemporary = requires(Block block) {
|
||||||
|
preconditioning::prepare(std::declval<const Problem &&>(), std::move(block));
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
|
[[nodiscard]] blocks::form_layout<Form> makeUnevenLayout() {
|
||||||
return {
|
return {
|
||||||
@@ -102,6 +130,12 @@ TEST_CASE(
|
|||||||
) {
|
) {
|
||||||
STATIC_CHECK(preconditioning::EquilibriumCoordinateComponentFor<GroupedComponent, Form>);
|
STATIC_CHECK(preconditioning::EquilibriumCoordinateComponentFor<GroupedComponent, Form>);
|
||||||
STATIC_CHECK_FALSE(preconditioning::EquilibriumCoordinateComponentFor<IncompleteComponent, Form>);
|
STATIC_CHECK_FALSE(preconditioning::EquilibriumCoordinateComponentFor<IncompleteComponent, Form>);
|
||||||
|
STATIC_CHECK(CanPrepareStellarPreconditioner<LifetimeProblem, LifetimeBlock>);
|
||||||
|
STATIC_CHECK_FALSE(CanPrepareStellarPreconditionerFromTemporary<LifetimeProblem, LifetimeBlock>);
|
||||||
|
STATIC_CHECK_FALSE(CanPrepareStellarPreconditionerFromConstTemporary<LifetimeProblem, LifetimeBlock>);
|
||||||
|
STATIC_CHECK(std::constructible_from<LifetimePrepared, const LifetimeProblem &, LifetimeBlock>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<LifetimePrepared, LifetimeProblem &&, LifetimeBlock>);
|
||||||
|
STATIC_CHECK_FALSE(std::constructible_from<LifetimePrepared, const LifetimeProblem &&, LifetimeBlock>);
|
||||||
|
|
||||||
const auto layout = makeUnevenLayout();
|
const auto layout = makeUnevenLayout();
|
||||||
preconditioning::EquilibriumPreconditionerCoordinateMap<Form, GroupedComponent> coordinates(layout);
|
preconditioning::EquilibriumPreconditionerCoordinateMap<Form, GroupedComponent> coordinates(layout);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
#include <memory>
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -13,6 +14,130 @@
|
|||||||
import mean_field;
|
import mean_field;
|
||||||
import test_helpers;
|
import test_helpers;
|
||||||
|
|
||||||
|
namespace material_surface_runtime_contract_test {
|
||||||
|
struct RegisteredAlternateEquationOfState final {
|
||||||
|
struct Parameters final { };
|
||||||
|
|
||||||
|
using ModelDefinition = mean_field::models::ConstitutiveLaw<
|
||||||
|
RegisteredAlternateEquationOfState,
|
||||||
|
"RegisteredAlternateMaterialSurfaceEquationOfState">;
|
||||||
|
using Relations = mean_field::eos::RelationCatalog<mean_field::eos::SpecificEnthalpyFromPressure>;
|
||||||
|
|
||||||
|
explicit RegisteredAlternateEquationOfState(Parameters) noexcept { }
|
||||||
|
|
||||||
|
[[nodiscard]] mean_field::dimensions::SpecificEnthalpyValue evaluate(
|
||||||
|
mean_field::eos::SpecificEnthalpyFromPressure,
|
||||||
|
mean_field::dimensions::PressureValue
|
||||||
|
) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AlternatePhysicalCore final : public mfem::Operator {
|
||||||
|
public:
|
||||||
|
using BackendSpecifications = mean_field::models::ModelTypeList<
|
||||||
|
RegisteredAlternateEquationOfState,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::models::FixedTotalMass>;
|
||||||
|
|
||||||
|
using mfem::Operator::Operator;
|
||||||
|
|
||||||
|
void Mult(const mfem::Vector &, mfem::Vector &) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] const mean_field::operators::StellarEquilibriumLayout &GetLayout() const noexcept;
|
||||||
|
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumReport Prepare(
|
||||||
|
const mfem::Vector &,
|
||||||
|
const mean_field::operators::StellarEquilibriumDependencies &,
|
||||||
|
const mean_field::physics::RigidRotation &
|
||||||
|
);
|
||||||
|
void BuildResidual(mfem::Vector &) const;
|
||||||
|
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||||
|
[[nodiscard]] mean_field::operators::RootConstraintReport GetFixedMassReport() const;
|
||||||
|
[[nodiscard]] const mean_field::operators::StellarEquilibriumDependencies &GetDependencies() const;
|
||||||
|
[[nodiscard]] const mean_field::operators::StellarEquilibriumDependencyStamp &
|
||||||
|
GetGeneratedDisplacementDependency() const;
|
||||||
|
[[nodiscard]] const mean_field::operators::PreparedPressureSurfaceConstraint &
|
||||||
|
GetSurfaceConstraintOperator() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <mean_field::model::StellarModelType Model>
|
||||||
|
class AlternateEquationOfStateRuntime final {
|
||||||
|
public:
|
||||||
|
using Report = mean_field::operators::EmptySpecificationPreparationReport;
|
||||||
|
|
||||||
|
AlternateEquationOfStateRuntime(
|
||||||
|
mean_field::fem::FEM &,
|
||||||
|
const mean_field::mapping::DomainMapper &,
|
||||||
|
AlternatePhysicalCore &,
|
||||||
|
const Model &
|
||||||
|
) noexcept { }
|
||||||
|
|
||||||
|
template <typename StateView, typename Controls>
|
||||||
|
void ReadPhysicalControls(const StateView &, Controls &) noexcept { }
|
||||||
|
|
||||||
|
template <typename StateView>
|
||||||
|
[[nodiscard]] Report PrepareAfterPhysical(
|
||||||
|
const StateView &,
|
||||||
|
const mean_field::operators::StellarEquilibriumDependencies &,
|
||||||
|
const AlternatePhysicalCore &
|
||||||
|
) noexcept {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ResidualView>
|
||||||
|
void AddResidual(const ResidualView &) const noexcept { }
|
||||||
|
|
||||||
|
template <typename DirectionView, typename ActionView>
|
||||||
|
void AddJacobianAction(
|
||||||
|
const DirectionView &,
|
||||||
|
const ActionView &,
|
||||||
|
const AlternatePhysicalCore &
|
||||||
|
) const noexcept { }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool IsPrepared() const noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace material_surface_runtime_contract_test
|
||||||
|
|
||||||
|
namespace mean_field::operators {
|
||||||
|
template <>
|
||||||
|
struct StellarEquilibriumCoreRuntime<
|
||||||
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
||||||
|
|
||||||
|
[[nodiscard]] static std::unique_ptr<CoreType> Make(
|
||||||
|
fem::FEM &,
|
||||||
|
const mapping::DomainMapper &,
|
||||||
|
const material_surface_runtime_contract_test::RegisteredAlternateEquationOfState &,
|
||||||
|
const models::CompiledFixedMass &,
|
||||||
|
PressureSurfaceConstraintView,
|
||||||
|
deformation::PreparedDomainDeformationRuntime
|
||||||
|
);
|
||||||
|
|
||||||
|
[[nodiscard]] static int SurfaceEquationCount(const CoreType &) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct StellarEquilibriumRuntimeContribution<
|
||||||
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>
|
||||||
|
: PreparedStellarEquilibriumContribution<
|
||||||
|
material_surface_runtime_contract_test::AlternateEquationOfStateRuntime> { };
|
||||||
|
} // namespace mean_field::operators
|
||||||
|
|
||||||
|
namespace mean_field::preconditioning {
|
||||||
|
/*
|
||||||
|
* This registration deliberately advertises a distinct core without an
|
||||||
|
* executable material/surface implementation. Registration alone must
|
||||||
|
* therefore remain insufficient for MaterialSurfaceRuntimeFor.
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
struct MaterialSurfaceEquationOfStateBackend<
|
||||||
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState> {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
using CoreType = material_surface_runtime_contract_test::AlternatePhysicalCore;
|
||||||
|
};
|
||||||
|
} // namespace mean_field::preconditioning
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
namespace backend = mean_field::preconditioning::backend;
|
namespace backend = mean_field::preconditioning::backend;
|
||||||
namespace blocks = mean_field::utils::blocks;
|
namespace blocks = mean_field::utils::blocks;
|
||||||
@@ -25,6 +150,11 @@ namespace {
|
|||||||
mean_field::constraint::FixedCentralDensity>>;
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||||
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
||||||
|
using RegisteredAlternateModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::integral::FixedTotalMass>>;
|
||||||
|
using RegisteredAlternateProblem = mean_field::equilibrium::StellarEquilibriumProblem<RegisteredAlternateModel>;
|
||||||
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
|
using MaterialSurfaceDiagonal = preconditioning::MaterialSurfaceBlock<
|
||||||
PolytropicMaterialSurfaceDescriptor,
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
backend::Diagonal,
|
backend::Diagonal,
|
||||||
@@ -44,6 +174,30 @@ namespace {
|
|||||||
PolytropicMaterialSurfaceDescriptor,
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
preconditioning::ApproximateMaterialSurfaceLDU,
|
preconditioning::ApproximateMaterialSurfaceLDU,
|
||||||
backend::FixedCycles>;
|
backend::FixedCycles>;
|
||||||
|
using RegisteredAlternateGravityComponent = preconditioning::GravityFieldBlock<
|
||||||
|
backend::Diagonal,
|
||||||
|
FixedCycleAMG,
|
||||||
|
preconditioning::GravityApproximateLDU>;
|
||||||
|
|
||||||
|
using RegisteredAlternateMaterialSurfaceDescriptor =
|
||||||
|
preconditioning::MaterialSurfaceDescriptorFor<RegisteredAlternateProblem>;
|
||||||
|
|
||||||
|
struct DistinctPhysicalCore final { };
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept CanMakeDefaultMaterialSurface = requires(const Problem &problem) {
|
||||||
|
preconditioning::materialSurfaceBlock(problem);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept CanPrepareDefaultMaterialSurface = requires(const Problem &problem) {
|
||||||
|
preconditioning::prepare(problem, preconditioning::materialSurfaceBlock(problem));
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept CanMakeDefaultStellarStructure = requires(const Problem &problem) {
|
||||||
|
preconditioning::stellarStructureBlock(problem);
|
||||||
|
};
|
||||||
|
|
||||||
class KnownCouplings final {
|
class KnownCouplings final {
|
||||||
public:
|
public:
|
||||||
@@ -161,6 +315,53 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
|
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceDiagonal>);
|
||||||
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
|
STATIC_CHECK(preconditioning::PreconditionerComponent<MaterialSurfaceH1>);
|
||||||
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
|
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<PolytropicMaterialSurfaceDescriptor>);
|
||||||
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<
|
||||||
|
mean_field::eos::Polytrope>);
|
||||||
|
STATIC_CHECK_FALSE(
|
||||||
|
preconditioning::ImplementedMaterialSurfaceEquationOfState<int>);
|
||||||
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<
|
||||||
|
PolytropicMaterialSurfaceDescriptor>);
|
||||||
|
STATIC_CHECK(preconditioning::ExecutableMaterialSurfaceRuntimeFor<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename preconditioning::MaterialSurfaceEquationOfStateBackend<
|
||||||
|
mean_field::eos::Polytrope>::CoreType,
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
||||||
|
STATIC_CHECK(preconditioning::MaterialSurfaceRuntimeFor<
|
||||||
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::MaterialSurfaceRuntimeFor<
|
||||||
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
|
DistinctPhysicalCore>);
|
||||||
|
STATIC_CHECK(preconditioning::MaterialSurfaceDescriptor<
|
||||||
|
RegisteredAlternateMaterialSurfaceDescriptor>);
|
||||||
|
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<RegisteredAlternateModel>);
|
||||||
|
STATIC_CHECK(std::same_as<
|
||||||
|
typename RegisteredAlternateProblem::PhysicalCoreType,
|
||||||
|
material_surface_runtime_contract_test::AlternatePhysicalCore>);
|
||||||
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceEquationOfState<
|
||||||
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState>);
|
||||||
|
STATIC_CHECK(preconditioning::ImplementedMaterialSurfaceDescriptor<
|
||||||
|
RegisteredAlternateMaterialSurfaceDescriptor>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::ExecutableMaterialSurfaceRuntimeFor<
|
||||||
|
material_surface_runtime_contract_test::RegisteredAlternateEquationOfState,
|
||||||
|
material_surface_runtime_contract_test::AlternatePhysicalCore>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::MaterialSurfaceRuntimeFor<
|
||||||
|
RegisteredAlternateMaterialSurfaceDescriptor,
|
||||||
|
material_surface_runtime_contract_test::AlternatePhysicalCore>);
|
||||||
|
STATIC_CHECK(preconditioning::MaterialSurfacePreconditionerProblem<PolytropicProblem>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::MaterialSurfacePreconditionerProblem<RegisteredAlternateProblem>);
|
||||||
|
STATIC_CHECK(CanMakeDefaultMaterialSurface<PolytropicProblem>);
|
||||||
|
STATIC_CHECK_FALSE(CanMakeDefaultMaterialSurface<RegisteredAlternateProblem>);
|
||||||
|
STATIC_CHECK(CanPrepareDefaultMaterialSurface<PolytropicProblem>);
|
||||||
|
STATIC_CHECK_FALSE(CanPrepareDefaultMaterialSurface<RegisteredAlternateProblem>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreconditionerProblem<RegisteredAlternateProblem>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::StellarStructurePreparableFor<
|
||||||
|
RegisteredAlternateProblem,
|
||||||
|
MaterialSurfaceDiagonal,
|
||||||
|
RegisteredAlternateGravityComponent>);
|
||||||
|
STATIC_CHECK_FALSE(CanMakeDefaultStellarStructure<RegisteredAlternateProblem>);
|
||||||
STATIC_CHECK(
|
STATIC_CHECK(
|
||||||
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
|
mean_field::material::CompiledThermodynamicEquations<typename PolytropicProblem::ThermodynamicEquationsType>
|
||||||
);
|
);
|
||||||
@@ -171,7 +372,7 @@ TEST_CASE(
|
|||||||
);
|
);
|
||||||
STATIC_CHECK(MaterialSurfaceDiagonal::CorrectionBlocks::size == 3);
|
STATIC_CHECK(MaterialSurfaceDiagonal::CorrectionBlocks::size == 3);
|
||||||
STATIC_CHECK(MaterialSurfaceDiagonal::ResidualBlocks::size == 3);
|
STATIC_CHECK(MaterialSurfaceDiagonal::ResidualBlocks::size == 3);
|
||||||
STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 8);
|
STATIC_CHECK(MaterialSurfaceDiagonal::RequiredCouplings::size == 9);
|
||||||
STATIC_CHECK(preconditioning::CompletePreconditionerFor<Plan, Form>);
|
STATIC_CHECK(preconditioning::CompletePreconditionerFor<Plan, Form>);
|
||||||
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<Plan, Form, JacobianForm>);
|
STATIC_CHECK(preconditioning::CompatiblePreconditionerFor<Plan, Form, JacobianForm>);
|
||||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceDiagonal::BackendType>);
|
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename MaterialSurfaceDiagonal::BackendType>);
|
||||||
@@ -409,7 +610,7 @@ TEST_CASE(
|
|||||||
|
|
||||||
mfem::Vector fullDirection(physical.Width());
|
mfem::Vector fullDirection(physical.Width());
|
||||||
fullDirection = 0.0;
|
fullDirection = 0.0;
|
||||||
const auto fullDirectionView = physical.GetRootManifest().directionView(fullDirection);
|
const auto fullDirectionView = physical.GetRootManifest().stateView(fullDirection);
|
||||||
const auto &offsets = restricted.GetOffsets();
|
const auto &offsets = restricted.GetOffsets();
|
||||||
const mfem::Vector densityDirection(restrictedDirection.GetData(), offsets[1]);
|
const mfem::Vector densityDirection(restrictedDirection.GetData(), offsets[1]);
|
||||||
const mfem::Vector surfaceDirection(restrictedDirection.GetData() + offsets[1], offsets[2] - offsets[1]);
|
const mfem::Vector surfaceDirection(restrictedDirection.GetData() + offsets[1], offsets[2] - offsets[1]);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,10 @@ namespace preconditioning_runtime_test {
|
|||||||
++m_snapshot.geometry.revision;
|
++m_snapshot.geometry.revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdvancePreparation() noexcept {
|
||||||
|
++m_snapshot.preparedOperatorGeneration;
|
||||||
|
}
|
||||||
|
|
||||||
void SetPrepared(const bool prepared) noexcept {
|
void SetPrepared(const bool prepared) noexcept {
|
||||||
m_prepared = prepared;
|
m_prepared = prepared;
|
||||||
}
|
}
|
||||||
@@ -108,6 +112,136 @@ namespace preconditioning_runtime_test {
|
|||||||
};
|
};
|
||||||
} // namespace preconditioning_runtime_test
|
} // namespace preconditioning_runtime_test
|
||||||
|
|
||||||
|
namespace unsupported_physical_preconditioner_test {
|
||||||
|
class Constraint;
|
||||||
|
class PreparedConstraint;
|
||||||
|
|
||||||
|
class Constraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
mean_field::dimensions::SpecificEnthalpyValue target;
|
||||||
|
};
|
||||||
|
|
||||||
|
using TargetValue = mean_field::dimensions::SpecificEnthalpyValue;
|
||||||
|
using ScalarDescription = mean_field::stellar::ScalarConstraint<
|
||||||
|
mean_field::dimensions::quantity::SpecificEnthalpy,
|
||||||
|
mean_field::dimensions::quantity::Dimensionless,
|
||||||
|
mean_field::dimensions::quantity::SpecificEnthalpy,
|
||||||
|
"test.unsupported_physical_edge.coordinate",
|
||||||
|
"q_u",
|
||||||
|
"test.unsupported_physical_edge.residual",
|
||||||
|
"R_u">;
|
||||||
|
using ModelDefinition = mean_field::constraint::ScalarPhaseCondition<
|
||||||
|
Constraint,
|
||||||
|
"UnsupportedPhysicalPreconditionerEdge",
|
||||||
|
mean_field::stellar::Reads<
|
||||||
|
mean_field::stellar::state::SpecificEnthalpy,
|
||||||
|
mean_field::stellar::state::OwnGeneratedCoordinate>,
|
||||||
|
mean_field::stellar::Changes<
|
||||||
|
mean_field::stellar::equation::PoissonEquation>,
|
||||||
|
ScalarDescription>;
|
||||||
|
using EquilibriumPhysics =
|
||||||
|
mean_field::operators::LocalSpecificationEquilibriumPhysics<
|
||||||
|
PreparedConstraint>;
|
||||||
|
|
||||||
|
explicit constexpr Constraint(const Parameters parameters) noexcept
|
||||||
|
: m_target(parameters.target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr TargetValue target() const noexcept {
|
||||||
|
return m_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TargetValue m_target;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparationReport final {
|
||||||
|
bool stateChanged{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
class PreparedConstraint final {
|
||||||
|
public:
|
||||||
|
using Report = PreparationReport;
|
||||||
|
|
||||||
|
explicit PreparedConstraint(const Constraint &) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename StateView>
|
||||||
|
[[nodiscard]] Report PrepareAfterPhysical(const StateView &) noexcept {
|
||||||
|
m_isPrepared = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||||
|
mean_field::stellar::equation::OwnConstraint,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::structuralZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddResidual(
|
||||||
|
mean_field::stellar::equation::PoissonEquation,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::structuralZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Direction, typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
|
mean_field::stellar::Derivative<
|
||||||
|
mean_field::stellar::equation::OwnConstraint,
|
||||||
|
mean_field::stellar::state::SpecificEnthalpy>,
|
||||||
|
const Direction &,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::zeroDerivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Direction, typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
|
mean_field::stellar::Derivative<
|
||||||
|
mean_field::stellar::equation::OwnConstraint,
|
||||||
|
mean_field::stellar::state::OwnGeneratedCoordinate>,
|
||||||
|
const Direction &,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::zeroDerivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Direction, typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
|
mean_field::stellar::Derivative<
|
||||||
|
mean_field::stellar::equation::PoissonEquation,
|
||||||
|
mean_field::stellar::state::SpecificEnthalpy>,
|
||||||
|
const Direction &,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::zeroDerivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Direction, typename Row>
|
||||||
|
[[nodiscard]] mean_field::stellar::StructuralZero AddJacobianAction(
|
||||||
|
mean_field::stellar::Derivative<
|
||||||
|
mean_field::stellar::equation::PoissonEquation,
|
||||||
|
mean_field::stellar::state::OwnGeneratedCoordinate>,
|
||||||
|
const Direction &,
|
||||||
|
Row &
|
||||||
|
) const noexcept {
|
||||||
|
return mean_field::stellar::zeroDerivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||||
|
return m_isPrepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_isPrepared{false};
|
||||||
|
};
|
||||||
|
} // namespace unsupported_physical_preconditioner_test
|
||||||
|
|
||||||
template <> struct mean_field::preconditioning::StellarEquilibriumProblemTraits<preconditioning_runtime_test::Problem> {
|
template <> struct mean_field::preconditioning::StellarEquilibriumProblemTraits<preconditioning_runtime_test::Problem> {
|
||||||
using Problem = preconditioning_runtime_test::Problem;
|
using Problem = preconditioning_runtime_test::Problem;
|
||||||
using Form = preconditioning_runtime_test::Form;
|
using Form = preconditioning_runtime_test::Form;
|
||||||
@@ -146,14 +280,40 @@ namespace {
|
|||||||
namespace blocks = mean_field::utils::blocks;
|
namespace blocks = mean_field::utils::blocks;
|
||||||
namespace preconditioning = mean_field::preconditioning;
|
namespace preconditioning = mean_field::preconditioning;
|
||||||
|
|
||||||
using ModelWithoutPhase = mean_field::operators::StellarEquilibriumSpecificationModel;
|
using ModelWithoutPhase = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
using CentralDensityModel = mean_field::operators::CentralDensityStellarEquilibriumSpecificationModel;
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::models::FixedTotalMass>>;
|
||||||
|
using CentralDensityModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::models::FixedTotalMass,
|
||||||
|
mean_field::models::FixedCentralDensity>>;
|
||||||
|
using AngularMomentumModel = mean_field::model::StellarModel<mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::models::FixedTotalMass,
|
||||||
|
mean_field::models::FixedAngularMomentum>>;
|
||||||
|
using ProvenZeroPhysicalEdgeModel = mean_field::model::StellarModel<
|
||||||
|
mean_field::models::SpecificationSet<
|
||||||
|
mean_field::eos::Polytrope,
|
||||||
|
mean_field::surface::Isobaric,
|
||||||
|
mean_field::models::FixedTotalMass,
|
||||||
|
unsupported_physical_preconditioner_test::Constraint>>;
|
||||||
|
|
||||||
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
|
using ProblemWithoutPhase = mean_field::equilibrium::StellarEquilibriumProblem<ModelWithoutPhase>;
|
||||||
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
using CentralDensityProblem = mean_field::equilibrium::StellarEquilibriumProblem<CentralDensityModel>;
|
||||||
|
using AngularMomentumProblem = mean_field::equilibrium::StellarEquilibriumProblem<AngularMomentumModel>;
|
||||||
|
using ProvenZeroPhysicalEdgeProblem =
|
||||||
|
mean_field::equilibrium::StellarEquilibriumProblem<ProvenZeroPhysicalEdgeModel>;
|
||||||
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
|
using PlanWithoutPhase = preconditioning::IdentityPreconditionerPlanFor<ProblemWithoutPhase>;
|
||||||
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
|
using CentralDensityPlan = preconditioning::IdentityPreconditionerPlanFor<CentralDensityProblem>;
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept CanMakeDefaultStellarStructureBlock = requires(const Problem &problem) {
|
||||||
|
preconditioning::stellarStructureBlock(problem);
|
||||||
|
};
|
||||||
|
|
||||||
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
|
using RefreshingDensityIdentity = preconditioning::ComponentDeclaration<
|
||||||
blocks::type_list<blocks::density::mass::value>,
|
blocks::type_list<blocks::density::mass::value>,
|
||||||
blocks::type_list<blocks::density::mass::residual>,
|
blocks::type_list<blocks::density::mass::residual>,
|
||||||
@@ -203,6 +363,73 @@ TEST_CASE(
|
|||||||
STATIC_CHECK(CentralDensityPlan::ComponentTypes::size == 7);
|
STATIC_CHECK(CentralDensityPlan::ComponentTypes::size == 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Default Stellar Structure Availability Distinguishes Proven Zeros From Unhandled Physical Edges",
|
||||||
|
"[preconditioning][stellar_structure][type_contract][compiler]"
|
||||||
|
) {
|
||||||
|
using BaseCompilation =
|
||||||
|
mean_field::operators::CompiledStellarEquilibriumSystem<ModelWithoutPhase>;
|
||||||
|
using BaseSupport =
|
||||||
|
preconditioning::DefaultStellarStructurePhysicalTopologySupport<
|
||||||
|
ModelWithoutPhase>;
|
||||||
|
using ProvenZeroSupport =
|
||||||
|
preconditioning::DefaultStellarStructurePhysicalTopologySupport<
|
||||||
|
ProvenZeroPhysicalEdgeModel>;
|
||||||
|
using TrustedFixedMassEdge =
|
||||||
|
mean_field::operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
blocks::enthalpy::specific::residual,
|
||||||
|
blocks::density::mass::value>;
|
||||||
|
using ProvenZeroPoissonEnthalpyEdge =
|
||||||
|
mean_field::operators::StellarEquilibriumJacobianCoupling<
|
||||||
|
blocks::gravity::poisson::residual,
|
||||||
|
blocks::enthalpy::specific::value>;
|
||||||
|
|
||||||
|
// FixedTotalMass contributes h <- rho outside the generic five-field base
|
||||||
|
// graph. It remains supported because that specification is explicitly
|
||||||
|
// embedded in the trusted numerical core, not because of a model-pack
|
||||||
|
// special case.
|
||||||
|
STATIC_CHECK_FALSE(mean_field::utils::blocks::contains_type_v<
|
||||||
|
TrustedFixedMassEdge,
|
||||||
|
typename BaseCompilation::BaseJacobianCouplings>);
|
||||||
|
STATIC_CHECK(mean_field::utils::blocks::contains_type_v<
|
||||||
|
TrustedFixedMassEdge,
|
||||||
|
typename BaseCompilation::ContributionJacobianCouplings>);
|
||||||
|
STATIC_CHECK(BaseSupport::UnsupportedCouplings::size == 0);
|
||||||
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
||||||
|
ModelWithoutPhase>);
|
||||||
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
||||||
|
CentralDensityModel>);
|
||||||
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
||||||
|
AngularMomentumModel>);
|
||||||
|
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<
|
||||||
|
ProblemWithoutPhase>);
|
||||||
|
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<ProblemWithoutPhase>);
|
||||||
|
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<
|
||||||
|
ProblemWithoutPhase>);
|
||||||
|
|
||||||
|
// The mock's novel Poisson <- enthalpy edge is absent from the structure
|
||||||
|
// backend, but its exact nested provider returns StructuralZero. That is
|
||||||
|
// a compile-time proof that no preconditioner term is missing; generated-
|
||||||
|
// coordinate edges are handled independently by the inferred border.
|
||||||
|
STATIC_CHECK(mean_field::equilibrium::StellarEquilibriumModel<
|
||||||
|
ProvenZeroPhysicalEdgeModel>);
|
||||||
|
STATIC_CHECK(mean_field::equilibrium::DiscretizedStellarEquilibriumProblem<
|
||||||
|
ProvenZeroPhysicalEdgeProblem>);
|
||||||
|
STATIC_CHECK(ProvenZeroSupport::UnsupportedCouplings::size == 0);
|
||||||
|
STATIC_CHECK(mean_field::utils::blocks::contains_type_v<
|
||||||
|
ProvenZeroPoissonEnthalpyEdge,
|
||||||
|
typename mean_field::operators::CompiledStellarEquilibriumSystem<
|
||||||
|
ProvenZeroPhysicalEdgeModel>::ContributionJacobianCouplings>);
|
||||||
|
STATIC_CHECK(preconditioning::DefaultStellarStructurePhysicalTopologySupportedFor<
|
||||||
|
ProvenZeroPhysicalEdgeModel>);
|
||||||
|
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<
|
||||||
|
ProvenZeroPhysicalEdgeProblem>);
|
||||||
|
STATIC_CHECK(CanMakeDefaultStellarStructureBlock<
|
||||||
|
ProvenZeroPhysicalEdgeProblem>);
|
||||||
|
STATIC_CHECK(preconditioning::DefaultStellarPreconditionerAvailableFor<
|
||||||
|
ProvenZeroPhysicalEdgeProblem>);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Prepared Stellar Identity Preconditioning Is Bitwise Equivalent To The P0 Baseline",
|
"Prepared Stellar Identity Preconditioning Is Bitwise Equivalent To The P0 Baseline",
|
||||||
tags::preconditioning_runtime_unit
|
tags::preconditioning_runtime_unit
|
||||||
@@ -286,6 +513,14 @@ TEST_CASE(
|
|||||||
CHECK_FALSE(geometryRefresh.changes.linearization);
|
CHECK_FALSE(geometryRefresh.changes.linearization);
|
||||||
CHECK(preconditioner.GetStatistics().refreshes == 2);
|
CHECK(preconditioner.GetStatistics().refreshes == 2);
|
||||||
|
|
||||||
|
problem.AdvancePreparation();
|
||||||
|
CHECK_FALSE(preconditioner.IsCurrent());
|
||||||
|
const auto preparationRefresh = preconditioner.Refresh();
|
||||||
|
CHECK(preparationRefresh.changes.linearization);
|
||||||
|
CHECK_FALSE(preparationRefresh.changes.geometry);
|
||||||
|
CHECK(preconditioner.IsCurrent());
|
||||||
|
CHECK(preconditioner.GetStatistics().refreshes == 3);
|
||||||
|
|
||||||
problem.SetPrepared(false);
|
problem.SetPrepared(false);
|
||||||
CHECK_FALSE(preconditioner.IsCurrent());
|
CHECK_FALSE(preconditioner.IsCurrent());
|
||||||
CHECK_THROWS_AS(preconditioner.Refresh(), std::logic_error);
|
CHECK_THROWS_AS(preconditioner.Refresh(), std::logic_error);
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ namespace {
|
|||||||
mean_field::integral::FixedTotalMass,
|
mean_field::integral::FixedTotalMass,
|
||||||
mean_field::constraint::FixedCentralDensity>>;
|
mean_field::constraint::FixedCentralDensity>>;
|
||||||
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
using PolytropicProblem = mean_field::equilibrium::StellarEquilibriumProblem<PolytropicModel>;
|
||||||
|
using PolytropicMaterialSurfaceDescriptor = preconditioning::MaterialSurfaceDescriptorFor<PolytropicProblem>;
|
||||||
using MaterialComponent =
|
using MaterialComponent =
|
||||||
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
|
decltype(preconditioning::materialSurfaceBlock(std::declval<const PolytropicProblem &>()));
|
||||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||||
@@ -126,6 +127,13 @@ namespace {
|
|||||||
preconditioning::IndependentStellarSubsystems{}
|
preconditioning::IndependentStellarSubsystems{}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept CanPrepareDefaultStellarStructure = requires(const Problem &problem) {
|
||||||
|
preconditioning::prepare(problem, preconditioning::stellarStructureBlock(problem));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DistinctPhysicalCore final { };
|
||||||
|
|
||||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies
|
||||||
makeDependencies(const std::uint64_t revision = 1) {
|
makeDependencies(const std::uint64_t revision = 1) {
|
||||||
return {
|
return {
|
||||||
@@ -173,11 +181,26 @@ TEST_CASE(
|
|||||||
preconditioning::Coupling<blocks::enthalpy::specific::residual, blocks::gravity::poisson::value>>;
|
preconditioning::Coupling<blocks::enthalpy::specific::residual, blocks::gravity::poisson::value>>;
|
||||||
|
|
||||||
STATIC_CHECK(preconditioning::PreconditionerComponent<PolytropicStructure>);
|
STATIC_CHECK(preconditioning::PreconditionerComponent<PolytropicStructure>);
|
||||||
|
STATIC_CHECK(preconditioning::ExecutableStellarStructureRuntimeFor<
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::ExecutableStellarStructureRuntimeFor<DistinctPhysicalCore>);
|
||||||
|
STATIC_CHECK(preconditioning::StellarStructureRuntimeFor<
|
||||||
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
|
mean_field::operators::PreparedStellarEquilibriumOperator>);
|
||||||
|
STATIC_CHECK_FALSE(preconditioning::StellarStructureRuntimeFor<
|
||||||
|
PolytropicMaterialSurfaceDescriptor,
|
||||||
|
DistinctPhysicalCore>);
|
||||||
|
STATIC_CHECK(preconditioning::StellarStructurePreconditionerProblem<PolytropicProblem>);
|
||||||
|
STATIC_CHECK(preconditioning::StellarStructurePreparableFor<
|
||||||
|
PolytropicProblem,
|
||||||
|
MaterialComponent,
|
||||||
|
GravityComponent>);
|
||||||
|
STATIC_CHECK(CanPrepareDefaultStellarStructure<PolytropicProblem>);
|
||||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
|
STATIC_CHECK(std::same_as<typename PolytropicStructure::MaterialToGravityCouplings, ExpectedMaterialToGravity>);
|
||||||
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
|
STATIC_CHECK(std::same_as<typename PolytropicStructure::GravityToMaterialCouplings, ExpectedGravityToMaterial>);
|
||||||
STATIC_CHECK(PolytropicStructure::MaterialToGravityCouplings::size == 3);
|
STATIC_CHECK(PolytropicStructure::MaterialToGravityCouplings::size == 3);
|
||||||
STATIC_CHECK(PolytropicStructure::GravityToMaterialCouplings::size == 2);
|
STATIC_CHECK(PolytropicStructure::GravityToMaterialCouplings::size == 2);
|
||||||
STATIC_CHECK(PolytropicStructure::RequiredCouplings::size == 16);
|
STATIC_CHECK(PolytropicStructure::RequiredCouplings::size == 17);
|
||||||
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename PolytropicStructure::BackendType>);
|
STATIC_CHECK(preconditioning::backend::ArnoldiAdmissible<typename PolytropicStructure::BackendType>);
|
||||||
|
|
||||||
using FixedMassIdentity = preconditioning::IdentityBlock<
|
using FixedMassIdentity = preconditioning::IdentityBlock<
|
||||||
@@ -265,7 +288,7 @@ TEST_CASE(
|
|||||||
|
|
||||||
mfem::Vector materialOnlyDirection(physical.Width());
|
mfem::Vector materialOnlyDirection(physical.Width());
|
||||||
materialOnlyDirection = 0.0;
|
materialOnlyDirection = 0.0;
|
||||||
auto materialOnlyView = physical.GetRootManifest().directionView(materialOnlyDirection);
|
auto materialOnlyView = physical.GetRootManifest().stateView(materialOnlyDirection);
|
||||||
mfem::Vector materialDensity = materialOnlyView.block(blocks::density_field.mass_term);
|
mfem::Vector materialDensity = materialOnlyView.block(blocks::density_field.mass_term);
|
||||||
mfem::Vector materialSurface = materialOnlyView.block(blocks::surface_deformation_field.parameters_term);
|
mfem::Vector materialSurface = materialOnlyView.block(blocks::surface_deformation_field.parameters_term);
|
||||||
mfem::Vector materialEnthalpy = materialOnlyView.block(blocks::enthalpy_field.specific_term);
|
mfem::Vector materialEnthalpy = materialOnlyView.block(blocks::enthalpy_field.specific_term);
|
||||||
@@ -290,7 +313,7 @@ TEST_CASE(
|
|||||||
|
|
||||||
mfem::Vector gravityOnlyDirection(physical.Width());
|
mfem::Vector gravityOnlyDirection(physical.Width());
|
||||||
gravityOnlyDirection = 0.0;
|
gravityOnlyDirection = 0.0;
|
||||||
auto gravityOnlyView = physical.GetRootManifest().directionView(gravityOnlyDirection);
|
auto gravityOnlyView = physical.GetRootManifest().stateView(gravityOnlyDirection);
|
||||||
mfem::Vector gravityGradient = gravityOnlyView.block(blocks::gravity_field.gradient_term);
|
mfem::Vector gravityGradient = gravityOnlyView.block(blocks::gravity_field.gradient_term);
|
||||||
mfem::Vector gravityPotential = gravityOnlyView.block(blocks::gravity_field.poisson_term);
|
mfem::Vector gravityPotential = gravityOnlyView.block(blocks::gravity_field.poisson_term);
|
||||||
const mfem::Vector sourceGravityGradient(
|
const mfem::Vector sourceGravityGradient(
|
||||||
|
|||||||
@@ -13,6 +13,137 @@ import mean_field;
|
|||||||
import test_helpers;
|
import test_helpers;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
template <typename... Specifications>
|
||||||
|
using ProjectionModelWith = mean_field::model::StellarModel<
|
||||||
|
mean_field::models::SpecificationSet<Specifications...>>;
|
||||||
|
|
||||||
|
class UnregisteredProjectionConstraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final { };
|
||||||
|
using ModelDefinition = mean_field::constraint::PhaseCondition<
|
||||||
|
UnregisteredProjectionConstraint,
|
||||||
|
"UnregisteredProjectionConstraint",
|
||||||
|
mean_field::models::DependsOn<mean_field::models::stellar::state::SpecificEnthalpy>,
|
||||||
|
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>>;
|
||||||
|
|
||||||
|
explicit constexpr UnregisteredProjectionConstraint(Parameters) noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExplicitNoChangeProjectionConstraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final { };
|
||||||
|
using ModelDefinition = mean_field::constraint::PhaseCondition<
|
||||||
|
ExplicitNoChangeProjectionConstraint,
|
||||||
|
"ExplicitNoChangeProjectionConstraint",
|
||||||
|
mean_field::models::DependsOn<mean_field::models::stellar::state::SpecificEnthalpy>,
|
||||||
|
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>>;
|
||||||
|
using RadialProjection = mean_field::seed::projection::Use<mean_field::seed::projection::NoStateChange>;
|
||||||
|
|
||||||
|
explicit constexpr ExplicitNoChangeProjectionConstraint(Parameters) noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IncompleteProjectionPhysics final {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = false;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IncompleteProjectionConstraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final { };
|
||||||
|
using ModelDefinition = mean_field::constraint::PhaseCondition<
|
||||||
|
IncompleteProjectionConstraint,
|
||||||
|
"IncompleteProjectionConstraint",
|
||||||
|
mean_field::models::DependsOn<mean_field::models::stellar::state::SpecificEnthalpy>,
|
||||||
|
mean_field::models::Affects<mean_field::models::stellar::equation::HydrostaticBalance>>;
|
||||||
|
using RadialProjection = mean_field::seed::projection::Use<IncompleteProjectionPhysics>;
|
||||||
|
|
||||||
|
explicit constexpr IncompleteProjectionConstraint(Parameters) noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnregisteredProjectionEquationOfState final {
|
||||||
|
public:
|
||||||
|
struct Parameters final { };
|
||||||
|
using ModelDefinition = mean_field::eos::ConstitutiveLaw<
|
||||||
|
UnregisteredProjectionEquationOfState,
|
||||||
|
"UnregisteredProjectionEquationOfState">;
|
||||||
|
|
||||||
|
explicit constexpr UnregisteredProjectionEquationOfState(Parameters) noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnregisteredProjectionSurface final {
|
||||||
|
public:
|
||||||
|
struct Parameters final { };
|
||||||
|
using ModelDefinition = mean_field::surface::BoundaryCondition<
|
||||||
|
UnregisteredProjectionSurface,
|
||||||
|
"UnregisteredProjectionSurface">;
|
||||||
|
|
||||||
|
explicit constexpr UnregisteredProjectionSurface(Parameters) noexcept {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SecondRadialMassProjectionPhysics final {
|
||||||
|
static constexpr bool registered = true;
|
||||||
|
static constexpr bool providesRadialMass = true;
|
||||||
|
|
||||||
|
template <typename Model>
|
||||||
|
static constexpr bool supports = true;
|
||||||
|
|
||||||
|
template <typename Specification>
|
||||||
|
[[nodiscard]] static mean_field::dimensions::MassValue targetMass(const Specification &specification) {
|
||||||
|
return specification.targetMass();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Specification, typename Model>
|
||||||
|
static void validate(
|
||||||
|
const Specification &,
|
||||||
|
const Model &,
|
||||||
|
const mean_field::seed::RadialProfile &,
|
||||||
|
const mean_field::seed::StellarEquilibriumProjectionOptions &
|
||||||
|
) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Specification, typename Model>
|
||||||
|
static void initialize(
|
||||||
|
const Specification &,
|
||||||
|
const Model &,
|
||||||
|
const mean_field::seed::RadialProjectionScales &,
|
||||||
|
mean_field::seed::RadialProjectionState &,
|
||||||
|
mfem::Vector coordinate
|
||||||
|
) {
|
||||||
|
if (coordinate.Size() == 1) {
|
||||||
|
coordinate(0) = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SecondRadialMassConstraint final {
|
||||||
|
public:
|
||||||
|
struct Parameters final {
|
||||||
|
mean_field::dimensions::MassValue mass;
|
||||||
|
};
|
||||||
|
using ModelDefinition = mean_field::integral::FixedWithMultiplier<
|
||||||
|
SecondRadialMassConstraint,
|
||||||
|
"SecondRadialMassConstraint">;
|
||||||
|
using RadialProjection = mean_field::seed::projection::Use<SecondRadialMassProjectionPhysics>;
|
||||||
|
|
||||||
|
explicit constexpr SecondRadialMassConstraint(Parameters parameters) noexcept : m_mass(parameters.mass) {
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr mean_field::dimensions::MassValue targetMass() const noexcept {
|
||||||
|
return m_mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mean_field::dimensions::MassValue m_mass;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
|
[[nodiscard]] mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
|
||||||
return {
|
return {
|
||||||
.discretization = {.identity = 7001, .revision = 1},
|
.discretization = {.identity = 7001, .revision = 1},
|
||||||
@@ -42,6 +173,65 @@ namespace {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Radial Projection Capabilities Are Inferred From Every Model Specification",
|
||||||
|
tags::stellar_seed_projection_type_contract
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
using BaseModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric, integral::FixedTotalMass>;
|
||||||
|
using CentralModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass,
|
||||||
|
constraint::FixedCentralDensity>;
|
||||||
|
using AngularModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass,
|
||||||
|
integral::FixedAngularMomentum>;
|
||||||
|
using ExplicitExtensionModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass,
|
||||||
|
ExplicitNoChangeProjectionConstraint>;
|
||||||
|
using MissingConstraintRuleModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass,
|
||||||
|
UnregisteredProjectionConstraint>;
|
||||||
|
using IncompleteConstraintRuleModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass,
|
||||||
|
IncompleteProjectionConstraint>;
|
||||||
|
using MissingEquationOfStateRuleModel = ProjectionModelWith<
|
||||||
|
UnregisteredProjectionEquationOfState,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass>;
|
||||||
|
using MissingSurfaceRuleModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
UnregisteredProjectionSurface,
|
||||||
|
integral::FixedTotalMass>;
|
||||||
|
using MissingMassProviderModel = ProjectionModelWith<eos::Polytrope, surface::Isobaric>;
|
||||||
|
using AmbiguousMassProviderModel = ProjectionModelWith<
|
||||||
|
eos::Polytrope,
|
||||||
|
surface::Isobaric,
|
||||||
|
integral::FixedTotalMass,
|
||||||
|
SecondRadialMassConstraint>;
|
||||||
|
|
||||||
|
STATIC_CHECK(seed::RadialProfileProjectableModel<BaseModel>);
|
||||||
|
STATIC_CHECK(seed::RadialProfileProjectableModel<CentralModel>);
|
||||||
|
STATIC_CHECK(seed::RadialProfileProjectableModel<AngularModel>);
|
||||||
|
STATIC_CHECK(seed::RadialProfileProjectableModel<ExplicitExtensionModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingConstraintRuleModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<IncompleteConstraintRuleModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingEquationOfStateRuleModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingSurfaceRuleModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<MissingMassProviderModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<AmbiguousMassProviderModel>);
|
||||||
|
STATIC_CHECK_FALSE(seed::RadialProfileProjectableModel<int>);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Projected Equilibrium States Preserve Their Compiled Stellar Model Type",
|
"Projected Equilibrium States Preserve Their Compiled Stellar Model Type",
|
||||||
tags::stellar_seed_projection_type_contract
|
tags::stellar_seed_projection_type_contract
|
||||||
@@ -116,9 +306,9 @@ TEST_CASE(
|
|||||||
REQUIRE(centralDensityBorder.Size() == 1);
|
REQUIRE(centralDensityBorder.Size() == 1);
|
||||||
CHECK(centralDensityBorder(0) == 0.0);
|
CHECK(centralDensityBorder(0) == 0.0);
|
||||||
|
|
||||||
const operators::PreparedCentralDensityStellarEquilibriumReport preparation =
|
const auto preparation = problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
|
||||||
problem.Prepare(projected.values, make_dependencies(), make_zero_rotation());
|
|
||||||
CHECK(preparation.assembledResidual);
|
CHECK(preparation.assembledResidual);
|
||||||
|
CHECK(preparation.template specification<models::FixedCentralDensity>().constraint.DidAnyWork());
|
||||||
|
|
||||||
mfem::Vector residual;
|
mfem::Vector residual;
|
||||||
problem.BuildResidual(residual);
|
problem.BuildResidual(residual);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <mfem.hpp>
|
||||||
|
|
||||||
import mean_field;
|
import mean_field;
|
||||||
import test_helpers;
|
import test_helpers;
|
||||||
@@ -11,6 +12,25 @@ namespace {
|
|||||||
const mean_field::utils::Args arguments = test_utils::setup_args();
|
const mean_field::utils::Args arguments = test_utils::setup_args();
|
||||||
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept PreparesWithGeneratedRotation = requires(
|
||||||
|
Problem &problem,
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const mean_field::operators::StellarEquilibriumDependencies &dependencies
|
||||||
|
) {
|
||||||
|
problem.Prepare(state, dependencies);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Problem>
|
||||||
|
concept PreparesWithPrescribedRotation = requires(
|
||||||
|
Problem &problem,
|
||||||
|
const mfem::Vector &state,
|
||||||
|
const mean_field::operators::StellarEquilibriumDependencies &dependencies,
|
||||||
|
const mean_field::physics::RigidRotation &rotation
|
||||||
|
) {
|
||||||
|
problem.Prepare(state, dependencies, rotation);
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
@@ -42,6 +62,49 @@ TEST_CASE(
|
|||||||
CHECK(gravity.potentialSchurBackend().application.cycles == 3);
|
CHECK(gravity.potentialSchurBackend().application.cycles == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE(
|
||||||
|
"Fixed Angular Momentum User API Generates Rotation And Its Composable Solver Border",
|
||||||
|
"[user-api][fixed-angular-momentum][type]"
|
||||||
|
) {
|
||||||
|
using namespace mean_field;
|
||||||
|
|
||||||
|
auto finiteElements = makeFiniteElements();
|
||||||
|
REQUIRE(finiteElements.okay());
|
||||||
|
|
||||||
|
auto model = model::StellarModel(
|
||||||
|
eos::Polytrope({.n = 1.0, .K = 0.25}),
|
||||||
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
||||||
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{1.0}}),
|
||||||
|
integral::FixedAngularMomentum({
|
||||||
|
.Jtotal = dimensions::AngularMomentumValue{0.2},
|
||||||
|
.axis = {0.0, 0.0, 2.0}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
auto problem = equilibrium::discretize(model, finiteElements);
|
||||||
|
auto preconditioner = preconditioning::makePreconditioner(problem);
|
||||||
|
using Problem = std::remove_cvref_t<decltype(problem)>;
|
||||||
|
using Preconditioner = std::remove_cvref_t<decltype(preconditioner)>;
|
||||||
|
|
||||||
|
STATIC_CHECK(Problem::hasFixedAngularMomentum);
|
||||||
|
STATIC_CHECK_FALSE(Problem::hasFixedCentralDensity);
|
||||||
|
STATIC_CHECK(PreparesWithGeneratedRotation<Problem>);
|
||||||
|
STATIC_CHECK_FALSE(PreparesWithPrescribedRotation<Problem>);
|
||||||
|
STATIC_CHECK(Problem::FormType::value_block_count == 7);
|
||||||
|
STATIC_CHECK(Problem::FormType::residual_block_count == 7);
|
||||||
|
STATIC_CHECK(preconditioning::PreconditionerComponent<Preconditioner>);
|
||||||
|
STATIC_CHECK(Preconditioner::borderValueArity == 2);
|
||||||
|
STATIC_CHECK(Preconditioner::borderResidualArity == 2);
|
||||||
|
|
||||||
|
CHECK(problem.StateSize() == problem.EquationSize());
|
||||||
|
CHECK(problem.StateSize() == problem.GetPhysicalOperator().Width() + 1);
|
||||||
|
REQUIRE(problem.GetManifest().constraints().size() == 3);
|
||||||
|
CHECK(problem.GetManifest().template specification<models::FixedAngularMomentum>().stableId ==
|
||||||
|
"FixedAngularMomentum");
|
||||||
|
CHECK(problem.GetManifest().valueBlocks().back().symbol == "Omega");
|
||||||
|
CHECK(problem.GetManifest().residualBlocks().back().symbol == "R_J");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(
|
TEST_CASE(
|
||||||
"Intermediate User API Selects A Coupled Stellar Factorization",
|
"Intermediate User API Selects A Coupled Stellar Factorization",
|
||||||
"[user-api][intermediate]"
|
"[user-api][intermediate]"
|
||||||
|
|||||||
Reference in New Issue
Block a user