(feat): OnTheFly Coefficient Strategy + AutoCoeffs Smart Resolution#109
(feat): OnTheFly Coefficient Strategy + AutoCoeffs Smart Resolution#109
OnTheFly Coefficient Strategy + AutoCoeffs Smart Resolution#109Conversation
…slope, eval overloads
- hermite_slope_methods.jl: PchipSlopes, CardinalSlopes{Tg}, AkimaSlopes tag types
- hermite_local_slopes.jl: _local_slope() O(1) per-index computation for all 3 methods
reuses existing _pchip_endpoint_slope and _akima_weighted_slope helpers
- hermite_eval.jl: add AbstractSlopeMethod overloads for _hermite_eval_at_point (6 scalar)
and _hermite_vector_loop! (4 vector) — dispatches on dy::AbstractSlopeMethod vs dy::AbstractVector
- hermite.jl: update include order (slope_methods + local_slopes before eval)
…terpolant constructors
- All 4 Hermite types gain CS <: AbstractCoeffStrategy as last type param (phantom, no field)
- DY constraint relaxed from <: AbstractVector{Tv} to unconstrained
- PreCompute: DY=Vector{Tv}, validates length(dy)==length(x)
- OnTheFly: DY=AbstractSlopeMethod, stores slope tag (PchipSlopes etc.)
- Interpolant constructors gain coeffs kwarg:
pchip_interp(x, y; coeffs=OnTheFly()) → PchipInterpolant1D{..., OnTheFly}
Refactor pchip/cardinal/akima oneshot into public → internal layering: - Public: pchip_interp(x, y, xq; coeffs=PreCompute(), ...) — user API - Internal PreCompute: _pchip_interp_precompute(...) — @with_pool bulk slopes - Internal OnTheFly: _pchip_interp_onthefly(...) — no pool, local slopes - Generic Real wrappers forward kwargs... including coeffs transparently
- interp_method_types.jl: PchipInterp, CardinalInterp{T}, AkimaInterp, CubicHermiteInterp
- hetero_eval.jl: _oneshot_eval_1d dispatch for 3 Hermite methods
- hetero_interpolant.jl: homogeneous Hermite dispatch (→ OnTheFly via Hetero),
CubicInterp+OnTheFly fallback, validation, _has_local_method guard for PreCompute
- hetero_build.jl: _deriv_size + _is_deriv_method for Hermite types
- cubic_nd_types.jl: AutoCoeffs type + _resolve_coeffs (N≥3→OnTheFly, all-local→OnTheFly)
- interp() wires _resolve_coeffs before dispatch
- test_hermite_onthefly.jl: 713 tests covering local slopes, interpolant, oneshot,
derivatives, extrapolation, type stability, edge cases, ND hetero/homo, AutoCoeffs
Scalar oneshot (single query point) always benefits from OnTheFly — computing 2 local slopes O(1) is strictly better than bulk O(n) slopes when only 1 query is made. Vector oneshot retains PreCompute() default since bulk slopes amortize when length(xq) >> n.
… AutoCoeffs() - _validate_nd_coeffs: reject PreCompute + local Hermite methods with clear error - _throw_precompute_unsupported: @noinline cold-path error formatting - _resolve_coeffs(AutoCoeffs): any local method → OnTheFly (not just all-local) - interp() default changed from PreCompute() to AutoCoeffs() - Tests: add @test_throws for explicit PreCompute + Hermite, auto-default test
- New src/core/coeff_policy.jl: single source of truth for _resolve_coeffs - Scalar query → always OnTheFly (O(1) local slopes beats O(n) bulk) - Vector query → length(xq) > length(x) ? PreCompute : OnTheFly (crossover at K≈n) - Interpolant (no query) → PreCompute (amortize slopes for reuse) - ND → dimensionality + method type check (existing logic, moved here) - All call-site defaults changed from hardcoded PreCompute()/OnTheFly() to AutoCoeffs() - Each typed entry point calls _resolve_coeffs(coeffs, x, xq) before branching - Explicit coeffs=PreCompute()/OnTheFly() still works (passthrough) - Runtime length check is zero-allocation and type-stable (verified)
… strategy - Type stability: @inferred for scalar resolve, scalar oneshot, internal onthefly paths - Zero allocation: function-barrier @allocated tests for pchip/akima/cardinal scalar - Runtime strategy: verify length(xq) vs length(x) threshold (few→OnTheFly, many→PreCompute) - Boundary: length(xq)==length(x) → OnTheFly (≤ not <) - Cross-method: all 3 methods (pchip/akima/cardinal) × both strategies → identical results
Only shown when OnTheFly strategy is active (PreCompute is default, not displayed).
Compact: "PchipInterpolant1D{Float64}(20 pts, monotone, on-the-fly)"
Detailed: adds "├─ Coeffs: on-the-fly" row between Extrap and Slopes.
…uadratic) - Homogeneous Cubic/Quadratic + OnTheFly → Hetero fallback (_collapse_dims) enables AutoCoeffs N≥3 rule for global-solve methods (2^N memory savings) - Linear/Constant: trivial methods (no slopes) — always PreCompute, skip OnTheFly _all_trivial_methods trait prevents unnecessary Hetero routing - Hermite + explicit PreCompute → clear error via _validate_nd_coeffs - AutoCoeffs ND policy: trivial→PreCompute, N≥3→OnTheFly, local→OnTheFly, else→PreCompute
- Add pchip_interp/akima_interp to _InterpMethod union + _adjoint_func dispatches (critical: ChainRules rrules now fire for PCHIP/Akima oneshot AD) - _safe_secant: replace unguarded else with explicit elseif + error fallback - Cardinal _local_slope: remove dead return-if-return antipattern - Remove no-op _validate_nd_coeffs(OnTheFly) call in interp() - Tests: WrapExtrap OnTheFly, ND gradient correctness, Float32 support
…ation - Move AbstractCoeffStrategy types to src/core/coeff_types.jl (was in cubic/nd/) so all interpolant families can reference them regardless of include order - cubic_interp(grids, data; coeffs=OnTheFly()) → HeteroInterpolantND (was ArgumentError) - quadratic_interp(grids, data; coeffs=OnTheFly()) → HeteroInterpolantND (new kwarg) - Update test: remove @test_throws ArgumentError, verify OnTheFly matches PreCompute
- Remove N>=3 → OnTheFly rule from AutoCoeffs ND resolution. This was routing Cubic/Quadratic 3D+ to HeteroInterpolantND which lacks integrate/adjoint support — breaking existing code. Global methods now always use PreCompute (specialized ND types). - Add _throw_onthefly_unsupported guard for integrate on OnTheFly Hermite interpolants (clear error instead of MethodError on PchipSlopes indexing). - Guard added to both bounded integrate() and full-domain integrate().
FastInterpolations.jl BenchmarksAll benchmarks (42 total, click to expand)
|
| Benchmark | Current | Previous | Imm. Ratio | Grad. Ratio | Tier |
|---|---|---|---|---|---|
10_nd_construct/bicubic_2d |
71429.0 ns |
36878.0 ns |
1.937 |
1.901 |
both |
10_nd_construct/bilinear_2d |
820.1 ns |
559.3 ns |
1.466 |
1.325 |
both |
10_nd_construct/tricubic_3d |
525025.0 ns |
358408.0 ns |
1.465 |
1.485 |
both |
10_nd_construct/trilinear_3d |
2557.6 ns |
1707.2 ns |
1.498 |
1.488 |
both |
12_cubic_eval_gridquery/range_random |
5033.1 ns |
4229.3 ns |
1.19 |
1.166 |
both |
12_cubic_eval_gridquery/range_sorted |
5027.5 ns |
4218.3 ns |
1.192 |
1.168 |
both |
2_cubic_construct/g1000 |
14350.3 ns |
12554.5 ns |
1.143 |
1.107 |
both |
3_cubic_eval/q00100 |
533.7 ns |
443.4 ns |
1.204 |
1.181 |
both |
3_cubic_eval/q10000 |
51493.4 ns |
42605.5 ns |
1.209 |
1.184 |
both |
4_linear_oneshot/q10000 |
25935.4 ns |
18612.7 ns |
1.393 |
1.378 |
both |
5_linear_construct/g0100 |
42.3 ns |
34.7 ns |
1.222 |
1.038 |
immediate |
5_linear_construct/g1000 |
371.0 ns |
275.6 ns |
1.346 |
1.402 |
both |
6_linear_eval/q00100 |
278.2 ns |
197.2 ns |
1.411 |
1.41 |
both |
6_linear_eval/q10000 |
25772.5 ns |
18473.4 ns |
1.395 |
1.383 |
both |
8_cubic_multi/eval_s010_q100 |
1924.0 ns |
1733.2 ns |
1.11 |
0.938 |
immediate |
8_cubic_multi/eval_s100_q100 |
14010.0 ns |
11447.4 ns |
1.224 |
1.174 |
both |
8_cubic_multi/eval_s100_q100_scalar_loop |
4206.4 ns |
3349.2 ns |
1.256 |
1.188 |
both |
9_nd_oneshot/bicubic_2d |
68598.5 ns |
36814.7 ns |
1.863 |
1.837 |
both |
9_nd_oneshot/tricubic_3d |
516173.8 ns |
364051.4 ns |
1.418 |
1.434 |
both |
Thresholds: immediate > 1.1x (vs latest master), gradual > 1.1x (vs sliding window)
This comment was automatically generated by Benchmark workflow.
There was a problem hiding this comment.
Pull request overview
Adds an OnTheFly coefficient strategy and a new AutoCoeffs() default across the Hermite interpolation family (PCHIP/Cardinal/Akima), with centralized runtime resolution and ND routing through HeteroInterpolantND when appropriate.
Changes:
- Introduces
AutoCoeffs+ centralized_resolve_coeffspolicy, and wires it into Hermite 1D constructors/oneshots plusinterp()ND. - Implements on-the-fly local slope tags (
AbstractSlopeMethod+PchipSlopes/CardinalSlopes/AkimaSlopes) and Hermite eval overloads that compute slopes per-cell in O(1). - Extends heterogeneous ND evaluation to support Hermite per-axis methods and enables cubic/quadratic ND
coeffs=OnTheFly()delegation toHeteroInterpolantND, with new tests.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| test/test_hermite_onthefly.jl | New test coverage for Hermite OnTheFly, AutoCoeffs resolution, ND routing, allocations, and edge cases. |
| test/test_cubic_nd.jl | Updates expectation: cubic ND OnTheFly now delegates to HeteroInterpolantND and matches PreCompute numerics. |
| test/runtests.jl | Adds the new Hermite OnTheFly test file to the suite. |
| src/quadratic/nd/quadratic_nd_interpolant.jl | Adds coeffs kwarg; routes OnTheFly to HeteroInterpolantND. |
| src/pchip/pchip_types.jl | Adds CS type parameter and supports slope-method-tag storage for OnTheFly interpolants. |
| src/pchip/pchip_oneshot.jl | Adds PreCompute/OnTheFly internal paths and AutoCoeffs() default routing for oneshot PCHIP. |
| src/pchip/pchip_interpolant.jl | Adds coeffs kwarg and AutoCoeffs() default for PCHIP interpolant construction. |
| src/method_traits.jl | Extends method trait mappings to include PCHIP/Akima oneshot → adjoint constructor. |
| src/integral/integrate_fulldomain.jl | Guards integration against OnTheFly Hermite interpolants (throws clear ArgumentError). |
| src/hetero/hetero_interpolant.jl | interp() default becomes AutoCoeffs(); resolves coeff strategy and routes ND OnTheFly to hetero build; adds ND coeff validation and axis validation for Hermite methods. |
| src/hetero/hetero_eval.jl | Adds 1D oneshot evaluation support for Hermite per-axis methods in hetero ND collapse. |
| src/hetero/hetero_build.jl | Marks Hermite methods as derivative axes for hetero mixed-radix partial handling. |
| src/hermite/hermite.jl | Updates include order and splits slope method/local slope code into new files. |
| src/hermite/hermite_types.jl | Adds CS type parameter; generalizes DY to allow slope method tags for OnTheFly. |
| src/hermite/hermite_slope_methods.jl | New slope-method tag types and shared unsupported-operation error helper. |
| src/hermite/hermite_local_slopes.jl | New O(1) per-index slope computation for PCHIP/Cardinal/Akima. |
| src/hermite/hermite_interpolant.jl | Updates Hermite interpolant constructor to use new CS parameterization (PreCompute). |
| src/hermite/hermite_integrate.jl | Adds OnTheFly guard for Hermite integrate path. |
| src/hermite/hermite_eval.jl | Adds Hermite eval overloads for AbstractSlopeMethod (scalar + vector loops, extrap specializations). |
| src/FastInterpolations.jl | Includes coeff_policy.jl; exports AutoCoeffs and Hermite ND method types. |
| src/cubic/nd/cubic_nd_types.jl | Removes coeff strategy type definitions (moved to core). |
| src/cubic/nd/cubic_nd_interpolant.jl | Adds coeffs kwarg handling: delegates OnTheFly to hetero path; updates OnTheFly builder doc/error. |
| src/core/show.jl | Shows “on-the-fly” coeff strategy in 1D Hermite interpolant display. |
| src/core/interp_method_types.jl | Adds ND per-axis method types for Hermite family (PchipInterp, CardinalInterp, AkimaInterp, CubicHermiteInterp). |
| src/core/core.jl | Includes new coeff_types.jl in core. |
| src/core/coeff_types.jl | New shared coefficient strategy types (AbstractCoeffStrategy, PreCompute, OnTheFly, AutoCoeffs). |
| src/core/coeff_policy.jl | New centralized coefficient strategy resolution policy for 1D/ND contexts. |
| src/cardinal/cardinal_types.jl | Adds CS type parameter and OnTheFly slope-method-tag storage support. |
| src/cardinal/cardinal_oneshot.jl | Adds PreCompute/OnTheFly internal paths and AutoCoeffs() default routing for oneshot Cardinal. |
| src/cardinal/cardinal_interpolant.jl | Adds coeffs kwarg and AutoCoeffs() default for Cardinal interpolant construction. |
| src/akima/akima_types.jl | Adds CS type parameter and OnTheFly slope-method-tag storage support. |
| src/akima/akima_oneshot.jl | Adds PreCompute/OnTheFly internal paths and AutoCoeffs() default routing for oneshot Akima. |
| src/akima/akima_interpolant.jl | Adds coeffs kwarg and AutoCoeffs() default for Akima interpolant construction. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #109 +/- ##
==========================================
+ Coverage 96.60% 96.66% +0.05%
==========================================
Files 132 136 +4
Lines 10661 11055 +394
==========================================
+ Hits 10299 10686 +387
- Misses 362 369 +7
🚀 New features to boost your workflow:
|
- Add length(x)>=2 validation in OnTheFly internal methods (PCHIP/Cardinal/Akima) - Remove dead _build_nd_interpolant(::OnTheFly) — OnTheFly handled in cubic_interp() - Fix stale include-order comment in coeff_policy.jl - Replace unseeded rand with deterministic non-uniform grid in tests - Fix garbled UTF-8 in test comment - Fix test_nd_comprehensive.jl: OnTheFly now delegates to Hetero (not ArgumentError) - Add AutoCoeffs, PchipInterp, CardinalInterp, AkimaInterp, CubicHermiteInterp to docs
Targets missing coverage from codecov report: - _*_interp_precompute paths (scalar + vector + in-place) - show() compact + detailed for OnTheFly interpolants (PCHIP/Cardinal/Akima) - _deriv_size/_is_deriv_method traits for Hermite method types
…y paths Remove 3 unreachable lines: _all_local_methods (unused), _format_coeffs for PreCompute/OnTheFly strategy types (dy field is Vector or SlopeMethod, never a strategy struct). Add 6 targeted testsets covering: OnTheFly+ClampExtrap in-domain oneshot, interpolant vector eval with spacing, Range+PreCompute disambiguation for akima/cardinal/pchip, QuadraticND OnTheFly→Hetero delegation, integrate guard error, and _adjoint_func pchip/akima traits.
* (feat): ND forwarders for pchip/cardinal/akima — unified `interp` entry Add ND-shape methods to `pchip_interp`/`cardinal_interp`/`akima_interp` (and their in-place `!` variants) that forward to `interp(grids, data; method=...)`. Covers all four ND call shapes: interpolant construction, scalar one-shot, batch one-shot, and in-place batch. Lets users keep the per-method entry point they already use in 1D instead of rewriting to `interp(...; method=PchipInterp())` by hand. `cardinal_interp` forwarders thread `tension` into `CardinalInterp(tension)`. `CubicHermiteInterp` is intentionally not forwarded — user-supplied per-axis slope arrays need a separate ND design. * test: ND forwarder coverage for pchip/cardinal/akima Smoke tests for the new `pchip_interp`/`cardinal_interp`/`akima_interp` ND forwarders. Verifies ULP-exact equivalence with the corresponding `interp(grids, data; method = ...)` calls across all four ND call shapes (build, scalar oneshot, batch oneshot, in-place batch) for 2D and 3D fixtures. Coverage matrix: - Equivalence (`===` / `==`) vs direct `interp` for every call shape - `@inferred` type stability on the scalar oneshot path - Cardinal `tension` kwarg passthrough (default vs 0.5, plus cross-check against `CardinalInterp(0.5)`) - Grid flavors: all-Vector, all-range, range×Vector, Vector×range — forwarder must preserve concrete grid tuple type into `interp` * Runic formatting * docs: address Copilot review on PR #114 — fix doc/code drift Two doc-only fixes flagged by Copilot review: 1. `src/hetero/local_hermite_nd_forward.jl`: header said "all three ND call shapes" but enumerated four (off-by-one). 2. `test/test_local_hermite_nd_forward.jl`: header claimed `===` for all four call shapes, but batch/array paths use `==` (array identity is meaningless). Clarified that scalar paths use `===`, array paths `==`. No code behavior change.
Summary
Add
coeffs=OnTheFly()support for the Hermite interpolation family (PCHIP, Cardinal, Akima) and introduceAutoCoeffs()— a runtime-smart default that automatically selects the optimal coefficient strategy based on query context, analogous toAutoSearch().PreCompute builds all coefficients at construction (slopes, moments, partials) — O(n) or O(n^N) upfront, O(1) eval. OnTheFly defers computation to eval time, calculating only what's needed per cell — O(1) construction, O(1) per query for local methods. Critical for scalar one-shot queries and ND tensor-product evaluation where each fiber gets only one query.
New API
Key Design Decisions
CS <: AbstractCoeffStrategytype parameter added to all Hermite structs (phantom, no field)AutoCoeffs()is now the default for all Hermite oneshot/interpolant constructors andinterp()NDcoeff_policy.jl: centralized resolution — scalar→OnTheFly, vector→length-based, interpolant→PreCompute, ND→method-awareinterp()level — clean architecture,_interp_nd_dispatchreceives PreCompute only_resolve_coeffs→_precompute/_ontheflyinternalWhat's NOT Included
ArgumentErrorinstead ofMethodError)Testing
test_hermite_onthefly.jl@inferred), edge cases (n=2,3,4)