summaryrefslogtreecommitdiff
path: root/gr-digital/python/digital/soft_dec_lut_gen.py
diff options
context:
space:
mode:
authorTom Rondeau <tom@trondeau.com>2013-09-04 15:44:20 -0400
committerTom Rondeau <tom@trondeau.com>2013-09-04 15:44:20 -0400
commit02fd90029a1ec465f7c8da4a0d97dcdb8b3f438f (patch)
tree9bc144a9cb5b0e877108302d13181e50bd885528 /gr-digital/python/digital/soft_dec_lut_gen.py
parentedf2ac24c9c2f723eb80918de7addf6d299958c2 (diff)
digital: Python functions to support soft decision making and look-up table generation.
Diffstat (limited to 'gr-digital/python/digital/soft_dec_lut_gen.py')
-rw-r--r--gr-digital/python/digital/soft_dec_lut_gen.py232
1 files changed, 232 insertions, 0 deletions
diff --git a/gr-digital/python/digital/soft_dec_lut_gen.py b/gr-digital/python/digital/soft_dec_lut_gen.py
new file mode 100644
index 0000000000..24d8bbdc2e
--- /dev/null
+++ b/gr-digital/python/digital/soft_dec_lut_gen.py
@@ -0,0 +1,232 @@
+#!/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
+ soft decisions for the constellation/bit mapping at any given
+ point in the complex space, (x,y).
+
+ The table is built to a precision specified by the 'prec'
+ argument. There are (2x2)^prec samples in the sample space, so we
+ get the precision of 2^prec samples in both the real and imaginary
+ axes.
+
+ The space is represented where index 0 is the bottom left corner
+ and the maximum index is the upper left. The table index for a
+ surface space with 4 bits of precision looks like the following:
+
+ 240 241 242 243 244 245 246 247 | 248 249 250 251 252 253 254 255
+ 224 225 226 227 228 229 230 231 | 232 233 234 235 236 237 238 239
+ 208 209 210 211 212 213 214 215 | 216 217 218 219 220 221 222 223
+ 192 193 194 195 196 197 198 199 | 200 201 202 203 204 205 206 207
+ 176 177 178 179 180 181 182 183 | 184 185 186 187 188 189 190 191
+ 160 161 162 163 164 165 166 167 | 168 169 170 171 172 173 174 175
+ 144 145 146 147 148 149 150 151 | 152 153 154 155 156 157 158 159
+ 128 129 130 131 132 133 134 135 | 136 137 138 139 140 141 142 143
+ -----------------------------------------------------------------
+ 112 113 114 115 116 117 118 119 | 120 121 122 123 124 125 126 127
+ 96 97 98 99 100 101 102 103 | 104 105 106 107 108 109 110 111
+ 80 81 82 83 84 85 86 87 | 88 89 90 91 92 93 94 95
+ 64 65 66 67 68 69 70 71 | 72 73 74 75 76 77 78 79
+ 48 49 50 51 52 53 54 55 | 56 57 58 59 60 61 62 63
+ 32 33 34 35 36 37 38 39 | 40 41 42 43 44 45 46 47
+ 16 17 18 19 20 21 22 23 | 24 25 26 27 28 29 30 31
+ 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
+
+ We then calculate coordinates from -1 to 1 with 2^prec points for
+ both the x and y axes. We then sample starting at (-1, -1) and
+ move left to right on the x-axis and then move up a row on the
+ y-axis. For every point in this sampled space, we calculate the
+ soft decisions for the given constellation/mapping. This is done
+ by passing in the function 'soft_dec_gen' as an argument to this
+ function. This takes in the x/y coordinates and outputs the soft
+ decisions. These soft decisions are stored into the list at the
+ index from the above table as a tuple.
+
+ The function 'calc_from_table' takes in a point and reverses this
+ operation. It converts the point from the coordinates (-1,-1) to
+ (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,
+ 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
+ yrng = numpy.linspace(-maxd, maxd, npts)
+ xrng = numpy.linspace(-maxd, maxd, npts)
+
+ table = []
+ for y in yrng:
+ for x in xrng:
+ pt = complex(x, y)
+ decs = soft_dec_gen(pt, Es)
+ table.append(decs)
+ return table
+
+def soft_dec_table(constel, symbols, prec, npwr=1):
+ '''
+ Similar in nature to soft_dec_table_generator above. Instead, this
+ takes in the constellation and symbol points along with the noise
+ power estimate and uses calc_soft_dec (below) to generate the
+ LUT.
+
+ Instead of assuming that the constellation is normalied (e.g., all
+ points are between -1 and 1), this function calculates the min/max
+ of both the real and imaginary axes and uses those when
+ constructing the LUT. So when using this version of the LUT, the
+ samples and the constellations must be working on the same
+ magnitudes.
+
+ Because this uses the calc_soft_dec function, it can be quite
+ a bit more expensive to generate the LUT, though it should be
+ one-time work.
+ '''
+
+ re_min = min(numpy.array(constel).real)
+ im_min = min(numpy.array(constel).imag)
+ re_max = max(numpy.array(constel).real)
+ im_max = max(numpy.array(constel).imag)
+
+ npts = 2.0**prec
+ yrng = numpy.linspace(im_min, im_max, npts)
+ xrng = numpy.linspace(re_min, re_max, npts)
+
+ table = []
+ for y in yrng:
+ for x in xrng:
+ pt = complex(x, y)
+ decs = calc_soft_dec(pt, constel, symbols, npwr)
+ table.append(decs)
+ return table
+
+def calc_soft_dec_from_table(sample, table, prec, Es=1):
+ '''
+ 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
+ location in the provided LUT 'table' and returns the soft
+ decisions tuple at that index.
+
+ sample: the complex sample to calculate the soft decisions
+ from.
+
+ table: the LUT.
+
+ prec: the precision used when generating the LUT.
+
+ Es: 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,
+ normalized so the outside points sit on +/-1, etc.) but still
+ 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
+
+ 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)
+
+ 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.")
+
+ return table[index]
+
+def calc_soft_dec(sample, constel, symbols, npwr=1):
+ '''
+ This function takes in any consteallation and symbol symbol set
+ (where symbols[i] is the set of bits at constellation point
+ constel[i] and an estimate of the noise power and produces the
+ soft decisions for the given sample.
+
+ If known, the noise power of the received sample may be passed in
+ to this function as npwr.
+
+ This is an incredibly costly algorthm because it must calculate
+ the Euclidean distance between the sample and all points in the
+ constellation to build up its probability
+ calculations. Conversely, it should work for any given
+ constellation/symbol map.
+
+ The function returns a vector of k soft decisions. Decisions less
+ 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
+
+ # Calculate the probability factor from the distance and the
+ # scaled noise power.
+ d = numpy.exp(-dist/(2*npwr*scale**2))
+
+ for j in range(k):
+ # Get the bit at the jth index
+ mask = 1<<j
+ bit = (symbols[i] & mask) >> j
+
+ # If the bit is a 0, add to the probability of a zero
+ if(bit == 0):
+ tmp[2*j+0] += d
+ # 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
+
+ return s