Skip to content

Commit 95cc89c

Browse files
authored
miscellaneous updates (#66)
* require one-based indexing in indexing muls * add inference test * make alpha and beta Bool by default in 5-arg mul! * prevent UniformScalingMaps with negative size * add some docstrings * remove unnecessary type specification * use isone/iszero
1 parent 98cc86a commit 95cc89c

10 files changed

+162
-28
lines changed

src/LinearMaps.jl

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ export LinearMap
55
using LinearAlgebra
66
using SparseArrays
77

8+
if VERSION < v"1.2-"
9+
import Base: has_offset_axes
10+
require_one_based_indexing(A...) = !has_offset_axes(A...) || throw(ArgumentError("offset arrays are not supported but got an array with index other than 1"))
11+
else
12+
import Base: require_one_based_indexing
13+
end
14+
815
abstract type LinearMap{T} end
916

1017
Base.eltype(::LinearMap{T}) where {T} = T
@@ -20,32 +27,32 @@ Base.size(A::LinearMap, n) = (n==1 || n==2 ? size(A)[n] : error("LinearMap objec
2027
Base.length(A::LinearMap) = size(A)[1] * size(A)[2]
2128

2229
Base.:(*)(A::LinearMap, x::AbstractVector) = mul!(similar(x, promote_type(eltype(A), eltype(x)), size(A, 1)), A, x)
23-
function LinearAlgebra.mul!(y::AbstractVector, A::LinearMap{T}, x::AbstractVector, α::Number=one(T), β::Number=zero(T)) where {T}
30+
function LinearAlgebra.mul!(y::AbstractVector, A::LinearMap, x::AbstractVector, α::Number=true, β::Number=false)
2431
length(y) == size(A, 1) || throw(DimensionMismatch("mul!"))
25-
if α == 1
26-
β == 0 && (A_mul_B!(y, A, x); return y)
27-
β == 1 && (y .+= A * x; return y)
32+
if isone(α)
33+
iszero(β) && (A_mul_B!(y, A, x); return y)
34+
isone(β) && (y .+= A * x; return y)
2835
# β != 0, 1
2936
rmul!(y, β)
3037
y .+= A * x
3138
return y
32-
elseif α == 0
33-
β == 0 && (fill!(y, zero(eltype(y))); return y)
34-
β == 1 && return y
39+
elseif iszero(α)
40+
iszero(β) && (fill!(y, zero(eltype(y))); return y)
41+
isone(β) && return y
3542
# β != 0, 1
3643
rmul!(y, β)
3744
return y
3845
else # α != 0, 1
39-
β == 0 && (A_mul_B!(y, A, x); rmul!(y, α); return y)
40-
β == 1 && (y .+= rmul!(A * x, α); return y)
46+
iszero(β) && (A_mul_B!(y, A, x); rmul!(y, α); return y)
47+
isone(β) && (y .+= rmul!(A * x, α); return y)
4148
# β != 0, 1
4249
rmul!(y, β)
4350
y .+= rmul!(A * x, α)
4451
return y
4552
end
4653
end
4754
# the following is of interest in, e.g., subspace-iteration methods
48-
function LinearAlgebra.mul!(Y::AbstractMatrix, A::LinearMap{T}, X::AbstractMatrix, α::Number=one(T), β::Number=zero(T)) where {T}
55+
function LinearAlgebra.mul!(Y::AbstractMatrix, A::LinearMap, X::AbstractMatrix, α::Number=true, β::Number=false)
4956
(size(Y, 1) == size(A, 1) && size(X, 1) == size(A, 2) && size(Y, 2) == size(X, 2)) || throw(DimensionMismatch("mul!"))
5057
@inbounds @views for i = 1:size(X, 2)
5158
mul!(Y[:, i], A, X[:, i], α, β)

src/blockmap.jl

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ end
5050
Base.size(A::BlockMap) = (last(A.rowranges[end]), last(A.colranges[end]))
5151

5252
############
53-
# hcat
53+
# concatenation
5454
############
5555

5656
for k in 1:8 # is 8 sufficient?
@@ -63,6 +63,32 @@ for k in 1:8 # is 8 sufficient?
6363
@eval Base.hvcat(rows::Tuple{Vararg{Int}}, $(Is...), $L, As::Union{LinearMap,UniformScaling}...) = _hvcat(rows, $(args...), As...)
6464
end
6565

66+
############
67+
# hcat
68+
############
69+
"""
70+
hcat(As::Union{LinearMap,UniformScaling}...)
71+
72+
Construct a `BlockMap <: LinearMap` object, a (lazy) representation of the
73+
horizontal concatenation of the arguments. `UniformScaling` objects are promoted
74+
to `LinearMap` automatically. To avoid fallback to the generic [`Base.hcat`](@ref),
75+
there must be a `LinearMap` object among the first 8 arguments.
76+
77+
# Examples
78+
```jldoctest; setup=(using LinearMaps)
79+
julia> CS = LinearMap{Int}(cumsum, 3)::LinearMaps.FunctionMap;
80+
81+
julia> L = [CS LinearMap(ones(Int, 3, 3))]::LinearMaps.BlockMap;
82+
83+
julia> L * ones(Int, 6)
84+
3-element Array{Int64,1}:
85+
4
86+
5
87+
6
88+
```
89+
"""
90+
Base.hcat
91+
6692
function _hcat(As::Union{LinearMap,UniformScaling}...)
6793
T = promote_type(map(eltype, As)...)
6894
nbc = length(As)
@@ -82,6 +108,31 @@ end
82108
############
83109
# vcat
84110
############
111+
"""
112+
vcat(As::Union{LinearMap,UniformScaling}...)
113+
114+
Construct a `BlockMap <: LinearMap` object, a (lazy) representation of the
115+
vertical concatenation of the arguments. `UniformScaling` objects are promoted
116+
to `LinearMap` automatically. To avoid fallback to the generic [`Base.vcat`](@ref),
117+
there must be a `LinearMap` object among the first 8 arguments.
118+
119+
# Examples
120+
```jldoctest; setup=(using LinearMaps)
121+
julia> CS = LinearMap{Int}(cumsum, 3)::LinearMaps.FunctionMap;
122+
123+
julia> L = [CS; LinearMap(ones(Int, 3, 3))]::LinearMaps.BlockMap;
124+
125+
julia> L * ones(Int, 3)
126+
6-element Array{Int64,1}:
127+
1
128+
2
129+
3
130+
3
131+
3
132+
3
133+
```
134+
"""
135+
Base.vcat
85136

86137
function _vcat(As::Union{LinearMap,UniformScaling}...)
87138
T = promote_type(map(eltype, As)...)
@@ -103,6 +154,35 @@ end
103154
############
104155
# hvcat
105156
############
157+
"""
158+
hvcat(rows::Tuple{Vararg{Int}}, As::Union{LinearMap,UniformScaling}...)
159+
160+
Construct a `BlockMap <: LinearMap` object, a (lazy) representation of the
161+
horizontal-vertical concatenation of the arguments. The first argument specifies
162+
the number of arguments to concatenate in each block row. `UniformScaling` objects
163+
are promoted to `LinearMap` automatically. To avoid fallback to the generic
164+
[`Base.hvcat`](@ref), there must be a `LinearMap` object among the first 8 arguments.
165+
166+
# Examples
167+
```jldoctest; setup=(using LinearMaps)
168+
julia> CS = LinearMap{Int}(cumsum, 3)::LinearMaps.FunctionMap;
169+
170+
julia> L = [CS CS; CS CS]::LinearMaps.BlockMap;
171+
172+
julia> L.rows
173+
(2, 2)
174+
175+
julia> L * ones(Int, 6)
176+
6-element Array{Int64,1}:
177+
2
178+
4
179+
6
180+
2
181+
4
182+
6
183+
```
184+
"""
185+
Base.hvcat
106186

107187
function _hvcat(rows::Tuple{Vararg{Int}}, As::Union{LinearMap,UniformScaling}...)
108188
nr = length(rows)
@@ -221,6 +301,7 @@ LinearAlgebra.adjoint(A::BlockMap) = AdjointMap(A)
221301
############
222302

223303
function A_mul_B!(y::AbstractVector, A::BlockMap, x::AbstractVector)
304+
require_one_based_indexing(y, x)
224305
m, n = size(A)
225306
@boundscheck (m == length(y) && n == length(x)) || throw(DimensionMismatch("A_mul_B!"))
226307
maps, rows, yinds, xinds = A.maps, A.rows, A.rowranges, A.colranges
@@ -238,6 +319,7 @@ function A_mul_B!(y::AbstractVector, A::BlockMap, x::AbstractVector)
238319
end
239320

240321
function At_mul_B!(y::AbstractVector, A::BlockMap, x::AbstractVector)
322+
require_one_based_indexing(y, x)
241323
m, n = size(A)
242324
@boundscheck (n == length(y) && m == length(x)) || throw(DimensionMismatch("At_mul_B!"))
243325
maps, rows, xinds, yinds = A.maps, A.rows, A.rowranges, A.colranges
@@ -262,6 +344,7 @@ function At_mul_B!(y::AbstractVector, A::BlockMap, x::AbstractVector)
262344
end
263345

264346
function Ac_mul_B!(y::AbstractVector, A::BlockMap, x::AbstractVector)
347+
require_one_based_indexing(y, x)
265348
m, n = size(A)
266349
@boundscheck (n == length(y) && m == length(x)) || throw(DimensionMismatch("At_mul_B!"))
267350
maps, rows, xinds, yinds = A.maps, A.rows, A.rowranges, A.colranges

src/composition.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,26 @@ Base.:(/)(A::LinearMap, α::Number) = A * inv(α)
7070
Base.:(-)(A::LinearMap) = -1 * A
7171

7272
# composition of linear maps
73-
function Base.:(*)(A₁::CompositeMap, A₂::CompositeMap)
73+
"""
74+
A::LinearMap * B::LinearMap
75+
76+
Construct a `CompositeMap <: LinearMap`, a (lazy) representation of the product
77+
of the two operators. Products of `LinearMap`/`CompositeMap` objects and
78+
`LinearMap`/`CompositeMap` objects are reduced to a single `CompositeMap`.
79+
In products of `LinearMap`s and `AbstractMatrix`/`UniformScaling` objects, the
80+
latter get promoted to `LinearMap`s automatically.
81+
82+
# Examples
83+
```jldoctest; setup=(using LinearAlgebra, LinearMaps)
84+
julia> CS = LinearMap{Int}(cumsum, 3)::LinearMaps.FunctionMap;
85+
86+
julia> LinearMap(ones(Int, 3, 3)) * CS * I * rand(3, 3);
87+
```
88+
"""
89+
function Base.:(*)(A₁::LinearMap, A₂::LinearMap)
7490
size(A₁, 2) == size(A₂, 1) || throw(DimensionMismatch("*"))
7591
T = promote_type(eltype(A₁), eltype(A₂))
76-
return CompositeMap{T}(tuple(A₂.maps..., A₁.maps...))
92+
return CompositeMap{T}(tuple(A₂, A₁))
7793
end
7894
function Base.:(*)(A₁::LinearMap, A₂::CompositeMap)
7995
size(A₁, 2) == size(A₂, 1) || throw(DimensionMismatch("*"))
@@ -85,10 +101,10 @@ function Base.:(*)(A₁::CompositeMap, A₂::LinearMap)
85101
T = promote_type(eltype(A₁), eltype(A₂))
86102
return CompositeMap{T}(tuple(A₂, A₁.maps...))
87103
end
88-
function Base.:(*)(A₁::LinearMap, A₂::LinearMap)
104+
function Base.:(*)(A₁::CompositeMap, A₂::CompositeMap)
89105
size(A₁, 2) == size(A₂, 1) || throw(DimensionMismatch("*"))
90106
T = promote_type(eltype(A₁), eltype(A₂))
91-
return CompositeMap{T}(tuple(A₂, A₁))
107+
return CompositeMap{T}(tuple(A₂.maps..., A₁.maps...))
92108
end
93109
Base.:(*)(A₁::LinearMap, A₂::UniformScaling) = A₁ * A₂.λ
94110
Base.:(*)(A₁::UniformScaling, A₂::LinearMap) = A₁.λ * A₂

src/functionmap.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,19 @@ function Base.:(*)(A::FunctionMap, x::AbstractVector)
5050
end
5151

5252
function A_mul_B!(y::AbstractVector, A::FunctionMap, x::AbstractVector)
53-
(length(x) == A.N && length(y) == A.M) || throw(DimensionMismatch())
53+
(length(x) == A.N && length(y) == A.M) || throw(DimensionMismatch("A_mul_B!"))
5454
ismutating(A) ? A.f(y, x) : copyto!(y, A.f(x))
5555
return y
5656
end
5757

5858
function At_mul_B!(y::AbstractVector, A::FunctionMap, x::AbstractVector)
5959
issymmetric(A) && return A_mul_B!(y, A, x)
60-
(length(x) == A.M && length(y) == A.N) || throw(DimensionMismatch())
60+
(length(x) == A.M && length(y) == A.N) || throw(DimensionMismatch("At_mul_B!"))
6161
if A.fc !== nothing
6262
if !isreal(A)
6363
x = conj(x)
6464
end
65-
(ismutating(A) ? A.fc(y, x) : copyto!(y, A.fc(x)))
65+
ismutating(A) ? A.fc(y, x) : copyto!(y, A.fc(x))
6666
if !isreal(A)
6767
conj!(y)
6868
end
@@ -74,9 +74,10 @@ end
7474

7575
function Ac_mul_B!(y::AbstractVector, A::FunctionMap, x::AbstractVector)
7676
ishermitian(A) && return A_mul_B!(y, A, x)
77-
(length(x) == A.M && length(y) == A.N) || throw(DimensionMismatch())
77+
(length(x) == A.M && length(y) == A.N) || throw(DimensionMismatch("Ac_mul_B!"))
7878
if A.fc !== nothing
79-
return (ismutating(A) ? A.fc(y, x) : copyto!(y, A.fc(x)))
79+
ismutating(A) ? A.fc(y, x) : copyto!(y, A.fc(x))
80+
return y
8081
else
8182
error("adjoint not implemented for $A")
8283
end

src/linearcombination.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,37 @@ LinearAlgebra.ishermitian(A::LinearCombination) = all(ishermitian, A.maps) # suf
2020
LinearAlgebra.isposdef(A::LinearCombination) = all(isposdef, A.maps) # sufficient but not necessary
2121

2222
# adding linear maps
23-
function Base.:(+)(A₁::LinearCombination, A₂::LinearCombination)
23+
"""
24+
A::LinearMap + B::LinearMap
25+
26+
Construct a `LinearCombination <: LinearMap`, a (lazy) representation of the sum
27+
of the two operators. Sums of `LinearMap`/`LinearCombination` objects and
28+
`LinearMap`/`LinearCombination` objects are reduced to a single `LinearCombination`.
29+
In sums of `LinearMap`s and `AbstractMatrix`/`UniformScaling` objects, the latter
30+
get promoted to `LinearMap`s automatically.
31+
32+
# Examples
33+
```jldoctest; setup=(using LinearAlgebra, LinearMaps)
34+
julia> CS = LinearMap{Int}(cumsum, 3)::LinearMaps.FunctionMap;
35+
36+
julia> LinearMap(ones(Int, 3, 3)) + CS + I + rand(3, 3);
37+
```
38+
"""
39+
function Base.:(+)(A₁::LinearMap, A₂::LinearMap)
2440
size(A₁) == size(A₂) || throw(DimensionMismatch("+"))
2541
T = promote_type(eltype(A₁), eltype(A₂))
26-
return LinearCombination{T}(tuple(A₁.maps..., A₂.maps...))
42+
return LinearCombination{T}(tuple(A₁, A₂))
2743
end
2844
function Base.:(+)(A₁::LinearMap, A₂::LinearCombination)
2945
size(A₁) == size(A₂) || throw(DimensionMismatch("+"))
3046
T = promote_type(eltype(A₁), eltype(A₂))
3147
return LinearCombination{T}(tuple(A₁, A₂.maps...))
3248
end
3349
Base.:(+)(A₁::LinearCombination, A₂::LinearMap) = +(A₂, A₁)
34-
function Base.:(+)(A₁::LinearMap, A₂::LinearMap)
50+
function Base.:(+)(A₁::LinearCombination, A₂::LinearCombination)
3551
size(A₁) == size(A₂) || throw(DimensionMismatch("+"))
3652
T = promote_type(eltype(A₁), eltype(A₂))
37-
return LinearCombination{T}(tuple(A₁, A₂))
53+
return LinearCombination{T}(tuple(A₁.maps..., A₂.maps...))
3854
end
3955
Base.:(-)(A₁::LinearMap, A₂::LinearMap) = +(A₁, -A₂)
4056

src/transpose.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ LinearAlgebra.adjoint(A::LinearMap{<:Real}) = transpose(A)
1414
LinearAlgebra.adjoint(A::LinearMap) = AdjointMap(A)
1515

1616
# properties
17-
Base.size(A::Union{TransposeMap, AdjointMap}) = (size(A.lmap, 2), size(A.lmap, 1))
17+
Base.size(A::Union{TransposeMap, AdjointMap}) = reverse(size(A.lmap))
1818
LinearAlgebra.issymmetric(A::Union{TransposeMap, AdjointMap}) = issymmetric(A.lmap)
1919
LinearAlgebra.ishermitian(A::Union{TransposeMap, AdjointMap}) = ishermitian(A.lmap)
2020
LinearAlgebra.isposdef(A::Union{TransposeMap, AdjointMap}) = isposdef(A.lmap)

src/uniformscalingmap.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
struct UniformScalingMap{T} <: LinearMap{T}
22
λ::T
33
M::Int
4+
function UniformScalingMap::Number, M::Int)
5+
M < 0 && throw(ArgumentError("size of UniformScalingMap must be non-negative, got $M"))
6+
return new{typeof(λ)}(λ, M)
7+
end
48
end
5-
UniformScalingMap::T, M::Int, N::Int) where {T} =
9+
UniformScalingMap::Number, M::Int, N::Int) =
610
(M == N ? UniformScalingMap(λ, M) : error("UniformScalingMap needs to be square"))
7-
UniformScalingMap::T, sz::Tuple{Int, Int}) where {T} =
11+
UniformScalingMap::Number, sz::Tuple{Int, Int}) =
812
(sz[1] == sz[2] ? UniformScalingMap(λ, sz[1]) : error("UniformScalingMap needs to be square"))
913

1014
# properties
@@ -49,7 +53,7 @@ function A_mul_B!(y::AbstractVector, A::UniformScalingMap, x::AbstractVector)
4953
end
5054
end # VERSION
5155

52-
function LinearAlgebra.mul!(y::AbstractVector, J::UniformScalingMap{T}, x::AbstractVector, α::Number=one(T), β::Number=zero(T)) where {T}
56+
function LinearAlgebra.mul!(y::AbstractVector, J::UniformScalingMap, x::AbstractVector, α::Number=true, β::Number=false)
5357
@boundscheck (length(x) == length(y) == J.M || throw(DimensionMismatch("mul!")))
5458
λ = J.λ
5559
@inbounds if isone(α)

test/composition.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ LinearAlgebra.mul!(y::Vector, A::Union{SimpleFunctionMap,SimpleComplexFunctionMa
1515

1616
@testset "composition" begin
1717
F = @inferred LinearMap(cumsum, y -> reverse(cumsum(reverse(x))), 10; ismutating=false)
18+
FC = @inferred LinearMap{ComplexF64}(cumsum, y -> reverse(cumsum(reverse(x))), 10; ismutating=false)
1819
A = 2 * rand(ComplexF64, (10, 10)) .- 1
1920
B = rand(size(A)...)
2021
M = @inferred 1 * LinearMap(A)
@@ -32,6 +33,10 @@ LinearAlgebra.mul!(y::Vector, A::Union{SimpleFunctionMap,SimpleComplexFunctionMa
3233
@test @inferred ishermitian(N * N')
3334
@test @inferred !issymmetric(M' * M)
3435
@test @inferred ishermitian(M' * M)
36+
@test @inferred issymmetric(F'F)
37+
@test @inferred ishermitian(F'F)
38+
@test @inferred !issymmetric(FC'FC)
39+
@test @inferred ishermitian(FC'FC)
3540
@test @inferred isposdef(transpose(F) * F * 3)
3641
@test @inferred isposdef(transpose(F) * 3 * F)
3742
@test @inferred !isposdef(-5*transpose(F) * F)

test/functionmap.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ using Test, LinearMaps, LinearAlgebra
5757
@test_throws ErrorException CS!'v
5858
@test_throws ErrorException adjoint(CS!) * v
5959
CS! = LinearMap{ComplexF64}(cumsum!, (y, x) -> (copyto!(y, x); reverse!(y); cumsum!(y, y)), 10; ismutating=true)
60+
@inferred adjoint(CS!)
6061
@test @inferred LinearMaps.ismutating(CS!)
6162
@test @inferred CS! * v == cv
6263
@test @inferred *(CS!, v) == cv

test/uniformscalingmap.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Test, LinearMaps, LinearAlgebra, BenchmarkTools
22

33
@testset "identity/scaling map" begin
4+
@test_throws ArgumentError LinearMaps.UniformScalingMap(true, -1)
45
A = 2 * rand(ComplexF64, (10, 10)) .- 1
56
B = rand(size(A)...)
67
M = @inferred 1 * LinearMap(A)

0 commit comments

Comments
 (0)