Basic Structures#
import tequila as tq
import numpy
In quantum computing you can basically represent everything with Paulistrings - tensor products of Pauli matrices. In tequila a Paulistring \(P\) can be used to initialize Hamiltonians (that will define measurements) $\( H = \sum_k c_k P_k \)\( and quantum gates as \)\( U(a) = e^{-i\frac{a}{2} P} \)$ where the minus and the 1/2 are convention.
Hamiltonians#
Here are some examples on how to initialize Hamiltonians on qubits
P = tq.QubitHamiltonian("X(0)Y(1)") # string based
P = tq.paulis.X(0)*tq.paulis.Y(1)
print(P)
+1.0000X(0)Y(1)
H = P + tq.paulis.X([0,1]) + 2.0
print(H)
+1.0000X(0)Y(1)+1.0000X(0)X(1)+2.0000
Often useful for demonstrations: You can convert to a full matrix (not recommended for large qubit numbers though).
matrix = H.to_matrix()
print(matrix)
[[2.+0.j 0.+0.j 0.+0.j 1.-1.j]
[0.+0.j 2.+0.j 1.+1.j 0.+0.j]
[0.+0.j 1.-1.j 2.+0.j 0.+0.j]
[1.+1.j 0.+0.j 0.+0.j 2.+0.j]]
Some convenience functions are implemented, like tq.paulis.Projector
that realizes the projector
as a sum over Paulistrings. The Wavefunction can be given in the form of a tq.QubitWaveFunction
(either returned from a simulation or manually initialized from strings or arrays).
wfn = "1.0|10> + 1.0|01>"
Proj = tq.paulis.Projector(wfn)
print(Proj)
print(Proj*Proj) # wfn was not normalized in this example
+0.5000-0.5000Z(0)Z(1)+0.5000X(0)X(1)+0.5000Y(0)Y(1)
+1.0000-1.0000Z(0)Z(1)+1.0000X(0)X(1)+1.0000Y(0)Y(1)
wfn = tq.QubitWaveFunction("1.0|10> + 1.0|01>").normalize()
Proj = tq.paulis.Projector(wfn)
print(Proj)
print(Proj*Proj)
+0.2500-0.2500Z(0)Z(1)+0.2500X(0)X(1)+0.2500Y(0)Y(1)
+0.2500-0.2500Z(0)Z(1)+0.2500X(0)X(1)+0.2500Y(0)Y(1)
# array based
v,vv = numpy.linalg.eigh(H.to_matrix())
wfn = tq.QubitWaveFunction(vv[:,0])
Proj = tq.paulis.Projector(wfn)
print(Proj)
print(Proj*Proj)
+0.2500+0.2500Z(0)Z(1)-0.1768X(0)X(1)-0.1768X(0)Y(1)-0.1768Y(0)X(1)+0.1768Y(0)Y(1)
+0.2500+0.2500Z(0)Z(1)-0.1768X(0)X(1)-0.1768X(0)Y(1)-0.1768Y(0)X(1)+0.1768Y(0)Y(1)+0.0000iZ(1)+0.0000iZ(0)
Circuits#
Circuits can be assembled from primitive Pauli-rotations. All gates can be controlled with the keyword control=[list of qubits]
and parametrized with the keyword angle=number/abstract-tequila-object
.
U = tq.gates.Rp(paulistring="X(0)Y(1)", angle=1.0)
U+= tq.gates.Rp(paulistring="Y(0)X(1)", angle=1.0)
wfn = tq.simulate(U)
print(U)
print("U|00> = ", wfn)
circuit:
Exp-Pauli(target=(0, 1), control=(), parameter=1.0, paulistring=X(0)Y(1))
Exp-Pauli(target=(0, 1), control=(), parameter=1.0, paulistring=Y(0)X(1))
U|00> = +0.5403|00> +0.8415|11>
More on circuits#
If you prefer typical gates, you can compile the circuits.
More information on tequila circuits can be found here: https://jakobkottmann.com/posts/tq-circuits/
U = tq.compile_circuit(U)
print(U)
tq.draw(U) # uses cirq (change with backend=...)
circuit:
Ry(target=(0,), parameter=-1.5707963267948966)
Rx(target=(1,), parameter=1.5707963267948966)
X(target=(1,), control=(0,))
Rz(target=(1,), parameter=1.0)
X(target=(1,), control=(0,))
Ry(target=(0,), parameter=1.5707963267948966)
Rx(target=(1,), parameter=-1.5707963267948966)
Rx(target=(0,), parameter=1.5707963267948966)
Ry(target=(1,), parameter=-1.5707963267948966)
X(target=(1,), control=(0,))
Rz(target=(1,), parameter=1.0)
X(target=(1,), control=(0,))
Rx(target=(0,), parameter=-1.5707963267948966)
Ry(target=(1,), parameter=1.5707963267948966)
0: ───Y^-0.5───@─────────────@───Y^0.5────X^0.5────@─────────────@───X^-0.5───
│ │ │ │
1: ───X^0.5────X───Z^0.318───X───X^-0.5───Y^-0.5───X───Z^0.318───X───Y^0.5────
''
# useful
tq.gates.CNOT(0,1)
tq.gates.X(0, control=1) # same as above
tq.gates.Toffoli(0,1,2)
tq.gates.X(0, control=[1,2]) # same as Tofolli
tq.gates.SWAP(0,1)
tq.gates.H(0)
circuit:
H(target=(0,))
Wavefunctions#
The tq.QubitWaveFunction
is a primitive class that is convenient for some things, but it is not how tequila is supposed to be used (as we cannot access wavefunctions directly). Here are however a few features that sometimes come in handy when debugging or illustrating concepts
# initialization
wfn = tq.QubitWaveFunction("1.0|00> + 1.0|11>")
wfn = tq.QubitWaveFunction([1.0,0.0,0.0,1.0])
# inner products
c=wfn.inner(wfn)
# normalization
wfn=wfn.normalize()
# apply Paulistrings (does not work with circuits -- could be realized though)
op = tq.paulis.X(0)
wfn2 = op(wfn)
Exercise#
As it is not the main focus of tequila, we currently don’t have a U.to_matrix()
function for a quantum circuit. For circuits consisting of Pauli-Gates, this can however be realized via the following formula
Try to write a function that converts a tequila circuit into a matrix. Assume that all gates are Pauli-Gates.
# example
U = tq.gates.Rp(paulistring="X(0)", angle=numpy.pi)
M = 0.0
for gate in U.gates:
G = gate.make_generator()
a = gate.parameter
m = tq.numpy.cos(a/2) - 1.0j*tq.numpy.sin(a/2)*G
M += m
print(M)
-1.0000iX(0)
For Experts:#
# if you want to be rigorous, you can check if the generator is a single Pauli string
H1 = tq.paulis.X(0)*tq.paulis.Y(1)
H2 = tq.paulis.Z(0)*tq.paulis.Y(1)
H3 = H1 + H2
print(H1, " ", len(H1), " primitives")
print(H2, " ", len(H2), " primitives")
print(H1 + 1.0, " ", len(H1), " primitives") # will ignore global phases
print(H3, " ", len(H3), " primitives")
# and assure with gate.is_controlled() that there are no control qubits on the gate
# with `make_generator(include_control_qubits=True)` you can get the generator for the controlled gate
# which allows you to decompose the controlled-Pauli-Gate into a sequence of two primitive Pauli Gates
+1.0000X(0)Y(1) 1 primitives
+1.0000Z(0)Y(1) 1 primitives
+1.0000X(0)Y(1)+1.0000 1 primitives
+1.0000X(0)Y(1)+1.0000Z(0)Y(1) 2 primitives