294 lines
13 KiB
C++
294 lines
13 KiB
C++
module;
|
|
#include "mfem.hpp"
|
|
#include "profile.h"
|
|
#include <array>
|
|
#include <cmath>
|
|
|
|
module mean_field;
|
|
|
|
namespace mean_field::physics {
|
|
mfem::DenseMatrix compute_quadrupole_moment_tensor(
|
|
const fem::FEM &fem,
|
|
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;
|
|
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
|
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)))
|
|
continue;
|
|
|
|
mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i);
|
|
using DensityField = field::Field<field::Density>;
|
|
const quadrature::Query query = DensityField::make_query<field::Density::Form::Quadrupole>(
|
|
quadrature::QuadratureRole::diagnostic, trans->OrderW(), std::array<int, 1>{2}, utils::DOMAINS::STELLAR,
|
|
fem.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none
|
|
);
|
|
const mfem::IntegrationRule &ir =
|
|
*fem.quadratureFactory->get(query, trans->GetGeometryType()).integration_rule;
|
|
|
|
for (int j = 0; j < ir.GetNPoints(); ++j) {
|
|
const mfem::IntegrationPoint &ip = ir.IntPoint(j);
|
|
trans->SetIntPoint(&ip);
|
|
|
|
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;
|
|
|
|
double r_sq = 0.0;
|
|
|
|
for (int d = 0; d < dim; ++d) {
|
|
x_prime(d) = phys_point(d) - com(d);
|
|
r_sq += x_prime(d) * x_prime(d);
|
|
}
|
|
|
|
for (int m = 0; m < dim; ++m) {
|
|
for (int n = 0; n < dim; ++n) {
|
|
const double delta = (m == n) ? 1.0 : 0.0;
|
|
const double contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq;
|
|
local_Q(m, n) += rho_val * contrib * weight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
return global_Q;
|
|
}
|
|
|
|
double l2_multipole_potential(
|
|
const fem::FEM &fem,
|
|
const double total_mass,
|
|
const mfem::Vector &phys_x
|
|
) {
|
|
const double r = phys_x.Norml2();
|
|
if (r < 1e-12)
|
|
return 0.0;
|
|
|
|
const int dim = fem.mesh->Dimension();
|
|
|
|
mfem::Vector n(phys_x);
|
|
n /= r;
|
|
|
|
double l2_mult_factor = 0.0;
|
|
for (int i = 0; i < dim; ++i) {
|
|
for (int j = 0; j < dim; ++j) {
|
|
l2_mult_factor += fem.Q(i, j) * n(i) * n(j);
|
|
}
|
|
}
|
|
|
|
const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor;
|
|
|
|
const double l0_contrib = -utils::G * total_mass / r;
|
|
|
|
// l1 contribution is zero for a system centered on its COM
|
|
return l0_contrib + l2_contrib;
|
|
}
|
|
|
|
GravitySolution solve_gravity_field(
|
|
fem::FEM &f,
|
|
const GravitySolveOptions &options,
|
|
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(
|
|
f.gravityPotentialFes != nullptr, "Gravity initialization requires the gravity-potential "
|
|
"finite-element "
|
|
"space."
|
|
);
|
|
MFEM_VERIFY(
|
|
f.gravityFluxFes != nullptr, "Gravity initialization requires the "
|
|
"gravity-gradient finite-element space."
|
|
);
|
|
MFEM_VERIFY(
|
|
f.displacementFes != nullptr, "Gravity initialization requires the "
|
|
"displacement finite-element space."
|
|
);
|
|
MFEM_VERIFY(f.domainMapperStateless != nullptr, "Gravity initialization requires the stateless domain mapper.");
|
|
MFEM_VERIFY(
|
|
rho.FESpace() == f.densityFes.get(), "Gravity initialization requires density to use the FEM density "
|
|
"space."
|
|
);
|
|
MFEM_VERIFY(
|
|
displacement.FESpace() == f.displacementFes.get(),
|
|
"Gravity initialization requires displacement to use the FEM "
|
|
"Vec_H1 "
|
|
"space."
|
|
);
|
|
MFEM_VERIFY(
|
|
std::isfinite(options.relativeTolerance) && options.relativeTolerance >= 0.0,
|
|
"Gravity solve requires a finite, nonnegative relative tolerance."
|
|
);
|
|
MFEM_VERIFY(
|
|
std::isfinite(options.absoluteTolerance) && options.absoluteTolerance >= 0.0,
|
|
"Gravity solve requires a finite, nonnegative absolute tolerance."
|
|
);
|
|
MFEM_VERIFY(options.maximumIterations > 0, "Gravity solve requires a positive MINRES iteration limit.");
|
|
|
|
using form = utils::blocks::gravity_field_form;
|
|
|
|
constexpr auto gravity_gradient_residual_block =
|
|
utils::blocks::get_residual_block<form>(utils::blocks::gravity_field.gradient_term);
|
|
|
|
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 = 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();
|
|
const field::FieldDofMap &gravity_flux_map = gravity_flux_adapter.dof_map();
|
|
const field::FieldDofMap &gravity_potential_map = gravity_potential_adapter.dof_map();
|
|
|
|
const std::array<int, form::value_block_count> value_sizes{
|
|
density_map.reduced_size(), displacement_map.reduced_size(), gravity_flux_map.reduced_size(),
|
|
gravity_potential_map.reduced_size()
|
|
};
|
|
|
|
const std::array<int, form::residual_block_count> residual_sizes{
|
|
gravity_flux_map.reduced_size(), gravity_potential_map.reduced_size()
|
|
};
|
|
|
|
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)
|
|
);
|
|
|
|
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::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::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::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)
|
|
);
|
|
|
|
mfem::Vector 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(),
|
|
"The reduced gravity right-hand side has the wrong size."
|
|
);
|
|
|
|
mfem::BlockVector gravity_state(reduced_operator.GetGravityOffsets());
|
|
gravity_state = 0.0;
|
|
|
|
mfem::MINRESSolver minres(f.mesh->GetComm());
|
|
minres.SetOperator(reduced_operator);
|
|
minres.SetPreconditioner(reduced_preconditioner);
|
|
minres.SetRelTol(options.relativeTolerance);
|
|
minres.SetAbsTol(options.absoluteTolerance);
|
|
minres.SetMaxIter(options.maximumIterations);
|
|
// minres.SetPrintLevel(args.verbose ? 1 : 0);
|
|
minres.SetPrintLevel(0);
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
GravitySolution solve_gravity_field(
|
|
fem::FEM &f,
|
|
const utils::Args &args,
|
|
const mfem::GridFunction &rho,
|
|
const mfem::GridFunction &displacement
|
|
) {
|
|
return solve_gravity_field(
|
|
f,
|
|
GravitySolveOptions{
|
|
.relativeTolerance = args.p.rtol,
|
|
.absoluteTolerance = args.p.atol,
|
|
.maximumIterations = args.p.max_iters
|
|
},
|
|
rho, displacement
|
|
);
|
|
}
|
|
} // namespace mean_field::physics
|