Skip to content

Commit 15c8870

Browse files
authored
Merge pull request #124 from florisvb/hack-the-optimizer
Optimizer things
2 parents a1d7aa7 + 2ca3740 commit 15c8870

8 files changed

Lines changed: 332 additions & 602 deletions

File tree

examples/1_basic_tutorial.ipynb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,15 @@
5050
"metadata": {},
5151
"outputs": [],
5252
"source": [
53-
"import os\n",
54-
"import sys\n",
55-
"import time\n",
56-
"import numpy as np\n",
53+
"import os, sys\n",
5754
"\n",
5855
"# local import\n",
5956
"module_path = os.path.abspath(os.path.join('..'))\n",
6057
"if module_path not in sys.path:\n",
6158
" sys.path.append(module_path)\n",
6259
" \n",
6360
"import pynumdiff\n",
64-
"import pynumdiff.utils.simulate as simulate\n",
65-
"import pynumdiff.utils.evaluate as evaluate"
61+
"from pynumdiff.utils import simulate, evaluate"
6662
]
6763
},
6864
{

examples/2a_optimizing_parameters_with_dxdt_known.ipynb

Lines changed: 29 additions & 36 deletions
Large diffs are not rendered by default.

pynumdiff/optimize/__init__.py

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,14 @@
1+
"""Import functions from the optimize module
12
"""
2-
Import useful functions from the optimize module
3-
"""
4-
5-
import logging as _logging
6-
_logging.basicConfig(
7-
level=_logging.INFO,
8-
format="%(asctime)s [%(levelname)s] %(message)s",
9-
handlers=[
10-
_logging.FileHandler("debug.log"),
11-
_logging.StreamHandler()
12-
]
13-
)
14-
153
try:
164
import cvxpy
17-
__cvxpy_installed__ = True
5+
from . import total_variation_regularization
186
except ImportError:
19-
_logging.info( 'Limited support for total variation regularization and linear model detected!\n\
20-
---> Some functions in pynumdiff.optimize.total_variation_regularization and require CVXPY to be installed.\n\
21-
---> Some functions in pynumdiff.linear_model and require CVXPY to be installed.\n\
22-
You can still pynumdiff.optimize for other functions.')
23-
__cvxpy_installed__ = False
24-
25-
from pynumdiff.optimize import finite_difference
26-
from pynumdiff.optimize import smooth_finite_difference
27-
from pynumdiff.optimize import total_variation_regularization
28-
from pynumdiff.optimize import linear_model
29-
from pynumdiff.optimize import kalman_smooth
30-
31-
7+
from warnings import warn
8+
warn("Limited support for total variation regularization and linear model detected! " +
9+
"Some functions in the `total_variation_regularization` and `linear_model` modules require " +
10+
"CVXPY to be installed. You can still pynumdiff.optimize for other functions.")
3211

12+
from . import finite_difference, smooth_finite_difference, linear_model, kalman_smooth
3313

14+
__all__ = ['finite_difference', 'smooth_finite_difference', 'linear_model', 'kalman_smooth', 'total_variation_regularization'] # So these get treated as direct members of the module by sphinx

pynumdiff/tests/test_optimize.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,15 @@
1212
from pynumdiff.optimize.linear_model import *
1313
from pynumdiff.optimize.kalman_smooth import constant_velocity, constant_acceleration, \
1414
constant_jerk
15-
from pynumdiff.utils import simulate
15+
from pynumdiff.utils.simulate import pi_control
1616

1717

1818
# simulation
1919
noise_type = 'normal'
2020
noise_parameters = [0, 0.01]
2121
dt = 0.01
22-
timeseries_length = 2
23-
problem = 'pi_control'
24-
x, x_truth, dxdt_truth, extras = simulate.__dict__[problem](timeseries_length,
25-
noise_parameters=noise_parameters,
26-
dt=dt)
22+
duration = 2
23+
x, x_truth, dxdt_truth, extras = pi_control(duration, noise_parameters=noise_parameters, dt=dt)
2724
cutoff_frequency = 0.1
2825
log_gamma = -1.6 * np.log(cutoff_frequency) - 0.71 * np.log(dt) - 5.1
2926
tvgamma = np.exp(log_gamma)

pynumdiff/total_variation_regularization/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,3 @@
1313
from ._total_variation_regularization import iterative_velocity
1414

1515
__all__ = ['velocity', 'acceleration', 'jerk', 'jerk_sliding', 'smooth_acceleration', 'iterative_velocity'] # So these get treated as direct members of the module by sphinx
16-

pynumdiff/utils/evaluate.py

Lines changed: 57 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,34 @@
1-
"""
2-
Metrics and evaluations?
3-
"""
4-
import numpy as _np
5-
import matplotlib.pyplot as _plt
6-
import scipy.stats as _scipy_stats
1+
"""Some tools to help evaluate and plot performance, used in optimization and in jupyter notebooks"""
2+
import numpy as np
3+
import matplotlib.pyplot as plt
4+
from scipy import stats
75

8-
# local imports
9-
from pynumdiff.utils import utility as _utility
10-
from pynumdiff.finite_difference import first_order as _finite_difference
6+
from pynumdiff.utils import utility
117

128

139
# pylint: disable-msg=too-many-locals, too-many-arguments
1410
def plot(x, dt, x_hat, dxdt_hat, x_truth, dxdt_truth, xlim=None, ax_x=None, ax_dxdt=None,
1511
show_error=True, markersize=5):
16-
"""
17-
Make comparison plots of 'x (blue) vs x_truth (black) vs x_hat (red)' and
18-
'dxdt_truth (black) vs dxdt_hat (red)'
19-
20-
:param x: array of noisy time series
21-
:type x: np.array (float)
22-
23-
:param dt: a float number representing the step size
24-
:type dt: float
25-
26-
:param x_hat: array of smoothed estimation of x
27-
:type x_hat: np.array (float)
28-
29-
:param dxdt_hat: array of estimated derivative
30-
:type dxdt_hat: np.array (float)
31-
32-
:param x_truth: array of noise-free time series
33-
:type x_truth: np.array (float)
34-
35-
:param dxdt_truth: array of true derivative
36-
:type dxdt_truth: np.array (float)
37-
38-
:param xlim: a list specifying range of x
39-
:type xlim: list (2 integers), optional
40-
41-
:param ax_x: axis of the first plot
42-
:type ax_x: :class:`matplotlib.axes`, optional
43-
44-
:param ax_dxdt: axis of the second plot
45-
:type ax_dxdt: :class:`matplotlib.axes`, optional
46-
47-
:param show_error: whether to show the rmse
48-
:type show_error: boolean, optional
49-
50-
:param markersize: marker size of noisy observations
51-
:type markersize: int, optional
12+
"""Make comparison plots of 'x (blue) vs x_truth (black) vs x_hat (red)' and 'dxdt_truth
13+
(black) vs dxdt_hat (red)'
14+
15+
:param np.array[float] x: array of noisy data
16+
:param float dt: a float number representing the step size
17+
:param np.array[float] x_hat: array of smoothed estimation of x
18+
:param np.array[float] dxdt_hat: array of estimated derivative
19+
:param np.array[float] x_truth: array of noise-free time series
20+
:param np.array[float] dxdt_truth: array of true derivative
21+
:param list[int] xlim: a list specifying range of x
22+
:param matplotlib.axes ax_x: axis of the first plot
23+
:param matplotlib.axes ax_dxdt: axis of the second plot
24+
:param bool show_error: whether to show the rmse
25+
:param int markersize: marker size of noisy observations
5226
5327
:return: Display two plots
54-
:rtype: None
5528
"""
56-
t = _np.arange(0, dt*len(x), dt)
29+
t = np.arange(0, dt*len(x), dt)
5730
if ax_x is None and ax_dxdt is None:
58-
fig = _plt.figure(figsize=(20, 6))
31+
fig = plt.figure(figsize=(20, 6))
5932
ax_x = fig.add_subplot(121)
6033
ax_dxdt = fig.add_subplot(122)
6134

@@ -89,125 +62,59 @@ def plot(x, dt, x_hat, dxdt_hat, x_truth, dxdt_truth, xlim=None, ax_x=None, ax_d
8962
print('RMS error in velocity: ', rms_dxdt)
9063

9164

92-
def __rms_error__(a, e):
93-
"""
94-
Calculate rms error
95-
96-
:param a: the first array
97-
:param e: the second array
98-
:return: a float number representing the rms error
99-
"""
100-
if _np.max(_np.abs(a-e)) > 1e16:
101-
return 1e16
102-
s_error = _np.ravel((a - e))**2
103-
ms_error = _np.mean(s_error)
104-
rms_error = _np.sqrt(ms_error)
105-
return rms_error
106-
107-
108-
def metrics(x, dt, x_hat, dxdt_hat, x_truth=None, dxdt_truth=None, padding=None):
109-
"""
110-
Evaluate x_hat based on various metrics, depending on whether dxdt_truth and x_truth are known or not.
111-
112-
:param x: time series that was differentiated
113-
:type x: np.array
114-
115-
:param dt: time step in seconds
116-
:type dt: float
117-
118-
:param x_hat: estimated (smoothed) x
119-
:type x_hat: np.array
65+
def metrics(x, dt, x_hat, dxdt_hat, x_truth=None, dxdt_truth=None, padding=0):
66+
"""Evaluate x_hat based on various metrics, depending on whether dxdt_truth and x_truth are known or not.
12067
121-
:param dxdt_hat: estimated xdot
122-
:type dxdt_hat: np.array
123-
124-
:param x_truth: true value of x, if known, optional
125-
:type x_truth: np.array like x or None
126-
127-
:param dxdt_truth: true value of dxdt, if known, optional
128-
:type dxdt_truth: np.array like x or None
129-
130-
:param padding: number of snapshots on either side of the array to ignore when calculating the metric. If autor or None, defaults to 2.5% of the size of x
131-
:type padding: int, None, or auto
132-
133-
:return: a tuple containing the following:
134-
- rms_rec_x: RMS error between the integral of dxdt_hat and x
135-
- rms_x: RMS error between x_hat and x_truth, returns None if x_truth is None
136-
- rms_dxdt: RMS error between dxdt_hat and dxdt_truth, returns None if dxdt_hat is None
137-
:rtype: tuple -> (float, float, float)
68+
:param np.array[float] x: data that was differentiated
69+
:param float dt: step size
70+
:param np.array[float] x_hat: estimated (smoothed) x
71+
:param np.array[float] dxdt_hat: estimated xdot
72+
:param np.array[float] x_truth: true value of x, if known
73+
:param np.array[float] dxdt_truth: true value of dxdt, if known, optional
74+
:param int padding: number of snapshots on either side of the array to ignore when calculating
75+
the metric. If :code:`'auto'`, defaults to 2.5% of the size of x
13876
77+
:return: tuple[float, float, float] containing\n
78+
- **rms_rec_x** -- RMS error between the integral of dxdt_hat and x
79+
- **rms_x** -- RMS error between x_hat and x_truth, returns None if x_truth is None
80+
- **rms_dxdt** -- RMS error between dxdt_hat and dxdt_truth, returns None if dxdt_hat is None
13981
"""
82+
if np.isnan(x_hat).any():
83+
return np.nan, np.nan, np.nan
14084
if padding is None or padding == 'auto':
14185
padding = int(0.025*len(x))
14286
padding = max(padding, 1)
143-
if _np.isnan(x_hat).any():
144-
return _np.nan, _np.nan, _np.nan
145-
146-
# RMS dxdt
147-
if dxdt_truth is not None:
148-
rms_dxdt = __rms_error__(dxdt_hat[padding:-padding], dxdt_truth[padding:-padding])
149-
else:
150-
rms_dxdt = None
151-
152-
# RMS x
153-
if x_truth is not None:
154-
rms_x = __rms_error__(x_hat[padding:-padding], x_truth[padding:-padding])
155-
else:
156-
rms_x = None
157-
158-
# RMS reconstructed x
159-
rec_x = _utility.integrate_dxdt_hat(dxdt_hat, dt)
160-
x0 = _utility.estimate_initial_condition(x, rec_x)
87+
s = slice(padding,len(x)-padding) # slice out where the data is
88+
89+
# RMS of dxdt and x_hat
90+
root = np.sqrt(s.stop - s.start)
91+
rms_dxdt = np.linalg.norm(dxdt_hat[s] - dxdt_truth[s]) / root if dxdt_truth is not None else None
92+
rms_x = np.linalg.norm(x_hat[s] - x_truth[s]) / root if x_truth is not None else None
93+
94+
# RMS reconstructed x from integrating dxdt vs given raw x, available even in the absence of ground truth
95+
rec_x = utility.integrate_dxdt_hat(dxdt_hat, dt)
96+
x0 = utility.estimate_initial_condition(x, rec_x)
16197
rec_x = rec_x + x0
162-
rms_rec_x = __rms_error__(rec_x[padding:-padding], x[padding:-padding])
98+
rms_rec_x = np.linalg.norm(rec_x[s] - x[s]) / root
16399

164100
return rms_rec_x, rms_x, rms_dxdt
165101

166102

167103
def error_correlation(dxdt_hat, dxdt_truth, padding=None):
168-
"""
169-
Calculate the error correlation (pearsons correlation coefficient) between the estimated dxdt and true dxdt
170-
171-
:param dxdt_hat: estimated xdot
172-
:type dxdt_hat: np.array
173-
174-
:param dxdt_truth: true value of dxdt, if known, optional
175-
:type dxdt_truth: np.array like x or None
104+
"""Calculate the error correlation (pearsons correlation coefficient) between the estimated
105+
dxdt and true dxdt
176106
177-
:param padding: number of snapshots on either side of the array to ignore when calculating the metric. If autor or None, defaults to 2.5% of the size of x
178-
:type padding: int, None, or auto
179-
180-
:return: r-squared correlation coefficient
181-
:rtype: float
107+
:param np.array[float] dxdt_hat: estimated xdot
108+
:param np.array[float] dxdt_truth: true value of dxdt, if known, optional
109+
:param int padding: number of snapshots on either side of the array to ignore when calculating
110+
the metric. If auto or None, defaults to 2.5% of the size of x
182111
112+
:return: (float) -- r-squared correlation coefficient
183113
"""
184114
if padding is None or padding == 'auto':
185115
padding = int(0.025*len(dxdt_hat))
186116
padding = max(padding, 1)
187117
errors = (dxdt_hat[padding:-padding] - dxdt_truth[padding:-padding])
188-
r = _scipy_stats.linregress(dxdt_truth[padding:-padding] -
189-
_np.mean(dxdt_truth[padding:-padding]), errors)
118+
r = stats.linregress(dxdt_truth[padding:-padding] -
119+
np.mean(dxdt_truth[padding:-padding]), errors)
190120
return r.rvalue**2
191-
192-
193-
def rmse(dxdt_hat, dxdt_truth, padding=None):
194-
"""
195-
Calculate the Root Mean Squared Error between the estimated dxdt and true dxdt
196-
197-
:param dxdt_hat: estimated xdot
198-
:type dxdt_hat: np.array
199-
200-
:param dxdt_truth: true value of dxdt, if known, optional
201-
:type dxdt_truth: np.array like x or None
202-
203-
:param padding: number of snapshots on either side of the array to ignore when calculating the metric. If autor or None, defaults to 2.5% of the size of x
204-
:type padding: int, None, or auto
205-
206-
:return: Root Mean Squared Error
207-
:rtype: float
208-
"""
209-
if padding is None or padding == 'auto':
210-
padding = int(0.025*len(dxdt_hat))
211-
padding = max(padding, 1)
212-
RMSE = _np.sqrt(_np.mean((dxdt_hat[padding:-padding] - dxdt_truth[padding:-padding])**2))
213-
return RMSE

0 commit comments

Comments
 (0)