feat(surface): major work on implementing surface constraints in a presciption agnostic manner
This commit is contained in:
@@ -24,6 +24,49 @@ Run only the budget and choose its output path with:
|
||||
./mean_field_experiments --experiment-output gravity_budget.csv --catch2 "[accuracy]"
|
||||
```
|
||||
|
||||
## Stellar-equilibrium null-space experiments
|
||||
|
||||
`stellar_null_space_experiments` is a dedicated diagnostic executable rather
|
||||
than an ordinary verification or validation test. It constructs the analytic
|
||||
`n = 3` Lane-Emden seed, probes the three computational translations and three
|
||||
computational rotations, and compares the Jacobian before and after the strong
|
||||
centering-row replacement. It records total and residual-block response norms,
|
||||
the isolated centering contribution, and centered finite-difference errors.
|
||||
|
||||
The experiment prints rank-zero progress messages while it builds the seed,
|
||||
solves its gravity field, and completes each rigid-mode case. Run it with:
|
||||
|
||||
```text
|
||||
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
|
||||
--experiment-output stellar_null_space.csv \
|
||||
--catch2 "[null_space][rigid_motion]"
|
||||
```
|
||||
|
||||
The rotation sweep includes zero rotation and a spherical-state diagnostic at
|
||||
half the Keplerian angular speed. The rotating result is an operator-symmetry
|
||||
probe, not a definitive rotating-equilibrium null-space measurement.
|
||||
|
||||
The gravity-completed probe solves the linearized mixed gravity subsystem for
|
||||
the gravity-gradient and gravity-potential variations accompanying each rigid
|
||||
displacement. It then measures the complete equilibrium response with and
|
||||
without the centering rows:
|
||||
|
||||
```text
|
||||
mpirun -np 1 ./cmake-build-debug-homebrew/stellar_null_space_experiments \
|
||||
--experiment-output gravity_completed_null_space.csv \
|
||||
--catch2 "[null_space][gravity_completed]"
|
||||
```
|
||||
|
||||
The gravity solver prints its convergence summary, while the experiment prints
|
||||
the current mode and completed-case count. This probe prepares each rotation
|
||||
state only once and does not repeat the expensive nonlinear finite-difference
|
||||
calculations from the original rigid-motion diagnostic.
|
||||
|
||||
A whole-Jacobian dense singular-value experiment is intentionally deferred.
|
||||
The checked-in `sandbox.smesh` is too large for a useful dense SVD, and the
|
||||
current matrix-free root operator does not provide a transpose action needed by
|
||||
a scalable smallest-singular-value method.
|
||||
|
||||
The executable needs the same dependencies, generated module mapping, and
|
||||
configuration registration as the existing Catch2 test executable. Add
|
||||
`experiment_main.cpp` and `gravity_accuracy_budget.cpp` as a second executable
|
||||
|
||||
273
experiments/gravity_completed_rigid_motion.cpp
Normal file
273
experiments/gravity_completed_rigid_motion.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
#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
|
||||
);
|
||||
}
|
||||
|
||||
void apply_centering_rows(
|
||||
const mean_field::operators::PreparedStellarEquilibriumOperator &stellarOperator,
|
||||
const mfem::Vector &direction,
|
||||
mfem::Vector &action
|
||||
) {
|
||||
const auto &layout = stellarOperator.GetLayout();
|
||||
const mfem::Vector displacementDirection =
|
||||
experiment::null_space::const_value_view(direction, layout, experiment::null_space::displacementValue);
|
||||
mfem::Vector displacementAction =
|
||||
experiment::null_space::residual_view(action, layout, experiment::null_space::displacementResidual);
|
||||
stellarOperator.GetCenteringConstraintOperator().ApplyJacobianRows(displacementDirection, displacementAction);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Gravity-Completed Rigid Motion Responses Of The Stellar Equilibrium Jacobian",
|
||||
"[null_space][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_rigid_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::RigidMode &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 displacementOnlyAction = fixture.unpinned_jacobian_action(mode.direction);
|
||||
mfem::Vector gravityRightHandSide =
|
||||
gravity_residual_blocks(displacementOnlyAction, 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 completedUnpinnedAction = fixture.unpinned_jacobian_action(completedDirection);
|
||||
mfem::Vector completedConstrainedAction(completedUnpinnedAction);
|
||||
apply_centering_rows(fixture.stellar_operator(), completedDirection, completedConstrainedAction);
|
||||
|
||||
mfem::Vector centeringContribution(completedConstrainedAction);
|
||||
centeringContribution -= completedUnpinnedAction;
|
||||
|
||||
std::map<std::string, double> metrics{
|
||||
{"displacement_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)},
|
||||
{"displacement_only_action_norm",
|
||||
experiment::null_space::global_norm(displacementOnlyAction, communicator)},
|
||||
{"gravity_completed_unpinned_action_norm",
|
||||
experiment::null_space::global_norm(completedUnpinnedAction, communicator)},
|
||||
{"gravity_completed_constrained_action_norm",
|
||||
experiment::null_space::global_norm(completedConstrainedAction, communicator)},
|
||||
{"centering_contribution_norm",
|
||||
experiment::null_space::global_norm(centeringContribution, 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, "displacement_only_",
|
||||
experiment::null_space::residual_block_norms(
|
||||
displacementOnlyAction, fixture.stellar_operator().GetLayout(), communicator
|
||||
)
|
||||
);
|
||||
add_block_metrics(
|
||||
metrics, "gravity_completed_unpinned_",
|
||||
experiment::null_space::residual_block_norms(
|
||||
completedUnpinnedAction, fixture.stellar_operator().GetLayout(), communicator
|
||||
)
|
||||
);
|
||||
add_block_metrics(
|
||||
metrics, "gravity_completed_constrained_",
|
||||
experiment::null_space::residual_block_norms(
|
||||
completedConstrainedAction, fixture.stellar_operator().GetLayout(), communicator
|
||||
)
|
||||
);
|
||||
|
||||
if (rank == 0) {
|
||||
experiment::record_experiment_result(
|
||||
"gravity_completed_stellar_rigid_motion_null_space", mode.name,
|
||||
{{"mode_kind",
|
||||
mode.kind == experiment::null_space::RigidModeKind::translation ? "translation" : "rotation"},
|
||||
{"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 rigid-mode cases"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
experiment::null_space::report_progress(
|
||||
communicator, "gravity-completed rigid-motion probe complete; writing CSV output"
|
||||
);
|
||||
}
|
||||
174
experiments/rigid_motion_null_space.cpp
Normal file
174
experiments/rigid_motion_null_space.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#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 {
|
||||
[[nodiscard]] double relative_difference(
|
||||
const mfem::Vector &computed,
|
||||
const mfem::Vector &reference,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
mfem::Vector difference(computed);
|
||||
difference -= reference;
|
||||
const double scale = std::max(
|
||||
{experiment::null_space::global_norm(computed, communicator),
|
||||
experiment::null_space::global_norm(reference, communicator), std::numeric_limits<double>::epsilon()}
|
||||
);
|
||||
return experiment::null_space::global_norm(difference, communicator) / scale;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE(
|
||||
"Rigid Motion Responses Of The Stellar Equilibrium Jacobian",
|
||||
"[null_space][rigid_motion]"
|
||||
) {
|
||||
mean_field::utils::Args args = test_utils::setup_args();
|
||||
args.p.rtol = 1.0e-12;
|
||||
args.p.atol = std::min(args.p.atol, 1.0e-14);
|
||||
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_rigid_modes(fixture);
|
||||
constexpr std::array<double, 2> rotationFractions{0.0, 0.5};
|
||||
constexpr std::array<double, 2> finiteDifferenceSteps{1.0e-4, 1.0e-6};
|
||||
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);
|
||||
|
||||
mfem::Vector constrainedResidual;
|
||||
fixture.stellar_operator().BuildResidual(constrainedResidual);
|
||||
const mfem::Vector unpinnedResidual = fixture.unpinned_residual();
|
||||
|
||||
REQUIRE(constrainedResidual.Size() == unpinnedResidual.Size());
|
||||
REQUIRE(std::isfinite(experiment::null_space::global_norm(constrainedResidual, communicator)));
|
||||
REQUIRE(std::isfinite(experiment::null_space::global_norm(unpinnedResidual, communicator)));
|
||||
|
||||
for (const experiment::null_space::RigidMode &mode : modes) {
|
||||
experiment::null_space::report_progress(
|
||||
communicator, "probing " + mode.name + " at rotation fraction " + std::to_string(rotationFraction) +
|
||||
" (" + std::to_string(completedCases + 1) + "/" + std::to_string(totalCases) + ")"
|
||||
);
|
||||
|
||||
fixture.prepare(fixture.state(), rotation);
|
||||
const mfem::Vector unpinnedAction = fixture.unpinned_jacobian_action(mode.direction);
|
||||
mfem::Vector constrainedAction;
|
||||
fixture.stellar_operator().Mult(mode.direction, constrainedAction);
|
||||
|
||||
mfem::Vector centeringContribution(constrainedAction);
|
||||
centeringContribution -= unpinnedAction;
|
||||
|
||||
const double inputNorm = experiment::null_space::global_norm(mode.direction, communicator);
|
||||
const double unpinnedNorm = experiment::null_space::global_norm(unpinnedAction, communicator);
|
||||
const double constrainedNorm = experiment::null_space::global_norm(constrainedAction, communicator);
|
||||
|
||||
REQUIRE(inputNorm > 0.0);
|
||||
REQUIRE(std::isfinite(unpinnedNorm));
|
||||
REQUIRE(std::isfinite(constrainedNorm));
|
||||
|
||||
std::map<std::string, double> metrics{
|
||||
{"input_algebraic_norm", inputNorm},
|
||||
{"unpinned_action_norm", unpinnedNorm},
|
||||
{"unpinned_action_per_input_norm", unpinnedNorm / inputNorm},
|
||||
{"constrained_action_norm", constrainedNorm},
|
||||
{"constrained_action_per_input_norm", constrainedNorm / inputNorm},
|
||||
{"centering_contribution_norm",
|
||||
experiment::null_space::global_norm(centeringContribution, communicator)},
|
||||
{"unpinned_base_residual_norm", experiment::null_space::global_norm(unpinnedResidual, communicator)},
|
||||
{"constrained_base_residual_norm",
|
||||
experiment::null_space::global_norm(constrainedResidual, communicator)}
|
||||
};
|
||||
|
||||
add_block_metrics(
|
||||
metrics, "unpinned_",
|
||||
experiment::null_space::residual_block_norms(
|
||||
unpinnedAction, fixture.stellar_operator().GetLayout(), communicator
|
||||
)
|
||||
);
|
||||
add_block_metrics(
|
||||
metrics, "constrained_",
|
||||
experiment::null_space::residual_block_norms(
|
||||
constrainedAction, fixture.stellar_operator().GetLayout(), communicator
|
||||
)
|
||||
);
|
||||
|
||||
for (const double step : finiteDifferenceSteps) {
|
||||
mfem::Vector plusState(fixture.state());
|
||||
plusState.Add(step, mode.direction);
|
||||
fixture.prepare(plusState, rotation);
|
||||
const mfem::Vector plusResidual = fixture.unpinned_residual();
|
||||
|
||||
mfem::Vector minusState(fixture.state());
|
||||
minusState.Add(-step, mode.direction);
|
||||
fixture.prepare(minusState, rotation);
|
||||
const mfem::Vector minusResidual = fixture.unpinned_residual();
|
||||
|
||||
mfem::Vector finiteDifference(plusResidual);
|
||||
finiteDifference -= minusResidual;
|
||||
finiteDifference /= 2.0 * step;
|
||||
|
||||
const std::string stepName = step == finiteDifferenceSteps.front() ? "1e-4" : "1e-6";
|
||||
metrics.emplace(
|
||||
"finite_difference_relative_error_" + stepName,
|
||||
relative_difference(unpinnedAction, finiteDifference, communicator)
|
||||
);
|
||||
}
|
||||
|
||||
fixture.prepare(fixture.state(), rotation);
|
||||
|
||||
if (rank == 0) {
|
||||
experiment::record_experiment_result(
|
||||
"stellar_rigid_motion_null_space", mode.name,
|
||||
{{"mode_kind",
|
||||
mode.kind == experiment::null_space::RigidModeKind::translation ? "translation" : "rotation"},
|
||||
{"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) + " rigid-mode cases"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
experiment::null_space::report_progress(communicator, "rigid-motion probe complete; writing CSV output");
|
||||
}
|
||||
519
experiments/stellar_null_space.cppm
Normal file
519
experiments/stellar_null_space.cppm
Normal file
@@ -0,0 +1,519 @@
|
||||
module;
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <mpi.h>
|
||||
|
||||
export module experiment.stellar_null_space;
|
||||
|
||||
import mean_field;
|
||||
import test_helpers;
|
||||
|
||||
export namespace experiment::null_space {
|
||||
using Form = mean_field::utils::blocks::barotropic_equilibrium_form;
|
||||
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
using Model = mean_field::models::StellarModel<mean_field::models::structure::PolytropicStructure>;
|
||||
|
||||
constexpr auto densityValue =
|
||||
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::density_field.mass_term);
|
||||
constexpr auto displacementValue =
|
||||
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::displacement_field.geometry_term);
|
||||
constexpr auto gravityGradientValue =
|
||||
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravityPotentialValue =
|
||||
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::gravity_field.poisson_term);
|
||||
constexpr auto enthalpyValue =
|
||||
mean_field::utils::blocks::get_value_block<Form>(mean_field::utils::blocks::enthalpy_field.specific_term);
|
||||
constexpr auto bernoulliValue = mean_field::utils::blocks::get_value_block<Form>(
|
||||
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
|
||||
);
|
||||
|
||||
constexpr auto gravityGradientResidual =
|
||||
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::gravity_field.gradient_term);
|
||||
constexpr auto gravityPotentialResidual =
|
||||
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::gravity_field.poisson_term);
|
||||
constexpr auto densityResidual =
|
||||
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::density_field.mass_term);
|
||||
constexpr auto displacementResidual = mean_field::utils::blocks::get_residual_block<Form>(
|
||||
mean_field::utils::blocks::displacement_field.geometry_term
|
||||
);
|
||||
constexpr auto enthalpyResidual =
|
||||
mean_field::utils::blocks::get_residual_block<Form>(mean_field::utils::blocks::enthalpy_field.specific_term);
|
||||
constexpr auto massResidual = mean_field::utils::blocks::get_residual_block<Form>(
|
||||
mean_field::utils::blocks::barotropic_constant_field.mass_normalization_term
|
||||
);
|
||||
|
||||
inline constexpr std::array<const char *, 6> residualBlockNames{"gravity_gradient", "gravity_potential", "closure",
|
||||
"displacement", "hydrostatic", "mass"};
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] mfem::Vector value_view(
|
||||
mfem::Vector &vector,
|
||||
const mean_field::operators::StellarEquilibriumLayout &layout,
|
||||
const mean_field::utils::blocks::value_block<index> block
|
||||
) {
|
||||
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
|
||||
}
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] mfem::Vector const_value_view(
|
||||
const mfem::Vector &vector,
|
||||
const mean_field::operators::StellarEquilibriumLayout &layout,
|
||||
const mean_field::utils::blocks::value_block<index> block
|
||||
) {
|
||||
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
|
||||
}
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] mfem::Vector residual_view(
|
||||
mfem::Vector &vector,
|
||||
const mean_field::operators::StellarEquilibriumLayout &layout,
|
||||
const mean_field::utils::blocks::residual_block<index> block
|
||||
) {
|
||||
return mfem::Vector(vector.GetData() + layout.offset(block), layout.size(block));
|
||||
}
|
||||
|
||||
template <int index>
|
||||
[[nodiscard]] mfem::Vector const_residual_view(
|
||||
const mfem::Vector &vector,
|
||||
const mean_field::operators::StellarEquilibriumLayout &layout,
|
||||
const mean_field::utils::blocks::residual_block<index> block
|
||||
) {
|
||||
return mfem::Vector(const_cast<mfem::real_t *>(vector.GetData()) + layout.offset(block), layout.size(block));
|
||||
}
|
||||
|
||||
template <int index>
|
||||
void assign_value_block(
|
||||
mfem::Vector &vector,
|
||||
const mean_field::operators::StellarEquilibriumLayout &layout,
|
||||
const mean_field::utils::blocks::value_block<index> block,
|
||||
const mfem::Vector &source
|
||||
) {
|
||||
MFEM_VERIFY(source.Size() == layout.size(block), "Null-space experiment received a block with the wrong size.");
|
||||
value_view(vector, layout, block) = source;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double global_norm(
|
||||
const mfem::Vector &vector,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
const double localNormSquared = vector * vector;
|
||||
double globalNormSquared = 0.0;
|
||||
MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, communicator);
|
||||
return std::sqrt(globalNormSquared);
|
||||
}
|
||||
|
||||
inline void report_progress(
|
||||
const MPI_Comm communicator,
|
||||
const std::string &message
|
||||
) {
|
||||
int rank = 0;
|
||||
MPI_Comm_rank(communicator, &rank);
|
||||
if (rank == 0) {
|
||||
std::cout << "[null-space experiment] " << message << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mean_field::operators::StellarEquilibriumDependencies make_dependencies() {
|
||||
return {
|
||||
.discretization = {.identity = 2003, .revision = 1},
|
||||
.density = {.identity = 2011, .revision = 1},
|
||||
.displacement = {.identity = 2017, .revision = 1},
|
||||
.gravityGradient = {.identity = 2027, .revision = 1},
|
||||
.gravityPotential = {.identity = 2029, .revision = 1},
|
||||
.enthalpy = {.identity = 2039, .revision = 1},
|
||||
.bernoulliConstant = {.identity = 2053, .revision = 1},
|
||||
.rotation = {.identity = 2063, .revision = 1},
|
||||
.targetMass = {.identity = 2069, .revision = 1}
|
||||
};
|
||||
}
|
||||
|
||||
inline void increment_state_revisions(mean_field::operators::StellarEquilibriumDependencies &dependencies) {
|
||||
++dependencies.density.revision;
|
||||
++dependencies.displacement.revision;
|
||||
++dependencies.gravityGradient.revision;
|
||||
++dependencies.gravityPotential.revision;
|
||||
++dependencies.enthalpy.revision;
|
||||
++dependencies.bernoulliConstant.revision;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector pack_gravity_state(
|
||||
const mfem::Vector &density,
|
||||
const mfem::Vector &displacement,
|
||||
const mfem::Vector &gravityGradient,
|
||||
const mfem::Vector &gravityPotential
|
||||
) {
|
||||
const std::array<int, 5> offsets{
|
||||
0, density.Size(), density.Size() + displacement.Size(),
|
||||
density.Size() + displacement.Size() + gravityGradient.Size(),
|
||||
density.Size() + displacement.Size() + gravityGradient.Size() + gravityPotential.Size()
|
||||
};
|
||||
mfem::Vector packed(offsets.back());
|
||||
mfem::Vector(packed.GetData() + offsets[0], density.Size()) = density;
|
||||
mfem::Vector(packed.GetData() + offsets[1], displacement.Size()) = displacement;
|
||||
mfem::Vector(packed.GetData() + offsets[2], gravityGradient.Size()) = gravityGradient;
|
||||
mfem::Vector(packed.GetData() + offsets[3], gravityPotential.Size()) = gravityPotential;
|
||||
return packed;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline Model make_model() {
|
||||
const double pi = std::acos(-1.0);
|
||||
const double targetMass = mean_field::utils::MASS;
|
||||
constexpr double dimensionlessMass = 2.0182359509662283534;
|
||||
const double polytropicConstant =
|
||||
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
|
||||
|
||||
return Model{
|
||||
mean_field::models::structure::PolytropicStructure{
|
||||
mean_field::eos::Polytrope{3.0, polytropicConstant}, targetMass
|
||||
},
|
||||
mean_field::surface::ConstantPressureSurface{mean_field::eos::PressureValue{0.0}}
|
||||
};
|
||||
}
|
||||
|
||||
class N3Equilibrium final {
|
||||
public:
|
||||
explicit N3Equilibrium(mean_field::utils::Args args)
|
||||
: m_args(std::move(args)),
|
||||
m_fem(
|
||||
mean_field::fem::setup_fem(
|
||||
m_args.mesh_file,
|
||||
m_args,
|
||||
0
|
||||
)
|
||||
),
|
||||
m_model(make_model()),
|
||||
m_operator(
|
||||
m_fem,
|
||||
*m_fem.domainMapperStateless,
|
||||
m_model
|
||||
),
|
||||
m_state(m_operator.GetLayout().value_offsets().Last()),
|
||||
m_dependencies(make_dependencies()) {
|
||||
MFEM_VERIFY(m_fem.okay(), "The null-space experiment could not construct the finite-element problem.");
|
||||
m_state = 0.0;
|
||||
initialize_state();
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::fem::FEM &fem() noexcept {
|
||||
return m_fem;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mean_field::fem::FEM &fem() const noexcept {
|
||||
return m_fem;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::operators::PreparedStellarEquilibriumOperator &stellar_operator() noexcept {
|
||||
return m_operator;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mean_field::operators::PreparedStellarEquilibriumOperator &
|
||||
stellar_operator() const noexcept {
|
||||
return m_operator;
|
||||
}
|
||||
|
||||
[[nodiscard]] const mfem::Vector &state() const noexcept {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
[[nodiscard]] mean_field::physics::RigidRotation rotation(const double fractionOfKeplerian) const {
|
||||
const double radius = mean_field::utils::RADIUS;
|
||||
const double mass = mean_field::utils::MASS;
|
||||
const double keplerianSpeed = std::sqrt(mean_field::utils::G * mass / (radius * radius * radius));
|
||||
|
||||
mfem::Vector angularVelocity(3);
|
||||
angularVelocity = 0.0;
|
||||
angularVelocity(2) = fractionOfKeplerian * keplerianSpeed;
|
||||
|
||||
mfem::Vector center(3);
|
||||
center = 0.0;
|
||||
return mean_field::physics::RigidRotation(angularVelocity, center);
|
||||
}
|
||||
|
||||
void prepare(
|
||||
const mfem::Vector &state,
|
||||
const mean_field::physics::RigidRotation &rotation
|
||||
) {
|
||||
m_currentState = state;
|
||||
increment_state_revisions(m_dependencies);
|
||||
++m_dependencies.rotation.revision;
|
||||
m_operator.Prepare(state, m_dependencies, rotation);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector unpinned_residual() const {
|
||||
const auto &layout = m_operator.GetLayout();
|
||||
const mfem::Vector reducedDensity = const_value_view(m_currentState, layout, densityValue);
|
||||
const mfem::Vector displacement = const_value_view(m_currentState, layout, displacementValue);
|
||||
const mfem::Vector gravityGradient = const_value_view(m_currentState, layout, gravityGradientValue);
|
||||
const mfem::Vector gravityPotential = const_value_view(m_currentState, layout, gravityPotentialValue);
|
||||
const mfem::Vector gravityState =
|
||||
pack_gravity_state(reducedDensity, displacement, gravityGradient, gravityPotential);
|
||||
|
||||
mfem::Vector gravity;
|
||||
mfem::Vector closure;
|
||||
mfem::Vector displacementRows;
|
||||
mfem::Vector hydrostatic;
|
||||
mfem::Vector mass;
|
||||
m_operator.GetGravityOperator().Mult(gravityState, gravity);
|
||||
m_operator.GetBarotropicClosureOperator().BuildResidual(closure);
|
||||
m_operator.GetDisplacementOperator().BuildResidual(displacementRows);
|
||||
m_operator.GetHydrostaticOperator().BuildResidual(hydrostatic);
|
||||
m_operator.GetSurfaceConstraintOperator().ApplyResidualRows(hydrostatic);
|
||||
m_operator.GetMassNormalizationOperator().BuildResidual(mass);
|
||||
|
||||
return pack_residual(gravity, closure, displacementRows, hydrostatic, mass);
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector unpinned_jacobian_action(const mfem::Vector &direction) const {
|
||||
const auto &layout = m_operator.GetLayout();
|
||||
const mfem::Vector reducedDensityDirection = const_value_view(direction, layout, densityValue);
|
||||
const mfem::Vector displacementDirection = const_value_view(direction, layout, displacementValue);
|
||||
const mfem::Vector gravityGradientDirection = const_value_view(direction, layout, gravityGradientValue);
|
||||
const mfem::Vector gravityPotentialDirection = const_value_view(direction, layout, gravityPotentialValue);
|
||||
const mfem::Vector reducedEnthalpyDirection = const_value_view(direction, layout, enthalpyValue);
|
||||
const mfem::Vector bernoulliDirection = const_value_view(direction, layout, bernoulliValue);
|
||||
|
||||
const mfem::Vector gravityDirection = pack_gravity_state(
|
||||
reducedDensityDirection, displacementDirection, gravityGradientDirection, gravityPotentialDirection
|
||||
);
|
||||
|
||||
mfem::Vector gravity;
|
||||
mfem::Vector closure;
|
||||
mfem::Vector displacementRows;
|
||||
mfem::Vector hydrostatic;
|
||||
mfem::Vector mass;
|
||||
m_operator.GetGravityJacobianOperator().Mult(gravityDirection, gravity);
|
||||
m_operator.GetBarotropicClosureOperator().Mult(
|
||||
reducedDensityDirection, reducedEnthalpyDirection, displacementDirection, closure
|
||||
);
|
||||
m_operator.GetDisplacementOperator().ApplyCompleteJacobianAction(
|
||||
reducedDensityDirection, displacementDirection, gravityGradientDirection, reducedEnthalpyDirection,
|
||||
displacementRows
|
||||
);
|
||||
m_operator.GetHydrostaticOperator().ApplyCompleteJacobianAction(
|
||||
reducedEnthalpyDirection, gravityPotentialDirection, bernoulliDirection(0), displacementDirection,
|
||||
hydrostatic
|
||||
);
|
||||
m_operator.GetSurfaceConstraintOperator().ApplyJacobianRows(reducedEnthalpyDirection, hydrostatic);
|
||||
m_operator.GetMassNormalizationOperator().ApplyCompleteJacobianAction(
|
||||
reducedDensityDirection, displacementDirection, mass
|
||||
);
|
||||
|
||||
return pack_residual(gravity, closure, displacementRows, hydrostatic, mass);
|
||||
}
|
||||
|
||||
private:
|
||||
void initialize_state() {
|
||||
report_progress(m_fem.mesh->GetComm(), "constructing the analytic n=3 Lane-Emden state");
|
||||
|
||||
constexpr double surfaceCoordinate = 6.8968486193769603755;
|
||||
constexpr int radialSampleCount = 8192;
|
||||
const double pi = std::acos(-1.0);
|
||||
const double radius = mean_field::utils::RADIUS;
|
||||
const double targetMass = mean_field::utils::MASS;
|
||||
constexpr double dimensionlessMass = 2.0182359509662283534;
|
||||
const double polytropicConstant =
|
||||
pi * mean_field::utils::G * std::pow(targetMass / (4.0 * pi * dimensionlessMass), 2.0 / 3.0);
|
||||
const double centralDensity =
|
||||
std::pow(surfaceCoordinate * std::sqrt(polytropicConstant / (pi * mean_field::utils::G)) / radius, 3.0);
|
||||
|
||||
const mean_field::models::structure::StructureSeed seed =
|
||||
m_model.makeInitialSeed({.centralDensity = centralDensity, .radialSampleCount = radialSampleCount});
|
||||
|
||||
const auto interpolate = [](const mfem::Vector &radii, const mfem::Vector &values, const double r) {
|
||||
if (r <= radii(0)) {
|
||||
return values(0);
|
||||
}
|
||||
const int finalIndex = radii.Size() - 1;
|
||||
if (r >= radii(finalIndex)) {
|
||||
return values(finalIndex);
|
||||
}
|
||||
int lower = 0;
|
||||
int upper = finalIndex;
|
||||
while (upper - lower > 1) {
|
||||
const int middle = lower + (upper - lower) / 2;
|
||||
if (radii(middle) <= r) {
|
||||
lower = middle;
|
||||
} else {
|
||||
upper = middle;
|
||||
}
|
||||
}
|
||||
const double fraction = (r - radii(lower)) / (radii(upper) - radii(lower));
|
||||
return (1.0 - fraction) * values(lower) + fraction * values(upper);
|
||||
};
|
||||
|
||||
mfem::FunctionCoefficient densityCoefficient([&seed, &interpolate](const mfem::Vector &position) {
|
||||
const double r = position.Norml2();
|
||||
return r >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.density, r);
|
||||
});
|
||||
mfem::FunctionCoefficient enthalpyCoefficient([&seed, &interpolate](const mfem::Vector &position) {
|
||||
const double r = position.Norml2();
|
||||
return r >= seed.stellarRadius ? 0.0 : interpolate(seed.radius, seed.enthalpy, r);
|
||||
});
|
||||
|
||||
mfem::ParGridFunction densityField(m_fem.densityFes.get());
|
||||
mfem::ParGridFunction enthalpyField(m_fem.enthalpyFes.get());
|
||||
mfem::ParGridFunction displacementField(m_fem.displacementFes.get());
|
||||
densityField = 0.0;
|
||||
enthalpyField = 0.0;
|
||||
displacementField = 0.0;
|
||||
densityField.ProjectCoefficient(densityCoefficient);
|
||||
enthalpyField.ProjectCoefficient(enthalpyCoefficient);
|
||||
*m_fem.displacement = displacementField;
|
||||
|
||||
report_progress(m_fem.mesh->GetComm(), "solving the gravity field for the seed state");
|
||||
const mean_field::physics::GravitySolution gravity =
|
||||
mean_field::physics::solve_gravity_field(m_fem, m_args, densityField, displacementField);
|
||||
|
||||
mfem::Vector densityTrue;
|
||||
mfem::Vector enthalpyTrue;
|
||||
mfem::Vector displacementTrue;
|
||||
mfem::Vector gravityGradientTrue;
|
||||
mfem::Vector gravityPotentialTrue;
|
||||
densityField.GetTrueDofs(densityTrue);
|
||||
enthalpyField.GetTrueDofs(enthalpyTrue);
|
||||
displacementField.GetTrueDofs(displacementTrue);
|
||||
gravity.gradPhi.GetTrueDofs(gravityGradientTrue);
|
||||
gravity.phi.GetTrueDofs(gravityPotentialTrue);
|
||||
|
||||
const auto &layout = m_operator.GetLayout();
|
||||
const mean_field::field::FieldDofMap densityMap =
|
||||
mean_field::field::make_field_dof_map<mean_field::field::Density, DomainSchema>(*m_fem.densityFes);
|
||||
const mean_field::field::FieldDofMap enthalpyMap =
|
||||
mean_field::field::make_field_dof_map<mean_field::field::Enthalpy, DomainSchema>(*m_fem.enthalpyFes);
|
||||
|
||||
assign_value_block(m_state, layout, densityValue, densityMap.gather(densityTrue));
|
||||
assign_value_block(m_state, layout, displacementValue, displacementTrue);
|
||||
assign_value_block(m_state, layout, gravityGradientValue, gravityGradientTrue);
|
||||
assign_value_block(m_state, layout, gravityPotentialValue, gravityPotentialTrue);
|
||||
assign_value_block(m_state, layout, enthalpyValue, enthalpyMap.gather(enthalpyTrue));
|
||||
value_view(m_state, layout, bernoulliValue)(0) = -mean_field::utils::G * targetMass / radius;
|
||||
|
||||
m_currentState = m_state;
|
||||
prepare(m_state, rotation(0.0));
|
||||
report_progress(m_fem.mesh->GetComm(), "analytic state is prepared");
|
||||
}
|
||||
|
||||
[[nodiscard]] mfem::Vector pack_residual(
|
||||
const mfem::Vector &gravity,
|
||||
const mfem::Vector &closure,
|
||||
const mfem::Vector &displacementRows,
|
||||
const mfem::Vector &hydrostatic,
|
||||
const mfem::Vector &mass
|
||||
) const {
|
||||
const auto &layout = m_operator.GetLayout();
|
||||
mfem::Vector result(layout.residual_offsets().Last());
|
||||
result = 0.0;
|
||||
const mfem::Vector gravityGradient(gravity.GetData(), layout.size(gravityGradientResidual));
|
||||
const mfem::Vector gravityPotential(
|
||||
gravity.GetData() + layout.size(gravityGradientResidual), layout.size(gravityPotentialResidual)
|
||||
);
|
||||
residual_view(result, layout, gravityGradientResidual) = gravityGradient;
|
||||
residual_view(result, layout, gravityPotentialResidual) = gravityPotential;
|
||||
residual_view(result, layout, densityResidual) = closure;
|
||||
residual_view(result, layout, displacementResidual) = displacementRows;
|
||||
residual_view(result, layout, enthalpyResidual) = hydrostatic;
|
||||
residual_view(result, layout, massResidual) = mass;
|
||||
return result;
|
||||
}
|
||||
|
||||
mean_field::utils::Args m_args;
|
||||
mean_field::fem::FEM m_fem;
|
||||
Model m_model;
|
||||
mean_field::operators::PreparedStellarEquilibriumOperator m_operator;
|
||||
mfem::Vector m_state;
|
||||
mfem::Vector m_currentState;
|
||||
mean_field::operators::StellarEquilibriumDependencies m_dependencies;
|
||||
};
|
||||
|
||||
enum class RigidModeKind : std::uint8_t { translation, rotation };
|
||||
|
||||
struct RigidMode final {
|
||||
std::string name;
|
||||
RigidModeKind kind;
|
||||
int axis;
|
||||
mfem::Vector direction;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline std::array<
|
||||
RigidMode,
|
||||
6>
|
||||
make_rigid_modes(const N3Equilibrium &fixture) {
|
||||
const auto &fem = fixture.fem();
|
||||
const auto &layout = fixture.stellar_operator().GetLayout();
|
||||
std::array<RigidMode, 6> modes;
|
||||
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
mfem::ParGridFunction translation(fem.displacementFes.get());
|
||||
mfem::Vector translationValue(3);
|
||||
translationValue = 0.0;
|
||||
translationValue(axis) = 1.0;
|
||||
mfem::VectorConstantCoefficient coefficient(translationValue);
|
||||
translation.ProjectCoefficient(coefficient);
|
||||
mfem::Vector translationTrue;
|
||||
translation.GetTrueDofs(translationTrue);
|
||||
|
||||
mfem::Vector direction(layout.value_offsets().Last());
|
||||
direction = 0.0;
|
||||
assign_value_block(direction, layout, displacementValue, translationTrue);
|
||||
modes[axis] = RigidMode{
|
||||
.name = std::string("translation_") + static_cast<char>('x' + axis),
|
||||
.kind = RigidModeKind::translation,
|
||||
.axis = axis,
|
||||
.direction = std::move(direction)
|
||||
};
|
||||
}
|
||||
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
mfem::ParGridFunction rotation(fem.displacementFes.get());
|
||||
mfem::VectorFunctionCoefficient coefficient(3, [axis](const mfem::Vector &position, mfem::Vector &value) {
|
||||
value.SetSize(3);
|
||||
value = 0.0;
|
||||
const int first = (axis + 1) % 3;
|
||||
const int second = (axis + 2) % 3;
|
||||
value(first) = -position(second);
|
||||
value(second) = position(first);
|
||||
});
|
||||
rotation.ProjectCoefficient(coefficient);
|
||||
mfem::Vector rotationTrue;
|
||||
rotation.GetTrueDofs(rotationTrue);
|
||||
|
||||
mfem::Vector direction(layout.value_offsets().Last());
|
||||
direction = 0.0;
|
||||
assign_value_block(direction, layout, displacementValue, rotationTrue);
|
||||
modes[3 + axis] = RigidMode{
|
||||
.name = std::string("rotation_") + static_cast<char>('x' + axis),
|
||||
.kind = RigidModeKind::rotation,
|
||||
.axis = axis,
|
||||
.direction = std::move(direction)
|
||||
};
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::array<
|
||||
double,
|
||||
6>
|
||||
residual_block_norms(
|
||||
const mfem::Vector &action,
|
||||
const mean_field::operators::StellarEquilibriumLayout &layout,
|
||||
const MPI_Comm communicator
|
||||
) {
|
||||
return {
|
||||
global_norm(const_residual_view(action, layout, gravityGradientResidual), communicator),
|
||||
global_norm(const_residual_view(action, layout, gravityPotentialResidual), communicator),
|
||||
global_norm(const_residual_view(action, layout, densityResidual), communicator),
|
||||
global_norm(const_residual_view(action, layout, displacementResidual), communicator),
|
||||
global_norm(const_residual_view(action, layout, enthalpyResidual), communicator),
|
||||
global_norm(const_residual_view(action, layout, massResidual), communicator)
|
||||
};
|
||||
}
|
||||
} // namespace experiment::null_space
|
||||
Reference in New Issue
Block a user