Skip to content

Commit 55823fa

Browse files
authored
Speed up WrappedMap constructor and LM-promotion (#154)
1 parent e4d3791 commit 55823fa

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

docs/src/history.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Version history
22

3+
## What's new in v3.4
4+
5+
* In `WrappedMap` constructors, as implicitly called in addition and mutliplication
6+
of `LinearMap`s and `AbstractMatrix` objects, (conjugate) symmetry and positive
7+
definiteness are only determined for matrix types for which these checks are expected
8+
to be very cheap or even known at compile time based on the concrete type. The default
9+
for `LinearMap` subtypes is to call, for instance, `issymmetric`, because symmetry
10+
properties are either stored or easily obtained from constituting maps. For custom matrix
11+
types, define corresponding methods `LinearMaps._issymmetric`, `LinearMaps._ishermitian`
12+
and `LinearMaps._isposdef` to hook into the property checking mechanism.
13+
314
## What's new in v3.3
415

516
* `AbstractVector`s can now be wrapped by a `LinearMap` just like `AbstractMatrix``

src/LinearMaps.jl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ LinearAlgebra.isposdef(::LinearMap) = false # default assumptions
4848

4949
Base.ndims(::LinearMap) = 2
5050
Base.size(A::LinearMap, n) =
51-
(n==1 || n==2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
51+
(n == 1 || n == 2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
5252
Base.length(A::LinearMap) = size(A)[1] * size(A)[2]
5353

5454
# check dimension consistency for multiplication A*B
@@ -267,24 +267,27 @@ include("show.jl") # show methods for LinearMap objects
267267
268268
Construct a linear map object, either from an existing `LinearMap` or `AbstractVecOrMat` `A`,
269269
with the purpose of redefining its properties via the keyword arguments `kwargs`;
270-
a `UniformScaling` object `J` with specified (square) dimension `M`; from a `Number`
271-
object to lazily represent filled matrices; or
270+
a `UniformScaling` object `J` with specified (square) dimension `M`; or
272271
from a function or callable object `f`. In the latter case, one also needs to specify
273272
the size of the equivalent matrix representation `(M, N)`, i.e., for functions `f` acting
274273
on length `N` vectors and producing length `M` vectors (with default value `N=M`).
275274
Preferably, also the `eltype` `T` of the corresponding matrix representation needs to be
276-
specified, i.e. whether the action of `f` on a vector will be similar to, e.g., multiplying
275+
specified, i.e., whether the action of `f` on a vector will be similar to, e.g., multiplying
277276
by numbers of type `T`. If not specified, the devault value `T=Float64` will be assumed.
278277
Optionally, a corresponding function `fc` can be specified that implements the adjoint
279278
(=transpose in the real case) of `f`.
280279
281280
The keyword arguments and their default values for the function-based constructor are:
282-
* `issymmetric::Bool = false` : whether `A` or `f` acts as a symmetric matrix
283-
* `ishermitian::Bool = issymmetric & T<:Real` : whether `A` or `f` acts as a Hermitian
281+
* `issymmetric::Bool = false` : whether `A` or `f` act as a symmetric matrix
282+
* `ishermitian::Bool = issymmetric & T<:Real` : whether `A` or `f` act as a Hermitian
284283
matrix
285-
* `isposdef::Bool = false` : whether `A` or `f` acts as a positive definite matrix.
284+
* `isposdef::Bool = false` : whether `A` or `f` act as a positive definite matrix.
286285
For existing linear maps or matrices `A`, the default values will be taken by calling
287-
`issymmetric`, `ishermitian` and `isposdef` on the existing object `A`.
286+
internal functions `_issymmetric`, `_ishermitian` and `_isposdef` on the existing object `A`.
287+
These in turn dispatch to (overloads of) `LinearAlgebra`'s `issymmetric`, `ishermitian`,
288+
and `isposdef` methods whenever these checks are expected to be computationally cheap or even
289+
known at compile time as for certain structured matrices, but return `false` for generic
290+
`AbstractMatrix` types.
288291
289292
For the function-based constructor, there is one more keyword argument:
290293
* `ismutating::Bool` : flags whether the function acts as a mutating matrix multiplication

src/wrappedmap.jl

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ struct WrappedMap{T, A<:MapOrVecOrMat} <: LinearMap{T}
55
_isposdef::Bool
66
end
77
function WrappedMap{T}(lmap::MapOrMatrix;
8-
issymmetric::Bool = issymmetric(lmap),
9-
ishermitian::Bool = ishermitian(lmap),
10-
isposdef::Bool = isposdef(lmap)) where {T}
8+
issymmetric::Bool = _issymmetric(lmap),
9+
ishermitian::Bool = _ishermitian(lmap),
10+
isposdef::Bool = _isposdef(lmap)) where {T}
1111
WrappedMap{T, typeof(lmap)}(lmap, issymmetric, ishermitian, isposdef)
1212
end
13-
WrappedMap(lmap::MapOrMatrix{T}; kwargs...) where {T} = WrappedMap{T}(lmap; kwargs...)
1413
function WrappedMap{T}(lmap::AbstractVector;
1514
issym::Bool = false,
1615
isherm::Bool = false,
@@ -20,7 +19,23 @@ function WrappedMap{T}(lmap::AbstractVector;
2019
length(lmap) == 1 && ishermitian(first(lmap)),
2120
length(lmap) == 1 && isposdef(first(lmap)))
2221
end
23-
WrappedMap(lmap::AbstractVector{T}; kwargs...) where {T} = WrappedMap{T}(lmap; kwargs...)
22+
WrappedMap(lmap::MapOrVecOrMat{T}; kwargs...) where {T} = WrappedMap{T}(lmap; kwargs...)
23+
24+
# cheap property checks (usually by type)
25+
_issymmetric(A::AbstractMatrix) = false
26+
_issymmetric(A::AbstractSparseMatrix) = issymmetric(A)
27+
_issymmetric(A::LinearMap) = issymmetric(A)
28+
_issymmetric(A::LinearAlgebra.RealHermSymComplexSym) = issymmetric(A)
29+
_issymmetric(A::Union{Bidiagonal,Diagonal,SymTridiagonal,Tridiagonal}) = issymmetric(A)
30+
31+
_ishermitian(A::AbstractMatrix) = false
32+
_ishermitian(A::AbstractSparseMatrix) = ishermitian(A)
33+
_ishermitian(A::LinearMap) = ishermitian(A)
34+
_ishermitian(A::LinearAlgebra.RealHermSymComplexHerm) = ishermitian(A)
35+
_ishermitian(A::Union{Bidiagonal,Diagonal,SymTridiagonal,Tridiagonal}) = ishermitian(A)
36+
37+
_isposdef(A::AbstractMatrix) = false
38+
_isposdef(A::LinearMap) = isposdef(A)
2439

2540
const VecOrMatMap{T} = WrappedMap{T,<:AbstractVecOrMat}
2641

test/wrappedmap.jl

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ using Test, LinearMaps, LinearAlgebra
33
@testset "wrapped maps" begin
44
A = rand(10, 20)
55
B = rand(ComplexF64, 10, 20)
6-
SA = A'A + I
7-
SB = B'B + I
6+
SA = Hermitian(A'A + I)
7+
SB = Hermitian(B'B + I)
88
L = @inferred LinearMap{Float64}(A)
99
@test summary(L) == "10×20 LinearMaps.WrappedMap{Float64}"
1010
@test occursin("10×20 LinearMaps.WrappedMap{Float64}", sprint((t, s) -> show(t, "text/plain", s), L))
11-
MA = @inferred LinearMap(SA)
12-
MB = @inferred LinearMap(SB)
11+
MA = @inferred LinearMap(SA, isposdef=true)
12+
MB = @inferred LinearMap(SB, isposdef=true)
1313
@test eltype(Matrix{Complex{Float32}}(LinearMap(A))) <: Complex
1414
@test size(L) == size(A)
1515
@test @inferred !issymmetric(L)
@@ -36,7 +36,7 @@ using Test, LinearMaps, LinearAlgebra
3636
@test @inferred LinearMap(M')' * v == A * v
3737
@test @inferred(transpose(transpose(M))) === M
3838
@test @inferred(adjoint(adjoint(M))) === M
39-
Mherm = @inferred LinearMap(A'A)
39+
Mherm = @inferred LinearMap(Hermitian(A'A), isposdef=true)
4040
@test @inferred ishermitian(Mherm)
4141
@test @inferred !issymmetric(Mherm)
4242
@test @inferred !issymmetric(transpose(Mherm))
@@ -96,4 +96,21 @@ using Test, LinearMaps, LinearAlgebra
9696
@test issymmetric(U)
9797
@test !ishermitian(U)
9898
@test !isposdef(U)
99+
# symmetry
100+
O4 = ones(4)
101+
O3 = ones(3)
102+
Z3 = zeros(3)
103+
for M in (Bidiagonal(O4, O3, :U),
104+
Bidiagonal(O4, Z3, :L),
105+
Diagonal(O4),
106+
Diagonal(im*O4),
107+
SymTridiagonal(O4, O3),
108+
Tridiagonal(O3, O4, O3),
109+
Tridiagonal(O3, O4, Z3),
110+
sparse(Tridiagonal(O3, O4, O3)),
111+
sparse(Diagonal(im*O4)),
112+
)
113+
@test issymmetric(LinearMap(M)) == issymmetric(M)
114+
@test ishermitian(LinearMap(M)) == ishermitian(M)
115+
end
99116
end

0 commit comments

Comments
 (0)