Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/src/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ Search policies control how the interpolant finds the correct interval for a que
AbstractSearchPolicy
AutoSearch
Binary
HintedBinary
Linear
LinearBinary
```
Expand Down
10 changes: 5 additions & 5 deletions docs/src/guides/search/hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Hints allow you to persist search state across calls, enabling O(1) lookup for s

## Basic Usage

For stateful policies (`HintedBinary`, `Linear`, `LinearBinary`), provide an external `Ref{Int}` to persist the hint:
For stateful policies (`Linear`, `LinearBinary`), provide an external `Ref{Int}` to persist the hint:

```julia
itp = linear_interp(x, y)
Expand All @@ -30,18 +30,18 @@ External hints are particularly useful for:

## Auto-Upgrade Behavior

When you provide a `hint` argument with `Binary()`, the search automatically upgrades to `HintedBinary` behavior:
When you provide a `hint` argument with `Binary()`, the search automatically upgrades to `LinearBinary{2}`:

```julia
hint = Ref(1)
val = itp(0.5; search=Binary(), hint=hint) # auto-upgrades to HintedBinary
val = itp(0.5; search=Binary(), hint=hint) # auto-upgrades to LinearBinary{2}
```

This also works with the default `AutoSearch()` — scalar queries resolve to `Binary()`, and if a hint is provided, they auto-upgrade to `HintedBinary`:
This also works with the default `AutoSearch()` — scalar queries resolve to `Binary()`, and if a hint is provided, they auto-upgrade to `LinearBinary{2}`:

```julia
hint = Ref(1)
val = itp(0.5; hint=hint) # AutoSearch → Binary() → auto-upgrades to HintedBinary
val = itp(0.5; hint=hint) # AutoSearch → Binary() → auto-upgrades to LinearBinary{2}
```

Without a hint, binary search is used (no hint tracking).
Expand Down
3 changes: 1 addition & 2 deletions docs/src/guides/search/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ nothing # hide
|:-------|:---------|:-----------|:--------------|
| [`AutoSearch()`](@ref search_policies) | **General use (default)** — adapts per query type | Delegates to Binary/LinearBinary | ✓ Stateless |
| [`Binary()`](@ref search_policies) | Random access (explicit) | O(log n) | ✓ Stateless |
| [`HintedBinary()`](@ref search_policies) | Repeated queries in same region | O(1) hit, O(log n) miss | ✓ With hint |
| [`LinearBinary()`](@ref search_policies) | **Monotonic queries (explicit)** | O(1) local, O(log n) fallback | ✓ With hint |
| [`Linear()`](@ref search_policies) | Close + monotonic queries (expert) | O(1) amortized | ✓ With hint |

Expand All @@ -50,7 +49,7 @@ nothing # hide

- **General use / unknown pattern** → `AutoSearch()` ✅ **default** — adapts scalar→`Binary()`, vector→`LinearBinary()`
- **Known random access** → `Binary()` (explicit; skips AutoSearch dispatch)
- **Queries cluster in same region** → `HintedBinary()`
- **Queries cluster in same region** → `LinearBinary(linear_window=0)` (hint check only)
- **Known monotonic queries (sorted, streaming, ODE)** → `LinearBinary()` (explicit)
- **Strictly monotonic, performance-critical** → `Linear()` (benchmark first! see below)

Expand Down
32 changes: 6 additions & 26 deletions docs/src/guides/search/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,6 @@ val = itp(0.5; search=Binary()) # explicit Binary

---

## HintedBinary

Caches the last-found interval. If the next query falls in the same interval, lookup is O(1).

**Complexity**: O(1) cache hit, O(log n) cache miss

**When to use**:
- Queries that cluster in the same region
- Monte Carlo sampling within a subregion
- Iterative refinement around a point

```julia
itp = linear_interp(x, y; search=HintedBinary())
for xi in query_points
val = itp(xi) # O(1) when consecutive queries hit same interval
end
```

**How it works**: Before binary search, checks if the query falls in the cached interval. If yes, returns immediately. If no, performs full binary search and updates the cache.

---

## Linear

Maximum-speed linear search for **strictly monotonic, closely-spaced queries**. Scans the grid sequentially one interval at a time from the hint until the target is found—no binary fallback, no window limit.
Expand Down Expand Up @@ -142,18 +120,20 @@ vals = itp(sorted_queries) # O(1) amortized for sorted input
You can tune the linear search window size before falling back to binary search:

```julia
LinearBinary() # default: linear_window=2
LinearBinary(linear_window=4) # moderate bound for known-sorted sequences
LinearBinary(linear_window=16) # larger bound for sparser-spaced sorted queries
LinearBinary() # default: linear_window=2
LinearBinary(linear_window=0) # hint check only, no walk (minimal random overhead)
LinearBinary(linear_window=4) # moderate bound for known-sorted sequences
LinearBinary(linear_window=16) # larger bound for sparser-spaced sorted queries
```

**Guidelines**:
- **Zero (0)**: Hint check only, no walk — minimal random-query overhead. Good when queries cluster in the same interval.
- **Small `linear_window` (1–2)**: Minimal overhead; best for mixed or unknown patterns. The default `LinearBinary()` uses `2`.
- **Medium `linear_window` (4–16)**: Good balance when queries are known-sorted
- **Large `linear_window` (32–128)**: For highly localized queries or very large datasets

!!! note "Type Parameter Restriction"
`linear_window` is restricted to powers of 2 (1, 2, 4, 8, 16, 32, 64) to prevent type parameter explosion. Each unique value creates a specialized method, so limiting choices keeps compile times reasonable.
`linear_window` is restricted to `0` plus powers of 2 (1, 2, 4, 8, 16, 32, 64, 128) to prevent type parameter explosion. Each unique value creates a specialized method, so limiting choices keeps compile times reasonable.

---

Expand Down
2 changes: 1 addition & 1 deletion src/FastInterpolations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export precompute_transpose! # Pre-allocate point-contiguous layout for scalar
export set_cubic_cache_size!, get_cubic_cache_size, clear_cubic_cache!

# Search policy types (user-facing algorithm selection)
export AbstractSearchPolicy, Binary, HintedBinary, Linear, LinearBinary, AutoSearch
export AbstractSearchPolicy, Binary, Linear, LinearBinary, AutoSearch

# Boundary condition types
export AbstractBC, PointBC, Deriv1, Deriv2, Deriv3, BCPair
Expand Down
2 changes: 1 addition & 1 deletion src/constant/constant_oneshot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ Constant (step/piecewise constant) interpolation at a single point.
- `deriv::DerivOp`: Derivative order (`EvalValue()`, `DerivOp(1)`, or `DerivOp(2)`). Derivatives are always 0.
- `search::AbstractSearchPolicy`: Search algorithm for interval finding
- `Binary()` (default): O(log n) binary search, stateless
- `HintedBinary()`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=0)`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=8)`: Linear search within window, then binary fallback

# Returns
Expand Down
89 changes: 17 additions & 72 deletions src/core/search.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
Abstract supertype for user-facing search policy selection.
Concrete subtypes encode the search algorithm choice.

See also: [`Binary`](@ref), [`HintedBinary`](@ref), [`LinearBinary`](@ref)
See also: [`Binary`](@ref), [`LinearBinary`](@ref)
"""
abstract type AbstractSearchPolicy end

Expand All @@ -35,37 +35,23 @@ Stateless and thread-safe. This is the default search policy.

# Hint Behavior
When a `hint` argument is provided with `Binary()`, the search automatically
upgrades to `HintedBinary` behavior to utilize the hint. Without a hint,
upgrades to `LinearBinary()` (default window) to utilize the hint for locality. Without a hint,
pure binary search is used.

# Example
```julia
val = linear_interp(x, y, 0.5; search=Binary()) # explicit binary search
val = linear_interp(x, y, 0.5) # default: AutoSearch()

# With hint: auto-upgrades to HintedBinary behavior
# With hint: auto-upgrades to LinearBinary() (default window)
hint = Ref(1)
val = itp(0.5; search=Binary(), hint=hint) # uses hinted binary internally
val = itp(0.5; search=Binary(), hint=hint) # uses LinearBinary() internally
```

See also: [`HintedBinary`](@ref), [`LinearBinary`](@ref)
See also: [`LinearBinary`](@ref)
"""
struct Binary <: AbstractSearchPolicy end

"""
HintedBinary <: AbstractSearchPolicy

Hinted binary search: O(1) if query hits cached interval, O(log n) fallback.
Useful when queries repeatedly access the same interval region.

# Example
```julia
itp = linear_interp(x, y)
vals = itp(query_points; search=HintedBinary())
```
"""
struct HintedBinary <: AbstractSearchPolicy end

"""
Linear <: AbstractSearchPolicy

Expand Down Expand Up @@ -103,7 +89,7 @@ for t in t_values # strictly increasing
end
```

See also: [`LinearBinary`](@ref), [`Binary`](@ref), [`HintedBinary`](@ref)
See also: [`LinearBinary`](@ref), [`Binary`](@ref)
"""
struct Linear <: AbstractSearchPolicy end

Expand All @@ -126,7 +112,8 @@ fall in adjacent or nearby intervals.
# Construction
Use the factory function (recommended) to construct with a curated set of values:
```julia
LinearBinary() # default MAX=2
LinearBinary() # default MAX=2
LinearBinary(linear_window=0) # hint check only, no walk
LinearBinary(linear_window=4) # custom MAX=4
```

Expand All @@ -141,7 +128,7 @@ sorted_queries = sort(rand(1000))
vals = linear_interp(x, y, sorted_queries; search=LinearBinary(linear_window=8))
```

See also: [`Binary`](@ref), [`HintedBinary`](@ref)
See also: [`Binary`](@ref)
"""
struct LinearBinary{MAX} <: AbstractSearchPolicy end

Expand All @@ -161,7 +148,7 @@ covering the practical range of use cases.

# Arguments
- `linear_window::Integer=2`: Size of the linear search window before binary fallback.
**Allowed values**: `1, 2, 4, 8, 16, 32, 64, 128` (powers of 2 from 2⁰ to 2⁷)
**Allowed values**: `0, 1, 2, 4, 8, 16, 32, 64, 128`

# Throws
- `ArgumentError`: If `linear_window` is not one of the allowed values.
Expand All @@ -175,11 +162,13 @@ policy = LinearBinary(linear_window=3) # ERROR: ArgumentError
```

# Choosing `linear_window`
- **Zero (0)**: Hint check only, no walk — minimal random-query overhead.
- **Small values (1–2)**: Minimal overhead, best for default usage and mixed query patterns
- **Medium values (4–16)**: Good balance for known-sorted query patterns
- **Large values (32–128)**: For highly localized queries or very large datasets
"""
function LinearBinary(linear_window::Integer)
linear_window == 0 && return LinearBinary{0}()
linear_window == 1 && return LinearBinary{1}()
linear_window == 2 && return LinearBinary{2}()
linear_window == 4 && return LinearBinary{4}()
Expand All @@ -188,7 +177,7 @@ function LinearBinary(linear_window::Integer)
linear_window == 32 && return LinearBinary{32}()
linear_window == 64 && return LinearBinary{64}()
linear_window == 128 && return LinearBinary{128}()
throw(ArgumentError("`linear_window` must be one of (1, 2, 4, 8, 16, 32, 64, 128), got $linear_window"))
throw(ArgumentError("`linear_window` must be one of (0, 1, 2, 4, 8, 16, 32, 64, 128), got $linear_window"))
end
LinearBinary(; linear_window::Integer=2) = LinearBinary(linear_window)

Expand Down Expand Up @@ -294,15 +283,15 @@ Internal searcher type combining search policy with hint state.
Type parameters enable compile-time dispatch with zero runtime overhead.

# Type Parameters
- `P`: Search policy type (`Binary`, `HintedBinary`, `LinearBinary{N}`)
- `P`: Search policy type (`Binary`, `LinearBinary{N}`)
- `H`: Hint type (`NoHint`, `RefHint`)

# Fields
- `hint::H`: The hint instance (NoHint singleton or RefHint with mutable Ref)

# Note
Users should not construct Searcher directly. Use the policy types (`Binary()`,
`HintedBinary()`, `LinearBinary()`) with the `search` keyword argument instead.
`LinearBinary()`) with the `search` keyword argument instead.
"""
struct Searcher{P<:AbstractSearchPolicy,H<:AbstractHint}
hint::H
Expand Down Expand Up @@ -331,7 +320,6 @@ Convert user-facing policy to internal Searcher with fresh hint state.
Creates a new RefHint for stateful policies, ensuring thread safety.
"""
@inline _to_searcher(::Binary) = Searcher{Binary,NoHint}(NoHint())
@inline _to_searcher(::HintedBinary) = Searcher{HintedBinary,RefHint}(RefHint())
@inline _to_searcher(::Linear) = Searcher{Linear,RefHint}(RefHint())
@inline _to_searcher(::LinearBinary{MAX}) where {MAX} = Searcher{LinearBinary{MAX},RefHint}(RefHint())

Expand All @@ -343,9 +331,7 @@ Creates a new RefHint for stateful policies, ensuring thread safety.
# When hint=Ref{Int}, stateful policies use the external Ref for persistence.

@inline _to_searcher(::Binary, ::Nothing) = Searcher{Binary,NoHint}(NoHint())
@inline _to_searcher(::Binary, hint::Base.RefValue{Int}) = Searcher{HintedBinary,RefHint}(RefHint(hint)) # auto-upgrade to hinted
@inline _to_searcher(::HintedBinary, ::Nothing) = Searcher{HintedBinary,RefHint}(RefHint())
@inline _to_searcher(::HintedBinary, hint::Base.RefValue{Int}) = Searcher{HintedBinary,RefHint}(RefHint(hint))
@inline _to_searcher(::Binary, hint::Base.RefValue{Int}) = _to_searcher(LinearBinary(), hint) # auto-upgrade to default LinearBinary
@inline _to_searcher(::Linear, ::Nothing) = Searcher{Linear,RefHint}(RefHint())
@inline _to_searcher(::Linear, hint::Base.RefValue{Int}) = Searcher{Linear,RefHint}(RefHint(hint))
@inline _to_searcher(::LinearBinary{MAX}, ::Nothing) where {MAX} = Searcher{LinearBinary{MAX},RefHint}(RefHint())
Expand All @@ -355,7 +341,7 @@ Creates a new RefHint for stateful policies, ensuring thread safety.
# code path misses resolution, fall back to Binary (safe stateless default).
@inline _to_searcher(::AutoSearch) = Searcher{Binary,NoHint}(NoHint())
@inline _to_searcher(::AutoSearch, ::Nothing) = Searcher{Binary,NoHint}(NoHint())
@inline _to_searcher(::AutoSearch, hint::Base.RefValue{Int}) = Searcher{HintedBinary,RefHint}(RefHint(hint))
@inline _to_searcher(::AutoSearch, hint::Base.RefValue{Int}) = _to_searcher(LinearBinary(), hint) # auto-upgrade to default LinearBinary

# ----------------------------------------
# Searcher passthrough (advanced usage)
Expand Down Expand Up @@ -533,25 +519,6 @@ end
# Core hinted implementations (type-matched)
# ----------------------------------------

"""
_search_hinted_binary!(x, xq, hint_ref) -> (idx, xL, xR)

Cached binary search: O(1) if hint valid, O(log n) fallback.
Updates `hint_ref` with the found interval index.
"""
@inline function _search_hinted_binary!(
x::AbstractVector{T}, xq::T, hint_ref::Base.RefValue{Int}
) where {T<:Real}
ix = hint_ref[]
n = length(x)
@inbounds if 1 <= ix <= n - 1 && x[ix] <= xq < x[ix + 1]
return ix, x[ix], x[ix + 1]
end
idx, xL, xR = _search_binary(x, xq)
hint_ref[] = idx
return idx, xL, xR
end

"""
_search_linear!(x, xq, hint_ref) -> (idx, xL, xR)

Expand Down Expand Up @@ -691,11 +658,6 @@ end
return _search_direct!(x, spacing, _to_grid_type(xq, Tg), hint_ref)
end

"""Generic wrapper for hinted binary search."""
@inline function _search_hinted_binary!(x::AbstractVector{Tg}, xq::Tq, hint_ref::Base.RefValue{Int}) where {Tg<:Real, Tq<:Real}
return _search_hinted_binary!(x, _to_grid_type(xq, Tg), hint_ref)
end

"""Generic wrapper for linear search."""
@inline function _search_linear!(x::AbstractVector{Tg}, xq::Tq, hint_ref::Base.RefValue{Int}) where {Tg<:Real, Tq<:Real}
return _search_linear!(x, _to_grid_type(xq, Tg), hint_ref)
Expand Down Expand Up @@ -733,16 +695,6 @@ end
@inline search_interval(::Searcher{Binary,NoHint}, x::AbstractRange{Tg}, spacing::ScalarSpacing{Tg}, xq::Real) where {Tg} =
_search_direct(x, spacing, xq)

# --- HintedBinary + RefHint ---

@inline function search_interval(p::Searcher{HintedBinary,RefHint}, x::AbstractVector, xq::Real)
return _search_hinted_binary!(x, xq, p.hint.idx)
end

# Range: O(1) direct + hint update
@inline search_interval(p::Searcher{HintedBinary,RefHint}, x::AbstractRange, xq::Real) =
_search_direct!(x, xq, p.hint.idx)

# --- Linear + RefHint ---

@inline function search_interval(p::Searcher{Linear,RefHint}, x::AbstractVector, xq::Real)
Expand All @@ -766,13 +718,6 @@ end
# For uniform grids (AbstractRange + ScalarSpacing): always O(1) direct
# For non-uniform grids (AbstractVector + VectorSpacing): delegate to standard search

# HintedBinary + spacing
@inline search_interval(p::Searcher{HintedBinary,RefHint}, x::AbstractVector, ::AbstractGridSpacing, xq::Real) =
_search_hinted_binary!(x, xq, p.hint.idx)

@inline search_interval(p::Searcher{HintedBinary,RefHint}, x::AbstractRange, spacing::ScalarSpacing, xq::Real) =
_search_direct!(x, spacing, xq, p.hint.idx)

# Linear + spacing
@inline search_interval(p::Searcher{Linear,RefHint}, x::AbstractVector, ::AbstractGridSpacing, xq::Real) =
_search_linear!(x, xq, p.hint.idx)
Expand Down
1 change: 0 additions & 1 deletion src/core/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ _format_side(side::AbstractSide) = string(typeof(side))

"""Format search policy name."""
_format_search(::Binary) = "Binary"
_format_search(::HintedBinary) = "HintedBinary"
_format_search(::Linear) = "Linear"
_format_search(::LinearBinary{MAX}) where {MAX} = "LinearBinary{$MAX}"
_format_search(::AutoSearch) = "AutoSearch (scalar→Binary, vector→LinearBinary)"
Expand Down
2 changes: 1 addition & 1 deletion src/cubic/cubic_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Returned by `cubic_interp(x, y)` (2-argument form).
- `Tv`: Value type for y-values (can be Tg, Complex{Tg}, or other Number)
- `C`: CubicSplineCache type (preserves grid type info for O(1) vs O(log n) lookup)
- `E`: Extrapolation mode type (compile-time specialized)
- `P`: Search policy type (AutoSearch, Binary, HintedBinary, LinearBinary, etc.)
- `P`: Search policy type (AutoSearch, Binary, LinearBinary, etc.)
- `BC`: Boundary condition type (BCPair or PeriodicBC)

# Fields
Expand Down
4 changes: 2 additions & 2 deletions src/linear/linear_oneshot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Zero-allocation linear interpolation with automatic dispatch:
- `deriv::DerivOp`: Derivative order (`EvalValue()` default, `DerivOp(1)` first derivative, `DerivOp(2)` second derivative)
- `search::AbstractSearchPolicy`: Search algorithm for interval finding
- `Binary()` (default): O(log n) binary search, stateless
- `HintedBinary()`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=0)`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=8)`: Linear search within window, then binary fallback

# Example
Expand Down Expand Up @@ -184,7 +184,7 @@ Zero-allocation scalar linear interpolation with automatic dispatch:
- `deriv::DerivOp`: Derivative order (`EvalValue()` default, `DerivOp(1)` first derivative)
- `search::AbstractSearchPolicy`: Search algorithm for interval finding
- `Binary()` (default): O(log n) binary search, stateless
- `HintedBinary()`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=0)`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=8)`: Linear search within window, then binary fallback

# Returns
Expand Down
2 changes: 1 addition & 1 deletion src/quadratic/quadratic_oneshot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ C1 piecewise quadratic spline interpolation at a single point.
- `deriv::DerivOp`: Derivative order -- use `EvalValue()` (default), `DerivOp(1)`, or `DerivOp(2)`
- `search::AbstractSearchPolicy`: Search algorithm for interval finding
- `Binary()` (default): O(log n) binary search, stateless
- `HintedBinary()`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=0)`: O(1) if hint valid, O(log n) fallback
- `LinearBinary(linear_window=8)`: Linear search within window, then binary fallback

# Returns
Expand Down
Loading
Loading