Python - QAM

Table of Contents

Introduction

In order to structure the utility relating to constellation modulation, I wrote a class Quadrature_Amplitude_Modulation in Python.

Source code

import numpy as np
import matplotlib.pyplot as plt

class Quadrature_Amplitude_Modulation:
    def __init__(self, mod_order: int) -> None:
        """! Constructor for quadrature amplitude modulation (QAM)
        @param mod_order Modulation order, e.g., 4 for QPSK.
        """
        self.mod_order = mod_order
        self.bit_num_sym = int(np.log2(mod_order))
        m = np.sqrt(mod_order)
        v = np.linspace(1 - m, m - 1, int(m))
        x, y = np.meshgrid(v, v)
        self.const = np.reshape(x + 1j * y, -1) / np.sqrt((mod_order - 1) * 2 / 3)
        self.name = "QPSK" if mod_order == 4 else f"{mod_order}QAM"
    def calc_mi(self, sinr: float, sample_num: int=1000) -> float:
        """! Calculate the per-symbol mutual information, a.k.a. RBIR, for the specified SINR.
        @param sinr SINR in dB
        @param sample_num Number of random samples
        @return Per-bit information
        """
        n_pwr = np.power(10, -0.1 * sinr)
        n = np.random.randn(sample_num) + 1j * np.random.randn(sample_num)
        n *= np.sqrt(n_pwr / 2)
        si = np.zeros((self.mod_order, sample_num))
        for sym_idx in range(self.mod_order):
            for sample_idx in range(sample_num):
                x = np.power(np.abs(self.const - self.const[sym_idx] + n[sample_idx]), 2) - np.power(np.abs(n[sample_idx]), 2)
                si[sym_idx][sample_idx] = np.sum(np.exp(-1 * x / n_pwr))
        return self.bit_num_sym - np.log2(si).mean()
    def visualize(self, ax: plt.Axes = None, **kwargs) -> None:
        """! Visualize the constellation
        @param ax An axis object
        @param kwargs Keyword argument for further customization
        """
        if ax is None:
            _, ax = plt.subplots()
        ax.scatter(self.const.real, self.const.imag, **kwargs)
        ax.set_xlabel('In-phase')
        ax.set_ylabel('Quadrature')
        ax.set_title(self.name)
        ax.set_aspect('equal')
        ax.grid(True)