v2.1.2: 1D interpolation module (Vector1D + Interpolator protocol + 20 types)#6
Merged
v2.1.2: 1D interpolation module (Vector1D + Interpolator protocol + 20 types)#6
Conversation
Adds Vector1D<T>, the trivial 1-dimensional VectorSpace conformer that completes the fixed-dimension vector type family (Vector1D, Vector2D, Vector3D). Vector1D is the natural Point type for 1D interpolation, time series, and any other inherently 1D domain — and lets generic algorithms over VectorSpace include the 1D scalar case naturally instead of special-casing scalars. Initializer is unnamed (Vector1D(2.5)) to match VectorN's pattern; the stored property is .value (not .x — there is no spatial axis to name). Conforms to VectorSpace, AdditiveArithmetic, Hashable, Codable, Sendable. 8 unit tests covering basic operations, norm/dot, array round-trip, zero/dimension/isFinite, distance, equality+hashability, Codable round-trip, and Sendable conformance. Also adds Tests/Validation/Interpolation_Playground.swift — a standalone hand-rolled implementation of all 10 1D interpolation methods (nearest, previous, next, linear, natural cubic spline, PCHIP, Akima/makima, CatmullRom, BSpline, barycentric Lagrange) with no BusinessMath dependency. Verifies pass-through invariant, linear-data invariant (every method must reproduce a*x+b exactly to machine precision), and monotonicity preservation. All invariants currently pass. The playground caught two design bugs before any package code was written: (1) NextValue at exact knots was returning ys[i+1] instead of ys[i] (fixed: pass-through at knots), and (2) the CatmullRom default tension in ADR-004 was 0.5, which is half-strength tangents and fails the linear-data invariant by 12.6%. The correct standard Catmull-Rom is tension τ = 0 (full-strength tangents), which passes at machine precision (~3.5e-15). ADR-004 and INTERPOLATION_PLAN.md updated locally to reflect the corrected default. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the single root Interpolator protocol per ADR-002, plus the four 1D scalar interpolators that don't require precomputed coefficients: - NearestNeighborInterpolator - PreviousValueInterpolator - NextValueInterpolator - LinearInterpolator The Interpolator protocol uses associated types Point: VectorSpace and Value: Sendable to encode the input domain shape and output codomain shape respectively. All v2.1.2 conformers use Point = Vector1D<T>. Scalar-output methods use Value = T; vector-output flavors (added in later commits) use Value = VectorN<T>. Concrete 1D types provide a scalar convenience overload callAsFunction(_ t: T) -> T so callers don't need to wrap query coordinates in Vector1D for the common case. Also adds the supporting types: ExtrapolationPolicy<T> (clamp/ extrapolate/constant, default clamp) and InterpolationError (validation errors thrown only at construction time — evaluation never throws). Internal helpers shared across all 1D conformers: - validateXY(xs:ysCount:minimumPoints:): early validation, throws on mismatch/unsorted/duplicate - bracket(_:in:): O(log n) binary search for [xs[lo], xs[hi]] containing t - extrapolatedValue(at:xs:ys:policy:): apply ExtrapolationPolicy 29 tests across the four method suites cover pass-through invariant, hand-computed expected values from the validation playground, linear- data invariant (Linear reproduces a*x+b exactly to machine precision), clamp/constant/extrapolate policies, Vector1D query equivalence, single-point degenerate case, batch evaluation, and all four error modes (insufficient points, mismatched sizes, unsorted xs, duplicate xs). One bug caught and fixed during test execution: PreviousValueInterpolator at the last exact knot was returning ys[n-2] instead of ys[n-1] because the extrapolatedValue helper returns nil for in-range queries (including the boundary). Fixed with a special-case check for `t == xs[hi]` after bracket lookup. Regression test included. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements 1D cubic spline interpolation with all four standard boundary conditions: - .natural: f''(x_first) = f''(x_last) = 0. Default. Kubios HRV standard. - .notAKnot: third derivative continuous at xs[1] and xs[n-2]. MATLAB / scipy default for cubic interpolation. - .clamped(left:right:): specified f'(x_first) and f'(x_last). - .periodic: f, f', f'' match at endpoints. Requires ys.first == ys.last. Each boundary condition has its own coefficient solver: - naturalSpline: solves the (n-2) interior tridiagonal via Thomas algorithm - clampedSpline: solves the full n-size tridiagonal with modified first and last rows for the specified endpoint slopes - notAKnotSpline: solves the (n-2) interior with modified first and last rows that enforce continuity of the third derivative at xs[1] and xs[n-2] - periodicSpline: solves a cyclic tridiagonal system via Sherman-Morrison 13 tests cover pass-through invariant on all four BCs, linear data invariant on all BCs that admit it, hand-computed reference values captured directly from the validation playground (locked at machine precision via 1e-12 tolerance), Vector1D query equivalence, two-point clamped degenerate case, periodic mismatch error, and the insufficient-points error path. The natural cubic spline matches the validation playground reference implementation to machine precision, confirming the Thomas algorithm implementation is mathematically equivalent to the standalone version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three Hermite-cubic 1D interpolators that share a common cubicHermite evaluator (defined in PCHIP.swift, internal): - PCHIPInterpolator: Fritsch-Carlson monotone cubic. Overshoot-safe. Slopes computed via weighted harmonic mean with sign-detection clamp to enforce monotonicity. Endpoint slopes via the Fritsch-Carlson 3-point formula. The scipy-recommended safe cubic for general data. - AkimaInterpolator: Local-slope cubic with optional makima modification (default true). Slopes weighted by neighbor segment slope differences plus (in modified mode) average-of-neighbors contributions that handle flat regions and repeated values better. Robust to outliers and oscillations. Endpoint handling via Akima's ghost-slope extrapolation (2 ghost slopes on each side). - CatmullRomInterpolator: Cardinal spline with configurable tension τ ∈ [0, 1]. Default τ = 0 (standard Catmull-Rom, full-strength tangents). Slopes via d[i] = (1 - τ) * (y[i+1] - y[i-1]) / (x[i+1] - x[i-1]). Throws InvalidParameter if tension is out of range. Documented that τ > 0 produces a tighter cardinal spline that does NOT reproduce linear data exactly. 19 tests cover pass-through, hand-computed reference values from the validation playground (locked at 1e-12), linear-data invariant on linear input, monotonicity preservation on sharp-gradient data (PCHIP and Akima both correctly stay within [data_min, data_max] on the playground monotonicity fixture), and the cardinal-spline behavioral test that demonstrates τ > 0 IS being applied (the test specifically probes at non-symmetric points within each interval because the wrong-slope Hermite contribution cancels by symmetry at exact midpoints — caught when an earlier midpoint-only test passed falsely). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two polynomial-style interpolators completing the v2.1.2 scalar method set: - BSplineInterpolator: cubic interpolating B-spline of configurable degree (1..5). For v1, degree 1 delegates to LinearInterpolator and degrees 2..5 delegate to CubicSplineInterpolator with notAKnot boundary condition (mathematically equivalent to a cubic interpolating B-spline). A full B-spline-basis implementation for degrees 2/4/5 is scheduled for a future release. Throws if degree is outside 1..5. - BarycentricLagrangeInterpolator: numerically stable polynomial interpolation through all n points via the barycentric form (Berrut & Trefethen 2004). Constructs the unique polynomial of degree ≤ n-1 that passes through the data; documented as suitable for small N (≤ ~20) due to Runge phenomenon at higher counts. Recovers the analytic polynomial exactly when the data lies on one (verified by the y = x² test fixture which is the exact 5-point polynomial through xs=[0,1,2,3,4], ys=[0,1,4,9,16]). 13 tests covering pass-through, linear-data invariant, BSpline degree delegation (degree=1 matches LinearInterpolator, degree=3 matches CubicSpline.notAKnot), polynomial recovery (BarycentricLagrange on y=x² returns 6.25 at x=2.5 within machine precision), and error paths (invalid degree, empty input, duplicate xs). All 10 scalar 1D interpolation methods now ship: 74 interpolator tests passing across 10 suites, plus 8 Vector1D tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vector-output flavors of all 10 1D interpolation methods. Each takes ys: [VectorN<T>] (one vector per knot) and produces VectorN<T> output at each query point. Internally constructs one scalar interpolator per output channel via channel transposition, so the underlying algorithm is shared verbatim with the scalar version. Use cases: - 3-axis accelerometer over time → 3-component vector per sample - Multi-channel EEG over time → N-component vector per sample - Stock portfolio historical values → M-component vector per sample - Multi-sensor fusion timestamps → K-component vector per sample Types added: - VectorNearestNeighborInterpolator - VectorPreviousValueInterpolator - VectorNextValueInterpolator - VectorLinearInterpolator - VectorCubicSplineInterpolator (with BoundaryCondition) - VectorPCHIPInterpolator - VectorAkimaInterpolator (with modified parameter) - VectorCatmullRomInterpolator (with tension parameter) - VectorBSplineInterpolator (with degree parameter) - VectorBarycentricLagrangeInterpolator All 10 conform to the Interpolator protocol with Point = Vector1D<T> and Value = VectorN<T>. outputDimension is set at construction time from the input vector dimension. Each provides scalar convenience callAsFunction(_ t: T) -> VectorN<T> alongside the protocol-required callAsFunction(at: Vector1D<T>) -> VectorN<T>. Internal helpers: - validateVectorYs: ensures all input vectors have the same dimension, throws InvalidParameter on mismatch - transposeChannels: pivots [VectorN<T>] of length n into dim channels of length n, each a [T] 15 tests covering: - outputDimension correctly reports the vector channel count - Mismatched vector dimensions throws InvalidParameter - Per-channel equivalence: vector interpolator output matches running each channel through the corresponding scalar interpolator (one test per method, all 10 methods) - Pass-through invariant for VectorLinear and VectorCubicSpline - Vector1D query equivalence for the protocol API All 20 v2.1.2 interpolator types now ship: 89 interpolator tests passing across 11 suites, plus 8 Vector1D tests = 97 total new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Swift compiler's type checker hits its time budget on the combined cubic Hermite and natural cubic spline evaluation expressions when building with -c release optimization. Tests passed in debug builds because debug uses a faster (less aggressive) type-checking path, but the v2.1.2 PR's CI Apple release build job failed with: error: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions Fix: bind each multiplicative term to its own `let` so the type checker handles them individually. Mathematically identical, just explicit about evaluation order. Verified locally with `swift build -c release` (240s, build complete). Affected files: - CubicSpline.swift: callAsFunction(_ t:) — natural spline evaluation - PCHIP.swift cubicHermite helper — shared by PCHIP, Akima, CatmullRom All 46 interpolation tests still pass after the refactor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the comprehensive 1D interpolation module to BusinessMath, designed from day one to extend cleanly to N-dimensional gridded and scattered interpolation in future releases without breaking changes.
Driven by HRV frequency-domain analysis (BioFeedbackKit) which needs accurate resampling of irregular RR-interval series, but the interpolation primitive is broadly useful far beyond HRV.
Changes (7 commits)
Vector1D<T>+ standalone validation playground (~800 LoC)Interpolatorprotocol + four simple methods (Nearest/Previous/Next/Linear)CubicSplineInterpolatorwith four boundary conditionsWhat's new in v2.1.2
Vector1D<T>Completes the fixed-dimension vector type family alongside
Vector2D,Vector3D,VectorN. Trivial wrapper around a single scalar that conforms toVectorSpace. Enables generic algorithms overVectorSpaceto include the 1D scalar case naturally instead of special-casing scalars.InterpolatorprotocolSingle root protocol with
Point: VectorSpaceandValue: Sendableassociated types. v2.1.2 conformers all usePoint = Vector1D<T>. Future 2D conformers will usePoint = Vector2D<T>, ND scattered will usePoint = VectorN<T>. No protocol changes ever — additive only.Ten 1D interpolation methods, scalar-output
NearestNeighborInterpolatorPreviousValueInterpolatorNextValueInterpolatorLinearInterpolatorCubicSplineInterpolator.natural(default, Kubios HRV),.notAKnot(MATLAB default),.clamped,.periodicPCHIPInterpolatorAkimaInterpolatormodified: Bool = true(makima default)CatmullRomInterpolatortension = 0(standard Catmull-Rom)BSplineInterpolatorBarycentricLagrangeInterpolatorTen 1D interpolation methods, vector-output
Vector*Interpolatorflavors of all 10. Each takesys: [VectorN<T>]and producesVectorN<T>output. Same algorithms as scalar versions, run channel-wise via internal transposition. Use cases: 3-axis sensor, multi-channel EEG, motion capture, multi-asset portfolios.Supporting types
ExtrapolationPolicy<T>:.clamp(default),.extrapolate,.constant(T)InterpolationError: thrown only at construction time, never during evaluationTest coverage
97 new tests across 11 suites, all passing. Total suite: 4817 / 4817, zero warnings.
a*x + bexactly to machine precision) on every cubic and polynomial methodTests/Validation/Interpolation_Playground.swiftlocked at1e-12Architecture decisions
Documented in
Instruction Set/00_CORE_RULES/10_ARCHITECTURE_DECISIONS.md:Bugs caught and fixed during development
The validation playground caught two bugs before any package code was written:
ys[i+1]instead ofys[i]. Fixed in playground, then enforced in package implementation with a regression test.τ = 0(full-strength tangents), which passes at machine precision. ADR-004 amended; default corrected.A third bug surfaced during test execution:
ys[n-2]becauseextrapolatedValuereturns nil for in-range queries (including the boundary). Fixed with a special-case check fort == xs[hi]. Regression test included.Compatibility
Purely additive at the public API level. No existing types or methods were changed. All 4720 tests from v2.1.1 continue to pass. v2.1.2 brings the total to 4817.
Test plan
🤖 Generated with Claude Code