summaryrefslogtreecommitdiff
path: root/gr-digital/python
diff options
context:
space:
mode:
authorTom Rondeau <tom@trondeau.com>2014-12-02 15:11:05 -0500
committerTom Rondeau <tom@trondeau.com>2014-12-03 19:02:26 -0500
commit57ee0e7e122668a2b7f628ecb84fe1f90f9d2d11 (patch)
tree4a2bee2978248f55c9f3c14cdb68d0bb953950ae /gr-digital/python
parent92ac02f7fd7289d11fa6523e0491c6a6d8c3cb56 (diff)
digital: fixes issues with the constellation soft decoder, specifically how the decisions are calculated in the C++ code and some issues with the QAM16 constellation in particular.
This addresses issue #737. The patch attached to that issue is not actually valid and is only an ordering problem/confusion. I will be adding an example GRC flowgraph that compares the output of the hard decision and soft decision versions to the original input stream to show how they match. Increased testing coverage in the QA to test certain known points as well as random samples.
Diffstat (limited to 'gr-digital/python')
-rw-r--r--gr-digital/python/digital/qa_constellation.py107
-rw-r--r--gr-digital/python/digital/qa_constellation_soft_decoder_cf.py153
-rwxr-xr-xgr-digital/python/digital/qam_constellations.py2
-rw-r--r--gr-digital/python/digital/soft_dec_lut_gen.py96
4 files changed, 244 insertions, 114 deletions
diff --git a/gr-digital/python/digital/qa_constellation.py b/gr-digital/python/digital/qa_constellation.py
index 00248f4f09..42e49bb059 100644
--- a/gr-digital/python/digital/qa_constellation.py
+++ b/gr-digital/python/digital/qa_constellation.py
@@ -1,26 +1,26 @@
#!/usr/bin/env python
#
# Copyright 2011,2013 Free Software Foundation, Inc.
-#
+#
# This file is part of GNU Radio
-#
+#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
-#
+#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
-#
+#
-import random
+import random, math
from cmath import exp, pi, log, sqrt
from gnuradio import gr, gr_unittest, digital, blocks
@@ -40,7 +40,7 @@ tested_mod_codes = (mod_codes.NO_CODE, mod_codes.GRAY_CODE)
def twod_constell():
"""
-
+
"""
points = ((1+0j), (0+1j),
(-1+0j), (0-1j))
@@ -76,12 +76,12 @@ easy_constellation_info = (
False, None),
(qam.qam_constellation,
{'constellation_points': (4,),
- 'mod_code': tested_mod_codes,
+ 'mod_code': tested_mod_codes,
'large_ampls_to_corners': [False],},
True, None),
(qam.qam_constellation,
{'constellation_points': (4, 16, 64),
- 'mod_code': tested_mod_codes,
+ 'mod_code': tested_mod_codes,
'differential': (False,)},
False, None),
(digital.constellation_bpsk, {}, True, None),
@@ -101,7 +101,7 @@ medium_constellation_info = (
True, None),
(qam.qam_constellation,
{'constellation_points': (16 ,),
- 'mod_code': tested_mod_codes,
+ 'mod_code': tested_mod_codes,
'large_ampls_to_corners': [False, True],},
True, None),
(qamlike.qam32_holeinside_constellation,
@@ -113,11 +113,20 @@ medium_constellation_info = (
difficult_constellation_info = (
(qam.qam_constellation,
{'constellation_points': (64,),
- 'mod_code': tested_mod_codes,
+ 'mod_code': tested_mod_codes,
'large_ampls_to_corners': [False, True],},
- True, None),
+ True, None),
)
+def slicer(x):
+ ret = []
+ for xi in x:
+ if(xi < 0):
+ ret.append(0.0)
+ else:
+ ret.append(1.0)
+ return ret
+
def tested_constellations(easy=True, medium=True, difficult=True):
"""
Generator to produce (constellation, differential) tuples for testing purposes.
@@ -153,7 +162,7 @@ def tested_constellations(easy=True, medium=True, difficult=True):
this_poss_arg[2] = 0
if sum([argindex for argname, argvalues, argindex in poss_args]) == 0:
break
-
+
class test_constellation(gr_unittest.TestCase):
@@ -170,7 +179,7 @@ class test_constellation(gr_unittest.TestCase):
for constellation, differential in tested_constellations():
if differential:
rs = constellation.rotational_symmetry()
- rotations = [exp(i*2*pi*(0+1j)/rs) for i in range(0, rs)]
+ rotations = [exp(i*2*pi*(0+1j)/rs) for i in range(0, rs)]
else:
rotations = [None]
for rotation in rotations:
@@ -216,17 +225,17 @@ class test_constellation(gr_unittest.TestCase):
y_cpp_raw_calc = []
y_cpp_table = []
for sample in samples:
- y_python_raw_calc += digital.calc_soft_dec(sample, constel, code)
- y_python_gen_calc += digital.sd_psk_4_0(sample, Es)
- y_python_table += digital.calc_soft_dec_from_table(sample, table, prec, Es)
+ y_python_raw_calc += slicer(digital.calc_soft_dec(sample, constel, code))
+ y_python_gen_calc += slicer(digital.sd_psk_4_0(sample, Es))
+ y_python_table += slicer(digital.calc_soft_dec_from_table(sample, table, prec, Es))
y_cpp_raw_calc += c.calc_soft_dec(sample)
y_cpp_table += c.soft_decision_maker(sample)
- self.assertFloatTuplesAlmostEqual(y_python_raw_calc, y_python_gen_calc, 4)
- self.assertFloatTuplesAlmostEqual(y_python_raw_calc, y_python_table, 2)
- self.assertFloatTuplesAlmostEqual(y_cpp_raw_calc, y_cpp_table, 4)
-
+ self.assertFloatTuplesAlmostEqual(y_python_raw_calc, y_python_gen_calc, 0)
+ self.assertFloatTuplesAlmostEqual(y_python_gen_calc, y_python_table, 0)
+ self.assertFloatTuplesAlmostEqual(y_cpp_raw_calc, y_cpp_table, 0)
+
def test_soft_qpsk_calc(self):
prec = 8
constel, code = digital.psk_4_0()
@@ -257,14 +266,54 @@ class test_constellation(gr_unittest.TestCase):
y_cpp_raw_calc = []
y_cpp_table = []
for sample in samples:
- y_python_raw_calc += digital.calc_soft_dec(sample, constel, code)
- y_python_table += digital.calc_soft_dec_from_table(sample, table, prec, Es)
+ y_python_raw_calc += slicer(digital.calc_soft_dec(sample, constel, code))
+ y_python_table += slicer(digital.calc_soft_dec_from_table(sample, table, prec, Es))
- y_cpp_raw_calc += c.calc_soft_dec(sample)
- y_cpp_table += c.soft_decision_maker(sample)
+ y_cpp_raw_calc += slicer(c.calc_soft_dec(sample))
+ y_cpp_table += slicer(c.soft_decision_maker(sample))
+
+ self.assertEqual(y_python_raw_calc, y_python_table)
+ self.assertEqual(y_cpp_raw_calc, y_cpp_table)
+
+
+ def test_soft_qam16_calc(self):
+ prec = 8
+ constel, code = digital.qam_16_0()
- self.assertFloatTuplesAlmostEqual(y_python_raw_calc, y_python_table, 4)
- self.assertFloatTuplesAlmostEqual(y_cpp_raw_calc, y_cpp_table, 4)
+ rot_sym = 1
+ side = 2
+ width = 2
+ c = digital.constellation_rect(constel, code, rot_sym,
+ side, side, width, width)
+
+ # Get max energy/symbol in constellation
+ constel = c.points()
+ Es = max([abs(constel_i) for constel_i in constel])
+
+ table = digital.soft_dec_table(constel, code, prec)
+ c.gen_soft_dec_lut(prec)
+
+ x = sqrt(2.0)/2.0
+ step = (x.real+x.real) / (2**prec - 1)
+ samples = [ -x-x*1j, -x+x*1j,
+ x+x*1j, x-x*1j,
+ (-x+128*step)+(-x+128*step)*1j,
+ (-x+64*step) +(-x+64*step)*1j, (-x+64*step) +(-x+192*step)*1j,
+ (-x+192*step)+(-x+192*step)*1j, (-x+192*step)+(-x+64*step)*1j,]
+
+ y_python_raw_calc = []
+ y_python_table = []
+ y_cpp_raw_calc = []
+ y_cpp_table = []
+ for sample in samples:
+ y_python_raw_calc += slicer(digital.calc_soft_dec(sample, constel, code))
+ y_python_table += slicer(digital.calc_soft_dec_from_table(sample, table, prec, Es))
+
+ y_cpp_raw_calc += slicer(c.calc_soft_dec(sample))
+ y_cpp_table += slicer(c.soft_decision_maker(sample))
+
+ self.assertFloatTuplesAlmostEqual(y_python_raw_calc, y_python_table, 0)
+ self.assertFloatTuplesAlmostEqual(y_cpp_raw_calc, y_cpp_table, 0)
class mod_demod(gr.hier_block2):
def __init__(self, constellation, differential, rotation):
@@ -317,7 +366,7 @@ class mod_demod(gr.hier_block2):
if self.constellation.apply_pre_diff_code():
self.blocks.append(digital.map_bb(
mod_codes.invert_code(self.constellation.pre_diff_code())))
- # unpack the k bit vector into a stream of bits
+ # unpack the k bit vector into a stream of bits
self.blocks.append(blocks.unpack_k_bits_bb(
self.constellation.bits_per_symbol()))
# connect to block output
@@ -326,6 +375,6 @@ class mod_demod(gr.hier_block2):
self.blocks.append(weakref.proxy(self))
self.connect(*self.blocks)
-
+
if __name__ == '__main__':
gr_unittest.run(test_constellation, "test_constellation.xml")
diff --git a/gr-digital/python/digital/qa_constellation_soft_decoder_cf.py b/gr-digital/python/digital/qa_constellation_soft_decoder_cf.py
index 6cd757537a..448e76e834 100644
--- a/gr-digital/python/digital/qa_constellation_soft_decoder_cf.py
+++ b/gr-digital/python/digital/qa_constellation_soft_decoder_cf.py
@@ -1,27 +1,28 @@
#!/usr/bin/env python
#
# Copyright 2013 Free Software Foundation, Inc.
-#
+#
# This file is part of GNU Radio
-#
+#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
-#
+#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
-#
+#
from gnuradio import gr, gr_unittest, digital, blocks
from math import sqrt
+from numpy import random, vectorize
class test_constellation_soft_decoder(gr_unittest.TestCase):
@@ -31,16 +32,15 @@ class test_constellation_soft_decoder(gr_unittest.TestCase):
def tearDown(self):
self.tb = None
- def test_constellation_soft_decoder_cf_bpsk(self):
- prec = 8
- src_data = (0.5 + 0.5j, 0.1 - 1.2j, -0.8 - 0.1j, -0.45 + 0.8j,
- 0.8 + 1.0j, -0.5 + 0.1j, 0.1 - 1.2j, 1+1j)
- lut = digital.soft_dec_table_generator(digital.sd_psk_2_0x0, prec)
+ def helper_with_lut(self, prec, src_data, const_gen, const_sd_gen):
+ cnst_pts, code = const_gen()
+ Es = max([abs(c) for c in cnst_pts])
+ lut = digital.soft_dec_table_generator(const_sd_gen, prec, Es)
expected_result = list()
for s in src_data:
- expected_result += digital.calc_soft_dec_from_table(s, lut, prec, Es=2/sqrt(2))
+ res = digital.calc_soft_dec_from_table(s, lut, prec, sqrt(2.0))
+ expected_result += res
- cnst_pts, code = digital.psk_2_0x0()
cnst = digital.constellation_calcdist(cnst_pts, code, 2, 1)
cnst.set_soft_dec_lut(lut, int(prec))
src = blocks.vector_source_c(src_data)
@@ -49,64 +49,123 @@ class test_constellation_soft_decoder(gr_unittest.TestCase):
self.tb.connect(src, op)
self.tb.connect(op, dst)
- self.tb.run() # run the graph and wait for it to finish
+ self.tb.run()
actual_result = dst.data() # fetch the contents of the sink
#print "actual result", actual_result
#print "expected result", expected_result
- self.assertFloatTuplesAlmostEqual(expected_result, actual_result, 4)
+ self.assertFloatTuplesAlmostEqual(expected_result, actual_result, 5)
- def test_constellation_soft_decoder_cf_qpsk(self):
- prec = 8
- src_data = (0.5 + 0.5j, 0.1 - 1.2j, -0.8 - 0.1j, -0.45 + 0.8j,
- 0.8 + 1.0j, -0.5 + 0.1j, 0.1 - 1.2j, 1+1j)
- lut = digital.soft_dec_table_generator(digital.sd_psk_4_0x0_0_1, prec)
+ def helper_no_lut(self, prec, src_data, const_gen, const_sd_gen):
+ cnst_pts, code = const_gen()
+ cnst = digital.constellation_calcdist(cnst_pts, code, 2, 1)
expected_result = list()
for s in src_data:
- expected_result += digital.calc_soft_dec_from_table(s, lut, prec)
+ res = digital.calc_soft_dec(s, cnst.points(), code)
+ expected_result += res
- cnst_pts,code = digital.psk_4_0x0_0_1()
- cnst = digital.constellation_calcdist(cnst_pts, code, 2, 1)
- cnst.set_soft_dec_lut(lut, int(prec))
src = blocks.vector_source_c(src_data)
op = digital.constellation_soft_decoder_cf(cnst.base())
dst = blocks.vector_sink_f()
self.tb.connect(src, op)
self.tb.connect(op, dst)
- self.tb.run() # run the graph and wait for it to finish
+ self.tb.run()
actual_result = dst.data() # fetch the contents of the sink
#print "actual result", actual_result
#print "expected result", expected_result
- self.assertFloatTuplesAlmostEqual(expected_result, actual_result, 5)
- def test_constellation_soft_decoder_cf_qam16(self):
+ # Double vs. float precision issues between Python and C++, so
+ # use only 4 decimals in comparisons.
+ self.assertFloatTuplesAlmostEqual(expected_result, actual_result, 4)
+
+ def test_constellation_soft_decoder_cf_bpsk_3(self):
+ prec = 3
+ src_data = (-1.0 - 1.0j, 1.0 - 1.0j, -1.0 + 1.0j, 1.0 + 1.0j,
+ -2.0 - 2.0j, 2.0 - 2.0j, -2.0 + 2.0j, 2.0 + 2.0j,
+ -0.2 - 0.2j, 0.2 - 0.2j, -0.2 + 0.2j, 0.2 + 0.2j,
+ 0.3 + 0.4j, 0.1 - 1.2j, -0.8 - 0.1j, -0.4 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 + 1.2j, -1.7 - 0.9j)
+ self.helper_with_lut(prec, src_data, digital.psk_2_0x0, digital.sd_psk_2_0x0)
+
+ def test_constellation_soft_decoder_cf_bpsk_8(self):
prec = 8
- src_data = (0.5 + 0.5j, 0.1 - 1.2j, -0.8 - 0.1j, -0.45 + 0.8j,
- 0.8 + 1.0j, -0.5 + 0.1j, 0.1 - 1.2j, 1+1j)
- lut = digital.soft_dec_table_generator(digital.sd_qam_16_0x0_0_1_2_3, prec)
- expected_result = list()
- for s in src_data:
- expected_result += digital.calc_soft_dec_from_table(s, lut, prec)
+ src_data = (-1.0 - 1.0j, 1.0 - 1.0j, -1.0 + 1.0j, 1.0 + 1.0j,
+ -2.0 - 2.0j, 2.0 - 2.0j, -2.0 + 2.0j, 2.0 + 2.0j,
+ -0.2 - 0.2j, 0.2 - 0.2j, -0.2 + 0.2j, 0.2 + 0.2j,
+ 0.3 + 0.4j, 0.1 - 1.2j, -0.8 - 0.1j, -0.4 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 + 1.2j, -1.7 - 0.9j)
+ self.helper_with_lut(prec, src_data, digital.psk_2_0x0, digital.sd_psk_2_0x0)
+
+ def test_constellation_soft_decoder_cf_bpsk_8_rand(self):
+ prec = 8
+ src_data = vectorize(complex)(2*random.randn(100), 2*random.randn(100))
+ self.helper_with_lut(prec, src_data, digital.psk_2_0x0, digital.sd_psk_2_0x0)
- cnst_pts = digital.qam_16_0x0_0_1_2_3()
- cnst = digital.constellation_calcdist(cnst_pts[0], cnst_pts[1], 2, 1)
- cnst.set_soft_dec_lut(lut, int(prec))
- src = blocks.vector_source_c(src_data)
- op = digital.constellation_soft_decoder_cf(cnst.base())
- dst = blocks.vector_sink_f()
+ def test_constellation_soft_decoder_cf_bpsk_8_rand2(self):
+ prec = 8
+ src_data = vectorize(complex)(2*random.randn(100), 2*random.randn(100))
+ self.helper_no_lut(prec, src_data, digital.psk_2_0x0, digital.sd_psk_2_0x0)
+
+ def test_constellation_soft_decoder_cf_qpsk_3(self):
+ prec = 3
+ src_data = (-1.0 - 1.0j, 1.0 - 1.0j, -1.0 + 1.0j, 1.0 + 1.0j,
+ -2.0 - 2.0j, 2.0 - 2.0j, -2.0 + 2.0j, 2.0 + 2.0j,
+ -0.2 - 0.2j, 0.2 - 0.2j, -0.2 + 0.2j, 0.2 + 0.2j,
+ 0.3 + 0.4j, 0.1 - 1.2j, -0.8 - 0.1j, -0.4 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 + 1.2j, -1.7 - 0.9j)
+ self.helper_with_lut(prec, src_data, digital.psk_4_0x0_0_1, digital.sd_psk_4_0x0_0_1)
+
+ def test_constellation_soft_decoder_cf_qpsk_8(self):
+ prec = 8
+ src_data = (-1.0 - 1.0j, 1.0 - 1.0j, -1.0 + 1.0j, 1.0 + 1.0j,
+ -2.0 - 2.0j, 2.0 - 2.0j, -2.0 + 2.0j, 2.0 + 2.0j,
+ -0.2 - 0.2j, 0.2 - 0.2j, -0.2 + 0.2j, 0.2 + 0.2j,
+ 0.3 + 0.4j, 0.1 - 1.2j, -0.8 - 0.1j, -0.4 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 + 1.2j, -1.7 - 0.9j)
+ self.helper_with_lut(prec, src_data, digital.psk_4_0x0_0_1, digital.sd_psk_4_0x0_0_1)
+
+ def test_constellation_soft_decoder_cf_qpsk_8_rand(self):
+ prec = 8
+ src_data = vectorize(complex)(2*random.randn(100), 2*random.randn(100))
+ self.helper_with_lut(prec, src_data, digital.psk_4_0x0_0_1, digital.sd_psk_4_0x0_0_1)
- self.tb.connect(src, op)
- self.tb.connect(op, dst)
- self.tb.run() # run the graph and wait for it to finish
+ def test_constellation_soft_decoder_cf_qpsk_8_rand2(self):
+ prec = 8
+ src_data = vectorize(complex)(2*random.randn(100), 2*random.randn(100))
+ self.helper_no_lut(prec, src_data, digital.psk_4_0x0_0_1, digital.sd_psk_4_0x0_0_1)
+
+ def test_constellation_soft_decoder_cf_qam16_3(self):
+ prec = 3
+ src_data = (-1.0 - 1.0j, 1.0 - 1.0j, -1.0 + 1.0j, 1.0 + 1.0j,
+ -2.0 - 2.0j, 2.0 - 2.0j, -2.0 + 2.0j, 2.0 + 2.0j,
+ -0.2 - 0.2j, 0.2 - 0.2j, -0.2 + 0.2j, 0.2 + 0.2j,
+ 0.3 + 0.4j, 0.1 - 1.2j, -0.8 - 0.1j, -0.4 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 + 1.2j, -1.7 - 0.9j)
+ self.helper_with_lut(prec, src_data, digital.qam_16_0x0_0_1_2_3, digital.sd_qam_16_0x0_0_1_2_3)
+
+ def test_constellation_soft_decoder_cf_qam16_8(self):
+ prec = 8
+ src_data = (-1.0 - 1.0j, 1.0 - 1.0j, -1.0 + 1.0j, 1.0 + 1.0j,
+ -2.0 - 2.0j, 2.0 - 2.0j, -2.0 + 2.0j, 2.0 + 2.0j,
+ -0.2 - 0.2j, 0.2 - 0.2j, -0.2 + 0.2j, 0.2 + 0.2j,
+ 0.3 + 0.4j, 0.1 - 1.2j, -0.8 - 0.1j, -0.4 + 0.8j,
+ 0.8 + 1.0j, -0.5 + 0.1j, 0.1 + 1.2j, -1.7 - 0.9j)
+ self.helper_with_lut(prec, src_data, digital.qam_16_0x0_0_1_2_3, digital.sd_qam_16_0x0_0_1_2_3)
+
+ def test_constellation_soft_decoder_cf_qam16_8_rand(self):
+ prec = 8
+ src_data = vectorize(complex)(2*random.randn(100), 2*random.randn(100))
+ self.helper_with_lut(prec, src_data, digital.qam_16_0x0_0_1_2_3, digital.sd_qam_16_0x0_0_1_2_3)
- actual_result = dst.data() # fetch the contents of the sink
- #print "actual result", actual_result
- #print "expected result", expected_result
- self.assertFloatTuplesAlmostEqual(expected_result, actual_result, 5)
+ def test_constellation_soft_decoder_cf_qam16_8_rand2(self):
+ prec = 8
+ #src_data = vectorize(complex)(2*random.randn(100), 2*random.randn(100))
+ src_data = vectorize(complex)(2*random.randn(2), 2*random.randn(2))
+ self.helper_no_lut(prec, src_data, digital.qam_16_0x0_0_1_2_3, digital.sd_qam_16_0x0_0_1_2_3)
if __name__ == '__main__':
- gr_unittest.run(test_constellation_soft_decoder, "test_constellation_soft_decoder.xml")
-
+ #gr_unittest.run(test_constellation_soft_decoder, "test_constellation_soft_decoder.xml")
+ gr_unittest.run(test_constellation_soft_decoder)
diff --git a/gr-digital/python/digital/qam_constellations.py b/gr-digital/python/digital/qam_constellations.py
index 6f9f6bfab5..ed86d7e1e3 100755
--- a/gr-digital/python/digital/qam_constellations.py
+++ b/gr-digital/python/digital/qam_constellations.py
@@ -270,7 +270,7 @@ def sd_qam_16_0x0_0_1_2_3(x, Es=1):
b2 = -abs(x_re) + boundary
b0 = -abs(x_im) + boundary
- return [(Es/2)*b3, (Es/2)*b2, (Es/2)*b1, (Es/2)*b0]
+ return [(Es/2.0)*b3, (Es/2.0)*b2, (Es/2.0)*b1, (Es/2.0)*b0]
sd_qam_16 = sd_qam_16_0x0_0_1_2_3
sd_qam_16_0 = sd_qam_16
diff --git a/gr-digital/python/digital/soft_dec_lut_gen.py b/gr-digital/python/digital/soft_dec_lut_gen.py
index 24d8bbdc2e..9c184d60cb 100644
--- a/gr-digital/python/digital/soft_dec_lut_gen.py
+++ b/gr-digital/python/digital/soft_dec_lut_gen.py
@@ -1,30 +1,29 @@
#!/usr/bin/env python
#
# Copyright 2013 Free Software Foundation, Inc.
-#
+#
# This file is part of GNU Radio
-#
+#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
-#
+#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
-#
+#
import numpy
def soft_dec_table_generator(soft_dec_gen, prec, Es=1):
- '''
- Builds a LUT that is a list of tuples. The tuple represents the
+ '''Builds a LUT that is a list of tuples. The tuple represents the
soft decisions for the constellation/bit mapping at any given
point in the complex space, (x,y).
@@ -70,18 +69,19 @@ def soft_dec_table_generator(soft_dec_gen, prec, Es=1):
(1,1) into an index value in the table and returns the tuple of
soft decisions at that index.
- Es is the energy per symbol. This is passed to the function to
- provide the bounds when calling the generator function since they
- don't know how the constellation was normalized. Using the
- (maximum) energy per symbol for constellation allows us to provide
- any scaling of the constellation (normalized to sum to 1,
+ Es is the maximum energy per symbol. This is passed to the
+ function to provide the bounds when calling the generator function
+ since they don't know how the constellation was normalized. Using
+ the (maximum) energy per symbol for constellation allows us to
+ provide any scaling of the constellation (normalized to sum to 1,
normalized so the outside points sit on +/-1, etc.) but still
calculate the soft decisions as we would given the full
constellation.
+
'''
npts = 2.0**prec
- maxd = Es*numpy.sqrt(2)/2
+ maxd = Es*numpy.sqrt(2.0)/2.0
yrng = numpy.linspace(-maxd, maxd, npts)
xrng = numpy.linspace(-maxd, maxd, npts)
@@ -129,7 +129,7 @@ def soft_dec_table(constel, symbols, prec, npwr=1):
table.append(decs)
return table
-def calc_soft_dec_from_table(sample, table, prec, Es=1):
+def calc_soft_dec_from_table(sample, table, prec, Es=1.0):
'''
Takes in a complex sample and converts it from the coordinates
(-1,-1) to (1,1) into an index value. The index value points to a
@@ -152,25 +152,25 @@ def calc_soft_dec_from_table(sample, table, prec, Es=1):
calculate the soft decisions as we would given the full
constellation.
'''
- lut_scale = 2**prec
- maxd = Es*numpy.sqrt(2)/2
- step = 2*maxd / lut_scale
- scale = (lut_scale) / (2*maxd) - step
-
+ lut_scale = 2.0**prec
+ maxd = Es*numpy.sqrt(2.0)/2.0
+ scale = (lut_scale) / (2.0*maxd)
+
+ alpha = 0.99 # to keep index within bounds
xre = sample.real
xim = sample.imag
- xre = int((maxd + min(maxd, max(-maxd, xre))) * scale)
- xim = int((maxd + min(maxd, max(-maxd, xim))) * scale)
- index = int(xre + lut_scale*xim)
+ xre = ((maxd + min(alpha*maxd, max(-alpha*maxd, xre))) * scale)
+ xim = ((maxd + min(alpha*maxd, max(-alpha*maxd, xim))) * scale)
+ index = int(xre) + lut_scale*int(xim)
max_index = lut_scale**2
- if(index > max_index):
- return table[0];
- if(index < 0):
- raise RuntimeError("calc_from_table: input sample out of range.")
+ while(index >= max_index):
+ index -= lut_scale;
+ while(index < 0):
+ index += lut_scale;
- return table[index]
+ return table[int(index)]
def calc_soft_dec(sample, constel, symbols, npwr=1):
'''
@@ -192,25 +192,21 @@ def calc_soft_dec(sample, constel, symbols, npwr=1):
than 0 are more likely to indicate a '0' bit and decisions greater
than 0 are more likely to indicate a '1' bit.
'''
-
+
M = len(constel)
k = int(numpy.log2(M))
tmp = 2*k*[0,]
s = k*[0,]
- # Find a scaling factor for the constellation, however it was normalized.
- constel = numpy.array(constel)
- scale = min(min(abs(constel.real)), min(abs(constel.imag)))
-
for i in range(M):
# Calculate the distance between the sample and the current
# constellation point.
- dist = abs(sample - constel[i])**2
+ dist = abs(sample - constel[i])
# Calculate the probability factor from the distance and the
# scaled noise power.
- d = numpy.exp(-dist/(2*npwr*scale**2))
-
+ d = numpy.exp(-dist/npwr)
+
for j in range(k):
# Get the bit at the jth index
mask = 1<<j
@@ -222,11 +218,37 @@ def calc_soft_dec(sample, constel, symbols, npwr=1):
# else, add to the probability of a one
else:
tmp[2*j+1] += d
-
+
# Calculate the log-likelihood ratio for all bits based on the
# probability of ones (tmp[2*i+1]) over the probability of a zero
# (tmp[2*i+0]).
for i in range(k):
- s[k-1-i] = (numpy.log(tmp[2*i+1]) - numpy.log(tmp[2*i+0])) * scale**2
+ s[k-1-i] = (numpy.log(tmp[2*i+1]) - numpy.log(tmp[2*i+0]))
return s
+
+
+def show_table(table):
+ prec = int(numpy.sqrt(len(table)))
+ nbits = len(table[0])
+ pp = ""
+ subi = 1
+ subj = 0
+ for i in reversed(xrange(prec+1)):
+ if(i == prec//2):
+ pp += "-----" + prec*((nbits*8)+3)*"-" + "\n"
+ subi = 0
+ continue
+ for j in xrange(prec+1):
+ if(j == prec//2):
+ pp += "| "
+ subj = 1
+ else:
+ item = table[prec*(i-subi) + (j-subj)]
+ pp += "( "
+ for t in xrange(nbits-1, -1, -1):
+ pp += "{0: .4f} ".format(item[t])
+ pp += ") "
+ pp += "\n"
+ subj = 0
+ print pp