update comments
[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 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 # the insight then was: to modify the *indirection* indices rather
46 # than swap the actual data, and then try the same trick as was done
47 # with the bit-reversed LOAD. by a bizarre twist of good fortune
48 # *that was not needed*! simply swapping the indices was enough!
49 # End result is that once the first butterfly is done - bear in mind
50 # it's in-place - the data is in the right order so that a second
51 # dead-straightforward iterative sum can be done: again, in-place.
52 # Really surprising.
53
54 import math
55 from copy import deepcopy
56
57 # bits of the integer 'val'.
58 def reverse_bits(val, width):
59 result = 0
60 for _ in range(width):
61 result = (result << 1) | (val & 1)
62 val >>= 1
63 return result
64
65
66 # DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
67 # See: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.118.3056&rep=rep1&type=pdf#page=34
68 # original (recursive) algorithm by Nayuki
69 def transform(vector, indent=0):
70 idt = " " * indent
71 n = len(vector)
72 if n == 1:
73 return list(vector)
74 elif n == 0 or n % 2 != 0:
75 raise ValueError()
76 else:
77 half = n // 2
78 alpha = [(vector[i] + vector[-(i + 1)]) for i in range(half)]
79 beta = [(vector[i] - vector[-(i + 1)]) /
80 (math.cos((i + 0.5) * math.pi / n) * 2.0)
81 for i in range(half)]
82 alpha = transform(alpha)
83 beta = transform(beta )
84 result = []
85 for i in range(half - 1):
86 result.append(alpha[i])
87 result.append(beta[i] + beta[i + 1])
88 result.append(alpha[-1])
89 result.append(beta [-1])
90 return result
91
92
93 # modified (iterative) algorithm by lkcl, based on Nayuki original
94 def transform(vector, indent=0):
95 idt = " " * indent
96 n = len(vector)
97 if n == 1:
98 return list(vector)
99 elif n == 0 or n % 2 != 0:
100 raise ValueError()
101 else:
102 half = n // 2
103 alpha = [0] * half
104 beta = [0] * half
105 print (idt, "xf", vector)
106 print (idt, "coeff", n, "->", end=" ")
107 for i in range(half):
108 t1, t2 = vector[i], vector[n-i-1]
109 k = (math.cos((i + 0.5) * math.pi / n) * 2.0)
110 print (i, n-i-1, "i/n", (i+0.5)/n, ":", k, end= " ")
111 alpha[i] = t1 + t2
112 beta[i] = (t1 - t2) * (1/k)
113 print ()
114 print (idt, "n", n, "alpha", end=" ")
115 for i in range(0, n, 2):
116 print (i, i//2, alpha[i//2], end=" ")
117 print()
118 print (idt, "n", n, "beta", end=" ")
119 for i in range(0, n, 2):
120 print (i, beta[i//2], end=" ")
121 print()
122 alpha = transform(alpha, indent+1)
123 beta = transform(beta , indent+1)
124 result = [0] * n
125 for i in range(half):
126 result[i*2] = alpha[i]
127 result[i*2+1] = beta[i]
128 print(idt, "merge", result)
129 for i in range(half - 1):
130 result[i*2+1] += result[i*2+3]
131 print(idt, "result", result)
132 return result
133
134
135 # totally cool *in-place* DCT algorithm
136 def transform2(vec):
137
138 vec = deepcopy(vec)
139 # Initialization
140 n = len(vec)
141 print ()
142 print ("transform2", n)
143 levels = n.bit_length() - 1
144
145 # reference (read/write) the in-place data in *reverse-bit-order*
146 ri = list(range(n))
147 ri = [ri[reverse_bits(i, levels)] for i in range(n)]
148
149 # and pretend we LDed the data in bit-reversed order as well
150 vec = [vec[reverse_bits(i, levels)] for i in range(n)]
151
152 size = n
153 while size >= 2:
154 halfsize = size // 2
155 tablestep = n // size
156 ir = list(range(0, n, size))
157 print (" xform", size, ir)
158 for i in ir:
159 k = 0
160 j = list(range(i, i + halfsize))
161 jr = list(range(i+halfsize, i + size))
162 jr.reverse()
163 print (" xform jr", j, jr)
164 for ci, (jl, jh) in enumerate(zip(j, jr)):
165 t1, t2 = vec[ri[jl]], vec[ri[jh]]
166 coeff = (math.cos((ci + 0.5) * math.pi / size) * 2.0)
167 # normally DCT would use jl+halfsize not jh, here.
168 # to be able to work in-place, the idea is to perform a
169 # high-half reverse/swap afterwards. however actually
170 # we swap the *indices*
171 vec[ri[jl]] = t1 + t2
172 vec[ri[jh]] = (t1 - t2) * (1/coeff)
173 print (" ", size, i, k, "ci", ci,
174 "jl", ri[jl], "jh", ri[jh],
175 "i/n", (k+0.5)/size, coeff, vec[ri[jl]], vec[ri[jh]])
176 k += tablestep
177 # instead of using jl+halfsize, perform a swap here.
178 # use half of j/jr because actually jl+halfsize = reverse(j)
179 # actually: swap the *indices*... not the actual data.
180 # incredibly... bizarrely... this works *without* having
181 # to do anything else.
182 hz2 = halfsize // 2 # can be zero which stops reversing 1-item lists
183 for ci, (jl, jh) in enumerate(zip(j[:hz2], jr[:hz2])):
184 tmp = ri[jl+halfsize]
185 ri[jl+halfsize] = ri[jh]
186 ri[jh] = tmp
187 print (" swap", size, i, ri[jl+halfsize], ri[jh])
188 size //= 2
189
190 print("post-swapped", ri)
191 print("transform2 pre-itersum", vec)
192
193 n = len(vec)
194 size = n // 2
195 while size >= 2:
196 halfsize = size // 2
197 ir = list(range(0, halfsize))
198 print ("itersum", halfsize, size, ir)
199 for i in ir:
200 jr = list(range(i+halfsize, i+n-halfsize, size))
201 print ("itersum jr", i+halfsize, i+size, jr)
202 for jh in jr:
203 vec[jh] += vec[jh+size]
204 print (" itersum", size, i, jh, jh+size)
205 size //= 2
206
207 print("transform2 result", vec)
208
209 return vec
210
211
212 # DCT type III, unscaled. Algorithm by Byeong Gi Lee, 1984.
213 # See: https://www.nayuki.io/res/fast-discrete-cosine-transform-algorithms/lee-new-algo-discrete-cosine-transform.pdf
214 def inverse_transform(vector, root=True, indent=0):
215 idt = " " * indent
216 if root:
217 vector = list(vector)
218 vector[0] /= 2
219 n = len(vector)
220 if n == 1:
221 return vector, [0]
222 elif n == 0 or n % 2 != 0:
223 raise ValueError()
224 else:
225 half = n // 2
226 alpha = [vector[0]]
227 beta = [vector[1]]
228 for i in range(2, n, 2):
229 alpha.append(vector[i])
230 beta.append(vector[i - 1] + vector[i + 1])
231 print (idt, "n", n, "alpha 0", end=" ")
232 for i in range(2, n, 2):
233 print (i, end=" ")
234 print ("beta 1", end=" ")
235 for i in range(2, n, 2):
236 print ("%d+%d" % (i-1, i+1), end=" ")
237 print()
238 inverse_transform(alpha, False, indent+1)
239 inverse_transform(beta , False, indent+1)
240 for i in range(half):
241 x = alpha[i]
242 y = beta[i] / (math.cos((i + 0.5) * math.pi / n) * 2)
243 vector[i] = x + y
244 vector[-(i + 1)] = x - y
245 print (idt, " v[%d] = alpha[%d]+beta[%d]" % (i, i, i))
246 print (idt, " v[%d] = alpha[%d]-beta[%d]" % (n-i-1, i, i))
247 return vector
248
249
250 def inverse_transform2(vector, root=True):
251 n = len(vector)
252 if root:
253 vector = list(vector)
254 if n == 1:
255 return vector
256 elif n == 0 or n % 2 != 0:
257 raise ValueError()
258 else:
259 half = n // 2
260 alpha = [0]
261 beta = [1]
262 for i in range(2, n, 2):
263 alpha.append(i)
264 beta.append(("add", i - 1, i + 1))
265 inverse_transform2(alpha, False)
266 inverse_transform2(beta , False)
267 for i in range(half):
268 x = alpha[i]
269 y = ("cos", beta[i], i)
270 vector[i] = ("add", x, y)
271 vector[-(i + 1)] = ("sub", x, y)
272 return vector
273
274
275 # does the outer butterfly in a recursive fashion, used in an
276 # intermediary development of the in-place DCT.
277 def transform_itersum(vector, indent=0):
278 idt = " " * indent
279 n = len(vector)
280 if n == 1:
281 return list(vector)
282 elif n == 0 or n % 2 != 0:
283 raise ValueError()
284 else:
285 half = n // 2
286 alpha = [0] * half
287 beta = [0] * half
288 for i in range(half):
289 t1, t2 = vector[i], vector[i+half]
290 alpha[i] = t1
291 beta[i] = t2
292 alpha = transform_itersum(alpha, indent+1)
293 beta = transform_itersum(beta , indent+1)
294 result = [0] * n
295 for i in range(half):
296 result[i*2] = alpha[i]
297 result[i*2+1] = beta[i]
298 print(idt, "iter-merge", result)
299 for i in range(half - 1):
300 result[i*2+1] += result[i*2+3]
301 print(idt, "iter-result", result)
302 return result
303
304
305 # prints out an "add" schedule for the outer butterfly, recursively,
306 # matching what transform_itersum does.
307 def itersum_explore(vector, indent=0):
308 idt = " " * indent
309 n = len(vector)
310 if n == 1:
311 return list(vector)
312 elif n == 0 or n % 2 != 0:
313 raise ValueError()
314 else:
315 half = n // 2
316 alpha = [0] * half
317 beta = [0] * half
318 for i in range(half):
319 t1, t2 = vector[i], vector[i+half]
320 alpha[i] = t1
321 beta[i] = t2
322 alpha = itersum_explore(alpha, indent+1)
323 beta = itersum_explore(beta , indent+1)
324 result = [0] * n
325 for i in range(half):
326 result[i*2] = alpha[i]
327 result[i*2+1] = beta[i]
328 print(idt, "iter-merge", result)
329 for i in range(half - 1):
330 result[i*2+1] = ("add", result[i*2+1], result[i*2+3])
331 print(idt, "iter-result", result)
332 return result
333
334
335 # prints out the exact same outer butterfly but does so iteratively.
336 # by comparing the output from itersum_explore and itersum_explore2
337 # and by drawing out the resultant ADDs as a graph it was possible
338 # to deduce what the heck was going on.
339 def itersum_explore2(vec, indent=0):
340 n = len(vec)
341 size = n // 2
342 while size >= 2:
343 halfsize = size // 2
344 ir = list(range(0, halfsize))
345 print ("itersum", halfsize, size, ir)
346 for i in ir:
347 jr = list(range(i+halfsize, i+n-halfsize, size))
348 print ("itersum jr", i+halfsize, i+size, jr)
349 for jh in jr:
350 vec[jh] = ("add", vec[jh], vec[jh+size])
351 print (" itersum", size, i, jh, jh+size)
352 size //= 2
353
354 return vec
355
356 if __name__ == '__main__':
357 n = 16
358 vec = list(range(n))
359 levels = n.bit_length() - 1
360 vec = [vec[reverse_bits(i, levels)] for i in range(n)]
361 ops = itersum_explore(vec)
362 for i, x in enumerate(ops):
363 print (i, x)
364
365 n = 16
366 vec = list(range(n))
367 levels = n.bit_length() - 1
368 ops = itersum_explore2(vec)
369 for i, x in enumerate(ops):
370 print (i, x)