Skip to content

komm.Modulation

General modulation scheme. A modulation scheme of order $M = 2^m$ is defined by a constellation $\mathbf{X}$, which is a real or complex vector of length $M$, and a binary labeling $\mathbf{Q}$, which is an $M \times m$ binary matrix whose rows are all distinct. The $i$-th element of $\mathbf{X}$, for $i \in [0:M)$, is denoted by $x_i$ and is called the $i$-th constellation symbol. The $i$-th row of $\mathbf{Q}$, for $i \in [0:M)$, is called the binary representation of the $i$-th constellation symbol. For more details, see SA15, Sec. 2.5.

Parameters:

  • constellation (ArrayLike)

    The constellation $\mathbf{X}$ of the modulation. Must be a 1D-array containing $M$ real or complex numbers.

  • labeling (ArrayLike)

    The binary labeling $\mathbf{Q}$ of the modulation. Must be a 2D-array of shape $(M, m)$ where each row is a distinct binary $m$-tuple.

Examples:

The real modulation scheme depicted in the figure below has $M = 4$ and $m = 2$.

Example for real modulation with M = 4

The constellation is given by $$ \mathbf{X} = \begin{bmatrix} -0.5 \\ 0.0 \\ 0.5 \\ 2.0 \end{bmatrix}, $$ and the binary labeling is given by $$ \mathbf{Q} = \begin{bmatrix} 1 & 0 \\ 1 & 1 \\ 0 & 1 \\ 0 & 0 \end{bmatrix}. $$

>>> modulation = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> modulation.constellation
array([-0.5,  0. ,  0.5,  2. ])
>>> modulation.labeling
array([[1, 0],
       [1, 1],
       [0, 1],
       [0, 0]])

The complex modulation scheme depicted in the figure below has $M = 4$ and $m = 2$.

Example for complex modulation with M = 4

The constellation is given by $$ \mathbf{X} = \begin{bmatrix} 0 \\ -1 \\ 1 \\ \mathrm{j} \end{bmatrix}, $$ and the binary labeling is given by $$ \mathbf{Q} = \begin{bmatrix} 0 & 0 \\ 0 & 1 \\ 1 & 0 \\ 1 & 1 \end{bmatrix}. $$

>>> modulation = komm.Modulation(constellation=[0, -1, 1, 1j], labeling=[[0, 0], [0, 1], [1, 0], [1, 1]])
>>> modulation.constellation
array([ 0.+0.j, -1.+0.j,  1.+0.j,  0.+1.j])
>>> modulation.labeling
array([[0, 0],
       [0, 1],
       [1, 0],
       [1, 1]])

order: int property

The order $M$ of the modulation.

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.order
4

inverse_labeling: dict[tuple[int, ...], int] property

The inverse labeling of the modulation. It is a dictionary that maps each binary tuple to the corresponding constellation index.

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.inverse_labeling
{(1, 0): 0, (1, 1): 1, (0, 1): 2, (0, 0): 3}

bits_per_symbol: int property

The number $m$ of bits per symbol of the modulation. It is given by $m = \log_2 M$, where $M$ is the order of the modulation.

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.bits_per_symbol
2

energy_per_symbol: float property

The average symbol energy $E_\mathrm{s}$ of the constellation. It assumes equiprobable symbols. It is given by $$ E_\mathrm{s} = \frac{1}{M} \sum_{i \in [0:M)} |x_i|^2, $$ where $|x_i|^2$ is the energy of constellation symbol $x_i$, and $M$ is the order of the modulation.

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.energy_per_symbol
np.float64(1.125)
>>> mod = komm.Modulation(constellation=[0, -1, 1, 1j], labeling=[[0, 0], [0, 1], [1, 0], [1, 1]])
>>> mod.energy_per_symbol
np.float64(0.75)

energy_per_bit: float property

The average bit energy $E_\mathrm{b}$ of the constellation. It assumes equiprobable symbols. It is given by $E_\mathrm{b} = E_\mathrm{s} / m$, where $E_\mathrm{s}$ is the average symbol energy, and $m$ is the number of bits per symbol of the modulation.

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.energy_per_bit
np.float64(0.5625)
>>> mod = komm.Modulation(constellation=[0, -1, 1, 1j], labeling=[[0, 0], [0, 1], [1, 0], [1, 1]])
>>> mod.energy_per_bit
np.float64(0.375)

symbol_mean: float | complex property

The mean $\mu_\mathrm{s}$ of the constellation. It assumes equiprobable symbols. It is given by $$ \mu_\mathrm{s} = \frac{1}{M} \sum_{i \in [0:M)} x_i. $$

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.symbol_mean
np.float64(0.5)
>>> mod = komm.Modulation(constellation=[0, -1, 1, 1j], labeling=[[0, 0], [0, 1], [1, 0], [1, 1]])
>>> mod.symbol_mean
np.complex128(0.25j)

minimum_distance: float property

The minimum Euclidean distance $d_\mathrm{min}$ of the constellation. It is given by $$ d_\mathrm{min} = \min_{i, j \in [0:M), ~ i \neq j} |x_i - x_j|. $$

Examples:

>>> mod = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> mod.minimum_distance
np.float64(0.5)
>>> mod = komm.Modulation(constellation=[0, -1, 1, 1j], labeling=[[0, 0], [0, 1], [1, 0], [1, 1]])
>>> mod.minimum_distance
np.float64(1.0)

modulate

Modulates one or more sequences of bits to their corresponding constellation symbols (real or complex numbers).

Parameters:

  • input (ArrayLike)

    The input sequence(s). Can be either a single sequence whose length is a multiple of $m$, or a multidimensional array where the last dimension is a multiple of $m$.

Returns:

  • output (NDArray[floating | complexfloating])

    The output sequence(s). Has the same shape as the input, with the last dimension divided by $m$.

Examples:

>>> modulation = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> modulation.modulate([0, 0, 1, 1, 0, 0, 1, 0])
array([ 2. ,  0. ,  2. , -0.5])
>>> modulation.modulate([[0, 0, 1, 1], [0, 0, 1, 0]])
array([[ 2. ,  0. ],
       [ 2. , -0.5]])

demodulate_hard

Demodulates one or more sequences of received points (real or complex numbers) to their corresponding sequences of hard bits ($\mathtt{0}$ or $\mathtt{1}$) using hard-decision decoding.

Parameters:

  • input (ArrayLike)

    The input sequence(s). Can be either a single sequence, or a multidimensional array.

Returns:

  • output (NDArray[integer])

    The output sequence(s). Has the same shape as the input, with the last dimension multiplied by $m$.

Examples:

>>> modulation = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> modulation.demodulate_hard([2.17, -0.06, 1.94, -0.61])
array([0, 0, 1, 1, 0, 0, 1, 0])
>>> modulation.demodulate_hard([[2.17, -0.06], [1.94, -0.61]])
array([[0, 0, 1, 1],
       [0, 0, 1, 0]])

demodulate_soft

Demodulates one or more sequences of received points (real or complex numbers) to their corresponding sequences of soft bits (L-values) using soft-decision decoding. The soft bits are the log-likelihood ratios of the bits, where positive values correspond to bit $\mathtt{0}$ and negative values correspond to bit $\mathtt{1}$.

Parameters:

  • input (ArrayLike)

    The received sequence(s). Can be either a single sequence, or a multidimensional array.

  • snr (float)

    The signal-to-noise ratio (SNR) of the channel. It should be a positive real number. The default value is 1.0.

Returns:

  • output (NDArray[floating])

    The output sequence(s). Has the same shape as the input, with the last dimension multiplied by $m$.

Examples:

>>> modulation = komm.Modulation(constellation=[-0.5, 0.0, 0.5, 2.0], labeling=[[1, 0], [1, 1], [0, 1], [0, 0]])
>>> modulation.demodulate_soft([2.17, -0.06, 1.94, -0.61], snr=100.0).round(1)
array([ 416. ,  245.3,  -27.6,  -16.9,  334.2,  184. , -108.4,   32. ])
>>> modulation.demodulate_soft([[2.17, -0.06], [1.94, -0.61]], snr=100.0).round(1)
array([[ 416. ,  245.3,  -27.6,  -16.9],
       [ 334.2,  184. , -108.4,   32. ]])