[ANN] Conjugates.jl

Edit: The package has been renamed to Conjugates.jl from PlusHC.jl

Here’s a snippet I’ve been using in some code for the past little while that I decided to roll up into a tiny package: Conjugates.jl

julia> using Conjugates

julia> (1 + im) + hc
2

julia> randn(ComplexF64, 3, 3) + hc
3×3 Hermitian{Complex{Float64},Array{Complex{Float64},2}}:
   0.86551+0.0im         1.38105+0.506667im  -0.761315-0.211322im
   1.38105-0.506667im   0.826828+0.0im       -0.764402-0.579842im
 -0.761315+0.211322im  -0.764402+0.579842im  -0.629772+0.0im

julia> randn(ComplexF64, 3, 3) * tp
3×3 Symmetric{Complex{Float64},Array{Complex{Float64},2}}:
   2.43339+0.0im        -1.48393-0.247791im  0.0867097+0.53651im
  -1.48393-0.247791im    1.38745+0.0im       0.0606936-0.470711im
 0.0867097+0.53651im   0.0606936-0.470711im    2.14239+0.0im

Writing x + h.c. is pretty common, at least in physics circles, to mean x + x^\dagger, and I often find myself wanting to manually symmetrize expressions in this way, so I figured why not?

hc stands for Hermitian Conjugate, and tp stands for transpose.

If you feel a burning desire for this, you can do

]add Conjugates

at the repl to get the package.

14 Likes

I did something similar for AA' in

(unregistered), but I like your approach better.

It would be great if the package contained a placeholder for products with the transpose — perhaps generalizing the package name a bit.

1 Like

Mhm yeah, multiying with the conjugate is often useful too. Maybe I should just rename it something like Conjugates.jl and then have hc And maybe tc or something for transpose, allowing elementary operations like addition, subtraction, multiplication, etc.

This is really nice! (I’ve been writing |> x->x+x', but this is much better.)

If I may bikeshed a little: How about using adjoint instead of hc? Something like

Base.:+(x, ::typeof(adjoint)) = x + x'

I think that somebody who is not a physicist (but is familiar with Julia) would more easily be able to guess what (1 + im) + adjoint does.

(This would be type piracy, since both adjoint and + are in Base, so even better would be to also make it a PR there. Once the PR is accepted, the package would be modified to do nothing for Julia versions where the functionality already exists.)

1 Like

I had a related idea in [1]. For some of these operation, their formula is almost the clearest name for the operation it self. I prefer

x + x'
A*A'
α*A*B + β*C

over

addhc(x)
symprod(A)
addmul(α, A, B, β, C)

So a macro for “speaking function names”

@call C = α*A*B + β*C

expanding to a canonical function name and call

var"_1 = _2 * _3 * _4 + _5 * _1"(C, α, A, B, β, C)

would be nice. + hc avoids that issue in a different way, nice!

I think that your proposal is a great idea, but the package might as well just export a singleton Adjoint type with the constant ADJOINT and avoid all these issues. Eg

x + ADJOINT
x * ADJOINT
ADJOINT * x

etc.

It could, but I would love to have this in Base.

Also: The name Adjoint is already exported by LinearAlgebra.

@Mason this is lovely!

Okay, to I enlarged the scope of the package a bit, renamed it to Conjugates.jl, added tp which is to transpose as hc is to adjoint and added support for subtraction and multiplication.

I like this, but I didn’t want to commit piracy here, and I strongly suspect that a PR adding this to Base will get shot down, though it may be worth a shot.

I might eventually try adding a macro so that a user can do something like @pirate adjoint transpose to get the methods you suggest put in.

I’m not really a fan of using something like ADJOINT, but if someone is, it’s easy enough to just write const ADJOINT = hc; TRANSPOSE = tp

Maybe it’d be kind of nice to use ◻' or something?

struct Hole end
const ◻ = Hole()
Base.adjoint(::Hole) = hc

julia> (1 + im) + ◻'
2
1 Like

This is also a symbol used for the d’Alembert operator, so probably not that symbol

1 Like

is there anyway to get the dagger in Julia?

\dagger is reserved syntax.

1 Like

Generalizing the the idea of @tkf a bit:

struct Idem{F}
    f::F
end

for f in (:adjoint, :transpose)
    @eval Base.$f(id::Idem{typeof(identity)}) = Idem($f)
end

for op in (:+, :*)
    @eval Base.$op(x, id::Idem{F}) where {F} = $op(x, id.f(x))
    @eval Base.$op(id::Idem{F}, x) where {F} = $op(id.f(x), x)
end

const idem = Idem(identity)
const hc = idem'
const tp = transpose(idem)

The lists of unary and binary operators could be extended as needed.

4 Likes

I was suggesting that the constant ADJOINT is used. The API does not need to have the type itself exported, which could be anything.

Okay, conjugates is now on the general registry!

4 Likes