feat(solver/normalization): Added normalization system
The dynamical range of different fields SERiF must solve for is massive. This just breaks any reasonable preconditioner / spreads the eigen modes of the system matrix out. This is a normalization system which uses charectaristic scale factors (e.g. the approximate radius estimated using the lane-emden equation) to rescale different fields to a more similar numeric scale. This turns out to be essential for stable convergence. Broadly this implements Riesz normalization along with exact diagnoal normalization.
This commit is contained in:
76
src/include/serif/solver/normalization/computations.hpp
Normal file
76
src/include/serif/solver/normalization/computations.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <expected>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/solver/normalization/detail/factor_computations.hpp"
|
||||
#include "serif/solver/normalization/detail/packing.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
/** @brief Test every coordinate factory without instantiating an unusable runtime graph. */
|
||||
template <typename RulesT, typename LayoutT, typename ScalesT, discretization::blocks::RepresentedValueKind Kind, typename... Coordinates>
|
||||
[[nodiscard]] consteval bool can_prepare_coordinates(utils::dependencies::KeyList<Coordinates...>)
|
||||
{
|
||||
return (requires
|
||||
{
|
||||
coordinate_computation<Kind, Coordinates, LayoutT, ScalesT>(utils::dependencies::bound_type_t<RulesT, Coordinates>{});
|
||||
} && ...);
|
||||
}
|
||||
|
||||
/** @brief Expand the supplied layout, looking up methods without imposing plan declaration order. */
|
||||
template <typename RulesT, typename LayoutT, typename ScalesT, discretization::blocks::RepresentedValueKind Kind, typename... Coordinates>
|
||||
[[nodiscard]] auto coordinate_computations(utils::dependencies::KeyList<Coordinates...>)
|
||||
{
|
||||
return std::tuple{coordinate_computation<Kind, Coordinates, LayoutT, ScalesT>(utils::dependencies::bound_type_t<RulesT, Coordinates>{})...};
|
||||
}
|
||||
|
||||
/** @brief Freeze the independently ordered state and residual coordinates into one owning result. */
|
||||
template <typename... Values, typename... Residuals>
|
||||
[[nodiscard]] auto packing_computation(utils::dependencies::KeyList<Values...>, utils::dependencies::KeyList<Residuals...>)
|
||||
{
|
||||
using namespace utils::dependencies;
|
||||
using discretization::blocks::RepresentedValueKind;
|
||||
|
||||
return make_computation<keys::Normalization, InputList<keys::Factors<RepresentedValueKind::value, Values>..., keys::Factors<RepresentedValueKind::residual, Residuals>...>>(
|
||||
[](const auto&... factors) -> std::expected<DiagonalNormalization, std::string>
|
||||
{
|
||||
const std::array<std::span<const double>, sizeof...(Values) + sizeof...(Residuals)> coordinates{factors...};
|
||||
const std::span<const std::span<const double>> blocks{coordinates};
|
||||
return pack_normalization(blocks.first(sizeof...(Values)), blocks.subspan(sizeof...(Values)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename PlanT, typename LayoutT, typename ScalesT = StellarReferenceScales>
|
||||
concept NormalizationPreparable = CoordinateLayoutType<LayoutT> &&
|
||||
CompleteNormalizationFor<PlanT, typename std::remove_cvref_t<LayoutT>::Values, typename std::remove_cvref_t<LayoutT>::Residuals> &&
|
||||
detail::can_prepare_coordinates<typename std::remove_cvref_t<PlanT>::Values, std::remove_cvref_t<LayoutT>, std::remove_cvref_t<ScalesT>, discretization::blocks::RepresentedValueKind::value>(typename std::remove_cvref_t<LayoutT>::Values{}) &&
|
||||
detail::can_prepare_coordinates<typename std::remove_cvref_t<PlanT>::Residuals, std::remove_cvref_t<LayoutT>, std::remove_cvref_t<ScalesT>, discretization::blocks::RepresentedValueKind::residual>(typename std::remove_cvref_t<LayoutT>::Residuals{});
|
||||
|
||||
/**
|
||||
* @brief Declare normalization as ordinary dependency computations; no metrics or factors are prepared here.
|
||||
* @note Supply keys::Layout, keys::Scales, and the plan's metric/selection/prescription sources. Copy the accepted DiagonalNormalization to freeze one solve before preparing later candidates.
|
||||
*/
|
||||
template <typename PlanT, typename ScalesT = StellarReferenceScales, typename LayoutT>
|
||||
requires NormalizationPreparable<PlanT, LayoutT, ScalesT>
|
||||
[[nodiscard]] auto make_normalization_computations(const LayoutT&)
|
||||
{
|
||||
using discretization::blocks::RepresentedValueKind;
|
||||
using Plan = std::remove_cvref_t<PlanT>;
|
||||
using Layout = std::remove_cvref_t<LayoutT>;
|
||||
using Scales = std::remove_cvref_t<ScalesT>;
|
||||
|
||||
return std::tuple_cat(
|
||||
detail::coordinate_computations<typename Plan::Values, Layout, Scales, RepresentedValueKind::value>(typename Layout::Values{}),
|
||||
detail::coordinate_computations<typename Plan::Residuals, Layout, Scales, RepresentedValueKind::residual>(typename Layout::Residuals{}),
|
||||
std::tuple{detail::packing_computation(typename Layout::Values{}, typename Layout::Residuals{})});
|
||||
}
|
||||
}
|
||||
95
src/include/serif/solver/normalization/configuration.hpp
Normal file
95
src/include/serif/solver/normalization/configuration.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
#include "serif/solver/normalization/scales.hpp"
|
||||
#include "serif/utils/dependencies/dependency_context.hpp"
|
||||
#include "serif/utils/laneemden/laneemden.hpp"
|
||||
|
||||
namespace serif::solver::normalization {
|
||||
/** @brief Obtain a reference value from the corresponding descriptive model prescription. */
|
||||
struct FromModel final {};
|
||||
|
||||
/** @brief Derive the coordinate rules from the selected fields, equations and boundary formulation. */
|
||||
struct AutomaticPlan final {};
|
||||
|
||||
/** @brief Remove physical and geometric units from the solver's coordinates and norms. */
|
||||
struct Dimensionless final {};
|
||||
|
||||
/**
|
||||
* @brief Estimate a reference radius from a Lane–Emden shape, mass and central density.
|
||||
* @note These describe the numerical reference star; they never alter the model's EOS or constraints.
|
||||
*/
|
||||
template <typename Mass = FromModel, typename CentralDensity = FromModel>
|
||||
requires (std::same_as<Mass, FromModel> || std::same_as<Mass, dimensions::MassValue>) &&
|
||||
(std::same_as<CentralDensity, FromModel> || std::same_as<CentralDensity, dimensions::DensityValue>)
|
||||
struct LaneEmdenApproximation final {
|
||||
double n{3.0};
|
||||
Mass mass;
|
||||
CentralDensity central_density;
|
||||
utils::laneemden::IntegrationOptions integration{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Declare normalization without evaluating a reference star or allocating coordinate factors.
|
||||
* @note G uses the model's declared units; its default is the existing G=1 code-unit convention.
|
||||
* Advanced users may supply a complete plan and ordinary graph sources/computations for custom rules.
|
||||
*/
|
||||
template <typename Scheme = LaneEmdenApproximation<>, typename Coordinates = Dimensionless,
|
||||
typename Plan = AutomaticPlan, typename Sources = utils::dependencies::DependencyContext<>, typename Computations = std::tuple<>>
|
||||
requires std::same_as<Coordinates, Dimensionless> && utils::dependencies::IsDependencyContext<Sources>
|
||||
struct NormalizationOptions final {
|
||||
Scheme scheme;
|
||||
Coordinates coordinates{};
|
||||
Plan plan;
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant{1.0};
|
||||
Sources sources;
|
||||
Computations computations;
|
||||
};
|
||||
|
||||
/** @brief Preserve the deduced numerical choices for later preparation with a model and discretization. */
|
||||
template <typename Scheme, typename Coordinates, typename Plan, typename Sources, typename Computations>
|
||||
[[nodiscard]] auto make_normalization(NormalizationOptions<Scheme, Coordinates, Plan, Sources, Computations> options) {
|
||||
return options;
|
||||
}
|
||||
|
||||
/** @brief Select the usual dimensionless Lane–Emden reference; supply its central density before resolving it against a model. */
|
||||
[[nodiscard]] inline auto make_normalization() {
|
||||
return NormalizationOptions<>{};
|
||||
}
|
||||
|
||||
/** @brief Derive the shared stellar scales after both dimensional reference inputs have been resolved. */
|
||||
[[nodiscard]] inline std::expected<StellarReferenceScales, std::string> prepare_reference_scales(
|
||||
const LaneEmdenApproximation<dimensions::MassValue, dimensions::DensityValue>& scheme,
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant) {
|
||||
const auto solution = utils::laneemden::solve(scheme.n, scheme.integration);
|
||||
if (!solution) {
|
||||
return std::unexpected(solution.error());
|
||||
}
|
||||
const auto reference = utils::laneemden::scale_from_mass(*solution, scheme.mass, scheme.central_density, gravitational_constant);
|
||||
if (!reference) {
|
||||
return std::unexpected(reference.error());
|
||||
}
|
||||
return prepare_stellar_reference_scales(reference->mass, reference->radius, gravitational_constant);
|
||||
}
|
||||
|
||||
/** @brief Use explicitly prepared reference scales through the same graph as estimated references. */
|
||||
[[nodiscard]] inline std::expected<StellarReferenceScales, std::string> prepare_reference_scales(
|
||||
const StellarReferenceScales& reference, dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant) {
|
||||
if (reference.gravitational_constant != gravitational_constant) {
|
||||
return std::unexpected("The explicit normalization reference and configuration use different gravitational constants. Supply one G in the model's units.");
|
||||
}
|
||||
return prepare_stellar_reference_scales(reference.mass, reference.radius, gravitational_constant);
|
||||
}
|
||||
|
||||
/** @brief A custom stellar reference strategy supplies the same typed scales and ordinary failure value. */
|
||||
template <typename Scheme>
|
||||
concept ReferenceScheme = requires(const Scheme& scheme, dimensions::QuantityValue<dimensions::GravitationalConstant> gravity) {
|
||||
{ prepare_reference_scales(scheme, gravity) } -> std::same_as<std::expected<StellarReferenceScales, std::string>>;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "serif/solver/normalization/keys.hpp"
|
||||
#include "serif/solver/normalization/layout.hpp"
|
||||
#include "serif/solver/normalization/plan.hpp"
|
||||
#include "serif/solver/normalization/diagonal.hpp"
|
||||
#include "serif/solver/normalization/detail/validation.hpp"
|
||||
#include "serif/solver/normalization/point_rows.hpp"
|
||||
#include "serif/solver/normalization/scales.hpp"
|
||||
#include "serif/utils/dependencies/computation.hpp"
|
||||
|
||||
namespace serif::solver::normalization::detail
|
||||
{
|
||||
template <typename ScalesT, typename QuantityT>
|
||||
concept ProvidesScale = requires(const ScalesT& scales)
|
||||
{
|
||||
{ characteristic_scale(scales, QuantityT{}) } -> std::same_as<dimensions::QuantityValue<QuantityT>>;
|
||||
};
|
||||
|
||||
/** @brief Reject a layout that changes the arity of a declared global scalar. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, CoordinateLayoutType LayoutT>
|
||||
[[nodiscard]] std::expected<std::size_t, std::string> coordinate_size(const LayoutT& layout)
|
||||
{
|
||||
const std::size_t size = layout.template size<Kind, CoordinateT>();
|
||||
using Term = typename decltype(metric_term<CoordinateT>())::type;
|
||||
return validate_coordinate_size(size, discretization::blocks::GlobalScalarTerm<Term>);
|
||||
}
|
||||
|
||||
/** @brief Check supplied metrics or custom factors before publishing a coordinate result. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, CoordinateLayoutType LayoutT>
|
||||
[[nodiscard]] std::expected<void, std::string> check_coordinate_size(const LayoutT& layout, std::size_t received)
|
||||
{
|
||||
const auto size = coordinate_size<Kind, CoordinateT>(layout);
|
||||
if (!size)
|
||||
{
|
||||
return std::unexpected(size.error());
|
||||
}
|
||||
return check_coordinate_size(*size, received);
|
||||
}
|
||||
|
||||
/** @brief Identity preparation requires a layout but no scale or metric sources. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT>
|
||||
[[nodiscard]] auto coordinate_computation(IdentityCoordinate)
|
||||
{
|
||||
using namespace utils::dependencies;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout>>(
|
||||
[](const LayoutT& layout) -> std::expected<std::vector<double>, std::string>
|
||||
{
|
||||
const auto size = coordinate_size<Kind, CoordinateT>(layout);
|
||||
if (!size)
|
||||
{
|
||||
return std::unexpected(size.error());
|
||||
}
|
||||
return std::vector<double>(*size, 1.0);
|
||||
});
|
||||
}
|
||||
|
||||
/** @brief Algebraic coordinates use a scalar metric without allocating an intermediate vector of ones. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename QuantityT>
|
||||
requires ProvidesScale<ScalesT, QuantityT>
|
||||
[[nodiscard]] auto coordinate_computation(PhysicalRieszCoordinate<QuantityT, UnitMetric>)
|
||||
{
|
||||
using namespace utils::dependencies;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout, keys::Scales>>(
|
||||
[](const LayoutT& layout, const ScalesT& scales) -> std::expected<std::vector<double>, std::string>
|
||||
{
|
||||
const auto size = coordinate_size<Kind, CoordinateT>(layout);
|
||||
if (!size)
|
||||
{
|
||||
return std::unexpected(size.error());
|
||||
}
|
||||
const auto factor = make_riesz_factor(Kind, 1.0, characteristic_scale(scales, QuantityT{}).value());
|
||||
if (!factor)
|
||||
{
|
||||
return std::unexpected(factor.error());
|
||||
}
|
||||
return std::vector<double>(*size, *factor);
|
||||
});
|
||||
}
|
||||
|
||||
/** @brief Both coordinate roles consume the same cached reference metric with different primal/dual formulas. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename QuantityT, typename MetricT>
|
||||
requires (!std::same_as<MetricT, UnitMetric> && ProvidesScale<ScalesT, QuantityT>)
|
||||
[[nodiscard]] auto coordinate_computation(PhysicalRieszCoordinate<QuantityT, MetricT>)
|
||||
{
|
||||
using namespace utils::dependencies;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout, keys::Scales, MetricT>>(
|
||||
[](const LayoutT& layout, const ScalesT& scales, const std::vector<double>& metric) -> std::expected<std::vector<double>, std::string>
|
||||
{
|
||||
const auto valid = check_coordinate_size<Kind, CoordinateT>(layout, metric.size());
|
||||
if (!valid)
|
||||
{
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
return make_riesz_factors(Kind, metric, characteristic_scale(scales, QuantityT{}).value());
|
||||
});
|
||||
}
|
||||
|
||||
/** @brief Read the replacement's quantity independently of the bulk equation's scale. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename QuantityT, typename MetricT, typename PointQuantityT, typename SelectionT>
|
||||
requires (Kind == discretization::blocks::RepresentedValueKind::residual && !std::same_as<MetricT, UnitMetric> && ProvidesScale<ScalesT, QuantityT> && ProvidesScale<ScalesT, PointQuantityT>)
|
||||
[[nodiscard]] auto coordinate_computation(WithPointRows<PhysicalRieszCoordinate<QuantityT, MetricT>, PointQuantityT, SelectionT>)
|
||||
{
|
||||
using namespace utils::dependencies;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout, keys::Scales, MetricT, SelectionT>>(
|
||||
[](const LayoutT& layout, const ScalesT& scales, const std::vector<double>& metric, const std::vector<int>& rows) -> std::expected<std::vector<double>, std::string>
|
||||
{
|
||||
const auto valid = check_coordinate_size<Kind, CoordinateT>(layout, metric.size());
|
||||
if (!valid)
|
||||
{
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
return make_point_replaced_residual_factors(metric, characteristic_scale(scales, QuantityT{}).value(), rows, characteristic_scale(scales, PointQuantityT{}).value());
|
||||
});
|
||||
}
|
||||
|
||||
/** @brief Global scalars have no geometric measure to remove. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename QuantityT>
|
||||
requires ProvidesScale<ScalesT, QuantityT>
|
||||
[[nodiscard]] auto coordinate_computation(DimensionlessRieszCoordinate<QuantityT, UnitMetric, ReferenceGeometry::algebraic>) {
|
||||
return coordinate_computation<Kind, CoordinateT, LayoutT, ScalesT>(PhysicalRieszCoordinate<QuantityT, UnitMetric>{});
|
||||
}
|
||||
|
||||
/** @brief Cancel the reference measure while retaining the native finite-element coordinate representation. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename QuantityT, typename MetricT, ReferenceGeometry Geometry>
|
||||
requires (!std::same_as<MetricT, UnitMetric> && ProvidesScale<ScalesT, QuantityT> && ProvidesScale<ScalesT, dimensions::Length>)
|
||||
[[nodiscard]] auto coordinate_computation(DimensionlessRieszCoordinate<QuantityT, MetricT, Geometry>) {
|
||||
using namespace utils::dependencies;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout, keys::Scales, MetricT>>(
|
||||
[](const LayoutT& layout, const ScalesT& scales, const std::vector<double>& metric) -> std::expected<std::vector<double>, std::string> {
|
||||
const auto valid = check_coordinate_size<Kind, CoordinateT>(layout, metric.size());
|
||||
if (!valid) {
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
const auto scale = dimensionless_riesz_scale(Kind, Geometry, characteristic_scale(scales, QuantityT{}).value(), characteristic_scale(scales, dimensions::Length{}).value());
|
||||
if (!scale) {
|
||||
return std::unexpected(scale.error());
|
||||
}
|
||||
return make_riesz_factors(Kind, metric, *scale);
|
||||
});
|
||||
}
|
||||
|
||||
/** @brief Remove geometric units only from weak rows, leaving each strong point equation in its own physical units. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename QuantityT, typename MetricT, ReferenceGeometry Geometry, typename PointQuantityT, typename SelectionT>
|
||||
requires (Kind == discretization::blocks::RepresentedValueKind::residual && !std::same_as<MetricT, UnitMetric> && ProvidesScale<ScalesT, QuantityT> && ProvidesScale<ScalesT, PointQuantityT> && ProvidesScale<ScalesT, dimensions::Length>)
|
||||
[[nodiscard]] auto coordinate_computation(WithPointRows<DimensionlessRieszCoordinate<QuantityT, MetricT, Geometry>, PointQuantityT, SelectionT>) {
|
||||
using namespace utils::dependencies;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout, keys::Scales, MetricT, SelectionT>>(
|
||||
[](const LayoutT& layout, const ScalesT& scales, const std::vector<double>& metric, const std::vector<int>& rows) -> std::expected<std::vector<double>, std::string> {
|
||||
const auto valid = check_coordinate_size<Kind, CoordinateT>(layout, metric.size());
|
||||
if (!valid) {
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
const auto scale = dimensionless_riesz_scale(Kind, Geometry, characteristic_scale(scales, QuantityT{}).value(), characteristic_scale(scales, dimensions::Length{}).value());
|
||||
if (!scale) {
|
||||
return std::unexpected(scale.error());
|
||||
}
|
||||
return make_point_replaced_residual_factors(metric, *scale, rows, characteristic_scale(scales, PointQuantityT{}).value());
|
||||
});
|
||||
}
|
||||
|
||||
/** @brief Invoke only the exact declared prescription, supplied as a tracked source rather than a hidden mutable capture. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT, typename LayoutT, typename ScalesT, typename PrescriptionT>
|
||||
requires requires(const std::remove_cvref_t<PrescriptionT>& prescription, std::size_t size)
|
||||
{
|
||||
{ prescription.prepare_factors(keys::Factors<Kind, CoordinateT>{}, size) } -> std::same_as<std::expected<std::vector<double>, std::string>>;
|
||||
}
|
||||
[[nodiscard]] auto coordinate_computation(RuntimePreparedCoordinate<PrescriptionT>)
|
||||
{
|
||||
using namespace utils::dependencies;
|
||||
using Prescription = std::remove_cvref_t<PrescriptionT>;
|
||||
|
||||
return make_computation<keys::Factors<Kind, CoordinateT>, InputList<keys::Layout, keys::Prescription<Prescription>>>(
|
||||
[]<typename SuppliedPrescription>(const LayoutT& layout, const SuppliedPrescription& prescription) -> std::expected<std::vector<double>, std::string>
|
||||
requires std::same_as<std::remove_cvref_t<SuppliedPrescription>, Prescription>
|
||||
{
|
||||
const auto size = coordinate_size<Kind, CoordinateT>(layout);
|
||||
if (!size)
|
||||
{
|
||||
return std::unexpected(size.error());
|
||||
}
|
||||
auto factors = prescription.prepare_factors(keys::Factors<Kind, CoordinateT>{}, *size);
|
||||
if (!factors)
|
||||
{
|
||||
return factors;
|
||||
}
|
||||
const auto valid_size = check_coordinate_size(*size, factors->size());
|
||||
if (!valid_size)
|
||||
{
|
||||
return std::unexpected(valid_size.error());
|
||||
}
|
||||
const auto valid_factors = validate_factors(*factors);
|
||||
if (!valid_factors)
|
||||
{
|
||||
return std::unexpected(valid_factors.error());
|
||||
}
|
||||
return factors;
|
||||
});
|
||||
}
|
||||
}
|
||||
13
src/include/serif/solver/normalization/detail/packing.hpp
Normal file
13
src/include/serif/solver/normalization/detail/packing.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "serif/solver/normalization/diagonal.hpp"
|
||||
|
||||
namespace serif::solver::normalization::detail
|
||||
{
|
||||
/** @brief Freeze independently ordered state and residual blocks into their packed solver coordinates. */
|
||||
[[nodiscard]] std::expected<DiagonalNormalization, std::string> pack_normalization(std::span<const std::span<const double>> states, std::span<const std::span<const double>> residuals);
|
||||
}
|
||||
18
src/include/serif/solver/normalization/detail/validation.hpp
Normal file
18
src/include/serif/solver/normalization/detail/validation.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <expected>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace serif::solver::normalization::detail
|
||||
{
|
||||
/** @brief Require finite positive factors before accepting custom or packed normalization. */
|
||||
[[nodiscard]] std::expected<void, std::string> validate_factors(std::span<const double> factors);
|
||||
|
||||
/** @brief Check representable coordinate storage and the fixed arity of global scalar terms. */
|
||||
[[nodiscard]] std::expected<std::size_t, std::string> validate_coordinate_size(std::size_t size, bool global_scalar);
|
||||
|
||||
/** @brief Compare metric or factor size with the independently declared coordinate layout. */
|
||||
[[nodiscard]] std::expected<void, std::string> check_coordinate_size(std::size_t expected, std::size_t received);
|
||||
}
|
||||
86
src/include/serif/solver/normalization/diagonal.hpp
Normal file
86
src/include/serif/solver/normalization/diagonal.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <expected>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "serif/discretization/blocks/base.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
/** @brief Require matching coordinate sizes before any output is modified. */
|
||||
void require_normalization_size(std::size_t received, std::size_t expected);
|
||||
|
||||
/** @brief Apply a diagonal in place or to disjoint storage, rejecting partial overlap. */
|
||||
void apply_normalization(std::span<const double> factors, std::span<const double> input, std::span<double> output, bool inverse);
|
||||
}
|
||||
|
||||
/** @brief Prepare one primal sqrt(metric)/scale or dual 1/(scale sqrt(metric)) factor. */
|
||||
[[nodiscard]] std::expected<double, std::string> make_riesz_factor(discretization::blocks::RepresentedValueKind kind, double metric, double physical_scale);
|
||||
|
||||
/** @brief Prepare diagonal Riesz factors, reporting which coordinate has invalid data. */
|
||||
[[nodiscard]] std::expected<std::vector<double>, std::string> make_riesz_factors(discretization::blocks::RepresentedValueKind kind, std::span<const double> metric, double physical_scale);
|
||||
|
||||
/** @brief Own the frozen, independent state and residual transformations used by one solve. */
|
||||
class DiagonalNormalization final
|
||||
{
|
||||
public:
|
||||
/** @brief Read the factors taking physical state coefficients to normalized coordinates. */
|
||||
[[nodiscard]] std::span<const double> state_factors() const noexcept
|
||||
{
|
||||
return m_state_factors;
|
||||
}
|
||||
|
||||
/** @brief Read the factors taking physical residual coefficients to normalized coordinates. */
|
||||
[[nodiscard]] std::span<const double> residual_factors() const noexcept
|
||||
{
|
||||
return m_residual_factors;
|
||||
}
|
||||
|
||||
/** @brief Number of state coordinates. */
|
||||
[[nodiscard]] std::size_t state_size() const noexcept
|
||||
{
|
||||
return m_state_factors.size();
|
||||
}
|
||||
|
||||
/** @brief Number of residual coordinates, which need not equal the state size. */
|
||||
[[nodiscard]] std::size_t residual_size() const noexcept
|
||||
{
|
||||
return m_residual_factors.size();
|
||||
}
|
||||
|
||||
/** @brief Convert physical state coefficients to normalized coordinates. */
|
||||
void normalize_state(std::span<const double> physical, std::span<double> normalized) const;
|
||||
|
||||
/** @brief Recover physical state coefficients from normalized coordinates. */
|
||||
void denormalize_state(std::span<const double> normalized, std::span<double> physical) const;
|
||||
|
||||
/** @brief Convert physical residual coefficients to normalized coordinates. */
|
||||
void normalize_residual(std::span<const double> physical, std::span<double> normalized) const;
|
||||
|
||||
/** @brief Recover physical residual coefficients from normalized coordinates. */
|
||||
void denormalize_residual(std::span<const double> normalized, std::span<double> physical) const;
|
||||
|
||||
/** @brief Squared primal norm of a physical state vector. */
|
||||
[[nodiscard]] double state_norm_squared(std::span<const double> physical) const;
|
||||
|
||||
/** @brief Squared dual norm of a physical residual vector. */
|
||||
[[nodiscard]] double residual_norm_squared(std::span<const double> physical) const;
|
||||
|
||||
private:
|
||||
friend std::expected<DiagonalNormalization, std::string> make_diagonal_normalization(std::vector<double>, std::vector<double>);
|
||||
|
||||
/** @brief Accept factors only after the factory has validated them. */
|
||||
DiagonalNormalization(std::vector<double> state, std::vector<double> residual);
|
||||
|
||||
std::vector<double> m_state_factors;
|
||||
std::vector<double> m_residual_factors;
|
||||
};
|
||||
|
||||
/** @brief Validate and freeze independent coordinate factors, including empty or rectangular systems. */
|
||||
[[nodiscard]] std::expected<DiagonalNormalization, std::string> make_diagonal_normalization(std::vector<double> state, std::vector<double> residual);
|
||||
}
|
||||
43
src/include/serif/solver/normalization/dimensionless.hpp
Normal file
43
src/include/serif/solver/normalization/dimensionless.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
#include "serif/solver/normalization/metrics.hpp"
|
||||
|
||||
namespace serif::solver::normalization {
|
||||
/** @brief Identify the geometric convention of a reference coordinate metric. */
|
||||
enum class ReferenceGeometry {
|
||||
volume,
|
||||
surface,
|
||||
algebraic
|
||||
};
|
||||
|
||||
/** @brief Require custom metrics to declare their geometric convention explicitly. */
|
||||
template <typename MetricT>
|
||||
struct DimensionlessMetricGeometry;
|
||||
|
||||
template <discretization::blocks::SpatialTerm TermT>
|
||||
struct DimensionlessMetricGeometry<ReferenceVolumeMetric<TermT>> {
|
||||
static constexpr ReferenceGeometry value = ReferenceGeometry::volume;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DimensionlessMetricGeometry<geometry::deformation::keys::SurfaceMetric> {
|
||||
static constexpr ReferenceGeometry value = ReferenceGeometry::surface;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DimensionlessMetricGeometry<UnitMetric> {
|
||||
static constexpr ReferenceGeometry value = ReferenceGeometry::algebraic;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* @brief Include reference geometry in the scale consumed by the existing Riesz factor calculation.
|
||||
* @note Volume residual quantities are densities before weak integration; surface residual quantities are integrated forces.
|
||||
*/
|
||||
[[nodiscard]] std::expected<double, std::string> dimensionless_riesz_scale(
|
||||
discretization::blocks::RepresentedValueKind kind, ReferenceGeometry geometry, double physical_scale, double reference_radius);
|
||||
}
|
||||
}
|
||||
37
src/include/serif/solver/normalization/keys.hpp
Normal file
37
src/include/serif/solver/normalization/keys.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/discretization/blocks/base.hpp"
|
||||
#include "serif/discretization/blocks/keys.hpp"
|
||||
|
||||
namespace serif::solver::normalization::keys
|
||||
{
|
||||
/** @brief Normalization consumes the same coordinate layout as every other numerical service. */
|
||||
using Layout = discretization::blocks::keys::Layout;
|
||||
|
||||
struct Scales
|
||||
{
|
||||
};
|
||||
|
||||
struct Normalization
|
||||
{
|
||||
};
|
||||
|
||||
/** @brief Distinguish state and residual factors even when they share the same physical term. */
|
||||
template <discretization::blocks::RepresentedValueKind Kind, typename CoordinateT>
|
||||
struct Factors
|
||||
{
|
||||
using Coordinate = CoordinateT;
|
||||
static constexpr discretization::blocks::RepresentedValueKind kind = Kind;
|
||||
};
|
||||
|
||||
/** @brief Bind the exact custom prescription named by a runtime-prepared coordinate. */
|
||||
template <typename PrescriptionT>
|
||||
struct Prescription
|
||||
{
|
||||
};
|
||||
|
||||
template <typename TermT>
|
||||
struct ReferenceBinding
|
||||
{
|
||||
};
|
||||
}
|
||||
14
src/include/serif/solver/normalization/layout.hpp
Normal file
14
src/include/serif/solver/normalization/layout.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/discretization/blocks/system_layout.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
/** @brief Compatibility spelling for the shared discretization layout. */
|
||||
template <utils::dependencies::IsKeyList Values, utils::dependencies::IsKeyList Residuals>
|
||||
using CoordinateLayout = discretization::blocks::SystemLayout<Values, Residuals>;
|
||||
|
||||
/** @brief Normalization consumes the same coordinate representation as physical operators and solvers. */
|
||||
template <typename Candidate>
|
||||
concept CoordinateLayoutType = discretization::blocks::SystemLayoutType<Candidate>;
|
||||
}
|
||||
85
src/include/serif/solver/normalization/metrics.hpp
Normal file
85
src/include/serif/solver/normalization/metrics.hpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
#include "serif/utils/dependencies/key_list_operations.hpp"
|
||||
|
||||
namespace serif::geometry::deformation::keys
|
||||
{
|
||||
struct SurfaceMetric;
|
||||
}
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
/** @brief A unit Gram metric for algebraic coordinates, still subject to physical scaling. */
|
||||
struct UnitMetric final
|
||||
{
|
||||
};
|
||||
|
||||
/** @brief Identify a reference volume metric by its physical term, including its support and FE representation. */
|
||||
template <discretization::blocks::SpatialTerm TermT>
|
||||
struct ReferenceVolumeMetric final
|
||||
{
|
||||
using Coordinates = utils::dependencies::KeyList<TermT>;
|
||||
};
|
||||
|
||||
/** @brief Metric keys advertise the coordinate identities they measure; a same-sized unrelated metric is insufficient. */
|
||||
template <typename MetricT, typename = void>
|
||||
struct MetricCoordinates
|
||||
{
|
||||
using Type = utils::dependencies::KeyList<>;
|
||||
};
|
||||
|
||||
template <typename MetricT>
|
||||
struct MetricCoordinates<MetricT, std::void_t<typename MetricT::Coordinates>>
|
||||
{
|
||||
using Type = typename MetricT::Coordinates;
|
||||
};
|
||||
|
||||
/** @brief Permit a Euclidean norm of algebraic surface moments after their measure is made dimensionless. */
|
||||
template <>
|
||||
struct MetricCoordinates<UnitMetric>
|
||||
{
|
||||
using Type = utils::dependencies::KeyList<discretization::blocks::surface_deformation::shape_equilibrium>;
|
||||
};
|
||||
|
||||
/** @brief Consume deformation's existing metric directly in both native shape-coordinate roles. */
|
||||
template <>
|
||||
struct MetricCoordinates<geometry::deformation::keys::SurfaceMetric>
|
||||
{
|
||||
using Type = utils::dependencies::KeyList<discretization::blocks::surface_deformation::coefficients, discretization::blocks::surface_deformation::shape_equilibrium>;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/** @brief Resolve assembled row identity without confusing a point-border row with its sampled field. */
|
||||
template <typename CoordinateT>
|
||||
[[nodiscard]] consteval auto metric_term()
|
||||
{
|
||||
if constexpr (requires
|
||||
{
|
||||
typename CoordinateT::Border;
|
||||
})
|
||||
{
|
||||
return std::type_identity<typename CoordinateT::Border>{};
|
||||
}
|
||||
else if constexpr (requires
|
||||
{
|
||||
typename CoordinateT::Test;
|
||||
})
|
||||
{
|
||||
return std::type_identity<typename CoordinateT::Test>{};
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::type_identity<CoordinateT>{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename MetricT, typename CoordinateT>
|
||||
concept MetricFor = (std::same_as<MetricT, UnitMetric> && discretization::blocks::GlobalScalarTerm<typename decltype(detail::metric_term<CoordinateT>())::type>) ||
|
||||
utils::dependencies::ContainsKey<typename decltype(detail::metric_term<CoordinateT>())::type, typename MetricCoordinates<MetricT>::Type>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/solver/normalization/plan.hpp"
|
||||
#include "serif/solver/normalization/physical_coordinates.hpp"
|
||||
#include "serif/solver/normalization/layout.hpp"
|
||||
#include "serif/solver/normalization/scales.hpp"
|
||||
#include "serif/solver/normalization/diagonal.hpp"
|
||||
#include "serif/solver/normalization/computations.hpp"
|
||||
|
||||
#include "serif/solver/normalization/configuration.hpp"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/solver/normalization/plan.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
/** @brief Select ordinary FE or algebraic metrics from the existing physical representation. */
|
||||
template <typename CoordinateT>
|
||||
[[nodiscard]] consteval auto default_metric()
|
||||
{
|
||||
using Term = typename decltype(metric_term<CoordinateT>())::type;
|
||||
if constexpr (discretization::blocks::SpatialTerm<Term>)
|
||||
{
|
||||
return std::type_identity<ReferenceVolumeMetric<Term>>{};
|
||||
}
|
||||
else if constexpr (discretization::blocks::GlobalScalarTerm<Term>)
|
||||
{
|
||||
return std::type_identity<UnitMetric>{};
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::type_identity<void>{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Describe an unknown from its existing quantity, support, and functional-space metadata. */
|
||||
template <discretization::blocks::HasQuantity TermT>
|
||||
requires (!std::same_as<typename decltype(detail::default_metric<TermT>())::type, void>)
|
||||
using PhysicalValueRule = utils::dependencies::Binding<TermT, PhysicalRieszCoordinate<typename TermT::Quantity, typename decltype(detail::default_metric<TermT>())::type>>;
|
||||
|
||||
/** @brief Describe a residual using the equation's declared quantity, which may differ from the unknown it closes. */
|
||||
template <typename EquationT>
|
||||
requires requires
|
||||
{
|
||||
typename EquationT::ResidualQuantity;
|
||||
} && (!std::same_as<typename decltype(detail::default_metric<EquationT>())::type, void>)
|
||||
using PhysicalResidualRule = utils::dependencies::Binding<EquationT, PhysicalRieszCoordinate<typename EquationT::ResidualQuantity, typename decltype(detail::default_metric<EquationT>())::type>>;
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/dimensions/quantities.hpp"
|
||||
#include "serif/discretization/blocks/base.hpp"
|
||||
#include "serif/solver/normalization/metrics.hpp"
|
||||
#include "serif/solver/normalization/dimensionless.hpp"
|
||||
#include "serif/utils/dependencies/binding.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
struct NormalizationPrescriptionTag
|
||||
{
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept NormalizationPrescription = std::derived_from<std::remove_cvref_t<Candidate>, NormalizationPrescriptionTag>;
|
||||
|
||||
/** @brief Leave the declared coordinates unchanged, including their physical units. */
|
||||
struct IdentityCoordinate final
|
||||
{
|
||||
};
|
||||
|
||||
/** @brief Measure a physical quantity with the named reference Gram diagonal. */
|
||||
template <dimensions::PhysicalQuantityType QuantityT, typename MetricKeyT>
|
||||
struct PhysicalRieszCoordinate final
|
||||
{
|
||||
using Quantity = QuantityT;
|
||||
using Metric = MetricKeyT;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Measure coordinates using dimensionless physical fields and reference geometry.
|
||||
* @note The surface convention applies to shape lengths and their integrated force residuals. Custom metrics must select their convention.
|
||||
*/
|
||||
template <dimensions::PhysicalQuantityType QuantityT, typename MetricKeyT, ReferenceGeometry Geometry = DimensionlessMetricGeometry<MetricKeyT>::value>
|
||||
struct DimensionlessRieszCoordinate final {
|
||||
using Quantity = QuantityT;
|
||||
using Metric = MetricKeyT;
|
||||
static constexpr ReferenceGeometry geometry = Geometry;
|
||||
};
|
||||
|
||||
/** @brief Replace selected volume residual rows by point residuals with their own physical quantity. */
|
||||
template <typename VolumeMethodT, dimensions::PhysicalQuantityType PointQuantityT, typename SelectionKeyT>
|
||||
struct WithPointRows final
|
||||
{
|
||||
using Selection = SelectionKeyT;
|
||||
};
|
||||
|
||||
/** @brief Identify the exact prescription supplying positive factors; this never claims identity scaling. */
|
||||
template <NormalizationPrescription PrescriptionT>
|
||||
struct RuntimePreparedCoordinate final
|
||||
{
|
||||
using Prescription = std::remove_cvref_t<PrescriptionT>;
|
||||
};
|
||||
|
||||
/** @brief Independent term-to-method and row-to-method maps; their order does not define the packed layout. */
|
||||
template <typename ValueRulesT, typename ResidualRulesT>
|
||||
struct NormalizationPlan final
|
||||
{
|
||||
using Values = ValueRulesT;
|
||||
using Residuals = ResidualRulesT;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <typename RulesT>
|
||||
struct RuleKeys;
|
||||
|
||||
template <typename... Rules>
|
||||
struct RuleKeys<utils::dependencies::Bindings<Rules...>>
|
||||
{
|
||||
using Type = utils::dependencies::KeyList<typename Rules::Key...>;
|
||||
};
|
||||
|
||||
/** @brief Compare memberships in both directions without deduplicating ownership declarations. */
|
||||
template <typename... Declared, typename... Required>
|
||||
[[nodiscard]] consteval bool exact_coordinates(utils::dependencies::KeyList<Declared...>, utils::dependencies::KeyList<Required...>)
|
||||
{
|
||||
return sizeof...(Declared) == sizeof...(Required) && (utils::dependencies::ContainsKey<Required, utils::dependencies::KeyList<Declared...>> && ...);
|
||||
}
|
||||
|
||||
/** @brief Check physics metadata when present; explicit low-level residual declarations remain their own authority. */
|
||||
template <typename KeyT, typename QuantityT, discretization::blocks::RepresentedValueKind Kind>
|
||||
[[nodiscard]] consteval bool quantity_matches()
|
||||
{
|
||||
if constexpr (Kind == discretization::blocks::RepresentedValueKind::value && requires
|
||||
{
|
||||
typename KeyT::Quantity;
|
||||
})
|
||||
{
|
||||
return std::same_as<QuantityT, typename KeyT::Quantity>;
|
||||
}
|
||||
else if constexpr (Kind == discretization::blocks::RepresentedValueKind::residual && requires
|
||||
{
|
||||
typename KeyT::ResidualQuantity;
|
||||
})
|
||||
{
|
||||
return std::same_as<QuantityT, typename KeyT::ResidualQuantity>;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename MethodT, typename KeyT, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct MethodSupports : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename KeyT, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct MethodSupports<IdentityCoordinate, KeyT, Kind> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename QuantityT, typename MetricT, typename KeyT, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct MethodSupports<PhysicalRieszCoordinate<QuantityT, MetricT>, KeyT, Kind> : std::bool_constant<MetricFor<MetricT, KeyT> && quantity_matches<KeyT, QuantityT, Kind>()>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename QuantityT, typename MetricT, typename PointQuantityT, typename SelectionT, typename KeyT>
|
||||
struct MethodSupports<WithPointRows<PhysicalRieszCoordinate<QuantityT, MetricT>, PointQuantityT, SelectionT>, KeyT, discretization::blocks::RepresentedValueKind::residual> : MethodSupports<PhysicalRieszCoordinate<QuantityT, MetricT>, KeyT, discretization::blocks::RepresentedValueKind::residual>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename QuantityT, typename MetricT, ReferenceGeometry Geometry, typename KeyT, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct MethodSupports<DimensionlessRieszCoordinate<QuantityT, MetricT, Geometry>, KeyT, Kind> : std::bool_constant<
|
||||
MetricFor<MetricT, KeyT> && quantity_matches<KeyT, QuantityT, Kind>() &&
|
||||
(Geometry == ReferenceGeometry::volume || Geometry == ReferenceGeometry::surface || Geometry == ReferenceGeometry::algebraic) &&
|
||||
(!std::same_as<MetricT, UnitMetric> || Geometry == ReferenceGeometry::algebraic)> {
|
||||
};
|
||||
|
||||
template <typename QuantityT, typename MetricT, ReferenceGeometry Geometry, typename PointQuantityT, typename SelectionT, typename KeyT>
|
||||
struct MethodSupports<WithPointRows<DimensionlessRieszCoordinate<QuantityT, MetricT, Geometry>, PointQuantityT, SelectionT>, KeyT, discretization::blocks::RepresentedValueKind::residual> :
|
||||
MethodSupports<DimensionlessRieszCoordinate<QuantityT, MetricT, Geometry>, KeyT, discretization::blocks::RepresentedValueKind::residual> {
|
||||
};
|
||||
|
||||
template <typename PrescriptionT, typename KeyT, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct MethodSupports<RuntimePreparedCoordinate<PrescriptionT>, KeyT, Kind> : std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename RulesT, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct ValidRules : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename... Rules, discretization::blocks::RepresentedValueKind Kind>
|
||||
struct ValidRules<utils::dependencies::Bindings<Rules...>, Kind> : std::bool_constant<utils::dependencies::IsBindings<utils::dependencies::Bindings<Rules...>> && (MethodSupports<typename Rules::Target, typename Rules::Key, Kind>::value && ...)>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
inline constexpr bool valid_plan = false;
|
||||
|
||||
template <typename ValuesT, typename ResidualsT>
|
||||
inline constexpr bool valid_plan<NormalizationPlan<ValuesT, ResidualsT>> = ValidRules<ValuesT, discretization::blocks::RepresentedValueKind::value>::value && ValidRules<ResidualsT, discretization::blocks::RepresentedValueKind::residual>::value;
|
||||
}
|
||||
|
||||
template <typename Candidate>
|
||||
concept NormalizationPlanType = detail::valid_plan<std::remove_cvref_t<Candidate>>;
|
||||
|
||||
template <typename PlanT, typename ValuesT, typename ResidualsT>
|
||||
concept CompleteNormalizationFor = NormalizationPlanType<PlanT> && utils::dependencies::IsKeyList<ValuesT> && utils::dependencies::IsKeyList<ResidualsT> &&
|
||||
detail::exact_coordinates(typename detail::RuleKeys<typename std::remove_cvref_t<PlanT>::Values>::Type{}, std::remove_cvref_t<ValuesT>{}) &&
|
||||
detail::exact_coordinates(typename detail::RuleKeys<typename std::remove_cvref_t<PlanT>::Residuals>::Type{}, std::remove_cvref_t<ResidualsT>{});
|
||||
}
|
||||
|
||||
15
src/include/serif/solver/normalization/point_rows.hpp
Normal file
15
src/include/serif/solver/normalization/point_rows.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
/**
|
||||
* @brief Measure volume and replacement point residuals in their independently specified physical units.
|
||||
* @note Only replaced rows may have a zero or otherwise unusable volume metric. No bulk factor is evaluated for them.
|
||||
*/
|
||||
[[nodiscard]] std::expected<std::vector<double>, std::string> make_point_replaced_residual_factors(std::span<const double> volume_metric, double volume_scale, std::span<const int> selected_rows, double point_scale);
|
||||
}
|
||||
115
src/include/serif/solver/normalization/scales.hpp
Normal file
115
src/include/serif/solver/normalization/scales.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
#include "serif/dimensions/dimensions.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
/** @brief Fixed reference scales used throughout a solve, independent of its candidate state. */
|
||||
struct StellarReferenceScales final
|
||||
{
|
||||
dimensions::QuantityValue<dimensions::Mass> mass;
|
||||
dimensions::QuantityValue<dimensions::Length> radius;
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant;
|
||||
dimensions::QuantityValue<dimensions::MassDensity> density;
|
||||
dimensions::QuantityValue<dimensions::Acceleration> acceleration;
|
||||
dimensions::QuantityValue<dimensions::InverseTimeSquared> inverse_time_squared;
|
||||
dimensions::QuantityValue<dimensions::SpecificEnergy> specific_energy;
|
||||
dimensions::QuantityValue<dimensions::Pressure> pressure;
|
||||
dimensions::QuantityValue<dimensions::AngularVelocity> angular_velocity;
|
||||
dimensions::QuantityValue<dimensions::AngularMomentum> angular_momentum;
|
||||
dimensions::QuantityValue<dimensions::Force> force;
|
||||
|
||||
/** @brief Compare reference values for dependency snapshot invalidation. */
|
||||
friend bool operator==(const StellarReferenceScales &, const StellarReferenceScales &) = default;
|
||||
};
|
||||
|
||||
/** @brief Derive gravitational stellar scales from a finite, positive reference mass, radius, and G. */
|
||||
[[nodiscard]] std::expected<StellarReferenceScales, std::string> prepare_stellar_reference_scales(
|
||||
dimensions::QuantityValue<dimensions::Mass> mass,
|
||||
dimensions::QuantityValue<dimensions::Length> radius,
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant = dimensions::QuantityValue<dimensions::GravitationalConstant>{1.0});
|
||||
|
||||
/** @brief Quantities whose characteristic scales follow from reference mass, radius, and G alone. */
|
||||
template <typename Quantity>
|
||||
concept StellarScaleQuantity = std::same_as<Quantity, dimensions::Dimensionless> || std::same_as<Quantity, dimensions::Mass> ||
|
||||
std::same_as<Quantity, dimensions::Length> || std::same_as<Quantity, dimensions::MassDensity> ||
|
||||
std::same_as<Quantity, dimensions::Acceleration> || std::same_as<Quantity, dimensions::InverseTimeSquared> ||
|
||||
std::same_as<Quantity, dimensions::SpecificEnergy> || std::same_as<Quantity, dimensions::SpecificEnthalpy> ||
|
||||
std::same_as<Quantity, dimensions::BarotropicPressurePotential> ||
|
||||
std::same_as<Quantity, dimensions::GravitationalPotential> || std::same_as<Quantity, dimensions::Pressure> ||
|
||||
std::same_as<Quantity, dimensions::AngularVelocity> || std::same_as<Quantity, dimensions::AngularAcceleration> || std::same_as<Quantity, dimensions::AngularMomentum> ||
|
||||
std::same_as<Quantity, dimensions::Force> || std::same_as<Quantity, dimensions::PressureLaplacian> ||
|
||||
std::same_as<Quantity, dimensions::Velocity> || std::same_as<Quantity, dimensions::MassDensityRate> || std::same_as<Quantity, dimensions::ForceDensity>;
|
||||
|
||||
/** @brief Select a typed scale; temperature and other unrelated quantities require an explicit prescription. */
|
||||
template <StellarScaleQuantity Quantity>
|
||||
[[nodiscard]] dimensions::QuantityValue<Quantity> characteristic_scale(const StellarReferenceScales &scales, Quantity)
|
||||
{
|
||||
if constexpr (std::same_as<Quantity, dimensions::Dimensionless>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{1.0};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::Mass>)
|
||||
{
|
||||
return scales.mass;
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::Length>)
|
||||
{
|
||||
return scales.radius;
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::MassDensity>)
|
||||
{
|
||||
return scales.density;
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::Acceleration>)
|
||||
{
|
||||
return scales.acceleration;
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::InverseTimeSquared> || std::same_as<Quantity, dimensions::AngularAcceleration>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{scales.inverse_time_squared.value()};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::SpecificEnergy> || std::same_as<Quantity, dimensions::SpecificEnthalpy> ||
|
||||
std::same_as<Quantity, dimensions::BarotropicPressurePotential> || std::same_as<Quantity, dimensions::GravitationalPotential>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{scales.specific_energy.value()};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::Pressure>)
|
||||
{
|
||||
return scales.pressure;
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::Velocity>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{std::sqrt(scales.specific_energy.value())};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::MassDensityRate>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{scales.density.value() * scales.angular_velocity.value()};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::ForceDensity>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{scales.density.value() * scales.acceleration.value()};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::PressureLaplacian>)
|
||||
{
|
||||
return dimensions::QuantityValue<Quantity>{scales.pressure.value() / (scales.radius.value() * scales.radius.value())};
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::AngularVelocity>)
|
||||
{
|
||||
return scales.angular_velocity;
|
||||
}
|
||||
else if constexpr (std::same_as<Quantity, dimensions::AngularMomentum>)
|
||||
{
|
||||
return scales.angular_momentum;
|
||||
}
|
||||
else
|
||||
{
|
||||
return scales.force;
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/lib/serif/solver/normalization/diagonal.cpp
Normal file
89
src/lib/serif/solver/normalization/diagonal.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "serif/solver/normalization/diagonal.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/solver/normalization/detail/validation.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
namespace {
|
||||
/** @brief Measure a physical vector in its prepared diagonal metric. */
|
||||
[[nodiscard]] double normalization_norm_squared(std::span<const double> factors, std::span<const double> physical) {
|
||||
detail::require_normalization_size(physical.size(), factors.size());
|
||||
double norm_squared = 0.0;
|
||||
for (std::size_t index = 0; index < physical.size(); ++index)
|
||||
{
|
||||
const double normalized = factors[index] * physical[index];
|
||||
norm_squared += normalized * normalized;
|
||||
}
|
||||
return norm_squared;
|
||||
}
|
||||
}
|
||||
|
||||
void detail::require_normalization_size(std::size_t received, std::size_t expected) {
|
||||
using namespace utils::errors;
|
||||
|
||||
serif_ensure<SERiFErrorCode::NORMALIZATION_VECTOR_SIZE_MISMATCH, std::invalid_argument>(received == expected, "Normalization received a vector of length {} but expected {}. Supply a vector in the prepared coordinate layout.", received, expected);
|
||||
}
|
||||
|
||||
void detail::apply_normalization(const std::span<const double> factors, const std::span<const double> input, std::span<double> output, const bool inverse)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
require_normalization_size(input.size(), factors.size());
|
||||
require_normalization_size(output.size(), factors.size());
|
||||
if (input.data() != output.data() && !input.empty())
|
||||
{
|
||||
const std::less<const double*> before;
|
||||
const bool input_ends_before_output = !before(output.data(), input.data() + input.size());
|
||||
const bool output_ends_before_input = !before(input.data(), output.data() + output.size());
|
||||
serif_ensure<SERiFErrorCode::NORMALIZATION_BUFFER_OVERLAP, std::invalid_argument>(input_ends_before_output || output_ends_before_input, "Normalization input and output overlap partially. Use the same span for an in-place conversion or provide disjoint storage.");
|
||||
}
|
||||
for (std::size_t index = 0; index < input.size(); ++index)
|
||||
{
|
||||
output[index] = inverse ? input[index] / factors[index] : factors[index] * input[index];
|
||||
}
|
||||
}
|
||||
|
||||
DiagonalNormalization::DiagonalNormalization(std::vector<double> state, std::vector<double> residual) : m_state_factors(std::move(state)), m_residual_factors(std::move(residual)) {}
|
||||
|
||||
void DiagonalNormalization::normalize_state(const std::span<const double> physical, const std::span<double> normalized) const {
|
||||
detail::apply_normalization(m_state_factors, physical, normalized, false);
|
||||
}
|
||||
|
||||
void DiagonalNormalization::denormalize_state(const std::span<const double> normalized, const std::span<double> physical) const {
|
||||
detail::apply_normalization(m_state_factors, normalized, physical, true);
|
||||
}
|
||||
|
||||
void DiagonalNormalization::normalize_residual(const std::span<const double> physical, const std::span<double> normalized) const {
|
||||
|
||||
detail::apply_normalization(m_residual_factors, physical, normalized, false);
|
||||
}
|
||||
|
||||
void DiagonalNormalization::denormalize_residual(const std::span<const double> normalized, const std::span<double> physical) const {
|
||||
detail::apply_normalization(m_residual_factors, normalized, physical, true);
|
||||
}
|
||||
|
||||
double DiagonalNormalization::state_norm_squared(const std::span<const double> physical) const {
|
||||
return normalization_norm_squared(m_state_factors, physical);
|
||||
}
|
||||
|
||||
double DiagonalNormalization::residual_norm_squared(const std::span<const double> physical) const {
|
||||
return normalization_norm_squared(m_residual_factors, physical);
|
||||
}
|
||||
|
||||
std::expected<DiagonalNormalization, std::string> make_diagonal_normalization(std::vector<double> state, std::vector<double> residual) {
|
||||
for (const std::span<const double> factors : {std::span<const double>{state}, std::span<const double>{residual}})
|
||||
{
|
||||
const auto valid = detail::validate_factors(factors);
|
||||
if (!valid)
|
||||
{
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
}
|
||||
return DiagonalNormalization{std::move(state), std::move(residual)};
|
||||
}
|
||||
}
|
||||
46
src/lib/serif/solver/normalization/dimensionless.cpp
Normal file
46
src/lib/serif/solver/normalization/dimensionless.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "serif/solver/normalization/dimensionless.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver::normalization::detail {
|
||||
std::expected<double, std::string> dimensionless_riesz_scale(
|
||||
discretization::blocks::RepresentedValueKind kind, ReferenceGeometry geometry, double physical_scale, double reference_radius) {
|
||||
using namespace utils::errors;
|
||||
using Kind = discretization::blocks::RepresentedValueKind;
|
||||
|
||||
if (!std::isfinite(physical_scale) || physical_scale <= 0.0 || !std::isfinite(reference_radius) || reference_radius <= 0.0) {
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>(
|
||||
"Dimensionless normalization needs finite positive physical and radius scales (received {} and {}). Choose a valid reference star.", physical_scale, reference_radius));
|
||||
}
|
||||
if (kind != Kind::value && kind != Kind::residual) {
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::INVALID_ARGUMENT>(
|
||||
"Dimensionless normalization requires a value or residual coordinate kind. Supply a supported RepresentedValueKind."));
|
||||
}
|
||||
|
||||
long double scale = physical_scale;
|
||||
switch (geometry) {
|
||||
case ReferenceGeometry::volume:
|
||||
// Raw volume Gram matrices retain the physical basis, including the RT Piola transform.
|
||||
scale *= static_cast<long double>(reference_radius) * std::sqrt(static_cast<long double>(reference_radius));
|
||||
break;
|
||||
case ReferenceGeometry::surface:
|
||||
// Shape values are lengths; their dual coordinates are integrated forces, not force per area.
|
||||
scale *= kind == Kind::value ? static_cast<long double>(reference_radius) : 1.0L / reference_radius;
|
||||
break;
|
||||
case ReferenceGeometry::algebraic:
|
||||
break;
|
||||
default:
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::INVALID_ARGUMENT>(
|
||||
"Dimensionless normalization received an unknown reference geometry. Select volume, surface, or algebraic coordinates."));
|
||||
}
|
||||
const double result = static_cast<double>(scale);
|
||||
if (!std::isfinite(result) || result <= 0.0) {
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>(
|
||||
"The dimensionless normalization scale is not representable as a finite positive double. Change the reference units."));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
52
src/lib/serif/solver/normalization/packing.cpp
Normal file
52
src/lib/serif/solver/normalization/packing.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "serif/solver/normalization/detail/packing.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver::normalization::detail
|
||||
{
|
||||
namespace
|
||||
{
|
||||
/** @brief Preserve the declared block order while checking the total before allocating packed storage. */
|
||||
[[nodiscard]] std::expected<std::vector<double>, std::string> pack_factors(std::span<const std::span<const double>> coordinates)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
std::size_t count = 0;
|
||||
for (const std::span<const double> factors : coordinates)
|
||||
{
|
||||
if (factors.size() > std::vector<double>{}.max_size() - count)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::INVALID_DOF_COUNT>("The packed normalization layout exceeds the supported vector size. Reduce its coordinate counts."));
|
||||
}
|
||||
count += factors.size();
|
||||
}
|
||||
|
||||
std::vector<double> packed;
|
||||
packed.reserve(count);
|
||||
for (const std::span<const double> factors : coordinates)
|
||||
{
|
||||
packed.insert(packed.end(), factors.begin(), factors.end());
|
||||
}
|
||||
return packed;
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<DiagonalNormalization, std::string> pack_normalization(std::span<const std::span<const double>> states, std::span<const std::span<const double>> residuals)
|
||||
{
|
||||
auto state_factors = pack_factors(states);
|
||||
if (!state_factors)
|
||||
{
|
||||
return std::unexpected(state_factors.error());
|
||||
}
|
||||
auto residual_factors = pack_factors(residuals);
|
||||
if (!residual_factors)
|
||||
{
|
||||
return std::unexpected(residual_factors.error());
|
||||
}
|
||||
return make_diagonal_normalization(std::move(*state_factors), std::move(*residual_factors));
|
||||
}
|
||||
}
|
||||
135
src/lib/serif/solver/normalization/riesz_factors.cpp
Normal file
135
src/lib/serif/solver/normalization/riesz_factors.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "serif/solver/normalization/point_rows.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "serif/solver/normalization/diagonal.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using Kind = discretization::blocks::RepresentedValueKind;
|
||||
|
||||
/** @brief Validate units independently of whether any coordinate uses their reciprocal. */
|
||||
[[nodiscard]] std::expected<void, std::string> validate_scale(double physical_scale)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
if (!std::isfinite(physical_scale) || physical_scale <= 0.0)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>("A physical normalization scale must be finite and positive (received {}). Choose a valid reference scale.", physical_scale));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** @brief Validate coordinate-wide settings once before scalar or bulk Riesz preparation. */
|
||||
[[nodiscard]] std::expected<void, std::string> validate_riesz_parameters(Kind kind, double physical_scale)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
const auto valid_scale = validate_scale(physical_scale);
|
||||
if (!valid_scale)
|
||||
{
|
||||
return valid_scale;
|
||||
}
|
||||
if (kind != Kind::value && kind != Kind::residual)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::INVALID_ARGUMENT>("Riesz factors require a value or residual coordinate kind. Supply a supported RepresentedValueKind."));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/** @brief Compute one metric-dependent factor after the coordinate's role and physical scale are validated. */
|
||||
[[nodiscard]] std::expected<double, std::string> riesz_factor(bool dual, double metric, double physical_scale, std::size_t coordinate)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
if (!std::isfinite(metric) || metric <= 0.0)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>("Normalization metric entry {} must be finite and positive (received {}). Use a valid reference Gram diagonal.", coordinate, metric));
|
||||
}
|
||||
const double root_metric = std::sqrt(metric);
|
||||
// Inverting the root first avoids overflow of scale * root_metric when its reciprocal is representable.
|
||||
const double factor = dual ? (1.0 / root_metric) / physical_scale : root_metric / physical_scale;
|
||||
if (!std::isfinite(factor) || factor <= 0.0)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>("Normalization factor {} is not representable as a finite positive double for metric {} and scale {}. Change the reference units.", coordinate, metric, physical_scale));
|
||||
}
|
||||
return factor;
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<double, std::string> make_riesz_factor(Kind kind, double metric, double physical_scale)
|
||||
{
|
||||
const auto valid = validate_riesz_parameters(kind, physical_scale);
|
||||
if (!valid)
|
||||
{
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
return riesz_factor(kind == Kind::residual, metric, physical_scale, 0);
|
||||
}
|
||||
|
||||
std::expected<std::vector<double>, std::string> make_riesz_factors(Kind kind, std::span<const double> metric, double physical_scale)
|
||||
{
|
||||
const auto valid = validate_riesz_parameters(kind, physical_scale);
|
||||
if (!valid)
|
||||
{
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
const bool dual = kind == Kind::residual;
|
||||
std::vector<double> factors(metric.size());
|
||||
for (std::size_t index = 0; index < metric.size(); ++index)
|
||||
{
|
||||
const auto factor = riesz_factor(dual, metric[index], physical_scale, index);
|
||||
if (!factor)
|
||||
{
|
||||
return std::unexpected(factor.error());
|
||||
}
|
||||
factors[index] = *factor;
|
||||
}
|
||||
return factors;
|
||||
}
|
||||
|
||||
std::expected<std::vector<double>, std::string> make_point_replaced_residual_factors(std::span<const double> volume_metric, double volume_scale, std::span<const int> selected_rows, double point_scale)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
for (const double scale : {volume_scale, point_scale})
|
||||
{
|
||||
const auto valid = validate_scale(scale);
|
||||
if (!valid)
|
||||
{
|
||||
return std::unexpected(valid.error());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<bool> replaced(volume_metric.size(), false);
|
||||
for (const int row : selected_rows)
|
||||
{
|
||||
if (row < 0 || static_cast<std::size_t>(row) >= volume_metric.size())
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::OUT_OF_RANGE_ERROR>("Replacement normalization row {} lies outside its residual coordinate of size {}. Use the physical equation's reduced row selection.", row, volume_metric.size()));
|
||||
}
|
||||
if (replaced[row])
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::INVALID_ARGUMENT>("Residual row {} was selected more than once for point normalization. Supply disjoint replacement rows.", row));
|
||||
}
|
||||
replaced[row] = true;
|
||||
}
|
||||
|
||||
std::vector<double> factors(volume_metric.size());
|
||||
for (std::size_t row = 0; row < factors.size(); ++row)
|
||||
{
|
||||
const double metric = replaced[row] ? 1.0 : volume_metric[row];
|
||||
const double scale = replaced[row] ? point_scale : volume_scale;
|
||||
const auto factor = riesz_factor(true, metric, scale, row);
|
||||
if (!factor)
|
||||
{
|
||||
return std::unexpected(factor.error());
|
||||
}
|
||||
factors[row] = *factor;
|
||||
}
|
||||
return factors;
|
||||
}
|
||||
}
|
||||
62
src/lib/serif/solver/normalization/scales.cpp
Normal file
62
src/lib/serif/solver/normalization/scales.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "serif/solver/normalization/scales.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver::normalization
|
||||
{
|
||||
/** @brief Derive gravitational stellar scales from a finite, positive reference mass, radius, and G. */
|
||||
std::expected<StellarReferenceScales, std::string> prepare_stellar_reference_scales(
|
||||
dimensions::QuantityValue<dimensions::Mass> mass,
|
||||
dimensions::QuantityValue<dimensions::Length> radius,
|
||||
dimensions::QuantityValue<dimensions::GravitationalConstant> gravitational_constant)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
for (const double value : {mass.value(), radius.value(), gravitational_constant.value()})
|
||||
{
|
||||
if (!std::isfinite(value) || value <= 0.0)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>("Stellar normalization requires finite, positive reference mass, radius, and gravitational constant. Correct the reference values."));
|
||||
}
|
||||
}
|
||||
|
||||
const double reference_mass = mass.value();
|
||||
const double reference_radius = radius.value();
|
||||
const double gravity = gravitational_constant.value();
|
||||
const double radius_squared = reference_radius * reference_radius;
|
||||
const double radius_cubed = radius_squared * reference_radius;
|
||||
const double density = reference_mass / radius_cubed;
|
||||
const double acceleration = gravity * reference_mass / radius_squared;
|
||||
const double inverse_time_squared = gravity * reference_mass / radius_cubed;
|
||||
const double specific_energy = gravity * reference_mass / reference_radius;
|
||||
const double pressure = density * specific_energy;
|
||||
const double angular_velocity = std::sqrt(inverse_time_squared);
|
||||
const double angular_momentum = reference_mass * std::sqrt(gravity * reference_mass * reference_radius);
|
||||
const double force = reference_mass * acceleration;
|
||||
|
||||
for (const double value : {density, acceleration, inverse_time_squared, specific_energy, pressure, angular_velocity, angular_momentum, force})
|
||||
{
|
||||
if (!std::isfinite(value) || value <= 0.0)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>("A characteristic stellar normalization scale is not finite and positive; choose reference units in which every derived scale is representable."));
|
||||
}
|
||||
}
|
||||
|
||||
return StellarReferenceScales{
|
||||
.mass = mass,
|
||||
.radius = radius,
|
||||
.gravitational_constant = gravitational_constant,
|
||||
.density = dimensions::QuantityValue<dimensions::MassDensity>{density},
|
||||
.acceleration = dimensions::QuantityValue<dimensions::Acceleration>{acceleration},
|
||||
.inverse_time_squared = dimensions::QuantityValue<dimensions::InverseTimeSquared>{inverse_time_squared},
|
||||
.specific_energy = dimensions::QuantityValue<dimensions::SpecificEnergy>{specific_energy},
|
||||
.pressure = dimensions::QuantityValue<dimensions::Pressure>{pressure},
|
||||
.angular_velocity = dimensions::QuantityValue<dimensions::AngularVelocity>{angular_velocity},
|
||||
.angular_momentum = dimensions::QuantityValue<dimensions::AngularMomentum>{angular_momentum},
|
||||
.force = dimensions::QuantityValue<dimensions::Force>{force}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
49
src/lib/serif/solver/normalization/validation.cpp
Normal file
49
src/lib/serif/solver/normalization/validation.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "serif/solver/normalization/detail/validation.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::solver::normalization::detail
|
||||
{
|
||||
std::expected<void, std::string> validate_factors(std::span<const double> factors)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
for (std::size_t index = 0; index < factors.size(); ++index)
|
||||
{
|
||||
if (!std::isfinite(factors[index]) || factors[index] <= 0.0)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_ERROR>("Normalization factor {} is not finite and positive (received {}). Supply finite positive factors for the declared coordinate.", index, factors[index]));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<std::size_t, std::string> validate_coordinate_size(std::size_t size, bool global_scalar)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
if (size > std::vector<double>{}.max_size())
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_VECTOR_SIZE_MISMATCH>("A normalization coordinate requests {} entries, exceeding supported vector storage. Reduce its state or residual layout count.", size));
|
||||
}
|
||||
if (global_scalar && size != 1)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_VECTOR_SIZE_MISMATCH>("A global scalar normalization coordinate requires one entry, but its layout declares {}. Correct its state or residual layout.", size));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
std::expected<void, std::string> check_coordinate_size(std::size_t expected, std::size_t received)
|
||||
{
|
||||
using namespace utils::errors;
|
||||
|
||||
if (expected != received)
|
||||
{
|
||||
return std::unexpected(format_error_message<SERiFErrorCode::NORMALIZATION_VECTOR_SIZE_MISMATCH>("Normalization received {} entries for a coordinate with {} entries. Use the metric and ordering of its reference representation.", received, expected));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user