Files
MeanField/experiments/gravity_completed_rigid_motion.cpp
Emily Boudreaux 85500fef3b feat(surface): surface deformation prescriptions
restricted the unknown state vector to surface deformation and implemented one prescription, NodalRadialSurface, while the full volumetric displacment field is reconstructed analytically from that. This reduced the number of degrees of freedom in the system by a factor of 80 while also removing many null vectors from the system.
2026-09-01 11:50:13 -04:00

242 lines
11 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <string>
#include <mfem.hpp>
#include <mpi.h>
import experiment;
import experiment.stellar_null_space;
import mean_field;
import test_helpers;
namespace {
class GravityUnknownJacobian final : public mfem::Operator {
public:
explicit GravityUnknownJacobian(
const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator
)
: mfem::Operator(
stellarOperator.GetLayout().size(experiment::null_space::gravityGradientValue) +
stellarOperator.GetLayout().size(experiment::null_space::gravityPotentialValue)
),
m_stellarOperator(stellarOperator),
m_gravityGradientSize(stellarOperator.GetLayout().size(experiment::null_space::gravityGradientValue)) {
MFEM_VERIFY(Width() == Height(), "The reduced gravity Jacobian must be square.");
}
void Mult(
const mfem::Vector &gravityDirection,
mfem::Vector &gravityAction
) const override {
MFEM_VERIFY(gravityDirection.Size() == Width(), "The reduced gravity direction has the wrong size.");
const mfem::Vector gravityGradientDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()), m_gravityGradientSize
);
const mfem::Vector gravityPotentialDirection(
const_cast<mfem::real_t *>(gravityDirection.GetData()) + m_gravityGradientSize,
Width() - m_gravityGradientSize
);
m_stellarOperator.GetGravityOperator().ApplyGravityUnknowns(
gravityGradientDirection, gravityPotentialDirection,
m_stellarOperator.GetGravityContext().GetGeometryContext(), gravityAction
);
}
[[nodiscard]] int gravity_gradient_size() const noexcept {
return m_gravityGradientSize;
}
private:
const mean_field::operators::PreparedStellarEquilibriumOperator &m_stellarOperator;
int m_gravityGradientSize;
};
void add_block_metrics(
std::map<
std::string,
double> &metrics,
const std::string &prefix,
const std::array<
double,
6> &norms
) {
for (std::size_t block = 0; block < norms.size(); ++block) {
metrics.emplace(prefix + experiment::null_space::residualBlockNames[block] + "_norm", norms[block]);
}
}
[[nodiscard]] mfem::Vector gravity_residual_blocks(
const mfem::Vector &completeAction,
const mean_field::operators::StellarEquilibriumLayout &layout
) {
const mfem::Vector gradient = experiment::null_space::const_residual_view(
completeAction, layout, experiment::null_space::gravityGradientResidual
);
const mfem::Vector potential = experiment::null_space::const_residual_view(
completeAction, layout, experiment::null_space::gravityPotentialResidual
);
mfem::Vector result(gradient.Size() + potential.Size());
mfem::Vector(result.GetData(), gradient.Size()) = gradient;
mfem::Vector(result.GetData() + gradient.Size(), potential.Size()) = potential;
return result;
}
void assign_gravity_completion(
mfem::Vector &completeDirection,
const mean_field::operators::StellarEquilibriumLayout &layout,
const mfem::Vector &gravityCompletion,
const int gravityGradientSize
) {
const mfem::Vector gravityGradient(
const_cast<mfem::real_t *>(gravityCompletion.GetData()), gravityGradientSize
);
const mfem::Vector gravityPotential(
const_cast<mfem::real_t *>(gravityCompletion.GetData()) + gravityGradientSize,
gravityCompletion.Size() - gravityGradientSize
);
experiment::null_space::assign_value_block(
completeDirection, layout, experiment::null_space::gravityGradientValue, gravityGradient
);
experiment::null_space::assign_value_block(
completeDirection, layout, experiment::null_space::gravityPotentialValue, gravityPotential
);
}
} // namespace
TEST_CASE(
"Gravity-Completed Reduced Surface Mode Responses Of The Stellar Equilibrium Jacobian",
"[null_space][surface_modes][gravity_completed]"
) {
mean_field::utils::Args args = test_utils::setup_args();
args.p.rtol = 1.0e-11;
args.p.atol = std::min(args.p.atol, 1.0e-13);
args.p.max_iters = std::max(args.p.max_iters, 2000);
experiment::null_space::N3Equilibrium fixture(std::move(args));
const MPI_Comm communicator = fixture.fem().mesh->GetComm();
int rank = 0;
MPI_Comm_rank(communicator, &rank);
const auto modes = experiment::null_space::make_surface_modes(fixture);
constexpr std::array<double, 2> rotationFractions{0.0, 0.5};
const int totalCases = static_cast<int>(rotationFractions.size() * modes.size());
int completedCases = 0;
for (const double rotationFraction : rotationFractions) {
const mean_field::physics::RigidRotation rotation = fixture.rotation(rotationFraction);
fixture.prepare(fixture.state(), rotation);
GravityUnknownJacobian gravityUnknownJacobian(fixture.stellar_operator());
mean_field::operators::ReducedGravityFieldPreconditioner gravityPreconditioner(
fixture.fem(), fixture.stellar_operator().GetGravityContext().GetGeometryContext()
);
mfem::MINRESSolver gravitySolver(communicator);
gravitySolver.SetOperator(gravityUnknownJacobian);
gravitySolver.SetPreconditioner(gravityPreconditioner);
gravitySolver.SetRelTol(1.0e-11);
gravitySolver.SetAbsTol(1.0e-13);
gravitySolver.SetMaxIter(2000);
gravitySolver.SetPrintLevel(1);
for (const experiment::null_space::SurfaceMode &mode : modes) {
experiment::null_space::report_progress(
communicator, "solving the gravity completion for " + mode.name + " at rotation fraction " +
std::to_string(rotationFraction) + " (" + std::to_string(completedCases + 1) + "/" +
std::to_string(totalCases) + ")"
);
const mfem::Vector surfaceOnlyAction = fixture.jacobian_action(mode.direction);
mfem::Vector gravityRightHandSide =
gravity_residual_blocks(surfaceOnlyAction, fixture.stellar_operator().GetLayout());
gravityRightHandSide *= -1.0;
mfem::Vector gravityCompletion(gravityUnknownJacobian.Width());
gravityCompletion = 0.0;
gravitySolver.Mult(gravityRightHandSide, gravityCompletion);
REQUIRE(gravitySolver.GetConverged());
mfem::Vector gravitySolveAction;
gravityUnknownJacobian.Mult(gravityCompletion, gravitySolveAction);
mfem::Vector gravitySolveResidual(gravitySolveAction);
gravitySolveResidual -= gravityRightHandSide;
const double gravityRightHandSideNorm =
experiment::null_space::global_norm(gravityRightHandSide, communicator);
const double gravitySolveResidualNorm =
experiment::null_space::global_norm(gravitySolveResidual, communicator);
const double gravitySolveRelativeResidual =
gravitySolveResidualNorm / std::max(gravityRightHandSideNorm, std::numeric_limits<double>::epsilon());
REQUIRE(std::isfinite(gravitySolveRelativeResidual));
mfem::Vector completedDirection(mode.direction);
assign_gravity_completion(
completedDirection, fixture.stellar_operator().GetLayout(), gravityCompletion,
gravityUnknownJacobian.gravity_gradient_size()
);
const mfem::Vector completedAction = fixture.jacobian_action(completedDirection);
std::map<std::string, double> metrics{
{"surface_only_input_norm", experiment::null_space::global_norm(mode.direction, communicator)},
{"gravity_completion_norm", experiment::null_space::global_norm(gravityCompletion, communicator)},
{"completed_input_norm", experiment::null_space::global_norm(completedDirection, communicator)},
{"surface_only_action_norm", experiment::null_space::global_norm(surfaceOnlyAction, communicator)},
{"gravity_completed_action_norm", experiment::null_space::global_norm(completedAction, communicator)},
{"gravity_solve_rhs_norm", gravityRightHandSideNorm},
{"gravity_solve_residual_norm", gravitySolveResidualNorm},
{"gravity_solve_relative_residual", gravitySolveRelativeResidual},
{"gravity_solve_iterations", static_cast<double>(gravitySolver.GetNumIterations())},
{"gravity_solve_final_norm", gravitySolver.GetFinalNorm()}
};
add_block_metrics(
metrics, "surface_only_",
experiment::null_space::residual_block_norms(
surfaceOnlyAction, fixture.stellar_operator().GetLayout(), communicator
)
);
add_block_metrics(
metrics, "gravity_completed_",
experiment::null_space::residual_block_norms(
completedAction, fixture.stellar_operator().GetLayout(), communicator
)
);
if (rank == 0) {
experiment::record_experiment_result(
"gravity_completed_reduced_surface_modes", mode.name,
{{"mode_kind", experiment::null_space::surface_mode_kind_name(mode.kind)},
{"axis", std::to_string(mode.axis)},
{"rotation_fraction_of_keplerian", std::to_string(rotationFraction)},
{"mesh_file", test_utils::setup_args().mesh_file},
{"local_state_dofs", std::to_string(fixture.stellar_operator().Width())}},
std::move(metrics)
);
}
++completedCases;
experiment::null_space::report_progress(
communicator, "completed " + std::to_string(completedCases) + "/" + std::to_string(totalCases) +
" gravity-completed reduced surface-mode cases"
);
}
}
experiment::null_space::report_progress(
communicator, "gravity-completed reduced surface-mode probe complete; writing CSV output"
);
}