feat(preconditioner): major work on preconditioner system
first preconditioner MVP
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
module;
|
||||
#include "profile.h"
|
||||
#include <array>
|
||||
#include <mfem.hpp>
|
||||
|
||||
@@ -62,6 +63,8 @@ namespace mean_field::analysis {
|
||||
utils::DOMAINS domain,
|
||||
mapping::COORDINATE_SPACE coord_space
|
||||
) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::domain_integrate_grid_function", 0);
|
||||
|
||||
mfem::LinearForm lf(fem.densityFes.get());
|
||||
mfem::GridFunctionCoefficient gf_c(&gf);
|
||||
double local_integral;
|
||||
@@ -107,11 +110,15 @@ namespace mean_field::analysis {
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &rho
|
||||
) {
|
||||
const int dim = fem.mesh->Dimension();
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::get_com", 0);
|
||||
|
||||
std::uint64_t mapping_evaluations = 0;
|
||||
const int dim = fem.mesh->Dimension();
|
||||
mapping::GridFunctionMappingEvaluator mapping_evaluator(
|
||||
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
|
||||
);
|
||||
mfem::Vector local_com(dim);
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
local_com = 0.0;
|
||||
double local_mass = 0.0;
|
||||
|
||||
@@ -127,11 +134,11 @@ namespace mean_field::analysis {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
|
||||
trans->SetIntPoint(&ip);
|
||||
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
MFEM_VERIFY(
|
||||
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
|
||||
"Center-of-mass integration encountered an invalid mapping."
|
||||
);
|
||||
++mapping_evaluations;
|
||||
const double weight = mapping_context.quadrature.weight;
|
||||
double rho_val = rho.GetValue(i, ip);
|
||||
|
||||
@@ -146,13 +153,23 @@ namespace mean_field::analysis {
|
||||
}
|
||||
}
|
||||
|
||||
double global_mass = 0.0;
|
||||
MEAN_FIELD_PROFILE_COUNT("analysis::get_com mapping evaluations", mapping_evaluations);
|
||||
|
||||
mfem::Vector local_integrals(dim + 1);
|
||||
mfem::Vector global_integrals(dim + 1);
|
||||
local_integrals(0) = local_mass;
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
local_integrals(d + 1) = local_com(d);
|
||||
}
|
||||
MPI_Allreduce(
|
||||
local_integrals.GetData(), global_integrals.GetData(), dim + 1, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm()
|
||||
);
|
||||
|
||||
const double global_mass = global_integrals(0);
|
||||
mfem::Vector global_com(dim);
|
||||
MPI_Comm comm = fem.mesh->GetComm();
|
||||
|
||||
MPI_Allreduce(&local_mass, &global_mass, 1, MPI_DOUBLE, MPI_SUM, comm);
|
||||
|
||||
MPI_Allreduce(local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM, comm);
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
global_com(d) = global_integrals(d + 1);
|
||||
}
|
||||
|
||||
if (global_mass > 1e-18) {
|
||||
global_com /= global_mass;
|
||||
@@ -168,6 +185,8 @@ namespace mean_field::analysis {
|
||||
mfem::GridFunction &rho,
|
||||
const double target_mass
|
||||
) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::conserve_mass", 0);
|
||||
|
||||
if (const double current_mass = domain_integrate_grid_function(fem, rho, utils::DOMAINS::STELLAR);
|
||||
current_mass > 1e-15)
|
||||
rho *= (target_mass / current_mass);
|
||||
@@ -177,6 +196,8 @@ namespace mean_field::analysis {
|
||||
const fem::FEM &fem,
|
||||
const mfem::GridFunction &rho
|
||||
) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::get_moment_of_inertia", 0);
|
||||
|
||||
auto s2_func = [](const mfem::Vector &x) { return std::pow(x(0), 2) + std::pow(x(1), 2); };
|
||||
|
||||
std::unique_ptr<mfem::Coefficient> s2_coeff;
|
||||
@@ -227,6 +248,8 @@ namespace mean_field::analysis {
|
||||
const mapping::COORDINATE_SPACE coordinate_space,
|
||||
const utils::DOMAINS domain
|
||||
) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::get_mesh_volume", 0);
|
||||
|
||||
mfem::ParMesh &mesh = *fem.mesh;
|
||||
const bool physical = (coordinate_space == mapping::COORDINATE_SPACE::PHYSICAL);
|
||||
|
||||
@@ -238,6 +261,7 @@ namespace mean_field::analysis {
|
||||
mapping::GridFunctionMappingEvaluator mapping_evaluator(
|
||||
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
|
||||
);
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
|
||||
for (int e = 0; e < mesh.GetNE(); ++e) {
|
||||
const int attr = mesh.GetAttribute(e);
|
||||
@@ -259,12 +283,11 @@ namespace mean_field::analysis {
|
||||
double dV = ip.weight * T->Weight();
|
||||
|
||||
if (physical) {
|
||||
mapping::VolumeMappingContext context;
|
||||
MFEM_VERIFY(
|
||||
mapping_evaluator.EvaluateVolume(*T, ip, context) == mapping::MappingStatus::valid,
|
||||
mapping_evaluator.EvaluateVolume(*T, ip, mapping_context) == mapping::MappingStatus::valid,
|
||||
"Mesh-volume integration encountered an invalid mapping."
|
||||
);
|
||||
dV = context.quadrature.weight;
|
||||
dV = mapping_context.quadrature.weight;
|
||||
}
|
||||
|
||||
local_volume += dV;
|
||||
|
||||
@@ -58,8 +58,8 @@ namespace mean_field::integrators {
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector x_phys(dim);
|
||||
mfem::Vector a(dim), b(dim);
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_ir, "CentrifugalForceIntegrator must be configured with an "
|
||||
@@ -72,24 +72,29 @@ namespace mean_field::integrators {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
|
||||
const mapping::MappingStatus mapping_status = m_mapping.EvaluateVolume(Tr, ip, mapping_context);
|
||||
MFEM_VERIFY(
|
||||
mapping_status == mapping::MappingStatus::valid,
|
||||
"Centrifugal-force assembly encountered an invalid volume mapping."
|
||||
);
|
||||
const double weight = mapping_context.quadrature.weight;
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
m_mapping.GetPhysicalPoint(Tr, ip, x_phys);
|
||||
const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
|
||||
|
||||
// ω x r
|
||||
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
|
||||
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
|
||||
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
|
||||
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
|
||||
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
|
||||
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
|
||||
|
||||
// ω x (ω x r) [centrifugal acceleration]
|
||||
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
|
||||
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
|
||||
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
|
||||
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
|
||||
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
|
||||
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
|
||||
|
||||
double rho_val = 0.0;
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
@@ -135,8 +140,8 @@ namespace mean_field::integrators {
|
||||
return;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector x_phys(dim);
|
||||
mfem::Vector a(dim), b(dim);
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
@@ -144,22 +149,27 @@ namespace mean_field::integrators {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_mapping.GetQuadratureContext(Tr, ip);
|
||||
const mapping::MappingStatus mapping_status = m_mapping.EvaluateVolume(Tr, ip, mapping_context);
|
||||
MFEM_VERIFY(
|
||||
mapping_status == mapping::MappingStatus::valid,
|
||||
"Centrifugal-force Jacobian assembly encountered an invalid volume mapping."
|
||||
);
|
||||
const double weight = mapping_context.quadrature.weight;
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
m_mapping.GetPhysicalPoint(Tr, ip, x_phys);
|
||||
const mfem::Vector &x_phys = mapping_context.mapping.physical_position;
|
||||
|
||||
// ω x r
|
||||
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
|
||||
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
|
||||
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
|
||||
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
|
||||
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
|
||||
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
|
||||
|
||||
// ω x (ω x r) [centrifugal acceleration]
|
||||
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
|
||||
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
|
||||
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
|
||||
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
|
||||
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
|
||||
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
|
||||
|
||||
// dR_dv_i_c / drho_j = φ_i * φ_j * b_c
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
|
||||
@@ -9,7 +9,132 @@ import :operators.context.gravity_field;
|
||||
namespace {
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<mfem::ParMixedBilinearForm> make_divergence_operator(const mean_field::fem::FEM &f) {
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &true_vector,
|
||||
mfem::Vector &local_vector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
true_vector.Size() == finite_element_space.GetTrueVSize(),
|
||||
"True-DOF operator received an input vector with the wrong size."
|
||||
);
|
||||
|
||||
local_vector.SetSize(finite_element_space.GetVSize());
|
||||
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->Mult(true_vector, local_vector);
|
||||
} else {
|
||||
local_vector = true_vector;
|
||||
}
|
||||
}
|
||||
|
||||
void local_to_true(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &local_vector,
|
||||
mfem::Vector &true_vector
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
local_vector.Size() == finite_element_space.GetVSize(),
|
||||
"True-DOF operator produced a local vector with the wrong size."
|
||||
);
|
||||
|
||||
true_vector.SetSize(finite_element_space.GetTrueVSize());
|
||||
const mfem::Operator *prolongation = finite_element_space.GetProlongationMatrix();
|
||||
if (prolongation != nullptr) {
|
||||
prolongation->MultTranspose(local_vector, true_vector);
|
||||
} else {
|
||||
true_vector = local_vector;
|
||||
}
|
||||
}
|
||||
|
||||
bool communicator_has_single_rank(const MPI_Comm communicator) {
|
||||
int size = 0;
|
||||
MFEM_VERIFY(MPI_Comm_size(communicator, &size) == MPI_SUCCESS, "Failed to query the MPI communicator size.");
|
||||
MFEM_VERIFY(size > 0, "The MPI communicator must contain at least one rank.");
|
||||
return size == 1;
|
||||
}
|
||||
|
||||
class TrueDofParMixedBilinearFormOperator final : public mfem::Operator {
|
||||
public:
|
||||
TrueDofParMixedBilinearFormOperator(
|
||||
const mfem::ParFiniteElementSpace &trial_space,
|
||||
const mfem::ParFiniteElementSpace &test_space,
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm> local_form
|
||||
)
|
||||
: Operator(
|
||||
test_space.GetTrueVSize(),
|
||||
trial_space.GetTrueVSize()
|
||||
),
|
||||
m_trial_space(trial_space),
|
||||
m_test_space(test_space),
|
||||
m_local_form(std::move(local_form)),
|
||||
m_single_rank(communicator_has_single_rank(trial_space.GetComm())) {
|
||||
int communicators_compare = MPI_UNEQUAL;
|
||||
MFEM_VERIFY(
|
||||
MPI_Comm_compare(trial_space.GetComm(), test_space.GetComm(), &communicators_compare) == MPI_SUCCESS,
|
||||
"Failed to compare mixed-operator MPI communicators."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
communicators_compare == MPI_IDENT || communicators_compare == MPI_CONGRUENT,
|
||||
"True-DOF mixed operator requires congruent trial and test communicators."
|
||||
);
|
||||
MFEM_VERIFY(m_local_form != nullptr, "True-DOF mixed operator requires a local bilinear form.");
|
||||
MFEM_VERIFY(
|
||||
m_local_form->Width() == m_trial_space.GetVSize(),
|
||||
"True-DOF mixed operator received an incompatible trial space."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_local_form->Height() == m_test_space.GetVSize(),
|
||||
"True-DOF mixed operator received an incompatible test space."
|
||||
);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &input,
|
||||
mfem::Vector &output
|
||||
) const override {
|
||||
MFEM_VERIFY(input.Size() == Width(), "True-DOF mixed operator received an input with the wrong size.");
|
||||
|
||||
if (m_single_rank) [[likely]] {
|
||||
output.SetSize(Height());
|
||||
m_local_form->Mult(input, output);
|
||||
return;
|
||||
}
|
||||
|
||||
true_to_local(m_trial_space, input, m_trial_local);
|
||||
m_test_local.SetSize(m_test_space.GetVSize());
|
||||
m_local_form->Mult(m_trial_local, m_test_local);
|
||||
local_to_true(m_test_space, m_test_local, output);
|
||||
}
|
||||
|
||||
void MultTranspose(
|
||||
const mfem::Vector &input,
|
||||
mfem::Vector &output
|
||||
) const override {
|
||||
MFEM_VERIFY(input.Size() == Height(), "True-DOF mixed transpose received an input with the wrong size.");
|
||||
|
||||
if (m_single_rank) [[likely]] {
|
||||
output.SetSize(Width());
|
||||
m_local_form->MultTranspose(input, output);
|
||||
return;
|
||||
}
|
||||
|
||||
true_to_local(m_test_space, input, m_test_local);
|
||||
m_trial_local.SetSize(m_trial_space.GetVSize());
|
||||
m_local_form->MultTranspose(m_test_local, m_trial_local);
|
||||
local_to_true(m_trial_space, m_trial_local, output);
|
||||
}
|
||||
|
||||
private:
|
||||
const mfem::ParFiniteElementSpace &m_trial_space;
|
||||
const mfem::ParFiniteElementSpace &m_test_space;
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm> m_local_form;
|
||||
mutable mfem::Vector m_trial_local;
|
||||
mutable mfem::Vector m_test_local;
|
||||
bool m_single_rank;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<mfem::Operator> make_divergence_operator(const mean_field::fem::FEM &f) {
|
||||
auto divergence =
|
||||
std::make_unique<mfem::ParMixedBilinearForm>(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
|
||||
|
||||
@@ -29,7 +154,9 @@ namespace {
|
||||
divergence->AddDomainIntegrator(integrator.release());
|
||||
divergence->Assemble();
|
||||
|
||||
return divergence;
|
||||
return std::make_unique<TrueDofParMixedBilinearFormOperator>(
|
||||
*f.gravityFluxFes, *f.gravityPotentialFes, std::move(divergence)
|
||||
);
|
||||
}
|
||||
|
||||
void validate_displacement(
|
||||
@@ -167,6 +294,25 @@ namespace mean_field::operators::context::gravity_field {
|
||||
const mfem::Vector &displacement,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
return PrepareImpl(
|
||||
displacement, discretization_revision, displacement_revision, PreparationMode::linearization
|
||||
);
|
||||
}
|
||||
|
||||
GravityFieldGeometryPreparation GravityFieldGeometryContext::PreparePrimal(
|
||||
const mfem::Vector &displacement,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision
|
||||
) {
|
||||
return PrepareImpl(displacement, discretization_revision, displacement_revision, PreparationMode::primal);
|
||||
}
|
||||
|
||||
GravityFieldGeometryPreparation GravityFieldGeometryContext::PrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
const DiscretizationRevision discretization_revision,
|
||||
const DisplacementRevision displacement_revision,
|
||||
const PreparationMode mode
|
||||
) {
|
||||
validate_displacement(m_displacement_map, displacement);
|
||||
|
||||
@@ -185,21 +331,38 @@ namespace mean_field::operators::context::gravity_field {
|
||||
|
||||
const bool discretization_changed = !m_is_prepared || discretization_revision != m_discretization_revision;
|
||||
const bool displacement_changed = !m_is_prepared || displacement_revision != m_displacement_revision;
|
||||
const bool requires_variation = mode == PreparationMode::linearization;
|
||||
const bool variation_upgrade = requires_variation && !m_variation_state_prepared;
|
||||
|
||||
GravityFieldGeometryPreparation preparation;
|
||||
|
||||
if (!discretization_changed && !displacement_changed) {
|
||||
if (!discretization_changed && !displacement_changed && !variation_upgrade) {
|
||||
return preparation;
|
||||
}
|
||||
|
||||
const auto prepare_mass = [&](PreparedMappedHDivMassOperator &mass_operator) {
|
||||
if (requires_variation) {
|
||||
mass_operator.Prepare(displacement);
|
||||
} else {
|
||||
mass_operator.PreparePrimal(displacement);
|
||||
}
|
||||
};
|
||||
const auto prepare_source = [&](PreparedMappedGravitySourceOperator &source_operator) {
|
||||
if (requires_variation) {
|
||||
source_operator.Prepare(displacement);
|
||||
} else {
|
||||
source_operator.PreparePrimal(displacement);
|
||||
}
|
||||
};
|
||||
|
||||
if (discretization_changed) {
|
||||
auto mass_operator = std::make_unique<PreparedMappedHDivMassOperator>(m_fem, m_domain_mapper);
|
||||
auto source_operator = std::make_unique<PreparedMappedGravitySourceOperator>(m_fem, m_domain_mapper);
|
||||
auto divergence_operator = make_divergence_operator(m_fem);
|
||||
auto transpose_divergence_operator = std::make_unique<mfem::TransposeOperator>(divergence_operator.get());
|
||||
|
||||
mass_operator->Prepare(displacement);
|
||||
source_operator->Prepare(displacement);
|
||||
prepare_mass(*mass_operator);
|
||||
prepare_source(*source_operator);
|
||||
|
||||
m_mass_operator = std::move(mass_operator);
|
||||
m_source_operator = std::move(source_operator);
|
||||
@@ -220,8 +383,8 @@ namespace mean_field::operators::context::gravity_field {
|
||||
"operator."
|
||||
);
|
||||
|
||||
m_mass_operator->Prepare(displacement);
|
||||
m_source_operator->Prepare(displacement);
|
||||
prepare_mass(*m_mass_operator);
|
||||
prepare_source(*m_source_operator);
|
||||
|
||||
preparation.rebuilt_mass_operator = true;
|
||||
preparation.rebuilt_source_operator = true;
|
||||
@@ -232,8 +395,9 @@ namespace mean_field::operators::context::gravity_field {
|
||||
m_discretization_revision = discretization_revision;
|
||||
m_displacement_revision = displacement_revision;
|
||||
m_is_prepared = true;
|
||||
m_variation_state_prepared = requires_variation;
|
||||
|
||||
preparation.refreshed_variation_state = true;
|
||||
preparation.refreshed_variation_state = requires_variation;
|
||||
|
||||
return preparation;
|
||||
}
|
||||
|
||||
@@ -346,20 +346,32 @@ namespace mean_field::operators {
|
||||
make_residual_view(action, m_residual_offsets, gravity_poisson_residual_block);
|
||||
const field::FieldDofMap &flux_map = geometry_context.GetMassOperator().GetFluxMap();
|
||||
const field::FieldDofMap &potential_map = geometry_context.GetSourceOperator().GetPotentialMap();
|
||||
mfem::Vector potential_true(potential_map.full_size());
|
||||
mfem::Vector transpose_divergence_action_true(flux_map.full_size());
|
||||
mfem::Vector transpose_divergence_action(flux_map.reduced_size());
|
||||
mfem::Vector gradient_true(flux_map.full_size());
|
||||
mfem::Vector divergence_action_true(potential_map.full_size());
|
||||
|
||||
geometry_context.GetMassOperator().Mult(gravity_gradient, gravity_gradient_action);
|
||||
potential_map.scatter(gravity_potential, potential_true);
|
||||
geometry_context.GetTransposeDivergenceOperator().Mult(potential_true, transpose_divergence_action_true);
|
||||
flux_map.gather(transpose_divergence_action_true, transpose_divergence_action);
|
||||
gravity_gradient_action += transpose_divergence_action;
|
||||
flux_map.scatter(gravity_gradient, gradient_true);
|
||||
geometry_context.GetDivergenceOperator().Mult(gradient_true, divergence_action_true);
|
||||
potential_map.gather(divergence_action_true, gravity_poisson_action);
|
||||
|
||||
if (flux_map.is_identity() && potential_map.is_identity()) [[likely]] {
|
||||
m_transpose_divergence_action_true.SetSize(flux_map.full_size());
|
||||
geometry_context.GetTransposeDivergenceOperator().Mult(
|
||||
gravity_potential, m_transpose_divergence_action_true
|
||||
);
|
||||
gravity_gradient_action += m_transpose_divergence_action_true;
|
||||
geometry_context.GetDivergenceOperator().Mult(gravity_gradient, gravity_poisson_action);
|
||||
return;
|
||||
}
|
||||
|
||||
m_potential_true.SetSize(potential_map.full_size());
|
||||
m_transpose_divergence_action_true.SetSize(flux_map.full_size());
|
||||
m_transpose_divergence_action.SetSize(flux_map.reduced_size());
|
||||
m_gradient_true.SetSize(flux_map.full_size());
|
||||
m_divergence_action_true.SetSize(potential_map.full_size());
|
||||
|
||||
potential_map.scatter(gravity_potential, m_potential_true);
|
||||
geometry_context.GetTransposeDivergenceOperator().Mult(m_potential_true, m_transpose_divergence_action_true);
|
||||
flux_map.gather(m_transpose_divergence_action_true, m_transpose_divergence_action);
|
||||
gravity_gradient_action += m_transpose_divergence_action;
|
||||
flux_map.scatter(gravity_gradient, m_gradient_true);
|
||||
geometry_context.GetDivergenceOperator().Mult(m_gradient_true, m_divergence_action_true);
|
||||
potential_map.gather(m_divergence_action_true, gravity_poisson_action);
|
||||
}
|
||||
|
||||
void GravityFieldOperator::ApplyDensitySource(
|
||||
@@ -529,7 +541,7 @@ namespace mean_field::operators {
|
||||
++displacement_revision.value;
|
||||
}
|
||||
|
||||
m_gravity_field_geometry_context.Prepare(displacement, discretization_revision, displacement_revision);
|
||||
m_gravity_field_geometry_context.PreparePrimal(displacement, discretization_revision, displacement_revision);
|
||||
m_displacement = displacement;
|
||||
}
|
||||
|
||||
|
||||
@@ -267,6 +267,9 @@ namespace mean_field::operators {
|
||||
|
||||
gravity_poisson_action -= source_action;
|
||||
gravity_poisson_action -= source_variation_action;
|
||||
|
||||
gravity_gradient_action.SyncAliasMemory(action);
|
||||
gravity_poisson_action.SyncAliasMemory(action);
|
||||
}
|
||||
|
||||
const context::gravity_field::GravityFieldLinearizationContext &
|
||||
|
||||
@@ -469,6 +469,35 @@ namespace mean_field::operators {
|
||||
m_densityMap.gather(m_fullResidual, residual);
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::AssembleDensityJacobianDiagonal(mfem::Vector &diagonal) const {
|
||||
VerifyPrepared();
|
||||
|
||||
mfem::Vector localDiagonal(m_fem.densityFes->GetVSize());
|
||||
localDiagonal = 0.0;
|
||||
mfem::Vector elementDiagonal;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
MFEM_VERIFY(
|
||||
data.densityDofTransformation == nullptr,
|
||||
"Density mass-diagonal assembly currently requires scalar L2 element DOFs without a DOF transform."
|
||||
);
|
||||
elementDiagonal.SetSize(data.densityDofs.Size());
|
||||
elementDiagonal = 0.0;
|
||||
for (int trialDof = 0; trialDof < data.densityDofs.Size(); ++trialDof) {
|
||||
for (int quadraturePoint = 0; quadraturePoint < data.quadratureWeights.Size(); ++quadraturePoint) {
|
||||
const double basis = data.densityBasis(quadraturePoint, trialDof);
|
||||
elementDiagonal(trialDof) += data.quadratureWeights(quadraturePoint) * basis * basis;
|
||||
}
|
||||
}
|
||||
localDiagonal.AddElementVector(data.densityDofs, elementDiagonal);
|
||||
}
|
||||
|
||||
mfem::Vector trueDiagonal;
|
||||
local_to_true(*m_fem.densityFes, localDiagonal, trueDiagonal);
|
||||
diagonal.SetSize(m_densityMap.reduced_size());
|
||||
m_densityMap.gather(trueDiagonal, diagonal);
|
||||
}
|
||||
|
||||
void PreparedBarotropicClosureOperator::Mult(
|
||||
const mfem::Vector &densityVariation,
|
||||
const mfem::Vector &enthalpyVariation,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module;
|
||||
#include "profile.h"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -304,6 +305,19 @@ namespace mean_field::operators {
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::Prepare(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare linearization", 0);
|
||||
PrepareImpl(displacement, PreparationMode::linearization);
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::PreparePrimal(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedGravitySourceOperator::Prepare primal", 0);
|
||||
PrepareImpl(displacement, PreparationMode::primal);
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::PrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
const PreparationMode mode
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
displacement.Size() == m_displacement_map.reduced_size(),
|
||||
"PreparedMappedGravitySourceOperator received a displacement "
|
||||
@@ -318,7 +332,8 @@ namespace mean_field::operators {
|
||||
);
|
||||
}
|
||||
|
||||
m_is_prepared = false;
|
||||
m_is_prepared = false;
|
||||
m_has_variation_data = false;
|
||||
m_displacement_true.SetSize(m_displacement_map.full_size());
|
||||
m_displacement_map.scatter(displacement, m_displacement_true);
|
||||
m_elements.clear();
|
||||
@@ -343,8 +358,10 @@ namespace mean_field::operators {
|
||||
data.potential_dof_transformation =
|
||||
m_fem.gravityPotentialFes->GetElementDofs(element_id, data.potential_dofs);
|
||||
|
||||
data.displacement_dof_transformation =
|
||||
m_fem.displacementFes->GetElementVDofs(element_id, data.displacement_dofs);
|
||||
if (mode == PreparationMode::linearization) {
|
||||
data.displacement_dof_transformation =
|
||||
m_fem.displacementFes->GetElementVDofs(element_id, data.displacement_dofs);
|
||||
}
|
||||
|
||||
const mfem::FiniteElement &density_element = *m_fem.densityFes->GetFE(element_id);
|
||||
|
||||
@@ -367,7 +384,9 @@ namespace mean_field::operators {
|
||||
data.potential_basis.SetSize(quadrature_point_count, potential_dof_count);
|
||||
|
||||
const int dimension = m_fem.mesh->Dimension();
|
||||
data.inverse_element_jacobians.SetSize(quadrature_point_count, dimension * dimension);
|
||||
if (mode == PreparationMode::linearization) {
|
||||
data.inverse_element_jacobians.SetSize(quadrature_point_count, dimension * dimension);
|
||||
}
|
||||
|
||||
data.quadrature_data.SetSize(quadrature_point_count);
|
||||
|
||||
@@ -395,11 +414,13 @@ namespace mean_field::operators {
|
||||
|
||||
const double coefficient_value = source_coefficient.Eval(transformation, integration_point);
|
||||
|
||||
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
data.inverse_element_jacobians(quadrature_point, row * dimension + column) =
|
||||
inverse_element_jacobian(row, column);
|
||||
if (mode == PreparationMode::linearization) {
|
||||
const mfem::DenseMatrix &inverse_element_jacobian = source_coefficient.GetInverseElementJacobian();
|
||||
for (int row = 0; row < dimension; ++row) {
|
||||
for (int column = 0; column < dimension; ++column) {
|
||||
data.inverse_element_jacobians(quadrature_point, row * dimension + column) =
|
||||
inverse_element_jacobian(row, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,13 +441,16 @@ namespace mean_field::operators {
|
||||
|
||||
MFEM_VERIFY(!m_elements.empty(), "PreparedMappedGravitySourceOperator found no stellar elements.");
|
||||
|
||||
m_is_prepared = true;
|
||||
m_is_prepared = true;
|
||||
m_has_variation_data = mode == PreparationMode::linearization;
|
||||
++m_preparation_count;
|
||||
}
|
||||
void PreparedMappedGravitySourceOperator::Mult(
|
||||
const mfem::Vector &density,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MEAN_FIELD_PROFILE_SCOPE("PreparedMappedGravitySourceOperator::Mult");
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "PreparedMappedGravitySourceOperator must be prepared before "
|
||||
"Mult is called."
|
||||
@@ -440,49 +464,47 @@ namespace mean_field::operators {
|
||||
m_density_true.SetSize(m_density_map.full_size());
|
||||
m_density_map.scatter(density, m_density_true);
|
||||
|
||||
mfem::Vector density_local;
|
||||
true_to_local(*m_fem.densityFes, m_density_true, m_density_local);
|
||||
|
||||
true_to_local(*m_fem.densityFes, m_density_true, density_local);
|
||||
|
||||
mfem::Vector local_action(m_fem.gravityPotentialFes->GetVSize());
|
||||
local_action = 0.0;
|
||||
|
||||
mfem::Vector element_density;
|
||||
mfem::Vector quadrature_density;
|
||||
mfem::Vector element_action;
|
||||
m_local_action.SetSize(m_fem.gravityPotentialFes->GetVSize());
|
||||
m_local_action = 0.0;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
density_local.GetSubVector(data.density_dofs, element_density);
|
||||
m_density_local.GetSubVector(data.density_dofs, m_element_input);
|
||||
|
||||
if (data.density_dof_transformation != nullptr) {
|
||||
data.density_dof_transformation->InvTransformPrimal(element_density);
|
||||
data.density_dof_transformation->InvTransformPrimal(m_element_input);
|
||||
}
|
||||
|
||||
quadrature_density.SetSize(data.quadrature_data.Size());
|
||||
m_quadrature_action.SetSize(data.quadrature_data.Size());
|
||||
|
||||
// B_density * x_e
|
||||
data.density_basis.Mult(element_density, quadrature_density);
|
||||
data.density_basis.Mult(m_element_input, m_quadrature_action);
|
||||
|
||||
// D * B_density * x_e
|
||||
for (int q = 0; q < quadrature_density.Size(); ++q) {
|
||||
quadrature_density(q) *= data.quadrature_data(q);
|
||||
for (int q = 0; q < m_quadrature_action.Size(); ++q) {
|
||||
m_quadrature_action(q) *= data.quadrature_data(q);
|
||||
}
|
||||
|
||||
element_action.SetSize(data.potential_dofs.Size());
|
||||
m_element_action.SetSize(data.potential_dofs.Size());
|
||||
|
||||
// B_potential^T * D * B_density * x_e
|
||||
data.potential_basis.MultTranspose(quadrature_density, element_action);
|
||||
data.potential_basis.MultTranspose(m_quadrature_action, m_element_action);
|
||||
|
||||
if (data.potential_dof_transformation != nullptr) {
|
||||
data.potential_dof_transformation->TransformDual(element_action);
|
||||
data.potential_dof_transformation->TransformDual(m_element_action);
|
||||
}
|
||||
|
||||
local_action.AddElementVector(data.potential_dofs, element_action);
|
||||
m_local_action.AddElementVector(data.potential_dofs, m_element_action);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.gravityPotentialFes, local_action, m_action_true);
|
||||
action.SetSize(Height());
|
||||
m_potential_map.gather(m_action_true, action);
|
||||
if (m_potential_map.is_identity()) {
|
||||
local_to_true(*m_fem.gravityPotentialFes, m_local_action, action);
|
||||
} else {
|
||||
local_to_true(*m_fem.gravityPotentialFes, m_local_action, m_action_true);
|
||||
action.SetSize(Height());
|
||||
m_potential_map.gather(m_action_true, action);
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedMappedGravitySourceOperator::MultDisplacementVariationTrue(
|
||||
@@ -494,6 +516,11 @@ namespace mean_field::operators {
|
||||
m_is_prepared,
|
||||
"PreparedMappedGravitySourceOperator must be prepared before applying a displacement variation."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_has_variation_data,
|
||||
"PreparedMappedGravitySourceOperator requires linearization preparation before applying a displacement "
|
||||
"variation."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
densityTrue.Size() == m_fem.densityFes->GetTrueVSize(), "The full density vector has the wrong size."
|
||||
);
|
||||
@@ -591,47 +618,44 @@ namespace mean_field::operators {
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
m_potential_true.SetSize(m_potential_map.full_size());
|
||||
m_potential_map.scatter(potential, m_potential_true);
|
||||
|
||||
mfem::Vector potential_local;
|
||||
|
||||
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, potential_local);
|
||||
|
||||
mfem::Vector local_action(m_fem.densityFes->GetVSize());
|
||||
local_action = 0.0;
|
||||
|
||||
mfem::Vector element_potential;
|
||||
mfem::Vector quadrature_potential;
|
||||
mfem::Vector element_action;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
potential_local.GetSubVector(data.potential_dofs, element_potential);
|
||||
|
||||
if (data.potential_dof_transformation != nullptr) {
|
||||
data.potential_dof_transformation->InvTransformPrimal(element_potential);
|
||||
}
|
||||
|
||||
quadrature_potential.SetSize(data.quadrature_data.Size());
|
||||
|
||||
data.potential_basis.Mult(element_potential, quadrature_potential);
|
||||
|
||||
for (int q = 0; q < quadrature_potential.Size(); ++q) {
|
||||
quadrature_potential(q) *= data.quadrature_data(q);
|
||||
}
|
||||
|
||||
element_action.SetSize(data.density_dofs.Size());
|
||||
|
||||
data.density_basis.MultTranspose(quadrature_potential, element_action);
|
||||
|
||||
if (data.density_dof_transformation != nullptr) {
|
||||
data.density_dof_transformation->TransformDual(element_action);
|
||||
}
|
||||
|
||||
local_action.AddElementVector(data.density_dofs, element_action);
|
||||
if (m_potential_map.is_identity()) {
|
||||
true_to_local(*m_fem.gravityPotentialFes, potential, m_potential_local);
|
||||
} else {
|
||||
m_potential_true.SetSize(m_potential_map.full_size());
|
||||
m_potential_map.scatter(potential, m_potential_true);
|
||||
true_to_local(*m_fem.gravityPotentialFes, m_potential_true, m_potential_local);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.densityFes, local_action, m_action_true);
|
||||
m_local_action.SetSize(m_fem.densityFes->GetVSize());
|
||||
m_local_action = 0.0;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
m_potential_local.GetSubVector(data.potential_dofs, m_element_input);
|
||||
|
||||
if (data.potential_dof_transformation != nullptr) {
|
||||
data.potential_dof_transformation->InvTransformPrimal(m_element_input);
|
||||
}
|
||||
|
||||
m_quadrature_action.SetSize(data.quadrature_data.Size());
|
||||
|
||||
data.potential_basis.Mult(m_element_input, m_quadrature_action);
|
||||
|
||||
for (int q = 0; q < m_quadrature_action.Size(); ++q) {
|
||||
m_quadrature_action(q) *= data.quadrature_data(q);
|
||||
}
|
||||
|
||||
m_element_action.SetSize(data.density_dofs.Size());
|
||||
|
||||
data.density_basis.MultTranspose(m_quadrature_action, m_element_action);
|
||||
|
||||
if (data.density_dof_transformation != nullptr) {
|
||||
data.density_dof_transformation->TransformDual(m_element_action);
|
||||
}
|
||||
|
||||
m_local_action.AddElementVector(data.density_dofs, m_element_action);
|
||||
}
|
||||
|
||||
local_to_true(*m_fem.densityFes, m_local_action, m_action_true);
|
||||
action.SetSize(Width());
|
||||
m_density_map.gather(m_action_true, action);
|
||||
}
|
||||
@@ -639,6 +663,10 @@ namespace mean_field::operators {
|
||||
return m_is_prepared;
|
||||
}
|
||||
|
||||
bool PreparedMappedGravitySourceOperator::HasVariationData() const noexcept {
|
||||
return m_has_variation_data;
|
||||
}
|
||||
|
||||
std::uint64_t PreparedMappedGravitySourceOperator::GetPreparationCount() const noexcept {
|
||||
return m_preparation_count;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module;
|
||||
#include "profile.h"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -19,6 +20,13 @@ namespace {
|
||||
.reduced_size();
|
||||
}
|
||||
|
||||
bool communicator_has_single_rank(const MPI_Comm communicator) {
|
||||
int size = 0;
|
||||
MFEM_VERIFY(MPI_Comm_size(communicator, &size) == MPI_SUCCESS, "Failed to query the MPI communicator size.");
|
||||
MFEM_VERIFY(size > 0, "The MPI communicator must contain at least one rank.");
|
||||
return size == 1;
|
||||
}
|
||||
|
||||
void true_to_local(
|
||||
const mfem::ParFiniteElementSpace &finite_element_space,
|
||||
const mfem::Vector &true_vector,
|
||||
@@ -361,7 +369,8 @@ namespace mean_field::operators {
|
||||
field::Displacement,
|
||||
DomainSchema>(*f.displacementFes)
|
||||
),
|
||||
m_variationWorkspace(domain_mapper.GetDimension()) {
|
||||
m_variationWorkspace(domain_mapper.GetDimension()),
|
||||
m_single_rank(communicator_has_single_rank(f.gravityFluxFes->GetComm())) {
|
||||
MFEM_VERIFY(f.mesh != nullptr, "PreparedMappedHDivMassOperator requires a mesh.");
|
||||
MFEM_VERIFY(
|
||||
f.gravityFluxFes != nullptr, "PreparedMappedHDivMassOperator requires the "
|
||||
@@ -409,6 +418,8 @@ namespace mean_field::operators {
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::PrepareVariationData() {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::PrepareVariationData", 0);
|
||||
|
||||
m_variationElements.clear();
|
||||
m_variationElements.reserve(m_fem.mesh->GetNE());
|
||||
|
||||
@@ -481,6 +492,19 @@ namespace mean_field::operators {
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::Prepare(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare linearization", 0);
|
||||
PrepareImpl(displacement, PreparationMode::linearization);
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::PreparePrimal(const mfem::Vector &displacement) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("PreparedMappedHDivMassOperator::Prepare primal", 0);
|
||||
PrepareImpl(displacement, PreparationMode::primal);
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::PrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
const PreparationMode mode
|
||||
) {
|
||||
MFEM_VERIFY(
|
||||
displacement.Size() == m_displacement_map.reduced_size(),
|
||||
"PreparedMappedHDivMassOperator received a displacement vector "
|
||||
@@ -496,6 +520,9 @@ namespace mean_field::operators {
|
||||
);
|
||||
}
|
||||
|
||||
m_is_prepared = false;
|
||||
m_has_variation_data = false;
|
||||
|
||||
m_displacement_true.SetSize(m_displacement_map.full_size());
|
||||
m_displacement_map.scatter(displacement, m_displacement_true);
|
||||
|
||||
@@ -541,7 +568,12 @@ namespace mean_field::operators {
|
||||
m_stellar_mass_form->Assemble();
|
||||
m_vacuum_mass_form->Assemble();
|
||||
|
||||
PrepareVariationData();
|
||||
if (mode == PreparationMode::linearization) {
|
||||
PrepareVariationData();
|
||||
m_has_variation_data = true;
|
||||
} else {
|
||||
m_variationElements.clear();
|
||||
}
|
||||
|
||||
m_is_prepared = true;
|
||||
++m_preparation_count;
|
||||
@@ -551,6 +583,8 @@ namespace mean_field::operators {
|
||||
const mfem::Vector &gravity_gradient,
|
||||
mfem::Vector &action
|
||||
) const {
|
||||
MEAN_FIELD_PROFILE_SCOPE("PreparedMappedHDivMassOperator::Mult");
|
||||
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared "
|
||||
"before Mult is called."
|
||||
@@ -564,15 +598,39 @@ namespace mean_field::operators {
|
||||
"with the wrong size."
|
||||
);
|
||||
|
||||
m_flux_true.SetSize(m_flux_map.full_size());
|
||||
m_action_true.SetSize(m_flux_map.full_size());
|
||||
m_domain_action_true.SetSize(m_flux_map.full_size());
|
||||
m_flux_map.scatter(gravity_gradient, m_flux_true);
|
||||
m_stellar_mass_form->Mult(m_flux_true, m_action_true);
|
||||
m_vacuum_mass_form->Mult(m_flux_true, m_domain_action_true);
|
||||
m_action_true += m_domain_action_true;
|
||||
action.SetSize(Height());
|
||||
m_flux_map.gather(m_action_true, action);
|
||||
const mfem::Vector *gravity_gradient_true = &gravity_gradient;
|
||||
if (!m_flux_map.is_identity()) [[unlikely]] {
|
||||
m_flux_true.SetSize(m_flux_map.full_size());
|
||||
m_flux_map.scatter(gravity_gradient, m_flux_true);
|
||||
gravity_gradient_true = &m_flux_true;
|
||||
}
|
||||
|
||||
mfem::Vector *action_true = &action;
|
||||
if (!m_flux_map.is_identity()) [[unlikely]] {
|
||||
m_action_true.SetSize(m_flux_map.full_size());
|
||||
action_true = &m_action_true;
|
||||
}
|
||||
|
||||
if (m_single_rank) [[likely]] {
|
||||
action_true->SetSize(m_flux_map.full_size());
|
||||
m_domain_action_true.SetSize(m_flux_map.full_size());
|
||||
m_stellar_mass_form->Mult(*gravity_gradient_true, *action_true);
|
||||
m_vacuum_mass_form->Mult(*gravity_gradient_true, m_domain_action_true);
|
||||
*action_true += m_domain_action_true;
|
||||
} else {
|
||||
true_to_local(*m_fem.gravityFluxFes, *gravity_gradient_true, m_flux_local);
|
||||
m_action_local.SetSize(m_fem.gravityFluxFes->GetVSize());
|
||||
m_domain_action_local.SetSize(m_fem.gravityFluxFes->GetVSize());
|
||||
m_stellar_mass_form->Mult(m_flux_local, m_action_local);
|
||||
m_vacuum_mass_form->Mult(m_flux_local, m_domain_action_local);
|
||||
m_action_local += m_domain_action_local;
|
||||
local_to_true(*m_fem.gravityFluxFes, m_action_local, *action_true);
|
||||
}
|
||||
|
||||
if (!m_flux_map.is_identity()) [[unlikely]] {
|
||||
action.SetSize(Height());
|
||||
m_flux_map.gather(m_action_true, action);
|
||||
}
|
||||
}
|
||||
|
||||
void PreparedMappedHDivMassOperator::MultDisplacementVariationTrue(
|
||||
@@ -583,6 +641,11 @@ namespace mean_field::operators {
|
||||
MFEM_VERIFY(
|
||||
m_is_prepared, "PreparedMappedHDivMassOperator must be prepared before applying a displacement variation."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
m_has_variation_data,
|
||||
"PreparedMappedHDivMassOperator requires linearization preparation before applying a displacement "
|
||||
"variation."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
gravityGradientTrue.Size() == m_fem.gravityFluxFes->GetTrueVSize(),
|
||||
"The full gravity-gradient vector has the wrong size."
|
||||
@@ -707,6 +770,10 @@ namespace mean_field::operators {
|
||||
return m_is_prepared;
|
||||
}
|
||||
|
||||
bool PreparedMappedHDivMassOperator::HasVariationData() const noexcept {
|
||||
return m_has_variation_data;
|
||||
}
|
||||
|
||||
std::uint64_t PreparedMappedHDivMassOperator::GetPreparationCount() const noexcept {
|
||||
return m_preparation_count;
|
||||
}
|
||||
|
||||
@@ -787,6 +787,36 @@ namespace mean_field::operators {
|
||||
++m_algebraicJacobianStatistics.enthalpyApplications;
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::AssembleEnthalpyJacobianDiagonal(mfem::Vector &diagonal) const {
|
||||
VerifyPrepared();
|
||||
|
||||
mfem::Vector localDiagonal(m_fem.enthalpyFes->GetVSize());
|
||||
localDiagonal = 0.0;
|
||||
mfem::Vector elementDiagonal;
|
||||
|
||||
for (const ElementPAData &data : m_elements) {
|
||||
MFEM_VERIFY(
|
||||
data.enthalpyDofTransformation == nullptr,
|
||||
"Enthalpy mass-diagonal assembly currently requires scalar H1 element DOFs without a DOF transform."
|
||||
);
|
||||
MFEM_VERIFY(
|
||||
data.enthalpyJacobian.Height() == data.enthalpyDofs.Size() &&
|
||||
data.enthalpyJacobian.Width() == data.enthalpyDofs.Size(),
|
||||
"The prepared enthalpy Jacobian block is not square on an element."
|
||||
);
|
||||
elementDiagonal.SetSize(data.enthalpyDofs.Size());
|
||||
for (int dof = 0; dof < data.enthalpyDofs.Size(); ++dof) {
|
||||
elementDiagonal(dof) = data.enthalpyJacobian(dof, dof);
|
||||
}
|
||||
localDiagonal.AddElementVector(data.enthalpyDofs, elementDiagonal);
|
||||
}
|
||||
|
||||
mfem::Vector trueDiagonal;
|
||||
local_to_true(*m_fem.enthalpyFes, localDiagonal, trueDiagonal);
|
||||
diagonal.SetSize(m_context.GetEnthalpyMap().reduced_size());
|
||||
m_context.GetEnthalpyMap().gather(trueDiagonal, diagonal);
|
||||
}
|
||||
|
||||
void PreparedHydrostaticEquilibriumOperator::ApplyGravityPotentialJacobianAction(
|
||||
const mfem::Vector &gravityPotentialVariation,
|
||||
mfem::Vector &action
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module;
|
||||
#include "mfem.hpp"
|
||||
#include "profile.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
@@ -11,6 +12,8 @@ namespace mean_field::physics {
|
||||
const mfem::GridFunction &rho,
|
||||
const mfem::Vector &com
|
||||
) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("analysis::quadrupole", 0);
|
||||
|
||||
const int dim = fem.mesh->Dimension();
|
||||
mfem::DenseMatrix local_Q(dim, dim);
|
||||
local_Q = 0.0;
|
||||
@@ -18,6 +21,9 @@ namespace mean_field::physics {
|
||||
mapping::GridFunctionMappingEvaluator mapping_evaluator(
|
||||
*fem.domainMapperStateless, *fem.displacement, *fem.compactificationCoordinate
|
||||
);
|
||||
std::uint64_t mapping_evaluations = 0;
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
mfem::Vector x_prime(dim);
|
||||
|
||||
for (int i = 0; i < fem.mesh->GetNE(); ++i) {
|
||||
if (!DomainSchema::template attribute_belongs_to<utils::domain::Stellar>(fem.mesh->GetAttribute(i)))
|
||||
@@ -36,19 +42,18 @@ namespace mean_field::physics {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
|
||||
trans->SetIntPoint(&ip);
|
||||
|
||||
mapping::VolumeMappingContext mapping_context;
|
||||
MFEM_VERIFY(
|
||||
mapping_evaluator.EvaluateVolume(*trans, ip, mapping_context) == mapping::MappingStatus::valid,
|
||||
"Quadrupole integration encountered an invalid mapping."
|
||||
);
|
||||
++mapping_evaluations;
|
||||
const double weight = mapping_context.quadrature.weight;
|
||||
|
||||
const double rho_val = rho.GetValue(i, ip);
|
||||
|
||||
const mfem::Vector &phys_point = mapping_context.mapping.physical_position;
|
||||
|
||||
mfem::Vector x_prime(dim);
|
||||
double r_sq = 0.0;
|
||||
double r_sq = 0.0;
|
||||
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
x_prime(d) = phys_point(d) - com(d);
|
||||
@@ -65,6 +70,8 @@ namespace mean_field::physics {
|
||||
}
|
||||
}
|
||||
|
||||
MEAN_FIELD_PROFILE_COUNT("analysis::quadrupole mapping evaluations", mapping_evaluations);
|
||||
|
||||
mfem::DenseMatrix global_Q(dim, dim);
|
||||
MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.mesh->GetComm());
|
||||
|
||||
@@ -106,6 +113,8 @@ namespace mean_field::physics {
|
||||
const mfem::GridFunction &rho,
|
||||
const mfem::GridFunction &displacement
|
||||
) {
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP("physics::solve_gravity_field", 0);
|
||||
|
||||
MFEM_VERIFY(f.mesh != nullptr, "Gravity initialization requires a parallel mesh.");
|
||||
MFEM_VERIFY(f.densityFes != nullptr, "Gravity initialization requires the density finite-element space.");
|
||||
MFEM_VERIFY(
|
||||
@@ -150,15 +159,23 @@ namespace mean_field::physics {
|
||||
constexpr auto gravity_poisson_residual_block =
|
||||
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.poisson_term);
|
||||
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
const field::FieldDofGridFunctionAdapter density_adapter =
|
||||
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes);
|
||||
const field::FieldDofGridFunctionAdapter displacement_adapter =
|
||||
field::make_field_dof_grid_function_adapter<field::Displacement, DomainSchema>(*f.displacementFes);
|
||||
const field::FieldDofGridFunctionAdapter gravity_flux_adapter =
|
||||
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityFluxFes);
|
||||
const field::FieldDofGridFunctionAdapter gravity_potential_adapter =
|
||||
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityPotentialFes);
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
const field::FieldDofGridFunctionAdapter density_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: density map", 0,
|
||||
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*f.densityFes)
|
||||
);
|
||||
const field::FieldDofGridFunctionAdapter displacement_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: displacement map", 0,
|
||||
field::make_field_dof_grid_function_adapter<field::Displacement, DomainSchema>(*f.displacementFes)
|
||||
);
|
||||
const field::FieldDofGridFunctionAdapter gravity_flux_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: flux map", 0,
|
||||
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityFluxFes)
|
||||
);
|
||||
const field::FieldDofGridFunctionAdapter gravity_potential_adapter = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: potential map", 0,
|
||||
field::make_field_dof_grid_function_adapter<field::Gravity, DomainSchema>(*f.gravityPotentialFes)
|
||||
);
|
||||
|
||||
const field::FieldDofMap &density_map = density_adapter.dof_map();
|
||||
const field::FieldDofMap &displacement_map = displacement_adapter.dof_map();
|
||||
@@ -174,34 +191,56 @@ namespace mean_field::physics {
|
||||
gravity_flux_map.reduced_size(), gravity_potential_map.reduced_size()
|
||||
};
|
||||
|
||||
const utils::blocks::form_layout<form> layout(value_sizes, residual_sizes);
|
||||
|
||||
const mfem::Vector density = density_adapter.gather(rho);
|
||||
const mfem::Vector reduced_displacement = displacement_adapter.gather(displacement);
|
||||
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context(
|
||||
f, *f.domainMapperStateless
|
||||
const utils::blocks::form_layout<form> layout = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: block layout", 0, utils::blocks::form_layout<form>(value_sizes, residual_sizes)
|
||||
);
|
||||
|
||||
operators::GravityFieldJacobianOperator gravity_jacobian(
|
||||
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
||||
const mfem::Vector density =
|
||||
MEAN_FIELD_PROFILE_EVALUATE_WARMUP("gravity solve: gather density", 0, density_adapter.gather(rho));
|
||||
const mfem::Vector reduced_displacement = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: gather displacement", 0, displacement_adapter.gather(displacement)
|
||||
);
|
||||
|
||||
operators::GravityFieldOperator gravity_operator(
|
||||
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext linearization_context =
|
||||
MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: linearization context", 0,
|
||||
operators::context::gravity_field::GravityFieldLinearizationContext(f, *f.domainMapperStateless)
|
||||
);
|
||||
|
||||
operators::GravityFieldJacobianOperator gravity_jacobian = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: jacobian operator", 0,
|
||||
operators::GravityFieldJacobianOperator(
|
||||
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), layout.residual_offsets()
|
||||
)
|
||||
);
|
||||
|
||||
operators::context::gravity_field::GravityFieldGeometryContext reduced_geometry_context(
|
||||
f, *f.domainMapperStateless
|
||||
operators::GravityFieldOperator gravity_operator = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: nonlinear operator", 0,
|
||||
operators::GravityFieldOperator(
|
||||
f, *f.domainMapperStateless, linearization_context, layout.value_offsets(), gravity_jacobian
|
||||
)
|
||||
);
|
||||
|
||||
operators::ReducedGravityFieldOperator reduced_operator(
|
||||
gravity_operator, reduced_geometry_context, reduced_displacement
|
||||
operators::context::gravity_field::GravityFieldGeometryContext reduced_geometry_context =
|
||||
MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: reduced geometry context", 0,
|
||||
operators::context::gravity_field::GravityFieldGeometryContext(f, *f.domainMapperStateless)
|
||||
);
|
||||
|
||||
operators::ReducedGravityFieldOperator reduced_operator = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: reduced operator", 0,
|
||||
operators::ReducedGravityFieldOperator(gravity_operator, reduced_geometry_context, reduced_displacement)
|
||||
);
|
||||
|
||||
operators::ReducedGravityFieldPreconditioner reduced_preconditioner = MEAN_FIELD_PROFILE_EVALUATE_WARMUP(
|
||||
"gravity solve: preconditioner construction", 0,
|
||||
operators::ReducedGravityFieldPreconditioner(f, reduced_geometry_context)
|
||||
);
|
||||
operators::ReducedGravityFieldPreconditioner reduced_preconditioner(f, reduced_geometry_context);
|
||||
|
||||
mfem::Vector right_hand_side;
|
||||
reduced_operator.BuildRightHandSide(density, right_hand_side);
|
||||
MEAN_FIELD_PROFILE_CALL_WARMUP(
|
||||
"gravity solve: right-hand side", 0, reduced_operator.BuildRightHandSide(density, right_hand_side)
|
||||
);
|
||||
|
||||
MFEM_VERIFY(
|
||||
right_hand_side.Size() == reduced_operator.Height(),
|
||||
@@ -219,14 +258,18 @@ namespace mean_field::physics {
|
||||
minres.SetMaxIter(options.maximumIterations);
|
||||
// minres.SetPrintLevel(args.verbose ? 1 : 0);
|
||||
minres.SetPrintLevel(0);
|
||||
minres.Mult(right_hand_side, gravity_state);
|
||||
MEAN_FIELD_PROFILE_CALL_WARMUP("gravity solve: MINRES", 0, minres.Mult(right_hand_side, gravity_state));
|
||||
MEAN_FIELD_PROFILE_COUNT("gravity solve: MINRES iterations", minres.GetNumIterations());
|
||||
|
||||
MFEM_VERIFY(minres.GetConverged(), "The reduced gravity solve failed to converge.");
|
||||
|
||||
GravitySolution solution(f);
|
||||
|
||||
gravity_flux_adapter.scatter(gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi);
|
||||
gravity_potential_adapter.scatter(gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi);
|
||||
MEAN_FIELD_PROFILE_CALL_WARMUP(
|
||||
"gravity solve: scatter solution", 0,
|
||||
gravity_flux_adapter.scatter(gravity_state.GetBlock(gravity_gradient_residual_block), solution.gradPhi);
|
||||
gravity_potential_adapter.scatter(gravity_state.GetBlock(gravity_poisson_residual_block), solution.phi)
|
||||
);
|
||||
|
||||
return solution;
|
||||
}
|
||||
|
||||
77
libmeanfield/impl/preconditioning/gravity_field.cpp
Normal file
77
libmeanfield/impl/preconditioning/gravity_field.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <mfem.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
module mean_field;
|
||||
|
||||
import :preconditioning.gravity_field;
|
||||
|
||||
namespace mean_field::preconditioning {
|
||||
std::unique_ptr<mfem::HypreParMatrix> assembleGravityDivergenceSurrogate(const fem::FEM &f) {
|
||||
if (f.mesh == nullptr || f.gravityFluxFes == nullptr || f.gravityPotentialFes == nullptr ||
|
||||
f.quadratureFactory == nullptr) {
|
||||
throw std::invalid_argument(
|
||||
"The gravity divergence surrogate requires its mesh, gravity spaces, and quadrature policy."
|
||||
);
|
||||
}
|
||||
|
||||
mfem::ParMixedBilinearForm divergence(f.gravityFluxFes.get(), f.gravityPotentialFes.get());
|
||||
auto integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
|
||||
|
||||
const mfem::FiniteElement &trialElement = *f.gravityFluxFes->GetTypicalFE();
|
||||
const mfem::FiniteElement &testElement = *f.gravityPotentialFes->GetTypicalFE();
|
||||
const mfem::ElementTransformation &transformation = *f.mesh->GetElementTransformation(0);
|
||||
|
||||
f.quadratureFactory->configure_gravity_divergence(
|
||||
*integrator, quadrature::QuadratureRole::preconditioner, trialElement, testElement, transformation,
|
||||
utils::DOMAINS::ALL, quadrature::MappingKind::none
|
||||
);
|
||||
|
||||
divergence.AddDomainIntegrator(integrator.release());
|
||||
divergence.Assemble();
|
||||
divergence.Finalize();
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> assembled(divergence.ParallelAssemble());
|
||||
if (assembled == nullptr) {
|
||||
throw std::runtime_error("MFEM did not assemble the gravity divergence surrogate.");
|
||||
}
|
||||
return assembled;
|
||||
}
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> assembleGravityPotentialSchurSurrogate(
|
||||
const fem::FEM &f,
|
||||
const mfem::Vector &trueMassDiagonal
|
||||
) {
|
||||
if (f.gravityFluxFes == nullptr || trueMassDiagonal.Size() != f.gravityFluxFes->GetTrueVSize()) {
|
||||
throw std::invalid_argument(
|
||||
"The gravity Schur surrogate requires one mass-diagonal entry per true gravity-gradient DOF."
|
||||
);
|
||||
}
|
||||
|
||||
mfem::Vector inverseMassDiagonal(trueMassDiagonal);
|
||||
for (int index = 0; index < inverseMassDiagonal.Size(); ++index) {
|
||||
const double entry = inverseMassDiagonal(index);
|
||||
if (!std::isfinite(entry) || entry <= 0.0) {
|
||||
throw std::invalid_argument(
|
||||
"The gravity Schur surrogate encountered a non-positive or non-finite mass diagonal."
|
||||
);
|
||||
}
|
||||
inverseMassDiagonal(index) = 1.0 / entry;
|
||||
}
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> divergence = assembleGravityDivergenceSurrogate(f);
|
||||
std::unique_ptr<mfem::HypreParMatrix> inverseMassDivergenceTranspose(divergence->Transpose());
|
||||
inverseMassDivergenceTranspose->ScaleRows(inverseMassDiagonal);
|
||||
|
||||
std::unique_ptr<mfem::HypreParMatrix> schur(
|
||||
mfem::ParMult(divergence.get(), inverseMassDivergenceTranspose.get())
|
||||
);
|
||||
if (schur == nullptr) {
|
||||
throw std::runtime_error("MFEM did not assemble the gravity potential-Schur surrogate.");
|
||||
}
|
||||
return schur;
|
||||
}
|
||||
} // namespace mean_field::preconditioning
|
||||
530
libmeanfield/impl/profile.cpp
Normal file
530
libmeanfield/impl/profile.cpp
Normal file
@@ -0,0 +1,530 @@
|
||||
#include "profile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
struct MpiContext {
|
||||
bool active{false};
|
||||
int rank{0};
|
||||
int size{1};
|
||||
};
|
||||
|
||||
void check_mpi(
|
||||
const int result,
|
||||
const std::string_view operation
|
||||
) {
|
||||
if (result == MPI_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<char, MPI_MAX_ERROR_STRING> buffer{};
|
||||
int length = 0;
|
||||
MPI_Error_string(result, buffer.data(), &length);
|
||||
|
||||
throw std::runtime_error(
|
||||
"MPI profiling operation '" + std::string(operation) +
|
||||
"' failed: " + std::string(buffer.data(), static_cast<std::size_t>(length))
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] MpiContext get_mpi_context(const MPI_Comm communicator) {
|
||||
int initialized = 0;
|
||||
check_mpi(MPI_Initialized(&initialized), "MPI_Initialized");
|
||||
|
||||
if (initialized == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int finalized = 0;
|
||||
check_mpi(MPI_Finalized(&finalized), "MPI_Finalized");
|
||||
|
||||
if (finalized != 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (communicator == MPI_COMM_NULL) {
|
||||
throw std::invalid_argument("Profiling aggregation requires a valid MPI communicator.");
|
||||
}
|
||||
|
||||
MpiContext context{.active = true};
|
||||
check_mpi(MPI_Comm_rank(communicator, &context.rank), "MPI_Comm_rank");
|
||||
check_mpi(MPI_Comm_size(communicator, &context.size), "MPI_Comm_size");
|
||||
return context;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string count_range(
|
||||
const std::uint64_t minimum,
|
||||
const std::uint64_t maximum
|
||||
) {
|
||||
if (minimum == maximum) {
|
||||
return std::to_string(minimum);
|
||||
}
|
||||
return std::to_string(minimum) + "-" + std::to_string(maximum);
|
||||
}
|
||||
|
||||
void write_csv_field(
|
||||
std::ostream &stream,
|
||||
const std::string_view field
|
||||
) {
|
||||
stream << '"';
|
||||
for (const char character : field) {
|
||||
if (character == '"') {
|
||||
stream << "\"\"";
|
||||
} else {
|
||||
stream << character;
|
||||
}
|
||||
}
|
||||
stream << '"';
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mean_field::profiling {
|
||||
struct Registry::Impl {
|
||||
struct Entry {
|
||||
std::string label;
|
||||
Statistics statistics;
|
||||
};
|
||||
|
||||
mutable std::mutex mutex;
|
||||
std::map<std::string, std::size_t, std::less<>> indices;
|
||||
std::vector<Entry> entries;
|
||||
};
|
||||
|
||||
Registry &Registry::Get() {
|
||||
static Registry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
Registry::Registry() : m_impl(std::make_unique<Impl>()) {
|
||||
}
|
||||
|
||||
Registry::~Registry() = default;
|
||||
|
||||
std::size_t Registry::Register(
|
||||
const std::string_view label,
|
||||
const std::uint64_t warmup_count
|
||||
) {
|
||||
if (label.empty()) {
|
||||
throw std::invalid_argument("A profiling region label cannot be empty.");
|
||||
}
|
||||
if (label.find('\0') != std::string_view::npos) {
|
||||
throw std::invalid_argument("A profiling region label cannot contain a null byte.");
|
||||
}
|
||||
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
if (const auto iterator = m_impl->indices.find(label); iterator != m_impl->indices.end()) {
|
||||
Impl::Entry &entry = m_impl->entries[iterator->second];
|
||||
entry.statistics.warmup_target = std::max(entry.statistics.warmup_target, warmup_count);
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
const std::size_t index = m_impl->entries.size();
|
||||
Impl::Entry entry{.label = std::string(label)};
|
||||
entry.statistics.warmup_target = warmup_count;
|
||||
m_impl->entries.push_back(std::move(entry));
|
||||
m_impl->indices.emplace(m_impl->entries.back().label, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
void Registry::Record(
|
||||
const std::string_view label,
|
||||
const double seconds,
|
||||
const std::uint64_t warmup_count
|
||||
) {
|
||||
if (!std::isfinite(seconds) || seconds < 0.0) {
|
||||
throw std::invalid_argument("A profiling duration must be finite and nonnegative.");
|
||||
}
|
||||
|
||||
const std::size_t region = Register(label, warmup_count);
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
Statistics &statistics = m_impl->entries[region].statistics;
|
||||
const bool is_warmup = statistics.observations < statistics.warmup_target;
|
||||
++statistics.observations;
|
||||
|
||||
if (is_warmup) {
|
||||
++statistics.warmups;
|
||||
return;
|
||||
}
|
||||
|
||||
++statistics.samples;
|
||||
statistics.total_seconds += seconds;
|
||||
if (statistics.samples == 1) {
|
||||
statistics.minimum_seconds = seconds;
|
||||
statistics.maximum_seconds = seconds;
|
||||
} else {
|
||||
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
|
||||
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
void Registry::AddCount(
|
||||
const std::string_view label,
|
||||
const std::uint64_t work_units
|
||||
) {
|
||||
const std::size_t region = Register(label, 0);
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
Statistics &statistics = m_impl->entries[region].statistics;
|
||||
if (work_units > std::numeric_limits<std::uint64_t>::max() - statistics.work_units) {
|
||||
throw std::overflow_error("A profiling work counter overflowed.");
|
||||
}
|
||||
statistics.work_units += work_units;
|
||||
}
|
||||
|
||||
void Registry::Record(
|
||||
const std::size_t region,
|
||||
const double seconds
|
||||
) noexcept {
|
||||
if (!std::isfinite(seconds) || seconds < 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
if (region >= m_impl->entries.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Statistics &statistics = m_impl->entries[region].statistics;
|
||||
const bool is_warmup = statistics.observations < statistics.warmup_target;
|
||||
++statistics.observations;
|
||||
|
||||
if (is_warmup) {
|
||||
++statistics.warmups;
|
||||
return;
|
||||
}
|
||||
|
||||
++statistics.samples;
|
||||
statistics.total_seconds += seconds;
|
||||
if (statistics.samples == 1) {
|
||||
statistics.minimum_seconds = seconds;
|
||||
statistics.maximum_seconds = seconds;
|
||||
} else {
|
||||
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
|
||||
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void Registry::AddCount(
|
||||
const std::size_t region,
|
||||
const std::uint64_t work_units
|
||||
) noexcept {
|
||||
try {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
if (region >= m_impl->entries.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Statistics &statistics = m_impl->entries[region].statistics;
|
||||
if (work_units > std::numeric_limits<std::uint64_t>::max() - statistics.work_units) {
|
||||
statistics.work_units = std::numeric_limits<std::uint64_t>::max();
|
||||
} else {
|
||||
statistics.work_units += work_units;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void Registry::Reset() {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
for (Impl::Entry &entry : m_impl->entries) {
|
||||
const std::uint64_t warmup_target = entry.statistics.warmup_target;
|
||||
entry.statistics = {};
|
||||
entry.statistics.warmup_target = warmup_target;
|
||||
}
|
||||
}
|
||||
|
||||
std::map<
|
||||
std::string,
|
||||
Statistics,
|
||||
std::less<>>
|
||||
Registry::Snapshot() const {
|
||||
std::map<std::string, Statistics, std::less<>> snapshot;
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
for (const Impl::Entry &entry : m_impl->entries) {
|
||||
snapshot.emplace(entry.label, entry.statistics);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::vector<DistributedStatistics> Registry::Aggregate(const MPI_Comm communicator) const {
|
||||
const std::map<std::string, Statistics, std::less<>> local_snapshot = Snapshot();
|
||||
const MpiContext mpi_context = get_mpi_context(communicator);
|
||||
|
||||
std::vector<std::string> labels;
|
||||
if (!mpi_context.active) {
|
||||
labels.reserve(local_snapshot.size());
|
||||
for (const auto &[label, statistics] : local_snapshot) {
|
||||
(void)statistics;
|
||||
labels.push_back(label);
|
||||
}
|
||||
} else {
|
||||
std::string serialized_labels;
|
||||
for (const auto &[label, statistics] : local_snapshot) {
|
||||
(void)statistics;
|
||||
serialized_labels.append(label);
|
||||
serialized_labels.push_back('\0');
|
||||
}
|
||||
|
||||
if (serialized_labels.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
||||
throw std::overflow_error("The local profiling label table is too large for MPI_Allgatherv.");
|
||||
}
|
||||
|
||||
const int local_bytes = static_cast<int>(serialized_labels.size());
|
||||
std::vector<int> byte_counts(static_cast<std::size_t>(mpi_context.size));
|
||||
check_mpi(
|
||||
MPI_Allgather(&local_bytes, 1, MPI_INT, byte_counts.data(), 1, MPI_INT, communicator),
|
||||
"MPI_Allgather(profile label sizes)"
|
||||
);
|
||||
|
||||
std::vector<int> displacements(static_cast<std::size_t>(mpi_context.size));
|
||||
int total_bytes = 0;
|
||||
for (int rank = 0; rank < mpi_context.size; ++rank) {
|
||||
if (byte_counts[rank] < 0 || byte_counts[rank] > std::numeric_limits<int>::max() - total_bytes) {
|
||||
throw std::overflow_error("The distributed profiling label table is too large for MPI_Allgatherv.");
|
||||
}
|
||||
displacements[rank] = total_bytes;
|
||||
total_bytes += byte_counts[rank];
|
||||
}
|
||||
|
||||
std::vector<char> all_serialized_labels(static_cast<std::size_t>(total_bytes));
|
||||
check_mpi(
|
||||
MPI_Allgatherv(
|
||||
serialized_labels.data(), local_bytes, MPI_CHAR, all_serialized_labels.data(), byte_counts.data(),
|
||||
displacements.data(), MPI_CHAR, communicator
|
||||
),
|
||||
"MPI_Allgatherv(profile labels)"
|
||||
);
|
||||
|
||||
std::set<std::string, std::less<>> unique_labels;
|
||||
for (int rank = 0; rank < mpi_context.size; ++rank) {
|
||||
const char *position = all_serialized_labels.data() + displacements[rank];
|
||||
const char *end = position + byte_counts[rank];
|
||||
while (position != end) {
|
||||
const void *terminator_address =
|
||||
std::memchr(position, '\0', static_cast<std::size_t>(end - position));
|
||||
if (terminator_address == nullptr) {
|
||||
throw std::runtime_error("A distributed profiling label table is malformed.");
|
||||
}
|
||||
const auto *terminator = static_cast<const char *>(terminator_address);
|
||||
unique_labels.emplace(position, terminator);
|
||||
position = terminator + 1;
|
||||
}
|
||||
}
|
||||
labels.assign(unique_labels.begin(), unique_labels.end());
|
||||
}
|
||||
|
||||
std::vector<DistributedStatistics> aggregate(labels.size());
|
||||
if (labels.empty()) {
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> local_samples(labels.size(), 0);
|
||||
std::vector<std::uint64_t> local_warmups(labels.size(), 0);
|
||||
std::vector<std::uint64_t> local_work_units(labels.size(), 0);
|
||||
std::vector<double> local_averages(labels.size(), 0.0);
|
||||
std::vector<double> local_minima(labels.size(), std::numeric_limits<double>::infinity());
|
||||
std::vector<double> local_maxima(labels.size(), 0.0);
|
||||
std::vector<double> local_totals(labels.size(), 0.0);
|
||||
|
||||
for (std::size_t index = 0; index < labels.size(); ++index) {
|
||||
if (const auto iterator = local_snapshot.find(labels[index]); iterator != local_snapshot.end()) {
|
||||
const Statistics &statistics = iterator->second;
|
||||
local_samples[index] = statistics.samples;
|
||||
local_warmups[index] = statistics.warmups;
|
||||
local_work_units[index] = statistics.work_units;
|
||||
local_totals[index] = statistics.total_seconds;
|
||||
if (statistics.samples != 0) {
|
||||
local_averages[index] = statistics.total_seconds / static_cast<double>(statistics.samples);
|
||||
local_minima[index] = statistics.minimum_seconds;
|
||||
local_maxima[index] = statistics.maximum_seconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> minimum_samples = local_samples;
|
||||
std::vector<std::uint64_t> maximum_samples = local_samples;
|
||||
std::vector<std::uint64_t> maximum_warmups = local_warmups;
|
||||
std::vector<std::uint64_t> minimum_work_units = local_work_units;
|
||||
std::vector<std::uint64_t> maximum_work_units = local_work_units;
|
||||
std::vector<double> maximum_rank_averages = local_averages;
|
||||
std::vector<double> global_minima = local_minima;
|
||||
std::vector<double> global_maxima = local_maxima;
|
||||
std::vector<double> maximum_rank_totals = local_totals;
|
||||
|
||||
if (mpi_context.active) {
|
||||
if (labels.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
|
||||
throw std::overflow_error("There are too many profiling regions for one MPI reduction.");
|
||||
}
|
||||
const int count = static_cast<int>(labels.size());
|
||||
|
||||
check_mpi(
|
||||
MPI_Allreduce(local_samples.data(), minimum_samples.data(), count, MPI_UINT64_T, MPI_MIN, communicator),
|
||||
"MPI_Allreduce(minimum profile samples)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(local_samples.data(), maximum_samples.data(), count, MPI_UINT64_T, MPI_MAX, communicator),
|
||||
"MPI_Allreduce(maximum profile samples)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(local_warmups.data(), maximum_warmups.data(), count, MPI_UINT64_T, MPI_MAX, communicator),
|
||||
"MPI_Allreduce(profile warmups)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(
|
||||
local_work_units.data(), minimum_work_units.data(), count, MPI_UINT64_T, MPI_MIN, communicator
|
||||
),
|
||||
"MPI_Allreduce(minimum profile work)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(
|
||||
local_work_units.data(), maximum_work_units.data(), count, MPI_UINT64_T, MPI_MAX, communicator
|
||||
),
|
||||
"MPI_Allreduce(maximum profile work)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(
|
||||
local_averages.data(), maximum_rank_averages.data(), count, MPI_DOUBLE, MPI_MAX, communicator
|
||||
),
|
||||
"MPI_Allreduce(profile averages)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(local_minima.data(), global_minima.data(), count, MPI_DOUBLE, MPI_MIN, communicator),
|
||||
"MPI_Allreduce(profile minima)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(local_maxima.data(), global_maxima.data(), count, MPI_DOUBLE, MPI_MAX, communicator),
|
||||
"MPI_Allreduce(profile maxima)"
|
||||
);
|
||||
check_mpi(
|
||||
MPI_Allreduce(
|
||||
local_totals.data(), maximum_rank_totals.data(), count, MPI_DOUBLE, MPI_MAX, communicator
|
||||
),
|
||||
"MPI_Allreduce(profile totals)"
|
||||
);
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < labels.size(); ++index) {
|
||||
aggregate[index] = {
|
||||
.label = labels[index],
|
||||
.minimum_samples = minimum_samples[index],
|
||||
.maximum_samples = maximum_samples[index],
|
||||
.maximum_warmups = maximum_warmups[index],
|
||||
.minimum_work_units = minimum_work_units[index],
|
||||
.maximum_work_units = maximum_work_units[index],
|
||||
.maximum_rank_average_seconds = maximum_rank_averages[index],
|
||||
.global_minimum_seconds = std::isfinite(global_minima[index]) ? global_minima[index] : 0.0,
|
||||
.global_maximum_seconds = global_maxima[index],
|
||||
.maximum_rank_total_seconds = maximum_rank_totals[index]
|
||||
};
|
||||
}
|
||||
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
void Registry::Print(
|
||||
const MPI_Comm communicator,
|
||||
std::ostream &stream
|
||||
) const {
|
||||
const std::vector<DistributedStatistics> aggregate = Aggregate(communicator);
|
||||
const MpiContext mpi_context = get_mpi_context(communicator);
|
||||
if (mpi_context.rank != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::ios old_state(nullptr);
|
||||
old_state.copyfmt(stream);
|
||||
|
||||
stream << '\n';
|
||||
stream << std::left << std::setw(58) << "Profile Region" << std::right << std::setw(13) << "Samples"
|
||||
<< std::setw(11) << "Warmups" << std::setw(15) << "Work/rank" << std::setw(14) << "Avg max ms"
|
||||
<< std::setw(14) << "Min ms" << std::setw(14) << "Max ms" << std::setw(14) << "Total max s" << '\n';
|
||||
stream << std::string(153, '-') << '\n';
|
||||
|
||||
for (const DistributedStatistics &statistics : aggregate) {
|
||||
stream << std::left << std::setw(58) << statistics.label << std::right << std::setw(13)
|
||||
<< count_range(statistics.minimum_samples, statistics.maximum_samples) << std::setw(11)
|
||||
<< statistics.maximum_warmups << std::setw(15)
|
||||
<< count_range(statistics.minimum_work_units, statistics.maximum_work_units) << std::setw(14)
|
||||
<< std::fixed << std::setprecision(3) << 1.0e3 * statistics.maximum_rank_average_seconds
|
||||
<< std::setw(14) << 1.0e3 * statistics.global_minimum_seconds << std::setw(14)
|
||||
<< 1.0e3 * statistics.global_maximum_seconds << std::setw(14) << std::setprecision(6)
|
||||
<< statistics.maximum_rank_total_seconds << '\n';
|
||||
}
|
||||
|
||||
stream << std::string(153, '=') << '\n';
|
||||
stream << "MPI ranks: " << mpi_context.size << "\n\n";
|
||||
stream.copyfmt(old_state);
|
||||
}
|
||||
|
||||
void Registry::Print(const MPI_Comm communicator) const {
|
||||
Print(communicator, std::cout);
|
||||
}
|
||||
|
||||
void Registry::PrintCsv(
|
||||
const MPI_Comm communicator,
|
||||
std::ostream &stream
|
||||
) const {
|
||||
const std::vector<DistributedStatistics> aggregate = Aggregate(communicator);
|
||||
const MpiContext mpi_context = get_mpi_context(communicator);
|
||||
if (mpi_context.rank != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stream << "label,minimum_samples,maximum_samples,maximum_warmups,minimum_work_units,maximum_work_units,"
|
||||
"maximum_rank_average_seconds,global_minimum_seconds,global_maximum_seconds,"
|
||||
"maximum_rank_total_seconds,mpi_ranks\n";
|
||||
|
||||
for (const DistributedStatistics &statistics : aggregate) {
|
||||
write_csv_field(stream, statistics.label);
|
||||
stream << ',' << statistics.minimum_samples << ',' << statistics.maximum_samples << ','
|
||||
<< statistics.maximum_warmups << ',' << statistics.minimum_work_units << ','
|
||||
<< statistics.maximum_work_units << ',' << std::setprecision(17)
|
||||
<< statistics.maximum_rank_average_seconds << ',' << statistics.global_minimum_seconds << ','
|
||||
<< statistics.global_maximum_seconds << ',' << statistics.maximum_rank_total_seconds << ','
|
||||
<< mpi_context.size << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
Region::Region(
|
||||
const std::string_view label,
|
||||
const std::uint64_t warmup_count
|
||||
)
|
||||
: m_region(
|
||||
Registry::Get().Register(
|
||||
label,
|
||||
warmup_count
|
||||
)
|
||||
) {
|
||||
}
|
||||
|
||||
void Region::Record(const double seconds) const noexcept {
|
||||
Registry::Get().Record(m_region, seconds);
|
||||
}
|
||||
|
||||
void Region::AddCount(const std::uint64_t work_units) const noexcept {
|
||||
Registry::Get().AddCount(m_region, work_units);
|
||||
}
|
||||
|
||||
ScopedTimer::ScopedTimer(const Region ®ion) noexcept
|
||||
: m_region(region),
|
||||
m_start(std::chrono::steady_clock::now()) {
|
||||
}
|
||||
|
||||
ScopedTimer::~ScopedTimer() noexcept {
|
||||
const auto stop = std::chrono::steady_clock::now();
|
||||
m_region.Record(std::chrono::duration<double>(stop - m_start).count());
|
||||
}
|
||||
} // namespace mean_field::profiling
|
||||
@@ -1,194 +1,216 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <mpi.h>
|
||||
|
||||
#ifndef MEAN_FIELD_ENABLE_PROFILING
|
||||
#define MEAN_FIELD_ENABLE_PROFILING 0
|
||||
#endif
|
||||
|
||||
namespace mean_field::profiling {
|
||||
struct Statistics {
|
||||
unsigned long long observations{0};
|
||||
unsigned long long warmups{0};
|
||||
unsigned long long samples{0};
|
||||
unsigned long long warmup_target{0};
|
||||
std::uint64_t observations{0};
|
||||
std::uint64_t warmups{0};
|
||||
std::uint64_t samples{0};
|
||||
std::uint64_t warmup_target{0};
|
||||
std::uint64_t work_units{0};
|
||||
double total_seconds{0.0};
|
||||
double minimum_seconds{std::numeric_limits<double>::infinity()};
|
||||
double minimum_seconds{0.0};
|
||||
double maximum_seconds{0.0};
|
||||
};
|
||||
|
||||
struct DistributedStatistics {
|
||||
std::string label;
|
||||
std::uint64_t minimum_samples{0};
|
||||
std::uint64_t maximum_samples{0};
|
||||
std::uint64_t maximum_warmups{0};
|
||||
std::uint64_t minimum_work_units{0};
|
||||
std::uint64_t maximum_work_units{0};
|
||||
double maximum_rank_average_seconds{0.0};
|
||||
double global_minimum_seconds{0.0};
|
||||
double global_maximum_seconds{0.0};
|
||||
double maximum_rank_total_seconds{0.0};
|
||||
};
|
||||
|
||||
class Registry {
|
||||
public:
|
||||
static Registry& Get() {
|
||||
static Registry registry;
|
||||
return registry;
|
||||
}
|
||||
static Registry &Get();
|
||||
|
||||
void Record(const std::string& label, const double seconds, const unsigned long long warmup_count) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
Statistics& statistics = m_statistics[label];
|
||||
Registry(const Registry &) = delete;
|
||||
Registry &operator=(const Registry &) = delete;
|
||||
Registry(Registry &&) = delete;
|
||||
Registry &operator=(Registry &&) = delete;
|
||||
|
||||
statistics.warmup_target = std::max(statistics.warmup_target, warmup_count);
|
||||
const bool is_warmup = statistics.observations < statistics.warmup_target;
|
||||
++statistics.observations;
|
||||
~Registry();
|
||||
|
||||
if (is_warmup) {
|
||||
++statistics.warmups;
|
||||
return;
|
||||
}
|
||||
void Record(
|
||||
std::string_view label,
|
||||
double seconds,
|
||||
std::uint64_t warmup_count = 0
|
||||
);
|
||||
|
||||
++statistics.samples;
|
||||
statistics.total_seconds += seconds;
|
||||
statistics.minimum_seconds = std::min(statistics.minimum_seconds, seconds);
|
||||
statistics.maximum_seconds = std::max(statistics.maximum_seconds, seconds);
|
||||
}
|
||||
void AddCount(
|
||||
std::string_view label,
|
||||
std::uint64_t work_units
|
||||
);
|
||||
|
||||
void Reset() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_statistics.clear();
|
||||
}
|
||||
void Reset();
|
||||
|
||||
void Print(MPI_Comm communicator) const {
|
||||
const std::map<std::string, Statistics> snapshot = GetSnapshot();
|
||||
[[nodiscard]] std::map<
|
||||
std::string,
|
||||
Statistics,
|
||||
std::less<>>
|
||||
Snapshot() const;
|
||||
|
||||
int mpi_initialized = 0;
|
||||
int mpi_finalized = 0;
|
||||
MPI_Initialized(&mpi_initialized);
|
||||
if (mpi_initialized) MPI_Finalized(&mpi_finalized);
|
||||
[[nodiscard]] std::vector<DistributedStatistics> Aggregate(MPI_Comm communicator) const;
|
||||
|
||||
const bool use_mpi = mpi_initialized && !mpi_finalized;
|
||||
int rank = 0;
|
||||
int communicator_size = 1;
|
||||
void Print(
|
||||
MPI_Comm communicator,
|
||||
std::ostream &stream
|
||||
) const;
|
||||
|
||||
if (use_mpi) {
|
||||
MPI_Comm_rank(communicator, &rank);
|
||||
MPI_Comm_size(communicator, &communicator_size);
|
||||
}
|
||||
void Print(MPI_Comm communicator) const;
|
||||
|
||||
if (rank == 0) {
|
||||
std::cout << '\n';
|
||||
std::cout << std::left << std::setw(42) << "Profile Region"
|
||||
<< std::right << std::setw(11) << "Samples"
|
||||
<< std::setw(10) << "Warmups"
|
||||
<< std::setw(14) << "Avg Max ms"
|
||||
<< std::setw(14) << "Min ms"
|
||||
<< std::setw(14) << "Max ms"
|
||||
<< std::setw(14) << "Total Max s" << '\n';
|
||||
std::cout << std::string(119, '-') << '\n';
|
||||
}
|
||||
|
||||
for (const auto& [label, local_statistics] : snapshot) {
|
||||
unsigned long long minimum_samples = local_statistics.samples;
|
||||
unsigned long long maximum_samples = local_statistics.samples;
|
||||
unsigned long long maximum_warmups = local_statistics.warmups;
|
||||
|
||||
double local_average = local_statistics.samples > 0 ? local_statistics.total_seconds / static_cast<double>(local_statistics.samples) : 0.0;
|
||||
double local_minimum = local_statistics.samples > 0 ? local_statistics.minimum_seconds : std::numeric_limits<double>::infinity();
|
||||
double local_maximum = local_statistics.maximum_seconds;
|
||||
double local_total = local_statistics.total_seconds;
|
||||
|
||||
double maximum_rank_average = local_average;
|
||||
double global_minimum = local_minimum;
|
||||
double global_maximum = local_maximum;
|
||||
double maximum_rank_total = local_total;
|
||||
|
||||
if (use_mpi) {
|
||||
MPI_Allreduce(&local_statistics.samples, &minimum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MIN, communicator);
|
||||
MPI_Allreduce(&local_statistics.samples, &maximum_samples, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_statistics.warmups, &maximum_warmups, 1, MPI_UNSIGNED_LONG_LONG, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_average, &maximum_rank_average, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_minimum, &global_minimum, 1, MPI_DOUBLE, MPI_MIN, communicator);
|
||||
MPI_Allreduce(&local_maximum, &global_maximum, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
MPI_Allreduce(&local_total, &maximum_rank_total, 1, MPI_DOUBLE, MPI_MAX, communicator);
|
||||
}
|
||||
|
||||
if (!std::isfinite(global_minimum)) global_minimum = 0.0;
|
||||
|
||||
if (rank == 0) {
|
||||
const std::string sample_string = minimum_samples == maximum_samples
|
||||
? std::to_string(minimum_samples)
|
||||
: std::to_string(minimum_samples) + "-" + std::to_string(maximum_samples);
|
||||
|
||||
std::cout << std::left << std::setw(100) << label
|
||||
<< std::right << std::setw(11) << sample_string
|
||||
<< std::setw(10) << maximum_warmups
|
||||
<< std::setw(14) << std::fixed << std::setprecision(3) << 1.0e3 * maximum_rank_average
|
||||
<< std::setw(14) << 1.0e3 * global_minimum
|
||||
<< std::setw(14) << 1.0e3 * global_maximum
|
||||
<< std::setw(14) << std::setprecision(6) << maximum_rank_total << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (rank == 0) {
|
||||
std::cout << std::string(119, '=') << '\n';
|
||||
std::cout << "MPI ranks: " << communicator_size << "\n\n";
|
||||
}
|
||||
}
|
||||
void PrintCsv(
|
||||
MPI_Comm communicator,
|
||||
std::ostream &stream
|
||||
) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::map<std::string, Statistics> GetSnapshot() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_statistics;
|
||||
}
|
||||
friend class Region;
|
||||
|
||||
Registry();
|
||||
|
||||
[[nodiscard]] std::size_t Register(
|
||||
std::string_view label,
|
||||
std::uint64_t warmup_count
|
||||
);
|
||||
|
||||
void Record(
|
||||
std::size_t region,
|
||||
double seconds
|
||||
) noexcept;
|
||||
|
||||
void AddCount(
|
||||
std::size_t region,
|
||||
std::uint64_t work_units
|
||||
) noexcept;
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
class Region {
|
||||
public:
|
||||
explicit Region(
|
||||
std::string_view label,
|
||||
std::uint64_t warmup_count = 0
|
||||
);
|
||||
|
||||
void Record(double seconds) const noexcept;
|
||||
void AddCount(std::uint64_t work_units) const noexcept;
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
std::map<std::string, Statistics> m_statistics;
|
||||
std::size_t m_region;
|
||||
};
|
||||
|
||||
class ScopedTimer {
|
||||
public:
|
||||
ScopedTimer(std::string label, const unsigned long long warmup_count)
|
||||
: m_label(std::move(label)),
|
||||
m_warmup_count(warmup_count),
|
||||
m_start(std::chrono::steady_clock::now()) {}
|
||||
explicit ScopedTimer(const Region ®ion) noexcept;
|
||||
|
||||
ScopedTimer(const ScopedTimer&) = delete;
|
||||
ScopedTimer& operator=(const ScopedTimer&) = delete;
|
||||
ScopedTimer(ScopedTimer&&) = delete;
|
||||
ScopedTimer& operator=(ScopedTimer&&) = delete;
|
||||
ScopedTimer(const ScopedTimer &) = delete;
|
||||
ScopedTimer &operator=(const ScopedTimer &) = delete;
|
||||
ScopedTimer(ScopedTimer &&) = delete;
|
||||
ScopedTimer &operator=(ScopedTimer &&) = delete;
|
||||
|
||||
~ScopedTimer() {
|
||||
try {
|
||||
const auto stop = std::chrono::steady_clock::now();
|
||||
const double seconds = std::chrono::duration<double>(stop - m_start).count();
|
||||
Registry::Get().Record(m_label, seconds, m_warmup_count);
|
||||
} catch (...) {}
|
||||
}
|
||||
~ScopedTimer() noexcept;
|
||||
|
||||
private:
|
||||
std::string m_label;
|
||||
unsigned long long m_warmup_count;
|
||||
const Region &m_region;
|
||||
std::chrono::steady_clock::time_point m_start;
|
||||
};
|
||||
}
|
||||
} // namespace mean_field::profiling
|
||||
|
||||
#define MEAN_FIELD_PROFILE_JOIN_IMPL(left, right) left##right
|
||||
#define MEAN_FIELD_PROFILE_JOIN(left, right) MEAN_FIELD_PROFILE_JOIN_IMPL(left, right)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) \
|
||||
::mean_field::profiling::ScopedTimer MEAN_FIELD_PROFILE_JOIN(mean_field_profile_timer_, __COUNTER__)(label, warmup_count)
|
||||
#if MEAN_FIELD_ENABLE_PROFILING
|
||||
|
||||
#define MEAN_FIELD_PROFILE_SCOPE(label) \
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, 1)
|
||||
#define MEAN_FIELD_PROFILE_SCOPE_IMPL(label, warmup_count, identifier) \
|
||||
static const ::mean_field::profiling::Region MEAN_FIELD_PROFILE_JOIN(mean_field_profile_region_, identifier)( \
|
||||
label, warmup_count \
|
||||
); \
|
||||
const ::mean_field::profiling::ScopedTimer MEAN_FIELD_PROFILE_JOIN(mean_field_profile_timer_, identifier)( \
|
||||
MEAN_FIELD_PROFILE_JOIN(mean_field_profile_region_, identifier) \
|
||||
)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
|
||||
do { \
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count); \
|
||||
__VA_ARGS__; \
|
||||
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) \
|
||||
MEAN_FIELD_PROFILE_SCOPE_IMPL(label, warmup_count, __COUNTER__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_SCOPE(label) MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, 1)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
|
||||
do { \
|
||||
MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count); \
|
||||
__VA_ARGS__; \
|
||||
} while (false)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL(label, ...) \
|
||||
MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
|
||||
#define MEAN_FIELD_PROFILE_CALL(label, ...) MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_RESET() \
|
||||
::mean_field::profiling::Registry::Get().Reset()
|
||||
#define MEAN_FIELD_PROFILE_EVALUATE_IMPL(label, warmup_count, identifier, ...) \
|
||||
([&]() -> decltype(auto) { \
|
||||
MEAN_FIELD_PROFILE_SCOPE_IMPL(label, warmup_count, identifier); \
|
||||
return (__VA_ARGS__); \
|
||||
}())
|
||||
|
||||
#define MEAN_FIELD_PROFILE_PRINT(communicator) \
|
||||
::mean_field::profiling::Registry::Get().Print(communicator)
|
||||
#define MEAN_FIELD_PROFILE_EVALUATE_WARMUP(label, warmup_count, ...) \
|
||||
MEAN_FIELD_PROFILE_EVALUATE_IMPL(label, warmup_count, __COUNTER__, __VA_ARGS__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_EVALUATE(label, ...) MEAN_FIELD_PROFILE_EVALUATE_WARMUP(label, 1, __VA_ARGS__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_COUNT_IMPL(label, work_units, identifier) \
|
||||
do { \
|
||||
static const ::mean_field::profiling::Region MEAN_FIELD_PROFILE_JOIN(mean_field_profile_counter_, identifier)( \
|
||||
label \
|
||||
); \
|
||||
MEAN_FIELD_PROFILE_JOIN(mean_field_profile_counter_, identifier).AddCount(work_units); \
|
||||
} while (false)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_COUNT(label, work_units) MEAN_FIELD_PROFILE_COUNT_IMPL(label, work_units, __COUNTER__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_RESET() ::mean_field::profiling::Registry::Get().Reset()
|
||||
|
||||
#define MEAN_FIELD_PROFILE_PRINT(communicator) ::mean_field::profiling::Registry::Get().Print(communicator)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_PRINT_CSV(communicator, stream) \
|
||||
::mean_field::profiling::Registry::Get().PrintCsv(communicator, stream)
|
||||
|
||||
#else
|
||||
|
||||
#define MEAN_FIELD_PROFILE_SCOPE_WARMUP(label, warmup_count) ((void)0)
|
||||
#define MEAN_FIELD_PROFILE_SCOPE(label) ((void)0)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL_WARMUP(label, warmup_count, ...) \
|
||||
do { \
|
||||
__VA_ARGS__; \
|
||||
} while (false)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_CALL(label, ...) MEAN_FIELD_PROFILE_CALL_WARMUP(label, 1, __VA_ARGS__)
|
||||
|
||||
#define MEAN_FIELD_PROFILE_EVALUATE_WARMUP(label, warmup_count, ...) (__VA_ARGS__)
|
||||
#define MEAN_FIELD_PROFILE_EVALUATE(label, ...) (__VA_ARGS__)
|
||||
#define MEAN_FIELD_PROFILE_COUNT(label, work_units) ((void)0)
|
||||
#define MEAN_FIELD_PROFILE_RESET() ((void)0)
|
||||
#define MEAN_FIELD_PROFILE_PRINT(communicator) ((void)0)
|
||||
#define MEAN_FIELD_PROFILE_PRINT_CSV(communicator, stream) ((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -764,6 +764,11 @@ export namespace mean_field::field {
|
||||
|
||||
require_reduced_size(reduced);
|
||||
|
||||
if (is_identity()) {
|
||||
reduced = full;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
|
||||
reduced(reducedDof) = full(m_reducedToTrue[reducedDof]);
|
||||
}
|
||||
@@ -798,6 +803,11 @@ export namespace mean_field::field {
|
||||
|
||||
require_full_size(full);
|
||||
|
||||
if (is_identity()) {
|
||||
full = reduced;
|
||||
return;
|
||||
}
|
||||
|
||||
full = 0.0;
|
||||
|
||||
scatter_into(reduced, full);
|
||||
@@ -828,6 +838,11 @@ export namespace mean_field::field {
|
||||
|
||||
require_full_size(full);
|
||||
|
||||
if (is_identity()) {
|
||||
full = reduced;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
|
||||
full(m_reducedToTrue[reducedDof]) = reduced(reducedDof);
|
||||
}
|
||||
@@ -847,6 +862,11 @@ export namespace mean_field::field {
|
||||
|
||||
require_full_size(full);
|
||||
|
||||
if (is_identity()) {
|
||||
full.Add(scale, reduced);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int reducedDof = 0; reducedDof < reduced_size(); ++reducedDof) {
|
||||
full(m_reducedToTrue[reducedDof]) += scale * reduced(reducedDof);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ module;
|
||||
|
||||
export module mean_field:field.registry;
|
||||
|
||||
export import :dimensions.quantities;
|
||||
export import :field.base;
|
||||
export import :quadrature.policy;
|
||||
export import :utils.domain;
|
||||
@@ -25,6 +26,7 @@ export namespace mean_field::field {
|
||||
static constexpr std::string_view name = "density";
|
||||
static constexpr int scalarOrder = 2 + uniformPolynomialOrderIncrement;
|
||||
|
||||
using PhysicalQuantity = dimensions::quantity::Density;
|
||||
using Support = DomainSupport<utils::domain::Stellar>;
|
||||
|
||||
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<L2, scalarOrder>> {
|
||||
@@ -261,6 +263,7 @@ export namespace mean_field::field {
|
||||
static constexpr std::string_view name = "specific_enthalpy";
|
||||
static constexpr int scalarOrder = 3 + uniformPolynomialOrderIncrement;
|
||||
|
||||
using PhysicalQuantity = dimensions::quantity::SpecificEnthalpy;
|
||||
using Support = DomainSupport<utils::domain::Stellar>;
|
||||
|
||||
struct Scalar final : ScalarQ<FieldRelation::Independent, Disc<H1, scalarOrder>> {
|
||||
|
||||
271
libmeanfield/interface/material/thermodynamic_equations.cppm
Normal file
271
libmeanfield/interface/material/thermodynamic_equations.cppm
Normal file
@@ -0,0 +1,271 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
export module mean_field:material.thermodynamic_equations;
|
||||
|
||||
export import :eos.polytrope;
|
||||
export import :surface.compiler;
|
||||
export import :utils.blocks;
|
||||
|
||||
export namespace mean_field::material {
|
||||
/**
|
||||
* A thermodynamic field identifies the physical quantity represented by
|
||||
* its discrete degree of freedom. The quantity belongs to the field, not
|
||||
* to an EOS-specific aggregate description.
|
||||
*/
|
||||
template <typename Candidate>
|
||||
concept ThermodynamicField = surface::SurfaceFieldType<Candidate> && requires {
|
||||
typename std::remove_cvref_t<Candidate>::PhysicalQuantity;
|
||||
requires eos::ThermodynamicQuantityType<typename std::remove_cvref_t<Candidate>::PhysicalQuantity>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Declares the algebraic blocks owned by one thermodynamic governing
|
||||
* equation. Its physical quantity is inferred from Field.
|
||||
*/
|
||||
template <ThermodynamicField Field, typename CorrectionBlock, typename ResidualBlock>
|
||||
requires std::derived_from<CorrectionBlock, utils::blocks::value_block_base> &&
|
||||
std::derived_from<ResidualBlock, utils::blocks::residual_block_base>
|
||||
struct ThermodynamicEquation final {
|
||||
using FieldType = Field;
|
||||
using PhysicalQuantity = typename Field::PhysicalQuantity;
|
||||
using Correction = CorrectionBlock;
|
||||
using Residual = ResidualBlock;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registry of thermodynamic governing equations available to a problem.
|
||||
* The problem form, rather than the registry, selects the active subset.
|
||||
*/
|
||||
template <typename... Equations> struct ThermodynamicEquationCatalog final {
|
||||
static constexpr int size = sizeof...(Equations);
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename Candidate> struct IsThermodynamicEquation : std::false_type { };
|
||||
|
||||
template <typename Field, typename CorrectionBlock, typename ResidualBlock>
|
||||
struct IsThermodynamicEquation<ThermodynamicEquation<Field, CorrectionBlock, ResidualBlock>> : std::true_type {
|
||||
};
|
||||
|
||||
template <typename Candidate> struct IsThermodynamicEquationCatalog : std::false_type { };
|
||||
|
||||
template <typename... Equations>
|
||||
struct IsThermodynamicEquationCatalog<ThermodynamicEquationCatalog<Equations...>>
|
||||
: std::bool_constant<(sizeof...(Equations) > 0) && (IsThermodynamicEquation<Equations>::value && ...)> { };
|
||||
|
||||
template <typename Catalog> struct CatalogEntriesAreUnique : std::false_type { };
|
||||
|
||||
template <typename... Equations>
|
||||
struct CatalogEntriesAreUnique<ThermodynamicEquationCatalog<Equations...>>
|
||||
: std::bool_constant<
|
||||
utils::blocks::types_are_unique_v<utils::blocks::type_list<typename Equations::FieldType...>> &&
|
||||
utils::blocks::types_are_unique_v<
|
||||
utils::blocks::type_list<typename Equations::PhysicalQuantity...>> &&
|
||||
utils::blocks::types_are_unique_v<utils::blocks::type_list<typename Equations::Correction...>> &&
|
||||
utils::blocks::types_are_unique_v<utils::blocks::type_list<typename Equations::Residual...>>> { };
|
||||
|
||||
template <typename Catalog, typename Field> struct EquationForField;
|
||||
|
||||
template <typename Field, typename First, typename... Remaining>
|
||||
struct EquationForField<ThermodynamicEquationCatalog<First, Remaining...>, Field>
|
||||
: std::conditional_t<
|
||||
std::same_as<Field, typename First::FieldType>,
|
||||
std::type_identity<First>,
|
||||
EquationForField<ThermodynamicEquationCatalog<Remaining...>, Field>> { };
|
||||
|
||||
template <typename Field> struct EquationForField<ThermodynamicEquationCatalog<>, Field> {
|
||||
using type = void;
|
||||
};
|
||||
|
||||
template <typename Catalog, typename Field> struct EquationFieldCount;
|
||||
|
||||
template <typename Field, typename... Equations>
|
||||
struct EquationFieldCount<ThermodynamicEquationCatalog<Equations...>, Field>
|
||||
: std::integral_constant<
|
||||
int,
|
||||
(int{0} + ... + (std::same_as<Field, typename Equations::FieldType> ? 1 : 0))> { };
|
||||
|
||||
template <typename Catalog, typename Correction> struct EquationForCorrection;
|
||||
|
||||
template <typename Correction, typename First, typename... Remaining>
|
||||
struct EquationForCorrection<ThermodynamicEquationCatalog<First, Remaining...>, Correction>
|
||||
: std::conditional_t<
|
||||
std::same_as<Correction, typename First::Correction>,
|
||||
std::type_identity<First>,
|
||||
EquationForCorrection<ThermodynamicEquationCatalog<Remaining...>, Correction>> { };
|
||||
|
||||
template <typename Correction> struct EquationForCorrection<ThermodynamicEquationCatalog<>, Correction> {
|
||||
using type = void;
|
||||
};
|
||||
|
||||
template <typename Head, typename Catalog> struct PrependEquation;
|
||||
|
||||
template <typename Head, typename... Equations>
|
||||
struct PrependEquation<Head, ThermodynamicEquationCatalog<Equations...>> {
|
||||
using Type = ThermodynamicEquationCatalog<Head, Equations...>;
|
||||
};
|
||||
|
||||
template <typename ValueBlocks, typename AvailableEquations> struct SelectActiveEquations;
|
||||
|
||||
template <typename AvailableEquations>
|
||||
struct SelectActiveEquations<utils::blocks::type_list<>, AvailableEquations> {
|
||||
using Type = ThermodynamicEquationCatalog<>;
|
||||
};
|
||||
|
||||
template <typename FirstValue, typename... RemainingValues, typename AvailableEquations>
|
||||
struct SelectActiveEquations<utils::blocks::type_list<FirstValue, RemainingValues...>, AvailableEquations> {
|
||||
private:
|
||||
using Tail =
|
||||
typename SelectActiveEquations<utils::blocks::type_list<RemainingValues...>, AvailableEquations>::Type;
|
||||
using Match = typename EquationForCorrection<AvailableEquations, FirstValue>::type;
|
||||
|
||||
public:
|
||||
using Type =
|
||||
std::conditional_t<std::same_as<Match, void>, Tail, typename PrependEquation<Match, Tail>::Type>;
|
||||
};
|
||||
|
||||
template <typename AvailableEquations, typename Form> struct CatalogMatchesForm : std::false_type { };
|
||||
|
||||
template <typename... Equations, typename... Values, typename... Residuals>
|
||||
struct CatalogMatchesForm<
|
||||
ThermodynamicEquationCatalog<Equations...>,
|
||||
utils::blocks::block_form<utils::blocks::type_list<Values...>, utils::blocks::type_list<Residuals...>>>
|
||||
: std::bool_constant<
|
||||
((utils::blocks::
|
||||
contains_type_v<typename Equations::Correction, utils::blocks::type_list<Values...>> ==
|
||||
utils::blocks::
|
||||
contains_type_v<typename Equations::Residual, utils::blocks::type_list<Residuals...>>) &&
|
||||
...)> { };
|
||||
|
||||
template <typename Equations> struct SurfaceBindingsForEquations;
|
||||
|
||||
template <typename... Equations>
|
||||
struct SurfaceBindingsForEquations<ThermodynamicEquationCatalog<Equations...>> {
|
||||
using Type = surface::SurfaceStateBindings<
|
||||
surface::SurfaceStateBinding<typename Equations::PhysicalQuantity, typename Equations::FieldType>...>;
|
||||
};
|
||||
|
||||
template <typename SurfaceFields, typename Equations>
|
||||
struct SurfaceFieldsBelongToEquations : std::false_type { };
|
||||
|
||||
template <typename... Fields, typename Equations>
|
||||
struct SurfaceFieldsBelongToEquations<field::TypeList<Fields...>, Equations>
|
||||
: std::bool_constant<((EquationFieldCount<Equations, Fields>::value == 1) && ...)> { };
|
||||
|
||||
template <typename EquationOfState, typename Form, typename AvailableEquations, typename = void>
|
||||
struct ThermodynamicCompilationIsAvailable : std::false_type { };
|
||||
|
||||
template <typename EquationOfState, typename Form, typename AvailableEquations>
|
||||
struct ThermodynamicCompilationIsAvailable<
|
||||
EquationOfState,
|
||||
Form,
|
||||
AvailableEquations,
|
||||
std::enable_if_t<
|
||||
eos::EquationOfStateModel<EquationOfState> && utils::blocks::block_form_is_valid_v<Form> &&
|
||||
IsThermodynamicEquationCatalog<AvailableEquations>::value &&
|
||||
CatalogEntriesAreUnique<AvailableEquations>::value &&
|
||||
CatalogMatchesForm<AvailableEquations, Form>::value>> {
|
||||
private:
|
||||
using ActiveEquations =
|
||||
typename SelectActiveEquations<typename Form::value_blocks, AvailableEquations>::Type;
|
||||
using StateBindings = typename SurfaceBindingsForEquations<ActiveEquations>::Type;
|
||||
|
||||
public:
|
||||
static constexpr bool value = (ActiveEquations::size > 0) &&
|
||||
surface::PressureSurfaceFormulationCompilable<StateBindings, EquationOfState>;
|
||||
};
|
||||
|
||||
template <typename Candidate, typename = void> struct IsCompiledThermodynamicEquations : std::false_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
struct IsCompiledThermodynamicEquations<
|
||||
Candidate,
|
||||
std::void_t<
|
||||
typename Candidate::EquationOfStateType,
|
||||
typename Candidate::Equations,
|
||||
typename Candidate::StateBindings,
|
||||
typename Candidate::PressureSurfaceFormulation>>
|
||||
: std::bool_constant<
|
||||
eos::EquationOfStateModel<typename Candidate::EquationOfStateType> &&
|
||||
IsThermodynamicEquationCatalog<typename Candidate::Equations>::value &&
|
||||
CatalogEntriesAreUnique<typename Candidate::Equations>::value &&
|
||||
surface::ValidSurfaceStateBindings<typename Candidate::StateBindings> &&
|
||||
std::same_as<
|
||||
typename Candidate::StateBindings,
|
||||
typename SurfaceBindingsForEquations<typename Candidate::Equations>::Type> &&
|
||||
surface::SurfaceConstraintFormulationType<typename Candidate::PressureSurfaceFormulation> &&
|
||||
std::same_as<
|
||||
typename Candidate::PressureSurfaceFormulation::StateBindings,
|
||||
typename Candidate::StateBindings>> { };
|
||||
} // namespace detail
|
||||
|
||||
template <typename Candidate>
|
||||
concept ThermodynamicEquationType = detail::IsThermodynamicEquation<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
template <typename Candidate>
|
||||
concept ValidThermodynamicEquationCatalog =
|
||||
detail::IsThermodynamicEquationCatalog<std::remove_cvref_t<Candidate>>::value &&
|
||||
detail::CatalogEntriesAreUnique<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
template <typename Candidate>
|
||||
concept CompiledThermodynamicEquations =
|
||||
detail::IsCompiledThermodynamicEquations<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
template <ValidThermodynamicEquationCatalog Equations, surface::SurfaceFieldType Field>
|
||||
requires(detail::EquationFieldCount<Equations, Field>::value == 1)
|
||||
using ThermodynamicEquationForFieldT = typename detail::EquationForField<Equations, Field>::type;
|
||||
|
||||
template <typename Fields, ValidThermodynamicEquationCatalog Equations>
|
||||
inline constexpr bool fieldsBelongToThermodynamicEquations =
|
||||
detail::SurfaceFieldsBelongToEquations<Fields, Equations>::value;
|
||||
|
||||
template <typename EquationOfState, typename Form, typename AvailableEquations>
|
||||
concept ThermodynamicEquationsCompilable = detail::ThermodynamicCompilationIsAvailable<
|
||||
std::remove_cvref_t<EquationOfState>,
|
||||
std::remove_cvref_t<Form>,
|
||||
std::remove_cvref_t<AvailableEquations>>::value;
|
||||
|
||||
template <eos::EquationOfStateModel EquationOfState, ValidThermodynamicEquationCatalog ActiveEquations>
|
||||
requires surface::PressureSurfaceFormulationCompilable<
|
||||
typename detail::SurfaceBindingsForEquations<ActiveEquations>::Type,
|
||||
EquationOfState>
|
||||
struct ThermodynamicEquationSet final {
|
||||
using EquationOfStateType = EquationOfState;
|
||||
using Equations = ActiveEquations;
|
||||
using StateBindings = typename detail::SurfaceBindingsForEquations<Equations>::Type;
|
||||
using PressureSurfaceFormulation =
|
||||
surface::CompiledPressureSurfaceFormulationT<StateBindings, EquationOfStateType>;
|
||||
};
|
||||
|
||||
template <
|
||||
eos::EquationOfStateModel EquationOfState,
|
||||
typename Form,
|
||||
ValidThermodynamicEquationCatalog AvailableEquations>
|
||||
requires ThermodynamicEquationsCompilable<EquationOfState, Form, AvailableEquations>
|
||||
struct CompileThermodynamicEquations final {
|
||||
using Equations = typename detail::SelectActiveEquations<typename Form::value_blocks, AvailableEquations>::Type;
|
||||
using Type = ThermodynamicEquationSet<EquationOfState, Equations>;
|
||||
};
|
||||
|
||||
template <typename EquationOfState, typename Form, typename AvailableEquations>
|
||||
requires ThermodynamicEquationsCompilable<EquationOfState, Form, AvailableEquations>
|
||||
using CompiledThermodynamicEquationsT = typename CompileThermodynamicEquations<
|
||||
std::remove_cvref_t<EquationOfState>,
|
||||
std::remove_cvref_t<Form>,
|
||||
std::remove_cvref_t<AvailableEquations>>::Type;
|
||||
|
||||
using StellarEquilibriumThermodynamicEquations = ThermodynamicEquationCatalog<
|
||||
ThermodynamicEquation<
|
||||
field::Density,
|
||||
utils::blocks::density::mass::value,
|
||||
utils::blocks::density::mass::residual>,
|
||||
ThermodynamicEquation<
|
||||
field::Enthalpy,
|
||||
utils::blocks::enthalpy::specific::value,
|
||||
utils::blocks::enthalpy::specific::residual>>;
|
||||
|
||||
static_assert(ValidThermodynamicEquationCatalog<StellarEquilibriumThermodynamicEquations>);
|
||||
} // namespace mean_field::material
|
||||
@@ -26,6 +26,7 @@ export import :quadrature.policy;
|
||||
export import :quadrature.mfem;
|
||||
export import :solver.fields;
|
||||
export import :solver.preconditioning_diagnostics;
|
||||
export import :preconditioning;
|
||||
export import :utils.blocks;
|
||||
export import :operators.gravity_field;
|
||||
export import :operators.gravity_field_jacobian;
|
||||
@@ -72,6 +73,7 @@ export import :surface.constant;
|
||||
export import :surface.dependencies;
|
||||
export import :surface.compiled;
|
||||
export import :surface.compiler;
|
||||
export import :material.thermodynamic_equations;
|
||||
export import :deformation.descriptors;
|
||||
export import :deformation.surface_prescription;
|
||||
export import :deformation.nodal_radial_surface;
|
||||
|
||||
@@ -76,6 +76,12 @@ export namespace mean_field::operators::context::gravity_field {
|
||||
DisplacementRevision displacement_revision
|
||||
);
|
||||
|
||||
GravityFieldGeometryPreparation PreparePrimal(
|
||||
const mfem::Vector &displacement,
|
||||
DiscretizationRevision discretization_revision,
|
||||
DisplacementRevision displacement_revision
|
||||
);
|
||||
|
||||
[[nodiscard]] const PreparedMappedHDivMassOperator &GetMassOperator() const;
|
||||
[[nodiscard]] const PreparedMappedGravitySourceOperator &GetSourceOperator() const;
|
||||
[[nodiscard]] const mfem::Operator &GetDivergenceOperator() const;
|
||||
@@ -87,12 +93,21 @@ export namespace mean_field::operators::context::gravity_field {
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
private:
|
||||
enum class PreparationMode : std::uint8_t { primal, linearization };
|
||||
|
||||
GravityFieldGeometryPreparation PrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
DiscretizationRevision discretization_revision,
|
||||
DisplacementRevision displacement_revision,
|
||||
PreparationMode mode
|
||||
);
|
||||
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapper &m_domain_mapper;
|
||||
|
||||
std::unique_ptr<PreparedMappedHDivMassOperator> m_mass_operator;
|
||||
std::unique_ptr<PreparedMappedGravitySourceOperator> m_source_operator;
|
||||
std::unique_ptr<mfem::ParMixedBilinearForm> m_divergence_operator;
|
||||
std::unique_ptr<mfem::Operator> m_divergence_operator;
|
||||
std::unique_ptr<mfem::TransposeOperator> m_transpose_divergence_operator;
|
||||
|
||||
field::FieldDofMap m_displacement_map;
|
||||
@@ -102,6 +117,7 @@ export namespace mean_field::operators::context::gravity_field {
|
||||
DisplacementRevision m_displacement_revision;
|
||||
|
||||
bool m_is_prepared{false};
|
||||
bool m_variation_state_prepared{false};
|
||||
};
|
||||
|
||||
struct GravityFieldPreparationReport {
|
||||
|
||||
@@ -60,6 +60,12 @@ export namespace mean_field::operators {
|
||||
mfem::Array<int> m_state_offsets;
|
||||
mfem::Array<int> m_residual_offsets;
|
||||
GravityFieldJacobianOperator &m_jacobian;
|
||||
|
||||
mutable mfem::Vector m_potential_true;
|
||||
mutable mfem::Vector m_transpose_divergence_action_true;
|
||||
mutable mfem::Vector m_transpose_divergence_action;
|
||||
mutable mfem::Vector m_gradient_true;
|
||||
mutable mfem::Vector m_divergence_action_true;
|
||||
};
|
||||
|
||||
class ReducedGravityFieldOperator final : public mfem::Operator {
|
||||
|
||||
@@ -54,6 +54,10 @@ export namespace mean_field::operators {
|
||||
|
||||
void BuildResidual(mfem::Vector &residual) const;
|
||||
|
||||
// Exact diagonal of the prepared density-to-closure block, expressed
|
||||
// in the reduced density coordinates used by the root operator.
|
||||
void AssembleDensityJacobianDiagonal(mfem::Vector &diagonal) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
[[nodiscard]] int GetDensitySize() const noexcept;
|
||||
|
||||
@@ -18,6 +18,7 @@ export namespace mean_field::operators {
|
||||
);
|
||||
|
||||
void Prepare(const mfem::Vector &displacement);
|
||||
void PreparePrimal(const mfem::Vector &displacement);
|
||||
void Mult(
|
||||
const mfem::Vector &density,
|
||||
mfem::Vector &action
|
||||
@@ -29,6 +30,7 @@ export namespace mean_field::operators {
|
||||
) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
[[nodiscard]] bool HasVariationData() const noexcept;
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
|
||||
[[nodiscard]] const field::FieldDofMap &GetDensityMap() const noexcept;
|
||||
@@ -41,6 +43,8 @@ export namespace mean_field::operators {
|
||||
) const override;
|
||||
|
||||
private:
|
||||
enum class PreparationMode : std::uint8_t { primal, linearization };
|
||||
|
||||
struct ElementPAData {
|
||||
int element_id{-1};
|
||||
|
||||
@@ -64,6 +68,11 @@ export namespace mean_field::operators {
|
||||
mfem::Vector quadrature_data;
|
||||
};
|
||||
|
||||
void PrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
PreparationMode mode
|
||||
);
|
||||
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapper &m_domain_mapper;
|
||||
|
||||
@@ -77,6 +86,11 @@ export namespace mean_field::operators {
|
||||
mutable mfem::Vector m_density_true;
|
||||
mutable mfem::Vector m_potential_true;
|
||||
mutable mfem::Vector m_action_true;
|
||||
mutable mfem::Vector m_potential_local;
|
||||
mutable mfem::Vector m_local_action;
|
||||
mutable mfem::Vector m_element_input;
|
||||
mutable mfem::Vector m_quadrature_action;
|
||||
mutable mfem::Vector m_element_action;
|
||||
mutable mfem::Vector m_density_local;
|
||||
mutable mfem::Vector m_displacement_variation_local;
|
||||
mutable mfem::Vector m_local_variation_action;
|
||||
@@ -90,5 +104,6 @@ export namespace mean_field::operators {
|
||||
|
||||
std::uint64_t m_preparation_count{0};
|
||||
bool m_is_prepared{false};
|
||||
bool m_has_variation_data{false};
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
|
||||
@@ -18,6 +18,7 @@ export namespace mean_field::operators {
|
||||
);
|
||||
|
||||
void Prepare(const mfem::Vector &displacement);
|
||||
void PreparePrimal(const mfem::Vector &displacement);
|
||||
void Mult(
|
||||
const mfem::Vector &gravity_gradient,
|
||||
mfem::Vector &action
|
||||
@@ -31,12 +32,15 @@ export namespace mean_field::operators {
|
||||
void AssembleTrueDiagonal(mfem::Vector &diagonal) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
[[nodiscard]] bool HasVariationData() const noexcept;
|
||||
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
|
||||
|
||||
[[nodiscard]] const field::FieldDofMap &GetFluxMap() const noexcept;
|
||||
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
|
||||
|
||||
private:
|
||||
enum class PreparationMode : std::uint8_t { primal, linearization };
|
||||
|
||||
struct ElementVariationData {
|
||||
int elementId{-1};
|
||||
mfem::Array<int> gravityGradientDofs;
|
||||
@@ -51,6 +55,10 @@ export namespace mean_field::operators {
|
||||
};
|
||||
|
||||
void PrepareVariationData();
|
||||
void PrepareImpl(
|
||||
const mfem::Vector &displacement,
|
||||
PreparationMode mode
|
||||
);
|
||||
|
||||
const fem::FEM &m_fem;
|
||||
const mapping::DomainMapper &m_domain_mapper;
|
||||
@@ -68,6 +76,9 @@ export namespace mean_field::operators {
|
||||
mutable mfem::Vector m_flux_true;
|
||||
mutable mfem::Vector m_action_true;
|
||||
mutable mfem::Vector m_domain_action_true;
|
||||
mutable mfem::Vector m_flux_local;
|
||||
mutable mfem::Vector m_action_local;
|
||||
mutable mfem::Vector m_domain_action_local;
|
||||
mfem::Vector m_displacement_true;
|
||||
std::vector<ElementVariationData> m_variationElements;
|
||||
|
||||
@@ -86,5 +97,7 @@ export namespace mean_field::operators {
|
||||
mutable mfem::DenseMatrix m_massTensorVariation;
|
||||
std::uint64_t m_preparation_count{0};
|
||||
bool m_is_prepared{false};
|
||||
bool m_has_variation_data{false};
|
||||
bool m_single_rank{true};
|
||||
};
|
||||
} // namespace mean_field::operators
|
||||
|
||||
@@ -138,6 +138,10 @@ export namespace mean_field::operators {
|
||||
mfem::Vector &action
|
||||
) const;
|
||||
|
||||
// Exact diagonal of the volume enthalpy-to-hydrostatic block. Surface
|
||||
// boundary-row replacement is deliberately applied by its owner.
|
||||
void AssembleEnthalpyJacobianDiagonal(mfem::Vector &diagonal) const;
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept;
|
||||
|
||||
[[nodiscard]] const context::hydrostatic::HydrostaticPreparationStatistics &
|
||||
|
||||
@@ -11,6 +11,7 @@ export module mean_field:operators.stellar_equilibrium_problem;
|
||||
|
||||
export import :deformation.domain_deformation;
|
||||
export import :equilibrium.stellar_discretization;
|
||||
export import :material.thermodynamic_equations;
|
||||
export import :model.typed_stellar;
|
||||
export import :operators.prepared_central_density_stellar_equilibrium;
|
||||
export import :surface.compiler;
|
||||
@@ -39,8 +40,25 @@ export namespace mean_field::equilibrium {
|
||||
hasFixedCentralDensity,
|
||||
operators::PreparedCentralDensityStellarEquilibriumOperator,
|
||||
operators::PreparedStellarEquilibriumOperator>;
|
||||
using CompiledSurfaceConstraintType =
|
||||
surface::CompiledPressureSurfaceConstraintT<surface::BarotropicSurfaceFormulation, eos::Polytrope>;
|
||||
using FormType = std::conditional_t<
|
||||
hasFixedCentralDensity,
|
||||
operators::CentralDensityStellarEquilibriumForm,
|
||||
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 ThermodynamicEquationsType =
|
||||
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
|
||||
using CompiledSurfaceConstraintType = surface::CompiledPressureSurfaceConstraintT<
|
||||
typename ThermodynamicEquationsType::PressureSurfaceFormulation,
|
||||
EquationOfStateType>;
|
||||
|
||||
StellarEquilibriumProblem(
|
||||
ModelType stellarModel,
|
||||
@@ -113,6 +131,26 @@ export namespace mean_field::equilibrium {
|
||||
return m_preparedOperator.GetRootManifest();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsPrepared() const noexcept {
|
||||
return m_preparedOperator.IsPrepared();
|
||||
}
|
||||
|
||||
[[nodiscard]] const operators::StellarEquilibriumDependencies &GetLinearizationDependencies() const {
|
||||
if constexpr (hasFixedCentralDensity) {
|
||||
return m_preparedOperator.GetPhysicalOperator().GetDependencies();
|
||||
} else {
|
||||
return m_preparedOperator.GetDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const operators::StellarEquilibriumDependencyStamp &GetGeometryDependency() const {
|
||||
if constexpr (hasFixedCentralDensity) {
|
||||
return m_preparedOperator.GetPhysicalOperator().GetGeneratedDisplacementDependency();
|
||||
} else {
|
||||
return m_preparedOperator.GetGeneratedDisplacementDependency();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
|
||||
if constexpr (hasFixedCentralDensity) {
|
||||
return m_preparedOperator.GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
|
||||
@@ -154,9 +192,10 @@ export namespace mean_field::equilibrium {
|
||||
|
||||
private:
|
||||
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
|
||||
return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
|
||||
return surface::compilePressureSurfaceConstraint<
|
||||
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
|
||||
stellarModel.template specification<surface::Isobaric>(),
|
||||
stellarModel.template specification<eos::Polytrope>()
|
||||
stellarModel.template specification<EquationOfStateType>()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -207,4 +246,12 @@ export namespace mean_field::equilibrium {
|
||||
) {
|
||||
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
|
||||
|
||||
332
libmeanfield/interface/preconditioning/backend.cppm
Normal file
332
libmeanfield/interface/preconditioning/backend.cppm
Normal file
@@ -0,0 +1,332 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
export module mean_field:preconditioning.backend;
|
||||
|
||||
export namespace mean_field::preconditioning {
|
||||
enum class OperatorCategory : std::uint8_t {
|
||||
identity,
|
||||
mass_like,
|
||||
elliptic_like,
|
||||
surface_like,
|
||||
mixed,
|
||||
dense_border
|
||||
};
|
||||
|
||||
enum class OperatorValueStructure : std::uint8_t { scalar, vector, block };
|
||||
|
||||
enum class OperatorSymmetry : std::uint8_t { symmetric, nonsymmetric };
|
||||
|
||||
enum class OperatorDefiniteness : std::uint8_t {
|
||||
positive_definite,
|
||||
positive_semidefinite,
|
||||
indefinite,
|
||||
unspecified
|
||||
};
|
||||
|
||||
enum class OperatorRepresentation : std::uint8_t { none, diagonal, matrix_free, assembled_sparse, assembled_dense };
|
||||
enum class OperatorDistribution : std::uint8_t { not_applicable, local, distributed_true_dof };
|
||||
enum class OperatorFESpace : std::uint8_t { not_applicable, h1, h_curl, h_div, l2, product };
|
||||
enum class OperatorNullspace : std::uint8_t { none, constant_mode, supplied_basis };
|
||||
enum class ApplicationContract : std::uint8_t { stationary_linear, flexible };
|
||||
enum class SymmetryRequirement : std::uint8_t { none, symmetric };
|
||||
enum class NullspaceRequirement : std::uint8_t { none, constant_mode_supported, supplied_basis_required };
|
||||
enum class SurrogateRequirement : std::uint8_t { none, diagonal, assembled_dense, assembled_sparse };
|
||||
|
||||
enum class PreparationDependency : std::uint8_t {
|
||||
discretization = 1U << 0U,
|
||||
geometry = 1U << 1U,
|
||||
equation_of_state = 1U << 2U,
|
||||
linearization = 1U << 3U
|
||||
};
|
||||
|
||||
template <PreparationDependency... Dependencies> struct PreparationDependencies final {
|
||||
static constexpr std::uint8_t mask = (std::uint8_t{0} | ... | static_cast<std::uint8_t>(Dependencies));
|
||||
|
||||
[[nodiscard]] static consteval bool contains(const PreparationDependency dependency) noexcept {
|
||||
return (mask & static_cast<std::uint8_t>(dependency)) != 0U;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Candidate> struct IsPreparationDependencies : std::false_type { };
|
||||
|
||||
template <PreparationDependency... Dependencies>
|
||||
struct IsPreparationDependencies<PreparationDependencies<Dependencies...>> : std::true_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept PreparationDependenciesType = IsPreparationDependencies<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
using NoPreparationDependencies = PreparationDependencies<>;
|
||||
|
||||
template <
|
||||
OperatorCategory Category,
|
||||
OperatorValueStructure ValueStructure,
|
||||
OperatorSymmetry Symmetry,
|
||||
OperatorDefiniteness Definiteness,
|
||||
OperatorRepresentation Representation,
|
||||
OperatorDistribution Distribution,
|
||||
OperatorFESpace FESpace = OperatorFESpace::not_applicable,
|
||||
OperatorNullspace Nullspace = OperatorNullspace::none>
|
||||
struct OperatorCharacteristics final {
|
||||
static constexpr OperatorCategory category = Category;
|
||||
static constexpr OperatorValueStructure valueStructure = ValueStructure;
|
||||
static constexpr OperatorSymmetry symmetry = Symmetry;
|
||||
static constexpr OperatorDefiniteness definiteness = Definiteness;
|
||||
static constexpr OperatorRepresentation representation = Representation;
|
||||
static constexpr OperatorDistribution distribution = Distribution;
|
||||
static constexpr OperatorFESpace finiteElementSpace = FESpace;
|
||||
static constexpr OperatorNullspace nullspace = Nullspace;
|
||||
};
|
||||
|
||||
template <typename Candidate> struct IsOperatorCharacteristics : std::false_type { };
|
||||
|
||||
template <
|
||||
OperatorCategory Category,
|
||||
OperatorValueStructure ValueStructure,
|
||||
OperatorSymmetry Symmetry,
|
||||
OperatorDefiniteness Definiteness,
|
||||
OperatorRepresentation Representation,
|
||||
OperatorDistribution Distribution,
|
||||
OperatorFESpace FESpace,
|
||||
OperatorNullspace Nullspace>
|
||||
struct IsOperatorCharacteristics<OperatorCharacteristics<
|
||||
Category,
|
||||
ValueStructure,
|
||||
Symmetry,
|
||||
Definiteness,
|
||||
Representation,
|
||||
Distribution,
|
||||
FESpace,
|
||||
Nullspace>> : std::true_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept OperatorCharacteristicsType = IsOperatorCharacteristics<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
using IdentityOperatorCharacteristics = OperatorCharacteristics<
|
||||
OperatorCategory::identity,
|
||||
OperatorValueStructure::block,
|
||||
OperatorSymmetry::symmetric,
|
||||
OperatorDefiniteness::positive_definite,
|
||||
OperatorRepresentation::none,
|
||||
OperatorDistribution::not_applicable>;
|
||||
|
||||
namespace backend {
|
||||
struct FixedCycles final {
|
||||
int cycles{1};
|
||||
};
|
||||
|
||||
struct SolveToTolerance final {
|
||||
double relativeTolerance{1.0e-8};
|
||||
int maximumCycles{100};
|
||||
};
|
||||
|
||||
template <typename Candidate> struct ApplicationModeTraits {
|
||||
static constexpr bool registered = false;
|
||||
};
|
||||
|
||||
template <> struct ApplicationModeTraits<FixedCycles> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
|
||||
};
|
||||
|
||||
template <> struct ApplicationModeTraits<SolveToTolerance> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationContract::flexible;
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept ApplicationMode = ApplicationModeTraits<std::remove_cvref_t<Candidate>>::registered;
|
||||
|
||||
struct Identity final { };
|
||||
struct Diagonal final { };
|
||||
struct DenseDirect final { };
|
||||
|
||||
struct MatrixFreeChebyshev final {
|
||||
int order{2};
|
||||
int powerIterations{10};
|
||||
double powerTolerance{1.0e-8};
|
||||
int powerSeed{12345};
|
||||
};
|
||||
|
||||
template <ApplicationMode Mode = FixedCycles> struct HypreBoomerAMG final {
|
||||
using ApplicationModeType = Mode;
|
||||
|
||||
Mode application{};
|
||||
|
||||
constexpr HypreBoomerAMG() = default;
|
||||
|
||||
constexpr explicit HypreBoomerAMG(Mode applicationMode) : application(std::move(applicationMode)) {
|
||||
}
|
||||
};
|
||||
|
||||
template <ApplicationMode Mode> HypreBoomerAMG(Mode) -> HypreBoomerAMG<Mode>;
|
||||
|
||||
template <typename Candidate> struct Traits {
|
||||
static constexpr bool registered = false;
|
||||
|
||||
using PreparationDependencies = NoPreparationDependencies;
|
||||
};
|
||||
|
||||
template <> struct Traits<Identity> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
|
||||
static constexpr bool supportsSerialExecution = true;
|
||||
static constexpr bool supportsDistributedExecution = true;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::none;
|
||||
static constexpr bool requiresAssembledSparseSurrogate = false;
|
||||
|
||||
using PreparationDependencies = NoPreparationDependencies;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports = Characteristics::category == OperatorCategory::identity &&
|
||||
Characteristics::representation == OperatorRepresentation::none;
|
||||
};
|
||||
|
||||
template <> struct Traits<Diagonal> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
|
||||
static constexpr bool supportsSerialExecution = true;
|
||||
static constexpr bool supportsDistributedExecution = true;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::diagonal;
|
||||
static constexpr bool requiresAssembledSparseSurrogate = false;
|
||||
|
||||
using PreparationDependencies =
|
||||
preconditioning::PreparationDependencies<PreparationDependency::linearization>;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports =
|
||||
(Characteristics::category == OperatorCategory::mass_like ||
|
||||
Characteristics::category == OperatorCategory::elliptic_like ||
|
||||
Characteristics::category == OperatorCategory::surface_like) &&
|
||||
Characteristics::symmetry == OperatorSymmetry::symmetric &&
|
||||
(Characteristics::representation == OperatorRepresentation::diagonal ||
|
||||
(Characteristics::category == OperatorCategory::mass_like &&
|
||||
Characteristics::representation == OperatorRepresentation::matrix_free)) &&
|
||||
Characteristics::nullspace == OperatorNullspace::none &&
|
||||
(Characteristics::distribution == OperatorDistribution::local ||
|
||||
Characteristics::distribution == OperatorDistribution::distributed_true_dof);
|
||||
};
|
||||
|
||||
template <> struct Traits<MatrixFreeChebyshev> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
|
||||
static constexpr bool supportsSerialExecution = true;
|
||||
static constexpr bool supportsDistributedExecution = true;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::diagonal;
|
||||
static constexpr bool requiresAssembledSparseSurrogate = false;
|
||||
|
||||
using PreparationDependencies = preconditioning::PreparationDependencies<
|
||||
PreparationDependency::discretization,
|
||||
PreparationDependency::geometry,
|
||||
PreparationDependency::linearization>;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports =
|
||||
Characteristics::category == OperatorCategory::mass_like &&
|
||||
Characteristics::symmetry == OperatorSymmetry::symmetric &&
|
||||
Characteristics::definiteness == OperatorDefiniteness::positive_definite &&
|
||||
Characteristics::representation == OperatorRepresentation::matrix_free &&
|
||||
Characteristics::finiteElementSpace == OperatorFESpace::h_div &&
|
||||
Characteristics::nullspace == OperatorNullspace::none &&
|
||||
(Characteristics::distribution == OperatorDistribution::local ||
|
||||
Characteristics::distribution == OperatorDistribution::distributed_true_dof);
|
||||
};
|
||||
|
||||
template <> struct Traits<DenseDirect> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationContract::stationary_linear;
|
||||
static constexpr bool supportsSerialExecution = true;
|
||||
static constexpr bool supportsDistributedExecution = false;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::none;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_dense;
|
||||
static constexpr bool requiresAssembledSparseSurrogate = false;
|
||||
|
||||
using PreparationDependencies =
|
||||
preconditioning::PreparationDependencies<PreparationDependency::linearization>;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports =
|
||||
Characteristics::category == OperatorCategory::dense_border &&
|
||||
Characteristics::representation == OperatorRepresentation::assembled_dense &&
|
||||
Characteristics::distribution == OperatorDistribution::local &&
|
||||
Characteristics::nullspace == OperatorNullspace::none;
|
||||
};
|
||||
|
||||
template <ApplicationMode Mode> struct Traits<HypreBoomerAMG<Mode>> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract = ApplicationModeTraits<Mode>::applicationContract;
|
||||
static constexpr bool supportsSerialExecution = false;
|
||||
static constexpr bool supportsDistributedExecution = true;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
|
||||
static constexpr bool requiresAssembledSparseSurrogate = true;
|
||||
|
||||
using PreparationDependencies = preconditioning::PreparationDependencies<
|
||||
PreparationDependency::discretization,
|
||||
PreparationDependency::geometry,
|
||||
PreparationDependency::equation_of_state,
|
||||
PreparationDependency::linearization>;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports =
|
||||
Characteristics::category == OperatorCategory::elliptic_like &&
|
||||
Characteristics::valueStructure == OperatorValueStructure::scalar &&
|
||||
Characteristics::symmetry == OperatorSymmetry::symmetric &&
|
||||
(Characteristics::definiteness == OperatorDefiniteness::positive_definite ||
|
||||
Characteristics::definiteness == OperatorDefiniteness::positive_semidefinite) &&
|
||||
Characteristics::representation == OperatorRepresentation::assembled_sparse &&
|
||||
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
|
||||
(Characteristics::finiteElementSpace == OperatorFESpace::h1 ||
|
||||
Characteristics::finiteElementSpace == OperatorFESpace::l2) &&
|
||||
(Characteristics::nullspace == OperatorNullspace::none ||
|
||||
Characteristics::nullspace == OperatorNullspace::constant_mode);
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept Registered = Traits<std::remove_cvref_t<Candidate>>::registered;
|
||||
|
||||
namespace detail {
|
||||
template <
|
||||
typename Backend,
|
||||
typename Characteristics,
|
||||
bool = Registered<Backend> && OperatorCharacteristicsType<Characteristics>>
|
||||
struct IsCompatible : std::false_type { };
|
||||
|
||||
template <typename Backend, typename Characteristics>
|
||||
struct IsCompatible<Backend, Characteristics, true>
|
||||
: std::bool_constant<
|
||||
Traits<std::remove_cvref_t<Backend>>::template supports<std::remove_cvref_t<Characteristics>>> {
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename Backend, typename Characteristics>
|
||||
inline constexpr bool isCompatible = detail::IsCompatible<Backend, Characteristics>::value;
|
||||
|
||||
template <typename Backend, typename Characteristics>
|
||||
concept Compatible = isCompatible<Backend, Characteristics>;
|
||||
|
||||
template <Registered Backend>
|
||||
inline constexpr ApplicationContract applicationContract =
|
||||
Traits<std::remove_cvref_t<Backend>>::applicationContract;
|
||||
|
||||
template <typename Backend>
|
||||
concept ArnoldiAdmissible = Registered<Backend> && applicationContract<std::remove_cvref_t<Backend>> ==
|
||||
ApplicationContract::stationary_linear;
|
||||
|
||||
template <Registered Backend>
|
||||
inline constexpr bool requiresAssembledSparseSurrogate =
|
||||
Traits<std::remove_cvref_t<Backend>>::requiresAssembledSparseSurrogate;
|
||||
} // namespace backend
|
||||
} // namespace mean_field::preconditioning
|
||||
@@ -0,0 +1,422 @@
|
||||
module;
|
||||
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:preconditioning.backend_implementations;
|
||||
|
||||
export import :preconditioning.backend;
|
||||
|
||||
export namespace mean_field::preconditioning::backend {
|
||||
struct BackendStatistics final {
|
||||
std::uint64_t setups{0};
|
||||
std::uint64_t applications{0};
|
||||
std::uint64_t innerIterations{0};
|
||||
std::uint64_t lastInnerIterations{0};
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
inline void verifyApplicationDimensions(
|
||||
const mfem::Solver &solver,
|
||||
const mfem::Vector &rightHandSide,
|
||||
const mfem::Vector &action
|
||||
) {
|
||||
if (rightHandSide.Size() != solver.Width() || action.Size() != solver.Height()) {
|
||||
throw std::invalid_argument(
|
||||
"A prepared preconditioning backend requires compatible, preallocated input and output vectors."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
inline void verifySquarePositiveSize(
|
||||
const int height,
|
||||
const int width
|
||||
) {
|
||||
if (height <= 0 || height != width) {
|
||||
throw std::invalid_argument("A preconditioning backend requires a positive square operator.");
|
||||
}
|
||||
}
|
||||
|
||||
inline void configure(
|
||||
mfem::HypreBoomerAMG &solver,
|
||||
const FixedCycles &mode
|
||||
) {
|
||||
if (mode.cycles <= 0) {
|
||||
throw std::invalid_argument("Fixed-cycle AMG requires at least one cycle.");
|
||||
}
|
||||
solver.SetMaxIter(mode.cycles);
|
||||
solver.SetTol(0.0);
|
||||
solver.SetPrintLevel(0);
|
||||
solver.iterative_mode = false;
|
||||
}
|
||||
|
||||
inline void configure(
|
||||
mfem::HypreBoomerAMG &solver,
|
||||
const SolveToTolerance &mode
|
||||
) {
|
||||
if (!std::isfinite(mode.relativeTolerance) || mode.relativeTolerance <= 0.0 ||
|
||||
mode.relativeTolerance >= 1.0) {
|
||||
throw std::invalid_argument("Tolerance-driven AMG requires a finite relative tolerance in (0, 1).");
|
||||
}
|
||||
if (mode.maximumCycles <= 0) {
|
||||
throw std::invalid_argument("Tolerance-driven AMG requires at least one permitted cycle.");
|
||||
}
|
||||
solver.SetMaxIter(mode.maximumCycles);
|
||||
solver.SetTol(mode.relativeTolerance);
|
||||
solver.SetPrintLevel(0);
|
||||
solver.iterative_mode = false;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
class PreparedDiagonal final : public mfem::Solver {
|
||||
public:
|
||||
PreparedDiagonal(
|
||||
Diagonal configuration,
|
||||
const mfem::Vector &diagonal
|
||||
)
|
||||
: mfem::Solver(diagonal.Size()),
|
||||
m_configuration(std::move(configuration)) {
|
||||
Refresh(diagonal);
|
||||
}
|
||||
|
||||
PreparedDiagonal(const PreparedDiagonal &) = delete;
|
||||
PreparedDiagonal &operator=(const PreparedDiagonal &) = delete;
|
||||
PreparedDiagonal(PreparedDiagonal &&) = delete;
|
||||
PreparedDiagonal &operator=(PreparedDiagonal &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument("The diagonal backend received an operator with incompatible dimensions.");
|
||||
}
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
detail::verifyApplicationDimensions(*this, rightHandSide, action);
|
||||
for (int index = 0; index < Height(); ++index) {
|
||||
action(index) = m_inverseDiagonal(index) * rightHandSide(index);
|
||||
}
|
||||
++m_statistics.applications;
|
||||
}
|
||||
|
||||
void Refresh(const mfem::Vector &diagonal) {
|
||||
if (diagonal.Size() <= 0 || diagonal.Size() != Height()) {
|
||||
throw std::invalid_argument("The diagonal backend requires a positive diagonal of unchanged size.");
|
||||
}
|
||||
|
||||
m_inverseDiagonal.SetSize(diagonal.Size());
|
||||
for (int index = 0; index < diagonal.Size(); ++index) {
|
||||
const double entry = diagonal(index);
|
||||
if (!std::isfinite(entry) || entry == 0.0) {
|
||||
throw std::invalid_argument("The diagonal backend cannot invert a zero or non-finite entry.");
|
||||
}
|
||||
m_inverseDiagonal(index) = 1.0 / entry;
|
||||
}
|
||||
++m_statistics.setups;
|
||||
}
|
||||
|
||||
[[nodiscard]] const Diagonal &GetConfiguration() const noexcept {
|
||||
return m_configuration;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetInverseDiagonal() const noexcept {
|
||||
return m_inverseDiagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
Diagonal m_configuration;
|
||||
mfem::Vector m_inverseDiagonal;
|
||||
mutable BackendStatistics m_statistics;
|
||||
};
|
||||
|
||||
class PreparedMatrixFreeChebyshev final : public mfem::Solver {
|
||||
public:
|
||||
PreparedMatrixFreeChebyshev(
|
||||
MatrixFreeChebyshev configuration,
|
||||
const mfem::Operator &operation,
|
||||
const MPI_Comm communicator
|
||||
)
|
||||
: mfem::Solver(operation.Height()),
|
||||
m_configuration(std::move(configuration)),
|
||||
m_communicator(communicator) {
|
||||
ValidateConfiguration();
|
||||
Refresh(operation);
|
||||
}
|
||||
|
||||
PreparedMatrixFreeChebyshev(const PreparedMatrixFreeChebyshev &) = delete;
|
||||
PreparedMatrixFreeChebyshev &operator=(const PreparedMatrixFreeChebyshev &) = delete;
|
||||
PreparedMatrixFreeChebyshev(PreparedMatrixFreeChebyshev &&) = delete;
|
||||
PreparedMatrixFreeChebyshev &operator=(PreparedMatrixFreeChebyshev &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
Refresh(operation);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
detail::verifyApplicationDimensions(*this, rightHandSide, action);
|
||||
m_smoother->Mult(rightHandSide, action);
|
||||
++m_statistics.applications;
|
||||
m_statistics.lastInnerIterations = static_cast<std::uint64_t>(m_configuration.order);
|
||||
m_statistics.innerIterations += static_cast<std::uint64_t>(m_configuration.order);
|
||||
}
|
||||
|
||||
void Refresh(const mfem::Operator &operation) {
|
||||
detail::verifySquarePositiveSize(operation.Height(), operation.Width());
|
||||
if (operation.Height() != Height()) {
|
||||
throw std::invalid_argument("The matrix-free Chebyshev backend cannot change size during refresh.");
|
||||
}
|
||||
|
||||
operation.AssembleDiagonal(m_diagonal);
|
||||
if (m_diagonal.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The matrix-free Chebyshev backend received an incompatible assembled diagonal."
|
||||
);
|
||||
}
|
||||
for (int index = 0; index < m_diagonal.Size(); ++index) {
|
||||
if (!std::isfinite(m_diagonal(index)) || m_diagonal(index) <= 0.0) {
|
||||
throw std::invalid_argument(
|
||||
"The matrix-free Chebyshev backend requires a finite, strictly positive diagonal."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
m_operation = std::addressof(operation);
|
||||
m_essentialTrueDofs.SetSize(0);
|
||||
m_smoother = std::make_unique<mfem::OperatorChebyshevSmoother>(
|
||||
operation, m_diagonal, m_essentialTrueDofs, m_configuration.order, m_communicator,
|
||||
m_configuration.powerIterations, m_configuration.powerTolerance, m_configuration.powerSeed
|
||||
);
|
||||
m_smoother->iterative_mode = false;
|
||||
++m_statistics.setups;
|
||||
}
|
||||
|
||||
[[nodiscard]] const MatrixFreeChebyshev &GetConfiguration() const noexcept {
|
||||
return m_configuration;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetOperator() const noexcept {
|
||||
return *m_operation;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &GetDiagonal() const noexcept {
|
||||
return m_diagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
void ValidateConfiguration() const {
|
||||
if (m_configuration.order <= 0 || m_configuration.order > 5) {
|
||||
throw std::invalid_argument("Matrix-free Chebyshev requires a polynomial order in [1, 5].");
|
||||
}
|
||||
if (m_configuration.powerIterations <= 0) {
|
||||
throw std::invalid_argument("Matrix-free Chebyshev requires at least one power iteration.");
|
||||
}
|
||||
if (!std::isfinite(m_configuration.powerTolerance) || m_configuration.powerTolerance <= 0.0 ||
|
||||
m_configuration.powerTolerance >= 1.0) {
|
||||
throw std::invalid_argument(
|
||||
"Matrix-free Chebyshev requires a finite power-method tolerance strictly between zero and one."
|
||||
);
|
||||
}
|
||||
if (m_configuration.powerSeed <= 0) {
|
||||
throw std::invalid_argument("Matrix-free Chebyshev requires a strictly positive power-method seed.");
|
||||
}
|
||||
}
|
||||
|
||||
MatrixFreeChebyshev m_configuration;
|
||||
MPI_Comm m_communicator;
|
||||
const mfem::Operator *m_operation{nullptr};
|
||||
mfem::Vector m_diagonal;
|
||||
mfem::Array<int> m_essentialTrueDofs;
|
||||
std::unique_ptr<mfem::OperatorChebyshevSmoother> m_smoother;
|
||||
mutable BackendStatistics m_statistics;
|
||||
};
|
||||
|
||||
class PreparedDenseDirect final : public mfem::Solver {
|
||||
public:
|
||||
PreparedDenseDirect(
|
||||
DenseDirect configuration,
|
||||
const mfem::DenseMatrix &matrix
|
||||
)
|
||||
: mfem::Solver(matrix.Height()),
|
||||
m_configuration(std::move(configuration)) {
|
||||
Refresh(matrix);
|
||||
}
|
||||
|
||||
PreparedDenseDirect(const PreparedDenseDirect &) = delete;
|
||||
PreparedDenseDirect &operator=(const PreparedDenseDirect &) = delete;
|
||||
PreparedDenseDirect(PreparedDenseDirect &&) = delete;
|
||||
PreparedDenseDirect &operator=(PreparedDenseDirect &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
const auto *matrix = dynamic_cast<const mfem::DenseMatrix *>(&operation);
|
||||
if (matrix == nullptr) {
|
||||
throw std::invalid_argument("The dense-direct backend requires an mfem::DenseMatrix.");
|
||||
}
|
||||
Refresh(*matrix);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
detail::verifyApplicationDimensions(*this, rightHandSide, action);
|
||||
m_inverse->Mult(rightHandSide, action);
|
||||
++m_statistics.applications;
|
||||
}
|
||||
|
||||
void Refresh(const mfem::DenseMatrix &matrix) {
|
||||
detail::verifySquarePositiveSize(matrix.Height(), matrix.Width());
|
||||
if (matrix.Height() != Height()) {
|
||||
throw std::invalid_argument("The dense-direct backend cannot change size during refresh.");
|
||||
}
|
||||
|
||||
m_matrix = matrix;
|
||||
m_inverse = std::make_unique<mfem::DenseMatrixInverse>(m_matrix);
|
||||
++m_statistics.setups;
|
||||
}
|
||||
|
||||
[[nodiscard]] const DenseDirect &GetConfiguration() const noexcept {
|
||||
return m_configuration;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::DenseMatrix &GetDenseSurrogate() const noexcept {
|
||||
return m_matrix;
|
||||
}
|
||||
|
||||
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
DenseDirect m_configuration;
|
||||
mfem::DenseMatrix m_matrix;
|
||||
std::unique_ptr<mfem::DenseMatrixInverse> m_inverse;
|
||||
mutable BackendStatistics m_statistics;
|
||||
};
|
||||
|
||||
template <ApplicationMode Mode> class PreparedHypreBoomerAMG final : public mfem::Solver {
|
||||
public:
|
||||
using Configuration = HypreBoomerAMG<Mode>;
|
||||
|
||||
PreparedHypreBoomerAMG(
|
||||
Configuration configuration,
|
||||
const mfem::HypreParMatrix &matrix
|
||||
)
|
||||
: mfem::Solver(matrix.Height()),
|
||||
m_configuration(std::move(configuration)) {
|
||||
Refresh(matrix);
|
||||
}
|
||||
|
||||
PreparedHypreBoomerAMG(const PreparedHypreBoomerAMG &) = delete;
|
||||
PreparedHypreBoomerAMG &operator=(const PreparedHypreBoomerAMG &) = delete;
|
||||
PreparedHypreBoomerAMG(PreparedHypreBoomerAMG &&) = delete;
|
||||
PreparedHypreBoomerAMG &operator=(PreparedHypreBoomerAMG &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
const auto *matrix = dynamic_cast<const mfem::HypreParMatrix *>(&operation);
|
||||
if (matrix == nullptr) {
|
||||
throw std::invalid_argument("The BoomerAMG backend requires an mfem::HypreParMatrix surrogate.");
|
||||
}
|
||||
Refresh(*matrix);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
detail::verifyApplicationDimensions(*this, rightHandSide, action);
|
||||
m_solver->Mult(rightHandSide, action);
|
||||
|
||||
int iterations = 0;
|
||||
m_solver->GetNumIterations(iterations);
|
||||
++m_statistics.applications;
|
||||
m_statistics.lastInnerIterations = static_cast<std::uint64_t>(iterations);
|
||||
m_statistics.innerIterations += static_cast<std::uint64_t>(iterations);
|
||||
}
|
||||
|
||||
void Refresh(const mfem::HypreParMatrix &matrix) {
|
||||
detail::verifySquarePositiveSize(matrix.Height(), matrix.Width());
|
||||
if (matrix.Height() != Height()) {
|
||||
throw std::invalid_argument("The BoomerAMG backend cannot change size during refresh.");
|
||||
}
|
||||
|
||||
m_sparseSurrogate = std::addressof(matrix);
|
||||
m_solver = std::make_unique<mfem::HypreBoomerAMG>(matrix);
|
||||
detail::configure(*m_solver, m_configuration.application);
|
||||
|
||||
mfem::Vector setupRightHandSide(Width());
|
||||
mfem::Vector setupAction(Height());
|
||||
setupRightHandSide = 0.0;
|
||||
setupAction = 0.0;
|
||||
m_solver->Setup(setupRightHandSide, setupAction);
|
||||
++m_statistics.setups;
|
||||
}
|
||||
|
||||
[[nodiscard]] const Configuration &GetConfiguration() const noexcept {
|
||||
return m_configuration;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::HypreParMatrix &GetSparseSurrogate() const noexcept {
|
||||
return *m_sparseSurrogate;
|
||||
}
|
||||
|
||||
[[nodiscard]] const BackendStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
Configuration m_configuration;
|
||||
const mfem::HypreParMatrix *m_sparseSurrogate{nullptr};
|
||||
std::unique_ptr<mfem::HypreBoomerAMG> m_solver;
|
||||
mutable BackendStatistics m_statistics;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline PreparedDiagonal prepare(
|
||||
Diagonal configuration,
|
||||
const mfem::Vector &diagonal
|
||||
) {
|
||||
return PreparedDiagonal{std::move(configuration), diagonal};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline PreparedMatrixFreeChebyshev prepare(
|
||||
MatrixFreeChebyshev configuration,
|
||||
const mfem::Operator &operation,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
return PreparedMatrixFreeChebyshev{std::move(configuration), operation, communicator};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline PreparedDenseDirect prepare(
|
||||
DenseDirect configuration,
|
||||
const mfem::DenseMatrix &matrix
|
||||
) {
|
||||
return PreparedDenseDirect{std::move(configuration), matrix};
|
||||
}
|
||||
|
||||
template <ApplicationMode Mode>
|
||||
[[nodiscard]] PreparedHypreBoomerAMG<Mode> prepare(
|
||||
HypreBoomerAMG<Mode> configuration,
|
||||
const mfem::HypreParMatrix &matrix
|
||||
) {
|
||||
return PreparedHypreBoomerAMG<Mode>{std::move(configuration), matrix};
|
||||
}
|
||||
} // namespace mean_field::preconditioning::backend
|
||||
@@ -0,0 +1,402 @@
|
||||
module;
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:preconditioning.equilibrium_coordinates;
|
||||
|
||||
export import :preconditioning.specification_border;
|
||||
|
||||
export namespace mean_field::preconditioning {
|
||||
namespace detail {
|
||||
template <typename CandidateList, typename Universe> struct EquilibriumCoordinateListIsSubset;
|
||||
|
||||
template <typename... Candidates, typename Universe>
|
||||
struct EquilibriumCoordinateListIsSubset<utils::blocks::type_list<Candidates...>, Universe>
|
||||
: std::bool_constant<(utils::blocks::contains_type_v<Candidates, Universe> && ...)> { };
|
||||
} // namespace detail
|
||||
|
||||
template <typename Component, typename Form>
|
||||
concept EquilibriumCoordinateComponentFor =
|
||||
PreconditionerComponent<Component> && utils::blocks::block_form_is_valid_v<Form> &&
|
||||
std::remove_cvref_t<Component>::CorrectionBlocks::size == Form::value_block_count &&
|
||||
std::remove_cvref_t<Component>::ResidualBlocks::size == Form::residual_block_count &&
|
||||
detail::EquilibriumCoordinateListIsSubset<
|
||||
typename std::remove_cvref_t<Component>::CorrectionBlocks,
|
||||
typename Form::value_blocks>::value &&
|
||||
detail::EquilibriumCoordinateListIsSubset<
|
||||
typename std::remove_cvref_t<Component>::ResidualBlocks,
|
||||
typename Form::residual_blocks>::value;
|
||||
|
||||
struct EquilibriumCoordinateRange final {
|
||||
int equilibriumOffset{0};
|
||||
int preconditionerOffset{0};
|
||||
int size{0};
|
||||
|
||||
constexpr bool operator==(const EquilibriumCoordinateRange &) const = default;
|
||||
};
|
||||
|
||||
struct EquilibriumCoordinateMapStatistics final {
|
||||
std::uint64_t residualPacks{0};
|
||||
std::uint64_t residualUnpacks{0};
|
||||
std::uint64_t correctionPacks{0};
|
||||
std::uint64_t correctionUnpacks{0};
|
||||
};
|
||||
|
||||
template <typename Form, typename Component>
|
||||
requires EquilibriumCoordinateComponentFor<Component, Form>
|
||||
class EquilibriumPreconditionerCoordinateMap final {
|
||||
private:
|
||||
using ComponentType = std::remove_cvref_t<Component>;
|
||||
using Layout = utils::blocks::form_layout<Form>;
|
||||
|
||||
static constexpr std::size_t correctionBlockCount = ComponentType::CorrectionBlocks::size;
|
||||
static constexpr std::size_t residualBlockCount = ComponentType::ResidualBlocks::size;
|
||||
|
||||
public:
|
||||
explicit EquilibriumPreconditionerCoordinateMap(const Layout &layout)
|
||||
: m_correctionRanges(MakeCorrectionRanges(
|
||||
layout,
|
||||
typename ComponentType::CorrectionBlocks{}
|
||||
)),
|
||||
m_residualRanges(MakeResidualRanges(
|
||||
layout,
|
||||
typename ComponentType::ResidualBlocks{}
|
||||
)),
|
||||
m_equilibriumStateSize(layout.value_offsets().Last()),
|
||||
m_equilibriumResidualSize(layout.residual_offsets().Last()),
|
||||
m_preconditionerCorrectionSize(TotalSize(m_correctionRanges)),
|
||||
m_preconditionerResidualSize(TotalSize(m_residualRanges)) {
|
||||
if (m_preconditionerCorrectionSize != m_equilibriumStateSize ||
|
||||
m_preconditionerResidualSize != m_equilibriumResidualSize) {
|
||||
throw std::logic_error(
|
||||
"The typed preconditioner coordinate map does not span the complete equilibrium operator."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void PackResidual(
|
||||
const mfem::Vector &equilibriumResidual,
|
||||
mfem::Vector &preconditionerResidual
|
||||
) const {
|
||||
VerifySizes(
|
||||
equilibriumResidual, m_equilibriumResidualSize, preconditionerResidual, m_preconditionerResidualSize,
|
||||
"residual pack"
|
||||
);
|
||||
EquilibriumToPreconditioner(equilibriumResidual, preconditionerResidual, m_residualRanges);
|
||||
++m_statistics.residualPacks;
|
||||
}
|
||||
|
||||
void UnpackResidual(
|
||||
const mfem::Vector &preconditionerResidual,
|
||||
mfem::Vector &equilibriumResidual
|
||||
) const {
|
||||
VerifySizes(
|
||||
preconditionerResidual, m_preconditionerResidualSize, equilibriumResidual, m_equilibriumResidualSize,
|
||||
"residual unpack"
|
||||
);
|
||||
PreconditionerToEquilibrium(preconditionerResidual, equilibriumResidual, m_residualRanges);
|
||||
++m_statistics.residualUnpacks;
|
||||
}
|
||||
|
||||
void PackCorrection(
|
||||
const mfem::Vector &equilibriumCorrection,
|
||||
mfem::Vector &preconditionerCorrection
|
||||
) const {
|
||||
VerifySizes(
|
||||
equilibriumCorrection, m_equilibriumStateSize, preconditionerCorrection, m_preconditionerCorrectionSize,
|
||||
"correction pack"
|
||||
);
|
||||
EquilibriumToPreconditioner(equilibriumCorrection, preconditionerCorrection, m_correctionRanges);
|
||||
++m_statistics.correctionPacks;
|
||||
}
|
||||
|
||||
void UnpackCorrection(
|
||||
const mfem::Vector &preconditionerCorrection,
|
||||
mfem::Vector &equilibriumCorrection
|
||||
) const {
|
||||
VerifySizes(
|
||||
preconditionerCorrection, m_preconditionerCorrectionSize, equilibriumCorrection, m_equilibriumStateSize,
|
||||
"correction unpack"
|
||||
);
|
||||
PreconditionerToEquilibrium(preconditionerCorrection, equilibriumCorrection, m_correctionRanges);
|
||||
++m_statistics.correctionUnpacks;
|
||||
}
|
||||
|
||||
[[nodiscard]] int EquilibriumStateSize() const noexcept {
|
||||
return m_equilibriumStateSize;
|
||||
}
|
||||
|
||||
[[nodiscard]] int EquilibriumResidualSize() const noexcept {
|
||||
return m_equilibriumResidualSize;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PreconditionerCorrectionSize() const noexcept {
|
||||
return m_preconditionerCorrectionSize;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PreconditionerResidualSize() const noexcept {
|
||||
return m_preconditionerResidualSize;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
correctionBlockCount> &
|
||||
GetCorrectionRanges() const noexcept {
|
||||
return m_correctionRanges;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
residualBlockCount> &
|
||||
GetResidualRanges() const noexcept {
|
||||
return m_residualRanges;
|
||||
}
|
||||
|
||||
[[nodiscard]] const EquilibriumCoordinateMapStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename... Blocks>
|
||||
[[nodiscard]] static std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
sizeof...(Blocks)>
|
||||
MakeCorrectionRanges(
|
||||
const Layout &layout,
|
||||
utils::blocks::type_list<Blocks...>
|
||||
) {
|
||||
std::array<EquilibriumCoordinateRange, sizeof...(Blocks)> ranges{};
|
||||
int preconditionerOffset = 0;
|
||||
std::size_t range = 0;
|
||||
(
|
||||
[&] {
|
||||
constexpr int equilibriumBlock = utils::blocks::type_index_v<Blocks, typename Form::value_blocks>;
|
||||
const int size = layout.size(utils::blocks::value_block<equilibriumBlock>{});
|
||||
ranges[range++] = {
|
||||
.equilibriumOffset = layout.offset(utils::blocks::value_block<equilibriumBlock>{}),
|
||||
.preconditionerOffset = preconditionerOffset,
|
||||
.size = size
|
||||
};
|
||||
preconditionerOffset += size;
|
||||
}(),
|
||||
...);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
template <typename... Blocks>
|
||||
[[nodiscard]] static std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
sizeof...(Blocks)>
|
||||
MakeResidualRanges(
|
||||
const Layout &layout,
|
||||
utils::blocks::type_list<Blocks...>
|
||||
) {
|
||||
std::array<EquilibriumCoordinateRange, sizeof...(Blocks)> ranges{};
|
||||
int preconditionerOffset = 0;
|
||||
std::size_t range = 0;
|
||||
(
|
||||
[&] {
|
||||
constexpr int equilibriumBlock =
|
||||
utils::blocks::type_index_v<Blocks, typename Form::residual_blocks>;
|
||||
const int size = layout.size(utils::blocks::residual_block<equilibriumBlock>{});
|
||||
ranges[range++] = {
|
||||
.equilibriumOffset = layout.offset(utils::blocks::residual_block<equilibriumBlock>{}),
|
||||
.preconditionerOffset = preconditionerOffset,
|
||||
.size = size
|
||||
};
|
||||
preconditionerOffset += size;
|
||||
}(),
|
||||
...);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
[[nodiscard]] static int TotalSize(
|
||||
const std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
Size> &ranges
|
||||
) noexcept {
|
||||
int size = 0;
|
||||
for (const auto &range : ranges) {
|
||||
size += range.size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
static void EquilibriumToPreconditioner(
|
||||
const mfem::Vector &equilibrium,
|
||||
mfem::Vector &preconditioner,
|
||||
const std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
Size> &ranges
|
||||
) {
|
||||
for (const auto &range : ranges) {
|
||||
for (int index = 0; index < range.size; ++index) {
|
||||
preconditioner(range.preconditionerOffset + index) = equilibrium(range.equilibriumOffset + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
static void PreconditionerToEquilibrium(
|
||||
const mfem::Vector &preconditioner,
|
||||
mfem::Vector &equilibrium,
|
||||
const std::array<
|
||||
EquilibriumCoordinateRange,
|
||||
Size> &ranges
|
||||
) {
|
||||
for (const auto &range : ranges) {
|
||||
for (int index = 0; index < range.size; ++index) {
|
||||
equilibrium(range.equilibriumOffset + index) = preconditioner(range.preconditionerOffset + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void VerifySizes(
|
||||
const mfem::Vector &source,
|
||||
const int expectedSourceSize,
|
||||
const mfem::Vector &destination,
|
||||
const int expectedDestinationSize,
|
||||
const char *operation
|
||||
) {
|
||||
if (source.Size() != expectedSourceSize || destination.Size() != expectedDestinationSize) {
|
||||
throw std::invalid_argument(
|
||||
std::string("The equilibrium preconditioner ") + operation + " received an incompatible vector."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<EquilibriumCoordinateRange, correctionBlockCount> m_correctionRanges;
|
||||
std::array<EquilibriumCoordinateRange, residualBlockCount> m_residualRanges;
|
||||
int m_equilibriumStateSize;
|
||||
int m_equilibriumResidualSize;
|
||||
int m_preconditionerCorrectionSize;
|
||||
int m_preconditionerResidualSize;
|
||||
mutable EquilibriumCoordinateMapStatistics m_statistics;
|
||||
};
|
||||
|
||||
struct PreparedStellarPreconditionerStatistics final {
|
||||
std::uint64_t applications{0};
|
||||
std::uint64_t residualCoordinateMappings{0};
|
||||
std::uint64_t correctionCoordinateMappings{0};
|
||||
};
|
||||
|
||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem, SpecificationBorderBlockType Block>
|
||||
requires EquilibriumCoordinateComponentFor<Block, typename std::remove_cvref_t<Problem>::FormType>
|
||||
class PreparedStellarPreconditioner final : public mfem::Solver {
|
||||
private:
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
using BlockType = std::remove_cvref_t<Block>;
|
||||
|
||||
public:
|
||||
using Form = typename ProblemType::FormType;
|
||||
using BackendType = typename BlockType::BackendType;
|
||||
using GroupedPreconditioner = PreparedSpecificationBorderBlock<ProblemType, BlockType>;
|
||||
using CoordinateMap = EquilibriumPreconditionerCoordinateMap<Form, BlockType>;
|
||||
|
||||
PreparedStellarPreconditioner(
|
||||
const ProblemType &problem,
|
||||
BlockType block
|
||||
)
|
||||
: mfem::Solver(problem.StateSize()),
|
||||
m_grouped(
|
||||
problem,
|
||||
std::move(block)
|
||||
),
|
||||
m_coordinates(problem.GetManifest().layout()),
|
||||
m_groupedResidual(m_coordinates.PreconditionerResidualSize()),
|
||||
m_groupedCorrection(m_coordinates.PreconditionerCorrectionSize()) {
|
||||
if (problem.StateSize() != problem.EquationSize() || m_grouped.Height() != problem.StateSize() ||
|
||||
m_grouped.Width() != problem.EquationSize()) {
|
||||
throw std::logic_error(
|
||||
"The prepared stellar preconditioner is incompatible with the complete equilibrium operator."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
|
||||
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
|
||||
PreparedStellarPreconditioner(PreparedStellarPreconditioner &&) = delete;
|
||||
PreparedStellarPreconditioner &operator=(PreparedStellarPreconditioner &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument(
|
||||
"The prepared stellar preconditioner received an incompatible equilibrium operator."
|
||||
);
|
||||
}
|
||||
m_grouped.SetOperator(operation);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &equilibriumResidual,
|
||||
mfem::Vector &equilibriumCorrection
|
||||
) const override {
|
||||
if (equilibriumResidual.Size() != Width() || equilibriumCorrection.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The prepared stellar preconditioner requires compatible, preallocated equilibrium vectors."
|
||||
);
|
||||
}
|
||||
m_coordinates.PackResidual(equilibriumResidual, m_groupedResidual);
|
||||
++m_statistics.residualCoordinateMappings;
|
||||
m_grouped.Mult(m_groupedResidual, m_groupedCorrection);
|
||||
m_coordinates.UnpackCorrection(m_groupedCorrection, equilibriumCorrection);
|
||||
++m_statistics.correctionCoordinateMappings;
|
||||
++m_statistics.applications;
|
||||
}
|
||||
|
||||
[[nodiscard]] SpecificationBorderBlockPreparationReport Refresh() {
|
||||
return m_grouped.Refresh();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCurrent() const {
|
||||
return m_grouped.IsCurrent();
|
||||
}
|
||||
|
||||
[[nodiscard]] const BlockType &GetBlock() const noexcept {
|
||||
return m_grouped.GetBlock();
|
||||
}
|
||||
|
||||
[[nodiscard]] const GroupedPreconditioner &GetGroupedPreconditioner() const noexcept {
|
||||
return m_grouped;
|
||||
}
|
||||
|
||||
[[nodiscard]] const CoordinateMap &GetCoordinateMap() const noexcept {
|
||||
return m_coordinates;
|
||||
}
|
||||
|
||||
[[nodiscard]] const PreparedStellarPreconditionerStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
GroupedPreconditioner m_grouped;
|
||||
CoordinateMap m_coordinates;
|
||||
mutable mfem::Vector m_groupedResidual;
|
||||
mutable mfem::Vector m_groupedCorrection;
|
||||
mutable PreparedStellarPreconditionerStatistics m_statistics;
|
||||
};
|
||||
|
||||
template <
|
||||
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
||||
SpecificationBorderBlockType Block>
|
||||
requires EquilibriumCoordinateComponentFor<
|
||||
Block,
|
||||
typename std::remove_cvref_t<Problem>::FormType>
|
||||
[[nodiscard]] auto prepare(
|
||||
const Problem &problem,
|
||||
Block block
|
||||
) {
|
||||
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
|
||||
}
|
||||
} // namespace mean_field::preconditioning
|
||||
676
libmeanfield/interface/preconditioning/gravity_field.cppm
Normal file
676
libmeanfield/interface/preconditioning/gravity_field.cppm
Normal file
@@ -0,0 +1,676 @@
|
||||
module;
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:preconditioning.gravity_field;
|
||||
|
||||
export import :fem;
|
||||
export import :operators.context.gravity_field;
|
||||
export import :preconditioning.backend_implementations;
|
||||
export import :preconditioning.plan;
|
||||
|
||||
export namespace mean_field::preconditioning {
|
||||
struct GravityBlockDiagonal final { };
|
||||
struct GravityLowerTriangular final { };
|
||||
struct GravityUpperTriangular final { };
|
||||
struct GravityApproximateLDU final { };
|
||||
|
||||
template <typename Candidate> struct IsGravityFactorizationPolicy : std::false_type { };
|
||||
|
||||
template <> struct IsGravityFactorizationPolicy<GravityBlockDiagonal> : std::true_type { };
|
||||
template <> struct IsGravityFactorizationPolicy<GravityLowerTriangular> : std::true_type { };
|
||||
template <> struct IsGravityFactorizationPolicy<GravityUpperTriangular> : std::true_type { };
|
||||
template <> struct IsGravityFactorizationPolicy<GravityApproximateLDU> : std::true_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept GravityFactorizationPolicy = IsGravityFactorizationPolicy<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
using GravityMassInverseCharacteristics = OperatorCharacteristics<
|
||||
OperatorCategory::mass_like,
|
||||
OperatorValueStructure::vector,
|
||||
OperatorSymmetry::symmetric,
|
||||
OperatorDefiniteness::positive_definite,
|
||||
OperatorRepresentation::matrix_free,
|
||||
OperatorDistribution::distributed_true_dof,
|
||||
OperatorFESpace::h_div>;
|
||||
|
||||
using GravityPotentialSchurCharacteristics = OperatorCharacteristics<
|
||||
OperatorCategory::elliptic_like,
|
||||
OperatorValueStructure::scalar,
|
||||
OperatorSymmetry::symmetric,
|
||||
OperatorDefiniteness::positive_semidefinite,
|
||||
OperatorRepresentation::assembled_sparse,
|
||||
OperatorDistribution::distributed_true_dof,
|
||||
OperatorFESpace::l2,
|
||||
OperatorNullspace::constant_mode>;
|
||||
|
||||
using CoupledGravityCharacteristics = OperatorCharacteristics<
|
||||
OperatorCategory::mixed,
|
||||
OperatorValueStructure::block,
|
||||
OperatorSymmetry::symmetric,
|
||||
OperatorDefiniteness::indefinite,
|
||||
OperatorRepresentation::matrix_free,
|
||||
OperatorDistribution::distributed_true_dof,
|
||||
OperatorFESpace::product>;
|
||||
|
||||
namespace backend {
|
||||
template <Registered MassInverseBackend, Registered PotentialSchurBackend, GravityFactorizationPolicy Policy>
|
||||
requires Compatible<MassInverseBackend, GravityMassInverseCharacteristics> &&
|
||||
Compatible<PotentialSchurBackend, GravityPotentialSchurCharacteristics>
|
||||
struct CoupledGravity final {
|
||||
using MassBackendType = MassInverseBackend;
|
||||
using PotentialSchurBackendType = PotentialSchurBackend;
|
||||
using FactorizationPolicyType = Policy;
|
||||
};
|
||||
|
||||
template <Registered MassInverseBackend, Registered PotentialSchurBackend, GravityFactorizationPolicy Policy>
|
||||
requires Compatible<MassInverseBackend, GravityMassInverseCharacteristics> &&
|
||||
Compatible<PotentialSchurBackend, GravityPotentialSchurCharacteristics>
|
||||
struct Traits<CoupledGravity<MassInverseBackend, PotentialSchurBackend, Policy>> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract =
|
||||
::mean_field::preconditioning::backend::applicationContract<MassInverseBackend> ==
|
||||
ApplicationContract::stationary_linear &&
|
||||
::mean_field::preconditioning::backend::applicationContract<PotentialSchurBackend> ==
|
||||
ApplicationContract::stationary_linear
|
||||
? ApplicationContract::stationary_linear
|
||||
: ApplicationContract::flexible;
|
||||
static constexpr bool supportsSerialExecution = false;
|
||||
static constexpr bool supportsDistributedExecution = true;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::symmetric;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
|
||||
static constexpr bool requiresAssembledSparseSurrogate = true;
|
||||
|
||||
using PreparationDependencies = preconditioning::PreparationDependencies<
|
||||
PreparationDependency::discretization,
|
||||
PreparationDependency::geometry,
|
||||
PreparationDependency::equation_of_state,
|
||||
PreparationDependency::linearization>;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports =
|
||||
Characteristics::category == OperatorCategory::mixed &&
|
||||
Characteristics::valueStructure == OperatorValueStructure::block &&
|
||||
Characteristics::symmetry == OperatorSymmetry::symmetric &&
|
||||
Characteristics::definiteness == OperatorDefiniteness::indefinite &&
|
||||
Characteristics::representation == OperatorRepresentation::matrix_free &&
|
||||
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
|
||||
Characteristics::finiteElementSpace == OperatorFESpace::product;
|
||||
};
|
||||
} // namespace backend
|
||||
|
||||
template <
|
||||
backend::Registered MassBackendT,
|
||||
backend::Registered PotentialSchurBackendT,
|
||||
GravityFactorizationPolicy FactorizationPolicyT>
|
||||
requires backend::Compatible<MassBackendT, GravityMassInverseCharacteristics> &&
|
||||
backend::Compatible<PotentialSchurBackendT, GravityPotentialSchurCharacteristics>
|
||||
class GravityFieldBlock final {
|
||||
public:
|
||||
using CorrectionBlocks =
|
||||
utils::blocks::type_list<utils::blocks::gravity::gradient::value, utils::blocks::gravity::poisson::value>;
|
||||
using ResidualBlocks = utils::blocks::
|
||||
type_list<utils::blocks::gravity::gradient::residual, utils::blocks::gravity::poisson::residual>;
|
||||
using RequiredCouplings = utils::blocks::type_list<
|
||||
Coupling<utils::blocks::gravity::gradient::residual, utils::blocks::gravity::gradient::value>,
|
||||
Coupling<utils::blocks::gravity::gradient::residual, utils::blocks::gravity::poisson::value>,
|
||||
Coupling<utils::blocks::gravity::poisson::residual, utils::blocks::gravity::gradient::value>>;
|
||||
using OperatorDescription = CoupledGravityCharacteristics;
|
||||
using BackendType = backend::CoupledGravity<MassBackendT, PotentialSchurBackendT, FactorizationPolicyT>;
|
||||
using PreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
|
||||
using MassBackend = MassBackendT;
|
||||
using PotentialSchurBackend = PotentialSchurBackendT;
|
||||
using Factorization = FactorizationPolicyT;
|
||||
|
||||
constexpr GravityFieldBlock(
|
||||
MassBackendT massInverseBackend = {},
|
||||
PotentialSchurBackendT potentialSchurBackend = {},
|
||||
FactorizationPolicyT factorizationPolicy = {}
|
||||
)
|
||||
: m_massInverseBackend(std::move(massInverseBackend)),
|
||||
m_potentialSchurBackend(std::move(potentialSchurBackend)),
|
||||
m_factorizationPolicy(std::move(factorizationPolicy)) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const MassBackendT &massInverseBackend() const noexcept {
|
||||
return m_massInverseBackend;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const PotentialSchurBackendT &potentialSchurBackend() const noexcept {
|
||||
return m_potentialSchurBackend;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const FactorizationPolicyT &factorizationPolicy() const noexcept {
|
||||
return m_factorizationPolicy;
|
||||
}
|
||||
|
||||
private:
|
||||
MassBackendT m_massInverseBackend;
|
||||
PotentialSchurBackendT m_potentialSchurBackend;
|
||||
FactorizationPolicyT m_factorizationPolicy;
|
||||
};
|
||||
|
||||
template <
|
||||
typename MassInverseBackend,
|
||||
typename PotentialSchurBackend,
|
||||
typename FactorizationPolicy>
|
||||
GravityFieldBlock(
|
||||
MassInverseBackend,
|
||||
PotentialSchurBackend,
|
||||
FactorizationPolicy
|
||||
)
|
||||
-> GravityFieldBlock<
|
||||
MassInverseBackend,
|
||||
PotentialSchurBackend,
|
||||
FactorizationPolicy>;
|
||||
|
||||
struct GravityFactorizationStatistics final {
|
||||
std::uint64_t applications{0};
|
||||
std::uint64_t massInverseApplications{0};
|
||||
std::uint64_t potentialSchurApplications{0};
|
||||
std::uint64_t divergenceApplications{0};
|
||||
std::uint64_t transposeDivergenceApplications{0};
|
||||
};
|
||||
|
||||
template <GravityFactorizationPolicy Policy> class GravityFactorizationOperator final : public mfem::Solver {
|
||||
public:
|
||||
GravityFactorizationOperator(
|
||||
Policy policy,
|
||||
const mfem::Solver &massInverse,
|
||||
const mfem::Solver &potentialSchurInverse,
|
||||
const mfem::Operator &divergence
|
||||
)
|
||||
: mfem::Solver(massInverse.Height() + potentialSchurInverse.Height()),
|
||||
m_policy(std::move(policy)),
|
||||
m_massInverse(std::addressof(massInverse)),
|
||||
m_potentialSchurInverse(std::addressof(potentialSchurInverse)),
|
||||
m_divergence(std::addressof(divergence)),
|
||||
m_offsets(3),
|
||||
m_potentialWorkspace(potentialSchurInverse.Height()),
|
||||
m_gradientWorkspace(massInverse.Height()),
|
||||
m_massCorrection(massInverse.Height()) {
|
||||
if (massInverse.Height() <= 0 || massInverse.Height() != massInverse.Width()) {
|
||||
throw std::invalid_argument("The gravity factorization requires a square gradient-mass inverse.");
|
||||
}
|
||||
if (potentialSchurInverse.Height() <= 0 ||
|
||||
potentialSchurInverse.Height() != potentialSchurInverse.Width()) {
|
||||
throw std::invalid_argument("The gravity factorization requires a square potential-Schur inverse.");
|
||||
}
|
||||
if (divergence.Width() != massInverse.Width() || divergence.Height() != potentialSchurInverse.Width()) {
|
||||
throw std::invalid_argument("The gravity divergence does not connect the supplied inverse blocks.");
|
||||
}
|
||||
|
||||
m_offsets[0] = 0;
|
||||
m_offsets[1] = massInverse.Height();
|
||||
m_offsets[2] = Height();
|
||||
}
|
||||
|
||||
GravityFactorizationOperator(const GravityFactorizationOperator &) = delete;
|
||||
GravityFactorizationOperator &operator=(const GravityFactorizationOperator &) = delete;
|
||||
GravityFactorizationOperator(GravityFactorizationOperator &&) = delete;
|
||||
GravityFactorizationOperator &operator=(GravityFactorizationOperator &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument("The gravity factorization received an operator of incompatible size.");
|
||||
}
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The gravity factorization requires compatible, preallocated input and output vectors."
|
||||
);
|
||||
}
|
||||
|
||||
const mfem::Vector gradientRightHandSide(
|
||||
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_offsets[0], m_offsets[1] - m_offsets[0]
|
||||
);
|
||||
const mfem::Vector potentialRightHandSide(
|
||||
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_offsets[1], m_offsets[2] - m_offsets[1]
|
||||
);
|
||||
mfem::Vector gradientAction(action.GetData() + m_offsets[0], m_offsets[1] - m_offsets[0]);
|
||||
mfem::Vector potentialAction(action.GetData() + m_offsets[1], m_offsets[2] - m_offsets[1]);
|
||||
|
||||
if constexpr (std::same_as<Policy, GravityBlockDiagonal>) {
|
||||
m_massInverse->Mult(gradientRightHandSide, gradientAction);
|
||||
m_potentialSchurInverse->Mult(potentialRightHandSide, potentialAction);
|
||||
++m_statistics.massInverseApplications;
|
||||
++m_statistics.potentialSchurApplications;
|
||||
} else if constexpr (std::same_as<Policy, GravityLowerTriangular>) {
|
||||
m_massInverse->Mult(gradientRightHandSide, gradientAction);
|
||||
m_divergence->Mult(gradientAction, m_potentialWorkspace);
|
||||
m_potentialWorkspace -= potentialRightHandSide;
|
||||
m_potentialSchurInverse->Mult(m_potentialWorkspace, potentialAction);
|
||||
++m_statistics.massInverseApplications;
|
||||
++m_statistics.divergenceApplications;
|
||||
++m_statistics.potentialSchurApplications;
|
||||
} else if constexpr (std::same_as<Policy, GravityUpperTriangular>) {
|
||||
m_potentialWorkspace = potentialRightHandSide;
|
||||
m_potentialWorkspace *= -1.0;
|
||||
m_potentialSchurInverse->Mult(m_potentialWorkspace, potentialAction);
|
||||
m_divergence->MultTranspose(potentialAction, m_gradientWorkspace);
|
||||
m_gradientWorkspace *= -1.0;
|
||||
m_gradientWorkspace += gradientRightHandSide;
|
||||
m_massInverse->Mult(m_gradientWorkspace, gradientAction);
|
||||
++m_statistics.potentialSchurApplications;
|
||||
++m_statistics.transposeDivergenceApplications;
|
||||
++m_statistics.massInverseApplications;
|
||||
} else {
|
||||
static_assert(std::same_as<Policy, GravityApproximateLDU>);
|
||||
m_massInverse->Mult(gradientRightHandSide, gradientAction);
|
||||
m_divergence->Mult(gradientAction, m_potentialWorkspace);
|
||||
m_potentialWorkspace -= potentialRightHandSide;
|
||||
m_potentialSchurInverse->Mult(m_potentialWorkspace, potentialAction);
|
||||
m_divergence->MultTranspose(potentialAction, m_gradientWorkspace);
|
||||
m_massInverse->Mult(m_gradientWorkspace, m_massCorrection);
|
||||
gradientAction -= m_massCorrection;
|
||||
m_statistics.massInverseApplications += 2;
|
||||
++m_statistics.divergenceApplications;
|
||||
++m_statistics.potentialSchurApplications;
|
||||
++m_statistics.transposeDivergenceApplications;
|
||||
}
|
||||
|
||||
++m_statistics.applications;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
|
||||
return m_offsets;
|
||||
}
|
||||
|
||||
[[nodiscard]] const GravityFactorizationStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
Policy m_policy;
|
||||
const mfem::Solver *m_massInverse;
|
||||
const mfem::Solver *m_potentialSchurInverse;
|
||||
const mfem::Operator *m_divergence;
|
||||
mfem::Array<int> m_offsets;
|
||||
mutable mfem::Vector m_potentialWorkspace;
|
||||
mutable mfem::Vector m_gradientWorkspace;
|
||||
mutable mfem::Vector m_massCorrection;
|
||||
mutable GravityFactorizationStatistics m_statistics;
|
||||
};
|
||||
|
||||
class ReducedGravityDivergenceOperator final : public mfem::Operator {
|
||||
public:
|
||||
ReducedGravityDivergenceOperator(
|
||||
const mfem::Operator &trueDofDivergence,
|
||||
field::FieldDofMap gradientMap,
|
||||
field::FieldDofMap potentialMap
|
||||
)
|
||||
: mfem::Operator(
|
||||
potentialMap.reduced_size(),
|
||||
gradientMap.reduced_size()
|
||||
),
|
||||
m_trueDofDivergence(std::addressof(trueDofDivergence)),
|
||||
m_gradientMap(std::move(gradientMap)),
|
||||
m_potentialMap(std::move(potentialMap)),
|
||||
m_gradientTrue(m_gradientMap.full_size()),
|
||||
m_potentialTrue(m_potentialMap.full_size()) {
|
||||
VerifyOperator(trueDofDivergence);
|
||||
}
|
||||
|
||||
void Rebind(const mfem::Operator &trueDofDivergence) {
|
||||
VerifyOperator(trueDofDivergence);
|
||||
m_trueDofDivergence = std::addressof(trueDofDivergence);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &gradient,
|
||||
mfem::Vector &potentialAction
|
||||
) const override {
|
||||
if (gradient.Size() != Width() || potentialAction.Size() != Height()) {
|
||||
throw std::invalid_argument("The reduced gravity divergence received incompatible vectors.");
|
||||
}
|
||||
m_gradientMap.scatter(gradient, m_gradientTrue);
|
||||
m_trueDofDivergence->Mult(m_gradientTrue, m_potentialTrue);
|
||||
m_potentialMap.gather(m_potentialTrue, potentialAction);
|
||||
}
|
||||
|
||||
void MultTranspose(
|
||||
const mfem::Vector &potential,
|
||||
mfem::Vector &gradientAction
|
||||
) const override {
|
||||
if (potential.Size() != Height() || gradientAction.Size() != Width()) {
|
||||
throw std::invalid_argument("The reduced transpose divergence received incompatible vectors.");
|
||||
}
|
||||
m_potentialMap.scatter(potential, m_potentialTrue);
|
||||
m_trueDofDivergence->MultTranspose(m_potentialTrue, m_gradientTrue);
|
||||
m_gradientMap.gather(m_gradientTrue, gradientAction);
|
||||
}
|
||||
|
||||
private:
|
||||
void VerifyOperator(const mfem::Operator &operation) const {
|
||||
if (operation.Width() != m_gradientMap.full_size() || operation.Height() != m_potentialMap.full_size()) {
|
||||
throw std::invalid_argument("The true-DOF divergence is incompatible with the gravity field maps.");
|
||||
}
|
||||
}
|
||||
|
||||
const mfem::Operator *m_trueDofDivergence;
|
||||
field::FieldDofMap m_gradientMap;
|
||||
field::FieldDofMap m_potentialMap;
|
||||
mutable mfem::Vector m_gradientTrue;
|
||||
mutable mfem::Vector m_potentialTrue;
|
||||
};
|
||||
|
||||
class ReducedFieldSolverAdapter final : public mfem::Solver {
|
||||
public:
|
||||
ReducedFieldSolverAdapter(
|
||||
const mfem::Solver &trueDofSolver,
|
||||
field::FieldDofMap map
|
||||
)
|
||||
: mfem::Solver(map.reduced_size()),
|
||||
m_trueDofSolver(std::addressof(trueDofSolver)),
|
||||
m_map(std::move(map)),
|
||||
m_rightHandSideTrue(m_map.full_size()),
|
||||
m_actionTrue(m_map.full_size()) {
|
||||
if (trueDofSolver.Height() != m_map.full_size() || trueDofSolver.Width() != m_map.full_size()) {
|
||||
throw std::invalid_argument("The true-DOF solver is incompatible with the reduced field map.");
|
||||
}
|
||||
}
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument("The reduced field solver received an operator of incompatible size.");
|
||||
}
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
|
||||
throw std::invalid_argument("The reduced field solver received incompatible vectors.");
|
||||
}
|
||||
m_map.scatter(rightHandSide, m_rightHandSideTrue);
|
||||
m_trueDofSolver->Mult(m_rightHandSideTrue, m_actionTrue);
|
||||
m_map.gather(m_actionTrue, action);
|
||||
}
|
||||
|
||||
private:
|
||||
const mfem::Solver *m_trueDofSolver;
|
||||
field::FieldDofMap m_map;
|
||||
mutable mfem::Vector m_rightHandSideTrue;
|
||||
mutable mfem::Vector m_actionTrue;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> assembleGravityDivergenceSurrogate(const fem::FEM &f);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> assembleGravityPotentialSchurSurrogate(
|
||||
const fem::FEM &f,
|
||||
const mfem::Vector &trueMassDiagonal
|
||||
);
|
||||
|
||||
struct GravityFieldBlockPreparationReport final {
|
||||
bool discretizationChanged{false};
|
||||
bool geometryChanged{false};
|
||||
bool rebuiltMassInverse{false};
|
||||
bool rebuiltDivergenceBinding{false};
|
||||
bool rebuiltPotentialSchur{false};
|
||||
|
||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||
return rebuiltMassInverse || rebuiltDivergenceBinding || rebuiltPotentialSchur;
|
||||
}
|
||||
};
|
||||
|
||||
struct PreparedGravityFieldBlockStatistics final {
|
||||
std::uint64_t setups{0};
|
||||
std::uint64_t refreshChecks{0};
|
||||
std::uint64_t refreshes{0};
|
||||
std::uint64_t noOpRefreshes{0};
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept ImplementedGravityMassBackend = std::same_as<std::remove_cvref_t<Candidate>, backend::Diagonal> ||
|
||||
std::same_as<std::remove_cvref_t<Candidate>, backend::MatrixFreeChebyshev>;
|
||||
|
||||
template <backend::Registered MassBackend, backend::ApplicationMode Mode, GravityFactorizationPolicy Policy>
|
||||
requires ImplementedGravityMassBackend<MassBackend> &&
|
||||
backend::Compatible<MassBackend, GravityMassInverseCharacteristics>
|
||||
class PreparedGravityFieldBlock final : public mfem::Solver {
|
||||
public:
|
||||
using Block = GravityFieldBlock<MassBackend, backend::HypreBoomerAMG<Mode>, Policy>;
|
||||
using PreparedMassInverse = std::conditional_t<
|
||||
std::same_as<MassBackend, backend::Diagonal>,
|
||||
backend::PreparedDiagonal,
|
||||
backend::PreparedMatrixFreeChebyshev>;
|
||||
|
||||
PreparedGravityFieldBlock(
|
||||
const fem::FEM &f,
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
|
||||
Block block
|
||||
)
|
||||
: mfem::Solver(GravitySize(geometryContext)),
|
||||
m_block(std::move(block)),
|
||||
m_geometryContext(std::addressof(geometryContext)),
|
||||
m_gradientMap(geometryContext.GetMassOperator().GetFluxMap()),
|
||||
m_potentialMap(geometryContext.GetSourceOperator().GetPotentialMap()),
|
||||
m_divergence(
|
||||
geometryContext.GetDivergenceOperator(),
|
||||
m_gradientMap,
|
||||
m_potentialMap
|
||||
),
|
||||
m_massInverse(MakeMassInverse(
|
||||
f,
|
||||
geometryContext,
|
||||
m_block.massInverseBackend()
|
||||
)),
|
||||
m_potentialSchurSurrogate(AssemblePotentialSchur(
|
||||
f,
|
||||
geometryContext
|
||||
)),
|
||||
m_potentialSchurInverse(
|
||||
m_block.potentialSchurBackend(),
|
||||
*m_potentialSchurSurrogate
|
||||
),
|
||||
m_reducedPotentialSchurInverse(
|
||||
m_potentialSchurInverse,
|
||||
m_potentialMap
|
||||
),
|
||||
m_factorization(
|
||||
m_block.factorizationPolicy(),
|
||||
m_massInverse,
|
||||
m_reducedPotentialSchurInverse,
|
||||
m_divergence
|
||||
),
|
||||
m_discretizationRevision(geometryContext.GetDiscretizationRevision()),
|
||||
m_displacementRevision(geometryContext.GetDisplacementRevision()) {
|
||||
if (!geometryContext.IsPrepared()) {
|
||||
throw std::logic_error("The gravity field block requires a prepared gravity geometry context.");
|
||||
}
|
||||
m_statistics.setups = 1;
|
||||
}
|
||||
|
||||
PreparedGravityFieldBlock(const PreparedGravityFieldBlock &) = delete;
|
||||
PreparedGravityFieldBlock &operator=(const PreparedGravityFieldBlock &) = delete;
|
||||
PreparedGravityFieldBlock(PreparedGravityFieldBlock &&) = delete;
|
||||
PreparedGravityFieldBlock &operator=(PreparedGravityFieldBlock &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
m_factorization.SetOperator(operation);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
if (!IsCurrent()) {
|
||||
throw std::logic_error("The gravity field block is stale; refresh it before application.");
|
||||
}
|
||||
m_factorization.Mult(rightHandSide, action);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCurrent() const noexcept {
|
||||
return m_geometryContext->IsPrepared() &&
|
||||
m_geometryContext->GetDiscretizationRevision() == m_discretizationRevision &&
|
||||
m_geometryContext->GetDisplacementRevision() == m_displacementRevision;
|
||||
}
|
||||
|
||||
[[nodiscard]] GravityFieldBlockPreparationReport Refresh(
|
||||
const fem::FEM &f,
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
|
||||
) {
|
||||
if (!geometryContext.IsPrepared()) {
|
||||
throw std::logic_error("The gravity field block cannot refresh from unprepared geometry.");
|
||||
}
|
||||
if (std::addressof(geometryContext) != m_geometryContext) {
|
||||
throw std::invalid_argument("A prepared gravity field block cannot change geometry-context identity.");
|
||||
}
|
||||
|
||||
++m_statistics.refreshChecks;
|
||||
GravityFieldBlockPreparationReport report{
|
||||
.discretizationChanged = geometryContext.GetDiscretizationRevision() != m_discretizationRevision,
|
||||
.geometryChanged = geometryContext.GetDisplacementRevision() != m_displacementRevision
|
||||
};
|
||||
if (!report.discretizationChanged && !report.geometryChanged) {
|
||||
++m_statistics.noOpRefreshes;
|
||||
return report;
|
||||
}
|
||||
|
||||
m_divergence.Rebind(geometryContext.GetDivergenceOperator());
|
||||
report.rebuiltDivergenceBinding = report.discretizationChanged;
|
||||
|
||||
RefreshMassInverse(geometryContext);
|
||||
report.rebuiltMassInverse = true;
|
||||
|
||||
auto potentialSchur = AssemblePotentialSchur(f, geometryContext);
|
||||
m_potentialSchurInverse.Refresh(*potentialSchur);
|
||||
m_potentialSchurSurrogate = std::move(potentialSchur);
|
||||
report.rebuiltPotentialSchur = true;
|
||||
|
||||
m_discretizationRevision = geometryContext.GetDiscretizationRevision();
|
||||
m_displacementRevision = geometryContext.GetDisplacementRevision();
|
||||
++m_statistics.refreshes;
|
||||
return report;
|
||||
}
|
||||
|
||||
[[nodiscard]] const Block &GetBlock() const noexcept {
|
||||
return m_block;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &GetOffsets() const noexcept {
|
||||
return m_factorization.GetOffsets();
|
||||
}
|
||||
|
||||
[[nodiscard]] const PreparedMassInverse &GetMassInverse() const {
|
||||
if (!IsCurrent()) {
|
||||
throw std::logic_error("The gravity mass inverse is stale; refresh its owning gravity block first.");
|
||||
}
|
||||
return m_massInverse;
|
||||
}
|
||||
|
||||
[[nodiscard]] const backend::PreparedHypreBoomerAMG<Mode> &GetPotentialSchurInverse() const noexcept {
|
||||
return m_potentialSchurInverse;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::HypreParMatrix &GetPotentialSchurSurrogate() const noexcept {
|
||||
return *m_potentialSchurSurrogate;
|
||||
}
|
||||
|
||||
[[nodiscard]] const GravityFactorizationOperator<Policy> &GetFactorization() const noexcept {
|
||||
return m_factorization;
|
||||
}
|
||||
|
||||
[[nodiscard]] const PreparedGravityFieldBlockStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static int
|
||||
GravitySize(const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext) {
|
||||
if (!geometryContext.IsPrepared()) {
|
||||
throw std::logic_error("The gravity field block requires a prepared gravity geometry context.");
|
||||
}
|
||||
return geometryContext.GetMassOperator().GetFluxMap().reduced_size() +
|
||||
geometryContext.GetSourceOperator().GetPotentialMap().reduced_size();
|
||||
}
|
||||
|
||||
[[nodiscard]] static mfem::Vector AssembleReducedMassDiagonal(
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
|
||||
) {
|
||||
mfem::Vector diagonal;
|
||||
geometryContext.GetMassOperator().AssembleDiagonal(diagonal);
|
||||
return diagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] static PreparedMassInverse MakeMassInverse(
|
||||
const fem::FEM &f,
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
|
||||
const MassBackend &backendConfiguration
|
||||
) {
|
||||
if constexpr (std::same_as<MassBackend, backend::Diagonal>) {
|
||||
return PreparedMassInverse{backendConfiguration, AssembleReducedMassDiagonal(geometryContext)};
|
||||
} else {
|
||||
static_assert(std::same_as<MassBackend, backend::MatrixFreeChebyshev>);
|
||||
return PreparedMassInverse{
|
||||
backendConfiguration, geometryContext.GetMassOperator(), f.gravityFluxFes->GetComm()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshMassInverse(const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext) {
|
||||
if constexpr (std::same_as<MassBackend, backend::Diagonal>) {
|
||||
m_massInverse.Refresh(AssembleReducedMassDiagonal(geometryContext));
|
||||
} else {
|
||||
static_assert(std::same_as<MassBackend, backend::MatrixFreeChebyshev>);
|
||||
m_massInverse.Refresh(geometryContext.GetMassOperator());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<mfem::HypreParMatrix> AssemblePotentialSchur(
|
||||
const fem::FEM &f,
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
|
||||
) {
|
||||
mfem::Vector trueMassDiagonal;
|
||||
geometryContext.GetMassOperator().AssembleTrueDiagonal(trueMassDiagonal);
|
||||
return assembleGravityPotentialSchurSurrogate(f, trueMassDiagonal);
|
||||
}
|
||||
|
||||
Block m_block;
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext *m_geometryContext;
|
||||
field::FieldDofMap m_gradientMap;
|
||||
field::FieldDofMap m_potentialMap;
|
||||
ReducedGravityDivergenceOperator m_divergence;
|
||||
PreparedMassInverse m_massInverse;
|
||||
std::unique_ptr<mfem::HypreParMatrix> m_potentialSchurSurrogate;
|
||||
backend::PreparedHypreBoomerAMG<Mode> m_potentialSchurInverse;
|
||||
ReducedFieldSolverAdapter m_reducedPotentialSchurInverse;
|
||||
GravityFactorizationOperator<Policy> m_factorization;
|
||||
operators::context::gravity_field::DiscretizationRevision m_discretizationRevision;
|
||||
operators::context::gravity_field::DisplacementRevision m_displacementRevision;
|
||||
PreparedGravityFieldBlockStatistics m_statistics;
|
||||
};
|
||||
|
||||
template <
|
||||
backend::Registered MassBackend,
|
||||
backend::ApplicationMode Mode,
|
||||
GravityFactorizationPolicy Policy>
|
||||
requires ImplementedGravityMassBackend<MassBackend> && backend::Compatible<
|
||||
MassBackend,
|
||||
GravityMassInverseCharacteristics>
|
||||
[[nodiscard]] auto prepare(
|
||||
const fem::FEM &f,
|
||||
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext,
|
||||
GravityFieldBlock<
|
||||
MassBackend,
|
||||
backend::HypreBoomerAMG<Mode>,
|
||||
Policy> block
|
||||
) {
|
||||
return PreparedGravityFieldBlock<MassBackend, Mode, Policy>{f, geometryContext, std::move(block)};
|
||||
}
|
||||
} // namespace mean_field::preconditioning
|
||||
2096
libmeanfield/interface/preconditioning/material_surface.cppm
Normal file
2096
libmeanfield/interface/preconditioning/material_surface.cppm
Normal file
File diff suppressed because it is too large
Load Diff
381
libmeanfield/interface/preconditioning/plan.cppm
Normal file
381
libmeanfield/interface/preconditioning/plan.cppm
Normal file
@@ -0,0 +1,381 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
export module mean_field:preconditioning.plan;
|
||||
|
||||
export import :preconditioning.backend;
|
||||
export import :utils.blocks;
|
||||
|
||||
export namespace mean_field::preconditioning {
|
||||
template <typename ResidualBlock, typename CorrectionBlock> struct Coupling final {
|
||||
using Residual = ResidualBlock;
|
||||
using Correction = CorrectionBlock;
|
||||
};
|
||||
|
||||
template <
|
||||
typename CorrectionBlockList,
|
||||
typename ResidualBlockList,
|
||||
typename RequiredCouplingList,
|
||||
typename Characteristics,
|
||||
typename Backend,
|
||||
typename PreparationRequirements =
|
||||
typename backend::Traits<std::remove_cvref_t<Backend>>::PreparationDependencies>
|
||||
struct ComponentDeclaration {
|
||||
using CorrectionBlocks = CorrectionBlockList;
|
||||
using ResidualBlocks = ResidualBlockList;
|
||||
using RequiredCouplings = RequiredCouplingList;
|
||||
using OperatorDescription = Characteristics;
|
||||
using BackendType = Backend;
|
||||
using PreparationDependencies = PreparationRequirements;
|
||||
};
|
||||
|
||||
template <typename CorrectionBlock, typename ResidualBlock>
|
||||
requires std::derived_from<CorrectionBlock, utils::blocks::value_block_base> &&
|
||||
std::derived_from<ResidualBlock, utils::blocks::residual_block_base>
|
||||
struct IdentityBlock final {
|
||||
using CorrectionBlocks = utils::blocks::type_list<CorrectionBlock>;
|
||||
using ResidualBlocks = utils::blocks::type_list<ResidualBlock>;
|
||||
using RequiredCouplings = utils::blocks::type_list<>;
|
||||
using OperatorDescription = IdentityOperatorCharacteristics;
|
||||
using BackendType = backend::Identity;
|
||||
using PreparationDependencies = NoPreparationDependencies;
|
||||
};
|
||||
|
||||
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 Candidate>
|
||||
inline constexpr bool isTypeList = IsTypeList<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
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 Candidate> struct IsCoupling : std::false_type { };
|
||||
|
||||
template <typename ResidualBlock, typename CorrectionBlock>
|
||||
struct IsCoupling<Coupling<ResidualBlock, CorrectionBlock>>
|
||||
: std::bool_constant<
|
||||
std::derived_from<ResidualBlock, utils::blocks::residual_block_base> &&
|
||||
std::derived_from<CorrectionBlock, utils::blocks::value_block_base>> { };
|
||||
|
||||
template <typename Candidate> struct IsCouplingList : std::false_type { };
|
||||
|
||||
template <typename... Couplings>
|
||||
struct IsCouplingList<utils::blocks::type_list<Couplings...>>
|
||||
: std::bool_constant<
|
||||
(IsCoupling<Couplings>::value && ...) &&
|
||||
utils::blocks::types_are_unique_v<utils::blocks::type_list<Couplings...>>> { };
|
||||
|
||||
template <typename Candidate, typename = void> struct ComponentTraits {
|
||||
static constexpr bool valid = false;
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
struct ComponentTraits<
|
||||
Candidate,
|
||||
std::void_t<
|
||||
typename Candidate::CorrectionBlocks,
|
||||
typename Candidate::ResidualBlocks,
|
||||
typename Candidate::RequiredCouplings,
|
||||
typename Candidate::OperatorDescription,
|
||||
typename Candidate::BackendType,
|
||||
typename Candidate::PreparationDependencies>> {
|
||||
using CorrectionBlocks = typename Candidate::CorrectionBlocks;
|
||||
using ResidualBlocks = typename Candidate::ResidualBlocks;
|
||||
using RequiredCouplings = typename Candidate::RequiredCouplings;
|
||||
using OperatorDescription = typename Candidate::OperatorDescription;
|
||||
using BackendType = typename Candidate::BackendType;
|
||||
using PreparationDependencies = typename Candidate::PreparationDependencies;
|
||||
using BackendPreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
|
||||
|
||||
static constexpr bool valid =
|
||||
IsUniqueDerivedBlockList<CorrectionBlocks, utils::blocks::value_block_base>::value &&
|
||||
IsUniqueDerivedBlockList<ResidualBlocks, utils::blocks::residual_block_base>::value &&
|
||||
IsCouplingList<RequiredCouplings>::value && OperatorCharacteristicsType<OperatorDescription> &&
|
||||
backend::Registered<BackendType> && backend::isCompatible<BackendType, OperatorDescription> &&
|
||||
PreparationDependenciesType<PreparationDependencies> &&
|
||||
((PreparationDependencies::mask & BackendPreparationDependencies::mask) ==
|
||||
BackendPreparationDependencies::mask);
|
||||
};
|
||||
|
||||
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 Type> struct Append<utils::blocks::type_list<Types...>, Type> {
|
||||
using Result = utils::blocks::type_list<Types..., Type>;
|
||||
};
|
||||
|
||||
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Result;
|
||||
|
||||
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 <bool AllowsOverlap, typename... Components> class PlanStorage {
|
||||
public:
|
||||
using ComponentTypes = utils::blocks::type_list<Components...>;
|
||||
using CorrectionBlocks = ConcatenateT<typename ComponentTraits<Components>::CorrectionBlocks...>;
|
||||
using ResidualBlocks = ConcatenateT<typename ComponentTraits<Components>::ResidualBlocks...>;
|
||||
using RequiredCouplings = ConcatenateT<typename ComponentTraits<Components>::RequiredCouplings...>;
|
||||
|
||||
static constexpr bool allowsOverlappingOwnership = AllowsOverlap;
|
||||
static constexpr bool stationaryLinear =
|
||||
((backend::applicationContract<typename ComponentTraits<Components>::BackendType> ==
|
||||
ApplicationContract::stationary_linear) &&
|
||||
...);
|
||||
|
||||
constexpr explicit PlanStorage(Components... components) : m_components(std::move(components)...) {
|
||||
}
|
||||
|
||||
template <typename Component> [[nodiscard]] constexpr const Component &component() const noexcept {
|
||||
return std::get<Component>(m_components);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const std::tuple<Components...> &components() const noexcept {
|
||||
return m_components;
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Components...> m_components;
|
||||
};
|
||||
|
||||
template <
|
||||
bool ComponentsAreValid,
|
||||
typename DeclaredCorrectionBlocks,
|
||||
typename DeclaredResidualBlocks,
|
||||
typename DeclaredCouplings,
|
||||
typename... Components>
|
||||
struct CoherentPlanDeclaration : std::false_type { };
|
||||
|
||||
template <
|
||||
typename DeclaredCorrectionBlocks,
|
||||
typename DeclaredResidualBlocks,
|
||||
typename DeclaredCouplings,
|
||||
typename... Components>
|
||||
struct CoherentPlanDeclaration<
|
||||
true,
|
||||
DeclaredCorrectionBlocks,
|
||||
DeclaredResidualBlocks,
|
||||
DeclaredCouplings,
|
||||
Components...>
|
||||
: std::bool_constant<
|
||||
std::same_as<
|
||||
DeclaredCorrectionBlocks,
|
||||
ConcatenateT<typename ComponentTraits<Components>::CorrectionBlocks...>> &&
|
||||
std::same_as<
|
||||
DeclaredResidualBlocks,
|
||||
ConcatenateT<typename ComponentTraits<Components>::ResidualBlocks...>> &&
|
||||
std::same_as<
|
||||
DeclaredCouplings,
|
||||
ConcatenateT<typename ComponentTraits<Components>::RequiredCouplings...>>> { };
|
||||
|
||||
template <
|
||||
typename ComponentList,
|
||||
typename DeclaredCorrectionBlocks,
|
||||
typename DeclaredResidualBlocks,
|
||||
typename DeclaredCouplings>
|
||||
struct PlanDeclarationIsCoherent : std::false_type { };
|
||||
|
||||
template <
|
||||
typename... Components,
|
||||
typename DeclaredCorrectionBlocks,
|
||||
typename DeclaredResidualBlocks,
|
||||
typename DeclaredCouplings>
|
||||
struct PlanDeclarationIsCoherent<
|
||||
utils::blocks::type_list<Components...>,
|
||||
DeclaredCorrectionBlocks,
|
||||
DeclaredResidualBlocks,
|
||||
DeclaredCouplings>
|
||||
: CoherentPlanDeclaration<
|
||||
(ComponentTraits<Components>::valid && ...),
|
||||
DeclaredCorrectionBlocks,
|
||||
DeclaredResidualBlocks,
|
||||
DeclaredCouplings,
|
||||
Components...> { };
|
||||
|
||||
template <typename Candidate, typename = void> struct PlanTraits {
|
||||
static constexpr bool valid = false;
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
struct PlanTraits<
|
||||
Candidate,
|
||||
std::void_t<
|
||||
typename Candidate::ComponentTypes,
|
||||
typename Candidate::CorrectionBlocks,
|
||||
typename Candidate::ResidualBlocks,
|
||||
typename Candidate::RequiredCouplings>> {
|
||||
static constexpr bool valid =
|
||||
isTypeList<typename Candidate::ComponentTypes> && isTypeList<typename Candidate::CorrectionBlocks> &&
|
||||
isTypeList<typename Candidate::ResidualBlocks> && isTypeList<typename Candidate::RequiredCouplings> &&
|
||||
PlanDeclarationIsCoherent<
|
||||
typename Candidate::ComponentTypes,
|
||||
typename Candidate::CorrectionBlocks,
|
||||
typename Candidate::ResidualBlocks,
|
||||
typename Candidate::RequiredCouplings>::value;
|
||||
};
|
||||
|
||||
template <typename CouplingList, typename JacobianForm> struct CouplingsExistInJacobian;
|
||||
|
||||
template <typename JacobianForm>
|
||||
struct CouplingsExistInJacobian<utils::blocks::type_list<>, JacobianForm> : std::true_type { };
|
||||
|
||||
template <typename Residual, typename Correction, typename... Remaining, typename JacobianForm>
|
||||
struct CouplingsExistInJacobian<
|
||||
utils::blocks::type_list<Coupling<Residual, Correction>, Remaining...>,
|
||||
JacobianForm>
|
||||
: std::bool_constant<
|
||||
utils::blocks::has_jacobian_coupling_v<Residual, Correction, JacobianForm> &&
|
||||
CouplingsExistInJacobian<utils::blocks::type_list<Remaining...>, JacobianForm>::value> { };
|
||||
|
||||
template <typename Plan, typename JacobianForm, bool = PlanTraits<std::remove_cvref_t<Plan>>::valid>
|
||||
struct RequiredCouplingsExist : std::false_type { };
|
||||
|
||||
template <typename Plan, typename JacobianForm>
|
||||
struct RequiredCouplingsExist<Plan, JacobianForm, true>
|
||||
: CouplingsExistInJacobian<typename std::remove_cvref_t<Plan>::RequiredCouplings, JacobianForm> { };
|
||||
} // namespace detail
|
||||
|
||||
template <typename Candidate>
|
||||
concept PreconditionerComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||
|
||||
template <PreconditionerComponent... Components>
|
||||
class PreconditionerPlan final : public detail::PlanStorage<false, Components...> {
|
||||
using Base = detail::PlanStorage<false, Components...>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
template <typename... Components> PreconditionerPlan(Components...) -> PreconditionerPlan<Components...>;
|
||||
|
||||
template <PreconditionerComponent... Components>
|
||||
class OverlappingPreconditionerPlan final : public detail::PlanStorage<true, Components...> {
|
||||
using Base = detail::PlanStorage<true, Components...>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
template <typename... Components>
|
||||
OverlappingPreconditionerPlan(Components...) -> OverlappingPreconditionerPlan<Components...>;
|
||||
|
||||
template <typename Candidate>
|
||||
concept PreconditionerPlanType = detail::PlanTraits<std::remove_cvref_t<Candidate>>::valid;
|
||||
|
||||
template <typename Form, typename Plan>
|
||||
requires utils::blocks::block_form_is_valid_v<Form> && PreconditionerPlanType<Plan>
|
||||
struct PreconditionerCoverage final {
|
||||
using DeclaredCorrectionBlocks = typename Plan::CorrectionBlocks;
|
||||
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
|
||||
|
||||
using MissingCorrectionBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredCorrectionBlocks>;
|
||||
using UnexpectedCorrectionBlocks =
|
||||
detail::ListDifferenceT<DeclaredCorrectionBlocks, typename Form::value_blocks>;
|
||||
using RepeatedCorrectionBlocks = detail::RepeatedTypesT<DeclaredCorrectionBlocks>;
|
||||
|
||||
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 hasEveryCorrectionBlock = MissingCorrectionBlocks::size == 0;
|
||||
static constexpr bool hasOnlyCorrectionBlocks = UnexpectedCorrectionBlocks::size == 0;
|
||||
static constexpr bool hasUniqueCorrectionOwners =
|
||||
Plan::allowsOverlappingOwnership || RepeatedCorrectionBlocks::size == 0;
|
||||
|
||||
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
|
||||
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
|
||||
static constexpr bool hasUniqueResidualOwners =
|
||||
Plan::allowsOverlappingOwnership || RepeatedResidualBlocks::size == 0;
|
||||
|
||||
static constexpr bool complete = hasEveryCorrectionBlock && hasOnlyCorrectionBlocks &&
|
||||
hasUniqueCorrectionOwners && hasEveryResidualBlock && hasOnlyResidualBlocks &&
|
||||
hasUniqueResidualOwners;
|
||||
};
|
||||
|
||||
template <typename Plan, typename Form>
|
||||
concept CompletePreconditionerFor = utils::blocks::block_form_is_valid_v<Form> && PreconditionerPlanType<Plan> &&
|
||||
PreconditionerCoverage<Form, std::remove_cvref_t<Plan>>::complete;
|
||||
|
||||
template <typename Plan, typename JacobianForm>
|
||||
inline constexpr bool requiredCouplingsExist = detail::RequiredCouplingsExist<Plan, JacobianForm>::value;
|
||||
|
||||
template <typename Plan, typename Form, typename JacobianForm>
|
||||
concept CompatiblePreconditionerFor =
|
||||
CompletePreconditionerFor<Plan, Form> && utils::blocks::valid_jacobian_form<Form, JacobianForm> &&
|
||||
requiredCouplingsExist<Plan, JacobianForm>;
|
||||
|
||||
template <typename Plan>
|
||||
concept StationaryLinearPreconditionerPlan =
|
||||
PreconditionerPlanType<Plan> && std::remove_cvref_t<Plan>::stationaryLinear;
|
||||
} // namespace mean_field::preconditioning
|
||||
11
libmeanfield/interface/preconditioning/preconditioning.cppm
Normal file
11
libmeanfield/interface/preconditioning/preconditioning.cppm
Normal file
@@ -0,0 +1,11 @@
|
||||
export module mean_field:preconditioning;
|
||||
|
||||
export import :preconditioning.backend;
|
||||
export import :preconditioning.backend_implementations;
|
||||
export import :preconditioning.gravity_field;
|
||||
export import :preconditioning.material_surface;
|
||||
export import :preconditioning.plan;
|
||||
export import :preconditioning.stellar_equilibrium;
|
||||
export import :preconditioning.stellar_structure;
|
||||
export import :preconditioning.specification_border;
|
||||
export import :preconditioning.equilibrium_coordinates;
|
||||
1077
libmeanfield/interface/preconditioning/specification_border.cppm
Normal file
1077
libmeanfield/interface/preconditioning/specification_border.cppm
Normal file
File diff suppressed because it is too large
Load Diff
513
libmeanfield/interface/preconditioning/stellar_equilibrium.cppm
Normal file
513
libmeanfield/interface/preconditioning/stellar_equilibrium.cppm
Normal file
@@ -0,0 +1,513 @@
|
||||
module;
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:preconditioning.stellar_equilibrium;
|
||||
|
||||
export import :operators.stellar_equilibrium_problem;
|
||||
export import :preconditioning.plan;
|
||||
|
||||
export namespace mean_field::preconditioning {
|
||||
struct StellarPreconditionerLifecycleSnapshot final {
|
||||
operators::StellarEquilibriumDependencyStamp discretization;
|
||||
operators::StellarEquilibriumDependencyStamp geometry;
|
||||
const void *equationOfStateIdentity{nullptr};
|
||||
operators::StellarEquilibriumDependencies linearization;
|
||||
|
||||
constexpr bool operator==(const StellarPreconditionerLifecycleSnapshot &) const = default;
|
||||
};
|
||||
|
||||
struct StellarPreconditionerPreparationChanges final {
|
||||
bool discretization{false};
|
||||
bool geometry{false};
|
||||
bool equationOfState{false};
|
||||
bool linearization{false};
|
||||
|
||||
[[nodiscard]] constexpr bool Any() const noexcept {
|
||||
return discretization || geometry || equationOfState || linearization;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Contains(const PreparationDependency dependency) const noexcept {
|
||||
switch (dependency) {
|
||||
case PreparationDependency::discretization:
|
||||
return discretization;
|
||||
case PreparationDependency::geometry:
|
||||
return geometry;
|
||||
case PreparationDependency::equation_of_state:
|
||||
return equationOfState;
|
||||
case PreparationDependency::linearization:
|
||||
return linearization;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr StellarPreconditionerPreparationChanges preparationChanges(
|
||||
const StellarPreconditionerLifecycleSnapshot &prepared,
|
||||
const StellarPreconditionerLifecycleSnapshot ¤t
|
||||
) noexcept {
|
||||
return {
|
||||
.discretization = prepared.discretization != current.discretization,
|
||||
.geometry = prepared.geometry != current.geometry,
|
||||
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
|
||||
.linearization = prepared.linearization != current.linearization
|
||||
};
|
||||
}
|
||||
|
||||
struct StellarPreconditionerPreparationReport final {
|
||||
StellarPreconditionerPreparationChanges changes;
|
||||
std::uint64_t refreshedComponents{0};
|
||||
|
||||
[[nodiscard]] constexpr bool DidAnyWork() const noexcept {
|
||||
return refreshedComponents != 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct StellarPreconditionerStatistics final {
|
||||
std::uint64_t setups{0};
|
||||
std::uint64_t refreshChecks{0};
|
||||
std::uint64_t refreshes{0};
|
||||
std::uint64_t noOpRefreshes{0};
|
||||
std::uint64_t componentSetups{0};
|
||||
std::uint64_t componentRefreshes{0};
|
||||
std::uint64_t operatorBindings{0};
|
||||
std::uint64_t applications{0};
|
||||
std::uint64_t backendApplications{0};
|
||||
std::uint64_t innerIterations{0};
|
||||
double setupSeconds{0.0};
|
||||
double refreshSeconds{0.0};
|
||||
double applicationSeconds{0.0};
|
||||
double maximumApplicationSeconds{0.0};
|
||||
};
|
||||
|
||||
template <typename Candidate> struct StellarEquilibriumProblemTraits {
|
||||
static constexpr bool registered = false;
|
||||
};
|
||||
|
||||
template <equilibrium::StellarEquilibriumModel Model>
|
||||
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model>> {
|
||||
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
|
||||
using Form = typename Problem::FormType;
|
||||
using JacobianForm = typename Problem::JacobianFormType;
|
||||
using Manifest = typename Problem::ManifestType;
|
||||
|
||||
static constexpr bool registered = true;
|
||||
|
||||
[[nodiscard]] static bool IsPrepared(const Problem &problem) noexcept {
|
||||
return problem.IsPrepared();
|
||||
}
|
||||
|
||||
[[nodiscard]] static int StateSize(const Problem &problem) noexcept {
|
||||
return problem.StateSize();
|
||||
}
|
||||
|
||||
[[nodiscard]] static int EquationSize(const Problem &problem) noexcept {
|
||||
return problem.EquationSize();
|
||||
}
|
||||
|
||||
[[nodiscard]] static const Manifest &ManifestOf(const Problem &problem) noexcept {
|
||||
return problem.GetManifest();
|
||||
}
|
||||
|
||||
[[nodiscard]] static const mfem::Operator &LinearizationOperator(const Problem &problem) noexcept {
|
||||
return problem.GetLinearizationOperator();
|
||||
}
|
||||
|
||||
[[nodiscard]] static StellarPreconditionerLifecycleSnapshot Snapshot(const Problem &problem) {
|
||||
const operators::StellarEquilibriumDependencies &dependencies = problem.GetLinearizationDependencies();
|
||||
return {
|
||||
.discretization = dependencies.discretization,
|
||||
.geometry = problem.GetGeometryDependency(),
|
||||
.equationOfStateIdentity =
|
||||
std::addressof(problem.GetStellarModel().template specification<eos::Polytrope>()),
|
||||
.linearization = dependencies
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
|
||||
|
||||
namespace backend {
|
||||
template <typename Component, typename Problem, typename Backend = typename Component::BackendType>
|
||||
class PreparedComponent;
|
||||
|
||||
template <typename Component, StellarPreconditionerProblem Problem>
|
||||
class PreparedComponent<Component, Problem, Identity> final {
|
||||
public:
|
||||
void Setup(
|
||||
const Problem &,
|
||||
const Component &
|
||||
) noexcept {
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Refresh(
|
||||
const Problem &,
|
||||
const Component &,
|
||||
const StellarPreconditionerPreparationChanges &
|
||||
) noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Component, typename Problem>
|
||||
concept PreparedComponentFor = requires(
|
||||
PreparedComponent<Component, Problem> &prepared,
|
||||
const Problem &problem,
|
||||
const Component &component,
|
||||
const StellarPreconditionerPreparationChanges &changes
|
||||
) {
|
||||
prepared.Setup(problem, component);
|
||||
{ prepared.Refresh(problem, component, changes) } -> std::same_as<bool>;
|
||||
};
|
||||
} // namespace backend
|
||||
|
||||
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 <> struct IdentityPlanForForm<utils::blocks::surface_deformed_stellar_equilibrium_form> {
|
||||
using Type = PreconditionerPlan<
|
||||
DensityIdentity,
|
||||
SurfaceIdentity,
|
||||
GravityGradientIdentity,
|
||||
GravityPotentialIdentity,
|
||||
EnthalpyIdentity,
|
||||
FixedMassIdentity>;
|
||||
|
||||
[[nodiscard]] static constexpr Type Make() {
|
||||
return Type{DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{},
|
||||
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{}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ComponentList> struct UsesOnlyIdentityBackends : std::false_type { };
|
||||
|
||||
template <typename... Components>
|
||||
struct UsesOnlyIdentityBackends<utils::blocks::type_list<Components...>>
|
||||
: std::bool_constant<(std::same_as<typename Components::BackendType, backend::Identity> && ...)> { };
|
||||
|
||||
template <typename ComponentList, typename Problem> struct PreparedComponentTuple;
|
||||
|
||||
template <typename... Components, typename Problem>
|
||||
struct PreparedComponentTuple<utils::blocks::type_list<Components...>, Problem> {
|
||||
using Type = std::tuple<backend::PreparedComponent<Components, Problem>...>;
|
||||
|
||||
static constexpr bool available = (backend::PreparedComponentFor<Components, Problem> && ...);
|
||||
};
|
||||
|
||||
template <typename Requirements>
|
||||
[[nodiscard]] constexpr bool
|
||||
componentRequiresRefresh(const StellarPreconditionerPreparationChanges &changes) noexcept {
|
||||
return (Requirements::contains(PreparationDependency::discretization) && changes.discretization) ||
|
||||
(Requirements::contains(PreparationDependency::geometry) && changes.geometry) ||
|
||||
(Requirements::contains(PreparationDependency::equation_of_state) && changes.equationOfState) ||
|
||||
(Requirements::contains(PreparationDependency::linearization) && changes.linearization);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename Plan, typename Problem>
|
||||
concept PreparedPreconditionerPlanFor =
|
||||
StellarPreconditionerProblem<Problem> && PreconditionerPlanType<Plan> &&
|
||||
CompletePreconditionerFor<Plan, typename StellarEquilibriumProblemTraits<Problem>::Form> &&
|
||||
CompatiblePreconditionerFor<
|
||||
Plan,
|
||||
typename StellarEquilibriumProblemTraits<Problem>::Form,
|
||||
typename StellarEquilibriumProblemTraits<Problem>::JacobianForm> &&
|
||||
(!std::remove_cvref_t<Plan>::allowsOverlappingOwnership) &&
|
||||
detail::UsesOnlyIdentityBackends<typename std::remove_cvref_t<Plan>::ComponentTypes>::value &&
|
||||
detail::PreparedComponentTuple<
|
||||
typename std::remove_cvref_t<Plan>::ComponentTypes,
|
||||
std::remove_cvref_t<Problem>>::available;
|
||||
|
||||
template <StellarPreconditionerProblem Problem>
|
||||
using IdentityPreconditionerPlanFor = typename detail::IdentityPlanForForm<
|
||||
typename StellarEquilibriumProblemTraits<std::remove_cvref_t<Problem>>::Form>::Type;
|
||||
|
||||
template <StellarPreconditionerProblem Problem>
|
||||
[[nodiscard]] constexpr IdentityPreconditionerPlanFor<Problem> makeIdentityPlan(const Problem &) {
|
||||
using Form = typename StellarEquilibriumProblemTraits<std::remove_cvref_t<Problem>>::Form;
|
||||
return detail::IdentityPlanForForm<Form>::Make();
|
||||
}
|
||||
|
||||
template <StellarPreconditionerProblem Problem, typename Plan>
|
||||
requires PreparedPreconditionerPlanFor<Plan, Problem>
|
||||
class StellarEquilibriumPreconditioner final : public mfem::Solver {
|
||||
private:
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
using PlanType = std::remove_cvref_t<Plan>;
|
||||
using Traits = StellarEquilibriumProblemTraits<ProblemType>;
|
||||
using Components = typename PlanType::ComponentTypes;
|
||||
using PreparedComponents = typename detail::PreparedComponentTuple<Components, ProblemType>::Type;
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
public:
|
||||
using FormType = typename Traits::Form;
|
||||
using JacobianFormType = typename Traits::JacobianForm;
|
||||
|
||||
StellarEquilibriumPreconditioner(
|
||||
ProblemType &problem,
|
||||
PlanType plan
|
||||
)
|
||||
: mfem::Solver(Traits::StateSize(problem)),
|
||||
m_problem(std::addressof(problem)),
|
||||
m_manifest(std::addressof(Traits::ManifestOf(problem))),
|
||||
m_linearization(std::addressof(Traits::LinearizationOperator(problem))),
|
||||
m_plan(std::move(plan)) {
|
||||
const Clock::time_point start = Clock::now();
|
||||
VerifyPreparedProblem();
|
||||
SetupComponents(std::make_index_sequence<std::tuple_size_v<PreparedComponents>>{});
|
||||
m_snapshot = Traits::Snapshot(*m_problem);
|
||||
m_statistics.setups = 1;
|
||||
m_statistics.setupSeconds = std::chrono::duration<double>(Clock::now() - start).count();
|
||||
}
|
||||
|
||||
StellarEquilibriumPreconditioner(const StellarEquilibriumPreconditioner &) = delete;
|
||||
StellarEquilibriumPreconditioner &operator=(const StellarEquilibriumPreconditioner &) = delete;
|
||||
StellarEquilibriumPreconditioner(StellarEquilibriumPreconditioner &&) = delete;
|
||||
StellarEquilibriumPreconditioner &operator=(StellarEquilibriumPreconditioner &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument(
|
||||
"The stellar-equilibrium preconditioner received an operator with incompatible dimensions."
|
||||
);
|
||||
}
|
||||
++m_statistics.operatorBindings;
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &residual,
|
||||
mfem::Vector &correction
|
||||
) const override {
|
||||
VerifyCurrent();
|
||||
if (residual.Size() != Width()) {
|
||||
throw std::invalid_argument(
|
||||
"The stellar-equilibrium preconditioner received a residual with the wrong size."
|
||||
);
|
||||
}
|
||||
if (correction.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The stellar-equilibrium preconditioner requires a preallocated correction of the correct size."
|
||||
);
|
||||
}
|
||||
|
||||
const Clock::time_point start = Clock::now();
|
||||
correction = residual;
|
||||
const double elapsed = std::chrono::duration<double>(Clock::now() - start).count();
|
||||
|
||||
++m_statistics.applications;
|
||||
++m_statistics.backendApplications;
|
||||
m_statistics.applicationSeconds += elapsed;
|
||||
m_statistics.maximumApplicationSeconds = std::max(m_statistics.maximumApplicationSeconds, elapsed);
|
||||
}
|
||||
|
||||
[[nodiscard]] StellarPreconditionerPreparationReport Refresh() {
|
||||
const Clock::time_point start = Clock::now();
|
||||
VerifyPreparedProblem();
|
||||
|
||||
const StellarPreconditionerLifecycleSnapshot current = Traits::Snapshot(*m_problem);
|
||||
const StellarPreconditionerPreparationChanges changes = preparationChanges(m_snapshot, current);
|
||||
++m_statistics.refreshChecks;
|
||||
|
||||
StellarPreconditionerPreparationReport report{.changes = changes};
|
||||
if (!changes.Any()) {
|
||||
++m_statistics.noOpRefreshes;
|
||||
} else {
|
||||
report.refreshedComponents =
|
||||
RefreshComponents(changes, std::make_index_sequence<std::tuple_size_v<PreparedComponents>>{});
|
||||
++m_statistics.refreshes;
|
||||
m_statistics.componentRefreshes += report.refreshedComponents;
|
||||
m_snapshot = current;
|
||||
}
|
||||
|
||||
m_statistics.refreshSeconds += std::chrono::duration<double>(Clock::now() - start).count();
|
||||
return report;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCurrent() const {
|
||||
return Traits::IsPrepared(*m_problem) && Traits::Snapshot(*m_problem) == m_snapshot;
|
||||
}
|
||||
|
||||
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
|
||||
return *m_problem;
|
||||
}
|
||||
|
||||
[[nodiscard]] const typename Traits::Manifest &GetManifest() const noexcept {
|
||||
return *m_manifest;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Operator &GetLinearizationOperator() const noexcept {
|
||||
return *m_linearization;
|
||||
}
|
||||
|
||||
template <typename CorrectionBlock>
|
||||
requires utils::blocks::contains_type_v<
|
||||
CorrectionBlock,
|
||||
typename FormType::value_blocks>
|
||||
[[nodiscard]] mfem::Vector GetCorrectionBlock(mfem::Vector &correction) const {
|
||||
if (correction.Size() != Height()) {
|
||||
throw std::invalid_argument("A correction block view requires a complete correction vector.");
|
||||
}
|
||||
constexpr int index = utils::blocks::type_index_v<CorrectionBlock, typename FormType::value_blocks>;
|
||||
return mfem::Vector(
|
||||
correction.GetData() + m_manifest->layout().offset(utils::blocks::value_block<index>{}),
|
||||
m_manifest->layout().size(utils::blocks::value_block<index>{})
|
||||
);
|
||||
}
|
||||
|
||||
template <typename ResidualBlock>
|
||||
requires utils::blocks::contains_type_v<
|
||||
ResidualBlock,
|
||||
typename FormType::residual_blocks>
|
||||
[[nodiscard]] mfem::Vector GetResidualBlock(const mfem::Vector &residual) const {
|
||||
if (residual.Size() != Width()) {
|
||||
throw std::invalid_argument("A residual block view requires a complete residual vector.");
|
||||
}
|
||||
constexpr int index = utils::blocks::type_index_v<ResidualBlock, typename FormType::residual_blocks>;
|
||||
return mfem::Vector(
|
||||
const_cast<mfem::real_t *>(residual.GetData()) +
|
||||
m_manifest->layout().offset(utils::blocks::residual_block<index>{}),
|
||||
m_manifest->layout().size(utils::blocks::residual_block<index>{})
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] const PlanType &GetPlan() const noexcept {
|
||||
return m_plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] const StellarPreconditionerLifecycleSnapshot &GetLifecycleSnapshot() const noexcept {
|
||||
return m_snapshot;
|
||||
}
|
||||
|
||||
[[nodiscard]] const StellarPreconditionerStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
void VerifyPreparedProblem() const {
|
||||
if (!Traits::IsPrepared(*m_problem)) {
|
||||
throw std::logic_error(
|
||||
"The stellar-equilibrium problem must be prepared before its preconditioner is prepared or "
|
||||
"refreshed."
|
||||
);
|
||||
}
|
||||
if (Traits::StateSize(*m_problem) <= 0 ||
|
||||
Traits::StateSize(*m_problem) != Traits::EquationSize(*m_problem)) {
|
||||
throw std::logic_error("A stellar-equilibrium preconditioner requires a positive square problem.");
|
||||
}
|
||||
|
||||
const auto &layout = Traits::ManifestOf(*m_problem).layout();
|
||||
if (layout.value_offsets().Last() != Traits::StateSize(*m_problem) ||
|
||||
layout.residual_offsets().Last() != Traits::EquationSize(*m_problem)) {
|
||||
throw std::logic_error(
|
||||
"The stellar-equilibrium manifest and discrete problem dimensions are inconsistent."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyCurrent() const {
|
||||
if (!IsCurrent()) {
|
||||
throw std::logic_error(
|
||||
"The stellar-equilibrium preconditioner is stale; call Refresh after preparing a new linearization."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t... Indices> void SetupComponents(std::index_sequence<Indices...>) {
|
||||
((std::get<Indices>(m_preparedComponents).Setup(*m_problem, std::get<Indices>(m_plan.components())),
|
||||
++m_statistics.componentSetups),
|
||||
...);
|
||||
}
|
||||
|
||||
template <std::size_t Index>
|
||||
[[nodiscard]] std::uint64_t RefreshComponent(const StellarPreconditionerPreparationChanges &changes) {
|
||||
using ComponentTuple = std::remove_cvref_t<decltype(m_plan.components())>;
|
||||
using Component = std::tuple_element_t<Index, ComponentTuple>;
|
||||
if (!detail::componentRequiresRefresh<typename Component::PreparationDependencies>(changes)) {
|
||||
return 0;
|
||||
}
|
||||
return std::get<Index>(m_preparedComponents)
|
||||
.Refresh(*m_problem, std::get<Index>(m_plan.components()), changes)
|
||||
? 1U
|
||||
: 0U;
|
||||
}
|
||||
|
||||
template <std::size_t... Indices>
|
||||
[[nodiscard]] std::uint64_t RefreshComponents(
|
||||
const StellarPreconditionerPreparationChanges &changes,
|
||||
std::index_sequence<Indices...>
|
||||
) {
|
||||
return (std::uint64_t{0} + ... + RefreshComponent<Indices>(changes));
|
||||
}
|
||||
|
||||
ProblemType *m_problem;
|
||||
const typename Traits::Manifest *m_manifest;
|
||||
const mfem::Operator *m_linearization;
|
||||
PlanType m_plan;
|
||||
PreparedComponents m_preparedComponents;
|
||||
StellarPreconditionerLifecycleSnapshot m_snapshot;
|
||||
mutable StellarPreconditionerStatistics m_statistics;
|
||||
};
|
||||
|
||||
template <
|
||||
StellarPreconditionerProblem Problem,
|
||||
typename Plan>
|
||||
requires PreparedPreconditionerPlanFor<
|
||||
std::remove_cvref_t<Plan>,
|
||||
std::remove_cvref_t<Problem>>
|
||||
[[nodiscard]] auto prepare(
|
||||
Problem &problem,
|
||||
Plan &&plan
|
||||
) {
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
using PlanType = std::remove_cvref_t<Plan>;
|
||||
return StellarEquilibriumPreconditioner<ProblemType, PlanType>{problem, std::forward<Plan>(plan)};
|
||||
}
|
||||
} // namespace mean_field::preconditioning
|
||||
830
libmeanfield/interface/preconditioning/stellar_structure.cppm
Normal file
830
libmeanfield/interface/preconditioning/stellar_structure.cppm
Normal file
@@ -0,0 +1,830 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
|
||||
export module mean_field:preconditioning.stellar_structure;
|
||||
|
||||
export import :preconditioning.gravity_field;
|
||||
export import :preconditioning.material_surface;
|
||||
|
||||
export namespace mean_field::preconditioning {
|
||||
struct IndependentStellarSubsystems final { };
|
||||
struct MaterialThenGravityTriangular final { };
|
||||
struct GravityThenMaterialTriangular final { };
|
||||
struct ApproximateStellarBlockLDU final { };
|
||||
|
||||
template <typename Candidate> struct IsStellarStructureFactorizationPolicy : std::false_type { };
|
||||
template <> struct IsStellarStructureFactorizationPolicy<IndependentStellarSubsystems> : std::true_type { };
|
||||
template <> struct IsStellarStructureFactorizationPolicy<MaterialThenGravityTriangular> : std::true_type { };
|
||||
template <> struct IsStellarStructureFactorizationPolicy<GravityThenMaterialTriangular> : std::true_type { };
|
||||
template <> struct IsStellarStructureFactorizationPolicy<ApproximateStellarBlockLDU> : std::true_type { };
|
||||
|
||||
template <typename Candidate>
|
||||
concept StellarStructureFactorizationPolicy =
|
||||
IsStellarStructureFactorizationPolicy<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
namespace detail {
|
||||
template <typename... Lists> struct StellarStructureConcatenate;
|
||||
|
||||
template <> struct StellarStructureConcatenate<> {
|
||||
using Type = utils::blocks::type_list<>;
|
||||
};
|
||||
|
||||
template <typename... Types> struct StellarStructureConcatenate<utils::blocks::type_list<Types...>> {
|
||||
using Type = utils::blocks::type_list<Types...>;
|
||||
};
|
||||
|
||||
template <typename... Left, typename... Right, typename... Remaining>
|
||||
struct StellarStructureConcatenate<
|
||||
utils::blocks::type_list<Left...>,
|
||||
utils::blocks::type_list<Right...>,
|
||||
Remaining...> {
|
||||
using Type =
|
||||
typename StellarStructureConcatenate<utils::blocks::type_list<Left..., Right...>, Remaining...>::Type;
|
||||
};
|
||||
|
||||
template <typename... Lists>
|
||||
using StellarStructureConcatenateT = typename StellarStructureConcatenate<Lists...>::Type;
|
||||
|
||||
template <typename Residual, typename Corrections, typename JacobianForm>
|
||||
struct StellarStructureCouplingsForResidual;
|
||||
|
||||
template <typename Residual, typename JacobianForm>
|
||||
struct StellarStructureCouplingsForResidual<Residual, utils::blocks::type_list<>, JacobianForm> {
|
||||
using Type = utils::blocks::type_list<>;
|
||||
};
|
||||
|
||||
template <typename Residual, typename First, typename... Remaining, typename JacobianForm>
|
||||
struct StellarStructureCouplingsForResidual<
|
||||
Residual,
|
||||
utils::blocks::type_list<First, Remaining...>,
|
||||
JacobianForm> {
|
||||
private:
|
||||
using Tail = typename StellarStructureCouplingsForResidual<
|
||||
Residual,
|
||||
utils::blocks::type_list<Remaining...>,
|
||||
JacobianForm>::Type;
|
||||
|
||||
public:
|
||||
using Type = std::conditional_t<
|
||||
utils::blocks::has_jacobian_coupling_v<Residual, First, JacobianForm>,
|
||||
StellarStructureConcatenateT<utils::blocks::type_list<Coupling<Residual, First>>, Tail>,
|
||||
Tail>;
|
||||
};
|
||||
|
||||
template <typename Residuals, typename Corrections, typename JacobianForm>
|
||||
struct StellarStructureInducedCouplings;
|
||||
|
||||
template <typename Corrections, typename JacobianForm>
|
||||
struct StellarStructureInducedCouplings<utils::blocks::type_list<>, Corrections, JacobianForm> {
|
||||
using Type = utils::blocks::type_list<>;
|
||||
};
|
||||
|
||||
template <typename First, typename... Remaining, typename Corrections, typename JacobianForm>
|
||||
struct StellarStructureInducedCouplings<
|
||||
utils::blocks::type_list<First, Remaining...>,
|
||||
Corrections,
|
||||
JacobianForm> {
|
||||
using Type = StellarStructureConcatenateT<
|
||||
typename StellarStructureCouplingsForResidual<First, Corrections, JacobianForm>::Type,
|
||||
typename StellarStructureInducedCouplings<
|
||||
utils::blocks::type_list<Remaining...>,
|
||||
Corrections,
|
||||
JacobianForm>::Type>;
|
||||
};
|
||||
|
||||
template <typename Left, typename Right> struct StellarStructureListsAreDisjoint;
|
||||
|
||||
template <typename... Left, typename Right>
|
||||
struct StellarStructureListsAreDisjoint<utils::blocks::type_list<Left...>, Right>
|
||||
: std::bool_constant<(!utils::blocks::contains_type_v<Left, Right> && ...)> { };
|
||||
|
||||
template <typename Candidate, typename Universe> struct StellarStructureListIsSubset;
|
||||
|
||||
template <typename... Candidates, typename Universe>
|
||||
struct StellarStructureListIsSubset<utils::blocks::type_list<Candidates...>, Universe>
|
||||
: std::bool_constant<(utils::blocks::contains_type_v<Candidates, Universe> && ...)> { };
|
||||
} // namespace detail
|
||||
|
||||
using CoupledStellarStructureCharacteristics = OperatorCharacteristics<
|
||||
OperatorCategory::mixed,
|
||||
OperatorValueStructure::block,
|
||||
OperatorSymmetry::nonsymmetric,
|
||||
OperatorDefiniteness::unspecified,
|
||||
OperatorRepresentation::matrix_free,
|
||||
OperatorDistribution::distributed_true_dof,
|
||||
OperatorFESpace::product>;
|
||||
|
||||
namespace backend {
|
||||
template <
|
||||
Registered MaterialSurfaceBackend,
|
||||
Registered GravityBackend,
|
||||
StellarStructureFactorizationPolicy Policy>
|
||||
struct CoupledStellarStructure final {
|
||||
using MaterialSurfaceBackendType = MaterialSurfaceBackend;
|
||||
using GravityBackendType = GravityBackend;
|
||||
using FactorizationPolicyType = Policy;
|
||||
};
|
||||
|
||||
template <
|
||||
Registered MaterialSurfaceBackend,
|
||||
Registered GravityBackend,
|
||||
StellarStructureFactorizationPolicy Policy>
|
||||
struct Traits<CoupledStellarStructure<MaterialSurfaceBackend, GravityBackend, Policy>> {
|
||||
static constexpr bool registered = true;
|
||||
static constexpr ApplicationContract applicationContract =
|
||||
::mean_field::preconditioning::backend::applicationContract<MaterialSurfaceBackend> ==
|
||||
ApplicationContract::stationary_linear &&
|
||||
::mean_field::preconditioning::backend::applicationContract<GravityBackend> ==
|
||||
ApplicationContract::stationary_linear
|
||||
? ApplicationContract::stationary_linear
|
||||
: ApplicationContract::flexible;
|
||||
static constexpr bool supportsSerialExecution = Traits<MaterialSurfaceBackend>::supportsSerialExecution &&
|
||||
Traits<GravityBackend>::supportsSerialExecution;
|
||||
static constexpr bool supportsDistributedExecution =
|
||||
Traits<MaterialSurfaceBackend>::supportsDistributedExecution &&
|
||||
Traits<GravityBackend>::supportsDistributedExecution;
|
||||
static constexpr SymmetryRequirement symmetryRequirement = SymmetryRequirement::none;
|
||||
static constexpr NullspaceRequirement nullspaceRequirement = NullspaceRequirement::constant_mode_supported;
|
||||
static constexpr SurrogateRequirement surrogateRequirement = SurrogateRequirement::assembled_sparse;
|
||||
static constexpr bool requiresAssembledSparseSurrogate =
|
||||
Traits<MaterialSurfaceBackend>::requiresAssembledSparseSurrogate ||
|
||||
Traits<GravityBackend>::requiresAssembledSparseSurrogate;
|
||||
|
||||
using PreparationDependencies = preconditioning::PreparationDependencies<
|
||||
PreparationDependency::discretization,
|
||||
PreparationDependency::geometry,
|
||||
PreparationDependency::equation_of_state,
|
||||
PreparationDependency::linearization>;
|
||||
|
||||
template <OperatorCharacteristicsType Characteristics>
|
||||
static constexpr bool supports =
|
||||
Characteristics::category == OperatorCategory::mixed &&
|
||||
Characteristics::valueStructure == OperatorValueStructure::block &&
|
||||
Characteristics::symmetry == OperatorSymmetry::nonsymmetric &&
|
||||
Characteristics::representation == OperatorRepresentation::matrix_free &&
|
||||
Characteristics::distribution == OperatorDistribution::distributed_true_dof &&
|
||||
Characteristics::finiteElementSpace == OperatorFESpace::product;
|
||||
};
|
||||
} // namespace backend
|
||||
|
||||
template <
|
||||
PreconditionerComponent MaterialSurfaceComponentT,
|
||||
PreconditionerComponent GravityComponentT,
|
||||
typename FormT,
|
||||
typename JacobianFormT,
|
||||
StellarStructureFactorizationPolicy PolicyT>
|
||||
requires utils::blocks::valid_jacobian_form<FormT, JacobianFormT> &&
|
||||
detail::StellarStructureListsAreDisjoint<
|
||||
typename MaterialSurfaceComponentT::CorrectionBlocks,
|
||||
typename GravityComponentT::CorrectionBlocks>::value &&
|
||||
detail::StellarStructureListsAreDisjoint<
|
||||
typename MaterialSurfaceComponentT::ResidualBlocks,
|
||||
typename GravityComponentT::ResidualBlocks>::value &&
|
||||
detail::StellarStructureListIsSubset<
|
||||
typename MaterialSurfaceComponentT::CorrectionBlocks,
|
||||
typename FormT::value_blocks>::value &&
|
||||
detail::StellarStructureListIsSubset<
|
||||
typename GravityComponentT::CorrectionBlocks,
|
||||
typename FormT::value_blocks>::value &&
|
||||
detail::StellarStructureListIsSubset<
|
||||
typename MaterialSurfaceComponentT::ResidualBlocks,
|
||||
typename FormT::residual_blocks>::value &&
|
||||
detail::StellarStructureListIsSubset<
|
||||
typename GravityComponentT::ResidualBlocks,
|
||||
typename FormT::residual_blocks>::value
|
||||
class StellarStructureBlock final {
|
||||
public:
|
||||
using MaterialSurfaceComponent = MaterialSurfaceComponentT;
|
||||
using GravityComponent = GravityComponentT;
|
||||
using Form = FormT;
|
||||
using JacobianForm = JacobianFormT;
|
||||
using Factorization = PolicyT;
|
||||
using CorrectionBlocks = detail::StellarStructureConcatenateT<
|
||||
typename MaterialSurfaceComponent::CorrectionBlocks,
|
||||
typename GravityComponent::CorrectionBlocks>;
|
||||
using ResidualBlocks = detail::StellarStructureConcatenateT<
|
||||
typename MaterialSurfaceComponent::ResidualBlocks,
|
||||
typename GravityComponent::ResidualBlocks>;
|
||||
using MaterialToGravityCouplings = typename detail::StellarStructureInducedCouplings<
|
||||
typename GravityComponent::ResidualBlocks,
|
||||
typename MaterialSurfaceComponent::CorrectionBlocks,
|
||||
JacobianForm>::Type;
|
||||
using GravityToMaterialCouplings = typename detail::StellarStructureInducedCouplings<
|
||||
typename MaterialSurfaceComponent::ResidualBlocks,
|
||||
typename GravityComponent::CorrectionBlocks,
|
||||
JacobianForm>::Type;
|
||||
using RequiredCouplings = detail::StellarStructureConcatenateT<
|
||||
typename MaterialSurfaceComponent::RequiredCouplings,
|
||||
typename GravityComponent::RequiredCouplings,
|
||||
MaterialToGravityCouplings,
|
||||
GravityToMaterialCouplings>;
|
||||
using OperatorDescription = CoupledStellarStructureCharacteristics;
|
||||
using BackendType = backend::CoupledStellarStructure<
|
||||
typename MaterialSurfaceComponent::BackendType,
|
||||
typename GravityComponent::BackendType,
|
||||
Factorization>;
|
||||
using PreparationDependencies = typename backend::Traits<BackendType>::PreparationDependencies;
|
||||
|
||||
constexpr StellarStructureBlock(
|
||||
MaterialSurfaceComponent materialSurfaceComponent,
|
||||
GravityComponent gravityComponent,
|
||||
Factorization factorization = {}
|
||||
)
|
||||
: m_materialSurfaceComponent(std::move(materialSurfaceComponent)),
|
||||
m_gravityComponent(std::move(gravityComponent)),
|
||||
m_factorization(std::move(factorization)) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const MaterialSurfaceComponent &materialSurfaceComponent() const noexcept {
|
||||
return m_materialSurfaceComponent;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const GravityComponent &gravityComponent() const noexcept {
|
||||
return m_gravityComponent;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr const Factorization &factorizationPolicy() const noexcept {
|
||||
return m_factorization;
|
||||
}
|
||||
|
||||
private:
|
||||
MaterialSurfaceComponent m_materialSurfaceComponent;
|
||||
GravityComponent m_gravityComponent;
|
||||
Factorization m_factorization;
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept StellarStructureCrossCouplingOperator = requires(
|
||||
const Candidate &couplings,
|
||||
const mfem::Vector &materialDirection,
|
||||
const mfem::Vector &gravityDirection,
|
||||
mfem::Vector &materialAction,
|
||||
mfem::Vector &gravityAction
|
||||
) {
|
||||
{ couplings.MaterialSize() } -> std::same_as<int>;
|
||||
{ couplings.GravitySize() } -> std::same_as<int>;
|
||||
couplings.ApplyMaterialToGravity(materialDirection, gravityAction);
|
||||
couplings.ApplyGravityToMaterial(gravityDirection, materialAction);
|
||||
};
|
||||
|
||||
struct StellarStructureFactorizationStatistics final {
|
||||
std::uint64_t applications{0};
|
||||
std::uint64_t materialSurfaceInverseApplications{0};
|
||||
std::uint64_t gravityInverseApplications{0};
|
||||
std::uint64_t materialToGravityApplications{0};
|
||||
std::uint64_t gravityToMaterialApplications{0};
|
||||
};
|
||||
|
||||
template <StellarStructureFactorizationPolicy Policy, StellarStructureCrossCouplingOperator CouplingOperator>
|
||||
class StellarStructureFactorizationOperator final : public mfem::Solver {
|
||||
public:
|
||||
StellarStructureFactorizationOperator(
|
||||
Policy policy,
|
||||
const mfem::Solver &materialSurfaceInverse,
|
||||
const mfem::Solver &gravityInverse,
|
||||
const CouplingOperator &couplings
|
||||
)
|
||||
: mfem::Solver(materialSurfaceInverse.Height() + gravityInverse.Height()),
|
||||
m_policy(std::move(policy)),
|
||||
m_materialSurfaceInverse(std::addressof(materialSurfaceInverse)),
|
||||
m_gravityInverse(std::addressof(gravityInverse)),
|
||||
m_couplings(std::addressof(couplings)),
|
||||
m_materialWorkspace(materialSurfaceInverse.Height()),
|
||||
m_gravityWorkspace(gravityInverse.Height()) {
|
||||
if (materialSurfaceInverse.Height() <= 0 ||
|
||||
materialSurfaceInverse.Height() != materialSurfaceInverse.Width() || gravityInverse.Height() <= 0 ||
|
||||
gravityInverse.Height() != gravityInverse.Width() ||
|
||||
materialSurfaceInverse.Height() != couplings.MaterialSize() ||
|
||||
gravityInverse.Height() != couplings.GravitySize()) {
|
||||
throw std::invalid_argument(
|
||||
"The stellar-structure inverse blocks do not match the cross-coupling operator."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StellarStructureFactorizationOperator(const StellarStructureFactorizationOperator &) = delete;
|
||||
StellarStructureFactorizationOperator &operator=(const StellarStructureFactorizationOperator &) = delete;
|
||||
StellarStructureFactorizationOperator(StellarStructureFactorizationOperator &&) = delete;
|
||||
StellarStructureFactorizationOperator &operator=(StellarStructureFactorizationOperator &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
if (operation.Height() != Height() || operation.Width() != Width()) {
|
||||
throw std::invalid_argument("The stellar-structure factorization received an incompatible operator.");
|
||||
}
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
if (rightHandSide.Size() != Width() || action.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The stellar-structure factorization requires compatible, preallocated vectors."
|
||||
);
|
||||
}
|
||||
|
||||
action = 0.0;
|
||||
const mfem::Vector materialRightHandSide(
|
||||
const_cast<mfem::real_t *>(rightHandSide.GetData()), m_materialSurfaceInverse->Width()
|
||||
);
|
||||
const mfem::Vector gravityRightHandSide(
|
||||
const_cast<mfem::real_t *>(rightHandSide.GetData()) + m_materialSurfaceInverse->Width(),
|
||||
m_gravityInverse->Width()
|
||||
);
|
||||
mfem::Vector materialAction(action, 0, m_materialSurfaceInverse->Height());
|
||||
mfem::Vector gravityAction(action, m_materialSurfaceInverse->Height(), m_gravityInverse->Height());
|
||||
|
||||
if constexpr (std::same_as<Policy, IndependentStellarSubsystems>) {
|
||||
m_materialSurfaceInverse->Mult(materialRightHandSide, materialAction);
|
||||
m_gravityInverse->Mult(gravityRightHandSide, gravityAction);
|
||||
++m_statistics.materialSurfaceInverseApplications;
|
||||
++m_statistics.gravityInverseApplications;
|
||||
} else if constexpr (std::same_as<Policy, MaterialThenGravityTriangular>) {
|
||||
m_materialSurfaceInverse->Mult(materialRightHandSide, materialAction);
|
||||
m_couplings->ApplyMaterialToGravity(materialAction, m_gravityWorkspace);
|
||||
m_gravityWorkspace *= -1.0;
|
||||
m_gravityWorkspace += gravityRightHandSide;
|
||||
m_gravityInverse->Mult(m_gravityWorkspace, gravityAction);
|
||||
++m_statistics.materialSurfaceInverseApplications;
|
||||
++m_statistics.materialToGravityApplications;
|
||||
++m_statistics.gravityInverseApplications;
|
||||
} else if constexpr (std::same_as<Policy, GravityThenMaterialTriangular>) {
|
||||
m_gravityInverse->Mult(gravityRightHandSide, gravityAction);
|
||||
m_couplings->ApplyGravityToMaterial(gravityAction, m_materialWorkspace);
|
||||
m_materialWorkspace *= -1.0;
|
||||
m_materialWorkspace += materialRightHandSide;
|
||||
m_materialSurfaceInverse->Mult(m_materialWorkspace, materialAction);
|
||||
++m_statistics.gravityInverseApplications;
|
||||
++m_statistics.gravityToMaterialApplications;
|
||||
++m_statistics.materialSurfaceInverseApplications;
|
||||
} else {
|
||||
static_assert(std::same_as<Policy, ApproximateStellarBlockLDU>);
|
||||
m_materialSurfaceInverse->Mult(materialRightHandSide, materialAction);
|
||||
m_couplings->ApplyMaterialToGravity(materialAction, m_gravityWorkspace);
|
||||
m_gravityWorkspace *= -1.0;
|
||||
m_gravityWorkspace += gravityRightHandSide;
|
||||
m_gravityInverse->Mult(m_gravityWorkspace, gravityAction);
|
||||
m_couplings->ApplyGravityToMaterial(gravityAction, m_materialWorkspace);
|
||||
m_materialWorkspace *= -1.0;
|
||||
m_materialWorkspace += materialRightHandSide;
|
||||
m_materialSurfaceInverse->Mult(m_materialWorkspace, materialAction);
|
||||
m_statistics.materialSurfaceInverseApplications += 2;
|
||||
++m_statistics.materialToGravityApplications;
|
||||
++m_statistics.gravityInverseApplications;
|
||||
++m_statistics.gravityToMaterialApplications;
|
||||
}
|
||||
materialAction.SyncAliasMemory(action);
|
||||
gravityAction.SyncAliasMemory(action);
|
||||
++m_statistics.applications;
|
||||
}
|
||||
|
||||
[[nodiscard]] const StellarStructureFactorizationStatistics &GetStatistics() const noexcept {
|
||||
return m_statistics;
|
||||
}
|
||||
|
||||
private:
|
||||
Policy m_policy;
|
||||
const mfem::Solver *m_materialSurfaceInverse;
|
||||
const mfem::Solver *m_gravityInverse;
|
||||
const CouplingOperator *m_couplings;
|
||||
mutable mfem::Vector m_materialWorkspace;
|
||||
mutable mfem::Vector m_gravityWorkspace;
|
||||
mutable StellarStructureFactorizationStatistics m_statistics;
|
||||
};
|
||||
|
||||
class StellarStructureCrossJacobianOperator final : public mfem::Operator {
|
||||
public:
|
||||
explicit StellarStructureCrossJacobianOperator(const operators::PreparedStellarEquilibriumOperator &operation)
|
||||
: mfem::Operator(MaterialSizeOf(operation) + GravitySizeOf(operation)),
|
||||
m_operation(std::addressof(operation)),
|
||||
m_materialOffsets(4),
|
||||
m_gravityOffsets(3),
|
||||
m_combinedOffsets(3),
|
||||
m_gravityDirection(operation.GetGravityJacobianOperator().Width()),
|
||||
m_volumeDisplacement(operation.GetDomainDeformation().volumeDisplacementSize()),
|
||||
m_mechanicalAction(operation.GetDomainDeformation().volumeDisplacementSize()),
|
||||
m_zeroEnthalpy(operation.GetBarotropicClosureOperator().GetEnthalpySize()) {
|
||||
const auto &context = operation.GetGravityContext();
|
||||
m_materialOffsets[0] = 0;
|
||||
m_materialOffsets[1] = context.GetDensityMap().reduced_size();
|
||||
m_materialOffsets[2] = m_materialOffsets[1] + operation.GetDomainDeformation().parameterCount();
|
||||
m_materialOffsets[3] = MaterialSizeOf(operation);
|
||||
m_gravityOffsets[0] = 0;
|
||||
m_gravityOffsets[1] = context.GetGravityGradientMap().reduced_size();
|
||||
m_gravityOffsets[2] = GravitySizeOf(operation);
|
||||
m_combinedOffsets[0] = 0;
|
||||
m_combinedOffsets[1] = MaterialSize();
|
||||
m_combinedOffsets[2] = Height();
|
||||
m_zeroEnthalpy = 0.0;
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
VerifyCombined(direction, action);
|
||||
action = 0.0;
|
||||
const mfem::Vector materialDirection(const_cast<mfem::real_t *>(direction.GetData()), MaterialSize());
|
||||
const mfem::Vector gravityDirection(
|
||||
const_cast<mfem::real_t *>(direction.GetData()) + MaterialSize(), GravitySize()
|
||||
);
|
||||
mfem::Vector materialAction(action, 0, MaterialSize());
|
||||
mfem::Vector gravityAction(action, MaterialSize(), GravitySize());
|
||||
ApplyMaterialToGravity(materialDirection, gravityAction);
|
||||
ApplyGravityToMaterial(gravityDirection, materialAction);
|
||||
materialAction.SyncAliasMemory(action);
|
||||
gravityAction.SyncAliasMemory(action);
|
||||
}
|
||||
|
||||
void ApplyMaterialToGravity(
|
||||
const mfem::Vector &materialDirection,
|
||||
mfem::Vector &gravityAction
|
||||
) const {
|
||||
VerifyMaterial(materialDirection, "direction");
|
||||
VerifyGravity(gravityAction, "action");
|
||||
const auto densityDirection = MaterialBlock(materialDirection, 0);
|
||||
const auto surfaceDirection = MaterialBlock(materialDirection, 1);
|
||||
const auto &gravityOffsets = m_operation->GetGravityOperator().GetStateOffsets();
|
||||
using GravityForm = utils::blocks::gravity_field_form;
|
||||
constexpr auto densityBlock =
|
||||
utils::blocks::get_value_block<GravityForm>(utils::blocks::density_field.mass_term);
|
||||
constexpr auto displacementBlock =
|
||||
utils::blocks::get_value_block<GravityForm>(utils::blocks::displacement_field.geometry_term);
|
||||
|
||||
m_gravityDirection = 0.0;
|
||||
auto packedDensityDirection = MutableBlock(m_gravityDirection, gravityOffsets, densityBlock.index);
|
||||
packedDensityDirection = densityDirection;
|
||||
packedDensityDirection.SyncAliasMemory(m_gravityDirection);
|
||||
m_operation->GetDomainDeformation().applyJacobian(
|
||||
m_operation->GetSurfaceDeformationParameters(), surfaceDirection, m_volumeDisplacement
|
||||
);
|
||||
auto packedDisplacementDirection =
|
||||
MutableBlock(m_gravityDirection, gravityOffsets, displacementBlock.index);
|
||||
packedDisplacementDirection = m_volumeDisplacement;
|
||||
packedDisplacementDirection.SyncAliasMemory(m_gravityDirection);
|
||||
m_operation->GetGravityJacobianOperator().Mult(m_gravityDirection, gravityAction);
|
||||
}
|
||||
|
||||
void ApplyGravityToMaterial(
|
||||
const mfem::Vector &gravityDirection,
|
||||
mfem::Vector &materialAction
|
||||
) const {
|
||||
VerifyGravity(gravityDirection, "direction");
|
||||
VerifyMaterial(materialAction, "action");
|
||||
const auto gravityGradientDirection = GravityBlock(gravityDirection, 0);
|
||||
const auto gravityPotentialDirection = GravityBlock(gravityDirection, 1);
|
||||
auto densityAction = MaterialBlock(materialAction, 0);
|
||||
auto surfaceAction = MaterialBlock(materialAction, 1);
|
||||
auto enthalpyAction = MaterialBlock(materialAction, 2);
|
||||
|
||||
densityAction = 0.0;
|
||||
m_operation->GetDisplacementOperator().ApplyGravityGradientJacobianAction(
|
||||
gravityGradientDirection, m_mechanicalAction
|
||||
);
|
||||
m_operation->GetDomainDeformation().applyJacobianTranspose(
|
||||
m_operation->GetSurfaceDeformationParameters(), m_mechanicalAction, surfaceAction
|
||||
);
|
||||
m_operation->GetHydrostaticOperator().ApplyGravityPotentialJacobianAction(
|
||||
gravityPotentialDirection, enthalpyAction
|
||||
);
|
||||
m_operation->GetSurfaceConstraintOperator().ApplyJacobianRows(m_zeroEnthalpy, enthalpyAction);
|
||||
densityAction.SyncAliasMemory(materialAction);
|
||||
surfaceAction.SyncAliasMemory(materialAction);
|
||||
enthalpyAction.SyncAliasMemory(materialAction);
|
||||
}
|
||||
|
||||
[[nodiscard]] int MaterialSize() const noexcept {
|
||||
return m_materialOffsets.Last();
|
||||
}
|
||||
|
||||
[[nodiscard]] int GravitySize() const noexcept {
|
||||
return m_gravityOffsets.Last();
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &GetMaterialOffsets() const noexcept {
|
||||
return m_materialOffsets;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &GetGravityOffsets() const noexcept {
|
||||
return m_gravityOffsets;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Array<int> &GetCombinedOffsets() const noexcept {
|
||||
return m_combinedOffsets;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static int MaterialSizeOf(const operators::PreparedStellarEquilibriumOperator &operation) {
|
||||
if (!operation.IsPrepared()) {
|
||||
throw std::logic_error("The stellar-structure cross Jacobian requires a prepared operator.");
|
||||
}
|
||||
return operation.GetGravityContext().GetDensityMap().reduced_size() +
|
||||
operation.GetDomainDeformation().parameterCount() +
|
||||
operation.GetBarotropicClosureOperator().GetEnthalpySize();
|
||||
}
|
||||
|
||||
[[nodiscard]] static int GravitySizeOf(const operators::PreparedStellarEquilibriumOperator &operation) {
|
||||
return operation.GetGravityContext().GetGravityGradientMap().reduced_size() +
|
||||
operation.GetGravityContext().GetGravityPotentialMap().reduced_size();
|
||||
}
|
||||
|
||||
[[nodiscard]] static mfem::Vector MutableBlock(
|
||||
mfem::Vector &vector,
|
||||
const mfem::Array<int> &offsets,
|
||||
const int block
|
||||
) {
|
||||
return mfem::Vector(vector, offsets[block], offsets[block + 1] - offsets[block]);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector MaterialBlock(
|
||||
const mfem::Vector &vector,
|
||||
const int block
|
||||
) const {
|
||||
return mfem::Vector(
|
||||
const_cast<mfem::real_t *>(vector.GetData()) + m_materialOffsets[block],
|
||||
m_materialOffsets[block + 1] - m_materialOffsets[block]
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector MaterialBlock(
|
||||
mfem::Vector &vector,
|
||||
const int block
|
||||
) const {
|
||||
return mfem::Vector(
|
||||
vector, m_materialOffsets[block], m_materialOffsets[block + 1] - m_materialOffsets[block]
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector GravityBlock(
|
||||
const mfem::Vector &vector,
|
||||
const int block
|
||||
) const {
|
||||
return mfem::Vector(
|
||||
const_cast<mfem::real_t *>(vector.GetData()) + m_gravityOffsets[block],
|
||||
m_gravityOffsets[block + 1] - m_gravityOffsets[block]
|
||||
);
|
||||
}
|
||||
|
||||
void VerifyCombined(
|
||||
const mfem::Vector &direction,
|
||||
const mfem::Vector &action
|
||||
) const {
|
||||
if (direction.Size() != Width() || action.Size() != Height()) {
|
||||
throw std::invalid_argument(
|
||||
"The stellar-structure cross Jacobian requires compatible, preallocated vectors."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyMaterial(
|
||||
const mfem::Vector &vector,
|
||||
const char *role
|
||||
) const {
|
||||
if (vector.Size() != MaterialSize()) {
|
||||
throw std::invalid_argument(
|
||||
std::string("The stellar-structure material ") + role + " has the wrong size."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyGravity(
|
||||
const mfem::Vector &vector,
|
||||
const char *role
|
||||
) const {
|
||||
if (vector.Size() != GravitySize()) {
|
||||
throw std::invalid_argument(
|
||||
std::string("The stellar-structure gravity ") + role + " has the wrong size."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const operators::PreparedStellarEquilibriumOperator *m_operation;
|
||||
mfem::Array<int> m_materialOffsets;
|
||||
mfem::Array<int> m_gravityOffsets;
|
||||
mfem::Array<int> m_combinedOffsets;
|
||||
mutable mfem::Vector m_gravityDirection;
|
||||
mutable mfem::Vector m_volumeDisplacement;
|
||||
mutable mfem::Vector m_mechanicalAction;
|
||||
mfem::Vector m_zeroEnthalpy;
|
||||
};
|
||||
|
||||
struct StellarStructureBlockPreparationReport final {
|
||||
MaterialSurfaceBlockPreparationReport materialSurface;
|
||||
GravityFieldBlockPreparationReport gravity;
|
||||
|
||||
[[nodiscard]] bool DidAnyWork() const noexcept {
|
||||
return materialSurface.DidAnyWork() || gravity.DidAnyWork();
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <equilibrium::StellarEquilibriumModel Model>
|
||||
[[nodiscard]] const operators::PreparedStellarEquilibriumOperator &
|
||||
physicalOperator(const equilibrium::StellarEquilibriumProblem<Model> &problem) {
|
||||
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
|
||||
return problem.GetPreparedOperator().GetPhysicalOperator();
|
||||
} else {
|
||||
return problem.GetPreparedOperator();
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <
|
||||
equilibrium::StellarEquilibriumModel Model,
|
||||
typename MaterialComponent,
|
||||
backend::Registered GravityMassBackend,
|
||||
backend::ApplicationMode Mode,
|
||||
GravityFactorizationPolicy GravityPolicy,
|
||||
StellarStructureFactorizationPolicy StructurePolicy>
|
||||
class PreparedStellarStructureBlock final : public mfem::Solver {
|
||||
private:
|
||||
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
|
||||
using GravityComponent = GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>;
|
||||
using Structure = StellarStructureBlock<
|
||||
MaterialComponent,
|
||||
GravityComponent,
|
||||
typename Problem::FormType,
|
||||
typename Problem::JacobianFormType,
|
||||
StructurePolicy>;
|
||||
using MaterialPrepared =
|
||||
decltype(preconditioning::prepare(std::declval<const Problem &>(), std::declval<MaterialComponent>()));
|
||||
using GravityPrepared = decltype(preconditioning::prepare(
|
||||
std::declval<const fem::FEM &>(),
|
||||
std::declval<const operators::context::gravity_field::GravityFieldGeometryContext &>(),
|
||||
std::declval<GravityComponent>()
|
||||
));
|
||||
|
||||
public:
|
||||
PreparedStellarStructureBlock(
|
||||
const Problem &problem,
|
||||
Structure structure
|
||||
)
|
||||
: mfem::Solver(StructureSize(problem)),
|
||||
m_problem(std::addressof(problem)),
|
||||
m_structure(std::move(structure)),
|
||||
m_materialSurface(
|
||||
preconditioning::prepare(
|
||||
problem,
|
||||
m_structure.materialSurfaceComponent()
|
||||
)
|
||||
),
|
||||
m_gravity(
|
||||
preconditioning::prepare(
|
||||
detail::physicalOperator(problem).GetHydrostaticOperator().GetFEM(),
|
||||
detail::physicalOperator(problem).GetGravityContext().GetGeometryContext(),
|
||||
m_structure.gravityComponent()
|
||||
)
|
||||
),
|
||||
m_crossCouplings(detail::physicalOperator(problem)),
|
||||
m_factorization(
|
||||
m_structure.factorizationPolicy(),
|
||||
m_materialSurface,
|
||||
m_gravity,
|
||||
m_crossCouplings
|
||||
) {
|
||||
}
|
||||
|
||||
PreparedStellarStructureBlock(const PreparedStellarStructureBlock &) = delete;
|
||||
PreparedStellarStructureBlock &operator=(const PreparedStellarStructureBlock &) = delete;
|
||||
PreparedStellarStructureBlock(PreparedStellarStructureBlock &&) = delete;
|
||||
PreparedStellarStructureBlock &operator=(PreparedStellarStructureBlock &&) = delete;
|
||||
|
||||
void SetOperator(const mfem::Operator &operation) override {
|
||||
m_factorization.SetOperator(operation);
|
||||
}
|
||||
|
||||
void Mult(
|
||||
const mfem::Vector &rightHandSide,
|
||||
mfem::Vector &action
|
||||
) const override {
|
||||
if (!IsCurrent()) {
|
||||
throw std::logic_error("The stellar-structure block is stale; refresh it before application.");
|
||||
}
|
||||
m_factorization.Mult(rightHandSide, action);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCurrent() const noexcept {
|
||||
return m_materialSurface.IsCurrent() && m_gravity.IsCurrent() &&
|
||||
detail::physicalOperator(*m_problem).IsPrepared();
|
||||
}
|
||||
|
||||
[[nodiscard]] StellarStructureBlockPreparationReport Refresh() {
|
||||
const auto &physical = detail::physicalOperator(*m_problem);
|
||||
return {
|
||||
.materialSurface = m_materialSurface.Refresh(physical),
|
||||
.gravity = m_gravity.Refresh(
|
||||
physical.GetHydrostaticOperator().GetFEM(), physical.GetGravityContext().GetGeometryContext()
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] const Structure &GetBlock() const noexcept {
|
||||
return m_structure;
|
||||
}
|
||||
|
||||
[[nodiscard]] const MaterialPrepared &GetMaterialSurfacePreconditioner() const noexcept {
|
||||
return m_materialSurface;
|
||||
}
|
||||
|
||||
[[nodiscard]] const GravityPrepared &GetGravityPreconditioner() const noexcept {
|
||||
return m_gravity;
|
||||
}
|
||||
|
||||
[[nodiscard]] const StellarStructureCrossJacobianOperator &GetCrossCouplings() const noexcept {
|
||||
return m_crossCouplings;
|
||||
}
|
||||
|
||||
[[nodiscard]] const StellarStructureFactorizationOperator<
|
||||
StructurePolicy,
|
||||
StellarStructureCrossJacobianOperator> &
|
||||
GetFactorization() const noexcept {
|
||||
return m_factorization;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static int StructureSize(const Problem &problem) {
|
||||
const auto &physical = detail::physicalOperator(problem);
|
||||
return physical.GetGravityContext().GetDensityMap().reduced_size() +
|
||||
physical.GetDomainDeformation().parameterCount() +
|
||||
physical.GetBarotropicClosureOperator().GetEnthalpySize() +
|
||||
physical.GetGravityContext().GetGravityGradientMap().reduced_size() +
|
||||
physical.GetGravityContext().GetGravityPotentialMap().reduced_size();
|
||||
}
|
||||
|
||||
const Problem *m_problem;
|
||||
Structure m_structure;
|
||||
MaterialPrepared m_materialSurface;
|
||||
GravityPrepared m_gravity;
|
||||
StellarStructureCrossJacobianOperator m_crossCouplings;
|
||||
StellarStructureFactorizationOperator<StructurePolicy, StellarStructureCrossJacobianOperator> m_factorization;
|
||||
};
|
||||
|
||||
template <
|
||||
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
|
||||
typename MaterialComponent,
|
||||
backend::Registered GravityMassBackend,
|
||||
backend::ApplicationMode Mode,
|
||||
GravityFactorizationPolicy GravityPolicy,
|
||||
StellarStructureFactorizationPolicy StructurePolicy>
|
||||
[[nodiscard]] constexpr auto stellarStructureBlock(
|
||||
const Problem &,
|
||||
MaterialComponent materialComponent,
|
||||
GravityFieldBlock<
|
||||
GravityMassBackend,
|
||||
backend::HypreBoomerAMG<Mode>,
|
||||
GravityPolicy> gravityComponent,
|
||||
StructurePolicy policy
|
||||
) {
|
||||
using ProblemType = std::remove_cvref_t<Problem>;
|
||||
return StellarStructureBlock<
|
||||
MaterialComponent, GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>,
|
||||
typename ProblemType::FormType, typename ProblemType::JacobianFormType, StructurePolicy>{
|
||||
std::move(materialComponent), std::move(gravityComponent), std::move(policy)
|
||||
};
|
||||
}
|
||||
|
||||
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
|
||||
[[nodiscard]] constexpr auto stellarStructureBlock(const Problem &problem) {
|
||||
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
|
||||
auto material = materialSurfaceBlock(problem);
|
||||
auto gravity = GravityFieldBlock(
|
||||
backend::MatrixFreeChebyshev{.order = 5, .powerIterations = 20},
|
||||
FixedAMG{backend::FixedCycles{.cycles = 3}}, GravityApproximateLDU{}
|
||||
);
|
||||
return stellarStructureBlock(problem, std::move(material), std::move(gravity), IndependentStellarSubsystems{});
|
||||
}
|
||||
|
||||
template <
|
||||
equilibrium::StellarEquilibriumModel Model,
|
||||
typename MaterialComponent,
|
||||
backend::Registered GravityMassBackend,
|
||||
backend::ApplicationMode Mode,
|
||||
GravityFactorizationPolicy GravityPolicy,
|
||||
StellarStructureFactorizationPolicy StructurePolicy>
|
||||
[[nodiscard]] auto prepare(
|
||||
const equilibrium::StellarEquilibriumProblem<Model> &problem,
|
||||
StellarStructureBlock<
|
||||
MaterialComponent,
|
||||
GravityFieldBlock<
|
||||
GravityMassBackend,
|
||||
backend::HypreBoomerAMG<Mode>,
|
||||
GravityPolicy>,
|
||||
typename equilibrium::StellarEquilibriumProblem<Model>::FormType,
|
||||
typename equilibrium::StellarEquilibriumProblem<Model>::JacobianFormType,
|
||||
StructurePolicy> structure
|
||||
) {
|
||||
return PreparedStellarStructureBlock<
|
||||
Model, MaterialComponent, GravityMassBackend, Mode, GravityPolicy, StructurePolicy>{
|
||||
problem, std::move(structure)
|
||||
};
|
||||
}
|
||||
} // namespace mean_field::preconditioning
|
||||
@@ -29,6 +29,25 @@ export namespace mean_field::surface {
|
||||
InputQuantities>)) &&
|
||||
...)> { };
|
||||
|
||||
template <typename RelationType, typename Bindings, typename EquationOfState>
|
||||
struct PressureSurfaceRelationMatchesBindings : std::false_type { };
|
||||
|
||||
template <typename OutputQuantity, typename... InputQuantities, typename Bindings, typename EquationOfState>
|
||||
struct PressureSurfaceRelationMatchesBindings<
|
||||
eos::Relation<OutputQuantity, InputQuantities...>,
|
||||
Bindings,
|
||||
EquationOfState>
|
||||
: std::bool_constant<
|
||||
(surfaceBindingCount<Bindings, OutputQuantity> == 1) &&
|
||||
(std::same_as<eos::quantity::Pressure, InputQuantities> || ...) &&
|
||||
((std::same_as<eos::quantity::Pressure, InputQuantities> ||
|
||||
(surfaceBindingCount<Bindings, InputQuantities> == 1 &&
|
||||
eos::SupportsPartialDerivative<
|
||||
EquationOfState,
|
||||
eos::Relation<OutputQuantity, InputQuantities...>,
|
||||
InputQuantities>)) &&
|
||||
...)> { };
|
||||
|
||||
template <typename Catalog, typename Formulation, typename EquationOfState>
|
||||
struct MatchingPressureSurfaceRelations;
|
||||
|
||||
@@ -44,6 +63,24 @@ export namespace mean_field::surface {
|
||||
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
|
||||
};
|
||||
|
||||
template <typename Catalog, typename Bindings, typename EquationOfState>
|
||||
struct MatchingPressureSurfaceRelationsForBindings;
|
||||
|
||||
template <typename... Relations, typename Bindings, typename EquationOfState>
|
||||
struct MatchingPressureSurfaceRelationsForBindings<
|
||||
eos::RelationCatalog<Relations...>,
|
||||
Bindings,
|
||||
EquationOfState> {
|
||||
using Tuple = decltype(std::tuple_cat(
|
||||
std::conditional_t<
|
||||
PressureSurfaceRelationMatchesBindings<Relations, Bindings, EquationOfState>::value,
|
||||
std::tuple<Relations>,
|
||||
std::tuple<>>{}...
|
||||
));
|
||||
|
||||
static constexpr std::size_t count = std::tuple_size_v<Tuple>;
|
||||
};
|
||||
|
||||
template <std::size_t Count, typename Tuple> struct UniquePressureSurfaceRelation {
|
||||
using Type = void;
|
||||
};
|
||||
@@ -52,6 +89,17 @@ export namespace mean_field::surface {
|
||||
using Type = std::tuple_element_t<0, Tuple>;
|
||||
};
|
||||
|
||||
template <std::size_t Count, typename Tuple, typename Bindings> struct UniquePressureSurfaceFormulation {
|
||||
using Type = void;
|
||||
};
|
||||
|
||||
template <typename Tuple, typename Bindings> struct UniquePressureSurfaceFormulation<1, Tuple, Bindings> {
|
||||
using Relation = std::tuple_element_t<0, Tuple>;
|
||||
using CarrierQuantity = eos::RelationOutputT<Relation>;
|
||||
using CarrierField = SurfaceFieldForQuantityT<Bindings, CarrierQuantity>;
|
||||
using Type = SurfaceConstraintFormulation<CarrierQuantity, CarrierField, Bindings>;
|
||||
};
|
||||
|
||||
template <typename Dependencies, typename Field> struct AppendSurfaceDependency;
|
||||
|
||||
template <typename RowField, typename... StateFields, typename Field>
|
||||
@@ -103,6 +151,16 @@ export namespace mean_field::surface {
|
||||
using Relation = typename UniquePressureSurfaceRelation<Matches::count, typename Matches::Tuple>::Type;
|
||||
};
|
||||
|
||||
template <ValidSurfaceStateBindings Bindings, eos::EquationOfStateModel EquationOfState>
|
||||
struct PressureSurfaceFormulationCompilation {
|
||||
using Matches = MatchingPressureSurfaceRelationsForBindings<
|
||||
typename EquationOfState::Relations,
|
||||
Bindings,
|
||||
EquationOfState>;
|
||||
using Formulation =
|
||||
typename UniquePressureSurfaceFormulation<Matches::count, typename Matches::Tuple, Bindings>::Type;
|
||||
};
|
||||
|
||||
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
|
||||
requires(PressureSurfaceCompilation<Formulation, EquationOfState>::Matches::count == 1)
|
||||
struct CompiledPressureSurfaceConstraintType {
|
||||
@@ -119,6 +177,19 @@ export namespace mean_field::surface {
|
||||
(detail::PressureSurfaceCompilation<std::remove_cvref_t<Formulation>, std::remove_cvref_t<EquationOfState>>::
|
||||
Matches::count == 1);
|
||||
|
||||
template <typename Bindings, typename EquationOfState>
|
||||
concept PressureSurfaceFormulationCompilable =
|
||||
ValidSurfaceStateBindings<Bindings> && eos::EquationOfStateModel<EquationOfState> &&
|
||||
(detail::PressureSurfaceFormulationCompilation<
|
||||
std::remove_cvref_t<Bindings>,
|
||||
std::remove_cvref_t<EquationOfState>>::Matches::count == 1);
|
||||
|
||||
template <ValidSurfaceStateBindings Bindings, eos::EquationOfStateModel EquationOfState>
|
||||
requires PressureSurfaceFormulationCompilable<Bindings, EquationOfState>
|
||||
using CompiledPressureSurfaceFormulationT = typename detail::PressureSurfaceFormulationCompilation<
|
||||
std::remove_cvref_t<Bindings>,
|
||||
std::remove_cvref_t<EquationOfState>>::Formulation;
|
||||
|
||||
template <SurfaceConstraintFormulationType Formulation, eos::EquationOfStateModel EquationOfState>
|
||||
requires PressureSurfaceCompilable<Formulation, EquationOfState>
|
||||
using CompiledPressureSurfaceConstraintT = typename detail::CompiledPressureSurfaceConstraintType<
|
||||
|
||||
Reference in New Issue
Block a user