Skip to main content

conspire/math/integrate/field/hermite/
mod.rs

1#[cfg(test)]
2mod test;
3
4use super::{Integrable, reconstruct_or_err};
5use crate::math::{Quantity, TensorVec, integrate::IntegrationError};
6use crate::units::Time;
7
8/// Cubic Hermite dense output over one accepted step, built in the field's Lie
9/// algebra rather than on the state itself.
10///
11/// The flat interpolant combines `y_{n}`, `y_{n+1}` and the two end rates
12/// affinely. That is meaningless for a group-valued state: the two states are
13/// different group elements and the two rates live in different tangent spaces,
14/// so the combination leaves the manifold (`det F_p` drifts) exactly the way the
15/// additive march did.
16///
17/// Anchor everything at the left endpoint instead. The step already produced
18/// `sigma` with `reconstruct(base, sigma) = y_{n+1}`, and the algebra is a flat
19/// vector space — so the Hermite polynomial is built *there*,
20///
21/// ```math
22/// \sigma(\theta) = h_{10}(\theta)\,\dot\sigma_0 + h_{01}(\theta)\,\sigma
23///                + h_{11}(\theta)\,\dot\sigma_1
24/// ,\qquad
25/// \mathbf{y}(\theta) = \mathrm{reconstruct}(\mathbf{y}_n, \sigma(\theta))
26/// ```
27///
28/// with `σ(0) = 0` and `σ(1) = sigma`, so both endpoints are reproduced exactly
29/// and every interior point is an `expm` of a trace-free element — on the group
30/// by construction. `slope_0` is the raw stage-0 slope (`dexpinv` at zero
31/// displacement is the identity); `slope_1` is the endpoint rate pulled back
32/// through [`Integrable::dexpinv`] at `sigma`, both already scaled by the
33/// step. On a [`super::Flat`] field `reconstruct` adds and `dexpinv` is the
34/// identity, and `h_{00} + h_{01} = 1` collapses this to the usual flat formula.
35pub struct HermiteSegment<Field: Integrable, T = Time> {
36    t_0: Quantity<T>,
37    h: Quantity<T>,
38    base: Field::Point,
39    sigma: Field::Increment,
40    slope_0: Field::Increment,
41    slope_1: Field::Increment,
42}
43
44impl<Field, T> HermiteSegment<Field, T>
45where
46    Field: Integrable,
47{
48    /// A segment of the accepted step `[t_0, t_0 + h]` from `base`, the algebra
49    /// displacement `sigma` over it, and the step-scaled algebra rates at its
50    /// two ends (`slope_1` already pulled back through
51    /// [`Integrable::dexpinv`] at `sigma`).
52    pub fn new(
53        t_0: Quantity<T>,
54        h: Quantity<T>,
55        base: Field::Point,
56        sigma: Field::Increment,
57        slope_0: Field::Increment,
58        slope_1: Field::Increment,
59    ) -> Self {
60        Self {
61            t_0,
62            h,
63            base,
64            sigma,
65            slope_0,
66            slope_1,
67        }
68    }
69    /// The state at `time`, reconstructed from the algebra Hermite polynomial.
70    pub fn evaluate(&self, time: Quantity<T>) -> Result<Field::Point, IntegrationError> {
71        let theta = (time - self.t_0).value() / self.h.value();
72        let theta_2 = theta * theta;
73        let theta_3 = theta_2 * theta;
74        let mut increment = self.slope_0.clone() * (theta_3 - 2.0 * theta_2 + theta);
75        increment += self.sigma.clone() * (3.0 * theta_2 - 2.0 * theta_3);
76        increment += self.slope_1.clone() * (theta_3 - theta_2);
77        reconstruct_or_err::<Field>(&self.base, &increment)
78    }
79}
80
81/// Evaluates `segments` at `time_k`, in the segment that contains it (the last
82/// one for a time past the final accepted step).
83pub(super) fn hermite_at<Field, T>(
84    segments: &[HermiteSegment<Field, T>],
85    time_k: Quantity<T>,
86) -> Result<Field::Point, IntegrationError>
87where
88    Field: Integrable,
89{
90    segments
91        .iter()
92        .find(|segment| time_k <= segment.t_0 + segment.h)
93        .unwrap_or(&segments[segments.len() - 1])
94        .evaluate(time_k)
95}
96
97/// Finds the containing segment and evaluates it there, over a whole grid of
98/// requested times.
99pub fn interpolate_hermite<Field, U, T>(
100    segments: &[HermiteSegment<Field, T>],
101    time: &[Quantity<T>],
102) -> Result<U, IntegrationError>
103where
104    Field: Integrable,
105    U: TensorVec<Item = Field::Point>,
106{
107    let mut points = U::new();
108    for time_k in time {
109        points.push(hermite_at::<Field, T>(segments, *time_k)?);
110    }
111    Ok(points)
112}