diff --git a/src/qibo/backends/_clifford_operations.py b/src/qibo/backends/_clifford_operations.py index a2e4565bda..a663bc4852 100644 --- a/src/qibo/backends/_clifford_operations.py +++ b/src/qibo/backends/_clifford_operations.py @@ -1,12 +1,15 @@ +import math from functools import cache, reduce +from typing import Optional, Tuple import numpy as np +from numpy.typing import ArrayLike, DTypeLike from scipy import sparse name = "numpy" -def _get_rxz(symplectic_matrix, nqubits): +def _get_rxz(symplectic_matrix: ArrayLike, nqubits: int) -> Tuple[ArrayLike, ...]: return ( symplectic_matrix[:, -1], symplectic_matrix[:, :nqubits], @@ -14,18 +17,20 @@ def _get_rxz(symplectic_matrix, nqubits): ) -def I(symplectic_matrix, q, nqubits): +def I(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: return symplectic_matrix -def H(symplectic_matrix, q, nqubits): +def H(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (x[:, q] & z[:, q]) symplectic_matrix[:, [q, nqubits + q]] = symplectic_matrix[:, [nqubits + q, q]] return symplectic_matrix -def CNOT(symplectic_matrix, control_q, target_q, nqubits): +def CNOT( + symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int +) -> ArrayLike: ind_zt = nqubits + target_q ind_zc = nqubits + control_q r = symplectic_matrix[:, -1] @@ -39,7 +44,7 @@ def CNOT(symplectic_matrix, control_q, target_q, nqubits): return symplectic_matrix -def CZ(symplectic_matrix, control_q, target_q, nqubits): +def CZ(symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int): """Decomposition --> H-CNOT-H""" ind_zt = nqubits + target_q ind_zc = nqubits + control_q @@ -59,28 +64,28 @@ def CZ(symplectic_matrix, control_q, target_q, nqubits): return symplectic_matrix -def S(symplectic_matrix, q, nqubits): +def S(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (x[:, q] & z[:, q]) symplectic_matrix[:, q + nqubits] = z[:, q] ^ x[:, q] return symplectic_matrix -def Z(symplectic_matrix, q, nqubits): +def Z(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> S-S""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ ((x[:, q] & z[:, q]) ^ x[:, q] & (z[:, q] ^ x[:, q])) return symplectic_matrix -def X(symplectic_matrix, q, nqubits): +def X(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> H-S-S-H""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (z[:, q] & (z[:, q] ^ x[:, q])) ^ (z[:, q] & x[:, q]) return symplectic_matrix -def Y(symplectic_matrix, q, nqubits): +def Y(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> S-S-H-S-S-H""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = ( @@ -89,7 +94,7 @@ def Y(symplectic_matrix, q, nqubits): return symplectic_matrix -def SX(symplectic_matrix, q, nqubits): +def SX(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> H-S-H""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (z[:, q] & (z[:, q] ^ x[:, q])) @@ -97,7 +102,7 @@ def SX(symplectic_matrix, q, nqubits): return symplectic_matrix -def SDG(symplectic_matrix, q, nqubits): +def SDG(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> S-S-S""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (x[:, q] & (z[:, q] ^ x[:, q])) @@ -105,7 +110,7 @@ def SDG(symplectic_matrix, q, nqubits): return symplectic_matrix -def SXDG(symplectic_matrix, q, nqubits): +def SXDG(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> H-S-S-S-H""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (z[:, q] & x[:, q]) @@ -113,29 +118,35 @@ def SXDG(symplectic_matrix, q, nqubits): return symplectic_matrix -def RX(symplectic_matrix, q, nqubits, theta): - if theta % (2 * np.pi) == 0: +def RX(symplectic_matrix: ArrayLike, q: int, nqubits: int, theta: float) -> ArrayLike: + if theta % (2 * math.pi) == 0: return I(symplectic_matrix, q, nqubits) - elif (theta / np.pi - 1) % 2 == 0: + + if (theta / math.pi - 1) % 2 == 0: return X(symplectic_matrix, q, nqubits) - elif (theta / (np.pi / 2) - 1) % 4 == 0: + + if (theta / (math.pi / 2) - 1) % 4 == 0: return SX(symplectic_matrix, q, nqubits) - else: # theta == 3*pi/2 + 2*n*pi - return SXDG(symplectic_matrix, q, nqubits) + + # theta == 3*pi/2 + 2*n*pi + return SXDG(symplectic_matrix, q, nqubits) -def RZ(symplectic_matrix, q, nqubits, theta): - if theta % (2 * np.pi) == 0: +def RZ(symplectic_matrix: ArrayLike, q: int, nqubits: int, theta: float) -> ArrayLike: + if theta % (2 * math.pi) == 0: return I(symplectic_matrix, q, nqubits) - elif (theta / np.pi - 1) % 2 == 0: + + if (theta / math.pi - 1) % 2 == 0: return Z(symplectic_matrix, q, nqubits) - elif (theta / (np.pi / 2) - 1) % 4 == 0: + + if (theta / (math.pi / 2) - 1) % 4 == 0: return S(symplectic_matrix, q, nqubits) - else: # theta == 3*pi/2 + 2*n*pi - return SDG(symplectic_matrix, q, nqubits) + + # theta == 3*pi/2 + 2*n*pi + return SDG(symplectic_matrix, q, nqubits) -def RY_pi(symplectic_matrix, q, nqubits): +def RY_pi(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> H-S-S""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (x[:, q] & (z[:, q] ^ x[:, q])) @@ -143,7 +154,7 @@ def RY_pi(symplectic_matrix, q, nqubits): return symplectic_matrix -def RY_3pi_2(symplectic_matrix, q, nqubits): +def RY_3pi_2(symplectic_matrix: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Decomposition --> H-S-S-H-S-S-H-S-S""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = r ^ (z[:, q] & (z[:, q] ^ x[:, q])) @@ -151,34 +162,39 @@ def RY_3pi_2(symplectic_matrix, q, nqubits): return symplectic_matrix -def RY(symplectic_matrix, q, nqubits, theta): - if theta % (2 * np.pi) == 0: +def RY(symplectic_matrix: ArrayLike, q: int, nqubits: int, theta: float) -> ArrayLike: + if theta % (2 * math.pi) == 0: return I(symplectic_matrix, q, nqubits) - elif (theta / np.pi - 1) % 2 == 0: + + if (theta / math.pi - 1) % 2 == 0: return Y(symplectic_matrix, q, nqubits) - elif (theta / (np.pi / 2) - 1) % 4 == 0: + + if (theta / (math.pi / 2) - 1) % 4 == 0: """Decomposition --> H-S-S""" return RY_pi(symplectic_matrix, q, nqubits) - else: # theta == 3*pi/2 + 2*n*pi - """Decomposition --> H-S-S-H-S-S-H-S-S""" - return RY_3pi_2(symplectic_matrix, q, nqubits) + + # theta == 3*pi/2 + 2*n*pi + # Decomposition --> H-S-S-H-S-S-H-S-S + return RY_3pi_2(symplectic_matrix, q, nqubits) -def GPI2(symplectic_matrix, q, nqubits, phi): - if phi % (2 * np.pi) == 0: - return RX(symplectic_matrix, q, nqubits, np.pi / 2) +def GPI2(symplectic_matrix: ArrayLike, q: int, nqubits: int, phi: float) -> ArrayLike: + if phi % (2 * math.pi) == 0: + return RX(symplectic_matrix, q, nqubits, math.pi / 2) - if (phi / np.pi - 1) % 2 == 0: - return RX(symplectic_matrix, q, nqubits, -np.pi / 2) + if (phi / math.pi - 1) % 2 == 0: + return RX(symplectic_matrix, q, nqubits, -math.pi / 2) - if (phi / (np.pi / 2) - 1) % 4 == 0: - return RY(symplectic_matrix, q, nqubits, np.pi / 2) + if (phi / (math.pi / 2) - 1) % 4 == 0: + return RY(symplectic_matrix, q, nqubits, math.pi / 2) # theta == 3*pi/2 + 2*n*pi - return RY(symplectic_matrix, q, nqubits, -np.pi / 2) + return RY(symplectic_matrix, q, nqubits, -math.pi / 2) -def SWAP(symplectic_matrix, control_q, target_q, nqubits): +def SWAP( + symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int +) -> ArrayLike: """Decomposition --> CNOT-CNOT-CNOT""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = ( @@ -203,7 +219,9 @@ def SWAP(symplectic_matrix, control_q, target_q, nqubits): return symplectic_matrix -def iSWAP(symplectic_matrix, control_q, target_q, nqubits): +def iSWAP( + symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int +) -> ArrayLike: """Decomposition --> H-CNOT-CNOT-H-S-S""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = ( @@ -233,19 +251,23 @@ def iSWAP(symplectic_matrix, control_q, target_q, nqubits): return symplectic_matrix -def FSWAP(symplectic_matrix, control_q, target_q, nqubits): +def FSWAP( + symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int +) -> ArrayLike: """Decomposition --> X-CNOT-RY-CNOT-RY-CNOT-CNOT-X""" symplectic_matrix = X(symplectic_matrix, target_q, nqubits) symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) - symplectic_matrix = RY(symplectic_matrix, control_q, nqubits, np.pi / 2) + symplectic_matrix = RY(symplectic_matrix, control_q, nqubits, math.pi / 2) symplectic_matrix = CNOT(symplectic_matrix, target_q, control_q, nqubits) - symplectic_matrix = RY(symplectic_matrix, control_q, nqubits, -np.pi / 2) + symplectic_matrix = RY(symplectic_matrix, control_q, nqubits, -math.pi / 2) symplectic_matrix = CNOT(symplectic_matrix, target_q, control_q, nqubits) symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) return X(symplectic_matrix, control_q, nqubits) -def CY(symplectic_matrix, control_q, target_q, nqubits): +def CY( + symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int +) -> ArrayLike: """Decomposition --> S-CNOT-SDG""" r, x, z = _get_rxz(symplectic_matrix, nqubits) symplectic_matrix[:, -1] = ( @@ -267,95 +289,123 @@ def CY(symplectic_matrix, control_q, target_q, nqubits): return symplectic_matrix -def CRX(symplectic_matrix, control_q, target_q, nqubits, theta): +def CRX( + symplectic_matrix: ArrayLike, + control_q: int, + target_q: int, + nqubits: int, + theta: float, +) -> ArrayLike: # theta = 4 * n * pi - if theta % (4 * np.pi) == 0: + if theta % (4 * math.pi) == 0: return I(symplectic_matrix, target_q, nqubits) + # theta = pi + 4 * n * pi - elif (theta / np.pi - 1) % 4 == 0: + if (theta / math.pi - 1) % 4 == 0: symplectic_matrix = X(symplectic_matrix, target_q, nqubits) symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) symplectic_matrix = X(symplectic_matrix, target_q, nqubits) return CY(symplectic_matrix, control_q, target_q, nqubits) + # theta = 2 * pi + 4 * n * pi - elif (theta / (2 * np.pi) - 1) % 2 == 0: + if (theta / (2 * math.pi) - 1) % 2 == 0: symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) symplectic_matrix = Y(symplectic_matrix, target_q, nqubits) symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) return Y(symplectic_matrix, target_q, nqubits) + # theta = 3 * pi + 4 * n * pi - elif (theta / np.pi - 3) % 4 == 0: - symplectic_matrix = X(symplectic_matrix, target_q, nqubits) - symplectic_matrix = CY(symplectic_matrix, control_q, target_q, nqubits) - symplectic_matrix = X(symplectic_matrix, target_q, nqubits) - return CZ(symplectic_matrix, control_q, target_q, nqubits) + symplectic_matrix = X(symplectic_matrix, target_q, nqubits) + symplectic_matrix = CY(symplectic_matrix, control_q, target_q, nqubits) + symplectic_matrix = X(symplectic_matrix, target_q, nqubits) + return CZ(symplectic_matrix, control_q, target_q, nqubits) -def CRZ(symplectic_matrix, control_q, target_q, nqubits, theta): +def CRZ( + symplectic_matrix: ArrayLike, + control_q: int, + target_q: int, + nqubits: int, + theta: float, +) -> ArrayLike: # theta = 4 * n * pi - if theta % (4 * np.pi) == 0: + if theta % (4 * math.pi) == 0: return I(symplectic_matrix, target_q, nqubits) + # theta = pi + 4 * n * pi - elif (theta / np.pi - 1) % 4 == 0: + if (theta / math.pi - 1) % 4 == 0: symplectic_matrix = X(symplectic_matrix, target_q, nqubits) symplectic_matrix = CY(symplectic_matrix, control_q, target_q, nqubits) symplectic_matrix = X(symplectic_matrix, target_q, nqubits) return CNOT(symplectic_matrix, control_q, target_q, nqubits) + # theta = 2 * pi + 4 * n * pi - elif (theta / (2 * np.pi) - 1) % 2 == 0: + if (theta / (2 * math.pi) - 1) % 2 == 0: symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) symplectic_matrix = X(symplectic_matrix, target_q, nqubits) symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) return X(symplectic_matrix, target_q, nqubits) + # theta = 3 * pi + 4 * n * pi - elif (theta / np.pi - 3) % 4 == 0: - symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) - symplectic_matrix = X(symplectic_matrix, target_q, nqubits) - symplectic_matrix = CY(symplectic_matrix, control_q, target_q, nqubits) - return X(symplectic_matrix, target_q, nqubits) + symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) + symplectic_matrix = X(symplectic_matrix, target_q, nqubits) + symplectic_matrix = CY(symplectic_matrix, control_q, target_q, nqubits) + return X(symplectic_matrix, target_q, nqubits) -def CRY(symplectic_matrix, control_q, target_q, nqubits, theta): +def CRY( + symplectic_matrix: ArrayLike, + control_q: int, + target_q: int, + nqubits: int, + theta: float, +) -> ArrayLike: # theta = 4 * n * pi - if theta % (4 * np.pi) == 0: + if theta % (4 * math.pi) == 0: return I(symplectic_matrix, target_q, nqubits) + # theta = pi + 4 * n * pi - elif (theta / np.pi - 1) % 4 == 0: + if (theta / math.pi - 1) % 4 == 0: symplectic_matrix = Z(symplectic_matrix, target_q, nqubits) symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) symplectic_matrix = Z(symplectic_matrix, target_q, nqubits) return CZ(symplectic_matrix, control_q, target_q, nqubits) + # theta = 2 * pi + 4 * n * pi - elif (theta / (2 * np.pi) - 1) % 2 == 0: + if (theta / (2 * math.pi) - 1) % 2 == 0: return CRZ(symplectic_matrix, control_q, target_q, nqubits, theta) + # theta = 3 * pi + 4 * n * pi - elif (theta / np.pi - 3) % 4 == 0: - symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) - symplectic_matrix = Z(symplectic_matrix, target_q, nqubits) - symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) - return Z(symplectic_matrix, target_q, nqubits) + symplectic_matrix = CZ(symplectic_matrix, control_q, target_q, nqubits) + symplectic_matrix = Z(symplectic_matrix, target_q, nqubits) + symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) + return Z(symplectic_matrix, target_q, nqubits) -def ECR(symplectic_matrix, control_q, target_q, nqubits): +def ECR( + symplectic_matrix: ArrayLike, control_q: int, target_q: int, nqubits: int +) -> ArrayLike: symplectic_matrix = S(symplectic_matrix, control_q, nqubits) symplectic_matrix = SX(symplectic_matrix, target_q, nqubits) symplectic_matrix = CNOT(symplectic_matrix, control_q, target_q, nqubits) return X(symplectic_matrix, control_q, nqubits) -def _exponent( - x1: np.ndarray, z1: np.ndarray, x2: np.ndarray, z2: np.ndarray -) -> np.ndarray: - """Helper function that computes the exponent to which i is raised for the product of the x and z paulis encoded in the symplectic matrix. This is used in _rowsum. The computation is performed parallely over the separated paulis x1[i], z1[i], x2[i] and z2[i]. +def _exponent(x1: ArrayLike, z1: ArrayLike, x2: ArrayLike, z2: ArrayLike) -> ArrayLike: + """Helper function that computes the exponent to which i is raised for the product + of the x and z paulis encoded in the symplectic matrix. + + This is used in _rowsum. The computation is performed parallely over the separated + paulis x1[i], z1[i], x2[i] and z2[i]. Args: - x1 (np.array): Bits of the first x paulis. - z1 (np.array): Bits of the first z paulis. - x2 (np.array): Bits of the second x paulis. - z2 (np.array): Bits of the second z paulis. + x1 (ArrayLike): Bits of the first x paulis. + z1 (ArrayLike): Bits of the first z paulis. + x2 (ArrayLike): Bits of the second x paulis. + z2 (ArrayLike): Bits of the second z paulis. Returns: - ndarray: The calculated exponents. + ArrayLike: The calculated exponents. """ # this cannot be performed in the packed representation for measurements (thus packed rows) # because bitwise arithmetic difference and sum are needed, which cannot be done directly @@ -363,17 +413,27 @@ def _exponent( return 2 * (x1 * x2 * (z2 - z1) + z1 * z2 * (x1 - x2)) - x1 * z2 + x2 * z1 -def _rowsum(symplectic_matrix, h, i, nqubits, determined=False): - """Helper function that updates the symplectic matrix by setting the h-th generator equal to the (i+h)-th one. This is done to keep track of the phase of the h-th row of the symplectic matrix (r[h]). The function is applied parallely over all the rows h and i passed. +def _rowsum( + symplectic_matrix: ArrayLike, + h: ArrayLike, + i: ArrayLike, + nqubits: int, + determined: bool = False, +) -> ArrayLike: + """Helper function that updates the symplectic matrix by setting the h-th generator + equal to the (i+h)-th one. + + This is done to keep track of the phase of the h-th row of the symplectic matrix (r[h]). + The function is applied parallely over all the rows h and i passed. Args: - symplectic_matrix (np.array): Input symplectic matrix. - h (np.array): Indices of the rows encoding the generators to update. - i (np.array): Indices of the rows encoding the generators to use. + symplectic_matrix (ArrayLike): Input symplectic matrix. + h (ArrayLike): Indices of the rows encoding the generators to update. + i (ArrayLike): Indices of the rows encoding the generators to use. nqubits (int): Total number of qubits. Returns: - ndarray: The updated symplectic matrix. + ArrayLike: The updated symplectic matrix. """ # calculate the exponent in the unpacked representation xi, zi = symplectic_matrix[i, :nqubits], symplectic_matrix[i, nqubits:-1] @@ -408,12 +468,14 @@ def _rowsum(symplectic_matrix, h, i, nqubits, determined=False): return _unpack_for_measurements(symplectic_matrix, nqubits) -def _determined_outcome(state, q, nqubits): +def _determined_outcome(state: ArrayLike, q: int, nqubits: int) -> ArrayLike: """Extracts the outcome for a measurement in case it is determined.""" state[-1, :] = 0 idx = (state[:nqubits, q].nonzero()[0] + nqubits).astype(np.uint) + if len(idx) == 0: return state, state[-1, -1] + state = _rowsum( state, _dim_xz(nqubits) * np.ones(idx.shape, dtype=np.uint), @@ -421,10 +483,11 @@ def _determined_outcome(state, q, nqubits): nqubits, True, ) + return state, state[-1, -1] -def _random_outcome(state, p, q, nqubits): +def _random_outcome(state: ArrayLike, p: int, q: int, nqubits: int) -> ArrayLike: """Extracts the outcome for a measurement in case it is random.""" p = p[0] + nqubits state[p, q] = 0 @@ -447,33 +510,33 @@ def _random_outcome(state, p, q, nqubits): @cache -def _dim(nqubits): - """Returns the dimension of the symplectic matrix for a given number of qubits.""" +def _dim(nqubits: int) -> int: + """Return the dimension of the symplectic matrix for a given number of qubits.""" return _dim_xz(nqubits) + 1 @cache -def _dim_xz(nqubits): +def _dim_xz(nqubits: int) -> int: """Returns the dimension of the symplectic matrix (only the de/stabilizers generators part, without the phases and scratch row) for a given number of qubits.""" return 2 * nqubits @cache -def _packed_size(n): - """Returns the size of an array of `n` booleans after packing.""" +def _packed_size(n: int) -> int: + """Return the size of an array of `n` booleans after packing.""" return np.ceil(n / 8).astype(int) -def _packbits(array, axis): +def _packbits(array: ArrayLike, axis: int) -> ArrayLike: return np.packbits(array, axis=axis) -def _unpackbits(array, axis, count): +def _unpackbits(array: ArrayLike, axis: int, count: int) -> ArrayLike: return np.unpackbits(array, axis=axis, count=count) -def _pack_for_measurements(state, nqubits): +def _pack_for_measurements(state: ArrayLike, nqubits: int) -> ArrayLike: """Prepares the state for measurements by packing the rows of the X and Z sections of the symplectic matrix.""" r, x, z = _get_rxz(state, nqubits) x = _packbits(x, axis=1) @@ -481,21 +544,26 @@ def _pack_for_measurements(state, nqubits): return np.hstack((x, z, r[:, None])) -def _unpack_for_measurements(state, nqubits): +def _unpack_for_measurements(state: ArrayLike, nqubits: int) -> ArrayLike: """Unpacks the symplectc matrix that was packed for measurements.""" x = _unpackbits(state[:, : _packed_size(nqubits)], axis=1, count=nqubits) z = _unpackbits(state[:, _packed_size(nqubits) : -1], axis=1, count=nqubits) return np.hstack((x, z, state[:, -1][:, None])) -def _init_state_for_measurements(state, nqubits, collapse): +def _init_state_for_measurements( + state: ArrayLike, nqubits: int, collapse: bool +) -> ArrayLike: if collapse: return _unpackbits(state, axis=0, count=_dim(nqubits)) + return state.copy() # valid for a standard basis measurement only -def M(state, qubits, nqubits, collapse=False): +def M( + state: ArrayLike, qubits: Tuple[int, ...], nqubits: int, collapse: bool = False +) -> ArrayLike: sample = [] state = _init_state_for_measurements(state, nqubits, collapse) # TODO: parallelize this and get rid of the loop @@ -508,52 +576,63 @@ def M(state, qubits, nqubits, collapse=False): else: _, outcome = _determined_outcome(state, q, nqubits) sample.append(outcome) + if collapse: state = _packbits(state, axis=0) + return sample -def cast(x, dtype=None, copy: bool = False): +def cast( + array: ArrayLike, dtype: Optional[DTypeLike] = None, copy: bool = False +) -> ArrayLike: if dtype is None: dtype = "complex128" - if isinstance(x, np.ndarray): - return x.astype(dtype, copy=copy) - elif sparse.issparse(x): # pragma: no cover - return x.astype(dtype, copy=copy) - return np.asarray(x, dtype=dtype, copy=copy if copy else None) + + if isinstance(array, np.ndarray): + return array.astype(dtype, copy=copy) + + if sparse.issparse(array): # pragma: no cover + return array.astype(dtype, copy=copy) + + return np.asarray(array, dtype=dtype, copy=copy if copy else None) -def _clifford_pre_execution_reshape(state): - """Reshape and packing applied to the symplectic matrix before execution to prepare the state in the form needed by each engine. +def _clifford_pre_execution_reshape(state: ArrayLike) -> ArrayLike: + """Reshape and packing applied to the symplectic matrix before execution to prepare + the state in the form needed by each engine. Args: - state (np.array): Input state. + state (ArrayLike): Input state. Returns: - (np.array) The packed and reshaped state. + ArrayLike: The packed and reshaped state. """ return _packbits(state, axis=0) -def _clifford_post_execution_reshape(state, nqubits: int): - """Reshape and unpacking applied to the state after execution to retrieve the standard symplectic matrix form. +def _clifford_post_execution_reshape(state: ArrayLike, nqubits: int) -> ArrayLike: + """Reshape and unpacking applied to the state after execution to retrieve + the standard symplectic matrix form. Args: - state (np.array): Input state. + state (ArrayLike): Input state. nqubits (int): Number of qubits. Returns: - (np.array) The unpacked and reshaped state. + ArrayLike: The unpacked and reshaped state. """ state = _unpackbits(state, axis=0, count=_dim(nqubits))[: _dim(nqubits)] return state -def csr_matrix(array, **kwargs): +def csr_matrix(array: ArrayLike, **kwargs) -> ArrayLike: return sparse.csr_matrix(array, **kwargs) -def _identity_sparse(dims: int, dtype=None, **kwargs): +def _identity_sparse( + dims: int, dtype: Optional[DTypeLike] = None, **kwargs +) -> ArrayLike: if dtype is None: # pragma: no cover dtype = "complex128" diff --git a/src/qibo/backends/clifford.py b/src/qibo/backends/clifford.py index b8bee994a4..a6b4c40fae 100644 --- a/src/qibo/backends/clifford.py +++ b/src/qibo/backends/clifford.py @@ -19,10 +19,11 @@ class CliffordBackend(Backend): """Backend for the simulation of Clifford circuits following Ref. [1]. - `Aaronson & Gottesman (2004) `_. Args: - :class:`qibo.backends.abstract.Backend`: Backend used for the calculation. + platform (str, optional): name of the backend that should be used + in calculations. Options are: ``"numpy"``,``"numba"``, ``"cupy"``, + or ``"stim"``. If ``None``, global backend is used. Defaults to ``None``. References: 1. S. Aaronson and D. Gottesman, *Improved Simulation of Stabilizer Circuits*, @@ -83,10 +84,6 @@ def __init__(self, platform: Optional[str] = None): setattr( self._platform, method, getattr(clifford_operations_gpu, method) ) - elif self.platform == "pytorch": # pragma: no cover - import torch # pylint: disable=import-outside-toplevel - - self.engine = torch else: # pragma: no cover raise_error( NotImplementedError, diff --git a/src/qibo/quantum_info/clifford.py b/src/qibo/quantum_info/clifford.py index b6da58070f..e06f2c75a4 100644 --- a/src/qibo/quantum_info/clifford.py +++ b/src/qibo/quantum_info/clifford.py @@ -19,7 +19,8 @@ @dataclass class Clifford: - """Object storing the results of a circuit execution with the :class:`qibo.backends.clifford.CliffordBackend`. + """Object storing the results of a circuit execution with the + :class:`qibo.backends.clifford.CliffordBackend`. Args: data (ndarray or :class:`qibo.models.circuit.Circuit`): If ``ndarray``, it is the @@ -45,9 +46,9 @@ class Clifford: nshots: int = 1000 platform: Optional[str] = None - _backend: Optional[CliffordBackend] = None - _measurement_gate: M = None - _samples: Optional[int] = None + _backend: Optional[CliffordBackend] = field(default=None, repr=False) + _measurement_gate: M = field(default=None, repr=False) + _samples: Optional[int] = field(default=None, repr=False) def __post_init__(self): if self._backend is None: