- class sdr.PSK(sdr.LinearModulation)
Implements phase-shift keying (PSK) modulation and demodulation.
Notes¶
Phase-shift keying (PSK) is a linear phase modulation scheme that encodes information by modulating the phase of a carrier sinusoid. The modulation order \(M = 2^k\) is a power of 2 and indicates the number of phases used. 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
\[a[k] = \exp \left[ j\left(\frac{2\pi}{M}s[k] + \phi\right) \right] .\]Note
The nomenclature for variable names in linear modulators is as follows: \(s[k]\) are decimal symbols, \(\hat{s}[k]\) are decimal symbol decisions, \(a[k]\) are complex symbols, \(\tilde{a}[k]\) are received complex symbols, \(\hat{a}[k]\) are complex symbol decisions, \(x[n]\) are pulse-shaped complex samples, and \(\tilde{x}[n]\) are received pulse-shaped complex samples. \(k\) indicates a symbol index and \(n\) indicates a sample index.
Examples¶
Create a QPSK modem whose constellation has a 45° phase offset.
In [1]: qpsk = sdr.PSK(4, phase_offset=45, pulse_shape="srrc"); qpsk Out[1]: sdr.PSK(4, phase_offset=45, symbol_labels='gray') In [2]: plt.figure(); \ ...: sdr.plot.symbol_map(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, qpsk.bps); symbols[0:4] Out[4]: array([1, 2, 3, 3], dtype=uint8) In [5]: complex_symbols = qpsk.map_symbols(symbols); complex_symbols[0:4] Out[5]: array([-0.70710678+0.70710678j, 0.70710678-0.70710678j, -0.70710678-0.70710678j, -0.70710678-0.70710678j]) In [6]: plt.figure(); \ ...: sdr.plot.constellation(complex_symbols, linestyle="-"); ...:
Modulate and pulse shape the symbols to a complex baseband signal.
In [7]: tx_samples = qpsk.modulate(symbols) In [8]: plt.figure(); \ ...: sdr.plot.time_domain(tx_samples[0:50*qpsk.sps]); ...:
Examine the eye diagram of the pulse-shaped transmitted signal. The SRRC pulse shape is not a Nyquist filter, so ISI is present.
In [9]: plt.figure(figsize=(8, 6)); \ ...: sdr.plot.eye(tx_samples[5*qpsk.sps : -5*qpsk.sps], qpsk.sps, persistence=True); \ ...: plt.suptitle("Noiseless transmitted signal with ISI"); ...:
Add AWGN noise such that \(E_b/N_0 = 30\) dB.
In [10]: ebn0 = 30; \ ....: snr = sdr.ebn0_to_snr(ebn0, bps=qpsk.bps, sps=qpsk.sps); \ ....: rx_samples = sdr.awgn(tx_samples, snr=snr) ....: In [11]: plt.figure(); \ ....: sdr.plot.time_domain(rx_samples[0:50*qpsk.sps]); ....:
Manually apply a matched filter. Examine the eye diagram of the matched filtered received signal. The two cascaded SRRC filters create a Nyquist RC filter. Therefore, the ISI is removed.
In [12]: mf = sdr.FIR(qpsk.pulse_shape); \ ....: mf_samples = mf(rx_samples) ....: In [13]: plt.figure(figsize=(8, 6)); \ ....: sdr.plot.eye(mf_samples[10*qpsk.sps : -10*qpsk.sps], qpsk.sps, persistence=True); \ ....: plt.suptitle("Noisy received and matched filtered signal without ISI"); ....:
Matched filter and demodulate.
In [14]: rx_symbols, rx_complex_symbols, _ = qpsk.demodulate(rx_samples) # The symbol decisions are error-free In [15]: np.array_equal(symbols, rx_symbols) Out[15]: True In [16]: plt.figure(); \ ....: sdr.plot.constellation(rx_complex_symbols); ....:
See the Phase-shift keying example.
Constructors¶
-
PSK(order: int, phase_offset: float =
0.0
, ...) Creates a new PSK object.
Methods¶
-
ber(ebn0: ArrayLike, diff_encoded: bool =
False
) NDArray[float64] Computes the bit error rate (BER) at the provided \(E_b/N_0\) values.
-
ser(esn0: ArrayLike, diff_encoded: bool =
False
) NDArray[float64] Computes the symbol error rate (SER) at the provided \(E_s/N_0\) values.
- map_symbols(s: ArrayLike) NDArray[complex128]
Converts the decimal symbols into complex symbols.
- decide_symbols(a_tilde) tuple[NDArray[int_], NDArray[complex128]]
Converts the received complex symbols into MLE symbol decisions.
- modulate(s: ArrayLike) NDArray[complex128]
Modulates the decimal symbols into pulse-shaped complex samples.
- demodulate(...) tuple[NDArray[int_], NDArray[complex128], NDArray[complex128]]
Demodulates the pulse-shaped complex samples.
Properties¶
- property phase_offset : float
The phase offset \(\phi\) in degrees.
- property symbol_map : NDArray[np.complex128]
The symbol map \(\{0, \dots, M-1\} \mapsto \mathbb{C}\).
- property pulse_shape : NDArray[np.float64]
The pulse shape \(h[n]\) of the modulated signal.
- property tx_filter : Interpolator
The transmit interpolating pulse shaping filter.
-
PSK(order: int, phase_offset: float =