- class sdr.PiMPSK(sdr.PSK)
Implements \(\pi/M\) phase-shift keying (\(\pi/M\) PSK) modulation and demodulation.
Notes¶
\(\pi/M\) M-PSK is a linear phase modulation scheme similar to conventional M-PSK. One key distinction is that in \(\pi/M\) M-PSK, the odd symbols are rotated by \(\pi/M\) radians relative to the even symbols. This prevents symbol transitions through the origin, which results in a lower peak-to-average power ratio (PAPR).
The modulation order \(M = 2^k\) is a power of 2 and indicates the number of phases used \(2M\). The input bit stream is taken \(k\) bits at a time to create decimal symbols \(s[k] \in \{0, \dots, M-1\}\). These decimal symbols \(s[k]\) are then mapped to complex symbols \(a[k] \in \mathbb{C}\) by the equation
\[\begin{split}a[k] = \begin{cases} \displaystyle \exp \left[ j\left(\frac{2\pi}{M}s[k] + \phi\right) \right] & k\ \text{even} \\ \displaystyle \exp \left[ j\left(\frac{2\pi}{M}s[k] + \phi + \pi/M\right) \right] & k\ \text{odd} \\ \end{cases} \end{split}\]Examples¶
Create a \(\pi/4\) QPSK modem.
In [1]: pi4_qpsk = sdr.PiMPSK(4, pulse_shape="srrc"); pi4_qpsk Out[1]: sdr.PiMPSK(4, phase_offset=0.0, symbol_labels='gray') In [2]: plt.figure(figsize=(8, 4)); \ ...: sdr.plot.symbol_map(pi4_qpsk); ...:
Generate a random bit stream, convert to 2-bit symbols, and map to complex symbols.
In [3]: bits = np.random.randint(0, 2, 1000); bits[0:8] Out[3]: array([0, 1, 1, 0, 1, 1, 1, 1]) In [4]: symbols = sdr.pack(bits, pi4_qpsk.bps); symbols[0:4] Out[4]: array([1, 2, 3, 3], dtype=uint8) In [5]: complex_symbols = pi4_qpsk.map_symbols(symbols); complex_symbols[0:4] Out[5]: array([ 6.12323400e-17+1.00000000e+00j, 7.07106781e-01-7.07106781e-01j, -1.00000000e+00+1.22464680e-16j, -7.07106781e-01-7.07106781e-01j]) In [6]: plt.figure(figsize=(8, 4)); \ ...: sdr.plot.constellation(complex_symbols, linestyle="-"); ...:
Modulate and pulse shape the symbols to a complex baseband signal.
In [7]: tx_samples = pi4_qpsk.modulate(symbols) In [8]: plt.figure(figsize=(8, 4)); \ ...: sdr.plot.time_domain(tx_samples[0:50*pi4_qpsk.sps], sample_rate=pi4_qpsk.sps); ...: In [9]: plt.figure(figsize=(8, 4)); \ ...: sdr.plot.eye(tx_samples[5*pi4_qpsk.sps : -5*pi4_qpsk.sps], pi4_qpsk.sps); ...:
Add AWGN noise such that \(E_b/N_0 = 20\) dB.
In [10]: ebn0 = 20; \ ....: snr = sdr.ebn0_to_snr(ebn0, bps=pi4_qpsk.bps, sps=pi4_qpsk.sps); \ ....: rx_samples = sdr.awgn(tx_samples, snr=snr) ....: In [11]: plt.figure(figsize=(8, 4)); \ ....: sdr.plot.time_domain(rx_samples[0:50*pi4_qpsk.sps], sample_rate=pi4_qpsk.sps); ....:
Matched filter and demodulate.
In [12]: rx_symbols, rx_complex_symbols = pi4_qpsk.demodulate(rx_samples) # The symbol decisions are error-free In [13]: np.array_equal(symbols, rx_symbols) Out[13]: True In [14]: plt.figure(figsize=(8, 4)); \ ....: sdr.plot.constellation(rx_complex_symbols); ....:
See the Phase-shift keying example.
Constructors¶
-
PiMPSK(order: int, phase_offset: float =
0.0
, ...) Creates a new \(\pi/M\) PSK object.
String representation¶
Methods¶
-
ber(ebn0: ArrayLike, diff_encoded: bool =
False
) NDArray[float_] Computes the bit error rate (BER) at the provided \(E_b/N_0\) values.
-
ser(esn0: ArrayLike, diff_encoded: bool =
False
) NDArray[float_] Computes the symbol error rate (SER) at the provided \(E_s/N_0\) values.
- map_symbols(s: ArrayLike) NDArray[complex_]
Converts the decimal symbols \(s[k]\) to complex symbols \(a[k]\).
- decide_symbols(a_hat: ArrayLike) NDArray[int_]
Converts the received complex symbols \(\hat{a}[k]\) into decimal symbol decisions \(\hat{s}[k]\) using maximum-likelihood estimation (MLE).
- modulate(s: ArrayLike) NDArray[complex_]
Modulates the decimal symbols \(s[k]\) into pulse-shaped complex samples \(x[n]\).
- demodulate(x_hat) tuple[NDArray[int_], NDArray[complex_]]
Demodulates the pulse-shaped complex samples \(\hat{x}[n]\) into decimal symbol decisions \(\hat{s}[k]\) using matched filtering and maximum-likelihood estimation.
Properties¶
- property phase_offset : float
The phase offset \(\phi\) in degrees.
- property symbol_map : NDArray[np.complex_]
The symbol map \(\{0, \dots, M-1\} \mapsto \mathbb{C}\). This maps decimal symbols from \(0\) to \(M-1\) to complex symbols.
- property pulse_shape : NDArray[np.float_]
The pulse shape \(h[n]\) of the modulated signal.
- property tx_filter : Interpolator
The transmit interpolating pulse shaping filter. The filter coefficients are the pulse shape \(h[n]\).
-
PiMPSK(order: int, phase_offset: float =