Skip to content

Commit

Permalink
express, qsimplify, and latexify updates (#57)
Browse files Browse the repository at this point in the history
* latexify additions

* updated express and new doc

* qsimplify updates

* doctest fix

* rearranging maketerm defs and doctest change

* clean docs

* update changelop and doc

* minor fix

* update project.toml
  • Loading branch information
apkille authored Jun 29, 2024
1 parent 01b1a55 commit 079ea5e
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# News

## v0.3.2 - 2024-06-28

- Added documentation for `express`.
- `qsimplify` can now traverse through subexpressions using Prewalk from SymbolicUtils.jl.
- Updated `latexify` capabilities.
- **(fix)** There was a bug for latexifying dagger objects.

## v0.3.1 - 2024-06-21

- Macros for defining symbolic quantum objects.
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "QuantumSymbolics"
uuid = "efa7fd63-0460-4890-beb7-be1bbdfbaeae"
authors = ["QuantumSymbolics.jl contributors"]
version = "0.3.1"
version = "0.3.2"

[deps]
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
Expand Down
59 changes: 59 additions & 0 deletions docs/src/express.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Express functionality

```@meta
DocTestSetup = quote
using QuantumSymbolics, QuantumOptics, QuantumClifford
end
```

A principle feature of `QuantumSymbolics` is to numerically represent symbolic quantum expressions in various formalisms using [`express`](@ref). 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:

```@example 1
using QuantumSymbolics, QuantumClifford, QuantumOptics # hide
ψ = Z1
```
Using [`express`](@ref), we can translate this symbolic object into its numerical state vector form in `QuantumOptics.jl`.

```@example 1
express(ψ)
```

By default, [`express`](@ref) converts a quantum object with `QuantumOpticRepr`. It should be noted that [`express`](@ref) 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 `ψ`.

```@example 1
ψ.metadata
```

The caching feature of [`express`](@ref) 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`:

```@example 1
express(ψ, CliffordRepr())
```

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:

```@example 1
ψ.metadata
```

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:

```@example 2
using QuantumSymbolics, QuantumClifford, QuantumOptics # hide
bellstate = (Z1⊗Z1+Z2⊗Z2)/√2
tp = σˣ⊗σʸ
express(tp*bellstate)
```

## 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 `σʸ`:

```jldoctest
julia> express(σʸ, CliffordRepr(), UseAsObservable())
+ Y
julia> express(σʸ, CliffordRepr(), UseAsOperation())
sY
```
3 changes: 2 additions & 1 deletion ext/QuantumCliffordExt/QuantumCliffordExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ end
express_nolookup(::XGate, ::CliffordRepr, ::UseAsOperation) = QuantumClifford.sX
express_nolookup(::YGate, ::CliffordRepr, ::UseAsOperation) = QuantumClifford.sY
express_nolookup(::ZGate, ::CliffordRepr, ::UseAsOperation) = QuantumClifford.sZ
express_nolookup(x::STensorOperator,r::CliffordRepr,u::UseAsOperation) = QCGateSequence([express(t,r,u) for t in x.terms])
express_nolookup(x::STensorOperator, r::CliffordRepr, u::UseAsOperation) = QCGateSequence([express(t,r,u) for t in x.terms])

express_nolookup(op::QuantumClifford.PauliOperator, ::CliffordRepr, ::UseAsObservable) = op
express_nolookup(op::STensorOperator, r::CliffordRepr, u::UseAsObservable) = QuantumClifford.tensor(express.(arguments(op),(r,),(u,))...)
express_nolookup(::XGate, ::CliffordRepr, ::UseAsObservable) = QuantumClifford.P"X"
express_nolookup(::YGate, ::CliffordRepr, ::UseAsObservable) = QuantumClifford.P"Y"
express_nolookup(::ZGate, ::CliffordRepr, ::UseAsObservable) = QuantumClifford.P"Z"
express_nolookup(op::SScaledOperator, r::CliffordRepr, u::UseAsObservable) = arguments(op)[1] * express(arguments(op)[2],r,u)
express_nolookup(x::SMulOperator, r::CliffordRepr, u::UseAsObservable) = (*)((express(t,r,u) for t in arguments(x))...)
express_nolookup(op, ::CliffordRepr, ::UseAsObservable) = error("Can not convert $(op) into a `PauliOperator`, which is the only observable that can be computed for QuantumClifford objects. Consider defining `express_nolookup(op, ::CliffordRepr, ::UseAsObservable)::PauliOperator` for this object.")

struct QCRandomSampler # TODO specify types
Expand Down
5 changes: 3 additions & 2 deletions src/QSymbolicsBase/QSymbolicsBase.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Symbolics
import Symbolics: simplify
using SymbolicUtils
import SymbolicUtils: Symbolic, _isone, flatten_term, isnotflat, Chain, Fixpoint
import SymbolicUtils: Symbolic, _isone, flatten_term, isnotflat, Chain, Fixpoint, Prewalk
using TermInterface
import TermInterface: isexpr, head, iscall, children, operation, arguments, metadata
import TermInterface: isexpr, head, iscall, children, operation, arguments, metadata, maketerm

using LinearAlgebra
import LinearAlgebra: eigvecs, ishermitian, inv
Expand Down Expand Up @@ -141,6 +141,7 @@ Base.:(-)(x::SymQObj) = (-1)*x
Base.:(-)(x::SymQObj,y::SymQObj) = x + (-y)
Base.hash(x::SymQObj, h::UInt) = isexpr(x) ? hash((head(x), arguments(x)), h) :
hash((typeof(x),symbollabel(x),basis(x)), h)
maketerm(::Type{<:SymQObj}, f, a, t, m) = f(a...)

function Base.isequal(x::X,y::Y) where {X<:SymQObj, Y<:SymQObj}
if X==Y
Expand Down
18 changes: 12 additions & 6 deletions src/QSymbolicsBase/basic_ops_homogeneous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,36 @@ arguments(x::SScaled) = [x.coeff,x.obj]
operation(x::SScaled) = *
head(x::SScaled) = :*
children(x::SScaled) = [:*,x.coeff,x.obj]
Base.:(*)(c, x::Symbolic{T}) where {T<:QObj} = iszero(c) || iszero(x) ? SZero{T}() : SScaled{T}(c, x)
function Base.:(*)(c, x::Symbolic{T}) where {T<:QObj}
if iszero(c) || iszero(x)
SZero{T}()
else
x isa SScaled ? SScaled{T}(c*x.coeff, x.obj) : SScaled{T}(c, x)
end
end
Base.:(*)(x::Symbolic{T}, c) where {T<:QObj} = c*x
Base.:(/)(x::Symbolic{T}, c) where {T<:QObj} = iszero(c) ? throw(DomainError(c,"cannot divide QSymbolics expressions by zero")) : (1/c)*x
basis(x::SScaled) = basis(x.obj)

const SScaledKet = SScaled{AbstractKet}
function Base.show(io::IO, x::SScaledKet)
if x.coeff isa Number
if x.coeff isa Real
print(io, "$(x.coeff)$(x.obj)")
else
print(io, "($(x.coeff))$(x.obj)")
end
end
const SScaledOperator = SScaled{AbstractOperator}
function Base.show(io::IO, x::SScaledOperator)
if x.coeff isa Number
if x.coeff isa Real
print(io, "$(x.coeff)$(x.obj)")
else
print(io, "($(x.coeff))$(x.obj)")
end
end
const SScaledBra = SScaled{AbstractBra}
function Base.show(io::IO, x::SScaledBra)
if x.coeff isa Number
if x.coeff isa Real
print(io, "$(x.coeff)$(x.obj)")
else
print(io, "($(x.coeff))$(x.obj)")
Expand Down Expand Up @@ -171,14 +177,14 @@ function ⊗(xs::Symbolic{T}...) where {T<:QObj}
end
basis(x::STensor) = tensor(basis.(x.terms)...)

const STensorBra = STensor{AbstractBra}
Base.show(io::IO, x::STensorBra) = print(io, join(map(string, arguments(x)),""))
const STensorKet = STensor{AbstractKet}
Base.show(io::IO, x::STensorKet) = print(io, join(map(string, arguments(x)),""))
const STensorOperator = STensor{AbstractOperator}
Base.show(io::IO, x::STensorOperator) = print(io, join(map(string, arguments(x)),""))
const STensorSuperOperator = STensor{AbstractSuperOperator}
Base.show(io::IO, x::STensorSuperOperator) = print(io, join(map(string, arguments(x)),""))
const STensorBra = STensor{AbstractBra}
Base.show(io::IO, x::STensorBra) = print(io, join(map(string, arguments(x)),""))

"""Symbolic commutator of two operators
Expand Down
7 changes: 7 additions & 0 deletions src/QSymbolicsBase/express.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ julia> express(X1, CliffordRepr())
𝒮𝓉𝒶𝒷
+ X
julia> express(QuantumSymbolics.X)
Operator(dim=2x2)
basis: Spin(1/2)sparse([2, 1], [1, 2], ComplexF64[1.0 + 0.0im, 1.0 + 0.0im], 2, 2)
julia> express(QuantumSymbolics.X, CliffordRepr(), UseAsOperation())
sX
julia> express(QuantumSymbolics.X, CliffordRepr(), UseAsObservable())
+ X
```
"""
function express end
Expand Down
30 changes: 23 additions & 7 deletions src/QSymbolicsBase/latexify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,48 @@ function num_to_sub(n::Int)
"0"=>"",
)
end

@latexrecipe function f(x::SBra)
return Expr(:latexifymerge, "\\left\\langle ", symbollabel(x), "\\right|")
end
@latexrecipe function f(x::Union{SpecialKet,SKet})
return Expr(:latexifymerge, "\\left|", symbollabel(x), "\\right\\rangle")
end
@latexrecipe function f(x::Union{SOperator,AbstractSingleQubitOp,AbstractTwoQubitOp,AbstractSingleBosonGate})
@latexrecipe function f(x::Union{SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,AbstractSingleQubitOp,AbstractTwoQubitOp,AbstractSingleBosonGate})
return LaTeXString("\\hat $(symbollabel(x))")
end
@latexrecipe function f(x::SZero)
return LaTeXString("\\bm{O}")
end
@latexrecipe function f(x::SDagger)
if isexpr(x.ket)
return Expr(:latexifymerge, "\\left( ", x.ket, "\\right)^\\dagger")
if isexpr(x.obj)
return Expr(:latexifymerge, "\\left( ", latexify(x.obj), "\\right)^\\dagger")
else
return Expr(:latexifymerge, "\\left\\langle ", symbollabel(x), "\\right|")
return Expr(:latexifymerge, latexify(x.obj), "\\^\\dagger")
end
end
@latexrecipe function f(x::SScaled)
@latexrecipe function f(x::Union{SScaled,SMulOperator,SOuterKetBra,SApplyKet,SApplyBra})
cdot --> false
return _toexpr(x)
end

@latexrecipe function f(x::SCommutator)
return Expr(:latexifymerge, "\\left\\lbrack", latexify(x.op1), ",", latexify(x.op2), "\\right\\rbrack")
end
@latexrecipe function f(x::SAnticommutator)
return Expr(:latexifymerge, "\\left\\{", latexify(x.op1), ",", latexify(x.op2), "\\right\\}")
end
@latexrecipe function f(x::SBraKet)
return Expr(:latexifymerge, "\\left\\langle ", symbollabel(x.bra), "\\mid ", symbollabel(x.ket), "\\right\\rangle")
end
@latexrecipe function f(x::MixedState)
return LaTeXString("\\mathbb{M}")
end

@latexrecipe function f(x::IdentityOp)
return LaTeXString("\\mathbb{I}")
end
@latexrecipe function f(x::SInvOperator)
return Expr(:latexifymerge, latexify(x.op), "\\^{-1}")
end

function _toexpr(x)
if isexpr(x)
Expand Down
2 changes: 1 addition & 1 deletion src/QSymbolicsBase/predefined.jl
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ end
julia> @ket a; @op A;
julia> dagger(2*im*A*a)
0 - 2im|a⟩†A†
(0 - 2im)|a⟩†A†
julia> @op B;
Expand Down
10 changes: 6 additions & 4 deletions src/QSymbolicsBase/rules.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,19 +121,21 @@ If the keyword `rewriter` is not specified, then `qsimplify` will apply every de
For performance or single-purpose motivations, the user has the option to define a specific rewriter for `qsimplify` to apply to the expression.
```jldoctest
julia> qsimplify(σʸ*commutator(σˣ*σᶻ, σᶻ))
(0 - 2im)Z
julia> qsimplify(anticommutator(σˣ, σˣ), rewriter=qsimplify_anticommutator)
2𝕀
```
"""
function qsimplify(s; rewriter=nothing)
if QuantumSymbolics.isexpr(s)
if isnothing(rewriter)
Fixpoint(Chain(RULES_ALL))(s)
Fixpoint(Prewalk(Chain(RULES_ALL)))(s)
else
Fixpoint(rewriter)(s)
Fixpoint(Prewalk(rewriter))(s)
end
else
error("Object $(s) of type $(typeof(s)) is not an expression.")
end
end

end
28 changes: 28 additions & 0 deletions test/test_express_cliff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,31 @@ withcache = @timed express(state2, CliffordRepr())
@test withcache.bytes <= 200
results = Set([express(state2, CliffordRepr()) for i in 1:20])
@test length(results)==2

CR = CliffordRepr()
UseOp = UseAsOperation()
UseObs = UseAsObservable()

@testset "Clifford representations for basis states" begin
isequal(express(X1, CR), MixedDestabilizer(S"X"))
isequal(express(X2, CR), MixedDestabilizer(S"-X"))
isequal(express(Y1, CR), MixedDestabilizer(S"Y"))
isequal(express(Y2, CR), MixedDestabilizer(S"-Y"))
isequal(express(Z1, CR), MixedDestabilizer(S"Z"))
isequal(express(Z2, CR), MixedDestabilizer(S"-Z"))
end

@testset "Clifford representations as observables" begin
isequal(express(σˣ, CR, UseObs), P"X")
isequal(express(σʸ, CR, UseObs), P"Y")
isequal(express(σᶻ, CR, UseObs), P"Z")
isequal(express(im*σˣ, CR, UseObs), im*P"X")
isequal(express(σˣσʸσᶻ), P"X"P"Y"P"Z")
isequal(express(σˣ*σʸ*σᶻ), P"X"*P"Y"*P"Z")
end

@testset "Clifford representations as operations" begin
isequal(express(σˣ, CR, UseOp), sX)
isequal(express(σʸ, CR, UseOp), sY)
isequal(express(σᶻ, CR, UseOp), sZ)
end

0 comments on commit 079ea5e

Please sign in to comment.