Skip to content

Commit 29f1fb4

Browse files
committed
First version
1 parent de9fb3c commit 29f1fb4

File tree

11 files changed

+1270
-1
lines changed

11 files changed

+1270
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
*.pyc

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,59 @@
1-
# Pruned-DFT-s-FBMC_Python
1+
# Pruned DFT spread FBMC
2+
3+
Pruned DFT spread FBMC is a novel transmission technique with the remarkable properties of a low PAPR, low latency transmissions and a high spectral efficiency.
4+
It is closely related to FBMC, OFDM and SC-FDMA and was first proposed in my [PhD thesis](http://publik.tuwien.ac.at/files/publik_265168.pdf), see Chapter 6. A more detailed description is currently under review in IEEE Transactions on Communications.
5+
6+
7+
The Python script simulates a pruned DFT spread FBMC transmission over a doubly-selective channel (time-variant multipath propagation) and compares the performance to OFDM, SC-FDMA and FBMC.
8+
9+
10+
Furthermore, the included classes (QAM, DoublySelectiveChannel, OFDM, FBMC) can also be reused in future projects.
11+
12+
13+
14+
## Usage
15+
16+
Just run **Simulation.py** in Python.
17+
18+
Requires the packages: numpy, scipy(sparse), matplotlib, time and mpl_toolkits.mplot3d.
19+
20+
21+
22+
## Simulation Results*
23+
\* for "nr_rep = 1000"
24+
25+
### Pruned DFT spread FBMC has the same PAPR as SC-FDMA:
26+
27+
![](png/Figure_5.png)
28+
29+
----------
30+
### Pruned DFT spread FBMC outperforms SC-FDMA in doubly-selective channels:
31+
32+
![](png/Figure_2.png)
33+
34+
Note that pruned DFT spread FBMC does not require a CP and thus has a higher data rate than conventional SC-FDMA.
35+
36+
----------
37+
### Pruned DFT spread FBMC shows very good spectral properties, comparable to FBMC:
38+
39+
![](png/Figure_4.png)
40+
41+
----------
42+
### Pruned DFT spread FBMC dramatically reduces the ramp-up and ramp-down period of FBMC:
43+
![](png/Figure_3.png)
44+
45+
46+
## Block Diagram
47+
48+
### Pruned DFT spead FBMC at symbol-time-position *k*
49+
![](png/BlockDiagram.png)
50+
51+
52+
Note that the absolute time position of symbol *x*<sub>*l*,*k*</sub> is *kT* and the absolute frequency position *l F*. Furthermore, the time-frequency spacing is *TF*=1/2. The total number of subcarriers is denoted by *L*.
53+
54+
55+
## References
56+
- R. Nissel, [“Filter bank multicarrier modulation for future wireless systems”](http://publik.tuwien.ac.at/files/publik_265168.pdf), Dissertation, TU Wien, 2017.
57+
- R. Nissel and M. Rupp, “Pruned DFT spread FBMC: low PAPR, low latency, high spectral efficiency”, submitted to IEEE Transactions on Communications, not publicly available yet.
58+
59+

Simulation.py

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

comm/channel.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# =============================================================================
2+
# ========================== (c) Ronald Nissel ================================
3+
# ======================== First version: 29.03.2018 ==========================
4+
# =============================================================================
5+
6+
import numpy as np
7+
import matplotlib.pyplot as plt
8+
from scipy import sparse
9+
10+
11+
class DoublySelectiveChannel:
12+
"""
13+
DoublySelectiveChannel(pdp, nu_max, fs, N, nrp)
14+
15+
OFDM modulation and demodulation, including SC-FDMA
16+
17+
Parameters
18+
----------
19+
pdp : Power delay profile, see below.
20+
21+
nu_max : Maximum doppler shift in Hz.
22+
23+
fs: Sampling rate (Hz). Should approximately match the predefined power delay profile.
24+
25+
N: Maximum number of samples in the time domain.
26+
27+
nrp: Number of multipath propagations for the WSSUS process
28+
29+
Usage
30+
----------
31+
- ".new_realization()" generates a new random channel realizatio
32+
33+
- ".convolution( signal )" performs a convolution of the signal with the time-variant impulse response of the channel.
34+
35+
Power delay profile
36+
----------
37+
- 'TDL-A_xxns', with 'xx' beeing the rms delay spread in ns.
38+
- 'TDL-B_xxns', with 'xx' beeing the rms delay spread in ns.
39+
- 'TDL-C_xxns', with 'xx' beeing the rms delay spread in ns.
40+
- 'VehicularA'
41+
- 'PedestrianA'
42+
- 'Flat', doubly-flat Rayleigh fading (not a doubly selective channel)
43+
- 'AWGN', pure AWGN channel (not a doubly selective channel)
44+
45+
"""
46+
def __init__( self,
47+
power_delay_profile,
48+
max_doppler_shift,
49+
sampling_rate,
50+
nr_samples,
51+
nr_paths_wssus):
52+
53+
# Determine the power delay profile
54+
if power_delay_profile[:3]=='TDL':
55+
pos1 = power_delay_profile.find('_')
56+
pos2 = power_delay_profile.find('ns')
57+
desired_rms_delay_spread = int(power_delay_profile[pos1+1:pos2])*10**(-9)
58+
if power_delay_profile[4] == 'A':
59+
ref_pdp_dB = np.array([-13.4,0,-2.2,-4,-6,-8.2,-9.9,-10.5,-7.5,-15.9,-6.6,-16.7,-12.4,-15.2,-10.8,-11.3,-12.7,-16.2,-18.3,-18.9,-16.6,-19.9,-29.7])
60+
ref_pdp_delay = desired_rms_delay_spread * np.array([0.0000,0.3819,0.4025,0.5868,0.4610,0.5375,0.6708,0.5750,0.7618,1.5375,1.8978,2.2242,2.1718,2.4942,2.5119,3.0582,4.0810,4.4579,4.5695,4.7966,5.0066,5.3043,9.6586])
61+
elif power_delay_profile[4] == 'B':
62+
ref_pdp_dB = np.array([0,-2.2,-4,-3.2,-9.8,-1.2,-3.4,-5.2,-7.6,-3,-8.9,-9,-4.8,-5.7,-7.5,-1.9,-7.6,-12.2,-9.8,-11.4,-14.9,-9.2,-11.3])
63+
ref_pdp_delay = desired_rms_delay_spread * np.array([0.0000,0.1072,0.2155,0.2095,0.2870,0.2986,0.3752,0.5055,0.3681,0.3697,0.5700,0.5283,1.1021,1.2756,1.5474,1.7842,2.0169,2.8294,3.0219,3.6187,4.1067,4.2790,4.7834])
64+
elif power_delay_profile[4] == 'C':
65+
ref_pdp_dB = np.array([-4.4,-1.2,-3.5,-5.2,-2.5,0,-2.2,-3.9,-7.4,-7.1,-10.7,-11.1,-5.1,-6.8,-8.7,-13.2,-13.9,-13.9,-15.8,-17.1,-16,-15.7,-21.6,-22.8])
66+
ref_pdp_delay = desired_rms_delay_spread * np.array([0,0.2099,0.2219,0.2329,0.2176,0.6366,0.6448,0.6560,0.6584,0.7935,0.8213,0.9336,1.2285,1.3083,2.1704,2.7105,4.2589,4.6003,5.4902,5.6077,6.3065,6.6374,7.0427,8.6523])
67+
else:
68+
if power_delay_profile == 'VehicularA':
69+
ref_pdp_dB = np.array([0,-1,-9,-10,-15,-20])
70+
ref_pdp_delay = np.array([0,310e-9,710e-9,1090e-9,1730e-9,2510e-9])
71+
elif power_delay_profile == 'PedestrianA':
72+
ref_pdp_dB = np.array([0,-9.7,-19.2,-22.8])
73+
ref_pdp_delay = np.array([0,110e-9,190e-9,410e-9])
74+
elif power_delay_profile == 'Flat':
75+
ref_pdp_dB = np.array([0])
76+
ref_pdp_delay = np.array([0])
77+
elif power_delay_profile == 'AWGN':
78+
ref_pdp_dB = np.array([0])
79+
ref_pdp_delay = np.array([0])
80+
else:
81+
print("Channel model \"" + power_delay_profile + "\" is not supported")
82+
83+
# Fit the channel delay taps to the sampling rate and normalize the power delay profile
84+
index_pdp_temp = np.round(ref_pdp_delay*sampling_rate).astype(int)
85+
pdp_temp = np.zeros((max(index_pdp_temp)+1,index_pdp_temp.size))
86+
for i_index in range(index_pdp_temp.size):
87+
pdp_temp[index_pdp_temp[i_index],i_index] = 10**(ref_pdp_dB[i_index]/10)
88+
pdp = np.sum(pdp_temp,axis=1)/np.sum(pdp_temp)
89+
pdp_index = np.unique(np.sort(index_pdp_temp))
90+
91+
# Calculate RMS delay spread
92+
dt = 1/sampling_rate
93+
tau = np.arange(0,len(pdp)) * dt
94+
mean_delay = sum(tau*pdp)
95+
rms_delay_spread = np.sqrt(sum(tau**2 * pdp) - mean_delay**2);
96+
if (ref_pdp_delay/dt % 1 > np.finfo(float).eps *10).any():
97+
print('Sampling rate does not fit the channel model => RMS delay spread is changed from', str(round(desired_rms_delay_spread * 10**9)) +'ns to', str(int(round(rms_delay_spread * 10**9))) + 'ns')
98+
99+
# Atributes
100+
self.imp_pdp = pdp
101+
self.imp_pdp_index = pdp_index
102+
self.imp_pdp_string = power_delay_profile
103+
self.imp_nr_paths_wssus = nr_paths_wssus
104+
self.phy_max_doppler_shift = max_doppler_shift
105+
self.phy_sampling_rate = sampling_rate
106+
self.phy_dt = dt
107+
self.nr_samples = nr_samples
108+
109+
# Generat random channel
110+
self.new_realization()
111+
112+
def new_realization(self):
113+
"""
114+
Generate a new channel realization and save the time-variant convolution matrix in "self.imp_convolution_matrix"
115+
116+
"""
117+
if self.imp_pdp_string=='AWGN':
118+
impulse_response_squeezed = np.ones((self.nr_samples,1))
119+
elif self.imp_pdp_string=='Flat':
120+
h = np.random.randn(1) + 1j * np.random.randn(1)
121+
impulse_response_squeezed = h * np.ones((self.nr_samples,1))
122+
else:
123+
impulse_response_squeezed = np.zeros((self.nr_samples,self.imp_pdp_index.size), dtype=np.complex)
124+
t = np.arange(self.nr_samples).reshape(self.nr_samples,1)*self.phy_dt
125+
126+
# Use a for loop because it is more memory efficient (but takes longer)
127+
for i in range(self.imp_nr_paths_wssus):
128+
doppler_shifts = np.cos(np.random.random((1,self.imp_pdp_index.size))*2*np.pi) * self.phy_max_doppler_shift
129+
random_phase = np.random.random((1,self.imp_pdp_index.size))
130+
argument = 2 * np.pi * (random_phase + doppler_shifts*t)
131+
impulse_response_squeezed += np.cos(argument) + 1j*np.sin(argument)
132+
impulse_response_squeezed *= np.sqrt(self.imp_pdp[self.imp_pdp_index]/self.imp_nr_paths_wssus)
133+
134+
self.imp_convolution_matrix = sparse.dia_matrix((np.transpose(impulse_response_squeezed), -self.imp_pdp_index), shape=(self.nr_samples, self.nr_samples)).tocsr()
135+
136+
def convolution(self, signal):
137+
"""
138+
Perform convolution of the signal with the channel impulse response, saved in "self.imp_convolution_matrix"
139+
140+
Input
141+
----------
142+
signal : Array of size (M,1), with M<= N = self.nr_samples
143+
144+
Returns
145+
-------
146+
out : Array of size (M,1)
147+
148+
"""
149+
signal_length = signal.shape[0]
150+
return self.imp_convolution_matrix[:signal_length,:signal_length].dot(signal)
151+
152+
def get_transfer_function(self, index_time_positions, FFTsize, index_active_subcarriers):
153+
"""
154+
Calculates the channel transfer function
155+
156+
Parameters
157+
----------
158+
index_time_positions : array, representing the time index for which the channel transfer function is calculated
159+
160+
FFTsize : integer, fft size of the OFDM and FBMC systems
161+
162+
index_active_subcarriers: arraym, representing the subcarrier index for which the channel transfer function is calculated
163+
164+
Returns
165+
-------
166+
out : Array of size (len(index_time_positions), len(index_active_subcarriers))
167+
168+
"""
169+
transfer_function = np.zeros( (index_time_positions.size, index_active_subcarriers.size) , dtype=complex)
170+
for i_n in range(index_time_positions.size):
171+
conv_row = self.imp_convolution_matrix[index_time_positions[i_n],:index_time_positions[i_n]+1]
172+
173+
if conv_row.shape[1]>FFTsize:
174+
impulse_response = np.fliplr(conv_row[:,-FFTsize:].toarray())
175+
else:
176+
impulse_response = np.hstack( (np.fliplr(conv_row.toarray()), np.zeros((1,FFTsize-conv_row.shape[1]))) )
177+
178+
transfer_function[i_n,:] = np.fft.fft(impulse_response)[:,index_active_subcarriers]
179+
180+
return transfer_function.transpose()
181+
182+
183+
184+
185+
186+
187+
188+
189+
190+
191+
192+
193+
194+
195+
196+
197+
198+
199+
200+
201+
202+

0 commit comments

Comments
 (0)