Skip to content

Commit b76249e

Browse files
committed
Add helper methods to build a correlated channel with exponential model.
1 parent c35634a commit b76249e

File tree

3 files changed

+185
-24
lines changed

3 files changed

+185
-24
lines changed

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 3-Clause License
22

3-
Copyright (c) 2012-2018, Veeresh Taranalli & contributors
3+
Copyright (c) 2012-2019, Veeresh Taranalli & contributors
44
All rights reserved.
55

66
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

commpy/channels.py

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Authors: Veeresh Taranalli <veeresht@gmail.com> & Bastien Trotobas <bastien.trotobas@gmail.com>
32
# License: BSD 3-Clause
43

@@ -20,7 +19,7 @@
2019

2120
from __future__ import division, print_function # Python 2 compatibility
2221

23-
from numpy import complex, abs, sqrt, sum, zeros, identity, hstack, einsum, trace, kron, absolute
22+
from numpy import complex, abs, sqrt, sum, zeros, identity, hstack, einsum, trace, kron, absolute, fromiter
2423
from numpy.random import randn, random, standard_normal
2524
from scipy.linalg import sqrtm
2625

@@ -71,7 +70,7 @@ def set_SNR_dB(self, SNR_dB, code_rate=1, Es=1):
7170
Average symbol energy
7271
"""
7372

74-
self.noise_std = sqrt((self.isComplex + 1) * self.nb_tx * Es / (code_rate * 10**(SNR_dB/10)))
73+
self.noise_std = sqrt((self.isComplex + 1) * self.nb_tx * Es / (code_rate * 10 ** (SNR_dB / 10)))
7574

7675
def set_SNR_lin(self, SNR_lin, code_rate=1, Es=1):
7776

@@ -99,7 +98,6 @@ def isComplex(self):
9998

10099

101100
class SISOFlatChannel(_FlatChannel):
102-
103101
"""
104102
Constructs a SISO channel with a flat fading.
105103
The channel coefficient are normalized i.e. the mean magnitude is 1.
@@ -241,7 +239,6 @@ def k_factor(self):
241239

242240

243241
class MIMOFlatChannel(_FlatChannel):
244-
245242
"""
246243
Constructs a MIMO channel with a flat fading based on the Kronecker model.
247244
The channel coefficient are normalized i.e. the mean magnitude is 1.
@@ -276,9 +273,7 @@ class MIMOFlatChannel(_FlatChannel):
276273
277274
Classical fadings:
278275
279-
* (zeros((nb_rx, nb_tx)), identity(nb_tx), identity(nb_rx)): Rayleigh fading.
280-
281-
* Others: rician fading.
276+
* (zeros((nb_rx, nb_tx)), identity(nb_tx), identity(nb_rx)): Uncorrelated Rayleigh fading.
282277
283278
noise_std : float
284279
Noise standard deviation. None is the value has not been set yet.
@@ -408,6 +403,119 @@ def k_factor(self):
408403
LOS_gain = einsum('ij,ij->', absolute(self.fading_param[0]), absolute(self.fading_param[0]))
409404
return LOS_gain / NLOS_gain
410405

406+
def uncorr_rayleigh_fading(self, dtype):
407+
""" Set the fading parameters to an uncorrelated Rayleigh channel.
408+
409+
Parameters
410+
----------
411+
dtype : dtype
412+
Type of the channel
413+
"""
414+
self.fading_param = zeros((self.nb_rx, self.nb_tx), dtype), identity(self.nb_tx), identity(self.nb_rx)
415+
416+
def expo_corr_rayleigh_fading(self, t, r):
417+
""" Set the fading parameters to a complex correlated Rayleigh channel following the exponential model.
418+
419+
See: S. L. Loyka, « Channel capacity of MIMO architecture using the exponential correlation matrix », IEEE
420+
Commun. Lett., vol. 5, nᵒ 9, p. 369‑371, sept. 2001.
421+
422+
Parameters
423+
----------
424+
t : complex with abs(t) = 1
425+
Correlation coefficient for the transceiver.
426+
427+
r : complex with abs(r) = 1
428+
Correlation coefficient for the receiver.
429+
430+
Raises
431+
------
432+
ValueError
433+
If abs(t) != 1 or abs(r) != 1
434+
"""
435+
# Check inputs
436+
if abs(t) - 1 > 1e-4:
437+
raise ValueError('abs(t) must be one.')
438+
if abs(r) - 1 > 1e-4:
439+
raise ValueError('abs(r) must be one.')
440+
441+
# Construct the exponent matrix
442+
expo_tx = fromiter((j - i for i in range(self.nb_tx) for j in range(self.nb_tx)), int, self.nb_tx ** 2)
443+
expo_rx = fromiter((j - i for i in range(self.nb_rx) for j in range(self.nb_rx)), int, self.nb_rx ** 2)
444+
445+
# Reshape
446+
expo_tx = expo_tx.reshape(self.nb_tx, self.nb_tx)
447+
expo_rx = expo_rx.reshape(self.nb_rx, self.nb_rx)
448+
449+
# Set fading
450+
self.fading_param = zeros((self.nb_rx, self.nb_tx), complex), t ** expo_tx, r ** expo_rx
451+
452+
def uncorr_rician_fading(self, mean, k_factor):
453+
""" Set the fading parameters to an uncorrelated rician channel.
454+
455+
mean will be scaled to fit the required k-factor.
456+
457+
Parameters
458+
----------
459+
mean : ndarray (shape: nb_rx x nb_tx)
460+
Mean of the channel gain.
461+
462+
k_factor : positive float
463+
Requested k-factor (the power ratio between LOS and NLOS).
464+
"""
465+
nb_antennas = mean.size
466+
NLOS_gain = nb_antennas / (k_factor + 1)
467+
mean = mean * sqrt(k_factor * NLOS_gain / einsum('ij,ij->', absolute(mean), absolute(mean)))
468+
self.fading_param = mean, identity(self.nb_tx) * NLOS_gain / nb_antennas, identity(self.nb_rx)
469+
470+
def expo_corr_rician_fading(self, mean, k_factor, t, r):
471+
""" Set the fading parameters to a complex correlated rician channel following the exponential model.
472+
473+
See: S. L. Loyka, « Channel capacity of MIMO architecture using the exponential correlation matrix », IEEE
474+
Commun. Lett., vol. 5, nᵒ 9, p. 369‑371, sept. 2001.
475+
476+
mean and correlation matricies will be scaled to fit the required k-factor.
477+
478+
Parameters
479+
----------
480+
mean : ndarray (shape: nb_rx x nb_tx)
481+
Mean of the channel gain.
482+
483+
k_factor : positive float
484+
Requested k-factor (the power ratio between LOS and NLOS).
485+
486+
t : complex with abs(t) = 1
487+
Correlation coefficient for the transceiver.
488+
489+
r : complex with abs(r) = 1
490+
Correlation coefficient for the receiver.
491+
492+
Raises
493+
------
494+
ValueError
495+
If abs(t) != 1 or abs(r) != 1
496+
"""
497+
# Check inputs
498+
if abs(t) - 1 > 1e-4:
499+
raise ValueError('abs(t) must be one.')
500+
if abs(r) - 1 > 1e-4:
501+
raise ValueError('abs(r) must be one.')
502+
503+
# Scaling
504+
nb_antennas = mean.size
505+
NLOS_gain = nb_antennas / (k_factor + 1)
506+
mean = mean * sqrt(k_factor * NLOS_gain / einsum('ij,ij->', absolute(mean), absolute(mean)))
507+
508+
# Construct the exponent matrix
509+
expo_tx = fromiter((j - i for i in range(self.nb_tx) for j in range(self.nb_tx)), int, self.nb_tx ** 2)
510+
expo_rx = fromiter((j - i for i in range(self.nb_rx) for j in range(self.nb_rx)), int, self.nb_rx ** 2)
511+
512+
# Reshape
513+
expo_tx = expo_tx.reshape(self.nb_tx, self.nb_tx)
514+
expo_rx = expo_rx.reshape(self.nb_rx, self.nb_rx)
515+
516+
# Set fading
517+
self.fading_param = mean, t ** expo_tx * NLOS_gain / nb_antennas, r ** expo_rx
518+
411519

412520
def bec(input_bits, p_e):
413521
"""
@@ -476,14 +584,14 @@ def awgn(input_signal, snr_dB, rate=1.0):
476584
Output signal from the channel with the specified SNR.
477585
"""
478586

479-
avg_energy = sum(abs(input_signal) * abs(input_signal))/len(input_signal)
480-
snr_linear = 10**(snr_dB/10.0)
481-
noise_variance = avg_energy/(2*rate*snr_linear)
587+
avg_energy = sum(abs(input_signal) * abs(input_signal)) / len(input_signal)
588+
snr_linear = 10 ** (snr_dB / 10.0)
589+
noise_variance = avg_energy / (2 * rate * snr_linear)
482590

483591
if isinstance(input_signal[0], complex):
484592
noise = (sqrt(noise_variance) * randn(len(input_signal))) + (sqrt(noise_variance) * randn(len(input_signal))*1j)
485593
else:
486-
noise = sqrt(2*noise_variance) * randn(len(input_signal))
594+
noise = sqrt(2 * noise_variance) * randn(len(input_signal))
487595

488596
output_signal = input_signal + noise
489597

commpy/tests/test_channels.py

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
from __future__ import division, print_function # Python 2 compatibility
55

6-
from numpy import ones, inf, sqrt, array, identity, zeros, dot, trace, einsum, absolute
6+
from numpy import ones, inf, sqrt, array, identity, zeros, dot, trace, einsum, absolute, exp, pi, fromiter, kron, \
7+
zeros_like
78
from numpy.random import seed, choice, randn
89
from numpy.testing import run_module_suite, assert_raises, assert_equal, assert_allclose, \
910
assert_array_equal, dec
@@ -274,6 +275,28 @@ def check_chan_gain(mod, chan):
274275
assert_allclose(P_unnoisy, P_msg * chan.nb_tx, rtol=0.2,
275276
err_msg='Channel add or remove energy')
276277

278+
def expo_correlation(t, r):
279+
# Construct the exponent matrix
280+
expo_tx = fromiter((j - i for i in range(chan.nb_tx) for j in range(chan.nb_tx)), int, chan.nb_tx ** 2)
281+
expo_rx = fromiter((j - i for i in range(chan.nb_rx) for j in range(chan.nb_rx)), int, chan.nb_rx ** 2)
282+
283+
# Reshape
284+
expo_tx = expo_tx.reshape(chan.nb_tx, chan.nb_tx)
285+
expo_rx = expo_rx.reshape(chan.nb_rx, chan.nb_rx)
286+
287+
return t ** expo_tx, r ** expo_rx
288+
289+
def check_correlation(chan, Rt, Rr):
290+
nb_ant = chan.nb_tx * chan.nb_rx
291+
Rdes = kron(Rt, Rr)
292+
H = chan.channel_gains
293+
Ract = zeros_like(Rdes)
294+
for i in range(len(H)):
295+
Ract += H[i].T.reshape(nb_ant, 1).dot(H[i].T.reshape(1, nb_ant).conj())
296+
Ract /= len(H)
297+
assert_allclose(Rdes, Ract, atol=0.05,
298+
err_msg='Wrong correlation matrix')
299+
277300
# Test value checking in constructor construction
278301
with assert_raises(ValueError):
279302
MIMOFlatChannel(nb_tx, nb_tx, 0, (ones((nb_tx, nb_tx)), ones((nb_tx, nb_tx)), ones((nb_rx, nb_rx))))
@@ -290,21 +313,26 @@ def check_chan_gain(mod, chan):
290313
# Test with Rayleigh fading
291314
chan.fading_param = (zeros((nb_rx, nb_tx)), identity(nb_tx), identity(nb_rx))
292315
check_chan_gain(mod, chan)
293-
assert_allclose(chan.channel_gains.mean(), 0, atol=2e-2,
294-
err_msg='Wrong channel mean with real channel')
295-
assert_allclose(chan.channel_gains.var(), 1, atol=5e-2,
296-
err_msg='Wrong channel variance with real channel')
297316

298317
# Test with rician fading
299318
mean = randn(nb_rx, nb_tx)
300319
mean *= sqrt(prod_nb * 0.75 / einsum('ij,ij->', absolute(mean), absolute(mean)))
301-
Rs = self.random_SDP_matrix(nb_tx) * sqrt(prod_nb) * 0.5
320+
Rt = self.random_SDP_matrix(nb_tx) * sqrt(prod_nb) * 0.5
302321
Rr = self.random_SDP_matrix(nb_rx) * sqrt(prod_nb) * 0.5
303-
chan.fading_param = (mean, Rs, Rr)
322+
chan.fading_param = (mean, Rt, Rr)
304323
check_chan_gain(mod, chan)
305324

306-
assert_allclose(chan.channel_gains.mean(0), mean, atol=0.2,
307-
err_msg='Wrong channel mean with real channel')
325+
# Test helper functions
326+
chan.uncorr_rayleigh_fading(float)
327+
check_chan_gain(mod, chan)
328+
assert_allclose(chan.k_factor, 0,
329+
err_msg='Wrong k-factor with uncorrelated Rayleigh fading')
330+
331+
mean = randn(nb_rx, nb_tx)
332+
chan.uncorr_rician_fading(mean, 10)
333+
check_chan_gain(mod, chan)
334+
assert_allclose(chan.k_factor, 10,
335+
err_msg='Wrong k-factor with uncorrelated rician fading')
308336

309337
# Test on complex channel
310338
for mod in self.all_mods:
@@ -323,16 +351,41 @@ def check_chan_gain(mod, chan):
323351
# Test with rician fading
324352
mean = randn(nb_rx, nb_tx) + 1j * randn(nb_rx, nb_tx)
325353
mean *= sqrt(prod_nb * 0.75 / einsum('ij,ij->', absolute(mean), absolute(mean)))
326-
Rs = self.random_SDP_matrix(nb_tx) * sqrt(prod_nb) * 0.5
354+
Rt = self.random_SDP_matrix(nb_tx) * sqrt(prod_nb) * 0.5
327355
Rr = self.random_SDP_matrix(nb_rx) * sqrt(prod_nb) * 0.5
328-
chan.fading_param = (mean, Rs, Rr)
356+
chan.fading_param = (mean, Rt, Rr)
329357
check_chan_gain(mod, chan)
330358

331359
assert_allclose(chan.channel_gains.mean(0).real, mean.real, atol=0.1,
332360
err_msg='Wrong channel mean with complex channel')
333361
assert_allclose(chan.channel_gains.mean(0).imag, mean.imag, atol=0.1,
334362
err_msg='Wrong channel mean with complex channel')
335363

364+
# Test helper functions
365+
chan.uncorr_rayleigh_fading(complex)
366+
check_chan_gain(mod, chan)
367+
assert_allclose(chan.k_factor, 0,
368+
err_msg='Wrong k-factor with uncorrelated Rayleigh fading')
369+
370+
mean = randn(nb_rx, nb_tx) + randn(nb_rx, nb_tx) * 1j
371+
chan.uncorr_rician_fading(mean, 10)
372+
check_chan_gain(mod, chan)
373+
assert_allclose(chan.k_factor, 10,
374+
err_msg='Wrong k-factor with uncorrelated rician fading')
375+
376+
chan.expo_corr_rayleigh_fading(exp(-0.2j*pi), exp(-0.1j*pi))
377+
check_chan_gain(mod, chan)
378+
assert_allclose(chan.k_factor, 0,
379+
err_msg='Wrong k-factor with correlated Rayleigh fading')
380+
Rt, Rr = expo_correlation(exp(-0.2j*pi), exp(-0.1j*pi))
381+
check_correlation(chan, Rt, Rr)
382+
383+
mean = randn(nb_rx, nb_tx) + randn(nb_rx, nb_tx) * 1j
384+
chan.expo_corr_rician_fading(mean, 10, exp(-0.1j*pi), exp(-0.2j*pi))
385+
check_chan_gain(mod, chan)
386+
assert_allclose(chan.k_factor, 10,
387+
err_msg='Wrong k-factor with correlated rician fading')
388+
336389

337390
@dec.slow
338391
class TestMIMONoiseGeneration(MIMOTestCase):

0 commit comments

Comments
 (0)