def unitary(
    val: Any, default: np.ndarray | TDefault = RaiseTypeErrorIfNotProvided
) -> 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."
    )
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.
    
cirq-core/cirq/protocols/unitary_protocol.py