Express functionality

A principle feature of QuantumSymbolics is to numerically represent symbolic quantum expressions in various formalisms using express. In particular, one can translate symbolic logic to back-end toolboxes such as QuantumOptics.jl or QuantumClifford.jl for simulating quantum systems with great flexibiity.

As a straightforward example, consider the spin-up state $|\uparrow\rangle = |0\rangle$, the eigenstate of the Pauli operator $Z$, which can be expressed in QuantumSymbolics as follows:

ψ = Z1

\[\left|Z_1\right\rangle\]

Using express, we can translate this symbolic object into its numerical state vector form in QuantumOptics.jl.

express(ψ)
Ket(dim=2)
  basis: Spin(1/2)
 1.0 + 0.0im
 0.0 + 0.0im

By default, express converts a quantum object with QuantumOpticRepr. It should be noted that express automatically caches this particular conversion of ψ. Thus, after running the above example, the numerical representation of the spin-up state is stored in the metadata of ψ.

ψ.metadata
QuantumSymbolics.Metadata(Dict{Tuple{AbstractRepresentation, AbstractUse}, Any}((QuantumOpticsRepr(2), UseAsState()) => Ket(dim=2)
  basis: Spin(1/2)
 1.0 + 0.0im
 0.0 + 0.0im))

The caching feature of express prevents a specific representation for a symbolic quantum object from being computed more than once. This becomes handy for translations of more complex operations, which can become computationally expensive. We also have the ability to express $|Z_1\rangle$ in the Clifford formalism with QuantumClifford.jl:

express(ψ, CliffordRepr())
𝒟ℯ𝓈𝓉𝒶𝒷
+ X
𝒮𝓉𝒶𝒷
+ Z

Here, we specified an instance of CliffordRepr in the second argument to convert ψ into a tableau of Pauli operators containing its stabilizer and destabilizer states. Now, both the state vector and Clifford representation of ψ have been cached:

ψ.metadata
QuantumSymbolics.Metadata(Dict{Tuple{AbstractRepresentation, AbstractUse}, Any}((QuantumOpticsRepr(2), UseAsState()) => Ket(dim=2)
  basis: Spin(1/2)
 1.0 + 0.0im
 0.0 + 0.0im, (CliffordRepr(), UseAsState()) => MixedDestablizer 1×1))

More involved examples can be explored. For instance, say we want to apply the tensor product $X\otimes Y$ of the Pauli operators $X$ and $Y$ to the Bell state $|\Phi^{+}\rangle = \dfrac{1}{\sqrt{2}}\left(|00\rangle + |11\rangle\right)$, and numerically express the result in the quantum optics formalism. This would be done as follows:

bellstate = (Z1⊗Z1+Z2⊗Z2)/√2
tp = σˣ⊗σʸ
express(tp*bellstate)
Ket(dim=4)
  basis: [Spin(1/2) ⊗ Spin(1/2)]
 0.0 - 0.7071067811865475im
 0.0 + 0.0im
 0.0 + 0.0im
 0.0 + 0.7071067811865475im

Examples of Edge Cases

For Pauli operators, additional flexibility is given for translations to the Clifford formalism. Users have the option to convert a multi-qubit Pauli operator to an observable or operation with instances of UseAsObservable and UseAsOperation, respectively. Take the Pauli operator $Y$, for example, which in QuantumSymbolics is the constants Y or σʸ:

julia> express(σʸ, CliffordRepr(), UseAsObservable())
+ Y

julia> express(σʸ, CliffordRepr(), UseAsOperation())
sY

Another edge case is translations with QuantumOpticsRepr, where we can additionally define a finite cutoff for bosonic states and operators, as discussed in the quantum harmonic oscillators page. The default cutoff for such objects is 2, however a different cutoff can be specified by passing an integer to QuantumOpticsRepr in an express call. Let us see an example with the number operator:

julia> express(N) |> dense
Operator(dim=3x3)
  basis: Fock(cutoff=2)
 0.0+0.0im  0.0+0.0im  0.0+0.0im
 0.0+0.0im  1.0+0.0im  0.0+0.0im
 0.0+0.0im  0.0+0.0im  2.0+0.0im

julia> express(N, QuantumOpticsRepr(cutoff=4)) |> dense
Operator(dim=5x5)
  basis: Fock(cutoff=4)
 0.0+0.0im  0.0+0.0im  0.0+0.0im  0.0+0.0im  0.0+0.0im
 0.0+0.0im  1.0+0.0im  0.0+0.0im  0.0+0.0im  0.0+0.0im
 0.0+0.0im  0.0+0.0im  2.0+0.0im  0.0+0.0im  0.0+0.0im
 0.0+0.0im  0.0+0.0im  0.0+0.0im  3.0+0.0im  0.0+0.0im
 0.0+0.0im  0.0+0.0im  0.0+0.0im  0.0+0.0im  4.0+0.0im