Skip to content

Commit 15eef39

Browse files
authored
Merge pull request #102 from BastienTr/master
Add Gray code to modems : close both #100 and #60.
2 parents 46512b9 + c373d79 commit 15eef39

File tree

6 files changed

+62
-64
lines changed

6 files changed

+62
-64
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ install:
2323
- conda info -a
2424

2525
# Replace dep1 dep2 ... with your dependencies
26-
- conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib nose
26+
- conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib nose sympy
2727
- source activate test-environment
2828
#- conda install --yes -c dan_blanchard python-coveralls nose-cov
2929
- pip install coveralls

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ Requirements/Dependencies
102102
- scipy 0.15 or above
103103
- matplotlib 1.4 or above
104104
- nose 1.3 or above
105+
- sympy 1.7 or above
105106

106107
Installation
107108
------------

commpy/modulation.py

+27-14
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
2222
"""
2323
from bisect import insort
24-
from itertools import product
2524

2625
import matplotlib.pyplot as plt
27-
from numpy import arange, array, zeros, pi, cos, sin, sqrt, log2, argmin, \
26+
from numpy import arange, array, zeros, pi, sqrt, log2, argmin, \
2827
hstack, repeat, tile, dot, shape, concatenate, exp, \
29-
log, vectorize, empty, eye, kron, inf, full, abs, newaxis, minimum, clip
28+
log, vectorize, empty, eye, kron, inf, full, abs, newaxis, minimum, clip, fromiter
3029
from numpy.fft import fft, ifft
3130
from numpy.linalg import qr, norm
31+
from sympy.combinatorics.graycode import GrayCode
3232

3333
from commpy.utilities import bitarray2dec, dec2bitarray, signal_power
3434

@@ -65,10 +65,16 @@ class Modem:
6565
If the constellation is changed to an array-like with length that is not a power of 2.
6666
"""
6767

68-
def __init__(self, constellation):
68+
def __init__(self, constellation, reorder_as_gray=True):
6969
""" Creates a custom Modem object. """
7070

71-
self.constellation = constellation
71+
if reorder_as_gray:
72+
m = log2(len(constellation))
73+
gray_code_sequence = GrayCode(m).generate_gray()
74+
gray_code_sequence_array = fromiter((int(g, 2) for g in gray_code_sequence), int, len(constellation))
75+
self.constellation = array(constellation)[gray_code_sequence_array.argsort()]
76+
else:
77+
self.constellation = constellation
7278

7379
def modulate(self, input_bits):
7480
""" Modulate (map) an array of bits to constellation symbols.
@@ -197,10 +203,11 @@ class PSKModem(Modem):
197203
def __init__(self, m):
198204
""" Creates a Phase Shift Keying (PSK) Modem object. """
199205

200-
def _constellation_symbol(i):
201-
return cos(2 * pi * (i - 1) / m) + sin(2 * pi * (i - 1) / m) * (0 + 1j)
206+
num_bits_symbol = log2(m)
207+
if num_bits_symbol != int(num_bits_symbol):
208+
raise ValueError('Constellation length must be a power of 2.')
202209

203-
self.constellation = list(map(_constellation_symbol, arange(m)))
210+
super().__init__(exp(1j * arange(0, 2 * pi, 2 * pi / m)))
204211

205212

206213
class QAMModem(Modem):
@@ -229,6 +236,7 @@ class QAMModem(Modem):
229236
------
230237
ValueError
231238
If the constellation is changed to an array-like with length that is not a power of 2.
239+
If the parameter m would lead to an non-square QAM during initialization.
232240
"""
233241

234242
def __init__(self, m):
@@ -237,16 +245,21 @@ def __init__(self, m):
237245
Parameters
238246
----------
239247
m : int
240-
Size of the QAM constellation.
248+
Size of the QAM constellation. Must lead to a square QAM (ie sqrt(m) is an integer).
241249
250+
Raises
251+
------
252+
ValueError
253+
If m would lead to an non-square QAM.
242254
"""
243255

244-
def _constellation_symbol(i):
245-
return (2 * i[0] - 1) + (2 * i[1] - 1) * (1j)
256+
num_symb_pam = sqrt(m)
257+
if num_symb_pam != int(num_symb_pam):
258+
raise ValueError('m must lead to a square QAM.')
246259

247-
mapping_array = arange(1, sqrt(m) + 1) - (sqrt(m) / 2)
248-
self.constellation = list(map(_constellation_symbol,
249-
list(product(mapping_array, repeat=2))))
260+
pam = arange(-num_symb_pam + 1, num_symb_pam, 2)
261+
constellation = tile(hstack((pam, pam[::-1])), int(num_symb_pam) // 2) * 1j + pam.repeat(num_symb_pam)
262+
super().__init__(constellation)
250263

251264

252265
def ofdm_tx(x, nfft, nsc, cp_length):

commpy/tests/test_modulation.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33

44
from itertools import product
55

6-
from numpy import zeros, identity, arange, concatenate, log2, array, inf
6+
from numpy import zeros, identity, arange, concatenate, log2, log10, array, inf, sqrt, sin, pi
77
from numpy.random import seed
88
from numpy.testing import run_module_suite, assert_allclose, dec, assert_raises, assert_array_equal
9+
from scipy.special import erf
910

10-
from commpy.channels import MIMOFlatChannel
11+
from commpy.channels import MIMOFlatChannel, SISOFlatChannel
1112
from commpy.links import *
1213
from commpy.modulation import QAMModem, mimo_ml, bit_lvl_repr, max_log_approx, PSKModem, Modem
1314
from commpy.utilities import signal_power
1415

1516

17+
def Qfunc(x):
18+
return 0.5 - 0.5 * erf(x / sqrt(2))
19+
20+
1621
@dec.slow
1722
def test_bit_lvl_repr():
1823
# Set seed
@@ -124,8 +129,33 @@ def do_custom(self, modem):
124129
pass
125130

126131

132+
@dec.slow
127133
class TestModulateHardDemodulate(ModemTestcase):
128134

135+
@staticmethod
136+
def check_BER(modem, EbN0dB, BERs_expected):
137+
seed(8071996)
138+
model = LinkModel(modem.modulate,
139+
SISOFlatChannel(fading_param=(1 + 0j, 0)),
140+
lambda y, _, __, ___: modem.demodulate(y, 'hard'),
141+
modem.num_bits_symbol, modem.constellation, modem.Es)
142+
BERs = model.link_performance(EbN0dB + 10 * log10(log2(modem.m)), 5e5, 400, 720)
143+
assert_allclose(BERs, BERs_expected, atol=1e-4, rtol=.1,
144+
err_msg='Wrong BER for a standard modulation with {} symbols'.format(modem.m))
145+
146+
def do_qam(self, modem):
147+
EbN0dB = arange(8, 25, 4)
148+
nb_symb_pam = sqrt(modem.m)
149+
BERs_expected = 2 * (1 - 1 / nb_symb_pam) / log2(nb_symb_pam) * \
150+
Qfunc(sqrt(3 * log2(nb_symb_pam) / (nb_symb_pam ** 2 - 1) * (2 * 10 ** (EbN0dB / 10))))
151+
self.check_BER(modem, EbN0dB, BERs_expected)
152+
153+
def do_psk(self, modem):
154+
EbN0dB = arange(15, 25, 4)
155+
SERs_expected = 2 * Qfunc(sqrt(2 * modem.num_bits_symbol * 10 ** (EbN0dB / 10)) * sin(pi / modem.m))
156+
BERs_expected = SERs_expected / modem.num_bits_symbol
157+
self.check_BER(modem, EbN0dB, BERs_expected)
158+
129159
def do(self, modem):
130160
for bits in product(*((0, 1),) * modem.num_bits_symbol):
131161
assert_array_equal(bits, modem.demodulate(modem.modulate(bits), 'hard'),

commpy/tests/test_wifi80211.py

-47
This file was deleted.

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ numpy>=1.9.2
22
scipy>=0.15.0
33
matplotlib>=1.4.3
44
nose>=1.3.4
5+
sympy>=1.7.1

0 commit comments

Comments
 (0)