Skip to content

Commit 2479b4e

Browse files
andyferristkelman
authored andcommitted
Added ConjArray wrapper type for conjugate views (#20047)
By default, this is used only in conjugation with `RowVector`, so that both `transpose(vec)` and `ctranspose(vec)` both return views.
1 parent 3257ec9 commit 2479b4e

File tree

10 files changed

+148
-33
lines changed

10 files changed

+148
-33
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ Library improvements
282282
283283
* New `@macroexpand` macro as a convenient alternative to the `macroexpand` function ([#18660]).
284284
285+
* Introduced a wrapper type for lazy complex conjugation of arrays, `ConjArray`.
286+
Currently, it is used by default for the new `RowVector` type only, and
287+
enforces that both `transpose(vec)` and `ctranspose(vec)` are views not copies ([#20047]).
288+
285289
Compiler/Runtime improvements
286290
-----------------------------
287291

base/exports.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export
5050
Complex128,
5151
Complex64,
5252
Complex32,
53+
ConjArray,
54+
ConjVector,
55+
ConjMatrix,
5356
DenseMatrix,
5457
DenseVecOrMat,
5558
DenseVector,

base/linalg/conjarray.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# This file is a part of Julia. License is MIT: http://julialang.org/license
2+
3+
"""
4+
ConjArray(array)
5+
6+
A lazy-view wrapper of an `AbstractArray`, taking the elementwise complex conjugate. This
7+
type is usually constructed (and unwrapped) via the [`conj`](@ref) function (or related
8+
[`ctranspose`](@ref)), but currently this is the default behavior for `RowVector` only. For
9+
other arrays, the `ConjArray` constructor can be used directly.
10+
11+
# Examples
12+
13+
```jldoctest
14+
julia> [1+im, 1-im]'
15+
1×2 RowVector{Complex{Int64},ConjArray{Complex{Int64},1,Array{Complex{Int64},1}}}:
16+
1-1im 1+1im
17+
18+
julia> ConjArray([1+im 0; 0 1-im])
19+
2×2 ConjArray{Complex{Int64},2,Array{Complex{Int64},2}}:
20+
1-1im 0+0im
21+
0+0im 1+1im
22+
```
23+
"""
24+
immutable ConjArray{T, N, A <: AbstractArray} <: AbstractArray{T, N}
25+
parent::A
26+
end
27+
28+
@inline ConjArray{T,N}(a::AbstractArray{T,N}) = ConjArray{conj_type(T), N, typeof(a)}(a)
29+
30+
typealias ConjVector{T, V <: AbstractVector} ConjArray{T, 1, V}
31+
@inline ConjVector{T}(v::AbstractVector{T}) = ConjArray{conj_type(T), 1, typeof(v)}(v)
32+
33+
typealias ConjMatrix{T, M <: AbstractMatrix} ConjArray{T, 2, M}
34+
@inline ConjMatrix{T}(m::AbstractMatrix{T}) = ConjArray{conj_type(T), 2, typeof(m)}(m)
35+
36+
# This type can cause the element type to change under conjugation - e.g. an array of complex arrays.
37+
@inline conj_type(x) = conj_type(typeof(x))
38+
@inline conj_type{T}(::Type{T}) = promote_op(conj, T)
39+
40+
@inline parent(c::ConjArray) = c.parent
41+
@inline parent_type(c::ConjArray) = parent_type(typeof(c))
42+
@inline parent_type{T,N,A}(::Type{ConjArray{T,N,A}}) = A
43+
44+
@inline size(a::ConjArray) = size(a.parent)
45+
linearindexing{CA <: ConjArray}(::CA) = linearindexing(parent_type(CA))
46+
linearindexing{CA <: ConjArray}(::Type{CA}) = linearindexing(parent_type(CA))
47+
48+
@propagate_inbounds getindex{T,N}(a::ConjArray{T,N}, i::Int) = conj(getindex(a.parent, i))
49+
@propagate_inbounds getindex{T,N}(a::ConjArray{T,N}, i::Vararg{Int,N}) = conj(getindex(a.parent, i...))
50+
@propagate_inbounds setindex!{T,N}(a::ConjArray{T,N}, v, i::Int) = setindex!(a.parent, conj(v), i)
51+
@propagate_inbounds setindex!{T,N}(a::ConjArray{T,N}, v, i::Vararg{Int,N}) = setindex!(a.parent, conj(v), i...)
52+
53+
@inline similar{T,N}(a::ConjArray, ::Type{T}, dims::Dims{N}) = similar(parent(a), T, dims)
54+
55+
# Currently, this is default behavior for RowVector only
56+
@inline conj(a::ConjArray) = parent(a)
57+
58+
# Helper functions, currently used by RowVector
59+
@inline _conj(a::AbstractArray) = ConjArray(a)
60+
@inline _conj{T<:Real}(a::AbstractArray{T}) = a
61+
@inline _conj(a::ConjArray) = parent(a)
62+
@inline _conj{T<:Real}(a::ConjArray{T}) = parent(a)

base/linalg/linalg.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export
2222

2323
# Types
2424
RowVector,
25+
ConjArray,
26+
ConjVector,
27+
ConjMatrix,
2528
SymTridiagonal,
2629
Tridiagonal,
2730
Bidiagonal,
@@ -237,6 +240,7 @@ end
237240
copy_oftype{T,N}(A::AbstractArray{T,N}, ::Type{T}) = copy(A)
238241
copy_oftype{T,N,S}(A::AbstractArray{T,N}, ::Type{S}) = convert(AbstractArray{S,N}, A)
239242

243+
include("conjarray.jl")
240244
include("transpose.jl")
241245
include("rowvector.jl")
242246

base/linalg/rowvector.jl

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
"""
22
RowVector(vector)
33
4-
A lazy-view wrapper of an `AbstractVector`, which turns a length-`n` vector into
5-
a `1×n` shaped row vector and represents the transpose of a vector (the elements
6-
are also transposed recursively). This type is usually constructed (and
7-
unwrapped) via the `transpose()` function or `.'` operator (or related
8-
`ctranspose()` or `'` operator).
9-
10-
By convention, a vector can be multiplied by a matrix on its left (`A * v`)
11-
whereas a row vector can be multiplied by a matrix on its right (such that
12-
`v.' * A = (A.' * v).'`). It differs from a `1×n`-sized matrix by the facts that
13-
its transpose returns a vector and the inner product `v1.' * v2` returns a
14-
scalar, but will otherwise behave similarly.
4+
A lazy-view wrapper of an `AbstractVector`, which turns a length-`n` vector into a `1×n`
5+
shaped row vector and represents the transpose of a vector (the elements are also transposed
6+
recursively). This type is usually constructed (and unwrapped) via the [`transpose`](@ref)
7+
function or `.'` operator (or related [`ctranspose`](@ref) or `'` operator).
8+
9+
By convention, a vector can be multiplied by a matrix on its left (`A * v`) whereas a row
10+
vector can be multiplied by a matrix on its right (such that `v.' * A = (A.' * v).'`). It
11+
differs from a `1×n`-sized matrix by the facts that its transpose returns a vector and the
12+
inner product `v1.' * v2` returns a scalar, but will otherwise behave similarly.
1513
"""
1614
immutable RowVector{T,V<:AbstractVector} <: AbstractMatrix{T}
1715
vec::V
@@ -21,11 +19,12 @@ immutable RowVector{T,V<:AbstractVector} <: AbstractMatrix{T}
2119
end
2220
end
2321

24-
2522
@inline check_types{T1,T2}(::Type{T1},::AbstractVector{T2}) = check_types(T1, T2)
2623
@pure check_types{T1,T2}(::Type{T1},::Type{T2}) = T1 === transpose_type(T2) ? nothing :
2724
error("Element type mismatch. Tried to create a `RowVector{$T1}` from an `AbstractVector{$T2}`")
2825

26+
typealias ConjRowVector{T, CV <: ConjVector} RowVector{T, CV}
27+
2928
# The element type may be transformed as transpose is recursive
3029
@inline transpose_type{T}(::Type{T}) = promote_op(transpose, T)
3130

@@ -47,10 +46,12 @@ end
4746
convert{T,V<:AbstractVector}(::Type{RowVector{T,V}}, rowvec::RowVector) =
4847
RowVector{T,V}(convert(V,rowvec.vec))
4948

50-
# similar()
51-
@inline similar(rowvec::RowVector) = RowVector(similar(rowvec.vec))
52-
@inline similar{T}(rowvec::RowVector, ::Type{T}) = RowVector(similar(rowvec.vec, transpose_type(T)))
53-
# There is no resizing similar() because it would be ambiguous if the result were a Matrix or a RowVector
49+
# similar tries to maintain the RowVector wrapper and the parent type
50+
@inline similar(rowvec::RowVector) = RowVector(similar(parent(rowvec)))
51+
@inline similar{T}(rowvec::RowVector, ::Type{T}) = RowVector(similar(parent(rowvec), transpose_type(T)))
52+
53+
# Resizing similar currently loses its RowVector property.
54+
@inline similar{T,N}(rowvec::RowVector, ::Type{T}, dims::Dims{N}) = similar(parent(rowvec), T, dims)
5455

5556
# Basic methods
5657
"""
@@ -73,18 +74,34 @@ julia> transpose(v)
7374
```
7475
"""
7576
@inline transpose(vec::AbstractVector) = RowVector(vec)
76-
@inline ctranspose{T}(vec::AbstractVector{T}) = RowVector(conj(vec))
77+
@inline ctranspose{T}(vec::AbstractVector{T}) = RowVector(_conj(vec))
7778
@inline ctranspose{T<:Real}(vec::AbstractVector{T}) = RowVector(vec)
7879

7980
@inline transpose(rowvec::RowVector) = rowvec.vec
81+
@inline transpose(rowvec::ConjRowVector) = copy(rowvec.vec) # remove the ConjArray wrapper from any raw vector
8082
@inline ctranspose{T}(rowvec::RowVector{T}) = conj(rowvec.vec)
8183
@inline ctranspose{T<:Real}(rowvec::RowVector{T}) = rowvec.vec
8284

8385
parent(rowvec::RowVector) = rowvec.vec
8486

85-
# Strictly, these are unnecessary but will make things stabler if we introduce
86-
# a "view" for conj(::AbstractArray)
87-
@inline conj(rowvec::RowVector) = RowVector(conj(rowvec.vec))
87+
"""
88+
conj(rowvector)
89+
90+
Returns a [`ConjArray`](@ref) lazy view of the input, where each element is conjugated.
91+
92+
### Example
93+
94+
```jldoctest
95+
julia> v = [1+im, 1-im].'
96+
1×2 RowVector{Complex{Int64},Array{Complex{Int64},1}}:
97+
1+1im 1-1im
98+
99+
julia> conj(v)
100+
1×2 RowVector{Complex{Int64},ConjArray{Complex{Int64},1,Array{Complex{Int64},1}}}:
101+
1-1im 1+1im
102+
```
103+
"""
104+
@inline conj(rowvec::RowVector) = RowVector(_conj(rowvec.vec))
88105
@inline conj{T<:Real}(rowvec::RowVector{T}) = rowvec
89106

90107
# AbstractArray interface
@@ -147,6 +164,11 @@ end
147164

148165
# Multiplication #
149166

167+
# inner product -> dot product specializations
168+
@inline *{T<:Real}(rowvec::RowVector{T}, vec::AbstractVector{T}) = dot(parent(rowvec), vec)
169+
@inline *(rowvec::ConjRowVector, vec::AbstractVector) = dot(rowvec', vec)
170+
171+
# Generic behavior
150172
@inline function *(rowvec::RowVector, vec::AbstractVector)
151173
if length(rowvec) != length(vec)
152174
throw(DimensionMismatch("A has dimensions $(size(rowvec)) but B has dimensions $(size(vec))"))

doc/src/stdlib/linalg.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Base.LinAlg.istriu
100100
Base.LinAlg.isdiag
101101
Base.LinAlg.ishermitian
102102
Base.LinAlg.RowVector
103+
Base.LinAlg.ConjArray
103104
Base.transpose
104105
Base.transpose!
105106
Base.ctranspose

test/choosetests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function choosetests(choices = [])
130130
"linalg/diagonal", "linalg/pinv", "linalg/givens",
131131
"linalg/cholesky", "linalg/lu", "linalg/symmetric",
132132
"linalg/generic", "linalg/uniformscaling", "linalg/lq",
133-
"linalg/hessenberg", "linalg/rowvector"]
133+
"linalg/hessenberg", "linalg/rowvector", "linalg/conjarray"]
134134
if Base.USE_GPL_LIBS
135135
push!(linalgtests, "linalg/arnoldi")
136136
end

test/linalg/conjarray.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This file is a part of Julia. License is MIT: http://julialang.org/license
2+
3+
@testset "Core" begin
4+
m = [1+im 2; 2 4-im]
5+
cm = ConjArray(m)
6+
@test cm[1,1] == 1-im
7+
@test trace(cm*m) == 27
8+
9+
v = [[1+im], [1-im]]
10+
cv = ConjArray(v)
11+
@test cv[1] == [1-im]
12+
end
13+
14+
@testset "RowVector conjugates" begin
15+
v = [1+im, 1-im]
16+
rv = v'
17+
@test (parent(rv) isa ConjArray)
18+
@test rv' === v
19+
20+
# Currently, view behavior defaults to only RowVectors.
21+
@test isa((v').', Vector)
22+
@test isa((v.')', Vector)
23+
end

test/linalg/rowvector.jl

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# This file is a part of Julia. License is MIT: http://julialang.org/license
22

3-
@testset "RowVector" begin
4-
53
@testset "Core" begin
64
v = [1,2,3]
75
z = [1+im,2,3]
@@ -104,15 +102,15 @@ end
104102

105103
@test (rv*v) === 14
106104
@test (rv*mat)::RowVector == [1 4 9]
107-
@test [1]*reshape([1],(1,1)) == reshape([1],(1,1))
105+
@test [1]*reshape([1],(1,1)) == reshape([1], (1,1))
108106
@test_throws DimensionMismatch rv*rv
109107
@test (v*rv)::Matrix == [1 2 3; 2 4 6; 3 6 9]
110108
@test_throws DimensionMismatch v*v # Was previously a missing method error, now an error message
111109
@test_throws DimensionMismatch mat*rv
112110

113111
@test_throws DimensionMismatch rv*v.'
114112
@test (rv*mat.')::RowVector == [1 4 9]
115-
@test [1]*reshape([1],(1,1)).' == reshape([1],(1,1))
113+
@test [1]*reshape([1],(1,1)).' == reshape([1], (1,1))
116114
@test rv*rv.' === 14
117115
@test_throws DimensionMismatch v*rv.'
118116
@test (v*v.')::Matrix == [1 2 3; 2 4 6; 3 6 9]
@@ -142,7 +140,7 @@ end
142140

143141
@test_throws DimensionMismatch cz*z'
144142
@test (cz*mat')::RowVector == [-2im 4 9]
145-
@test [1]*reshape([1],(1,1))' == reshape([1],(1,1))
143+
@test [1]*reshape([1],(1,1))' == reshape([1], (1,1))
146144
@test cz*cz' === 15 + 0im
147145
@test_throws DimensionMismatch z*cz'
148146
@test (z*z')::Matrix == [2 2+2im 3+3im; 2-2im 4 6; 3-3im 6 9]
@@ -251,5 +249,3 @@ end
251249
@test A'*x' == A'*y == B*x' == B*y == C'
252250
end
253251
end
254-
255-
end # @testset "RowVector"

test/sparse/sparse.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,11 +1641,11 @@ end
16411641
@test At_ldiv_B(ltintmat, sparse(intmat)) At_ldiv_B(ltintmat, intmat)
16421642
end
16431643

1644-
# Test temporary fix for issue #16548 in PR #16979. Brittle. Expect to remove with `\` revisions.
1645-
# This is broken by the introduction of RowVector... see brittle comment above.
1646-
#@testset "issue #16548" begin
1647-
# @test which(\, (SparseMatrixCSC, AbstractVecOrMat)).module == Base.SparseArrays
1648-
#end
1644+
# Test temporary fix for issue #16548 in PR #16979. Somewhat brittle. Expect to remove with `\` revisions.
1645+
@testset "issue #16548" begin
1646+
ms = methods(\, (SparseMatrixCSC, AbstractVecOrMat)).ms
1647+
@test all(m -> m.module == Base.SparseArrays, ms)
1648+
end
16491649

16501650
@testset "row indexing a SparseMatrixCSC with non-Int integer type" begin
16511651
A = sparse(UInt32[1,2,3], UInt32[1,2,3], [1.0,2.0,3.0])

0 commit comments

Comments
 (0)