# Path: pyphysim/util/conversion.py
# def binary2gray(num: IntOrIntArray) -> IntOrIntArray:
#     """
#     Convert a number (in decimal format) to the corresponding Gray code
#     (still in decimal format).
# 
#     Parameters
#     ----------
#     num : int | np.ndarray
#         The number in decimal encoding
# 
#     Returns
#     -------
#     num_gray : int | np.ndarray
#         Corresponding gray code (in decimal format) of `num`.
# 
#     Examples
#     --------
#     >>> binary2gray(np.arange(0, 8))
#     array([0, 1, 3, 2, 6, 7, 5, 4])
#     """
#     return xor((num >> 1), num)
# 
# def dB2Linear(valueIndB: NumberOrArray) -> NumberOrArray:
#     """
#     Convert input from dB to linear scale.
# 
#     Parameters
#     ----------
#     valueIndB : int | float | np.ndarray
#         Value in dB
# 
#     Returns
#     -------
#     valueInLinear : int | float | np.ndarray
#         Value in Linear scale.
# 
#     Examples
#     --------
#     >>> dB2Linear(30)
#     1000.0
#     """
#     return pow(10, valueIndB / 10.0)
# 
# def gray2binary(num: IntOrIntArray) -> IntOrIntArray:
#     """
#     Convert a number in Gray code (in decimal format) to its original
#     value (in decimal format).
# 
#     Parameters
#     ----------
#     num : int | np.ndarray
#         The number in gray coding
# 
#     Returns
#     -------
#     num_orig : int | np.ndarray
#         The original number (in decimal format) whose Gray code
#         correspondent is `num`.
# 
#     Examples
#     --------
#     >>> gray2binary(binary2gray(np.arange(0,10)))
#     array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 
#     """
#     temp = xor(num, (num >> 8))
#     temp = xor(temp, (temp >> 4))
#     temp = xor(temp, (temp >> 2))
#     temp = xor(temp, (temp >> 1))
# 
#     return temp
# 
# Path: pyphysim/util/misc.py
# def level2bits(n: int) -> int:
#     """
#     Calculates the number of bits needed to represent n different
#     values.
# 
#     Parameters
#     ----------
#     n : int
#         Number of different levels.
# 
#     Returns
#     -------
#     num_bits : int
#         Number of bits required to represent `n` levels.
# 
#     Examples
#     --------
#     >>> list(map(level2bits,range(1,20)))
#     [1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5]
#     """
#     if n < 1:
#         raise ValueError("level2bits: n must be greater then one")
#     return int2bits(n - 1)
# 
# def qfunc(x: float) -> float:
#     """
#     Calculates the 'q' function of x.
# 
#     Parameters
#     ----------
#     x : float
#         The value to apply the Q function.
# 
#     Returns
#     -------
#     result : float
#         Qfunc(x)
# 
#     Examples
#     --------
#     >>> qfunc(0.0)
#     0.5
#     >>> round(qfunc(1.0), 9)
#     0.158655254
#     >>> round(qfunc(3.0), 9)
#     0.001349898
#     """
#     return cast(float, 0.5 * erfc(x / math.sqrt(2)))


    import matplotlib.pyplot as plt
import math
import numpy as np
from typing import Optional, TypeVar, Union
from pyphysim.util.conversion import binary2gray, dB2Linear, gray2binary
from pyphysim.util.misc import level2bits, qfunc
        As an example, for the 16-QAM modulation the indexes can be
        organized (row order) in the matrix below

        ====  ======  ======  ======  ======
         /      00      01      11      10
        ====  ======  ======  ======  ======
         00    0000    0001    0011    0010
         01    0100    0101    0111    0110
         11    1100    1101    1111    1110
         10    1000    1001    1011    1010
        ====  ======  ======  ======  ======

        This is equivalent to concatenate a Gray mapping for the row with a
        Gray mapping for the column, and the corresponding indexes are
        [0, 1, 3, 2, 4, 5, 7, 6, 12, 13, 15, 14, 8, 9, 11, 10]

        Parameters
        ----------
        L : int
            Square root of the modulation cardinality (must be an integer).

        Returns
        -------
        indexes : np.ndarray
            indexes that should be applied to the constellation created by
            _createConstellation in order to correspond to Gray mapping

        """
        # Row vector with the column variation (second half of the index in
        # binary form)

# Path: pyphysim/util/conversion.py
# def binary2gray(num: IntOrIntArray) -> IntOrIntArray:
#     """
#     Convert a number (in decimal format) to the corresponding Gray code
#     (still in decimal format).
# 
#     Parameters
#     ----------
#     num : int | np.ndarray
#         The number in decimal encoding
# 
#     Returns
#     -------
#     num_gray : int | np.ndarray
#         Corresponding gray code (in decimal format) of `num`.
# 
#     Examples
#     --------
#     >>> binary2gray(np.arange(0, 8))
#     array([0, 1, 3, 2, 6, 7, 5, 4])
#     """
#     return xor((num >> 1), num)
# 
# def dB2Linear(valueIndB: NumberOrArray) -> NumberOrArray:
#     """
#     Convert input from dB to linear scale.
# 
#     Parameters
#     ----------
#     valueIndB : int | float | np.ndarray
#         Value in dB
# 
#     Returns
#     -------
#     valueInLinear : int | float | np.ndarray
#         Value in Linear scale.
# 
#     Examples
#     --------
#     >>> dB2Linear(30)
#     1000.0
#     """
#     return pow(10, valueIndB / 10.0)
# 
# def gray2binary(num: IntOrIntArray) -> IntOrIntArray:
#     """
#     Convert a number in Gray code (in decimal format) to its original
#     value (in decimal format).
# 
#     Parameters
#     ----------
#     num : int | np.ndarray
#         The number in gray coding
# 
#     Returns
#     -------
#     num_orig : int | np.ndarray
#         The original number (in decimal format) whose Gray code
#         correspondent is `num`.
# 
#     Examples
#     --------
#     >>> gray2binary(binary2gray(np.arange(0,10)))
#     array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 
#     """
#     temp = xor(num, (num >> 8))
#     temp = xor(temp, (temp >> 4))
#     temp = xor(temp, (temp >> 2))
#     temp = xor(temp, (temp >> 1))
# 
#     return temp
# 
# Path: pyphysim/util/misc.py
# def level2bits(n: int) -> int:
#     """
#     Calculates the number of bits needed to represent n different
#     values.
# 
#     Parameters
#     ----------
#     n : int
#         Number of different levels.
# 
#     Returns
#     -------
#     num_bits : int
#         Number of bits required to represent `n` levels.
# 
#     Examples
#     --------
#     >>> list(map(level2bits,range(1,20)))
#     [1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5]
#     """
#     if n < 1:
#         raise ValueError("level2bits: n must be greater then one")
#     return int2bits(n - 1)
# 
# def qfunc(x: float) -> float:
#     """
#     Calculates the 'q' function of x.
# 
#     Parameters
#     ----------
#     x : float
#         The value to apply the Q function.
# 
#     Returns
#     -------
#     result : float
#         Qfunc(x)
# 
#     Examples
#     --------
#     >>> qfunc(0.0)
#     0.5
#     >>> round(qfunc(1.0), 9)
#     0.158655254
#     >>> round(qfunc(3.0), 9)
#     0.001349898
#     """
#     return cast(float, 0.5 * erfc(x / math.sqrt(2)))


# Path: pyphysim/modulators/fundamental.py
    import matplotlib.pyplot as plt
import math
import numpy as np
from typing import Optional, TypeVar, Union
from pyphysim.util.conversion import binary2gray, dB2Linear, gray2binary
from pyphysim.util.misc import level2bits, qfunc

        As an example, for the 16-QAM modulation the indexes can be
        organized (row order) in the matrix below

        ====  ======  ======  ======  ======
         /      00      01      11      10
        ====  ======  ======  ======  ======
         00    0000    0001    0011    0010
         01    0100    0101    0111    0110
         11    1100    1101    1111    1110
         10    1000    1001    1011    1010
        ====  ======  ======  ======  ======

        This is equivalent to concatenate a Gray mapping for the row with a
        Gray mapping for the column, and the corresponding indexes are
        [0, 1, 3, 2, 4, 5, 7, 6, 12, 13, 15, 14, 8, 9, 11, 10]

        Parameters
        ----------
        L : int
            Square root of the modulation cardinality (must be an integer).

        Returns
        -------
        indexes : np.ndarray
            indexes that should be applied to the constellation created by
            _createConstellation in order to correspond to Gray mapping

        """
        # Row vector with the column variation (second half of the index in
        # binary form)

        column = binary2gray(np.arange(0, L, dtype=int))