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 all 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
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 @@
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])

Check warning on line 50 in ext/QuantumCliffordExt/QuantumCliffordExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/QuantumCliffordExt/QuantumCliffordExt.jl#L50

Added line #L50 was not covered by tests

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))...)

Check warning on line 58 in ext/QuantumCliffordExt/QuantumCliffordExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/QuantumCliffordExt/QuantumCliffordExt.jl#L58

Added line #L58 was not covered by tests
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 @@
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 @@
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)),""))

Check warning on line 181 in src/QSymbolicsBase/basic_ops_homogeneous.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/basic_ops_homogeneous.jl#L181

Added line #L181 was not covered by tests
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 @@
"0"=>"₀",
)
end

@latexrecipe function f(x::SBra)
return Expr(:latexifymerge, "\\left\\langle ", symbollabel(x), "\\right|")

Check warning on line 24 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L23-L24

Added lines #L23 - L24 were not covered by tests
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})

Check warning on line 29 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L29

Added line #L29 was not covered by tests
return LaTeXString("\\hat $(symbollabel(x))")
end
@latexrecipe function f(x::SZero)
return LaTeXString("\\bm{O}")

Check warning on line 33 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L32-L33

Added lines #L32 - L33 were not covered by tests
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")

Check warning on line 37 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L36-L37

Added lines #L36 - L37 were not covered by tests
else
return Expr(:latexifymerge, "\\left\\langle ", symbollabel(x), "\\right|")
return Expr(:latexifymerge, latexify(x.obj), "\\^\\dagger")

Check warning on line 39 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L39

Added line #L39 was not covered by tests
end
end
@latexrecipe function f(x::SScaled)
@latexrecipe function f(x::Union{SScaled,SMulOperator,SOuterKetBra,SApplyKet,SApplyBra})

Check warning on line 42 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L42

Added line #L42 was not covered by tests
cdot --> false
return _toexpr(x)
end

@latexrecipe function f(x::SCommutator)
return Expr(:latexifymerge, "\\left\\lbrack", latexify(x.op1), ",", latexify(x.op2), "\\right\\rbrack")

Check warning on line 47 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L46-L47

Added lines #L46 - L47 were not covered by tests
end
@latexrecipe function f(x::SAnticommutator)
return Expr(:latexifymerge, "\\left\\{", latexify(x.op1), ",", latexify(x.op2), "\\right\\}")

Check warning on line 50 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L49-L50

Added lines #L49 - L50 were not covered by tests
end
@latexrecipe function f(x::SBraKet)
return Expr(:latexifymerge, "\\left\\langle ", symbollabel(x.bra), "\\mid ", symbollabel(x.ket), "\\right\\rangle")

Check warning on line 53 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L52-L53

Added lines #L52 - L53 were not covered by tests
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}")

Check warning on line 63 in src/QSymbolicsBase/latexify.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/latexify.jl#L62-L63

Added lines #L62 - L63 were not covered by tests
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
Loading