Skip to content

Commit 99bbc0e

Browse files
authored
Minor polish before v3.7 release (#182)
1 parent 0a42082 commit 99bbc0e

File tree

10 files changed

+155
-105
lines changed

10 files changed

+155
-105
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ julia> import Pkg; Pkg.add("LinearMaps")
3939
[license-img]: http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat
4040
[license-url]: LICENSE.md
4141

42-
[aqua-img]: https://img.shields.io/badge/Aqua.jl-%F0%9F%8C%A2-aqua.svg
42+
[aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg
4343
[aqua-url]: https://github.com/JuliaTesting/Aqua.jl

docs/src/custom.jl

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ mul!(ones(3,3), A, reshape(collect(1:9), 3, 3), 2, 2)
7373
using BenchmarkTools
7474

7575
@benchmark mul!($(zeros(3)), $A, $x)
76-
76+
7777
#-
7878

7979
@benchmark mul!($(zeros(3)), $A, $x, $(rand()), $(rand()))
@@ -150,12 +150,13 @@ transpose(A)*x
150150

151151
try MyFillMap(5.0, (3, 4))' * ones(3) catch e println(e) end
152152

153-
# which require explicit adjoint/transpose handling, for which there exist two *distinct* paths.
153+
# which require explicit adjoint/transpose handling, for which there exist two *distinct*
154+
# paths.
154155

155156
# ### Path 1: Generic, non-invariant `LinearMap` subtypes
156157

157-
# The first option is to write `LinearAlgebra.mul!` methods for the corresponding wrapped
158-
# map types; for instance,
158+
# The first option is to write `LinearMaps._unsafe_mul!` methods for the corresponding
159+
# wrapped map types; for instance,
159160

160161
function LinearMaps._unsafe_mul!(
161162
y::AbstractVecOrMat,
@@ -228,3 +229,56 @@ mul!(similar(x)', x', A)
228229
# corresponding 5-arg `mul!` methods. This may seem like a lot of methods to
229230
# be implemented, but note that adding such methods is only necessary/recommended
230231
# for performance.
232+
233+
# ## Computing a matrix representation
234+
235+
# In some cases, it might be necessary to compute a matrix representation of a `LinearMap`.
236+
# This is essentially done via the
237+
# `[LinearMaps._unsafe_mul!(::Matrix,::LinearMap,::Number)]`(@ref) method, for which a
238+
# generic fallback exists: it applies the `LinearMap` successively to the standard unit
239+
# vectors.
240+
241+
F = MyFillMap(5, (100,100))
242+
M = Matrix{eltype(F)}(undef, size(F))
243+
@benchmark Matrix($F)
244+
245+
#-
246+
247+
@benchmark LinearMaps._unsafe_mul!($(Matrix{Int}(undef, (100,100))), $(MyFillMap(5, (100,100))), true)
248+
249+
# If a more performant implementation exists, it is recommended to overwrite this method,
250+
# for instance (as before, size checks need not be included here since they are handled by
251+
# the corresponding `LinearAlgebra.mul!` method):
252+
253+
LinearMaps._unsafe_mul!(M::AbstractMatrix, A::MyFillMap, s::Number) = fill!(M, A.λ*s)
254+
@benchmark Matrix($F)
255+
256+
#-
257+
258+
@benchmark LinearMaps._unsafe_mul!($(Matrix{Int}(undef, (100,100))), $(MyFillMap(5, (100,100))), true)
259+
260+
# As one can see, the above runtimes are dominated by the allocation of the output matrix,
261+
# but still overwriting the multiplication kernel yields a speed-up of about factor 3 for
262+
# the matrix filling part.
263+
264+
# ## Slicing
265+
266+
# As usual, generic fallbacks for `LinearMap` slicing exist and are handled by the following
267+
# method hierarchy, where at least one of `I` and `J` has to be a `Colon`:
268+
#
269+
# Base.getindex(::LinearMap, I, J)
270+
# -> LinearMaps._getindex(::LinearMap, I, J)
271+
#
272+
# The method `Base.getindex` checks the validity of the the requested indices and calls
273+
# `LinearMaps._getindex`, which should be overloaded for custom `LinearMap`s subtypes.
274+
# For instance:
275+
276+
@benchmark F[1,:]
277+
278+
#-
279+
280+
LinearMaps._getindex(A::MyFillMap, ::Integer, J::Base.Slice) = fill(A.λ, axes(J))
281+
@benchmark F[1,:]
282+
283+
# Note that in `Base.getindex` `Colon`s are converted to `Base.Slice` via
284+
# `Base.to_indices`, thus the dispatch must be on `Base.Slice` rather than on `Colon`.

docs/src/index.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@ import Arpack, IterativeSolvers, KrylovKit, TSVD, ArnoldiMethod
5050
# Example 1, 1-dimensional Laplacian with periodic boundary conditions
5151
function leftdiff!(y::AbstractVector, x::AbstractVector) # left difference assuming periodic boundary conditions
5252
N = length(x)
53-
length(y) == N || throw(DimensionMismatch())
54-
@inbounds for i=1:N
53+
axes(y) == axes(x) || throw(DimensionMismatch())
54+
@inbounds for i in eachindex(x, y)
5555
y[i] = x[i] - x[mod1(i-1, N)]
5656
end
5757
return y
5858
end
5959

6060
function mrightdiff!(y::AbstractVector, x::AbstractVector) # minus right difference
6161
N = length(x)
62-
length(y) == N || throw(DimensionMismatch())
63-
@inbounds for i=1:N
62+
axes(y) == axes(x) || throw(DimensionMismatch())
63+
@inbounds for i in eachindex(x, y)
6464
y[i] = x[i] - x[mod1(i+1, N)]
6565
end
6666
return y
@@ -107,7 +107,7 @@ In Julia v1.3 and above, the last line can be simplified to
107107

108108
```julia
109109
KrylovKit.eigsolve(Δ, size(Δ, 1), 3, :LR)
110-
````
110+
```
111111

112112
leveraging the fact that objects of type `L <: LinearMap` are callable.
113113

docs/src/related.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The following open-source packages provide similar or even extended functionalit
88
[`LinearOperators.jl`](https://github.com/JuliaSmoothOptimizers/LinearOperators.jl)
99
and the Python package [`PyLops`](https://github.com/equinor/pylops)
1010
* [`fastmat`: fast linear transforms in Python](https://pypi.org/project/fastmat/)
11+
* [`JOLI.jl`: Julia Operators LIbrary](https://github.com/slimgroup/JOLI.jl)
1112
* [`FunctionOperators.jl`](https://github.com/hakkelt/FunctionOperators.jl)
1213
and [`LinearMapsAA.jl`](https://github.com/JeffFessler/LinearMapsAA.jl)
1314
also support mappings between `Array`s, inspired by the `fatrix` object type in the
@@ -19,8 +20,10 @@ there exist further related packages in the Julia ecosystem:
1920
* [`LazyArrays.jl`](https://github.com/JuliaArrays/LazyArrays.jl)
2021
* [`BlockArrays.jl`](https://github.com/JuliaArrays/BlockArrays.jl)
2122
* [`BlockDiagonals.jl`](https://github.com/invenia/BlockDiagonals.jl)
23+
* [`BlockFactorizations.jl`](https://github.com/SebastianAment/BlockFactorizations.jl)
2224
* [`Kronecker.jl`](https://github.com/MichielStock/Kronecker.jl)
2325
* [`FillArrays.jl`](https://github.com/JuliaArrays/FillArrays.jl)
26+
* [`LiftedMaps.jl](https://github.com/krcools/LiftedMaps.jl)
2427

2528
Since these packages provide types that are subtypes of Julia `Base`'s `AbstractMatrix` type,
2629
objects of those types can be wrapped by a `LinearMap` and freely mixed with, for instance,

docs/src/types.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ Type for lazily representing constantly filled matrices.
9292
LinearMaps.FillMap
9393
```
9494

95+
### `EmbeddedMap`
96+
97+
Type for representing linear maps that are embedded in larger zero maps.
98+
9599
## Methods
96100

97101
### Multiplication methods
@@ -104,7 +108,7 @@ LinearAlgebra.mul!(::AbstractVecOrMat,::LinearMap,::AbstractVector)
104108
LinearAlgebra.mul!(::AbstractVecOrMat,::LinearMap,::AbstractVector,::Number,::Number)
105109
LinearAlgebra.mul!(::AbstractMatrix,::AbstractMatrix,::LinearMap)
106110
LinearAlgebra.mul!(::AbstractVecOrMat,::LinearMap,::Number)
107-
LinearAlgebra.mul!(::AbstractVecOrMat,::LinearMap,::Number,::Number,::Number)
111+
LinearAlgebra.mul!(::AbstractMatrix,::LinearMap,::Number,::Number,::Number)
108112
*(::LinearAlgebra.AdjointAbsVec,::LinearMap)
109113
*(::LinearAlgebra.TransposeAbsVec,::LinearMap)
110114
```
@@ -139,3 +143,13 @@ as in the usual matrix case: `transpose(A) * x` and `mul!(y, A', x)`, for instan
139143
purposes or if you want to have the explicit sparse matrix representation of
140144
a linear map for which you only have a function definition (e.g. to be able
141145
to use its `transpose` or `adjoint`).
146+
147+
### Slicing methods
148+
149+
Complete slicing, i.e., `A[:,j]`, `A[:,J]`, `A[i,:]`, `A[I,:]` and `A[:,:]` for `i`, `j`
150+
`Integer` and `I`, `J` `AbstractVector{<:Integer}` is generically available for any
151+
`A::LinearMap` subtype via application of `A` (or `A'` for (predominantly) horizontal
152+
slicing) to standard unit vectors of appropriate length. By complete slicing we refer
153+
two-dimensional Cartesian indexing where at least one of the "indices" is a colon. This is
154+
facilitated by overloads of `Base.getindex`. Partial slicing à la `A[I,J]` and scalar or
155+
linear indexing are _not_ supported.

src/LinearMaps.jl

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -258,16 +258,16 @@ end
258258
_unsafe_mul!(y, A::MapOrVecOrMat, x) = mul!(y, A, x)
259259
_unsafe_mul!(y, A::AbstractVecOrMat, x, α, β) = mul!(y, A, x, α, β)
260260
_unsafe_mul!(y::AbstractVecOrMat, A::LinearMap, x::AbstractVector, α, β) =
261-
_generic_mapvec_mul!(y, A, x, α, β)
261+
_generic_map_mul!(y, A, x, α, β)
262262
_unsafe_mul!(y::AbstractMatrix, A::LinearMap, x::AbstractMatrix) =
263-
_generic_mapmat_mul!(y, A, x)
263+
_generic_map_mul!(y, A, x)
264264
_unsafe_mul!(y::AbstractMatrix, A::LinearMap, x::AbstractMatrix, α::Number, β::Number) =
265-
_generic_mapmat_mul!(y, A, x, α, β)
266-
_unsafe_mul!(Y::AbstractMatrix, A::LinearMap, s::Number) = _generic_mapnum_mul!(Y, A, s)
265+
_generic_map_mul!(y, A, x, α, β)
266+
_unsafe_mul!(Y::AbstractMatrix, A::LinearMap, s::Number) = _generic_map_mul!(Y, A, s)
267267
_unsafe_mul!(Y::AbstractMatrix, A::LinearMap, s::Number, α::Number, β::Number) =
268-
_generic_mapnum_mul!(Y, A, s, α, β)
268+
_generic_map_mul!(Y, A, s, α, β)
269269

270-
function _generic_mapvec_mul!(y, A, x, α, β)
270+
function _generic_map_mul!(y, A, x::AbstractVector, α, β)
271271
# this function needs to call mul! for, e.g., AdjointMap{...,<:CustomMap}
272272
if isone(α)
273273
iszero(β) && return mul!(y, A, x)
@@ -294,21 +294,19 @@ function _generic_mapvec_mul!(y, A, x, α, β)
294294
return y
295295
end
296296
end
297-
298-
function _generic_mapmat_mul!(Y, A, X)
297+
function _generic_map_mul!(Y, A, X::AbstractMatrix)
299298
for (Xi, Yi) in zip(eachcol(X), eachcol(Y))
300299
mul!(Yi, A, Xi)
301300
end
302301
return Y
303302
end
304-
function _generic_mapmat_mul!(Y, A, X, α, β)
303+
function _generic_map_mul!(Y, A, X::AbstractMatrix, α, β)
305304
for (Xi, Yi) in zip(eachcol(X), eachcol(Y))
306305
mul!(Yi, A, Xi, α, β)
307306
end
308307
return Y
309308
end
310-
311-
function _generic_mapnum_mul!(Y, A, s)
309+
function _generic_map_mul!(Y, A, s::Number)
312310
T = promote_type(eltype(A), typeof(s))
313311
ax2 = axes(A)[2]
314312
xi = zeros(T, ax2)
@@ -319,7 +317,7 @@ function _generic_mapnum_mul!(Y, A, s)
319317
end
320318
return Y
321319
end
322-
function _generic_mapnum_mul!(Y, A, s, α, β)
320+
function _generic_map_mul!(Y, A, s::Number, α, β)
323321
T = promote_type(eltype(A), typeof(s))
324322
ax2 = axes(A)[2]
325323
xi = zeros(T, ax2)

src/blockmap.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ MulStyle(A::BlockMap) = MulStyle(A.maps...)
2626
function _getranges(maps, dim, inds=1:length(maps))
2727
ends = map(i -> size(maps[i], dim)::Int, inds)
2828
cumsum!(ends, ends)
29-
starts = vcat(1, 1 .+ @views ends[1:end-1])
29+
starts = vcat(1, 1 .+ @views ends[firstindex(ends):lastindex(ends)-1])
3030
return UnitRange.(starts, ends)
3131
end
3232

@@ -172,7 +172,7 @@ function Base.hvcat(rows::Tuple{Vararg{Int}},
172172
throw(ArgumentError("mismatch between row sizes and number of arguments"))
173173
n = fill(-1, length(As))
174174
j = 0
175-
for i in 1:nr # infer UniformScaling sizes from row counts, if possible:
175+
for i in eachindex(rows) # infer UniformScaling sizes from row counts, if possible:
176176
ni = -1 # number of rows in this block-row, -1 indicates unknown
177177
for k in 1:rows[i]
178178
if !isa(As[j+k], UniformScaling)
@@ -190,10 +190,10 @@ function Base.hvcat(rows::Tuple{Vararg{Int}},
190190
# check for consistent total column number
191191
nc = -1
192192
j = 0
193-
for i in 1:nr
193+
for i in eachindex(rows)
194194
nci = 0
195195
rows[i] > 0 && n[j+1] == -1 && (j += rows[i]; continue)
196-
for k = 1:rows[i]
196+
for k in 1:rows[i]
197197
nci += isa(As[j+k], UniformScaling) ? n[j+k] : size(As[j+k], 2)::Int
198198
end
199199
nc >= 0 && nc != nci && throw(DimensionMismatch("mismatch in number of columns"))
@@ -202,11 +202,11 @@ function Base.hvcat(rows::Tuple{Vararg{Int}},
202202
end
203203
nc == -1 && throw(ArgumentError("sizes of UniformScalings could not be inferred"))
204204
j = 0
205-
for i in 1:nr
205+
for i in eachindex(rows)
206206
if rows[i] > 0 && n[j+1] == -1 # this row consists entirely of UniformScalings
207207
nci, r = divrem(nc, rows[i])
208208
r != 0 && throw(DimensionMismatch("indivisible UniformScaling sizes"))
209-
for k = 1:rows[i]
209+
for k in 1:rows[i]
210210
n[j+k] = nci
211211
end
212212
end

src/conversion.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Matrix: create matrix representation of LinearMap
22
function Base.Matrix{T}(A::LinearMap) where {T}
33
mat = Matrix{T}(undef, size(A))
4-
mul!(mat, A, one(T))
4+
_unsafe_mul!(mat, A, true)
55
end
66

77
function Base.AbstractMatrix{T}(A::LinearMap) where {T}
88
mat = similar(Array{T}, axes(A))
9-
mul!(mat, A, one(T))
9+
_unsafe_mul!(mat, A, true)
1010
end
1111

1212
Base.Matrix(A::LinearMap{T}) where {T} = Matrix{T}(A)
@@ -28,7 +28,7 @@ function SparseArrays.sparse(A::LinearMap{T}) where {T}
2828
v = fill(zero(T), N)
2929
Av = Vector{T}(undef, M)
3030

31-
@inbounds for i in 1:N
31+
@inbounds for i in eachindex(v)
3232
v[i] = one(T)
3333
_unsafe_mul!(Av, A, v)
3434
js = findall(!iszero, Av)

src/transpose.jl

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -50,44 +50,24 @@ Base.:(==)(A::LinearMap, B::TransposeMap) = issymmetric(A) && B.lmap == A
5050
Base.:(==)(A::LinearMap, B::AdjointMap) = ishermitian(A) && B.lmap == A
5151

5252
# multiplication with vector/matrices
53-
# # TransposeMap
54-
_unsafe_mul!(y::AbstractVecOrMat, A::TransposeMap, x::AbstractVector) =
55-
issymmetric(A.lmap) ?
56-
_unsafe_mul!(y, A.lmap, x) : error("transpose not implemented for $(A.lmap)")
57-
_unsafe_mul!(y::AbstractMatrix, A::TransposeMap, x::AbstractMatrix) =
58-
issymmetric(A.lmap) ?
59-
_unsafe_mul!(y, A.lmap, x) : _generic_mapmat_mul!(y, A, x)
60-
_unsafe_mul!(y::AbstractMatrix, A::TransposeMap, x::Number) =
61-
issymmetric(A.lmap) ?
62-
_unsafe_mul!(y, A.lmap, x) : _generic_mapnum_mul!(y, A, x)
63-
_unsafe_mul!(y::AbstractVecOrMat, A::TransposeMap, x::AbstractVector, α::Number, β::Number)=
64-
issymmetric(A.lmap) ?
65-
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_mapvec_mul!(y, A, x, α, β)
66-
_unsafe_mul!(y::AbstractMatrix, A::TransposeMap, x::AbstractMatrix, α::Number, β::Number) =
67-
issymmetric(A.lmap) ?
68-
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_mapmat_mul!(y, A, x, α, β)
69-
_unsafe_mul!(y::AbstractMatrix, A::TransposeMap, x::Number, α::Number, β::Number) =
70-
issymmetric(A.lmap) ?
71-
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_mapnum_mul!(y, A, x, α, β)
72-
# # AdjointMap
73-
_unsafe_mul!(y::AbstractVecOrMat, A::AdjointMap, x::AbstractVector) =
74-
ishermitian(A.lmap) ?
75-
_unsafe_mul!(y, A.lmap, x) : error("adjoint not implemented for $(A.lmap)")
76-
_unsafe_mul!(y::AbstractMatrix, A::AdjointMap, x::AbstractMatrix) =
77-
ishermitian(A.lmap) ?
78-
_unsafe_mul!(y, A.lmap, x) : _generic_mapmat_mul!(y, A, x)
79-
_unsafe_mul!(y::AbstractMatrix, A::AdjointMap, x::Number) =
80-
ishermitian(A.lmap) ?
81-
_unsafe_mul!(y, A.lmap, x) : _generic_mapnum_mul!(y, A, x)
82-
_unsafe_mul!(y::AbstractVecOrMat, A::AdjointMap, x::AbstractVector, α::Number, β::Number) =
83-
ishermitian(A.lmap) ?
84-
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_mapvec_mul!(y, A, x, α, β)
85-
_unsafe_mul!(y::AbstractMatrix, A::AdjointMap, x::AbstractMatrix, α::Number, β::Number) =
86-
ishermitian(A.lmap) ?
87-
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_mapmat_mul!(y, A, x, α, β)
88-
_unsafe_mul!(y::AbstractMatrix, A::AdjointMap, x::Number, α::Number, β::Number) =
89-
ishermitian(A.lmap) ?
90-
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_mapnum_mul!(y, A, x, α, β)
53+
for (Typ, prop, text) in ((AdjointMap, ishermitian, "adjoint"), (TransposeMap, issymmetric, "transpose"))
54+
@eval _unsafe_mul!(y::AbstractVecOrMat, A::$Typ, x::AbstractVector) =
55+
$prop(A.lmap) ?
56+
_unsafe_mul!(y, A.lmap, x) : error($text * " not implemented for $(A.lmap)")
57+
@eval _unsafe_mul!(y::AbstractVecOrMat, A::$Typ, x::AbstractVector, α::Number, β::Number) =
58+
$prop(A.lmap) ?
59+
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_map_mul!(y, A, x, α, β)
60+
61+
for In in (Number, AbstractMatrix)
62+
@eval _unsafe_mul!(y::AbstractMatrix, A::$Typ, x::$In) =
63+
$prop(A.lmap) ?
64+
_unsafe_mul!(y, A.lmap, x) : _generic_map_mul!(y, A, x)
65+
66+
@eval _unsafe_mul!(y::AbstractMatrix, A::$Typ, x::$In, α::Number, β::Number) =
67+
ishermitian(A.lmap) ?
68+
_unsafe_mul!(y, A.lmap, x, α, β) : _generic_map_mul!(y, A, x, α, β)
69+
end
70+
end
9171

9272
# # ConjugateMap
9373
const ConjugateMap = AdjointMap{<:Any, <:TransposeMap}
@@ -104,9 +84,10 @@ for (In, Out) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractM
10484
end
10585
end
10686
end
87+
_unsafe_mul!(y::AbstractMatrix, Ac::ConjugateMap, x::Number) = _conjmul!(y, Ac.lmap.lmap, x)
10788

10889
# multiplication helper function
109-
_conjmul!(y, A, x) = conj!(mul!(y, A, conj(x)))
90+
_conjmul!(y, A, x) = conj!(_unsafe_mul!(y, A, conj(x)))
11091
function _conjmul!(y, A, x::AbstractVector, α, β)
11192
xca = conj!(x * α)
11293
z = A * xca

0 commit comments

Comments
 (0)