Symbolics
In this tutorial, we show how to work with symbolic operators using the Symbolics.jl package. Make sure you have Symbolics.jl
installed.
Construction and basic operations
First import the necessary packages:
using Symbolics
using PauliStrings
Define a symbolic Operator type where coefficients are Complex{Symbolics.Num}
:
OperatorSymbolic(N::Int) = Operator{paulistringtype(N),Complex{Num}}()
Initialize an empty 2-qubit symbolic operator:
N = 2
H = OperatorSymbolic(N)
println(typeof(H))
Operator{PauliString{2, UInt8}, Complex{Num}}
Now let's create a two-site Ising model, where the value of the transverse field is a symbolic variable h
@variables h
H += "Z", 1, "Z", 2
H += h, "X", 1
H += h, "X", 2
We can perform the usual operations supported for Operator
println(H)
println(H + H + 0.5)
println(trace_product(H, 4))
(h) 1X
(h) X1
(1.0) ZZ
(0.5) 11
(2h) 1X
(2h) X1
(2.0) ZZ
4.0(4(h^4) + (1 + 2(h^2))^2)
Simplification of symbolic operators
Here is a helper function that simplifies a symbolic operator:
"""
Simplifies an Operator defined with symbolic coefficients. Uses `Symbolics.simplify` to simplify the symbolic
expressions in each of the coefficients of `o`. Returns a new `Operator`.
"""
function simplify_op(o::Operator)
o2 = typeof(o)()
for i in 1:length(o)
c = simplify(o.coeffs[i])
if !iszero(c)
push!(o2.coeffs, c)
push!(o2.strings, o.strings[i])
end
end
return o2
end
Let's apply this to H^2
:
H2 = H^2
println(H2)
println(simplify_op(H2))
(1 + 2(h^2)) 11
(0.0) ZY
(0.0) YZ
(2(h^2)) XX
(1 + 2(h^2)) 11
(2(h^2)) XX
However, keep in mind that simplify
doesn't reduce all the expressions:
H3 = H^3
println(H3)
println(simplify_op(H3))
(2(h^3) + h*(1 + 2(h^2))) 1X
(-2.0(h^2)) YY
(2(h^3) + h*(1 + 2(h^2))) X1
(1 + 2(h^2)) ZZ
(2(h^3) + h*(1 + 2(h^2))) 1X
(-2.0(h^2)) YY
(2(h^3) + h*(1 + 2(h^2))) X1
(1 + 2(h^2)) ZZ
As a final example, let's calculate some commutators with another operator O
O1 = OperatorSymbolic(N)
O1 += "X", 1
O2 = commutator(H, O1)
O3 = commutator(H, O2)
println(O2)
println(O3)
(2.0im) YZ
(4.0h) YY
(4.0) X1
(-4h) ZZ
Substituting variables with numericald values
"""
Substitutes some or all of the variables in `o` according to the rule(s) in dict.
If all the substitutions are to concrete numeric values, then it will return an `Operator` with
`Complex64` coefficients.
"""
function substitute_op(o::Operator, dict::Dict)
o = simplify_op(o)
ps, cs = o.strings, o.coeffs
cs_expr = substitute.(o.coeffs, (dict,))
cs_vals = ComplexF64[]
# Attempt to convert all the coefficients to ComplexF64, not possible if one or more variables remained unassigned
all_vals = true
for c in cs_expr
try
push!(cs_vals, ComplexF64(Symbolics.value(c)))
catch
all_vals = false
break
end
end
if all_vals
return Operator{paulistringtype(qubitlength(o)),ComplexF64}(copy(ps), cs_vals)
else
return Operator{paulistringtype(qubitlength(o)),Complex{Num}}(copy(ps), cs_expr)
end
end
To substitute the variables for concrete numerical values we use substitute_op
O = substitute_op(O3, Dict(h => 0.5))
println(typeof(O))
println(O)
Operator{PauliString{2, UInt8}, ComplexF64}
(2.0 - 0.0im) YY
(4.0 + 0.0im) X1
(-2.0 + 0.0im) ZZ