Skip to content

(refac): Relax periodic endpoint check + PeriodicBC(check=false) opt-out#94

Open
mgyoo86 wants to merge 5 commits intomasterfrom
refac/periodic_condition
Open

(refac): Relax periodic endpoint check + PeriodicBC(check=false) opt-out#94
mgyoo86 wants to merge 5 commits intomasterfrom
refac/periodic_condition

Conversation

@mgyoo86
Copy link
Member

@mgyoo86 mgyoo86 commented Mar 25, 2026

Summary

Replace strict == in periodic endpoint validation with isapprox + atol = 8eps(T) noise floor. Add PeriodicBC(check=false) to skip validation for scaled data.

Motivation

y = sin.(range(0, 2π, 101))
cubic_interp(x, y, 0.5; bc=PeriodicBC())  # was ERROR: y[1] != y[end]
# sin(0) = 0.0, sin(2π) = -2.45e-16 — now passes via atol noise floor

Design

Endpoint dispatch

Element type Comparison atol
AbstractFloat isapprox 8eps(T) (compile-time folded)
Complex{<:AbstractFloat} isapprox 8eps(real(T))
Other _PromotableValue isapprox default
Duck types strict == N/A

PeriodicBC(check=false)

For scaled data (1e6 .* sin.(x)) where noise exceeds 8eps. check is a type parameter (not field) — PeriodicBC stays zero-size singleton, no allocation regression.

mgyoo86 added 5 commits March 24, 2026 20:37
Three-tier dispatch for _check_periodic_endpoints:
- AbstractFloat / Complex{<:AbstractFloat}: isapprox with atol=8eps(T)
  (compile-time folded, zero overhead). Covers both rtol-relative diffs
  and near-zero noise floor (sin(0) vs sin(2π)).
- Other _PromotableValue (Integer, Rational): isapprox default tolerances.
- Duck types: strict == (isapprox semantics not guaranteed).
Add `check::Bool` field to PeriodicBC (default true). When false,
skips _check_periodic_endpoints entirely — useful for scaled periodic
data (e.g. 1e6*sin.(x)) where noise exceeds the 8eps atol floor.

Also adds atol=8eps(T) noise floor for AbstractFloat/Complex endpoints,
covering direct evaluations like sin.(x) where endpoints are near zero.
Error message now mentions check=false as an escape hatch.

Three-tier endpoint validation:
- AbstractFloat/Complex: isapprox with atol=8eps(T) (compile-time folded)
- Other _PromotableValue: isapprox default tolerances
- Duck types: strict ==
…singleton

check::Bool field made PeriodicBC sizeof 1 (was 0), causing 48-byte
allocation regression in ND one-shot paths. Move to type parameter C
so PeriodicBC{E, P, C} remains a zero-size singleton when period is
Nothing, matching pre-change behavior.
Replace == with ≈ in all periodic BC documentation. Document v0.4.0–v0.4.5
strict equality vs v0.4.6+ isapprox behavior. Add check=false and scaled
data guidance.
Write LocalPreferences.toml before test runs to activate pool
bounds checking during CI. Applies to both main test and extension
test jobs.
@github-actions
Copy link
Contributor

FastInterpolations.jl Benchmarks

All benchmarks (42 total, click to expand)
Benchmark Current: 36661be Previous Imm. Ratio Grad. Ratio
10_nd_construct/bicubic_2d 37229.0 ns 36769.0 ns 1.013 0.845
10_nd_construct/bilinear_2d 617.8 ns 564.5 ns 1.094 0.981
10_nd_construct/tricubic_3d 359782.0 ns 355444.0 ns 1.012 0.914
10_nd_construct/trilinear_3d 1785.7 ns 1641.3 ns 1.088 0.948
11_nd_eval/bicubic_2d_batch 1578.0 ns 1582.9 ns 0.997 0.943
11_nd_eval/bicubic_2d_scalar 15.7 ns 15.7 ns 1.0 0.572
11_nd_eval/bilinear_2d_scalar 11.2 ns 11.3 ns 0.992 0.449
11_nd_eval/tricubic_3d_batch 3318.2 ns 3310.2 ns 1.002 0.957
11_nd_eval/tricubic_3d_scalar 32.9 ns 32.9 ns 1.0 0.689
11_nd_eval/trilinear_3d_scalar 18.4 ns 18.6 ns 0.989 0.563
12_cubic_eval_gridquery/range_random 4272.8 ns 4234.7 ns 1.009 0.863
12_cubic_eval_gridquery/range_sorted 4222.9 ns 4226.3 ns 0.999 0.855
12_cubic_eval_gridquery/vec_random 9550.8 ns 9111.2 ns 1.048 0.991
12_cubic_eval_gridquery/vec_sorted 3211.8 ns 3196.6 ns 1.005 0.898
1_cubic_oneshot/q00001 453.9 ns 448.8 ns 1.011 1.018
1_cubic_oneshot/q10000 61730.3 ns 61695.3 ns 1.001 0.989
2_cubic_construct/g0100 1302.2 ns 1305.4 ns 0.998 1.035
2_cubic_construct/g1000 12596.5 ns 12622.6 ns 0.998 1.019
3_cubic_eval/q00001 17.6 ns 18.3 ns 0.962 0.512
3_cubic_eval/q00100 443.2 ns 441.4 ns 1.004 0.821
3_cubic_eval/q10000 42649.7 ns 42616.7 ns 1.001 0.845
4_linear_oneshot/q00001 22.3 ns 22.2 ns 1.004 0.737
4_linear_oneshot/q10000 18623.7 ns 18628.8 ns 1.0 0.555
5_linear_construct/g0100 35.9 ns 36.1 ns 0.994 0.877
5_linear_construct/g1000 243.0 ns 238.5 ns 1.019 0.856
6_linear_eval/q00001 10.1 ns 9.8 ns 1.031 0.555
6_linear_eval/q00100 198.2 ns 196.8 ns 1.007 0.432
6_linear_eval/q10000 18602.7 ns 18541.6 ns 1.003 0.437
7_cubic_range/scalar_query 8.0 ns 7.9 ns 1.013 0.338
7_cubic_vec/scalar_query 10.5 ns 11.1 ns 0.947 0.853
8_cubic_multi/construct_s001_q100 580.7 ns 577.5 ns 1.006 1.009
8_cubic_multi/construct_s010_q100 4361.2 ns 4370.0 ns 0.998 0.989
8_cubic_multi/construct_s100_q100 39635.0 ns 39604.1 ns 1.001 0.978
8_cubic_multi/eval_s001_q100 1046.1 ns 1043.5 ns 1.002 0.793
8_cubic_multi/eval_s010_q100 2037.6 ns 2021.8 ns 1.008 0.866
8_cubic_multi/eval_s010_q100_scalar_loop 2562.2 ns 2567.2 ns 0.998 0.683
8_cubic_multi/eval_s100_q100 11952.3 ns 11653.8 ns 1.026 0.938
8_cubic_multi/eval_s100_q100_scalar_loop 3475.5 ns 3509.5 ns 0.99 0.728
9_nd_oneshot/bicubic_2d 37527.1 ns 36491.2 ns 1.028 0.869
9_nd_oneshot/bilinear_2d 1029.7 ns 1035.3 ns 0.995 0.972
9_nd_oneshot/tricubic_3d 364398.6 ns 365402.0 ns 0.997 0.925
9_nd_oneshot/trilinear_3d 1647.1 ns 1649.0 ns 0.999 0.975

1 benchmark(s) were flagged but verified as CI noise after 10 re-run(s)

This comment was automatically generated by Benchmark workflow.

Copy link

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

Relaxes periodic endpoint validation for cubic splines by switching from strict equality to isapprox with an atol = 8eps(T) noise floor, and introduces an opt-out via PeriodicBC(check=false) to skip endpoint validation for scaled/noisy periodic signals.

Changes:

  • Implement type-dispatched periodic endpoint checks (AbstractFloat/Complex use isapprox with atol=8eps, others keep strict ==) and add PeriodicBC(check=false) (type parameter) to bypass validation.
  • Update 1D and ND cubic build paths to honor the new check flag; adjust error messages and docs to describe the relaxed behavior.
  • Expand tests to cover near-zero tolerance behavior and the check=false opt-out; tweak an ND allocation threshold test and enable AdaptiveArrayPools runtime checks in CI.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/core/bc_types.jl Adds check::Bool as a type parameter to PeriodicBC and a periodic_check accessor.
src/core/periodic.jl Implements new endpoint validation dispatch + check=false gating; updates error message text.
src/cubic/cubic_oneshot.jl Threads bc into _check_periodic_endpoints so check=false is honored in one-shot periodic solve.
src/cubic/cubic_interpolant.jl Threads bc into _check_periodic_endpoints during periodic interpolant build.
src/cubic/nd/cubic_nd_build.jl Updates ND periodic data validation to use isapprox/== based on element type and gates checks on periodic_check.
test/test_periodic_bc.jl Adds coverage for relaxed endpoint checks, scaled-data failure, and PeriodicBC(check=false) + @inferred type stability.
test/test_cubic_nd_oneshot.jl Loosens a mixed-BC allocation assertion with an explanatory comment.
docs/src/migration/to_v0.4.md Documents the change from strict equality to isapprox and mentions check=false for scaled data.
docs/src/interpolation/cubic.md Updates PeriodicBC documentation to reflect requirement.
docs/src/boundary-conditions/periodicbc.md Updates periodic data requirement and adds guidance for scaled data / check=false.
docs/src/boundary-conditions/overview.md Updates the PeriodicBC row to reflect rather than ==.
.github/workflows/CI.yml Enables AdaptiveArrayPools runtime checks during CI test runs.

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

Comment on lines +261 to +267
elseif Tv <: Complex && Tv.parameters[1] <: AbstractFloat
RT = Tv.parameters[1]
check = quote
v1 = @inbounds data[$(first_idx...)]
vn = @inbounds data[$(last_idx...)]
isapprox(v1, vn; atol = 8 * eps($RT)) || _throw_periodic_nd_error($D, v1, vn)
end
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

In the @generated periodic ND validation, the branch elseif Tv <: Complex && Tv.parameters[1] <: AbstractFloat is unsafe: if Tv is a UnionAll/abstract type (e.g. an AbstractArray{Complex} with eltype == Complex), Tv.parameters will throw during code generation. Use a subtype check that doesn’t require accessing .parameters (or guard with isconcretetype(Tv) / Tv isa DataType) before indexing the parameter, so this path can’t error at compile time.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +108
"Tip: set y[end] = y[1] explicitly, use " *
"PeriodicBC(endpoint=:exclusive) if your data does not repeat the first point, " *
"or PeriodicBC(check=false) to skip this validation."
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The endpoint mismatch error message always suggests PeriodicBC(check=false), but _throw_periodic_endpoint_error is also reachable from call sites that don’t have a PeriodicBC in scope (e.g. cache-based APIs that call _check_periodic_endpoints(y) directly). In those cases the tip isn’t actionable and may confuse users. Consider either (a) only mentioning check=false when the caller passes a PeriodicBC, or (b) updating the relevant call sites/APIs to thread the PeriodicBC through so the opt-out is actually available everywhere this error can be raised.

Suggested change
"Tip: set y[end] = y[1] explicitly, use " *
"PeriodicBC(endpoint=:exclusive) if your data does not repeat the first point, " *
"or PeriodicBC(check=false) to skip this validation."
"Tip: set y[end] = y[1] explicitly, or use " *
"PeriodicBC(endpoint=:exclusive) if your data does not repeat the first point."

Copilot uses AI. Check for mistakes.
Comment on lines +208 to 215
struct PeriodicBC{E, P, C} <: AbstractBC
period::P # Nothing or AbstractFloat
function PeriodicBC{E, P}(period::P) where {E, P}
function PeriodicBC{E, P, C}(period::P) where {E, P, C}
E isa Symbol || error("PeriodicBC type parameter E must be a Symbol")
E in (:inclusive, :exclusive) || error("PeriodicBC type parameter E must be :inclusive or :exclusive")
return new{E, P}(period)
C isa Bool || error("PeriodicBC type parameter C must be a Bool")
return new{E, P, C}(period)
end
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

PeriodicBC now has three type parameters ({E,P,C}), but the surrounding docstring section (signature line and “Type Parameters” list above) still describes it as PeriodicBC{E,P} and doesn’t mention the new C/check parameter. Please update the docstring to match the actual type/constructor so generated docs and IDE help aren’t misleading.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to 71
!!! note "Scaled near-zero endpoints"
`atol = 8eps` covers direct evaluations (e.g., `sin.(x)`), but not scaled
variants (e.g., `1e6 .* sin.(x)` where noise ≈ 1e6·eps). For those cases,
set `y[end] = y[1]` explicitly.

Throws `ArgumentError` if endpoints differ.
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

In the docstring note on “Scaled near-zero endpoints”, the guidance currently only says to set y[end] = y[1] explicitly, but the PR also introduces PeriodicBC(check=false) as the supported opt-out for this case. Consider mentioning check=false here as well to keep the docstring consistent with the new API and the updated error message tip.

Suggested change
!!! note "Scaled near-zero endpoints"
`atol = 8eps` covers direct evaluations (e.g., `sin.(x)`), but not scaled
variants (e.g., `1e6 .* sin.(x)` where noise 1e6·eps). For those cases,
set `y[end] = y[1]` explicitly.
Throws `ArgumentError` if endpoints differ.
!!! note "Scaled near-zero endpoints"
`atol = 8eps` covers direct evaluations (e.g., `sin.(x)`), but not scaled
variants (e.g., `1e6 .* sin.(x)` where noise 1e6·eps). For those cases,
either set `y[end] = y[1]` explicitly, or construct the boundary condition as
`PeriodicBC(check=false)` to opt out of this validation when you know the data
are already periodic.
Throws `ArgumentError` if endpoints differ.

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 84.61538% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.41%. Comparing base (4e47a3d) to head (36661be).

Files with missing lines Patch % Lines
src/core/periodic.jl 78.57% 3 Missing ⚠️
src/cubic/nd/cubic_nd_build.jl 80.00% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #94      +/-   ##
==========================================
- Coverage   96.46%   96.41%   -0.06%     
==========================================
  Files         103      103              
  Lines        8919     8944      +25     
==========================================
+ Hits         8604     8623      +19     
- Misses        315      321       +6     
Files with missing lines Coverage Δ
src/core/bc_types.jl 98.29% <100.00%> (+0.02%) ⬆️
src/cubic/cubic_interpolant.jl 98.09% <100.00%> (ø)
src/cubic/cubic_oneshot.jl 99.05% <100.00%> (ø)
src/core/periodic.jl 94.83% <78.57%> (-1.65%) ⬇️
src/cubic/nd/cubic_nd_build.jl 96.36% <80.00%> (-1.71%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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