Skip to content

(fix): Close the right boundary of WrapExtrap's domain#142

Merged
mgyoo86 merged 11 commits into
masterfrom
fix/closed_domain
May 18, 2026
Merged

(fix): Close the right boundary of WrapExtrap's domain#142
mgyoo86 merged 11 commits into
masterfrom
fix/closed_domain

Conversation

@mgyoo86
Copy link
Copy Markdown
Member

@mgyoo86 mgyoo86 commented May 17, 2026

Summary

WrapExtrap's in-domain interval is now closed: [first(x), last(x)]. Previously it was half-open [first(x), last(x)), so a query at exactly xq == last(x) wrapped to the left edge and returned y[1]. With closed semantics it stays at the right edge and returns y[end].

This is a behavioral change at exactly one point — xq == last(x) under plain WrapExtrap. All other queries (xq < last(x), xq > last(x), xq < first(x)) are unchanged.

PeriodicBC users are observably unaffected:

  • :inclusive requires y[1] == y[end] by construction, so the new return value at the seam is equal to the old one.
  • :exclusive reaches the seam through _ExclusivePeriodicAxis.search_interval, which already used an inclusive xq >= x[n] predicate — its fast path returns the cyclic value (y[1]) regardless of the wrap-predicate change.

mgyoo86 added 10 commits May 16, 2026 19:05
Switch from half-open [first(x), last(x)) to closed [first(x), last(x)].
xq == last(x) now returns xi unchanged (was wrap-to-first).

:inclusive PeriodicBC unchanged (validation enforces y[1] = y[end]).
:exclusive PeriodicBC unchanged (seam search returns idx_R=1 at xq>=inner[n]).
Plain WrapExtrap at xq==last(x) now returns y[end] (was y[1]).
…osed Union

Phase 1.2 of WrapExtrap closed-domain conversion. WrapExtrap batch
dispatch now shares the closed-domain promotion with ClampExtrap and
FillExtrap via one Union{Clamp,Fill,Wrap} method.

Also fixes a latent bug in _constant_eval_at_anchor's right-edge
short-circuit: y[end] was correct under half-open _wrap_to_domain
(which wrapped seam queries to x_min before this point fired), but
returns wrong value y[n] instead of cyclic y[1] under closed semantics
on _ExclusivePeriodicAxis (where x_last is the virtual seam endpoint).
Routes through y[aq.idxR] which equals y[end] for non-periodic
(idxR == n) and y[1] for :exclusive PeriodicBC (idxR == 1 post-fold).
Phase 2 of WrapExtrap closed-domain conversion. _anchor_loc's wrap branch
now skips at exact xq==x_max (the inner _wrap_to_domain already became
a no-op there under Phase 1.1; this is the cleanup for code clarity).

Test test_anchor_common.jl 'wrap at x_max' rewritten: query at x_max now
asserts in-domain state at the last cell (idx==n-1, xR==x_max), not
wrap-to-x_min. New test guards strictly-OOB right still wraps.
Phase 3 of WrapExtrap closed-domain conversion. The slow path already
handled qmax==x_max correctly under closed semantics (since
_wrap_to_domain is a no-op there); this change moves it to the fast
path to skip the per-element wrap overhead.

Adds skeleton test_wrapextrap_closed_boundary.jl (Phase 5 will expand
to full method matrix).
Replace half-open form '[x[1], x[end])' / '[x_min, x_max)' in docstrings
and comments across:
- src/core/eval_ops.jl (WrapExtrap docstring)
- src/constant/constant_oneshot.jl (public docstring)
- src/cubic/cubic_adjoint.jl, cubic/cubic_anchor.jl (anchor doc)
- src/{linear,constant,quadratic}/{,linear,constant,quadratic}_anchor.jl
- test_periodic_bc.jl (NOTE on Linear WrapExtrap endpoint validation)
- test_constant_periodic.jl (seam-search explanation)
- test_hermite_1d.jl (tighten 'fast-path includes x_max' to literal assert)

linear/nd/linear_nd_adjoint.jl 'half-open grid' refers to user input data
layout for :exclusive PeriodicBC (n samples covering [first, first+period)),
not the WrapExtrap domain — unchanged.
Phase 5 of WrapExtrap closed-domain conversion. test_wrapextrap_closed_boundary.jl
expands the Phase 3 stub into a full coverage matrix:

- Linear / Constant / Cubic / Quadratic 1D × every extrap × Vector + Range grid
- Hermite (precomputed dy) + OnTheFly Hermite (PCHIP / Cardinal / Akima)
- :inclusive PeriodicBC at xq = last(x) — verify y[1] (== y[end] by validation)
- :exclusive PeriodicBC at xq = inner[1]+period — verify y[1] via seam search
- :exclusive series oneshot — regression guard for the constant-anchor short-
  circuit fix (was returning y[end] cyclically instead of y[idxR=1])
- ND per-axis WrapExtrap at corner (last(gx), last(gy)) — scalar + SoA batch
Tier 2 regression caught 7 failures in test_constant.jl asserting the
old half-open WrapExtrap behavior at xq == last(x). Investigation showed
that constant scalar oneshot _constant_eval_at_point(::WrapExtrap) was
the only constant path lacking the 'xq == last(x) -> y[end]' short-circuit
that the InBounds core and the persistent anchor path both carry.

Under old half-open semantics, this didn't matter because xq == last(x)
got wrapped to first(x) before reaching the kernel. Under closed semantics
(this PR) the query is preserved, and the kernel returned y[idx_L] for
LeftSide / y[idx_R] for RightSide instead of the uniform y[end] the rest
of the API uses at the right boundary.

Add the short-circuit, mirroring the InBounds + anchor-path implementations.
 preserves cyclic semantics for
:exclusive PeriodicBC. Updated 7 test_constant.jl assertions to reflect
the new closed contract (and the consistent side-flag collapse at the
boundary).
- Compress 4 copy-pasted 7-line right-edge short-circuit comments in
  constant_anchor.jl into one durable 2-line note per overload.
- Drop `PR refac/wrap_closed` references and `claudedocs/TODO/` paths
  from src comments (per repo convention, src comments stay PR-agnostic).
- Update `[first(x), last(x))` notation to `[first(x), last(x)]` in
  linear_oneshot.jl and test_periodic_bc.jl to match the closed domain.
…/ND hetero)

Adds five new `@testitem` blocks to `test_wrapextrap_closed_boundary.jl`
covering paths previously untested at `xq == last(x)` under WrapExtrap:

- ND hetero corner: Cubic×Cubic, Quadratic×Quadratic, Cubic×Linear,
  Cardinal×Cardinal (OnTheFly cell-local path), and 3D Cubic×Cubic×Cubic.
- Persistent (callable) interpolant at `last(x)` for every method family
  (Linear, Constant, Cubic, Quadratic, PCHIP, Cardinal, Akima, Hermite).
- Adjoint at `last(x)` lands at `f_bar[end]`, not `f_bar[1]` (every
  adjoint family, including data-dependent OnTheFly slope adjoints).
- Derivative at `last(x)` matches the last-segment slope (using
  asymmetric `exp(x)` data so first vs last-segment slopes differ).
- Zero-alloc guard on the new short-circuits (constant scalar oneshot,
  linear batch in-place), gated by `ALLOC_THRESHOLD` for LTS slack.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

FastInterpolations.jl Benchmarks

All benchmarks (50 total, click to expand)
Benchmark Current: e5ec222 Previous Imm. Ratio Grad. Ratio
10_nd_construct/bicubic_2d 37049.0 ns 37079.0 ns 0.999 0.985
10_nd_construct/bilinear_2d 626.2 ns 642.4 ns 0.975 0.929
10_nd_construct/tricubic_3d 354453.0 ns 357058.0 ns 0.993 1.02
10_nd_construct/trilinear_3d 1678.1 ns 1675.5 ns 1.002 0.912
11_nd_eval/bicubic_2d_batch 1434.7 ns 1435.7 ns 0.999 0.877
11_nd_eval/bicubic_2d_scalar 16.3 ns 16.4 ns 0.994 1.033
11_nd_eval/bilinear_2d_scalar 7.3 ns 7.3 ns 1.0 1.037
11_nd_eval/tricubic_3d_batch 3236.1 ns 3235.0 ns 1.0 0.957
11_nd_eval/tricubic_3d_scalar 33.9 ns 33.9 ns 1.0 0.989
11_nd_eval/trilinear_3d_scalar 13.7 ns 13.6 ns 1.007 1.054
12_cubic_eval_gridquery/range_random 4228.5 ns 4229.1 ns 1.0 0.961
12_cubic_eval_gridquery/range_sorted 4223.9 ns 4218.3 ns 1.001 0.961
12_cubic_eval_gridquery/vec_random 9564.5 ns 9568.7 ns 1.0 1.01
12_cubic_eval_gridquery/vec_sorted 3203.4 ns 3202.2 ns 1.0 1.0
13_nd_oneshot_gridquery/bicubic_2d_rand_rand 66794.9 ns 65405.2 ns 1.021 -
13_nd_oneshot_gridquery/bicubic_2d_sort_rand 63511.7 ns 62270.3 ns 1.02 -
13_nd_oneshot_gridquery/bicubic_2d_sort_sort 59437.2 ns 58260.8 ns 1.02 -
13_nd_oneshot_gridquery/bilinear_2d_rand_rand 15913.0 ns 19156.6 ns 0.831 -
13_nd_oneshot_gridquery/bilinear_2d_sort_rand 9353.3 ns 9749.4 ns 0.959 -
13_nd_oneshot_gridquery/bilinear_2d_sort_sort 5683.8 ns 5695.0 ns 0.998 -
14_series_oneshot_batch/constant_inplace_vec_k8_q1000_rand 17989.7 ns 19389.2 ns 0.928 -
14_series_oneshot_batch/linear_inplace_vec_k8_q1000_rand 18919.4 ns 18956.4 ns 0.998 -
1_cubic_oneshot/q00001 537.4 ns 538.2 ns 0.998 1.152
1_cubic_oneshot/q10000 43543.4 ns 43531.4 ns 1.0 0.7
2_cubic_construct/g0100 1382.0 ns 1397.4 ns 0.989 1.005
2_cubic_construct/g1000 12715.7 ns 12777.9 ns 0.995 0.962
3_cubic_eval/q00001 20.0 ns 19.6 ns 1.02 0.9
3_cubic_eval/q00100 444.4 ns 443.2 ns 1.003 0.958
3_cubic_eval/q10000 42680.8 ns 42628.6 ns 1.001 0.962
4_linear_oneshot/q00001 24.7 ns 24.6 ns 1.004 0.937
4_linear_oneshot/q10000 18709.9 ns 18739.0 ns 0.998 0.984
5_linear_construct/g0100 37.4 ns 49.2 ns 0.76 0.978
5_linear_construct/g1000 278.5 ns 268.7 ns 1.037 0.983
6_linear_eval/q00001 10.2 ns 10.4 ns 0.982 1.015
6_linear_eval/q00100 196.0 ns 195.8 ns 1.001 0.985
6_linear_eval/q10000 18452.5 ns 18470.5 ns 0.999 0.985
7_cubic_range/scalar_query 8.3 ns 8.3 ns 1.0 1.134
7_cubic_vec/scalar_query 10.4 ns 12.0 ns 0.867 0.956
8_cubic_multi/construct_s001_q100 637.8 ns 637.8 ns 1.0 1.122
8_cubic_multi/construct_s010_q100 4503.0 ns 4444.5 ns 1.013 0.994
8_cubic_multi/construct_s100_q100 39994.8 ns 39977.6 ns 1.0 0.963
8_cubic_multi/eval_s001_q100 819.3 ns 810.9 ns 1.01 1.125
8_cubic_multi/eval_s010_q100 1798.4 ns 1796.2 ns 1.001 1.038
8_cubic_multi/eval_s010_q100_scalar_loop 2285.5 ns 2306.7 ns 0.991 0.968
8_cubic_multi/eval_s100_q100 11503.5 ns 11349.2 ns 1.014 0.986
8_cubic_multi/eval_s100_q100_scalar_loop 3340.3 ns 3355.3 ns 0.996 0.977
9_nd_oneshot/bicubic_2d 45526.2 ns 44060.2 ns 1.033 1.14
9_nd_oneshot/bilinear_2d 545.6 ns 539.0 ns 1.012 0.949
9_nd_oneshot/tricubic_3d 432014.8 ns 424975.7 ns 1.017 1.154
9_nd_oneshot/trilinear_3d 1032.9 ns 1036.9 ns 0.996 0.959

⚠️ Performance Regression Confirmed ⚠️

After re-running 6 flagged benchmark(s) 10 time(s), 6 regression(s) confirmed.

Benchmark Current Previous Imm. Ratio Grad. Ratio Tier
1_cubic_oneshot/q00001 537.4 ns 538.2 ns 0.998 1.152 gradual
7_cubic_range/scalar_query 8.3 ns 8.3 ns 1.0 1.134 gradual
8_cubic_multi/construct_s001_q100 637.8 ns 637.8 ns 1.0 1.122 gradual
8_cubic_multi/eval_s001_q100 819.3 ns 810.9 ns 1.01 1.125 gradual
9_nd_oneshot/bicubic_2d 45526.2 ns 44060.2 ns 1.033 1.14 gradual
9_nd_oneshot/tricubic_3d 432014.8 ns 424975.7 ns 1.017 1.154 gradual

Thresholds: immediate > 1.1x (vs latest master), gradual > 1.1x (vs sliding window)

This comment was automatically generated by Benchmark workflow.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes WrapExtrap seam handling so exact right-boundary queries are treated as in-domain ([first(x), last(x)]) rather than wrapping to the left edge, and updates tests/docs around that behavior.

Changes:

  • Updates _wrap_to_domain, batch domain checks, linear batch fast path, and constant right-edge handling to use closed-domain semantics.
  • Adjusts anchor and extrapolation documentation from half-open to closed-domain wording.
  • Adds and updates tests covering boundary values, periodic invariants, ND corners, adjoints, derivatives, and allocation behavior.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/core/periodic.jl Changes wrap fast path to include x_max and documents closed-domain behavior.
src/core/utils.jl Makes WrapExtrap share the closed-domain batch InBounds promotion.
src/core/eval_ops.jl Updates WrapExtrap public documentation for closed-domain semantics.
src/core/anchor_common.jl Keeps exact x_max unwrapped during anchored query construction.
src/constant/constant_oneshot.jl Adds right-edge short-circuit for scalar constant WrapExtrap.
src/constant/constant_anchor.jl Uses anchor right index for right-edge constant anchor evaluation.
src/linear/linear_oneshot.jl Allows linear batch fast path when qmax == x_max.
src/linear/linear_anchor.jl Updates linear anchor docs for closed wrap domain.
src/cubic/cubic_anchor.jl Updates cubic anchor docs/comments for closed wrap domain.
src/cubic/cubic_adjoint.jl Updates periodic cubic adjoint wrap documentation.
src/quadratic/quadratic_anchor.jl Updates quadratic anchor docs for closed wrap domain.
test/test_wrapextrap_closed_boundary.jl Adds broad regression coverage for exact right-boundary WrapExtrap behavior.
test/test_wrap_to_domain_closed.jl Adds focused _wrap_to_domain and _check_domain closed-boundary tests.
test/test_periodic_bc.jl Updates periodic comments for closed-domain semantics.
test/test_hermite_1d.jl Tightens Hermite right-boundary WrapExtrap expectation.
test/test_constant.jl Updates constant interpolation seam expectations for closed WrapExtrap.
test/test_constant_periodic.jl Updates exclusive periodic series seam comments/assertions.
test/test_anchor_common.jl Updates anchor-location tests for no-wrap at exact x_max.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/core/periodic.jl Outdated
Comment thread src/core/eval_ops.jl
@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.43%. Comparing base (5de9ea1) to head (e5ec222).

Files with missing lines Patch % Lines
src/core/periodic.jl 50.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #142      +/-   ##
==========================================
- Coverage   96.43%   96.43%   -0.01%     
==========================================
  Files         143      143              
  Lines       12001    11994       -7     
==========================================
- Hits        11573    11566       -7     
  Misses        428      428              
Files with missing lines Coverage Δ
src/constant/constant_anchor.jl 98.57% <100.00%> (ø)
src/constant/constant_oneshot.jl 100.00% <100.00%> (ø)
src/core/anchor_common.jl 100.00% <100.00%> (ø)
src/core/eval_ops.jl 92.10% <ø> (ø)
src/core/utils.jl 88.99% <ø> (-0.76%) ⬇️
src/cubic/cubic_adjoint.jl 100.00% <ø> (ø)
src/cubic/cubic_anchor.jl 97.82% <ø> (ø)
src/linear/linear_anchor.jl 97.40% <ø> (ø)
src/linear/linear_oneshot.jl 100.00% <100.00%> (ø)
src/quadratic/quadratic_anchor.jl 100.00% <ø> (ø)
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mgyoo86 mgyoo86 merged commit b325b41 into master May 18, 2026
13 of 14 checks passed
@mgyoo86 mgyoo86 deleted the fix/closed_domain branch May 19, 2026 03:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants