Motivation
Every generic type in ninterp today (Interp1D<T, S>, InterpData<T, N>,
Strategy1D<D>'s D::Elem, ...) uses one type parameter for both grid coordinates and
interpolated values, and that parameter is bounded Float nearly everywhere (see
src/strategy/cubic.rs, src/strategy/utils.rs, src/interpolator/mod.rs,
src/interpolator/enums.rs). That forces grid axes to be the same float type as the
function values, even when they conceptually aren't, e.g. an integer or otherwise
non-float grid axis paired with float-valued data.
Splitting into Tg (grid) and Tv (value) removes that coupling. This is a breaking
change; per this project's pre-1.0 convention it lands as a future 0.x -> 0.(x+1) bump,
not gated to any 1.0.
API changes
Every site that currently carries one T (or bounds D::Elem) needs to declare which
side of the split it's on:
InterpDataBase<D, N> (src/interpolator/data.rs): splits into two storage
parameters, grid: [ArrayBase<Dg, Ix1>; N] and values: ArrayBase<Dv, Dim<[Ix; N]>>.
Interp1D/2D/3D/ND<D, S> and their *Base/*View/owned type aliases: follow
InterpDataBase onto Dg/Dv.
Strategy1D/2D/3D/ND<D> (src/strategy/traits.rs): interpolate's point: &[Tg; N] (or &[Tg] for ND) is grid-side; its Result<Tv, InterpolateError> return is
value-side. (This stays Tg-typed only as long as Tg: Float; see Tp below for
when that stops holding.)
Extrapolate<T>::Fill(T) (src/interpolator/mod.rs): the held value is interpolation
output, so Fill(Tv), even though Interp1D<T, S> around it today reads as
grid-coordinate-typed.
InterpolateError/ValidateError: stay non-generic. (InterpolateError was already
kept non-generic in the 0.10 error rework partly for this; ValidateError would only
need to follow Extrapolate onto Tv if it ever went generic, which it isn't doing
here.)
Open questions
- Query-point type,
Tp. A query point falls between grid points, so once Tg
isn't Float, the point type can't just be Tg (an integer or date-like grid can't
represent a fractional position). It can't just be Tv either: an unsigned-int grid
image (Tg = pixel index, Tv = u8 grayscale) still wants high-precision
fractional interpolation with the result rounded/cast down to u8, so Tv isn't
float-shaped in general either. That points at Tp as a genuine third type
parameter, bounded Float and defaulting to f64 so the common case needs no extra
annotation, with numeric casts at each boundary: Tg -> Tp to compute fractional
position, Tp -> Tv (round/clamp) to produce the final value. interpolate's point
argument becomes Tp-typed once this lands, not Tg-typed. (Named Tp rather than
Tq/"query" to match this codebase's existing point parameter naming, and because
q reads as too visually similar to g next to Tg.)
- Internal float-only math, resolved by the above:
CubicC1/CubicC2's tridiagonal
solves and Hermite evaluation (thomas, compute_m, hermite_eval_1d, etc. in
src/strategy/cubic.rs) are T: Float today and stay float-like as Tp, rather than
inventing a separate type for them.
- Confirm no other cached strategy state (e.g. spline coefficient caches) is silently
grid-typed when it should be value/float-typed.
Non-goals for this issue
Not implementing Tp here. This issue is scoped to the Tg/Tv split itself; Tp is
a follow-up once it's clear whether non-Float grids are something anyone actually
wants, or shipped speculatively. Its rough shape (a Float-bounded third parameter
defaulting to f64, with numeric casts at the Tg/Tv boundaries) is captured above
so the Tg/Tv split doesn't foreclose it.
Motivation
Every generic type in
ninterptoday (Interp1D<T, S>,InterpData<T, N>,Strategy1D<D>'sD::Elem, ...) uses one type parameter for both grid coordinates andinterpolated values, and that parameter is bounded
Floatnearly everywhere (seesrc/strategy/cubic.rs,src/strategy/utils.rs,src/interpolator/mod.rs,src/interpolator/enums.rs). That forces grid axes to be the same float type as thefunction values, even when they conceptually aren't, e.g. an integer or otherwise
non-float grid axis paired with float-valued data.
Splitting into
Tg(grid) andTv(value) removes that coupling. This is a breakingchange; per this project's pre-1.0 convention it lands as a future
0.x -> 0.(x+1)bump,not gated to any 1.0.
API changes
Every site that currently carries one
T(or boundsD::Elem) needs to declare whichside of the split it's on:
InterpDataBase<D, N>(src/interpolator/data.rs): splits into two storageparameters,
grid: [ArrayBase<Dg, Ix1>; N]andvalues: ArrayBase<Dv, Dim<[Ix; N]>>.Interp1D/2D/3D/ND<D, S>and their*Base/*View/owned type aliases: followInterpDataBaseontoDg/Dv.Strategy1D/2D/3D/ND<D>(src/strategy/traits.rs):interpolate'spoint: &[Tg; N](or&[Tg]forND) is grid-side; itsResult<Tv, InterpolateError>return isvalue-side. (This stays
Tg-typed only as long asTg: Float; seeTpbelow forwhen that stops holding.)
Extrapolate<T>::Fill(T)(src/interpolator/mod.rs): the held value is interpolationoutput, so
Fill(Tv), even thoughInterp1D<T, S>around it today reads asgrid-coordinate-typed.
InterpolateError/ValidateError: stay non-generic. (InterpolateErrorwas alreadykept non-generic in the 0.10 error rework partly for this;
ValidateErrorwould onlyneed to follow
ExtrapolateontoTvif it ever went generic, which it isn't doinghere.)
Open questions
Tp. A query point falls between grid points, so onceTgisn't
Float, the point type can't just beTg(an integer or date-like grid can'trepresent a fractional position). It can't just be
Tveither: an unsigned-int gridimage (
Tg= pixel index,Tv=u8grayscale) still wants high-precisionfractional interpolation with the result rounded/cast down to
u8, soTvisn'tfloat-shaped in general either. That points at
Tpas a genuine third typeparameter, bounded
Floatand defaulting tof64so the common case needs no extraannotation, with numeric casts at each boundary:
Tg -> Tpto compute fractionalposition,
Tp -> Tv(round/clamp) to produce the final value.interpolate'spointargument becomes
Tp-typed once this lands, notTg-typed. (NamedTprather thanTq/"query" to match this codebase's existingpointparameter naming, and becauseqreads as too visually similar tognext toTg.)CubicC1/CubicC2's tridiagonalsolves and Hermite evaluation (
thomas,compute_m,hermite_eval_1d, etc. insrc/strategy/cubic.rs) areT: Floattoday and stay float-like asTp, rather thaninventing a separate type for them.
grid-typed when it should be value/float-typed.
Non-goals for this issue
Not implementing
Tphere. This issue is scoped to theTg/Tvsplit itself;Tpisa follow-up once it's clear whether non-
Floatgrids are something anyone actuallywants, or shipped speculatively. Its rough shape (a
Float-bounded third parameterdefaulting to
f64, with numeric casts at theTg/Tvboundaries) is captured aboveso the
Tg/Tvsplit doesn't foreclose it.