Source code for bqskit.qis.unitary.unitary

"""This module implements the Unitary abstract base class."""
from __future__ import annotations

import abc
from typing import Sequence
from typing import TYPE_CHECKING
from typing import Union

import numpy as np

from bqskit.qis.unitary.meta import UnitaryMeta
from bqskit.utils.typing import is_real_number
from bqskit.utils.typing import is_sequence

if TYPE_CHECKING:
    from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix


[docs] class Unitary(metaclass=UnitaryMeta): """ A unitary-valued function. A `Unitary` is a map from zero or more real numbers to a unitary matrix. This is captured in the `get_unitary` abstract method. """ _num_params: int _num_qudits: int _radixes: tuple[int, ...] _dim: int @property def num_params(self) -> int: """The number of real parameters this unitary-valued function takes.""" return getattr(self, '_num_params') @property def num_qudits(self) -> int: """The number of qudits this unitary can act on.""" if hasattr(self, '_num_qudits'): return self._num_qudits return len(self.radixes) @property def radixes(self) -> tuple[int, ...]: """The number of orthogonal states for each qudit.""" return getattr(self, '_radixes') @property def dim(self) -> int: """The matrix dimension for this unitary.""" if hasattr(self, '_dim'): return self._dim return int(np.prod(self.radixes))
[docs] @abc.abstractmethod def get_unitary(self, params: RealVector = []) -> UnitaryMatrix: """ Map real-valued `params` to a `UnitaryMatrix`. Args: params (RealVector): Unconstrained vector of real number parameters for parameterized unitaries. Returns: UnitaryMatrix: The unitary matrix. """
[docs] def is_qubit_only(self) -> bool: """Return true if this unitary can only act on qubits.""" return all([radix == 2 for radix in self.radixes])
[docs] def is_qutrit_only(self) -> bool: """Return true if this unitary can only act on qutrits.""" return all([radix == 3 for radix in self.radixes])
[docs] def is_qudit_only(self, radix: int) -> bool: """ Return true if this unitary can only act on `radix`-qudits. Args: radix (int): Check all qudits have this many orthogonal states. """ return all([r == radix for r in self.radixes])
[docs] def is_parameterized(self) -> bool: """Return true if this unitary is parameterized.""" return self.num_params != 0
[docs] def is_constant(self) -> bool: """Return true if this unitary doesn't take parameters.""" return not self.is_parameterized()
[docs] def check_parameters(self, params: RealVector) -> None: """ Check parameters are valid and match the unitary. Args: params(RealVector): The parameters to check. Raises: ValueError: If parameter length does not match expected number. """ if not is_sequence(params): raise TypeError( 'Expected a sequence type for params, got %s.' % type(params), ) if not all(is_real_number(p) for p in params): typechecks = [is_real_number(p) for p in params] fail_idx = typechecks.index(False) raise TypeError( 'Expected params to be floats, got %s.' % type(params[fail_idx]), ) if len(params) != self.num_params: raise ValueError( 'Expected %d params, got %d.' % (self.num_params, len(params)), )
[docs] def is_self_inverse(self, params: RealVector = []) -> bool: """ Checks whether the unitary is its own inverse. A unitary is its own inverse if its matrix is equal to its Hermitian conjugate. Args: params (RealVector): The parameters of the unitary to check. Returns: bool: True if the unitary is self-inverse, False otherwise. Note: - This checks that the unitary is self-inverse for the given parameters only. """ # Get the unitary matrix of the gate unitary_matrix = self.get_unitary(params) # Calculate the Hermitian conjugate (adjoint) of the unitary matrix hermitian_conjugate = unitary_matrix.dagger # Check if the unitary matrix is equal to its Hermitian conjugate return np.allclose(unitary_matrix, hermitian_conjugate)
RealVector = Union[Sequence[float], np.ndarray]