Skip to content

Commit c2461d2

Browse files
authored
Merge pull request #112 from florisvb/fix-lineardiff
Fix lineardiff tests
2 parents bd74bc1 + 5ce7660 commit c2461d2

3 files changed

Lines changed: 41 additions & 47 deletions

File tree

pynumdiff/linear_model/_linear_model.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
from pynumdiff.finite_difference import first_order as finite_difference
77
from pynumdiff.utils import utility
88

9-
try:
10-
import cvxpy
11-
except ImportError:
12-
pass
9+
try: import cvxpy
10+
except ImportError: pass
1311

1412
KERNELS = {'friedrichs': utility._friedrichs_kernel,
1513
'gaussian': utility._gaussian_kernel}
@@ -299,7 +297,7 @@ def polydiff(x, dt, params=None, options=None, polynomial_order=None, window_siz
299297

300298

301299
def __solve_for_A_and_C_given_X_and_Xdot__(X, Xdot, num_integrations, dt, gammaC=1e-1, gammaA=1e-6,
302-
solver='MOSEK', A_known=None, epsilon=1e-6, rows_of_interest='all'):
300+
solver=None, A_known=None, epsilon=1e-6, rows_of_interest='all'):
303301
"""Given state and the derivative, find the system evolution and measurement matrices.
304302
"""
305303

@@ -338,7 +336,7 @@ def __solve_for_A_and_C_given_X_and_Xdot__(X, Xdot, num_integrations, dt, gammaC
338336

339337
# Solve the problem
340338
prob = cvxpy.Problem(obj, constraints)
341-
prob.solve(solver=solver) # MOSEK does not take max_iters
339+
prob.solve(solver=solver)
342340

343341
A = np.array(A.value)
344342
return A, np.array(C.value)
@@ -355,7 +353,7 @@ def __integrate_dxdt_hat_matrix__(dxdt_hat, dt):
355353
return x
356354

357355

358-
def _lineardiff(x, dt, N, gamma, solver='MOSEK', weights=None):
356+
def _lineardiff(x, dt, N, gamma, solver=None, weights=None):
359357
"""Estimate the parameters for a system xdot = Ax, and use that to calculate the derivative
360358
361359
:param np.array[float] x: time series to differentiate
@@ -405,7 +403,7 @@ def _lineardiff(x, dt, N, gamma, solver='MOSEK', weights=None):
405403

406404

407405
def lineardiff(x, dt, params=None, options=None, order=None, gamma=None, window_size=None,
408-
sliding=True, step_size=10, kernel='friedrichs', solver='MOSEK'):
406+
sliding=True, step_size=10, kernel='friedrichs', solver=None):
409407
"""Slide a smoothing derivative function across a time series with specified window size.
410408
411409
:param np.array[float] x: array of time series to differentiate

pynumdiff/tests/test_diff_methods.py

Lines changed: 22 additions & 18 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
(first_order, {}), # empty dictionary for the case of no parameters. no params -> no diff in new vs old
3434
(iterated_first_order, {'num_iterations':5}), (iterated_first_order, [5], {'iterate':True}),
3535
(second_order, {}),
36-
#(lineardiff, {'order':3, 'gamma':5, 'window_size':10, 'solver':'CVXOPT'}),
36+
(lineardiff, {'order':3, 'gamma':5, 'window_size':10, 'solver':'CLARABEL'}), (lineardiff, [3, 5, 10], {'solver':'CLARABEL'}),
3737
(polydiff, {'polynomial_order':2, 'window_size':3}), (polydiff, [2, 3]),
3838
(savgoldiff, {'polynomial_order':2, 'window_size':4, 'smoothing_win':4}), (savgoldiff, [2, 4, 4]),
3939
(spectraldiff, {'high_freq_cutoff':0.1}), (spectraldiff, [0.1]),
@@ -48,7 +48,6 @@ def iterated_first_order(*args, **kwargs): return first_order(*args, **kwargs)
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
5050
]
51-
diff_methods_and_params = [(constant_jerk, {'r':1e-4, 'q':10})]
5251

5352
# All the testing methodology follows the exact same pattern; the only thing that changes is the
5453
# closeness to the right answer various methods achieve with the given parameterizations. So index a
@@ -72,7 +71,12 @@ def iterated_first_order(*args, **kwargs): return first_order(*args, **kwargs)
7271
[(-25, -25), (0, -1), (0, 0), (1, 1)],
7372
[(-25, -25), (1, 1), (0, 0), (1, 1)],
7473
[(-25, -25), (3, 3), (0, 0), (3, 3)]],
75-
#lineardiff: [TBD when #91 is solved],
74+
lineardiff: [[(-6, -6), (-5, -6), (0, -1), (0, 0)],
75+
[(0, 0), (1, 1), (0, 0), (1, 1)],
76+
[(1, 0), (2, 1), (1, 0), (2, 1)],
77+
[(1, 0), (1, 1), (1, 0), (1, 1)],
78+
[(1, 1), (2, 2), (1, 1), (2, 2)],
79+
[(1, 1), (3, 3), (1, 1), (3, 3)]],
7680
polydiff: [[(-14, -15), (-14, -14), (0, -1), (1, 1)],
7781
[(-14, -14), (-13, -13), (0, -1), (1, 1)],
7882
[(-14, -14), (-13, -13), (0, -1), (1, 1)],
@@ -173,21 +177,6 @@ def test_diff_method(diff_method_and_params, test_func_and_deriv, request): # re
173177
x_hat_noisy, dxdt_hat_noisy = diff_method(x_noisy, dt, **params) if isinstance(params, dict) \
174178
else diff_method(x_noisy, dt, params) if (isinstance(params, list) and len(diff_method_and_params) < 3) \
175179
else diff_method(x_noisy, dt, params, options)
176-
177-
# check x_hat and x_hat_noisy are close to x and that dxdt_hat and dxdt_hat_noisy are close to dxdt
178-
if request.config.getoption("--bounds"): print("]\n[", end="")
179-
for j,(a,b) in enumerate([(x,x_hat), (dxdt,dxdt_hat), (x,x_hat_noisy), (dxdt,dxdt_hat_noisy)]):
180-
if request.config.getoption("--bounds"):
181-
l2_error = np.linalg.norm(a - b)
182-
linf_error = np.max(np.abs(a - b))
183-
#print(f"({l2_error},{linf_error})", end=", ")
184-
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=", ")
185-
else:
186-
log_l2_bound, log_linf_bound = error_bounds[diff_method][i][j]
187-
assert np.linalg.norm(a - b) < 10**log_l2_bound
188-
assert np.max(np.abs(a - b)) < 10**log_linf_bound
189-
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):
190-
print(f"Improvement detected for method {diff_method.__name__}")
191180

192181
if request.config.getoption("--plot") and not isinstance(params, list): # Get the plot flag from pytest configuration
193182
fig, axes = request.config.plots[diff_method] # get the appropriate plot, set up by the store_plots fixture in conftest.py
@@ -207,3 +196,18 @@ def test_diff_method(diff_method_and_params, test_func_and_deriv, request): # re
207196
else: axes[i, 1].set_xlabel('t')
208197
axes[i, 1].set_yticklabels([])
209198
if i == 0: axes[i, 1].set_title('with noise')
199+
200+
# 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="")
202+
for j,(a,b) in enumerate([(x,x_hat), (dxdt,dxdt_hat), (x,x_hat_noisy), (dxdt,dxdt_hat_noisy)]):
203+
if request.config.getoption("--bounds"):
204+
l2_error = np.linalg.norm(a - b)
205+
linf_error = np.max(np.abs(a - b))
206+
#print(f"({l2_error},{linf_error})", end=", ")
207+
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=", ")
208+
else:
209+
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):
213+
print(f"Improvement detected for method {diff_method.__name__}")

pynumdiff/total_variation_regularization/_total_variation_regularization.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,6 @@
77
try: import cvxpy
88
except ImportError: pass
99

10-
try:
11-
import mosek
12-
solver = 'MOSEK' # https://www.mosek.com/
13-
except ImportError:
14-
warn("MOSEK not installed, falling back to CVXPY's defaults")
15-
solver = None # passing this to solve() allows CVXPY to use whatever it deems best
16-
1710

1811
def iterative_velocity(x, dt, params, options=None, num_iterations=None, gamma=None, cg_maxiter=1000, scale='small'):
1912
"""Use an iterative solver to find the total variation regularized 1st derivative. See
@@ -61,17 +54,16 @@ def iterative_velocity(x, dt, params, options=None, num_iterations=None, gamma=N
6154
return x_hat, dxdt_hat
6255

6356

64-
def _total_variation_regularized_derivative(x, dt, order, gamma, solver=solver):
57+
def _total_variation_regularized_derivative(x, dt, order, gamma, solver=None):
6558
"""Generalized total variation regularized derivatives. Use convex optimization (cvxpy) to solve for a
66-
total variation regularized derivative. Default solver is MOSEK: , which is not
67-
always available. Fall back to CVXPY's default.
59+
total variation regularized derivative.
6860
6961
:param np.array[float] x: data to differentiate
7062
:param float dt: time step
7163
:param int order: 1, 2, or 3, the derivative to regularize
7264
:param float gamma: regularization parameter
7365
:param str solver: Solver to use. Solver options include: 'MOSEK', 'CVXOPT', 'CLARABEL', 'ECOS'.
74-
In testing, 'MOSEK' was the most robust.
66+
In testing, 'MOSEK' was the most robust. If not given, fall back to CVXPY's default.
7567
7668
:return: tuple[np.array, np.array] of\n
7769
- **x_hat** -- estimated (smoothed) x
@@ -116,7 +108,7 @@ def _total_variation_regularized_derivative(x, dt, order, gamma, solver=solver):
116108
return x_hat*std+mean, dxdt_hat*std
117109

118110

119-
def velocity(x, dt, params, options=None, gamma=None, solver=solver):
111+
def velocity(x, dt, params, options=None, gamma=None, solver=None):
120112
"""Use convex optimization (cvxpy) to solve for the velocity total variation regularized derivative.
121113
122114
:param np.array[float] x: data to differentiate
@@ -125,7 +117,7 @@ def velocity(x, dt, params, options=None, gamma=None, solver=solver):
125117
:param dict options: (**deprecated**, prefer :code:`solver`) a dictionary consisting of {'solver': (str)}
126118
:param float gamma: the regularization parameter
127119
:param str solver: the solver CVXPY should use, 'MOSEK', 'CVXOPT', 'CLARABEL', 'ECOS', etc.
128-
In testing, 'MOSEK' was the most robust. Default is to use CVXPY's default.
120+
In testing, 'MOSEK' was the most robust. If not given, fall back to CVXPY's default.
129121
130122
:return: tuple[np.array, np.array] of\n
131123
- **x_hat** -- estimated (smoothed) x
@@ -143,7 +135,7 @@ def velocity(x, dt, params, options=None, gamma=None, solver=solver):
143135
return _total_variation_regularized_derivative(x, dt, 1, gamma, solver=solver)
144136

145137

146-
def acceleration(x, dt, params, options=None, gamma=None, solver=solver):
138+
def acceleration(x, dt, params, options=None, gamma=None, solver=None):
147139
"""Use convex optimization (cvxpy) to solve for the acceleration total variation regularized derivative.
148140
149141
:param np.array[float] x: data to differentiate
@@ -152,7 +144,7 @@ def acceleration(x, dt, params, options=None, gamma=None, solver=solver):
152144
:param dict options: (**deprecated**, prefer :code:`solver`) a dictionary consisting of {'solver': (str)}
153145
:param float gamma: the regularization parameter
154146
:param str solver: the solver CVXPY should use, 'MOSEK', 'CVXOPT', 'CLARABEL', 'ECOS', etc.
155-
In testing, 'MOSEK' was the most robust. Default is to use CVXPY's default.
147+
In testing, 'MOSEK' was the most robust. If not given, fall back to CVXPY's default.
156148
157149
:return: tuple[np.array, np.array] of\n
158150
- **x_hat** -- estimated (smoothed) x
@@ -170,7 +162,7 @@ def acceleration(x, dt, params, options=None, gamma=None, solver=solver):
170162
return _total_variation_regularized_derivative(x, dt, 2, gamma, solver=solver)
171163

172164

173-
def jerk(x, dt, params, options=None, gamma=None, solver=solver):
165+
def jerk(x, dt, params, options=None, gamma=None, solver=None):
174166
"""Use convex optimization (cvxpy) to solve for the jerk total variation regularized derivative.
175167
176168
:param np.array[float] x: data to differentiate
@@ -179,7 +171,7 @@ def jerk(x, dt, params, options=None, gamma=None, solver=solver):
179171
:param dict options: (**deprecated**, prefer :code:`solver`) a dictionary consisting of {'solver': (str)}
180172
:param float gamma: the regularization parameter
181173
:param str solver: the solver CVXPY should use, 'MOSEK', 'CVXOPT', 'CLARABEL', 'ECOS', etc.
182-
In testing, 'MOSEK' was the most robust. Default is to use CVXPY's default.
174+
In testing, 'MOSEK' was the most robust. If not given, fall back to CVXPY's default.
183175
184176
:return: tuple[np.array, np.array] of\n
185177
- **x_hat** -- estimated (smoothed) x
@@ -197,7 +189,7 @@ def jerk(x, dt, params, options=None, gamma=None, solver=solver):
197189
return _total_variation_regularized_derivative(x, dt, 3, gamma, solver=solver)
198190

199191

200-
def smooth_acceleration(x, dt, params, options=None, gamma=None, window_size=None, solver=solver):
192+
def smooth_acceleration(x, dt, params, options=None, gamma=None, window_size=None, solver=None):
201193
"""Use convex optimization (cvxpy) to solve for the acceleration total variation regularized derivative,
202194
and then apply a convolutional gaussian smoother to the resulting derivative to smooth out the peaks.
203195
The end result is similar to the jerk method, but can be more time-efficient.
@@ -209,7 +201,7 @@ def smooth_acceleration(x, dt, params, options=None, gamma=None, window_size=Non
209201
:param float gamma: the regularization parameter
210202
:param int window_size: window size for gaussian kernel
211203
:param str solver: the solver CVXPY should use, 'MOSEK', 'CVXOPT', 'CLARABEL', 'ECOS', etc.
212-
In testing, 'MOSEK' was the most robust. Default is to use CVXPY's default.
204+
In testing, 'MOSEK' was the most robust. If not given, fall back to CVXPY's default.
213205
214206
:return: tuple[np.array, np.array] of\n
215207
- **x_hat** -- estimated (smoothed) x
@@ -236,7 +228,7 @@ def smooth_acceleration(x, dt, params, options=None, gamma=None, window_size=Non
236228
return x_hat, dxdt_hat
237229

238230

239-
def jerk_sliding(x, dt, params, options=None, gamma=None, solver=solver):
231+
def jerk_sliding(x, dt, params, options=None, gamma=None, solver=None):
240232
"""Use convex optimization (cvxpy) to solve for the jerk total variation regularized derivative.
241233
242234
:param np.array[float] x: data to differentiate
@@ -245,7 +237,7 @@ def jerk_sliding(x, dt, params, options=None, gamma=None, solver=solver):
245237
:param dict options: (**deprecated**, prefer :code:`solver`) a dictionary consisting of {'solver': (str)}
246238
:param float gamma: the regularization parameter
247239
:param str solver: the solver CVXPY should use, 'MOSEK', 'CVXOPT', 'CLARABEL', 'ECOS', etc.
248-
In testing, 'MOSEK' was the most robust. Default is to use CVXPY's default.
240+
In testing, 'MOSEK' was the most robust. If not given, fall back to CVXPY's default.
249241
250242
:return: tuple[np.array, np.array] of\n
251243
- **x_hat** -- estimated (smoothed) x

0 commit comments

Comments
 (0)