# Path: pyphysim/util/conversion.py
# def linear2dB(valueInLinear: NumberOrArray) -> NumberOrArray:
#     """
#     Convert input from linear to dB scale.
# 
#     Parameters
#     ----------
#     valueInLinear : int | float | np.ndarray
#         Value in Linear scale.
# 
#     Returns
#     -------
#     valueIndB : int | float | np.ndarray
#         Value in dB scale.
# 
#     Examples
#     --------
#     >>> linear2dB(1000)
#     30.0
#     """
#     return 10.0 * np.log10(valueInLinear)  # type: ignore
# 
# Path: pyphysim/util/misc.py
# def leig(A: np.ndarray, n: int) -> Tuple[np.ndarray, np.ndarray]:
#     """
#     Returns a matrix whose columns are the `n` least significant
#     eigenvectors of `A` (eigenvectors corresponding to the `n` dominant
#     eigenvalues).
# 
#     Parameters
#     ----------
#     A : np.ndarray
#         A symmetric matrix (bi-dimensional numpy array)
#     n : int
#         Number of desired least significant eigenvectors.
# 
#     Returns
#     -------
#     np.ndarray,np.ndarray
#         A list with two elements where the first element is a 2D numpy
#         array with the desired eigenvectors, while the second element is a
#         1D numpy array with the corresponding eigenvalues.
# 
#     Notes
#     -----
#     `A` must be a symmetric matrix so that its eigenvalues are real and
#     positive.
# 
#     Raises
#     ------
#     ValueError
#         If `n` is greater than the number of columns of `A`.
# 
#     Examples
#     --------
#     >>> A = np.random.randn(3,3) + 1j*np.random.randn(3,3)
#     >>> V, D = peig(A, 1)
# 
#     """
#     (_, ncols) = A.shape
#     if n > ncols:  # A is symmetric -> we could get either nrows or ncols
#         raise ValueError("`n` must be lower then the number of columns "
#                          "in `A`")
# 
#     [D, V] = np.linalg.eig(A)
#     indexes = np.argsort(D.real)
#     V = V[:, indexes[0:n]]
#     D = D[indexes[0:n]]
#     return V, D
# 
# def randn_c_RS(RS: np.random.RandomState,
#                *args: int) -> np.ndarray:  # pragma: no cover
#     """
#     Generates a random circularly complex gaussian matrix.
# 
#     This is essentially the same as the the randn_c function. The only
#     difference is that the randn_c function uses the global RandomState
#     object in numpy, while randn_c_RS use the provided RandomState
#     object. This allow us greater control.
# 
#     Parameters
#     ----------
#     RS : np.random.RandomState
#         The RandomState object used to generate the random values.
#     *args : any
#         Variable number of arguments specifying the dimensions of the
#         returned array. This is directly passed to the
#         numpy.random.randn function.
# 
#     Returns
#     -------
#     result : np.ndarray
#         A random N-dimensional numpy array (complex dtype) where the
#         `N` is equal to the number of parameters passed to `randn_c`.
# 
#     """
#     if RS is None:
#         # noinspection PyArgumentList
#         return randn_c(*args)
# 
#     # noinspection PyArgumentList
#     return (1.0 / math.sqrt(2.0)) * (RS.randn(*args) + (1j * RS.randn(*args)))


from abc import ABCMeta, abstractmethod
from typing import Any, List, Optional, Sequence, TypeVar, Union, cast
from ..util.conversion import linear2dB
from ..util.misc import leig, randn_c_RS
import numpy as np
import pyphysim.channels.multiuser as muchannels

    def randomizeF(self,
                   Ns: Union[int, List[int], Sequence[int]],
                   P: Optional[np.ndarray] = None) -> None:
        """
        Generates a random precoder for each user.

        Parameters
        ----------
        Ns : int | list[int] | np.ndarray
            Number of streams of each user.
        P : np.ndarray, optional
            Power of each user. If not provided, a value of 1 will be used
            for each user.
        """
        self._clear_precoder_filter()

        if isinstance(Ns, int):
            Ns = np.ones(self.K, dtype=int) * Ns
        assert (not isinstance(Ns, int))

        self.P = P

        # Local function that returns a normalized version of the input
        # numpy array
        def normalized(A: np.ndarray) -> np.ndarray:
            return A / np.linalg.norm(A, 'fro')

        self._F = np.zeros(self.K, dtype=np.ndarray)
        for k in range(self.K):

# Path: pyphysim/util/conversion.py
# def linear2dB(valueInLinear: NumberOrArray) -> NumberOrArray:
#     """
#     Convert input from linear to dB scale.
# 
#     Parameters
#     ----------
#     valueInLinear : int | float | np.ndarray
#         Value in Linear scale.
# 
#     Returns
#     -------
#     valueIndB : int | float | np.ndarray
#         Value in dB scale.
# 
#     Examples
#     --------
#     >>> linear2dB(1000)
#     30.0
#     """
#     return 10.0 * np.log10(valueInLinear)  # type: ignore
# 
# Path: pyphysim/util/misc.py
# def leig(A: np.ndarray, n: int) -> Tuple[np.ndarray, np.ndarray]:
#     """
#     Returns a matrix whose columns are the `n` least significant
#     eigenvectors of `A` (eigenvectors corresponding to the `n` dominant
#     eigenvalues).
# 
#     Parameters
#     ----------
#     A : np.ndarray
#         A symmetric matrix (bi-dimensional numpy array)
#     n : int
#         Number of desired least significant eigenvectors.
# 
#     Returns
#     -------
#     np.ndarray,np.ndarray
#         A list with two elements where the first element is a 2D numpy
#         array with the desired eigenvectors, while the second element is a
#         1D numpy array with the corresponding eigenvalues.
# 
#     Notes
#     -----
#     `A` must be a symmetric matrix so that its eigenvalues are real and
#     positive.
# 
#     Raises
#     ------
#     ValueError
#         If `n` is greater than the number of columns of `A`.
# 
#     Examples
#     --------
#     >>> A = np.random.randn(3,3) + 1j*np.random.randn(3,3)
#     >>> V, D = peig(A, 1)
# 
#     """
#     (_, ncols) = A.shape
#     if n > ncols:  # A is symmetric -> we could get either nrows or ncols
#         raise ValueError("`n` must be lower then the number of columns "
#                          "in `A`")
# 
#     [D, V] = np.linalg.eig(A)
#     indexes = np.argsort(D.real)
#     V = V[:, indexes[0:n]]
#     D = D[indexes[0:n]]
#     return V, D
# 
# def randn_c_RS(RS: np.random.RandomState,
#                *args: int) -> np.ndarray:  # pragma: no cover
#     """
#     Generates a random circularly complex gaussian matrix.
# 
#     This is essentially the same as the the randn_c function. The only
#     difference is that the randn_c function uses the global RandomState
#     object in numpy, while randn_c_RS use the provided RandomState
#     object. This allow us greater control.
# 
#     Parameters
#     ----------
#     RS : np.random.RandomState
#         The RandomState object used to generate the random values.
#     *args : any
#         Variable number of arguments specifying the dimensions of the
#         returned array. This is directly passed to the
#         numpy.random.randn function.
# 
#     Returns
#     -------
#     result : np.ndarray
#         A random N-dimensional numpy array (complex dtype) where the
#         `N` is equal to the number of parameters passed to `randn_c`.
# 
#     """
#     if RS is None:
#         # noinspection PyArgumentList
#         return randn_c(*args)
# 
#     # noinspection PyArgumentList
#     return (1.0 / math.sqrt(2.0)) * (RS.randn(*args) + (1j * RS.randn(*args)))


# Path: pyphysim/ia/iabase.py
from abc import ABCMeta, abstractmethod
from typing import Any, List, Optional, Sequence, TypeVar, Union, cast
from ..util.conversion import linear2dB
from ..util.misc import leig, randn_c_RS
import numpy as np
import pyphysim.channels.multiuser as muchannels


    def randomizeF(self,
                   Ns: Union[int, List[int], Sequence[int]],
                   P: Optional[np.ndarray] = None) -> None:
        """
        Generates a random precoder for each user.

        Parameters
        ----------
        Ns : int | list[int] | np.ndarray
            Number of streams of each user.
        P : np.ndarray, optional
            Power of each user. If not provided, a value of 1 will be used
            for each user.
        """
        self._clear_precoder_filter()

        if isinstance(Ns, int):
            Ns = np.ones(self.K, dtype=int) * Ns
        assert (not isinstance(Ns, int))

        self.P = P

        # Local function that returns a normalized version of the input
        # numpy array
        def normalized(A: np.ndarray) -> np.ndarray:
            return A / np.linalg.norm(A, 'fro')

        self._F = np.zeros(self.K, dtype=np.ndarray)
        for k in range(self.K):

            self._F[k] = normalized(randn_c_RS(self._rs, self.Nt[k], Ns[k]))