Skip to content

Commit bc62f53

Browse files
authored
Make ismutable a type-parameter of FunctionMap (#194)
1 parent d25eb05 commit bc62f53

19 files changed

+288
-201
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "LinearMaps"
22
uuid = "7a12625a-238d-50fd-b39a-03d52299707e"
3-
version = "3.9.0"
3+
version = "3.10.0-DEV"
44

55
[deps]
66
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"

docs/src/history.md

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Version history
22

3+
## What's new in v3.10
4+
5+
* A new `MulStyle` trait called `TwoArg` has been added. It should be used for `LinearMap`s
6+
that do not admit a mutating multiplication à la (3-arg or 5-arg) `mul!`, but only
7+
out-of-place multiplication à la `A * x`. Products (aka `CompositeMap`s) and sums (aka
8+
`LinearCombination`s) of `TwoArg`-`LinearMap`s now have memory-optimized multiplication
9+
kernels. For instance, `A*B*C*x` for three `TwoArg`-`LinearMap`s `A`, `B` and `C` now
10+
allocates only `y = C*x`, `z = B*y` and the result of `A*z`.
11+
* The construction of function-based `LinearMap`s, typed `FunctionMap`, has been rearranged.
12+
Additionally to the convenience constructor `LinearMap{T=Float64}(f, [fc,] M, N=M; kwargs...)`,
13+
the newly exported constructor `FunctionMap{T,iip}(f, [fc], M, N; kwargs...)` is readily
14+
available. Here, `iip` is either `true` or `false`, and encodes whether `f` (and `fc` if
15+
present) are mutating functions. In the convenience constructor, this is determined via the
16+
`Bool` keyword argument `ismutating` and may not be fully inferred.
17+
318
## What's new in v3.9
419

520
* The application of `LinearMap`s to vectors operation, i.e., `(A,x) -> A*x = A(x)`, is now

docs/src/index.md

+2-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
pkg> add LinearMaps
1111
```
1212

13-
in package mode, to be entered by typing `]` in the Julia REPL.
13+
in package mode, which can be entered by typing `]` in the Julia REPL.
1414

1515
## Examples
1616

@@ -100,16 +100,10 @@ KrylovKit.eigsolve(-A, size(A, 1), 3, :SR)
100100

101101
Arpack.eigs(Δ; nev=3, which=:LR)
102102
ArnoldiMethod.partialeigen(ArnoldiMethod.partialschur(Δ; nev=3, which=ArnoldiMethod.LR())[1])
103-
KrylovKit.eigsolve(x -> Δ*x, size(Δ, 1), 3, :LR)
104-
```
105-
106-
In Julia v1.3 and above, the last line can be simplified to
107-
108-
```julia
109103
KrylovKit.eigsolve(Δ, size(Δ, 1), 3, :LR)
110104
```
111105

112-
leveraging the fact that objects of type `L <: LinearMap` are callable.
106+
In the last line above we leverage the fact that objects of type `L <: LinearMap` are callable.
113107

114108
### Inverse map with conjugate gradient
115109

@@ -156,7 +150,6 @@ result = C * tmp2
156150
i.e. inside the CG solver for solving `Sx = b` we use CG to solve another inner linear
157151
system.
158152

159-
160153
## Philosophy
161154

162155
Several iterative linear algebra methods such as linear solvers or eigensolvers

docs/src/types.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ constructor described next.
1111
Abstract supertype
1212

1313
```@docs
14-
LinearMaps.LinearMap
14+
LinearMap
1515
```
1616

1717
### `FunctionMap`
1818

1919
Type for wrapping an arbitrary function that is supposed to implement the
2020
matrix-vector product as a `LinearMap`; see above.
2121

22+
```@docs
23+
FunctionMap
24+
```
25+
2226
### `WrappedMap`
2327

2428
Type for wrapping an `AbstractMatrix` or `LinearMap` and to possible redefine
@@ -99,20 +103,19 @@ SparseArrays.blockdiag
99103
Type for lazily representing constantly filled matrices.
100104

101105
```@docs
102-
LinearMaps.FillMap
106+
FillMap
103107
```
104108

105109
### `EmbeddedMap`
106110

107111
Type for representing linear maps that are embedded in larger zero maps.
108112

109-
110113
### `InverseMap`
111114

112115
Type for lazy inverse of another linear map.
113116

114117
```@docs
115-
LinearMaps.InverseMap
118+
InverseMap
116119
```
117120

118121
### `KhatriRaoMap` and `FaceSplittingMap`

src/LinearMaps.jl

+15-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module LinearMaps
22

3-
export LinearMap, FillMap, InverseMap
3+
export LinearMap, FunctionMap, FillMap, InverseMap
44
export , squarekron, kronsum, , sumkronsum, khatrirao, facesplitting
55

66
using LinearAlgebra
@@ -40,13 +40,19 @@ convert_to_lmaps(A) = (convert(LinearMap, A),)
4040

4141
abstract type MulStyle end
4242

43-
struct FiveArg <: MulStyle end
44-
struct ThreeArg <: MulStyle end
43+
struct FiveArg <: MulStyle end # types admit in-place multiplication and addition
44+
struct ThreeArg <: MulStyle end # types "only" admit in-place multiplication
45+
struct TwoArg <: MulStyle end # types "only" admit out-of-place multiplication
4546

4647
MulStyle(::FiveArg, ::FiveArg) = FiveArg()
47-
MulStyle(::ThreeArg, ::FiveArg) = ThreeArg()
4848
MulStyle(::FiveArg, ::ThreeArg) = ThreeArg()
49+
MulStyle(::FiveArg, ::TwoArg) = TwoArg()
50+
MulStyle(::ThreeArg, ::FiveArg) = ThreeArg()
4951
MulStyle(::ThreeArg, ::ThreeArg) = ThreeArg()
52+
MulStyle(::ThreeArg, ::TwoArg) = ThreeArg()
53+
MulStyle(::TwoArg, ::FiveArg) = TwoArg()
54+
MulStyle(::TwoArg, ::ThreeArg) = ThreeArg()
55+
MulStyle(::TwoArg, ::TwoArg) = TwoArg()
5056
MulStyle(::LinearMap) = ThreeArg() # default
5157
MulStyle(::AbstractVecOrMat) = FiveArg()
5258
MulStyle(::AbstractQ) = ThreeArg()
@@ -113,10 +119,6 @@ _combine(As::LinearMapVector, Bs::LinearMapVector) = Base.vect(As..., Bs...)
113119
114120
Compute the action of the linear map `A` on the vector `x`.
115121
116-
!!! compat "Julia 1.3"
117-
In Julia versions v1.3 and above, objects `L` of any subtype of `LinearMap`
118-
are callable in the sense that `L(x) = L*x` for `x::AbstractVector`.
119-
120122
## Examples
121123
```jldoctest; setup=(using LinearAlgebra, LinearMaps)
122124
julia> A=LinearMap([1.0 2.0; 3.0 4.0]); x=[1.0, 1.0];
@@ -136,7 +138,7 @@ function Base.:(*)(A::LinearMap, x::AbstractVector)
136138
check_dim_mul(A, x)
137139
T = promote_type(eltype(A), eltype(x))
138140
y = similar(x, T, axes(A)[1])
139-
return mul!(y, A, x)
141+
return @inbounds mul!(y, A, x)
140142
end
141143

142144
(L::LinearMap)(x::AbstractVector) = L*x
@@ -166,8 +168,8 @@ julia> Y
166168
7.0 7.0
167169
```
168170
"""
169-
function mul!(y::AbstractVecOrMat, A::LinearMap, x::AbstractVector)
170-
check_dim_mul(y, A, x)
171+
@inline function mul!(y::AbstractVecOrMat, A::LinearMap, x::AbstractVector)
172+
@boundscheck check_dim_mul(y, A, x)
171173
return _unsafe_mul!(y, A, x)
172174
end
173175
# the following is of interest in, e.g., subspace-iteration methods
@@ -194,7 +196,7 @@ julia> mul!(Y, A, b)
194196
```
195197
"""
196198
function mul!(y::AbstractVecOrMat, A::LinearMap, s::Number)
197-
size(y) == size(A) ||
199+
size(y) == size(A) ||
198200
throw(
199201
DimensionMismatch("y has size $(size(y)), A has size $(size(A))."))
200202
return _unsafe_mul!(y, A, s)
@@ -257,7 +259,7 @@ julia> mul!(Y, A, b, 2, 1)
257259
```
258260
"""
259261
function mul!(y::AbstractMatrix, A::LinearMap, s::Number, α::Number, β::Number)
260-
size(y) == size(A) ||
262+
size(y) == size(A) ||
261263
throw(
262264
DimensionMismatch("y has size $(size(y)), A has size $(size(A))."))
263265
return _unsafe_mul!(y, A, s, α, β)

src/blockmap.jl

+1
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ end
322322
# provide one global intermediate storage vector if necessary
323323
__blockmul!(::FiveArg, y, A, x::AbstractVecOrMat, α, β) = ___blockmul!(y, A, x, α, β, nothing)
324324
__blockmul!(::ThreeArg, y, A, x::AbstractVecOrMat, α, β) = ___blockmul!(y, A, x, α, β, similar(y))
325+
__blockmul!(::TwoArg, y, A, x::AbstractVecOrMat, α, β) = ___blockmul!(y, A, x, α, β, nothing)
325326
function ___blockmul!(y, A, x, α, β, ::Nothing)
326327
maps, rows, yinds, xinds = A.maps, A.rows, A.rowranges, A.colranges
327328
mapind = 0

src/composition.jl

+55-49
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,18 @@ Base.:(==)(A::CompositeMap, B::CompositeMap) =
165165
(eltype(A) == eltype(B) && all(A.maps .== B.maps))
166166

167167
# multiplication with vectors/matrices
168-
_unsafe_mul!(y, A::CompositeMap, x::AbstractVector) = _compositemul!(y, A, x)
168+
function Base.:(*)(A::CompositeMap, x::AbstractVector)
169+
MulStyle(A) === TwoArg() ?
170+
foldr(*, reverse(A.maps), init=x) :
171+
invoke(*, Tuple{LinearMap, AbstractVector}, A, x)
172+
end
173+
174+
function _unsafe_mul!(y, A::CompositeMap, x::AbstractVector)
175+
MulStyle(A) === TwoArg() ?
176+
copyto!(y, foldr(*, reverse(A.maps), init=x)) :
177+
_compositemul!(y, A, x)
178+
return y
179+
end
169180
_unsafe_mul!(y, A::CompositeMap, x::AbstractMatrix) = _compositemul!(y, A, x)
170181

171182
function _compositemul!(y, A::CompositeMap{<:Any,<:Tuple{LinearMap}}, x,
@@ -174,10 +185,50 @@ function _compositemul!(y, A::CompositeMap{<:Any,<:Tuple{LinearMap}}, x,
174185
return _unsafe_mul!(y, A.maps[1], x)
175186
end
176187
function _compositemul!(y, A::CompositeMap{<:Any,<:Tuple{LinearMap,LinearMap}}, x,
177-
source = similar(y, (size(A.maps[1],1), size(x)[2:end]...)),
188+
source = nothing,
189+
dest = nothing)
190+
if isnothing(source)
191+
z = convert(AbstractArray, A.maps[1] * x)
192+
_unsafe_mul!(y, A.maps[2], z)
193+
return y
194+
else
195+
_unsafe_mul!(source, A.maps[1], x)
196+
_unsafe_mul!(y, A.maps[2], source)
197+
return y
198+
end
199+
end
200+
_compositemul!(y, A::CompositeMap{<:Any,<:LinearMapTuple}, x, s = nothing, d = nothing) =
201+
_compositemulN!(y, A, x, s, d)
202+
function _compositemul!(y, A::CompositeMap{<:Any,<:LinearMapVector}, x,
203+
source = nothing,
178204
dest = nothing)
179-
_unsafe_mul!(source, A.maps[1], x)
180-
_unsafe_mul!(y, A.maps[2], source)
205+
N = length(A.maps)
206+
if N == 1
207+
return _unsafe_mul!(y, A.maps[1], x)
208+
elseif N == 2
209+
return _unsafe_mul!(y, A.maps[2] * A.maps[1], x)
210+
else
211+
return _compositemulN!(y, A, x, source, dest)
212+
end
213+
end
214+
215+
function _compositemulN!(y, A::CompositeMap, x,
216+
src = nothing,
217+
dst = nothing)
218+
N = length(A.maps) # ≥ 3
219+
source = isnothing(src) ?
220+
convert(AbstractArray, A.maps[1] * x) :
221+
_unsafe_mul!(src, A.maps[1], x)
222+
dest = isnothing(dst) ?
223+
convert(AbstractArray, A.maps[2] * source) :
224+
_unsafe_mul!(dst, A.maps[2], source)
225+
dest, source = source, dest # alternate dest and source
226+
for n in 3:N-1
227+
dest = _resize(dest, (size(A.maps[n], 1), size(x)[2:end]...))
228+
_unsafe_mul!(dest, A.maps[n], source)
229+
dest, source = source, dest # alternate dest and source
230+
end
231+
_unsafe_mul!(y, A.maps[N], source)
181232
return y
182233
end
183234

@@ -197,48 +248,3 @@ function _resize(dest::AbstractMatrix, sz::Tuple{<:Integer,<:Integer})
197248
size(dest) == sz && return dest
198249
similar(dest, sz)
199250
end
200-
201-
function _compositemul!(y, A::CompositeMap{<:Any,<:LinearMapTuple}, x,
202-
source = similar(y, (size(A.maps[1],1), size(x)[2:end]...)),
203-
dest = similar(y, (size(A.maps[2],1), size(x)[2:end]...)))
204-
N = length(A.maps)
205-
_unsafe_mul!(source, A.maps[1], x)
206-
for n in 2:N-1
207-
dest = _resize(dest, (size(A.maps[n],1), size(x)[2:end]...))
208-
_unsafe_mul!(dest, A.maps[n], source)
209-
dest, source = source, dest # alternate dest and source
210-
end
211-
_unsafe_mul!(y, A.maps[N], source)
212-
return y
213-
end
214-
215-
function _compositemul!(y, A::CompositeMap{<:Any,<:LinearMapVector}, x)
216-
N = length(A.maps)
217-
if N == 1
218-
return _unsafe_mul!(y, A.maps[1], x)
219-
elseif N == 2
220-
return _compositemul2!(y, A, x)
221-
else
222-
return _compositemulN!(y, A, x)
223-
end
224-
end
225-
226-
function _compositemul2!(y, A::CompositeMap{<:Any,<:LinearMapVector}, x,
227-
source = similar(y, (size(A.maps[1],1), size(x)[2:end]...)))
228-
_unsafe_mul!(source, A.maps[1], x)
229-
_unsafe_mul!(y, A.maps[2], source)
230-
return y
231-
end
232-
function _compositemulN!(y, A::CompositeMap{<:Any,<:LinearMapVector}, x,
233-
source = similar(y, (size(A.maps[1],1), size(x)[2:end]...)),
234-
dest = similar(y, (size(A.maps[2],1), size(x)[2:end]...)))
235-
N = length(A.maps)
236-
_unsafe_mul!(source, A.maps[1], x)
237-
for n in 2:N-1
238-
dest = _resize(dest, (size(A.maps[n],1), size(x)[2:end]...))
239-
_unsafe_mul!(dest, A.maps[n], source)
240-
dest, source = source, dest # alternate dest and source
241-
end
242-
_unsafe_mul!(y, A.maps[N], source)
243-
return y
244-
end

src/fillmap.jl

-5
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ Base.:(==)(A::FillMap, B::FillMap) = A.λ == B.λ && A.size == B.size
2727
LinearAlgebra.adjoint(A::FillMap) = FillMap(adjoint(A.λ), reverse(A.size))
2828
LinearAlgebra.transpose(A::FillMap) = FillMap(transpose(A.λ), reverse(A.size))
2929

30-
function Base.:(*)(A::FillMap, x::AbstractVector)
31-
T = typeof(oneunit(eltype(A)) * (zero(eltype(x)) + zero(eltype(x))))
32-
return fill(iszero(A.λ) ? zero(T) : A.λ*sum(x), A.size[1])
33-
end
34-
3530
function _unsafe_mul!(y, A::FillMap, x::AbstractVector)
3631
return fill!(y, iszero(A.λ) ? zero(eltype(y)) : A.λ*sum(x))
3732
end

0 commit comments

Comments
 (0)