Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion notebooks/textbook/Bells_Inequality.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
"# from braket.aws import AwsDevice\n",
"# iqm_garnet = AwsDevice(\"arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet\")\n",
"# iqm_tasks = run_bell_inequality([circAB, circAC, circBC], iqm_garnet, shots=1000)\n",
"# results, pAB, pAC, pBC = get_bell_inequality_results(iqm_tasks)\n"
"# results, pAB, pAC, pBC = get_bell_inequality_results(iqm_tasks)"
]
},
{
Expand Down
375 changes: 375 additions & 0 deletions notebooks/textbook/Hadamard_Test.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hadamard Test\n",
"---\n",
"\n",
"\n",
"The Hadamard test is a quantum circuit that allows estimation of the real and imaginary parts of the expected value of a unitary operator. It is a fundamental subroutine used in many quantum algorithms. The test works by applying a controlled-unitary operation between an auxiliary qubit and the system of interest, with the measurement statistics of the auxiliary qubit encoding information about the unitary's expectation value. For the original paper, see [1]. For a more modern treatment, see [2].\n",
"\n",
"In this notebook, we explore the Braket implementation of the Hadamard test for estimating the real and imaginary parts of the expected value of a unitary operator.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## References\n",
"\n",
"[1] R. Cleve, A. Ekert, C. Macchiavello, and M. Mosca (1998). Quantum algorithms revisited. [arXiv:quant-ph/9708016](https://arxiv.org/abs/quant-ph/9708016).\n",
"\n",
"[2] Nielsen, Michael A., Chuang, Isaac L. (2010). Quantum Computation and Quantum Information (2nd ed.). Cambridge: Cambridge University Press."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Description\n",
"\n",
"Given a unitary operator $U$ and qubits $|0\\rangle|\\psi\\rangle$, the Hadamard test is used to estimate the expected value of $U$ on $|\\psi\\rangle$, i.e. $\\langle \\psi | U |\\psi \\rangle$. We will walk through the algorithm step by step. First, we apply a Hadamard gate to $|0\\rangle$, putting the system into the state,\n",
" \n",
"$$\n",
"\\dfrac{|0\\rangle + |1\\rangle}{\\sqrt{2}}|\\psi\\rangle.\n",
"$$ \n",
" \n",
"We then apply the unitary operator $U$ on $|\\psi\\rangle$, controlled on the first qubit, to obtain the state, \n",
"$$\n",
"\\dfrac{|0\\rangle |\\psi\\rangle + |1\\rangle U |\\psi\\rangle}{\\sqrt{2}} .\n",
"$$ \n",
"Then, a Hadamard gate is applied to the first qubit once more, giving the state,\n",
"$$\n",
"\\dfrac{|0\\rangle(|\\psi\\rangle + U |\\psi\\rangle) + |1\\rangle(|\\psi\\rangle - U |\\psi\\rangle)}{2}.\n",
"$$\n",
"\n",
"And this completes the Hadamard test. Now, we get the probability of measuring the first qubit in the state $|0\\rangle$ as\n",
"$$\n",
"\\begin{align*}\n",
"P_0 &= \\bigg\\lVert\\dfrac{|\\psi\\rangle + U|\\psi\\rangle}{2}\\bigg\\rVert^2 \\\\\n",
" &= \\dfrac{\\langle \\psi | \\psi \\rangle + \\langle \\psi | U |\\psi \\rangle + \\langle \\psi | U^\\dagger |\\psi \\rangle + \\langle \\psi | U^\\dagger U |\\psi \\rangle}{4} \\\\\n",
" &= \\dfrac{1 + 2\\langle \\psi | U |\\psi \\rangle + 1}{4}\\\\\n",
" &= \\dfrac{1 + Re\\langle \\psi | U |\\psi \\rangle}{2},\n",
"\\end{align*}\n",
"$$\n",
"allowing us to estimate the expected value of the real part as $ Re(\\langle \\psi | U |\\psi \\rangle) = 2 P_0 - 1$. The algorithm can be modified to estimate the imaginary part by applying a phase shift to the first qubit after the first Hadamard gate, the proof of which is left as an exercise or inspiration for further reading.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Run on a local simulator\n",
"\n",
"Braket provides an implementation of the Hadamard test, `hadamard_test_circuit`, for estimating the expected values of both the real and imaginary parts of a given unitary operator, `U`.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"from notebook_plotting import plot_bitstrings_formatted\n",
"import numpy as np\n",
"\n",
"%matplotlib inline\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"%matplotlib inline\n",

This shouldn't be needed for modern Jupyter versions.

"\n",
"from braket.aws import AwsDevice\n",
"from braket.circuits import Circuit, Qubit\n",
"from braket.devices import LocalSimulator\n",
"from braket.tracking import Tracker\n",
"\n",
"from braket.experimental.algorithms.hadamard_test.hadamard_test import hadamard_test_circuit\n",
"\n",
"tracker = Tracker().start() # to track Braket costs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For our controlled unitary operator, we will use the Pauli-Z gate, which is represented by the matrix,\n",
"$$\n",
"\\begin{bmatrix}\n",
"1 & 0 \\\\\n",
"0 & -1\n",
"\\end{bmatrix}\n",
"$$\n",
"Though Braket has built-in support for the Pauli-Z gate, we will use the `unitary` method to apply the gate to the qubit. Consider playing around with different matrices to test your understanding of the algorithm."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"T : │ 0 │\n",
" ┌───┐ \n",
"q0 : ─┤ U ├─\n",
" └───┘ \n",
"T : │ 0 │\n"
]
}
],
"source": [
"pauli_z = np.array([[1, 0], [0, -1]])\n",
"controlled_unitary = Circuit().unitary([0], pauli_z, \"U\")\n",
"ancilla = Qubit(0)\n",
"print(controlled_unitary)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Real Part Estimation"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"T : │ 0 │ 1 │ 2 │\n",
" ┌───┐ ┌───┐ \n",
"q0 : ─┤ H ├───●───┤ H ├─\n",
" └───┘ │ └───┘ \n",
" ┌─┴─┐ \n",
"q1 : ───────┤ U ├───────\n",
" └───┘ \n",
"T : │ 0 │ 1 │ 2 │\n"
]
}
],
"source": [
"ht_circuit = hadamard_test_circuit(ancilla, controlled_unitary)\n",
"print(ht_circuit)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default, Braket will measure all qubits. Since the Hadamard test only requires measuring the ancilla qubits, we will explicitly add a measurement instruction to the circuit to avoid unnecessary measurements."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"T : │ 0 │ 1 │ 2 │ 3 │\n",
" ┌───┐ ┌───┐ ┌───┐ \n",
"q0 : ─┤ H ├───●───┤ H ├─┤ M ├─\n",
" └───┘ │ └───┘ └───┘ \n",
" ┌─┴─┐ \n",
"q1 : ───────┤ U ├─────────────\n",
" └───┘ \n",
"T : │ 0 │ 1 │ 2 │ 3 │\n"
]
}
],
"source": [
"ht_circuit.measure(0)\n",
"print(ht_circuit)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we can run the circuit on a local simulator. Take note of the small bit of algebra we need to extract the real part, which can also be seen in the description of the algorithm above."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0\n"
]
}
],
"source": [
"device = LocalSimulator()\n",
"task = device.run(ht_circuit, shots=1000)\n",
"\n",
"probs = task.result().measurement_probabilities\n",
"p_zero = probs.get('0', 0)\n",
"\n",
"real_part = 2 * p_zero - 1\n",
"\n",
"print(real_part)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Imaginary Part Estimation\n",
"\n",
"Braket's `hadamard_test_circuit` supports the real part estimation by default, but we can also estimate the imaginary part by setting the `component` argument to `'imaginary'`. We will use the same controlled unitary operator as before."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n",
" ┌───┐ ┌───┐ ┌───┐ ┌───┐ \n",
"q0 : ─┤ H ├─┤ S ├───●───┤ H ├─┤ M ├─\n",
" └───┘ └───┘ │ └───┘ └───┘ \n",
" ┌─┴─┐ \n",
"q1 : ─────────────┤ U ├─────────────\n",
" └───┘ \n",
"T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n"
]
}
],
"source": [
"ht_circuit = hadamard_test_circuit(Qubit(0), controlled_unitary, component='imaginary')\n",
"ht_circuit.measure(0)\n",
"print(ht_circuit)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The only difference in the circuit is the addition of a phase shift gate to the first qubit, which allows us to estimate the expected value of the imaginary part."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-0.01200000000000001\n"
]
}
],
"source": [
"device = LocalSimulator()\n",
"task = device.run(ht_circuit, shots=1000)\n",
"\n",
"probs = task.result().measurement_probabilities\n",
"p_zero = probs.get('0', 0)\n",
"\n",
"imaginary_part = 2 * p_zero - 1\n",
"\n",
"print(imaginary_part)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Run on a QPU \n",
"\n",
"To run the Hadamard test on a QPU, we replace the LocalSimulator with an AwsDevice. The cost to run this experiment is $0.3 per task and $0.00145 per shot on the IQM Garnet device, that totals $1.75 USD. Because Garnet does not support arbitrary unitary operators, we will directly apply the Pauli-Z gate in this example."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"To run the Hadamard test on a QPU, we replace the LocalSimulator with an AwsDevice. The cost to run this experiment is $0.3 per task and $0.00145 per shot on the IQM Garnet device, that totals $1.75 USD. Because Garnet does not support arbitrary unitary operators, we will directly apply the Pauli-Z gate in this example."
"To run the Hadamard test on a QPU, we replace the LocalSimulator with an AwsDevice. The cost to run this experiment is \\\\$0.3 per task and \\\\$0.00145 per shot on the IQM Garnet device, which totals \\\\$1.75 USD. Because Garnet does not support arbitrary unitary operators, we will directly apply the Pauli-Z gate in this example."

]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"# controlled_unitary = Circuit().z(0)\n",
"# ht_circuit = hadamard_test_circuit(controlled_unitary)\n",
Comment on lines +306 to +307
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can uncomment this and run on Garnet before we merge the PR if you want.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be great!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"# controlled_unitary = Circuit().z(0)\n",
"# ht_circuit = hadamard_test_circuit(controlled_unitary)\n",
"# controlled_unitary = Circuit().z(0)\n",
"# ht_circuit = hadamard_test_circuit(Qubit(0), controlled_unitary)\n",

"# ht_circuit.measure(0)\n",
"# print(ht_circuit)\n",
"\n",
"# device = AwsDevice(\"arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet\")\n",
"# task = device.run(ht_circuit, shots=1000)\n",
"\n",
"# counts = task.result().measurement_counts\n",
"# p_zero = counts.get('0', 0) / 1000\n",
"\n",
"# real_part = 2 * p_zero - 1\n",
"\n",
"# print(real_part)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Task Summary\n",
"{} \n",
"\n",
"Estimated cost to run this example: 0.00 USD\n"
]
}
],
"source": [
"print(\"Task Summary\")\n",
"print(f\"{tracker.quantum_tasks_statistics()} \\n\")\n",
"print(\n",
" f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost():.2f} USD\"\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "env",
"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.12.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The Hadamard test is a quantum circuit that allows estimation of the real and imaginary parts of the expected value of a unitary operator. It is a fundamental subroutine used in many quantum algorithms, including quantum phase estimation where it helps extract eigenvalue information through controlled operations. The test works by applying a controlled-unitary operation between an auxiliary qubit and the system of interest, with the measurement statistics of the auxiliary qubit encoding information about the unitary's expectation value.

<!--
[metadata-name]: Hadamard Test
[metadata-tags]: Textbook
[metadata-url]: https://github.com/amazon-braket/amazon-braket-algorithm-library/tree/main/src/braket/experimental/algorithms/hadamard_test
-->
Loading