-
Notifications
You must be signed in to change notification settings - Fork 0
/
HQAM.py
executable file
·284 lines (265 loc) · 14.4 KB
/
HQAM.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import numpy as np
import matplotlib.pyplot as plt
import sorting
def hqam(M, nn_dist=2):
# M is the order of constellation, nn_dist is the nearest neighbour dist
# The idea behind this algo is that we will create an M-ary constellation with a center point at(0,-y_1)
# and then we will shift the constellation backwards in the x-axis in order to achieve the symmetry
# of the constellation around the origin for a regular HQAM
# We have to parametrize the func in order to generate different types of HQAM
# depending on M and nn_dist
# First we check if M is the square of an even integer
even = 0 # even number that is even^2=M or the first even number that is even^2>M
for i in range(2, M, 2):
if i*i == M:
even = i
break
elif i*i > M:
even = i
break
diff = even**2 - M
symbols = np.array([])
# this works only for nn_dist = 2, something went wrong with listing comprehension below (check the bounds)
# We can observe that we have two possible arrays for the x_axis values
if diff == 0:
pos_x_axis1 = np.array([x for x in range(nn_dist//2, even+1) if x % 2 == 1]) # for 64-HQAM
neg_x_axis1 = np.array([-x for x in range(nn_dist//2, even+1) if x % 2 == 1])
pos_x_axis2 = np.array([x for x in range(nn_dist//2, even+1) if x % 2 == 0])
neg_x_axis2 = np.array([-x for x in range(0, even) if x % 2 == 0]) # for 64-HQAM
# We can observe that we have only one array for y_axis values
y_unity = np.sqrt(3*(nn_dist**2)/4) # the height of the basic equilateral triangle
pos_y_axis = np.array([y_unity/2 + y_unity*i for i in range(0, even//2)]) # for 64-HQAM not exactly xd
neg_y_axis = np.array([-y_unity/2 - y_unity*(even//2-i-1) for i in range(0, even//2)])
# build 1st quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if column % 2 == 0:
temp = np.ones(np.ceil(even//4).astype(int))*pos_x_axis1[cnt1] # the real part of the symbol
temp = temp + 1j*np.array([pos_y_axis[i] for i in range(0, even//2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even//4).astype(int)) * pos_x_axis2[cnt2]
temp = temp + 1j * np.array([pos_y_axis[i] for i in range(0, even//2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# build 2nd quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if column % 2 == 0:
temp = np.ones(np.ceil(even//4).astype(int))*neg_x_axis1[cnt1] # the ceil and astype(int) are added
# because of the posibility that we want to produce 32-HQAM and therefore even == 6
temp = temp + 1j*np.array([pos_y_axis[i] for i in range(0, even//2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even//4).astype(int)) * neg_x_axis2[cnt2]
temp = temp + 1j * np.array([pos_y_axis[i] for i in range(0, even//2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# build 3rd quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if column % 2 == 0:
temp = np.ones(np.ceil(even//4).astype(int))*neg_x_axis1[cnt1]
temp = temp + 1j*np.array([neg_y_axis[i] for i in range(0, even//2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even//4).astype(int)) * neg_x_axis2[cnt2]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even//2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# build 4th quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if column % 2 == 0:
temp = np.ones(np.ceil(even//4).astype(int))*pos_x_axis1[cnt1]
temp = temp + 1j*np.array([neg_y_axis[i] for i in range(0, even//2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even//4).astype(int)) * pos_x_axis2[cnt2]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even//2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# Now we will shift the constellation in x-axis direction
shift = np.sqrt((nn_dist/2)**2 - (y_unity/2)**2) # calculated by pythagorean theorem
for k in range(0, len(symbols)):
symbols[k] -= shift # by this move we achieve the symmetry
return symbols
else: # diff !=0
# We will assume that we can create the constellation with even * even points and then we will erase
# diff points from the constellation, we will erase them depending on their energy
# First, we have created an even^2 constellation,
# symbols is an already constructed np.array with points on the complex plane
# Now , we are going to remove the diff points from the symbols
pos_x_axis1 = np.array([x for x in range(nn_dist // 2, even + 1) if x % 2 == 1]) # for 64-HQAM
neg_x_axis1 = np.array([-x for x in range(nn_dist // 2, even + 1) if x % 2 == 1])
pos_x_axis2 = np.array([x for x in range(nn_dist // 2, even + 1) if x % 2 == 0])
neg_x_axis2 = np.array([-x for x in range(0, even) if x % 2 == 0]) # for 64-HQAM
# We can observe that we have only one array for y_axis values
y_unity = np.sqrt(3 * (nn_dist ** 2) / 4) # the height of the basic equilateral triangle
pos_y_axis = np.array([y_unity / 2 + y_unity * i for i in range(0, even // 2)]) # for 64-HQAM not exactly xd
neg_y_axis = np.array([-y_unity / 2 - y_unity * (even // 2 - i - 1) for i in range(0, even // 2)])
# build 1st quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if column % 2 == 0:
temp = np.ones(np.ceil(even // 4).astype(int)) * pos_x_axis1[cnt1] # the real part of the symbol
temp = temp + 1j * np.array([pos_y_axis[i] for i in range(0, even // 2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even // 4).astype(int)) * pos_x_axis2[cnt2]
temp = temp + 1j * np.array([pos_y_axis[i] for i in range(0, even // 2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# build 2nd quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if column % 2 == 0:
temp = np.ones(np.ceil(even // 4).astype(int)) * neg_x_axis1[cnt1]
temp = temp + 1j * np.array([pos_y_axis[i] for i in range(0, even // 2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even // 4).astype(int)) * neg_x_axis2[cnt2]
temp = temp + 1j * np.array([pos_y_axis[i] for i in range(0, even // 2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# build 3rd quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if (even//2) % 2 == 0:
if column % 2 == 0:
temp = np.ones(np.ceil(even // 4).astype(int)) * neg_x_axis1[cnt1]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even // 4).astype(int)) * neg_x_axis2[cnt2]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
else: # exception when we have 32-HQAM even = 6 and even//2 = 3
if column % 2 == 0:
temp = np.ones(np.ceil(even // 4).astype(int)) * neg_x_axis2[cnt1]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even // 4).astype(int)) * neg_x_axis1[cnt2]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
# build 4th quadrant
cnt1 = 0
cnt2 = 0
for column in range(0, even, 1):
if (even // 2) % 2 == 0:
if column % 2 == 0:
temp = np.ones(np.ceil(even // 4).astype(int)) * pos_x_axis1[cnt1]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even // 4).astype(int)) * pos_x_axis2[cnt2]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
else: # exception when we have 32-HQAM even = 6 and even//2 = 3
if column % 2 == 0:
temp = np.ones(np.ceil(even // 4).astype(int)) * pos_x_axis2[cnt1]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 == 0])
symbols = np.concatenate((symbols, temp))
cnt1 += 1
else:
temp = np.ones(np.ceil(even // 4).astype(int)) * pos_x_axis1[cnt2]
temp = temp + 1j * np.array([neg_y_axis[i] for i in range(0, even // 2) if i % 2 != 0])
symbols = np.concatenate((symbols, temp))
cnt2 += 1
shift = np.sqrt((nn_dist / 2) ** 2 - (y_unity / 2) ** 2)
for k in range(0, len(symbols)):
symbols[k] -= shift # by this move we achieve the symmetry
energy = [np.real(symbol)**2 + np.imag(symbol)**2 for symbol in symbols]
energy = sorting.merge(energy) # sorted in ascending order O(n*log n) algorithm complexity
erased = []
removed = 0
for k in range(0, len(symbols)):
symbol_energy = np.real(symbols[k])**2 + np.imag(symbols[k])**2 # calculate energy per symbol while traversing symbols array
# compare it to the diff-biggest elements of the energy array and if it is inside delete it
for j in range(len(energy)-1, len(energy)-diff, -1):
if energy[j] == symbol_energy:
removed += 1
erased.append(k)
break
if removed == diff:
break
print(len(erased))
symbols = np.delete(symbols, erased)
print(len(symbols))
return symbols
def IrrHQAM(M, nn_dist=2):
# we define as center point (nn_dist/2 , 0)
regular = hqam(M, 2) # a python list that contains the symbols of the regular M-HQAM
y_unity = np.sqrt(3 * (nn_dist ** 2) / 4)
x_shift = np.sqrt((nn_dist / 2) ** 2 - (y_unity / 2) ** 2)
for i in range(0, len(regular)):
regular[i] += x_shift # by this move we break the symmetry of the regular HQAM and we shift the
# constellation on the x_axis
regular[i] -= 1j*y_unity/2
# by this move we create a constellation row on the y = 0 line
# after we have made the above shifts on the x and y axis we will insert an extra row and an extra column
# extra column on -max{np.real(regular)} and extra row on max{abs(np.imag(regular))}
max_real_part = 0 # will be positive
min_imag_part = 0 # will be negative
for i in range(0, len(regular)):
if np.real(regular[i]) > max_real_part:
max_real_part = np.real(regular[i])
if np.imag(regular[i]) < min_imag_part:
min_imag_part = np.imag(regular[i])
temp = []
for i in range(0, len(regular)):
if np.real(regular[i]) == max_real_part:
temp.append(-max_real_part + 1j*np.imag(regular[i]))
if np.imag(regular[i]) == min_imag_part:
temp.append(np.real(regular[i]) + 1j*abs(min_imag_part))
temp = np.array(temp)
temp = np.concatenate((regular, temp))
#now we will create a hash map between symbols index and its energy
hash_map = {
}
for i in range(len(temp)):
hash_map[i] = np.real(temp[i])**2 + np.imag(temp[i])**2
hash_map = sorted(hash_map.items(), key=lambda x: x[1]) # we sort the hash_map with respect to energy
idx = 0
irregular = []
while idx < M:
irregular.append(temp[hash_map[idx][0]]) # we create the irregular constelation by selecting the
# the lowest M energies from the hash_map
idx += 1
irregular = np.array(irregular)
return irregular
if __name__ == '__main__':
constellation = IrrHQAM(16, 2) # Generate Constellation with nn_dist = 2
n = np.random.normal(0,0.1,10000) + 1j*np.random.normal(0,0.1,10000) # AWGN np.random.normal(mean, sigma, size)
#by changing sigma we change noise power and because energy per symol is fixed due to standard nn_dist we can
#change SNR parameter
noise_power = 0.5 # SNR parameter
r = np.random.choice(constellation, 10000) + n # received symbol
energy = [np.real(symbol)**2 + np.imag(symbol)**2 for symbol in constellation]
total = 0
for k in energy:
total += k
Es = total/16
plt.plot(np.real(r), np.imag(r), '.')
plt.grid(True)
plt.show()