I agree.
I’ve learned that.
The following is an example. Presumably sm
is a more fundamental operation than s_conj_m
.
And transpose
(rather than '
) corresponds to sm
. (That’s why in some sense I think transpose
is more fundamental).
function sm(p, x) return sum(p .* x) end
function s_conj_m(p, x) return sum(conj(p) .* x) end
import LinearAlgebra.tr as tr
A = rand(ComplexF64, 3, 2)
B = rand(ComplexF64, 3, 2)
# ✅ The following LHS == RHS
LHS, RHS = sm(A, B), tr(transpose(A) * B)
@assert LHS == RHS "might due to tolerance";
# ✅ The following LHS == RHS
LHS, RHS = s_conj_m(A, B), tr(A' * B)
@assert LHS == RHS "might due to tolerance";
You seem to imply that, conceptually, adjoint == conj ∘ transpose
, thus transpose
is simpler and more fundamental when your arrays are real. I think @stevengj is arguing that you should reframe this as follows:
Vector space |
Linear algebra concept |
Implementation for arrays |
Real |
adjoint |
transpose |
Complex |
adjoint |
conj ∘ transpose |
In other words, you should let go of the idea that adjoint
always implies conjugation. Rather, adjoint is the linear algebra concept that transpose
accomplishes on a real-valued array, and conj ∘ transpose
accomplishes on a complex-valued array.
The beauty of multiple dispatch is that function names can live on the conceptual level while compiling down to code that’s specific to the case at hand. You don’t have to worry about redundant conj
calls under the hood when using adjoint
on real vectors, the same way you don’t have to worry about hitting the complex code path applicable to exp(2.0 + 2.0im)
when calling exp(2.0)
.
6 Likes
This makes sense, which is reassuring.