Skip to content

Commit

Permalink
Introduce specific show methods, add Base.parent overload (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkarrasch authored Oct 7, 2020
1 parent 2d8dcdf commit 630d0a0
Show file tree
Hide file tree
Showing 22 changed files with 231 additions and 101 deletions.
6 changes: 6 additions & 0 deletions docs/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Abstract supertype
LinearMaps.LinearMap
```

Unwrapping function

```@docs
Base.parent
```

### `FunctionMap`

Type for wrapping an arbitrary function that is supposed to implement the
Expand Down
10 changes: 10 additions & 0 deletions src/LinearMaps.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ Base.ndims(::LinearMap) = 2
Base.size(A::LinearMap, n) = (n==1 || n==2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
Base.length(A::LinearMap) = size(A)[1] * size(A)[2]

"""
parent(A::LinearMap)
Return the underlying "parent map". This parent map is what was passed as an argument to
the specific `LinearMap` constructor, including implicit constructors and up to implicit
promotion to a `LinearMap` subtype. The fallback is to return the input itself.
"""
Base.parent(A::LinearMap) = A

# check dimension consistency for multiplication A*B
_iscompatible((A, B)) = size(A, 2) == size(B, 1)
function check_dim_mul(A, B)
Expand Down Expand Up @@ -235,6 +244,7 @@ include("functionmap.jl") # using a function as linear map
include("blockmap.jl") # block linear maps
include("kronecker.jl") # Kronecker product of linear maps
include("conversion.jl") # conversion of linear maps to matrices
include("show.jl") # show methods for LinearMap objects

"""
LinearMap(A::LinearMap; kwargs...)::WrappedMap
Expand Down
6 changes: 6 additions & 0 deletions src/blockmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ BlockMap{T}(maps::As, rows::S) where {T,As<:Tuple{Vararg{LinearMap}},S} = BlockM

MulStyle(A::BlockMap) = MulStyle(A.maps...)

Base.parent(A::BlockMap) = A.maps

"""
rowcolranges(maps, rows)
Expand Down Expand Up @@ -458,6 +460,10 @@ Base.cat

Base.size(A::BlockDiagonalMap) = (last(A.rowranges[end]), last(A.colranges[end]))

MulStyle(A::BlockDiagonalMap) = MulStyle(A.maps...)

Base.parent(A::BlockDiagonalMap) = A.maps

LinearAlgebra.issymmetric(A::BlockDiagonalMap) = all(issymmetric, A.maps)
LinearAlgebra.ishermitian(A::BlockDiagonalMap{<:Real}) = all(issymmetric, A.maps)
LinearAlgebra.ishermitian(A::BlockDiagonalMap) = all(ishermitian, A.maps)
Expand Down
1 change: 1 addition & 0 deletions src/composition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ CompositeMap{T}(maps::As) where {T, As<:Tuple{Vararg{LinearMap}}} = CompositeMap
# basic methods
Base.size(A::CompositeMap) = (size(A.maps[end], 1), size(A.maps[1], 2))
Base.isreal(A::CompositeMap) = all(isreal, A.maps) # sufficient but not necessary
Base.parent(A::CompositeMap) = A.maps

# the following rules are sufficient but not necessary
for (f, _f, g) in ((:issymmetric, :_issymmetric, :transpose),
Expand Down
9 changes: 1 addition & 8 deletions src/functionmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,9 @@ FunctionMap{T}(f, M::Int; kwargs...) where {T} = FunctionMap{T}(f, nothi
FunctionMap{T}(f, M::Int, N::Int; kwargs...) where {T} = FunctionMap{T}(f, nothing, M, N; kwargs...)
FunctionMap{T}(f, fc, M::Int; kwargs...) where {T} = FunctionMap{T}(f, fc, M, M; kwargs...)

# show
function Base.show(io::IO, A::FunctionMap{T, F, Nothing}) where {T, F}
print(io, "LinearMaps.FunctionMap{$T}($(A.f), $(A.M), $(A.N); ismutating=$(A._ismutating), issymmetric=$(A._issymmetric), ishermitian=$(A._ishermitian), isposdef=$(A._isposdef))")
end
function Base.show(io::IO, A::FunctionMap{T}) where {T}
print(io, "LinearMaps.FunctionMap{$T}($(A.f), $(A.fc), $(A.M), $(A.N); ismutating=$(A._ismutating), issymmetric=$(A._issymmetric), ishermitian=$(A._ishermitian), isposdef=$(A._isposdef))")
end

# properties
Base.size(A::FunctionMap) = (A.M, A.N)
Base.parent(A::FunctionMap) = (A.f, A.fc)
LinearAlgebra.issymmetric(A::FunctionMap) = A._issymmetric
LinearAlgebra.ishermitian(A::FunctionMap) = A._ishermitian
LinearAlgebra.isposdef(A::FunctionMap) = A._isposdef
Expand Down
5 changes: 4 additions & 1 deletion src/kronecker.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ Base.:(^)(A::MapOrMatrix, ::KronPower{p}) where {p} =

Base.size(A::KroneckerMap) = map(*, size.(A.maps)...)

Base.parent(A::KroneckerMap) = A.maps

LinearAlgebra.issymmetric(A::KroneckerMap) = all(issymmetric, A.maps)
LinearAlgebra.ishermitian(A::KroneckerMap{<:Real}) = issymmetric(A)
LinearAlgebra.ishermitian(A::KroneckerMap) = all(ishermitian, A.maps)
Expand Down Expand Up @@ -123,7 +125,7 @@ end
if nb*ma < mb*na
_unsafe_mul!(Y, B, Matrix(X*At))
else
_unsafe_mul!(Y, Matrix(B*X), At isa MatrixMap ? At.lmap : At.λ)
_unsafe_mul!(Y, Matrix(B*X), parent(At))
end
return y
end
Expand Down Expand Up @@ -248,6 +250,7 @@ Base.:(^)(A::MapOrMatrix, ::KronSumPower{p}) where {p} = kronsum(ntuple(n -> con

Base.size(A::KroneckerSumMap, i) = prod(size.(A.maps, i))
Base.size(A::KroneckerSumMap) = (size(A, 1), size(A, 2))
Base.parent(A::KroneckerSumMap) = A.maps

LinearAlgebra.issymmetric(A::KroneckerSumMap) = all(issymmetric, A.maps)
LinearAlgebra.ishermitian(A::KroneckerSumMap{<:Real}) = all(issymmetric, A.maps)
Expand Down
1 change: 1 addition & 0 deletions src/linearcombination.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ MulStyle(A::LinearCombination) = MulStyle(A.maps...)

# basic methods
Base.size(A::LinearCombination) = size(A.maps[1])
Base.parent(A::LinearCombination) = A.maps
LinearAlgebra.issymmetric(A::LinearCombination) = all(issymmetric, A.maps) # sufficient but not necessary
LinearAlgebra.ishermitian(A::LinearCombination) = all(ishermitian, A.maps) # sufficient but not necessary
LinearAlgebra.isposdef(A::LinearCombination) = all(isposdef, A.maps) # sufficient but not necessary
Expand Down
7 changes: 1 addition & 6 deletions src/scaledmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ end
ScaledMap::S, lmap::A) where {S<:RealOrComplex,A<:LinearMap} =
ScaledMap{Base.promote_op(*, S, eltype(lmap))}(λ, lmap)

# show
function Base.show(io::IO, A::ScaledMap{T}) where {T}
println(io, "LinearMaps.ScaledMap{$T}, scale = $(A.λ)")
show(io, A.lmap)
end

# basic methods
Base.size(A::ScaledMap) = size(A.lmap)
Base.parent(A::ScaledMap) = (A.λ, A.lmap)
Base.isreal(A::ScaledMap) = isreal(A.λ) && isreal(A.lmap)
LinearAlgebra.issymmetric(A::ScaledMap) = issymmetric(A.lmap)
LinearAlgebra.ishermitian(A::ScaledMap) = ishermitian(A.lmap)
Expand Down
96 changes: 96 additions & 0 deletions src/show.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# summary
function Base.summary(io::IO, A::LinearMap)
print(io, Base.dims2string(size(A)))
print(io, ' ')
_show_typeof(io, A)
end

# show
Base.show(io::IO, A::LinearMap) = (summary(io, A); _show(io, A))
_show(io::IO, ::LinearMap) = nothing
function _show(io::IO, A::FunctionMap{T,F,Nothing}) where {T,F}
print(io, "($(A.f); ismutating=$(A._ismutating), issymmetric=$(A._issymmetric), ishermitian=$(A._ishermitian), isposdef=$(A._isposdef))")
end
function _show(io::IO, A::FunctionMap)
print(io, "($(A.f), $(A.fc); ismutating=$(A._ismutating), issymmetric=$(A._issymmetric), ishermitian=$(A._ishermitian), isposdef=$(A._isposdef))")
end
function _show(io::IO, A::Union{CompositeMap,LinearCombination,KroneckerMap,KroneckerSumMap})
n = length(A.maps)
println(io, " with $n map", n>1 ? "s" : "", ":")
print_maps(io, A.maps)
end
function _show(io::IO, A::Union{AdjointMap,TransposeMap,WrappedMap})
print(io, " of ")
L = A.lmap
if A isa MatrixMap
# summary(io, L)
# println(io, ":")
# Base.print_matrix(io, L)
print(io, typeof(L))
else
show(io, L)
end
end
function _show(io::IO, A::BlockMap)
nrows = length(A.rows)
n = length(A.maps)
println(io, " with $n block map", n>1 ? "s" : "", " in $nrows block row", nrows>1 ? "s" : "")
print_maps(io, A.maps)
end
function _show(io::IO, A::BlockDiagonalMap)
n = length(A.maps)
println(io, " with $n diagonal block map", n>1 ? "s" : "")
print_maps(io, A.maps)
end
function _show(io::IO, J::UniformScalingMap)
s = "$(J.λ)"
print(io, " with scaling factor: $s")
end
function _show(io::IO, A::ScaledMap{T}) where {T}
println(io, " with scale: $(A.λ) of")
show(io, A.lmap)
end

# helper functions
function _show_typeof(io::IO, A::LinearMap{T}) where {T}
Base.show_type_name(io, typeof(A).name)
print(io, '{')
show(io, T)
print(io, '}')
end

function print_maps(io::IO, maps::Tuple{Vararg{LinearMap}})
n = length(maps)
if get(io, :limit, true) && n > 10
s = 1:5
e = n-5:n
if e[1] - s[end] > 1
for i in s
# print(io, ' ')
show(io, maps[i])
println(io, "")
end
print(io, "")
for i in e
println(io, "")
show(io, maps[i])
end
else
for i in 1:n-1
# print(io, ' ')
show(io, maps[i])
println(io, "")
end
# print(io, ' ')
show(io, last(maps))
end
else
for i in 1:n-1
# print(io, ' ')
show(io, maps[i])
println(io, "")
end
# print(io, ' ')
show(io, last(maps))
end
end
2 changes: 2 additions & 0 deletions src/transpose.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ MulStyle(A::Union{TransposeMap,AdjointMap}) = MulStyle(A.lmap)
LinearAlgebra.transpose(A::TransposeMap) = A.lmap
LinearAlgebra.adjoint(A::AdjointMap) = A.lmap

Base.parent(A::Union{AdjointMap,TransposeMap}) = A.lmap

"""
transpose(A::LinearMap)
Expand Down
1 change: 1 addition & 0 deletions src/uniformscalingmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ MulStyle(::UniformScalingMap) = FiveArg()

# properties
Base.size(A::UniformScalingMap) = (A.M, A.M)
Base.parent(A::UniformScalingMap) = A.λ
Base.isreal(A::UniformScalingMap) = isreal(A.λ)
LinearAlgebra.issymmetric(::UniformScalingMap) = true
LinearAlgebra.ishermitian(A::UniformScalingMap) = isreal(A)
Expand Down
1 change: 1 addition & 0 deletions src/wrappedmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Base.:(==)(A::MatrixMap, B::MatrixMap) =

# properties
Base.size(A::WrappedMap) = size(A.lmap)
Base.parent(A::WrappedMap) = A.lmap
LinearAlgebra.issymmetric(A::WrappedMap) = A._issymmetric
LinearAlgebra.ishermitian(A::WrappedMap) = A._ishermitian
LinearAlgebra.isposdef(A::WrappedMap) = A._isposdef
Expand Down
9 changes: 9 additions & 0 deletions test/blockmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, Interactive
A12 = rand(elty, 10, n2)
v = rand(elty, 10)
L = @inferred hcat(LinearMap(A11), LinearMap(A12))
@test parent(L) == (LinearMap(A11), LinearMap(A12))
@test occursin("10×$(10+n2) LinearMaps.BlockMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), L))
@test @inferred(LinearMaps.MulStyle(L)) === matrixstyle
@test L isa LinearMaps.BlockMap{elty}
if elty <: Complex
Expand All @@ -30,6 +32,10 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, Interactive
x = rand(elty, 61)
@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))
Expand All @@ -45,6 +51,7 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, Interactive
@test Matrix(L) A11
A21 = rand(elty, 20, 10)
L = @inferred vcat(LinearMap(A11), LinearMap(A21))
@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)) === matrixstyle
@test (@which [A11; A21]).module != LinearMaps
Expand Down Expand Up @@ -193,6 +200,8 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools, Interactive
@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 parent(Bd) == (L1, L2, L3, L2, L1)
@test occursin("25×39 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
Expand Down
2 changes: 2 additions & 0 deletions test/composition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays
@test @inferred (A * F) * v == @inferred A * (F * v)
@test @inferred A * (F * F) * v == @inferred A * (F * (F * v))
F2 = F*F
@test parent(F2) == (F, F)
FC2 = FC*FC
F4 = 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 Matrix(M * transpose(M)) A * transpose(A)
Expand Down
3 changes: 3 additions & 0 deletions test/functionmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools
MyFT = @inferred LinearMap{ComplexF64}(myft, N) / sqrt(N)
U = Matrix(MyFT) # will be a unitary matrix
@test @inferred U'U Matrix{eltype(U)}(I, N, N)
@test occursin("$N×$N LinearMaps.FunctionMap{$(eltype(MyFT))}", sprint((t, s) -> show(t, "text/plain", s), MyFT))
@test parent(LinearMap{ComplexF64}(myft, N)) === (myft, nothing)

CS = @inferred LinearMap(cumsum, 2)
@test size(CS) == (2, 2)
Expand All @@ -33,6 +35,7 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools
@test *(CS, v) == cv
@test_throws ErrorException CS' * v
CS = @inferred LinearMap(cumsum, x -> reverse(cumsum(reverse(x))), 10; ismutating=false)
@test occursin("10×10 LinearMaps.FunctionMap{Float64}", sprint((t, s) -> show(t, "text/plain", s), CS))
cv = cumsum(v)
@test @inferred CS * v == cv
@test @inferred *(CS, v) == cv
Expand Down
4 changes: 4 additions & 0 deletions test/kronecker.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays
LA = LinearMap(A)
LB = LinearMap(B)
LK = @inferred kron(LA, LB)
@test parent(LK) == (LA, LB)
@test_throws AssertionError LinearMaps.KroneckerMap{Float64}((LA, LB))
@test occursin("6×6 LinearMaps.KroneckerMap{$(eltype(LK))}", sprint((t, s) -> show(t, "text/plain", s), LK))
@test @inferred size(LK) == size(K)
@test LinearMaps.MulStyle(LK) === LinearMaps.ThreeArg()
for i in (1, 2)
Expand Down Expand Up @@ -68,6 +70,8 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays
LA = LinearMap(A)
LB = LinearMap(B)
KS = @inferred kronsum(LA, B)
@test parent(KS) == (LA, LB)
@test occursin("6×6 LinearMaps.KroneckerSumMap{$elty}", sprint((t, s) -> show(t, "text/plain", s), KS))
@test_throws ArgumentError kronsum(LA, [B B]) # non-square map
KSmat = kron(A, Matrix(I, 2, 2)) + kron(Matrix(I, 3, 3), B)
@test Matrix(KS) Matrix(kron(A, LinearMap(I, 2)) + kron(LinearMap(I, 3), B))
Expand Down
2 changes: 2 additions & 0 deletions test/linearcombination.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ using Test, LinearMaps, LinearAlgebra, BenchmarkTools
n = 10
L = sum(fill(CS!, n))
@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 parent(L) == ntuple(_ -> CS!, 10)
@test mul!(u, L, v) n * cumsum(v)
b = @benchmarkable mul!($u, $L, $v, 2, 2)
@test run(b, samples=5).allocs <= 1
Expand Down
Loading

0 comments on commit 630d0a0

Please sign in to comment.