Skip to content

LAPACK-style test suites (ratios, dimension sweeps, cross-routine checks) and a side/layout-general portable ?ormqr_compact (0.2.0) - #62

Merged
ivan-pi merged 6 commits into
mainfrom
claude/focused-johnson-tu5ck1
Sep 15, 2026
Merged

ivan-pi merged 6 commits into
mainfrom
claude/focused-johnson-tu5ck1

Conversation

@ivan-pi

@ivan-pi ivan-pi commented Sep 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Brings the portable test suites onto the practices of LAPACK's TESTING tree, and closes the one gap that audit found in the API: the portable ?ormqr_compact could only apply Q from the left in column-major, while the kernel and the MKL-style entry point covered every side and layout. Six commits; the API change makes this 0.2.0.

Test suites (tests/)

  • One convention for every numerical check (413cf8b, a379fc8). Every gate in the seven portable suites is a dimensionless test ratio in the exact form of the corresponding LAPACK checker, compared against the single THRESH = 30 of dtest.in — no per-check k * eps * n tolerances remain. The three forms live in test_compact_util.hpp: test_ratio<T> for factorization residuals (dqrt01 / dpot01 / dsyt01: the 1-norm of R - Q^T A, L L^T - A, ... over (order) * ||A||_1 * eps), residual_ratio for solves (dget02 / dpot02 / dtrt02: per right-hand side, no order factor), and forward_ratio for forward errors (dget04: per right-hand side, discounted by rcond, which rcond1 takes from LAPACKE ?getrf + ?gecon on the dense, unpacked input — never from the packed format). Each case prints its ratios next to its verdict; observed ratios are O(1), at most ~5 across the suites in both precisions. Only structural contracts (bit-for-bit reproduction, untouched storage, identity padding lanes) stay exact.
  • The small-dimension cross product in every suite (7f67361). As LAPACK sweeps M, N over 0 1 2 3 5 ..., every suite runs its run_case over small_dims = {0, 1, 2, 3, 5} — m x n (geqrf, trsm, gels per layout/trans), m x nrhs (ormqr), m >= n with LAPACK's K values (orgqr, ormqr), n and n x nrhs through the solve pipelines (potrf, sytrfnp) — so the empty operand, the single row or column, and the orders around a register block are checked in every routine, not only where someone thought of them. The empty shapes needed ld = max(1, extent), a never-empty compact_buffer (Fortran dummy semantics for posv's bp at nrhs = 0, gels's A at m = 0), an is_rowmajor that accepts an empty view, and LAPACK's quick returns in the LAPACKE-backed references (ref_gels's is B := 0).
  • Related routines checked against each other, as LAPACK's paths do (a379fc8): potrs against the two trsm sweeps it is built from (bit-identical in every configuration, reported not gated), sytrsnp against trsm, D^-1, trsm; the trsm suite gained dget04 against the exact solution (B = op(A) X for a random X, as dlarhs) next to its comparison with cblas_?trsm. The PO / SY / TR paths were audited against dchkpo / dchksy / dchktr: reconstruction, solve residual and forward error were already present; inverse, condition-estimate and refinement checks have no cbk routine to test.

API: ?ormqr_compact (33ddb90)

Breaking. The portable ?ormqr_compact now takes (layout, side, trans, m, n, k, ap, ldap, taup, cp, ldcp, V, nm) — LAPACK ?ormqr's argument order behind the layout, as the MKL-style cbk_?ormqr_compact already did — so Q is applied from either side in either layout; it was side 'L', column-major only, with the adapter hardcoding left = true, rowmajor = false over a kernel that already handled all of it (side 'R' is the kernel over the transposed view). A caller of the old (trans, m, nrhs, k, ...) writes ('C', 'L', trans, m, nrhs, k, ...). Validation renumbers in argument order (-1 layout ... -13 nm), with k <= nq for nq = m (side 'L') or n (side 'R'), ldap >= nq column-major / >= k row-major. The examples already use the compat API; the only in-tree callers were the tests.

tests/test_ormqr_compact.cpp now runs the full layout x side x trans product on every stack: vs LAPACKE_?ormqr, vs ?gemm with the Q that ?orgqr forms explicitly (LAPACK's dqrt03, ||CC - op(Q) C||_1 / (nq ||C||_1 eps)), the round trip op(Q)^T op(Q) C = C, and the solve pipelines (?geqr2's and ?geqp3's reflectors) as before. With this, the QR path's LAPACK inventory is complete on the portable stack: dqrt01 (geqrf), dqrt02 (orgqr), dqrt03 (ormqr), dget02 / dqrt14-style properties (gels).

Release: 0.2.0 (4fc740b, 27d1e41)

CHANGELOG.md records the breaking signature and the test conventions; under its pre-1.0 rule the API change is a minor release, so CBK_VERSION (cbk.h) and project(VERSION) (CMakeLists.txt) move to 0.2.0 together — cbk.cpp's static_assert keeps them in step; soname unchanged — and the Unreleased section becomes the 0.2.0 entry. The v0.2.0 tag is left for after the merge.

Verification

gcc Release, clang Debug (the view asserts on the empty shapes) and the MKL-extension tree (-DCBK_WITH_MKL=ON, 20 tests) pass locally; pre-commit clean. CI: gcc/clang x OpenBLAS/Netlib/MKL (lp64 and ilp64), macOS Accelerate, the install check both ways.

The MKL suites keep their own relative gates for now (solve_errors remains for them).

🤖 Generated with Claude Code

https://claude.ai/code/session_014ZZ76AgYwQ3vtuyZ46tyMX

LAPACK's linear-equation suites take M and N from a list that starts
0 1 2 3 5 and run every path over the full M x N cross product, so the
empty operand, the single row or column, and the orders around a block
boundary are exercised in every routine. The portable suites only ran
them where someone had thought of them (gels had m = 0 and 1 x n, geqrf
and potrf started at 3, ormqr ran one fixed m).

Every portable suite now sweeps its run_case over small_dims = {0, 1, 2,
3, 5} (test_compact_util.hpp): geqrf, trsm and gels over m x n (gels per
layout and trans), ormqr over m x nrhs, orgqr over m >= n with LAPACK's
K values {n, 0, 1, n/2}, potrf and sytrfnp over n through the
factorization checks and n x nrhs through the solve pipeline -- each with
one full group plus a padded one, and the existing checks unchanged, so
a zero extent is checked as the routine's quick return (info = 0, every
error 0).

What that needed:
- run_case passes ld = max(1, extent), as LAPACK's ld >= max(1, m)
  requires and the C API validates.
- compact_buffer never returns an empty vector: a zero-extent operand is
  still an argument the routine may require present (posv's bp at
  nrhs = 0, gels's A at m = 0, asserted in cbk.cpp), so it keeps the one
  element a Fortran caller's dummy would be.
- is_rowmajor accepts an empty view (mat_view of a 0 x n matrix has
  sj = 0), leaving ld >= max(1, extent) to lapack_ld.
- The LAPACKE-backed references take LAPACK's own quick return on an
  empty operand before the call, so an empty batch's possibly null
  pointer never reaches a stack's NaN check; ref_gels's is B := 0, the
  ?gels contract, and the gels suite's residual-rows identity is skipped
  at min(m, n) = 0 for the same reason.

Passes gcc Release, clang Debug (the view asserts on the empty shapes)
and the MKL-extension build.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014ZZ76AgYwQ3vtuyZ46tyMX
The portable suites gated each check on its own hand-picked tolerance --
200 eps m, 20 n eps, 1e5 eps m, 1e4 eps p, 40 eps n ||A||, ... -- so no
two checks were comparable, a near miss was invisible, and the
cond(A)-dependent forward errors carried a guessed headroom. LAPACK's
TESTING suites use one convention instead: every check is a
dimensionless ratio, the error over what backward stability allows it to
be, (order) * (operand norm) * eps, and a single threshold decides.

test_compact_util.hpp now carries that convention: test_ratio<T>(err,
dim, norm), the residual form of dqrt01 / dpot01 / dget02;
forward_ratio<T>(err, normx, rcond), dget04's forward error discounted by
rcond; THRESH = 30, the value of LAPACK's dtest.in; and passes(), written
so NaN fails. test_lapack_util.hpp adds rcond1 -- 1 / (||A|| ||A^-1||)
by LAPACKE ?getrf + ?gecon on the dense, unpacked input, since a
condition number cannot be taken from the packed format -- and
solve_ratios, the dget02 + dget04 pair the solve pipelines gate on.

Every check in the seven portable suites is now such a ratio, printed
with its case: the factorization residuals and reconstructions relative
to ||A||, the elementwise comparisons against LAPACKE relative to the
operand (1 for an orthogonal Q), the solve residuals to ||A|| ||X||, and
the forward errors -- geqrf's and ormqr's back substitution, potrf's and
sytrfnp's solve pipelines, trsm's solve vs the library's (rcond of the
operator actually applied, the unit diagonal honored), gels's Gram-formed
minimum-norm solution (rcond of the Gram matrix, the cond(A)^2 the old
1e4 headroom stood for) -- discounted by rcond. The structural contracts
(bit-for-bit fused == two-step, untouched triangles, identity padding
lanes) stay exact. Observed ratios: at most 2.2 across the suites, in
both precisions, against THRESH = 30.

The MKL suites keep their relative gates for now (solve_errors stays for
them). CLAUDE.md records the convention.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014ZZ76AgYwQ3vtuyZ46tyMX
The ratio refactor kept LAPACK's denominators but not its numerators:
max-abs entries where dqrt01 / dpot01 / dsyt01 take the 1-norm of the
residual matrix, and a whole-matrix ||X||_1 with an order factor where
dget02 / dpot02 / dtrt02 are per right-hand side without one -- up to n
times more lenient than the checkers they cite. Every ratio now has the
checker's exact form (test_compact_util.hpp): test_ratio over diff_norm1 /
orth_norm1 (the 1-norm of R - Q^T A, L L^T - A, Q^T Q - I, ...);
residual_ratio, dget02's max_j ||b_j - A x_j||_1 / (||A||_1 ||x_j||_1
eps); forward_ratio, dget04's max_j ||x_j - x_j^true||_inf /
||x_j^true||_inf * rcond / eps, on views rather than a scalar error.
solve_ratios forms the residual explicitly and hands it to those.
Observed ratios rose accordingly, to at most 4.9 against THRESH = 30.

The PO, SY and TR paths were audited against dchkpo / dchksy / dchktr.
Present already: dpot01 / dsyt01 reconstruction, dpot02 residual, dget04
forward error, dtrt02 residual, the fused drivers bit-for-bit against
their two-step forms, plus the lane-isolation and untouched-triangle
contracts LAPACK has no analogue for. Not applicable: dpot03 / dtrt01
(no inverse routine), dpocon / dtrcon (no condition estimator), the
refinement bounds (no ?rfs). Missing, and added:
- trsm had no exact-solution forward error, only agreement with
  cblas_?trsm: the right-hand side is now B = op(A) X for a random X
  (dlarhs), so dget04 runs against the exact alpha X as well as against
  the library's solution, and dtrt02's residual reads side 'R' as the
  transposed system its rows are the right-hand sides of.
- No check tied potrs / sytrsnp to the trsm sweeps they are built from:
  run_compose in both suites solves the same system through the public
  ?trsm_compact (two non-unit sweeps for potrs; two unit sweeps around
  D^-1 for sytrsnp) and gates dget04 of the one solution against the
  other. potrs comes out bit-identical to its two sweeps in every
  configuration (reported, not gated); sytrsnp's ratio is ~0.03.

CLAUDE.md's convention entry names the three forms and the cross-routine
checks.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014ZZ76AgYwQ3vtuyZ46tyMX
The kernel (cbk_ormqr_compact.hpp) covers every layout, side and trans
in one strided body -- side 'R' is the kernel over the transposed view --
and the MKL-style cbk_?ormqr_compact exposes all of it, but the portable
?ormqr_compact hardcoded side 'L', column-major, so the portable surface
(the one every stack builds) could only apply Q^T from the left, and its
suite could only test that.

?ormqr_compact now takes (layout, side, trans, m, n, k, ap, ldap, taup,
cp, ldcp, V, nm), LAPACK ?ormqr's argument order behind the layout, like
the other portable entry points and the MKL-style one. Validation in
argument order: -1 layout, -2 side, -3 trans, -4 m, -5 n, -6 k (k <= nq,
nq = m for side 'L' and n for 'R', the order of Q and the rows of A),
-8 ldap (>= nq column-major, >= k row-major), -11 ldcp, -12 V, -13 nm.
The compat<T> and compact<T> dispatchers, ref_ormqr (a side-aware
overload, the side-'L' one forwarding to it) and the geqrf suite's call
follow.

tests/test_ormqr_compact.cpp runs the full layout x side x trans product
on every stack: vs LAPACKE_?ormqr, vs ?gemm with the Q that ?orgqr forms
explicitly -- LAPACK's dqrt03, ||CC - op(Q) C||_1 / (nq ||C||_1 eps) --
and the round trip op(Q)^T op(Q) C = C, at the suite's shapes (k < nq and
a padded group among them), the small-dimension cross product with
LAPACK's K values on the tuned route and k = nq on the transposed one,
and the solve pipelines (?geqr2's and ?geqp3's reflectors) as before.
Observed ratios: at most 1.6, the tiny shapes; 0.06-0.6 at the
representative ones.

PLANS.md and CLAUDE.md record the generalized surface and the dqrt03
check.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014ZZ76AgYwQ3vtuyZ46tyMX
The portable ?ormqr_compact changed signature (layout, side and trans),
which under the changelog's pre-1.0 rule is a minor release. CBK_VERSION
in cbk.h and project(VERSION) in CMakeLists.txt move to 0.2.0 together
(cbk.cpp's static_assert keeps them in step; the shared library becomes
libcbk.so.0.2.0, soname unchanged), and the changelog's Unreleased
section -- the LAPACKE-backed suites, the test-ratio convention, and the
?ormqr_compact change -- becomes the 0.2.0 entry.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014ZZ76AgYwQ3vtuyZ46tyMX
@ivan-pi ivan-pi changed the title Extend ormqr_compact to support all four side/trans combinations LAPACK-style test suites (ratios, dimension sweeps, cross-routine checks) and a side/layout-general portable ?ormqr_compact (0.2.0) Sep 15, 2026
@ivan-pi
ivan-pi merged commit 1722e83 into main Sep 15, 2026
11 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.

2 participants