From 1d39d74831856478d3f8a753cce16aabf127a9f5 Mon Sep 17 00:00:00 2001 From: Sheehan Olver Date: Tue, 13 Oct 2026 19:41:16 +0100 Subject: [PATCH] Avoid reshape when materialising DualLayout views of matrices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_dual_adjoint`/`_dual_transpose` assumed the parent of a `DualLayout` view is an adjoint/transpose of a vector. When it is a 1×n matrix with `DualLayout` (e.g. a `BlockedMatrix` wrapping an adjoint vector), the adjoint is an n×1 matrix and linear indexing into it reshapes, which fails for infinite arrays. Take a column view instead. Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 +- src/memorylayout.jl | 8 ++++++-- test/test_layoutarray.jl | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 6f723e6..de648e1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ArrayLayouts" uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -version = "1.16" +version = "1.16.1" authors = ["Sheehan Olver "] [deps] diff --git a/src/memorylayout.jl b/src/memorylayout.jl index 6f2263c..4375ce0 100644 --- a/src/memorylayout.jl +++ b/src/memorylayout.jl @@ -299,8 +299,12 @@ sublayout(::DualLayout{ML}, INDS::Type) where ML = sublayout(ML(), INDS) # try to maintain "type" of parent ie adjoint/transpose when materialising, even though layouts are equivalent # to preserve special overloads -_dual_adjoint(a::SubArray{<:Any, 2, <:Any, <:Tuple{Slice,Any}}) = view(parent(a)', parentindices(a)[2]) -_dual_transpose(a::SubArray{<:Any, 2, <:Any, <:Tuple{Slice,Any}}) = view(transpose(parent(a)), parentindices(a)[2]) +# parent may be a 1×n matrix with DualLayout (e.g. a wrapper around an adjoint vector), in which case +# we take a column view of its adjoint rather than linear indexing (which would reshape) +_dual_view(v::AbstractVector, jr) = view(v, jr) +_dual_view(A::AbstractMatrix, jr) = view(A, jr, only(axes(A,2))) +_dual_adjoint(a::SubArray{<:Any, 2, <:Any, <:Tuple{Slice,Any}}) = _dual_view(parent(a)', parentindices(a)[2]) +_dual_transpose(a::SubArray{<:Any, 2, <:Any, <:Tuple{Slice,Any}}) = _dual_view(transpose(parent(a)), parentindices(a)[2]) sub_materialize(::DualLayout{ML}, A::AbstractMatrix{<:Real}) where ML = sub_materialize(adjointlayout(eltype(A), ML()), _dual_adjoint(A))' sub_materialize(::DualLayout{ML}, A::AbstractMatrix) where ML<:ConjLayout = sub_materialize(adjointlayout(eltype(A), ML()), _dual_adjoint(A))' sub_materialize(::DualLayout{ML}, A::AbstractMatrix) where ML = transpose(sub_materialize(transposelayout(ML()), _dual_transpose(A))) diff --git a/test/test_layoutarray.jl b/test/test_layoutarray.jl index ffa2cf6..ad34e70 100644 --- a/test/test_layoutarray.jl +++ b/test/test_layoutarray.jl @@ -910,4 +910,22 @@ end @test transpose(lv) * F == transpose(v) * F end +@testset "DualLayout matrix parent" begin + # a 1×n matrix with DualLayout that is not itself an adjoint vector + a = randn(5) + for (M, f) in ((MyMatrix(a'), adjoint), (MyMatrix(transpose(a)), transpose), + (MyMatrix((a .+ im)'), adjoint), (MyMatrix(transpose(a .+ im)), transpose)) + @test MemoryLayout(M) isa DualLayout + V = view(M, :, 1:3) + @test MemoryLayout(V) isa DualLayout + @test M[:,1:3] == f(f(M)[1:3]) + # avoid linear indexing into a matrix, which reshapes + @test !(parent(ArrayLayouts._dual_adjoint(V)) isa Base.ReshapedArray) + @test !(parent(ArrayLayouts._dual_transpose(V)) isa Base.ReshapedArray) + end + # linear indexing into an infinite matrix is not supported, so must avoid reshape + M = MyMatrix(InfiniteArrays.InfVec()') + @test M[:,1:3] == M.A[:,1:3] +end + end