I would like convolve a discrete signal with a discrete filter. The signal and filter is sequences of float in F#.
The only way I can figure out how to do it is with two nested for loops and a mutable array to store the result, but it does not feel very functional.
Here is how I would do it non-functional:
conv = double[len(signal) + len(filter) - 1]
for i = 1 to len(signal)
for j = 1 to len(filter)
conv[i + j] = conv[i + j] + signal(i) * filter(len(filter) - j)
I don't know F#, but I'll post some Haskell and hopefully it will be close enough to use. (I only have VS 2005 and an ancient version of F#, so I think it would be more confusing to post something that works on my machine)
Let me start by posting a Python implementation of your pseudocode to make sure I'm getting the right answer:
Now
convolve([1,1,1], [1,2,3])
gives[3, 5, 6, 3, 1]
. If this is wrong, please tell me.The first thing we can do is turn the inner loop into a zipWith; we're essentially adding a series of rows in a special way, in the example above:
[[3,2,1], [3,2,1], [3,2,1]]
. To generate each row, we'll zip eachi
in thesignal
with the reversed filter:(Note: according to a quick google,
zipWith
ismap2
in F#. You might have to use a list comprehension instead ofrepeat
) Now:To get this for all
i
, we need to map over signal:Good. Now we just need a way to combine the rows properly. We can do this by noticing that combining is adding the new row to the existing array, except for the first element, which is stuck on front. For example:
So we just need to write some code that does this in the general case:
Now that we have a way to generate all the rows and a way to combine a new row with an existing array, all we have to do is stick the two together with a fold:
So that's a functional version. I think it's reasonably clear, as long as you understand
foldr
andzipWith
. But it's at least as long as the imperative version and like other commenters said, probably less efficient in F#. Here's the whole thing in one place.Edit:
As promised, here is an F# version. This was written using a seriously ancient version (1.9.2.9) on VS2005, so be careful. Also I couldn't find
splitAt
in the standard library, but then I don't know F# that well.Indeed, you generally want to avoid loops (plain, nested, whatever) and anything mutable in functional programming.
There happens to be a very simple solution in F# (and probably almost every other functional language):
The
zip
function simply combines the two sequences into one of pairs containing the element fromseq1
and the element fromseq2
. As a note, there also exist similar zip functions for theList
andArray
modules, as well as variants for combining three lists into triples (zip3
). If you want tom ore generally zip (or "convolute") n lists into a list of n-tuples, then you'll need to write your own function, but it's pretty straightforward.(I've been going by this description of convolution by the way - tell me if you mean something else.)
In principle, it should be possible to use the (Fast) Fourier Transform, or the related (Discrete) Cosine Transform, to calculate the convolution of two functions reasonably efficiently. You calculate the FFT for both functions, multiply them, and apply the inverse FFT on the result.
mathematical background
That's the theory. In practice you'd probably best find a math library that implements it for you.
Try this function:
It's probably not the nicest function solution, but it should do the job. I doubt there exists a purely functional solution that will match the imperative one for speed however.
Hope that helps.
Note: The function is currently untested (though I've confirmed it compiles). Let me know if it doesn't quite do what it should. Also, observe that the
i
andj
variables do not refer to the same things as is your original post.