Skip to content

Commit 1f45774

Browse files
authored
Add FillMaps (#113)
1 parent 77f538d commit 1f45774

File tree

10 files changed

+202
-79
lines changed

10 files changed

+202
-79
lines changed

docs/src/index.md

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,24 @@ Let
1616

1717
A = LinearMap(rand(10, 10))
1818
B = LinearMap(cumsum, reverse∘cumsum∘reverse, 10)
19-
19+
2020
be a matrix- and function-based linear map, respectively. Then the following code just works,
2121
indistinguishably from the case when `A` and `B` are both `AbstractMatrix`-typed objects.
2222

23-
```
24-
3.0A + 2B
25-
A*B'
26-
[A B; B A]
27-
kron(A, B)
28-
```
23+
3.0A + 2B
24+
A + I
25+
A*B'
26+
[A B; B A]
27+
kron(A, B)
2928

3029
The `LinearMap` type and corresponding methods combine well with the following packages:
30+
3131
* [Arpack.jl](https://github.com/JuliaLinearAlgebra/Arpack.jl): iterative eigensolver
3232
`eigs` and SVD `svds`;
3333
* [IterativeSolvers.jl](https://github.com/JuliaMath/IterativeSolvers.jl): iterative
3434
solvers, eigensolvers, and SVD;
35-
* [KrylovKit.jl](https://github.com/Jutho/KrylovKit.jl): Krylov-based algorithms for linear problems, singular value and eigenvalue problems
35+
* [KrylovKit.jl](https://github.com/Jutho/KrylovKit.jl): Krylov-based algorithms for linear
36+
problems, singular value and eigenvalue problems
3637
* [TSVD.jl](https://github.com/andreasnoack/TSVD.jl): truncated SVD `tsvd`.
3738

3839
```julia
@@ -95,34 +96,34 @@ operator in the special case of a square matrix).
9596

9697
The LinearMaps package provides the following functionality:
9798

98-
1. A `LinearMap` type that shares with the `AbstractMatrix` type that it
99-
responds to the functions `size`, `eltype`, `isreal`, `issymmetric`,
100-
`ishermitian` and `isposdef`, `transpose` and `adjoint` and multiplication
101-
with a vector using both `*` or the in-place version `mul!`. Linear algebra
102-
functions that use duck-typing for their arguments can handle `LinearMap`
103-
objects similar to `AbstractMatrix` objects, provided that they can be
104-
written using the above methods. Unlike `AbstractMatrix` types, `LinearMap`
105-
objects cannot be indexed, neither using `getindex` or `setindex!`.
106-
107-
2. A single function `LinearMap` that acts as a general purpose
108-
constructor (though it is only an abstract type) and allows to construct
109-
linear map objects from functions, or to wrap objects of type
110-
`AbstractMatrix` or `LinearMap`. The latter functionality is useful to
111-
(re)define the properties (`isreal`, `issymmetric`, `ishermitian`,
112-
`isposdef`) of the existing matrix or linear map.
113-
114-
3. A framework for combining objects of type `LinearMap` and of type
115-
`AbstractMatrix` using linear combinations, transposition, composition,
116-
concatenation and Kronecker product/sums,
117-
where the linear map resulting from these operations is never explicitly
118-
evaluated but only its matrix-vector product is defined (i.e. lazy
119-
evaluation). The matrix-vector product is written to minimize memory
120-
allocation by using a minimal number of temporary vectors. There is full
121-
support for the in-place version `mul!`, which should be preferred for
122-
higher efficiency in critical algorithms. In addition, it tries to recognize
123-
the properties of combinations of linear maps. In particular, compositions
124-
such as `A'*A` for arbitrary `A` or even `A'*B*C*B'*A` with arbitrary `A`
125-
and `B` and positive definite `C` are recognized as being positive definite
126-
and hermitian. In case a certain property of the resulting `LinearMap`
127-
object is not correctly inferred, the `LinearMap` method can be called to
128-
redefine the properties.
99+
1. A `LinearMap` type that shares with the `AbstractMatrix` type that it
100+
responds to the functions `size`, `eltype`, `isreal`, `issymmetric`,
101+
`ishermitian` and `isposdef`, `transpose` and `adjoint` and multiplication
102+
with a vector using both `*` or the in-place version `mul!`. Linear algebra
103+
functions that use duck-typing for their arguments can handle `LinearMap`
104+
objects similar to `AbstractMatrix` objects, provided that they can be
105+
written using the above methods. Unlike `AbstractMatrix` types, `LinearMap`
106+
objects cannot be indexed, neither using `getindex` or `setindex!`.
107+
108+
2. A single function `LinearMap` that acts as a general purpose
109+
constructor (though it is only an abstract type) and allows to construct
110+
linear map objects from functions, or to wrap objects of type
111+
`AbstractMatrix` or `LinearMap`. The latter functionality is useful to
112+
(re)define the properties (`isreal`, `issymmetric`, `ishermitian`,
113+
`isposdef`) of the existing matrix or linear map.
114+
115+
3. A framework for combining objects of type `LinearMap` and of type
116+
`AbstractMatrix` using linear combinations, transposition, composition,
117+
concatenation and Kronecker product/sums,
118+
where the linear map resulting from these operations is never explicitly
119+
evaluated but only its matrix-vector product is defined (i.e. lazy
120+
evaluation). The matrix-vector product is written to minimize memory
121+
allocation by using a minimal number of temporary vectors. There is full
122+
support for the in-place version `mul!`, which should be preferred for
123+
higher efficiency in critical algorithms. In addition, it tries to recognize
124+
the properties of combinations of linear maps. In particular, compositions
125+
such as `A'*A` for arbitrary `A` or even `A'*B*C*B'*A` with arbitrary `A`
126+
and `B` and positive definite `C` are recognized as being positive definite
127+
and hermitian. In case a certain property of the resulting `LinearMap`
128+
object is not correctly inferred, the `LinearMap` method can be called to
129+
redefine the properties.

docs/src/related.md

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,25 @@
33
The following open-source packages provide similar or even extended functionality as
44
`LinearMaps.jl`.
55

6-
* [`Spot`: A linear-operator toolbox for Matlab](https://github.com/mpf/spot),
7-
which seems to have heavily inspired the Julia package
8-
[`LinearOperators.jl`](https://github.com/JuliaSmoothOptimizers/LinearOperators.jl)
9-
and the Python package [`PyLops`](https://github.com/equinor/pylops)
10-
11-
* [`fastmat`: fast linear transforms in Python](https://pypi.org/project/fastmat/)
12-
13-
* [`FunctionOperators.jl`](https://github.com/hakkelt/FunctionOperators.jl)
14-
and [`LinearMapsAA.jl`](https://github.com/JeffFessler/LinearMapsAA.jl)
15-
also support mappings between `Array`s, inspired by the `fatrix` object type in the
16-
[Matlab version of the Michigan Image Reconstruction Toolbox (MIRT)](https://github.com/JeffFessler/mirt).
6+
* [`Spot`: A linear-operator toolbox for Matlab](https://github.com/mpf/spot),
7+
which seems to have heavily inspired the Julia package
8+
[`LinearOperators.jl`](https://github.com/JuliaSmoothOptimizers/LinearOperators.jl)
9+
and the Python package [`PyLops`](https://github.com/equinor/pylops)
10+
* [`fastmat`: fast linear transforms in Python](https://pypi.org/project/fastmat/)
11+
* [`FunctionOperators.jl`](https://github.com/hakkelt/FunctionOperators.jl)
12+
and [`LinearMapsAA.jl`](https://github.com/JeffFessler/LinearMapsAA.jl)
13+
also support mappings between `Array`s, inspired by the `fatrix` object type in the
14+
[Matlab version of the Michigan Image Reconstruction Toolbox (MIRT)](https://github.com/JeffFessler/mirt).
1715

1816
As for lazy array manipulation (like addition, composition, Kronecker products and concatenation),
1917
there exist further related packages in the Julia ecosystem:
2018

21-
* [`LazyArrays.jl`](https://github.com/JuliaArrays/LazyArrays.jl)
22-
23-
* [`BlockArrays.jl`](https://github.com/JuliaArrays/BlockArrays.jl)
24-
25-
* [`BlockDiagonals.jl`](https://github.com/invenia/BlockDiagonals.jl)
26-
27-
* [`Kronecker.jl`](https://github.com/MichielStock/Kronecker.jl)
19+
* [`LazyArrays.jl`](https://github.com/JuliaArrays/LazyArrays.jl)
20+
* [`BlockArrays.jl`](https://github.com/JuliaArrays/BlockArrays.jl)
21+
* [`BlockDiagonals.jl`](https://github.com/invenia/BlockDiagonals.jl)
22+
* [`Kronecker.jl`](https://github.com/MichielStock/Kronecker.jl)
23+
* [`FillArrays.jl`](https://github.com/JuliaArrays/FillArrays.jl)
2824

29-
* [`FillArrays.jl`](https://github.com/JuliaArrays/FillArrays.jl)
3025
Since these packages provide types that are subtypes of Julia `Base`'s `AbstractMatrix` type,
3126
objects of those types can be wrapped by a `LinearMap` and freely mixed with, for instance,
3227
function-based linear maps. The same applies to custom matrix types as provided, for instance,

docs/src/types.md

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ Base.cat
8484
SparseArrays.blockdiag
8585
```
8686

87+
### `FillMap`
88+
89+
Type for lazily representing constantly filled matrices.
90+
91+
```@docs
92+
LinearMaps.FillMap
93+
```
94+
8795
## Methods
8896

8997
### Multiplication methods
@@ -103,28 +111,28 @@ as in the usual matrix case: `transpose(A) * x` and `mul!(y, A', x)`, for instan
103111

104112
### Conversion methods
105113

106-
* `Array`, `Matrix` and associated `convert` methods
114+
* `Array`, `Matrix` and associated `convert` methods
107115

108-
Create a dense matrix representation of the `LinearMap` object, by
109-
multiplying it with the successive basis vectors. This is mostly for testing
110-
purposes or if you want to have the explicit matrix representation of a
111-
linear map for which you only have a function definition (e.g. to be able to
112-
use its `transpose` or `adjoint`). This way, one may conveniently make `A`
113-
act on the columns of a matrix `X`, instead of interpreting `A * X` as a
114-
composed linear map: `Matrix(A * X)`. For generic code, that is supposed to
115-
handle both `A::AbstractMatrix` and `A::LinearMap`, it is recommended to use
116-
`convert(Matrix, A*X)`.
116+
Create a dense matrix representation of the `LinearMap` object, by
117+
multiplying it with the successive basis vectors. This is mostly for testing
118+
purposes or if you want to have the explicit matrix representation of a
119+
linear map for which you only have a function definition (e.g. to be able to
120+
use its `transpose` or `adjoint`). This way, one may conveniently make `A`
121+
act on the columns of a matrix `X`, instead of interpreting `A * X` as a
122+
composed linear map: `Matrix(A * X)`. For generic code, that is supposed to
123+
handle both `A::AbstractMatrix` and `A::LinearMap`, it is recommended to use
124+
`convert(Matrix, A*X)`.
117125

118-
* `convert(Abstract[Matrix/Array], A::LinearMap)`
126+
* `convert(Abstract[Matrix/Array], A::LinearMap)`
119127

120-
Create an `AbstractMatrix` representation of the `LinearMap`. This falls
121-
back to `Matrix(A)`, but avoids explicit construction in case the `LinearMap`
122-
object is matrix-based.
128+
Create an `AbstractMatrix` representation of the `LinearMap`. This falls
129+
back to `Matrix(A)`, but avoids explicit construction in case the `LinearMap`
130+
object is matrix-based.
123131

124-
* `SparseArrays.sparse(A::LinearMap)` and `convert(SparseMatrixCSC, A::LinearMap)`
132+
* `SparseArrays.sparse(A::LinearMap)` and `convert(SparseMatrixCSC, A::LinearMap)`
125133

126-
Create a sparse matrix representation of the `LinearMap` object, by
127-
multiplying it with the successive basis vectors. This is mostly for testing
128-
purposes or if you want to have the explicit sparse matrix representation of
129-
a linear map for which you only have a function definition (e.g. to be able
130-
to use its `transpose` or `adjoint`).
134+
Create a sparse matrix representation of the `LinearMap` object, by
135+
multiplying it with the successive basis vectors. This is mostly for testing
136+
purposes or if you want to have the explicit sparse matrix representation of
137+
a linear map for which you only have a function definition (e.g. to be able
138+
to use its `transpose` or `adjoint`).

src/LinearMaps.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module LinearMaps
22

33
export LinearMap
44
export , kronsum,
5+
export FillMap
56

67
using LinearAlgebra
78
import LinearAlgebra: mul!
@@ -239,18 +240,22 @@ include("composition.jl") # composition of linear maps
239240
include("functionmap.jl") # using a function as linear map
240241
include("blockmap.jl") # block linear maps
241242
include("kronecker.jl") # Kronecker product of linear maps
243+
include("fillmap.jl") # linear maps representing constantly filled matrices
242244
include("conversion.jl") # conversion of linear maps to matrices
243245
include("show.jl") # show methods for LinearMap objects
244246

245247
"""
246248
LinearMap(A::LinearMap; kwargs...)::WrappedMap
247249
LinearMap(A::AbstractMatrix; kwargs...)::WrappedMap
248250
LinearMap(J::UniformScaling, M::Int)::UniformScalingMap
251+
LinearMap(λ::Number, M::Int, N::Int) = FillMap(λ, (M, N))::FillMap
252+
LinearMap(λ::Number, dims::Dims{2}) = FillMap(λ, dims)::FillMap
249253
LinearMap{T=Float64}(f, [fc,], M::Int, N::Int = M; kwargs...)::FunctionMap
250254
251255
Construct a linear map object, either from an existing `LinearMap` or `AbstractMatrix` `A`,
252256
with the purpose of redefining its properties via the keyword arguments `kwargs`;
253-
a `UniformScaling` object `J` with specified (square) dimension `M`; or
257+
a `UniformScaling` object `J` with specified (square) dimension `M`; from a `Number`
258+
object to lazily represent filled matrices; or
254259
from a function or callable object `f`. In the latter case, one also needs to specify
255260
the size of the equivalent matrix representation `(M, N)`, i.e., for functions `f` acting
256261
on length `N` vectors and producing length `M` vectors (with default value `N=M`).

src/conversion.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,6 @@ function SparseArrays.sparse(L::KroneckerSumMap)
171171
IB = sparse(Diagonal(ones(Bool, size(B, 1))))
172172
return kron(convert(AbstractMatrix, A), IB) + kron(IA, convert(AbstractMatrix, B))
173173
end
174+
175+
# FillMap
176+
Base.Matrix{T}(A::FillMap) where {T} = fill(T(A.λ), size(A))

src/fillmap.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
FillMap(λ, (m, n))::FillMap
3+
FillMap(λ, m, n)::FillMap
4+
5+
Construct a (lazy) representation of an operator whose matrix representation
6+
would be an m×n-matrix filled constantly with the value `λ`.
7+
"""
8+
struct FillMap{T} <: LinearMap{T}
9+
λ::T
10+
size::Dims{2}
11+
function FillMap::T, dims::Dims{2}) where {T}
12+
(dims[1]>=0 && dims[2]>=0) || throw(ArgumentError("dims of FillMap must be non-negative"))
13+
return new{T}(λ, dims)
14+
end
15+
end
16+
17+
FillMap(λ, m::Int, n::Int) = FillMap(λ, (m, n))
18+
19+
# properties
20+
Base.size(A::FillMap) = A.size
21+
MulStyle(A::FillMap) = FiveArg()
22+
LinearAlgebra.issymmetric(A::FillMap) = A.size[1] == A.size[2]
23+
LinearAlgebra.ishermitian(A::FillMap) = isreal(A.λ) && A.size[1] == A.size[2]
24+
LinearAlgebra.isposdef(A::FillMap) = (size(A, 1) == size(A, 2) == 1 && isposdef(A.λ))
25+
Base.:(==)(A::FillMap, B::FillMap) = A.λ == B.λ && A.size == B.size
26+
27+
LinearAlgebra.adjoint(A::FillMap) = FillMap(adjoint(A.λ), reverse(A.size))
28+
LinearAlgebra.transpose(A::FillMap) = FillMap(transpose(A.λ), reverse(A.size))
29+
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+
35+
function _unsafe_mul!(y::AbstractVecOrMat, A::FillMap, x::AbstractVector)
36+
return fill!(y, iszero(A.λ) ? zero(eltype(y)) : A.λ*sum(x))
37+
end
38+
39+
function _unsafe_mul!(y::AbstractVecOrMat, A::FillMap, x::AbstractVector, α::Number, β::Number)
40+
if iszero(α)
41+
!isone(β) && rmul!(y, β)
42+
else
43+
temp = A.λ * sum(x) * α
44+
if iszero(β)
45+
y .= temp
46+
elseif isone(β)
47+
y .+= temp
48+
else
49+
y .= y .* β .+ temp
50+
end
51+
end
52+
return y
53+
end
54+
55+
Base.:(+)(A::FillMap, B::FillMap) = A.size == B.size ? FillMap(A.λ + B.λ, A.size) : throw(DimensionMismatch())
56+
Base.:(-)(A::FillMap) = FillMap(-A.λ, A.size)
57+
Base.:(*)(λ::Number, A::FillMap) = FillMap* A.λ, size(A))
58+
Base.:(*)(A::FillMap, λ::Number) = FillMap(A.λ * λ, size(A))
59+
Base.:(*)(λ::RealOrComplex, A::FillMap) = FillMap* A.λ, size(A))
60+
Base.:(*)(A::FillMap, λ::RealOrComplex) = FillMap(A.λ * λ, size(A))
61+
62+
function Base.:(*)(A::FillMap, B::FillMap)
63+
check_dim_mul(A, B)
64+
return FillMap(A.λ*B.λ*size(A, 2), (size(A, 1), size(B, 2)))
65+
end

src/show.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ end
3939
function _show(io::IO, A::ScaledMap{T}, i) where {T}
4040
" with scale: $(A.λ) of\n" * map_show(io, A.lmap, i+2)
4141
end
42+
function _show(io::IO, A::FillMap{T}, _) where {T}
43+
" with fill value: $(A.λ)"
44+
end
4245

4346
# helper functions
4447
function _show_typeof(A::LinearMap{T}) where {T}

test/fillmap.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using LinearMaps, LinearAlgebra, Test
2+
3+
@testset "filled maps" begin
4+
M, N = 2, 3
5+
μ = rand()
6+
for λ in (true, false, 3, μ, μ + 2im)
7+
L = FillMap(λ, (M, N))
8+
@test L == FillMap(λ, M, N)
9+
@test occursin("$M×$N FillMap{$(typeof(λ))} with fill value: ", sprint((t, s) -> show(t, "text/plain", s), L))
10+
@test LinearMaps.MulStyle(L) === LinearMaps.FiveArg()
11+
A = fill(λ, (M, N))
12+
x = rand(typeof(λ) <: Real ? Float64 : ComplexF64, 3)
13+
X = rand(typeof(λ) <: Real ? Float64 : ComplexF64, 3, 4)
14+
w = similar(x, 2)
15+
W = similar(X, 2, 4)
16+
@test size(L) == (M, N)
17+
@test adjoint(L) == FillMap(adjoint(λ), (3,2))
18+
@test transpose(L) == FillMap(λ, (3,2))
19+
@test Matrix(L) == A
20+
@test L * x A * x
21+
@test mul!(w, L, x) A * x
22+
@test mul!(W, L, X) A * X
23+
for α in (true, false, 1, 0, randn()), β in (true, false, 1, 0, randn())
24+
@test mul!(copy(w), L, x, α, β) fill* sum(x) * α, M) + w * β
25+
@test mul!(copy(W), L, X, α, β) λ * reduce(vcat, sum(X, dims=1) for _ in 1:2) * α + W * β
26+
end
27+
end
28+
@test issymmetric(FillMap+ 1im, (3, 3)))
29+
@test ishermitian(FillMap+ 0im, (3, 3)))
30+
@test isposdef(FillMap(μ, (1,1))) == isposdef(μ)
31+
@test !isposdef(FillMap(μ, (3,3)))
32+
α = rand()
33+
β = rand()
34+
@test FillMap(μ, (M, N)) + FillMap(α, (M, N)) == FillMap+ α, (M, N))
35+
@test FillMap(μ, (M, N)) - FillMap(α, (M, N)) == FillMap- α, (M, N))
36+
@test α*FillMap(μ, (M, N)) == FillMap* μ, (M, N))
37+
@test FillMap(μ, (M, N))*α == FillMap* α, (M, N))
38+
@test FillMap(μ, (M, N))*FillMap(μ, (N, M)) == FillMap^2*N, (M, M))
39+
@test Matrix(FillMap(μ, (M, N))*FillMap(μ, (N, M))) fill(μ, (M, N))*fill(μ, (N, M))
40+
end

test/numbertypes.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ using Test, LinearMaps, LinearAlgebra, Quaternions
4545
@test M == (L * L * γ) * β == (L * L * α) * β == (L * L * α) * β.λ
4646
@test length(M.maps) == 3
4747
@test M.maps[1].λ == γ*β.λ
48+
@test γ*FillMap(γ, (3, 4)) == FillMap^2, (3, 4)) == FillMap(γ, (3, 4))*γ
4849

4950
# exercise non-RealOrComplex scalar operations
5051
@test Array* (L'*L)) γ * (A'*A) # CompositeMap

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ include("kronecker.jl")
3030
include("conversion.jl")
3131

3232
include("left.jl")
33+
34+
include("fillmap.jl")

0 commit comments

Comments
 (0)