Skip to content

Commit f8eaf98

Browse files
authored
Allow vectors for LinearMap storage (#171)
1 parent c804b06 commit f8eaf98

20 files changed

+398
-240
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
name = "LinearMaps"
22
uuid = "7a12625a-238d-50fd-b39a-03d52299707e"
3-
version = "3.5.2"
3+
version = "3.6.0"
44

55
[deps]
66
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
77
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
8+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
89

910
[compat]
1011
julia = "1.6"

docs/Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e"
66
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
77

88
[compat]
9-
BenchmarkTools = "0.4, 0.5"
10-
Documenter = "0.25, 0.26"
9+
BenchmarkTools = "1"
10+
Documenter = "0.25, 0.26, 0.27"
1111
Literate = "2"

docs/src/history.md

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

3+
## What's new in v3.6
4+
5+
* Support for Julia versions below v1.6 has been dropped.
6+
* `Block[Diagonal]Map`, `CompositeMap`, `KroneckerMap` and `LinearCombination` type objects
7+
can now be backed by a `Vector` of `LinearMap`-type elements. This can be beneficial in
8+
cases where these higher-order `LinearMap`s are constructed from many maps where a tuple
9+
backend may get inefficient or impose hard work for the compiler at construction.
10+
The default behavior, however, does not change, and construction of vector-based
11+
`LinearMap`s requires usage of the unexported constructors ("expert usage").
12+
313
## What's new in v3.5
414

515
* `WrappedMap`, `ScaledMap`, `LinearCombination`, `AdjointMap`, `TransposeMap` and
@@ -24,7 +34,7 @@
2434
## What's new in v3.3
2535

2636
* `AbstractVector`s can now be wrapped by a `LinearMap` just like `AbstractMatrix``
27-
typed objects. Upon wrapping, there are not implicitly reshaped to matrices. This
37+
typed objects. Upon wrapping, they are not implicitly reshaped to matrices. This
2838
feature might be helpful, for instance, in the lazy representation of rank-1
2939
operators `kron(LinearMap(u), v') == ⊗(u, v') == u ⊗ v'` for vectors `u` and `v`.
3040
The action on vectors,`(u⊗v')*x`, is implemented optimally via `u*(v'x)`.

src/LinearMaps.jl

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ using LinearAlgebra
88
import LinearAlgebra: mul!
99
using SparseArrays
1010

11+
import Statistics: mean
12+
1113
using Base: require_one_based_indexing
1214

1315
abstract type LinearMap{T} end
@@ -16,8 +18,21 @@ const MapOrVecOrMat{T} = Union{LinearMap{T}, AbstractVecOrMat{T}}
1618
const MapOrMatrix{T} = Union{LinearMap{T}, AbstractMatrix{T}}
1719
const RealOrComplex = Union{Real, Complex}
1820

21+
const LinearMapTuple = Tuple{Vararg{LinearMap}}
22+
const LinearMapVector = AbstractVector{<:LinearMap}
23+
const LinearMapTupleOrVector = Union{LinearMapTuple,LinearMapVector}
24+
1925
Base.eltype(::LinearMap{T}) where {T} = T
2026

27+
# conversion to LinearMap
28+
Base.convert(::Type{LinearMap}, A::LinearMap) = A
29+
Base.convert(::Type{LinearMap}, A::AbstractVecOrMat) = LinearMap(A)
30+
31+
convert_to_lmaps() = ()
32+
convert_to_lmaps(A) = (convert(LinearMap, A),)
33+
@inline convert_to_lmaps(A, B, Cs...) =
34+
(convert(LinearMap, A), convert(LinearMap, B), convert_to_lmaps(Cs...)...)
35+
2136
abstract type MulStyle end
2237

2338
struct FiveArg <: MulStyle end
@@ -61,13 +76,20 @@ function check_dim_mul(C, A, B)
6176
return nothing
6277
end
6378

64-
# conversion of AbstractVecOrMat to LinearMap
65-
convert_to_lmaps_(A::AbstractVecOrMat) = LinearMap(A)
66-
convert_to_lmaps_(A::LinearMap) = A
67-
convert_to_lmaps() = ()
68-
convert_to_lmaps(A) = (convert_to_lmaps_(A),)
69-
@inline convert_to_lmaps(A, B, Cs...) =
70-
(convert_to_lmaps_(A), convert_to_lmaps_(B), convert_to_lmaps(Cs...)...)
79+
_front(As::Tuple) = Base.front(As)
80+
_front(As::AbstractVector) = @inbounds @views As[1:end-1]
81+
_tail(As::Tuple) = Base.tail(As)
82+
_tail(As::AbstractVector) = @inbounds @views As[2:end]
83+
84+
_combine(A::LinearMap, B::LinearMap) = tuple(A, B)
85+
_combine(A::LinearMap, Bs::LinearMapTuple) = tuple(A, Bs...)
86+
_combine(As::LinearMapTuple, B::LinearMap) = tuple(As..., B)
87+
_combine(As::LinearMapTuple, Bs::LinearMapTuple) = tuple(As..., Bs...)
88+
_combine(A::LinearMap, Bs::LinearMapVector) = Base.vect(A, Bs...)
89+
_combine(As::LinearMapVector, B::LinearMap) = Base.vect(As..., B)
90+
_combine(As::LinearMapVector, Bs::LinearMapTuple) = Base.vect(As..., Bs...)
91+
_combine(As::LinearMapTuple, Bs::LinearMapVector) = Base.vect(As..., Bs...)
92+
_combine(As::LinearMapVector, Bs::LinearMapVector) = Base.vect(As..., Bs...)
7193

7294
# The (internal) multiplication logic is as follows:
7395
# - `*(A, x)` calls `mul!(y, A, x)` for appropriately-sized y
@@ -233,8 +255,6 @@ function _unsafe_mul!(y::AbstractMatrix, A::LinearMap, x::AbstractMatrix, α, β
233255
return _generic_mapmat_mul!(y, A, x, α, β)
234256
end
235257

236-
const LinearMapTuple = Tuple{Vararg{LinearMap}}
237-
238258
include("left.jl") # left multiplication by a transpose or adjoint vector
239259
include("transpose.jl") # transposing linear maps
240260
include("wrappedmap.jl") # wrap a matrix of linear map in a new type, thereby allowing to alter its properties

src/blockmap.jl

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,32 @@
11
struct BlockMap{T,
2-
As<:LinearMapTuple,
3-
Rs<:Tuple{Vararg{Int}},
4-
Rranges<:Tuple{Vararg{UnitRange{Int}}},
5-
Cranges<:Tuple{Vararg{UnitRange{Int}}}} <: LinearMap{T}
2+
As<:LinearMapTupleOrVector,
3+
Rs<:Tuple{Vararg{Int}}} <: LinearMap{T}
64
maps::As
75
rows::Rs
8-
rowranges::Rranges
9-
colranges::Cranges
6+
rowranges::Vector{UnitRange{Int}}
7+
colranges::Vector{UnitRange{Int}}
108
function BlockMap{T,As,Rs}(maps::As, rows::Rs) where
11-
{T, As<:LinearMapTuple, Rs<:Tuple{Vararg{Int}}}
9+
{T, As<:LinearMapTupleOrVector, Rs<:Tuple{Vararg{Int}}}
1210
for TA in Base.Generator(eltype, maps)
1311
promote_type(T, TA) == T ||
1412
error("eltype $TA cannot be promoted to $T in BlockMap constructor")
1513
end
1614
rowranges, colranges = rowcolranges(maps, rows)
17-
Rranges, Cranges = typeof(rowranges), typeof(colranges)
18-
return new{T, As, Rs, Rranges, Cranges}(maps, rows, rowranges, colranges)
15+
return new{T, As, Rs}(maps, rows, rowranges, colranges)
1916
end
2017
end
2118

22-
BlockMap{T}(maps::As, rows::Rs) where {T, As<:LinearMapTuple, Rs} =
19+
BlockMap{T}(maps::As, rows::Rs) where {T, As<:LinearMapTupleOrVector, Rs} =
2320
BlockMap{T, As, Rs}(maps, rows)
21+
BlockMap(maps::As, rows::Rs) where {As<:LinearMapTupleOrVector, Rs} =
22+
BlockMap{promote_type(map(eltype, maps)...), As, Rs}(maps, rows)
2423

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

27-
function _getranges(maps, dim, inds=ntuple(identity, Val(length(maps))))
28-
sizes = map(i -> size(maps[i], dim)::Int, inds)
29-
ends = cumsum(sizes)
30-
starts = (1, (1 .+ Base.front(ends))...)
26+
function _getranges(maps, dim, inds=1:length(maps))
27+
ends = map(i -> size(maps[i], dim)::Int, inds)
28+
cumsum!(ends, ends)
29+
starts = vcat(1, 1 .+ @views ends[1:end-1])
3130
return UnitRange.(starts, ends)
3231
end
3332

@@ -40,14 +39,15 @@ block linear map obtained from `hvcat(rows, maps...)`.
4039
"""
4140
function rowcolranges(maps, rows)
4241
# find indices of the row-wise first maps
43-
firstmapinds = cumsum((1, Base.front(rows)...))
42+
firstmapinds = vcat(1, Base.front(rows)...)
43+
cumsum!(firstmapinds, firstmapinds)
4444
# compute rowranges from first dimension of the row-wise first maps
4545
rowranges = _getranges(maps, 1, firstmapinds)
4646

4747
# compute ranges from second dimension as if all in one row
4848
temp = _getranges(maps, 2)
4949
# introduce "line breaks"
50-
colranges = ntuple(Val(length(maps))) do i
50+
colranges = map(1:length(maps)) do i
5151
# for each map find the index of the respective row-wise first map
5252
# something-trick just to assure the compiler that the index is an Int
5353
@inbounds firstmapind = firstmapinds[something(findlast(<=(i), firstmapinds), 1)]
@@ -277,7 +277,7 @@ end
277277
############
278278

279279
Base.:(==)(A::BlockMap, B::BlockMap) =
280-
(eltype(A) == eltype(B) && A.maps == B.maps && A.rows == B.rows)
280+
(eltype(A) == eltype(B) && all(A.maps .== B.maps) && all(A.rows .== B.rows))
281281

282282
############
283283
# multiplication helper functions
@@ -350,9 +350,9 @@ function _transblockmul!(y, A::BlockMap, x, α, β, transform)
350350
# subsequent block rows of A (block columns of A'),
351351
# add results to corresponding parts of y
352352
# TODO: think about multithreading
353-
for (row, xi) in zip(Base.tail(rows), Base.tail(xinds))
354-
xrow = selectdim(x, 1, xi)
355-
for _ in 1:row
353+
@inbounds for i in 2:length(rows)
354+
xrow = selectdim(x, 1, xinds[i])
355+
for _ in 1:rows[i]
356356
mapind +=1
357357
yrow = selectdim(y, 1, yinds[mapind])
358358
_unsafe_mul!(yrow, transform(maps[mapind]), xrow, α, true)
@@ -396,24 +396,22 @@ end
396396
############
397397
# BlockDiagonalMap
398398
############
399-
struct BlockDiagonalMap{T,
400-
As<:LinearMapTuple,
401-
Ranges<:Tuple{Vararg{UnitRange{Int}}}} <: LinearMap{T}
399+
struct BlockDiagonalMap{T, As<:LinearMapTupleOrVector} <: LinearMap{T}
402400
maps::As
403-
rowranges::Ranges
404-
colranges::Ranges
405-
function BlockDiagonalMap{T, As}(maps::As) where {T, As<:LinearMapTuple}
401+
rowranges::Vector{UnitRange{Int}}
402+
colranges::Vector{UnitRange{Int}}
403+
function BlockDiagonalMap{T, As}(maps::As) where {T, As<:LinearMapTupleOrVector}
406404
for TA in Base.Generator(eltype, maps)
407405
promote_type(T, TA) == T ||
408406
error("eltype $TA cannot be promoted to $T in BlockDiagonalMap constructor")
409407
end
410408
rowranges = _getranges(maps, 1)
411409
colranges = _getranges(maps, 2)
412-
return new{T, As, typeof(rowranges)}(maps, rowranges, colranges)
410+
return new{T, As}(maps, rowranges, colranges)
413411
end
414412
end
415413

416-
BlockDiagonalMap{T}(maps::As) where {T, As<:LinearMapTuple} =
414+
BlockDiagonalMap{T}(maps::As) where {T, As<:LinearMapTupleOrVector} =
417415
BlockDiagonalMap{T,As}(maps)
418416
BlockDiagonalMap(maps::LinearMap...) =
419417
BlockDiagonalMap{promote_type(map(eltype, maps)...)}(maps)
@@ -478,7 +476,7 @@ LinearAlgebra.transpose(A::BlockDiagonalMap{T}) where {T} =
478476
BlockDiagonalMap{T}(map(transpose, A.maps))
479477

480478
Base.:(==)(A::BlockDiagonalMap, B::BlockDiagonalMap) =
481-
(eltype(A) == eltype(B) && A.maps == B.maps)
479+
(eltype(A) == eltype(B) && all(A.maps .== B.maps))
482480

483481
for (In, Out) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix))
484482
@eval begin
@@ -496,7 +494,7 @@ end
496494
function _blockscaling!(y, A::BlockDiagonalMap, x, α, β)
497495
maps, yinds, xinds = A.maps, A.rowranges, A.colranges
498496
# TODO: think about multi-threading here
499-
@inbounds for i in eachindex(yinds, maps, xinds)
497+
@inbounds for i in 1:length(maps)
500498
_unsafe_mul!(selectdim(y, 1, yinds[i]), maps[i], selectdim(x, 1, xinds[i]), α, β)
501499
end
502500
return y

src/composition.jl

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
struct CompositeMap{T, As<:LinearMapTuple} <: LinearMap{T}
1+
struct CompositeMap{T, As<:LinearMapTupleOrVector} <: LinearMap{T}
22
maps::As # stored in order of application to vector
33
function CompositeMap{T, As}(maps::As) where {T, As}
44
N = length(maps)
@@ -12,7 +12,12 @@ struct CompositeMap{T, As<:LinearMapTuple} <: LinearMap{T}
1212
new{T, As}(maps)
1313
end
1414
end
15-
CompositeMap{T}(maps::As) where {T, As<:LinearMapTuple} = CompositeMap{T, As}(maps)
15+
CompositeMap{T}(maps::As) where {T, As<:LinearMapTupleOrVector} = CompositeMap{T, As}(maps)
16+
17+
Base.mapreduce(::typeof(identity), ::typeof(Base.mul_prod), maps::LinearMapTupleOrVector) =
18+
CompositeMap{promote_type(map(eltype, maps)...)}(reverse(maps))
19+
Base.mapreduce(::typeof(identity), ::typeof(Base.mul_prod), maps::AbstractVector{<:LinearMap{T}}) where {T} =
20+
CompositeMap{T}(reverse(maps))
1621

1722
# basic methods
1823
Base.size(A::CompositeMap) = (size(A.maps[end], 1), size(A.maps[1], 2))
@@ -26,8 +31,18 @@ for (f, _f, g) in ((:issymmetric, :_issymmetric, :transpose),
2631
LinearAlgebra.$f(A::CompositeMap) = $_f(A.maps)
2732
$_f(maps::Tuple{}) = true
2833
$_f(maps::Tuple{<:LinearMap}) = $f(maps[1])
29-
$_f(maps::Tuple{Vararg{LinearMap}}) =
34+
$_f(maps::LinearMapTuple) =
3035
maps[end] == $g(maps[1]) && $_f(Base.front(Base.tail(maps)))
36+
function $_f(maps::LinearMapVector)
37+
n = length(maps)
38+
if n == 0
39+
return true
40+
elseif n == 1
41+
return ($f(maps[1]))::Bool
42+
else
43+
return ((maps[end] == $g(maps[1]))::Bool && $_f(@views maps[2:end-1]))
44+
end
45+
end
3146
# since the introduction of ScaledMap, the following cases cannot occur
3247
# function $_f(maps::Tuple{Vararg{LinearMap}}) # length(maps) >= 2
3348
# if maps[1] isa UniformScalingMap{<:RealOrComplex}
@@ -45,23 +60,34 @@ end
4560
LinearAlgebra.isposdef(A::CompositeMap) = _isposdef(A.maps)
4661
_isposdef(maps::Tuple{}) = true # empty product is equivalent to "I" which is pos. def.
4762
_isposdef(maps::Tuple{<:LinearMap}) = isposdef(maps[1])
48-
function _isposdef(maps::Tuple{Vararg{LinearMap}})
49-
(maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) &&
50-
isposdef(maps[1]) && _isposdef(Base.front(Base.tail(maps)))
63+
function _isposdef(maps::LinearMapTuple)
64+
(maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) &&
65+
isposdef(maps[1]) && _isposdef(Base.front(Base.tail(maps)))
66+
end
67+
function _isposdef(maps::LinearMapVector)
68+
n = length(maps)
69+
if n == 0
70+
return true
71+
elseif n == 1
72+
return isposdef(maps[1])
73+
else
74+
return (maps[end] == adjoint(maps[1]) || maps[end] == maps[1]) &&
75+
isposdef(maps[1]) && _isposdef(maps[2:end-1])
76+
end
5177
end
5278

5379
# scalar multiplication and division (non-commutative case)
5480
function Base.:(*)(α::Number, A::LinearMap)
5581
T = promote_type(typeof(α), eltype(A))
56-
return CompositeMap{T}(tuple(A, UniformScalingMap(α, size(A, 1))))
82+
return CompositeMap{T}(_combine(A, UniformScalingMap(α, size(A, 1))))
5783
end
5884
function Base.:(*)(α::Number, A::CompositeMap)
5985
T = promote_type(typeof(α), eltype(A))
6086
Alast = last(A.maps)
6187
if Alast isa UniformScalingMap
62-
return CompositeMap{T}(tuple(Base.front(A.maps)..., α * Alast))
88+
return CompositeMap{T}(_combine(_front(A.maps), α * Alast))
6389
else
64-
return CompositeMap{T}(tuple(A.maps..., UniformScalingMap(α, size(A, 1))))
90+
return CompositeMap{T}(_combine(A.maps, UniformScalingMap(α, size(A, 1))))
6591
end
6692
end
6793
# needed for disambiguation
@@ -71,15 +97,15 @@ function Base.:(*)(α::RealOrComplex, A::CompositeMap{<:RealOrComplex})
7197
end
7298
function Base.:(*)(A::LinearMap, α::Number)
7399
T = promote_type(typeof(α), eltype(A))
74-
return CompositeMap{T}(tuple(UniformScalingMap(α, size(A, 2)), A))
100+
return CompositeMap{T}(_combine(UniformScalingMap(α, size(A, 2)), A))
75101
end
76102
function Base.:(*)(A::CompositeMap, α::Number)
77103
T = promote_type(typeof(α), eltype(A))
78104
Afirst = first(A.maps)
79105
if Afirst isa UniformScalingMap
80-
return CompositeMap{T}(tuple(Afirst * α, Base.tail(A.maps)...))
106+
return CompositeMap{T}(_combine(Afirst * α, _tail(A.maps)))
81107
else
82-
return CompositeMap{T}(tuple(UniformScalingMap(α, size(A, 2)), A.maps...))
108+
return CompositeMap{T}(_combine(UniformScalingMap(α, size(A, 2)), A.maps))
83109
end
84110
end
85111
# needed for disambiguation
@@ -110,19 +136,19 @@ julia> LinearMap(ones(Int, 3, 3)) * CS * I * rand(3, 3);
110136
"""
111137
function Base.:(*)(A₁::LinearMap, A₂::LinearMap)
112138
T = promote_type(eltype(A₁), eltype(A₂))
113-
return CompositeMap{T}(tuple(A₂, A₁))
139+
return CompositeMap{T}(_combine(A₂, A₁))
114140
end
115141
function Base.:(*)(A₁::LinearMap, A₂::CompositeMap)
116142
T = promote_type(eltype(A₁), eltype(A₂))
117-
return CompositeMap{T}(tuple(A₂.maps..., A₁))
143+
return CompositeMap{T}(_combine(A₂.maps, A₁))
118144
end
119145
function Base.:(*)(A₁::CompositeMap, A₂::LinearMap)
120146
T = promote_type(eltype(A₁), eltype(A₂))
121-
return CompositeMap{T}(tuple(A₂, A₁.maps...))
147+
return CompositeMap{T}(_combine(A₂, A₁.maps))
122148
end
123149
function Base.:(*)(A₁::CompositeMap, A₂::CompositeMap)
124150
T = promote_type(eltype(A₁), eltype(A₂))
125-
return CompositeMap{T}(tuple(A₂.maps..., A₁.maps...))
151+
return CompositeMap{T}(_combine(A₂.maps, A₁.maps))
126152
end
127153
# needed for disambiguation
128154
Base.:(*)(A₁::ScaledMap, A₂::CompositeMap) = A₁.λ * (A₁.lmap * A₂)
@@ -135,7 +161,8 @@ LinearAlgebra.adjoint(A::CompositeMap{T}) where {T} =
135161
CompositeMap{T}(map(adjoint, reverse(A.maps)))
136162

137163
# comparison of CompositeMap objects
138-
Base.:(==)(A::CompositeMap, B::CompositeMap) = (eltype(A) == eltype(B) && A.maps == B.maps)
164+
Base.:(==)(A::CompositeMap, B::CompositeMap) =
165+
(eltype(A) == eltype(B) && all(A.maps .== B.maps))
139166

140167
# multiplication with vectors/matrices
141168
_unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap, x::AbstractVector) =

0 commit comments

Comments
 (0)