"""Generates a random positive semidefinite operator."""

import numpy as np


def random_psd_operator(
    dim: int,
    is_real: bool = False,
    seed: int | None = None,
) -> np.ndarray:
    r"""Generate a random positive semidefinite operator.

    A positive semidefinite operator is a Hermitian operator that has only real and non-negative eigenvalues.
    This function generates a random positive semidefinite operator by constructing a Hermitian matrix,
    based on the fact that a Hermitian matrix can have real eigenvalues.

    Examples:
        Using `|toqito⟩`, we may generate a random positive semidefinite matrix.
        For \(\text{dim}=2\), this can be accomplished as follows.

        ```python exec="1" source="above" session="psd_operator"
        from toqito.rand import random_psd_operator

        complex_psd_mat = random_psd_operator(2)

        print(complex_psd_mat)
        ```

        We can confirm that this matrix indeed represents a valid positive semidefinite matrix by utilizing
        the `is_positive_semidefinite` function from the `|toqito⟩` library, as demonstrated below:

        ```python exec="1" source="above" session="psd_operator"
        from toqito.matrix_props import is_positive_semidefinite

        print(is_positive_semidefinite(complex_psd_mat))
        ```


        We can also generate random positive semidefinite matrices that are real-valued as follows.

        ```python exec="1" source="above" session="psd_operator"
        from toqito.rand import random_psd_operator

        real_psd_mat = random_psd_operator(2, is_real=True)

        print(real_psd_mat)
        ```


        Again, verifying that this is a valid positive semidefinite matrix can be done as follows.

        ```python exec="1" source="above" session="psd_operator"
        from toqito.matrix_props import is_positive_semidefinite
        print(is_positive_semidefinite(real_psd_mat))
        ```


        It is also possible to add a seed for reproducibility.

        ```python exec="1" source="above" session="psd_operator"
        from toqito.rand import random_psd_operator

        seeded = random_psd_operator(2, is_real=True, seed=42)

        print(seeded)
        ```

    Args:
        dim: The dimension of the operator.
        is_real: Boolean denoting whether the returned matrix will have all real entries or not. Default is `False`.
        seed: A seed used to instantiate numpy's random number generator.

    Returns:
        A `dim` x `dim` random positive semidefinite matrix.

    """
    # Generate a random matrix of dimension dim x dim.
    gen = np.random.default_rng(seed=seed)
    rand_mat = gen.random((dim, dim))

    # If is_real is False, add an imaginary component to the matrix.
    if not is_real:
        rand_mat = rand_mat + 1j * gen.random((dim, dim))

    # Constructing a Hermitian matrix.
    rand_mat = (rand_mat.conj().T + rand_mat) / 2
    eigenvals, eigenvecs = np.linalg.eigh(rand_mat)

    Q, R = np.linalg.qr(eigenvecs)

    return Q @ np.diag(np.abs(eigenvals)) @ Q.conj().T
