Python - QAM

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)
Table of Contents