Source code for bqskit.qis.permutation

"""This module implements the PermutationMatrix class."""
from __future__ import annotations

import logging
from typing import Sequence

import numpy as np

from bqskit.qis.unitary.unitarybuilder import UnitaryBuilder
from bqskit.qis.unitary.unitarymatrix import UnitaryLike
from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
from bqskit.utils.typing import is_integer
_logger = logging.getLogger(__name__)


[docs] class PermutationMatrix(UnitaryMatrix): """A binary, unitary matrix with a single 1 in each row and column."""
[docs] def __init__( self, input: UnitaryLike, radixes: Sequence[int] = [], check_arguments: bool = True, ) -> None: """ Constructs a `PermutationMatrix` from the supplied matrix. See :class:`UnitaryMatrix` for more info. """ if check_arguments and not PermutationMatrix.is_permutation(input): raise ValueError('Input failed permutation condition.') super().__init__(input, radixes, check_arguments)
[docs] @staticmethod def from_qubit_location( num_qubits: int, location: Sequence[int], ) -> PermutationMatrix: """ Creates a qubit permutation matrix. See :func:`from_qudit_location` for more info. """ return PermutationMatrix.from_qudit_location(num_qubits, 2, location)
[docs] @staticmethod def from_qudit_location( num_qudits: int, radix: int, location: Sequence[int], ) -> PermutationMatrix: """ Creates the permutation matrix specified by arguments. The resulting matrix will move the location's qudits into positions defined by their index in location. For example: P = Permutationmatrix.from_qudit_location(3, 2, (1, 2, 0)) .-----. 0 -| |- 1 1 -| P |- 2 2 -| |- 0 '-----' Args: num_qudits (int): Total number of qudits. radix (int): The base of all qudits. location (Sequence[int]): The desired qudits to swap to their index. Returns: (PermutationMatrix): A permutation matrix that moves the location's qudits into positions defined by their index in location. """ swap_utry = PermutationMatrix.gen_swap_unitary(radix) current_perm = list(location) for i in range(num_qudits): if i not in current_perm: current_perm.append(i) perm_builder = UnitaryBuilder(num_qudits, [radix] * num_qudits) for index, qudit in enumerate(current_perm): if index != qudit: current_pos = current_perm.index(index) perm_builder.apply_left(swap_utry, (index, current_pos)) tmp = current_perm[index] current_perm[index] = current_perm[current_pos] current_perm[current_pos] = tmp return PermutationMatrix(perm_builder.get_unitary())
[docs] @staticmethod def is_permutation(P: np.typing.ArrayLike, tol: float = 1e-8) -> bool: """ Check if P is a permutation matrix. Args: P (np.typing.ArrayLike): The matrix to check. tol (float): The numerical precision of the check. Returns: bool: True if P is a permutation matrix. """ if isinstance(P, PermutationMatrix): return True if not UnitaryMatrix.is_unitary(P, tol): return False if not isinstance(P, np.ndarray): P = np.array(P) if not all(s == 1 for s in P.sum(0)): _logger.debug('Not all rows sum to 1.') return False if not all(s == 1 for s in P.sum(1)): _logger.debug('Not all columns sum to 1.') return False if not all(e == 1 or e == 0 for row in P for e in row): _logger.debug('Not all elements are 0 or 1.') return False return True
[docs] @staticmethod def gen_swap_unitary(radix: int = 2) -> UnitaryMatrix: """ Generate a unitary matrix that swaps the state of two qudits. Args: radix (int): The base of the qudits being swapped. Defaults to qubits or base 2. (Default: 2) Raises: ValueError: If radix is less than two. """ if not is_integer(radix): raise TypeError('Expected a single integer radix.') if radix < 2: raise ValueError('Radix must be at least 2.') dim = radix * radix mat = [[0 for _j in range(dim)] for _i in range(dim)] for col in range(dim): # col = a * radix + b; a, b < radix a = col // radix b = col % radix row = b * radix + a mat[row][col] = 1 return UnitaryMatrix(mat, [radix, radix])