Skip to content

Native reverse-order dispatch (closes #67)#68

Merged
ChrisRackauckas merged 1 commit into
SciML:mainfrom
ChrisRackauckas-Claude:reverse-order-dispatch
May 20, 2026
Merged

Native reverse-order dispatch (closes #67)#68
ChrisRackauckas merged 1 commit into
SciML:mainfrom
ChrisRackauckas-Claude:reverse-order-dispatch

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Contributor

Closes #67. Implements native Base.Order.Reverse paths for the four
strategies that used to fall back to BinaryBracket / LinearScan:
InterpolationSearch, ExpFromLeft, SIMDLinearScan,
BitInterpolationSearch.

Approach

  • InterpolationSearch: the existing _interp_guess math
    ((x - vlo) / (vhi - vlo)) handles negative span for Reverse-sorted
    data — the ratio still lands in [0, 1] for queries inside the range.
    Dropping the Forward-only guard makes it native via BracketGallop's
    already-order-aware refinement.

  • ExpFromLeft: parameterised searchsortedfirstexp on order
    (!lt(order, v[ind], x) replacing the hardcoded x <= v[ind]).
    Added a sibling searchsortedlastexp using the strict comparison
    lt(order, x, v[ind]) to find the last position correctly. The old
    code post-processed searchsortedfirstexp's result with
    isequal(v[y], x) ? y : y - 1, which returned the FIRST occurrence
    when x had duplicates. The duplicates bug was pre-existing under
    Forward too but never exercised because the prior custom-order tests
    used a no-duplicates 10.0:-1.0:1.0 range; the new Reverse parity
    tests on random Int64 in [-100, 100] across 200 elements (lots of
    duplicates) caught it.

  • SIMDLinearScan: four new LLVM IR predicates generated from the
    existing _simd_scan_ir(t, pred) template: slt / sle for Int64
    and olt / ole for Float64. Backing primitives _simd_first_lt /
    _simd_first_le in simd_ir.jl. _simdscan_last_specialized and
    _simdscan_first_specialized now take an order::Ordering argument
    and branch on Forward vs Reverse at runtime — the branch
    compiles away when order is statically known. Non-Forward /
    non-Reverse orderings fall back to scalar LinearScan
    (order-agnostic).

  • BitInterpolationSearch: _bit_interp_guess_f64 branches on the
    ordering and mirrors the unsigned arithmetic for the descending case
    (span = vlo_bits - vhi_bits, num = vlo_bits - xu). A new
    _bit_interp_eligible helper folds the positivity / finiteness /
    supported-order guards into one check.

Tests

A new "Native reverse-order paths (issue #67)" testset:

  • 5000 random parity comparisons each on Reverse-sorted Float64 and
    Int64 vectors of length 200, across every shipped strategy at every
    hint position, vs Base.searchsorted{first,last}(v, x, Reverse).
  • 400 parity comparisons for BitInterpolationSearch on Reverse
    log-spaced positive Float64 (the strategy's intended regime), plus
    signed-Float64 fallback checks.
  • Edge cases: queries past either end of the array, hint at boundary.

100047 tests pass (was 84626; +15421 from the new parity tests).

Bonus item deliberately not done

The "generalize beyond Reverse to arbitrary user-supplied orderings"
bonus from the issue is not implemented. SIMDLinearScan and
BitInterpolationSearch use raw machine compares (icmp/fcmp) that
aren't aware of Base.Order, so custom By / Lt orderings continue
to fall back to scalar LinearScan (which is order-agnostic by
construction).

🤖 Note for reviewer: this PR is in draft. Please ignore until reviewed
by @ChrisRackauckas.

Closes SciML#67. Drops the `BinaryBracket` / `LinearScan` fallback paths that
`InterpolationSearch`, `ExpFromLeft`, `SIMDLinearScan`, and
`BitInterpolationSearch` used to take for `Base.Order.Reverse` ordering.

Approach per strategy:

  - **InterpolationSearch**: the linear-extrapolation guess
    `(x - vlo) / (vhi - vlo)` works for both polarities of `vlo`/`vhi`
    (negative span for `Reverse`-sorted decreasing data) — the ratio still
    lands in [0, 1] for queries inside the range. Dropping the
    Forward-only guard makes it native for both orderings via
    `BracketGallop`'s already-order-aware refinement.

  - **ExpFromLeft**: parameterised `searchsortedfirstexp` on `order`, with
    `!lt(order, v[ind], x)` replacing the hardcoded `x <= v[ind]`. Added
    a sibling `searchsortedlastexp` that uses the strict comparison
    `lt(order, x, v[ind])` to find the last position correctly — the old
    code post-processed `searchsortedfirstexp`'s result with
    `isequal(v[y], x) ? y : y - 1`, which returned the FIRST occurrence
    when `x` had duplicates instead of the LAST. The duplicates bug was
    pre-existing under Forward too but never exercised because the prior
    custom-order tests used a no-duplicates `10.0:-1.0:1.0` range; the
    new Reverse parity tests on random Int64 in [-100, 100] across 200
    elements (lots of duplicates) caught it.

  - **SIMDLinearScan**: four new LLVM IR predicates generated from the
    existing `_simd_scan_ir(t, pred)` template: `slt` / `sle` for Int64
    and `olt` / `ole` for Float64. Backing primitives `_simd_first_lt` /
    `_simd_first_le` in `simd_ir.jl`. `_simdscan_last_specialized` and
    `_simdscan_first_specialized` now take an `order::Ordering` argument
    and branch on `order === Forward` vs `Reverse` at runtime — the
    branch compiles away in concrete callsites where order is statically
    known. A `_simd_supported_order` helper gates the SIMD path to
    `Forward` and `Reverse` only; custom orderings (`By`, `Lt`, etc.)
    fall back to scalar `LinearScan` (which is order-agnostic).

  - **BitInterpolationSearch**: `_bit_interp_guess_f64` branches on the
    ordering and mirrors the unsigned arithmetic for the descending case
    (`span = vlo_bits - vhi_bits`, `num = vlo_bits - xu`). A new
    `_bit_interp_eligible` helper folds the positivity / finiteness /
    supported-order guards into one check. Custom orderings fall back to
    `BinaryBracket`.

Tests: a new "Native reverse-order paths (issue SciML#67)" testset adds:

  - 5000 random parity comparisons each on Reverse-sorted Float64 and
    Int64 vectors of length 200, across every shipped strategy at every
    hint position, vs `Base.searchsorted{first,last}(v, x, Reverse)`.
  - 400 parity comparisons specifically for `BitInterpolationSearch` on
    Reverse log-spaced positive Float64 (the strategy's intended
    regime), plus signed-Float64 fallback checks.
  - Edge cases: queries past either end of the array, hint at boundary.

100047 tests pass (was 84626; +15421 from the new parity tests).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas marked this pull request as ready for review May 20, 2026 15:37
@ChrisRackauckas ChrisRackauckas merged commit 7ac0998 into SciML:main May 20, 2026
15 of 21 checks passed
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.

Native reverse-order paths for InterpolationSearch, ExpFromLeft, SIMDLinearScan, BitInterpolationSearch

2 participants