Skip to content

pbc

Pauli-based computation (PBC) read off a reduced ZX component.

Here a PBC is a sequence of Pauli product rotations and Pauli product measurements on |0…0⟩ [1], held in PauliOps. Unlike the PBC of [2], it keeps the rotations instead of consuming magic states.

The quantum part of a reduced component is graph-like [3]: a graph state [4] of its quantum spiders, followed by one rotation and one single-qubit measurement per spider. The phase π/2·q + 2θ of spider v rotates its qubit by exp(-iθ Z_v) and then measures m_v = S^-q X S^q (a signed X or Y), with outcome bit y_v. The classical spiders only relate the bits y to the outputs. Eliminating them over GF(2) (_Relations) leaves each output as a parity of bits y, f-bits and fair coins, plus parities of the bits y that the diagram postselects.

A sampler cannot postselect, so the postselections are moved to the start, where they act on the graph state. For that, every rotation must commute with them. Let K_T be the product of the graph-state stabilizers K_u = X_u Z_N(u) over the spiders u in T. If K_T commutes with Z_v and stabilizes the current state, exp(-iθ Z_v) acts on that state like exp(-iθ Z_v K_T). Both hold when no spider in T is rotated at or before v. _rotation_axes finds sets T that make every rotation commute with every postselection, with a backward greedy search like the gflow search of [5].

A Clifford frame then maps the postselected graph state to |0…0⟩. Noise-dependent postselection targets become f-dependent signs, lines that no operation flips are dropped, and Paulis that commute with every operation are measured first: they change no outcome and keep the active width small.

References

[1] D. Litinski, "A Game of Surface Codes: Large-Scale Quantum Computing with Lattice Surgery", Quantum 3, 128 (2019). [2] S. Bravyi, G. Smith, J. A. Smolin, "Trading classical and quantum computational resources", Phys. Rev. X 6, 021043 (2016). [3] R. Duncan, A. Kissinger, S. Perdrix, J. van de Wetering, "Graph-theoretic simplification of quantum circuits with the ZX-calculus", Quantum 4, 279 (2020). [4] M. Hein, J. Eisert, H. J. Briegel, "Multiparty entanglement in graph states", Phys. Rev. A 69, 062311 (2004). [5] M. Mhalla, S. Perdrix, "Finding optimal flows efficiently", ICALP 2008, LNCS 5125.

ComponentLayout dataclass

ComponentLayout(
    f_selection: tuple[int, ...],
    f_position: Mapping[int, int],
    output_indices: tuple[int, ...],
)

Where a component's parameters and outputs go.

Attributes:

Name Type Description
f_selection tuple[int, ...]

Global f-bit index of each component f-bit, ascending.

f_position Mapping[int, int]

Position in f_selection of each phase parameter ID.

output_indices tuple[int, ...]

Global output index of each output slot.

of classmethod

of(
    component: Graph,
    output_indices: Sequence[int],
    layout: ParamLayout,
) -> ComponentLayout

Return the layout of component, whose outputs are output_indices.

Source code in src/tsim/statevector/pbc.py
78
79
80
81
82
83
84
85
86
87
@classmethod
def of(
    cls, component: Graph, output_indices: Sequence[int], layout: ParamLayout
) -> ComponentLayout:
    """Return the layout of ``component``, whose outputs are ``output_indices``."""
    f_selection = tuple(
        sorted(layout.basis_index(p) for p in component.parameters())
    )
    f_position = {layout.basis(i): pos for pos, i in enumerate(f_selection)}
    return cls(f_selection, f_position, tuple(output_indices))

PauliOps dataclass

PauliOps(
    kind: ndarray,
    x: ndarray,
    z: ndarray,
    angle: ndarray,
    sign: ndarray,
    slot: ndarray,
)

Rotations and measurements in the frame of the initial |0…0⟩ state.

Op j is the rotation exp(-iθ (-1)^s P) or the measurement of (-1)^s P, where P has the bit rows x[j] and z[j] (Y where both are set) and s is the parity of sign[j] against [1, f_0, f_1, …]. A measurement records its eigenvalue bit.

Attributes:

Name Type Description
kind ndarray

ROTATION or MEASUREMENT, shape (N,).

x ndarray

X part of each Pauli, shape (N, num_lines).

z ndarray

Z part of each Pauli, shape (N, num_lines).

angle ndarray

θ of each rotation, 0 for measurements, shape (N,).

sign ndarray

Constant, then one column per f-bit, shape (N, 1 + num_f).

slot ndarray

Output slot of a recorded measurement, -1 for a forgotten measurement or a rotation, shape (N,).

num_f property

num_f: int

Return the number of component f-bits the signs depend on.

num_lines property

num_lines: int

Return the number of qubit lines.

num_ops property

num_ops: int

Return the number of operations.

num_outputs property

num_outputs: int

Return the number of recorded measurements.

take

take(order: ndarray) -> PauliOps

Return the operations in the given order.

The result describes the same distribution when order keeps every pair of anticommuting operations in their original order.

Source code in src/tsim/statevector/pbc.py
149
150
151
152
153
154
155
def take(self, order: np.ndarray) -> PauliOps:
    """Return the operations in the given order.

    The result describes the same distribution when ``order`` keeps every
    pair of anticommuting operations in their original order.
    """
    return PauliOps(*(getattr(self, f.name)[order] for f in fields(self)))

anticommute

anticommute(
    x1: ndarray, z1: ndarray, x2: ndarray, z2: ndarray
) -> np.ndarray

Return which Paulis (x1, z1) anticommute with which Paulis (x2, z2).

Each row of an argument is a Pauli; a 1-D argument is one Pauli, whose axis the result drops.

Source code in src/tsim/statevector/pbc.py
90
91
92
93
94
95
96
97
98
99
def anticommute(
    x1: np.ndarray, z1: np.ndarray, x2: np.ndarray, z2: np.ndarray
) -> np.ndarray:
    """Return which Paulis ``(x1, z1)`` anticommute with which Paulis ``(x2, z2)``.

    Each row of an argument is a Pauli; a 1-D argument is one Pauli, whose axis
    the result drops.
    """
    f = np.float32
    return (x1.astype(f) @ z2.T.astype(f) + z1.astype(f) @ x2.T.astype(f)) % 2 == 1

read_pauli_ops

read_pauli_ops(
    component: Graph, layout: ComponentLayout
) -> PauliOps

Return the operations of a component of the reduced sampling graph.

Forgotten measurements of Paulis that commute with every op come first, then the rotations, then one measurement per output slot.

Raises:

Type Description
ValueError

If the component is not a graph-like diagram of Z spiders with classical outputs, its classical part does not describe probabilities, or no rotation order exists.

Source code in src/tsim/statevector/pbc.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def read_pauli_ops(component: Graph, layout: ComponentLayout) -> PauliOps:
    """Return the operations of a component of the reduced sampling graph.

    Forgotten measurements of Paulis that commute with every op come first,
    then the rotations, then one measurement per output slot.

    Raises:
        ValueError: If the component is not a graph-like diagram of Z spiders
            with classical outputs, its classical part does not describe
            probabilities, or no rotation order exists.

    """
    reading = _Reading(component, layout)
    n, num_f = reading.num_spiders, len(layout.f_selection)
    patterns = _patterns(reading)
    axes = _rotation_axes(reading, patterns)
    frame, targets = _frame(reading, patterns[0], num_f)
    paulis = []
    for v, t in axes:
        axis = stim.PauliString(n)
        axis[v] = "Z"
        paulis.append((axis * reading.stabilizer(t), 0, 0))
    paulis += [(reading.measured(y), coins, rhs) for y, coins, rhs in reading.outputs]
    images = [frame(pauli) for pauli, _, _ in paulis]
    bits = [image.to_numpy() for image in images]
    flipped = np.zeros(n, dtype=bool)
    for image_x, _ in bits:
        flipped |= image_x
    lines = np.flatnonzero(flipped)
    x = np.zeros((len(paulis), len(lines) + reading.num_coins), dtype=bool)
    z = np.zeros_like(x)
    sign = np.zeros((len(paulis), 1 + num_f), dtype=bool)
    for j, ((_, coins, rhs), image, (image_x, image_z)) in enumerate(
        zip(paulis, images, bits, strict=True)
    ):
        x[j, : len(lines)], z[j, : len(lines)] = image_x[lines], image_z[lines]
        x[j, len(lines) :] = _unmask(coins, reading.num_coins)
        sign[j] = _unmask(rhs, 1 + num_f)
        sign[j, 0] ^= image.sign == -1
        # NOTE: frame line i holds the i-th independent postselection of _frame,
        # not postselection i, with the f-dependent target targets[i]. That line
        # starts in |targets[i]⟩, so an op with a Z on it flips with targets[i].
        on_targets = np.flatnonzero(image_z[: len(targets)])
        sign[j, 1:] ^= targets[on_targets].sum(axis=0) % 2 == 1
    num_outputs = len(reading.outputs)
    ops = PauliOps(
        kind=np.array([ROTATION] * len(axes) + [MEASUREMENT] * num_outputs, np.uint8),
        x=x,
        z=z,
        angle=np.array([reading.angle[v] for v, _ in axes] + [0.0] * num_outputs),
        sign=sign,
        slot=np.array([-1] * len(axes) + list(range(num_outputs)), dtype=np.int32),
    )
    return _with_commutant_measurements(ops)