Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

express, qsimplify, and latexify updates #57

Merged
merged 9 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions docs/src/express.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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:

```jldoctest
julia> ψ = Z1
|Z₁⟩
```
Using [`express`](@ref), we can translate this symbolic object into its numerical state vector form in `QuantumOptics.jl`.

```jldoctest
julia> ψ = Z1;

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

julia> ψ.metadata
QuantumSymbolics.Metadata(Dict{Tuple{AbstractRepresentation, AbstractUse}, Any}((QuantumOpticsRepr(), UseAsState()) => Ket(dim=2)
basis: Spin(1/2)
1.0 + 0.0im
0.0 + 0.0im))
```
By default, [`express`](@ref) converts a quantum object with [`QuantumOpticRepr`](@ref). 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 `ψ`.

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`:
```jldoctest
julia> ψ = Z1;

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

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

Here, we specified an instance of [`CliffordRepr`](@ref) 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.

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`](@ref) and [`UseAsOperation`](@ref), respectively. Take the Pauli operator $Y$, for example, which in `QuantumSymbolics` is the constants `Y` or `σʸ`:
apkille marked this conversation as resolved.
Show resolved Hide resolved

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

julia> express(σʸ, CliffordRepr(), UseAsOperation())
sY
```
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:

```jldoctest
julia> bellstate = (Z1⊗Z1+Z2⊗Z2)/√2
0.7071067811865475(|Z₁⟩|Z₁⟩+|Z₂⟩|Z₂⟩)

julia> tp = σˣ⊗σʸ
X⊗Y

julia> 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
```
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
4 changes: 2 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
31 changes: 25 additions & 6 deletions src/QSymbolicsBase/basic_ops_homogeneous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,39 @@ 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}
maketerm(::Type{SScaledKet}, f, a, t, m) = f(a...)
apkille marked this conversation as resolved.
Show resolved Hide resolved
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}
maketerm(::Type{SScaledOperator}, f, a, t, m) = f(a...)
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}
maketerm(::Type{SScaledBra}, f, a, t, m) = f(a...)
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 @@ -94,16 +103,19 @@ Base.:(+)(xs::Vararg{Symbolic{<:QObj},0}) = 0 # to avoid undefined type paramete
basis(x::SAdd) = basis(first(x.dict).first)

const SAddKet = SAdd{AbstractKet}
maketerm(::Type{SAddKet}, f, a, t, m) = f(a...)
function Base.show(io::IO, x::SAddKet)
ordered_terms = sort([repr(i) for i in arguments(x)])
print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference
end
const SAddOperator = SAdd{AbstractOperator}
maketerm(::Type{SAddOperator}, f, a, t, m) = f(a...)
function Base.show(io::IO, x::SAddOperator)
ordered_terms = sort([repr(i) for i in arguments(x)])
print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference
end
const SAddBra = SAdd{AbstractBra}
maketerm(::Type{SAddBra}, f, a, t, m) = f(a...)
function Base.show(io::IO, x::SAddBra)
ordered_terms = sort([repr(i) for i in arguments(x)])
print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference
Expand Down Expand Up @@ -131,6 +143,7 @@ arguments(x::SMulOperator) = x.terms
operation(x::SMulOperator) = *
head(x::SMulOperator) = :*
children(x::SMulOperator) = [:*;x.terms]
maketerm(::Type{SMulOperator}, f, a, t, m) = f(a...)
function Base.:(*)(xs::Symbolic{AbstractOperator}...)
zero_ind = findfirst(x->iszero(x), xs)
isnothing(zero_ind) ? SMulOperator(collect(xs)) : SZeroOperator()
Expand Down Expand Up @@ -171,14 +184,18 @@ function ⊗(xs::Symbolic{T}...) where {T<:QObj}
end
basis(x::STensor) = tensor(basis.(x.terms)...)

const STensorBra = STensor{AbstractBra}
maketerm(::Type{STensorBra}, f, a, t, m) = f(a...)
Base.show(io::IO, x::STensorBra) = print(io, join(map(string, arguments(x)),""))
const STensorKet = STensor{AbstractKet}
maketerm(::Type{STensorKet}, f, a, t, m) = f(a...)
Base.show(io::IO, x::STensorKet) = print(io, join(map(string, arguments(x)),""))
const STensorOperator = STensor{AbstractOperator}
maketerm(::Type{STensorOperator}, f, a, t, m) = f(a...)
Base.show(io::IO, x::STensorOperator) = print(io, join(map(string, arguments(x)),"⊗"))
const STensorSuperOperator = STensor{AbstractSuperOperator}
maketerm(::Type{STensorSuperOperator}, f, a, t, m) = f(a...)
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 Expand Up @@ -206,6 +223,7 @@ arguments(x::SCommutator) = [x.op1, x.op2]
operation(x::SCommutator) = commutator
head(x::SCommutator) = :commutator
children(x::SCommutator) = [:commutator, x.op1, x.op2]
maketerm(::Type{SCommutator}, f, a, t, m) = f(a...)
commutator(o1::Symbolic{AbstractOperator}, o2::Symbolic{AbstractOperator}) = SCommutator(o1, o2)
commutator(o1::SZeroOperator, o2::Symbolic{AbstractOperator}) = SZeroOperator()
commutator(o1::Symbolic{AbstractOperator}, o2::SZeroOperator) = SZeroOperator()
Expand Down Expand Up @@ -237,6 +255,7 @@ arguments(x::SAnticommutator) = [x.op1, x.op2]
operation(x::SAnticommutator) = anticommutator
head(x::SAnticommutator) = :anticommutator
children(x::SAnticommutator) = [:anticommutator, x.op1, x.op2]
maketerm(::Type{SAnticommutator}, f, a, t, m) = f(a...)
anticommutator(o1::Symbolic{AbstractOperator}, o2::Symbolic{AbstractOperator}) = SAnticommutator(o1, o2)
anticommutator(o1::SZeroOperator, o2::Symbolic{AbstractOperator}) = SZeroOperator()
anticommutator(o1::Symbolic{AbstractOperator}, o2::SZeroOperator) = SZeroOperator()
Expand Down
5 changes: 5 additions & 0 deletions src/QSymbolicsBase/basic_ops_inhomogeneous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ arguments(x::SApplyKet) = [x.op,x.ket]
operation(x::SApplyKet) = *
head(x::SApplyKet) = :*
children(x::SApplyKet) = [:*,x.op,x.ket]
maketerm(::Type{SApplyKet}, f, a, t, m) = f(a...)
Base.:(*)(op::Symbolic{AbstractOperator}, k::Symbolic{AbstractKet}) = SApplyKet(op,k)
Base.:(*)(op::SZeroOperator, k::Symbolic{AbstractKet}) = SZeroKet()
Base.:(*)(op::Symbolic{AbstractOperator}, k::SZeroKet) = SZeroKet()
Expand Down Expand Up @@ -55,6 +56,7 @@ arguments(x::SApplyBra) = [x.bra,x.op]
operation(x::SApplyBra) = *
head(x::SApplyBra) = :*
children(x::SApplyBra) = [:*,x.bra,x.op]
maketerm(::Type{SApplyBra}, f, a, t, m) = f(a...)
Base.:(*)(b::Symbolic{AbstractBra}, op::Symbolic{AbstractOperator}) = SApplyBra(b,op)
Base.:(*)(b::SZeroBra, op::Symbolic{AbstractOperator}) = SZeroBra()
Base.:(*)(b::Symbolic{AbstractBra}, op::SZeroOperator) = SZeroBra()
Expand All @@ -81,6 +83,7 @@ arguments(x::SBraKet) = [x.bra,x.ket]
operation(x::SBraKet) = *
head(x::SBraKet) = :*
children(x::SBraKet) = [:*,x.bra,x.ket]
maketerm(::Type{SBraKet}, f, a, t, m) = f(a...)
Base.:(*)(b::Symbolic{AbstractBra}, k::Symbolic{AbstractKet}) = SBraKet(b,k)
Base.:(*)(b::SZeroBra, k::Symbolic{AbstractKet}) = 0
Base.:(*)(b::Symbolic{AbstractBra}, k::SZeroKet) = 0
Expand All @@ -99,6 +102,7 @@ arguments(x::SSuperOpApply) = [x.sop,x.op]
operation(x::SSuperOpApply) = *
head(x::SSuperOpApply) = :*
children(x::SSuperOpApply) = [:*,x.sop,x.op]
maketerm(::Type{SSuperOpApply}, f, a, t, m) = f(a...)
Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) = SSuperOpApply(sop,op)
Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::SZeroOperator) = SZeroOperator()
Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k))
Expand Down Expand Up @@ -128,6 +132,7 @@ arguments(x::SOuterKetBra) = [x.ket,x.bra]
operation(x::SOuterKetBra) = *
head(x::SOuterKetBra) = :*
children(x::SOuterKetBra) = [:*,x.ket,x.bra]
maketerm(::Type{SOuterKetBra}, f, a, t, m) = f(a...)
Base.:(*)(k::Symbolic{AbstractKet}, b::Symbolic{AbstractBra}) = SOuterKetBra(k,b)
Base.:(*)(k::SZeroKet, b::Symbolic{AbstractBra}) = SZeroOperator()
Base.:(*)(k::Symbolic{AbstractKet}, b::SZeroBra) = SZeroOperator()
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
5 changes: 4 additions & 1 deletion src/QSymbolicsBase/predefined.jl
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ arguments(x::SProjector) = [x.ket]
operation(x::SProjector) = projector
head(x::SProjector) = :projector
children(x::SProjector) = [:projector,x.ket]
maketerm(::Type{SProjector}, f, a, t, m) = f(a...)
projector(x::Symbolic{AbstractKet}) = SProjector(x)
projector(x::SZeroKet) = SZeroOperator()
basis(x::SProjector) = basis(x.ket)
Expand All @@ -236,7 +237,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 All @@ -261,6 +262,7 @@ arguments(x::SDagger) = [x.obj]
operation(x::SDagger) = dagger
head(x::SDagger) = :dagger
children(x::SDagger) = [:dagger, x.obj]
maketerm(::Type{SDagger}, f, a, t, m) = f(a...)
dagger(x::Symbolic{AbstractBra}) = SDagger{AbstractKet}(x)
dagger(x::Symbolic{AbstractKet}) = SDagger{AbstractBra}(x)
dagger(x::Symbolic{AbstractOperator}) = SDagger{AbstractOperator}(x)
Expand Down Expand Up @@ -309,6 +311,7 @@ arguments(x::SInvOperator) = [x.op]
operation(x::SInvOperator) = inv
head(x::SInvOperator) = :inv
children(x::SInvOperator) = [:inv, x.op]
maketerm(::Type{SInvOperator}, f, a, t, m) = f(a...)
basis(x::SInvOperator) = basis(x.op)
Base.show(io::IO, x::SInvOperator) = print(io, "$(x.op)⁻¹")
Base.:(*)(invop::SInvOperator, op::SOperator) = isequal(invop.op, op) ? IdentityOp(basis(op)) : SMulOperator(invop, op)
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
Loading
Loading