Skip to content

Commit b7c24b1

Browse files
Left vector multiply (#100)
Co-authored-by: Daniel Karrasch <[email protected]>
1 parent b06ba20 commit b7c24b1

File tree

5 files changed

+134
-10
lines changed

5 files changed

+134
-10
lines changed

src/LinearMaps.jl

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,15 @@ Base.ndims(::LinearMap) = 2
4747
Base.size(A::LinearMap, n) = (n==1 || n==2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
4848
Base.length(A::LinearMap) = size(A)[1] * size(A)[2]
4949

50-
# check dimension consistency for y = A*x and Y = A*X
51-
function check_dim_mul(y::AbstractVector, A::LinearMap, x::AbstractVector)
50+
# check dimension consistency for multiplication C = A*B
51+
function check_dim_mul(C, A, B)
5252
# @info "checked vector dimensions" # uncomment for testing
53-
m, n = size(A)
54-
(m == length(y) && n == length(x)) || throw(DimensionMismatch("mul!"))
55-
return nothing
56-
end
57-
function check_dim_mul(Y::AbstractMatrix, A::LinearMap, X::AbstractMatrix)
58-
# @info "checked matrix dimensions" # uncomment for testing
59-
m, n = size(A)
60-
(m == size(Y, 1) && n == size(X, 1) && size(Y, 2) == size(X, 2)) || throw(DimensionMismatch("mul!"))
53+
mA, nA = size(A) # A always has two dimensions
54+
mB, nB = size(B, 1), size(B, 2)
55+
(mB == nA) ||
56+
throw(DimensionMismatch("left factor has dimensions ($mA,$nA), right factor has dimensions ($mB,$nB)"))
57+
(size(C, 1) != mA || size(C, 2) != nB) &&
58+
throw(DimensionMismatch("result has dimensions $(size(C)), needs ($mA,$nB)"))
6159
return nothing
6260
end
6361

@@ -118,6 +116,7 @@ A_mul_B!(y::AbstractVector, A::AbstractMatrix, x::AbstractVector) = mul!(y, A,
118116
At_mul_B!(y::AbstractVector, A::AbstractMatrix, x::AbstractVector) = mul!(y, transpose(A), x)
119117
Ac_mul_B!(y::AbstractVector, A::AbstractMatrix, x::AbstractVector) = mul!(y, adjoint(A), x)
120118

119+
include("left.jl") # left multiplication by a transpose or adjoint vector
121120
include("wrappedmap.jl") # wrap a matrix of linear map in a new type, thereby allowing to alter its properties
122121
include("uniformscalingmap.jl") # the uniform scaling map, to be able to make linear combinations of LinearMap objects and multiples of I
123122
include("transpose.jl") # transposing linear maps

src/left.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# left.jl
2+
# "special cases" related left vector multiplication like x = y'*A
3+
# The subtlety is that y' is a AdjointAbsVec or a TransposeAbsVec
4+
# which is a subtype of AbstractMatrix but it is really "vector like"
5+
# so we want handle it like a vector (Issue#99)
6+
# So this is an exception to the left multiplication rule by a AbstractMatrix
7+
# that usually makes a WrappedMap.
8+
# The "transpose" versions may be of dubious use, but the adjoint versions
9+
# are useful for ensuring that (y'A)*x ≈ y'*(A*x) are both scalars.
10+
11+
import LinearAlgebra: AdjointAbsVec, TransposeAbsVec
12+
13+
# x = y'*A ⇐⇒ x' = (A'*y)
14+
Base.:(*)(y::AdjointAbsVec, A::LinearMap) = adjoint(A' * y')
15+
Base.:(*)(y::TransposeAbsVec, A::LinearMap) = transpose(transpose(A) * transpose(y))
16+
17+
# multiplication with vector/matrix
18+
for Atype in (AdjointAbsVec, Adjoint{<:Any,<:AbstractMatrix})
19+
@eval begin
20+
Base.@propagate_inbounds function LinearAlgebra.mul!(x::AbstractMatrix, y::$Atype, A::LinearMap)
21+
@boundscheck check_dim_mul(x, y, A)
22+
@inbounds mul!(x', A', y')
23+
return x
24+
end
25+
26+
Base.@propagate_inbounds function LinearAlgebra.mul!(x::AbstractMatrix, y::$Atype, A::LinearMap,
27+
α::Number, β::Number)
28+
@boundscheck check_dim_mul(x, y, A)
29+
@inbounds mul!(x', A', y', conj(α), conj(β))
30+
return x
31+
end
32+
end
33+
end
34+
for Atype in (TransposeAbsVec, Transpose{<:Any,<:AbstractMatrix})
35+
@eval begin
36+
Base.@propagate_inbounds function LinearAlgebra.mul!(x::AbstractMatrix, y::$Atype, A::LinearMap)
37+
@boundscheck check_dim_mul(x, y, A)
38+
@inbounds mul!(transpose(x), transpose(A), transpose(y))
39+
return x
40+
end
41+
42+
Base.@propagate_inbounds function LinearAlgebra.mul!(x::AbstractMatrix, y::$Atype, A::LinearMap,
43+
α::Number, β::Number)
44+
@boundscheck check_dim_mul(x, y, A)
45+
@inbounds mul!(transpose(x), transpose(A), transpose(y), α, β)
46+
return x
47+
end
48+
end
49+
end

test/left.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using Test, LinearMaps, LinearAlgebra
2+
3+
function left_tester(L::LinearMap{T}) where {T}
4+
M, N = size(L)
5+
A = Matrix(L)
6+
7+
x = rand(T, N)
8+
y = rand(T, M)
9+
10+
# *
11+
@test y' * A y' * L
12+
@test (y' * A) * x y' * (L * x) # associative
13+
@test transpose(y) * A transpose(y) * L
14+
15+
# mul!
16+
α = rand(T); β = rand(T)
17+
b1 = y' * A
18+
b2 = similar(b1)
19+
bt = copy(b1')'
20+
# bm = Matrix(bt) # TODO: this requires a generalization of the output to AbstractVecOrMat
21+
@test mul!(b2, y', L) mul!(bt, y', L)# ≈ mul!(bm, y', L)# 3-arg
22+
@test mul!(b2, y', L) === b2
23+
@test b1 b2 bt
24+
b3 = copy(b2)
25+
mul!(b3, y', L, α, β)
26+
@test b3 b2*β + b1*α
27+
28+
b1 = transpose(y) * A
29+
b2 = similar(b1)
30+
bt = transpose(copy(transpose(b1)))
31+
@test mul!(b2, transpose(y), L) mul!(bt, transpose(y), L)
32+
@test b1 b2 bt
33+
b3 = copy(b2)
34+
mul!(b3, transpose(y), L, α, β)
35+
@test b3 b2*β + b1*α
36+
37+
Y = rand(T, M, 3)
38+
X = similar(Y, 3, N)
39+
Xt = copy(X')'
40+
@test Y'L isa LinearMap
41+
@test Matrix(Y'L) Y'A
42+
@test mul!(X, Y', L) mul!(Xt, Y', L) Y'A
43+
@test mul!(Xt, Y', L) === Xt
44+
@test mul!(copy(X), Y', L, α, β) X*β + Y'A*α
45+
@test mul!(X, Y', L) === X
46+
@test mul!(X, Y', L, α, β) === X
47+
48+
true
49+
end
50+
51+
@testset "left multiplication" begin
52+
T = ComplexF32
53+
N = 5
54+
L = LinearMap{T}(cumsum, reverse cumsum reverse, N)
55+
56+
@test left_tester(L) # FunctionMap
57+
@test left_tester(L'*L) # CompositeMap
58+
@test left_tester(2L) # ScaledMap
59+
@test left_tester(kron(L, L')) # KroneckerMap
60+
@test left_tester(2L + 3L') # LinearCombination
61+
@test left_tester([L L]) # BlockMap
62+
63+
W = LinearMap(randn(T,5,4))
64+
@test left_tester(W) # WrappedMap
65+
end

test/linearmaps.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ using Test, LinearMaps, LinearAlgebra, SparseArrays, BenchmarkTools
4747
@test @inferred mul!(copy(W), M, V) AV
4848
@test typeof(M * V) <: LinearMap
4949
end
50+
51+
@testset "dimension checking" begin
52+
@test_throws DimensionMismatch M * similar(v, length(v) + 1)
53+
@test_throws DimensionMismatch mul!(similar(w, length(w) + 1), M, v)
54+
@test_throws DimensionMismatch similar(w, length(w) + 1)' * M
55+
@test_throws DimensionMismatch mul!(copy(v)', similar(w, length(w) + 1)', M)
56+
@test_throws DimensionMismatch mul!(similar(W, size(W).+(0,1)), M, V)
57+
@test_throws DimensionMismatch mul!(copy(W), M, similar(V, size(V).+(0,1)))
58+
end
5059
end
5160

5261
# new type

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ include("blockmap.jl")
2828
include("kronecker.jl")
2929

3030
include("conversion.jl")
31+
32+
include("left.jl")

0 commit comments

Comments
 (0)