import tequila as tq
import numpy
Frequently Asked Questions
It is recommended to take a look at the BasicUsage
notebook before looking at this
Which optimization methods can I use?
tq.show_available_optimizers
shows you all installed modules on your systems and the methods which tq.minimize
understands. Method names are not case sensitive when passed to tq.minimize
.
In the end you see which modules are supported and which of them are actually installed on your system.
The table with methods and modules will only show you the methods for modules that are currently installed within your environment.
Of course you can also use tequila objectives for your own optimizers.
You don’t need to use the modules here.
tq.show_available_optimizers()
available methods for optimizer modules found on your system:
method | optimizer module
--------------------------
NELDER-MEAD | scipy
COBYLA | scipy
POWELL | scipy
SLSQP | scipy
L-BFGS-B | scipy
BFGS | scipy
CG | scipy
TNC | scipy
TRUST-KRYLOV | scipy
NEWTON-CG | scipy
DOGLEG | scipy
TRUST-NCG | scipy
TRUST-EXACT | scipy
TRUST-CONSTR | scipy
adam | gd
adagrad | gd
adamax | gd
nadam | gd
sgd | gd
momentum | gd
nesterov | gd
rmsprop | gd
rmsprop-nesterov | gd
spsa | gd
gpyopt-lbfgs | gpyopt
gpyopt-direct | gpyopt
gpyopt-cma | gpyopt
Supported optimizer modules: ['scipy', 'gpyopt', 'gd']
Installed optimizer modules: ['scipy', 'gd', 'gpyopt']
Which simulators/Quantum-Backends can I use?
tq.show_available_simulators
shows all simulators/quantum backends which are supported by tequila as well as which are installed within your current environment.
The default choice if you don’t specify a backend when for example simulating a tequila objective with tq.simulate
is the first entry of the supported backends which is installed on your system.
tq.show_available_simulators()
backend | wfn | sampling | noise | installed
--------------------------------------------------------------------
qulacs_gpu | False | False | False | False
qulacs | True | True | True | True
qibo | False | False | False | False
qiskit | False | False | False | False
cirq | False | False | False | False
pyquil | True | True | True | True
symbolic | True | False | False | True
qlm | False | False | False | False
Can I avoid re-translation/compilation on my objectives/circuits?
Yes, you can. By calling tq.compile
instead of tq.simulate
. This will give you back a callable objective.
Check also the basic usage
tutorial notebook
= tq.gates.H(target=1) + tq.gates.Rx(angle="a", target=0, control=1)
U
# simulate the wavefunction with different variables
= tq.simulate(U, variables={"a": 1.0})
wfn0 = tq.simulate(U, variables={"a": 2.0})
wfn1
print(wfn0)
print(wfn1)
# the same, but avoiding re-compilation
# Note that your compiled object is translated to a quantum backend
# if the backend was not set, tequila it will pick the default which depends
# on which backends you have installed. You will seee it in the printout of the
# compiled circuits
= tq.compile(U)
compiled_U = compiled_U(variables={"a":1.0})
wfn0 = compiled_U(variables={"a":2.0})
wfn1
print("compiled circuit:", compiled_U)
print(wfn0)
print(wfn1)
# With Objectives it works in the same way
= tq.paulis.Y(0)
H = tq.ExpectationValue(H=H, U=U)
E = E**2 + 1.0
objective
# simulate the objective with different variables
= tq.simulate(objective, variables={"a": 1.0})
result0 = tq.simulate(objective, variables={"a": 2.0})
result1
print("compiled objective:", objective)
print(result0)
print(result1)
# compile and then simulate
= tq.compile(objective)
compiled_objective = compiled_objective(variables={"a":1.0})
result0 = compiled_objective(variables={"a":2.0})
result1
print("compiled objective:", compiled_objective)
print(result0)
print(result1)
+0.7071|00> +0.6205|01> -0.3390i|11>
+0.7071|00> +0.3821|01> -0.5950i|11>
compiled circuit: <tequila.simulators.simulator_qulacs.BackendCircuitQulacs object at 0x3075b72e0>
+0.7071|00> +0.6205|01> -0.3390i|11>
+0.7071|00> +0.3821|01> -0.5950i|11>
compiled objective: Objective with 1 unique expectation values
total measurements = 1
variables = [a]
types = not compiled
1.1770183545683928
1.2067054526079515
compiled objective: Objective with 1 unique expectation values
total measurements = 1
variables = [a]
types = [<class 'tequila.simulators.simulator_qulacs.BackendExpectationValueQulacs'>]
1.1770183545683928
1.2067054526079515
How can I emulate a real quantum computer?
Emulation is performed similarly to running on real devices. All you need to do is pass down the right string to the ‘device’ keyword. For qiskit, these are the same as for regular backends, but have ‘fake_’ at the beginning; I.E to emulate ‘armonk;, set device="fake_armonk"
. For PyQuil, this is done by adding’-qvm’ to the end of the chosen string, i.e, ‘Aspen-8’ becomes device=Aspen-8-qvm'
. For Cirq, only emulation is currently possible; the only string options for cirq are ‘foxtail’,‘bristlecone’,‘sycamore’, and ‘sycamore23’.
When emulating, a few things about the real device will be mimicked, principally its native gate set and its connectivities. Emulation will NOT include noisy emulation by default; If you want to emulate noise, pass down the keyword noise='device'
. Using this option without specifying a device will result in an error.
Below, we will emulate pyquil’s Aspen 8, with emulated noise. You need pyquil installed for this to work.
additionally: when real backends cannot be accessed, emulation will be attempted, with a warning.
= tq.gates.Ry(angle="a", target=0)
U = tq.paulis.X(0)
H = tq.ExpectationValue(H=H, U=U)
E
# simulate the square of the expectation value with a specific set of variables
= tq.simulate(E**2, variables={"a":1.0}, samples=1000, backend="pyquil")
result print('sampling from pyquil yielded: ', result)
= tq.simulate(E**2, variables={"a":1.0}, samples=1000, backend="pyquil",device='Aspen-8-qvm')
result print('sampling from pyquil while emulating Aspen-8 yielded: ', result)
= tq.optimizer_scipy.minimize(E**2, initial_values={"a":1.0}, samples=1000,
result ='pyquil', device="Aspen-8-qvm",
backend='device')
noiseprint('optimizing while emulating Aspen-8 with noise yielded a best energy of: ', result.energy)
QVMError: Could not communicate with QVM at http://127.0.0.1:5000
Can I compile Objectives into different backends?
Yes you can. Tequila will print a warning if this happens. Warnings can be ignored by filtering them out (see the python warnings documentation)
If a compiled circuit is used as input to compile then tequila will re-compile the circuit to the new backend (it it differs from the previous one)
If a compiled objective is used as input to compile then tequila will only compile non-compiled expectationvalues into the different backend. Already compiled expectation values will remain untouched
Note that you need at least two different backends for the following cell to execute.
Just change the key to whatever you have installed.
= "qulacs"
backend1 = "cirq"
backend2
= tq.gates.X(target=[0,1])
U print("Example Circuit: ", U)
= tq.compile(U, backend=backend1)
compiled_1 = tq.compile(compiled_1, backend=backend2)
compiled_2 print("Circuit compiled to {} -> ".format(backend1), compiled_1)
print("Circuit compiled to {} -> ".format(backend1), compiled_1)
= tq.paulis.X(0)*tq.paulis.Y(1) + tq.paulis.X([0,1])
H print("\nmake objective with H = ", H)
= tq.ExpectationValue(H=H, U=U)
objective = tq.compile(objective, backend=backend1)
compiled_1
print("\nExpectationValues of objective 1:")
print(compiled_1)
= compiled_1 + objective # Its recommended to avoid those hybrids, but in principle it works
objective2
print("\nExpectationValues of partly compiled objective:")
print(objective2)
= tq.compile(objective2, backend=backend2)
compiled_2 print("\nExpectationValues of hybdrid compiled objective:")
print(compiled_2)
How do I transform Measurements into Hamiltonians?
We can not answer this question in general, but we can try to give a small example here.
Assume you have a quantum circuit with \(4\) Qubits and you are measuring Qubit \(0\) and \(2\). You define your cost function in the following way:
\[ L(AB) = A + B, \qquad A,B \in \left\{ 0,1 \right\} \]
meaning you accumulate the number of ones measured in your circuit.
The corresponding expectationvalue would be
\[ L = \langle \Psi \rvert H \lvert \Psi \rangle \qquad H = 1 - \frac{1}{2}\left(Z(0) + Z(1)\right) \]
The Hamiltonian could also be written as
\[ H = 2\lvert 11 \rangle \langle 11 \rvert + \lvert 10 \rangle \langle 10 \rvert + \lvert 01 \rangle \langle 01 \rvert \]
Tequila provides the convenience function tq.gates.Projector
to initialize Hamiltonians like that
2*tq.paulis.Projector("|11>") + tq.paulis.Projector("|01>") + tq.paulis.Projector("|10>")
The projector can also be initialized with more structured QubitWaveFunction
s which can itself be initialized from array or string.
Here are some examples
= tq.QubitWaveFunction.from_string("1.0*|00> + 1.0*|11>")
wfn = tq.QubitWaveFunction.from_array(arr=[1.0, 0.0, 0.0, 1.0])
wfnx print(wfn == wfnx)
= wfn.normalize()
wfn print(wfn)
= tq.paulis.Projector(wfn=wfn)
P print("P = ", P)
Apart from Projector
there is also KetBra
which intialized more general operators like \[
\lvert \Psi \rangle \langle \Phi \rvert
\]
Keep in mind that those are not hermitian.
But they can be split up into their hermitian and anti hermitian part where both can then be used as hamiltonians for expectationvalues.
If the hermitian = True
key is set, the function returns the hermitian version of the operator (which is the same as the hermitian part of the old operator)
\[ \frac{1}{2}\left(\lvert \Psi \rangle \langle \Phi \rvert + \lvert \Phi \rangle \langle \Psi \rvert \right) \]
= tq.QubitWaveFunction.from_string("1.0*|00> + 1.0*|11>").normalize()
wfn1
= tq.paulis.KetBra(bra=wfn1, ket="|00>")
op
= op.split()
H1, H2
print("operator=", op)
print("hermitian part = ", H1)
print("anti-hermitian part =", H2)
= tq.paulis.KetBra(bra=wfn1, ket="|00>", hermitian=True)
H print("hermitianized operator = ", H)
Can I do basic operations on wavefunctions and operators without quantum backends?
In principle yes. But keep in mind that tequila was not made for this.
However, some of those operations might come in handy for debugging or small examples.
You can not execute circuits without a simulator since they are just abstract data types (no matrices or anything). Tequila has however its own small debug simulator backend = symbolic
but there is no reason to use it if you have any other quantum backend installed.
Hamiltonians can be converted to matrices.
We give a few examples here
= tq.QubitWaveFunction.from_string("1.0*|0> + 1.0*|1>").normalize()
wfn = 1.0/numpy.sqrt(2.0)*(tq.paulis.Z(0) + tq.paulis.X(0))
H = wfn.apply_qubitoperator(H).simplify()
wfn2
print("|wfn> = ", wfn)
print("H = ", H)
print("H|wfn> = ", wfn2)
= tq.QubitWaveFunction.from_string("1.0*|0> + 1.0*|1>").normalize()
wfn1 = tq.QubitWaveFunction.from_string("1.0*|0> - 1.0*|1>").normalize()
wfn2 print("<wfn1|wfn2> = ", wfn1.inner(wfn2))
= 1.0/numpy.sqrt(2.0)*(tq.paulis.Z(0) + tq.paulis.X(0))
H print(H.to_matrix())
Can I import an Hamiltonian from OpenFermion?
Yes! OpernFermion is currently tequilas backend for Hamiltonians, which makes importing from it straight forward. You just need to wrap the OpenFermion QubitOperator into tequilas QubitHamiltonian.
We show a few examples
from openfermion import QubitOperator
# get OpenFermion QubitOperator from tequila QubitHamiltonian
= tq.paulis.X(0)
H = H.to_openfermion()
of_operator
print("{} = {}".format(type(H), H))
print("{} = {}".format(type(of_operator), of_operator))
# init tequila QubitHamiltonian with OpenFermion QubitOperator
= tq.QubitHamiltonian.from_openfermion(of_operator)
H print("{} = {}".format(type(H), H))
# initialization from file os often read in the string form
= str(of_operator)
of_string = str(H)
tq_string
print(of_string)
print(tq_string)
= tq.QubitHamiltonian.from_string(string=of_string, openfermion_format=True)
H print(H)
= tq.QubitHamiltonian.from_string(string=tq_string, openfermion_format=False)
H print(H)
Can I compile into a regular function instead of one which takes dictionaries?
Not recommended but yes. The order of the function arguments is the order you get from extract_variables
= tq.gates.Ry(angle="a", target=0)
U += tq.gates.X(power = "b", target=1)
U = tq.QubitHamiltonian.from_string("X(0)Z(1)")
H = tq.ExpectationValue(H=H, U=U)
E
= tq.compile_to_function(E)
f
print("order is : ", E.extract_variables())
print(f(0.5, 1.0))
print(tq.simulate(E, variables={"a":0.5, "b":1.0}))
If you also want to fix the samples and other entries to your compiled objective you can build wrappers
def mywrapper(compiled_obj, samples):
return lambda *x: compiled_obj(*x, samples=samples)
= mywrapper(f, samples=100)
wrapped
# don't expect same results, since samples are taken individually
print(wrapped(1.0, 0.5)) # always takes 100 samples
print(f(1.0, 0.5, samples=100)) # samples need to be given
print(f(1.0, 0.5, samples=1000)) # but sampling rate can be changed
print(f(1.0, 0.5)) # you can go back to full simulation which you cannot with the wrapped function
How do numerical gradients work?
Yes this is possible by passing for example gradient={'method':'2-point', 'stepsize': 1.e-4}
to the tq.minimize
function.
The default is a central 2-point derivative stencil where h
is the stepsize: \[\displaystyle
\frac{\partial f}{\partial a} = \frac{f(a+\frac{h}{2}) - f(a-\frac{h}{2})} {h}
\]
Other methods are: 2-point-forward
: Forward derivative stencil:
\[\displaystyle \frac{\partial f}{\partial a} = \frac{f(a+h) - f(a)} {h} \]
2-point-backward
: Backward derivative stencil:
\[\displaystyle \frac{\partial f}{\partial a} = \frac{f(a) - f(a-h)} {h} \]
You can also use your own numerical derivative stencil by passing a callable function as method
.
The function should have the signature which is given in the example below.
Here is an example:
import tequila as tq
# form a simple example objective
= tq.paulis.X(0)
H = tq.gates.Ry(angle="a", target=0)
U = tq.ExpectationValue(U=U, H=H)
E
# make it more interesting by using analytical gradients for the objective
# and numerical gradients to optimize it
= tq.grad(E, 'a')**2 # integer multiples of pi/2 are minima
objective
# start from the same point in all examples
= {'a': 2.3} initial_values
# optimize with analytical derivatives
= tq.minimize(objective=objective, method="bfgs", initial_values=initial_values)
result #result.history.plot("energies")
#result.history.plot("gradients")
# optimize with 2-point stencil
= tq.minimize(objective=E, method="bfgs", gradient={'method': '2-point', 'stepsize':1.e-4}, initial_values=initial_values)
result #result.history.plot("energies")
#result.history.plot("gradients")
# optimize with custom stencil
# here this is the same as the default
import copy
def mymethod(obj, angles, key, step, *args, **kwargs):
= copy.deepcopy(angles)
left += step / 2
left[key] = copy.deepcopy(angles)
right -= step / 2
right[key] return 1.0 / step * (obj(left, *args, **kwargs) - obj(right, *args, **kwargs))
= tq.minimize(objective=E, method="bfgs", gradient={'method': mymethod, 'stepsize':1.e-4}, initial_values=initial_values)
result #result.history.plot("energies")
#result.history.plot("gradients")
# optimize with a scipy method and use scipys 2-point
# the scipy protocol will have more function evaluations and less gradient evaluations for some methods
# the stepsize in scipy has to be passed with the `method_options` dictionary
# with the keyword `eps`
= tq.minimize(objective=E, method="bfgs", gradient='2-point', method_options={'eps':1.e-4}, initial_values=initial_values)
result #result.history.plot("energies")
Can I use the numerical gradient protocols from SciPy?
Yes you can for all scipy methods by passing gradient='2-point'
to tq.minimize
.
See the scipy documentation for the stepsize and more options which can be passed with method_options
dictionary where the key for the stepsize is usually eps
. Note that not all scipy methods support numerical gradients, but you can always fall back to tequilas numerical gradients. See the previous cell for an example.