Source code for loom.cliffordsim.tableau

"""
Copyright (c) Entropica Labs Pte Ltd 2025.

Use, distribution and reproduction of this program in its source or compiled
form is prohibited without the express written consent of Entropica Labs Pte
Ltd.

"""

from __future__ import annotations
from dataclasses import dataclass
from copy import deepcopy

import numpy as np

from loom.eka.utilities import is_stabarray_equivalent, StabArray, sparse_formatter

from .exceptions import TableauSizeError


[docs] @dataclass(frozen=True) class TableauSnapshot: """ A snapshot of the Tableau at a specific point in time. """ nqubits: int rand_gen: int tableau_w_scratch: np.ndarray
[docs] class Tableau: # pylint: disable=too-many-instance-attributes """ The Tableau class represents the stabilizer state of a quantum system using the tableau representation. It provides methods to manipulate and query the stabilizer state. """ dtype = np.int8 def __init__( self, nqubits: int = None, initial_tableau: np.ndarray = None, seed: int = None ) -> None: """ Initializes the the state `|00...0>`. """ if not (nqubits is None) ^ (initial_tableau is None): raise ValueError( "Tableau should be defined using nqubits XOR initial_tableau" ) self.rand_gen = np.random.default_rng(seed) if nqubits is not None: # construct tableau array as concatenation of an identity array # and an empty vector self.tableau_w_scratch = np.zeros( (2 * nqubits + 1, 2 * nqubits + 1), dtype=self.dtype ) id_array = np.identity(2 * nqubits, dtype=self.dtype) self.tableau_w_scratch[:-1, :-1] = id_array else: self._init_w_tableau(initial_tableau) self.update() def _init_w_tableau(self, input_tableau: np.ndarray): """ Assigns a custom tableau to the Tableau. The method adds an empty scratch row to the custom tableau and assigns it to the tableau_w_scratch attribute. """ tableau = np.array(input_tableau, dtype=self.dtype) # form the tableau with the scratch row scratch_row = np.zeros((1, tableau.shape[1]), dtype=self.dtype) # set the tableau with the scratch and update self.tableau_w_scratch = np.vstack((tableau, scratch_row)) @property def nqubits(self) -> int: """ The number of qubits in the tableau. """ return self.__nqubits @nqubits.setter def nqubits(self, n_qubits: int): assert n_qubits > 0 and isinstance( n_qubits, int ), "Number of qubits should be positive integer." self.__nqubits = n_qubits # pylint: disable=attribute-defined-outside-init def _define_tableau_views(self) -> None: """ Defines different views of the tableau array. To be called upon initialization or upon adding or deleting a qubit. """ self.tableau = self.tableau_w_scratch[:-1, :] self.scratch_row = self.tableau_w_scratch[-1, :] # different parts of the tableau self.x = self.tableau[:, : self.nqubits] self.z = self.tableau[:, self.nqubits : -1] self.r = self.tableau[:, -1] self.x_w_scratch = self.tableau_w_scratch[:, : self.nqubits] self.z_w_scratch = self.tableau_w_scratch[:, self.nqubits : -1] self.r_w_scratch = self.tableau_w_scratch[:, -1] self.stabilizer_array = self.tableau[self.nqubits :, :] self.destabilizer_array = self.tableau[: self.nqubits, :] self.z_stabilizers = self.z[self.nqubits :, :] self.x_stabilizers = self.x[self.nqubits :, :] self.r_stabilizers = self.r[self.nqubits :]
[docs] def update(self): """Updates the internal structure after the tableau has been modified""" dim = self.tableau_w_scratch.shape[0] self.nqubits = (dim - 1) // 2 self._define_tableau_views()
@property def stabilizer_set(self) -> set[str]: """ Generates the stabilizers in human readable format. """ return set(StabArray(self.stabilizer_array).as_paulistrings) @property def stabilizer_set_sparse_format(self) -> list[dict]: """ Returns the stabilizers in more human readable format. This is the sparse format. Note: the formatter function can be called by user directly as well. Look for it in QCDutils Parameters ---------- Returns ------- list[dict] A list of dicts, each dict representing one stabilizer operator. The keys refer to the Pauli operators, while value refer to the qubit indices where the Pauli operator resides. E.g. +ZXIIYXZII gets returned as {'sign':'+', 'X':(1,5), 'Z':(0,6), 'Y':(4,)} """ return sparse_formatter(set(StabArray(self.stabilizer_array).as_paulistrings))
[docs] def create_snapshot(self) -> TableauSnapshot: """ Creates a TableauSnapshot object, a snapshot of the state of the Tableau, that contains important properties of the Tableau. The TableauSnapshot object can then be used to restore the state of the Tableau at the time the TableauSnapshot was created. """ return TableauSnapshot( nqubits=deepcopy(self.nqubits), rand_gen=deepcopy(self.rand_gen), tableau_w_scratch=deepcopy(self.tableau_w_scratch), )
[docs] def restore(self, engine_snapshot: TableauSnapshot) -> None: """ This method allows us to restore the state of the Tableau with a TableauSnapshot Object. Once the Tableau has been restored, the views would be redefined. """ self.nqubits = engine_snapshot.nqubits self.rand_gen = engine_snapshot.rand_gen self.tableau_w_scratch = engine_snapshot.tableau_w_scratch self._define_tableau_views()
[docs] def rewrite_tableau(self, input_tableau: np.ndarray) -> Tableau: """ This method updates the internal tableau of this Tableau by returning a new Tableau with the updated tableau. The tableau is updated as a whole. This method also checks for compatibility of the updated tableau. """ # Check that new Tableau has same number of qubits as old tableau. Raise printable warning. if not self.tableau.shape == input_tableau.shape: raise TableauSizeError( f"The Tableau has been updated from shape {self.tableau.shape} to" f" shape {input_tableau.shape}" ) self._init_w_tableau(input_tableau=input_tableau) self.update() return self
[docs] def compare_stabilizer_set(engine_rep_1: Tableau, engine_rep_2: Tableau) -> bool: """Returns true if the stabilizer set of both Tableau(s) are equal when they are reduced. """ # return false directly in case of different qubits if engine_rep_1.nqubits != engine_rep_2.nqubits: return False # find row echelon form of stabilizer arrays return is_stabarray_equivalent( StabArray(engine_rep_1.stabilizer_array), StabArray(engine_rep_2.stabilizer_array), )