perf(jacobian-action): major updates to jacobian action application by removing redudant quadrature work. ~5x increase in speed

This commit is contained in:
2026-09-02 17:01:50 -04:00
parent 85500fef3b
commit 25510008dd
74 changed files with 8967 additions and 814 deletions

View File

@@ -0,0 +1,120 @@
module;
#include <cmath>
#include <concepts>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include <mfem.hpp>
export module mean_field:seed.lane_emden;
export import :dimensions.quantities;
export import :eos.polytrope;
export import :model.typed_stellar;
export namespace mean_field::seed {
struct DimensionlessLaneEmdenSolution final {
mfem::Vector coordinate;
mfem::Vector theta;
mfem::Vector thetaDerivative;
std::optional<double> firstZeroCoordinate;
};
/*
* Integrate the dimensionless Lane-Emden equation from the regular center
* to either the first zero of theta or coordinateLimit, whichever occurs
* first. This numerical kernel also supports the n = 0 and n = 5 analytic
* benchmark cases even though they do not both define admissible seeds for
* the current Polytrope EOS and finite stellar domain.
*/
[[nodiscard]] DimensionlessLaneEmdenSolution integrateLaneEmden(
double polytropicIndex,
double coordinateLimit,
double integrationStep = 1.0e-3
);
struct RadialProfile final {
mfem::Vector radius;
mfem::Vector density;
mfem::Vector specificEnthalpy;
dimensions::LengthValue stellarRadius;
dimensions::DensityValue centralDensity;
dimensions::SpecificEnthalpyValue centralSpecificEnthalpy;
};
class LaneEmden final {
public:
struct Parameters final {
std::optional<dimensions::DensityValue> centralDensity{std::nullopt};
int radialSampleCount{512};
};
LaneEmden()
: m_centralDensity(std::nullopt),
m_radialSampleCount(512) {
}
explicit LaneEmden(const Parameters parameters)
: m_centralDensity(parameters.centralDensity),
m_radialSampleCount(parameters.radialSampleCount) {
if (m_centralDensity.has_value() &&
(!std::isfinite(m_centralDensity->value()) || m_centralDensity->value() <= 0.0)) {
throw std::invalid_argument("A Lane-Emden seed central density must be finite and positive.");
}
if (m_radialSampleCount < 2) {
throw std::invalid_argument("A Lane-Emden seed requires at least two radial samples.");
}
}
[[nodiscard]] const std::optional<dimensions::DensityValue> &centralDensity() const noexcept {
return m_centralDensity;
}
[[nodiscard]] int radialSampleCount() const noexcept {
return m_radialSampleCount;
}
private:
std::optional<dimensions::DensityValue> m_centralDensity;
int m_radialSampleCount;
};
[[nodiscard]] RadialProfile generateLaneEmdenProfile(
const eos::Polytrope &equationOfState,
dimensions::DensityValue centralDensity,
int radialSampleCount
);
template <model::StellarModelType Model>
requires std::remove_cvref_t<Model>::template
containsSpecification<eos::Polytrope> [[nodiscard]] RadialProfile generateRadialProfile(
const Model &stellarModel,
const LaneEmden &strategy
) {
std::optional<dimensions::DensityValue> centralDensity = strategy.centralDensity();
if (!centralDensity.has_value()) {
if constexpr (std::remove_cvref_t<Model>::template containsSpecification<models::FixedCentralDensity>) {
centralDensity = stellarModel.template specification<models::FixedCentralDensity>().targetDensity();
} else {
throw std::invalid_argument(
"Lane-Emden seed generation requires either FixedCentralDensity or an explicit seed-only central "
"density."
);
}
}
return generateLaneEmdenProfile(
stellarModel.template specification<eos::Polytrope>(), *centralDensity, strategy.radialSampleCount()
);
}
template <typename Strategy, typename Model>
concept RadialSeedStrategyFor = requires(const Model &stellarModel, const Strategy &strategy) {
{ generateRadialProfile(stellarModel, strategy) } -> std::same_as<RadialProfile>;
};
} // namespace mean_field::seed

View File

@@ -0,0 +1,136 @@
module;
#include <concepts>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:seed.stellar_equilibrium_projection;
export import :operators.stellar_equilibrium_problem;
export import :physics.gravity;
export import :seed.lane_emden;
export namespace mean_field::seed {
struct StellarEquilibriumProjectionOptions final {
physics::GravitySolveOptions gravity{};
double surfaceRadiusRelativeTolerance{5.0e-4};
};
template <equilibrium::StellarEquilibriumModel Model> struct ProjectedEquilibriumState final {
using ModelType = std::remove_cvref_t<Model>;
mfem::Vector values;
};
namespace detail {
struct ProjectedRadialFields final {
mfem::Vector density;
mfem::Vector gravityGradient;
mfem::Vector gravityPotential;
mfem::Vector specificEnthalpy;
double bernoulliConstant;
};
[[nodiscard]] ProjectedRadialFields projectRadialFields(
const equilibrium::StellarDiscretization &discretization,
const RadialProfile &profile,
dimensions::MassValue targetMass,
dimensions::PressureValue targetSurfacePressure,
const StellarEquilibriumProjectionOptions &options
);
inline void assignProjectedBlock(
mfem::Vector destination,
const mfem::Vector &source,
const char *name
) {
if (destination.Size() != source.Size()) {
throw std::invalid_argument(name);
}
destination = source;
}
} // namespace detail
template <equilibrium::StellarEquilibriumModel Model>
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options = {}
) {
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
problem.GetDiscretization(), profile,
problem.GetStellarModel().template specification<models::FixedTotalMass>().targetMass(),
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure(), options
);
mfem::Vector values(problem.StateSize());
values = 0.0;
const auto stateView = problem.GetManifest().stateView(values);
detail::assignProjectedBlock(
stateView.block(utils::blocks::density_field.mass_term), fields.density,
"The projected density does not match the compiled equilibrium-state block."
);
stateView.block(utils::blocks::surface_deformation_field.parameters_term) = 0.0;
detail::assignProjectedBlock(
stateView.block(utils::blocks::gravity_field.gradient_term), fields.gravityGradient,
"The projected gravity gradient does not match the compiled equilibrium-state block."
);
detail::assignProjectedBlock(
stateView.block(utils::blocks::gravity_field.poisson_term), fields.gravityPotential,
"The projected gravity potential does not match the compiled equilibrium-state block."
);
detail::assignProjectedBlock(
stateView.block(utils::blocks::enthalpy_field.specific_term), fields.specificEnthalpy,
"The projected specific enthalpy does not match the compiled equilibrium-state block."
);
/*
* Projection of a continuous spherical profile onto a faceted
* reference mesh generally leaves a small trace error on the physical
* surface. The pressure condition replaces these carrier rows in the
* compiled equilibrium problem, so impose its required carrier value
* exactly after bulk projection instead of treating that geometric
* mismatch as part of the initial residual.
*/
mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
problem.GetStellarModel().template specification<eos::Polytrope>(),
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure()
);
for (const int surfaceRow : problem.GetPressureSurfaceRows().reduced_dofs()) {
enthalpy(surfaceRow) = requiredSurfaceEnthalpy.value();
}
mfem::Vector fixedMassCoordinate =
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
if (fixedMassCoordinate.Size() != 1) {
throw std::invalid_argument("FixedTotalMass must generate exactly one equilibrium-state coordinate.");
}
fixedMassCoordinate(0) = fields.bernoulliConstant;
if constexpr (std::remove_cvref_t<Model>::template containsSpecification<models::FixedCentralDensity>) {
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.0;
}
return {.values = std::move(values)};
}
template <
equilibrium::StellarEquilibriumModel Model,
typename Strategy>
requires RadialSeedStrategyFor<
Strategy,
typename equilibrium::StellarEquilibriumProblem<Model>::ModelType>
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const Strategy &strategy,
const StellarEquilibriumProjectionOptions &options = {}
) {
return projectRadialProfile(problem, generateRadialProfile(problem.GetStellarModel(), strategy), options);
}
} // namespace mean_field::seed