{
"cells": [
{
"cell_type": "markdown",
"id": "85be67db",
"metadata": {},
"source": [
"# Quantum circuits and simulation of noisy algorithms\n",
"\n",
"Welcome to this hands-on session. The session is build around interactive Notebooks, that include code, explanations and tasks. Please run the code cells on your computer and follow the instructions of the tasks to deepen your learning. \n",
"\n",
"Jami Rönkkö, IQM Quantum Computers, email: jami@meetiqm.com"
]
},
{
"cell_type": "markdown",
"id": "7fafd7c7",
"metadata": {},
"source": [
"# Quantum Phase Estimation algorithm\n",
"\n",
"In this notebook you will learn:\n",
"- What is the Quantum Phase Estimation algorithm \n",
"- How it works and how to implement it\n",
"\n",
"In an earlier notebook we learned about Quantum Fourier Transformation and how it transforms states expressed in *computational basis* into *Fourier basis*.\n",
"\n",
"As the name suggests, the goal of Quantum Phase Estimation is to estimate the phase of an eigenvalue of a unitary operator. In mathematical terms, given some statevector $|\\psi$⟩ and unitary operator $U$ (for example some quantum gate) whose eigenstate is $|\\psi$⟩, the QPE algorithm finds the phase $\\theta$ appearing in the eigenvalue equation:\n",
"$$\n",
"U∣\\hspace{-0.1cm}\\psi⟩=e^{2\\pi i \\theta}∣\\hspace{-0.1cm}\\psi⟩\n",
"$$\n",
"\n",
"To make this immediately more concrete, let us take as an example single-qubit state $|\\psi$⟩ = ∣1⟩ that is an eigenstate of the phase gate $U=P(\\phi)$, since\n",
"$$\n",
"P(\\phi)∣\\hspace{-0.1cm}1⟩=e^{i\\phi}∣\\hspace{-0.1cm}1⟩\n",
"$$\n",
"We can then use QPE to find $\\phi$. Note that knowing the phase $\\theta$ in the eigenvalue $e^{2\\pi i \\theta}$, means that we know the eigenvalue itself. Finding eigenvalues (especially eigenenergies for ground states) is important in computational chemistry. \n",
"\n",
"## Intuition behind QPE\n",
"QPE utilises the *symmetry of two-qubit phase gates*: either qubit can be though of as the control or target (**phase kickback**). Because of this, we can write the phase produced by unitary operator (gate) to a target qubit(s) into a separate register of 'counting qubits'. This is done by setting the counting qubit register into **Fourier basis** and changing the phases of their states based on the phase the unitary operator $U$ adds to the target qubit.\n",
"\n",
"Specifically, the nearest integer $k$ to $2^n\\theta$ will be encoded in the Fourier state ($n$ is the number of counting qubits). If $2^n\\theta$ happens to be integer, we obtain the exact phase by reading out $k$ and solving $\\theta = k / 2^n$. (Otherwise QPE will give approximate $\\theta$ if one repeats QPE many times and considers the mean of the outcome as estimate for $\\theta$.)\n",
"\n",
"From the Quantum Fourier Transform notebook we remember that reading out integer from a Fourier basis state requires application of **inverse Quantum Fourier Transform**; after which the integer can be read as a binary string from the measured computational basis state. This trick will conclude QPE algorithm.\n",
"\n",
"To express this in algorithmic steps:\n",
"\n",
"**Algorithm**: Quantum Phase Estimation\n",
"\n",
"**Goal**:\\\n",
" Figure out the phase $\\theta$ caused by (in general unknown) gate $U$ to (in general unknown) target qubit(s) state ∣$\\psi$⟩, given that ∣$\\psi$⟩ is eigenstate of $U$ with eigenvalue $e^{2\\pi i\\theta}$ \n",
" \n",
"**Procedure**:\n",
"1) Initalize the $n$ qubit counting register to Fourier basis by applying Hadamard gate to each (bigger $n$ yields in general better estimate for $\\theta$)\n",
"2) Apply the controlled-$U$ gate $2^j$ times to $j\\text{'th}$ counting qubit and target qubit for $j$ between $0$ and $n$-1\n",
"3) Apply the inverse QFT to the counting qubit register\n",
"4) Read out the counting qubit register to obtain estimation for $2^n\\theta$ in binary format (this is exact if $2^n\\theta$ is integer)\n",
"5) In general, repeat many times to obtain most probable state as the solution (if $2^n\\theta$ is integer, the phase is exact and one measurement is enough in ideal case)\n"
]
},
{
"cell_type": "markdown",
"id": "416f94fb",
"metadata": {},
"source": [
"# Circuit for Quantum Phase Estimation Algorithm\n",
"\n",
"### GOAL: Using QPE, find out phase produced by single-qubit phase gate P($\\pi/4$).\n",
"\n",
"We follow the steps displayed above with $U=P(\\pi/4)$. **In this case we know the answer ahead of time**: \n",
"\n",
"Since $\\theta$ is defined as the number multiplying $2\\pi i$ in the eigenvalue $e^{2\\pi i \\theta}$ of the state ∣1⟩ and the phase gate $P(\\pi/4)$ adds phase $e^{i\\pi/4}$ to ∣1⟩, we see that now $\\theta=1/8$. \n",
" \n",
"Finally we note that, for three counting qubits $n=3$ and $\\theta=1/8$, the number stored in our qubit register will be $2^n\\theta=8/8=1$. This means that we can solve $\\theta$ with 100% accuracy with one measurement (in absence of noise). If $\\theta$ would be $1/4$, we would need only two counting qubits to have integer valued $2^n\\theta$. You'll find the general case of non-integer $2^n\\theta$ here: https://qiskit.org/textbook/ch-algorithms/quantum-phase-estimation.html\n",
"\n",
"Let us now go ahead and create the circuit that implements QPE."
]
},
{
"cell_type": "markdown",
"id": "e12fc468",
"metadata": {},
"source": [
"## TASK 1

\n",
"> Write a code snippet that creates a `statevector_simulator`, executes the `QPE_circuit` and views the statevector. Hint: you get this from the previous notebooks\n",
">\n",
"> Read and execute all the cells below. As you do this, paste the aforementioned code snippet to the cells reading 'Simulate the circuit and view statevector here'. \n",
">\n",
"> In this way we can see how the statevector of the QPU looks at each stage of the algorithm. This is valuable insight to algorithms that can be only obtained through simulators (or pen and paper) as opposed to running real QPU's."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "46109548",
"metadata": {},
"outputs": [],
"source": [
"# Import everything from qiskit \n",
"from qiskit import *\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"id": "419e0807",
"metadata": {},
"source": [
"## 1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14d0a73f",
"metadata": {},
"outputs": [],
"source": [
"# Create a circuit with 3 counting qubits and one qubit for the eigenstate of phase gate\n",
"QPE_circuit = QuantumCircuit(4, 3) # Only the counting qubits need be measured -> add 3 classical qubits for storing measurement outcome\n",
"QPE_circuit.x(3) # Set the last qubit to |1> i.e. eigenstate\n",
"\n",
"# Apply Hadamard gates to the counting qubits to transform the register to the Fourier basis: |000> -> |+++> (equal superposition of all computational states) \n",
"QPE_circuit.h([0,1,2])\n",
"\n",
"QPE_circuit.draw(output='mpl')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "500cce1d",
"metadata": {},
"outputs": [],
"source": [
"# Simulate the circuit and view statevector here"
]
},
{
"cell_type": "markdown",
"id": "38b99c17",
"metadata": {},
"source": [
"## 2)\n",
"\n",
"Then comes the main part of QPE, we perform controlled phase gates to encode number $2^n\\theta$ to the counting qubits in the Fourier basis. This is essentially same thing we do in Quantum Fourier Transform:\n",
"1) first qubit gets phase-rotated $\\pi/4$ radians (since our target gate is P($\\pi/4$))\n",
"2) second qubit gets rotated twice that much (we apply two $\\pi/4$ phase-rotations)\n",
"3) third qubit gets rotated again twice that much (four $\\pi/4$ phase-rotations)\n",
"4) we would continue like this if there would be more counting qubits \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e59f691d",
"metadata": {},
"outputs": [],
"source": [
"repetitions = 1\n",
"for counting_qubit in range(3): # Loop over all counting qubits\n",
" for i in range(repetitions): # Each qubit will be phase-rotated different multiple of pi/4 to encode this phase in the Fourier state of the register\n",
" QPE_circuit.cp(np.pi/4, counting_qubit, 3) # Apply conditional P(pi/4) gate to target qubit and counting qubit\n",
" repetitions *= 2 # Increment repetitions such that each qubit gets phase-rotated double twice as much as previous \n",
"QPE_circuit.draw(output='mpl')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "247162dc",
"metadata": {},
"outputs": [],
"source": [
"# Simulate the circuit and view statevector here"
]
},
{
"cell_type": "markdown",
"id": "3ba11166",
"metadata": {},
"source": [
"## 3) \n",
"\n",
"Final step of QPE circuit transforms the counting qubit register from Fourier basis back to computational basis. This is done by the inverse Quantum Fourier Transform. Let's recreate the inverse QFT function of previous notebook here:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "164d3b2f",
"metadata": {},
"outputs": [],
"source": [
"# Function that applies n qubit QFT to a circuit\n",
"def QFT(circuit, n):\n",
" for i in range(n):\n",
" circuit.barrier() # For sake of clarity, insert visual barriers between the QFT segments\n",
" circuit.h(i) # Apply Hadamard to each qubit\n",
" for j in range(i+1, n): \n",
" circuit.cp(np.pi / 2**(j-i), i, j) # Apply controlled P gate with angle pi/2^(j-i) from qubit i to all qubits with higher index j\n",
" return circuit\n",
"\n",
"# Function that applies n-qubit inverse QFT to a circuit\n",
"def inverse_QFT(circuit, n):\n",
" qft_circuit = QFT(QuantumCircuit(n, name =\"QFT\"), n) # Create a QFT circuit\n",
" inverse_qft_circuit = qft_circuit.inverse() # Create an inverse of the circuit\n",
" circuit.append(inverse_qft_circuit, circuit.qubits[:n]) # Add the gates of inverse QFT to the first n qubits of the targer circuit\n",
" return circuit"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74a57993",
"metadata": {},
"outputs": [],
"source": [
"# Apply inverse QFT\n",
"QPE_circuit = inverse_QFT(QPE_circuit, 3)\n",
"# Measure\n",
"QPE_circuit.barrier()\n",
"QPE_circuit.measure([0,1,2],[0,1,2])\n",
"\n",
"QPE_circuit.draw(output='mpl')"
]
},
{
"cell_type": "markdown",
"id": "1eae0cd9",
"metadata": {},
"source": [
"> Note: the inverse QFT circuit is here truncated to a single box for the printout. This is good way of visualizing circuits with known subroutines."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3bdf8409",
"metadata": {},
"outputs": [],
"source": [
"# Simulate the circuit and view statevector here"
]
},
{
"cell_type": "markdown",
"id": "9599ed6f",
"metadata": {},
"source": [
"We then measure the counting register:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ede78c69",
"metadata": {},
"outputs": [],
"source": [
"simulator = Aer.get_backend('qasm_simulator') # We use another simulator for measurement simulations\n",
"result = execute(QPE_circuit, backend = simulator, shots = 1).result() # Single shot should suffice to give right answer with 100%\n",
"\n",
"from qiskit.visualization import plot_histogram\n",
"plot_histogram(result.get_counts())"
]
},
{
"cell_type": "markdown",
"id": "0ed8ffdd",
"metadata": {},
"source": [
"We have now succesfully executed the Quantum Phase Estimation algorithm and read out binary representation for $8\\theta$ from the three counting qubits. The reason for this extra factor of 8 is that the application of controlled P($2\\pi\\theta$) gates in the Fourier basis differs a bit from Quantum Fourier Transform. Hence, when we apply the inverse QFT we obtain $2^n\\theta$ instead of just $\\theta$ in the computational basis. "
]
},
{
"cell_type": "markdown",
"id": "d03cd53a",
"metadata": {},
"source": [
"## TASK 2

\n",
"> We read bitstring $100$ from our qubits. Confirm that this is always the case by increasing `shots` in the cell above.\n",
">\n",
"> Solve $\\theta$ from the equation $2^n \\theta = k$, where $n$ is the number of counting qubits and $k$ is the number we read out from the counting register in decimal format. \n",
">\n",
"> Note: QFT inverts the qubit order. Hence you should read the bitstring $100$ in opposite order as $001$ \n",
">\n",
"> Did we get the correct phase $\\phi=\\pi/4$? See 'Interpreting the result' below."
]
},
{
"cell_type": "markdown",
"id": "4ff1a767",
"metadata": {},
"source": [
"### Interpreting the result\n",
"Once we have figured out $\\theta$, we obtain the phase $e^{i\\pi/4}$ added by P($\\phi=\\pi/4$) to ∣1⟩ gate by inserting $\\theta$ to the eigenvalue defined as $e^{2\\pi i\\theta}$."
]
},
{
"cell_type": "markdown",
"id": "cd2cee5c",
"metadata": {},
"source": [
"## EXTRA TASK

\n",
"> You can try the QPE for phase gate P($\\phi$) with different angle $\\phi$. \n",
"> Remember that if $2^n \\theta$ is not integer, you will not get exact result and will have to run the circuit multiple times for estimate. \n",
">\n",
"> For guidance, see: https://qiskit.org/textbook/ch-algorithms/quantum-phase-estimation.html"
]
},
{
"cell_type": "markdown",
"id": "b00868be",
"metadata": {},
"source": [
"## Takeaway\n",
"\n",
"- Quantum Phase Estimation is a useful application of the Quantum Fourier Transform for finding phase or eigenvalue of a unitary operator (gate)\n",
" \n",
"- It is used also in Shor's algorithm and for finding ground states of molecules\n",
"\n",
"In the last notebook we will discover the effect of noise on quantum computers and simulate noisy execution of QPE algorithm on the Helmi QPU. "
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.9.13"
},
"vscode": {
"interpreter": {
"hash": "36cf16204b8548560b1c020c4e8fb5b57f0e4c58016f52f2d4be01e192833930"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}