do in-place swap
[openpower-isa.git] / src / openpower / decoder / isa / fastdctlee.py
1 #
2 # Fast discrete cosine transform algorithms (Python)
3 #
4 # Copyright (c) 2020 Project Nayuki. (MIT License)
5 # https://www.nayuki.io/page/fast-discrete-cosine-transform-algorithms
6 #
7 # License for original fastdctlee.py by Nayuki:
8 #
9 # Permission is hereby granted, free of charge, to any person obtaining
10 # a copy of this software and associated documentation files (the
11 # "Software"), to deal in the Software without restriction, including
12 # without limitation the rights to use, copy, modify, merge, publish,
13 # distribute, sublicense, and/or sell copies of the Software, and to
14 # permit persons to whom the Software is furnished to do so, subject to
15 # the following conditions:
16 # - The above copyright notice and this permission notice shall be included in
17 # all copies or substantial portions of the Software.
18 # - The Software is provided "as is", without warranty of any kind, express or
19 # implied, including but not limited to the warranties of merchantability,
20 # fitness for a particular purpose and noninfringement. In no event shall the
21 # authors or copyright holders be liable for any claim, damages or other
22 # liability, whether in an action of contract, tort or otherwise,
23 # arising from, out of or in connection with the Software or the use
24 # or other dealings in the Software.
25 #
26 #
27 # Modifications made to create an in-place iterative DCT - SPDX: LGPLv3+
28 # Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
29 #
30 # The modifications made are firstly to create an iterative schedule,
31 # rather than the more normal recursive algorithm. Secondly, the
32 # two butterflys are also separated out: inner butterfly does COS +/-
33 # whilst outer butterfly does the iterative summing.
34 #
35 # However, to avoid data copying some additional tricks are played:
36 # - firstly, the data is LOADed in bit-reversed order (which is normally
37 # covered by the recursive algorithm due to the odd-even reconstruction)
38 # but then to reference the data in the correct order an array of
39 # bit-reversed indices is created, as a level of indirection.
40 # the data is bit-reversed but so are the indices, making it all A-Ok.
41 # - secondly, normally in DCT a 2nd target (copy) array is used where
42 # the top half is read in reverse order (7 6 5 4) and written out
43 # to the target 4 5 6 7. the plan was to do this in two stages:
44 # write in-place in order 4 5 6 7 then swap afterwards (7-4), (6-5).
45 #
46 # End result is that once the first butterfly is done - bear in mind
47 # it's in-place - the data is in the right order so that a second
48 # dead-straightforward iterative sum can be done: again, in-place.
49 # Really surprising.
50
51 import math
52 from copy import deepcopy
53
54 # bits of the integer 'val'.
55 def reverse_bits(val, width):
56 result = 0
57 for _ in range(width):
58 result = (result << 1) | (val & 1)
59 val >>= 1
60 return result
61
62
63 # DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
64 # See: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.118.3056&rep=rep1&type=pdf#page=34
65 # original (recursive) algorithm by Nayuki
66 def transform(vector, indent=0):
67 idt = " " * indent
68 n = len(vector)
69 if n == 1:
70 return list(vector)
71 elif n == 0 or n % 2 != 0:
72 raise ValueError()
73 else:
74 half = n // 2
75 alpha = [(vector[i] + vector[-(i + 1)]) for i in range(half)]
76 beta = [(vector[i] - vector[-(i + 1)]) /
77 (math.cos((i + 0.5) * math.pi / n) * 2.0)
78 for i in range(half)]
79 alpha = transform(alpha)
80 beta = transform(beta )
81 result = []
82 for i in range(half - 1):
83 result.append(alpha[i])
84 result.append(beta[i] + beta[i + 1])
85 result.append(alpha[-1])
86 result.append(beta [-1])
87 return result
88
89
90 # modified (iterative) algorithm by lkcl, based on Nayuki original
91 def transform(vector, indent=0):
92 idt = " " * indent
93 n = len(vector)
94 if n == 1:
95 return list(vector)
96 elif n == 0 or n % 2 != 0:
97 raise ValueError()
98 else:
99 half = n // 2
100 alpha = [0] * half
101 beta = [0] * half
102 print (idt, "xf", vector)
103 print (idt, "coeff", n, "->", end=" ")
104 for i in range(half):
105 t1, t2 = vector[i], vector[n-i-1]
106 k = (math.cos((i + 0.5) * math.pi / n) * 2.0)
107 print (i, n-i-1, "i/n", (i+0.5)/n, ":", k, end= " ")
108 alpha[i] = t1 + t2
109 beta[i] = (t1 - t2) * (1/k)
110 print ()
111 print (idt, "n", n, "alpha", end=" ")
112 for i in range(0, n, 2):
113 print (i, i//2, alpha[i//2], end=" ")
114 print()
115 print (idt, "n", n, "beta", end=" ")
116 for i in range(0, n, 2):
117 print (i, beta[i//2], end=" ")
118 print()
119 alpha = transform(alpha, indent+1)
120 beta = transform(beta , indent+1)
121 result = [0] * n
122 for i in range(half):
123 result[i*2] = alpha[i]
124 result[i*2+1] = beta[i]
125 print(idt, "merge", result)
126 for i in range(half - 1):
127 result[i*2+1] += result[i*2+3]
128 print(idt, "result", result)
129 return result
130
131
132 # totally cool *in-place* DCT algorithm
133 def transform2(vec):
134
135 vec = deepcopy(vec)
136 # Initialization
137 n = len(vec)
138 print ()
139 print ("transform2", n)
140 levels = n.bit_length() - 1
141
142 # reference (read/write) the in-place data in *reverse-bit-order*
143 ri = list(range(n))
144 ri = [ri[reverse_bits(i, levels)] for i in range(n)]
145
146 # and pretend we LDed the data in bit-reversed order as well
147 vec = [vec[ri[i]] for i in range(n)]
148
149 size = n
150 while size >= 2:
151 halfsize = size // 2
152 tablestep = n // size
153 ir = list(range(0, n, size))
154 print (" xform", size, ir)
155 for i in ir:
156 k = 0
157 j = list(range(i, i + halfsize))
158 jr = list(range(i+halfsize, i + size))
159 jr.reverse()
160 print (" xform jr", j, jr)
161 for ci, (jl, jh) in enumerate(zip(j, jr)):
162 t1, t2 = vec[ri[jl]], vec[ri[jh]]
163 coeff = (math.cos((ci + 0.5) * math.pi / size) * 2.0)
164 # normally DCT would use jl+halfsize not jh, here.
165 # to be able to work in-place, the idea is to perform a
166 # swap afterwards.
167 vec[ri[jl]] = t1 + t2
168 vec[ri[jh]] = (t1 - t2) * (1/coeff)
169 print ("coeff", size, i, k, "ci", ci,
170 "jl", ri[jl], "jh", ri[jh],
171 "i/n", (k+0.5)/size, coeff, vec[ri[jl]], vec[ri[jh]])
172 k += tablestep
173 # instead of using jl+halfsize, perform a swap here.
174 # use half of j/jr because actually jl+halfsize = reverse(j)
175 hz2 = halfsize // 2 # can be zero which stops reversing 1-item lists
176 for ci, (jl, jh) in enumerate(zip(j[:hz2], jr[:hz2])):
177 jlh = jl+halfsize
178 # swap
179 tmp = vec[ri[jlh]]
180 vec[ri[jlh]] = vec[ri[jh]]
181 vec[ri[jh]] = tmp
182 print (" swap", size, i, ri[jlh], ri[jh])
183 size //= 2
184
185 print("post-swapped", ri)
186 print("transform2 pre-itersum", vec)
187
188 n = len(vec)
189 size = n // 2
190 while size >= 2:
191 halfsize = size // 2
192 ir = list(range(0, halfsize))
193 print ("itersum", halfsize, size, ir)
194 for i in ir:
195 jr = list(range(i+halfsize, i+n-halfsize, size))
196 print ("itersum jr", i+halfsize, i+size, jr)
197 for jh in jr:
198 vec[jh] += vec[jh+size]
199 print (" itersum", size, i, jh, jh+size)
200 size //= 2
201
202 print("transform2 result", vec)
203
204 return vec
205
206
207 # DCT type III, unscaled. Algorithm by Byeong Gi Lee, 1984.
208 # See: https://www.nayuki.io/res/fast-discrete-cosine-transform-algorithms/lee-new-algo-discrete-cosine-transform.pdf
209 def inverse_transform(vector, root=True, indent=0):
210 idt = " " * indent
211 if root:
212 vector = list(vector)
213 vector[0] /= 2
214 n = len(vector)
215 if n == 1:
216 return vector, [0]
217 elif n == 0 or n % 2 != 0:
218 raise ValueError()
219 else:
220 half = n // 2
221 alpha = [vector[0]]
222 beta = [vector[1]]
223 for i in range(2, n, 2):
224 alpha.append(vector[i])
225 beta.append(vector[i - 1] + vector[i + 1])
226 print (idt, "n", n, "alpha 0", end=" ")
227 for i in range(2, n, 2):
228 print (i, end=" ")
229 print ("beta 1", end=" ")
230 for i in range(2, n, 2):
231 print ("%d+%d" % (i-1, i+1), end=" ")
232 print()
233 inverse_transform(alpha, False, indent+1)
234 inverse_transform(beta , False, indent+1)
235 for i in range(half):
236 x = alpha[i]
237 y = beta[i] / (math.cos((i + 0.5) * math.pi / n) * 2)
238 vector[i] = x + y
239 vector[-(i + 1)] = x - y
240 print (idt, " v[%d] = alpha[%d]+beta[%d]" % (i, i, i))
241 print (idt, " v[%d] = alpha[%d]-beta[%d]" % (n-i-1, i, i))
242 return vector
243
244
245 def inverse_transform2(vector, root=True):
246 n = len(vector)
247 if root:
248 vector = list(vector)
249 if n == 1:
250 return vector
251 elif n == 0 or n % 2 != 0:
252 raise ValueError()
253 else:
254 half = n // 2
255 alpha = [0]
256 beta = [1]
257 for i in range(2, n, 2):
258 alpha.append(i)
259 beta.append(("add", i - 1, i + 1))
260 inverse_transform2(alpha, False)
261 inverse_transform2(beta , False)
262 for i in range(half):
263 x = alpha[i]
264 y = ("cos", beta[i], i)
265 vector[i] = ("add", x, y)
266 vector[-(i + 1)] = ("sub", x, y)
267 return vector
268
269
270 # does the outer butterfly in a recursive fashion, used in an
271 # intermediary development of the in-place DCT.
272 def transform_itersum(vector, indent=0):
273 idt = " " * indent
274 n = len(vector)
275 if n == 1:
276 return list(vector)
277 elif n == 0 or n % 2 != 0:
278 raise ValueError()
279 else:
280 half = n // 2
281 alpha = [0] * half
282 beta = [0] * half
283 for i in range(half):
284 t1, t2 = vector[i], vector[i+half]
285 alpha[i] = t1
286 beta[i] = t2
287 alpha = transform_itersum(alpha, indent+1)
288 beta = transform_itersum(beta , indent+1)
289 result = [0] * n
290 for i in range(half):
291 result[i*2] = alpha[i]
292 result[i*2+1] = beta[i]
293 print(idt, "iter-merge", result)
294 for i in range(half - 1):
295 result[i*2+1] += result[i*2+3]
296 print(idt, "iter-result", result)
297 return result
298
299
300 # prints out an "add" schedule for the outer butterfly, recursively,
301 # matching what transform_itersum does.
302 def itersum_explore(vector, indent=0):
303 idt = " " * indent
304 n = len(vector)
305 if n == 1:
306 return list(vector)
307 elif n == 0 or n % 2 != 0:
308 raise ValueError()
309 else:
310 half = n // 2
311 alpha = [0] * half
312 beta = [0] * half
313 for i in range(half):
314 t1, t2 = vector[i], vector[i+half]
315 alpha[i] = t1
316 beta[i] = t2
317 alpha = itersum_explore(alpha, indent+1)
318 beta = itersum_explore(beta , indent+1)
319 result = [0] * n
320 for i in range(half):
321 result[i*2] = alpha[i]
322 result[i*2+1] = beta[i]
323 print(idt, "iter-merge", result)
324 for i in range(half - 1):
325 result[i*2+1] = ("add", result[i*2+1], result[i*2+3])
326 print(idt, "iter-result", result)
327 return result
328
329
330 # prints out the exact same outer butterfly but does so iteratively.
331 # by comparing the output from itersum_explore and itersum_explore2
332 # and by drawing out the resultant ADDs as a graph it was possible
333 # to deduce what the heck was going on.
334 def itersum_explore2(vec, indent=0):
335 n = len(vec)
336 size = n // 2
337 while size >= 2:
338 halfsize = size // 2
339 ir = list(range(0, halfsize))
340 print ("itersum", halfsize, size, ir)
341 for i in ir:
342 jr = list(range(i+halfsize, i+n-halfsize, size))
343 print ("itersum jr", i+halfsize, i+size, jr)
344 for jh in jr:
345 vec[jh] = ("add", vec[jh], vec[jh+size])
346 print (" itersum", size, i, jh, jh+size)
347 size //= 2
348
349 return vec
350
351 if __name__ == '__main__':
352 n = 16
353 vec = list(range(n))
354 levels = n.bit_length() - 1
355 vec = [vec[reverse_bits(i, levels)] for i in range(n)]
356 ops = itersum_explore(vec)
357 for i, x in enumerate(ops):
358 print (i, x)
359
360 n = 16
361 vec = list(range(n))
362 levels = n.bit_length() - 1
363 ops = itersum_explore2(vec)
364 for i, x in enumerate(ops):
365 print (i, x)