-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathLinearMaps.jl
More file actions
167 lines (145 loc) · 7.32 KB
/
LinearMaps.jl
File metadata and controls
167 lines (145 loc) · 7.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
module LinearMaps
export LinearMap
export ⊗, kronsum, ⊕
using LinearAlgebra
using SparseArrays
import Base.Broadcast: materialize!
if VERSION < v"1.2-"
import Base: has_offset_axes
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"))
else
import Base: require_one_based_indexing
end
abstract type LinearMap{T} end
const MapOrMatrix{T} = Union{LinearMap{T},AbstractMatrix{T}}
Base.eltype(::LinearMap{T}) where {T} = T
abstract type MulStyle end
struct FiveArg <: MulStyle end
struct ThreeArg <: MulStyle end
MulStyle(::FiveArg, ::FiveArg) = FiveArg()
MulStyle(::ThreeArg, ::FiveArg) = ThreeArg()
MulStyle(::FiveArg, ::ThreeArg) = ThreeArg()
MulStyle(::ThreeArg, ::ThreeArg) = ThreeArg()
MulStyle(::LinearMap) = ThreeArg() # default
@static if VERSION ≥ v"1.3.0-alpha.115"
MulStyle(::AbstractMatrix) = FiveArg()
else
MulStyle(::AbstractMatrix) = ThreeArg()
end
MulStyle(A::LinearMap, As::LinearMap...) = MulStyle(MulStyle(A), MulStyle(As...))
Base.isreal(A::LinearMap) = eltype(A) <: Real
LinearAlgebra.issymmetric(::LinearMap) = false # default assumptions
LinearAlgebra.ishermitian(A::LinearMap{<:Real}) = issymmetric(A)
LinearAlgebra.ishermitian(::LinearMap) = false # default assumptions
LinearAlgebra.isposdef(::LinearMap) = false # default assumptions
Base.ndims(::LinearMap) = 2
Base.size(A::LinearMap, n) = (n==1 || n==2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
Base.length(A::LinearMap) = size(A)[1] * size(A)[2]
# check dimension consistency for y = A*x and Y = A*X
function check_dim_mul(y::AbstractVector, A::LinearMap, x::AbstractVector)
# @info "checked vector dimensions" # uncomment for testing
m, n = size(A)
(m == length(y) && n == length(x)) || throw(DimensionMismatch("mul!"))
return nothing
end
function check_dim_mul(Y::AbstractMatrix, A::LinearMap, X::AbstractMatrix)
# @info "checked matrix dimensions" # uncomment for testing
m, n = size(A)
(m == size(Y, 1) && n == size(X, 1) && size(Y, 2) == size(X, 2)) || throw(DimensionMismatch("mul!"))
return nothing
end
# conversion of AbstractMatrix to LinearMap
convert_to_lmaps_(A::AbstractMatrix) = LinearMap(A)
convert_to_lmaps_(A::LinearMap) = A
convert_to_lmaps() = ()
convert_to_lmaps(A) = (convert_to_lmaps_(A),)
@inline convert_to_lmaps(A, B, Cs...) =
(convert_to_lmaps_(A), convert_to_lmaps_(B), convert_to_lmaps(Cs...)...)
function Base.:(*)(A::LinearMap, x::AbstractVector)
size(A, 2) == length(x) || throw(DimensionMismatch("mul!"))
return @inbounds mul!(similar(x, promote_type(eltype(A), eltype(x)), size(A, 1)), A, x)
end
function LinearAlgebra.mul!(y::AbstractVector, A::LinearMap, x::AbstractVector, α::Number=true, β::Number=false)
@boundscheck check_dim_mul(y, A, x)
if isone(α)
iszero(β) && (A_mul_B!(y, A, x); return y)
isone(β) && (y .+= A * x; return y)
# β != 0, 1
rmul!(y, β)
y .+= A * x
return y
elseif iszero(α)
iszero(β) && (fill!(y, zero(eltype(y))); return y)
isone(β) && return y
# β != 0, 1
rmul!(y, β)
return y
else # α != 0, 1
iszero(β) && (A_mul_B!(y, A, x); rmul!(y, α); return y)
isone(β) && (y .+= rmul!(A * x, α); return y)
# β != 0, 1
rmul!(y, β)
y .+= rmul!(A * x, α)
return y
end
end
# the following is of interest in, e.g., subspace-iteration methods
Base.@propagate_inbounds function LinearAlgebra.mul!(Y::AbstractMatrix, A::LinearMap, X::AbstractMatrix, α::Number=true, β::Number=false)
@boundscheck check_dim_mul(Y, A, X)
@inbounds @views for i = 1:size(X, 2)
mul!(Y[:, i], A, X[:, i], α, β)
end
# starting from Julia v1.1, we could use the `eachcol` iterator
# for (Xi, Yi) in zip(eachcol(X), eachcol(Y))
# mul!(Yi, A, Xi, α, β)
# end
return Y
end
A_mul_B!(y::AbstractVector, A::AbstractMatrix, x::AbstractVector) = mul!(y, A, x)
At_mul_B!(y::AbstractVector, A::AbstractMatrix, x::AbstractVector) = mul!(y, transpose(A), x)
Ac_mul_B!(y::AbstractVector, A::AbstractMatrix, x::AbstractVector) = mul!(y, adjoint(A), x)
include("wrappedmap.jl") # wrap a matrix of linear map in a new type, thereby allowing to alter its properties
include("uniformscalingmap.jl") # the uniform scaling map, to be able to make linear combinations of LinearMap objects and multiples of I
include("transpose.jl") # transposing linear maps
include("linearcombination.jl") # defining linear combinations of linear maps
include("composition.jl") # composition of linear maps
include("functionmap.jl") # using a function as linear map
include("blockmap.jl") # block linear maps
include("kronecker.jl") # Kronecker product of linear maps
include("conversion.jl") # conversion of linear maps to matrices
"""
LinearMap(A; kwargs...)
LinearMap(J, M::Int)
LinearMap{T=Float64}(f, [fc,], M::Int, N::Int = M; kwargs...)
Construct a linear map object, either from an existing `LinearMap` or `AbstractMatrix` `A`,
with the purpose of redefining its properties via the keyword arguments `kwargs`;
a `UniformScaling` object `J` with specified (square) dimension `M`; or
from a function or callable object `f`. In the latter case, one also needs to specify
the size of the equivalent matrix representation `(M, N)`, i.e. for functions `f` acting
on length `N` vectors and producing length `M` vectors (with default value `N=M`). Preferably,
also the `eltype` `T` of the corresponding matrix representation needs to be specified, i.e.
whether the action of `f` on a vector will be similar to e.g. multiplying by numbers of type `T`.
If not specified, the devault value `T=Float64` will be assumed. Optionally, a corresponding
function `fc` can be specified that implements the transpose/adjoint of `f`.
The keyword arguments and their default values for functions `f` are
* issymmetric::Bool = false : whether `A` or `f` acts as a symmetric matrix
* ishermitian::Bool = issymmetric & T<:Real : whether `A` or `f` acts as a Hermitian matrix
* isposdef::Bool = false : whether `A` or `f` acts as a positive definite matrix.
For existing linear maps or matrices `A`, the default values will be taken by calling
`issymmetric`, `ishermitian` and `isposdef` on the existing object `A`.
For functions `f`, there is one more keyword argument
* ismutating::Bool : flags whether the function acts as a mutating matrix multiplication
`f(y,x)` where the result vector `y` is the first argument (in case of `true`),
or as a normal matrix multiplication that is called as `y=f(x)` (in case of `false`).
The default value is guessed by looking at the number of arguments of the first occurence
of `f` in the method table.
"""
LinearMap(A::MapOrMatrix; kwargs...) = WrappedMap(A; kwargs...)
LinearMap(J::UniformScaling, M::Int) = UniformScalingMap(J.λ, M)
LinearMap(f, M::Int; kwargs...) = LinearMap{Float64}(f, M; kwargs...)
LinearMap(f, M::Int, N::Int; kwargs...) = LinearMap{Float64}(f, M, N; kwargs...)
LinearMap(f, fc, M::Int; kwargs...) = LinearMap{Float64}(f, fc, M; kwargs...)
LinearMap(f, fc, M::Int, N::Int; kwargs...) = LinearMap{Float64}(f, fc, M, N; kwargs...)
LinearMap{T}(A::MapOrMatrix; kwargs...) where {T} = WrappedMap{T}(A; kwargs...)
LinearMap{T}(f, args...; kwargs...) where {T} = FunctionMap{T}(f, args...; kwargs...)
end # module