Skip to content

Split element type T into grid (Tg) and value (Tv) type parameters #57

Description

@kylecarow

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions