Skip to content

Commit 97979a2

Browse files
authored
add support for noncommutative number types (#37)
* add support for noncommutative number types * delete identitymap.jl * add comparison of CompositeMap objects * add (noncommutative) quaternion tests * improve logic of issymmetric & friends * add 138 inference tests
1 parent 73bec0d commit 97979a2

File tree

7 files changed

+273
-263
lines changed

7 files changed

+273
-263
lines changed

src/LinearMaps.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ include("transpose.jl") # transposing linear maps
105105
include("linearcombination.jl") # defining linear combinations of linear maps
106106
include("composition.jl") # composition of linear maps
107107
include("wrappedmap.jl") # wrap a matrix of linear map in a new type, thereby allowing to alter its properties
108-
include("identitymap.jl") # the identity map, to be able to make linear combinations of LinearMap objects and I
108+
include("uniformscalingmap.jl") # the uniform scaling map, to be able to make linear combinations of LinearMap objects and multiples of I
109109
include("functionmap.jl") # using a function as linear map
110110

111111
"""

src/composition.jl

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,36 +18,56 @@ Base.size(A::CompositeMap) = (size(A.maps[end], 1), size(A.maps[1], 2))
1818
Base.isreal(A::CompositeMap) = all(isreal, A.maps) # sufficient but not necessary
1919

2020
# the following rules are sufficient but not necessary
21-
function LinearAlgebra.issymmetric(A::CompositeMap)
22-
N = length(A.maps)
23-
if isodd(N)
24-
issymmetric(A.maps[div(N+1, 2)]) || return false
25-
end
26-
for n = 1:div(N, 2)
27-
A.maps[n] == transpose(A.maps[N-n+1]) || return false
21+
const RealOrComplex = Union{Real,Complex}
22+
for (f, _f, g) in ((:issymmetric, :_issymmetric, :transpose),
23+
(:ishermitian, :_ishermitian, :adjoint),
24+
(:isposdef, :_isposdef, :adjoint))
25+
@eval begin
26+
LinearAlgebra.$f(A::CompositeMap) = $_f(A.maps)
27+
$_f(maps::Tuple{}) = true
28+
$_f(maps::Tuple{<:LinearMap}) = $f(maps[1])
29+
function $_f(maps::Tuple{Vararg{<:LinearMap}}) # length(maps) >= 2
30+
if maps[1] isa UniformScalingMap{<:RealOrComplex}
31+
return $f(maps[1]) && $_f(Base.tail(maps))
32+
elseif maps[end] isa UniformScalingMap{<:RealOrComplex}
33+
return $f(maps[end]) && $_f(Base.front(maps))
34+
else
35+
return maps[end] == $g(maps[1]) && $_f(Base.front(Base.tail(maps)))
36+
end
37+
end
2838
end
29-
return true
3039
end
31-
function LinearAlgebra.ishermitian(A::CompositeMap)
32-
N = length(A.maps)
33-
if isodd(N)
34-
ishermitian(A.maps[div(N+1, 2)]) || return false
35-
end
36-
for n = 1:div(N, 2)
37-
A.maps[n] == adjoint(A.maps[N-n+1]) || return false
38-
end
39-
return true
40+
41+
# scalar multiplication and division
42+
function Base.:(*)(α::Number, A::LinearMap)
43+
T = promote_type(eltype(α), eltype(A))
44+
return CompositeMap{T}(tuple(A, UniformScalingMap(α, size(A, 1))))
4045
end
41-
function LinearAlgebra.isposdef(A::CompositeMap)
42-
N = length(A.maps)
43-
if isodd(N)
44-
isposdef(A.maps[div(N+1, 2)]) || return false
46+
function Base.:(*)(α::Number, A::CompositeMap)
47+
T = promote_type(eltype(α), eltype(A))
48+
Alast = last(A.maps)
49+
if Alast isa UniformScalingMap
50+
return CompositeMap{T}(tuple(Base.front(A.maps)..., UniformScalingMap* Alast.λ, size(Alast, 1))))
51+
else
52+
return CompositeMap{T}(tuple(A.maps..., UniformScalingMap(α, size(A, 1))))
4553
end
46-
for n = 1:div(N, 2)
47-
A.maps[n] == adjoint(A.maps[N-n+1]) || return false
54+
end
55+
function Base.:(*)(A::LinearMap, α::Number)
56+
T = promote_type(eltype(α), eltype(A))
57+
return CompositeMap{T}(tuple(UniformScalingMap(α, size(A, 2)), A))
58+
end
59+
function Base.:(*)(A::CompositeMap, α::Number)
60+
T = promote_type(eltype(α), eltype(A))
61+
Afirst = first(A.maps)
62+
if Afirst isa UniformScalingMap
63+
return CompositeMap{T}(tuple(UniformScalingMap(Afirst.λ * α, size(Afirst, 1)), Base.tail(A.maps)...))
64+
else
65+
return CompositeMap{T}(tuple(UniformScalingMap(α, size(A, 2)), A.maps...))
4866
end
49-
return true
5067
end
68+
Base.:(\)(α::Number, A::LinearMap) = inv(α) * A
69+
Base.:(/)(A::LinearMap, α::Number) = A * inv(α)
70+
Base.:(-)(A::LinearMap) = -1 * A
5171

5272
# composition of linear maps
5373
function Base.:(*)(A1::CompositeMap, A2::CompositeMap)
@@ -70,11 +90,16 @@ function Base.:(*)(A1::LinearMap, A2::LinearMap)
7090
T = promote_type(eltype(A1),eltype(A2))
7191
return CompositeMap{T}(tuple(A2, A1))
7292
end
93+
Base.:(*)(A1::LinearMap, A2::UniformScaling{T}) where {T} = A1 * A2[1,1]
94+
Base.:(*)(A1::UniformScaling{T}, A2::LinearMap) where {T} = A1[1,1] * A2
7395

7496
# special transposition behavior
7597
LinearAlgebra.transpose(A::CompositeMap{T}) where {T} = CompositeMap{T}(map(transpose, reverse(A.maps)))
7698
LinearAlgebra.adjoint(A::CompositeMap{T}) where {T} = CompositeMap{T}(map(adjoint, reverse(A.maps)))
7799

100+
# comparison of LinearCombination objects
101+
Base.:(==)(A::CompositeMap, B::CompositeMap) = (eltype(A) == eltype(B) && A.maps == B.maps)
102+
78103
# multiplication with vectors
79104
function A_mul_B!(y::AbstractVector, A::CompositeMap, x::AbstractVector)
80105
# no size checking, will be done by individual maps

src/identitymap.jl

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/linearcombination.jl

Lines changed: 18 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,86 @@
1-
struct LinearCombination{T, As<:Tuple{Vararg{LinearMap}}, Ts<:Tuple} <: LinearMap{T}
1+
struct LinearCombination{T, As<:Tuple{Vararg{LinearMap}}} <: LinearMap{T}
22
maps::As
3-
coeffs::Ts
4-
function LinearCombination{T, As, Ts}(maps::As, coeffs::Ts) where {T, As, Ts}
3+
function LinearCombination{T, As}(maps::As) where {T, As}
54
N = length(maps)
6-
N == length(coeffs) || error("number of coefficients doesn't match number of linear maps")
75
sz = size(maps[1])
86
for n = 1:N
97
size(maps[n]) == sz || throw(DimensionMismatch("LinearCombination"))
10-
promote_type(T, eltype(maps[n]), typeof(coeffs[n])) == T || throw(InexactError())
8+
promote_type(T, eltype(maps[n])) == T || throw(InexactError())
119
end
12-
new{T, As, Ts}(maps, coeffs)
10+
new{T, As}(maps)
1311
end
1412
end
1513

16-
LinearCombination{T}(maps::As, coeffs::Ts) where {T, As, Ts} = LinearCombination{T, As, Ts}(maps, coeffs)
14+
LinearCombination{T}(maps::As) where {T, As} = LinearCombination{T, As}(maps)
1715

1816
# basic methods
1917
Base.size(A::LinearCombination) = size(A.maps[1])
2018
LinearAlgebra.issymmetric(A::LinearCombination) = all(issymmetric, A.maps) # sufficient but not necessary
21-
LinearAlgebra.ishermitian(A::LinearCombination) = all(ishermitian, A.maps) && all(isreal, A.coeffs) # sufficient but not necessary
22-
LinearAlgebra.isposdef(A::LinearCombination) = all(isposdef, A.maps) && all(isposdef, A.coeffs) # sufficient but not necessary
19+
LinearAlgebra.ishermitian(A::LinearCombination) = all(ishermitian, A.maps) # sufficient but not necessary
20+
LinearAlgebra.isposdef(A::LinearCombination) = all(isposdef, A.maps) # sufficient but not necessary
2321

2422
# adding linear maps
2523
function Base.:(+)(A1::LinearCombination, A2::LinearCombination)
2624
size(A1) == size(A2) || throw(DimensionMismatch("+"))
2725
T = promote_type(eltype(A1), eltype(A2))
28-
return LinearCombination{T}(tuple(A1.maps..., A2.maps...), tuple(A1.coeffs..., A2.coeffs...))
26+
return LinearCombination{T}(tuple(A1.maps..., A2.maps...))
2927
end
3028
function Base.:(+)(A1::LinearMap, A2::LinearCombination)
3129
size(A1) == size(A2) || throw(DimensionMismatch("+"))
3230
T = promote_type(eltype(A1), eltype(A2))
33-
return LinearCombination{T}(tuple(A1, A2.maps...), tuple(one(T), A2.coeffs...))
31+
return LinearCombination{T}(tuple(A1, A2.maps...))
3432
end
3533
Base.:(+)(A1::LinearCombination, A2::LinearMap) = +(A2, A1)
3634
function Base.:(+)(A1::LinearMap, A2::LinearMap)
3735
size(A1)==size(A2) || throw(DimensionMismatch("+"))
3836
T = promote_type(eltype(A1), eltype(A2))
39-
return LinearCombination{T}(tuple(A1, A2), tuple(one(T), one(T)))
37+
return LinearCombination{T}(tuple(A1, A2))
4038
end
41-
function Base.:(-)(A1::LinearCombination, A2::LinearCombination)
42-
size(A1) == size(A2) || throw(DimensionMismatch("-"))
43-
T = promote_type(eltype(A1), eltype(A2))
44-
return LinearCombination{T}(tuple(A1.maps..., A2.maps...), tuple(A1.coeffs..., map(-, A2.coeffs)...))
45-
end
46-
function Base.:(-)(A1::LinearMap, A2::LinearCombination)
47-
size(A1) == size(A2) || throw(DimensionMismatch("-"))
48-
T = promote_type(eltype(A1), eltype(A2))
49-
return LinearCombination{T}(tuple(A1, A2.maps...), tuple(one(T), map(-, A2.coeffs)...))
50-
end
51-
function Base.:(-)(A1::LinearCombination, A2::LinearMap)
52-
size(A1) == size(A2) || throw(DimensionMismatch("-"))
53-
T = promote_type(eltype(A1), eltype(A2))
54-
return LinearCombination{T}(tuple(A1.maps..., A2), tuple(A1.coeffs..., -one(T)))
55-
end
56-
function Base.:(-)(A1::LinearMap, A2::LinearMap)
57-
size(A1) == size(A2) || throw(DimensionMismatch("-"))
58-
T = promote_type(eltype(A1), eltype(A2))
59-
return LinearCombination{T}(tuple(A1, A2), tuple(one(T), -one(T)))
60-
end
61-
62-
# scalar multiplication
63-
Base.:(-)(A::LinearMap) = LinearCombination{eltype(A)}(tuple(A), tuple(-one(eltype(A))))
64-
Base.:(-)(A::LinearCombination) = LinearCombination{eltype(A)}(A.maps, map(-, A.coeffs))
65-
66-
function Base.:(*)(α::Number, A::LinearMap)
67-
T = promote_type(eltype(α), eltype(A))
68-
return LinearCombination{T}(tuple(A), tuple(α))
69-
end
70-
Base.:(*)(A::LinearMap, α::Number) = *(α, A)
71-
function Base.:(*)(α::Number, A::LinearCombination)
72-
T = promote_type(eltype(α), eltype(A))
73-
return LinearCombination{T}(A.maps, map(x->α*x, A.coeffs))
74-
end
75-
Base.:(*)(A::LinearCombination, α::Number) = *(α, A)
76-
77-
function Base.:(\)(α::Number, A::LinearMap)
78-
T = promote_type(eltype(α), eltype(A))
79-
return LinearCombination{T}(tuple(A), tuple(1/α))
80-
end
81-
Base.:(/)(A::LinearMap, α::Number) = \(α, A)
82-
function Base.:(\)(α::Number, A::LinearCombination)
83-
T = promote_type(eltype(α), eltype(A))
84-
return LinearCombination{T}(A.maps, map(x->α\x, A.coeffs))
85-
end
86-
Base.:(/)(A::LinearCombination, α::Number) = \(α, A)
39+
Base.:(-)(A1::LinearMap, A2::LinearMap) = +(A1, -A2)
8740

88-
# comparison of LinearCombination objects
89-
Base.:(==)(A::LinearCombination, B::LinearCombination) = (eltype(A)==eltype(B) && A.maps==B.maps && A.coeffs==B.coeffs)
41+
# comparison of LinearCombination objects, sufficient but not necessary
42+
Base.:(==)(A::LinearCombination, B::LinearCombination) = (eltype(A) == eltype(B) && A.maps == B.maps)
9043

9144
# special transposition behavior
92-
LinearAlgebra.transpose(A::LinearCombination) = LinearCombination{eltype(A)}(map(transpose, A.maps), A.coeffs)
93-
LinearAlgebra.adjoint(A::LinearCombination) = LinearCombination{eltype(A)}(map(adjoint, A.maps), map(conj, A.coeffs))
45+
LinearAlgebra.transpose(A::LinearCombination) = LinearCombination{eltype(A)}(map(transpose, A.maps))
46+
LinearAlgebra.adjoint(A::LinearCombination) = LinearCombination{eltype(A)}(map(adjoint, A.maps))
9447

9548
# multiplication with vectors
9649
function A_mul_B!(y::AbstractVector, A::LinearCombination, x::AbstractVector)
9750
# no size checking, will be done by individual maps
9851
A_mul_B!(y, A.maps[1], x)
99-
A.coeffs[1] == 1 || lmul!(A.coeffs[1], y)
10052
l = length(A.maps)
10153
if l>1
10254
z = similar(y)
10355
for n=2:l
10456
A_mul_B!(z, A.maps[n], x)
105-
axpy!(A.coeffs[n], z, y)
57+
y .+= z
10658
end
10759
end
10860
return y
10961
end
11062
function At_mul_B!(y::AbstractVector, A::LinearCombination, x::AbstractVector)
11163
# no size checking, will be done by individual maps
11264
At_mul_B!(y, A.maps[1], x)
113-
A.coeffs[1] == 1 || lmul!(A.coeffs[1], y)
11465
l = length(A.maps)
11566
if l>1
11667
z = similar(y)
11768
for n = 2:l
11869
At_mul_B!(z, A.maps[n], x)
119-
axpy!(A.coeffs[n], z, y)
70+
y .+= z
12071
end
12172
end
12273
return y
12374
end
12475
function Ac_mul_B!(y::AbstractVector, A::LinearCombination, x::AbstractVector)
12576
# no size checking, will be done by individual maps
12677
Ac_mul_B!(y, A.maps[1], x)
127-
A.coeffs[1] == 1 || lmul!(conj(A.coeffs[1]), y)
12878
l = length(A.maps)
12979
if l>1
13080
z = similar(y)
13181
for n=2:l
13282
Ac_mul_B!(z, A.maps[n], x)
133-
axpy!(conj(A.coeffs[n]), z, y)
83+
y .+= z
13484
end
13585
end
13686
return y

src/uniformscalingmap.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
struct UniformScalingMap{T} <: LinearMap{T} # T will be determined from maps to which this is added
2+
λ::T
3+
M::Int
4+
end
5+
UniformScalingMap::T, M::Int, N::Int) where {T} =
6+
(M == N ? UniformScalingMap(λ, M) : error("UniformScalingMap needs to be square"))
7+
UniformScalingMap::T, sz::Tuple{Int, Int}) where {T} =
8+
(sz[1] == sz[2] ? UniformScalingMap(λ, sz[1]) : error("UniformScalingMap needs to be square"))
9+
10+
# properties
11+
Base.size(A::UniformScalingMap) = (A.M, A.M)
12+
Base.isreal(A::UniformScalingMap) = isreal(A.λ)
13+
LinearAlgebra.issymmetric(::UniformScalingMap) = true
14+
LinearAlgebra.ishermitian(A::UniformScalingMap) = isreal(A)
15+
LinearAlgebra.isposdef(A::UniformScalingMap) = isposdef(A.λ)
16+
17+
# special transposition behavior
18+
LinearAlgebra.transpose(A::UniformScalingMap) = A
19+
LinearAlgebra.adjoint(A::UniformScalingMap) = UniformScalingMap(conj(A.λ), size(A))
20+
21+
# multiplication with vector
22+
# call of LinearAlgebra.generic_mul! since order of arguments in mul! in stdlib/LinearAlgebra/src/generic.jl
23+
# TODO: either leave it as is or use mul! (and lower bound on version) once fixed in LinearAlgebra
24+
A_mul_B!(y::AbstractVector, A::UniformScalingMap, x::AbstractVector) =
25+
(length(x) == length(y) == A.M ? LinearAlgebra.generic_mul!(y, A.λ, x) : throw(DimensionMismatch("A_mul_B!")))
26+
Base.:(*)(A::UniformScalingMap, x::AbstractVector) = A.λ * x
27+
28+
At_mul_B!(y::AbstractVector, A::UniformScalingMap, x::AbstractVector) =
29+
(length(x) == length(y) == A.M ? LinearAlgebra.generic_mul!(y, A.λ, x) : throw(DimensionMismatch("At_mul_B!")))
30+
31+
Ac_mul_B!(y::AbstractVector, A::UniformScalingMap, x::AbstractVector) =
32+
(length(x) == length(y) == A.M ? LinearAlgebra.generic_mul!(y, conj(A.λ), x) : throw(DimensionMismatch("Ac_mul_B!")))
33+
34+
# combine LinearMap and UniformScaling objects in linear combinations
35+
Base.:(+)(A1::LinearMap, A2::UniformScaling{T}) where {T} = A1 + UniformScalingMap{T}(A2[1,1], size(A1, 1))
36+
Base.:(+)(A1::UniformScaling{T}, A2::LinearMap) where {T} = UniformScalingMap{T}(A1[1,1], size(A2, 1)) + A2
37+
Base.:(-)(A1::LinearMap, A2::UniformScaling{T}) where {T} = A1 - UniformScalingMap{T}(A2[1,1], size(A1, 1))
38+
Base.:(-)(A1::UniformScaling{T}, A2::LinearMap) where {T} = UniformScalingMap{T}(A1[1,1], size(A2, 1)) - A2

test/REQUIRE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
julia 0.7
2+
Quaternions

0 commit comments

Comments
 (0)