Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 26 additions & 23 deletions cirq-core/cirq/protocols/unitary_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from cirq.protocols.apply_unitary_protocol import apply_unitaries, ApplyUnitaryArgs
from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits

# This is a special indicator value used by the unitary method to determine
# This is a special indicator value used by the function unitary() to determine
# whether or not the caller provided a 'default' argument. It must be of type
# np.ndarray to ensure the method has the correct type signature in that case.
# It is checked for using `is`, so it won't have a false positive if the user
Expand All @@ -41,22 +41,22 @@ class SupportsUnitary(Protocol):

@doc_private
def _unitary_(self) -> np.ndarray | NotImplementedType:
"""A unitary matrix describing this value, e.g. the matrix of a gate.
"""A unitary matrix describing this value, i.e., 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
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.)
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
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
ordering of `numpy.kron(A, B)`, where A is a qubit earlier in the list
than the qubit B.

Returns:
Expand All @@ -70,7 +70,7 @@ def _has_unitary_(self) -> bool:

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.
to using `_unitary_()` with a default value, or False if neither exist.

Returns:
True if the value has a unitary matrix representation, False
Expand All @@ -83,17 +83,19 @@ def unitary(
) -> np.ndarray | TDefault:
"""Returns a unitary matrix describing the given value.

The matrix is determined by any one of the following techniques:
The matrix is determined by the first of these strategies that succeeds:

- If the value is a numpy array, it is returned directly.
- The value has a `_unitary_` method that returns something besides None or
NotImplemented. The matrix is whatever the method returned.
- If the value is a NumPy array, it is tested using `linalg.is_unitary()`.
If the result is `True`, the array is returned directly; if `is_unitary()`
returns `False`, a `ValueError` exception is raised.
- The value has a `_unitary_` method that returns something besides `None` or
`NotImplemented`. The matrix is whatever the method returned.
- 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.
- 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
Expand All @@ -102,8 +104,8 @@ def unitary(
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.
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.
Expand All @@ -112,11 +114,11 @@ def unitary(
Raises:
TypeError: `val` doesn't have a unitary effect and no default value was
specified.
ValueError: `val` is a numpy array that is not unitary.
ValueError: `val` is a NumPy array that is not unitary.
"""
if isinstance(val, np.ndarray):
if not linalg.is_unitary(val):
raise ValueError("The provided numpy array is not unitary.")
raise ValueError("The provided NumPy array is not unitary.")
return val

strats = [
Expand All @@ -140,13 +142,14 @@ def unitary(
f"type: {type(val)}\n"
f"value: {val!r}\n"
"\n"
"The value failed to satisfy any of the following criteria:\n"
"The given value failed to satisfy any of the following criteria:\n"
"- A NumPy array for which `linalg.is_unitary()` returned `True`.\n"
"- A `_unitary_(self)` method that returned a value "
"besides None or NotImplemented.\n"
"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."
"- An `_apply_unitary_(self, args)` method that returned a value "
"besides `None` or `NotImplemented`."
)


Expand Down
Loading