Examples

All of these example codes and more can be found on GitHub.

Quantum Random Numbers

The most basic example is a quantum random number generator (QRNG). It can be found in the examples-folder of ProjectQ. The code looks as follows

# -*- coding: utf-8 -*-
# pylint: skip-file

"""Example of a simple quantum random number generator."""

from projectq import MainEngine
from projectq.ops import H, Measure

# create a main compiler engine
eng = MainEngine()

# allocate one qubit
q1 = eng.allocate_qubit()

# put it in superposition
H | q1

# measure
Measure | q1

eng.flush()
# print the result:
print("Measured: {}".format(int(q1)))

Running this code three times may yield, e.g.,

$ python examples/quantum_random_numbers.py
Measured: 0
$ python examples/quantum_random_numbers.py
Measured: 0
$ python examples/quantum_random_numbers.py
Measured: 1

These values are obtained by simulating this quantum algorithm classically. By changing three lines of code, we can run an actual quantum random number generator using the IBM Quantum Experience back-end:

$ python examples/quantum_random_numbers_ibm.py
Measured: 1
$ python examples/quantum_random_numbers_ibm.py
Measured: 0

All you need to do is:

--- /home/docs/checkouts/readthedocs.org/user_builds/projectq/checkouts/latest/examples/quantum_random_numbers.py
+++ /home/docs/checkouts/readthedocs.org/user_builds/projectq/checkouts/latest/examples/quantum_random_numbers_ibm.py
@@ -1,13 +1,15 @@
 # -*- coding: utf-8 -*-
 # pylint: skip-file
 
-"""Example of a simple quantum random number generator."""
+"""Example of a simple quantum random number generator using IBM's API."""
 
+import projectq.setups.ibm
 from projectq import MainEngine
+from projectq.backends import IBMBackend
 from projectq.ops import H, Measure
 
 # create a main compiler engine
-eng = MainEngine()
+eng = MainEngine(IBMBackend(), engine_list=projectq.setups.ibm.get_engine_list())
 
 # allocate one qubit
 q1 = eng.allocate_qubit()

Quantum Teleportation

Alice has a qubit in some interesting state \(|\psi\rangle\), which she would like to show to Bob. This does not really make sense, since Bob would not be able to look at the qubit without collapsing the superposition; but let’s just assume Alice wants to send her state to Bob for some reason. What she can do is use quantum teleportation to achieve this task. Yet, this only works if Alice and Bob share a Bell-pair (which luckily happens to be the case). A Bell-pair is a pair of qubits in the state

\[|A\rangle \otimes |B\rangle = \frac 1{\sqrt 2} \left( |0\rangle\otimes|0\rangle + |1\rangle\otimes|1\rangle \right)\]

They can create a Bell-pair using a very simple circuit which first applies a Hadamard gate to the first qubit, and then flips the second qubit conditional on the first qubit being in \(|1\rangle\). The circuit diagram can be generated by calling the function

from projectq import MainEngine
    Args:
        eng (MainEngine): MainEngine from which to allocate the qubits.

    Returns:
        bell_pair (tuple<Qubits>): The Bell-pair.
    """
    b1 = eng.allocate_qubit()
    b2 = eng.allocate_qubit()

with a main compiler engine which has a CircuitDrawer back-end, i.e.,

# -*- coding: utf-8 -*-
# pylint: skip-file

"""Example implementation of a quantum circuit generating a Bell pair state."""

import matplotlib.pyplot as plt
from teleport import create_bell_pair

from projectq import MainEngine
from projectq.backends import CircuitDrawer
from projectq.libs.hist import histogram
from projectq.setups.default import get_engine_list

# create a main compiler engine
drawing_engine = CircuitDrawer()
eng = MainEngine(engine_list=get_engine_list() + [drawing_engine])

qb0, qb1 = create_bell_pair(eng)

eng.flush()
print(drawing_engine.get_latex())

histogram(eng.backend, [qb0, qb1])
plt.show()

The resulting LaTeX code can be compiled to produce the circuit diagram:

$ python examples/bellpair_circuit.py > bellpair_circuit.tex
$ pdflatex bellpair_circuit.tex

The output looks as follows:

_images/bellpair_circuit.png

Now, this Bell-pair can be used to achieve the quantum teleportation: Alice entangles her qubit with her share of the Bell-pair. Then, she measures both qubits; one in the Z-basis (Measure) and one in the Hadamard basis (Hadamard, then Measure). She then sends her measurement results to Bob who, depending on these outcomes, applies a Pauli-X or -Z gate.

The complete example looks as follows:

 1# -*- coding: utf-8 -*-
 2# pylint: skip-file
 3
 4"""Example of a quantum teleportation circuit."""
 5
 6from projectq import MainEngine
 7    Args:
 8        eng (MainEngine): MainEngine from which to allocate the qubits.
 9
10    Returns:
11        bell_pair (tuple<Qubits>): The Bell-pair.
12    """
13    b1 = eng.allocate_qubit()
14    b2 = eng.allocate_qubit()
15
16    H | b1
17            then transforms to the state that Alice would like to send to Bob.
18        verbose (bool): If True, info messages will be printed.
19
20    """
21    # make a Bell-pair
22    b1, b2 = create_bell_pair(eng)
23
24    # Alice creates a nice state to send
25    psi = eng.allocate_qubit()
26    if verbose:
27        print("Alice is creating her state from scratch, i.e., |0>.")
28    state_creation_function(eng, psi)
29
30    # entangle it with Alice's b1
31    CNOT | (psi, b1)
32    if verbose:
33        print("Alice entangled her qubit with her share of the Bell-pair.")
34
35    # measure two values (once in Hadamard basis) and send the bits to Bob
36    H | psi
37    Measure | psi
38    Measure | b1
39    msg_to_bob = [int(psi), int(b1)]
40    if verbose:
41        print("Alice is sending the message {} to Bob.".format(msg_to_bob))
42
43    # Bob may have to apply up to two operation depending on the message sent
44    # by Alice:
45    with Control(eng, b1):
46        X | b2
47    with Control(eng, psi):
48        Z | b2
49
50    # try to uncompute the psi state
51    if verbose:
52        print("Bob is trying to uncompute the state.")
53    with Dagger(eng):
54        state_creation_function(eng, b2)
55
56    # check whether the uncompute was successful. The simulator only allows to
57    # delete qubits which are in a computational basis state.
58    del b2
59    eng.flush()
60
61    if verbose:
62        print("Bob successfully arrived at |0>")
63
64
65if __name__ == "__main__":
66    # create a main compiler engine with a simulator backend:
67    eng = MainEngine()
68
69    # define our state-creation routine, which transforms a |0> to the state
70    # we would like to send. Bob can then try to uncompute it and, if he
71    # arrives back at |0>, we know that the teleportation worked.
72    def create_state(eng, qb):
73        """Create a quantum state."""

and the corresponding circuit can be generated using

$ python examples/teleport_circuit.py > teleport_circuit.tex
$ pdflatex teleport_circuit.tex

which produces (after renaming of the qubits inside the tex-file):

_images/teleport_circuit.png

Shor’s algorithm for factoring

As a third example, consider Shor’s algorithm for factoring, which for a given (large) number \(N\) determines the two prime factor \(p_1\) and \(p_2\) such that \(p_1\cdot p_2 = N\) in polynomial time! This is a superpolynomial speed-up over the best known classical algorithm (which is the number field sieve) and enables the breaking of modern encryption schemes such as RSA on a future quantum computer.

A tiny bit of number theory

There is a small amount of number theory involved, which reduces the problem of factoring to period-finding of the function

\[f(x) = a^x\operatorname{mod} N\]

for some a (relative prime to N, otherwise we get a factor right away anyway by calling gcd(a,N)). The period r for a function f(x) is the number for which \(f(x) = f(x+r)\forall x\) holds. In this case, this means that \(a^x = a^{x+r}\;\; (\operatorname{mod} N)\;\forall x\). Therefore, \(a^r = 1 + qN\) for some integer q and hence, \(a^r - 1 = (a^{r/2} - 1)(a^{r/2}+1) = qN\). This suggests that using the gcd on N and \(a^{r/2} \pm 1\) we may find a factor of N!

Factoring on a quantum computer: An example

At the heart of Shor’s algorithm lies modular exponentiation of a classically known constant (denoted by a in the code) by a quantum superposition of numbers \(x\), i.e.,

\[|x\rangle|0\rangle \mapsto |x\rangle|a^x\operatorname{mod} N\rangle\]

Using \(N=15\) and \(a=2\), and applying this operation to the uniform superposition over all \(x\) leads to the superposition (modulo renormalization)

\[|0\rangle|1\rangle + |1\rangle|2\rangle + |2\rangle|4\rangle + |3\rangle|8\rangle + |4\rangle|1\rangle + |5\rangle|2\rangle + |6\rangle|4\rangle + \cdots\]

In Shor’s algorithm, the second register will not be touched again before the end of the quantum program, which means it might as well be measured now. Let’s assume we measure 2; this collapses the state above to

\[|1\rangle|2\rangle + |5\rangle|2\rangle + |9\rangle|2\rangle + \cdots\]

The period of a modulo N can now be read off. On a quantum computer, this information can be accessed by applying an inverse quantum Fourier transform to the x-register, followed by a measurement of x.

Implementation

There is an implementation of Shor’s algorithm in the examples folder. It uses the implementation by Beauregard, arxiv:0205095 to factor an n-bit number using 2n+3 qubits. In this implementation, the modular exponentiation is carried out using modular multiplication and shift. Furthermore it uses the semi-classical quantum Fourier transform [see arxiv:9511007]: Pulling the final measurement of the x-register through the final inverse quantum Fourier transform allows to run the 2n modular multiplications serially, which keeps one from having to store the 2n qubits of x.

Let’s run it using the ProjectQ simulator:

$ python3 examples/shor.py

projectq
--------
Implementation of Shor's algorithm.
Number to factor: 15

Factoring N = 15: 00000001

Factors found :-) : 3 * 5 = 15

Simulating Shor’s algorithm at the level of single-qubit gates and CNOTs already takes quite a bit of time for larger numbers than 15. To turn on our emulation feature, which does not decompose the modular arithmetic to low-level gates, but carries it out directly instead, we can change the line

86    r = Fraction(y).limit_denominator(N - 1).denominator
87
88    # return the (potential) period
89    return r
90
91
92# Filter function, which defines the gate set for the first optimization
93# (don't decompose QFTs and iQFTs to make cancellation easier)
94def high_level_gates(eng, cmd):
95    """Filter high-level gates."""
96    g = cmd.gate
97    if g == QFT or get_inverse(g) == QFT or g == Swap:
98        return True
99    if isinstance(g, BasicMathGate):

in examples/shor.py to return True. This allows to factor, e.g. \(N=4,028,033\) in under 3 minutes on a regular laptop!

The most important part of the code is

50
51   X | x[0]
52
53   measurements = [0] * (2 * n)  # will hold the 2n measurement results
54
55   ctrl_qubit = eng.allocate_qubit()
56
57   for k in range(2 * n):
58       current_a = pow(a, 1 << (2 * n - 1 - k), N)
59       # one iteration of 1-qubit QPE
60       H | ctrl_qubit
61       with Control(eng, ctrl_qubit):
62           MultiplyByConstantModN(current_a, N) | x
63
64       # perform inverse QFT --> Rotations conditioned on previous outcomes
65       for i in range(k):
66           if measurements[i]:
67               R(-math.pi / (1 << (k - i))) | ctrl_qubit
68       H | ctrl_qubit
69

which executes the 2n modular multiplications conditioned on a control qubit ctrl_qubit in a uniform superposition of 0 and 1. The control qubit is then measured after performing the semi-classical inverse quantum Fourier transform and the measurement outcome is saved in the list measurements, followed by a reset of the control qubit to state 0.