From af9a62727a3759977f8a9ece981007f8a3922c05 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 28 Feb 2022 12:36:48 +0100 Subject: [PATCH 01/17] add tests --- test/blockmap.jl | 36 ++++++++++++++++++++++++++---------- test/composition.jl | 3 ++- test/kronecker.jl | 3 +++ test/linearcombination.jl | 16 +++++++++++----- test/runtests.jl | 1 - 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/test/blockmap.jl b/test/blockmap.jl index 1c250cc5..8bffb410 100644 --- a/test/blockmap.jl +++ b/test/blockmap.jl @@ -1,5 +1,5 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, InteractiveUtils -using LinearMaps: FiveArg, ThreeArg +using LinearMaps: FiveArg @testset "block maps" begin @testset "hcat" begin @@ -8,6 +8,10 @@ using LinearMaps: FiveArg, ThreeArg A12 = rand(elty, 10, n2) v = rand(elty, 10) L = @inferred hcat(LinearMap(A11), LinearMap(A12)) + @test L.maps isa Tuple + Lv = @inferred LinearMaps.BlockMap{elty}([LinearMap(A11), LinearMap(A12)], (2,)) + @test Lv.maps isa Vector + @test L == Lv == LinearMaps.BlockMap([LinearMap(A11), LinearMap(A12)], (2,)) @test occursin("10×$(10+n2) LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() @test L isa LinearMaps.BlockMap{elty} @@ -16,9 +20,9 @@ using LinearMaps: FiveArg, ThreeArg end A = [A11 A12] x = rand(10+n2) - @test size(L) == size(A) - @test Matrix(L) ≈ A - @test L * x ≈ A * x + @test size(L) == size(A) == size(Lv) + @test Matrix(L) ≈ A ≈ Matrix(Lv) + @test L * x ≈ A * x ≈ Lv * x L = @inferred hcat(LinearMap(A11), LinearMap(A12), LinearMap(A11)) A = [A11 A12 A11] @test Matrix(L) ≈ A @@ -53,6 +57,10 @@ using LinearMaps: FiveArg, ThreeArg @test Matrix(L) ≈ A11 A21 = rand(elty, 20, 10) L = @inferred vcat(LinearMap(A11), LinearMap(A21)) + @test L.maps isa Tuple + Lv = LinearMaps.BlockMap{elty}([LinearMap(A11), LinearMap(A21)], (1,1)) + @test Lv.maps isa Vector + @test L == Lv @test occursin("30×10 LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) @test L isa LinearMaps.BlockMap{elty} @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() @@ -60,8 +68,8 @@ using LinearMaps: FiveArg, ThreeArg A = [A11; A21] x = rand(10) @test size(L) == size(A) - @test Matrix(L) ≈ A - @test L * x ≈ A * x + @test Matrix(L) == Matrix(Lv) == A + @test L * x ≈ Lv * x ≈ A * x A = [I; I; I; A11; A11; A11; v v v v v v v v v v] @test (@which [I; I; I; A11; A11; A11; v v v v v v v v v v]).module != LinearMaps L = @inferred vcat(I, I, I, LinearMap(A11), LinearMap(A11), LinearMap(A11), reduce(hcat, fill(v, 10))) @@ -85,14 +93,17 @@ using LinearMaps: FiveArg, ThreeArg @test (@which [A11 A12; A21 A22]).module != LinearMaps @inferred hvcat((2,2), LinearMap(A11), LinearMap(A12), LinearMap(A21), LinearMap(A22)) L = [LinearMap(A11) LinearMap(A12); LinearMap(A21) LinearMap(A22)] + @test L.maps isa Tuple + Lv = @inferred LinearMaps.BlockMap{elty}([LinearMap(A11), LinearMap(A12), LinearMap(A21), LinearMap(A22)], (2,2)) + @test Lv.maps isa Vector @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() @test @inferred !issymmetric(L) @test @inferred !ishermitian(L) x = rand(30) @test L isa LinearMaps.BlockMap{elty} @test size(L) == size(A) - @test L * x ≈ A * x - @test Matrix(L) == A + @test L * x ≈ Lv * x ≈ A * x + @test Matrix(L) == Matrix(Lv) == A @test convert(AbstractMatrix, L) == A A = [I A12; A21 I] @test (@which [I A12; A21 I]).module != LinearMaps @@ -202,6 +213,9 @@ using LinearMaps: FiveArg, ThreeArg @test (@which cat(M1, M2, M3, M2, M1; dims=(1,2))).module != LinearMaps x = randn(elty, size(Md, 2)) Bd = @inferred blockdiag(L1, L2, L3, L2, L1) + @test Bd.maps isa Tuple + Bdv = @inferred LinearMaps.BlockDiagonalMap{elty}([L1, L2, L3, L2, L1]) + @test Bdv.maps isa Vector @test @inferred(LinearMaps.MulStyle(Bd)) === FiveArg() @test occursin("25×39 LinearMaps.BlockDiagonalMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), Bd)) @test Matrix(Bd) == Md @@ -211,12 +225,12 @@ using LinearMaps: FiveArg, ThreeArg @test Matrix(@inferred blockdiag(L1, L2)) == blockdiag(sparse.((M1, M2))...) Bd2 = @inferred cat(L1, L2, L3, L2, L1; dims=(1,2)) @test_throws ArgumentError cat(L1, L2, L3, L2, L1; dims=(2,2)) - @test Bd == Bd2 + @test Bd == Bdv == Bd2 @test Bd == blockdiag(L1, M2, M3, M2, M1) @test size(Bd) == (25, 39) @test !issymmetric(Bd) @test !ishermitian(Bd) - @test @inferred Bd * x ≈ Md * x + @test (@inferred Bd * x) ≈ Bdv * x ≈ Md * x for transform in (identity, adjoint, transpose) @test Matrix(@inferred transform(Bd)) == transform(Md) @test Matrix(@inferred transform(LinearMap(Bd))) == transform(Md) @@ -224,11 +238,13 @@ using LinearMaps: FiveArg, ThreeArg y = randn(elty, size(Md, 1)) for α in (0, 1, rand(elty)), β in (0, 1, rand(elty)) @test mul!(copy(y), Bd, x, α, β) ≈ y*β .+ Md*x*α + @test mul!(copy(y), Bdv, x, α, β) ≈ y*β .+ Md*x*α end X = randn(elty, size(Md, 2), 10) Y = randn(elty, size(Md, 1), 10) for α in (0, 1, rand(elty)), β in (0, 1, rand(elty)) @test mul!(copy(Y), Bd, X, α, β) ≈ Y*β .+ Md*X*α + @test mul!(copy(Y), Bdv, X, α, β) ≈ Y*β .+ Md*X*α end end end diff --git a/test/composition.jl b/test/composition.jl index 235d7123..da8613ac 100644 --- a/test/composition.jl +++ b/test/composition.jl @@ -23,9 +23,10 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays F2 = F*F FC2 = FC*FC F4 = FC2 * F2 + F4v = @inferred LinearMaps.CompositeMap{ComplexF64}([FC2, F2]) @test occursin("10×10 LinearMaps.CompositeMap{$(eltype(F4))}", sprint((t, s) -> show(t, "text/plain", s), F4)) @test length(F4.maps) == 4 - @test @inferred F4 * v == @inferred F * (F * (F * (F * v))) + @test (@inferred F4 * v) == F4v * v == (@inferred F * (F * (F * (F * v)))) @test @inferred Matrix(M * transpose(M)) ≈ A * transpose(A) @test @inferred !isposdef(M * transpose(M)) @test @inferred isposdef(LinearMap(M * M', isposdef=true)) diff --git a/test/kronecker.jl b/test/kronecker.jl index 29eaa0b9..e080c46f 100644 --- a/test/kronecker.jl +++ b/test/kronecker.jl @@ -8,6 +8,9 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays LA = LinearMap(A) LB = LinearMap(B) LK = @inferred kron(LA, LB) + LKv = @inferred LinearMaps.KroneckerMap{ComplexF64}([LA, LB]) + @test LK * ones(6) ≈ LKv * ones(6) + @test LKv.maps isa Vector @test kron(LA, 2LB) isa LinearMaps.ScaledMap @test kron(3LA, LB) isa LinearMaps.ScaledMap @test kron(3LA, 2LB) isa LinearMaps.ScaledMap diff --git a/test/linearcombination.jl b/test/linearcombination.jl index 3f2ed8a2..f922f3f3 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -1,4 +1,5 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools +using LinearMaps: FiveArg @testset "linear combinations" begin CS! = LinearMap{ComplexF64}(cumsum!, @@ -9,15 +10,20 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools b = @benchmarkable mul!($u, $CS!, $v) @test run(b, samples=3).allocs == 0 n = 10 - L = sum(fill(CS!, n)) - M = Matrix(L) - @test M == LowerTriangular(fill(n, size(L))) + L = @inferred sum(ntuple(_ -> CS!, n)) + Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) + @test L == Lv + M, Mv = Matrix.((L, Lv)) + @test M == Mv == LowerTriangular(fill(n, size(L))) @test_throws AssertionError LinearMaps.LinearCombination{Float64}((CS!, CS!)) - @test occursin("10×10 LinearMaps.LinearCombination{$(eltype(L))}", sprint((t, s) -> show(t, "text/plain", s), L)) - @test occursin("10×10 LinearMaps.LinearCombination{$(eltype(L))}", sprint((t, s) -> show(t, "text/plain", s), L+CS!)) + @test occursin("10×10 $LinearMaps.LinearCombination{$(eltype(L))}", sprint((t, s) -> show(t, "text/plain", s), L)) + @test occursin("10×10 $LinearMaps.LinearCombination{$(eltype(L))}", sprint((t, s) -> show(t, "text/plain", s), L+CS!)) @test mul!(u, L, v) ≈ n * cumsum(v) + @test mul!(u, Lv, v) ≈ n * cumsum(v) b = @benchmarkable mul!($u, $L, $v, 2, 2) @test run(b, samples=5).allocs <= 1 + b = @benchmarkable mul!($u, $Lv, $v, 2, 2) + @test run(b, samples=5).allocs <= 1 for α in (false, true, rand(ComplexF64)), β in (false, true, rand(ComplexF64)) for transform in (identity, adjoint, transpose) @test mul!(copy(u), transform(L), v, α, β) ≈ transform(M)*v*α + u*β diff --git a/test/runtests.jl b/test/runtests.jl index 60a89407..5e7cf97f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,4 @@ using Test, LinearMaps, Aqua -using LinearMaps: FiveArg, ThreeArg @testset "code quality" begin Aqua.test_all(LinearMaps) From 25f053536379758dcdfcc5bb63459a35f9f448cc Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 28 Feb 2022 12:42:06 +0100 Subject: [PATCH 02/17] add the code --- src/LinearMaps.jl | 21 ++++++++++- src/blockmap.jl | 45 +++++++++++----------- src/composition.jl | 54 +++++++++++++++++++-------- src/conversion.jl | 4 +- src/kronecker.jl | 35 ++++++++++------- src/linearcombination.jl | 81 +++++++++++++++++++++------------------- src/show.jl | 2 +- 7 files changed, 148 insertions(+), 94 deletions(-) diff --git a/src/LinearMaps.jl b/src/LinearMaps.jl index 9d0c88d9..88acbf8f 100644 --- a/src/LinearMaps.jl +++ b/src/LinearMaps.jl @@ -16,6 +16,10 @@ const MapOrVecOrMat{T} = Union{LinearMap{T}, AbstractVecOrMat{T}} const MapOrMatrix{T} = Union{LinearMap{T}, AbstractMatrix{T}} const RealOrComplex = Union{Real, Complex} +const LinearMapTuple = Tuple{Vararg{LinearMap}} +const LinearMapVector = AbstractVector{<:LinearMap} +const LinearMapTupleOrVector = Union{LinearMapTuple,LinearMapVector} + Base.eltype(::LinearMap{T}) where {T} = T abstract type MulStyle end @@ -69,6 +73,21 @@ convert_to_lmaps(A) = (convert_to_lmaps_(A),) @inline convert_to_lmaps(A, B, Cs...) = (convert_to_lmaps_(A), convert_to_lmaps_(B), convert_to_lmaps(Cs...)...) +_front(As::Tuple) = Base.front(As) +_front(As::AbstractVector) = @inbounds @views As[1:end-1] +_tail(As::Tuple) = Base.tail(As) +_tail(As::AbstractVector) = @inbounds @views As[2:end] + +_combine(A::LinearMap, B::LinearMap) = tuple(A, B) +_combine(A::LinearMap, Bs::LinearMapTuple) = tuple(A, Bs...) +_combine(As::LinearMapTuple, B::LinearMap) = tuple(As..., B) +_combine(As::LinearMapTuple, Bs::LinearMapTuple) = tuple(As..., Bs...) +_combine(A::LinearMap, Bs::LinearMapVector) = vcat(A, Bs...) +_combine(As::LinearMapVector, B::LinearMap) = vcat(As..., B) +_combine(As::LinearMapVector, Bs::LinearMapTuple) = vcat(As..., Bs...) +_combine(As::LinearMapTuple, Bs::LinearMapVector) = vcat(As..., Bs...) +_combine(As::LinearMapVector, Bs::LinearMapVector) = vcat(As..., Bs...) + # The (internal) multiplication logic is as follows: # - `*(A, x)` calls `mul!(y, A, x)` for appropriately-sized y # - `mul!` checks consistency of the sizes, and calls `_unsafe_mul!`, @@ -233,8 +252,6 @@ function _unsafe_mul!(y::AbstractMatrix, A::LinearMap, x::AbstractMatrix, α, β return _generic_mapmat_mul!(y, A, x, α, β) end -const LinearMapTuple = Tuple{Vararg{LinearMap}} - include("left.jl") # left multiplication by a transpose or adjoint vector include("transpose.jl") # transposing linear maps include("wrappedmap.jl") # wrap a matrix of linear map in a new type, thereby allowing to alter its properties diff --git a/src/blockmap.jl b/src/blockmap.jl index d3957ea3..1bf32b08 100644 --- a/src/blockmap.jl +++ b/src/blockmap.jl @@ -1,14 +1,14 @@ struct BlockMap{T, - As<:LinearMapTuple, + As<:LinearMapTupleOrVector, Rs<:Tuple{Vararg{Int}}, - Rranges<:Tuple{Vararg{UnitRange{Int}}}, - Cranges<:Tuple{Vararg{UnitRange{Int}}}} <: LinearMap{T} + Rranges<:Vector{UnitRange{Int}}, + Cranges<:Vector{UnitRange{Int}}} <: LinearMap{T} maps::As rows::Rs rowranges::Rranges colranges::Cranges function BlockMap{T,As,Rs}(maps::As, rows::Rs) where - {T, As<:LinearMapTuple, Rs<:Tuple{Vararg{Int}}} + {T, As<:LinearMapTupleOrVector, Rs<:Tuple{Vararg{Int}}} for TA in Base.Generator(eltype, maps) promote_type(T, TA) == T || error("eltype $TA cannot be promoted to $T in BlockMap constructor") @@ -19,15 +19,17 @@ struct BlockMap{T, end end -BlockMap{T}(maps::As, rows::Rs) where {T, As<:LinearMapTuple, Rs} = +BlockMap{T}(maps::As, rows::Rs) where {T, As<:LinearMapTupleOrVector, Rs} = BlockMap{T, As, Rs}(maps, rows) +BlockMap(maps::As, rows::Rs) where {As<:LinearMapTupleOrVector, Rs} = + BlockMap{promote_type(map(eltype, maps)...), As, Rs}(maps, rows) MulStyle(A::BlockMap) = MulStyle(A.maps...) -function _getranges(maps, dim, inds=ntuple(identity, Val(length(maps)))) - sizes = map(i -> size(maps[i], dim)::Int, inds) - ends = cumsum(sizes) - starts = (1, (1 .+ Base.front(ends))...) +function _getranges(maps, dim, inds=1:length(maps)) + ends = map(i -> size(maps[i], dim)::Int, inds) + cumsum!(ends, ends) + starts = vcat(1, 1 .+ @views ends[1:end-1]) return UnitRange.(starts, ends) end @@ -40,14 +42,15 @@ block linear map obtained from `hvcat(rows, maps...)`. """ function rowcolranges(maps, rows) # find indices of the row-wise first maps - firstmapinds = cumsum((1, Base.front(rows)...)) + firstmapinds = vcat(1, Base.front(rows)...) + cumsum!(firstmapinds, firstmapinds) # compute rowranges from first dimension of the row-wise first maps rowranges = _getranges(maps, 1, firstmapinds) # compute ranges from second dimension as if all in one row temp = _getranges(maps, 2) # introduce "line breaks" - colranges = ntuple(Val(length(maps))) do i + colranges = map(1:length(maps)) do i # for each map find the index of the respective row-wise first map # something-trick just to assure the compiler that the index is an Int @inbounds firstmapind = firstmapinds[something(findlast(<=(i), firstmapinds), 1)] @@ -277,7 +280,7 @@ end ############ Base.:(==)(A::BlockMap, B::BlockMap) = - (eltype(A) == eltype(B) && A.maps == B.maps && A.rows == B.rows) + (eltype(A) == eltype(B) && all(A.maps .== B.maps) && all(A.rows .== B.rows)) ############ # multiplication helper functions @@ -350,9 +353,9 @@ function _transblockmul!(y, A::BlockMap, x, α, β, transform) # subsequent block rows of A (block columns of A'), # add results to corresponding parts of y # TODO: think about multithreading - for (row, xi) in zip(Base.tail(rows), Base.tail(xinds)) - xrow = selectdim(x, 1, xi) - for _ in 1:row + @inbounds for i in 2:length(rows) + xrow = selectdim(x, 1, xinds[i]) + for _ in 1:rows[i] mapind +=1 yrow = selectdim(y, 1, yinds[mapind]) _unsafe_mul!(yrow, transform(maps[mapind]), xrow, α, true) @@ -397,12 +400,12 @@ end # BlockDiagonalMap ############ struct BlockDiagonalMap{T, - As<:LinearMapTuple, - Ranges<:Tuple{Vararg{UnitRange{Int}}}} <: LinearMap{T} + As<:LinearMapTupleOrVector, + Ranges<:Vector{UnitRange{Int}}} <: LinearMap{T} maps::As rowranges::Ranges colranges::Ranges - function BlockDiagonalMap{T, As}(maps::As) where {T, As<:LinearMapTuple} + function BlockDiagonalMap{T, As}(maps::As) where {T, As<:LinearMapTupleOrVector} for TA in Base.Generator(eltype, maps) promote_type(T, TA) == T || error("eltype $TA cannot be promoted to $T in BlockDiagonalMap constructor") @@ -413,7 +416,7 @@ struct BlockDiagonalMap{T, end end -BlockDiagonalMap{T}(maps::As) where {T, As<:LinearMapTuple} = +BlockDiagonalMap{T}(maps::As) where {T, As<:LinearMapTupleOrVector} = BlockDiagonalMap{T,As}(maps) BlockDiagonalMap(maps::LinearMap...) = BlockDiagonalMap{promote_type(map(eltype, maps)...)}(maps) @@ -478,7 +481,7 @@ LinearAlgebra.transpose(A::BlockDiagonalMap{T}) where {T} = BlockDiagonalMap{T}(map(transpose, A.maps)) Base.:(==)(A::BlockDiagonalMap, B::BlockDiagonalMap) = - (eltype(A) == eltype(B) && A.maps == B.maps) + (eltype(A) == eltype(B) && all(A.maps .== B.maps)) for (In, Out) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix)) @eval begin @@ -496,7 +499,7 @@ end function _blockscaling!(y, A::BlockDiagonalMap, x, α, β) maps, yinds, xinds = A.maps, A.rowranges, A.colranges # TODO: think about multi-threading here - @inbounds for i in eachindex(yinds, maps, xinds) + @inbounds for i in 1:length(maps) _unsafe_mul!(selectdim(y, 1, yinds[i]), maps[i], selectdim(x, 1, xinds[i]), α, β) end return y diff --git a/src/composition.jl b/src/composition.jl index e1b008a4..e9d2ceb4 100644 --- a/src/composition.jl +++ b/src/composition.jl @@ -1,4 +1,4 @@ -struct CompositeMap{T, As<:LinearMapTuple} <: LinearMap{T} +struct CompositeMap{T, As<:LinearMapTupleOrVector} <: LinearMap{T} maps::As # stored in order of application to vector function CompositeMap{T, As}(maps::As) where {T, As} N = length(maps) @@ -12,7 +12,7 @@ struct CompositeMap{T, As<:LinearMapTuple} <: LinearMap{T} new{T, As}(maps) end end -CompositeMap{T}(maps::As) where {T, As<:LinearMapTuple} = CompositeMap{T, As}(maps) +CompositeMap{T}(maps::As) where {T, As<:LinearMapTupleOrVector} = CompositeMap{T, As}(maps) # basic methods Base.size(A::CompositeMap) = (size(A.maps[end], 1), size(A.maps[1], 2)) @@ -26,8 +26,18 @@ for (f, _f, g) in ((:issymmetric, :_issymmetric, :transpose), LinearAlgebra.$f(A::CompositeMap) = $_f(A.maps) $_f(maps::Tuple{}) = true $_f(maps::Tuple{<:LinearMap}) = $f(maps[1]) - $_f(maps::Tuple{Vararg{LinearMap}}) = + $_f(maps::LinearMapTuple) = maps[end] == $g(maps[1]) && $_f(Base.front(Base.tail(maps))) + function $_f(maps::LinearMapVector) + n = length(maps) + if n == 0 + return true + elseif n == 1 + return $f(maps[1]) + else + return maps[end] == $g(maps[1]) && $_f(maps[2:end-1]) + end + end # since the introduction of ScaledMap, the following cases cannot occur # function $_f(maps::Tuple{Vararg{LinearMap}}) # length(maps) >= 2 # if maps[1] isa UniformScalingMap{<:RealOrComplex} @@ -45,23 +55,34 @@ end LinearAlgebra.isposdef(A::CompositeMap) = _isposdef(A.maps) _isposdef(maps::Tuple{}) = true # empty product is equivalent to "I" which is pos. def. _isposdef(maps::Tuple{<:LinearMap}) = isposdef(maps[1]) -function _isposdef(maps::Tuple{Vararg{LinearMap}}) +function _isposdef(maps::LinearMapTuple) (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) && - isposdef(maps[1]) && _isposdef(Base.front(Base.tail(maps))) + isposdef(maps[1]) && _isposdef(Base.front(Base.tail(maps))) +end +function _isposdef(maps::LinearMapVector) + n = length(maps) + if n == 0 + return true + elseif n == 1 + return isposdef(maps[1]) + else + return (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) && + isposdef(maps[1]) && _isposdef(maps[2:end-1]) + end end # scalar multiplication and division (non-commutative case) function Base.:(*)(α::Number, A::LinearMap) T = promote_type(typeof(α), eltype(A)) - return CompositeMap{T}(tuple(A, UniformScalingMap(α, size(A, 1)))) + return CompositeMap{T}(_combine(A, UniformScalingMap(α, size(A, 1)))) end function Base.:(*)(α::Number, A::CompositeMap) T = promote_type(typeof(α), eltype(A)) Alast = last(A.maps) if Alast isa UniformScalingMap - return CompositeMap{T}(tuple(Base.front(A.maps)..., α * Alast)) + return CompositeMap{T}(_combine(_front(A.maps), α * Alast)) else - return CompositeMap{T}(tuple(A.maps..., UniformScalingMap(α, size(A, 1)))) + return CompositeMap{T}(_combine(A.maps, UniformScalingMap(α, size(A, 1)))) end end # needed for disambiguation @@ -71,15 +92,15 @@ function Base.:(*)(α::RealOrComplex, A::CompositeMap{<:RealOrComplex}) end function Base.:(*)(A::LinearMap, α::Number) T = promote_type(typeof(α), eltype(A)) - return CompositeMap{T}(tuple(UniformScalingMap(α, size(A, 2)), A)) + return CompositeMap{T}(_combine(UniformScalingMap(α, size(A, 2)), A)) end function Base.:(*)(A::CompositeMap, α::Number) T = promote_type(typeof(α), eltype(A)) Afirst = first(A.maps) if Afirst isa UniformScalingMap - return CompositeMap{T}(tuple(Afirst * α, Base.tail(A.maps)...)) + return CompositeMap{T}(_combine(Afirst * α, _tail(A.maps))) else - return CompositeMap{T}(tuple(UniformScalingMap(α, size(A, 2)), A.maps...)) + return CompositeMap{T}(_combine(UniformScalingMap(α, size(A, 2)), A.maps)) end end # needed for disambiguation @@ -110,19 +131,19 @@ julia> LinearMap(ones(Int, 3, 3)) * CS * I * rand(3, 3); """ function Base.:(*)(A₁::LinearMap, A₂::LinearMap) T = promote_type(eltype(A₁), eltype(A₂)) - return CompositeMap{T}(tuple(A₂, A₁)) + return CompositeMap{T}(_combine(A₂, A₁)) end function Base.:(*)(A₁::LinearMap, A₂::CompositeMap) T = promote_type(eltype(A₁), eltype(A₂)) - return CompositeMap{T}(tuple(A₂.maps..., A₁)) + return CompositeMap{T}(_combine(A₂.maps, A₁)) end function Base.:(*)(A₁::CompositeMap, A₂::LinearMap) T = promote_type(eltype(A₁), eltype(A₂)) - return CompositeMap{T}(tuple(A₂, A₁.maps...)) + return CompositeMap{T}(_combine(A₂, A₁.maps)) end function Base.:(*)(A₁::CompositeMap, A₂::CompositeMap) T = promote_type(eltype(A₁), eltype(A₂)) - return CompositeMap{T}(tuple(A₂.maps..., A₁.maps...)) + return CompositeMap{T}(_combine(A₂.maps, A₁.maps)) end # needed for disambiguation Base.:(*)(A₁::ScaledMap, A₂::CompositeMap) = A₁.λ * (A₁.lmap * A₂) @@ -135,7 +156,8 @@ LinearAlgebra.adjoint(A::CompositeMap{T}) where {T} = CompositeMap{T}(map(adjoint, reverse(A.maps))) # comparison of CompositeMap objects -Base.:(==)(A::CompositeMap, B::CompositeMap) = (eltype(A) == eltype(B) && A.maps == B.maps) +Base.:(==)(A::CompositeMap, B::CompositeMap) = + (eltype(A) == eltype(B) && all(A.maps .== B.maps)) # multiplication with vectors/matrices _unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap, x::AbstractVector) = diff --git a/src/conversion.jl b/src/conversion.jl index a3ceb96d..8262fdf7 100644 --- a/src/conversion.jl +++ b/src/conversion.jl @@ -136,7 +136,7 @@ function SparseArrays.sparse(A::BlockMap) return hvcat( A.rows, convert(SparseMatrixCSC, first(A.maps)), - convert.(AbstractMatrix, Base.tail(A.maps))... + convert.(AbstractArray, _tail(A.maps))... ) end Base.Matrix{T}(A::BlockDiagonalMap) where {T} = Base._cat((1,2), convert.(Matrix{T}, A.maps)...) @@ -152,7 +152,7 @@ Base.convert(::Type{AbstractMatrix}, A::KroneckerMap) = function SparseArrays.sparse(A::KroneckerMap) return kron( convert(SparseMatrixCSC, first(A.maps)), - convert.(AbstractMatrix, Base.tail(A.maps))... + convert.(AbstractMatrix, _tail(A.maps))... ) end diff --git a/src/kronecker.jl b/src/kronecker.jl index 6fb80ed9..cdd72826 100644 --- a/src/kronecker.jl +++ b/src/kronecker.jl @@ -1,6 +1,6 @@ -struct KroneckerMap{T, As<:LinearMapTuple} <: LinearMap{T} +struct KroneckerMap{T, As<:LinearMapTupleOrVector} <: LinearMap{T} maps::As - function KroneckerMap{T}(maps::LinearMapTuple) where {T} + function KroneckerMap{T}(maps::LinearMapTupleOrVector) where {T} for TA in Base.Iterators.map(eltype, maps) promote_type(T, TA) == T || error("eltype $TA cannot be promoted to $T in KroneckerMap constructor") @@ -47,11 +47,11 @@ julia> Matrix(Δ) Base.kron(A::LinearMap, B::LinearMap) = KroneckerMap{promote_type(eltype(A), eltype(B))}((A, B)) Base.kron(A::LinearMap, B::KroneckerMap) = - KroneckerMap{promote_type(eltype(A), eltype(B))}(tuple(A, B.maps...)) + KroneckerMap{promote_type(eltype(A), eltype(B))}(_combine(A, B.maps)) Base.kron(A::KroneckerMap, B::LinearMap) = - KroneckerMap{promote_type(eltype(A), eltype(B))}(tuple(A.maps..., B)) + KroneckerMap{promote_type(eltype(A), eltype(B))}(_combine(A.maps, B)) Base.kron(A::KroneckerMap, B::KroneckerMap) = - KroneckerMap{promote_type(eltype(A), eltype(B))}(tuple(A.maps..., B.maps...)) + KroneckerMap{promote_type(eltype(A), eltype(B))}(_combine(A.maps, B.maps)) # hoist out scalings Base.kron(A::ScaledMap, B::LinearMap) = A.λ * kron(A.lmap, B) Base.kron(A::LinearMap, B::ScaledMap) = kron(A, B.lmap) * B.λ @@ -107,7 +107,8 @@ LinearAlgebra.ishermitian(A::KroneckerMap) = all(ishermitian, A.maps) LinearAlgebra.adjoint(A::KroneckerMap) = KroneckerMap{eltype(A)}(map(adjoint, A.maps)) LinearAlgebra.transpose(A::KroneckerMap) = KroneckerMap{eltype(A)}(map(transpose, A.maps)) -Base.:(==)(A::KroneckerMap, B::KroneckerMap) = (eltype(A) == eltype(B) && A.maps == B.maps) +Base.:(==)(A::KroneckerMap, B::KroneckerMap) = + (eltype(A) == eltype(B) && all(A.maps .== B.maps)) ################# # multiplication helper functions @@ -166,6 +167,9 @@ const AdjOrTransVectorMap{T} = WrappedMap{T,<:LinearAlgebra.AdjOrTransAbsVec} const KroneckerMap2{T} = KroneckerMap{T, <:Tuple{LinearMap, LinearMap}} +_krontail(maps::LinearMapTuple) = kron(Base.tail(maps)...) +_krontail(maps::LinearMapVector) = kron(_tail(maps)) + function _unsafe_mul!(y::AbstractVecOrMat, L::KroneckerMap2, x::AbstractVector) require_one_based_indexing(y) A, B = L.maps @@ -174,9 +178,14 @@ function _unsafe_mul!(y::AbstractVecOrMat, L::KroneckerMap2, x::AbstractVector) end function _unsafe_mul!(y::AbstractVecOrMat, L::KroneckerMap, x::AbstractVector) require_one_based_indexing(y) - A = first(L.maps) - B = kron(Base.tail(L.maps)...) - _kronmul!(y, B, x, A, eltype(L)) + maps = L.maps + if length(maps) == 2 # reachable only for L.maps::Vector + @inbounds _kronmul!(y, maps[2], x, maps[1], eltype(L)) + else + A = first(maps) + B = _krontail(maps) + _kronmul!(y, B, x, A, eltype(L)) + end return y end # mixed-product rule, prefer the right if possible @@ -196,13 +205,13 @@ end # mixed-product rule, prefer the right if possible # (A₁⊗B₁) * (A₂⊗B₂) * ... * (Aᵣ⊗Bᵣ) = (A₁*A₂*...*Aᵣ) ⊗ (B₁*B₂*...*Bᵣ) function _unsafe_mul!(y::AbstractVecOrMat, - L::CompositeMap{T, <:Tuple{Vararg{KroneckerMap2}}}, + L::CompositeMap{T, <:Union{Tuple{Vararg{KroneckerMap2}},Vector{<:KroneckerMap2}}}, x::AbstractVector) where {T} require_one_based_indexing(y) As = map(AB -> AB.maps[1], L.maps) Bs = map(AB -> AB.maps[2], L.maps) - As1, As2 = Base.front(As), Base.tail(As) - Bs1, Bs2 = Base.front(Bs), Base.tail(Bs) + As1, As2 = _front(As), _tail(As) + Bs1, Bs2 = _front(Bs), _tail(Bs) apply = all(_iscompatible, zip(As1, As2)) && all(_iscompatible, zip(Bs1, Bs2)) if apply _unsafe_mul!(y, kron(prod(As), prod(Bs)), x) @@ -295,7 +304,7 @@ LinearAlgebra.transpose(A::KroneckerSumMap) = KroneckerSumMap{eltype(A)}(map(transpose, A.maps)) Base.:(==)(A::KroneckerSumMap, B::KroneckerSumMap) = - (eltype(A) == eltype(B) && A.maps == B.maps) + (eltype(A) == eltype(B) && all(A.maps .== B.maps)) function _unsafe_mul!(y::AbstractVecOrMat, L::KroneckerSumMap, x::AbstractVector) A, B = L.maps diff --git a/src/linearcombination.jl b/src/linearcombination.jl index a5d72e89..b03bb8b6 100644 --- a/src/linearcombination.jl +++ b/src/linearcombination.jl @@ -1,6 +1,6 @@ -struct LinearCombination{T, As<:LinearMapTuple} <: LinearMap{T} +struct LinearCombination{T, As<:LinearMapTupleOrVector} <: LinearMap{T} maps::As - function LinearCombination{T, As}(maps::As) where {T, As} + function LinearCombination{T, As}(maps::As) where {T, As<:LinearMapTupleOrVector} N = length(maps) ax = axes(maps[1]) for n in eachindex(maps) @@ -44,24 +44,24 @@ julia> LinearMap(ones(Int, 3, 3)) + CS + I + rand(3, 3); function Base.:(+)(A₁::LinearMap, A₂::LinearMap) size(A₁) == size(A₂) || throw(DimensionMismatch("+")) T = promote_type(eltype(A₁), eltype(A₂)) - return LinearCombination{T}(tuple(A₁, A₂)) + return LinearCombination{T}(_combine(A₁, A₂)) end function Base.:(+)(A₁::LinearMap, A₂::LinearCombination) size(A₁) == size(A₂) || throw(DimensionMismatch("+")) T = promote_type(eltype(A₁), eltype(A₂)) - return LinearCombination{T}(tuple(A₁, A₂.maps...)) + return LinearCombination{T}(_combine(A₁, A₂.maps)) end Base.:(+)(A₁::LinearCombination, A₂::LinearMap) = +(A₂, A₁) function Base.:(+)(A₁::LinearCombination, A₂::LinearCombination) size(A₁) == size(A₂) || throw(DimensionMismatch("+")) T = promote_type(eltype(A₁), eltype(A₂)) - return LinearCombination{T}(tuple(A₁.maps..., A₂.maps...)) + return LinearCombination{T}(_combine(A₁.maps, A₂.maps)) end Base.:(-)(A₁::LinearMap, A₂::LinearMap) = +(A₁, -A₂) # comparison of LinearCombination objects, sufficient but not necessary Base.:(==)(A::LinearCombination, B::LinearCombination) = - (eltype(A) == eltype(B) && A.maps == B.maps) + (eltype(A) == eltype(B) && all(A.maps .== B.maps)) # special transposition behavior LinearAlgebra.transpose(A::LinearCombination) = @@ -71,49 +71,52 @@ LinearAlgebra.adjoint(A::LinearCombination) = # multiplication with vectors & matrices for (In, Out) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix)) - @eval begin - function _unsafe_mul!(y::$Out, A::LinearCombination, x::$In) - _unsafe_mul!(y, first(A.maps), x) - _mul!(MulStyle(A), y, A, x, true) - return y - end - function _unsafe_mul!(y::$Out, A::LinearCombination, x::$In, α::Number, β::Number) - if iszero(α) # trivial cases - iszero(β) && return fill!(y, zero(eltype(y))) - isone(β) && return y - return rmul!(y, β) - else - A1 = first(A.maps) - if MulStyle(A1) === ThreeArg() && !iszero(β) - # if we need an intermediate vector, allocate here and reuse in - # LinearCombination multiplication - !isone(β) && rmul!(y, β) - z = similar(y) - muladd!(ThreeArg(), y, A1, x, α, z) - __mul!(y, Base.tail(A.maps), x, α, z) - else # MulStyle(A1) === FiveArg() || β == 0 - # this is allocation-free - _unsafe_mul!(y, A1, x, α, β) - # let _mul! decide whether an intermediate vector needs to be allocated - _mul!(MulStyle(A), y, A, x, α) - end - return y + @eval function _unsafe_mul!(y::$Out, A::LinearCombination, x::$In) + _unsafe_mul!(y, first(A.maps), x) + _mul!(MulStyle(A), y, A, x, true) + return y + end + @eval function _unsafe_mul!(y::$Out, A::LinearCombination, x::$In, α::Number, β::Number) + if iszero(α) # trivial cases + iszero(β) && return fill!(y, zero(eltype(y))) + isone(β) && return y + return rmul!(y, β) + else + A1 = first(A.maps) + if MulStyle(A1) === ThreeArg() && !iszero(β) + # if we need an intermediate vector, allocate here and reuse in + # LinearCombination multiplication + !isone(β) && rmul!(y, β) + z = similar(y) + muladd!(ThreeArg(), y, A1, x, α, z) + __mul!(y, _tail(A.maps), x, α, z) + else # MulStyle(A1) === FiveArg() || β == 0 + # this is allocation-free + _unsafe_mul!(y, A1, x, α, β) + # let _mul! decide whether an intermediate vector needs to be allocated + _mul!(MulStyle(A), y, A, x, α) end + return y end end end -function _mul!(::FiveArg, y, A::LinearCombination, x, α) - return __mul!(y, Base.tail(A.maps), x, α, nothing) -end -function _mul!(::ThreeArg, y, A::LinearCombination, x, α) - return __mul!(y, Base.tail(A.maps), x, α, similar(y)) -end +_mul!(::FiveArg, y, A::LinearCombination, x, α) = __mul!(y, _tail(A.maps), x, α, nothing) +_mul!(::ThreeArg, y, A::LinearCombination, x, α) = __mul!(y, _tail(A.maps), x, α, similar(y)) +# For tuple-like storage of the maps (default), we recurse on the tail of the tuple. __mul!(y, As::LinearMapTuple, x, α, z) = __mul!(__mul!(y, first(As), x, α, z), Base.tail(As), x, α, z) __mul!(y, A::Tuple{LinearMap}, x, α, z) = __mul!(y, first(A), x, α, z) __mul!(y, A::LinearMap, x, α, z) = muladd!(MulStyle(A), y, A, x, α, z) +# For vector-like storage of the maps, we simply loop over the maps. +function __mul!(y, As::LinearMapVector, x, α, z) + @inbounds for i in eachindex(As) + Ai = As[i] + muladd!(MulStyle(Ai), y, Ai, x, α, z) + end + y +end muladd!(::FiveArg, y, A, x, α, _) = _unsafe_mul!(y, A, x, α, true) function muladd!(::ThreeArg, y, A, x, α, z) diff --git a/src/show.jl b/src/show.jl index e0c25856..6fb1a560 100644 --- a/src/show.jl +++ b/src/show.jl @@ -48,7 +48,7 @@ function _show_typeof(A::LinearMap{T}) where {T} split(string(typeof(A)), '{')[1] * '{' * string(T) * '}' end -function print_maps(io::IO, maps::LinearMapTuple, k) +function print_maps(io::IO, maps, k) n = length(maps) str = "" if get(io, :limit, true) && n > 10 From 6aef693fd92198ea52b3d342dcc5431a571d78a0 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 28 Feb 2022 13:35:22 +0100 Subject: [PATCH 03/17] include review comment --- src/LinearMaps.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/LinearMaps.jl b/src/LinearMaps.jl index 88acbf8f..6decdfc5 100644 --- a/src/LinearMaps.jl +++ b/src/LinearMaps.jl @@ -82,11 +82,11 @@ _combine(A::LinearMap, B::LinearMap) = tuple(A, B) _combine(A::LinearMap, Bs::LinearMapTuple) = tuple(A, Bs...) _combine(As::LinearMapTuple, B::LinearMap) = tuple(As..., B) _combine(As::LinearMapTuple, Bs::LinearMapTuple) = tuple(As..., Bs...) -_combine(A::LinearMap, Bs::LinearMapVector) = vcat(A, Bs...) -_combine(As::LinearMapVector, B::LinearMap) = vcat(As..., B) -_combine(As::LinearMapVector, Bs::LinearMapTuple) = vcat(As..., Bs...) -_combine(As::LinearMapTuple, Bs::LinearMapVector) = vcat(As..., Bs...) -_combine(As::LinearMapVector, Bs::LinearMapVector) = vcat(As..., Bs...) +_combine(A::LinearMap, Bs::LinearMapVector) = vcat(A, Bs) +_combine(As::LinearMapVector, B::LinearMap) = vcat(As, B) +_combine(As::LinearMapVector, Bs::LinearMapTuple) = vcat(As, Bs...) +_combine(As::LinearMapTuple, Bs::LinearMapVector) = vcat(As..., Bs) +_combine(As::LinearMapVector, Bs::LinearMapVector) = vcat(As, Bs) # The (internal) multiplication logic is as follows: # - `*(A, x)` calls `mul!(y, A, x)` for appropriately-sized y From c82c90b029c415dd768e39f15c834bee88f36159 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Mon, 28 Feb 2022 21:16:30 +0100 Subject: [PATCH 04/17] whatever is not tested is probably broken --- src/LinearMaps.jl | 8 ++++---- src/composition.jl | 4 ++-- src/kronecker.jl | 9 +++------ test/composition.jl | 4 ++++ test/kronecker.jl | 4 ++++ 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/LinearMaps.jl b/src/LinearMaps.jl index 6decdfc5..33c18969 100644 --- a/src/LinearMaps.jl +++ b/src/LinearMaps.jl @@ -82,10 +82,10 @@ _combine(A::LinearMap, B::LinearMap) = tuple(A, B) _combine(A::LinearMap, Bs::LinearMapTuple) = tuple(A, Bs...) _combine(As::LinearMapTuple, B::LinearMap) = tuple(As..., B) _combine(As::LinearMapTuple, Bs::LinearMapTuple) = tuple(As..., Bs...) -_combine(A::LinearMap, Bs::LinearMapVector) = vcat(A, Bs) -_combine(As::LinearMapVector, B::LinearMap) = vcat(As, B) -_combine(As::LinearMapVector, Bs::LinearMapTuple) = vcat(As, Bs...) -_combine(As::LinearMapTuple, Bs::LinearMapVector) = vcat(As..., Bs) +_combine(A::LinearMap, Bs::LinearMapVector) = vcat(A, Bs...) +_combine(As::LinearMapVector, B::LinearMap) = vcat(As..., B) +_combine(As::LinearMapVector, Bs::LinearMapTuple) = vcat(As..., Bs...) +_combine(As::LinearMapTuple, Bs::LinearMapVector) = vcat(As..., Bs...) _combine(As::LinearMapVector, Bs::LinearMapVector) = vcat(As, Bs) # The (internal) multiplication logic is as follows: diff --git a/src/composition.jl b/src/composition.jl index e9d2ceb4..2761c657 100644 --- a/src/composition.jl +++ b/src/composition.jl @@ -33,9 +33,9 @@ for (f, _f, g) in ((:issymmetric, :_issymmetric, :transpose), if n == 0 return true elseif n == 1 - return $f(maps[1]) + return ($f(maps[1]))::Bool else - return maps[end] == $g(maps[1]) && $_f(maps[2:end-1]) + return ((maps[end] == $g(maps[1]))::Bool && $_f(@views maps[2:end-1])) end end # since the introduction of ScaledMap, the following cases cannot occur diff --git a/src/kronecker.jl b/src/kronecker.jl index cdd72826..416a50be 100644 --- a/src/kronecker.jl +++ b/src/kronecker.jl @@ -167,9 +167,6 @@ const AdjOrTransVectorMap{T} = WrappedMap{T,<:LinearAlgebra.AdjOrTransAbsVec} const KroneckerMap2{T} = KroneckerMap{T, <:Tuple{LinearMap, LinearMap}} -_krontail(maps::LinearMapTuple) = kron(Base.tail(maps)...) -_krontail(maps::LinearMapVector) = kron(_tail(maps)) - function _unsafe_mul!(y::AbstractVecOrMat, L::KroneckerMap2, x::AbstractVector) require_one_based_indexing(y) A, B = L.maps @@ -183,7 +180,7 @@ function _unsafe_mul!(y::AbstractVecOrMat, L::KroneckerMap, x::AbstractVector) @inbounds _kronmul!(y, maps[2], x, maps[1], eltype(L)) else A = first(maps) - B = _krontail(maps) + B = KroneckerMap{eltype(L)}(_tail(maps)) _kronmul!(y, B, x, A, eltype(L)) end return y @@ -196,7 +193,7 @@ function _unsafe_mul!(y::AbstractVecOrMat, require_one_based_indexing(y) B, A = L.maps if length(A.maps) == length(B.maps) && all(_iscompatible, zip(A.maps, B.maps)) - _unsafe_mul!(y, kron(map(*, A.maps, B.maps)...), x) + _unsafe_mul!(y, KroneckerMap{eltype(L)}(map(*, A.maps, B.maps)), x) else _unsafe_mul!(y, LinearMap(A)*B, x) end @@ -205,7 +202,7 @@ end # mixed-product rule, prefer the right if possible # (A₁⊗B₁) * (A₂⊗B₂) * ... * (Aᵣ⊗Bᵣ) = (A₁*A₂*...*Aᵣ) ⊗ (B₁*B₂*...*Bᵣ) function _unsafe_mul!(y::AbstractVecOrMat, - L::CompositeMap{T, <:Union{Tuple{Vararg{KroneckerMap2}},Vector{<:KroneckerMap2}}}, + L::CompositeMap{T, <:Union{Tuple{Vararg{KroneckerMap2}},AbstractVector{<:KroneckerMap2}}}, x::AbstractVector) where {T} require_one_based_indexing(y) As = map(AB -> AB.maps[1], L.maps) diff --git a/test/composition.jl b/test/composition.jl index da8613ac..b8e2bf41 100644 --- a/test/composition.jl +++ b/test/composition.jl @@ -37,7 +37,9 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays @test @inferred issymmetric(F'F) @test @inferred issymmetric(F'*S*F) @test @inferred ishermitian(F'F) + @test @inferred ishermitian(LinearMaps.CompositeMap{ComplexF64}([F, F'])) @test @inferred ishermitian(F'*H*F) + @test @inferred ishermitian(LinearMaps.CompositeMap{ComplexF64}([F, H, F'])) @test @inferred !issymmetric(FC'FC) @test @inferred ishermitian(FC'FC) @test @inferred ishermitian(FC'*H*FC) @@ -138,5 +140,7 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays B = LinearMap([1 0; 0 1], isposdef=true) # isposdef! C = B' * B * B * B * B # no B' at end on purpose @test @inferred isposdef(C) + @test @inferred isposdef(LinearMaps.CompositeMap{Float64}([B, B, B, B, B'])) @test @inferred isposdef(B * B) # even case for empty tuple test + @test @inferred isposdef(LinearMaps.CompositeMap{Float64}([B, B])) end diff --git a/test/kronecker.jl b/test/kronecker.jl index e080c46f..63fcca16 100644 --- a/test/kronecker.jl +++ b/test/kronecker.jl @@ -11,6 +11,8 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays LKv = @inferred LinearMaps.KroneckerMap{ComplexF64}([LA, LB]) @test LK * ones(6) ≈ LKv * ones(6) @test LKv.maps isa Vector + LKv = LinearMaps.KroneckerMap{ComplexF64}([LA, LB, LA]) + @test kron(A, B, A) * ones(18) ≈ LKv * ones(18) @test kron(LA, 2LB) isa LinearMaps.ScaledMap @test kron(3LA, LB) isa LinearMaps.ScaledMap @test kron(3LA, 2LB) isa LinearMaps.ScaledMap @@ -66,6 +68,8 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays @test @inferred ishermitian(kron(LA'LA, LB'LB)) # use mixed-product rule K = kron(LA, LB) * kron(LA, LB) * kron(LA, LB) + Kv = LinearMaps.CompositeMap{ComplexF64}(fill(LA ⊗ LB, 3)) + @test kron(A, B)^3 * ones(6) ≈ Kv * ones(6) @test Matrix(K) ≈ kron(A, B)^3 # example that doesn't use mixed-product rule A = rand(3, 2); B = rand(2, 3) From 4009f4d4182977a19969240437db857631fbf750 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Tue, 1 Mar 2022 10:36:05 +0100 Subject: [PATCH 05/17] add sum test --- test/linearcombination.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/linearcombination.jl b/test/linearcombination.jl index f922f3f3..38cebae6 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -11,7 +11,9 @@ using LinearMaps: FiveArg @test run(b, samples=3).allocs == 0 n = 10 L = @inferred sum(ntuple(_ -> CS!, n)) + @test (@inferred sum(L.maps::LinearMaps.LinearMapTuple)) == L Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) + @test sum(Lv.maps::LinearMaps.LinearMapVector) == Lv @test L == Lv M, Mv = Matrix.((L, Lv)) @test M == Mv == LowerTriangular(fill(n, size(L))) From 9fe9a9b3237c6054bddf2cb501be41abd11be5e3 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 2 Mar 2022 09:53:00 +0100 Subject: [PATCH 06/17] fix the vectormaps case --- src/LinearMaps.jl | 10 +++++----- test/linearcombination.jl | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/LinearMaps.jl b/src/LinearMaps.jl index 33c18969..6bd39051 100644 --- a/src/LinearMaps.jl +++ b/src/LinearMaps.jl @@ -82,11 +82,11 @@ _combine(A::LinearMap, B::LinearMap) = tuple(A, B) _combine(A::LinearMap, Bs::LinearMapTuple) = tuple(A, Bs...) _combine(As::LinearMapTuple, B::LinearMap) = tuple(As..., B) _combine(As::LinearMapTuple, Bs::LinearMapTuple) = tuple(As..., Bs...) -_combine(A::LinearMap, Bs::LinearMapVector) = vcat(A, Bs...) -_combine(As::LinearMapVector, B::LinearMap) = vcat(As..., B) -_combine(As::LinearMapVector, Bs::LinearMapTuple) = vcat(As..., Bs...) -_combine(As::LinearMapTuple, Bs::LinearMapVector) = vcat(As..., Bs...) -_combine(As::LinearMapVector, Bs::LinearMapVector) = vcat(As, Bs) +_combine(A::LinearMap, Bs::LinearMapVector) = Base.vect(A, Bs...) +_combine(As::LinearMapVector, B::LinearMap) = Base.vect(As..., B) +_combine(As::LinearMapVector, Bs::LinearMapTuple) = Base.vect(As..., Bs...) +_combine(As::LinearMapTuple, Bs::LinearMapVector) = Base.vect(As..., Bs...) +_combine(As::LinearMapVector, Bs::LinearMapVector) = Base.vect(As..., Bs...) # The (internal) multiplication logic is as follows: # - `*(A, x)` calls `mul!(y, A, x)` for appropriately-sized y diff --git a/test/linearcombination.jl b/test/linearcombination.jl index 38cebae6..e29284b4 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -15,6 +15,14 @@ using LinearMaps: FiveArg Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) @test sum(Lv.maps::LinearMaps.LinearMapVector) == Lv @test L == Lv + for sum1 in (CS!, L, Lv), sum2 in (CS!, L, Lv) + m1 = sum1 == CS! ? 1 : 10 + m2 = sum2 == CS! ? 1 : 10 + vect = any(x -> isa(x, LinearMaps.LinearCombination{ComplexF64,<:LinearMaps.LinearMapVector}), (sum1, sum2)) + maptyp = vect ? LinearMaps.LinearMapVector : LinearMaps.LinearMapTuple + @test (sum1+sum2) isa LinearMaps.LinearCombination{ComplexF64,<:maptyp} + @test (sum1+sum2) * v ≈ (m1+m2)*cumsum(v) + end M, Mv = Matrix.((L, Lv)) @test M == Mv == LowerTriangular(fill(n, size(L))) @test_throws AssertionError LinearMaps.LinearCombination{Float64}((CS!, CS!)) From e43c57e0500fdc174677efce1b48f176ce818e27 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 2 Mar 2022 12:48:14 +0100 Subject: [PATCH 07/17] mapreduce & type stability for homogeneous map-vectors --- src/composition.jl | 9 +++++++-- src/linearcombination.jl | 5 +++++ test/composition.jl | 5 ++++- test/linearcombination.jl | 10 +++++----- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/composition.jl b/src/composition.jl index 2761c657..f4e40388 100644 --- a/src/composition.jl +++ b/src/composition.jl @@ -14,6 +14,11 @@ struct CompositeMap{T, As<:LinearMapTupleOrVector} <: LinearMap{T} end CompositeMap{T}(maps::As) where {T, As<:LinearMapTupleOrVector} = CompositeMap{T, As}(maps) +Base.mapreduce(::typeof(identity), ::typeof(Base.mul_prod), maps::LinearMapTupleOrVector) = + CompositeMap{promote_type(map(eltype, maps)...)}(reverse(maps)) +Base.mapreduce(::typeof(identity), ::typeof(Base.mul_prod), maps::AbstractVector{<:LinearMap{T}}) where {T} = + CompositeMap{T}(reverse(maps)) + # basic methods Base.size(A::CompositeMap) = (size(A.maps[end], 1), size(A.maps[1], 2)) Base.axes(A::CompositeMap) = (axes(A.maps[end])[1], axes(A.maps[1])[2]) @@ -56,7 +61,7 @@ LinearAlgebra.isposdef(A::CompositeMap) = _isposdef(A.maps) _isposdef(maps::Tuple{}) = true # empty product is equivalent to "I" which is pos. def. _isposdef(maps::Tuple{<:LinearMap}) = isposdef(maps[1]) function _isposdef(maps::LinearMapTuple) - (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) && + (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) && isposdef(maps[1]) && _isposdef(Base.front(Base.tail(maps))) end function _isposdef(maps::LinearMapVector) @@ -66,7 +71,7 @@ function _isposdef(maps::LinearMapVector) elseif n == 1 return isposdef(maps[1]) else - return (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) && + return (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) && isposdef(maps[1]) && _isposdef(maps[2:end-1]) end end diff --git a/src/linearcombination.jl b/src/linearcombination.jl index b03bb8b6..ef9d526f 100644 --- a/src/linearcombination.jl +++ b/src/linearcombination.jl @@ -14,6 +14,11 @@ end LinearCombination{T}(maps::As) where {T, As} = LinearCombination{T, As}(maps) +Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::LinearMapTupleOrVector) = + LinearCombination{promote_type(map(eltype, maps)...)}(maps) +Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::AbstractVector{<:LinearMap{T}}) where {T} = + LinearCombination{T}(maps) + MulStyle(A::LinearCombination) = MulStyle(A.maps...) # basic methods diff --git a/test/composition.jl b/test/composition.jl index b8e2bf41..834cf2ff 100644 --- a/test/composition.jl +++ b/test/composition.jl @@ -1,4 +1,5 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays +using LinearMaps: LinearMapVector, LinearMapTuple @testset "composition" begin F = @inferred LinearMap(cumsum, reverse ∘ cumsum ∘ reverse, 10; ismutating=false) @@ -58,7 +59,9 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays R3 = rand(ComplexF64, 10, 10); L3 = LinearMap(R3) CompositeR = *(R1, R2, R3) CompositeL = prod(LinearMap, [R1, R2, R3]) - @test @inferred L1 * L2 * L3 == CompositeL + @test (@inferred prod([L1, L2, L3])) isa LinearMaps.CompositeMap{<:Any,<:LinearMapVector} + @test (@inferred prod((L1, L2, L3))) isa LinearMaps.CompositeMap{<:Any,<:LinearMapTuple} + @test (@inferred L1 * L2 * L3) == CompositeL == prod([L1, L2, L3]) @test Matrix(L1 * L2) ≈ sparse(L1 * L2) ≈ R1 * R2 @test Matrix(@inferred((α * L1) * (L2 * L3))::LinearMaps.ScaledMap) ≈ α * CompositeR @test Matrix(@inferred((L1 * L2) * (L3 * α))::LinearMaps.ScaledMap) ≈ α * CompositeR diff --git a/test/linearcombination.jl b/test/linearcombination.jl index e29284b4..2b9662b1 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -1,5 +1,5 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools -using LinearMaps: FiveArg +using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @testset "linear combinations" begin CS! = LinearMap{ComplexF64}(cumsum!, @@ -11,15 +11,15 @@ using LinearMaps: FiveArg @test run(b, samples=3).allocs == 0 n = 10 L = @inferred sum(ntuple(_ -> CS!, n)) - @test (@inferred sum(L.maps::LinearMaps.LinearMapTuple)) == L + @test (@inferred sum(L.maps::LinearMapTuple)) == L Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) - @test sum(Lv.maps::LinearMaps.LinearMapVector) == Lv + @test (@inferred sum(Lv.maps::LinearMapVector)) == Lv @test L == Lv for sum1 in (CS!, L, Lv), sum2 in (CS!, L, Lv) m1 = sum1 == CS! ? 1 : 10 m2 = sum2 == CS! ? 1 : 10 - vect = any(x -> isa(x, LinearMaps.LinearCombination{ComplexF64,<:LinearMaps.LinearMapVector}), (sum1, sum2)) - maptyp = vect ? LinearMaps.LinearMapVector : LinearMaps.LinearMapTuple + vect = any(x -> isa(x, LinearMaps.LinearCombination{ComplexF64,<:LinearMapVector}), (sum1, sum2)) + maptyp = vect ? LinearMapVector : LinearMapTuple @test (sum1+sum2) isa LinearMaps.LinearCombination{ComplexF64,<:maptyp} @test (sum1+sum2) * v ≈ (m1+m2)*cumsum(v) end From d5944a4a862d50e991003e4e6e7552af31ea1341 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 2 Mar 2022 13:30:12 +0100 Subject: [PATCH 08/17] more tests --- test/composition.jl | 3 ++- test/linearcombination.jl | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/composition.jl b/test/composition.jl index 834cf2ff..5197eccb 100644 --- a/test/composition.jl +++ b/test/composition.jl @@ -56,10 +56,11 @@ using LinearMaps: LinearMapVector, LinearMapTuple @test Array(L) ≈ LF R1 = rand(ComplexF64, 10, 10); L1 = LinearMap(R1) R2 = rand(ComplexF64, 10, 10); L2 = LinearMap(R2) - R3 = rand(ComplexF64, 10, 10); L3 = LinearMap(R3) + R3 = rand(Float64, 10, 10); L3 = LinearMap(R3) CompositeR = *(R1, R2, R3) CompositeL = prod(LinearMap, [R1, R2, R3]) @test (@inferred prod([L1, L2, L3])) isa LinearMaps.CompositeMap{<:Any,<:LinearMapVector} + @test prod([L1, L2, L3]) == (@inferred L1 * prod([L2, L3])) == (@inferred prod([L1, L2]) * L3) @test (@inferred prod((L1, L2, L3))) isa LinearMaps.CompositeMap{<:Any,<:LinearMapTuple} @test (@inferred L1 * L2 * L3) == CompositeL == prod([L1, L2, L3]) @test Matrix(L1 * L2) ≈ sparse(L1 * L2) ≈ R1 * R2 diff --git a/test/linearcombination.jl b/test/linearcombination.jl index 2b9662b1..98081392 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -15,6 +15,8 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) @test (@inferred sum(Lv.maps::LinearMapVector)) == Lv @test L == Lv + @inferred sum([CS!, LinearMap(randn(eltype(CS!), size(CS!)))]) + @inferred sum([CS!, LinearMap(randn(real(eltype(CS!)), size(CS!)))]) for sum1 in (CS!, L, Lv), sum2 in (CS!, L, Lv) m1 = sum1 == CS! ? 1 : 10 m2 = sum2 == CS! ? 1 : 10 From dda0eae2cf03d23faa8ef2eea2641cb65b1a24ca Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 2 Mar 2022 14:05:03 +0100 Subject: [PATCH 09/17] fix test --- test/composition.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/composition.jl b/test/composition.jl index 5197eccb..ae6af4db 100644 --- a/test/composition.jl +++ b/test/composition.jl @@ -56,9 +56,10 @@ using LinearMaps: LinearMapVector, LinearMapTuple @test Array(L) ≈ LF R1 = rand(ComplexF64, 10, 10); L1 = LinearMap(R1) R2 = rand(ComplexF64, 10, 10); L2 = LinearMap(R2) - R3 = rand(Float64, 10, 10); L3 = LinearMap(R3) + R3 = rand(ComplexF64, 10, 10); L3 = LinearMap(R3) CompositeR = *(R1, R2, R3) CompositeL = prod(LinearMap, [R1, R2, R3]) + @test prod([L1, L2, LinearMap(randn(10, 10))]) isa LinearMaps.CompositeMap{ComplexF64,<:LinearMapVector} @test (@inferred prod([L1, L2, L3])) isa LinearMaps.CompositeMap{<:Any,<:LinearMapVector} @test prod([L1, L2, L3]) == (@inferred L1 * prod([L2, L3])) == (@inferred prod([L1, L2]) * L3) @test (@inferred prod((L1, L2, L3))) isa LinearMaps.CompositeMap{<:Any,<:LinearMapTuple} From e4334cb418022539b321dcc80769323c3ec8cb4b Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Wed, 2 Mar 2022 14:52:56 +0100 Subject: [PATCH 10/17] fix another test --- test/linearcombination.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/linearcombination.jl b/test/linearcombination.jl index 98081392..e52d8e0a 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -15,8 +15,10 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) @test (@inferred sum(Lv.maps::LinearMapVector)) == Lv @test L == Lv - @inferred sum([CS!, LinearMap(randn(eltype(CS!), size(CS!)))]) - @inferred sum([CS!, LinearMap(randn(real(eltype(CS!)), size(CS!)))]) + @test isa((@inferred sum([CS!, LinearMap(randn(eltype(CS!), size(CS!)))])), + LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) + @test isa(sum([CS!, LinearMap(randn(real(eltype(CS!)), size(CS!)))]), + LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) for sum1 in (CS!, L, Lv), sum2 in (CS!, L, Lv) m1 = sum1 == CS! ? 1 : 10 m2 = sum2 == CS! ? 1 : 10 From 373f3ef1ddd92da60646773b5e3aa3e230dd48c2 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Fri, 4 Mar 2022 10:51:41 +0100 Subject: [PATCH 11/17] make `mean` work stably --- Project.toml | 2 +- docs/src/history.md | 12 +++++++++++- src/linearcombination.jl | 8 ++++++++ test/Project.toml | 1 + test/linearcombination.jl | 9 +++++++-- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 6716c788..5f0ab021 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "LinearMaps" uuid = "7a12625a-238d-50fd-b39a-03d52299707e" -version = "3.5.2" +version = "3.6.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/docs/src/history.md b/docs/src/history.md index 2cef13ad..263d60ab 100644 --- a/docs/src/history.md +++ b/docs/src/history.md @@ -1,5 +1,15 @@ # Version history +## What's new in v3.6 + +* Support for Julia versions below v1.6 has been dropped. +* `Block[Diagonal]Map`, `CompositeMap`, `KroneckerMap` and `LinearCombination` type objects + can now be backed by a `Vector` of `LinearMap`-type elements. This can be beneficial in + cases where these higher-order `LinearMap`s are constructed from many maps where a tuple + backend may get inefficient or impose hard work for the compiler at construction. + The default behavior, however, does not change, and construction of vector-based + `LinearMap`s requires usage of the unexported constructors ("expert usage"). + ## What's new in v3.5 * `WrappedMap`, `ScaledMap`, `LinearCombination`, `AdjointMap`, `TransposeMap` and @@ -24,7 +34,7 @@ ## What's new in v3.3 * `AbstractVector`s can now be wrapped by a `LinearMap` just like `AbstractMatrix`` - typed objects. Upon wrapping, there are not implicitly reshaped to matrices. This + typed objects. Upon wrapping, they are not implicitly reshaped to matrices. This feature might be helpful, for instance, in the lazy representation of rank-1 operators `kron(LinearMap(u), v') == ⊗(u, v') == u ⊗ v'` for vectors `u` and `v`. The action on vectors,`(u⊗v')*x`, is implemented optimally via `u*(v'x)`. diff --git a/src/linearcombination.jl b/src/linearcombination.jl index ef9d526f..c03cd672 100644 --- a/src/linearcombination.jl +++ b/src/linearcombination.jl @@ -14,10 +14,18 @@ end LinearCombination{T}(maps::As) where {T, As} = LinearCombination{T, As}(maps) +# this method avoids the afoldl-mechanism even for LinearMapTuple Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::LinearMapTupleOrVector) = LinearCombination{promote_type(map(eltype, maps)...)}(maps) +# this method is required for type stability in the mixed-map-equal-eltype case Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::AbstractVector{<:LinearMap{T}}) where {T} = LinearCombination{T}(maps) +# the following two methods are needed to make e.g. `mean` work, +# for which `f` is some sort of promotion function +Base.mapreduce(f::F, ::typeof(Base.add_sum), maps::LinearMapTupleOrVector) where {F} = + LinearCombination{promote_type(map(eltype, maps)...)}(f.(maps)) +Base.mapreduce(f::F, ::typeof(Base.add_sum), maps::AbstractVector{<:LinearMap{T}}) where {F,T} = + LinearCombination{T}(f.(maps)) MulStyle(A::LinearCombination) = MulStyle(A.maps...) diff --git a/test/Project.toml b/test/Project.toml index a7157c49..6e16e494 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -6,6 +6,7 @@ InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] diff --git a/test/linearcombination.jl b/test/linearcombination.jl index e52d8e0a..12b34133 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -1,4 +1,4 @@ -using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools +using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, Statistics using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @testset "linear combinations" begin @@ -14,10 +14,15 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @test (@inferred sum(L.maps::LinearMapTuple)) == L Lv = @inferred LinearMaps.LinearCombination{ComplexF64}(fill(CS!, n)) @test (@inferred sum(Lv.maps::LinearMapVector)) == Lv + @test isa((@inferred mean(Lv.maps)), + LinearMaps.ScaledMap{ComplexF64,Float64,<:LinearMaps.LinearCombination{ComplexF64,<:LinearMapVector}}) + @test (@inferred mean(x -> x*x, Lv.maps)) == (@inferred sum(x -> x*x, Lv.maps)/n) @test L == Lv @test isa((@inferred sum([CS!, LinearMap(randn(eltype(CS!), size(CS!)))])), LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) - @test isa(sum([CS!, LinearMap(randn(real(eltype(CS!)), size(CS!)))]), + A = randn(eltype(CS!), size(CS!)) + @test (@inferred mean([CS!, LinearMap(A)])) == (@inferred sum([CS!, LinearMap(A)])/2) + @test isa(sum([CS!, LinearMap(real(A))]), LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) for sum1 in (CS!, L, Lv), sum2 in (CS!, L, Lv) m1 = sum1 == CS! ? 1 : 10 From a3dca0aed0c6490b0946b809947dbd4f2c4d04b5 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Fri, 4 Mar 2022 14:49:57 +0100 Subject: [PATCH 12/17] remove mapreduce for generic f, add Statistics.mean --- Project.toml | 1 + src/LinearMaps.jl | 19 +++++++++++-------- src/kronecker.jl | 4 ++-- src/linearcombination.jl | 9 +++------ test/linearcombination.jl | 17 +++++++++++------ 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Project.toml b/Project.toml index 5f0ab021..f721da32 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "3.6.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] julia = "1.6" diff --git a/src/LinearMaps.jl b/src/LinearMaps.jl index 6bd39051..ed673532 100644 --- a/src/LinearMaps.jl +++ b/src/LinearMaps.jl @@ -8,6 +8,8 @@ using LinearAlgebra import LinearAlgebra: mul! using SparseArrays +import Statistics: mean + using Base: require_one_based_indexing abstract type LinearMap{T} end @@ -22,6 +24,15 @@ const LinearMapTupleOrVector = Union{LinearMapTuple,LinearMapVector} Base.eltype(::LinearMap{T}) where {T} = T +# conversion to LinearMap +Base.convert(::Type{LinearMap}, A::LinearMap) = A +Base.convert(::Type{LinearMap}, A::AbstractVecOrMat) = LinearMap(A) + +convert_to_lmaps() = () +convert_to_lmaps(A) = (convert(LinearMap, A),) +@inline convert_to_lmaps(A, B, Cs...) = + (convert(LinearMap, A), convert(LinearMap, B), convert_to_lmaps(Cs...)...) + abstract type MulStyle end struct FiveArg <: MulStyle end @@ -65,14 +76,6 @@ function check_dim_mul(C, A, B) return nothing end -# conversion of AbstractVecOrMat to LinearMap -convert_to_lmaps_(A::AbstractVecOrMat) = LinearMap(A) -convert_to_lmaps_(A::LinearMap) = A -convert_to_lmaps() = () -convert_to_lmaps(A) = (convert_to_lmaps_(A),) -@inline convert_to_lmaps(A, B, Cs...) = - (convert_to_lmaps_(A), convert_to_lmaps_(B), convert_to_lmaps(Cs...)...) - _front(As::Tuple) = Base.front(As) _front(As::AbstractVector) = @inbounds @views As[1:end-1] _tail(As::Tuple) = Base.tail(As) diff --git a/src/kronecker.jl b/src/kronecker.jl index 416a50be..dc74edab 100644 --- a/src/kronecker.jl +++ b/src/kronecker.jl @@ -97,7 +97,7 @@ Construct a lazy representation of the `k`-th Kronecker power ⊗(A, B, Cs...) = kron(convert_to_lmaps(A, B, Cs...)...) Base.:(^)(A::MapOrMatrix, ::KronPower{p}) where {p} = - kron(ntuple(n -> convert_to_lmaps_(A), Val(p))...) + kron(ntuple(n -> convert(LinearMap, A), Val(p))...) Base.size(A::KroneckerMap) = map(*, size.(A.maps)...) @@ -287,7 +287,7 @@ where `A` can be a square `AbstractMatrix` or a `LinearMap`. ⊕(a, b, c...) = kronsum(a, b, c...) Base.:(^)(A::MapOrMatrix, ::KronSumPower{p}) where {p} = - kronsum(ntuple(n->convert_to_lmaps_(A), Val(p))...) + kronsum(ntuple(n -> convert(LinearMap, A), Val(p))...) Base.size(A::KroneckerSumMap, i) = prod(size.(A.maps, i)) Base.size(A::KroneckerSumMap) = (size(A, 1), size(A, 2)) diff --git a/src/linearcombination.jl b/src/linearcombination.jl index c03cd672..86d40afd 100644 --- a/src/linearcombination.jl +++ b/src/linearcombination.jl @@ -20,12 +20,9 @@ Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::LinearMapTupleO # this method is required for type stability in the mixed-map-equal-eltype case Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::AbstractVector{<:LinearMap{T}}) where {T} = LinearCombination{T}(maps) -# the following two methods are needed to make e.g. `mean` work, -# for which `f` is some sort of promotion function -Base.mapreduce(f::F, ::typeof(Base.add_sum), maps::LinearMapTupleOrVector) where {F} = - LinearCombination{promote_type(map(eltype, maps)...)}(f.(maps)) -Base.mapreduce(f::F, ::typeof(Base.add_sum), maps::AbstractVector{<:LinearMap{T}}) where {F,T} = - LinearCombination{T}(f.(maps)) + +mean(f::F, maps::LinearMapTupleOrVector) where {F} = sum(f, maps) / length(maps) +mean(maps::LinearMapTupleOrVector) = sum(maps) / length(maps) MulStyle(A::LinearCombination) = MulStyle(A.maps...) diff --git a/test/linearcombination.jl b/test/linearcombination.jl index 12b34133..950e0b05 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -16,14 +16,19 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @test (@inferred sum(Lv.maps::LinearMapVector)) == Lv @test isa((@inferred mean(Lv.maps)), LinearMaps.ScaledMap{ComplexF64,Float64,<:LinearMaps.LinearCombination{ComplexF64,<:LinearMapVector}}) - @test (@inferred mean(x -> x*x, Lv.maps)) == (@inferred sum(x -> x*x, Lv.maps)/n) + @test (@inferred mean(L.maps)) == (@inferred mean(Lv.maps)) == (@inferred sum(Lv.maps))/n + @test (@inferred mean(x -> x*x, L.maps)) == (@inferred sum(x -> x*x, L.maps))/n + @test mean(x -> x*x, Lv.maps) == (sum(x -> x*x, Lv.maps))/n @test L == Lv - @test isa((@inferred sum([CS!, LinearMap(randn(eltype(CS!), size(CS!)))])), - LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) - A = randn(eltype(CS!), size(CS!)) - @test (@inferred mean([CS!, LinearMap(A)])) == (@inferred sum([CS!, LinearMap(A)])/2) - @test isa(sum([CS!, LinearMap(real(A))]), + A = LinearMap(randn(eltype(CS!), size(CS!))) + Ar = LinearMap(real(A.lmap)) + @test isa((@inferred sum([CS!, A])), LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) + @test (@inferred mean([CS!, A])) == (@inferred sum([CS!, A]))/2 + @test (@inferred mean([CS!, A])) == (@inferred mean(identity, [CS!, A])) == (@inferred sum([CS!, A]))/2 + @test isa(sum([CS!, Ar]), LinearMaps.LinearCombination{<:ComplexF64,<:LinearMapVector}) + @test sum([CS!, Ar])/2 == mean([CS!, Ar]) + @test sum([CS!, Ar]) == sum(identity, [CS!, Ar]) for sum1 in (CS!, L, Lv), sum2 in (CS!, L, Lv) m1 = sum1 == CS! ? 1 : 10 m2 = sum2 == CS! ? 1 : 10 From d5dc7d2be33af394d526deccb0e4a606831283e5 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Fri, 4 Mar 2022 15:16:22 +0100 Subject: [PATCH 13/17] add mean of LinearCombination --- src/linearcombination.jl | 3 ++- test/linearcombination.jl | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/linearcombination.jl b/src/linearcombination.jl index 86d40afd..9b0078e0 100644 --- a/src/linearcombination.jl +++ b/src/linearcombination.jl @@ -22,7 +22,8 @@ Base.mapreduce(::typeof(identity), ::typeof(Base.add_sum), maps::AbstractVector{ LinearCombination{T}(maps) mean(f::F, maps::LinearMapTupleOrVector) where {F} = sum(f, maps) / length(maps) -mean(maps::LinearMapTupleOrVector) = sum(maps) / length(maps) +mean(maps::LinearMapTupleOrVector) = mean(identity, maps) +mean(A::LinearCombination) = mean(A.maps) MulStyle(A::LinearCombination) = MulStyle(A.maps...) diff --git a/test/linearcombination.jl b/test/linearcombination.jl index 950e0b05..e5604b30 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -17,6 +17,7 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @test isa((@inferred mean(Lv.maps)), LinearMaps.ScaledMap{ComplexF64,Float64,<:LinearMaps.LinearCombination{ComplexF64,<:LinearMapVector}}) @test (@inferred mean(L.maps)) == (@inferred mean(Lv.maps)) == (@inferred sum(Lv.maps))/n + @test (@inferred mean(L)) == (@inferred mean(Lv)) @test (@inferred mean(x -> x*x, L.maps)) == (@inferred sum(x -> x*x, L.maps))/n @test mean(x -> x*x, Lv.maps) == (sum(x -> x*x, Lv.maps))/n @test L == Lv From fe4226266f167dd338a09966cc49122c9d51d042 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Fri, 4 Mar 2022 17:16:27 +0100 Subject: [PATCH 14/17] simplify BlockMap constructors --- src/blockmap.jl | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/blockmap.jl b/src/blockmap.jl index 1bf32b08..afc7d8fd 100644 --- a/src/blockmap.jl +++ b/src/blockmap.jl @@ -1,12 +1,10 @@ struct BlockMap{T, As<:LinearMapTupleOrVector, - Rs<:Tuple{Vararg{Int}}, - Rranges<:Vector{UnitRange{Int}}, - Cranges<:Vector{UnitRange{Int}}} <: LinearMap{T} + Rs<:Tuple{Vararg{Int}}} <: LinearMap{T} maps::As rows::Rs - rowranges::Rranges - colranges::Cranges + rowranges::Vector{UnitRange{Int}} + colranges::Vector{UnitRange{Int}} function BlockMap{T,As,Rs}(maps::As, rows::Rs) where {T, As<:LinearMapTupleOrVector, Rs<:Tuple{Vararg{Int}}} for TA in Base.Generator(eltype, maps) @@ -14,8 +12,7 @@ struct BlockMap{T, error("eltype $TA cannot be promoted to $T in BlockMap constructor") end rowranges, colranges = rowcolranges(maps, rows) - Rranges, Cranges = typeof(rowranges), typeof(colranges) - return new{T, As, Rs, Rranges, Cranges}(maps, rows, rowranges, colranges) + return new{T, As, Rs}(maps, rows, rowranges, colranges) end end @@ -399,12 +396,10 @@ end ############ # BlockDiagonalMap ############ -struct BlockDiagonalMap{T, - As<:LinearMapTupleOrVector, - Ranges<:Vector{UnitRange{Int}}} <: LinearMap{T} +struct BlockDiagonalMap{T, As<:LinearMapTupleOrVector} <: LinearMap{T} maps::As - rowranges::Ranges - colranges::Ranges + rowranges::Vector{UnitRange{Int}} + colranges::Vector{UnitRange{Int}} function BlockDiagonalMap{T, As}(maps::As) where {T, As<:LinearMapTupleOrVector} for TA in Base.Generator(eltype, maps) promote_type(T, TA) == T || @@ -412,7 +407,7 @@ struct BlockDiagonalMap{T, end rowranges = _getranges(maps, 1) colranges = _getranges(maps, 2) - return new{T, As, typeof(rowranges)}(maps, rowranges, colranges) + return new{T, As}(maps, rowranges, colranges) end end From b998bf2d34b7e6a1b9f7c00a6ff31a7ed818a31c Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Sun, 6 Mar 2022 20:42:43 +0100 Subject: [PATCH 15/17] clean-up tests --- docs/Project.toml | 4 +- test/Project.toml | 2 - test/blockmap.jl | 127 ++++++++++++++++++++------------------ test/functionmap.jl | 17 ++--- test/linearcombination.jl | 40 ++++++------ test/linearmaps.jl | 2 +- test/scaledmap.jl | 7 ++- test/uniformscalingmap.jl | 36 +++++------ 8 files changed, 124 insertions(+), 111 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 8bd55964..4c6dd40b 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,6 +6,6 @@ LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" [compat] -BenchmarkTools = "0.4, 0.5" -Documenter = "0.25, 0.26" +BenchmarkTools = "1" +Documenter = "0.25, 0.26, 0.27" Literate = "2" \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index 6e16e494..7f327c7e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,5 @@ [deps] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -11,7 +10,6 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] Aqua = "0.5" -BenchmarkTools = "1" BlockArrays = "0.16" Quaternions = "0.5" julia = "1.6" diff --git a/test/blockmap.jl b/test/blockmap.jl index 8bffb410..9ed73e70 100644 --- a/test/blockmap.jl +++ b/test/blockmap.jl @@ -1,94 +1,100 @@ -using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, InteractiveUtils +using Test, LinearMaps, LinearAlgebra, SparseArrays, InteractiveUtils using LinearMaps: FiveArg @testset "block maps" begin @testset "hcat" begin - for elty in (Float32, ComplexF64), n2 = (0, 20) - A11 = rand(elty, 10, 10) - A12 = rand(elty, 10, n2) - v = rand(elty, 10) + m = 3 + n = 4 + for elty in (Float32, ComplexF64), n2 in (0, 2) + A11 = rand(elty, m, n) + A12 = rand(elty, m, n2) + a = rand(elty, m) L = @inferred hcat(LinearMap(A11), LinearMap(A12)) @test L.maps isa Tuple Lv = @inferred LinearMaps.BlockMap{elty}([LinearMap(A11), LinearMap(A12)], (2,)) @test Lv.maps isa Vector @test L == Lv == LinearMaps.BlockMap([LinearMap(A11), LinearMap(A12)], (2,)) - @test occursin("10×$(10+n2) LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) + @test occursin("$m×$(n+n2) LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() @test L isa LinearMaps.BlockMap{elty} if elty <: Complex @test_throws ErrorException LinearMaps.BlockMap{Float64}((LinearMap(A11), LinearMap(A12)), (2,)) end A = [A11 A12] - x = rand(10+n2) + x = rand(n+n2) @test size(L) == size(A) == size(Lv) - @test Matrix(L) ≈ A ≈ Matrix(Lv) + @test Matrix(L) == A == Matrix(Lv) @test L * x ≈ A * x ≈ Lv * x L = @inferred hcat(LinearMap(A11), LinearMap(A12), LinearMap(A11)) A = [A11 A12 A11] - @test Matrix(L) ≈ A - A = [I I I A11 A11 A11 v] + @test Matrix(L) == A + A = [I I I A11 A11 A11 a] @test (@which [A11 A11 A11]).module != LinearMaps @test (@which [I I I A11 A11 A11]).module != LinearMaps @test (@which hcat(I, I, I)).module != LinearMaps @test (@which hcat(I, I, I, LinearMap(A11), A11, A11)).module == LinearMaps - maps = @inferred LinearMaps.promote_to_lmaps(ntuple(i->10, 7), 1, 1, I, I, I, LinearMap(A11), A11, A11, v) + maps = @inferred LinearMaps.promote_to_lmaps(ntuple(i->m, 7), 1, 1, I, I, I, LinearMap(A11), A11, A11, a) @inferred LinearMaps.rowcolranges(maps, (7,)) - L = @inferred hcat(I, I, I, LinearMap(A11), A11, A11, v) - @test L == [I I I LinearMap(A11) LinearMap(A11) LinearMap(A11) LinearMap(v)] - x = rand(elty, 61) + L = @inferred hcat(I, I, I, LinearMap(A11), A11, A11, a) + @test L == [I I I LinearMap(A11) LinearMap(A11) LinearMap(A11) LinearMap(a)] + x = ones(elty, size(L, 2)) @test L isa LinearMaps.BlockMap{elty} @test L * x ≈ A * x - L = @inferred hcat(I, I, I, LinearMap(A11), A11, A11, v, v, v, v) - @test occursin("10×64 LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) - L = @inferred hcat(I, I, I, LinearMap(A11), A11, A11, v, v, v, v, v, v, v) - @test occursin("10×67 LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) - A11 = rand(elty, 11, 10) - A12 = rand(elty, 10, n2) - @test_throws DimensionMismatch hcat(LinearMap(A11), LinearMap(A12)) + L = @inferred hcat(I, I, I, LinearMap(A11), A11, A11, a, a, a, a) + @test occursin("$m×$(3m+3n+4) LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) + L = @inferred hcat(I, I, I, LinearMap(A11), A11, A11, a, a, a, a, a, a, a) + @test occursin("$m×$(3m+3n+7) LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) end + A11 = zeros(m+1, n) + A12 = zeros(m, n) + @test_throws DimensionMismatch hcat(LinearMap(A11), LinearMap(A12)) end @testset "vcat" begin + m = 2 + n = 3 for elty in (Float32, ComplexF64) - A11 = rand(elty, 10, 10) - v = rand(elty, 10) + A11 = rand(elty, m, n) + v = rand(elty, n) L = @inferred vcat(LinearMap(A11)) @test L == [LinearMap(A11);] @test Matrix(L) ≈ A11 - A21 = rand(elty, 20, 10) + A21 = rand(elty, 2m, n) L = @inferred vcat(LinearMap(A11), LinearMap(A21)) @test L.maps isa Tuple + @test L isa LinearMaps.BlockMap{elty} + @test occursin("$(3m)×$n LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) + @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() Lv = LinearMaps.BlockMap{elty}([LinearMap(A11), LinearMap(A21)], (1,1)) @test Lv.maps isa Vector @test L == Lv - @test occursin("30×10 LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L)) - @test L isa LinearMaps.BlockMap{elty} - @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() @test (@which [A11; A21]).module != LinearMaps A = [A11; A21] - x = rand(10) + x = rand(elty, n) @test size(L) == size(A) @test Matrix(L) == Matrix(Lv) == A @test L * x ≈ Lv * x ≈ A * x - A = [I; I; I; A11; A11; A11; v v v v v v v v v v] + A = [I; I; I; A11; A11; A11; reduce(hcat, fill(v, n))] @test (@which [I; I; I; A11; A11; A11; v v v v v v v v v v]).module != LinearMaps - L = @inferred vcat(I, I, I, LinearMap(A11), LinearMap(A11), LinearMap(A11), reduce(hcat, fill(v, 10))) - @test L == [I; I; I; LinearMap(A11); LinearMap(A11); LinearMap(A11); reduce(hcat, fill(v, 10))] - x = rand(elty, 10) + L = @inferred vcat(I, I, I, LinearMap(A11), LinearMap(A11), LinearMap(A11), reduce(hcat, fill(v, n))) + @test L == [I; I; I; LinearMap(A11); LinearMap(A11); LinearMap(A11); reduce(hcat, fill(v, n))] @test L isa LinearMaps.BlockMap{elty} @test L * x ≈ A * x - A11 = rand(elty, 10, 11) - A21 = rand(elty, 20, 10) - @test_throws DimensionMismatch vcat(LinearMap(A11), LinearMap(A21)) end + A11 = zeros(m, n+1) + A21 = zeros(2m, n) + @test_throws DimensionMismatch vcat(LinearMap(A11), LinearMap(A21)) end @testset "hvcat" begin + m1 = 2 + m2 = 3 + n = 3 for elty in (Float32, ComplexF64) - A11 = rand(elty, 10, 10) - A12 = rand(elty, 10, 20) - A21 = rand(elty, 20, 10) - A22 = rand(elty, 20, 20) + A11 = rand(elty, m1, m1) + A12 = ones(elty, m1, m2) + A21 = rand(elty, m2, m1) + A22 = ones(elty, m2, m2) A = [A11 A12; A21 A22] @test (@which [A11 A12; A21 A22]).module != LinearMaps @inferred hvcat((2,2), LinearMap(A11), LinearMap(A12), LinearMap(A21), LinearMap(A22)) @@ -99,7 +105,7 @@ using LinearMaps: FiveArg @test @inferred(LinearMaps.MulStyle(L)) === FiveArg() @test @inferred !issymmetric(L) @test @inferred !ishermitian(L) - x = rand(30) + x = rand(m1+m2) @test L isa LinearMaps.BlockMap{elty} @test size(L) == size(A) @test L * x ≈ Lv * x ≈ A * x @@ -110,32 +116,32 @@ using LinearMaps: FiveArg @inferred hvcat((2,2), I, LinearMap(A12), LinearMap(A21), I) L = @inferred hvcat((2,2), I, LinearMap(A12), LinearMap(A21), I) @test L isa LinearMaps.BlockMap{elty} - @test size(L) == (30, 30) + @test size(L) == (m1+m2, m1+m2) @test Matrix(L) ≈ A @test L * x ≈ A * x y = randn(elty, size(L, 1)) for α in (0, 1, rand(elty)), β in (0, 1, rand(elty)) @test mul!(copy(y), L, x, α, β) ≈ y*β .+ A*x*α end - X = rand(elty, 30, 10) - Y = randn(elty, size(L, 1), 10) + X = rand(elty, m1+m2, n) + Y = randn(elty, size(L, 1), n) for α in (0, 1, rand(elty)), β in (0, 1, rand(elty)) @test mul!(copy(Y), L, X, α, β) ≈ Y*β .+ A*X*α end - A = rand(elty, 10,10); LA = LinearMap(A) - B = rand(elty, 20,30); LB = LinearMap(B) + A = ones(elty, m1, m1); LA = LinearMap(A) + B = zeros(elty, m2, 3m1); LB = LinearMap(B) @test [LA LA LA; LB] isa LinearMaps.BlockMap{elty} @test Matrix([LA LA LA; LB]) ≈ [A A A; B] @test [LB; LA LA LA] isa LinearMaps.BlockMap{elty} @test Matrix([LB; LA LA LA]) ≈ [B; A A A] @test [I; LA LA LA] isa LinearMaps.BlockMap{elty} @test Matrix([I; LA LA LA]) ≈ [I; A A A] - A12 = LinearMap(rand(elty, 10, 21)) - A21 = LinearMap(rand(elty, 20, 10)) - @test_throws DimensionMismatch A = [I A12; A21 I] - @test_throws DimensionMismatch A = [I A21; A12 I] - @test_throws DimensionMismatch A = [A12 A12; A21 A21] - @test_throws DimensionMismatch A = [A12 A21; A12 A21] + A12 = LinearMap(zeros(elty, m1, m2+1)) + A21 = LinearMap(zeros(elty, m2, m1)) + @test_throws DimensionMismatch [I A12; A21 I] + @test_throws DimensionMismatch [I A21; A12 I] + @test_throws DimensionMismatch [A12 A12; A21 A21] + @test_throws DimensionMismatch [A12 A21; A12 A21] # basic test of "misaligned" blocks M = ones(elty, 3, 2) # non-square @@ -199,7 +205,7 @@ using LinearMaps: FiveArg @testset "block diagonal maps" begin for elty in (Float32, ComplexF64) - m = 5; n = 6 + m = 2; n = 3 M1 = 10*(1:m) .+ (1:(n+1))'; L1 = LinearMap(M1) M2 = randn(elty, m, n+2); L2 = LinearMap(M2) M3 = randn(elty, m, n+3); L3 = LinearMap(M3) @@ -217,7 +223,7 @@ using LinearMaps: FiveArg Bdv = @inferred LinearMaps.BlockDiagonalMap{elty}([L1, L2, L3, L2, L1]) @test Bdv.maps isa Vector @test @inferred(LinearMaps.MulStyle(Bd)) === FiveArg() - @test occursin("25×39 LinearMaps.BlockDiagonalMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), Bd)) + @test occursin("$(5m)×$(5n+9) LinearMaps.BlockDiagonalMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), Bd)) @test Matrix(Bd) == Md @test convert(AbstractMatrix, Bd) isa SparseMatrixCSC @test sparse(Bd) == Md @@ -227,7 +233,7 @@ using LinearMaps: FiveArg @test_throws ArgumentError cat(L1, L2, L3, L2, L1; dims=(2,2)) @test Bd == Bdv == Bd2 @test Bd == blockdiag(L1, M2, M3, M2, M1) - @test size(Bd) == (25, 39) + @test size(Bd) == (5m, 5n+9) @test !issymmetric(Bd) @test !ishermitian(Bd) @test (@inferred Bd * x) ≈ Bdv * x ≈ Md * x @@ -240,8 +246,8 @@ using LinearMaps: FiveArg @test mul!(copy(y), Bd, x, α, β) ≈ y*β .+ Md*x*α @test mul!(copy(y), Bdv, x, α, β) ≈ y*β .+ Md*x*α end - X = randn(elty, size(Md, 2), 10) - Y = randn(elty, size(Md, 1), 10) + X = randn(elty, size(Md, 2), 3) + Y = randn(elty, size(Md, 1), 3) for α in (0, 1, rand(elty)), β in (0, 1, rand(elty)) @test mul!(copy(Y), Bd, X, α, β) ≈ Y*β .+ Md*X*α @test mul!(copy(Y), Bdv, X, α, β) ≈ Y*β .+ Md*X*α @@ -250,7 +256,7 @@ using LinearMaps: FiveArg end @testset "function block map" begin - N = 100 + N = 5 T = ComplexF64 CS! = LinearMap{T}(cumsum!, (y, x) -> (copyto!(y, x); reverse!(cumsum!(y, reverse!(y)))), N; @@ -272,8 +278,11 @@ using LinearMaps: FiveArg @test mul!(copy(v), transform(L), u, α, β) ≈ transform(M)*u*α + v*β @test mul!(copy(v), transform(LinearMap(L)), u, α, β) ≈ transform(M)*u*α + v*β @test mul!(copy(v), LinearMap(transform(L)), u, α, β) ≈ transform(M)*u*α + v*β - bmap = @benchmarkable mul!($(copy(v)), $(transform(L)), $u, $α, $β) - transform != adjoint && @test run(bmap, samples=3).memory < 2sizeof(u) + if transform != adjoint + transL = transform(L) + alloc = @allocated similar(v) + @test (@allocated mul!(v, transL, u, α, β)) <= alloc broken = (L == L2 && α != false) + end end end end diff --git a/test/functionmap.jl b/test/functionmap.jl index e73a31f8..51275aee 100644 --- a/test/functionmap.jl +++ b/test/functionmap.jl @@ -1,4 +1,4 @@ -using Test, LinearMaps, LinearAlgebra, BenchmarkTools +using Test, LinearMaps, LinearAlgebra @testset "function maps" begin N = 100 @@ -71,10 +71,12 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools @test @inferred mul!(similar(v), transpose(CS), v) == reverse!(cumsum(reverse(v))) @test @inferred mul!(similar(v), adjoint(CS), v) == reverse!(cumsum(reverse(v))) u = similar(v) - b = @benchmarkable mul!($u, $(3*CS!), $v) - @test run(b, samples=3).allocs == 0 - b = @benchmarkable mul!($u, $(3*CS!'), $v) - @test run(b, samples=3).allocs == 0 + CS!3 = 3*CS! + mul!(u, CS!3, v) + @test (@allocated mul!(u, CS!3, v)) == 0 + CS!3t = 3*CS!' + mul!(u, CS!3t, v) + @test (@allocated mul!(u, CS!3t, v)) == 0 u = rand(ComplexF64, 10) v = rand(ComplexF64, 10) for α in (false, true, rand(ComplexF64)), β in (false, true, rand(ComplexF64)) @@ -83,8 +85,9 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools @test mul!(copy(v), transform(LinearMap(CS!)), u, α, β) ≈ transform(M)*u*α + v*β @test mul!(copy(v), LinearMap(transform(CS!)), u, α, β) ≈ transform(M)*u*α + v*β if transform != transpose - bm = @benchmarkable mul!($(copy(v)), $(transform(CS!)), $u, $α, $β) - @test run(bm, samples=3).allocs <= 1 + transCS! = transform(CS!) + alloc = @allocated similar(v) + @test (@allocated mul!(v, transCS!, u, α, β)) <= alloc end end end diff --git a/test/linearcombination.jl b/test/linearcombination.jl index e5604b30..12798320 100644 --- a/test/linearcombination.jl +++ b/test/linearcombination.jl @@ -1,4 +1,4 @@ -using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, Statistics +using Test, LinearMaps, LinearAlgebra, SparseArrays, Statistics using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @testset "linear combinations" begin @@ -7,8 +7,8 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector ismutating=true) v = rand(ComplexF64, 10) u = similar(v) - b = @benchmarkable mul!($u, $CS!, $v) - @test run(b, samples=3).allocs == 0 + mul!(u, CS!, v) + @test (@allocated mul!(u, CS!, v)) == 0 n = 10 L = @inferred sum(ntuple(_ -> CS!, n)) @test (@inferred sum(L.maps::LinearMapTuple)) == L @@ -45,10 +45,11 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @test occursin("10×10 $LinearMaps.LinearCombination{$(eltype(L))}", sprint((t, s) -> show(t, "text/plain", s), L+CS!)) @test mul!(u, L, v) ≈ n * cumsum(v) @test mul!(u, Lv, v) ≈ n * cumsum(v) - b = @benchmarkable mul!($u, $L, $v, 2, 2) - @test run(b, samples=5).allocs <= 1 - b = @benchmarkable mul!($u, $Lv, $v, 2, 2) - @test run(b, samples=5).allocs <= 1 + alloc = @allocated similar(u) + mul!(u, L, v, 2, 2) + @test (@allocated mul!(u, L, v, 2, 2)) <= alloc + mul!(u, Lv, v, 2, 2) + @test (@allocated mul!(u, Lv, v, 2, 2)) <= alloc for α in (false, true, rand(ComplexF64)), β in (false, true, rand(ComplexF64)) for transform in (identity, adjoint, transpose) @test mul!(copy(u), transform(L), v, α, β) ≈ transform(M)*v*α + u*β @@ -78,20 +79,21 @@ using LinearMaps: FiveArg, LinearMapTuple, LinearMapVector @test sparse(LC) == Matrix(LC) == A+B v = rand(ComplexF64, 10) w = similar(v) - b = @benchmarkable mul!($w, $M, $v) - @test run(b, samples=3).allocs == 0 - b = @benchmarkable mul!($w, $LC, $v) - @test run(b, samples=3).allocs == 0 + mul!(w, M, v) + @test (@allocated mul!(w, M, v)) == 0 + mul!(w, LC, v) + @test (@allocated mul!(w, LC, v)) == 0 for α in (false, true, rand(ComplexF64)), β in (false, true, rand(ComplexF64)) - b = @benchmarkable mul!($w, $LC, $v, $α, $β) - @test run(b, samples=3).allocs == 0 - b = @benchmarkable mul!($w, $(I + LC), $v, $α, $β) - @test run(b, samples=3).allocs == 0 - b = @benchmarkable mul!($w, $(LC + I), $v, $α, $β) - @test run(b, samples=3).allocs == 0 y = rand(ComplexF64, size(v)) - @test mul!(copy(y), LC, v, α, β) ≈ Matrix(LC)*v*α + y*β - @test mul!(copy(y), LC+I, v, α, β) ≈ Matrix(LC + I)*v*α + y*β + MC = Matrix(LC) + @test mul!(copy(y), LC, v, α, β) ≈ MC*v*α + y*β + @test mul!(copy(y), LC+I, v, α, β) ≈ (MC+I)*v*α + y*β + @test mul!(copy(y), I+LC, v, α, β) ≈ (I+MC)*v*α + y*β + @test (@allocated mul!(w, LC, v, α, β)) == 0 + ILC = I + LC + @test (@allocated mul!(w, ILC, v, α, β)) == 0 + LCI = LC + I + @test (@allocated mul!(w, LCI, v, α, β)) == 0 end # @test_throws ErrorException LinearMaps.LinearCombination{ComplexF64}((M, N), (1, 2, 3)) @test @inferred size(3M + 2.0N) == size(A) diff --git a/test/linearmaps.jl b/test/linearmaps.jl index 025a10ea..713c88d5 100644 --- a/test/linearmaps.jl +++ b/test/linearmaps.jl @@ -1,4 +1,4 @@ -using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools +using Test, LinearMaps, LinearAlgebra, SparseArrays @testset "basic functionality" begin A = 2 * rand(ComplexF64, (20, 10)) .- 1 diff --git a/test/scaledmap.jl b/test/scaledmap.jl index e73a1c46..1c4bf362 100644 --- a/test/scaledmap.jl +++ b/test/scaledmap.jl @@ -1,4 +1,4 @@ -using Test, LinearMaps, LinearAlgebra, BenchmarkTools +using Test, LinearMaps, LinearAlgebra @testset "scaledmap" begin N = 7 @@ -94,7 +94,8 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools for (A, alloc) in ((A0, 1), (A1, 0), (B0, 1), (B1, 0), (A0', 3), (A1', 0), (B0', 3), (B1', 0)) x = rand(N) y = similar(x) - b = @benchmarkable mul!($y, $A, $x) - @test run(b, samples = 3).allocs <= alloc + allocsize = @allocated similar(y) + mul!(y, A, x) + @test (@allocated mul!(y, A, x)) == alloc*allocsize end end diff --git a/test/uniformscalingmap.jl b/test/uniformscalingmap.jl index 530b8f97..e51b7093 100644 --- a/test/uniformscalingmap.jl +++ b/test/uniformscalingmap.jl @@ -1,19 +1,20 @@ -using Test, LinearMaps, LinearAlgebra, BenchmarkTools +using Test, LinearMaps, LinearAlgebra @testset "identity/scaling map" begin @test_throws ArgumentError LinearMaps.UniformScalingMap(true, -1) - A = 2 * rand(ComplexF64, (10, 10)) .- 1 + m = 5 + A = 2 * rand(ComplexF64, (m, m)) .- 1 B = rand(size(A)...) M = @inferred 1 * LinearMap(A) N = @inferred LinearMap(B) LC = @inferred M + N - v = rand(ComplexF64, 10) + v = rand(ComplexF64, m) w = similar(v) - Id = @inferred LinearMap(I, 10) - @test occursin("10×10 LinearMaps.UniformScalingMap{Bool}", sprint((t, s) -> show(t, "text/plain", s), Id)) - @test_throws ErrorException LinearMaps.UniformScalingMap(1, 10, 20) - @test_throws ErrorException LinearMaps.UniformScalingMap(1, (10, 20)) - @test size(Id) == (10, 10) + Id = @inferred LinearMap(I, m) + @test occursin("$m×$m LinearMaps.UniformScalingMap{Bool}", sprint((t, s) -> show(t, "text/plain", s), Id)) + @test_throws ErrorException LinearMaps.UniformScalingMap(1, m, 2m) + @test_throws ErrorException LinearMaps.UniformScalingMap(1, (m, 2m)) + @test size(Id) == (m, m) @test @inferred isreal(Id) @test @inferred issymmetric(Id) @test @inferred ishermitian(Id) @@ -26,25 +27,24 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools @test (3 * I - 2 * M') * v == -2 * A'v + 3v @test transpose(LinearMap(2 * M' + 3 * I)) * v ≈ transpose(2 * A' + 3 * I) * v @test LinearMap(2 * M' + 0I)' * v ≈ (2 * A')' * v - for λ in (0, 1, rand()), α in (0, 1, rand()), β in (0, 1, rand()), sz in (10, (10,5)) - Λ = @inferred LinearMap(λ*I, 10) + for λ in (0, 1, rand()), α in (0, 1, rand()), β in (0, 1, rand()), sz in (m, (m,5)) + Λ = @inferred LinearMap(λ*I, m) x = rand(Float64, sz) - y = rand(Float64, sz) - b = @benchmarkable mul!($y, $Λ, $x, $α, $β) - @test run(b, samples=3).allocs == 0 - y = deepcopy(x) + y = ones(Float64, sz) + @test (@allocated mul!(y, Λ, x, α, β)) == 0 + y = copy(x) @inferred mul!(y, Λ, x, α, β) @test y ≈ λ * x * α + β * x end for elty in (Float64, ComplexF64), transform in (identity, transpose, adjoint) λ = rand(elty) - x = rand(10) - J = @inferred LinearMap(LinearMaps.UniformScalingMap(λ, 10)) + x = rand(m) + J = @inferred LinearMap(LinearMaps.UniformScalingMap(λ, m)) @test transform(J) * x == transform(λ) * x - J = @inferred LinearMap(λ*I, 10) + J = @inferred LinearMap(λ*I, m) @test (λ * J) * x == (J * λ) * x == (λ * λ) * x end - X = rand(10, 10); Y = similar(X) + X = rand(m, m); Y = similar(X) @test mul!(Y, Id, X) == X @test Id*X isa LinearMap @test X*Id isa LinearMap From 39149861decf5c1448991e712f4f90fe2e24359f Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Sun, 6 Mar 2022 20:51:08 +0100 Subject: [PATCH 16/17] fix test --- test/uniformscalingmap.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/uniformscalingmap.jl b/test/uniformscalingmap.jl index e51b7093..ad430565 100644 --- a/test/uniformscalingmap.jl +++ b/test/uniformscalingmap.jl @@ -31,6 +31,7 @@ using Test, LinearMaps, LinearAlgebra Λ = @inferred LinearMap(λ*I, m) x = rand(Float64, sz) y = ones(Float64, sz) + mul!(y, Λ, x, α, β) @test (@allocated mul!(y, Λ, x, α, β)) == 0 y = copy(x) @inferred mul!(y, Λ, x, α, β) From 6ba6b94161e6305c94e305d876843a7eb5895db8 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Sun, 6 Mar 2022 21:31:02 +0100 Subject: [PATCH 17/17] fix test_broken syntax on v1.6 --- test/blockmap.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/blockmap.jl b/test/blockmap.jl index 9ed73e70..e8f8b5e8 100644 --- a/test/blockmap.jl +++ b/test/blockmap.jl @@ -281,7 +281,11 @@ using LinearMaps: FiveArg if transform != adjoint transL = transform(L) alloc = @allocated similar(v) - @test (@allocated mul!(v, transL, u, α, β)) <= alloc broken = (L == L2 && α != false) + if L == L2 && α != false + @test_broken (@allocated mul!(v, transL, u, α, β)) <= alloc + else + @test (@allocated mul!(v, transL, u, α, β)) <= alloc + end end end end