Before beginning, we will import the necessary modules into the colab.
try:
import recirq
except ImportError:
!pip install --quiet git+https://github.com/quantumlib/ReCirq
import recirq
try:
import qsimcirq
except ImportError:
!pip install qsimcirq --quiet
import qsimcirq
import cirq
import matplotlib.pyplot as plt
import recirq.toric_code.toric_code_plaquettes as tcp
import recirq.toric_code.toric_code_plotter as tcplot
import recirq.toric_code.toric_code_rectangle as tcr
import recirq.toric_code.toric_code_state_prep as tcsp
plt.rcParams['figure.dpi'] = 144
Toric code Hamiltonian
The toric code Hamiltonian
H=−∑sAs−∑pBp
involves local four-qubit parity operators, where each qubit lives on an edge in a square lattice. Here, the "star" operators As are products of Pauli Z operators around a vertex, while the "plaquette" operators Bp are products of X operators around a square, for example,
As=Zi⊗Zj⊗Zk⊗Zl Bp=Xa⊗Xb⊗Xc⊗Xd.
These local parity operators all commute with each other: all As commute, all Bp commute, and As and Bp commute with each other because they overlap on an even number of qubits. They can thus all be simultaneously diagonalized, and those shared eigenstates are also the eigenstates of H.
In our paper, we mostly work with the 31-qubit lattice above. With these boundary conditions, there is a unique ground state that has a +1 eigenvalue for all As and Bp. Note for different boundary conditions, we can have degeneracies that are locally-indistinguishable (for example on a torus, or with the "surface code" logical qubits we explore in Figure 4 of our paper).
In this module, we will primarily work with the smaller 22-qubit to avoid time and memory constraints associated with the larger rectangle.
Understanding the ground state
In this example, we focus on reproducing our first figure, where we create this unique ground state |G⟩ using a shallow unitary circuit. The general idea is to start out with |0⟩⊗22, so all ⟨As⟩=+1. We then apply projection operators I+Bp which project the state into a +1 eigenstate of Bp, after which all the local parities are +1:
|G⟩∝∏p(I+Bp)|0⟩⊗22.
To create this state, we assign a "team captain" qubit to each plaquette Bp. Starting from |0⟩⊗n, we perform a Hadamard on each team captain, and then each team captain is responsible for performing a CNOT to each of its team mates. We have to be careful with the ordering to keep things efficient and avoid the captains stepping on each other's toes. This is easier to visualize for a smaller system, for example the 12-qubit version in Figure S2, reproduced below. Note the superposition of 24 states, as there are four plaquettes Bp.
Creating |G⟩ with ReCirq
Basics: 22-qubit circuit
First, we can create a example 22-qubit grid by instantiate it using a ToricCodeRectangle
object and then plot a visualization using a ToricCodePlotter
object, both found in the ReCirq repository.
short_rectangle = tcr.ToricCodeRectangle(
origin_qubit=cirq.GridQubit(3, 0), row_vector=(1, 1), rows=2, cols=4
)
plotter = tcplot.ToricCodePlotter()
plotter.plot_code(short_rectangle)
<Axes: >
We can also see the full circuit of how to create this code (using CNOT gates) using these objects as well. By printing out the circuit moment by moment, we can see the gates lined up in a visual manner.
full_circuit = tcsp.toric_code_cnot_circuit(short_rectangle)
for idx, moment in enumerate(full_circuit):
print(f'moment {idx}\n{moment}\n')
moment 0 ╷ 0 1 2 3 4 ╶─┼─────────── 0 │ H │ 1 │ H H │ 2 │ H H │ 3 │ H H │ 4 │ H │ moment 1 ╷ 1 2 3 ╶─┼─────── 1 │ @ │ │ 2 │ @ X @ │ │ │ 3 │ X @ X │ │ 4 │ X │ moment 2 ╷ 1 2 3 4 ╶─┼───────── 1 │ @─X │ 2 │ @─X @─X │ 3 │ @─X │ moment 3 ╷ 0 1 2 3 4 5 ╶─┼───────────── 0 │ @─X │ 1 │ @ @─X │ │ 2 │ X @ │ │ 3 │ @ @─X X │ │ 4 │ X @ @─X │ │ 5 │ X │ moment 4 ╷ 0 1 2 3 4 ╶─┼─────────── 0 │ @ │ │ 1 │ X @ │ │ 2 │ X │ 3 │ @─X │ 4 │ @─X │ moment 5 ╷ 0 1 2 4 5 ╶─┼─────────── 0 │ @ │ │ 1 │ X @ │ │ 2 │ X │ 4 │ @─X │ 5 │ @─X │
Simulating the parities
For a given circuit, we can determine all the parity expectation values ⟨As⟩ by sampling 22-qubit bitstrings and then computing each expectation value. We do the same thing with for ⟨Bp⟩, but we include a layer of Hadamards before measurement to effectively "measure in X basis."
def partial_circuit(
n_moments_to_include: int, *, x_basis: bool
) -> cirq.Circuit:
"""Create the first N moments of a toric in Z or X basis.
Args:
n_moments_to_include: number of moments to include
x_basis: If True, add Hadamards to effectively measure in the X basis.
If False, measure in the computational (Z) basis.
Returns: First N moments of a toric code circuit plus an optional
layer of Hadamard gates to effectively measure in the X basis.
This circuit also includes measurement gates.
"""
sliced_circuit = full_circuit[:n_moments_to_include]
qubits = sorted(short_rectangle.qubits)
if x_basis:
sliced_circuit += cirq.Moment(cirq.H.on_each(*qubits))
return sliced_circuit + cirq.measure(*qubits)
def get_plaquettes(
n_moment_to_include: int, repetitions: int = 1000,
sampler: cirq.Sampler = qsimcirq.QSimSimulator()
) -> tcp.ToricCodePlaquettes:
"""Simulates the results in both bases and determine plaquette values.
Args:
n_moments_to_include: number of moments to include
repetitions: number of repetitions (shots) to sample
sampler: Sampler (simulator) to execute circuits. Defaults to qsim.
"""
x_data = sampler.run(
partial_circuit(n_moment_to_include, x_basis=True), repetitions=repetitions
)
z_data = sampler.run(
partial_circuit(n_moment_to_include, x_basis=False), repetitions=repetitions
)
return tcp.ToricCodePlaquettes.from_global_measurements(
short_rectangle, x_data.data, z_data.data
)
We can step through the circuit one moment at a time to see how the parities As and Bp evolve through the circuit. This is similar to Figure 1B in paper (but simulating instead of using experimental data). We begin with |0⟩⊗22, which corresponds to n_moments_to_include=0
. There, all ⟨As⟩=+1 but ⟨Bp⟩=0 (see colorbars below). The subsequent moments apply Hadamard and CNOT gates to stitch entanglement across the device and create |G⟩.
for n in range(len(tcsp.toric_code_cnot_circuit(short_rectangle)) + 1):
p = get_plaquettes(n)
ax = plotter.plot_expectation_values(p)
ax.set_title(f'n_moments_to_include={n}')
plt.pause(0.001)
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
/tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:112: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead. parities = data.applymap( /tmpfs/src/tf_docs_env/lib/python3.10/site-packages/recirq/toric_code/toric_code_plaquettes.py:115: FutureWarning: Calling float on a single element Series is deprecated and will raise a TypeError in the future. Use float(ser.iloc[0]) instead return float(parities.mean())
After the final step, all the parities are +1 (see colorbars below), indicating we have successfully created |G⟩.
ax_z = plotter.make_colorbar(x_basis=False, orientation='horizontal')
ax_z.set_label(r'Z parity, $\langle A\rangle$')
ax_x = plotter.make_colorbar(x_basis=True, orientation='horizontal')
ax_x.set_label(r'X parity, $\langle B\rangle$')