Commit 8f0a0c0
authored
(feat): duck-typed grid support for linear interpolation (#116)
* (feat): Linear 1D duck-typed grid support — Vector{ForwardDiff.Dual} (#81)
Enable LinearInterpolant to accept duck-typed grid scalars (e.g. ForwardDiff.Dual)
following the same Tv duck-typing pattern. Grid-side Dual propagates derivative
partials through spacing cache, binary search, and kernel evaluation.
Core changes:
- Add _PromotableGrid union (Integer, AbstractFloat, Rational) for fast-path gating
- Relax AbstractInterpolant, AbstractInterpolant1D type params (Tg unconstrained)
- Relax AbstractGridSpacing, ScalarSpacing, VectorSpacing (T unconstrained)
- Duck branch in _promote_itp_inputs: _PromotableGrid → Float path, else pass-through
- Relax scalar callable where-clause in interpolant_protocol.jl
Linear changes:
- LinearInterpolant struct: Tg unconstrained (duck grids get VectorSpacing{Tg})
- All 5 _linear_kernel overloads: drop Tg<:AbstractFloat
- Unified scalar one-shot entry: absorbs _promote_itp_inputs inline, removing the
redundant Real→Float wrapper that caused infinite recursion on duck grids
Extension:
- _to_grid_type(::Real, ::Type{<:Dual}) override avoids Dual(Float) MethodError
in _search_binary; returns primal for index comparison
Scope: Phase 1 — scalar eval + scalar one-shot only.
Vector queries (Phase 2), linear series (Phase 3), Range{Dual} (Phase 4),
adjoint, ND, and non-linear methods are out of scope.
Performance: zero regression on Float/Int/Range paths (12 benchmark cases,
byte-identical allocation counts vs master).
* (feat): Range{Dual} support via _CachedRange + DirectSearch O(1)
Extend the duck-typed grid infrastructure from the previous commit (Vector{Dual})
to also support Range{Dual} grids, preserving the O(1) DirectSearch fast path.
Core changes:
- Relax _CachedRange{T}, all constructors, _to_float overloads, and
_to_float_adding_endpoint from T<:AbstractFloat to unconstrained T.
x86 TwicePrecision specialization retains FT<:AbstractFloat and Dual
ranges naturally bypass it via the generic _to_float fallback.
- _search_direct: extract primal before unsafe_trunc for index calculation;
xL/xR retain full Dual type for kernel partial propagation.
- _create_spacing: relax AbstractRange{T<:AbstractFloat} to AbstractRange{T}.
Range{Dual} gets ScalarSpacing{Dual} — O(1) cached h/inv_h.
- Simplify _promote_itp_inputs: remove _PromotableGrid branch entirely.
All grid types (Int, Float, Dual, custom) now flow through a single path
via _promote_grid_float → _to_float. Duck grids (Dual) are normalized to
_CachedRange{Dual} just like Float ranges, with no y-value widening when
Tg is not AbstractFloat.
- Remove unused _PromotableGrid constant and all references.
- Relax _to_float Vector identity/broadcast paths from FT<:AbstractFloat to T.
Pipeline for Range{Dual}:
StepRangeLen{Dual} → _to_float → _CachedRange{Dual}
→ ScalarSpacing{Dual} → DirectSearch O(1) (primal-based trunc)
→ xL/xR as Dual → kernel partial propagation → Dual result
Tests:
- Range{Dual} construction, all 6 range source types (StepRangeLen, LinRange,
UnitRange, StepRange, broadcast and scalar-mult variants)
- Range vs Vector path mid-interval parity (primal match, partial within 1e-10)
- Exact-knot behavior: analytic chain-rule verification + one-sided FD cross-check
(DirectSearch→RIGHT, BinarySearch→LEFT — both match their respective one-sided FD)
- ForwardDiff.derivative MWE through Range grid
- TDG duck type: added float() protocol requirement
* (feat): complete Linear 1D Dual-grid support — vector, anchor, one-shot paths
Open all remaining Linear 1D paths to duck-typed grids (Vector{Dual}, Range{Dual}).
Protocol + shared:
- interpolant_protocol.jl: relax vector/in-place callable Tg constraint; include Tg
in output eltype via promote_type(Tv, Tg, Tq)
- anchor_common.jl: relax _AnchorLoc{Tg} constraint
- utils.jl: relax 3-arg _promote_itp_inputs (query stays Float when Tg is duck);
add generic _promote_for_anchor fallback for duck grids;
relax _promote_value_type guard (skip y-widening when Tg is not AbstractFloat)
Linear:
- linear_interpolant.jl: relax _linear_vector_loop! and generic constructor
- linear_oneshot.jl: unify linear_interp! as single entry (absorbs promotion,
removes redundant Real/Range wrappers that caused infinite recursion on duck
grids); unify allocating vector one-shot similarly; relax _linear_interp_loop!
to accept heterogeneous grid/query types
- linear_anchor.jl: relax _LinearAnchoredQuery and all overloads; add outer
constructor that infers Tq from alpha's arithmetic type and promotes xq via
convert — eliminates _promote_for_anchor calls from linear anchor path;
merge two scalar _anchor_query overloads into one
Net effect: -54 lines (removed redundant wrappers, merged overloads).
All Linear 1D paths now accept duck-typed grids: constructor, scalar/vector
callable, scalar/vector one-shot, in-place, and anchor-based evaluation.
* (feat): Linear Series — complete Dual-grid support
Relax Tg<:AbstractFloat constraints on LinearSeriesInterpolant, all eval
overloads, oneshot series, and AbstractSeriesInterpolant abstract type.
Key changes beyond type constraint relaxation:
- _make_anchor: accept any xq type (was xq::Tg); _anchor_loc + outer constructor
handle primal extraction and type widening internally
- Remove explicit _extract_primal + Tg(xq_primal) conversion from scalar eval
caller — this caused MethodError on Dual grids (Dual(Float) constructor absent)
- _eval_linear_series_point!: use aq.xq instead of separate xq argument; aq.xq
is already widened by the outer constructor to carry both query-side and grid-side
Dual information
- Series output type: include Tg via promote_type(Tv, Tg) in _series_output_type
so that Dual-grid results allocate the correct output vector
Both query-side Dual (ForwardDiff.derivative through series) and grid-side Dual
(Vector{Dual}/Range{Dual} grids) are now functional.
* (feat): Linear Adjoint — Dual-grid support
Relax Tg<:AbstractFloat on LinearAdjoint, AbstractAdjoint, AbstractAdjoint1D.
Key change: query normalization in linear_adjoint() avoids converting Float queries
to Dual (which would inject false grid-parameter partials). Queries stay at their
own float precision; the anchor outer constructor widens xq to match alpha's
arithmetic type during scatter.
Dot-product identity ⟨Wf, ȳ⟩ = ⟨f, Wᵀȳ⟩ verified with Dual grids.
* (test): add Series, Adjoint, and full-path coverage for Dual grids
Extend test/ext/test_linear_dual_grid.jl with:
- Linear Series: Vector{Dual} + Range{Dual} grids, primal parity with Float path
- Linear Adjoint: Vector{Dual} + Range{Dual} grids, dot-product identity
- All-paths coverage: scalar/vector callable, in-place, scalar/vector one-shot,
in-place one-shot — for both Vector{Dual} and Range{Dual}
- Float in-place zero-alloc gate: itp(out, xq_vec) and linear_interp!(out, ...)
both verified @Allocations == 0 after warmup
* (feat): Linear ND — complete Dual-grid support
Extend duck-typed grid support to all Linear ND paths: interpolant constructor,
scalar/batch eval (via interpolant_protocol.jl), one-shot scalar/batch, and
ND adjoint operator.
Changes:
- Relax AbstractInterpolantND, AbstractAdjointND abstract types
- Relax LinearInterpolantND, LinearAdjointND, _LinearNDAdjointAnchor structs
- Replace `Tg = Tg <: AbstractFloat ? Tg : Float64` promotion pattern with
`Tg = float(Tg)` throughout (5 sites: interpolant, oneshot×3, adjoint×2)
- Add _value_type duck-Tg fallback: `where {T, Tg} = T` — when grid is Dual,
y values are not promoted to grid type (no grid-parameter partials in data)
- Relax _linear_nd_oneshot_eval! where-clauses
Tests:
- 2D homogeneous Dual grids: primal parity + FD cross-check
- 2D heterogeneous axis (Dual×Float): per-axis FD cross-check
- 2D Range{Dual} + Vector{Float} mix: CachedRange verification + primal parity
* (fix): oneshot series Real wrapper infinite recursion on Julia 1.10 + stale docstrings
Bug fix:
- linear_oneshot_series.jl: remove 4 Real type promotion wrappers that caused
infinite recursion on Julia 1.10 LTS (same pattern as the scalar/vector one-shot
fix from earlier commits). Typed methods now handle promotion internally via
_promote_grid_float + _to_float.
- Fix output type computation to use promoted Tg (eltype after _to_float), not
the original raw Tg.
- Fix pool anchor type to use promoted Tq = promote_type(Tq, Tg_actual).
Docstring cleanup (from code review):
- cached_range.jl: docstring header now matches struct `_CachedRange{T}`
- linear_anchor.jl: Tg/Tq descriptions updated for duck-typed grids
- linear_series_interp.jl: _eval_linear_series_point! docstring updated
(removed stale xq argument references)
* (fix): 4 Codex-identified issues — ND oneshot spacing, hinted search, series output types
TDD RED→GREEN for 4 identified issues:
P1-1: _create_spacing_pooled in nd_utils.jl had T<:AbstractFloat constraint,
blocking ND one-shot paths for Dual grids. Fixed: T<:AbstractFloat → T.
P1-2: _search_direct! (hinted mutating variant) had T<:AbstractFloat on both
overloads, blocking Range{Dual} grids with hint-based eval. Fixed: T → T.
P2-3: LinearSeriesInterpolant vector eval allocated Vector{Vector{Tv}} without
including Tg in output type. Dual grid results couldn't be stored. Fixed:
_series_output_type(Tv, Tq) → _series_output_type(promote_type(Tv, Tg), Tq).
P2-4: Oneshot series vector allocator used _value_type(Tv, Tg_p) which returns
Tv unchanged for duck Tg. Fixed: use promote_type(_series_eltype(s), Tg_p)
to include grid type in output allocation.
All 4 issues verified with dedicated test cases (RED→GREEN cycle).
* Runic formatting
* Runic formatting
* (fix): use _output_eltype instead of raw promote_type for output allocation
promote_type(MyDuck, Float64) returns Any on LTS (no promote_rule defined for
custom duck types), causing Vector{Any} output allocation. _output_eltype already
has the isconcretetype fallback: returns Tv when promote_type gives non-concrete.
Replaced promote_type(Tv, Tg, ...) with _output_eltype(Tv, Tg, ...) in:
- interpolant_protocol.jl: vector callable + ND batch allocator
- linear_oneshot.jl: vector one-shot allocator
- linear_series_interp.jl: scalar + vector series allocator
- linear_anchor.jl: anchor vector allocator
- linear_oneshot_series.jl: scalar + vector oneshot series allocator
Both duck-Tv (MyDuck) and duck-Tg (Dual) paths verified.
* (chore): PR review fixes — include test in runtests, update stale headers/docstrings
- Add test_linear_dual_grid.jl to runtests.jl default suite (Copilot review)
- Update ext test header: remove stale "Phase 1 scalar-only" scope note,
reflect full coverage (1D/ND/series/adjoint/vector/anchor)
- Update LinearInterpolantND docstring: Tg description matches unconstrained code
- Runic formatting pass
* test: update allocation test to use @allocated with threshold1 parent b604af8 commit 8f0a0c0
File tree
26 files changed
+1195
-366
lines changed- ext
- src
- core
- linear
- nd
- test/ext
26 files changed
+1195
-366
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
30 | 44 | | |
31 | 45 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
29 | 32 | | |
30 | | - | |
| 33 | + | |
31 | 34 | | |
32 | | - | |
| 35 | + | |
33 | 36 | | |
34 | 37 | | |
35 | 38 | | |
36 | 39 | | |
37 | 40 | | |
38 | 41 | | |
39 | | - | |
| 42 | + | |
40 | 43 | | |
41 | 44 | | |
42 | 45 | | |
| |||
63 | 66 | | |
64 | 67 | | |
65 | 68 | | |
66 | | - | |
| 69 | + | |
67 | 70 | | |
68 | 71 | | |
69 | 72 | | |
| |||
119 | 122 | | |
120 | 123 | | |
121 | 124 | | |
122 | | - | |
| 125 | + | |
123 | 126 | | |
124 | 127 | | |
125 | 128 | | |
| |||
150 | 153 | | |
151 | 154 | | |
152 | 155 | | |
153 | | - | |
| 156 | + | |
154 | 157 | | |
155 | 158 | | |
156 | 159 | | |
| |||
184 | 187 | | |
185 | 188 | | |
186 | 189 | | |
187 | | - | |
| 190 | + | |
188 | 191 | | |
189 | 192 | | |
190 | 193 | | |
| |||
206 | 209 | | |
207 | 210 | | |
208 | 211 | | |
209 | | - | |
| 212 | + | |
210 | 213 | | |
211 | 214 | | |
212 | 215 | | |
| |||
225 | 228 | | |
226 | 229 | | |
227 | 230 | | |
228 | | - | |
| 231 | + | |
229 | 232 | | |
230 | 233 | | |
231 | 234 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
| 54 | + | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
115 | | - | |
116 | | - | |
117 | | - | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
| |||
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
139 | | - | |
| 139 | + | |
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
145 | | - | |
146 | | - | |
147 | | - | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
| |||
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | | - | |
| 162 | + | |
163 | 163 | | |
164 | | - | |
| 164 | + | |
165 | 165 | | |
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
169 | 169 | | |
170 | | - | |
171 | | - | |
172 | | - | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
176 | 176 | | |
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
180 | | - | |
| 180 | + | |
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| 153 | + | |
153 | 154 | | |
154 | 155 | | |
155 | 156 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
20 | 26 | | |
21 | | - | |
| 27 | + | |
22 | 28 | | |
23 | 29 | | |
24 | 30 | | |
| |||
43 | 49 | | |
44 | 50 | | |
45 | 51 | | |
46 | | - | |
| 52 | + | |
47 | 53 | | |
48 | 54 | | |
49 | 55 | | |
50 | 56 | | |
51 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
52 | 60 | | |
53 | 61 | | |
54 | 62 | | |
| |||
66 | 74 | | |
67 | 75 | | |
68 | 76 | | |
69 | | - | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
70 | 84 | | |
71 | 85 | | |
72 | 86 | | |
73 | | - | |
74 | | - | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
75 | 92 | | |
76 | 93 | | |
77 | | - | |
| 94 | + | |
78 | 95 | | |
79 | 96 | | |
80 | 97 | | |
| |||
133 | 150 | | |
134 | 151 | | |
135 | 152 | | |
136 | | - | |
| 153 | + | |
137 | 154 | | |
138 | 155 | | |
139 | 156 | | |
| |||
153 | 170 | | |
154 | 171 | | |
155 | 172 | | |
156 | | - | |
| 173 | + | |
157 | 174 | | |
158 | 175 | | |
159 | 176 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
60 | 62 | | |
61 | 63 | | |
62 | 64 | | |
| |||
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
76 | | - | |
| 78 | + | |
77 | 79 | | |
78 | 80 | | |
79 | 81 | | |
| |||
191 | 193 | | |
192 | 194 | | |
193 | 195 | | |
194 | | - | |
| 196 | + | |
195 | 197 | | |
196 | 198 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
864 | 864 | | |
865 | 865 | | |
866 | 866 | | |
867 | | - | |
| 867 | + | |
868 | 868 | | |
869 | 869 | | |
870 | 870 | | |
| |||
873 | 873 | | |
874 | 874 | | |
875 | 875 | | |
876 | | - | |
| 876 | + | |
877 | 877 | | |
878 | 878 | | |
879 | 879 | | |
| |||
0 commit comments