1717####################
1818# Helper functions #
1919####################
20- def __slide_function__ (func , x , dt , params , window_size , step_size , kernel_name , solver = None ):
21- """
22- Slide a smoothing derivative function across a timeseries with specified window size.
23-
24- :param func: (function) name of the function to slide
25- :param x: (np.array of floats, 1xN) time series to differentiate
26- :param dt: (float) time step
27- :param params: (list) see func for requirements
28- :param window_size: (int) size of the sliding window
29- :param step_size: (int) step size for slide (e.g. 1 means slide by 1 step)
30- :param kernel_name: (string) name of the smoothing kernel (e.g. 'friedrichs' or 'gaussian')
31- :return: x_hat : estimated (smoothed) x
32- dxdt_hat : estimated derivative of x
20+ def _slide_function (func , x , dt , args , window_size , step_size , kernel_name ):
21+ """Slide a smoothing derivative function across a timeseries with specified window size.
22+
23+ :param callable func: name of the function to slide
24+ :param np.array[float] x: time series to differentiate
25+ :param float dt: time step
26+ :param dict args: see func for requirements
27+ :param int window_size: size of the sliding window
28+ :param int step_size: step size for slide (e.g. 1 means slide by 1 step)
29+ :param str kernel_name: name of the smoothing kernel (e.g. 'friedrichs' or 'gaussian')
30+
31+ :return: tuple[np.array, np.array] of\n
32+ - **x_hat** -- estimated (smoothed) x
33+ - **dxdt_hat** -- estimated derivative of x
3334 """
3435
3536 # get smoothing kernel
36- if not window_size % 2 : # then make odd
37+ if not window_size % 2 : # then make odd
3738 window_size += 1
3839 ker = KERNELS [kernel_name ](window_size )
3940
@@ -62,7 +63,7 @@ def __slide_function__(func, x, dt, params, window_size, step_size, kernel_name,
6263
6364 # run the function on the window
6465 _x = x [start :end ]
65- x_hat , dxdt_hat = func (_x , dt , params , options = { ' weights' : w , 'solver' : solver } )
66+ x_hat , dxdt_hat = func (_x , dt , * args , weights = w )
6667
6768 # stack results
6869 z_x_hat = np .zeros ([len (x )])
@@ -150,97 +151,70 @@ def savgoldiff(x, dt, params, options=None):
150151######################
151152# Polynomial fitting #
152153######################
153- def __polydiff__ (x , dt , params , options = None ):
154- """
155- Fit polynomials to the timeseries, and differentiate the polynomials.
154+ def _polydiff (x , dt , polynomial_order , weights = None ):
155+ """Fit polynomials to the timeseries, and differentiate the polynomials.
156156
157- :param x: (np.array of floats, 1xN) time series to differentiate
158- :param dt: (float) time step
159- :param params: (list) [N] : (int) order of the polynomial
160- :param options:(dict) {'weights'} : (np.array, optional) weights applied to each point in calculating the
161- polynomial fit. Defaults to 1s if missing.
162-
163- :return: x_hat : estimated (smoothed) x
164- dxdt_hat : estimated derivative of x
157+ :param np.array[float] x: time series to differentiate
158+ :param float dt: time step
159+ :param int polynomial_order: order of the polynomial
160+ :param np.array[float] weights: weights applied to each point in calculating the polynomial fit.
161+ Defaults to 1s if missing.
165162
163+ :return: tuple[np.array, np.array] of\n
164+ - **x_hat** -- estimated (smoothed) x
165+ - **dxdt_hat** -- estimated derivative of x
166166 """
167- if isinstance (options , dict ) and 'weights' in options .keys ():
168- w = options ['weights' ]
169- else :
170- w = np .ones_like (x )
171-
172- if isinstance (params , list ):
173- order = params [0 ]
174- else :
175- order = params
167+ if weights is None :
168+ weights = np .ones (x .shape )
176169
177170 t = np .arange (1 , len (x )+ 1 )* dt
178171
179- # polyfit
180- r = np .polyfit ( t , x , order , w = w )[:: - 1 ]
172+ r = np . polyfit ( t , x , polynomial_order , w = weights ) # polyfit returns highest order first
173+ dr = np .polyder ( r ) # power rule already implemented for us
181174
182- # derivative coefficients
183- dr = copy .copy (r [1 :])
184- for i , _ in enumerate (dr ):
185- dr [i ] = dr [i ]* (i + 1 )
186-
187- # evaluate dxdt_hat
188- dxdt_hat = 0
189- for i , _ in enumerate (dr ):
190- dxdt_hat += dr [i ]* t ** i
191-
192- # evaluate smooth x
193- x_hat = 0
194- for i , _ in enumerate (r ):
195- x_hat += r [i ]* t ** i
175+ dxdt_hat = np .polyval (dr , t ) # evaluate the derivative and original polynomials at points t
176+ x_hat = np .polyval (r , t ) # smoothed x
196177
197178 return x_hat , dxdt_hat
198179
199180
200- def polydiff (x , dt , params , options = None ):
201- """
202- Fit polynomials to the time series, and differentiate the polynomials.
181+ def polydiff (x , dt , params = None , options = None , polynomial_order = None , window_size = None ,
182+ sliding = True , step_size = 1 , kernel = 'friedrichs' ):
183+ """ Fit polynomials to the time series, and differentiate the polynomials.
203184
204- :param x: array of time series to differentiate
205- :type x: np.array (float)
206-
207- :param dt: time step size
208- :type dt: float
209-
210- :param params: a list of 2 elements:
211-
212- - N: order of the polynomial
213- - window_size: size of the sliding window (ignored if not sliding)
214-
215- :type params: list (int)
216-
217- :param options: a dictionary consisting of 3 key value pairs:
218-
219- - 'sliding': whether to use sliding approach
220- - 'step_size': step size for sliding
221- - 'kernel_name': kernel to use for weighting and smoothing windows ('gaussian' or 'friedrichs')
222-
223- :type options: dict {'sliding': (bool), 'step_size': (int), 'kernel_name': (string)}, optional
224-
225- :return: a tuple consisting of:
226-
227- - x_hat: estimated (smoothed) x
228- - dxdt_hat: estimated derivative of x
185+ :param np.array[float] x: array of time series to differentiate
186+ :param float dt: time step size
187+ :param list[int] params: (**deprecated**, prefer :code:`polynomial_order` and :code:`window_size`)
188+ :param dict options: (**deprecated**, prefer :code:`sliding`, :code:`step_size`, and :code:`kernel`)
189+ a dictionary consisting of {'sliding': (bool), 'step_size': (int), 'kernel_name': (str)}
190+ :param int polynomial_order: order of the polynomial
191+ :param int window_size: size of the sliding window (ignored if not sliding)
192+ :param bool sliding: whether to use sliding approach
193+ :param int step_size: step size for sliding
194+ :param str kernel: kernel to use for weighting and smoothing windows ('gaussian' or 'friedrichs')
229195
230- :rtype: tuple -> (np.array, np.array)
196+ :return: tuple[np.array, np.array] of\n
197+ - **x_hat** -- estimated (smoothed) x
198+ - **dxdt_hat** -- estimated derivative of x
231199 """
200+ if params != None :
201+ warn ("""`params` and `options` parameters will be removed in a future version. Use `polynomial_order`
202+ and `window_size` instead.""" , DeprecationWarning )
203+ polynomial_order , window_size = params
204+ if options != None :
205+ if 'sliding' in options : sliding = options ['sliding' ]
206+ if 'step_size' in options : step_size = options ['step_size' ]
207+ if 'kernel_name' in options : kernel = options ['kernel_name' ]
208+ elif polynomial_order == None or window_size == None :
209+ raise ValueError ("`polynomial_order` and `window_size` must be given." )
232210
233- if options is None :
234- options = { 'sliding' : True , 'step_size' : 1 , 'kernel_name' : 'friedrichs' }
211+ if window_size < polynomial_order * 3 :
212+ window_size = polynomial_order * 3 + 1
235213
236- if 'sliding' in options .keys () and options ['sliding' ] is True :
237- window_size = copy .copy (params [- 1 ])
238- if window_size < params [0 ]* 3 :
239- window_size = params [0 ]* 3 + 1
240- params [1 ] = window_size
241- return __slide_function__ (__polydiff__ , x , dt , params , window_size , options ['step_size' ], options ['kernel_name' ])
214+ if sliding :
215+ return _slide_function (_polydiff , x , dt , [polynomial_order ], window_size , step_size , kernel )
242216
243- return __polydiff__ (x , dt , params , options = {} )
217+ return _polydiff (x , dt , polynomial_order )
244218
245219
246220#############
@@ -402,25 +376,23 @@ def __integrate_dxdt_hat_matrix__(dxdt_hat, dt):
402376 return x
403377
404378
405- def __lineardiff__ (x , dt , params , options = None ):
406- """
407- Estimate the parameters for a system xdot = Ax, and use that to calculate the derivative
408-
409- :param x: (np.array of floats, 1xN) time series to differentiate
410- :param dt: (float) time step
411- :param params: (list) [N, : (int, >1) order (e.g. 2: velocity; 3: acceleration)
412- gamma] : (float) regularization term
413- :return: x_hat : estimated (smoothed) x
414- dxdt_hat : estimated derivative of x
415- """
416- if options is None :
417- options = {'solver' : 'MOSEK' }
379+ def _lineardiff (x , dt , N , gamma , solver = 'MOSEK' , weights = None ):
380+ """Estimate the parameters for a system xdot = Ax, and use that to calculate the derivative
418381
419- N , gamma = params
382+ :param np.array[float] x: time series to differentiate
383+ :param float dt: time step
384+ :param int > 1 N: order (e.g. 2: velocity; 3: acceleration)
385+ :param float gamma: regularization term
386+ :param np.array[float] weights: Bug? Currently not used here, although `lineardiff` takes a kernel
387+ :param str solver: which cvxpy solver to use
388+
389+ :return: tuple[np.array, np.array] of\n
390+ - **x_hat** -- estimated (smoothed) x
391+ - **dxdt_hat** -- estimated derivative of x
392+ """
420393 mean = np .mean (x )
421394 x = x - mean
422395
423-
424396 # Generate the matrix of integrals of x
425397 X = [x ]
426398 for n in range (1 , N ):
@@ -430,7 +402,7 @@ def __lineardiff__(x, dt, params, options=None):
430402 integral_X = __integrate_dxdt_hat_matrix__ (X , dt )
431403
432404 # Solve for A and the integration constants
433- A , C = __solve_for_A_and_C_given_X_and_Xdot__ (integral_X , integral_Xdot , N , dt , gamma , solver = options [ ' solver' ] )
405+ A , C = __solve_for_A_and_C_given_X_and_Xdot__ (integral_X , integral_Xdot , N , dt , gamma , solver = solver )
434406
435407 # Add the integration constants
436408 Csum = 0
@@ -453,61 +425,45 @@ def __lineardiff__(x, dt, params, options=None):
453425 return x_hat , dxdt_hat
454426
455427
456- def lineardiff (x , dt , params , options = None ):
457- """
458- Slide a smoothing derivative function across a time series with specified window size.
459-
460- :param x: array of time series to differentiate
461- :type x: np.array (float)
462-
463- :param dt: time step size
464- :type dt: float
465-
466- :param params: a list of 3 elements:
428+ def lineardiff (x , dt , params = None , options = None , polynomial_order = None , gamma = None , window_size = None ,
429+ sliding = True , step_size = 10 , kernel = 'friedrichs' , solver = 'MOSEK' ):
430+ """Slide a smoothing derivative function across a time series with specified window size.
467431
468- - N: order of the polynomial
469- - gamma: regularization term
470- - window_size: size of the sliding window (ignored if not sliding)
471-
472- :type params: list (int, float, int)
473-
474- :param options: a dictionary consisting of 3 key value pairs:
475-
476- - 'sliding': whether to use sliding approach
477- - 'step_size': step size for sliding
478- - 'kernel_name': kernel to use for weighting and smoothing windows ('gaussian' or 'friedrichs')
479-
480- :type options: dict {'sliding': (bool), 'step_size': (int), 'kernel_name': (string)}, optional
481-
482- :return: a tuple consisting of:
483-
484- - x_hat: estimated (smoothed) x
485- - dxdt_hat: estimated derivative of x
432+ :param np.array[float] x: array of time series to differentiate
433+ :param float dt: time step size
434+ :param list[int, float, int] params: (**deprecated**, prefer :code:`polynomial_order`, :code:`gamma`, and :code:`window_size`)
435+ :param dict options: (**deprecated**, prefer :code:`sliding`, :code:`step_size`, :code:`kernel`, and :code:`solver`
436+ a dictionary consisting of {'sliding': (bool), 'step_size': (int), 'kernel_name': (str), 'solver': (str)}
437+ :param int polynomial_order: order of the polynomial
438+ :param float gamma: regularization term
439+ :param int window_size: size of the sliding window (ignored if not sliding)
440+ :param bool sliding: whether to use sliding approach
441+ :param int step_size: step size for sliding
442+ :param str kernel_name: kernel to use for weighting and smoothing windows ('gaussian' or 'friedrichs')
443+ :param str solver: CVXPY solver to use, one of :code:`cvxpy.installed_solvers()`
486444
487- :rtype: tuple -> (np.array, np.array)
445+ :return: tuple[np.array, np.array] of\n
446+ - **x_hat** -- estimated (smoothed) x
447+ - **dxdt_hat** -- estimated derivative of x
488448 """
489-
490- if options is None :
491- options = {'sliding' : True , 'step_size' : 10 , 'kernel_name' : 'friedrichs' , 'solver' : 'MOSEK' }
492-
493- if 'sliding' not in options .keys ():
494- options ['sliding' ] = True
495- if 'step_size' not in options .keys ():
496- options ['step_size' ] = 10
497- if 'kernel_name' not in options .keys ():
498- options ['kernel_name' ] = 'friedrichs'
499- if 'solver' not in options .keys ():
500- options ['solver' ] = 'MOSEK'
501-
502- if 'sliding' in options .keys () and options ['sliding' ] is True :
503- window_size = copy .copy (params [- 1 ])
504- params = params [0 :- 1 ]
505-
449+ if params != None :
450+ warn ("""`params` and `options` parameters will be removed in a future version. Use `polynomial_order`,
451+ `gamma`, and `window_size` instead.""" , DeprecationWarning )
452+ polynomial_order , gamma , window_size = params
453+ if options != None :
454+ if 'sliding' in options : sliding = options ['sliding' ]
455+ if 'step_size' in options : step_size = options ['step_size' ]
456+ if 'kernel_name' in options : kernel = options ['kernel_name' ]
457+ if 'solver' in options : solver = options ['solver' ]
458+ elif polynomial_order == None or gamma == None or window_size == None :
459+ raise ValueError ("`polynomial_order`, `gamma`, and `window_size` must be given." )
460+
461+ if sliding :
506462 # forward and backward
507- x_hat_forward , _ = __slide_function__ ( __lineardiff__ , x , dt , params , window_size , options [ ' step_size' ] ,
508- options [ 'kernel_name' ], options [ 'solver' ] )
509- x_hat_backward , _ = __slide_function__ ( __lineardiff__ , x [::- 1 ], dt , params , window_size , options [ ' step_size' ] ,
510- options [ 'kernel_name' ], options [ 'solver' ] )
463+ x_hat_forward , _ = _slide_function ( _lineardiff , x , dt , [ polynomial_order , gamma , solver ], window_size , step_size ,
464+ kernel )
465+ x_hat_backward , _ = _slide_function ( _lineardiff , x [::- 1 ], dt , [ polynomial_order , gamma , solver ], window_size , step_size ,
466+ kernel )
511467
512468 # weights
513469 w = np .arange (1 , len (x_hat_forward )+ 1 ,1 )[::- 1 ]
@@ -525,7 +481,7 @@ def lineardiff(x, dt, params, options=None):
525481
526482 return x_hat , dxdt_hat
527483
528- return __lineardiff__ (x , dt , params , options )
484+ return _lineardiff (x , dt , params , options )
529485
530486###############################
531487# Fourier Spectral derivative #
@@ -549,11 +505,11 @@ def spectraldiff(x, dt, params=None, options=None, high_freq_cutoff=None, even_e
549505 """
550506 if params != None : # Warning to support old interface for a while. Remove these lines along with params in a future release.
551507 warn ("""`params` and `options` parameters will be removed in a future version. Use `high_freq_cutoff`,
552- `even_extension`, and `pad_to_zero_dxdt` instead.""" )
553- if options is None :
554- even_extension = True
555- pad_to_zero_dxdt = True
508+ `even_extension`, and `pad_to_zero_dxdt` instead.""" , DeprecationWarning )
556509 high_freq_cutoff = params [0 ] if isinstance (params , list ) else params
510+ if options != None :
511+ if 'even_extension' in options : high_freq_cutoff = options ['even_extension' ]
512+ if 'pad_to_zero_dxdt' in options : pad_to_zero_dxdt = options ['pad_to_zero_dxdt' ]
557513 elif high_freq_cutoff == None :
558514 raise ValueError ("`high_freq_cutoff` must be given." )
559515
0 commit comments