Should `conj` always materialize the result?

The docstring for conj states that

conj(A::AbstractArray)

  Return an array containing the complex conjugate of each entry in array A.

  Equivalent to `conj.(A)`, except that when `eltype(A) <: Real` `A` is returned without copying, and that when `A` has zero dimensions, a 0-dimensional array is returned (rather than a scalar).

Except, conj is special-cased to be lazy for Adjoint and Transpose, so it is not equivalent to conj.(A) as the result shares memory with the original array. For example:

julia> A = rand(ComplexF64,2,2)'
2Ă—2 adjoint(::Matrix{ComplexF64}) with eltype ComplexF64:
 0.891314-0.0686279im  0.0718785-0.679878im
 0.685828-0.0606725im   0.155366-0.0544453im

julia> B = conj(A)
2Ă—2 transpose(::Matrix{ComplexF64}) with eltype ComplexF64:
 0.891314+0.0686279im  0.0718785+0.679878im
 0.685828+0.0606725im   0.155366+0.0544453im

julia> B .*= 2
2Ă—2 transpose(::Matrix{ComplexF64}) with eltype ComplexF64:
 1.78263+0.137256im  0.143757+1.35976im
 1.37166+0.121345im  0.310732+0.108891im

julia> A
2Ă—2 adjoint(::Matrix{ComplexF64}) with eltype ComplexF64:
 1.78263-0.137256im  0.143757-1.35976im
 1.37166-0.121345im  0.310732-0.108891im

This behavior has explicit tests for it, so this appears intentional. Would it be better to change this behavior and be consistent with the docstring (and with other matrices in general)?

This seems to have been discussed in spezialized conj of Transpose/Adjoint by simeonschaub · Pull Request #33609 · JuliaLang/julia · GitHub

I think it would be better to clarify the docstring.

conj(A) is algebraically equivalent to conj.(A) in that conj.(A) == conj(A) for all A. It isn’t necessarily computationally equivalent, both because conj(A) may not allocate a new matrix (e.g. if eltype(A) <: Real) and because conj.(A) “fuses” with other dot calls (like any dot call).

3 Likes