Source code for surface_sim.layouts.library.rot_surface_codes

from collections import defaultdict
from collections.abc import Mapping, Sequence
from functools import partial
from itertools import count, product

from ...log_gates.rot_surface_code_css import (
    set_encoding,
    set_fold_trans_s,
    set_idle,
    set_trans_cnot,
    set_trans_cnot_mid_cycle_css,
    set_x,
    set_z,
)
from ..layout import Layout, QubitDict
from .util import check_distance, invert_shift, is_valid, set_missing_neighbours_to_none

DEFAULT_INTERACTION_ORDER = dict(
    z_type=["north_west", "north_east", "south_west", "south_east"],
    x_type=["north_west", "south_west", "north_east", "south_east"],
)


def get_data_index(col: int, row: int, row_size: int, start_ind: int = 1) -> int:
    """Converts row and column to data qubit index.

    The data qubits are numbered starting from the bottom-left data qubit,
    and increasing the index by 1 on the horizontal direction.
    Assumes the initial index is 1 (as opposed to 0).

    Parameters
    ----------
    col
        The column of the data qubit.
    row
        The row of the data qubit.
    row_size
        Row size of the code.
    start_ind
        The starting index for the data qubits, by default 1.

    Returns
    -------
    int
        The index of the data qubit.
    """
    row_ind = row // 2
    col_ind = col // 2
    index = start_ind + (col_ind * row_size) + row_ind
    return index


def shift_direction(col_shift: int, row_shift: int) -> str:
    """Translates a row and column shift to a direction.

    Parameters
    ----------
    col_shift
        The column shift.
    row_shift
        The row shift.

    Returns
    -------
    str
        The direction.
    """
    ver_direction = "north" if row_shift > 0 else "south"
    hor_direction = "east" if col_shift > 0 else "west"
    direction = f"{ver_direction}_{hor_direction}"
    return direction


[docs] def rot_surface_code_rectangle( distance_x: int, distance_z: int, logical_qubit_label: str = "L0", init_point: tuple[int | float, int | float] = (1, 1), init_data_qubit_id: int = 1, init_zanc_qubit_id: int = 1, init_xanc_qubit_id: int = 1, init_ind: int = 0, init_logical_ind: int = 0, interaction_order: Mapping[str, Sequence[str]] = DEFAULT_INTERACTION_ORDER, ) -> Layout: """Generates a rotated surface code layout. Parameters ---------- distance_x The logical X distance of the code. distance_z The logical Z distance of the code. logical_qubit_label Label for the logical qubit, by default ``"L0"``. init_point Coordinates for the bottom left (i.e. southest west) data qubit. By default ``(1, 1)``. init_data_qubit_id Index for the bottom left (i.e. southest west) data qubit. By default ``1``, so the label is ``"D1"``. init_zanc_qubit_id Index for the bottom left (i.e. southest west) Z-type ancilla qubit. By default ``1``, so the label is ``"Z1"``. init_xanc_qubit_id Index for the bottom left (i.e. southest west) X-type ancilla qubit. By default ``1``, so the label is ``"X1"``. init_ind Minimum index that is going to be associated to a qubit. init_logical_ind Minimum index that is going to be associated to a logical qubit. interaction_order Dictionary specifying the interaction order for the ``x_type`` and ``z_type`` stabilizers. The possible interaction directions are: ``"north_east"``, ``"north_west"``, ``"south_east"``, and ``"south_west"``. Returns ------- Layout The layout of the code. """ check_distance(distance_x) check_distance(distance_z) if not isinstance(init_point, tuple): raise TypeError( f"'init_point' must be a tuple, but {type(init_point)} was given." ) if (len(init_point) != 2) or any( not isinstance(p, (float, int)) for p in init_point ): raise TypeError("'init_point' must have two elements that are floats or ints.") if not isinstance(logical_qubit_label, str): raise TypeError( "'logical_qubit_label' must be a string, " f"but {type(logical_qubit_label)} was given." ) if not isinstance(init_data_qubit_id, int): raise TypeError( "'init_data_qubit_id' must be an int, " f"but {type(init_data_qubit_id)} was given." ) if not isinstance(init_zanc_qubit_id, int): raise TypeError( "'init_zanc_qubit_id' must be an int, " f"but {type(init_zanc_qubit_id)} was given." ) if not isinstance(init_xanc_qubit_id, int): raise TypeError( "'init_xanc_qubit_id' must be an int, " f"but {type(init_xanc_qubit_id)} was given." ) name = f"Rotated dx-{distance_x} dz-{distance_z} surface code layout." code = "rotated_surface_code" description = "" log_z = [f"D{i + init_data_qubit_id}" for i in range(distance_z)] log_x = [f"D{i * distance_z + init_data_qubit_id}" for i in range(distance_x)] logical_qubits = { logical_qubit_label: dict(log_x=log_x, log_z=log_z, ind=init_logical_ind) } layout_setup = dict( name=name, code=code, logical_qubits=logical_qubits, description=description, distance_x=distance_x, distance_z=distance_z, interaction_order=interaction_order, ) if distance_x == distance_z: layout_setup["distance"] = distance_z col_size = 2 * distance_x + 1 row_size = 2 * distance_z + 1 data_indexer = partial( get_data_index, row_size=distance_z, start_ind=init_data_qubit_id ) valid_coord = partial(is_valid, max_size_col=col_size, max_size_row=row_size) pos_shifts = (1, -1) nbr_shifts = tuple(product(pos_shifts, repeat=2)) layout_data: list[QubitDict] = [] neighbor_data: defaultdict[str, dict[str, str | None]] = defaultdict(dict) ind = init_ind # change initial point because by default the code places the "D1" qubit # in the (1,1) point. init_point = (init_point[0] - 1, init_point[1] - 1) for col in range(1, col_size, 2): for row in range(1, row_size, 2): index = data_indexer(col, row) qubit_info = dict( qubit=f"D{index}", role="data", coords=[col + init_point[0], row + init_point[1]], stab_type=None, ind=ind, ) layout_data.append(qubit_info) ind += 1 x_index = count(start=init_xanc_qubit_id) for col in range(0, col_size, 2): for row in range(2 + col % 4, row_size - 1, 4): anc_qubit = f"X{next(x_index)}" qubit_info = dict( qubit=anc_qubit, role="anc", coords=[col + init_point[0], row + init_point[1]], stab_type="x_type", ind=ind, ) layout_data.append(qubit_info) ind += 1 for col_shift, row_shift in nbr_shifts: data_row, data_col = row + row_shift, col + col_shift if not valid_coord(data_col, data_row): continue data_index = data_indexer(data_col, data_row) data_qubit = f"D{data_index}" direction = shift_direction(col_shift, row_shift) neighbor_data[anc_qubit][direction] = data_qubit inv_shifts = invert_shift(col_shift, row_shift) inv_direction = shift_direction(*inv_shifts) neighbor_data[data_qubit][inv_direction] = anc_qubit z_index = count(start=init_zanc_qubit_id) for col in range(2, col_size - 1, 2): for row in range(col % 4, row_size, 4): anc_qubit = f"Z{next(z_index)}" qubit_info = dict( qubit=anc_qubit, role="anc", coords=[col + init_point[0], row + init_point[1]], stab_type="z_type", ind=ind, ) layout_data.append(qubit_info) ind += 1 for col_shift, row_shift in nbr_shifts: data_row, data_col = row + row_shift, col + col_shift if not valid_coord(data_col, data_row): continue data_index = data_indexer(data_col, data_row) data_qubit = f"D{data_index}" direction = shift_direction(col_shift, row_shift) neighbor_data[anc_qubit][direction] = data_qubit inv_shifts = invert_shift(col_shift, row_shift) inv_direction = shift_direction(*inv_shifts) neighbor_data[data_qubit][inv_direction] = anc_qubit set_missing_neighbours_to_none(neighbor_data) for qubit_info in layout_data: qubit = qubit_info["qubit"] qubit_info["neighbors"] = neighbor_data[qubit] layout_setup["layout"] = layout_data layout = Layout(layout_setup) return layout
[docs] def rot_surface_code( distance: int, logical_qubit_label: str = "L0", init_point: tuple[int | float, int | float] = (1, 1), init_data_qubit_id: int = 1, init_zanc_qubit_id: int = 1, init_xanc_qubit_id: int = 1, init_ind: int = 0, init_logical_ind: int = 0, interaction_order: Mapping[str, Sequence[str]] = DEFAULT_INTERACTION_ORDER, ) -> Layout: """Generates a rotated surface code layout. Parameters ---------- distance The distance of the code. logical_qubit_label Label for the logical qubit, by default ``"L0"``. init_point Coordinates for the bottom left (i.e. southest west) data qubit. By default ``1``, so the label is ``"D1"``. init_data_qubit_id Index for the bottom left (i.e. southest west) data qubit. By default ``1``, so the label is ``"D1"``. init_zanc_qubit_id Index for the bottom left (i.e. southest west) Z-type ancilla qubit. By default ``1``, so the label is ``"Z1"``. init_xanc_qubit_id Index for the bottom left (i.e. southest west) X-type ancilla qubit. By default ``1``, so the label is ``"X1"``. init_ind Minimum index that is going to be associated to a qubit. init_logical_ind Minimum index that is going to be associated to a logical qubit. interaction_order Dictionary specifying the interaction order for the ``x_type`` and ``z_type`` stabilizers. The possible interaction directions are: ``"north_east"``, ``"north_west"``, ``"south_east"``, and ``"south_west"``. Returns ------- Layout The layout of the code. """ return rot_surface_code_rectangle( distance_x=distance, distance_z=distance, logical_qubit_label=logical_qubit_label, init_point=init_point, init_data_qubit_id=init_data_qubit_id, init_zanc_qubit_id=init_zanc_qubit_id, init_xanc_qubit_id=init_xanc_qubit_id, init_ind=init_ind, init_logical_ind=init_logical_ind, interaction_order=interaction_order, )
[docs] def rot_surface_codes(num_layouts: int, distance: int) -> list[Layout]: """ Returns a list of (square) rotated surface codes of the specified distance that are set up to be used in any logical circuit (i.e. they have all the implemented logical gate attributes). Parameters ---------- num_layouts Number of layouts to generate. distance The code distance. Returns ------- layouts List of layouts. """ if not isinstance(num_layouts, int): raise TypeError( f"'num_layouts' must be an int, but {type(num_layouts)} was given." ) layouts: list[Layout] = [] num_data = distance**2 num_anc = num_data - 1 for k in range(num_layouts): layout = rot_surface_code( distance=distance, logical_qubit_label=f"L{k}", init_point=(0, (2 * distance + 2) * k), init_data_qubit_id=1 + k * num_data, init_zanc_qubit_id=1 + k * num_anc // 2, init_xanc_qubit_id=1 + k * num_anc // 2, init_ind=k * (num_data + num_anc), init_logical_ind=k, ) layouts.append(layout) # set up the parameters for all the logical gates for k, layout in enumerate(layouts): set_x(layout) set_z(layout) set_idle(layout) set_encoding(layout) for other_layout in layouts: if layout == other_layout: continue set_trans_cnot(layout, other_layout) set_trans_cnot_mid_cycle_css(layout, other_layout) return layouts
[docs] def rot_surface_code_rectangles(num_layouts: int, distance: int) -> list[Layout]: """ Returns a list of rotated surface codes of the specified distance that are set up to be used in any logical circuit (i.e. they have all the implemented logical gate attributes). Parameters ---------- num_layouts Number of layouts to generate. distance The distance of logical Pauli X of the layouts. The distance of logical Pauli Z is ``distance + 1``. Returns ------- layouts List of layouts. """ if not isinstance(num_layouts, int): raise TypeError( f"'num_layouts' must be an int, but {type(num_layouts)} was given." ) layouts: list[Layout] = [] num_data = (distance + 1) * distance num_anc = num_data - 1 for k in range(num_layouts): layout = rot_surface_code_rectangle( distance_x=distance, distance_z=distance + 1, logical_qubit_label=f"L{k}", init_point=(0, (2 * distance + 4) * k), init_data_qubit_id=1 + k * num_data, init_zanc_qubit_id=1 + k * num_anc // 2, init_xanc_qubit_id=1 + k * (num_anc // 2 + 1), init_ind=k * (num_data + num_anc), init_logical_ind=k, ) layouts.append(layout) # set up the parameters for all the logical gates for k, layout in enumerate(layouts): set_fold_trans_s(layout, data_qubit=f"D{1 + k * num_data}") set_x(layout) set_z(layout) set_idle(layout) for other_layout in layouts: if layout == other_layout: continue set_trans_cnot(layout, other_layout) return layouts
[docs] def rot_surface_stability_rectangle( stab_type: str, width: int, height: int, observable: str = "O0", init_point: tuple[int | float, int | float] = (1, 1), init_data_qubit_id: int = 1, init_zanc_qubit_id: int = 1, init_xanc_qubit_id: int = 1, init_ind: int = 0, interaction_order: Mapping[str, Sequence[str]] = DEFAULT_INTERACTION_ORDER, ) -> Layout: """ Generates a rotated surface layout for stability experiments. Parameters ---------- stab_type Type of the stabilizer that lead to the identity when multiplying all stabilizers of that type. It must be ``"x_type"`` or ``"z_type"``. width Width of the rotated_surface code layout in terms of the number of data qubit along the horizontal dimension. height Heigh of the rotated_surface code layout in terms of the number of data qubit along the vertical dimension. observable Label for the observable, by default ``"L0"``. init_point Coordinates for the bottom left (i.e. southest west) data qubit. By default ``(1, 1)``. init_data_qubit_id Index for the bottom left (i.e. southest west) data qubit. By default ``1``, so the label is ``"D1"``. init_zanc_qubit_id Index for the bottom left (i.e. southest west) Z-type ancilla qubit. By default ``1``, so the label is ``"Z1"``. init_xanc_qubit_id Index for the bottom left (i.e. southest west) X-type ancilla qubit. By default ``1``, so the label is ``"X1"``. init_ind Minimum index that is going to be associated to a qubit. interaction_order Dictionary specifying the interaction order for the ``x_type`` and ``z_type`` stabilizers. The possible interaction directions are: ``"north_east"``, ``"north_west"``, ``"south_east"``, and ``"south_west"``. Returns ------- Layout The layout. """ check_distance(width) check_distance(height) if stab_type not in ("x_type", "z_type"): raise ValueError( f"'stab_type' must be 'x_type' or 'z_type', but {stab_type} was given." ) if not isinstance(init_point, tuple): raise TypeError( f"'init_point' must be a tuple, but {type(init_point)} was given." ) if (len(init_point) != 2) or any( not isinstance(p, (float, int)) for p in init_point ): raise TypeError("'init_point' must have two elements that are floats or ints.") if not isinstance(observable, str): raise TypeError( f"'observable' must be a string, but {type(observable)} was given." ) if not isinstance(init_data_qubit_id, int): raise TypeError( "'init_data_qubit_id' must be an int, " f"but {type(init_data_qubit_id)} was given." ) if not isinstance(init_zanc_qubit_id, int): raise TypeError( "'init_zanc_qubit_id' must be an int, " f"but {type(init_zanc_qubit_id)} was given." ) if not isinstance(init_xanc_qubit_id, int): raise TypeError( "'init_xanc_qubit_id' must be an int, " f"but {type(init_xanc_qubit_id)} was given." ) name = f"Rotated w-{width} h-{height} surface layout for stability experiments." code = "rotated_surface_stability" description = "" col_size = 2 * width row_size = 2 * height data_indexer = partial( get_data_index, row_size=height, start_ind=init_data_qubit_id ) valid_coord = partial(is_valid, max_size_col=col_size, max_size_row=row_size) pos_shifts = (1, -1) nbr_shifts = tuple(product(pos_shifts, repeat=2)) layout_data: list[QubitDict] = [] neighbor_data: defaultdict[str, dict[str, str | None]] = defaultdict(dict) ind = init_ind # change initial point because by default the code places the "D1" qubit # in the (1,1) point. init_point = (init_point[0] - 1, init_point[1] - 1) for row in range(1, row_size, 2): for col in range(1, col_size, 2): index = data_indexer(col, row) qubit_info = dict( qubit=f"D{index}", role="data", coords=[col + init_point[0], row + init_point[1]], stab_type=None, ind=ind, ) layout_data.append(qubit_info) ind += 1 if stab_type == "x_type": init_sanc_qubit_id = init_xanc_qubit_id init_oanc_qubit_id = init_zanc_qubit_id other_stab_type = "z_type" s, o = "X", "Z" else: init_sanc_qubit_id = init_zanc_qubit_id init_oanc_qubit_id = init_xanc_qubit_id other_stab_type = "x_type" s, o = "Z", "X" s_index = count(start=init_sanc_qubit_id) for row in range(0, row_size + 1, 2): for col in range((2 + row) % 4, col_size + 1, 4): anc_qubit = f"{s}{next(s_index)}" qubit_info = dict( qubit=anc_qubit, role="anc", coords=[col + init_point[0], row + init_point[1]], stab_type=stab_type, ind=ind, ) layout_data.append(qubit_info) ind += 1 for col_shift, row_shift in nbr_shifts: data_row, data_col = row + row_shift, col + col_shift if not valid_coord(data_col, data_row): continue data_index = data_indexer(data_col, data_row) data_qubit = f"D{data_index}" direction = shift_direction(col_shift, row_shift) neighbor_data[anc_qubit][direction] = data_qubit inv_shifts = invert_shift(col_shift, row_shift) inv_direction = shift_direction(*inv_shifts) neighbor_data[data_qubit][inv_direction] = anc_qubit o_index = count(start=init_oanc_qubit_id) for row in range(2, row_size - 1, 2): for col in range(2 + (2 + row) % 4, col_size, 4): anc_qubit = f"{o}{next(o_index)}" qubit_info = dict( qubit=anc_qubit, role="anc", coords=[col + init_point[0], row + init_point[1]], stab_type=other_stab_type, ind=ind, ) layout_data.append(qubit_info) ind += 1 for col_shift, row_shift in nbr_shifts: data_row, data_col = row + row_shift, col + col_shift if not valid_coord(data_col, data_row): continue data_index = data_indexer(data_col, data_row) data_qubit = f"D{data_index}" direction = shift_direction(col_shift, row_shift) neighbor_data[anc_qubit][direction] = data_qubit inv_shifts = invert_shift(col_shift, row_shift) inv_direction = shift_direction(*inv_shifts) neighbor_data[data_qubit][inv_direction] = anc_qubit set_missing_neighbours_to_none(neighbor_data) anc_redundant_stab_type: list[str] = [] for qubit_info in layout_data: qubit = qubit_info["qubit"] qubit_info["neighbors"] = neighbor_data[qubit] if qubit_info["role"] == "anc" and qubit_info["stab_type"] == stab_type: anc_redundant_stab_type.append(qubit) layout_setup = dict( name=name, code=code, observables={observable: anc_redundant_stab_type}, description=description, interaction_order=interaction_order, ) layout_setup["layout"] = layout_data layout = Layout(layout_setup) return layout