Skip to content

Commit 926901a

Browse files
authored
Merge pull request #113 from florisvb/move-tvr-tests
moved tvr tests to new paradigm
2 parents c2461d2 + c74ac0f commit 926901a

3 files changed

Lines changed: 62 additions & 134 deletions

File tree

pynumdiff/tests/test_diff_methods.py

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from warnings import warn
55

66
from ..linear_model import lineardiff, polydiff, savgoldiff, spectraldiff
7-
from ..total_variation_regularization import velocity, acceleration, jerk, iterative_velocity
8-
from ..kalman_smooth import constant_velocity, constant_acceleration, constant_jerk, known_dynamics
7+
from ..total_variation_regularization import velocity, acceleration, jerk, iterative_velocity, smooth_acceleration
8+
from ..kalman_smooth import constant_velocity, constant_acceleration, constant_jerk
99
from ..smooth_finite_difference import mediandiff, meandiff, gaussiandiff, friedrichsdiff, butterdiff, splinediff
1010
from ..finite_difference import first_order, second_order
1111
# Function aliases for testing cases where parameters change the behavior in a big way
@@ -47,6 +47,12 @@ def iterated_first_order(*args, **kwargs): return first_order(*args, **kwargs)
4747
(constant_acceleration, {'r':1e-4, 'q':1e-1}), (constant_acceleration, [1e-4, 1e-1]),
4848
(constant_jerk, {'r':1e-4, 'q':10}), (constant_jerk, [1e-4, 10]),
4949
# TODO (known_dynamics), but presently it doesn't calculate a derivative
50+
(velocity, {'gamma':0.5}), (velocity, [0.5]),
51+
(acceleration, {'gamma':1}), (acceleration, [1]),
52+
(jerk, {'gamma':10}), (jerk, [10]),
53+
(iterative_velocity, {'num_iterations':5, 'gamma':0.05}), (iterative_velocity, [5, 0.05]),
54+
(smooth_acceleration, {'gamma':2, 'window_size':5}), (smooth_acceleration, [2, 5])
55+
# TODO (jerk_sliding), because with the test cases here (len < 1000) it would just be a duplicate of jerk
5056
]
5157

5258
# All the testing methodology follows the exact same pattern; the only thing that changes is the
@@ -148,7 +154,37 @@ def iterated_first_order(*args, **kwargs): return first_order(*args, **kwargs)
148154
[(-4, -4), (-2, -2), (0, 0), (1, 0)],
149155
[(-1, -2), (1, 0), (0, 0), (1, 0)],
150156
[(1, 0), (2, 2), (1, 0), (2, 2)],
151-
[(1, 1), (3, 3), (1, 1), (3, 3)]]
157+
[(1, 1), (3, 3), (1, 1), (3, 3)]],
158+
velocity: [[(-25, -25), (-18, -19), (0, -1), (1, 0)],
159+
[(-12, -12), (-11, -12), (-1, -1), (-1, -2)],
160+
[(0, 0), (1, 0), (0, 0), (1, 0)],
161+
[(0, -1), (1, 1), (0, 0), (1, 0)],
162+
[(1, 1), (2, 2), (1, 1), (2, 2)],
163+
[(1, 0), (3, 3), (1, 0), (3, 3)]],
164+
acceleration: [[(-25, -25), (-18, -18), (0, -1), (0, 0)],
165+
[(-10, -10), (-9, -9), (-1, -1), (0, -1)],
166+
[(-10, -10), (-9, -10), (-1, -1), (0, -1)],
167+
[(0, -1), (1, 0), (0, -1), (1, 0)],
168+
[(1, 1), (2, 2), (1, 1), (2, 2)],
169+
[(1, 1), (3, 3), (1, 1), (3, 3)]],
170+
jerk: [[(-25, -25), (-18, -18), (-1, -1), (0, 0)],
171+
[(-9, -10), (-9, -9), (-1, -1), (0, 0)],
172+
[(-10, -10), (-9, -10), (-1, -1), (0, 0)],
173+
[(0, 0), (1, 1), (0, 0), (1, 1)],
174+
[(1, 1), (2, 2), (1, 1), (2, 2)],
175+
[(1, 1), (3, 3), (1, 1), (3, 3)]],
176+
iterative_velocity: [[(-9, -10), (-25, -25), (0, -1), (0, 0)],
177+
[(0, 0), (0, 0), (0, 0), (1, 0)],
178+
[(0, 0), (1, 0), (1, 0), (1, 0)],
179+
[(1, 0), (1, 1), (1, 0), (1, 1)],
180+
[(2, 1), (2, 2), (2, 1), (2, 2)],
181+
[(1, 1), (3, 3), (1, 1), (3, 3)]],
182+
smooth_acceleration: [[(-9, -10), (-18, -18), (0, -1), (0, 0)],
183+
[(-9, -9), (-10, -10), (-1, -1), (-1, -1)],
184+
[(-2, -2), (-1, -1), (-1, -1), (0, -1)],
185+
[(0, 0), (1, 0), (0, -1), (1, 0)],
186+
[(1, 1), (2, 2), (1, 1), (2, 2)],
187+
[(1, 1), (3, 3), (1, 1), (3, 3)]]
152188
}
153189

154190
# Essentially run the cartesian product of [diff methods] x [test functions] through this one test
@@ -162,13 +198,14 @@ def test_diff_method(diff_method_and_params, test_func_and_deriv, request): # re
162198
i, latex_name, f, df = test_func_and_deriv
163199

164200
# some methods rely on cvxpy, and we'd like to allow use of pynumdiff without convex optimization
165-
if diff_method in [lineardiff, velocity]:
201+
if diff_method in [lineardiff, velocity, acceleration, jerk, smooth_acceleration]:
166202
try: import cvxpy
167203
except: warn(f"Cannot import cvxpy, skipping {diff_method} test."); return
168204

169-
x = f(t) # sample the function
170-
x_noisy = x + noise # add a little noise
171-
dxdt = df(t) # true values of the derivative at samples
205+
# sample the true function and make noisy samples, and sample true derivative
206+
x = f(t)
207+
x_noisy = x + noise
208+
dxdt = df(t)
172209

173210
# differentiate without and with noise, accounting for new and old styles of calling functions
174211
x_hat, dxdt_hat = diff_method(x, dt, **params) if isinstance(params, dict) \
@@ -178,6 +215,7 @@ def test_diff_method(diff_method_and_params, test_func_and_deriv, request): # re
178215
else diff_method(x_noisy, dt, params) if (isinstance(params, list) and len(diff_method_and_params) < 3) \
179216
else diff_method(x_noisy, dt, params, options)
180217

218+
# plotting code
181219
if request.config.getoption("--plot") and not isinstance(params, list): # Get the plot flag from pytest configuration
182220
fig, axes = request.config.plots[diff_method] # get the appropriate plot, set up by the store_plots fixture in conftest.py
183221
axes[i, 0].plot(t, f(t), label=r"$x(t)$")
@@ -198,16 +236,20 @@ def test_diff_method(diff_method_and_params, test_func_and_deriv, request): # re
198236
if i == 0: axes[i, 1].set_title('with noise')
199237

200238
# check x_hat and x_hat_noisy are close to x and that dxdt_hat and dxdt_hat_noisy are close to dxdt
201-
if request.config.getoption("--bounds"): print("]\n[", end="")
239+
if request.config.getoption("--bounds"): print("\n[", end="")
202240
for j,(a,b) in enumerate([(x,x_hat), (dxdt,dxdt_hat), (x,x_hat_noisy), (dxdt,dxdt_hat_noisy)]):
241+
l2_error = np.linalg.norm(a - b)
242+
linf_error = np.max(np.abs(a - b))
243+
244+
# bounds-printing for establishing bounds
203245
if request.config.getoption("--bounds"):
204-
l2_error = np.linalg.norm(a - b)
205-
linf_error = np.max(np.abs(a - b))
206246
#print(f"({l2_error},{linf_error})", end=", ")
207247
print(f"({int(np.ceil(np.log10(l2_error))) if l2_error > 0 else -25}, {int(np.ceil(np.log10(linf_error))) if linf_error > 0 else -25})", end=", ")
248+
# bounds checking
208249
else:
209250
log_l2_bound, log_linf_bound = error_bounds[diff_method][i][j]
210-
assert np.linalg.norm(a - b) < 10**log_l2_bound
211-
assert np.max(np.abs(a - b)) < 10**log_linf_bound
212-
if 0 < np.linalg.norm(a - b) < 10**(log_l2_bound - 1) or 0 < np.max(np.abs(a - b)) < 10**(log_linf_bound - 1):
251+
assert l2_error < 10**log_l2_bound
252+
assert linf_error < 10**log_linf_bound
253+
# methods that get super duper close can converge to different very small limits on different runs
254+
if 1e-18 < l2_error < 10**(log_l2_bound - 1) or 1e-18 < linf_error < 10**(log_linf_bound - 1):
213255
print(f"Improvement detected for method {diff_method.__name__}")

pynumdiff/tests/test_total_variation_regularization.py

Lines changed: 0 additions & 115 deletions
This file was deleted.

pynumdiff/total_variation_regularization/_total_variation_regularization.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
except ImportError: pass
99

1010

11-
def iterative_velocity(x, dt, params, options=None, num_iterations=None, gamma=None, cg_maxiter=1000, scale='small'):
11+
def iterative_velocity(x, dt, params=None, options=None, num_iterations=None, gamma=None, cg_maxiter=1000, scale='small'):
1212
"""Use an iterative solver to find the total variation regularized 1st derivative. See
1313
_chartrand_tvregdiff.py for details, author info, and license. Methods described in:
1414
Rick Chartrand, "Numerical differentiation of noisy, nonsmooth data," ISRN Applied Mathematics,
@@ -72,6 +72,7 @@ def _total_variation_regularized_derivative(x, dt, order, gamma, solver=None):
7272
# Normalize
7373
mean = np.mean(x)
7474
std = np.std(x)
75+
if std == 0: std = 1 # safety guard
7576
x = (x-mean)/std
7677

7778
# Define the variables for the highest order derivative and the integration constants
@@ -108,7 +109,7 @@ def _total_variation_regularized_derivative(x, dt, order, gamma, solver=None):
108109
return x_hat*std+mean, dxdt_hat*std
109110

110111

111-
def velocity(x, dt, params, options=None, gamma=None, solver=None):
112+
def velocity(x, dt, params=None, options=None, gamma=None, solver=None):
112113
"""Use convex optimization (cvxpy) to solve for the velocity total variation regularized derivative.
113114
114115
:param np.array[float] x: data to differentiate
@@ -135,7 +136,7 @@ def velocity(x, dt, params, options=None, gamma=None, solver=None):
135136
return _total_variation_regularized_derivative(x, dt, 1, gamma, solver=solver)
136137

137138

138-
def acceleration(x, dt, params, options=None, gamma=None, solver=None):
139+
def acceleration(x, dt, params=None, options=None, gamma=None, solver=None):
139140
"""Use convex optimization (cvxpy) to solve for the acceleration total variation regularized derivative.
140141
141142
:param np.array[float] x: data to differentiate
@@ -162,7 +163,7 @@ def acceleration(x, dt, params, options=None, gamma=None, solver=None):
162163
return _total_variation_regularized_derivative(x, dt, 2, gamma, solver=solver)
163164

164165

165-
def jerk(x, dt, params, options=None, gamma=None, solver=None):
166+
def jerk(x, dt, params=None, options=None, gamma=None, solver=None):
166167
"""Use convex optimization (cvxpy) to solve for the jerk total variation regularized derivative.
167168
168169
:param np.array[float] x: data to differentiate
@@ -189,7 +190,7 @@ def jerk(x, dt, params, options=None, gamma=None, solver=None):
189190
return _total_variation_regularized_derivative(x, dt, 3, gamma, solver=solver)
190191

191192

192-
def smooth_acceleration(x, dt, params, options=None, gamma=None, window_size=None, solver=None):
193+
def smooth_acceleration(x, dt, params=None, options=None, gamma=None, window_size=None, solver=None):
193194
"""Use convex optimization (cvxpy) to solve for the acceleration total variation regularized derivative,
194195
and then apply a convolutional gaussian smoother to the resulting derivative to smooth out the peaks.
195196
The end result is similar to the jerk method, but can be more time-efficient.
@@ -228,7 +229,7 @@ def smooth_acceleration(x, dt, params, options=None, gamma=None, window_size=Non
228229
return x_hat, dxdt_hat
229230

230231

231-
def jerk_sliding(x, dt, params, options=None, gamma=None, solver=None):
232+
def jerk_sliding(x, dt, params=None, options=None, gamma=None, solver=None):
232233
"""Use convex optimization (cvxpy) to solve for the jerk total variation regularized derivative.
233234
234235
:param np.array[float] x: data to differentiate

0 commit comments

Comments
 (0)