Skip to content

Commit c3f8377

Browse files
author
Alexandre Marquet
committed
Initial.
1 parent 204b913 commit c3f8377

File tree

6 files changed

+454
-0
lines changed

6 files changed

+454
-0
lines changed

PyTurbo.pyx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# distutils: language = c++
2+
3+
# Copyright 2019 Free Software Foundation, Inc.
4+
#
5+
# This is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 3, or (at your option)
8+
# any later version.
9+
#
10+
# This software is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this software; see the file COPYING. If not, write to
17+
# the Free Software Foundation, Inc., 51 Franklin Street,
18+
# Boston, MA 02110-1301, USA.
19+
20+
21+
from libcpp.vector cimport vector
22+
23+
cdef extern from "viterbi.cc":
24+
pass
25+
26+
cdef extern from "viterbi.h":
27+
cppclass viterbi:
28+
viterbi(int, int, int, vector[int], vector[int]) except +
29+
void viterbi_algorithm(int K, int S0, int, const float*, unsigned char*)
30+
int get_I()
31+
int get_S()
32+
int get_O()
33+
34+
import numpy
35+
36+
cdef class PyViterbi:
37+
cdef int I, S, O
38+
cdef viterbi* cpp_viterbi
39+
40+
def __cinit__(self, int I, int S, int O, vector[int] NS, vector[int] OS):
41+
self.cpp_viterbi = new viterbi(I, S, O, NS, OS)
42+
self.I = self.cpp_viterbi.get_I()
43+
self.S = self.cpp_viterbi.get_S()
44+
self.O = self.cpp_viterbi.get_O()
45+
46+
def __dealloc__(self):
47+
del self.cpp_viterbi
48+
49+
def viterbi_algorithm(self, K, S0, SK, float[::1] _in):
50+
cdef unsigned char[::1] _out = numpy.zeros(K, dtype=numpy.uint8)
51+
52+
self.cpp_viterbi.viterbi_algorithm(K, S0, SK, &_in[0], &_out[0])
53+
54+
return numpy.asarray(_out)

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# PyTurbo
2+
3+
A work-in-progress set of Python class (actually, Python wrappers for C++ class)
4+
implementing several basic turbo-algorithms (turbo-decoding, turbo-equalization, etc.).
5+
6+
## Currelently Implements
7+
* The Viterbi Algorithm
8+
9+
# Installation
10+
## Dependencies
11+
You will need Cython, as well as a C++ compiler.
12+
13+
## Command line
14+
```
15+
python3 setup.py install
16+
```
17+
18+
# Based on
19+
* Viterbi algorithm implementation is taken from the gr-lazyviterbi GNURadio OOT module (https://github.com/alexmrqt/gr-lazyviterbi).
20+
* Trellis description is taken for the gr-trellis module of GNURadio (https://github.com/gnuradio/gnuradio).

examples/75_cc.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from PyTurbo import PyViterbi as viterbi
2+
3+
import numpy
4+
5+
#Quick implementation of a (7,5) convolutive code encoder
6+
def encode75(msg):
7+
reg = [0,0]
8+
out_msg = numpy.zeros(2*len(msg), dtype=numpy.bool)
9+
10+
for i in range(0, len(msg)):
11+
#Compute outputs
12+
out_msg[2*i] = msg[i]^reg[1]
13+
out_msg[2*i+1] = msg[i]^reg[0]^reg[1]
14+
15+
#Update shift register
16+
reg[1] = reg[0]
17+
reg[0] = msg[i]
18+
19+
return out_msg
20+
21+
#Compute branch metrics for a pair of received bits
22+
def branch_metrics(bits_rcvd):
23+
ret_val = numpy.zeros(4, dtype=numpy.float32);
24+
cw = [[0.0,0.0], [0.0,1.0], [1.0,0.0], [1.0,1.0]] #The 4 different codewords
25+
26+
bits_rcvd = numpy.array(bits_rcvd)
27+
cw = numpy.array(cw)
28+
29+
for i in range(0, len(cw)):
30+
ret_val[i] = numpy.sum(numpy.abs(bits_rcvd-cw[i])**2)
31+
32+
return ret_val
33+
34+
#Define trellis
35+
I=2
36+
S=4
37+
O=4
38+
NS = [0, 2, \
39+
0, 2, \
40+
1, 3, \
41+
1, 3]
42+
OS = [0, 3, \
43+
3, 0, \
44+
1, 2, \
45+
2, 1]
46+
47+
#Create decoder instance
48+
dec = viterbi(I, S, O, NS, OS)
49+
50+
#Length of the message
51+
K_m = 100;
52+
#Length of the coded message
53+
K_c = 200 #Code efficiency is 1/2
54+
55+
#Generate message
56+
m = numpy.random.randint(0, 2, K_m, dtype=numpy.bool)
57+
58+
#Encode message
59+
c = encode75(m)
60+
61+
#Add noise
62+
r = c + numpy.random.normal(0.0, 0.01, K_c)
63+
64+
#Compute branch metrics
65+
bm = numpy.zeros(K_m*O, dtype=numpy.float32)
66+
for i in range(0, K_m):
67+
bm[O*i:O*(i+1)] = branch_metrics([r[2*i], r[2*i+1]])
68+
69+
#decode message
70+
m_hat = dec.viterbi_algorithm(K_m, -1, -1, bm);
71+
72+
print(numpy.array(m, dtype=numpy.int))
73+
print('...')
74+
print(m_hat)
75+
76+
##Compute BER
77+
BER = numpy.sum(numpy.abs(m-m_hat))/K_m
78+
print(BER)

setup.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from distutils.core import setup
2+
from Cython.Build import cythonize
3+
4+
setup(
5+
name="PyTurbo",
6+
ext_modules = cythonize("PyTurbo.pyx"),
7+
)

viterbi.cc

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/* -*- c++ -*- */
2+
/*
3+
* Copyright 2019 Free Software Foundation, Inc.
4+
*
5+
* This is free software; you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation; either version 3, or (at your option)
8+
* any later version.
9+
*
10+
* This software is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this software; see the file COPYING. If not, write to
17+
* the Free Software Foundation, Inc., 51 Franklin Street,
18+
* Boston, MA 02110-1301, USA.
19+
*/
20+
21+
#include "viterbi.h"
22+
23+
viterbi::viterbi(int I, int S, int O,
24+
const std::vector<int> &NS,
25+
const std::vector<int> &OS)
26+
: d_I(I), d_S(S), d_O(O)
27+
{
28+
if (NS.size() != S*I) {
29+
throw std::runtime_error("Invalid size for NS.");
30+
}
31+
d_NS = NS;
32+
33+
if (OS.size() != S*I) {
34+
throw std::runtime_error("Invalid size for OS.");
35+
}
36+
d_OS = OS;
37+
38+
generate_PS_PI();
39+
}
40+
41+
void
42+
viterbi::generate_PS_PI()
43+
{
44+
d_PS.resize(d_S);
45+
d_PI.resize(d_S);
46+
47+
for(int i=0;i<d_S;i++) {
48+
d_PS[i].resize(d_I*d_S); // max possible size
49+
d_PI[i].resize(d_I*d_S);
50+
int j=0;
51+
for(int ii=0;ii<d_S;ii++) for(int jj=0;jj<d_I;jj++) {
52+
if(d_NS[ii*d_I+jj]!=i) continue;
53+
d_PS[i][j]=ii;
54+
d_PI[i][j]=jj;
55+
j++;
56+
}
57+
d_PS[i].resize(j);
58+
d_PI[i].resize(j);
59+
}
60+
}
61+
62+
void
63+
viterbi::viterbi_algorithm(int K, int S0, int SK, const float *in,
64+
unsigned char *out)
65+
{
66+
viterbi_algorithm(d_I, d_S, d_O, d_NS, d_OS, d_PS, d_PI, K, S0, SK, in, out);
67+
}
68+
69+
void
70+
viterbi::viterbi_algorithm(int I, int S, int O, const std::vector<int> &NS,
71+
const std::vector<int> &OS, const std::vector< std::vector<int> > &PS,
72+
const std::vector< std::vector<int> > &PI, int K, int S0, int SK,
73+
const float *in, unsigned char *out)
74+
{
75+
int tb_state, pidx;
76+
float can_metric = std::numeric_limits<float>::max();
77+
float min_metric = std::numeric_limits<float>::max();
78+
79+
std::vector<int> trace(K*S);
80+
std::vector<float> alpha_prev(S, std::numeric_limits<float>::max());
81+
std::vector<float> alpha_curr(S, std::numeric_limits<float>::max());
82+
83+
std::vector<float>::iterator alpha_curr_it;
84+
std::vector<int>::const_iterator PS_it, PI_it;
85+
std::vector<int>::iterator trace_it = trace.begin();
86+
87+
//If initial state was specified
88+
if(S0 != -1) {
89+
alpha_prev[S0] = 0.0;
90+
}
91+
92+
for(float* in_k=(float*)in ; in_k < (float*)in + K*O ; in_k += O) {
93+
//Current path metric iterator
94+
alpha_curr_it = alpha_curr.begin();
95+
for(int s=0 ; s < S ; ++s) {
96+
//Iterators for previous state and previous input lists
97+
PS_it=PS[s].begin();
98+
PI_it=PI[s].begin();
99+
100+
//ACS for state s
101+
//Pre-loop
102+
*alpha_curr_it = alpha_prev[*PS_it] + in_k[OS[(*PS_it)*I + (*PI_it)]];
103+
*trace_it = 0;
104+
105+
//Loop
106+
for(size_t i=1 ; i<(PS[s]).size() ; ++i) {
107+
//Update PS/PI iterators
108+
++PS_it;
109+
++PI_it;
110+
111+
//ADD
112+
can_metric = alpha_prev[*PS_it] + in_k[OS[(*PS_it)*I + (*PI_it)]];
113+
114+
//COMPARE
115+
if(can_metric < *alpha_curr_it) {
116+
//SELECT
117+
*alpha_curr_it = can_metric;
118+
*trace_it = i; //Store previous input index for traceback
119+
}
120+
}
121+
122+
//Update trace and path metric iterator
123+
++trace_it;
124+
++alpha_curr_it;
125+
}
126+
127+
//Metrics normalization
128+
min_metric = *std::min_element(alpha_curr.begin(), alpha_curr.end());
129+
std::transform(alpha_curr.begin(), alpha_curr.end(), alpha_curr.begin(),
130+
std::bind2nd(std::minus<double>(), min_metric));
131+
132+
//At this point, current path metrics becomes previous path metrics
133+
alpha_prev.swap(alpha_curr);
134+
}
135+
136+
//If final state was specified
137+
if(SK != -1) {
138+
tb_state = SK;
139+
}
140+
else{
141+
//at this point, alpha_prev contains the path metrics of states after time K
142+
tb_state = (int)(min_element(alpha_prev.begin(), alpha_prev.end()) - alpha_prev.begin());
143+
}
144+
145+
//Traceback
146+
trace_it = trace.end() - S; //place trace_it at the last time index
147+
148+
for(unsigned char* out_k = out+K-1 ; out_k >= out ; --out_k) {
149+
//Retrieve previous input index from trace
150+
pidx=*(trace_it + tb_state);
151+
//Update trace_it for next output symbol
152+
trace_it -= S;
153+
154+
//Output previous input
155+
*out_k = (unsigned char) PI[tb_state][pidx];
156+
157+
//Update tb_state with the previous state on the shortest path
158+
tb_state = PS[tb_state][pidx];
159+
}
160+
}

0 commit comments

Comments
 (0)