Skip to content

Commit d81942c

Browse files
authored
added 1d convolution implementation in python (#874)
* added 1d convolution implementation in python * fixed some mistakes in the code so it outputs correct results * making the code look better * spacing code properly for readability
1 parent f3bd8f4 commit d81942c

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

contents/convolutions/1d/1d.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ With this in mind, we can almost directly transcribe the discrete equation into
5656
[import:27-46, lang:"julia"](code/julia/1d_convolution.jl)
5757
{% sample lang="cs" %}
5858
[import:63-84, lang:"csharp"](code/csharp/1DConvolution.cs)
59+
{% sample lang="py" %}
60+
[import:18-27, lang:"python"](code/python/1d_convolution.py)
5961
{% endmethod %}
6062

6163
The easiest way to reason about this code is to read it as you might read a textbook.
@@ -189,6 +191,8 @@ Here it is again for clarity:
189191
[import:27-46, lang:"julia"](code/julia/1d_convolution.jl)
190192
{% sample lang="cs" %}
191193
[import:63-84, lang:"csharp"](code/csharp/1DConvolution.cs)
194+
{% sample lang="py" %}
195+
[import:18-27, lang:"python"](code/python/1d_convolution.py)
192196
{% endmethod %}
193197

194198
Here, the main difference between the bounded and unbounded versions is that the output array size is smaller in the bounded case.
@@ -199,6 +203,8 @@ For an unbounded convolution, the function would be called with a the output arr
199203
[import:58-59, lang:"julia"](code/julia/1d_convolution.jl)
200204
{% sample lang="cs" %}
201205
[import:96-97, lang:"csharp"](code/csharp/1DConvolution.cs)
206+
{% sample lang="py" %}
207+
[import:37-38, lang:"python"](code/python/1d_convolution.py)
202208
{% endmethod %}
203209

204210
On the other hand, the bounded call would set the output array size to simply be the length of the signal
@@ -208,6 +214,8 @@ On the other hand, the bounded call would set the output array size to simply be
208214
[import:61-62, lang:"julia"](code/julia/1d_convolution.jl)
209215
{% sample lang="cs" %}
210216
[import:98-99, lang:"csharp"](code/csharp/1DConvolution.cs)
217+
{% sample lang="py" %}
218+
[import:40-41, lang:"python"](code/python/1d_convolution.py)
211219
{% endmethod %}
212220

213221
Finally, as we mentioned before, it is possible to center bounded convolutions by changing the location where we calculate the each point along the filter.
@@ -218,6 +226,8 @@ This can be done by modifying the following line:
218226
[import:35-35, lang:"julia"](code/julia/1d_convolution.jl)
219227
{% sample lang="cs" %}
220228
[import:71-71, lang:"csharp"](code/csharp/1DConvolution.cs)
229+
{% sample lang="py" %}
230+
[import:22-22, lang:"python"](code/python/1d_convolution.py)
221231
{% endmethod %}
222232

223233
Here, `j` counts from `i-length(filter)` to `i`.
@@ -252,6 +262,8 @@ In code, this typically amounts to using some form of modulus operation, as show
252262
[import:4-25, lang:"julia"](code/julia/1d_convolution.jl)
253263
{% sample lang="cs" %}
254264
[import:38-61, lang:"csharp"](code/csharp/1DConvolution.cs)
265+
{% sample lang="py" %}
266+
[import:5-15, lang:"python"](code/python/1d_convolution.py)
255267
{% endmethod %}
256268

257269
This is essentially the same as before, except for the modulus operations, which allow us to work on a periodic domain.
@@ -269,6 +281,8 @@ For the code associated with this chapter, we have used the convolution to gener
269281
[import, lang:"julia"](code/julia/1d_convolution.jl)
270282
{% sample lang="cs" %}
271283
[import, lang:"csharp"](code/csharp/1DConvolution.cs)
284+
{% sample lang="py" %}
285+
[import, lang:"python"](code/python/1d_convolution.py)
272286
{% endmethod %}
273287

274288
At a test case, we have chosen to use two sawtooth functions, which should produce the following images:
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import numpy as np
2+
3+
def mod1(x, y): return ((x % y) + y) % y
4+
5+
def convolve_cyclic(signal, filter_array):
6+
output_size = max(len(signal), len(filter_array))
7+
out = np.zeros(output_size)
8+
s = 0
9+
10+
for i in range(output_size):
11+
for j in range(output_size):
12+
if(mod1(i - j, output_size) < len(filter_array)):
13+
s += signal[mod1(j - 1, output_size)] * filter_array[mod1(i - j, output_size)]
14+
out[i] = s
15+
s = 0
16+
17+
return out
18+
19+
20+
def convolve_linear(signal, filter_array, output_size):
21+
out = np.zeros(output_size)
22+
s = 0
23+
24+
for i in range(output_size):
25+
for j in range(max(0, i - len(filter_array)), i + 1):
26+
if j < len(signal) and (i - j) < len(filter_array):
27+
s += signal[j] * filter_array[i - j]
28+
out[i] = s
29+
s = 0
30+
31+
return out
32+
33+
# sawtooth functions for x and y
34+
x = [float(i + 1)/200 for i in range(200)]
35+
y = [float(i + 1)/200 for i in range(200)]
36+
37+
# Normalization is not strictly necessary, but good practice
38+
x /= np.linalg.norm(x)
39+
y /= np.linalg.norm(y)
40+
41+
# full convolution, output will be the size of x + y - 1
42+
full_linear_output = convolve_linear(x, y, len(x) + len(y) - 1)
43+
44+
# simple boundaries
45+
simple_linear_output = convolve_linear(x, y, len(x))
46+
47+
# cyclic convolution
48+
cyclic_output = convolve_cyclic(x, y)
49+
50+
# outputting convolutions to different files for plotting in external code
51+
np.savetxt('full_linear.dat', full_linear_output)
52+
np.savetxt('simple_linear.dat', simple_linear_output)
53+
np.savetxt('cyclic.dat', cyclic_output)

0 commit comments

Comments
 (0)