Skip to content

Commit d5e02ae

Browse files
committed
caught a goof in optimize that was causing bad results in my experiments and, inspired by what it showed me, rewrote the optimizer unit tests to hopefully catch things like that in the future
1 parent 852ad4c commit d5e02ae

3 files changed

Lines changed: 34 additions & 47 deletions

File tree

examples/4_performance_analysis.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
{
1414
"cell_type": "code",
15-
"execution_count": 1,
15+
"execution_count": null,
1616
"id": "5e5ea3c3-aed3-4b8d-a7c7-1db05fc73f3c",
1717
"metadata": {},
1818
"outputs": [],
@@ -26,7 +26,7 @@
2626
"from tqdm import tqdm\n",
2727
"from collections import defaultdict\n",
2828
"import multiprocessing as mp\n",
29-
"mp.set_start_method(\"fork\", force=True)\n",
29+
"#mp.set_start_method(\"fork\", force=True)\n",
3030
"\n",
3131
"from pynumdiff.utils.simulate import sine, triangle, pop_dyn, linear_autonomous, pi_cruise_control, lorenz_x\n",
3232
"from pynumdiff.utils.evaluate import rmse, error_correlation\n",
@@ -277,8 +277,8 @@
277277
"\tlegend1 = ax[0].legend(ncol=2, columnspacing=0.5, handletextpad=0, loc='upper left', fontsize=15)\n",
278278
"\tax[0].add_artist(legend1)\n",
279279
"\tfor handle in legend1.legend_handles:\n",
280-
"\t handle.set_edgecolor('dimgray')\n",
281-
"\t if len(handle.get_facecolor()) == 1: handle.set_facecolor('dimgray') # for those that are filled\n",
280+
"\t\thandle.set_edgecolor('dimgray')\n",
281+
"\t\tif len(handle.get_facecolor()) == 1: handle.set_facecolor('dimgray') # for those that are filled\n",
282282
"\tsim_patches = [mpatches.Patch(color=colors[j], label=sname) for j,(sim,sname) in enumerate(sims)]\n",
283283
"\tlegend2 = ax[0].legend(handles=sim_patches, loc='upper left', fontsize=15, bbox_to_anchor=(0.175, 1.0))\n",
284284
"\n",

pynumdiff/optimize/_optimize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def optimize(func, x, dt, dxdt_truth=None, tvgamma=1e-2, search_space_updates={}
230230
categorical_params=categorical_combo, search_space_types=search_space_types, dxdt_truth=dxdt_truth,
231231
metric=metric, tvgamma=tvgamma, padding=padding, cache=cache)
232232
_minimize = partial(scipy.optimize.minimize, _obj_fun, method=opt_method, bounds=bounds, options={'maxiter':maxiter})
233-
results = [_minimize(p) for p in starting_points]
233+
results += [_minimize(p) for p in starting_points]
234234

235235
opt_idx = np.nanargmin([r.fun for r in results])
236236
opt_point = results[opt_idx].x

pynumdiff/tests/test_optimize.py

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,46 @@
22
from pytest import skip
33

44
from ..finite_difference import first_order as iterated_finite_difference
5-
from ..smooth_finite_difference import mediandiff, meandiff, gaussiandiff, friedrichsdiff, butterdiff
5+
from ..smooth_finite_difference import butterdiff
66
from ..basis_fit import spectraldiff
77
from ..polynomial_fit import polydiff, savgoldiff, splinediff
88
from ..total_variation_regularization import velocity, acceleration, iterative_velocity
99
from ..kalman_smooth import rtsdiff
1010
from ..optimize import optimize
1111
from ..utils.simulate import pi_cruise_control
12+
from ..utils.evaluate import rmse
1213

1314

14-
# simulation
1515
dt = 0.01
1616
x, x_truth, dxdt_truth = pi_cruise_control(duration=2, noise_type='normal', noise_parameters=[0, 0.01], dt=dt)
17-
cutoff_frequency = 10 # in Hz
18-
log_gamma = -1.6 * np.log(cutoff_frequency) - 0.71 * np.log(dt) - 5.1
19-
tvgamma = np.exp(log_gamma)
17+
cutoff_frequency = 3 # in Hz
18+
tvgamma = np.exp(-1.6 * np.log(cutoff_frequency) - 0.71 * np.log(dt) - 5.1)
2019

2120

22-
def test_finite_difference():
23-
params1, val1 = optimize(iterated_finite_difference, x, dt, dxdt_truth=dxdt_truth, padding='auto')
24-
params2, val2 = optimize(iterated_finite_difference, x, dt, tvgamma=tvgamma, dxdt_truth=None, padding='auto')
25-
assert params1['num_iterations'] == 5
26-
assert params2['num_iterations'] == 1
21+
def test_parallel_same_as_serial():
22+
"""Ensure running optimize across several processes returns the same result as running in a single process"""
23+
params_parallel, val_parallel = optimize(rtsdiff, x, dt, tvgamma=tvgamma, parallel=True)
24+
params_serial, val_serial = optimize(rtsdiff, x, dt, tvgamma=tvgamma, parallel=False)
2725

28-
def test_iterative_velocity():
29-
params1, val1 = optimize(iterative_velocity, x, dt, dxdt_truth=dxdt_truth, search_space_updates={'num_iterations':1}, padding='auto')
30-
params2, val2 = optimize(iterative_velocity, x, dt, tvgamma=tvgamma, search_space_updates={'num_iterations':1}, padding='auto')
26+
assert np.allclose(val_serial, val_parallel)
27+
assert params_serial == params_parallel
28+
29+
30+
def test_targeting_rmse_vs_tvgamma_loss():
31+
"""Ensure optimization properly targets different metrics"""
32+
params_rmse, val_rmse = optimize(splinediff, x, dt, dxdt_truth=dxdt_truth)
33+
params_loss, val_loss = optimize(splinediff, x, dt, tvgamma=tvgamma)
3134

32-
np.testing.assert_almost_equal(params1['gamma'], 0.0001, decimal=4)
33-
np.testing.assert_almost_equal(params2['gamma'], 0.0001, decimal=4)
34-
35-
def test_savgoldiff():
36-
params1, val1 = optimize(savgoldiff, x, dt, dxdt_truth=dxdt_truth, padding='auto')
37-
params2, val2 = optimize(savgoldiff, x, dt, tvgamma=tvgamma, padding='auto')
38-
assert (params1['degree'], params1['window_size'], params1['smoothing_win']) == (7, 41, 3)
39-
assert (params2['degree'], params2['window_size'], params2['smoothing_win']) == (3, 3, 5)
40-
41-
def test_spectraldiff():
42-
params1, val1 = optimize(spectraldiff, x, dt, dxdt_truth=dxdt_truth, padding='auto')
43-
params2, val2 = optimize(spectraldiff, x, dt, tvgamma=tvgamma, padding='auto')
44-
np.testing.assert_almost_equal(params1['high_freq_cutoff'], 0.18, decimal=2)
45-
np.testing.assert_almost_equal(params2['high_freq_cutoff'], 0.155, decimal=2)
46-
47-
def test_polydiff():
48-
params1, val1 = optimize(polydiff, x, dt, dxdt_truth=dxdt_truth, search_space_updates={'step_size':1}, padding='auto')
49-
params2, val2 = optimize(polydiff, x, dt, tvgamma=tvgamma, search_space_updates={'step_size':1}, padding='auto')
50-
assert (params1['degree'], params1['window_size'], params1['kernel']) == (6, 50, 'friedrichs')
51-
assert (params2['degree'], params2['window_size'], params2['kernel']) == (3, 10, 'gaussian')
52-
53-
# This test runs in a reasonable amount of time locally but for some reason takes forever in CI
54-
# def test_rtsdiff_with_irregular_step():
55-
# t = np.arange(len(x))*dt; np.random.seed(7) # seed so the test can't randomly fail
56-
# t_irreg = t + np.random.uniform(-dt/10, dt/10, *t.shape) # add jostle
57-
# params1, val1 = optimize(rtsdiff, x, t, dxdt_truth=dxdt_truth)
58-
# params2, val2 = optimize(rtsdiff, x, t_irreg, dxdt_truth=dxdt_truth)
59-
# assert val2 < 1.15*val1 # optimization works and comes out similar, since jostle is small
60-
# assert params1['qr_ratio']*0.85 < params2['qr_ratio'] < params1['qr_ratio']*1.15
35+
x_hat, dxdt_hat = splinediff(x, dt, **params_loss)
36+
loss_rmse = rmse(dxdt_truth, dxdt_hat)
37+
38+
assert val_rmse < loss_rmse < 1.1*val_rmse # This exact bound might break if using a different diff method or data series, but the point is they should be close
39+
40+
41+
def test_search_space_updates_applied():
42+
"""Ensure search space updates are used in optimization"""
43+
params2, _ = optimize(butterdiff, x, dt, search_space_updates={'filter_order':2}, tvgamma=tvgamma)
44+
params3, _ = optimize(butterdiff, x, dt, search_space_updates={'filter_order':3}, tvgamma=tvgamma)
45+
46+
assert params2['filter_order'] == 2
47+
assert params3['filter_order'] == 3

0 commit comments

Comments
 (0)