Skip to content

fix(phs): ship PHS as experimental and reject inputs it cannot evaluate - #222

Merged
mgyoo86 merged 1 commit into
masterfrom
fix/phs-release-gates
Oct 14, 2026
Merged

mgyoo86 merged 1 commit into
masterfrom
fix/phs-release-gates

Conversation

@mgyoo86

@mgyoo86 mgyoo86 commented Oct 13, 2026

Copy link
Copy Markdown
Member

Follow-up to #136 and #209.

Summary

Problem: PHS (merged in #136, not yet released) accepted inputs it cannot evaluate correctly and returned wrong values without any error, and it was exported as public API although parts of it are still being reworked.

Fix: PHS moves to a new FastInterpolations.Experimental submodule (outside SemVer), and inputs it cannot evaluate correctly now raise an error. The evaluator and the defaults are unchanged.

Input / query Before After
non-uniform Vector axis wrong values ArgumentError
stencil spanning more nodes than an axis wrong values near the boundary ArgumentError
blend_factor leaving cell centres uncovered (incl. the 4-D default) 0.0 ArgumentError
Clamp/Wrap/Extend extrapolation 0.0 out of domain ArgumentError
call-time extrap= override silently ignored only InBounds(), as for the other interpolants
derivative of order ≥ 3 0.0 ArgumentError
log_reference + 2nd derivative with a wide blend (e.g. the one-shot default 2.0 in 3-D) BoundsError (out-of-bounds write without bounds checks) ArgumentError
batch query outside the domain garbage / 0.0 DomainError, like scalar queries
complex / unit / dual data built, then an internal error on first evaluation (InexactError, MethodError); unit grids failed inside the build ArgumentError at construction

Usage

This adds the package's first Experimental submodule: its features are usable but outside SemVer, so they may change in any release. Users opt in explicitly; graduating a feature later only adds an export to the main module, and the Experimental names keep working.

using FastInterpolations
using FastInterpolations.Experimental                # phs_interp, phs_interp!, PHSInterpolantND

x = range(0.0, 1.0, 21); y = range(0.0, 2.0, 41)
data = [sin(2a) * cos(b) for a in x, b in y]

itp = phs_interp((x, y), data)
itp((0.33, 0.71))                                    # value
itp((0.33, 0.71); deriv = (DerivOp(1), DerivOp(0)))  # ∂/∂x

phs_interp((x .^ 2, y), data)                        # ArgumentError: non-uniform axis
phs_interp((x, y), data; extrap = ClampExtrap())     # ArgumentError: unsupported extrapolation

Changes

  • src/experimental.jl: new submodule re-exporting phs_interp, phs_interp!, PHSInterpolantND; removed from the main exports.
  • src/phs/phs_validate.jl: the input gates; phs_interpolant.jl calls them in the constructor and at the scalar/batch entry, before any pool buffer is acquired.
  • Docs and README marked experimental. Scripts load PHS from Experimental (the blend study no longer runs blend_factor = 0.5, now rejected in 3-D); the CI benchmark calls FastInterpolations.phs_interp, which works on both master and this branch.

Not covered

  • Known wrong values that a gate cannot catch — first derivatives exactly on a grid node, discontinuous log-transform second derivatives — and default tuning (stencil_size, blend_factor): follow-up PRs.
  • True non-uniform grids, Clamp/Wrap extrapolation, derivatives of order ≥ 3, complex data, gradient/hessian, GriddedQuery with N ≥ 2: still unsupported, tracked as @test_broken pins.

Tests

  • New test/test_phs_gates.jl (its rejection checks fail on master); §O2 ×4 and §R4 pins promoted in test/test_phs_broken_pins.jl.

PHS moves out of the main exports into a new `FastInterpolations.Experimental`
submodule (not covered by SemVer; README and docs say so). Inputs the
implementation cannot evaluate correctly now raise an error instead of
returning wrong values. The evaluator and the defaults are unchanged.

- New src/phs/phs_validate.jl with the gates, called from the constructor and
  the scalar/batch entry points before any pool buffer is acquired: real float
  grids and data only; uniform axes (ranges as is, vectors up to rounding);
  stencil_size ≥ 3 in 1-D/2-D (2 for N ≥ 3) and a stencil whose per-axis
  extent fits every axis; blend_factor finite and covering cell centres
  (rejects the 4-D default 1.0, which evaluated to 0.0 there); NoExtrap,
  FillExtrap or InBounds only; derivatives up to total order 2; log_reference
  with second derivatives limited to a blend candidate box of 27 nodes (the
  kernel's fixed buffer, which overflowed with a BoundsError before).
- A call-time `extrap` override is validated like the other interpolants
  (only InBounds() passes) instead of being silently ignored.
- Batch queries check the domain like scalar queries (NoExtrap → DomainError).
- Docs and README mark PHS experimental; scripts and the CI benchmark load it
  from `Experimental`.
- Tests: new test_phs_gates.jl; pins §O2 ×4 and §R4 promoted.
@github-actions

Copy link
Copy Markdown
Contributor

FastInterpolations.jl Benchmarks

🚨 Regression baseline is a different CPU

CPU
This PR run `znver3
Master baseline (latest commit) `icelake-server

The Previous / Imm. Ratio / Grad. Ratio columns compare against master history, but master's most recent commit was benchmarked on a different CPU — so a naive comparison is cross-CPU and unreliable. The ratios below use only same-znver3|4c history and are blank if master has none on this CPU yet.

All benchmarks (71 total, click to expand)
Benchmark Current: d7fc873 Previous Imm. Ratio Grad. Ratio
10_nd_construct/bicubic_2d 35397 ns 36669.0 ns 0.965 0.932
10_nd_construct/bilinear_2d 623.56 ns 607.5 ns 1.026 1.032
10_nd_construct/phs_2d 1128669 ns 1111486.5 ns 1.015 -
10_nd_construct/phs_3d 17536589.4 ns 17814900.4 ns 0.984 -
10_nd_construct/tricubic_3d 313809 ns 334816.0 ns 0.937 0.875
10_nd_construct/trilinear_3d 1849.68 ns 1867.9 ns 0.99 1.059
11_nd_eval/bicubic_2d_batch 1430.7 ns 1423.6 ns 1.005 1.027
11_nd_eval/bicubic_2d_scalar 15.92 ns 15.8 ns 1.006 0.892
11_nd_eval/bilinear_2d_scalar 8.51 ns 8.5 ns 1.0 0.991
11_nd_eval/phs_2d_batch 38753.7 ns 38495.1 ns 1.007 -
11_nd_eval/phs_2d_scalar 300.36 ns 300.7 ns 0.999 -
11_nd_eval/phs_3d_batch 238841.9 ns 238726.8 ns 1.0 -
11_nd_eval/phs_3d_scalar 2283.88 ns 2263.6 ns 1.009 -
11_nd_eval/tricubic_3d_batch 3086.7 ns 3072.8 ns 1.005 0.945
11_nd_eval/tricubic_3d_scalar 35.26 ns 34.1 ns 1.035 1.036
11_nd_eval/trilinear_3d_scalar 12.82 ns 13.5 ns 0.948 0.921
12_cubic_eval_gridquery/range_random 2858.16 ns 2876.4 ns 0.994 0.675
12_cubic_eval_gridquery/range_sorted 2847.96 ns 2871.8 ns 0.992 0.675
12_cubic_eval_gridquery/vec_random 8354.06 ns 8285.7 ns 1.008 0.913
12_cubic_eval_gridquery/vec_sorted 2608.5 ns 2606.3 ns 1.001 0.809
13_nd_oneshot_gridquery/bicubic_2d_rand_rand 60516.4 ns 60304.8 ns 1.004 0.929
13_nd_oneshot_gridquery/bicubic_2d_sort_rand 56974.1 ns 56775.2 ns 1.004 0.918
13_nd_oneshot_gridquery/bicubic_2d_sort_sort 55299.6 ns 55050.0 ns 1.005 0.918
13_nd_oneshot_gridquery/bilinear_2d_rand_rand 16881.52 ns 16483.6 ns 1.024 1.027
13_nd_oneshot_gridquery/bilinear_2d_sort_rand 10068.48 ns 8824.3 ns 1.141 1.08
13_nd_oneshot_gridquery/bilinear_2d_sort_sort 5504.92 ns 5539.6 ns 0.994 1.076
14_series_oneshot_batch/constant_inplace_vec_k8_q1000_rand 13584.4 ns 13582.5 ns 1.0 0.928
14_series_oneshot_batch/linear_inplace_vec_k8_q1000_rand 13857 ns 13843.9 ns 1.001 0.963
15_gridded_query/cubic_oneshot_2d_40x32 41184.2 ns 42532.6 ns 0.968 0.929
15_gridded_query/cubic_persistent_2d_40x32 8313.6 ns 8328.6 ns 0.998 0.976
15_gridded_query/linear_oneshot_2d_40x32 1523.26 ns 1519.6 ns 1.002 0.988
15_gridded_query/linear_oneshot_3d_12x10x8 1922.22 ns 1922.2 ns 1.0 0.955
15_gridded_query/linear_persistent_2d_40x32 1493.4 ns 1497.4 ns 0.997 0.992
15_gridded_query/linear_persistent_3d_12x10x8 1862.28 ns 1874.7 ns 0.993 0.935
16_phs/construct_g0100 36386.22 ns 36642.8 ns 0.993 -
16_phs/construct_g1000 35991.5 ns 36293.9 ns 0.992 -
16_phs/eval_q00001 113.21 ns 111.3 ns 1.017 -
16_phs/eval_q00100 11877.26 ns 11955.4 ns 0.993 -
16_phs/eval_q10000 1172139.3 ns 1175100.3 ns 0.997 -
16_phs/oneshot_q00001 37082.54 ns 37900.1 ns 0.978 -
16_phs/oneshot_q10000 1804455.8 ns 1782651.5 ns 1.012 -
1_cubic_oneshot/q00001 456.46 ns 454.4 ns 1.004 1.002
1_cubic_oneshot/q10000 29640.6 ns 29592.4 ns 1.002 0.683
2_cubic_construct/g0100 1297.44 ns 1297.8 ns 1.0 0.99
2_cubic_construct/g1000 12580.6 ns 13302.9 ns 0.946 0.998
3_cubic_eval/q00001 9.61 ns 9.6 ns 1.0 0.47
3_cubic_eval/q00100 305.38 ns 305.4 ns 1.0 0.69
3_cubic_eval/q10000 28750.9 ns 29054.4 ns 0.99 0.674
4_linear_oneshot/q00001 27.55 ns 26.7 ns 1.03 1.041
4_linear_oneshot/q10000 18570.8 ns 18576.7 ns 1.0 1.007
5_linear_construct/g0100 35.66 ns 36.9 ns 0.967 0.97
5_linear_construct/g1000 297.15 ns 288.0 ns 1.032 1.088
6_linear_eval/q00001 8.71 ns 8.7 ns 1.0 0.827
6_linear_eval/q00100 205.18 ns 200.0 ns 1.026 1.046
6_linear_eval/q10000 18506.7 ns 18379.4 ns 1.007 1.017
7_cubic_range/scalar_query 5.61 ns 5.7 ns 0.984 0.697
7_cubic_vec/scalar_query 9.71 ns 8.5 ns 1.141 0.882
8_cubic_multi/construct_s001_q100 504.54 ns 509.1 ns 0.991 0.986
8_cubic_multi/construct_s010_q100 4267.6 ns 4515.9 ns 0.945 0.995
8_cubic_multi/construct_s100_q100 39592.3 ns 41612.9 ns 0.951 1.001
8_cubic_multi/eval_s001_q100 428.8 ns 429.6 ns 0.998 0.735
8_cubic_multi/eval_s010_q100 1303.44 ns 1319.3 ns 0.988 0.891
8_cubic_multi/eval_s010_q100_scalar_loop 2188.9 ns 2185.7 ns 1.001 0.943
8_cubic_multi/eval_s100_q100 9961.6 ns 10490.6 ns 0.95 0.977
8_cubic_multi/eval_s100_q100_scalar_loop 3440.5 ns 3365.3 ns 1.022 1.012
9_nd_oneshot/bicubic_2d 34294.3 ns 34441.5 ns 0.996 0.915
9_nd_oneshot/bilinear_2d 463.06 ns 451.0 ns 1.027 1.024
9_nd_oneshot/phs_2d 1373451 ns 1359800.9 ns 1.01 -
9_nd_oneshot/phs_3d 20154574.3 ns 20277302.2 ns 0.994 -
9_nd_oneshot/tricubic_3d 319840.7 ns 322555.7 ns 0.992 0.86
9_nd_oneshot/trilinear_3d 853.6 ns 844.6 ns 1.011 0.931

⚠️ Performance Regression Confirmed ⚠️

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

Benchmark Current Previous Imm. Ratio Grad. Ratio Tier
13_nd_oneshot_gridquery/bilinear_2d_sort_rand 10068.48 ns 8824.3 ns 1.141 1.08 immediate
7_cubic_vec/scalar_query 9.71 ns 8.5 ns 1.141 0.882 immediate

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

Runner: znver3|4c — AMD EPYC 7763 64-Core Processor, julia 1.13.1. Times are min-merged and compared only against this same machine's history.

This comment was automatically generated by Benchmark workflow.

@codecov

codecov Bot commented Oct 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.31%. Comparing base (28b05fe) to head (d7fc873).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #222      +/-   ##
==========================================
+ Coverage   96.29%   96.31%   +0.01%     
==========================================
  Files         172      173       +1     
  Lines       15484    15560      +76     
==========================================
+ Hits        14910    14986      +76     
  Misses        574      574              
Files with missing lines Coverage Δ
src/FastInterpolations.jl 100.00% <ø> (ø)
src/phs/phs_interpolant.jl 100.00% <100.00%> (ø)
src/phs/phs_types.jl 100.00% <ø> (ø)
src/phs/phs_validate.jl 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mgyoo86
mgyoo86 merged commit 2391df6 into master Oct 14, 2026
26 checks passed
@mgyoo86

mgyoo86 commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

Hi @twilsonco,

I moved phs_interp and related stuff to under the new Experimental submodule, as described in this PR.
This way allows us to more freely change API or implementations of PHS interpolation going forward.
Once the PHS interpolation becomes more mature then we can move this back to the normal FastInterpolations module.

@twilsonco

Copy link
Copy Markdown
Contributor

That sounds like a good move. Thanks!

@mgyoo86

mgyoo86 commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

@twilsonco
FYI, the new version (v0.4.20), including PHS interpolation, is finally released 🎉
Please give it a try and let me know if you find any issues.

Thank you again for your patience and great contribution for this new feature!

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