├── SNR_plot_data_for_LSE.mat ├── SNR_plot_data_for_MMSE.mat ├── data └── desktop.ini ├── OFDM.py └── OFDM_with_pytorch.py /SNR_plot_data_for_LSE.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohsequ/OFDM-System-Implementation-in-Python/HEAD/SNR_plot_data_for_LSE.mat -------------------------------------------------------------------------------- /SNR_plot_data_for_MMSE.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohsequ/OFDM-System-Implementation-in-Python/HEAD/SNR_plot_data_for_MMSE.mat -------------------------------------------------------------------------------- /data/desktop.ini: -------------------------------------------------------------------------------- 1 | [.ShellClassInfo] 2 | ConfirmFileOp=0 3 | IconResource=C:\Program Files\Google\Drive File Stream\56.0.11.0\GoogleDriveFS.exe,23 4 | -------------------------------------------------------------------------------- /OFDM.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import scipy 4 | import scipy.interpolate 5 | from scipy.io import savemat 6 | import os 7 | from tqdm import tqdm 8 | 9 | class OFDM: 10 | def __init__(self, no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame, SNRdb) -> None: 11 | 12 | self.K = no_of_OFDM_subcarriers # number of OFDM subcarriers 13 | self.CP = self.K//4 # length of the cyclic prefix: 25% of the block 14 | 15 | 16 | self.P = 8 # number of pilot carriers per OFDM block 17 | self.pilotValue = 3+3j # The known value each pilot transmits 18 | 19 | self.allCarriers = np.arange(self.K) # indices of all subcarriers ([0, 1, ... K-1]) 20 | 21 | self.pilotCarriers = self.allCarriers[::self.K//self.P] # Pilots is every (K/P)th carrier. 22 | 23 | # For convenience of channel estimation, let's make the last carriers also be a pilot 24 | self.pilotCarriers = np.hstack([self.pilotCarriers, np.array([self.allCarriers[-1]])]) 25 | self.P = self.P+1 26 | 27 | # data carriers are all remaining carriers 28 | self.dataCarriers = np.delete(self.allCarriers, self.pilotCarriers) 29 | 30 | self.QAM_string = "16_QAM" 31 | self.mu = 4 # bits per symbol (i.e. 16QAM) 32 | self.payloadBits_per_OFDM = len(self.dataCarriers)*self.mu # number of payload bits per OFDM symbol 33 | 34 | self.set_QAM_mapping_table() 35 | 36 | self.demapping_table = {v : k for k, v in self.mapping_table.items()} 37 | 38 | # self.channelResponse = np.array([1, 0, 0.3+0.3j]) # the impulse response of the wireless channel 39 | # self.channelResponse = np.array([1]) # the impulse response of the wireless channel 40 | 41 | # self.H_exact = np.fft.fft(self.channelResponse, self.K) 42 | # plt.plot(self.allCarriers, abs(self.H_exact)) 43 | 44 | self.SNRdb = SNRdb # signal to noise-ratio in dB at the receiver 45 | 46 | self.ofdmSymbolsPerFrame = no_of_OFDM_symbols_per_frame 47 | 48 | self.no_taps=5 49 | self.H_exact=np.zeros((self.ofdmSymbolsPerFrame,self.K),dtype=np.complex64) 50 | self.channelResponse=np.zeros((self.ofdmSymbolsPerFrame,self.no_taps),dtype=np.complex64) 51 | self.center_freq = 5.16 52 | 53 | self.bits_stream = np.zeros((self.ofdmSymbolsPerFrame, self.payloadBits_per_OFDM)) 54 | self.OFDM_Transmitted_frames = np.zeros((self.ofdmSymbolsPerFrame, self.K+self.CP), dtype =np.complex64) 55 | self.OFDM_Channel_frames = np.zeros((self.ofdmSymbolsPerFrame, self.K+self.CP), dtype =np.complex64) 56 | self.OFDM_receiver_Demod = np.zeros((self.ofdmSymbolsPerFrame, self.K), dtype=np.complex64) 57 | 58 | # Variables for MMSE 59 | self.beta = 17/9 # For 16 QAM 60 | 61 | def set_QAM_mapping_table(self): 62 | if self.QAM_string == "16_QAM": 63 | self.mapping_table = { 64 | (0,0,0,0) : -3-3j, 65 | (0,0,0,1) : -3-1j, 66 | (0,0,1,0) : -3+3j, 67 | (0,0,1,1) : -3+1j, 68 | (0,1,0,0) : -1-3j, 69 | (0,1,0,1) : -1-1j, 70 | (0,1,1,0) : -1+3j, 71 | (0,1,1,1) : -1+1j, 72 | (1,0,0,0) : 3-3j, 73 | (1,0,0,1) : 3-1j, 74 | (1,0,1,0) : 3+3j, 75 | (1,0,1,1) : 3+1j, 76 | (1,1,0,0) : 1-3j, 77 | (1,1,0,1) : 1-1j, 78 | (1,1,1,0) : 1+3j, 79 | (1,1,1,1) : 1+1j 80 | } 81 | elif self.QAM_string == "4_QAM": 82 | self.mapping_table = { 83 | (0,0) : -1-1j, 84 | (0,1) : -1+1j, 85 | (1,0) : +1-1j, 86 | (1,1) : +1+1j 87 | } 88 | 89 | def OFDM_Frame_Transmitter(self): 90 | for i in range(self.ofdmSymbolsPerFrame): 91 | bits = np.random.binomial(n=1, p=0.5, size=(self.payloadBits_per_OFDM, )) 92 | self.bits_stream[i, :] = bits 93 | self.OFDM_Transmitted_frames[i, :] = self.OFDM_transmitter(bits) 94 | 95 | 96 | return self.OFDM_Transmitted_frames 97 | 98 | def OFDM_Frame_Receiver(self, OFDM_RX_frame): 99 | OFDM_RX_matrix = OFDM_RX_frame.reshape((self.ofdmSymbolsPerFrame, self.K+self.CP)) 100 | H_est_matrix = np.zeros(( self.ofdmSymbolsPerFrame, self.K), dtype=np.complex64) 101 | for i,OFDM_sym in enumerate(OFDM_RX_matrix): 102 | self.OFDM_receiver_Demod[i,:] = self.OFDM_receiver(OFDM_sym) 103 | 104 | # Hest = self.channelEstimate(self.OFDM_receiver_Demod[i,:]) 105 | Hest = self.MMSE_channelEstimate(self.OFDM_receiver_Demod[i,:], i) 106 | H_est_matrix[i,:] = Hest 107 | 108 | return H_est_matrix, self.OFDM_receiver_Demod 109 | 110 | def OFDM_transmitter(self, bits): 111 | # Randomly generated bits 112 | self.bits = bits 113 | 114 | # Serial To Parallel Converter which groups data into bits per Symbol 115 | def SerialToParallel(bits): 116 | return bits.reshape((len(self.dataCarriers), self.mu)) 117 | self.bits_SP = SerialToParallel(self.bits) 118 | 119 | # Map the bits to QAM complex mapping. 120 | def Mapping(bits): 121 | return np.array([self.mapping_table[tuple(b)] for b in bits]) 122 | QAM = Mapping(self.bits_SP) 123 | 124 | # Prepare One OFDM Symbol. 125 | def OFDM_symbol(QAM_payload): 126 | symbol = np.zeros(self.K, dtype=complex) # the overall K subcarriers 127 | symbol[self.pilotCarriers] = self.pilotValue # allocate the pilot subcarriers 128 | symbol[self.dataCarriers] = QAM_payload # allocate the pilot subcarriers 129 | return symbol 130 | self.OFDM_data = OFDM_symbol(QAM) 131 | # print ("Number of OFDM carriers in frequency domain: ", len(self.OFDM_data)) 132 | 133 | # Compute IFFT to convert frequency domain to Time domain 134 | def IDFT(OFDM_data): 135 | return np.fft.ifft(OFDM_data) 136 | self.OFDM_time = IDFT(self.OFDM_data) 137 | # print ("Number of OFDM samples in time-domain before CP: ", len(self.OFDM_time)) 138 | 139 | # Add Cyclic Prefix bits to the Received Time signals. 140 | def addCyclicPrefix(OFDM_time): 141 | cp = OFDM_time[-self.CP:] # take the last CP samples ... 142 | return np.hstack([cp, OFDM_time]) # ... and add them to the beginning 143 | self.OFDM_withCP = addCyclicPrefix(self.OFDM_time) 144 | # print ("Number of OFDM samples in time domain with CP: ", len(self.OFDM_withCP)) 145 | return self.OFDM_withCP 146 | 147 | def generate_random_taps(self, nsymbol): 148 | for i in range(self.no_taps): 149 | list_rx = [np.random.uniform(0,1) for i in range(self.no_taps-1)] 150 | list_rx=np.sort(list_rx) 151 | 152 | tou= np.hstack((0,list_rx)) 153 | 154 | ray= np.hstack((1,np.random.rayleigh(0.5, self.no_taps-1))) 155 | self.channelResponse[nsymbol, i] = ray[i]*np.exp(-1j*2*self.center_freq*tou[i]) 156 | self.H_exact[nsymbol, :] = np.fft.fft(self.channelResponse[nsymbol,:], self.K) 157 | 158 | def Channel(self, OFDM_TX): 159 | for i in range(self.ofdmSymbolsPerFrame): 160 | self.generate_random_taps(i) 161 | convolved = np.convolve(OFDM_TX[i,:], self.channelResponse[i,:], mode='full') 162 | convolved = convolved[:(self.K+self.CP)] 163 | # Generate complex noise with given variance 164 | signal_power = np.mean(abs(convolved**2)) 165 | sigma2 = signal_power * 10**(-self.SNRdb/10) # calculate noise power based on signal power and SNR 166 | # print ("RX Signal power: %.4f. Noise power: %.4f" % (signal_power, sigma2)) 167 | noise = np.sqrt(sigma2/2) * (np.random.randn(*convolved.shape)+1j*np.random.randn(*convolved.shape)) 168 | self.OFDM_Channel_frames[i,:] = convolved + noise 169 | return np.hstack(self.OFDM_Channel_frames) 170 | 171 | def OFDM_receiver(self, OFDM_RX_CP): 172 | 173 | # Remove Cyclic Prefix bits from the Received Time signals. 174 | def removeCP(signal): 175 | return signal[self.CP:(self.CP+self.K)] 176 | self.OFDM_RX_noCP = removeCP(OFDM_RX_CP) 177 | 178 | # Demodulation from Time to Frequency using FFT 179 | def DFT(OFDM_RX): 180 | return np.fft.fft(OFDM_RX) 181 | self.OFDM_demod = DFT(self.OFDM_RX_noCP) 182 | 183 | return self.OFDM_demod 184 | 185 | def channelEstimate(self, OFDM_demod): 186 | pilots = OFDM_demod[self.pilotCarriers] # extract the pilot values from the RX signal 187 | Hest_at_pilots = pilots / self.pilotValue # divide by the transmitted pilot values 188 | 189 | # Perform interpolation between the pilot carriers to get an estimate 190 | # of the channel in the data carriers. Here, we interpolate absolute value and phase 191 | # separately 192 | Hest_abs = scipy.interpolate.interp1d(self.pilotCarriers, abs(Hest_at_pilots), kind='linear')(self.allCarriers) 193 | Hest_phase = scipy.interpolate.interp1d(self.pilotCarriers, np.angle(Hest_at_pilots), kind='linear')(self.allCarriers) 194 | Hest = Hest_abs * np.exp(1j*Hest_phase) 195 | 196 | # plt.plot(self.allCarriers, abs(self.H_exact), label='Correct Channel') 197 | # plt.stem(self.pilotCarriers, abs(Hest_at_pilots), label='Pilot estimates') 198 | # plt.plot(self.allCarriers, abs(Hest), label='Estimated channel via interpolation') 199 | # plt.grid(True); plt.xlabel('Carrier index'); plt.ylabel('$|H(f)|$'); plt.legend(fontsize=10) 200 | # plt.ylim(0,2) 201 | 202 | return Hest 203 | 204 | def MMSE_channelEstimate(self, OFDM_demod, nsymbol): 205 | # import pdb;pdb.set_trace() 206 | C_response = np.array(self.H_exact[nsymbol,:]).reshape(-1,1) 207 | C_response_H = np.conj(C_response).T 208 | R_HH = np.matmul(C_response, C_response_H) 209 | snr = 10**(self.SNRdb/10) 210 | # W = R_HH/(R_HH+(self.beta/snr)*np.eye(self.K)) 211 | W = np.matmul(R_HH,np.linalg.inv((R_HH+(self.beta/snr)*np.eye(self.K)))) 212 | HhatLS = self.channelEstimate(OFDM_demod) 213 | HhatLS = HhatLS.reshape(-1,1) 214 | HhatLMMSE = np.matmul(W, HhatLS) 215 | 216 | return HhatLMMSE.squeeze() 217 | 218 | def OFDM_equalize(self, OFDM_demod, Hest): 219 | return OFDM_demod / Hest 220 | 221 | def get_payload_stream(self, equalized): 222 | return equalized[:, self.dataCarriers] 223 | 224 | def get_payload(self, equalized): 225 | return equalized[self.dataCarriers] 226 | 227 | def Demapping_payload_stream(self, QAM): 228 | demapped_bit_stream = np.zeros((self.ofdmSymbolsPerFrame, len(self.dataCarriers), self.mu)) 229 | hardDecision_stream = np.zeros((self.ofdmSymbolsPerFrame, self.dataCarriers.shape[0]), dtype =np.complex64) 230 | for i, QAM_OFDM_symbol in enumerate(QAM): 231 | demapped_bits, hardDecision = self.Demapping(QAM_OFDM_symbol) 232 | demapped_bit_stream[i,:,:] = demapped_bits 233 | hardDecision_stream[i,:] = hardDecision 234 | return demapped_bit_stream, hardDecision_stream 235 | 236 | def Demapping(self, QAM): 237 | # array of possible constellation points 238 | constellation = np.array([x for x in self.demapping_table.keys()]) 239 | 240 | # calculate distance of each RX point to each possible point 241 | dists = abs(QAM.reshape((-1,1)) - constellation.reshape((1,-1))) 242 | 243 | # for each element in QAM, choose the index in constellation 244 | # that belongs to the nearest constellation point 245 | const_index = dists.argmin(axis=1) 246 | 247 | # get back the real constellation point 248 | hardDecision = constellation[const_index] 249 | 250 | # transform the constellation point into the bit groups 251 | return np.vstack([self.demapping_table[C] for C in hardDecision]), hardDecision 252 | 253 | def get_bit_stream(self): 254 | return self.bits_stream 255 | 256 | def display_carrier_distribution(self): 257 | print ("allCarriers: %s" % self.allCarriers) 258 | print ("pilotCarriers: %s" % self.pilotCarriers) 259 | print ("dataCarriers: %s" % self.dataCarriers) 260 | plt.plot(self.pilotCarriers, np.zeros_like(self.pilotCarriers), 'bo', label='pilot') 261 | plt.plot(self.dataCarriers, np.zeros_like(self.dataCarriers), 'ro', label='data') 262 | 263 | def display_constellation_map(self): 264 | for b3 in [0, 1]: 265 | for b2 in [0, 1]: 266 | for b1 in [0, 1]: 267 | for b0 in [0, 1]: 268 | B = (b3, b2, b1, b0) 269 | Q = self.mapping_table[B] 270 | plt.plot(Q.real, Q.imag, 'bo') 271 | plt.text(Q.real, Q.imag+0.2, "".join(str(x) for x in B), ha='center') 272 | 273 | 274 | def main(): 275 | 276 | # no_of_epochs = 100000 277 | SNRdb = 1 278 | no_of_OFDM_subcarriers = 64 279 | no_of_OFDM_symbols_per_frame = 14 280 | no_of_epochs = 100000 281 | 282 | if not os.path.isdir(f"data\Dataset_for_{SNRdb}db"): 283 | os.mkdir(f"data\Dataset_for_{SNRdb}db") 284 | 285 | 286 | print(f"Creating Dataset for SNR: {SNRdb} dB") 287 | perfect_dataset = np.zeros((no_of_epochs, no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame), dtype=np.complex64) 288 | noisy_dataset = np.zeros((no_of_epochs, no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame), dtype=np.complex64) 289 | for epoch in tqdm(range(no_of_epochs)): 290 | OFDM_system = OFDM(no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame, SNRdb) 291 | # print(f"SNR set is: {OFDM_system.SNRdb} dB") 292 | # bits = np.random.binomial(n=1, p=0.5, size=(OFDM_system.payloadBits_per_OFDM, )) 293 | # OFDM_TX = OFDM_system.OFDM_transmitter(bits) 294 | 295 | OFDM_TX = OFDM_system.OFDM_Frame_Transmitter() 296 | 297 | OFDM_RX = OFDM_system.Channel(OFDM_TX) 298 | 299 | # plt.figure(figsize=(8,2)) 300 | # plt.plot(abs(OFDM_TX), label='TX signal') 301 | # plt.plot(abs(OFDM_RX), label='RX signal') 302 | # plt.legend(fontsize=10) 303 | # plt.xlabel('Time'); plt.ylabel('$|x(t)|$') 304 | # plt.grid(True) 305 | # plt.show() 306 | 307 | H_est_matrix, OFDM_Frame_demod = OFDM_system.OFDM_Frame_Receiver(OFDM_RX) 308 | 309 | equalized_Hest = OFDM_system.OFDM_equalize(OFDM_Frame_demod, H_est_matrix) 310 | 311 | QAM_est = OFDM_system.get_payload_stream(equalized_Hest) 312 | 313 | PS_est, hardDecision = OFDM_system.Demapping_payload_stream(QAM_est) 314 | 315 | # def ParallelToSerial(bits): 316 | # return bits.reshape((-1,)) 317 | # bits_est = ParallelToSerial(PS_est) 318 | 319 | # bits_transmitted = OFDM_system.get_bit_stream() 320 | # bits_transmitted = bits_transmitted.reshape((-1,)) 321 | 322 | perfect_dataset[epoch,:,:] = OFDM_system.H_exact.T 323 | noisy_dataset[epoch,:,:] = OFDM_system.OFDM_receiver_Demod.T 324 | ######print ("Obtained Bit error rate: ", np.sum(abs(bits_transmitted-bits_est))/len(bits_transmitted)) 325 | # for qam, hard in zip(QAM_est, hardDecision): 326 | # plt.plot([qam.real, hard.real], [qam.imag, hard.imag], 'b-o'); 327 | # plt.plot(hardDecision.real, hardDecision.imag, 'ro') 328 | 329 | 330 | # plt.plot(QAM_est.real, QAM_est.imag, 'bo') 331 | 332 | savemat(f"data/Dataset_for_{SNRdb}db/Perfect_H_dataset_for_{SNRdb}dB", {"Perfect_H": perfect_dataset}) 333 | savemat(f"data/Dataset_for_{SNRdb}db/Noisy_H_dataset_for_{SNRdb}dB", {"Noisy_H": noisy_dataset}) 334 | 335 | 336 | 337 | 338 | main() -------------------------------------------------------------------------------- /OFDM_with_pytorch.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import scipy 4 | import scipy.interpolate 5 | from scipy.io import savemat 6 | import os 7 | from tqdm import tqdm 8 | 9 | class OFDM: 10 | def __init__(self, no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame, SNRdb) -> None: 11 | 12 | self.K = no_of_OFDM_subcarriers # number of OFDM subcarriers 13 | self.CP = self.K//4 # length of the cyclic prefix: 25% of the block 14 | 15 | 16 | self.P = 8 # number of pilot carriers per OFDM block 17 | self.pilotValue = 3+3j # The known value each pilot transmits 18 | 19 | self.allCarriers = np.arange(self.K) # indices of all subcarriers ([0, 1, ... K-1]) 20 | 21 | self.pilotCarriers = self.allCarriers[::self.K//self.P] # Pilots is every (K/P)th carrier. 22 | 23 | # For convenience of channel estimation, let's make the last carriers also be a pilot 24 | self.pilotCarriers = np.hstack([self.pilotCarriers, np.array([self.allCarriers[-1]])]) 25 | self.P = self.P+1 26 | 27 | # data carriers are all remaining carriers 28 | self.dataCarriers = np.delete(self.allCarriers, self.pilotCarriers) 29 | 30 | self.QAM_string = "16_QAM" 31 | self.mu = 4 # bits per symbol (i.e. 16QAM) 32 | self.payloadBits_per_OFDM = len(self.dataCarriers)*self.mu # number of payload bits per OFDM symbol 33 | 34 | self.set_QAM_mapping_table() 35 | 36 | self.demapping_table = {v : k for k, v in self.mapping_table.items()} 37 | 38 | # self.channelResponse = np.array([1, 0, 0.3+0.3j]) # the impulse response of the wireless channel 39 | # self.channelResponse = np.array([1]) # the impulse response of the wireless channel 40 | 41 | # self.H_exact = np.fft.fft(self.channelResponse, self.K) 42 | # plt.plot(self.allCarriers, abs(self.H_exact)) 43 | 44 | self.SNRdb = SNRdb # signal to noise-ratio in dB at the receiver 45 | 46 | self.ofdmSymbolsPerFrame = no_of_OFDM_symbols_per_frame 47 | 48 | self.no_taps=5 49 | self.H_exact=np.zeros((self.ofdmSymbolsPerFrame,self.K),dtype=np.complex64) 50 | self.channelResponse=np.zeros((self.ofdmSymbolsPerFrame,self.no_taps),dtype=np.complex64) 51 | self.center_freq = 5.16 52 | 53 | self.bits_stream = np.zeros((self.ofdmSymbolsPerFrame, self.payloadBits_per_OFDM)) 54 | self.OFDM_Transmitted_frames = np.zeros((self.ofdmSymbolsPerFrame, self.K+self.CP), dtype =np.complex64) 55 | self.OFDM_Channel_frames = np.zeros((self.ofdmSymbolsPerFrame, self.K+self.CP), dtype =np.complex64) 56 | self.OFDM_receiver_Demod = np.zeros((self.ofdmSymbolsPerFrame, self.K), dtype=np.complex64) 57 | 58 | # Variables for MMSE 59 | self.beta = 17/9 # For 16 QAM 60 | 61 | def set_QAM_mapping_table(self): 62 | if self.QAM_string == "16_QAM": 63 | self.mapping_table = { 64 | (0,0,0,0) : -3-3j, 65 | (0,0,0,1) : -3-1j, 66 | (0,0,1,0) : -3+3j, 67 | (0,0,1,1) : -3+1j, 68 | (0,1,0,0) : -1-3j, 69 | (0,1,0,1) : -1-1j, 70 | (0,1,1,0) : -1+3j, 71 | (0,1,1,1) : -1+1j, 72 | (1,0,0,0) : 3-3j, 73 | (1,0,0,1) : 3-1j, 74 | (1,0,1,0) : 3+3j, 75 | (1,0,1,1) : 3+1j, 76 | (1,1,0,0) : 1-3j, 77 | (1,1,0,1) : 1-1j, 78 | (1,1,1,0) : 1+3j, 79 | (1,1,1,1) : 1+1j 80 | } 81 | elif self.QAM_string == "4_QAM": 82 | self.mapping_table = { 83 | (0,0) : -1-1j, 84 | (0,1) : -1+1j, 85 | (1,0) : +1-1j, 86 | (1,1) : +1+1j 87 | } 88 | 89 | def OFDM_Frame_Transmitter(self): 90 | for i in range(self.ofdmSymbolsPerFrame): 91 | bits = np.random.binomial(n=1, p=0.5, size=(self.payloadBits_per_OFDM, )) 92 | self.bits_stream[i, :] = bits 93 | self.OFDM_Transmitted_frames[i, :] = self.OFDM_transmitter(bits) 94 | 95 | 96 | return self.OFDM_Transmitted_frames 97 | 98 | def OFDM_Frame_Receiver(self, OFDM_RX_frame): 99 | OFDM_RX_matrix = OFDM_RX_frame.reshape((self.ofdmSymbolsPerFrame, self.K+self.CP)) 100 | H_est_matrix = np.zeros(( self.ofdmSymbolsPerFrame, self.K), dtype=np.complex64) 101 | for i,OFDM_sym in enumerate(OFDM_RX_matrix): 102 | self.OFDM_receiver_Demod[i,:] = self.OFDM_receiver(OFDM_sym) 103 | 104 | Hest = self.channelEstimate(self.OFDM_receiver_Demod[i,:]) 105 | # Hest = self.MMSE_channelEstimate(self.OFDM_receiver_Demod[i,:], i) 106 | H_est_matrix[i,:] = Hest 107 | 108 | return H_est_matrix, self.OFDM_receiver_Demod 109 | 110 | def OFDM_transmitter(self, bits): 111 | # Randomly generated bits 112 | self.bits = bits 113 | 114 | # Serial To Parallel Converter which groups data into bits per Symbol 115 | def SerialToParallel(bits): 116 | return bits.reshape((len(self.dataCarriers), self.mu)) 117 | self.bits_SP = SerialToParallel(self.bits) 118 | 119 | # Map the bits to QAM complex mapping. 120 | def Mapping(bits): 121 | return np.array([self.mapping_table[tuple(b)] for b in bits]) 122 | QAM = Mapping(self.bits_SP) 123 | 124 | # Prepare One OFDM Symbol. 125 | def OFDM_symbol(QAM_payload): 126 | symbol = np.zeros(self.K, dtype=complex) # the overall K subcarriers 127 | symbol[self.pilotCarriers] = self.pilotValue # allocate the pilot subcarriers 128 | symbol[self.dataCarriers] = QAM_payload # allocate the pilot subcarriers 129 | return symbol 130 | self.OFDM_data = OFDM_symbol(QAM) 131 | # print ("Number of OFDM carriers in frequency domain: ", len(self.OFDM_data)) 132 | 133 | # Compute IFFT to convert frequency domain to Time domain 134 | def IDFT(OFDM_data): 135 | return np.fft.ifft(OFDM_data) 136 | self.OFDM_time = IDFT(self.OFDM_data) 137 | # print ("Number of OFDM samples in time-domain before CP: ", len(self.OFDM_time)) 138 | 139 | # Add Cyclic Prefix bits to the Received Time signals. 140 | def addCyclicPrefix(OFDM_time): 141 | cp = OFDM_time[-self.CP:] # take the last CP samples ... 142 | return np.hstack([cp, OFDM_time]) # ... and add them to the beginning 143 | self.OFDM_withCP = addCyclicPrefix(self.OFDM_time) 144 | # print ("Number of OFDM samples in time domain with CP: ", len(self.OFDM_withCP)) 145 | return self.OFDM_withCP 146 | 147 | def generate_random_taps(self, nsymbol): 148 | for i in range(self.no_taps): 149 | list_rx = [np.random.uniform(0,1) for i in range(self.no_taps-1)] 150 | list_rx=np.sort(list_rx) 151 | 152 | tou= np.hstack((0,list_rx)) 153 | 154 | ray= np.hstack((1,np.random.rayleigh(0.5, self.no_taps-1))) 155 | self.channelResponse[nsymbol, i] = ray[i]*np.exp(-1j*2*self.center_freq*tou[i]) 156 | self.H_exact[nsymbol, :] = np.fft.fft(self.channelResponse[nsymbol,:], self.K) 157 | 158 | def Channel(self, OFDM_TX): 159 | for i in range(self.ofdmSymbolsPerFrame): 160 | self.generate_random_taps(i) 161 | convolved = np.convolve(OFDM_TX[i,:], self.channelResponse[i,:], mode='full') 162 | convolved = convolved[:(self.K+self.CP)] 163 | # Generate complex noise with given variance 164 | signal_power = np.mean(abs(convolved**2)) 165 | sigma2 = signal_power * 10**(-self.SNRdb/10) # calculate noise power based on signal power and SNR 166 | # print ("RX Signal power: %.4f. Noise power: %.4f" % (signal_power, sigma2)) 167 | noise = np.sqrt(sigma2/2) * (np.random.randn(*convolved.shape)+1j*np.random.randn(*convolved.shape)) 168 | self.OFDM_Channel_frames[i,:] = convolved + noise 169 | return np.hstack(self.OFDM_Channel_frames) 170 | 171 | def OFDM_receiver(self, OFDM_RX_CP): 172 | 173 | # Remove Cyclic Prefix bits from the Received Time signals. 174 | def removeCP(signal): 175 | return signal[self.CP:(self.CP+self.K)] 176 | self.OFDM_RX_noCP = removeCP(OFDM_RX_CP) 177 | 178 | # Demodulation from Time to Frequency using FFT 179 | def DFT(OFDM_RX): 180 | return np.fft.fft(OFDM_RX) 181 | self.OFDM_demod = DFT(self.OFDM_RX_noCP) 182 | 183 | return self.OFDM_demod 184 | 185 | def channelEstimate(self, OFDM_demod): 186 | pilots = OFDM_demod[self.pilotCarriers] # extract the pilot values from the RX signal 187 | Hest_at_pilots = pilots / self.pilotValue # divide by the transmitted pilot values 188 | 189 | # Perform interpolation between the pilot carriers to get an estimate 190 | # of the channel in the data carriers. Here, we interpolate absolute value and phase 191 | # separately 192 | Hest_abs = scipy.interpolate.interp1d(self.pilotCarriers, abs(Hest_at_pilots), kind='linear')(self.allCarriers) 193 | Hest_phase = scipy.interpolate.interp1d(self.pilotCarriers, np.angle(Hest_at_pilots), kind='linear')(self.allCarriers) 194 | Hest = Hest_abs * np.exp(1j*Hest_phase) 195 | 196 | # plt.plot(self.allCarriers, abs(self.H_exact), label='Correct Channel') 197 | # plt.stem(self.pilotCarriers, abs(Hest_at_pilots), label='Pilot estimates') 198 | # plt.plot(self.allCarriers, abs(Hest), label='Estimated channel via interpolation') 199 | # plt.grid(True); plt.xlabel('Carrier index'); plt.ylabel('$|H(f)|$'); plt.legend(fontsize=10) 200 | # plt.ylim(0,2) 201 | 202 | return Hest 203 | 204 | def MMSE_channelEstimate(self, OFDM_demod, nsymbol): 205 | # import pdb;pdb.set_trace() 206 | C_response = np.array(self.H_exact[nsymbol,:]).reshape(-1,1) 207 | C_response_H = np.conj(C_response).T 208 | R_HH = np.matmul(C_response, C_response_H) 209 | snr = 10**(self.SNRdb/10) 210 | # W = R_HH/(R_HH+(self.beta/snr)*np.eye(self.K)) 211 | W = np.matmul(R_HH,np.linalg.inv((R_HH+(self.beta/snr)*np.eye(self.K)))) 212 | HhatLS = self.channelEstimate(OFDM_demod) 213 | HhatLS = HhatLS.reshape(-1,1) 214 | HhatLMMSE = np.matmul(W, HhatLS) 215 | 216 | return HhatLMMSE.squeeze() 217 | 218 | def OFDM_equalize(self, OFDM_demod, Hest): 219 | return OFDM_demod / Hest 220 | 221 | def get_payload_stream(self, equalized): 222 | return equalized[:, self.dataCarriers] 223 | 224 | def get_payload(self, equalized): 225 | return equalized[self.dataCarriers] 226 | 227 | def Demapping_payload_stream(self, QAM): 228 | demapped_bit_stream = np.zeros((self.ofdmSymbolsPerFrame, len(self.dataCarriers), self.mu)) 229 | hardDecision_stream = np.zeros((self.ofdmSymbolsPerFrame, self.dataCarriers.shape[0]), dtype =np.complex64) 230 | for i, QAM_OFDM_symbol in enumerate(QAM): 231 | demapped_bits, hardDecision = self.Demapping(QAM_OFDM_symbol) 232 | demapped_bit_stream[i,:,:] = demapped_bits 233 | hardDecision_stream[i,:] = hardDecision 234 | return demapped_bit_stream, hardDecision_stream 235 | 236 | def Demapping(self, QAM): 237 | # array of possible constellation points 238 | constellation = np.array([x for x in self.demapping_table.keys()]) 239 | 240 | # calculate distance of each RX point to each possible point 241 | dists = abs(QAM.reshape((-1,1)) - constellation.reshape((1,-1))) 242 | 243 | # for each element in QAM, choose the index in constellation 244 | # that belongs to the nearest constellation point 245 | const_index = dists.argmin(axis=1) 246 | 247 | # get back the real constellation point 248 | hardDecision = constellation[const_index] 249 | 250 | # transform the constellation point into the bit groups 251 | return np.vstack([self.demapping_table[C] for C in hardDecision]), hardDecision 252 | 253 | def get_bit_stream(self): 254 | return self.bits_stream 255 | 256 | def display_carrier_distribution(self): 257 | print ("allCarriers: %s" % self.allCarriers) 258 | print ("pilotCarriers: %s" % self.pilotCarriers) 259 | print ("dataCarriers: %s" % self.dataCarriers) 260 | plt.plot(self.pilotCarriers, np.zeros_like(self.pilotCarriers), 'bo', label='pilot') 261 | plt.plot(self.dataCarriers, np.zeros_like(self.dataCarriers), 'ro', label='data') 262 | 263 | def display_constellation_map(self): 264 | for b3 in [0, 1]: 265 | for b2 in [0, 1]: 266 | for b1 in [0, 1]: 267 | for b0 in [0, 1]: 268 | B = (b3, b2, b1, b0) 269 | Q = self.mapping_table[B] 270 | plt.plot(Q.real, Q.imag, 'bo') 271 | plt.text(Q.real, Q.imag+0.2, "".join(str(x) for x in B), ha='center') 272 | 273 | 274 | def main(): 275 | 276 | # no_of_epochs = 100000 277 | # SNRdb = 1 278 | SNR_values = [5,10,15,20,25 ] 279 | no_of_OFDM_subcarriers = 64 280 | no_of_OFDM_symbols_per_frame = 14 281 | no_of_epochs = 1 282 | 283 | # if not os.path.isdir(f"data\Dataset_for_{SNRdb}db"): 284 | # os.mkdir(f"data\Dataset_for_{SNRdb}db") 285 | 286 | 287 | # print(f"Creating Dataset for SNR: {SNRdb} dB") 288 | # perfect_dataset = np.zeros((no_of_epochs, no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame), dtype=np.complex64) 289 | # noisy_dataset = np.zeros((no_of_epochs, no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame), dtype=np.complex64) 290 | data = [] 291 | for SNRdb in SNR_values: 292 | OFDM_system = OFDM(no_of_OFDM_subcarriers, no_of_OFDM_symbols_per_frame, SNRdb) 293 | print(f"SNR set is: {OFDM_system.SNRdb} dB") 294 | # bits = np.random.binomial(n=1, p=0.5, size=(OFDM_system.payloadBits_per_OFDM, )) 295 | # OFDM_TX = OFDM_system.OFDM_transmitter(bits) 296 | 297 | OFDM_TX = OFDM_system.OFDM_Frame_Transmitter() 298 | 299 | OFDM_RX = OFDM_system.Channel(OFDM_TX) 300 | 301 | # plt.figure(figsize=(8,2)) 302 | # plt.plot(abs(OFDM_TX), label='TX signal') 303 | # plt.plot(abs(OFDM_RX), label='RX signal') 304 | # plt.legend(fontsize=10) 305 | # plt.xlabel('Time'); plt.ylabel('$|x(t)|$') 306 | # plt.grid(True) 307 | # plt.show() 308 | 309 | H_est_matrix, OFDM_Frame_demod = OFDM_system.OFDM_Frame_Receiver(OFDM_RX) 310 | 311 | equalized_Hest = OFDM_system.OFDM_equalize(OFDM_Frame_demod, H_est_matrix)# OFDM_system.H_exact) 312 | 313 | QAM_est = OFDM_system.get_payload_stream(equalized_Hest) 314 | 315 | PS_est, hardDecision = OFDM_system.Demapping_payload_stream(QAM_est) 316 | 317 | def ParallelToSerial(bits): 318 | return bits.reshape((-1,)) 319 | bits_est = ParallelToSerial(PS_est) 320 | 321 | bits_transmitted = OFDM_system.get_bit_stream() 322 | bits_transmitted = bits_transmitted.reshape((-1,)) 323 | 324 | # perfect_dataset[epoch,:,:] = OFDM_system.H_exact.T 325 | # noisy_dataset[epoch,:,:] = OFDM_system.OFDM_receiver_Demod.T 326 | print ("Obtained Bit error rate: ", np.sum(abs(bits_transmitted-bits_est))/len(bits_transmitted)) 327 | # for qam, hard in zip(QAM_est, hardDecision): 328 | # plt.plot([qam.real, hard.real], [qam.imag, hard.imag], 'b-o'); 329 | # plt.plot(hardDecision.real, hardDecision.imag, 'ro') 330 | 331 | data.append((SNRdb, np.sum(abs(bits_transmitted-bits_est))/len(bits_transmitted))) 332 | 333 | 334 | # plt.plot(QAM_est.real, QAM_est.imag, 'bo') 335 | 336 | # savemat(f"data/Dataset_for_{SNRdb}db/Perfect_H_dataset_for_{SNRdb}dB", {"Perfect_H": perfect_dataset}) 337 | # savemat(f"data/Dataset_for_{SNRdb}db/Noisy_H_dataset_for_{SNRdb}dB", {"Noisy_H": noisy_dataset}) 338 | print(data) 339 | savemat(f"SNR_plot_data_for_LSE.mat", {"Data":data}) 340 | 341 | 342 | 343 | 344 | main() --------------------------------------------------------------------------------