# Source code for qutip.core.expect

    __all__ = ['expect', 'variance']

    from typing import overload, Sequence

    from .qobj import Qobj
    from . import data as _data
    from ..settings import settings
    from ..core.numpy_backend import np

    @overload
    def expect(oper: Qobj, state: Qobj) -> complex: ...

    @overload
    def expect(
        oper: Qobj,
        state: Qobj | Sequence[Qobj],
    ) -> np.typing.NDArray[complex]: ...

    @overload
    def expect(
        oper: Qobj | Sequence[Qobj],
        state: Qobj,
    ) -> list[complex]: ...

    @overload
    def expect(
        oper: Qobj | Sequence[Qobj],
        state: Qobj | Sequence[Qobj]
    ) -> list[np.typing.NDArray[complex]]: ...

    def expect(oper, state):
        """
        Calculate the expectation value for operator(s) and state(s).  The
        expectation of state ``k`` on operator ``A`` is defined as
        ``k.dag() @ A @ k``, and for density matrix ``R`` on operator ``A`` it is
        ``trace(A @ R)``.

        Parameters
        ----------
        oper : qobj / list of Qobj
            A single or a `list` of operators for expectation value.

        state : qobj / list of Qobj
            A single or a `list` of quantum states or density matrices.

        Returns
        -------
        expt : float / complex / list / array
            Expectation value(s).  ``real`` if ``oper`` is Hermitian, ``complex``
            otherwise. If multiple ``oper`` are passed, a list of array.
            A (nested) array of expectaction values if ``state`` or
            ``oper`` are arrays.

        Examples
        --------
        >>> expect(num(4), basis(4, 3)) == 3 # doctest: +NORMALIZE_WHITESPACE
            True

        """
        if isinstance(state, Qobj) and isinstance(oper, Qobj):
            return _single_qobj_expect(oper, state)

        elif isinstance(oper, Sequence):
            return [expect(op, state) for op in oper]

        elif isinstance(state, Sequence):
            dtype = np.complex128
            if (
                oper.isherm
                and all(op.isherm or op.isket for op in state)
                and settings.core["auto_real_casting"]
            ):
                dtype = np.float64
            return np.array([_single_qobj_expect(oper, x) for x in state],
                            dtype=dtype)
        raise TypeError('Arguments must be quantum objects')

    def _single_qobj_expect(oper, state):
        """
        Private function used by expect to calculate expectation values of Qobjs.
        """
        if not oper.isoper or not (state.isket or state.isoper):
            raise TypeError('invalid operand types')
        if oper._dims[1] != state._dims[0]:
            msg = (
                "incompatible dimensions "
                + str(oper.dims[1]) + " and " + str(state.dims[0])
            )
            raise ValueError(msg)
        out = _data.expect(oper.data, state.data)

        # This ensures that expect can return something that is not a number such
        # as a `tensorflow.Tensor` in qutip-tensorflow.
        if (
            settings.core["auto_real_casting"]
            and oper.isherm
            and (state.isket or state.isherm)
        ):
            out = out.real
        return out

    @overload
    def variance(oper: Qobj, state: Qobj) -> complex: ...

    @overload
    def variance(oper: Qobj, state: list[Qobj]) -> np.typing.NDArray[complex]: ...

    def variance(oper, state):
        """
        Variance of an operator for the given state vector or density matrix.

        Parameters
        ----------
        oper : Qobj
            Operator for expectation value.

        state : Qobj / list of Qobj
            A single or ``list`` of quantum states or density matrices..

        Returns
        -------
        var : float
            Variance of operator 'oper' for given state.

        """
        return expect(oper**2, state) - expect(oper, state)**2