comnum.CeOfdm#

class comnum.CeOfdm(n_subcarriers, h, *, oversampling=4, n_cp=0)#

A constant-envelope OFDM (CE-OFDM) modulator and phase demodulator (Thompson et al. 2008).

After Thompson, Ahmed, Proakis, Zeidler & Geile, “Constant envelope OFDM,” IEEE Trans. Commun. 56(8), 2008.

A block carries K data symbols d (unit mean energy) on the subcarriers 1..K of a DFT of size N = 2J(K+1), J the oversampling factor; the real OFDM signal x = (F d + F* d*)/√(2K) has a unit variance and the samples are s_n = exp(j2πh x_n), so that the phase has the standard deviation 2πh, h being the modulation index (Sec. II). An optional cyclic prefix of N_cp samples is prepended. The receiver (Sec. III, Fig. 1) takes the phase of the samples, unwrapped along the stream or not, removes the prefix and projects it on the data bins: d̂ = G ψ/(2πh).

SNR convention. E_s/N_0 is per data symbol: a block of energy N + N_cp carries K symbols, so the noise variance per sample is σ² = (N + N_cp)/(K E_s/N_0) (noise_variance(); sps = (N + N_cp)/K samples per data symbol). Above the threshold of the phase demodulator (a carrier-to-noise ratio near 10 dB, Sec. III-B), every data subcarrier sees the SNR (2πh)² E_s/N_0 N/(N + N_cp) (snr()), and the error rates are those of the constellation over AWGN at that SNR (ser_esn0(), ber_esn0()). For M-PAM on every real subcarrier this is eq. (15) of the paper, BER = 2(M-1)/(M log₂M) Q(2πh √(6 log₂M E_b/N_0 / (M²-1))), the nearest-neighbour term of the Gray BER; ber_esn0() uses the exact Gray BER of the square QAM of order M² whose real and imaginary parts are the two PAM symbols (within 1 % of eq. (15) once the BER is under 1e-2).

Parameters:
  • n_subcarriers (int) – data subcarriers K per block.

  • h (float) – modulation index (phase standard deviation 2πh).

  • oversampling (int) – J, the DFT has N = 2J(K+1) points.

  • n_cp (int) – cyclic prefix (samples).

Raises:

ValueError – for an invalid parameter (the message names it).

Examples

>>> import comnum, numpy as np
>>> ce = comnum.CeOfdm(16, h=0.1, oversampling=2)
>>> c = comnum.Constellation("qam", 16).normalized()
>>> _, d = comnum.symbols(c, 64, comnum.Rng(1))
>>> s = ce.modulate(d)
>>> s.size == 4 * ce.n_fft and np.allclose(np.abs(s), 1.0)
True
>>> np.allclose(ce.demodulate(s), d)
True
ber_esn0(m, esn0_db, kind='qam')#

The BER of the linear regime, Gray mapping: the exact Gray BER of a square QAM (eq. (15) of Thompson et al. with its further neighbours), else theory_ber().

Parameters:
  • m (int) – constellation order.

  • esn0_db (float | array_like) – E_s/N_0 (dB) per data symbol.

  • kind (str) – "qam", "psk" or "pam".

Returns:

the BER.

Return type:

float | numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

ber_receiver(m, esn0_db, kind='qam', *, receiver='arctangent')#

The BER of a Gray-mapped constellation with the receiver, averaged over the subcarriers of subcarrier_snr().

Parameters:
  • m (int) – constellation order.

  • esn0_db (float | array_like) – E_s/N_0 (dB) per data symbol.

  • kind (str) – "qam", "psk" or "pam".

  • receiver (str) – "arctangent", "linear" or "enhanced".

Returns:

the BER.

Return type:

float | numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

cycle_slip_response(start)#

The change of the estimates of the arctangent receiver caused by a cycle slip of +2π from sample start (after the prefix) to the end of the block (Ahmed et al. 2012, eqs. (11)–(14)).

Parameters:

start (int) – first sample of the slip, 0 ≤ start < N.

Returns:

K complex changes.

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter (the message names it).

demodulate(samples, *, unwrap='none', receiver='arctangent')#

The symbol estimates of every block.

Parameters:
  • samples (array_like) – a multiple of N + N_cp received samples.

  • unwrap (str) – for the arctangent receiver, "none" (the principal phase, wraps are errors) or "stream" (the phase unwrapped along the stream, the phase unwrapper of the paper).

  • receiver (str) – "arctangent" (d̂ = G ψ/(2πh), Thompson et al. 2008), "linear" (Im r) or "enhanced" (Im r (2 - Re r)), the low-complexity receivers of Ahmed and Zeidler (2015), divided by their Bussgang gain.

Returns:

K complex estimates per block.

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

gamp(samples, noise_var, constellation, *, iterations=50, damping=0.5, points=64)#

The posterior means of the symbols of every block by the GAMP receiver of López Morales, Dinis and García Armada (2022), near the optimum where the phase leaves (-π, π].

Parameters:
  • samples (array_like) – a multiple of N + N_cp received samples.

  • noise_var (float) – σ² per sample (noise_variance()).

  • constellation (Constellation) – the symbols as transmitted (square QAM, PAM or BPSK).

  • iterations (int) – iterations per block.

  • damping (float) – damping factor in (0, 1].

  • points (int) – points of the quadrature of the output step.

Returns:

K complex estimates per block.

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

modulate(symbols)#

The transmitted samples exp(j2πh x) with the cyclic prefix of every block.

Parameters:

symbols (array_like) – a multiple of K data symbols.

Returns:

N + N_cp unit-envelope samples per block.

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

noise_variance(esn0_db)#

The noise variance per sample for an E_s/N_0 per data symbol and a unit envelope, σ² = (N + N_cp)/(K E_s/N_0).

Parameters:

esn0_db (float | array_like) – E_s/N_0 (dB) per data symbol.

Returns:

σ² (for Rng.complex_noise).

Return type:

float | numpy.ndarray

Raises:

ValueError – for values that are not real numbers.

ofdm_signal(symbols)#

The real OFDM signal x = (F d + F* d*)/√(2K) of every block (no prefix).

Parameters:

symbols (array_like) – a multiple of K data symbols.

Returns:

N real samples per block.

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

psd()#

The expected periodogram E|S_k|²/N of a transmitted block under the Gaussian model (Guerreiro et al. 2015): the bins average to 1, bin 0 holds the carrier line.

Returns:

N values, in the order of the DFT.

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter (the message names it).

received_phase(samples, *, unwrap='none')#

The phase of the receiver before the DFT, prefix included.

Parameters:
  • samples (array_like) – received samples.

  • unwrap (str) – "none" or "stream", as demodulate().

Returns:

one phase (rad) per sample.

Return type:

numpy.ndarray

Raises:

ValueError – for an input that is not an array of numbers of the expected shape.

ser_esn0(m, esn0_db, kind='qam')#

The SER of the linear regime: that of a constellation of m points over AWGN at snr() (theory_ser()).

Parameters:
  • m (int) – constellation order.

  • esn0_db (float | array_like) – E_s/N_0 (dB) per data symbol.

  • kind (str) – "qam", "psk" or "pam".

Returns:

the SER.

Return type:

float | numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

snr(esn0_db)#

The SNR of every data subcarrier in the linear regime (linear scale), (2πh)² E_s/N_0 N/(N + N_cp).

Parameters:

esn0_db (float | array_like) – E_s/N_0 (dB) per data symbol.

Returns:

the SNR (linear).

Return type:

float | numpy.ndarray

Raises:

ValueError – for an invalid parameter or input (the message names it).

subcarrier_snr(esn0_db, *, receiver='arctangent')#

The SNR of every data subcarrier with the receiver: flat for the arctangent receiver above its threshold, Bussgang’s analysis for the linear receivers.

Parameters:
  • esn0_db (float) – E_s/N_0 (dB) per data symbol.

  • receiver (str) – "arctangent", "linear" or "enhanced".

Returns:

K SNRs (linear).

Return type:

numpy.ndarray

Raises:

ValueError – for an invalid parameter (the message names it).

block_len#

Samples per block, N + N_cp.

Type:

int

h#

The modulation index h.

Type:

float

n_cp#

The cyclic prefix N_cp (samples).

Type:

int

n_fft#

The DFT size N = 2J(K+1).

Type:

int

n_subcarriers#

Data subcarriers K per block.

Type:

int

oversampling#

The oversampling factor J.

Type:

int

sps#

Samples per data symbol, (N + N_cp)/K.

Type:

float