| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| from types import NotImplementedType |
| from typing import Any, Optional, TypeVar, Union |
|
|
| import numpy as np |
| from typing_extensions import Protocol |
|
|
| from cirq._doc import doc_private |
| from cirq.protocols import qid_shape_protocol |
| from cirq.protocols.apply_unitary_protocol import apply_unitaries, ApplyUnitaryArgs |
| from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits |
|
|
| |
| |
| |
| |
| |
| RaiseTypeErrorIfNotProvided: np.ndarray = np.array([]) |
|
|
| TDefault = TypeVar('TDefault') |
|
|
|
|
| class SupportsUnitary(Protocol): |
| """An object that may be describable by a unitary matrix.""" |
|
|
| @doc_private |
| def _unitary_(self) -> Union[np.ndarray, NotImplementedType]: |
| """A unitary matrix describing this value, e.g. the matrix of a gate. |
| |
| This method is used by the global `cirq.unitary` method. If this method |
| is not present, or returns NotImplemented, it is assumed that the |
| receiving object doesn't have a unitary matrix (resulting in a TypeError |
| or default result when calling `cirq.unitary` on it). (The ability to |
| return NotImplemented is useful when a class cannot know if it has a |
| matrix until runtime, e.g. cirq.X**c normally has a matrix but |
| cirq.X**sympy.Symbol('a') doesn't.) |
| |
| The order of cells in the matrix is always implicit with respect to the |
| object being called. For example, for gates the matrix must be ordered |
| with respect to the list of qubits that the gate is applied to. For |
| operations, the matrix is ordered to match the list returned by its |
| `qubits` attribute. The qubit-to-amplitude order mapping matches the |
| ordering of numpy.kron(A, B), where A is a qubit earlier in the list |
| than the qubit B. |
| |
| Returns: |
| A unitary matrix describing this value, or NotImplemented if there |
| is no such matrix. |
| """ |
|
|
| @doc_private |
| def _has_unitary_(self) -> bool: |
| """Whether this value has a unitary matrix representation. |
| |
| This method is used by the global `cirq.has_unitary` method. If this |
| method is not present, or returns NotImplemented, it will fallback |
| to using _unitary_ with a default value, or False if neither exist. |
| |
| Returns: |
| True if the value has a unitary matrix representation, False |
| otherwise. |
| """ |
|
|
|
|
| def unitary( |
| val: Any, default: Union[np.ndarray, TDefault] = RaiseTypeErrorIfNotProvided |
| ) -> Union[np.ndarray, TDefault]: |
| """Returns a unitary matrix describing the given value. |
| |
| The matrix is determined by any one of the following techniques: |
| |
| - The value has a `_unitary_` method that returns something besides None or |
| NotImplemented. The matrix is whatever the method returned. |
| - The value has a `_decompose_` method that returns a list of operations, |
| and each operation in the list has a unitary effect. The matrix is |
| created by aggregating the sub-operations' unitary effects. |
| - The value has an `_apply_unitary_` method, and it returns something |
| besides None or NotImplemented. The matrix is created by applying |
| `_apply_unitary_` to an identity matrix. |
| |
| If none of these techniques succeeds, it is assumed that `val` doesn't have |
| a unitary effect. The order in which techniques are attempted is |
| unspecified. |
| |
| Args: |
| val: The value to describe with a unitary matrix. |
| default: Determines the fallback behavior when `val` doesn't have |
| a unitary effect. If `default` is not set, a TypeError is raised. If |
| `default` is set to a value, that value is returned. |
| |
| Returns: |
| If `val` has a unitary effect, the corresponding unitary matrix. |
| Otherwise, if `default` is specified, it is returned. |
| |
| Raises: |
| TypeError: `val` doesn't have a unitary effect and no default value was |
| specified. |
| """ |
| strats = [ |
| _strat_unitary_from_unitary, |
| _strat_unitary_from_apply_unitary, |
| _strat_unitary_from_decompose, |
| ] |
| for strat in strats: |
| result = strat(val) |
| if result is None: |
| break |
| if result is not NotImplemented: |
| return result |
|
|
| if default is not RaiseTypeErrorIfNotProvided: |
| return default |
| raise TypeError( |
| "cirq.unitary failed. " |
| "Value doesn't have a (non-parameterized) unitary effect.\n" |
| "\n" |
| f"type: {type(val)}\n" |
| f"value: {val!r}\n" |
| "\n" |
| "The value failed to satisfy any of the following criteria:\n" |
| "- A `_unitary_(self)` method that returned a value " |
| "besides None or NotImplemented.\n" |
| "- A `_decompose_(self)` method that returned a " |
| "list of unitary operations.\n" |
| "- An `_apply_unitary_(self, args) method that returned a value " |
| "besides None or NotImplemented." |
| ) |
|
|
|
|
| def _strat_unitary_from_unitary(val: Any) -> Optional[np.ndarray]: |
| """Attempts to compute a value's unitary via its _unitary_ method.""" |
| getter = getattr(val, '_unitary_', None) |
| if getter is None: |
| return NotImplemented |
| return getter() |
|
|
|
|
| def _strat_unitary_from_apply_unitary(val: Any) -> Optional[np.ndarray]: |
| """Attempts to compute a value's unitary via its _apply_unitary_ method.""" |
| |
| method = getattr(val, '_apply_unitary_', None) |
| if method is None: |
| return NotImplemented |
|
|
| |
| val_qid_shape = qid_shape_protocol.qid_shape(val, None) |
| if val_qid_shape is None: |
| return NotImplemented |
|
|
| |
| result = method(ApplyUnitaryArgs.for_unitary(qid_shape=val_qid_shape)) |
|
|
| if result is NotImplemented or result is None: |
| return result |
| state_len = np.prod(val_qid_shape, dtype=np.int64) |
| return result.reshape((state_len, state_len)) |
|
|
|
|
| def _strat_unitary_from_decompose(val: Any) -> Optional[np.ndarray]: |
| """Attempts to compute a value's unitary via its _decompose_ method.""" |
| |
| operations, qubits, val_qid_shape = _try_decompose_into_operations_and_qubits(val) |
| if operations is None: |
| return NotImplemented |
|
|
| all_qubits = frozenset(q for op in operations for q in op.qubits) |
| work_qubits = frozenset(qubits) |
| ancillas = tuple(sorted(all_qubits.difference(work_qubits))) |
|
|
| ordered_qubits = ancillas + tuple(qubits) |
| val_qid_shape = qid_shape_protocol.qid_shape(ancillas) + val_qid_shape |
|
|
| |
| result = apply_unitaries( |
| operations, ordered_qubits, ApplyUnitaryArgs.for_unitary(qid_shape=val_qid_shape), None |
| ) |
|
|
| |
| if result is None: |
| return None |
|
|
| state_len = np.prod(val_qid_shape, dtype=np.int64) |
| result = result.reshape((state_len, state_len)) |
| |
| |
| |
| work_state_len = np.prod(val_qid_shape[len(ancillas) :], dtype=np.int64) |
| return result[:work_state_len, :work_state_len] |
|
|