Skip to content

Implement conv in python #408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 3, 2018
Merged

Conversation

jasmaa
Copy link
Contributor

@jasmaa jasmaa commented Oct 1, 2018

Note: Discrete convolution implemented differently because I couldn't get the previous implementations to get the same answer as the fft ones.

@june128 june128 added Implementation This provides an implementation for an algorithm. (Code and maybe md files are edited.) Hacktoberfest The label for all Hacktoberfest related things! labels Oct 1, 2018
@jiegillet
Copy link
Member

That's normal, the FFT method does not work the same way.

Copy link
Member

@jiegillet jiegillet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things to change. I think if you want to use numpy that's fine, but then we should use more of its tools to make your code cleaner. But maybe for the purpose of the AAA, not using is is better? I'm not sure.

@@ -0,0 +1,46 @@
import numpy as np
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're only using numpy to make examples, I don't think it's worth it...

"""Discrete convolution by definition"""

out = []
n = min(len(signal1), len(signal2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should end up with len(signal1) + len(signal2) - 1 points, not min(len(signal1), len(signal2))

for i in range(n):
s = 0
for j in range(n):
s += signal1[j] * signal2[i - j]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not overlapping all the points. Look at the Julia code for reference.
Don't try to mimic the FFT method, they have different assumptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback!
I'm not that familiar with convolutions so please bear with me:

The discrete convolution from the definition is as listed:

f*g[n] = sum[-inf,inf][ f[m]*g[n-m] ]

In the code, I am going for each element of f*g using the outer for loop.

Taking the [-inf,inf] to effectively be the full length of the signal arrays,
for each element in f*g, I sum the values f[m]*g[n-m] where n and m are represented
by i in the outer for loop and j in the inner for loop respectively.
This j should go the length of the signal as m is in the range, [-inf,inf].

The code follows the discrete convolution definition to my knowledge and,
additionally, also appears to give the same answer as the fft version,
so I am unsure as to why it is not valid.

I will experiment with the Julia code when I can get a chance but a cursory
look at the code shows the length of the outputted signal to be the sum of
the two inputted signals. Since the outer for loop runs through this combined
length, isn't there a chance of an array-out-of-bounds when indexing the smaller
signal2 with i-j when i is big and j is small?

FFT also appears to output a signal of equal length to the input signal, so
I am unclear if there is ever a case where the fft version of the convolution
could give back the same output signal described by the Julia implementation.

I will try to find more information since there is likely a misunderstanding on
my part. Meanwhile, it would be helpful if some of these questions can be cleared up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trick is to simulate a [-inf,inf] range by adding an infinite number of zeroes to the left and right of both signals, convolute them and clip the zeros at the end. For example, I want to convolve a=[1,2,3,4] and b=[8,9]. So if I flip b and slide it across a, I have:

0 0 1 2 3 4 0 0
9 8 0 0 0 0 0 0  => sum of product = 0

0 0 1 2 3 4 0 0
0 9 8 0 0 0 0 0  => sum of product = 0*9 + 1*8 = 8

0 0 1 2 3 4 0 0
0 0 9 8 0 0 0 0  => sum of product = 1*9 + 2*8 = 25

0 0 1 2 3 4 0 0
0 0 0 9 8 0 0 0  => sum of product = 2*9 + 3*8 = 42

0 0 1 2 3 4 0 0
0 0 0 0 9 8 0 0  => sum of product = 3*9 + 4*8 = 59

0 0 1 2 3 4 0 0
0 0 0 0 0 9 8 0  => sum of product = 4*9 + 0*8 = 36

0 0 1 2 3 4 0 0
0 0 0 0 0 0 9 8  => sum of product = 0

so the output should be [8, 25, 42, 59, 36], which is of length 5 = len(a)+len(b)-1.
This is the best way to mimic the continuous case.

The FFT method reproduces the same result only if the signals you want to convolve are infinite and periodic. If you use the FFT method on finite signals it will assume that both signals repeat left and right forever, so if I did the same process above, we would have something like:

. . 1 2 3 4 . .
9 8 9 8 9 8 9 8  => sum of product = 1*9 + 2*8 + 3*9 + 4*8 = A

. . 1 2 3 4 . .
8 9 8 9 8 9 8 9  => sum of product = 1*8 + 2*9 + 3*8 + 4*9 = B

. . 1 2 3 4 . .
9 8 9 8 9 8 9 8  => sum of product = 1*9 + 2*8 + 3*9 + 4*8 = A

. . 1 2 3 4 . .
8 9 8 9 8 9 8 9  => sum of product = 1*8 + 2*9 + 3*8 + 4*9 = B

and the output would be [B, A] (or maybe [A,B]?) of length 2 = min(len(a), len(b)).

But it gets a bit more messy when you actually use the FFT when you have signals of different lengths, you need to either cut the longest signal (which you have done, and is actually a valid choice) or pad the shorter signal with zeros (that would be my preference, but it's not more valid).

Depending on your data you might chose different methods. The FFT is a faster algorithm but comes with different assumptions on the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Thanks for the explanation!

fft_s2 = fft(signal2)
out = []

for i in range(min(len(signal1), len(signal2))):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there is an official method in the case the signals have different lengths. I think instead of cutting the longer signal, I would add zeros to the shorter one until they have the same length. That's the usual method when using FFTs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was based off of what I could gather from the C implementation on AAA, but I'll try to change it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Wikipedia, both ways of doing it are valid, the results will a bit look different. As long as you're making an informed decision it's all good.

# Example convolution with sin and cos
x = np.linspace(0, 1, 5)
s1 = np.sin(x)
s2 = np.cos(x)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can get away with something like s1 = [ math.sin(x) for x in range(5)] if we want to skip numpy.

@jiegillet jiegillet self-assigned this Oct 2, 2018
Copy link
Member

@jiegillet jiegillet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output is correct but you can simplify your code by a lot!

"""Discrete convolution by definition"""

n = len(signal1) + len(signal2)
signal1 = signal1.copy()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to modify the signals if your loops are done right. Lines 8 to 17 can be deleted without any effect on the algorithm.

out = []

for i in range(n):
s = complex(0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why complex? just 0 is fine, if the data is complex it will get updated anyway.


out.append(s)

return out[1:]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return out.

def conv(signal1, signal2):
"""Discrete convolution by definition"""

n = len(signal1) + len(signal2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define n = len(signal1) + len(signal2) -1 that's the most natural value

for i in range(n):
s = complex(0)

for j in range(i):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to range(i+1) this way all the i-1 become i and it's clearer.

s = complex(0)

for j in range(i):
if j < len(signal1) and i - j - 1 < len(signal2):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i - j - 1 => i - j


for j in range(i):
if j < len(signal1) and i - j - 1 < len(signal2):
s += signal1[j] * signal2[i - j - 1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i - j - 1 => i - j

Copy link
Member

@jiegillet jiegillet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Thank you for the quick updates and your great contribution.

@jiegillet jiegillet merged commit 0fb0325 into algorithm-archivists:master Oct 3, 2018
PaddyKe pushed a commit to PaddyKe/algorithm-archive that referenced this pull request Oct 3, 2018
* Implement conv in python

* Fix conv func

* fix range

* remove numpy usage

* Fix discrete convolution

* Revert julia file

* Add padding for fft

* Make requested changes
kenpower pushed a commit to kenpower/algorithm-archive that referenced this pull request Oct 22, 2018
* Implement conv in python

* Fix conv func

* fix range

* remove numpy usage

* Fix discrete convolution

* Revert julia file

* Add padding for fft

* Make requested changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Hacktoberfest The label for all Hacktoberfest related things! Implementation This provides an implementation for an algorithm. (Code and maybe md files are edited.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants