From 72c96e4bd330da5dd01b7e7061b8d8ae676b61af Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:20:20 +0100 Subject: [PATCH 01/19] add python file for QPE from previous PR --- examples/python/phase_estimation.py | 445 ++++++++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 examples/python/phase_estimation.py diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py new file mode 100644 index 00000000..08e1486a --- /dev/null +++ b/examples/python/phase_estimation.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python +# coding: utf-8 + +# # Quantum Phase Estimation using `pytket` Boxes +# +# When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates. +# +# In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit. +# +# This notebook is intended to complement the [boxes section](https://cqcl.github.io/pytket/manual/manual_circuit.html#boxes) of the user manual which introduces the different box types. +# +# To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry. +# +# ## Overview of Phase Estimation +# +# The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision. +# +# The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation +# +# $$ +# \begin{equation} +# U |\psi \rangle = e^{2 \pi i \theta} |\psi\rangle\,, \quad 0 \leq \theta \leq 1 +# \end{equation} +# $$ +# +# Here $|\psi \rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \pi i \theta}$ by approximating $\theta$. +# +# +# The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes. +# +# ![](phase_est.png "Quantum Phase Estimation Circuit") + +# QPE is generally split up into three stages +# +# 1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\theta$. +# +# 2. Secondly we apply successive controlled $U$ gates. This has the effect of "kicking back" phases onto the ancilla qubits according to the eigenvalue equation above. +# +# 3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from "undesirable states" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability. +# +# +# There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$. + +# We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit. + +# ## The Quantum Fourier Transform + +# Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine. +# +# Mathematically, the QFT has the following action. +# +# \begin{equation} +# QFT : |j\rangle\ \longmapsto \sum_{k=0}^{N - 1} e^{2 \pi ijk/N}|k\rangle, \quad N= 2^k +# \end{equation} +# +# This is essentially the Discrete Fourier transform except the input is a quantum state $|j\rangle$. +# +# It is well known that the QFT can be implemented efficently with a quantum circuit +# +# We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\frac{n}{2}$ swap gates and $\frac{n(n-1)}{2}$ controlled unitary rotations $\text{CU1}$. +# +# $$ +# \begin{equation} +# CU1(\phi) = +# \begin{pmatrix} +# I & 0 \\ +# 0 & U1(\phi) +# \end{pmatrix} +# \,, \quad +# U1(\phi) = +# \begin{pmatrix} +# 1 & 0 \\ +# 0 & e^{i \phi} +# \end{pmatrix} +# \end{equation} +# $$ +# +# The circuit for the Quantum Fourier transform on three qubits is the following +# +# ![](qft.png "QFT Circuit") +# +# We can build this circuit in `pytket` by adding gate operations manually: + + +from pytket.circuit import Circuit + +# lets build the QFT for three qubits +qft3_circ = Circuit(3) + +qft3_circ.H(0) +qft3_circ.CU1(0.5, 1, 0) +qft3_circ.CU1(0.25, 2, 0) + +qft3_circ.H(1) +qft3_circ.CU1(0.5, 2, 1) + +qft3_circ.H(2) + +qft3_circ.SWAP(0, 2) + + +from pytket.circuit.display import render_circuit_jupyter + +render_circuit_jupyter(qft3_circ) + + +# We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows + + +def build_qft_circuit(n_qubits: int) -> Circuit: + circ = Circuit(n_qubits, name="QFT") + for i in range(n_qubits): + circ.H(i) + for j in range(i + 1, n_qubits): + circ.CU1(1 / 2 ** (j - i), j, i) + + for k in range(0, n_qubits // 2): + circ.SWAP(k, n_qubits - k - 1) + + return circ + + +qft4_circ: Circuit = build_qft_circuit(4) + +render_circuit_jupyter(qft4_circ) + + +# Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine. + +from pytket.circuit import CircBox + +qft4_box: CircBox = CircBox(qft4_circ) + +qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3]) + +render_circuit_jupyter(qft_circ) + + +# Note how the `CircBox` inherits the name `QFT` from the underlying circuit. + +# Recall that in our phase estimation algorithm we need to use the inverse QFT. +# +# $$ +# \begin{equation} +# \text{QFT}^† : \sum_{k=0}^{N - 1} e^{2 \pi ijk/N}|k\rangle \longmapsto |j\rangle\,, \quad N= 2^k +# \end{equation} +# $$ +# +# +# Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`. + + +inv_qft4_box = qft4_box.dagger + +render_circuit_jupyter(inv_qft4_box.get_circuit()) + + +# ## The Controlled Unitary Stage + +# Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficents $\alpha_j$. +# +# \begin{equation} +# H = \sum_j \alpha_j P_j\,, \quad \, P_j \in \{I, X, Y, Z\}^{\otimes n} +# \end{equation} +# +# Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \times 2^n$ matrices. + +# Firstly we need to define our Hamiltonian. In `pytket` this can be done with the `QubitPauliOperator` class. +# +# To define this object we use a python dictionary where the keys are the Pauli Strings $P_j$ and the values are the coefficents $\alpha_j$ +# +# As an example lets consider the operator. +# +# $$ +# \begin{equation} +# H = \frac{1}{4} XXY + \frac{1}{7} ZXZ + \frac{1}{3} YYX +# \end{equation} +# $$ +# +# This is an artifical example. We will later consider a physically motivated Hamiltonian. + +from pytket import Qubit +from pytket.pauli import Pauli, QubitPauliString +from pytket.utils import QubitPauliOperator + +xxy = QubitPauliString({Qubit(0): Pauli.X, Qubit(1): Pauli.X, Qubit(2): Pauli.Y}) +zxz = QubitPauliString({Qubit(0): Pauli.Z, Qubit(1): Pauli.X, Qubit(2): Pauli.Z}) +yyx = QubitPauliString({Qubit(0): Pauli.Y, Qubit(1): Pauli.Y, Qubit(2): Pauli.X}) + +qpo = QubitPauliOperator({xxy: 1 / 4, zxz: 1 / 7, yyx: 1 / 3}) + + +# We can generate a circuit to approximate the unitary evolution of $e^{i H t}$ with the `gen_term_sequence_circuit` utility function. + + +from pytket.circuit import CircBox +from pytket.utils import gen_term_sequence_circuit + +op_circ = gen_term_sequence_circuit(qpo, Circuit(3)) +u_box = CircBox(op_circ) + + +# We can create a controlled unitary $U$ with a `QControlBox` with $n$ controls. In phase estimation only a single control is needed so $n=1$. + + +from pytket.circuit import QControlBox + +controlled_u = QControlBox(u_box, n=1) + + +test_circ = Circuit(4).add_gate(controlled_u, [0, 1, 2, 3]) +render_circuit_jupyter(test_circ) + + +# ## Putting it all together + + +def build_phase_est_circuit( + n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit +) -> Circuit: + qpe_circ: Circuit = Circuit() + n_ancillas = state_prep_circuit.n_qubits + measurement_register = qpe_circ.add_q_register("m", n_measurement_qubits) + state_prep_register = qpe_circ.add_q_register("p", n_ancillas) + + qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register)) + + unitary_circuit.name = "U" + controlled_u = QControlBox(CircBox(unitary_circuit), 1) + + # Add Hadamard gates to every qubit in the measurement register + for m_qubit in range(n_measurement_qubits): + qpe_circ.H(m_qubit) + + # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially + for m_qubit in range(n_measurement_qubits): + control_index = n_measurement_qubits - m_qubit - 1 + control_qubit = [measurement_register[control_index]] + for _ in range(2**m_qubit): + qpe_circ.add_qcontrolbox( + controlled_u, control_qubit + list(state_prep_register) + ) + + # Finally, append the inverse qft and measure the qubits + qft_box = CircBox(build_qft_circuit(n_measurement_qubits)) + inverse_qft_box = qft_box.dagger + + qpe_circ.add_circbox(inverse_qft_box, list(measurement_register)) + + qpe_circ.measure_register(measurement_register, "c") + + return qpe_circ + + +# ## Phase Estimation with a Trivial Eigenstate +# +# Lets test our circuit construction by preparing a trivial $|1\rangle $eigenstate of the $\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue. + +# $$ +# \begin{equation} +# U1(\phi)|1\rangle = e^{i\phi} = e^{2 \pi i \theta} \implies \theta = \frac{\phi}{2} +# \end{equation} +# $$ +# +# So we expect that our ideal phase $\theta$ will be half the input angle $\phi$ to our $U1$ gate. + + +state_prep_circuit = Circuit(1).X(0) + +input_angle = 0.73 # angle as number of half turns + +unitary_circuit = Circuit(1).add_gate(OpType.U1, [input_angle], [0]) + + +qpe_circ_trivial = build_phase_est_circuit( + 4, state_prep_circuit=state_prep_circuit, unitary_circuit=unitary_circuit +) + + +render_circuit_jupyter(qpe_circ_trivial) + + +# Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit. + + +from pytket.extensions.qiskit import AerBackend + +backend = AerBackend() + +compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial) + + +n_shots = 1000 +result = backend.run_circuit(compiled_circ, n_shots) + +print(result.get_counts()) + + +# We can now plot our results. The plotting is imported from the `plotting.py` file. + +from plotting import plot_qpe_results + +plot_qpe_results(result, y_limit=1.2 * n_shots) + + +# As expected we see one outcome with high probability. Lets now extract our approximation of $\theta$ from our output bitstrings. +# +# suppose the $j$ is an integer representation of our most commonly measured bitstring. + +# $$ +# \begin{equation} +# \theta_{estimate} = \frac{j}{N} +# \end{equation} +# $$ + +# Here $N = 2 ^n$ where $n$ is the number of measurement qubits. + + +from pytket.backends.backendresult import BackendResult + + +def single_phase_from_backendresult(result: BackendResult) -> float: + # Extract most common measurement outcome + basis_state = result.get_counts().most_common()[0][0] + bitstring = "".join([str(bit) for bit in basis_state]) + integer = int(bitstring, 2) + + # Calculate theta estimate + return integer / (2 ** len(bitstring)) + + +theta = single_phase_from_backendresult(result) + + +print(theta) + + +print(input_angle / 2) + + +# Our output is close to half our input angle $\phi$ as expected. Lets calculate our error. + + +error = round(abs(input_angle - (2 * theta)), 3) +print(error) + + +# ## State Preparation + +# Lets now consider a more interesting Hamiltonian. We will look at the $H_2$ Hamiltonian for a bond length of 5 angstroms. +# +# We can define the Hamiltonian as a `pytket` `QubitPauliOperator` and then synthesise a circuit for the time evolved Hamiltonian with the `gen_term_sequence_circuit` utility method. +# +# Here we can load in our `QubitPauliOperator` from a JSON file. + + +import json +from pytket.utils import QubitPauliOperator + +with open("h2_5A.json") as f: + qpo_h25A = QubitPauliOperator.from_list(json.load(f)) + + +ham_circ = gen_term_sequence_circuit(qpo_h25A, Circuit(4)) + + +from pytket.passes import DecomposeBoxes + + +DecomposeBoxes().apply(ham_circ) + + +render_circuit_jupyter(ham_circ) + + +# Now have to come up with an ansatz state to feed into our phase estimation algorithm. +# +# We will use the following ansatz +# +# $$ +# \begin{equation} +# |\psi_0\rangle = e^{i \frac{\pi}{2}\theta YXXX}|1100\rangle\,. +# \end{equation} +# $$ +# +# We can synthesise a circuit for the Pauli exponential using `PauliExpBox`. + + +from pytket.pauli import Pauli +from pytket.circuit import PauliExpBox + +state_circ = Circuit(4).X(0).X(1) +yxxx = [Pauli.Y, Pauli.X, Pauli.X, Pauli.X] +yxxx_box = PauliExpBox(yxxx, 0.1) # here theta=0.1 +state_circ.add_gate(yxxx_box, [0, 1, 2, 3]) + +# we can extract the statevector for $e^{i \frac{\pi}{2}\theta YXXX}|1100\rangle$ using the `Circuit.get_statvector()` method. + + +initial_state = state_circ.get_statevector() + +# Finally we can prepare our initial state using `StatePreparationBox`. + + +from pytket.circuit import StatePreparationBox + +state_prep_box = StatePreparationBox(initial_state) + + +# We now have all of the ingredients we need to build our phase estimation circuit for the $H_2$ molecule. Here we will use 8 qubits to estimate the phase. +# +# Note that the number of controlled unitaries in our circuit will scale exponentially with the number of measurement qubits. Decomposing these controlled boxes to native gates can lead to very deep circuits. +# +# We will again use the idealised `AerBackend` simulator for our simulation. If we were instead using a simulator with a noise model or a NISQ device we would expect our results to be degraded due to the large circuit depth. + + +ham_state_prep_circuit = Circuit(4).add_gate(state_prep_box, [0, 1, 2, 3]) + + +h2_qpe_circuit = build_phase_est_circuit( + 8, state_prep_circuit=ham_state_prep_circuit, unitary_circuit=ham_circ +) + + +render_circuit_jupyter(h2_qpe_circuit) + + +compiled_ham_circ = backend.get_compiled_circuit(h2_qpe_circuit, 0) + + +n_shots = 2000 + +ham_result = backend.run_circuit(compiled_ham_circ, n_shots=n_shots) + +plot_qpe_results(ham_result, y_limit=1.2 * n_shots, n_strings=5) + + +# ## Suggestions for further reading +# +# In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants. +# +# Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf +# +# +# As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding. From b791a49a422daeed9fe4ccb80781a12f9693c6c2 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:41:39 +0100 Subject: [PATCH 02/19] add JSON file for hydrogen Hamiltonian --- examples/h2_5A.json | 409 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 examples/h2_5A.json diff --git a/examples/h2_5A.json b/examples/h2_5A.json new file mode 100644 index 00000000..a5f829c0 --- /dev/null +++ b/examples/h2_5A.json @@ -0,0 +1,409 @@ +[ + { + "string": [], + "coefficient": [ + -0.5458607027942332, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.03970104575308908, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 2 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.039577818133605905, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 1 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.03970104575308908, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 3 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.039577818133605905, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "Z" + ], + [ + [ + "q", + [ + 2 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.026458859479781598, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 1 + ] + ], + "Z" + ], + [ + [ + "q", + [ + 3 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.026458859479781598, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "Z" + ], + [ + [ + "q", + [ + 1 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.11004294256569602, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 1 + ] + ], + "Z" + ], + [ + [ + "q", + [ + 2 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.11005517318966757, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "Y" + ], + [ + [ + "q", + [ + 1 + ] + ], + "X" + ], + [ + [ + "q", + [ + 2 + ] + ], + "X" + ], + [ + [ + "q", + [ + 3 + ] + ], + "Y" + ] + ], + "coefficient": [ + 0.08359631370988596, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "X" + ], + [ + [ + "q", + [ + 1 + ] + ], + "X" + ], + [ + [ + "q", + [ + 2 + ] + ], + "Y" + ], + [ + [ + "q", + [ + 3 + ] + ], + "Y" + ] + ], + "coefficient": [ + -0.08359631370988596, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "Y" + ], + [ + [ + "q", + [ + 1 + ] + ], + "Y" + ], + [ + [ + "q", + [ + 2 + ] + ], + "X" + ], + [ + [ + "q", + [ + 3 + ] + ], + "X" + ] + ], + "coefficient": [ + -0.08359631370988596, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "X" + ], + [ + [ + "q", + [ + 1 + ] + ], + "Y" + ], + [ + [ + "q", + [ + 2 + ] + ], + "Y" + ], + [ + [ + "q", + [ + 3 + ] + ], + "X" + ] + ], + "coefficient": [ + 0.08359631370988596, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 0 + ] + ], + "Z" + ], + [ + [ + "q", + [ + 3 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.11005517318966757, + 0.0 + ] + }, + { + "string": [ + [ + [ + "q", + [ + 2 + ] + ], + "Z" + ], + [ + [ + "q", + [ + 3 + ] + ], + "Z" + ] + ], + "coefficient": [ + 0.11006740930024865, + 0.0 + ] + } +] \ No newline at end of file From 05d585548d4689bbd43445836b3f3380d155ce11 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:55:04 +0100 Subject: [PATCH 03/19] clean up python file --- examples/python/phase_estimation.py | 67 +++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index 08e1486a..bde9b4a5 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -225,11 +225,12 @@ def build_phase_est_circuit( qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register)) + # Create a controlled unitary with a single control qubit unitary_circuit.name = "U" - controlled_u = QControlBox(CircBox(unitary_circuit), 1) + controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1) # Add Hadamard gates to every qubit in the measurement register - for m_qubit in range(n_measurement_qubits): + for m_qubit in measurement_register: qpe_circ.H(m_qubit) # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially @@ -238,7 +239,7 @@ def build_phase_est_circuit( control_qubit = [measurement_register[control_index]] for _ in range(2**m_qubit): qpe_circ.add_qcontrolbox( - controlled_u, control_qubit + list(state_prep_register) + controlled_u_gate, control_qubit + list(state_prep_register) ) # Finally, append the inverse qft and measure the qubits @@ -265,15 +266,15 @@ def build_phase_est_circuit( # So we expect that our ideal phase $\theta$ will be half the input angle $\phi$ to our $U1$ gate. -state_prep_circuit = Circuit(1).X(0) +prep_circuit = Circuit(1).X(0) input_angle = 0.73 # angle as number of half turns -unitary_circuit = Circuit(1).add_gate(OpType.U1, [input_angle], [0]) +unitary_circuit = Circuit(1).U1(input_angle, 0) qpe_circ_trivial = build_phase_est_circuit( - 4, state_prep_circuit=state_prep_circuit, unitary_circuit=unitary_circuit + 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit ) @@ -296,11 +297,48 @@ def build_phase_est_circuit( print(result.get_counts()) -# We can now plot our results. The plotting is imported from the `plotting.py` file. - -from plotting import plot_qpe_results - -plot_qpe_results(result, y_limit=1.2 * n_shots) +from pytket.backends.backendresult import BackendResult +import matplotlib.pyplot as plt + + +# plotting function for QPE Notebook +def plot_qpe_results( + sim_result: BackendResult, + n_strings: int = 4, + dark_mode: bool = False, + y_limit: int = 1000, +) -> None: + """ + Plots results in a barchart given a BackendResult. the number of stings displayed + can be specified with the n_strings argument. + """ + counts_dict = sim_result.get_counts() + sorted_shots = counts_dict.most_common() + + n_most_common_strings = sorted_shots[:n_strings] + x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states + y_axis_values = [entry[1] for entry in n_most_common_strings] # counts + + if dark_mode: + plt.style.use("dark_background") + + fig = plt.figure() + ax = fig.add_axes((0, 0, 0.75, 0.5)) + color_list = ["orange"] * (len(x_axis_values)) + ax.bar( + x=x_axis_values, + height=y_axis_values, + color=color_list, + ) + ax.set_title(label="Results") + plt.ylim([0, y_limit]) + plt.xlabel("Basis State") + plt.ylabel("Number of Shots") + plt.xticks(rotation=90) + plt.show() + + +plot_qpe_results(result, y_limit=int(1.2 * n_shots)) # As expected we see one outcome with high probability. Lets now extract our approximation of $\theta$ from our output bitstrings. @@ -355,9 +393,10 @@ def single_phase_from_backendresult(result: BackendResult) -> float: import json -from pytket.utils import QubitPauliOperator -with open("h2_5A.json") as f: +with open( + "h2_5A.json", +) as f: qpo_h25A = QubitPauliOperator.from_list(json.load(f)) @@ -432,7 +471,7 @@ def single_phase_from_backendresult(result: BackendResult) -> float: ham_result = backend.run_circuit(compiled_ham_circ, n_shots=n_shots) -plot_qpe_results(ham_result, y_limit=1.2 * n_shots, n_strings=5) +plot_qpe_results(ham_result, y_limit=int(1.2 * n_shots), n_strings=5) # ## Suggestions for further reading From 72d8a93fafcfc5db0bc61357dc0612a78c2f1880 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:01:36 +0100 Subject: [PATCH 04/19] add notebook file with
tags removed --- examples/phase_estimation.ipynb | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/phase_estimation.ipynb diff --git a/examples/phase_estimation.ipynb b/examples/phase_estimation.ipynb new file mode 100644 index 00000000..a4dbccec --- /dev/null +++ b/examples/phase_estimation.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://cqcl.github.io/pytket/manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.H(2)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.SWAP(0, 2)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Stage"]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficents $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["Firstly we need to define our Hamiltonian. In `pytket` this can be done with the `QubitPauliOperator` class.\n","\n","To define this object we use a python dictionary where the keys are the Pauli Strings $P_j$ and the values are the coefficents $\\alpha_j$\n","\n","As an example lets consider the operator.\n","\n","$$\n","\\begin{equation}\n","H = \\frac{1}{4} XXY + \\frac{1}{7} ZXZ + \\frac{1}{3} YYX\n","\\end{equation}\n","$$\n","\n","This is an artifical example. We will later consider a physically motivated Hamiltonian."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket import Qubit\n","from pytket.pauli import Pauli, QubitPauliString\n","from pytket.utils import QubitPauliOperator"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["xxy = QubitPauliString({Qubit(0): Pauli.X, Qubit(1): Pauli.X, Qubit(2): Pauli.Y})\n","zxz = QubitPauliString({Qubit(0): Pauli.Z, Qubit(1): Pauli.X, Qubit(2): Pauli.Z})\n","yyx = QubitPauliString({Qubit(0): Pauli.Y, Qubit(1): Pauli.Y, Qubit(2): Pauli.X})"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpo = QubitPauliOperator({xxy: 1 / 4, zxz: 1 / 7, yyx: 1 / 3})"]},{"cell_type":"markdown","metadata":{},"source":["We can generate a circuit to approximate the unitary evolution of $e^{i H t}$ with the `gen_term_sequence_circuit` utility function."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox\n","from pytket.utils import gen_term_sequence_circuit"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["op_circ = gen_term_sequence_circuit(qpo, Circuit(3))\n","u_box = CircBox(op_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can create a controlled unitary $U$ with a `QControlBox` with $n$ controls. In phase estimation only a single control is needed so $n=1$."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import QControlBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["controlled_u = QControlBox(u_box, n=1)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["test_circ = Circuit(4).add_gate(controlled_u, [0, 1, 2, 3])\n","render_circuit_jupyter(test_circ)"]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_ancillas = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_ancillas)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle $eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## State Preparation"]},{"cell_type":"markdown","metadata":{},"source":["Lets now consider a more interesting Hamiltonian. We will look at the $H_2$ Hamiltonian for a bond length of 5 angstroms.\n","\n","We can define the Hamiltonian as a `pytket` `QubitPauliOperator` and then synthesise a circuit for the time evolved Hamiltonian with the `gen_term_sequence_circuit` utility method.\n","\n","Here we can load in our `QubitPauliOperator` from a JSON file."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["import json"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["with open(\n"," \"h2_5A.json\",\n",") as f:\n"," qpo_h25A = QubitPauliOperator.from_list(json.load(f))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["ham_circ = gen_term_sequence_circuit(qpo_h25A, Circuit(4))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.passes import DecomposeBoxes"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["DecomposeBoxes().apply(ham_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(ham_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now have to come up with an ansatz state to feed into our phase estimation algorithm.\n","\n","We will use the following ansatz\n","\n","$$\n","\\begin{equation}\n","|\\psi_0\\rangle = e^{i \\frac{\\pi}{2}\\theta YXXX}|1100\\rangle\\,.\n","\\end{equation}\n","$$\n","\n","We can synthesise a circuit for the Pauli exponential using `PauliExpBox`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.pauli import Pauli\n","from pytket.circuit import PauliExpBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["state_circ = Circuit(4).X(0).X(1)\n","yxxx = [Pauli.Y, Pauli.X, Pauli.X, Pauli.X]\n","yxxx_box = PauliExpBox(yxxx, 0.1) # here theta=0.1\n","state_circ.add_gate(yxxx_box, [0, 1, 2, 3])"]},{"cell_type":"markdown","metadata":{},"source":["we can extract the statevector for $e^{i \\frac{\\pi}{2}\\theta YXXX}|1100\\rangle$ using the `Circuit.get_statvector()` method."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["initial_state = state_circ.get_statevector()"]},{"cell_type":"markdown","metadata":{},"source":["Finally we can prepare our initial state using `StatePreparationBox`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import StatePreparationBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["state_prep_box = StatePreparationBox(initial_state)"]},{"cell_type":"markdown","metadata":{},"source":["We now have all of the ingredients we need to build our phase estimation circuit for the $H_2$ molecule. Here we will use 8 qubits to estimate the phase.\n","\n","Note that the number of controlled unitaries in our circuit will scale exponentially with the number of measurement qubits. Decomposing these controlled boxes to native gates can lead to very deep circuits.\n","\n","We will again use the idealised `AerBackend` simulator for our simulation. If we were instead using a simulator with a noise model or a NISQ device we would expect our results to be degraded due to the large circuit depth."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["ham_state_prep_circuit = Circuit(4).add_gate(state_prep_box, [0, 1, 2, 3])"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["h2_qpe_circuit = build_phase_est_circuit(\n"," 8, state_prep_circuit=ham_state_prep_circuit, unitary_circuit=ham_circ\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(h2_qpe_circuit)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_ham_circ = backend.get_compiled_circuit(h2_qpe_circuit, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 2000"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["ham_result = backend.run_circuit(compiled_ham_circ, n_shots=n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(ham_result, y_limit=int(1.2 * n_shots), n_strings=5)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2} From 00518228178be816aa89600257efdc904efe240e Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:06:11 +0100 Subject: [PATCH 05/19] update list of maintained notebooks --- examples/maintained-notebooks.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/maintained-notebooks.txt b/examples/maintained-notebooks.txt index e1d44cab..19cfebee 100644 --- a/examples/maintained-notebooks.txt +++ b/examples/maintained-notebooks.txt @@ -18,3 +18,4 @@ symbolics_example pytket-qujax_qaoa pytket-qujax-classification ucc_vqe +phase_estimation From add7f28508fb4d416f847a089d0379a6b0601c76 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:08:10 +0100 Subject: [PATCH 06/19] add QFT and QPE images --- examples/phase_est.png | Bin 0 -> 45897 bytes examples/qft.png | Bin 0 -> 13931 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/phase_est.png create mode 100644 examples/qft.png diff --git a/examples/phase_est.png b/examples/phase_est.png new file mode 100644 index 0000000000000000000000000000000000000000..4e2c7aa24d2d2ada87dfdb7185f1db0844b2757a GIT binary patch literal 45897 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 BJ}v+N literal 0 HcmV?d00001 From 68843f2d941be519fc4b1d80b1d1c7f1857e3b83 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:15:24 +0100 Subject: [PATCH 07/19] add QPE notebook to table of contents --- examples/_toc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/_toc.yml b/examples/_toc.yml index d1f14deb..d0acdb5d 100644 --- a/examples/_toc.yml +++ b/examples/_toc.yml @@ -16,6 +16,7 @@ chapters: - file: symbolics_example - file: ucc_vqe - file: pytket-qujax_qaoa +- file: phase_estimation - file: benchmarking/README # The following notebooks are not executed - file: backends_example From 4b9fdac1b0c8f80cd3744a46ae328791657b2cf3 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:39:46 +0100 Subject: [PATCH 08/19] Execute QPE notebook --- examples/_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/_config.yml b/examples/_config.yml index a392c511..650283e8 100644 --- a/examples/_config.yml +++ b/examples/_config.yml @@ -11,7 +11,7 @@ sphinx: execute: # Exclude some examples from execution (these are still deployed as html pages) - exclude_patterns: [".venv/*", "Boxes_QPE_example.ipynb", "Forest_portability_example.ipynb", "backends_example.ipynb", "qiskit_integration.ipynb", "comparing_simulators.ipynb", "expectation_value_example.ipynb", "pytket-qujax_heisenberg_vqe.ipynb", "spam_example.ipynb", "tket_benchmarking.ipynb", "entanglement_swapping.ipynb", "pytket-qujax-classification.ipynb"] + exclude_patterns: [".venv/*", "Forest_portability_example.ipynb", "backends_example.ipynb", "qiskit_integration.ipynb", "comparing_simulators.ipynb", "expectation_value_example.ipynb", "pytket-qujax_heisenberg_vqe.ipynb", "spam_example.ipynb", "tket_benchmarking.ipynb", "entanglement_swapping.ipynb", "pytket-qujax-classification.ipynb"] timeout: 90 # The maximum time (in seconds) each notebook cell is allowed to run. # Information about where the book exists on the web From bcdbf352d38a7febcd0131a4cc6ccd3d02cb82c5 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:18:51 +0100 Subject: [PATCH 09/19] fix markdown spacing Co-authored-by: yao-cqc <75305462+yao-cqc@users.noreply.github.com> --- examples/python/phase_estimation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index bde9b4a5..c4b71d0d 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -255,7 +255,7 @@ def build_phase_est_circuit( # ## Phase Estimation with a Trivial Eigenstate # -# Lets test our circuit construction by preparing a trivial $|1\rangle $eigenstate of the $\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue. +# Lets test our circuit construction by preparing a trivial $|1\rangle$ eigenstate of the $\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue. # $$ # \begin{equation} From 7532e8ffc6ab516555003591e0cce2fbc01ee01c Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:33:18 +0100 Subject: [PATCH 10/19] add checkpoints to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a150e1e5..1f20360e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist manual/build manual/jupyter_execute examples/_build/ +*.ipynb_checkpoints From ea853ebbacc637065f7d54dab15b073aa1a33c03 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:34:56 +0100 Subject: [PATCH 11/19] put all of the 3 qubit qft in one cell --- examples/python/phase_estimation.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index c4b71d0d..a747f759 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -83,24 +83,18 @@ from pytket.circuit import Circuit +from pytket.circuit.display import render_circuit_jupyter # lets build the QFT for three qubits qft3_circ = Circuit(3) - qft3_circ.H(0) qft3_circ.CU1(0.5, 1, 0) qft3_circ.CU1(0.25, 2, 0) - qft3_circ.H(1) qft3_circ.CU1(0.5, 2, 1) - qft3_circ.H(2) - qft3_circ.SWAP(0, 2) - -from pytket.circuit.display import render_circuit_jupyter - render_circuit_jupyter(qft3_circ) From f7f4ee8c809cfd9cb7668f3cf2a07cfc3d4e763a Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:27:35 +0100 Subject: [PATCH 12/19] remove artifical use of StatePreparationBox --- examples/python/phase_estimation.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index a747f759..9696f532 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -115,7 +115,6 @@ def build_qft_circuit(n_qubits: int) -> Circuit: qft4_circ: Circuit = build_qft_circuit(4) - render_circuit_jupyter(qft4_circ) @@ -124,9 +123,7 @@ def build_qft_circuit(n_qubits: int) -> Circuit: from pytket.circuit import CircBox qft4_box: CircBox = CircBox(qft4_circ) - qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3]) - render_circuit_jupyter(qft_circ) @@ -145,7 +142,6 @@ def build_qft_circuit(n_qubits: int) -> Circuit: inv_qft4_box = qft4_box.dagger - render_circuit_jupyter(inv_qft4_box.get_circuit()) @@ -427,19 +423,6 @@ def single_phase_from_backendresult(result: BackendResult) -> float: yxxx_box = PauliExpBox(yxxx, 0.1) # here theta=0.1 state_circ.add_gate(yxxx_box, [0, 1, 2, 3]) -# we can extract the statevector for $e^{i \frac{\pi}{2}\theta YXXX}|1100\rangle$ using the `Circuit.get_statvector()` method. - - -initial_state = state_circ.get_statevector() - -# Finally we can prepare our initial state using `StatePreparationBox`. - - -from pytket.circuit import StatePreparationBox - -state_prep_box = StatePreparationBox(initial_state) - - # We now have all of the ingredients we need to build our phase estimation circuit for the $H_2$ molecule. Here we will use 8 qubits to estimate the phase. # # Note that the number of controlled unitaries in our circuit will scale exponentially with the number of measurement qubits. Decomposing these controlled boxes to native gates can lead to very deep circuits. @@ -447,11 +430,8 @@ def single_phase_from_backendresult(result: BackendResult) -> float: # We will again use the idealised `AerBackend` simulator for our simulation. If we were instead using a simulator with a noise model or a NISQ device we would expect our results to be degraded due to the large circuit depth. -ham_state_prep_circuit = Circuit(4).add_gate(state_prep_box, [0, 1, 2, 3]) - - h2_qpe_circuit = build_phase_est_circuit( - 8, state_prep_circuit=ham_state_prep_circuit, unitary_circuit=ham_circ + 8, state_prep_circuit=state_circ, unitary_circuit=ham_circ ) From 3085b9845a6978b2b65bbb85ef50a1a762ed4c5d Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:33:34 +0100 Subject: [PATCH 13/19] n_ancillas -> n_state_prep_qubits --- examples/python/phase_estimation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index 9696f532..50808b07 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -209,9 +209,9 @@ def build_phase_est_circuit( n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit ) -> Circuit: qpe_circ: Circuit = Circuit() - n_ancillas = state_prep_circuit.n_qubits + n_state_prep_qubits = state_prep_circuit.n_qubits measurement_register = qpe_circ.add_q_register("m", n_measurement_qubits) - state_prep_register = qpe_circ.add_q_register("p", n_ancillas) + state_prep_register = qpe_circ.add_q_register("p", n_state_prep_qubits) qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register)) From b521baa919c59b29ad245bb80acbc73a671cd17f Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:04:46 +0000 Subject: [PATCH 14/19] scrap section on state preparation --- examples/python/phase_estimation.py | 121 ---------------------------- 1 file changed, 121 deletions(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index 50808b07..f3f035a3 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -155,52 +155,6 @@ def build_qft_circuit(n_qubits: int) -> Circuit: # # Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \times 2^n$ matrices. -# Firstly we need to define our Hamiltonian. In `pytket` this can be done with the `QubitPauliOperator` class. -# -# To define this object we use a python dictionary where the keys are the Pauli Strings $P_j$ and the values are the coefficents $\alpha_j$ -# -# As an example lets consider the operator. -# -# $$ -# \begin{equation} -# H = \frac{1}{4} XXY + \frac{1}{7} ZXZ + \frac{1}{3} YYX -# \end{equation} -# $$ -# -# This is an artifical example. We will later consider a physically motivated Hamiltonian. - -from pytket import Qubit -from pytket.pauli import Pauli, QubitPauliString -from pytket.utils import QubitPauliOperator - -xxy = QubitPauliString({Qubit(0): Pauli.X, Qubit(1): Pauli.X, Qubit(2): Pauli.Y}) -zxz = QubitPauliString({Qubit(0): Pauli.Z, Qubit(1): Pauli.X, Qubit(2): Pauli.Z}) -yyx = QubitPauliString({Qubit(0): Pauli.Y, Qubit(1): Pauli.Y, Qubit(2): Pauli.X}) - -qpo = QubitPauliOperator({xxy: 1 / 4, zxz: 1 / 7, yyx: 1 / 3}) - - -# We can generate a circuit to approximate the unitary evolution of $e^{i H t}$ with the `gen_term_sequence_circuit` utility function. - - -from pytket.circuit import CircBox -from pytket.utils import gen_term_sequence_circuit - -op_circ = gen_term_sequence_circuit(qpo, Circuit(3)) -u_box = CircBox(op_circ) - - -# We can create a controlled unitary $U$ with a `QControlBox` with $n$ controls. In phase estimation only a single control is needed so $n=1$. - - -from pytket.circuit import QControlBox - -controlled_u = QControlBox(u_box, n=1) - - -test_circ = Circuit(4).add_gate(controlled_u, [0, 1, 2, 3]) -render_circuit_jupyter(test_circ) - # ## Putting it all together @@ -373,81 +327,6 @@ def single_phase_from_backendresult(result: BackendResult) -> float: print(error) -# ## State Preparation - -# Lets now consider a more interesting Hamiltonian. We will look at the $H_2$ Hamiltonian for a bond length of 5 angstroms. -# -# We can define the Hamiltonian as a `pytket` `QubitPauliOperator` and then synthesise a circuit for the time evolved Hamiltonian with the `gen_term_sequence_circuit` utility method. -# -# Here we can load in our `QubitPauliOperator` from a JSON file. - - -import json - -with open( - "h2_5A.json", -) as f: - qpo_h25A = QubitPauliOperator.from_list(json.load(f)) - - -ham_circ = gen_term_sequence_circuit(qpo_h25A, Circuit(4)) - - -from pytket.passes import DecomposeBoxes - - -DecomposeBoxes().apply(ham_circ) - - -render_circuit_jupyter(ham_circ) - - -# Now have to come up with an ansatz state to feed into our phase estimation algorithm. -# -# We will use the following ansatz -# -# $$ -# \begin{equation} -# |\psi_0\rangle = e^{i \frac{\pi}{2}\theta YXXX}|1100\rangle\,. -# \end{equation} -# $$ -# -# We can synthesise a circuit for the Pauli exponential using `PauliExpBox`. - - -from pytket.pauli import Pauli -from pytket.circuit import PauliExpBox - -state_circ = Circuit(4).X(0).X(1) -yxxx = [Pauli.Y, Pauli.X, Pauli.X, Pauli.X] -yxxx_box = PauliExpBox(yxxx, 0.1) # here theta=0.1 -state_circ.add_gate(yxxx_box, [0, 1, 2, 3]) - -# We now have all of the ingredients we need to build our phase estimation circuit for the $H_2$ molecule. Here we will use 8 qubits to estimate the phase. -# -# Note that the number of controlled unitaries in our circuit will scale exponentially with the number of measurement qubits. Decomposing these controlled boxes to native gates can lead to very deep circuits. -# -# We will again use the idealised `AerBackend` simulator for our simulation. If we were instead using a simulator with a noise model or a NISQ device we would expect our results to be degraded due to the large circuit depth. - - -h2_qpe_circuit = build_phase_est_circuit( - 8, state_prep_circuit=state_circ, unitary_circuit=ham_circ -) - - -render_circuit_jupyter(h2_qpe_circuit) - - -compiled_ham_circ = backend.get_compiled_circuit(h2_qpe_circuit, 0) - - -n_shots = 2000 - -ham_result = backend.run_circuit(compiled_ham_circ, n_shots=n_shots) - -plot_qpe_results(ham_result, y_limit=int(1.2 * n_shots), n_strings=5) - - # ## Suggestions for further reading # # In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants. From 3e0424d21eeadd484fce721b5db920a69ea21c7e Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:53:19 +0000 Subject: [PATCH 15/19] text edits --- examples/python/phase_estimation.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index f3f035a3..77a91984 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -7,7 +7,7 @@ # # In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit. # -# This notebook is intended to complement the [boxes section](https://cqcl.github.io/pytket/manual/manual_circuit.html#boxes) of the user manual which introduces the different box types. +# This notebook is intended to complement the [boxes section](https://tket.quantinuum.com/user-manual/manual_circuit.html#boxes) of the user manual which introduces the different box types. # # To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry. # @@ -94,7 +94,6 @@ qft3_circ.CU1(0.5, 2, 1) qft3_circ.H(2) qft3_circ.SWAP(0, 2) - render_circuit_jupyter(qft3_circ) @@ -145,9 +144,13 @@ def build_qft_circuit(n_qubits: int) -> Circuit: render_circuit_jupyter(inv_qft4_box.get_circuit()) -# ## The Controlled Unitary Stage +# ## The Controlled Unitary Operations + +# In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will equal $2^m - 1$ where $m$ is the number of measurement qubits. + +# The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian. -# Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficents $\alpha_j$. +# Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficients $\alpha_j$. # # \begin{equation} # H = \sum_j \alpha_j P_j\,, \quad \, P_j \in \{I, X, Y, Z\}^{\otimes n} @@ -155,9 +158,14 @@ def build_qft_circuit(n_qubits: int) -> Circuit: # # Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \times 2^n$ matrices. +# If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes). + +# In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\text{CU1}$ gates. # ## Putting it all together +# We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate. + def build_phase_est_circuit( n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit From b4894d3e3e517871c59fa7696810ebd4bd9173d2 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:53:35 +0000 Subject: [PATCH 16/19] add clean notebook --- examples/phase_estimation.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/phase_estimation.ipynb b/examples/phase_estimation.ipynb index a4dbccec..55ab074c 100644 --- a/examples/phase_estimation.ipynb +++ b/examples/phase_estimation.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://cqcl.github.io/pytket/manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.H(2)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ.SWAP(0, 2)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Stage"]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficents $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["Firstly we need to define our Hamiltonian. In `pytket` this can be done with the `QubitPauliOperator` class.\n","\n","To define this object we use a python dictionary where the keys are the Pauli Strings $P_j$ and the values are the coefficents $\\alpha_j$\n","\n","As an example lets consider the operator.\n","\n","$$\n","\\begin{equation}\n","H = \\frac{1}{4} XXY + \\frac{1}{7} ZXZ + \\frac{1}{3} YYX\n","\\end{equation}\n","$$\n","\n","This is an artifical example. We will later consider a physically motivated Hamiltonian."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket import Qubit\n","from pytket.pauli import Pauli, QubitPauliString\n","from pytket.utils import QubitPauliOperator"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["xxy = QubitPauliString({Qubit(0): Pauli.X, Qubit(1): Pauli.X, Qubit(2): Pauli.Y})\n","zxz = QubitPauliString({Qubit(0): Pauli.Z, Qubit(1): Pauli.X, Qubit(2): Pauli.Z})\n","yyx = QubitPauliString({Qubit(0): Pauli.Y, Qubit(1): Pauli.Y, Qubit(2): Pauli.X})"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpo = QubitPauliOperator({xxy: 1 / 4, zxz: 1 / 7, yyx: 1 / 3})"]},{"cell_type":"markdown","metadata":{},"source":["We can generate a circuit to approximate the unitary evolution of $e^{i H t}$ with the `gen_term_sequence_circuit` utility function."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox\n","from pytket.utils import gen_term_sequence_circuit"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["op_circ = gen_term_sequence_circuit(qpo, Circuit(3))\n","u_box = CircBox(op_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can create a controlled unitary $U$ with a `QControlBox` with $n$ controls. In phase estimation only a single control is needed so $n=1$."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import QControlBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["controlled_u = QControlBox(u_box, n=1)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["test_circ = Circuit(4).add_gate(controlled_u, [0, 1, 2, 3])\n","render_circuit_jupyter(test_circ)"]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_ancillas = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_ancillas)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle $eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## State Preparation"]},{"cell_type":"markdown","metadata":{},"source":["Lets now consider a more interesting Hamiltonian. We will look at the $H_2$ Hamiltonian for a bond length of 5 angstroms.\n","\n","We can define the Hamiltonian as a `pytket` `QubitPauliOperator` and then synthesise a circuit for the time evolved Hamiltonian with the `gen_term_sequence_circuit` utility method.\n","\n","Here we can load in our `QubitPauliOperator` from a JSON file."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["import json"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["with open(\n"," \"h2_5A.json\",\n",") as f:\n"," qpo_h25A = QubitPauliOperator.from_list(json.load(f))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["ham_circ = gen_term_sequence_circuit(qpo_h25A, Circuit(4))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.passes import DecomposeBoxes"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["DecomposeBoxes().apply(ham_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(ham_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now have to come up with an ansatz state to feed into our phase estimation algorithm.\n","\n","We will use the following ansatz\n","\n","$$\n","\\begin{equation}\n","|\\psi_0\\rangle = e^{i \\frac{\\pi}{2}\\theta YXXX}|1100\\rangle\\,.\n","\\end{equation}\n","$$\n","\n","We can synthesise a circuit for the Pauli exponential using `PauliExpBox`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.pauli import Pauli\n","from pytket.circuit import PauliExpBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["state_circ = Circuit(4).X(0).X(1)\n","yxxx = [Pauli.Y, Pauli.X, Pauli.X, Pauli.X]\n","yxxx_box = PauliExpBox(yxxx, 0.1) # here theta=0.1\n","state_circ.add_gate(yxxx_box, [0, 1, 2, 3])"]},{"cell_type":"markdown","metadata":{},"source":["we can extract the statevector for $e^{i \\frac{\\pi}{2}\\theta YXXX}|1100\\rangle$ using the `Circuit.get_statvector()` method."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["initial_state = state_circ.get_statevector()"]},{"cell_type":"markdown","metadata":{},"source":["Finally we can prepare our initial state using `StatePreparationBox`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import StatePreparationBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["state_prep_box = StatePreparationBox(initial_state)"]},{"cell_type":"markdown","metadata":{},"source":["We now have all of the ingredients we need to build our phase estimation circuit for the $H_2$ molecule. Here we will use 8 qubits to estimate the phase.\n","\n","Note that the number of controlled unitaries in our circuit will scale exponentially with the number of measurement qubits. Decomposing these controlled boxes to native gates can lead to very deep circuits.\n","\n","We will again use the idealised `AerBackend` simulator for our simulation. If we were instead using a simulator with a noise model or a NISQ device we would expect our results to be degraded due to the large circuit depth."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["ham_state_prep_circuit = Circuit(4).add_gate(state_prep_box, [0, 1, 2, 3])"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["h2_qpe_circuit = build_phase_est_circuit(\n"," 8, state_prep_circuit=ham_state_prep_circuit, unitary_circuit=ham_circ\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(h2_qpe_circuit)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_ham_circ = backend.get_compiled_circuit(h2_qpe_circuit, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 2000"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["ham_result = backend.run_circuit(compiled_ham_circ, n_shots=n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(ham_result, y_limit=int(1.2 * n_shots), n_strings=5)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2} +{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://tket.quantinuum.com/user-manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit\n","from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)\n","qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)\n","qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)\n","qft3_circ.H(2)\n","qft3_circ.SWAP(0, 2)\n","render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)\n","render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)\n","qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])\n","render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger\n","render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Operations"]},{"cell_type":"markdown","metadata":{},"source":["In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will equal $2^m - 1$ where $m$ is the number of measurement qubits."]},{"cell_type":"markdown","metadata":{},"source":["The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian."]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficients $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes)."]},{"cell_type":"markdown","metadata":{},"source":["In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\\text{CU1}$ gates."]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"markdown","metadata":{},"source":["We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_state_prep_qubits = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_state_prep_qubits)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle$ eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2} From 84ea310af74653e7d68cb06fdf80455354f3e102 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:13:47 +0000 Subject: [PATCH 17/19] add clean notebook with updated text --- examples/phase_estimation.ipynb | 2 +- examples/python/phase_estimation.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/phase_estimation.ipynb b/examples/phase_estimation.ipynb index 55ab074c..80103569 100644 --- a/examples/phase_estimation.ipynb +++ b/examples/phase_estimation.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://tket.quantinuum.com/user-manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit\n","from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)\n","qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)\n","qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)\n","qft3_circ.H(2)\n","qft3_circ.SWAP(0, 2)\n","render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)\n","render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)\n","qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])\n","render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger\n","render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Operations"]},{"cell_type":"markdown","metadata":{},"source":["In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will equal $2^m - 1$ where $m$ is the number of measurement qubits."]},{"cell_type":"markdown","metadata":{},"source":["The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian."]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficients $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes)."]},{"cell_type":"markdown","metadata":{},"source":["In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\\text{CU1}$ gates."]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"markdown","metadata":{},"source":["We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_state_prep_qubits = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_state_prep_qubits)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle$ eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2} +{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://tket.quantinuum.com/user-manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficiently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit\n","from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)\n","qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)\n","qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)\n","qft3_circ.H(2)\n","qft3_circ.SWAP(0, 2)\n","render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)\n","render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)\n","qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])\n","render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger\n","render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Operations"]},{"cell_type":"markdown","metadata":{},"source":["In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will be $2^m - 1$ where $m$ is the number of measurement qubits."]},{"cell_type":"markdown","metadata":{},"source":["The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian."]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficients $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes)."]},{"cell_type":"markdown","metadata":{},"source":["Once we have a circuit to implement our time evolution operator $U(t)$, we can construct the controlled $U(t)$ operations using [QControlBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.QControlBox). If our base unitary is a sequence of `PauliExpBox`(es) then there is some structure we can exploit to simplify our circuit. See this [blog post](https://tket.quantinuum.com/tket-blog/posts/controlled_gates/) on [ConjugationBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.ConjugationBox) for more."]},{"cell_type":"markdown","metadata":{},"source":["In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\\text{CU1}$ gates."]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"markdown","metadata":{},"source":["We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import QControlBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_state_prep_qubits = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_state_prep_qubits)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle$ eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","Blog post on `ConjugationBox` -> https://tket.quantinuum.com/tket-blog/posts/controlled_gates/ - efficient circuits for controlled Pauli gadgets.\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2} diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index 77a91984..4c374f56 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -55,7 +55,7 @@ # # This is essentially the Discrete Fourier transform except the input is a quantum state $|j\rangle$. # -# It is well known that the QFT can be implemented efficently with a quantum circuit +# It is well known that the QFT can be implemented efficiently with a quantum circuit # # We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\frac{n}{2}$ swap gates and $\frac{n(n-1)}{2}$ controlled unitary rotations $\text{CU1}$. # @@ -146,7 +146,7 @@ def build_qft_circuit(n_qubits: int) -> Circuit: # ## The Controlled Unitary Operations -# In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will equal $2^m - 1$ where $m$ is the number of measurement qubits. +# In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will be $2^m - 1$ where $m$ is the number of measurement qubits. # The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian. @@ -160,6 +160,8 @@ def build_qft_circuit(n_qubits: int) -> Circuit: # If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes). +# Once we have a circuit to implement our time evolution operator $U(t)$, we can construct the controlled $U(t)$ operations using [QControlBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.QControlBox). If our base unitary is a sequence of `PauliExpBox`(es) then there is some structure we can exploit to simplify our circuit. See this [blog post](https://tket.quantinuum.com/tket-blog/posts/controlled_gates/) on [ConjugationBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.ConjugationBox) for more. + # In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\text{CU1}$ gates. # ## Putting it all together @@ -167,6 +169,9 @@ def build_qft_circuit(n_qubits: int) -> Circuit: # We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate. +from pytket.circuit import QControlBox + + def build_phase_est_circuit( n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit ) -> Circuit: @@ -340,6 +345,6 @@ def single_phase_from_backendresult(result: BackendResult) -> float: # In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants. # # Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf -# +# Blog post on `ConjugationBox` -> https://tket.quantinuum.com/tket-blog/posts/controlled_gates/ - efficient circuits for controlled Pauli gadgets. # # As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding. From e32ff63871183c4101487b0da6bef15907aed1bc Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:22:11 +0000 Subject: [PATCH 18/19] fix minus sign --- examples/python/phase_estimation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/python/phase_estimation.py b/examples/python/phase_estimation.py index 4c374f56..7e9fe0ee 100644 --- a/examples/python/phase_estimation.py +++ b/examples/python/phase_estimation.py @@ -41,7 +41,7 @@ # # There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$. -# We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit. +# We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{-iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit. # ## The Quantum Fourier Transform From 984c3183d61d0d164033710083630f6edc201706 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:23:29 +0000 Subject: [PATCH 19/19] add clean notebook after fixing minus sign --- examples/phase_estimation.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/phase_estimation.ipynb b/examples/phase_estimation.ipynb index 80103569..80c0c9e2 100644 --- a/examples/phase_estimation.ipynb +++ b/examples/phase_estimation.ipynb @@ -1 +1 @@ -{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://tket.quantinuum.com/user-manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficiently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit\n","from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)\n","qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)\n","qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)\n","qft3_circ.H(2)\n","qft3_circ.SWAP(0, 2)\n","render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)\n","render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)\n","qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])\n","render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger\n","render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Operations"]},{"cell_type":"markdown","metadata":{},"source":["In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will be $2^m - 1$ where $m$ is the number of measurement qubits."]},{"cell_type":"markdown","metadata":{},"source":["The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian."]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficients $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes)."]},{"cell_type":"markdown","metadata":{},"source":["Once we have a circuit to implement our time evolution operator $U(t)$, we can construct the controlled $U(t)$ operations using [QControlBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.QControlBox). If our base unitary is a sequence of `PauliExpBox`(es) then there is some structure we can exploit to simplify our circuit. See this [blog post](https://tket.quantinuum.com/tket-blog/posts/controlled_gates/) on [ConjugationBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.ConjugationBox) for more."]},{"cell_type":"markdown","metadata":{},"source":["In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\\text{CU1}$ gates."]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"markdown","metadata":{},"source":["We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import QControlBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_state_prep_qubits = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_state_prep_qubits)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle$ eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","Blog post on `ConjugationBox` -> https://tket.quantinuum.com/tket-blog/posts/controlled_gates/ - efficient circuits for controlled Pauli gadgets.\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2} +{"cells":[{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["#!/usr/bin/env python\n","# coding: utf-8"]},{"cell_type":"markdown","metadata":{},"source":["# Quantum Phase Estimation using `pytket` Boxes\n","\n","When constructing circuits for quantum algorithms it is useful to think of higher level operations than just individual quantum gates.\n","\n","In `pytket` we can construct circuits using box structures which abstract away the complexity of the underlying circuit.\n","\n","This notebook is intended to complement the [boxes section](https://tket.quantinuum.com/user-manual/manual_circuit.html#boxes) of the user manual which introduces the different box types.\n","\n","To demonstrate boxes in `pytket` we will consider the Quantum Phase Estimation algorithm (QPE). This is an important subroutine in several quantum algorithms including Shor's algorithm and fault-tolerant approaches to quantum chemistry.\n","\n","## Overview of Phase Estimation\n","\n","The Quantum Phase Estimation algorithm can be used to estimate the eigenvalues of some unitary operator $U$ to some desired precision.\n","\n","The eigenvalues of $U$ lie on the unit circle, giving us the following eigenvalue equation\n","\n","$$\n","\\begin{equation}\n","U |\\psi \\rangle = e^{2 \\pi i \\theta} |\\psi\\rangle\\,, \\quad 0 \\leq \\theta \\leq 1\n","\\end{equation}\n","$$\n","\n","Here $|\\psi \\rangle$ is an eigenstate of the operator $U$. In phase estimation we estimate the eigenvalue $e^{2 \\pi i \\theta}$ by approximating $\\theta$.\n","\n","\n","The circuit for Quantum phase estimation is itself composed of several subroutines which we can realise as boxes.\n","\n","![](phase_est.png \"Quantum Phase Estimation Circuit\")"]},{"cell_type":"markdown","metadata":{},"source":["QPE is generally split up into three stages\n","\n","1. Firstly we prepare an initial state in one register. In parallel we prepare a uniform superposition state using Hadamard gates on some ancilla qubits. The number of ancilla qubits determines how precisely we can estimate the phase $\\theta$.\n","\n","2. Secondly we apply successive controlled $U$ gates. This has the effect of \"kicking back\" phases onto the ancilla qubits according to the eigenvalue equation above.\n","\n","3. Finally we apply the inverse Quantum Fourier Transform (QFT). This essentially plays the role of destructive interference, suppressing amplitudes from \"undesirable states\" and hopefully allowing us to measure a single outcome (or a small number of outcomes) with high probability.\n","\n","\n","There is some subtlety around the first point. The initial state used can be an exact eigenstate of $U$ however this may be difficult to prepare if we don't know the eigenvalues of $U$ in advance. Alternatively we could use an initial state that is a linear combination of eigenstates, as the phase estimation will project into the eigenspace of $U$."]},{"cell_type":"markdown","metadata":{},"source":["We also assume that we can implement $U$ with a quantum circuit. In chemistry applications $U$ could be of the form $U=e^{-iHt}$ where $H$ is the Hamiltonian of some system of interest. In the cannonical algorithm, the number of controlled unitaries we apply scales exponentially with the number of ancilla qubits. This allows more precision at the expense of a larger quantum circuit."]},{"cell_type":"markdown","metadata":{},"source":["## The Quantum Fourier Transform"]},{"cell_type":"markdown","metadata":{},"source":["Before considering the other parts of the QPE algorithm, lets focus on the Quantum Fourier Transform (QFT) subroutine.\n","\n","Mathematically, the QFT has the following action.\n","\n","\\begin{equation}\n","QFT : |j\\rangle\\ \\longmapsto \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle, \\quad N= 2^k\n","\\end{equation}\n","\n","This is essentially the Discrete Fourier transform except the input is a quantum state $|j\\rangle$.\n","\n","It is well known that the QFT can be implemented efficiently with a quantum circuit\n","\n","We can build the circuit for the $n$ qubit QFT using $n$ Hadamard gates $\\frac{n}{2}$ swap gates and $\\frac{n(n-1)}{2}$ controlled unitary rotations $\\text{CU1}$.\n","\n","$$\n"," \\begin{equation}\n"," CU1(\\phi) =\n"," \\begin{pmatrix}\n"," I & 0 \\\\\n"," 0 & U1(\\phi)\n"," \\end{pmatrix}\n"," \\,, \\quad\n","U1(\\phi) =\n"," \\begin{pmatrix}\n"," 1 & 0 \\\\\n"," 0 & e^{i \\phi}\n"," \\end{pmatrix}\n"," \\end{equation}\n","$$\n","\n","The circuit for the Quantum Fourier transform on three qubits is the following\n","\n","![](qft.png \"QFT Circuit\")\n","\n","We can build this circuit in `pytket` by adding gate operations manually:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import Circuit\n","from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"markdown","metadata":{},"source":["lets build the QFT for three qubits"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft3_circ = Circuit(3)\n","qft3_circ.H(0)\n","qft3_circ.CU1(0.5, 1, 0)\n","qft3_circ.CU1(0.25, 2, 0)\n","qft3_circ.H(1)\n","qft3_circ.CU1(0.5, 2, 1)\n","qft3_circ.H(2)\n","qft3_circ.SWAP(0, 2)\n","render_circuit_jupyter(qft3_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can generalise the quantum Fourier transform to $n$ qubits by iterating over the qubits as follows"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_qft_circuit(n_qubits: int) -> Circuit:\n"," circ = Circuit(n_qubits, name=\"QFT\")\n"," for i in range(n_qubits):\n"," circ.H(i)\n"," for j in range(i + 1, n_qubits):\n"," circ.CU1(1 / 2 ** (j - i), j, i)\n"," for k in range(0, n_qubits // 2):\n"," circ.SWAP(k, n_qubits - k - 1)\n"," return circ"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_circ: Circuit = build_qft_circuit(4)\n","render_circuit_jupyter(qft4_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have the generalised circuit we can wrap it up in a `CircBox` which can then be added to another circuit as a subroutine."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import CircBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qft4_box: CircBox = CircBox(qft4_circ)\n","qft_circ = Circuit(4).add_gate(qft4_box, [0, 1, 2, 3])\n","render_circuit_jupyter(qft_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Note how the `CircBox` inherits the name `QFT` from the underlying circuit."]},{"cell_type":"markdown","metadata":{},"source":["Recall that in our phase estimation algorithm we need to use the inverse QFT.\n","\n","$$\n","\\begin{equation}\n","\\text{QFT}^† : \\sum_{k=0}^{N - 1} e^{2 \\pi ijk/N}|k\\rangle \\longmapsto |j\\rangle\\,, \\quad N= 2^k\n","\\end{equation}\n","$$\n","\n","\n","Now that we have the QFT circuit we can obtain the inverse by using `CircBox.dagger`. We can also verify that this is correct by inspecting the circuit inside with `CircBox.get_circuit()`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["inv_qft4_box = qft4_box.dagger\n","render_circuit_jupyter(inv_qft4_box.get_circuit())"]},{"cell_type":"markdown","metadata":{},"source":["## The Controlled Unitary Operations"]},{"cell_type":"markdown","metadata":{},"source":["In the phase estimation algorithm we repeatedly perform controlled unitary operations. In the canonical variant, the number of controlled unitaries will be $2^m - 1$ where $m$ is the number of measurement qubits."]},{"cell_type":"markdown","metadata":{},"source":["The form of $U$ will vary depending on the application. For chemistry or condensed matter physics $U$ typically be the time evolution operator $U(t) = e^{- i H t}$ where $H$ is the problem Hamiltonian."]},{"cell_type":"markdown","metadata":{},"source":["Suppose that we had the following decomposition for $H$ in terms of Pauli strings $P_j$ and complex coefficients $\\alpha_j$.\n","\n","\\begin{equation}\n","H = \\sum_j \\alpha_j P_j\\,, \\quad \\, P_j \\in \\{I, X, Y, Z\\}^{\\otimes n}\n","\\end{equation}\n","\n","Here Pauli strings refers to tensor products of Pauli operators. These strings form an orthonormal basis for $2^n \\times 2^n$ matrices."]},{"cell_type":"markdown","metadata":{},"source":["If we have a Hamiltonian in the form above, we can then implement $U(t)$ as a sequence of Pauli gadget circuits. We can do this with the [PauliExpBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.PauliExpBox) construct in pytket. For more on `PauliExpBox` see the [user manual](https://tket.quantinuum.com/user-manual/manual_circuit.html#pauli-exponential-boxes)."]},{"cell_type":"markdown","metadata":{},"source":["Once we have a circuit to implement our time evolution operator $U(t)$, we can construct the controlled $U(t)$ operations using [QControlBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.QControlBox). If our base unitary is a sequence of `PauliExpBox`(es) then there is some structure we can exploit to simplify our circuit. See this [blog post](https://tket.quantinuum.com/tket-blog/posts/controlled_gates/) on [ConjugationBox](https://tket.quantinuum.com/api-docs/circuit.html#pytket.circuit.ConjugationBox) for more."]},{"cell_type":"markdown","metadata":{},"source":["In what follows, we will just construct a simplified instance of QPE where the controlled unitaries are just $\\text{CU1}$ gates."]},{"cell_type":"markdown","metadata":{},"source":["## Putting it all together"]},{"cell_type":"markdown","metadata":{},"source":["We can now define a function to build our entire QPE circuit. We can make this function take a state preparation circuit and a unitary circuit as input as well. The function also has the number of measurement qubits as input which will determine the precision of our phase estimate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit import QControlBox"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def build_phase_est_circuit(\n"," n_measurement_qubits: int, state_prep_circuit: Circuit, unitary_circuit: Circuit\n",") -> Circuit:\n"," qpe_circ: Circuit = Circuit()\n"," n_state_prep_qubits = state_prep_circuit.n_qubits\n"," measurement_register = qpe_circ.add_q_register(\"m\", n_measurement_qubits)\n"," state_prep_register = qpe_circ.add_q_register(\"p\", n_state_prep_qubits)\n"," qpe_circ.add_circuit(state_prep_circuit, list(state_prep_register))\n","\n"," # Create a controlled unitary with a single control qubit\n"," unitary_circuit.name = \"U\"\n"," controlled_u_gate = QControlBox(CircBox(unitary_circuit), 1)\n","\n"," # Add Hadamard gates to every qubit in the measurement register\n"," for m_qubit in measurement_register:\n"," qpe_circ.H(m_qubit)\n","\n"," # Add all (2**n_measurement_qubits - 1) of the controlled unitaries sequentially\n"," for m_qubit in range(n_measurement_qubits):\n"," control_index = n_measurement_qubits - m_qubit - 1\n"," control_qubit = [measurement_register[control_index]]\n"," for _ in range(2**m_qubit):\n"," qpe_circ.add_qcontrolbox(\n"," controlled_u_gate, control_qubit + list(state_prep_register)\n"," )\n","\n"," # Finally, append the inverse qft and measure the qubits\n"," qft_box = CircBox(build_qft_circuit(n_measurement_qubits))\n"," inverse_qft_box = qft_box.dagger\n"," qpe_circ.add_circbox(inverse_qft_box, list(measurement_register))\n"," qpe_circ.measure_register(measurement_register, \"c\")\n"," return qpe_circ"]},{"cell_type":"markdown","metadata":{},"source":["## Phase Estimation with a Trivial Eigenstate\n","\n","Lets test our circuit construction by preparing a trivial $|1\\rangle$ eigenstate of the $\\text{U1}$ gate. We can then see if our phase estimation circuit returns the expected eigenvalue."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","U1(\\phi)|1\\rangle = e^{i\\phi} = e^{2 \\pi i \\theta} \\implies \\theta = \\frac{\\phi}{2}\n","\\end{equation}\n","$$\n","\n","So we expect that our ideal phase $\\theta$ will be half the input angle $\\phi$ to our $U1$ gate."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["prep_circuit = Circuit(1).X(0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["input_angle = 0.73 # angle as number of half turns"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["unitary_circuit = Circuit(1).U1(input_angle, 0)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["qpe_circ_trivial = build_phase_est_circuit(\n"," 4, state_prep_circuit=prep_circuit, unitary_circuit=unitary_circuit\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(qpe_circ_trivial)"]},{"cell_type":"markdown","metadata":{},"source":["Lets use the noiseless `AerBackend` simulator to run our phase estimation circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import AerBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["backend = AerBackend()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["compiled_circ = backend.get_compiled_circuit(qpe_circ_trivial)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_shots = 1000\n","result = backend.run_circuit(compiled_circ, n_shots)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(result.get_counts())"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult\n","import matplotlib.pyplot as plt"]},{"cell_type":"markdown","metadata":{},"source":["plotting function for QPE Notebook"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def plot_qpe_results(\n"," sim_result: BackendResult,\n"," n_strings: int = 4,\n"," dark_mode: bool = False,\n"," y_limit: int = 1000,\n",") -> None:\n"," \"\"\"\n"," Plots results in a barchart given a BackendResult. the number of stings displayed\n"," can be specified with the n_strings argument.\n"," \"\"\"\n"," counts_dict = sim_result.get_counts()\n"," sorted_shots = counts_dict.most_common()\n"," n_most_common_strings = sorted_shots[:n_strings]\n"," x_axis_values = [str(entry[0]) for entry in n_most_common_strings] # basis states\n"," y_axis_values = [entry[1] for entry in n_most_common_strings] # counts\n"," if dark_mode:\n"," plt.style.use(\"dark_background\")\n"," fig = plt.figure()\n"," ax = fig.add_axes((0, 0, 0.75, 0.5))\n"," color_list = [\"orange\"] * (len(x_axis_values))\n"," ax.bar(\n"," x=x_axis_values,\n"," height=y_axis_values,\n"," color=color_list,\n"," )\n"," ax.set_title(label=\"Results\")\n"," plt.ylim([0, y_limit])\n"," plt.xlabel(\"Basis State\")\n"," plt.ylabel(\"Number of Shots\")\n"," plt.xticks(rotation=90)\n"," plt.show()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["plot_qpe_results(result, y_limit=int(1.2 * n_shots))"]},{"cell_type":"markdown","metadata":{},"source":["As expected we see one outcome with high probability. Lets now extract our approximation of $\\theta$ from our output bitstrings.\n","\n","suppose the $j$ is an integer representation of our most commonly measured bitstring."]},{"cell_type":"markdown","metadata":{},"source":["$$\n","\\begin{equation}\n","\\theta_{estimate} = \\frac{j}{N}\n","\\end{equation}\n","$$"]},{"cell_type":"markdown","metadata":{},"source":["Here $N = 2 ^n$ where $n$ is the number of measurement qubits."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.backends.backendresult import BackendResult"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["def single_phase_from_backendresult(result: BackendResult) -> float:\n"," # Extract most common measurement outcome\n"," basis_state = result.get_counts().most_common()[0][0]\n"," bitstring = \"\".join([str(bit) for bit in basis_state])\n"," integer = int(bitstring, 2)\n","\n"," # Calculate theta estimate\n"," return integer / (2 ** len(bitstring))"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["theta = single_phase_from_backendresult(result)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(theta)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(input_angle / 2)"]},{"cell_type":"markdown","metadata":{},"source":["Our output is close to half our input angle $\\phi$ as expected. Lets calculate our error."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["error = round(abs(input_angle - (2 * theta)), 3)\n","print(error)"]},{"cell_type":"markdown","metadata":{},"source":["## Suggestions for further reading\n","\n","In this notebook we have shown the canonical variant of quantum phase estimation. There are several other variants.\n","\n","Quantinuum paper on Bayesian phase estimation -> https://arxiv.org/pdf/2306.16608.pdf\n","Blog post on `ConjugationBox` -> https://tket.quantinuum.com/tket-blog/posts/controlled_gates/ - efficient circuits for controlled Pauli gadgets.\n","\n","As mentioned quantum phase estimation is a subroutine in Shor's algorithm. Read more about how phase estimation is used in period finding."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2}