Skip to content

Commit 057efcd

Browse files
committed
tutorial
1 parent a705c14 commit 057efcd

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

docs/Tutorial_FFT_Based_SDP

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Sliding Dot product, (Circular) Convolution, and Overlap-Add!
2+
3+
One way to compute the sliding-dot-product (sdp) between a query Q and a time series T is
4+
FFT-based convolution. But first, let's start with a simple example to understand how the concepts are related.
5+
6+
```
7+
T = [1, 2, 3, 4]
8+
Q = [A, B]
9+
```
10+
11+
Let's first see the sdp:
12+
13+
```
14+
sdp(T, Q) = [1*A + 2*B, 2*A + 3*B, 3*A + 4*B]
15+
```
16+
17+
To compute this using FFT-based convolution, we need to reverse the query Q and pad it with zeros to match the length of T.
18+
19+
```
20+
T = [1, 2, 3, 4]
21+
Q_reversed_padded = [B, A, 0, 0]
22+
```
23+
24+
Then, we use circular convolution to compute the result, $QT_{conv}$. The formula for circular convolution in time domain is:
25+
26+
27+
$$QT_{conv}[i] = \sum_{j=0}^{N-1} T[j] \cdot Q[(i - j) \mod N]$$
28+
29+
where $N$ is the length of the sequences (in this case, 4).
30+
31+
Let's compute the circular convolution:
32+
33+
```
34+
conv(T, Q_reversed_padded) = [
35+
1B + 4A,
36+
1A + 2B,
37+
2A + 3B,
38+
3A + 4B,
39+
] =
40+
```
41+
42+
In sdp, we only care about the slice [M-1:N], which is called 'valid' mode in convolution terminology, and that slice gives us the same result as sdp.
43+
44+
45+
Now, let's consider one more elelment in T, say:
46+
<br>
47+
`T_new = [1, 2, 3, 4, 5]`
48+
49+
We know that the sdp between `Q` and `T_new` is:
50+
51+
```
52+
sdp(T_new, Q) = [
53+
1A + 2B,
54+
2A + 3B,
55+
3A + 4B,
56+
4A + 5B
57+
]
58+
```
59+
60+
So, the only new item is `4A + 5B`. However, if we look closely, we can see that the only new multiplication we need to perform is `5*B` IF we already have `4A` computed from the previous sdp. Note that we did not compute `4A` previously because the circular convolution wraps around and mixes it with `1B`. However, if there is a way to get that `4A` only in the previous step, we can only compute the new multiplication `5*B` and add it to `4A` to get the new sdp value. If the circular convolution avoids the wrap-around, we can achieve this. This is where zero-padding comes into play! We can see `T_new` as two parts: `T1=[1, 2, 3, 4]` and `T2=[5]`.
61+
62+
```
63+
T1_with_0 = [1, 2, 3, 4, 0]
64+
Q_reversed_padded = [B, A, 0, 0, 0]
65+
66+
conv(T1_with_0, Q_reversed_padded) = [
67+
1B + 0A,
68+
1A + 2B,
69+
2A + 3B,
70+
3A + 4B,
71+
4A + 0B,
72+
]
73+
```
74+
75+
Great! We have `4A`! How about `5B` part? We can compute this by convolving `T2` with `Q` as well:
76+
77+
```
78+
T2_with_0 = [5, 0, 0, 0, 0]
79+
Q_reversed_padded = [B, A, 0, 0, 0]
80+
81+
conv(T2_with_0, Q_reversed_padded) = [
82+
5B + 0A,
83+
0,
84+
0,
85+
0,
86+
0,
87+
]
88+
```
89+
90+
So, we can see the sdp can be computed as:
91+
92+
```
93+
SDP([A, B], [1, 2, 3, 4, 5]) = [
94+
1A + 2B, # from "valid" portion
95+
2A + 3B, # from "valid" portion
96+
3A + 4B, # from "valid" portion
97+
(4A + 0B) + (5B + 0A) = 4A + 5B
98+
# last element of conv(T1...) + first element of conv(T2...)
99+
]
100+
```
101+
102+
This is the basic concept behind overlap-add. As far as I understand, there is no need to have same-size chunks. However, having same-size chunks makes the implementation easier. Note that we need `(M-1)` zeros to avoid wrap-around, where `M` is the length of the query. So, for a given block size of `B`, we need to have chunks of size `B - (M - 1)`. That last chunk can be padded with more zeroes to make it of size `B`. We know that the first `chunksize` elements of each circular convolution are what we need for the sdp, while adding the last `(M-1)` elements of `c-th` chunk to the first `(M-1)` elements of `(c+1)-th` chunk.
103+

0 commit comments

Comments
 (0)