docs: def apply_unitary(unitary_value: Any,
                    args: ApplyUnitaryArgs,
                    default: TDefault = RaiseTypeErrorIfNotProvided
                    ) -> Union[np.ndarray, TDefault]:
      """High performance left-multiplication of a unitary effect onto a tensor.
  
      If `unitary_value` defines an `_apply_unitary_` method, that method will be
      used to apply `unitary_value`'s unitary effect to the target tensor.
      Otherwise, if `unitary_value` defines a `_unitary_` method, its unitary
      matrix will be retrieved and applied using a generic method. Otherwise the
      application fails, and either an exception is raised or the specified
      default value is returned.
  
      Args:
          unitary_value: The value with a unitary effect to apply to the target.
          args: A mutable `cirq.ApplyUnitaryArgs` object describing the target
              tensor, available workspace, and axes to operate on. The attributes
              of this object will be mutated as part of computing the result.
          default: What should be returned if `unitary_value` doesn't have a
              unitary effect. If not specified, a TypeError is raised instead of
              returning a default value.
  
      Returns:
          If the receiving object is not able to apply its unitary effect,
          the specified default value is returned (or a TypeError is raised). If
          this occurs, then `target_tensor` should not have been mutated.
  
          If the receiving object was able to work inline, directly
          mutating target_tensor it will return target_tensor. The caller is
          responsible for checking if the result is target_tensor.
  
          If the receiving object wrote its output over available_buffer, the
          result will be available_buffer. The caller is responsible for
          checking if the result is available_buffer (and e.g. swapping
          the buffer for the target tensor before the next call).
  
          The receiving object may also write its output over a new buffer
          that it created, in which case that new array is returned.
  
      Raises:
          TypeError: `unitary_value` doesn't have a unitary effect and `default`
              wasn't specified.
      """
  
      # Check if the specialized method is present.
      func = getattr(unitary_value, '_apply_unitary_', None)
      if func is not None:
          result = func(args)
          if result is not NotImplemented and result is not None:
              return result
  
      # Fallback to using the object's _unitary_ matrix.
      matrix = unitary(unitary_value, None)
      if matrix is not None:
          # Special case for single-qubit operations.
          if matrix.shape == (2, 2):
              zero = args.subspace_index(0)
              one = args.subspace_index(1)
              return linalg.apply_matrix_to_slices(args.target_tensor,
                                                   matrix,
                                                   [zero, one],
                                                   out=args.available_buffer)
  
          # Fallback to np.einsum for the general case.
          return linalg.targeted_left_multiply(
              matrix.astype(args.target_tensor.dtype).reshape(
                  (2,) * (2 * len(args.axes))),
              args.target_tensor,
              args.axes,
              out=args.available_buffer)
  
      # Don't know how to apply. Fallback to specified default behavior.
      if default is not RaiseTypeErrorIfNotProvided:
          return default
      raise TypeError(
          "object of type '{}' has no _apply_unitary_ or _unitary_ methods "
          "(or they returned None or NotImplemented).".format(
              type(unitary_value)))