feat(equations): simplified the supported equation set
previously we modeled gravity as a mixed formulation. However, this turns out to not be needed with the improved surface prescription. Removing this and returning to a simpler gravity system dramatically reduces the number of unknowns and therefore speeds up the system.
This commit is contained in:
@@ -1,192 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file arithmetic.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the EquationScale and EquationSum templates, which allow for scaling and summing equations.
|
||||
*
|
||||
* The astute observer will notice that none of the code in this file or indeed in in any part of the equations module actually implements summation or scaled
|
||||
* addition more generally. Rather, the code to manage that is implemented in the backend/mfem/operators module. Effectively these
|
||||
* templates are compile time tags telling that system how to combine weak contributions. The reason for this comes down to we dont really want to sum
|
||||
* at the point wise level; rather, we generally want to sum already integrated contributions. Since equations, by design, do not know any details about
|
||||
* the global system they are unable to manage this themselves.
|
||||
*
|
||||
* There is another, as yet unexplored case, where we introduce some point-wise summation equation. This could be implemented in a much more
|
||||
* local fashion and would probably be easier to trace; however, it would be semantically different in its meaning.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/equations/concepts.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
|
||||
/**
|
||||
* @brief Scale an equation by a constant coefficient.
|
||||
*
|
||||
* This class is a compile-time tag that indicates that the weak contributions from a given equation should be scaled by the given coefficient.
|
||||
* The actual scaling is performed by the backend. A simple example would be if the scale is set to 1 then the weak contributions would be integrated unchanged;
|
||||
* whereas if the scale were set to -1 then the weak contributions would be integrated and then negated.
|
||||
*
|
||||
* @tparam EquationT The equation type to scale.
|
||||
*/
|
||||
template <Equation EquationT>
|
||||
class EquationScale final {
|
||||
public:
|
||||
using Test = typename EquationT::Test; ///< The test type of the scaled equation is the same as the test type of the original equation.
|
||||
|
||||
static constexpr std::string_view name = "scaled equation"; ///< Compile time name for the scaled equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a scaled equation.
|
||||
*
|
||||
* @param equation The equation to scale.
|
||||
* @param coefficient The coefficient to scale the equation by. Must be finite.
|
||||
*
|
||||
* @contribution Residuals to all rows which are shared by the summed equations
|
||||
*
|
||||
* @throws std::invalid_argument If the coefficient is not finite.
|
||||
*/
|
||||
explicit EquationScale(
|
||||
EquationT equation,
|
||||
const double coefficient
|
||||
) :
|
||||
m_equation(std::move(equation)),
|
||||
m_coefficient(coefficient) {
|
||||
using namespace utils::errors;
|
||||
|
||||
serif_ensure<SERiFErrorCode::NON_FINITE_VALUE_ERROR, std::invalid_argument>(std::isfinite(coefficient), "An equation coefficient must be finite.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Return the contributions of the scaled equation.
|
||||
*
|
||||
* @return A tuple containing the contributions of the scaled equation.
|
||||
*/
|
||||
[[nodiscard]] auto contributions() const noexcept {
|
||||
return std::tie(m_equation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the coefficient of the scaled equation.
|
||||
*
|
||||
* @return The coefficient of the scaled equation.
|
||||
*/
|
||||
[[nodiscard]] double coefficient() const noexcept {
|
||||
return m_coefficient;
|
||||
}
|
||||
|
||||
private:
|
||||
EquationT m_equation; ///< The equation being scaled.
|
||||
double m_coefficient; ///< The coefficient to scale the equation by. Must be finite.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sum multiple equations together.
|
||||
*
|
||||
* This class is a compile-time tag that indicates that the weak contributions from multiple equations should be summed together. The actual summation is performed by the backend.
|
||||
* A simple example would be if two equations are summed together, then the weak contributions from both equations would be integrated and then added together to form the final residual.
|
||||
* Note also that we do not define a coefficient here. The backend only ever sums things, as you can see in EquationDifference to subtract we locally
|
||||
* introduce a -1 scaling factor.
|
||||
*
|
||||
* @requires The FirstT and RestTs equations must share the same test type, otherwise the summation is not well-defined.
|
||||
*
|
||||
* @contribution Residuals to all rows which are shared by the summed equations
|
||||
*
|
||||
* @note Only equations sharing a residual row can or should be summed together.
|
||||
*
|
||||
* @tparam FirstT The first equation type to sum.
|
||||
* @tparam RestTs The remaining equation types to sum.
|
||||
*/
|
||||
template <Equation FirstT, Equation... RestTs>
|
||||
requires (std::same_as<typename FirstT::Test, typename RestTs::Test> && ...)
|
||||
class EquationSum final {
|
||||
public:
|
||||
using Test = typename FirstT::Test; ///< The test type of the summed equations is the same as the test type of the first equation.
|
||||
|
||||
static constexpr std::string_view name = "equation sum"; ///< Compile time name for the summed equations, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a summed equation.
|
||||
*
|
||||
* @param first The first equation to sum.
|
||||
* @param rest Pack of the remaining equations to sum.
|
||||
*
|
||||
* @note the constructor uses the initializer list for m_equations, note that the coefficient defaults to 1.0
|
||||
*/
|
||||
explicit EquationSum(
|
||||
FirstT first,
|
||||
RestTs... rest
|
||||
) :
|
||||
m_equations(std::move(first), std::move(rest)...) {}
|
||||
|
||||
/**
|
||||
* @brief Return the contributions of the summed equations.
|
||||
*
|
||||
* @return A tuple containing the contributions of the summed equations.
|
||||
*/
|
||||
[[nodiscard]] const auto& contributions() const noexcept {
|
||||
return m_equations;
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<FirstT, RestTs...> m_equations; ///< The equations being summed together.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Subtract one equation from another.
|
||||
*
|
||||
* This class is a compile-time tag that indicates that the weak contributions from the right equation should be subtracted from the weak contributions of the left equation.
|
||||
* The actual subtraction is performed by the backend. A simple example would be if the left equation has a weak contribution of 1 and the right equation has a weak contribution of 2,
|
||||
* then the final residual would be -1.
|
||||
*
|
||||
* @requires The left and right equations must share the same test type, otherwise the subtraction is not well-defined.
|
||||
*
|
||||
* @contribution Residuals to all rows which are shared by the summed equations
|
||||
*
|
||||
* @note Only equations sharing a residual row can or should be subtracted from one another.
|
||||
*
|
||||
* @tparam LeftT The left equation type to subtract from.
|
||||
* @tparam RightT The right equation type to subtract.
|
||||
*/
|
||||
template <Equation LeftT, Equation RightT>
|
||||
requires std::same_as<typename LeftT::Test, typename RightT::Test>
|
||||
class EquationDifference final {
|
||||
public:
|
||||
using Test = typename LeftT::Test; ///< The test type of the difference equation is the same as the test type of the left equation.
|
||||
|
||||
static constexpr std::string_view name = "equation difference"; ///< Compile time name for the difference equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a difference equation.
|
||||
*
|
||||
* @param left The left equation to subtract from.
|
||||
* @param right The right equation to subtract.
|
||||
*/
|
||||
explicit EquationDifference(
|
||||
LeftT left,
|
||||
RightT right
|
||||
) :
|
||||
m_equation(std::move(left), EquationScale{std::move(right), -1.0}) {}
|
||||
|
||||
/**
|
||||
* @brief Return the contributions of the difference equation.
|
||||
*
|
||||
* @return A tuple containing the contributions of the difference equation.
|
||||
*/
|
||||
[[nodiscard]] const auto& contributions() const noexcept {
|
||||
return m_equation.contributions();
|
||||
}
|
||||
|
||||
private:
|
||||
EquationSum<LeftT, EquationScale<RightT>> m_equation; ///< The equation representing the difference, which is a sum of the left equation and the scaled right equation (scaled by -1).
|
||||
};
|
||||
} // namespace serif::equations
|
||||
@@ -1,175 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file barotropic_closure.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the barotropic closure equation
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/eos/evaluation.hpp"
|
||||
#include "serif/eos/exceptions.hpp"
|
||||
#include "serif/eos/models/polytropic.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
// On stellar domains: integral [rho - rho_EOS(h)] q_rho dV = 0.
|
||||
|
||||
/**
|
||||
* @class BarotropicClosure
|
||||
* @tparam EOST The equation of state type, this must satisfy the BarotropicClosureEOS concept. The requires section below lays out
|
||||
* what the BarotriopicClosureEOS concept requires.
|
||||
*
|
||||
* @requires The equation of state type must publish a valid Relation catalog
|
||||
* @requires The equation of state type must implement all relations required by the catalog it publishes
|
||||
* @requires The equation of state type must support the relation density from specific enthalpy
|
||||
* @requires The equation of state type must support the partial deriviative density from specific enthalpy with respect to specific enthalpy
|
||||
*
|
||||
* In a barotropic model surfaces of constant pressure coincide with surfaces of constant density and constant specific enthalpy.
|
||||
* The barotropic closure equation enforces that the density of the model argrees with the density of the the equation of state
|
||||
* given the model specific enthalpy. Put another way, for a barotropic model, this equation is what enforces
|
||||
* that the ethalpy and density must be consistent with each other and with the equation of state. The residual
|
||||
* is given by \f$ \rho - \rho_{\text{EOS}}(h) \f$, where \f$ \rho \f$ is the model density, \f$ h \f$ is the model specific enthalpy.
|
||||
*
|
||||
* The partial derivatives implemented are
|
||||
* - \f$ \frac{\partial r}{\partial \rho} = 1 \f$
|
||||
* - \f$ \frac{\partial \rho}{\partial h} = -\frac{\partial \rho_{\text{EOS}}(h)}{\partial h} \f$
|
||||
*
|
||||
* @note The eos derivative is implemented by the eos::partial_derivative function and is up to the eos model to implement correctly.
|
||||
*/
|
||||
template <eos::models::BarotropicClosureEOS EOST>
|
||||
class BarotropicClosure final {
|
||||
public:
|
||||
using Test = discretization::blocks::density::mass; ///< The test type is the model density, which is the quantity being constrained by the barotropic closure.
|
||||
using ResidualQuantity = dimensions::MassDensity; ///< The residual quantity is the mass density, which is the difference between the model density and the density given by the equation of state.
|
||||
using Enthalpy = discretization::blocks::enthalpy::specific; ///< The enthalpy type is the model specific enthalpy, which is the quantity being used to compute the density from the equation of state. Really this is just an alias.
|
||||
|
||||
/**
|
||||
* @brief The operand here is a pack of the model mass density and the model enthalpy.
|
||||
*/
|
||||
using Operands = utils::types::TypeList<discretization::forms::Operand<Test>, discretization::forms::Operand<Enthalpy>>;
|
||||
|
||||
static constexpr std::string_view name = "barotropic closure"; ///< Compile time name for the barotropic closure equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a barotropic closure equation.
|
||||
*
|
||||
* @param equation_of_state The equation of state to use for the closure.
|
||||
* @param enthalpy_power The power of the enthalpy to use for the quadrature. Must be positive and finite. This is only used if the ::additional_order method is called.
|
||||
*
|
||||
* @throws std::invalid_argument If the enthalpy power is not positive and finite.
|
||||
*
|
||||
* @todo should we add a requires that the equation of state is the same as eos::models::Polytrope here?
|
||||
*/
|
||||
explicit BarotropicClosure(
|
||||
EOST equation_of_state,
|
||||
const double enthalpy_power
|
||||
) :
|
||||
m_eos(std::move(equation_of_state)), m_enthalpy_power(enthalpy_power) {
|
||||
using namespace utils::errors;
|
||||
|
||||
serif_ensure<SERiFErrorCode::INVALID_QUADRATURE_ORDER, std::invalid_argument>(std::isfinite(enthalpy_power) && enthalpy_power > 0.0,"The density quadrature power must be positive and finite.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a barotropic closure equation for a polytropic equation of state.
|
||||
*
|
||||
* @param equation_of_state The polytropic equation of state to use for the closure.
|
||||
*
|
||||
* @requires The equation of state type must be a polytropic equation of state.
|
||||
*
|
||||
* @note This constructor is only available for polytropic equations of state. It uses the polytropic index as the enthalpy power.
|
||||
* @note Generally this is the constructor that you should likely use.
|
||||
*/
|
||||
explicit BarotropicClosure(
|
||||
EOST equation_of_state
|
||||
)
|
||||
requires std::same_as<EOST, eos::models::Polytrope>
|
||||
: BarotropicClosure(equation_of_state, equation_of_state.polytropic_index()) {}
|
||||
|
||||
/**
|
||||
* @brief Evaluate the barotropic closure equation point-wise integrand and its partial derivatives.
|
||||
*
|
||||
* The point-wise integrand is calculated as \f$ \rho - \rho_{\text{EOS}}(h) \f$, where \f$ \rho \f$ is the model density, and \f$ h \f$ is the model specific enthalpy.
|
||||
* The partial derivatives are calculated as:
|
||||
* - \f$ \frac{\partial r}{\partial \rho} = 1 \f$
|
||||
* - \f$ \frac{\partial \rho}{\partial h} = -\frac{\partial \rho_{\text{EOS}}(h)}{\partial h} \f$
|
||||
*
|
||||
* @param point The point at which to evaluate the equation.
|
||||
* @returns The point-wise linearization of the barotropic closure equation, which contains the residual and the partial derivatives with respect to the model density and enthalpy.
|
||||
*
|
||||
* @throws eos::EOSEvaluationError If the density or enthalpy are not finite, or if the enthalpy is negative, or if the equation of state returns a non-finite density or derivative.
|
||||
*/
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) const {
|
||||
using namespace utils::errors;
|
||||
|
||||
const double density = point.fields.template get<Test>();
|
||||
const double enthalpy = point.fields.template get<Enthalpy>();
|
||||
|
||||
serif_ensure<SERiFErrorCode::EOS_NONFINITE_INPUT, eos::EOSEvaluationError>(std::isfinite(density) && std::isfinite(enthalpy), "Closure requires finite density and enthalpy.");
|
||||
serif_ensure<SERiFErrorCode::EOS_OUTSIDE_DOMAIN, eos::EOSEvaluationError>(enthalpy >= 0.0, "Stellar closure requires non-negative enthalpy.");
|
||||
|
||||
const dimensions::SpecificEnthalpyValue input{enthalpy};
|
||||
|
||||
const double eos_density = eos::evaluate<dimensions::MassDensity>(m_eos, input).value();
|
||||
const double density_derivative = eos::partial_derivative<dimensions::MassDensity, dimensions::SpecificEnthalpy>(m_eos, input).value();
|
||||
|
||||
serif_ensure<SERiFErrorCode::EOS_NONFINITE_RESULT, eos::EOSEvaluationError>(std::isfinite(eos_density) && std::isfinite(density_derivative), "Barotropic closure received a non-finite EOS result.");
|
||||
|
||||
PointLinearization<Operands> result;
|
||||
result.residual = density - eos_density;
|
||||
result.field_derivatives.template get<Test>() = 1.0;
|
||||
result.field_derivatives.template get<Enthalpy>() = -density_derivative;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional quadrature order required for the barotropic closure equation.
|
||||
*
|
||||
* The additional order is calculated as \f$ \text{order} = \text{density order} + 2 \times \text{position order} \f$, where the density order is the order of the functional space of the model density, and the position order is the order of the position functional space.
|
||||
*
|
||||
* @tparam OperandT The operand type for which to calculate the additional order. This is only used to determine if the operand is the enthalpy or not. If not enthalpy then the additional order is returned as 0.
|
||||
* @param operand The operand type for which to calculate the additional order. This is only used to determine if the operand is the enthalpy or not. If not enthalpy then the additional order is returned as 0.
|
||||
* @param position_order The order of the position functional space.
|
||||
*
|
||||
* @returns The additional quadrature order required for the barotropic closure equation.
|
||||
*
|
||||
* @throws std::invalid_argument If the extra order exceeds the supported range of int.
|
||||
*/
|
||||
template <typename OperandT>
|
||||
[[nodiscard]] int additional_order(OperandT /*operand*/, int /*position_order*/) const {
|
||||
using namespace utils::errors;
|
||||
|
||||
if constexpr (std::same_as<typename OperandT::term_type, Enthalpy>) {
|
||||
const double extra = std::ceil(std::max(0.0, m_enthalpy_power - 1.0) * Enthalpy::FunctionalSpace::order);
|
||||
|
||||
serif_ensure<SERiFErrorCode::INVALID_QUADRATURE_ORDER, std::invalid_argument>(extra <= std::numeric_limits<int>::max(), "The EOS quadrature order exceeds the supported range.");
|
||||
|
||||
return static_cast<int>(extra);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @brief Inspect the immutable EOS defining this closure. */
|
||||
[[nodiscard]] const EOST& equation_of_state() const noexcept { return m_eos; }
|
||||
|
||||
/** @brief Inspect the declared power used to choose quadrature. */
|
||||
[[nodiscard]] double enthalpy_power() const noexcept { return m_enthalpy_power; }
|
||||
|
||||
private:
|
||||
EOST m_eos; ///< The equation of state to use for the barotropic closure. (@todo should this be a reference?)
|
||||
double m_enthalpy_power; ///< The power of the enthalpy to use for the quadrature. Must be positive and finite. This is only used if the ::additional_order method is called.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
138
src/include/serif/equations/barotropic_density.hpp
Normal file
138
src/include/serif/equations/barotropic_density.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/eos/evaluation.hpp"
|
||||
#include "serif/eos/exceptions.hpp"
|
||||
#include "serif/eos/relations/relations.hpp"
|
||||
#include "serif/equations/companion.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/equations/rigid_rotation.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief Enforce A(rho) - A(rho_surface) + Phi - Omega^2 Psi - U_surface = 0 using density test values.
|
||||
* This closure assumes a connected barotrope in rigid rotation.
|
||||
*
|
||||
* TODO: Add more detail to this comment, pull from the paper?
|
||||
*/
|
||||
template <eos::models::EOSModel EOS>
|
||||
requires eos::models::EOSSupportingRelation<EOS, eos::relations::PressurePotentialFromDensity> &&
|
||||
eos::models::EOSSupportingRelation<EOS, eos::relations::DensityFromPressure> &&
|
||||
eos::models::EOSSupportingPartialDerivative<EOS, eos::relations::PressureFromDensity, dimensions::MassDensity>
|
||||
class BarotropicDensityBalance final : public Contribution<discretization::forms::Operand<discretization::blocks::density::mass>> {
|
||||
public:
|
||||
using Density = discretization::blocks::density::mass;
|
||||
using Potential = discretization::blocks::gravity::potential;
|
||||
using SurfaceLevel = discretization::blocks::barotropic_constant::mass_normalization;
|
||||
using AngularVelocity = discretization::blocks::rotation::angular_velocity;
|
||||
using Operands = utils::types::TypeList<discretization::forms::Operand<Density>,
|
||||
discretization::forms::Operand<Potential>, discretization::forms::Operand<SurfaceLevel>,
|
||||
discretization::forms::Operand<AngularVelocity>>;
|
||||
|
||||
using ResidualQuantity = dimensions::BarotropicPressurePotential;
|
||||
using IsobaricSurfaceField = Density;
|
||||
static constexpr std::string_view name = "barotropic density balance";
|
||||
|
||||
/** @brief Borrow or own the EOS and fix its surface reference at the model's pressure and rotation convention. */
|
||||
explicit BarotropicDensityBalance(
|
||||
EOS equation_of_state,
|
||||
const dimensions::PressureValue surface_pressure = dimensions::PressureValue{0.0},
|
||||
const RigidRotation::Vector& axis = {0.0, 0.0, 1.0},
|
||||
const RigidRotation::Vector& center = {},
|
||||
std::optional<PointMassCompanion> companion = std::nullopt
|
||||
) :
|
||||
m_eos(std::move(equation_of_state)),
|
||||
m_surface_pressure(surface_pressure),
|
||||
m_rotation(RigidRotation::unit_rotation(axis, center)),
|
||||
m_companion(companion) {
|
||||
using namespace utils::errors;
|
||||
|
||||
serif_ensure<SERiFErrorCode::EOS_INVALID_THERMODYNAMIC_INPUT, eos::EOSEvaluationError>(
|
||||
std::isfinite(surface_pressure.value()) && surface_pressure.value() >= 0.0,
|
||||
"Barotropic density balance requires finite nonnegative surface pressure; received {}.", surface_pressure.value());
|
||||
|
||||
const auto surface_density = eos::evaluate<dimensions::MassDensity>(m_eos, surface_pressure);
|
||||
serif_ensure<SERiFErrorCode::EOS_INVALID_THERMODYNAMIC_INPUT, eos::EOSEvaluationError>(
|
||||
std::isfinite(surface_density.value()) && surface_density.value() >= 0.0,
|
||||
"The EOS returned surface density {} at pressure {}; choose a valid nonnegative barotropic branch.",
|
||||
surface_density.value(), surface_pressure.value());
|
||||
|
||||
m_surface_pressure_potential = eos::evaluate<dimensions::BarotropicPressurePotential>(m_eos, surface_density).value();
|
||||
serif_ensure<SERiFErrorCode::EOS_NONFINITE_RESULT, eos::EOSEvaluationError>(
|
||||
std::isfinite(m_surface_pressure_potential),
|
||||
"The EOS pressure potential is not finite at surface density {}; choose a surface pressure with a finite primitive.",
|
||||
surface_density.value());
|
||||
}
|
||||
|
||||
/** @brief Evaluate the local pressure-potential balance and its tangent on the positive density branch. */
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) const {
|
||||
using namespace utils::errors;
|
||||
|
||||
const double density = point.fields.template get<Density>();
|
||||
serif_ensure<SERiFErrorCode::EOS_INVALID_THERMODYNAMIC_INPUT, eos::EOSEvaluationError>(
|
||||
std::isfinite(density) && density > 0.0,
|
||||
"Barotropic density balance requires positive finite interior density; received {}. Impose the vacuum trace separately.", density);
|
||||
|
||||
const dimensions::DensityValue state{density};
|
||||
const double pressure_potential = eos::evaluate<dimensions::BarotropicPressurePotential>(m_eos, state).value();
|
||||
const double density_derivative = eos::partial_derivative<dimensions::Pressure, dimensions::MassDensity>(m_eos, state).value() / density;
|
||||
|
||||
serif_ensure<SERiFErrorCode::EOS_NONFINITE_RESULT, eos::EOSEvaluationError>(
|
||||
std::isfinite(pressure_potential) && std::isfinite(density_derivative),
|
||||
"The EOS pressure potential or P_rho/rho is not finite at interior density {}; use a valid barotropic state.", density);
|
||||
|
||||
const double omega = point.fields.template get<AngularVelocity>();
|
||||
const double centrifugal_potential = m_rotation.centrifugal_potential(point.position);
|
||||
PointLinearization<Operands> result;
|
||||
|
||||
result.residual = pressure_potential - m_surface_pressure_potential + point.fields.template get<Potential>() - omega * omega * centrifugal_potential - point.fields.template get<SurfaceLevel>();
|
||||
result.field_derivatives.template get<Density>() = density_derivative;
|
||||
result.field_derivatives.template get<Potential>() = 1.0;
|
||||
result.field_derivatives.template get<SurfaceLevel>() = -1.0;
|
||||
result.field_derivatives.template get<AngularVelocity>() = -2.0 * omega * centrifugal_potential;
|
||||
result.position_derivative = m_rotation.centrifugal_potential_gradient(point.position);
|
||||
|
||||
for (double& component : result.position_derivative) {
|
||||
component *= -omega * omega;
|
||||
}
|
||||
|
||||
if (m_companion) {
|
||||
result.residual += m_companion->potential(point.position);
|
||||
const auto gradient = m_companion->potential_gradient(point.position);
|
||||
for (int axis = 0; axis < 3; ++axis) { result.position_derivative[axis] += gradient[axis]; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @brief Reserve a nonlinear coefficient budget; general EOS quadrature remains user-overridable. */
|
||||
template <typename Operand>
|
||||
[[nodiscard]] static int additional_order(Operand, const int position_order) {
|
||||
return Density::FunctionalSpace::order + position_order;
|
||||
}
|
||||
|
||||
/** @brief Inspect the EOS supplying the pressure primitive and its slope. */
|
||||
[[nodiscard]] const EOS& equation_of_state() const noexcept { return m_eos; }
|
||||
/** @brief Inspect the pressure defining the primitive's surface reference. */
|
||||
[[nodiscard]] dimensions::PressureValue surface_pressure() const noexcept { return m_surface_pressure; }
|
||||
/** @brief Inspect the shared physical rotation convention. */
|
||||
[[nodiscard]] const RigidRotation& rotation() const noexcept { return m_rotation; }
|
||||
/** @brief Inspect the companion, if any. */
|
||||
[[nodiscard]] const std::optional<PointMassCompanion>& companion() const noexcept { return m_companion; }
|
||||
|
||||
private:
|
||||
EOS m_eos;
|
||||
dimensions::PressureValue m_surface_pressure;
|
||||
RigidRotation m_rotation;
|
||||
std::optional<PointMassCompanion> m_companion;
|
||||
double m_surface_pressure_potential;
|
||||
};
|
||||
}
|
||||
52
src/include/serif/equations/barycenter.hpp
Normal file
52
src/include/serif/equations/barycenter.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief Fix one component of integral rho (x-center)/M_target.
|
||||
*/
|
||||
template <int Axis, typename Row = discretization::blocks::frame_acceleration::component<Axis>>
|
||||
class BarycenterConstraint final : public Contribution<discretization::forms::Operand<Row>> {
|
||||
public:
|
||||
static_assert(Axis >= 0 && Axis < 3, "A barycenter constraint requires a Cartesian axis from 0 through 2.");
|
||||
|
||||
using Density = discretization::blocks::density::mass;
|
||||
using Operands = utils::types::TypeList<discretization::forms::Operand<Density>>;
|
||||
using ResidualQuantity = dimensions::Length;
|
||||
static constexpr std::string_view name = "barycenter constraint";
|
||||
|
||||
/** @brief Use a fixed positive mass scale and the same finite center as the rotation and deformation. */
|
||||
explicit BarycenterConstraint(double target_mass, std::array<double, 3> center = {}) : m_target_mass(target_mass), m_center(center) {
|
||||
using namespace utils::errors;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(std::isfinite(target_mass) && target_mass > 0.0 &&
|
||||
std::isfinite(center[0]) && std::isfinite(center[1]) && std::isfinite(center[2]),
|
||||
"A barycenter constraint requires a finite positive target mass and a finite center.");
|
||||
}
|
||||
|
||||
/** @brief Differentiate the normalized first mass moment in density and physical position. */
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) const {
|
||||
const double radius = (point.position[Axis] - m_center[Axis]) / m_target_mass;
|
||||
PointLinearization<Operands> result;
|
||||
result.residual = point.fields.template get<Density>() * radius;
|
||||
result.field_derivatives.template get<Density>() = radius;
|
||||
result.position_derivative[Axis] = point.fields.template get<Density>() / m_target_mass;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @brief Include the linear position factor in quadrature selection. */
|
||||
[[nodiscard]] static int additional_order(discretization::forms::Operand<Density>, int position_order) { return position_order; }
|
||||
/** @brief Inspect the fixed normalization mass. */
|
||||
[[nodiscard]] double target_mass() const noexcept { return m_target_mass; }
|
||||
/** @brief Inspect the desired barycenter. */
|
||||
[[nodiscard]] const std::array<double, 3>& center() const noexcept { return m_center; }
|
||||
private:
|
||||
double m_target_mass;
|
||||
std::array<double, 3> m_center;
|
||||
};
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file centering.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the CenteringConstraint equation, which fixes the material point at the computational origin.
|
||||
*
|
||||
* @note This is probably the simplest equation we have right now and therefore may serve as a useful example to learn from.
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief Equation that fixes the material point at the computational origin.
|
||||
*
|
||||
* @contribution Residuals to the global displacement vector.
|
||||
*
|
||||
* @note This is a translational gauge, not a center-of-mass constraint.
|
||||
*/
|
||||
struct CenteringConstraint final : Contribution<discretization::forms::Operand<discretization::blocks::displacement::vector>> {
|
||||
using Operands = utils::types::TypeList<TestOperand>; ///< The list of operands for the centering constraint equation.
|
||||
static constexpr std::string_view name = "centering constraint"; ///< Compile time name for the centering constraint equation, used for debugging and logging.
|
||||
static constexpr bool position_independent = true; ///< A centering constraint is position independent, as it does not depend on the spatial location of the material point. This member is only used for compile-time checks.
|
||||
static constexpr std::array<double, 3> reference_position{}; ///< The reference position for the centering constraint equation, which is the origin (0, 0, 0). This member is only used for compile-time checks.
|
||||
|
||||
|
||||
/**
|
||||
* @brief Evaluate the centering constraint equation at a given point. Fundamentally all this this is doing is
|
||||
* returning the displacement of the material point at the origin since to center that the displacement of that point should be zero.
|
||||
* The derivative of the residual with respect to displacement along each axis is then just the identity matrix.
|
||||
*
|
||||
* @param point The point at which to evaluate the centering constraint equation.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the centering constraint equation at the given point.
|
||||
*/
|
||||
[[nodiscard]] static PointLinearization<Operands, discretization::forms::VectorValue> evaluate(const EquationPoint<Operands>& point) {
|
||||
PointLinearization<Operands, discretization::forms::VectorValue> result;
|
||||
result.residual = point.fields.get<Test>();
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
result.field_derivatives.get<Test>()[axis][axis] = 1.0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
} // namespace serif::equations
|
||||
@@ -1,134 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file central_density.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief Prescribe central density through the continuous enthalpy: h(0) - h_EOS(rho_c) = 0. Generally this is not needed; however, in the particular
|
||||
* case of an n=3 polytrope with no rotation the central density becomes ill-defined and must be prescribed to avoid a singularity in the solution.
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
#include "serif/eos/evaluation.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief Prescribe central density through the continuous enthalpy: \f$h(0) - h_{EOS}(\rho_c) = 0\f$. Generally this is not needed; however, in the particular
|
||||
* case of an n=3 polytrope with no rotation the central density becomes ill-defined and must be prescribed to avoid a singularity in the solution. Note that
|
||||
* this equation is not responsible for sorting out what point is the center of the star; rather, this is provided to the
|
||||
* evaluate method.
|
||||
*
|
||||
* @tparam EOST The EOS model type to use for evaluating the enthalpy from the prescribed density.
|
||||
*
|
||||
* @requires The EOS model must support the relation SpecificEnthalpyFromDensity
|
||||
*
|
||||
* @contribution Residuals to the specific enthalpy row of the system
|
||||
*
|
||||
* @throws std::invalid_argument If the prescribed density is not finite and strictly positive.
|
||||
* @throws std::domain_error If the EOS returns a non-finite enthalpy for the prescribed density.
|
||||
*
|
||||
*/
|
||||
template <eos::models::EOSModel EOST>
|
||||
requires eos::models::EOSSupportingRelation<EOST, eos::relations::SpecificEnthalpyFromDensity>
|
||||
class CentralDensityConstraint final : public Contribution<discretization::forms::Operand<discretization::blocks::enthalpy::specific>> {
|
||||
public:
|
||||
using Operands = utils::types::TypeList<TestOperand>; ///< The operand types of the equation, that is the types of the fields which are used to evaluate the equation.
|
||||
using Border = discretization::blocks::central_density::border; ///< The border type of the equation, that is what row of the global system this equation contributes to.
|
||||
using ResidualQuantity = dimensions::SpecificEnthalpy; ///< The physical quantity which is being constrained by this equation, that is the quantity which is being set to a target value.
|
||||
static constexpr std::string_view name = "central density constraint"; ///< Compile time name for the equation, used for debugging and logging.
|
||||
static constexpr bool position_independent = true; ///< Whether the equation is independent of the position of the point being evaluated, that is whether the equation is invariant under translations and rotations of the point. This is only used for compile-time checks
|
||||
static constexpr bool affine = true; ///< Whether the equation is affine, that is whether the equation is linear in the fields being evaluated. This is only used for compile-time checks
|
||||
static constexpr std::array<double, 3> reference_position{}; ///< The reference position of the equation, this is only used for compile-time checks and is not used in the evaluation of the equation.
|
||||
|
||||
|
||||
/**
|
||||
* @brief Construct a central density constraint.
|
||||
*
|
||||
* @param equation_of_state The EOS model to use for evaluating the enthalpy from the prescribed density.
|
||||
* @param target_density The prescribed central density, which must be finite and strictly positive.
|
||||
*
|
||||
* @throws std::invalid_argument If the prescribed density is not finite and strictly positive.
|
||||
* @throws std::domain_error If the EOS returns a non-finite enthalpy for the prescribed density.
|
||||
*
|
||||
* @todo We should consider whether we actually need to own the EOS, this choice made lifetime management easier but introduces copy overhead. Moreover,
|
||||
* it means that the EOS must be copyable and, more importantly in my view, that the caller must know that the EOS is being
|
||||
* copied.
|
||||
*/
|
||||
CentralDensityConstraint(EOST equation_of_state, dimensions::DensityValue target_density) :
|
||||
m_eos(std::move(equation_of_state)),
|
||||
m_target_density(target_density) {
|
||||
using namespace utils::errors;
|
||||
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(std::isfinite(target_density.value()) && target_density.value() > 0.0, "Central density must be finite and strictly positive.");
|
||||
m_target_enthalpy = eos::evaluate<dimensions::SpecificEnthalpy>(m_eos, target_density).value();
|
||||
serif_ensure<SERiFErrorCode::NON_FINITE_VALUE_ERROR, std::domain_error>(std::isfinite(m_target_enthalpy), "The EOS returned a non-finite central enthalpy.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Evaluate the central density constraint at a given point.
|
||||
*
|
||||
* @param point The point at which to evaluate the equation, which must be the center of the star.
|
||||
*
|
||||
* @note It is the responsibility of the caller (and generally of the border equation backend) to ensure that this residual
|
||||
* is applied to the correct row of the global system, the row defined by the ::Border type alias.
|
||||
*
|
||||
* @return The linearization of the equation at the given point, which contains the residual and the derivatives of the residual with respect to the fields being evaluated.
|
||||
*/
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) const {
|
||||
PointLinearization<Operands> result;
|
||||
result.residual = point.fields.get<Test>() - m_target_enthalpy;
|
||||
result.field_derivatives.get<Test>() = 1.0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the prescribed central density.
|
||||
*
|
||||
* @return The prescribed central density.
|
||||
*/
|
||||
[[nodiscard]] dimensions::DensityValue target_density() const noexcept {
|
||||
return m_target_density;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the target enthalpy corresponding to the prescribed central density.
|
||||
*
|
||||
* @return The target enthalpy corresponding to the prescribed central density.
|
||||
*/
|
||||
[[nodiscard]] dimensions::SpecificEnthalpyValue target_enthalpy() const noexcept {
|
||||
return dimensions::SpecificEnthalpyValue{m_target_enthalpy};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert some enthalpy to a density for physical diagnostics, when the inverse relation exists.
|
||||
* @param enthalpy The specific enthalpy to convert.
|
||||
*
|
||||
* @return The corresponding density.
|
||||
*
|
||||
* @requires The EOS model must support the relation DensityFromSpecificEnthalpy, otherwise this method is not available.
|
||||
*
|
||||
* @note Generally this method is only for debugging, if you want to use the density you should really call the EOS directly.
|
||||
*/
|
||||
[[nodiscard]] dimensions::DensityValue density_from_enthalpy(dimensions::SpecificEnthalpyValue enthalpy) const
|
||||
requires eos::models::EOSSupportingRelation<EOST, eos::relations::DensityFromSpecificEnthalpy> {
|
||||
return eos::evaluate<dimensions::MassDensity>(m_eos, enthalpy);
|
||||
}
|
||||
|
||||
/** @brief Inspect the EOS defining the target central enthalpy. */
|
||||
[[nodiscard]] const EOST& equation_of_state() const noexcept { return m_eos; }
|
||||
|
||||
private:
|
||||
EOST m_eos; ///< The EOS model to use for evaluating the enthalpy from the prescribed density.
|
||||
dimensions::DensityValue m_target_density; ///< The prescribed central density, which must be finite and strictly positive.
|
||||
double m_target_enthalpy; ///< The target enthalpy corresponding to the prescribed central density, which is computed from the EOS and must be finite.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
73
src/include/serif/equations/companion.hpp
Normal file
73
src/include/serif/equations/companion.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "serif/equations/rigid_rotation.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief The potential -G M_c/|x - x_c| of a point-mass companion outside the star.
|
||||
*/
|
||||
class PointMassCompanion final {
|
||||
public:
|
||||
using Vector = RigidRotation::Vector;
|
||||
|
||||
/** @brief gravitational_mass is G M_c in the model's units; position is the companion's fixed location. */
|
||||
PointMassCompanion(
|
||||
const double gravitational_mass,
|
||||
const Vector& position,
|
||||
const bool tidal_only = false
|
||||
) :
|
||||
m_gravitational_mass(gravitational_mass),
|
||||
m_position(position),
|
||||
m_tidal_only(tidal_only) {
|
||||
|
||||
using namespace utils::errors;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(std::isfinite(gravitational_mass) && gravitational_mass > 0.0 &&
|
||||
std::isfinite(position[0]) && std::isfinite(position[1]) && std::isfinite(position[2]),
|
||||
"A companion requires a finite positive mass and a finite position.");
|
||||
}
|
||||
|
||||
/** @brief Evaluate -G M_c/|x - x_c|, plus G M_c x.x_c/|x_c|^3 when only the tide acts. */
|
||||
[[nodiscard]] double potential(const Vector& x) const {
|
||||
double result = -m_gravitational_mass / std::hypot(x[0] - m_position[0], x[1] - m_position[1], x[2] - m_position[2]);
|
||||
if (m_tidal_only) {
|
||||
result += uniform_scale() * (x[0] * m_position[0] + x[1] * m_position[1] + x[2] * m_position[2]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @brief Evaluate G M_c (x - x_c)/|x - x_c|^3, plus G M_c x_c/|x_c|^3 when only the tide acts. */
|
||||
[[nodiscard]] Vector potential_gradient(const Vector& x) const {
|
||||
const double distance = std::hypot(x[0] - m_position[0], x[1] - m_position[1], x[2] - m_position[2]);
|
||||
const double scale = m_gravitational_mass / (distance * distance * distance);
|
||||
|
||||
Vector result{scale * (x[0] - m_position[0]), scale * (x[1] - m_position[1]), scale * (x[2] - m_position[2])};
|
||||
if (m_tidal_only) {
|
||||
for (int axis = 0; axis < 3; ++axis) { result[axis] += uniform_scale() * m_position[axis]; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @brief Inspect G M_c. */
|
||||
[[nodiscard]] double gravitational_mass() const noexcept { return m_gravitational_mass; }
|
||||
/** @brief Inspect the companion's location. */
|
||||
[[nodiscard]] const Vector& position() const noexcept { return m_position; }
|
||||
/** @brief Whether the companion's uniform pull is removed, leaving only its tide. */
|
||||
[[nodiscard]] bool tidal_only() const noexcept { return m_tidal_only; }
|
||||
|
||||
private:
|
||||
/** @brief G M_c/|x_c|^3, the coefficient of the uniform pull at the origin. */
|
||||
[[nodiscard]] double uniform_scale() const {
|
||||
const double distance = std::hypot(m_position[0], m_position[1], m_position[2]);
|
||||
return m_gravitational_mass / (distance * distance * distance);
|
||||
}
|
||||
|
||||
double m_gravitational_mass;
|
||||
Vector m_position;
|
||||
bool m_tidal_only;
|
||||
};
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file displacement_force.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the DisplacementForce class, which represents the mechanical balance equation in SERiF.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/equations/arithmetic.hpp"
|
||||
#include "serif/equations/gravity_force.hpp"
|
||||
#include "serif/equations/pressure_force.hpp"
|
||||
#include "serif/equations/rotational_force.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
|
||||
/**
|
||||
* @class DisplacementForce
|
||||
* @tparam EOST The equation of state type, this must satisfy the PressureForceEOS concept. The requires section below lays out
|
||||
* what the PressureForceEOS concept requires.
|
||||
*
|
||||
* @requires The equation of state type must publish a valid Relation catalog
|
||||
* @requires The equation of state type must implement all relations required by the catalog it publishes
|
||||
* @requires The equation of state type must support the relation pressure from specific enthalpy
|
||||
* @requires The equation of state type must support the partial derivative pressure from specific enthalpy with respect to specific enthalpy
|
||||
*
|
||||
* This class represents the mechanical balance equation in SERiF. It is a sum of the pressure force, gravity force, and rotational force equations.
|
||||
*
|
||||
* As I have said in a number of other locations this is an important file / class to understand. You might note that this file
|
||||
* is where we actually build the momentum equation (though in the case of a polytrope it looks a lot more like a
|
||||
* mechanical balance equation). You may note though how little code there actually is here. That is because we have
|
||||
* split up the separated physics into the gravity_force, the pressure_force, and the rotational_force equations. Then
|
||||
* we use the arithmetic equation routines sum their contributions together. Further, this is where we actually define
|
||||
* the rotational axis of the star (note how we set it along the z-axis) and the center of rotation.
|
||||
*
|
||||
* Generally this equation should be used as a model for how to implement other multi-term equations.
|
||||
* We want to avoid monolithic equations. There are a few reasons for this, one would be maintainability. This software is,
|
||||
* by requirement, exceedingly complex, the more we can break things up into semantically meaningful pieces
|
||||
* the easier it will be for a developer to hold parts of the code in their head. However, more than just maintainability there
|
||||
* is a performance reason for this (though one that we admittedly do not exploit at the moment). The backend of
|
||||
* SERiF makes heavy use of a computational graph with aggressive inter-node caching. If we split equations up
|
||||
* into smaller pieces and then represent their sums as operations on this graph the backend can more aggressively
|
||||
* avoid recomputing certain parts which may, provably, have not changes from one evaluation to the next. Currently,
|
||||
* we do not do this; however, this is future optimization will not require modifying this code at all only the
|
||||
* equation sum backend.
|
||||
*
|
||||
* @note The DisplacementForce equation owns all three contributing equations
|
||||
*/
|
||||
template <eos::models::PressureForceEOS EOST>
|
||||
class DisplacementForce final {
|
||||
public:
|
||||
using Test = discretization::blocks::displacement::vector; ///< The test type is the model displacement vector, which is the quantity being constrained by the mechanical balance equation. Effectively here we say that all these forces control what the geometry of the surface is (and then other equations connect that geometry to the mass distribution)
|
||||
|
||||
static constexpr std::string_view name = "displacement force"; ///< Compile time name for the mechanical balance equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a mechanical balance equation.
|
||||
*
|
||||
* @param pressure The pressure force equation, which must be constructed with the same EOS type as the DisplacementForce (note generally this can be inferred).
|
||||
* @param axis The axis of rotation for the rotational force equation. Defaults to the z-axis.
|
||||
* @param center The center of rotation for the rotational force equation. Defaults to the origin.
|
||||
*/
|
||||
explicit DisplacementForce(
|
||||
PressureForce<EOST> pressure,
|
||||
RigidRotation::Vector axis = {0.0, 0.0, 1.0},
|
||||
RigidRotation::Vector center = {}
|
||||
) :
|
||||
m_equation(std::move(pressure), GravityForce{}, RotationalForce{axis, center}) {}
|
||||
|
||||
/**
|
||||
* @brief Return the contributions of the mechanical balance equation.
|
||||
*
|
||||
* @return A tuple containing the contributions of the mechanical balance equation.
|
||||
*/
|
||||
[[nodiscard]] const auto& contributions() const noexcept {
|
||||
return m_equation.contributions();
|
||||
}
|
||||
|
||||
private:
|
||||
EquationSum<PressureForce<EOST>, GravityForce, RotationalForce> m_equation; ///< The mechanical balance residual is a sum of residuals from the pressure force, gravity force, and rotational force equations.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
@@ -11,19 +11,14 @@
|
||||
namespace serif::equations {}
|
||||
|
||||
#include "serif/equations/angular_momentum.hpp"
|
||||
#include "serif/equations/arithmetic.hpp"
|
||||
#include "serif/equations/barotropic_closure.hpp"
|
||||
#include "serif/equations/centering.hpp"
|
||||
#include "serif/equations/central_density.hpp"
|
||||
#include "serif/equations/barotropic_density.hpp"
|
||||
#include "serif/equations/concepts.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/displacement_force.hpp"
|
||||
#include "serif/equations/gravity_force.hpp"
|
||||
#include "serif/equations/hydrostatic_equilibrium.hpp"
|
||||
#include "serif/equations/mass_normalization.hpp"
|
||||
#include "serif/equations/mixed_gravity.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/equations/pressure_force.hpp"
|
||||
#include "serif/equations/registry.hpp"
|
||||
#include "serif/equations/rigid_rotation.hpp"
|
||||
#include "serif/equations/rotational_force.hpp"
|
||||
#include "serif/equations/primal_gravity.hpp"
|
||||
#include "serif/equations/surface_potential.hpp"
|
||||
#include "serif/equations/barycenter.hpp"
|
||||
#include "serif/equations/companion.hpp"
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file gravity_force.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the gravitational force contribution to the mechanical balance equation (displacement_force.hpp).
|
||||
*/
|
||||
|
||||
#include <concepts>
|
||||
#include <string_view>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
|
||||
/**
|
||||
* @class GravityForce
|
||||
*
|
||||
* @brief The gravitational force contribution to the mechanical balance equation.
|
||||
*
|
||||
* @contribution Residuals to the global displacement vector supported on the stellar domain.
|
||||
*
|
||||
* @note The gravitational force is given by \f$ \mathbf{F} = \rho \mathbf{g} \f$, where \f$ \rho \f$ is the mass density and \f$ \mathbf{g} \f$ is the gravitational acceleration. The residual is given by the difference between the model gravitational force and the gravitational force given by the equation of state. The partial derivatives implemented are:
|
||||
* - \f$ \frac{\partial F}{\partial \rho} = g \f$
|
||||
* - \f$ \frac{\partial F}{\partial g} = \rho I \f$
|
||||
*
|
||||
* @note For some reason which only past me can fathom I adopted \f$ \mathbf{g} = \nabla \Phi \f$ rather than \f$ \mathbf{g} = -\nabla \Phi \f$. The implication of this is that acceleration is \f$ -\mathbf{g} \f$. We may want to change this but we will need to be careful.
|
||||
*
|
||||
* @note The reason is actually completely fathomable: I just didn't think. Carry on now.
|
||||
*
|
||||
* @note You know I've written, over the last year about 60k lines of code for this project, much of it deleted and rewritten multiple time. I am
|
||||
* going through now and adding comments to this version which I am finally happy with. In doing so I suspect that a reader can see
|
||||
* the slow(ish?) deterioration of my mental state as these comments get more and more conversational. I wonder if anyone will have anything to say about this
|
||||
* or if it will simply become some marginal curiosity buried in the docs. I suspect the former, though I am not so selfless as to not hope for the latter.
|
||||
*/
|
||||
struct GravityForce : Contribution<discretization::forms::Operand<discretization::blocks::displacement::vector>, discretization::domain::StellarDomains> {
|
||||
using Density = discretization::blocks::density::mass; ///< The density type is the model's mass density,
|
||||
using Gradient = discretization::blocks::gravity::gradient; ///< The gradient type is the model's negative gravitational acceleration (see class comment for why this is negative).
|
||||
|
||||
using DensityValue = discretization::forms::Operand<Density>; ///< The density value type is a density Operand
|
||||
using GradientValue = discretization::forms::Operand<Gradient>; ///< The gradient value type is a gradient Operand
|
||||
|
||||
using Operands = utils::types::TypeList<DensityValue, GradientValue>; ///< The operands for this equation are the density and gradient operands
|
||||
using Result = discretization::forms::VectorValue; ///< This equation's results are vector valued.
|
||||
|
||||
static constexpr std::string_view name = "gravitational force"; ///< Compile time name for the gravitational force equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Evaluate the gravitational force equation at a given point.
|
||||
*
|
||||
* @param point The point at which to evaluate the gravitational force equation.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the gravitational force equation at the given point.
|
||||
*
|
||||
* This function evaluates
|
||||
* - \f$ F = \rho g \f$
|
||||
* - \f$ \frac{\partial F}{\partial \rho} = g \f$
|
||||
* - \f$ \frac{\partial F}{\partial g} = \rho I \f$
|
||||
*/
|
||||
[[nodiscard]] static PointLinearization<Operands, Result> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) {
|
||||
const double density = point.fields.get<Density>();
|
||||
const auto& gradient = point.fields.get<Gradient>();
|
||||
|
||||
PointLinearization<Operands, Result> result;
|
||||
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
result.residual[axis] = density * gradient[axis];
|
||||
result.field_derivatives.get<Density>()[axis] = gradient[axis];
|
||||
result.field_derivatives.get<Gradient>()[axis][axis] = density;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional quadrature order required for the gravitational force equation. We add one
|
||||
* to the registered order of the gradient functional space to satisfy the InfSup condition for the density operand (which is in L2)
|
||||
*
|
||||
* @param operand The operand for which to determine the additional quadrature order.
|
||||
* @param position_order The order of the position operand, which is not used in this implementation.
|
||||
* @return The additional quadrature order required for the given operand.
|
||||
*
|
||||
* This function returns the additional quadrature order required for the gravitational force equation. For the density operand, it returns the order of the gradient functional space plus one. For other operands, it returns the order of the density functional space.
|
||||
*/
|
||||
template <typename OperandT>
|
||||
[[nodiscard]] static int additional_order(OperandT /*operand_type*/, int /*position_order*/) {
|
||||
if constexpr (std::same_as<OperandT, DensityValue>) {
|
||||
// The registered RT family of order p has vector components of degree up to p+1.
|
||||
return Gradient::FunctionalSpace::order + 1;
|
||||
} else {
|
||||
return Density::FunctionalSpace::order;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace serif::equations
|
||||
@@ -1,193 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/equations/rigid_rotation.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
|
||||
// So we had a previous version of this code which had roughly 20k lines of bespoke operators for each equation
|
||||
// down that route lie madness eh?
|
||||
// We have tried to make this production version far more generic. HydrostaticEquilibrium here is a useful
|
||||
// test case since its on the simpler side (being a scalar valued thing)
|
||||
|
||||
/**
|
||||
*
|
||||
* @class BarotropicHydrostaticEquilibrium
|
||||
* @brief The constraint balancing the pressure gradient, gravitational potential, and centrifugal potential.
|
||||
*
|
||||
* @todo This may make sense to break into smaller contributions in the same way as the displacement force equation
|
||||
* does already.
|
||||
*
|
||||
* If you are a physicist, astronomer, or general science goober looking to modify physics in SERiF
|
||||
* this is likely a good place to start. Here we lay out a simple case of how to implement an
|
||||
* equation.
|
||||
*
|
||||
* Consider the strong form of this equation, generally
|
||||
*
|
||||
* \f[
|
||||
* \theta = -\left(\frac{1}{\rho}\right)\nabla P - \nabla \phi + \omega^2 s
|
||||
* \f]
|
||||
*
|
||||
* where
|
||||
* \f$s = \sqrt{x^2 + y^2}\f$ and is the cylindrical radius
|
||||
*
|
||||
* In this particular equation we specialize this for a barotrope, for a barotrope
|
||||
*
|
||||
* \f[
|
||||
* dh = \frac{dP}{\rho}
|
||||
* \f]
|
||||
* therefore
|
||||
* \f[
|
||||
* \nabla h = \frac{1}{\rho} \nabla P
|
||||
* \f]
|
||||
*
|
||||
* Then
|
||||
*
|
||||
* \f[
|
||||
* \theta = -\nabla h - \nabla \phi + \omega^2 s
|
||||
* \f]
|
||||
* or equivalently
|
||||
* \f[
|
||||
* \theta = \nabla h + \nabla \phi - \omega^2 s
|
||||
* \f]
|
||||
*
|
||||
* Then, in the corotating frame, we can define an effective potential \f$\psi = \frac{1}{2}\omega^{2}\left(x^{2} + y^{2}\right)\f$
|
||||
*
|
||||
* therefore
|
||||
*
|
||||
* \f[
|
||||
* \theta = \nabla h + \nabla \phi - \nabla \psi = \nabla (h + \phi - \psi)
|
||||
* \f]
|
||||
*
|
||||
* hence
|
||||
*
|
||||
*
|
||||
* \f[
|
||||
* h + \phi - \psi = C
|
||||
* \f]
|
||||
* or
|
||||
* \f[
|
||||
* h + \phi - \psi - C = 0
|
||||
* \f]
|
||||
*
|
||||
* This is a pointwise residual equation. Now broadly for finite element methods we need the weak form of this equation; this system is
|
||||
* broadly and roughly intended to reduce friction. Look at the above equation then investigate HydrostaticEquilibrium::evaluate
|
||||
* where we set the residual in the return value. The backend system integrates the produce of these residuals and a test function over
|
||||
* a domain. The goal here is that the above step is where you as a research get to stop.
|
||||
*
|
||||
* Now there are a few other things to specify here. For one which term does this residual contribute too. You must decide this
|
||||
* however there are natural choices. In the case of HydrostaticEquilibrium the residual is a scalar and so it contributes to the enthalpy equation.
|
||||
* You must alias the residual term to a Test member.
|
||||
*
|
||||
* In addition to enthalpy computing the residual also depends on the gravitational potential, though the residual does not
|
||||
* contribute to the gravitational potential equation. You must specify this dependency by listing the gravitational potential as an operand in the Operands member.
|
||||
*
|
||||
* In addition to the residual there are a few additional derivatives that should be implemented or explicitly set to 0, broadly these
|
||||
* should be a field derivative for each operand and, if the term has a spatial component, a position derivative.
|
||||
*
|
||||
* The implementation below shows these trivially
|
||||
*
|
||||
* Other Notes
|
||||
* ===========
|
||||
* - This is a particularly simple case as we start with a scalar valued operation.
|
||||
* - This equation does not use the Contributions system (mostly this is because I wrote this equation first before I wrote the contribution system).
|
||||
* There is a benefit to this in so far as I think it makes it a bit more approachable initially as it is more self contained; however,
|
||||
* it does break the pattern that other equations use.
|
||||
* - This is very much a place where the polytropic assumption has slipped in. This equation will need to be reworked or replaced to move beyond polytropes.
|
||||
*/
|
||||
class BarotropicHydrostaticEquilibrium final {
|
||||
public:
|
||||
using Test = discretization::blocks::enthalpy::specific; ///< The test type is the model specific enthalpy, which is the quantity being constrained by the hydrostatic equilibrium equation.
|
||||
using ResidualQuantity = dimensions::SpecificEnthalpy; ///< The residual quantity is the specific enthalpy, which is the difference between the model specific enthalpy and the specific enthalpy given by the gravitational and centrifugal potentials.
|
||||
using Potential = discretization::blocks::gravity::potential; ///< We alias the gravitational potential to this type for convenience.
|
||||
using Constant = discretization::blocks::barotropic_constant::mass_normalization; ///< We alias the barotropic constant to this type for convenience.
|
||||
using AngularVelocity = discretization::blocks::rotation::angular_velocity; ///< We alias the angular velocity to this type for convenience.
|
||||
|
||||
/**
|
||||
* @brief The operand here is a pack of the model specific enthalpy, the gravitational potential, the barotropic constant, and the angular velocity.
|
||||
*
|
||||
* Think of this as saying that the residual for this equation depends on all of the things in this type list.
|
||||
*/
|
||||
using Operands = utils::types::TypeList<
|
||||
discretization::forms::Operand<Test>,
|
||||
discretization::forms::Operand<Potential>,
|
||||
discretization::forms::Operand<Constant>,
|
||||
discretization::forms::Operand<AngularVelocity>
|
||||
>;
|
||||
|
||||
static constexpr std::string_view name = "hydrostatic equilibrium"; ///< Compile time name for the hydrostatic equilibrium equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a barotropic hydrostatic equilibrium equation.
|
||||
*
|
||||
* @param axis The axis of rotation for the centrifugal potential. This is a vector in physical space.
|
||||
* @param center The center of rotation for the centrifugal potential. This is a vector in physical space.
|
||||
*
|
||||
* @todo Currently there is nothing constraining this axis or position to agree with the axis and center or rotation set
|
||||
* in the force equation. The defaults are the same; however, we should introduce some system to ensure that these agree
|
||||
* and that if they dont agree an error is thrown (since I cannot imagine any world in which a reasonable model
|
||||
* would have these disagree).
|
||||
*/
|
||||
explicit BarotropicHydrostaticEquilibrium(
|
||||
const RigidRotation::Vector &axis = {0.0, 0.0, 1.0},
|
||||
const RigidRotation::Vector ¢er = {}
|
||||
) :
|
||||
m_rotation(RigidRotation::unit_rotation(axis, center)) {}
|
||||
|
||||
/// Evaluate h + Phi - Psi - C and its partial derivatives at a physical point.
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) const {
|
||||
PointLinearization<Operands> result;
|
||||
const double omega = point.fields.get<AngularVelocity>();
|
||||
const double omega_squared = omega * omega;
|
||||
|
||||
// Below there is some level of maybe wierd (though not awful in the context of the language) C++ syntax. We need to get access to the correct
|
||||
// field values in a type safe manner, they are tuples so we use the get method with the
|
||||
// types we aliased at the top of this class. At other points in the code you may see something like
|
||||
// point.fields.template get<Test>() which is a bit more verbose but is the same thing. The template keyword is required in that case because
|
||||
// there are cases where there is an ambiguity between a template and a comparator operator (e.g. <) (I think this is the reason, frankly I've never
|
||||
// really sorted it out, I generally write the .template version and them my IDE tells me the template is not needed sometimes...)
|
||||
|
||||
// Here is where we implement the residual, this is directly from the derivation above
|
||||
result.residual = point.fields.template get<Test>() + point.fields.get<Potential>() - omega_squared * m_rotation.centrifugal_potential(point.position) - point.fields.get<Constant>();
|
||||
|
||||
result.field_derivatives.get<Test>() = 1.0;
|
||||
result.field_derivatives.get<Potential>() = 1.0;
|
||||
result.field_derivatives.get<Constant>() = -1.0;
|
||||
result.field_derivatives.get<AngularVelocity>() = -2.0 * omega * m_rotation.centrifugal_potential(point.position);
|
||||
|
||||
result.position_derivative = m_rotation.centrifugal_potential_gradient(point.position);
|
||||
for (double& component : result.position_derivative) { component *= -omega_squared; }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional quadrature order required for the operands of this equation.
|
||||
* If the operand has a functional space then the additional order is 2 * position_order - functional_space_order, otherwise it is 2 * position_order.
|
||||
*
|
||||
* @param operand The operand type for which to determine the additional quadrature order.
|
||||
* @param position_order The order of the position basis functions.
|
||||
* @return The additional quadrature order required for the given operand type.
|
||||
*/
|
||||
template <typename OperandT>
|
||||
[[nodiscard]] int additional_order(OperandT, int position_order) const {
|
||||
if constexpr (discretization::blocks::HasFunctionalSpace<typename OperandT::term_type>) {
|
||||
return std::max(0, 2 * position_order - OperandT::term_type::FunctionalSpace::order);
|
||||
} else {
|
||||
return 2 * position_order;
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Inspect the physical rotation axis and origin. */
|
||||
[[nodiscard]] const RigidRotation& rotation() const noexcept { return m_rotation; }
|
||||
|
||||
private:
|
||||
RigidRotation m_rotation; ///< The rigid rotation object used to compute the centrifugal potential and its gradient. This is constructed from the axis and center of rotation provided to the constructor.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
@@ -1,313 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file mixed_gravity.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the mixed gravity equation and its contributions.
|
||||
*
|
||||
* In SERiF we used a mixed formulation when solving for the gravitational potential and gravitational field.
|
||||
* This is a pragmatic choice to try to avoid loss of accuracy when differentiating the potential to get the field.
|
||||
*
|
||||
* More specifically, if we were to just solve for the potential and then differentiate to get the field
|
||||
* we would lose one order of accuracy in the field when compared to the potential. Due to
|
||||
* the required accuracy in virialization (which is fundamentally related to the gravitational field)
|
||||
* we cannot afford to loose that order and maintain the low-ish resolution mesh we use. I was already
|
||||
* solving for potential when I went this route so I wanted to keep it. I suppose there is some world where you
|
||||
* could solve for the field and then integrate it to get the potential; however, I have
|
||||
* not thought deeply about the effects that would have on either the accuracy or run-time
|
||||
* performance.
|
||||
*
|
||||
* As it stands we solve for the potential and the field separately but simultaneously and we weakly
|
||||
* constrain them to agree.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <string_view>
|
||||
#include <stdexcept>
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
|
||||
namespace serif::equations {
|
||||
|
||||
// TODO: I know that these deeply nested namespaces are really a C++ no-no (or as we in the biz call them an anit-pattern). I think
|
||||
// my background as a python dev makes me like them, but perhaps at some point we should go through and flatten this out so we
|
||||
// avoid having these long alias definitions at the starts of classes and namespaces (which is also bad in a header to begin with)?
|
||||
// Something to think about
|
||||
|
||||
namespace gravity_contributions {
|
||||
using Gradient = discretization::blocks::gravity::gradient; ///< Alias for the gravitational field type, which is the gradient of the gravitational potential.
|
||||
using Potential = discretization::blocks::gravity::potential; ///< Alias for the gravitational potential type, which is the scalar potential field that generates the gravitational field.
|
||||
using Density = discretization::blocks::density::mass; ///< Alias for the mass density type, which is the source term in the Poisson equation for gravity.
|
||||
|
||||
using Value = discretization::forms::Operation::Value; ///< Alias for the value operation, which is used to evaluate the point-wise value of a field.
|
||||
using Divergence = discretization::forms::Operation::Divergence; ///< Alias for the divergence operation, which is used to evaluate the point-wise divergence of a vector field.
|
||||
|
||||
/**
|
||||
* @tparam Term The type of the term to create an operand for, which must be a valid discretization block.
|
||||
* @tparam Operation The type of the operation to create an operand for, which must
|
||||
*
|
||||
* @brief Alias for an @ref Operand of type Operand<Term, Operation>. This is a convenience alias to avoid having to write out the full type name. See
|
||||
* my above (likely unparsed in doxygen comment about my bad habit of using deeply nested namespaces and types. I should probably fix that at some point).
|
||||
*/
|
||||
template <typename Term, typename Operation = Value>
|
||||
using Operand = discretization::forms::Operand<Term, Operation>;
|
||||
|
||||
/**
|
||||
* @class GradientValue
|
||||
*
|
||||
* @brief The point wise contribution to the gravity-gradient relation. Note that these contributions
|
||||
* are just the point-wise field values. You can get to this by going through the
|
||||
* weak form derivation. The derivative is just the identity matrix since the residual is just the field value.
|
||||
*
|
||||
* @contribution Residuals to the gravity-gradient relation block of the system.
|
||||
*/
|
||||
struct GradientValue : Contribution<Operand<Gradient>> {
|
||||
using Operands = utils::types::TypeList<Operand<Gradient>>; ///< This equation only depends on the gradient field
|
||||
|
||||
/**
|
||||
* @brief Evaluate the gravity-gradient relation at a given point. Fundamentally all this this is doing is
|
||||
* returning the gradient of the gravitational potential since to satisfy the gravity-gradient relation that should be zero.
|
||||
* The derivative of the residual with respect to the gradient along each axis is then just the identity matrix.
|
||||
*
|
||||
* @param point The point at which to evaluate the gravity-gradient relation.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the gravity-gradient relation at the given point.
|
||||
*/
|
||||
static PointLinearization<Operands, discretization::forms::VectorValue> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) {
|
||||
PointLinearization<Operands, discretization::forms::VectorValue> result;
|
||||
|
||||
result.residual = point.fields.get<Gradient>();
|
||||
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
result.field_derivatives.get<Gradient>()[axis][axis] = 1.0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional order of the gravity-gradient relation with respect to the gradient operand.
|
||||
*
|
||||
* @param operand-type The type of the operand being evaluated, which must be the gradient operand.
|
||||
* @param position-order The order of the position being evaluated, which is not used in this implementation.
|
||||
* @return The additional order of the gravity-gradient relation with respect to the gradient operand, which is always 0.
|
||||
*/
|
||||
static int additional_order(Operand<Gradient> /*operand-type*/, int /*position-order*/) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PotentialValue
|
||||
*
|
||||
* @brief The point wise contribution to the Poisson balance equation. Note that these contributions
|
||||
* are just the point-wise potential values. You can get to this by going through the
|
||||
* weak form derivation. The derivative is just one since the residual is just the variable.
|
||||
*
|
||||
* @contribution Residuals to the gravity-gradient relation block of the system. Note how this is declares the divergence operator. Therefore whatever is returned from here
|
||||
* will be integrated against the divergence of the test function.
|
||||
*/
|
||||
struct PotentialValue : Contribution<Operand<Gradient, Divergence>> {
|
||||
using Operands = utils::types::TypeList<Operand<Potential>>; ///< This equation only depends on the potential field
|
||||
|
||||
/**
|
||||
* @brief Evaluate the Poisson balance equation at a given point. Fundamentally all this this is doing is
|
||||
* returning the gravitational potential since to satisfy the Poisson balance equation that should be zero.
|
||||
* The derivative of the residual with respect to the potential is then just one.
|
||||
*
|
||||
* When you go through the weak form derivation and the integration by parts you find that
|
||||
* the point wise contribution to the gravity gradient row of the system is
|
||||
* the divergence of the test function times the potential. \f$ \left(\nabla \cdot v\right) \phi \f$.
|
||||
* The derivative is then trivially one.
|
||||
*
|
||||
* @param point The point at which to evaluate the Poisson balance equation.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the Poisson balance equation at the given point.
|
||||
*/
|
||||
static PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) {
|
||||
PointLinearization<Operands> result;
|
||||
|
||||
result.residual = point.fields.get<Potential>();
|
||||
result.field_derivatives.get<Potential>() = 1.0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional order of the Poisson balance equation with respect to the potential operand. The contribution
|
||||
* is linear in potential so the additional order is always 0.
|
||||
*
|
||||
* @param operand-type The type of the operand being evaluated, which must be the potential operand.
|
||||
* @param position-order The order of the position being evaluated, which is not used in this implementation.
|
||||
* @return The additional order of the Poisson balance equation with respect to the potential operand, which is always 0.
|
||||
*/
|
||||
static int additional_order(Operand<Potential> /*operand-type*/, int /*position-order*/) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class GradientDivergence
|
||||
*
|
||||
* @brief The point wise contribution to the Poisson balance equation. Note that these contributions
|
||||
* are just the point-wise divergence of the gradient field. You can get to this by going through the
|
||||
* weak form derivation. The derivative is just the identity matrix since the residual is just the divergence of the gradient field.
|
||||
*
|
||||
* @contribution Residuals to the gravitational potential block of the system.
|
||||
*/
|
||||
struct GradientDivergence : Contribution<Operand<Potential>> {
|
||||
using Operands = utils::types::TypeList<Operand<Gradient, Divergence>>; ///< This equation only depends on the divergence of the gradient field
|
||||
|
||||
/**
|
||||
* @brief Evaluate the point-wise residual of the potential. Specifically, this uses the
|
||||
* gets the divergence of the gradient. Note how directly related this is to the name of this
|
||||
* class
|
||||
*
|
||||
* @param point The point at which to evaluate the Poisson balance equation.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the Poisson balance equation at the given point.
|
||||
*/
|
||||
static PointLinearization<Operands> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) {
|
||||
PointLinearization<Operands> result;
|
||||
|
||||
result.residual = point.fields.get<Operand<Gradient, Divergence>>();
|
||||
result.field_derivatives.get<Operand<Gradient, Divergence>>() = 1.0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional order of the Poisson balance equation with respect to the divergence of the gradient operand. The contribution
|
||||
* is linear in the divergence of the gradient so the additional order is always 0.
|
||||
*
|
||||
* @param operand-type The type of the operand being evaluated, which must be the divergence of the gradient operand.
|
||||
* @param position-order The order of the position being evaluated, which is not used in this implementation.
|
||||
* @return The additional order of the Poisson balance equation with respect to the divergence of the gradient operand, which is always 0.
|
||||
*/
|
||||
static int additional_order(Operand<Gradient, Divergence> /*operand-type*/, int /*position-order*/) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class DensitySource
|
||||
*
|
||||
* @brief The point wise contribution to the Poisson balance equation. Note that these contributions
|
||||
* are just the point-wise density values multiplied by \f$-4\pi G\f#. You can get to this by going through the
|
||||
* weak form derivation. The derivative is just \f$-4\pi G\f$.
|
||||
*
|
||||
* @contribution Residuals to the gravitational potential block of the system where that block is supported by the stellar domain (e.g. mass is only defined on the stellar domain).
|
||||
*/
|
||||
struct DensitySource : Contribution<Operand<Potential>, discretization::domain::StellarDomains> {
|
||||
using Operands = utils::types::TypeList<Operand<Density>>; ///< This equation only depends on the density field
|
||||
double four_pi_G; ///< The coefficient to multiply the density by, which is typically \f$-4\pi G\f$. (we let this vary so that the user can select the value of G they want to use in their simulation, primarily since I am writing this with natural units where G=1 but we may eventually switch internally to cgs)
|
||||
|
||||
/**
|
||||
* @brief Construct a DensitySource contribution with a given coefficient.
|
||||
*
|
||||
* @param coefficient The coefficient to multiply the density by, which is typically \f$-4\pi G\f$.
|
||||
*/
|
||||
explicit DensitySource(const double coefficient) : four_pi_G(coefficient) {}
|
||||
|
||||
/**
|
||||
* @brief Evaluate the point-wise residual of the potential. Specifically, this uses the
|
||||
* gets the density multiplied by \f$-4\pi G\f$. Note how directly related this is to the name of this
|
||||
* class
|
||||
*
|
||||
* @param point The point at which to evaluate the Poisson balance equation.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the Poisson balance equation at the given point.
|
||||
*/
|
||||
PointLinearization<Operands> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) const {
|
||||
PointLinearization<Operands> result;
|
||||
|
||||
result.residual = -four_pi_G * point.fields.get<Density>();
|
||||
result.field_derivatives.get<Density>() = -four_pi_G;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional order of the Poisson balance equation with respect to the density operand. The contribution
|
||||
* is linear in density so the additional order is always 0.
|
||||
*
|
||||
* @param operand-type The type of the operand being evaluated, which must be the density operand.
|
||||
* @param position-order The order of the position being evaluated, which is not used in this implementation.
|
||||
* @return The additional order of the Poisson balance equation with respect to the density operand, which is always 0.
|
||||
*/
|
||||
static int additional_order(Operand<Density>, int) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
} // namespace gravity_contributions
|
||||
|
||||
/**
|
||||
* @class GravityGradientRelation
|
||||
*
|
||||
* @brief The gravity-gradient relation equation, which weakly constrains the gravitational potential and gravitational field to agree.
|
||||
*
|
||||
* @contribution Residuals to the gravity-gradient relation block of the system.
|
||||
*/
|
||||
struct GravityGradientRelation {
|
||||
using Test = discretization::blocks::gravity::gradient; ///< The test function for the gravity-gradient relation equation, which is the gravitational field.
|
||||
using ResidualQuantity = dimensions::Acceleration; ///< The physical quantity which is being constrained by this equation, that is the quantity which is being set to a target value.
|
||||
|
||||
static constexpr std::string_view name = "gravity-gradient relation"; ///< Compile time name for the equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Return the contributions to the gravity-gradient relation equation.
|
||||
*
|
||||
* @return A tuple containing the contributions to the gravity-gradient relation equation, which are the point-wise residuals of the gradient field and the divergence of the test function times the potential field.
|
||||
*/
|
||||
static auto contributions() {
|
||||
return std::tuple{gravity_contributions::GradientValue{}, gravity_contributions::PotentialValue{}};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PoissonBalance
|
||||
*
|
||||
* @brief The Poisson balance equation, which weakly constrains the gravitational potential to satisfy the Poisson equation with the mass density as a source term.
|
||||
*
|
||||
* @contribution Residuals to the gravitational potential block of the system where that block is supported by the stellar domain (e.g. mass is only defined on the stellar domain).
|
||||
*/
|
||||
class PoissonBalance {
|
||||
public:
|
||||
using Test = discretization::blocks::gravity::potential; ///< The test function for the Poisson balance equation, which is the gravitational potential.
|
||||
using ResidualQuantity = dimensions::InverseTimeSquared; ///< The physical quantity which is being constrained by this equation, that is the quantity which is being set to a target value.
|
||||
|
||||
static constexpr std::string_view name = "Poisson balance"; ///< Compile time name for the equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a Poisson balance equation with a given coefficient.
|
||||
*
|
||||
* @param four_pi_G The coefficient to multiply the density by, which is typically \f$-4\pi G\f$.
|
||||
*
|
||||
* @throws std::invalid_argument If the coefficient is not finite or not positive.
|
||||
*/
|
||||
explicit PoissonBalance(const double four_pi_G)
|
||||
: m_four_pi_G(four_pi_G) {
|
||||
using namespace utils::errors;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(std::isfinite(four_pi_G) && four_pi_G > 0.0, "Poisson balance requires positive finite 4 pi G.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the contributions to the Poisson balance equation.
|
||||
*
|
||||
* @return A tuple containing the contributions to the Poisson balance equation, which are the point-wise residuals of the divergence of the gradient field and the density field multiplied by \f$-4\pi G\f$.
|
||||
*/
|
||||
auto contributions() const {
|
||||
return std::tuple{gravity_contributions::GradientDivergence{}, gravity_contributions::DensitySource{m_four_pi_G}};
|
||||
}
|
||||
|
||||
private:
|
||||
double m_four_pi_G; ///< The coefficient to multiply the density by, which is typically \f$-4\pi G\f$.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
@@ -1,160 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file pressure_force.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the PressureForce contribution to the displacement balance equation.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/eos/evaluation.hpp"
|
||||
#include "serif/eos/exceptions.hpp"
|
||||
#include "serif/eos/models/polytropic.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief The pressure force contribution to the displacement balance equation. This equation
|
||||
* is really the only one that makes heavy use of the eos. Broadly, when we switch to a non polytropic
|
||||
* case this will likely need some major refactoring.
|
||||
*
|
||||
* @tparam EOST The EOS model type to use for evaluating the pressure from the specific enthalpy.
|
||||
*
|
||||
* @requires The EOS model must support the relation PressureFromSpecificEnthalpy and its partial derivative with respect to specific enthalpy.
|
||||
*
|
||||
* @contribution Residuals to the displacement vector residual block where only on the parts of that block which are part of the stellar domain. This contribution is through the divergence of the test function.
|
||||
*/
|
||||
template <eos::models::PressureForceEOS EOST>
|
||||
class PressureForce final : public Contribution<
|
||||
discretization::forms::Operand<discretization::blocks::displacement::vector, discretization::forms::Operation::Divergence>,
|
||||
discretization::domain::StellarDomains
|
||||
> {
|
||||
public:
|
||||
using Enthalpy = discretization::blocks::enthalpy::specific; ///< Alias for the specific enthalpy
|
||||
using EnthalpyValue = discretization::forms::Operand<Enthalpy>; ///< Alias for the specific enthalpy operand
|
||||
|
||||
using Operands = utils::types::TypeList<EnthalpyValue>; ///< This equation only depends on the specific enthalpy operand
|
||||
|
||||
static constexpr std::string_view name = "pressure force"; ///< Compile time name for the pressure force equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a pressure force contribution with a given EOS model and enthalpy power.
|
||||
*
|
||||
* @param equation_of_state The EOS model to use for evaluating the pressure from the specific enthalpy. Note that the PressureForce will take ownership of the EOS model.
|
||||
* @param enthalpy_power The effective power of the pressure as a function of specific enthalpy, which must be positive and finite. This is used for quadrature selection.
|
||||
*
|
||||
* @throws std::invalid_argument If the enthalpy power is not finite and strictly positive.
|
||||
*/
|
||||
explicit PressureForce(
|
||||
EOST equation_of_state,
|
||||
const double enthalpy_power
|
||||
) :
|
||||
m_eos(std::move(equation_of_state)),
|
||||
m_enthalpy_power(enthalpy_power) {
|
||||
using namespace utils::errors;
|
||||
|
||||
serif_ensure<SERiFErrorCode::INVALID_QUADRATURE_ORDER, std::invalid_argument>(std::isfinite(enthalpy_power) && enthalpy_power > 0.0, "The pressure quadrature power must be positive and finite.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a pressure force contribution with a given EOS model. This constructor is only available for polytropic EOS models, where the enthalpy power is inferred from the polytropic index.
|
||||
* In this case we call the other constructor with the polytropic index + 1 as the enthalpy power.
|
||||
*
|
||||
* @param equation_of_state The polytropic EOS model to use for evaluating the pressure from the specific enthalpy. Note that the PressureForce will take ownership of the EOS model.
|
||||
*
|
||||
* @requires The EOS model must be a polytropic EOS model, otherwise this constructor is not available.
|
||||
*/
|
||||
explicit PressureForce(
|
||||
EOST equation_of_state
|
||||
)
|
||||
requires std::same_as<EOST, eos::models::Polytrope>
|
||||
: PressureForce(equation_of_state, equation_of_state.polytropic_index() + 1.0) {}
|
||||
|
||||
/**
|
||||
* @brief Evaluate the pressure force contribution at a given point.
|
||||
*
|
||||
* @param point The point at which to evaluate the pressure force contribution.
|
||||
* @return A PointLinearization object containing the residual and field derivatives for the pressure force contribution at the given point.
|
||||
*
|
||||
*
|
||||
* Here we let the point-wise residual return as \f$-P\f$, recall that since we set the contribution operand to be the divergence and supported on
|
||||
* the stellar domain this will be integrated as (for some trial function \f$\phi\f$)
|
||||
*
|
||||
* \f[
|
||||
* R = \int_{\Omega_{\text{stellar}}} -\left(\nabla \mathbf{\phi} \right)P dV
|
||||
* \f]
|
||||
*
|
||||
* Further, this implements the partial derivative of this residual with respect to the
|
||||
* specific enthalpy as
|
||||
*
|
||||
* - \f$ \frac{\partial R}{\partial h} = -\frac{\partial P}{\partial h} \f$
|
||||
*
|
||||
* @throws eos::EOSEvaluationError If the EOS model returns a non-finite pressure or pressure derivative for the given specific enthalpy.
|
||||
* @throws eos::EOSEvaluationError If the specific enthalpy is not finite or is negative, which is outside the domain of the EOS model.
|
||||
*/
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) const {
|
||||
using namespace utils::errors;
|
||||
|
||||
// TODO: Here is a place where the assumptions about a polytrope have slipped into my code.
|
||||
// As we move to a more general case we cannot rely on the enthalpy to be a general carrier field
|
||||
const double enthalpy = point.fields.get<Enthalpy>();
|
||||
|
||||
serif_ensure<SERiFErrorCode::EOS_NONFINITE_INPUT, eos::EOSEvaluationError>(std::isfinite(enthalpy), "Pressure force requires finite enthalpy.");
|
||||
serif_ensure<SERiFErrorCode::EOS_OUTSIDE_DOMAIN, eos::EOSEvaluationError>(enthalpy >= 0.0, "Stellar pressure force requires non-negative enthalpy.");
|
||||
|
||||
const dimensions::SpecificEnthalpyValue input{enthalpy};
|
||||
|
||||
const double pressure = eos::evaluate<dimensions::Pressure>(m_eos, input).value();
|
||||
const double pressure_derivative = eos::partial_derivative<dimensions::Pressure, dimensions::SpecificEnthalpy>(m_eos, input).value();
|
||||
|
||||
serif_ensure<SERiFErrorCode::EOS_NONFINITE_RESULT, eos::EOSEvaluationError>(std::isfinite(pressure) && std::isfinite(pressure_derivative), "Pressure force received a non-finite EOS result.");
|
||||
|
||||
PointLinearization<Operands> result;
|
||||
result.residual = -pressure;
|
||||
result.field_derivatives.get<Enthalpy>() = -pressure_derivative;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the additional quadrature order requires for integrating the pressure force contribution.
|
||||
* This is calculated as \f$\lceil \max(0, n-1) p \rceil\f$ where \f$n\f$ is the enthalpy power and \f$p\f$ is the order of the specific enthalpy functional space.
|
||||
*
|
||||
* @param operand-type The operand type for which to determine the additional quadrature order. This is not used in this implementation since the pressure force contribution only depends on the specific enthalpy operand.
|
||||
* @param position_order The order of the position operand, which is not used in this implementation.
|
||||
*
|
||||
* @throws std::invalid_argument If the calculated additional quadrature order exceeds the maximum value of an int.
|
||||
*
|
||||
* @return The additional quadrature order required for integrating the pressure force contribution.
|
||||
*/
|
||||
[[nodiscard]] int additional_order(EnthalpyValue /*operand-type*/, int /*position_order*/) const {
|
||||
using namespace utils::errors;
|
||||
|
||||
const double extra = std::ceil(std::max(0.0, m_enthalpy_power - 1.0) * Enthalpy::FunctionalSpace::order);
|
||||
serif_ensure<SERiFErrorCode::INVALID_QUADRATURE_ORDER, std::invalid_argument>(extra <= std::numeric_limits<int>::max(), "The pressure quadrature order exceeds the supported range.");
|
||||
|
||||
return static_cast<int>(extra);
|
||||
}
|
||||
|
||||
/** @brief Inspect the immutable EOS defining pressure. */
|
||||
[[nodiscard]] const EOST& equation_of_state() const noexcept { return m_eos; }
|
||||
|
||||
/** @brief Inspect the declared power used to choose quadrature. */
|
||||
[[nodiscard]] double enthalpy_power() const noexcept { return m_enthalpy_power; }
|
||||
|
||||
private:
|
||||
EOST m_eos; ///< The EOS model to use for evaluating the pressure from the specific enthalpy.
|
||||
double m_enthalpy_power; ///< The effective power of the pressure as a function of specific enthalpy, which must be positive and finite. This is used for quadrature selection.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
80
src/include/serif/equations/primal_gravity.hpp
Normal file
80
src/include/serif/equations/primal_gravity.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/** @brief Solve Delta(Phi) = 4 pi G rho with one conforming scalar potential field. */
|
||||
class PrimalPoissonBalance final {
|
||||
public:
|
||||
using Test = discretization::blocks::gravity::potential;
|
||||
using Density = discretization::blocks::density::mass;
|
||||
using PotentialGradient = discretization::forms::Operand<Test, discretization::forms::Operation::Gradient>;
|
||||
using ResidualQuantity = dimensions::InverseTimeSquared;
|
||||
static constexpr std::string_view name = "primal Poisson balance";
|
||||
|
||||
/** @brief Integrate grad(v) dot grad(Phi) throughout the star and vacuum. */
|
||||
struct PotentialFlux : Contribution<PotentialGradient> {
|
||||
using Operands = utils::types::TypeList<PotentialGradient>;
|
||||
|
||||
/** @brief The potential flux and its derivative are grad(Phi) and identity. */
|
||||
[[nodiscard]] static PointLinearization<Operands, discretization::forms::VectorValue>
|
||||
evaluate(const EquationPoint<Operands>& point) {
|
||||
PointLinearization<Operands, discretization::forms::VectorValue> result;
|
||||
result.residual = point.fields.template get<PotentialGradient>();
|
||||
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
result.field_derivatives.template get<PotentialGradient>()[axis][axis] = 1.0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
/** @brief A linear flux introduces no additional coefficient degree. */
|
||||
[[nodiscard]] static int additional_order(PotentialGradient, int) { return 0; }
|
||||
};
|
||||
|
||||
/** @brief Integrate +4 pi G v rho only where matter is supported. */
|
||||
struct DensitySource : Contribution<discretization::forms::Operand<Test>, discretization::domain::StellarDomains> {
|
||||
using Operands = utils::types::TypeList<discretization::forms::Operand<Density>>;
|
||||
|
||||
double four_pi_G;
|
||||
|
||||
/** @brief Store the already validated source coefficient. */
|
||||
explicit DensitySource(const double coefficient) : four_pi_G(coefficient) {}
|
||||
|
||||
/** @brief Return the source with the positive sign of the primal weak form. */
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) const {
|
||||
PointLinearization<Operands> result;
|
||||
result.residual = four_pi_G * point.fields.template get<Density>();
|
||||
result.field_derivatives.template get<Density>() = four_pi_G;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @brief The source is linear in density. */
|
||||
[[nodiscard]] static int additional_order(discretization::forms::Operand<Density>, int) { return 0; }
|
||||
};
|
||||
|
||||
/** @brief Set positive finite 4 pi G; impose the zero trace at infinity separately. */
|
||||
explicit PrimalPoissonBalance(const double four_pi_G) : m_four_pi_G(four_pi_G) {
|
||||
using namespace utils::errors;
|
||||
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(std::isfinite(four_pi_G) && four_pi_G > 0.0, "Primal Poisson balance requires positive finite 4 pi G.");
|
||||
}
|
||||
|
||||
/** @brief Publish the diffusion term and stellar source to the existing volume assembler. */
|
||||
[[nodiscard]] auto contributions() const {
|
||||
return std::tuple{PotentialFlux{}, DensitySource{m_four_pi_G}};
|
||||
}
|
||||
/** @brief Inspect the physical source coefficient for serialization. */
|
||||
[[nodiscard]] double four_pi_G() const noexcept { return m_four_pi_G; }
|
||||
|
||||
private:
|
||||
double m_four_pi_G;
|
||||
};
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file rotational_force.hpp
|
||||
* @author Emily M. Boudreaux
|
||||
*
|
||||
* @brief File containing the RotationalForce contribution class, which computes the rotational force contribution to the displacement balance equation.
|
||||
*/
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/equations/contributions.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/equations/rigid_rotation.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @class RotationalForce
|
||||
*
|
||||
* @brief The rotational force contribution to the displacement balance equation. This equation computes the rotational force contribution
|
||||
* to the displacement balance equation, which is given by \f$-\int_{\Omega_{\text{stellar}}}\rho\nabla\Psi \cdot w \, dV\f$ where \f$\Psi\f$ is the centrifugal potential
|
||||
* and \f$w\f$ is the test function.
|
||||
*
|
||||
* @contribution Residuals to the displacement vector residual block where only on the parts of that block which are part of the stellar domain.
|
||||
*
|
||||
*/
|
||||
class RotationalForce final : public Contribution<discretization::forms::Operand<discretization::blocks::displacement::vector>, discretization::domain::StellarDomains> {
|
||||
public:
|
||||
using Density = discretization::blocks::density::mass; ///< Alias for mass density
|
||||
using DensityValue = discretization::forms::Operand<Density>; ///< Operand type for mass density
|
||||
|
||||
using AngularVelocity = discretization::blocks::rotation::angular_velocity; ///< Alias for angular velocity
|
||||
using AngularVelocityValue = discretization::forms::Operand<AngularVelocity>; ///< Operand type for angular velocity
|
||||
|
||||
using Operands = utils::types::TypeList<DensityValue, AngularVelocityValue>; ///< The rotational force depends on the mass density and the angular velocity operands.
|
||||
using Result = discretization::forms::VectorValue; ///< The result type will be a vector value.
|
||||
|
||||
static constexpr std::string_view name = "rotational force"; ///< Compile time name for the rotational force equation, used for debugging and logging.
|
||||
|
||||
/**
|
||||
* @brief Construct a rotational force contribution with a given axis and center of rotation.
|
||||
*
|
||||
* @param axis The axis of rotation, which must be finite. Defaults to the z-axis.
|
||||
* @param center The center of rotation, which must be finite. Defaults to the origin.
|
||||
*/
|
||||
explicit RotationalForce(
|
||||
const Result &axis = {0.0, 0.0, 1.0},
|
||||
const Result ¢er = {}
|
||||
) :
|
||||
m_rotation(RigidRotation::unit_rotation(axis, center)) {}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Evaluate the rotational force contribution at a given point.
|
||||
*
|
||||
* @param point The point at which to evaluate the rotational force contribution, which must contain the mass density and angular velocity operands.
|
||||
* @return A PointLinearization containing the residual and derivatives of the rotational force contribution at the given point.
|
||||
*
|
||||
* Here the point-wise integrand is computed as a three-vector, where each component \f$r_{i}\f$ is given by \f$ r_{i} = \rho\left(-\omega^{2}\nabla\Psi_{i}\right)
|
||||
*
|
||||
* There are then derivatives of this given with respect to mass density, angular velocity, and position
|
||||
*
|
||||
* - \frac{\partial r_{i}}{\partial \rho} = -\omega^{2}\nabla\Psi_{i}
|
||||
* - \frac{\partial r_{i}}{\partial \omega} = -2\omega\rho\nabla\Psi_{i}
|
||||
* - \frac{\partial r_{i}}{\partial x_{j}} = -\rho \omega^{2} \frac{\partial^{2} \Psi_{i}}{\partial x_{i} \partial x_{j}}
|
||||
*/
|
||||
[[nodiscard]] PointLinearization<Operands, Result> evaluate(
|
||||
const EquationPoint<Operands>& point
|
||||
) const {
|
||||
const double density = point.fields.get<Density>();
|
||||
const double omega = point.fields.get<AngularVelocity>();
|
||||
const double omega_squared = omega * omega;
|
||||
const auto gradient = m_rotation.centrifugal_potential_gradient(point.position);
|
||||
|
||||
PointLinearization<Operands, Result> result;
|
||||
|
||||
for (int row = 0; row < 3; ++row) {
|
||||
result.residual[row] = density * (-omega_squared * gradient[row]);
|
||||
result.field_derivatives.get<Density>()[row] = -omega_squared * gradient[row];
|
||||
result.field_derivatives.get<AngularVelocity>()[row] = -2.0 * omega * density * gradient[row];
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
result.position_derivative[row][column] = -density * (omega_squared * m_rotation.centrifugal_potential_hessian()[row][column]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the additional quadrature order required for the operands of this equation when integrating density.
|
||||
*
|
||||
* @param operand-type The operand type for which to determine the additional quadrature order. Note this is only used to select the correct overload.
|
||||
* @param position_order The quadrature order associated with the position.
|
||||
* @return The additional quadrature order required for the given operand type. For density this just returns the position order;
|
||||
*/
|
||||
[[nodiscard]] static int additional_order(DensityValue /*operand-type*/, const int position_order) {
|
||||
return position_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the additional quadrature order required for the operands of this equation when integrating angular velocity.
|
||||
*
|
||||
* @param operand-type The operand type for which to determine the additional quadrature order. Note this is only used to select the correct overload.
|
||||
* @param position_order The quadrature order associated with the position.
|
||||
* @return The additional quadrature order required for the given operand type. For angular velocity this returns the sum of the density functional space order and the position order.
|
||||
*/
|
||||
[[nodiscard]] static int additional_order(AngularVelocityValue /*operand-type*/, const int position_order) {
|
||||
return Density::FunctionalSpace::order + position_order;
|
||||
}
|
||||
|
||||
/** @brief Inspect the physical rotation axis and origin. */
|
||||
[[nodiscard]] const RigidRotation& rotation() const noexcept { return m_rotation; }
|
||||
|
||||
private:
|
||||
RigidRotation m_rotation; ///< The rigid rotation object used to compute the centrifugal potential and its derivatives, which is constructed from the axis and center of rotation provided to the constructor.
|
||||
};
|
||||
} // namespace serif::equations
|
||||
67
src/include/serif/equations/surface_potential.hpp
Normal file
67
src/include/serif/equations/surface_potential.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
#include "serif/equations/companion.hpp"
|
||||
#include "serif/equations/point.hpp"
|
||||
#include "serif/equations/rigid_rotation.hpp"
|
||||
|
||||
namespace serif::equations {
|
||||
/**
|
||||
* @brief Set the effective potential to one unknown level on the selected isobar.
|
||||
*
|
||||
* Integrate this point relation against the existing surface shape basis with
|
||||
* a fixed dimensionless reference measure. The constant mode is retained.
|
||||
* This is a boundary equation, not a volume displacement force.
|
||||
*/
|
||||
class SurfacePotentialBalance final {
|
||||
public:
|
||||
using Potential = discretization::blocks::gravity::potential;
|
||||
using SurfaceLevel = discretization::blocks::barotropic_constant::mass_normalization;
|
||||
using AngularVelocity = discretization::blocks::rotation::angular_velocity;
|
||||
using Operands = utils::types::TypeList<discretization::forms::Operand<Potential>,
|
||||
discretization::forms::Operand<SurfaceLevel>, discretization::forms::Operand<AngularVelocity>>;
|
||||
using ResidualQuantity = dimensions::GravitationalPotential;
|
||||
static constexpr std::string_view name = "surface effective potential balance";
|
||||
|
||||
/** @brief Use the same physical rotation axis, origin and companion as the interior equation. */
|
||||
explicit SurfacePotentialBalance(const RigidRotation::Vector& axis = {0.0, 0.0, 1.0},
|
||||
const RigidRotation::Vector& center = {}, std::optional<PointMassCompanion> companion = std::nullopt) :
|
||||
m_rotation(RigidRotation::unit_rotation(axis, center)), m_companion(companion) {}
|
||||
|
||||
/** @brief Differentiate at fixed pulled-back potential; geometry moves only the centrifugal term. */
|
||||
[[nodiscard]] PointLinearization<Operands> evaluate(const EquationPoint<Operands>& point) const {
|
||||
const double omega = point.fields.get<AngularVelocity>();
|
||||
const double centrifugal = m_rotation.centrifugal_potential(point.position);
|
||||
PointLinearization<Operands> result;
|
||||
|
||||
result.residual = point.fields.get<Potential>() - omega * omega * centrifugal - point.fields.get<SurfaceLevel>();
|
||||
result.field_derivatives.get<Potential>() = 1.0;
|
||||
result.field_derivatives.get<SurfaceLevel>() = -1.0;
|
||||
result.field_derivatives.get<AngularVelocity>() = -2.0 * omega * centrifugal;
|
||||
result.position_derivative = m_rotation.centrifugal_potential_gradient(point.position);
|
||||
|
||||
for (double& component : result.position_derivative) {
|
||||
component *= -omega * omega;
|
||||
}
|
||||
|
||||
if (m_companion) {
|
||||
result.residual += m_companion->potential(point.position);
|
||||
const auto gradient = m_companion->potential_gradient(point.position);
|
||||
for (int axis = 0; axis < 3; ++axis) { result.position_derivative[axis] += gradient[axis]; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
/** @brief Inspect the physical rotation convention. */
|
||||
[[nodiscard]] const RigidRotation& rotation() const noexcept { return m_rotation; }
|
||||
/** @brief Inspect the companion, if any. */
|
||||
[[nodiscard]] const std::optional<PointMassCompanion>& companion() const noexcept { return m_companion; }
|
||||
|
||||
private:
|
||||
RigidRotation m_rotation;
|
||||
std::optional<PointMassCompanion> m_companion;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user