import numpy as np
from typing import *

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector, Operator
from qiskit.visualization import plot_bloch_multivector
sim = AerSimulator()

from util import zero, one, demonstrate_measure, PlotGateOpOnBloch, plot_bloch_vector

Foundations: Quantum Circuits for a Single Qubit System#

In this notebook, we will introduce quantum circuits that act on single qubit system to perform computations in the gate-based model of quantum computation. Later, we will extend our definitions to cover multi-qubit quantum systems and multi-qubit quantum circuits.

References

  1. Introduction to Classical and Quantum Computing: Chapter 2

Quantum Gates on a Single Qubit#

Similar to how we could apply classical logic gates to classical bits to perform computation, we can apply quantum gates to qubits to perform quantum computation. We will introduce a few single qubit quantum gates now, starting with the Pauli gates.

Pauli Gates: Rotations on the Bloch Sphere#

As a reminder, the quantum state of a single qubit system can be encoded on a Bloch sphere. The Pauli gates are a set of 3 gates that encode rotations around the 3 axes of the Bloch sphere: \(X\), \(Y\), and \(Z\). We’ll cover these in turn now.

X Gate#

The X gate performs a rotation around the \(X\)-axis of the Bloch sphere. It is similar to a logical negation.

qc_x = QuantumCircuit(1)  # Create a quantum circuit with a single qubit
qc_x.x(0)                 # Apply an x gate to qubit 0
qc_x.draw(output="mpl", style="iqp")
../_images/8e537ee8bd07a68ac8588b2fb75857016640648ecae959d20b73fbfbe92b35be.png
with PlotGateOpOnBloch() as ctx:
    plot_bloch_vector(zero, ax=ctx.ax1, title="Before X")
    plot_bloch_vector(zero.evolve(Operator(qc_x)), ax=ctx.ax2, title="After X")
../_images/bf1668ca2a649ddf52e0f597451af3558e078917c42c423042d9ed3f305c132b.png

Y Gate#

The Y gate performs a rotation around the \(Y\)-axis of the Bloch sphere. It performs a flip and phase flip.

qc_y = QuantumCircuit(1)
qc_y.y(0)
qc_y.draw(output="mpl", style="iqp")
../_images/fecefe33118ad2f0c9e4951466657494eef4a2c33c64ab42621e366a932600db.png
zero.evolve(Operator(qc_y)), one.evolve(Operator(qc_y))
(Statevector([0.+0.j, 0.+1.j],
             dims=(2,)),
 Statevector([0.-1.j, 0.+0.j],
             dims=(2,)))
with PlotGateOpOnBloch() as ctx:
    plot_bloch_vector(zero, ax=ctx.ax1, title="Before Y")
    plot_bloch_vector(zero.evolve(Operator(qc_y)), ax=ctx.ax2, title="After Y")
../_images/79b28a10f823d052191c6ed5d1015550678d4d0dec35557900ede188c5aeb67e.png
with PlotGateOpOnBloch() as ctx:
    q = 1/np.sqrt(2)*zero + 1/np.sqrt(2)*one
    plot_bloch_vector(q, ax=ctx.ax1, title="Before Y")
    plot_bloch_vector(q.evolve(Operator(qc_y)), ax=ctx.ax2, title="After Y")
../_images/7151e2825ae94d3d11797ef143d38f761587e1727b62bf463c9ae732e0f9395c.png

Z Gate#

The \(Z\) gate performs a rotation around the \(Z\) axis of the Bloch sphere. It performs a phase flip.

qc_z = QuantumCircuit(1)
qc_z.z(0)
qc_z.draw(output="mpl", style="iqp")
../_images/097c5b4e2b40882b9ac2ea15aa46bffba5fe26f67a1f67bcd8e1cb8aa05e758b.png
zero.evolve(Operator(qc_z)), one.evolve(Operator(qc_z))
(Statevector([1.+0.j, 0.+0.j],
             dims=(2,)),
 Statevector([ 0.+0.j, -1.+0.j],
             dims=(2,)))
plot_bloch_multivector(zero.evolve(Operator(qc_z)))
../_images/1a3de8fec85b8951ccb8d7fc4f4ddf6cd1e3590c2ad7484391bcf713f5b96dfe.png

Hadamard Gate: Superposition#

The Hadamard gate, written \(H\), takes a \(|0\rangle\) qubit and puts it in superposition. The \(H\) gate is a commonly used gate in many quantum algorithms and protocols.

qc_h = QuantumCircuit(1)
qc_h.h(0)
qc_h.draw(output="mpl", style="iqp")
../_images/bf121b29c1a81f9af5793486ef9ff19e58d824dd403302af865603a84f662b1a.png
with PlotGateOpOnBloch() as ctx:
    plot_bloch_vector(zero, ax=ctx.ax1, title="Before H")
    plot_bloch_vector(zero.evolve(Operator(qc_h)), ax=ctx.ax2, title="After H")
../_images/53ddf902c56d3711be8b293c492827d2ad0b93d1db6e9a447811eef6f21a5f1a.png

Quantum Circuits#

  1. As we have seen, a quantum gate translates a point on the Bloch sphere to another point on the Bloch sphere.

  2. Since each point on the Bloch sphere encodes a quantum state, a quantum gate can be used to convert input quantum states into output quantum states. We will call a function from an input quantum state to an output quantum state a quantum computation.

  3. By sequencing the applications of quantum gates to form a quantum circuit, we can perform more complex transformations.

Example 1; H-Z#

Here is an example quantum circuit that first applies an \(H\) gate followed by a \(Z\) gate. We read the circuit from left-to-right.

qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.draw(output="mpl", style="iqp")
../_images/9146ba9db66831637c278595931a7bab465b9fbd496bd381b0587109fb36eac4.png
  1. This quantum circuit first puts the qubit in superposition with the \(H\) gate (north pole to positive \(X\)).

  2. Then, we flip the phase with the \(Z\) gate (positive \(X\) to negative \(X\)).

with PlotGateOpOnBloch() as ctx:
    plot_bloch_vector(zero, ax=ctx.ax1, title="Before H-Z")
    plot_bloch_vector(zero.evolve(Operator(qc)), ax=ctx.ax2, title="After H-Z")
../_images/e106c78b56e1e6936776211ce5748ee0cf3b8b3491319c4b74a313de9506e6f7.png

Example 2: Sequencing H Gates#

We define a function that sequentially applies \(H\) gates. The output quantum state from a previous application of a H can be fed as the input to the next \(H\).

def seq_H(n: int) -> QuantumCircuit:
    qc = QuantumCircuit(1)
    for i in range(n):
        qc.h(0)
    return qc

qc2 = seq_H(3)
qc2.draw(output="mpl", style="iqp")
../_images/96e29aa4cfff949dd3cd262321f3d6ed8cf0a76afa42a2f75f90738d23388cfe.png
with PlotGateOpOnBloch() as ctx:
    plot_bloch_vector(zero, ax=ctx.ax1, title="Before H-H")
    plot_bloch_vector(zero.evolve(Operator(qc2)), ax=ctx.ax2, title="After H-H")
../_images/2888a3a83fe5229a80ebc79c80f4c909a24a64a0cb52b17e04ce16d7cd0b8d01.png

Notice that the application of 2 H gates acted like a noop, i.e, it did not affect the quantum state. We’ll check 3 \(H\) gates now.

qc3 = seq_H(3)
qc3.draw(output="mpl", style="iqp")
../_images/96e29aa4cfff949dd3cd262321f3d6ed8cf0a76afa42a2f75f90738d23388cfe.png
with PlotGateOpOnBloch() as ctx:
    plot_bloch_vector(zero, ax=ctx.ax1, title="Before H-H-H")
    plot_bloch_vector(zero.evolve(Operator(qc3)), ax=ctx.ax2, title="After H-H-H")
../_images/5706815090cf3c0440ae907d61b6fa1065993b903b48cf2931db3251bce638a9.png

Observation: Non-Uniqueness#

  1. We observed that applying 3 \(H\) gates in a row produced the same operator as a single \(H\) gate.

  2. Thus there are many quantum circuits that perform the same quantum computation. In other words, a quantum circuit performs a non-unique quantum computation.

Summary#

  1. We saw that an operation on a single qubit systems can be implemented by a quantum gate that performs transformations on Bloch spheres.

  2. We reviewed important single qubit gates, including the Pauli gates and the Hadamard gate.