3.3. Visualize Eka objects
Note
The widgets in this notebook are interactive!
This notebook demonstrates the functions of the visualizer module for creating stabilizer plots. More concretely, this notebook contains:
plotting the stabilizers of a
Blockplotting the logical operators of a
Blockplotting arbitrary pauli strings
plotting the pauli charges of a
Blockplotting multiple
Blocks on a lattice
First, we import all necessary modules:
import loom.visualizer as vis
from loom.eka import Lattice, PauliOperator, Stabilizer, Block
import plotly.io as pio
pio.renderers.default = "notebook"
3.3.1. Rotated surface code
Let’s create a 3x3 rotated surface code block on a square lattice. We make the square lattice a 5x5 lattice and place the rotated surface code block in the upper left corner.
The (0,0) position is at the top left. The first coordinate is the x-axis with the positive x-axis going to the right. The second coordinate is the y-axis with the positive y-axis going to the bottom.
dx = 3
dz = 3
lattice = Lattice.square_2d((dx, dz))
# Let's define a rotated surface code on a 3x3 lattice at the origin (0, 0).
def build_3x3_rotated_surface_code() -> Block:
rsc_stabilizers = (
Stabilizer(
"ZZZZ",
((1, 0, 0), (0, 0, 0), (1, 1, 0), (0, 1, 0)),
ancilla_qubits=((1, 1, 1),),
),
Stabilizer(
"ZZZZ",
((2, 1, 0), (1, 1, 0), (2, 2, 0), (1, 2, 0)),
ancilla_qubits=((2, 2, 1),),
),
Stabilizer(
"XXXX",
((1, 1, 0), (1, 2, 0), (0, 1, 0), (0, 2, 0)),
ancilla_qubits=((1, 2, 1),),
),
Stabilizer(
"XXXX",
((2, 0, 0), (2, 1, 0), (1, 0, 0), (1, 1, 0)),
ancilla_qubits=((2, 1, 1),),
),
Stabilizer(
"XX",
((0, 0, 0), (0, 1, 0)),
ancilla_qubits=((0, 1, 1),),
),
Stabilizer(
"XX",
((2, 1, 0), (2, 2, 0)),
ancilla_qubits=((3, 2, 1),),
),
Stabilizer(
"ZZ",
((2, 0, 0), (1, 0, 0)),
ancilla_qubits=((2, 0, 1),),
),
Stabilizer(
"ZZ",
((1, 2, 0), (0, 2, 0)),
ancilla_qubits=((1, 3, 1),),
),
)
rot_surf_code_1 = Block(
unique_label="q1",
stabilizers=rsc_stabilizers,
logical_x_operators=(PauliOperator("XXX", ((0, 0, 0), (1, 0, 0), (2, 0, 0))),),
logical_z_operators=(PauliOperator("ZZZ", ((0, 0, 0), (0, 1, 0), (0, 2, 0))),),
)
return rot_surf_code_1
rot_surf_code_1 = build_3x3_rotated_surface_code()
stab_plot = vis.StabilizerPlot(
lattice,
title=f"Rotated Surface Code, {dx} x {dz}",
)
stab_plot.add_dqubit_traces()
stab_plot.plot_blocks([rot_surf_code_1])
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), width=680
) # Sphinx formatting
stab_plot.show()
The surface code block does not need to be a square. Let’s repeat the process of creation and plotting for a bigger code:
from itertools import product
dx = 9
dz = 5
lattice = Lattice.square_2d((dx + 2, dz + 2))
def create_9x5_rotated_surface_code() -> Block:
xxxx_stabs = tuple(
Stabilizer("XXXX", ((i, j, 0), (i + 1, j, 0), (i + 1, j + 1, 0), (i, j + 1, 0)))
for i, j in product(range(9 - 1), range(5 - 1))
if (i + j) % 2 == 1
)
zzzz_stabs = tuple(
Stabilizer("ZZZZ", ((i, j, 0), (i + 1, j, 0), (i + 1, j + 1, 0), (i, j + 1, 0)))
for i, j in product(range(9 - 1), range(5 - 1))
if (i + j) % 2 == 0
)
xx_stabs = tuple(
Stabilizer("XX", ((i, j, 0), (i, j + 1, 0)))
for i, j in ((0, 0), (0, 2), (8, 1), (8, 3))
)
zz_stabs = tuple(
Stabilizer("ZZ", ((i, j, 0), (i + 1, j, 0)))
for i, j in ((1, 0), (3, 0), (5, 0), (7, 0), (0, 4), (2, 4), (4, 4), (6, 4))
)
logical_x = PauliOperator("XXXXXXXXX", tuple((i, 0, 0) for i in range(9)))
logical_z = PauliOperator("ZZZZZ", tuple((0, i, 0) for i in range(5)))
return Block(
unique_label="q1",
stabilizers=xxxx_stabs + zzzz_stabs + xx_stabs + zz_stabs,
logical_x_operators=(logical_x,),
logical_z_operators=(logical_z,),
).shift((1, 1))
big_rsc_block = create_9x5_rotated_surface_code()
stab_plot = vis.StabilizerPlot(
lattice,
title=f"Rotated Surface Code, {dx} x {dz}",
height=800,
width=1200,
)
stab_plot.add_dqubit_traces()
stab_plot.plot_blocks([big_rsc_block])
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[1, 7]), width=680
) # Sphinx formatting
stab_plot.show()
3.3.2. Plot arbitrary Pauli strings
One can plot an arbitrary Pauli string by defining a corresponding PauliOperator and then plotting it using plot_pauli_string().
pauli_string = PauliOperator(
pauli="XYZZYX",
data_qubits=[(3, 1, 0), (3, 2, 0), (4, 3, 0), (6, 3, 0), (6, 4, 0), (5, 5, 0)],
)
stab_plot = vis.StabilizerPlot(
lattice,
title=f"Rotated Surface Code, {dx} x {dz}",
height=800,
width=1200,
)
stab_plot.add_dqubit_traces()
stab_plot.plot_blocks(big_rsc_block, plot_logical_operators=False)
stab_plot.plot_pauli_string(pauli_string)
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[1, 6]), width=680
) # Sphinx formatting
stab_plot.show()
3.3.3. Plot Pauli charges
There is also a fast way for plotting Pauli charges of a block.
What are Pauli charges?
The Pauli charges are calculated from the stabilizers of a Block. For every data qubit, one counts how often the data qubit is included in stabilizers in the X, Y, and Z basis respectively. If it is included in an odd number of X, Y, or Z stabilizers, the data qubit has a Pauli charge of X, Y, or Z respectively. We only report a single Pauli charge where multiple charges are combined into one charge according to the product of Pauli matrices. E.g. if a data qubit has a Pauli charge of both X and Z, the combined Pauli charge is Y since Y=iXZ.
stab_plot = vis.StabilizerPlot(
lattice,
title=f"Rotated Surface Code, {dx} x {dz}",
height=800,
width=1200,
)
stab_plot.add_dqubit_traces()
stab_plot.plot_blocks(
big_rsc_block, plot_logical_operators=False, plot_pauli_charges=True
)
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[1, 6]), width=680
)
stab_plot.show()
3.3.4. Plot multiple qubits on the same lattice
Of course one can also plot multiple blocks on the same lattice. Note how the arguments x_boundary and weight_2_stab_is_first_row are used here to control the boundaries and the position of weight-2 stabilizers of the blocks.
lattice = Lattice.square_2d((7, 7))
q1 = rot_surf_code_1
q2 = rot_surf_code_1.shift((0, 4), new_label="q2")
q3 = rot_surf_code_1.shift((4, 0), new_label="q3")
stab_plot = vis.StabilizerPlot(
lattice,
height=800,
width=800,
)
stab_plot.add_dqubit_traces()
stab_plot.plot_blocks([q1, q2, q3], plot_logical_operators=False)
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[0, 5]), width=680
) # Sphinx formatting
stab_plot.show()
3.3.5. Plot only a subset of stabilizers
Using the function add_stabilizers, one can plot an arbitrary set of stabilizers. This could be useful for blocks with overlapping stabilizers where one only wants to plot some of them for better visibility.
For example, one could only plot the Z type stabilizers of a rotated surface code block:
dx = 9
dz = 5
lattice = Lattice.square_2d((dx + 2, dz + 2))
Z_stabs = [stab for stab in big_rsc_block.stabilizers if set(stab.pauli) == {"Z"}]
stab_plot = vis.StabilizerPlot(
lattice,
title=f"Rotated Surface Code, {dx} x {dz}",
height=800,
width=1200,
)
stab_plot.add_dqubit_traces()
stab_plot.add_stabilizers(Z_stabs)
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[1, 4]), width=680
) # Sphinx formatting
stab_plot.show()
3.3.6. Creating a plot from scratch
The stabilizer plot does not need to be based on blocks. One can also manually define stabilizers and plot those.
lattice = Lattice.square_2d((4, 4))
stabs = [
Stabilizer("XXX", ((0, 0, 0), (0, 1, 0), (1, 0, 0))),
Stabilizer("YZZY", ((2, 0, 0), (2, 1, 0), (1, 2, 0), (0, 2, 0))),
Stabilizer("XXX", ((0, 2, 0), (0, 3, 0), (1, 3, 0))),
Stabilizer("YZZY", ((3, 1, 0), (3, 2, 0), (3, 3, 0), (1, 3, 0))),
]
stab_plot = vis.StabilizerPlot(
lattice,
)
stab_plot.add_dqubit_traces()
stab_plot.add_stabilizers(stabs)
stab_plot._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[0, 3]), width=680
) # Sphinx formatting
stab_plot.show()
3.3.7. Non-rotated surface code
Another common code is the non-rotated surface code. In this example, observe the parameter dqb_plot_indices=False which hides the labels of data qubits in the plot:
d = 5
custom_lattice = Lattice(
lattice_vectors=((1, 0), (0, 1)),
basis_vectors=((0, 0),),
size=(2 * d - 1, 2 * d - 1),
)
def create_5x5_non_rotated_surface_code() -> Block:
x_stabs = []
z_stabs = []
for base_point in product(range(0, 2 * d, 2), repeat=2):
x_stab_indices = (
(base_point[0], base_point[1]),
(base_point[0] + 1, base_point[1] + 1),
(base_point[0] + 1, base_point[1] - 1),
(base_point[0] + 2, base_point[1]),
)
valid_x_stab_indices = [
coord
for coord in x_stab_indices
if coord[0] >= 0
and coord[1] >= 0
and coord[0] < 2 * d - 1
and coord[1] < 2 * d - 1
]
if len(valid_x_stab_indices) > 2:
x_stab = Stabilizer(
pauli="X" * len(valid_x_stab_indices),
data_qubits=valid_x_stab_indices,
)
x_stabs.append(x_stab)
z_stab_indices = (
(base_point[0], base_point[1]),
(base_point[0] + 1, base_point[1] + 1),
(base_point[0] - 1, base_point[1] + 1),
(base_point[0], base_point[1] + 2),
)
valid_z_stab_indices = [
coord
for coord in z_stab_indices
if coord[0] >= 0
and coord[1] >= 0
and coord[0] < 2 * d - 1
and coord[1] < 2 * d - 1
]
if len(valid_z_stab_indices) > 2:
z_stab = Stabilizer(
pauli="Z" * len(valid_z_stab_indices),
data_qubits=valid_z_stab_indices,
)
z_stabs.append(z_stab)
logical_x = PauliOperator(
"XXXXX", data_qubits=tuple((0, i) for i in range(0, 2 * d, 2))
)
logical_z = PauliOperator(
"ZZZZZ", data_qubits=tuple((i, 0) for i in range(0, 2 * d, 2))
)
surface_code_block = Block(
unique_label="q1",
stabilizers=x_stabs + z_stabs,
logical_x_operators=(logical_x,),
logical_z_operators=(logical_z,),
)
return surface_code_block
surface_code_block = create_5x5_non_rotated_surface_code()
fig = vis.StabilizerPlot(
lattice=custom_lattice,
title=f"Non-Rotated Surface Code, d={d}",
dqb_plot_indices=False, # Hide the numbers of data qubits in the plot
opacity_stabs=0.8,
height=800,
width=1200,
)
# We only plot the right qubits (pair indices)
fig.add_dqubit_traces(
{i: True if i % 2 == 0 else False for i in range((2 * d - 1) ** 2)}
)
fig.plot_blocks([surface_code_block])
fig._fig.update_layout(
margin=dict(t=60, l=30, b=30), xaxis=dict(range=[0, 5]), width=680
) # Sphinx formatting
fig.show()