Tequila Basic Usage¶
Create and Simulate Quantum Circuits¶
pip install --upgrade name
in your command lineimport tequila as tq
tq.show_available_simulators()
from numpy import pi
Create Simple Circuits¶
tq.gates
module.+
operation.Gates can receive the keyword arguments target
and control
which
defines qubit(s) on which the gates act
circuit = tq.gates.H(target=0) + tq.gates.CNOT(target=1,control=0)
backend
keyword, e.g. tq.draw(circuit,backend='cirq'
)print(circuit)
tq.draw(circuit)
Lets do the same with a list of qubits and see what happens
circuit = tq.gates.H(target=[0,1]) + tq.gates.X(target=1, control=0)
tq.draw(circuit)
angle
and some can be
parametrized by power
.Tequila uses the standard convention for qubit rotations:
which is carried over for multi-qubit rotations
# Some further examples
circuit0 = tq.gates.Ry(angle=1.0, target=0) + tq.gates.X(target=1, control=0)
circuit1 = tq.gates.Y(power=0.5, target=0) + tq.gates.Ry(angle=1.0, target=1, control=0)
circuit2 = tq.gates.Rp(angle=1.0, paulistring="Y(0)") + tq.gates.X(target=1, control=0) # acts the same as circuit0
circuit3 = tq.gates.Rp(angle=1.0, paulistring="X(0)Y(1)")
generator = tq.paulis.Y(0)
circuit4 = tq.gates.Trotterized(generators=[generator], angles=[1.0], steps=1) # acts the same as circuit0
generator = tq.paulis.X(0)*tq.paulis.Y(1)
circuit5 = tq.gates.Trotterized(generators=[generator], angles=[1.0], steps=1) # acts the same as circuit3
tq.draw(circuit3)
Simulate and Sample Simple Circuits¶
tq.simulate
function.QubitWaveFunction
wfn = tq.simulate(circuit)
print(wfn)
You can control on which backend the simulation is executed with the
backend=name
keyword where name is one of the backends that are
installed on your system (see first cell)
# simulate on 'qulacs' backend (which is the default)
# Note that this cell will crash if qulacs is not installed
# just switch the name with something that is installed on your system (check the first cell)
wfn = tq.simulate(circuit, backend='qulacs')
print(wfn)
If you don’t want to simulate a full wavefunction but rather simulate
individual samples from it you can pass down the samples=integer
keyword
measurements = tq.simulate(circuit, samples=10)
print(measurements)
You can access the individual measurements either by integers in binary notation or by binary strings
print(measurements(0))
print(measurements("00"))
print(measurements(2))
print(measurements("10"))
Individual Measurement instructions can be added to the circuit via
tq.gates.Measurement
measurements = tq.simulate(circuit+tq.gates.Measurement(target=[0]), samples=10)
print(measurements)
Create Parametrized Circuits¶
angle
and power
can be set to hashable types.# initialize the parametrized circuit
circuit = tq.gates.Ry(angle="a", target=0)
pi = tq.numpy.pi
# set the value we want to simulate
variables = {"a" : pi**2}
wfn = tq.simulate(circuit, variables=variables)
print(wfn)
extract_variables
from the circuit.print("circuit has variables: ", circuit.extract_variables())
a = tq.Variable("a")
circuit = tq.gates.Ry(angle=(a*pi)**2, target=0)
# set the value we want to simulate
variables = {"a" : 1.0}
wfn = tq.simulate(circuit, variables=variables)
print(wfn)
variable.apply(your_function)
.tq.numpy
since this will be
the jax
numpy used for automaticl differentiation.a = tq.Variable("a")
circuit = tq.gates.Ry(angle=((a*pi)**2).apply(tq.numpy.exp), target=0)
# set the value we want to simulate
variables = {"a" : 1.0}
wfn = tq.simulate(circuit, variables=variables)
print(wfn)
Lets do the same thing once more, but this time with a customized transformation (which will do the same as in the last cell)
# define your own transformation
def my_trafo(x):
return tq.numpy.exp(x**2)
a = tq.Variable("a")
# we will put the variable manipulation here for more overview
a = a*pi # a is now in a*pi
a = a.apply(my_trafo) # a is now exp((a*pi)**2)
circuit = tq.gates.Ry(angle=a, target=0)
# set the value we want to simulate
variables = {"a" : 1.0}
wfn = tq.simulate(circuit, variables=variables)
print(wfn)
circuit = tq.gates.Ry(angle=(1,"a", "its a stupid example"), target=0)
print(circuit.extract_variables())
circuit = tq.gates.Ry(angle=(1,2,3), target=0)
print(circuit.extract_variables())
Create and Simulate Objectives/Expectationvalues¶
Within tequila you can define qubit operators which can either be used
to generate gates and circuits over tq.gates.Rp
,
tq.gates.Trotterized
or tq.gates.GeneralizedRotation
or as
Hamiltonians defining the measurements on the quantum experiments.
QCircuit
and QubitHamiltonian
objects can be combined to
expectation values which can be combined and transformed to become more
general Objectives
(in tequila an expectation value is already
objective in its simplest form).
where \(H=\sigma_x=X\) and \(U\left(a\right)=Ry(a)\)
# the circuit
U = tq.gates.Ry(angle="a", target=0)
# the Hamiltonian
H = tq.paulis.X(0)
# the Objective (a single expectation value)
E = tq.ExpectationValue(H=H, U=U)
print("Hamiltonian ", H)
print(E)
# better not use it for large objectives
tq.draw(E)
backend
and sample
key in the same
way.Since the objective is defined with parametrized quantum circuits, the values of the variables have to be passed down in the same way as before.
Note that not all expectationvalues in the objective need to be parametrized and that the parameters don’t need to be the same.
variables = {"a": 1.0}
value = tq.simulate(E, variables=variables)
print("Objective({}) = {}".format(variables["a"], value))
split
function to get the hermitian and/or
antihermitian partFAQ
notebook for some more information.# Pauli Operators can be initialilzed and added/multipled
H = tq.paulis.X(qubit=[0,1,2,3]) + tq.paulis.Y(2) + tq.paulis.Z(qubit=[0,1])*tq.paulis.X(2)
print(H, " is hermitian = ", H.is_hermitian())
H = tq.paulis.Z(0)*tq.paulis.Y(0) + tq.paulis.X(0)
print(H, " is hermitian = ", H.is_hermitian())
hermitian_part, anti_hermitian_part = H.split()
print("hermitian part = ", hermitian_part)
print("anti-hermitian part = ", anti_hermitian_part)
H = tq.paulis.Projector("|00>")
print(H, " is hermitian = ", H.is_hermitian())
H = tq.paulis.Projector("1.0*|00> + 1.0*|11>")
print(H, " is hermitian = ", H.is_hermitian())
Lets proceed with our previous simple example of a single qubit rotation and a single X as Hamiltonian.
If you intent to evaluate the objective with lots of different choices of the variables it is useful to compile it. A compiled objective is tied to a specific backend and can be used like a function taking a dictionary of variables.
The compile function takes also the backend
and sample
keyword.
If no backend is chosen tequila will pick automatically from the
installed ones. If you intent to sample your objective instead of fully
simulate it you can give a dummy integer of samples to compile
. This
will help tequila pick the best available backend but will not fix the
number of samples for future evaluations.
Compiling first will make your code faster since then the abstract circuits do not need to be re-translated to the backend every time.
compiled_objective = tq.compile(E)
# the compiled objective can now be used like a function
for value in [0.0, 0.5, 1.0]:
evaluated = compiled_objective(variables={"a": value})
print("objective({}) = {}".format(value, evaluated))
Lets simulate our compiled objetive and plot the results
def compile_and_evaluate(objective, steps=25, samples=None, start=0, stop=2*pi):
from matplotlib import pyplot as plt
plt.figure()
compiled_objective = tq.compile(objective, samples=samples)
values = [start + (stop-start)*step/steps for step in range(steps)]
evaluated = [compiled_objective(variables={"a": value}, samples=samples) for value in values]
plt.plot(values, evaluated)
plt.show()
return values, evaluated
compile_and_evaluate(E);
# this is the new objective
L = E**2 + 1
compile_and_evaluate(L);
And the same, but shifted by \(e^{-a^2}\), so the shift is not constant and will only affect \(L\) for small values of \(a\).
# For completeness we initialize the variable again here
a = tq.Variable("a")
# to be sure that the variable is the same as the one from the objective we could also do
# a = objective.extract_variables()[0]
# this is the new objective
L = E**2 + (-a**2).apply(tq.numpy.exp)
compile_and_evaluate(L);
Now we also transform the expectation value in a more complicated way. It works the same way as it works for variables
# For completeness we initialize the variable again here
a = tq.Variable("a")
# to be sure that the variable is the same as the one from the objective we could also do
# a = objective.extract_variables()[0]
# this is the new objective
L = E**2 + (-a**2).apply(tq.numpy.exp)*E
compile_and_evaluate(L);
# check how many (unique) expectation values are in the objective
print(L)
# For completeness we initialize the variable again here
a = tq.Variable("a")
# to be sure that the variable is the same as the one from the objective we could also do
# a = objective.extract_variables()[0]
# this is the new objective
L = E**2 + (-a**2).apply(tq.numpy.exp)*E
compile_and_evaluate(L, samples=1000);
# check how many (unique) expectation values are in the objective
print(L)
Derivatives of Objectives¶
Derivatives of objectives are objectives themselves. They can be simply
created by applying tq.grad
on a objective.
Lets take first objective and plot its derivative. The first is easy to check since the derivative should just be the shifted sinus curve. Feel free to change the objective and play around.
L = E
dLda = tq.grad(L, "a")
d2Ld2a = tq.grad(dLda, "a")
print("Objective:\n", L)
compile_and_evaluate(L);
print("First Derivative:\n",dLda)
compile_and_evaluate(dLda);
print("Second Derivative:\n",d2Ld2a)
compile_and_evaluate(d2Ld2a);
# another example
L = E**2 + (-a**2).apply(tq.numpy.exp)*E
dLda = tq.grad(L, "a")
d2Ld2a = tq.grad(dLda, "a")
print("Objective:\n", L)
compile_and_evaluate(L);
print("First Derivative:\n",dLda)
compile_and_evaluate(dLda);
print("Second Derivative:\n",d2Ld2a)
compile_and_evaluate(d2Ld2a);
Optimization example¶
At last we will create a small toy objective and use the phoenics optimizer to find local minima.
As expectation value we will use an entangled circuit with one CNOT gate and one Ry rotation and an arbitrary chosen Hamiltonian.
Our objective is defined as
with
and
# All in one
a = tq.Variable("a")
U = tq.gates.Ry(angle=(-a**2).apply(tq.numpy.exp)*pi, target=0)
U += tq.gates.X(target=1, control=0)
H = tq.QubitHamiltonian.from_string("-1.0*X(0)X(1)+0.5Z(0)+Y(1)")
E = tq.ExpectationValue(H=H, U=U)
dE = tq.grad(E, "a")
objective = E + (-dE**2).apply(tq.numpy.exp)
param, values = compile_and_evaluate(objective, steps=100, start =-5, stop=5);
# We need to specify the phoenics config here
# the only change is that we are changing the interval for periodic boundary conditions to [-3,3] istead df [0, 2pi]
# not that the original interval would also work here
# we are restricting the search to 10 iterations here
# check out the notebook on phoenics for more information
phoenics_config = {'general': {'auto_desc_gen': 'False', 'batches': 5, 'boosted': 'False', 'parallel': 'False'}, 'parameters': [{'name': a, 'periodic': 'True', 'type': 'continuous', 'size': 1, 'low': -3., 'high': 3.}], 'objectives': [{'name': 'Energy', 'goal': 'minimize'}]}
result = tq.minimize(method='phoenics', objective = objective, phoenics_config = phoenics_config, maxiter=10)
# plot the points phoenics visited
# the information is in the optimizer history
# we have 'angles' and `energies' giving us information about every evaluation
import matplotlib.pyplot as plt
points = {}
energies = result.history.energies
angles = result.history.angles
for it, energy in enumerate(energies):
angle = angles[it][tq.Variable("a")]
points[angle] = energy
plt.plot(param, values, linestyle="--")
plt.plot(list(points.keys()), list(points.values()), marker = "o", linestyle=" ", label="phoenics")
plt.legend()
plt.show()
In case you want to play with different optimizers (see also the SciPyOptimizers and FAQ notebooks)
tq.show_available_optimizers()