Skip to content

Use PureGebal for the Base-exp balancing path - #268

Draft
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:agent/puregebal-balancing
Draft

Use PureGebal for the Base-exp balancing path#268
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:agent/puregebal-balancing

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

Please ignore until reviewed by @ChrisRackauckas. Draft.

⚠️ Blocked on registration

PureGebal is not in the General registry yet — JuliaRegistries/General#163748 is open, and new packages sit for three days. CI on this branch will fail at Pkg.instantiate until it merges. Local verification below used a dev'd PureGebal. Nothing to fix here; it just cannot go green yet.

Summary

Replace the hand-written LAPACK GEBAL ccall with PureGebal.

That wrapper existed for one reason: LinearAlgebra.LAPACK.gebal! allocates its scale vector on every call, which the Krylov hot path cannot afford, so this package duplicated ~20 lines of stdlib ccall to get a caller-supplied buffer. PureGebal provides that buffer as a first-class workspace, so the duplication goes away.

  • the cache's bare scale vector becomes a PureGebal.GebalWorkspace
  • the hand-rolled undo — the diagonal rescale plus the two permutation replays that decode LAPACK's packed scale layout — becomes PureGebal.unbalance!

+15 / −81 across four files.

This removes all dependence on non-public LAPACK internals

Six ExplicitImports exceptions existed solely for that wrapper and are deleted: @blasfunc, BlasInt, chkfinite, chklapackerror, chkstride1, libblastrampoline — along with the entire all_qualified_accesses_via_owners block. What remains (promote_op, arithmetic_closure, parameterless_type) are other packages' non-public names, unrelated to balancing.

Behaviour

Unchanged. PureGebal is bit-identical to LAPACK.gebal! on the inputs this path sees (asserted in its own suite across dense, wide-dynamic-range, sparse, triangular and block-isolated families), and the balanced Padé evaluation still runs at zero allocations — checked directly, with cache[7]::PureGebal.GebalWorkspace{Float64}.

Local verification

  • Julia 1.12.6 Core: 742 passed, 1 pre-existing broken
  • Julia 1.12.6 QA: 22 passed, 2 pre-existing broken
  • A master baseline run of the same suite gives 742 passed, 1 broken — identical, so no test silently went missing
  • Runic 1.5.1 and git diff --check: clean
  • Julia 1.10 Core and QA: in flight; I will post the numbers here rather than assume them

Deliberately not in this PR

src/exp_noalloc.jl still balances via GenericSchur.balance! for ExpMethodHigham2005, which allocates (~672 B at n=30). PureGebal could replace that too and remove the allocation, plus the separate prow/pcol undo path — but it changes the generic (non-BLAS) path's behaviour, so it belongs in its own PR.

This is independent of #267, which is documentation only.

Replace the hand-written LAPACK GEBAL ccall with PureGebal. The wrapper existed
only because `LinearAlgebra.LAPACK.gebal!` allocates its scale vector on every
call, which the Krylov hot path cannot afford, so this package duplicated ~20
lines of stdlib ccall to get a caller-supplied buffer. PureGebal provides that
buffer as a first-class workspace, so the duplication goes away.

The cache's bare `scale` vector becomes a `PureGebal.GebalWorkspace`, and the
hand-rolled undo -- the diagonal rescale plus the two permutation replays that
decode LAPACK's packed `scale` layout -- becomes `PureGebal.unbalance!`.

This removes the package's entire dependence on non-public LAPACK internals.
Six ExplicitImports exceptions existed solely for that wrapper and are now
deleted: `@blasfunc`, `BlasInt`, `chkfinite`, `chklapackerror`, `chkstride1`,
`libblastrampoline`, along with the whole `all_qualified_accesses_via_owners`
block. What remains are other packages' non-public names, not LAPACK's.

Behaviour is unchanged. PureGebal is bit-identical to `LAPACK.gebal!` on the
inputs this path sees, and the balanced Padé evaluation still runs with zero
allocations.

Verified locally on Julia 1.12.6: Core 742 passed / 1 pre-existing broken --
identical to a master baseline run of the same suite -- and QA 22 passed /
2 pre-existing broken. Runic clean.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member Author

Local verification complete

Both Julia versions, with a master baseline run of the same suite on each so the numbers are attributable rather than assumed.

Run This branch master baseline
Core, Julia 1.12.6 742 passed, 1 broken 742 passed, 1 broken
Core, Julia 1.10.11 741 passed, 1 broken 741 passed, 1 broken
QA, Julia 1.12.6 22 passed, 2 broken
QA, Julia 1.10.11 20 passed, 2 broken

Every broken is pre-existing (the AllocCheck static-analysis markers in the QA group, and the one in Core).

Two count differences that are not attributable to this PR:

  • Core is 741 on 1.10 vs 742 on 1.12 — master shows exactly the same split, so it is a version-conditional assertion in the suite.
  • QA is 20 on 1.10 vs 22 on 1.12 — the same split appeared on an unrelated branch of this repo earlier today, so it is the Julia version, not the exception list this PR shortens.

Also: Runic 1.5.1 and git diff --check clean, and the balanced Padé path re-checked at zero allocations with cache[7]::PureGebal.GebalWorkspace{Float64}.

CI here will stay red until JuliaRegistries/General#163748 merges — the local runs above used a dev'd PureGebal.

For what it is worth, PureGebal's own CI is green on Julia 1 and lts across Ubuntu, macOS and Windows, so the balancing code this now depends on has cross-platform evidence behind it.

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