From 129e2cf509074d8ebe517149dfed69926a9fa8cf Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 12 Sep 2026 13:52:42 -0400 Subject: [PATCH 1/6] Resolve materialize! ambiguity with LazyArrays 2.13 padded ldiv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LazyArrays 2.13 added `materialize!(::MatLdivVec{<:UnitOrUpperTriangularLayout, <:PaddedColumns})`, which overlaps the AlmostBandedLayout upper-triangular `materialize!` methods on the B side: Aqua's ambiguity check fails with the pair for both 'U','N' and 'U','U', and the call throws `MethodError: ambiguous` at runtime. Add the joint specializations. The padded solve only needs the leading square block of the triangular factor, since the right-hand side is zero past its data (and the `Zeros` tail cannot be written into); solve that block densely. `import LazyArrays` is also added so the module name is bound — the existing `LazyArrays.arguments` calls were already resolving through it. Verified: `test/qa/qa.jl` passes 20/20 on Julia 1.13 (was 19/20 with the two ambiguities), and `UpperTriangular(A) \ Vcat(p, Zeros)` returns the exact solution on the padded prefix with a zero tail (previously MethodError). Co-Authored-By: Chris Rackauckas Co-Authored-By: Devin Agent-Harness: Devin CLI 3000.10.21 Agent-Model: SWE-2 Max Agent-Session: /home/crackauc/.local/share/devin/cli/summaries/history_1130a74e0ebc4347.md --- src/FastAlmostBandedMatrices.jl | 14 ++++++++++++++ test/core_tests.jl | 5 +++++ test/qa/qa.jl | 1 + 3 files changed, 20 insertions(+) diff --git a/src/FastAlmostBandedMatrices.jl b/src/FastAlmostBandedMatrices.jl index f01eb59..422ad96 100644 --- a/src/FastAlmostBandedMatrices.jl +++ b/src/FastAlmostBandedMatrices.jl @@ -6,6 +6,7 @@ import ArrayInterface import ArrayLayouts import ArrayLayouts: LayoutMatrix, LayoutVector, Ldiv, Lmul, TriangularLayout import ConcreteStructs: @concrete +import LazyArrays import LazyArrays: LazyArray, Mul import LinearAlgebra import LinearAlgebra: LowerTriangular, NoPivot, UnitLowerTriangular, UnitUpperTriangular, @@ -813,6 +814,19 @@ function Base.materialize!(M::MatLdivVec{TriangularLayout{'U', 'U', AlmostBanded return x end +# A padded right-hand side is zero past its data, so an upper-triangular solve only +# needs the leading square block; the `Zeros` tail cannot be written into anyway. +function _padded_upper_ldiv!(T, R, x) + A = triangulardata(R) + p = LazyArrays.paddeddata(x) + n = size(p, 1) + ldiv!(T(Matrix(view(A, 1:n, 1:n))), p) + return x +end + +Base.materialize!(M::MatLdivVec{TriangularLayout{'U', 'N', AlmostBandedLayout}, <:LazyArrays.PaddedColumns}) = _padded_upper_ldiv!(UpperTriangular, M.A, M.B) +Base.materialize!(M::MatLdivVec{TriangularLayout{'U', 'U', AlmostBandedLayout}, <:LazyArrays.PaddedColumns}) = _padded_upper_ldiv!(UnitUpperTriangular, M.A, M.B) + function Base.materialize!(M::MatLdivVec{TriangularLayout{'L', 'N', AlmostBandedLayout}}) R, x = M.A, M.B A = triangulardata(R) diff --git a/test/core_tests.jl b/test/core_tests.jl index 8fc8035..80e77d9 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -194,6 +194,11 @@ end @test_broken UnitUpperTriangular(Matrix(A)) \ b ≈ UnitUpperTriangular(A) \ b @test LowerTriangular(Matrix(A)) \ b ≈ LowerTriangular(A) \ b @test UnitLowerTriangular(Matrix(A)) \ b ≈ UnitLowerTriangular(A) \ b + + const LA = FastAlmostBandedMatrices.LazyArrays + bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) + @test UpperTriangular(A) \ bpad ≈ UpperTriangular(Matrix(A)) \ Vector(bpad) + @test UnitUpperTriangular(A) \ bpad ≈ UnitUpperTriangular(Matrix(A)) \ Vector(bpad) end # https://github.com/SciML/FastAlmostBandedMatrices.jl/issues/19 diff --git a/test/qa/qa.jl b/test/qa/qa.jl index 84a5f75..9b95c46 100644 --- a/test/qa/qa.jl +++ b/test/qa/qa.jl @@ -32,6 +32,7 @@ run_qa( ignore = ( :OneTo, :array_summary, :dims2string, :inds2string, :materialize!, :QRPackedQ, :arguments, :fast_scalar_indexing, :qr_instance, + :PaddedColumns, :paddeddata, ), ), ), From 805c172e0f14425606cde3eadaba1c81b31239ba Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 12 Sep 2026 15:42:10 -0400 Subject: [PATCH 2/6] Drop const on local binding in regression test `const` on a local variable is a syntax error; the alias is only used in the enclosing testset anyway. Co-Authored-By: Chris Rackauckas Co-Authored-By: Devin Agent-Harness: Devin CLI 3000.10.21 Agent-Model: SWE-2 Max Agent-Session: /home/crackauc/.local/share/devin/cli/summaries/history_70c8672a582f4377.md --- test/core_tests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core_tests.jl b/test/core_tests.jl index 80e77d9..c3cc82e 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -195,7 +195,7 @@ end @test LowerTriangular(Matrix(A)) \ b ≈ LowerTriangular(A) \ b @test UnitLowerTriangular(Matrix(A)) \ b ≈ UnitLowerTriangular(A) \ b - const LA = FastAlmostBandedMatrices.LazyArrays + LA = FastAlmostBandedMatrices.LazyArrays bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) @test UpperTriangular(A) \ bpad ≈ UpperTriangular(Matrix(A)) \ Vector(bpad) @test UnitUpperTriangular(A) \ bpad ≈ UnitUpperTriangular(Matrix(A)) \ Vector(bpad) From 2804e5554e52dfdc56bdd65cf8fff46c6b9c9b84 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 12 Sep 2026 18:17:12 -0400 Subject: [PATCH 3/6] Gate the padded-ldiv regression test on LazyArrays 2.13+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LazyArrays' `materialize!` for `MatLdivVec{<:UnitOrUpperTriangularLayout, <:PaddedColumns}` — the padded right-hand-side path the new methods disambiguate — was added in LazyArrays 2.13, and only then does `Vcat(data, Zeros)` take on a `PaddedColumns` layout. On the downgrade lane's floor resolution (LazyArrays 2.0.0), `A \ bpad` has no upstream path and errored with `size(::Nothing, ::Int64)`, turning the previously-green Downgrade job red. The new `materialize!` methods are inert on older LazyArrays (the layout never arises), so gate only the test. Verified locally: the Triangular testset reports 5 pass / 2 broken on LazyArrays 2.13.0 and 3 pass / 2 broken on LazyArrays 2.0.0 — identical to the pre-PR state there. Co-Authored-By: Chris Rackauckas Co-Authored-By: Devin Agent-Harness: Devin CLI Agent-Model: SWE-2 Max Agent-Session: local session, /home/crackauc/.local/share/devin/cli/sessions.db --- test/core_tests.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/core_tests.jl b/test/core_tests.jl index c3cc82e..5b5e584 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -196,9 +196,15 @@ end @test UnitLowerTriangular(Matrix(A)) \ b ≈ UnitLowerTriangular(A) \ b LA = FastAlmostBandedMatrices.LazyArrays - bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) - @test UpperTriangular(A) \ bpad ≈ UpperTriangular(Matrix(A)) \ Vector(bpad) - @test UnitUpperTriangular(A) \ bpad ≈ UnitUpperTriangular(Matrix(A)) \ Vector(bpad) + # The padded-ldiv path (LazyArrays' `materialize!` for + # `MatLdivVec{<:UnitOrUpperTriangularLayout, <:PaddedColumns}`, and `Vcat` + # with a `Zeros` tail producing a `PaddedColumns`) only exists in + # LazyArrays 2.13; below it padded triangular solves have no upstream path. + if Base.pkgversion(LA) >= v"2.13" + bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) + @test UpperTriangular(A) \ bpad ≈ UpperTriangular(Matrix(A)) \ Vector(bpad) + @test UnitUpperTriangular(A) \ bpad ≈ UnitUpperTriangular(Matrix(A)) \ Vector(bpad) + end end # https://github.com/SciML/FastAlmostBandedMatrices.jl/issues/19 From 2d907d6eb289b8decbfd00f3e94cea2f7eaaba08 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 3 Oct 2026 14:05:29 -0400 Subject: [PATCH 4/6] Address review of padded ldiv: public prefix view and O(n) backsolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop non-public paddeddata in favor of ArrayLayouts.colsupport, back-substitute the padded prefix without a dense n×n copy, and document that PaddedColumns (and in-place ldiv!) already apply on the LazyArrays 2.0 compat floor. Co-Authored-By: Chris Rackauckas Co-Authored-By: Cursor Agent Agent-Harness: Cursor Agent CLI Agent-Model: auto Agent-Session: ~/sandbox/goals/ci-green/work/cig-fabm91-r1/log.txt (local transcript, amdci2) --- src/FastAlmostBandedMatrices.jl | 29 +++++++++++++++++++++++++---- test/core_tests.jl | 20 ++++++++++++++------ test/qa/qa.jl | 3 ++- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/FastAlmostBandedMatrices.jl b/src/FastAlmostBandedMatrices.jl index 422ad96..2447bcc 100644 --- a/src/FastAlmostBandedMatrices.jl +++ b/src/FastAlmostBandedMatrices.jl @@ -816,11 +816,32 @@ end # A padded right-hand side is zero past its data, so an upper-triangular solve only # needs the leading square block; the `Zeros` tail cannot be written into anyway. -function _padded_upper_ldiv!(T, R, x) +# Back-substitute on that prefix in-place (O(n) storage). Densifying the leading +# block would be O(n²), and the existing almost-banded upper `materialize!` path +# cannot be reused here: it goes through `__original_almostbandedrank` / a +# `SubArray` of `AlmostBandedMatrix` and is already `@test_broken` for a dense fill. +@views function _padded_upper_ldiv!(Tri, R, x) A = triangulardata(R) - p = LazyArrays.paddeddata(x) - n = size(p, 1) - ldiv!(T(Matrix(view(A, 1:n, 1:n))), p) + b = view(x, colsupport(x)) + n = length(b) + iszero(n) && return x + unit = Tri === UnitUpperTriangular + Av = view(A, 1:n, 1:n) + rnk = almostbandedrank(Av) + _, u = almostbandwidths(Av) + for k in n:-1:1 + s = b[k] + jmax_band = min(n, k + u) + for j in (k + 1):jmax_band + s -= Av[k, j] * b[j] + end + if k <= rnk + for j in (jmax_band + 1):n + s -= Av[k, j] * b[j] + end + end + b[k] = unit ? s : s / Av[k, k] + end return x end diff --git a/test/core_tests.jl b/test/core_tests.jl index 5b5e584..983b1c7 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -196,14 +196,22 @@ end @test UnitLowerTriangular(Matrix(A)) \ b ≈ UnitLowerTriangular(A) \ b LA = FastAlmostBandedMatrices.LazyArrays - # The padded-ldiv path (LazyArrays' `materialize!` for - # `MatLdivVec{<:UnitOrUpperTriangularLayout, <:PaddedColumns}`, and `Vcat` - # with a `Zeros` tail producing a `PaddedColumns`) only exists in - # LazyArrays 2.13; below it padded triangular solves have no upstream path. + # `Vcat(v, Zeros)` already has `PaddedColumns` layout on the LazyArrays 2.0 + # compat floor. LazyArrays 2.13 added its own padded upper-triangular + # `materialize!`, which is what created the ambiguity, but the intersection + # methods are selected for in-place `ldiv!` on the whole compat range. + # Out-of-place `\` on older LazyArrays copies the RHS to a dense `Vector` + # first and therefore misses this path (hitting the pre-existing broken + # dense-fill upper solve instead). + bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) + @test ldiv!(UpperTriangular(A), copy(bpad)) ≈ + UpperTriangular(Matrix(A)) \ Vector(bpad) + @test ldiv!(UnitUpperTriangular(A), copy(bpad)) ≈ + UnitUpperTriangular(Matrix(A)) \ Vector(bpad) if Base.pkgversion(LA) >= v"2.13" - bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) @test UpperTriangular(A) \ bpad ≈ UpperTriangular(Matrix(A)) \ Vector(bpad) - @test UnitUpperTriangular(A) \ bpad ≈ UnitUpperTriangular(Matrix(A)) \ Vector(bpad) + @test UnitUpperTriangular(A) \ bpad ≈ + UnitUpperTriangular(Matrix(A)) \ Vector(bpad) end end diff --git a/test/qa/qa.jl b/test/qa/qa.jl index 9b95c46..f0e79b3 100644 --- a/test/qa/qa.jl +++ b/test/qa/qa.jl @@ -32,7 +32,8 @@ run_qa( ignore = ( :OneTo, :array_summary, :dims2string, :inds2string, :materialize!, :QRPackedQ, :arguments, :fast_scalar_indexing, :qr_instance, - :PaddedColumns, :paddeddata, + # needed to name LazyArrays' padded layout to resolve the ambiguity; not public in LazyArrays as of the tested version + :PaddedColumns, ), ), ), From 16c275c5cd78a231e1e9ff8c0525af2c69229925 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 3 Oct 2026 16:59:03 -0400 Subject: [PATCH 5/6] Fix padded upper ldiv for negative bandwidths and singular pivots Restrict back-substitution to strictly-upper columns so u=-1 and u=-2 match the dense triangular solve, and throw SingularException on a zero prefix pivot instead of returning NaN/Inf. Co-Authored-By: Chris Rackauckas Co-Authored-By: Cursor Agent Agent-Harness: Cursor Agent CLI Agent-Model: auto Agent-Session: ~/sandbox/goals/ci-green/work/cig-fabm91-r1/log.txt (local transcript, amdci2) --- src/FastAlmostBandedMatrices.jl | 15 +++++++++++---- test/core_tests.jl | 33 ++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/FastAlmostBandedMatrices.jl b/src/FastAlmostBandedMatrices.jl index 2447bcc..7ea500f 100644 --- a/src/FastAlmostBandedMatrices.jl +++ b/src/FastAlmostBandedMatrices.jl @@ -9,8 +9,8 @@ import ConcreteStructs: @concrete import LazyArrays import LazyArrays: LazyArray, Mul import LinearAlgebra -import LinearAlgebra: LowerTriangular, NoPivot, UnitLowerTriangular, UnitUpperTriangular, - UpperTriangular, diagind, lmul!, lu, qr, rank, triu! +import LinearAlgebra: LowerTriangular, NoPivot, SingularException, UnitLowerTriangular, + UnitUpperTriangular, UpperTriangular, diagind, lmul!, lu, qr, rank, triu! import MatrixFactorizations # The BandedMatrices.jl surface that FastAlmostBandedMatrices reexports (see the second @@ -831,16 +831,23 @@ end _, u = almostbandwidths(Av) for k in n:-1:1 s = b[k] + # Superdiagonals only: `k + u` can be < `k` when `u` is negative. jmax_band = min(n, k + u) for j in (k + 1):jmax_band s -= Av[k, j] * b[j] end if k <= rnk - for j in (jmax_band + 1):n + for j in max(jmax_band + 1, k + 1):n s -= Av[k, j] * b[j] end end - b[k] = unit ? s : s / Av[k, k] + if unit + b[k] = s + else + akk = Av[k, k] + iszero(akk) && throw(SingularException(k)) + b[k] = s / akk + end end return x end diff --git a/test/core_tests.jl b/test/core_tests.jl index 983b1c7..3d53d79 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -196,13 +196,8 @@ end @test UnitLowerTriangular(Matrix(A)) \ b ≈ UnitLowerTriangular(A) \ b LA = FastAlmostBandedMatrices.LazyArrays - # `Vcat(v, Zeros)` already has `PaddedColumns` layout on the LazyArrays 2.0 - # compat floor. LazyArrays 2.13 added its own padded upper-triangular - # `materialize!`, which is what created the ambiguity, but the intersection - # methods are selected for in-place `ldiv!` on the whole compat range. - # Out-of-place `\` on older LazyArrays copies the RHS to a dense `Vector` - # first and therefore misses this path (hitting the pre-existing broken - # dense-fill upper solve instead). + # In-place `ldiv!` on `Vcat(v, Zeros)` uses these methods for all LazyArrays + # 2.x; out-of-place `\` only keeps a padded RHS from LazyArrays 2.13. bpad = LA.Vcat(b[1:5], LA.Zeros(n - 5)) @test ldiv!(UpperTriangular(A), copy(bpad)) ≈ UpperTriangular(Matrix(A)) \ Vector(bpad) @@ -213,6 +208,30 @@ end @test UnitUpperTriangular(A) \ bpad ≈ UnitUpperTriangular(Matrix(A)) \ Vector(bpad) end + + # Negative upper bandwidths: only strictly-upper columns may be subtracted. + for u in (-1, -2) + F = [ + 2.0 0.1 0.2 0.3 + 0.1 2.0 0.3 0.2 + 0.2 0.3 2.0 0.1 + 0.3 0.2 0.1 2.0 + ] + Aneg = AlmostBandedMatrix(BandedMatrix(zeros(4, 4), (3, u)), F) + bneg = LA.Vcat([1.0, 2.0], LA.Zeros(2)) + for Tri in (UpperTriangular, UnitUpperTriangular) + @test ldiv!(Tri(Aneg), copy(bneg)) ≈ Tri(Matrix(Aneg)) \ Vector(bneg) + end + end + + Asing = AlmostBandedMatrix(BandedMatrix(zeros(4, 4), (1, 1)), zeros(1, 4)) + Asing[band(0)] .= 2 + Asing[2, 2] = 0 + bsing = LA.Vcat([1.0, 2.0], LA.Zeros(2)) + @test_throws SingularException(2) ldiv!(UpperTriangular(Asing), copy(bsing)) + @test_throws SingularException(2) ldiv!( + UpperTriangular(Matrix(Asing)), copy(Vector(bsing)) + ) end # https://github.com/SciML/FastAlmostBandedMatrices.jl/issues/19 From 94e7b9898f69e5a0964182ebf796b3c4ed14611e Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 3 Oct 2026 18:14:19 -0400 Subject: [PATCH 6/6] Drop dense LAPACK SingularException control from padded singular test On Julia 1.10 the dense UpperTriangular path throws LAPACKException(2) rather than SingularException(2). Keep the AlmostBanded padded-solve assertion strict and stop depending on the dense exception type. Co-Authored-By: Chris Rackauckas Co-Authored-By: Cursor Agent Agent-Harness: Cursor Agent CLI Agent-Model: auto Agent-Session: ~/sandbox/goals/ci-green/work/cig-fabm91-r1/log.txt (local transcript, amdci2) --- test/core_tests.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/core_tests.jl b/test/core_tests.jl index 3d53d79..6cfcc51 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -228,10 +228,9 @@ end Asing[band(0)] .= 2 Asing[2, 2] = 0 bsing = LA.Vcat([1.0, 2.0], LA.Zeros(2)) + # Package path must throw SingularException; dense LAPACK may throw + # LAPACKException instead (Julia 1.10) so is not asserted here. @test_throws SingularException(2) ldiv!(UpperTriangular(Asing), copy(bsing)) - @test_throws SingularException(2) ldiv!( - UpperTriangular(Matrix(Asing)), copy(Vector(bsing)) - ) end # https://github.com/SciML/FastAlmostBandedMatrices.jl/issues/19