Skip to content

Commit f563ee8

Browse files
authored
Merge pull request #130 from florisvb/simplify-convolutional-smoother
making convolutional smoother more efficient to address #129
2 parents ac9691b + 35ab06c commit f563ee8

3 files changed

Lines changed: 22 additions & 14 deletions

File tree

pynumdiff/tests/test_diff_methods.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def iterated_first_order(*args, **kwargs): return first_order(*args, **kwargs)
3333
(iterated_first_order, {'num_iterations':2}), (iterated_first_order, [2], {'iterate':True}),
3434
(lineardiff, {'order':3, 'gamma':5, 'window_size':11, 'solver':'CLARABEL'}), (lineardiff, [3, 5, 11], {'solver':'CLARABEL'}),
3535
(polydiff, {'poly_order':2, 'window_size':3}), (polydiff, [2, 3]),
36-
(savgoldiff, {'poly_order':2, 'window_size':4, 'smoothing_win':4}), (savgoldiff, [2, 4, 4]),
36+
(savgoldiff, {'poly_order':2, 'window_size':5, 'smoothing_win':5}), (savgoldiff, [2, 5, 5]),
3737
(spectraldiff, {'high_freq_cutoff':0.1}), (spectraldiff, [0.1]),
3838
(mediandiff, {'window_size':3, 'num_iterations':2}), (mediandiff, [3, 2], {'iterate':True}),
3939
(meandiff, {'window_size':3, 'num_iterations':2}), (meandiff, [3, 2], {'iterate':True}),
@@ -95,13 +95,13 @@ def iterated_first_order(*args, **kwargs): return first_order(*args, **kwargs)
9595
[(0, 0), (3, 3), (0, 0), (3, 3)]],
9696
savgoldiff: [[(-9, -10), (-13, -14), (0, -1), (0, 0)],
9797
[(-9, -10), (-13, -13), (0, -1), (0, 0)],
98-
[(-1, -1), (0, -1), (0, -1), (0, 0)],
99-
[(0, -1), (0, 0), (0, -1), (1, 0)],
98+
[(-2, -2), (-1, -1), (0, -1), (0, 0)],
99+
[(0, -1), (0, 0), (0, 0), (1, 0)],
100100
[(1, 1), (2, 2), (1, 1), (2, 2)],
101101
[(1, 1), (3, 3), (1, 1), (3, 3)]],
102102
spectraldiff: [[(-9, -10), (-14, -15), (-1, -1), (0, 0)],
103103
[(0, 0), (1, 1), (0, 0), (1, 1)],
104-
[(1, 0), (1, 1), (1, 1), (1, 1)],
104+
[(1, 1), (1, 1), (1, 1), (1, 1)],
105105
[(0, 0), (1, 1), (0, 0), (1, 1)],
106106
[(1, 1), (2, 2), (1, 1), (2, 2)],
107107
[(1, 1), (3, 3), (1, 1), (3, 3)]],

pynumdiff/tests/test_utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ def test_estimate_integration_constant():
3333
assert 0.9 < x0 < 1.1 # The result should be close to 1.0, but not exactly due to noise
3434

3535

36+
def test_convolutional_smoother():
37+
"""Ensure the convolutional smoother isn't introducing edge effects"""
38+
x = np.ones(10)
39+
kernel_odd = np.ones(3)/3
40+
kernel_even = np.ones(4)/4
41+
42+
assert np.allclose(utility.convolutional_smoother(x, kernel_odd, num_iterations=3), np.ones(len(x)))
43+
assert np.allclose(utility.convolutional_smoother(x, kernel_even, num_iterations=3), np.ones(len(x)))
44+
45+
3646
def test_hankel_matrix():
3747
"""Ensure Hankel matrix comes back as defined"""
3848
assert np.allclose(utility.hankel_matrix([1, 2, 3, 4, 5], 3), [[1, 2, 3],[2, 3, 4],[3, 4, 5]])

pynumdiff/utils/utility.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,25 +140,23 @@ def friedrichs_kernel(window_size):
140140
ker = np.exp(-1/(1-x**2))
141141
return ker / np.sum(ker)
142142

143-
def convolutional_smoother(x, kernel, iterations=1):
143+
def convolutional_smoother(x, kernel, num_iterations=1):
144144
"""Perform smoothing by convolving x with a kernel.
145145
146146
:param np.array[float] x: 1D data
147147
:param np.array[float] kernel: kernel to use in convolution
148-
:param int iterations: number of iterations, >=1
148+
:param int num_iterations: number of iterations, >=1
149149
150150
:return: **x_hat** (np.array[float]) -- smoothed x
151151
"""
152-
x_hat = np.hstack((x[::-1], x, x[::-1])) # pad
153-
w = np.linspace(0, 1, len(x_hat)) # weights
152+
pad_width = len(kernel)//2
153+
x_hat = x
154154

155-
for _ in range(iterations):
156-
x_hat_f = np.convolve(x_hat, kernel, 'same')
157-
x_hat_b = np.convolve(x_hat[::-1], kernel, 'same')[::-1]
158-
159-
x_hat = x_hat_f*w + x_hat_b*(1-w)
155+
for i in range(num_iterations):
156+
x_padded = np.pad(x_hat, pad_width, mode='symmetric') # pad with repetition of the edges
157+
x_hat = np.convolve(x_padded, kernel, 'valid')[:len(x)] # 'valid' slices out only full-overlap spots
160158

161-
return x_hat[len(x):len(x)*2]
159+
return x_hat
162160

163161

164162
def slide_function(func, x, dt, kernel, *args, stride=1, pass_weights=False, **kwargs):

0 commit comments

Comments
 (0)