Defining Operators as Functions
Mathematically, one can represent an operator Ô
with the following definitions:
Action of Ô on Ket:
Ô  i ⟩ = ∑ⱼ cᵢⱼ  j ⟩
Action of Ô on Bra:
⟨ u  Ô = ∑ᵥ cᵤᵥ ⟨ v 
QuDirac allows us to define normal Julia functions that act like Ô
using
the @def_op
macro:
# define "a" on Kets
julia> @def_op " a  n > = √n *  n1 > "
a (generic function with 1 methods)
julia> d" a *  42 > "
Ket{KroneckerDelta,1,Float64} with 1 state(s):
6.48074069840786  41 ⟩
julia> d" a * ( 1 > +  2 >   3 >) "
Ket{KroneckerDelta,1,Float64} with 3 state(s):
1.7320508075688772  2 ⟩
1.0  0 ⟩
1.4142135623730951  1 ⟩
Action of the operator's dual on Bras is welldefined by the definition of
the operator on Kets, since < 1  * Ô' == ( Ô *  1 > )'
:
julia> d" < 2  * a' "
Bra{KroneckerDelta,1,Float64} with 1 state(s):
1.4142135623730951 ⟨ 1 
To act the operator on a Bra (or its dual on a Ket), we need to define its action on the Bra:
# define "a" on Bras
julia> @def_op " < n  a = √(n+1) * < n+1  "
a (generic function with 2 methods)
julia> d" < 5  * a "
Bra{KroneckerDelta,1,Float64} with 1 state(s):
2.449489742783178 ⟨ 6 
julia> d" a' * ( 5 > +  2 >)"
Ket{KroneckerDelta,1,Float64} with 2 state(s):
1.7320508075688772  3 ⟩
2.449489742783178  6 ⟩
julia> d" < 5  * a *  6 > "
2.449489742783178
The @def_op
macro works for product bases as well:
julia> @def_op " a₂  x,y,z > = √y *  x,y1,z > "
a₂ (generic function with 2 methods)
julia> d" a₂ * ( 0,10,8 >   12,31,838 >) "
Ket{KroneckerDelta,3,Float64} with 2 state(s):
3.1622776601683795  0,9,8 ⟩
5.5677643628300215  12,30,838 ⟩
For an example of an operator that throws its basis Kets into superpositions, here's a function emulating a Hadamard operator:
julia> @def_op " h  n > = 1/√2 * (  0 > + (1)^n * 1 > )"
h (generic function with 1 methods)
julia> d" h *  0 > "
Ket{KroneckerDelta,1,Float64} with 2 state(s):
0.7071067811865475  0 ⟩
0.7071067811865475  1 ⟩
julia> d" h *  1 > "
Ket{KroneckerDelta,1,Float64} with 2 state(s):
0.7071067811865475  0 ⟩
0.7071067811865475  1 ⟩
Grammar for the Definition String
The grammar of the string passed to @def_op
is:

Defining action on Kets:
@def_op " $op_name  $label_args > = f($label_args...) "
where
f
is an arbitrary expanded function that takes in the$label_args
and returns a Ket. 
Defining action on Bras:
@def_op " < $label_args  $op_name = f($label_args...) "
where
f
is an arbitrary expanded function that takes in the$label_args
and returns a Bra.
Allowable syntax for the righthand side of the equation
is exactly the same syntax allowed by d"..."
.
Generating Operator Representations
The operatorfunctions described in the previous example are no doubt useful, but they are just normal Julia functions, and so are quite limited when it comes to mimicking the behavior of actual quantum operators. For example, they can't be added or be factors of a tensor product (this functionality may indeed be implemented in the future, however).
For those capabilities, we'll need to generate an OpSum
representation in a basis. To do so,
we can use the @rep_op
macro. Here's a familiar example:
julia> @rep_op " a  n > = √n *  n1 > " 1:10;
julia> a
OpSum{KroneckerDelta,1,Float64} with 10 operator(s):
2.449489742783178  5 ⟩⟨ 6 
3.1622776601683795  9 ⟩⟨ 10 
2.23606797749979  4 ⟩⟨ 5 
2.8284271247461903  7 ⟩⟨ 8 
2.6457513110645907  6 ⟩⟨ 7 
2.0  3 ⟩⟨ 4 
1.0  0 ⟩⟨ 1 
3.0  8 ⟩⟨ 9 
1.4142135623730951  1 ⟩⟨ 2 
1.7320508075688772  2 ⟩⟨ 3 
The @rep_op
macro takes in a definition string, and an iterable of items to be used as basis labels.
The grammar and allowable syntax of the definition string is exactly that of the definition string passed
to @def_op
. The only difference between the two is that the @rep_op
macro feeds in the given basis labels
to produce an OpSum
.
To generate a representation on a product basis, one can provide multiple iterables to @rep_op
.
Their cartesian product will then be used as the basis for the representation:
# define P₁₃₂ by it's action on a Bra
julia> @rep_op " < i,j,k  P₁₃₂ = < i,k,j  " 1:10 'a':'f' (1:10)
OpSum{KroneckerDelta,3,Int64} with 600 operator(s):
1  10,'e',2 ⟩⟨ 10,2,'e' 
1  4,'c',5 ⟩⟨ 4,5,'c' 
1  3,'c',10 ⟩⟨ 3,10,'c' 
1  2,'e',3 ⟩⟨ 2,3,'e' 
1  7,'c',9 ⟩⟨ 7,9,'c' 
1  1,'f',9 ⟩⟨ 1,9,'f' 
1  3,'f',6 ⟩⟨ 3,6,'f' 
1  9,'b',2 ⟩⟨ 9,2,'b' 
1  1,'c',3 ⟩⟨ 1,3,'c' 
1  4,'d',8 ⟩⟨ 4,8,'d' 
julia> d" P₁₃₂ *  10,2,'e' > "
Ket{KroneckerDelta,3,Int64} with 1 state(s):
1  10,'e',2 ⟩
Let's say I already have an operatorfunction defined, and want
to represent it in a basis. For example, take the Hadamard operatorfunction
h
, constructed in the previous section as:
julia> @def_op " h  n > = 1/√2 * (  0 > + (1)^n * 1 > )"
h (generic function with 1 methods)
I can easily generate a representation for this function by using @rep_op
and
calling h
on the righthand side:
julia> @rep_op " H  n > = h *  n > " 0:1
OpSum{KroneckerDelta,1,Float64} with 4 operator(s):
0.7071067811865475  1 ⟩⟨ 0 
0.7071067811865475  0 ⟩⟨ 0 
0.7071067811865475  0 ⟩⟨ 1 
0.7071067811865475  1 ⟩⟨ 1 
The above strategy works to represent any operatorfunction. Just be aware that the function and the actual representation need to have unique names:
julia> @rep_op " h  n > = h *  n > " 0:1
ERROR: invalid redefinition of constant h
in anonymous at no file:70