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
|
||||
Reference in New Issue
Block a user