Skip to content

Commit 80c39d0

Browse files
Clear20-22Copilotcclauss
authored
Add Fast Walsh-Hadamard Transform (FWHT) for bitwise convolutions (#15084)
* Add Fast Walsh-Hadamard Transform (FWHT) for bitwise convolutions * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * refactor: remove unnecessary whitespace in fast_walsh_hadamard_transform.py * Move fast_walsh_hadamard_transform.py to bit_manipulation --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Christian Clauss <cclauss@me.com>
1 parent fe67369 commit 80c39d0

1 file changed

Lines changed: 209 additions & 0 deletions

File tree

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
"""
2+
Fast Walsh-Hadamard Transform (FWHT) for Bitwise Convolutions.
3+
4+
Reference: https://en.wikipedia.org/wiki/Fast_Walsh%E2%80%93Hadamard_transform
5+
Reference: https://cp-algorithms.com/algebra/walsh-hadamard-transform.html
6+
7+
Computes bitwise XOR, AND, and OR convolutions of two numeric sequences in O(N log N)
8+
time, where N is a positive power of 2.
9+
"""
10+
11+
12+
def fwht_xor(sequence: list[int], inverse: bool = False) -> list[int]:
13+
"""
14+
Perform Fast Walsh-Hadamard Transform (or inverse) for XOR operation.
15+
16+
Time Complexity: O(N log N)
17+
18+
>>> fwht_xor([1, 2, 3, 4])
19+
[10, -2, -4, 0]
20+
>>> fwht_xor([10, -2, -4, 0], inverse=True)
21+
[1, 2, 3, 4]
22+
>>> fwht_xor([1, 2, 3])
23+
Traceback (most recent call last):
24+
...
25+
ValueError: Length of sequence must be a positive power of 2.
26+
>>> fwht_xor([])
27+
Traceback (most recent call last):
28+
...
29+
ValueError: Length of sequence must be a positive power of 2.
30+
>>> fwht_xor([1, 2, 3, 5], inverse=True)
31+
Traceback (most recent call last):
32+
...
33+
ValueError: Inverse XOR transform requires elements divisible by sequence length.
34+
"""
35+
sequence_length = len(sequence)
36+
if sequence_length == 0 or (sequence_length & (sequence_length - 1)) != 0:
37+
raise ValueError("Length of sequence must be a positive power of 2.")
38+
39+
result = list(sequence)
40+
half_block = 1
41+
while half_block < sequence_length:
42+
block_size = half_block * 2
43+
for block_start in range(0, sequence_length, block_size):
44+
for offset in range(half_block):
45+
left_index = block_start + offset
46+
right_index = left_index + half_block
47+
left_val = result[left_index]
48+
right_val = result[right_index]
49+
result[left_index] = left_val + right_val
50+
result[right_index] = left_val - right_val
51+
half_block *= 2
52+
53+
if inverse:
54+
if any(element % sequence_length != 0 for element in result):
55+
raise ValueError(
56+
"Inverse XOR transform requires elements divisible by sequence length."
57+
)
58+
result = [element // sequence_length for element in result]
59+
return result
60+
61+
62+
def xor_convolution(sequence_a: list[int], sequence_b: list[int]) -> list[int]:
63+
"""
64+
Compute bitwise XOR convolution C[k] = sum_{i ^ j = k} (A[i] * B[j]).
65+
66+
Time Complexity: O(N log N)
67+
68+
>>> xor_convolution([1, 2], [3, 4])
69+
[11, 10]
70+
>>> xor_convolution([1, 2], [3])
71+
Traceback (most recent call last):
72+
...
73+
ValueError: Input sequences must have equal length.
74+
"""
75+
if len(sequence_a) != len(sequence_b):
76+
raise ValueError("Input sequences must have equal length.")
77+
78+
transformed_a = fwht_xor(sequence_a)
79+
transformed_b = fwht_xor(sequence_b)
80+
pointwise_product = [
81+
transformed_a[index] * transformed_b[index] for index in range(len(sequence_a))
82+
]
83+
return fwht_xor(pointwise_product, inverse=True)
84+
85+
86+
def fwht_or(sequence: list[int], inverse: bool = False) -> list[int]:
87+
"""
88+
Perform Fast Walsh-Hadamard Transform for OR operation.
89+
90+
Time Complexity: O(N log N)
91+
92+
>>> fwht_or([1, 2])
93+
[1, 3]
94+
>>> fwht_or([1, 3], inverse=True)
95+
[1, 2]
96+
>>> fwht_or([1, 2, 3])
97+
Traceback (most recent call last):
98+
...
99+
ValueError: Length of sequence must be a positive power of 2.
100+
"""
101+
sequence_length = len(sequence)
102+
if sequence_length == 0 or (sequence_length & (sequence_length - 1)) != 0:
103+
raise ValueError("Length of sequence must be a positive power of 2.")
104+
105+
result = list(sequence)
106+
half_block = 1
107+
while half_block < sequence_length:
108+
block_size = half_block * 2
109+
for block_start in range(0, sequence_length, block_size):
110+
for offset in range(half_block):
111+
left_index = block_start + offset
112+
right_index = left_index + half_block
113+
if not inverse:
114+
result[right_index] += result[left_index]
115+
else:
116+
result[right_index] -= result[left_index]
117+
half_block *= 2
118+
119+
return result
120+
121+
122+
def or_convolution(sequence_a: list[int], sequence_b: list[int]) -> list[int]:
123+
"""
124+
Compute bitwise OR convolution C[k] = sum_{i | j = k} (A[i] * B[j]).
125+
126+
Time Complexity: O(N log N)
127+
128+
>>> or_convolution([1, 2], [3, 4])
129+
[3, 18]
130+
>>> or_convolution([1, 2], [3])
131+
Traceback (most recent call last):
132+
...
133+
ValueError: Input sequences must have equal length.
134+
"""
135+
if len(sequence_a) != len(sequence_b):
136+
raise ValueError("Input sequences must have equal length.")
137+
138+
transformed_a = fwht_or(sequence_a)
139+
transformed_b = fwht_or(sequence_b)
140+
pointwise_product = [
141+
transformed_a[index] * transformed_b[index] for index in range(len(sequence_a))
142+
]
143+
return fwht_or(pointwise_product, inverse=True)
144+
145+
146+
def fwht_and(sequence: list[int], inverse: bool = False) -> list[int]:
147+
"""
148+
Perform Fast Walsh-Hadamard Transform for AND operation.
149+
150+
Time Complexity: O(N log N)
151+
152+
>>> fwht_and([1, 2])
153+
[3, 2]
154+
>>> fwht_and([3, 2], inverse=True)
155+
[1, 2]
156+
>>> fwht_and([1, 2, 3])
157+
Traceback (most recent call last):
158+
...
159+
ValueError: Length of sequence must be a positive power of 2.
160+
"""
161+
sequence_length = len(sequence)
162+
if sequence_length == 0 or (sequence_length & (sequence_length - 1)) != 0:
163+
raise ValueError("Length of sequence must be a positive power of 2.")
164+
165+
result = list(sequence)
166+
half_block = 1
167+
while half_block < sequence_length:
168+
block_size = half_block * 2
169+
for block_start in range(0, sequence_length, block_size):
170+
for offset in range(half_block):
171+
left_index = block_start + offset
172+
right_index = left_index + half_block
173+
if not inverse:
174+
result[left_index] += result[right_index]
175+
else:
176+
result[left_index] -= result[right_index]
177+
half_block *= 2
178+
179+
return result
180+
181+
182+
def and_convolution(sequence_a: list[int], sequence_b: list[int]) -> list[int]:
183+
"""
184+
Compute bitwise AND convolution C[k] = sum_{i & j = k} (A[i] * B[j]).
185+
186+
Time Complexity: O(N log N)
187+
188+
>>> and_convolution([1, 2], [3, 4])
189+
[13, 8]
190+
>>> and_convolution([1, 2], [3])
191+
Traceback (most recent call last):
192+
...
193+
ValueError: Input sequences must have equal length.
194+
"""
195+
if len(sequence_a) != len(sequence_b):
196+
raise ValueError("Input sequences must have equal length.")
197+
198+
transformed_a = fwht_and(sequence_a)
199+
transformed_b = fwht_and(sequence_b)
200+
pointwise_product = [
201+
transformed_a[index] * transformed_b[index] for index in range(len(sequence_a))
202+
]
203+
return fwht_and(pointwise_product, inverse=True)
204+
205+
206+
if __name__ == "__main__":
207+
import doctest
208+
209+
doctest.testmod()

0 commit comments

Comments
 (0)