Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions docs/source/finite_difference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@ finite_difference
.. automodule:: pynumdiff.finite_difference
:no-members:

.. autofunction:: pynumdiff.finite_difference.finitediff
.. autofunction:: pynumdiff.finite_difference.first_order
.. autofunction:: pynumdiff.finite_difference.second_order
.. autofunction:: pynumdiff.finite_difference.fourth_order
.. autofunction:: pynumdiff.finite_difference.finitediff
3 changes: 0 additions & 3 deletions docs/source/kalman_smooth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ kalman_smooth

.. autofunction:: pynumdiff.kalman_smooth.rtsdiff
.. autofunction:: pynumdiff.kalman_smooth.robustdiff
.. autofunction:: pynumdiff.kalman_smooth.constant_velocity
.. autofunction:: pynumdiff.kalman_smooth.constant_acceleration
.. autofunction:: pynumdiff.kalman_smooth.constant_jerk
.. autofunction:: pynumdiff.kalman_smooth.kalman_filter
.. autofunction:: pynumdiff.kalman_smooth.rts_smooth
.. autofunction:: pynumdiff.kalman_smooth.convex_smooth
4 changes: 0 additions & 4 deletions docs/source/smooth_finite_difference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,3 @@ smooth_finite_difference

.. autofunction:: pynumdiff.smooth_finite_difference.kerneldiff
.. autofunction:: pynumdiff.smooth_finite_difference.butterdiff
.. autofunction:: pynumdiff.smooth_finite_difference.meandiff
.. autofunction:: pynumdiff.smooth_finite_difference.mediandiff
.. autofunction:: pynumdiff.smooth_finite_difference.gaussiandiff
.. autofunction:: pynumdiff.smooth_finite_difference.friedrichsdiff
3 changes: 0 additions & 3 deletions docs/source/total_variation_regularization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,5 @@ total_variation_regularization
:no-members:

.. autofunction:: pynumdiff.total_variation_regularization.tvrdiff
.. autofunction:: pynumdiff.total_variation_regularization.velocity
.. autofunction:: pynumdiff.total_variation_regularization.acceleration
.. autofunction:: pynumdiff.total_variation_regularization.jerk
.. autofunction:: pynumdiff.total_variation_regularization.iterative_velocity
.. autofunction:: pynumdiff.total_variation_regularization.smooth_acceleration
282 changes: 51 additions & 231 deletions notebooks/1_basic_tutorial.ipynb

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions pynumdiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
from warnings import warn
warn("tvrdiff, robustdiff, and lineardiff not available due to lack of convex solver. To use those, install CVXPY.")
else: # executes if try is successful
from .total_variation_regularization import tvrdiff, velocity, acceleration, jerk, smooth_acceleration
from .total_variation_regularization import tvrdiff, smooth_acceleration
from .kalman_smooth import robustdiff, convex_smooth
from .linear_model import lineardiff

from .finite_difference import finitediff, first_order, second_order, fourth_order
from .smooth_finite_difference import kerneldiff, meandiff, mediandiff, gaussiandiff, friedrichsdiff, butterdiff
from .finite_difference import finitediff
from .smooth_finite_difference import kerneldiff, butterdiff
from .polynomial_fit import splinediff, polydiff, savgoldiff
from .basis_fit import spectraldiff, rbfdiff, waveletdiff
from .total_variation_regularization import iterative_velocity
from .kalman_smooth import kalman_filter, rts_smooth, rtsdiff, constant_velocity, constant_acceleration, constant_jerk
from .kalman_smooth import kalman_filter, rts_smooth, rtsdiff
17 changes: 1 addition & 16 deletions pynumdiff/basis_fit.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
"""Methods based on fitting basis functions to data"""
from warnings import warn
import numpy as np
from scipy import sparse
import pywt

from pynumdiff.utils import utility

def spectraldiff(x, dt, params=None, options=None, high_freq_cutoff=None, even_extension=True,
pad_to_zero_dxdt=True, axis=0):
def spectraldiff(x, dt, high_freq_cutoff, even_extension=True, pad_to_zero_dxdt=True, axis=0):
"""Take a derivative in the Fourier domain, with high frequency attentuation.

:param np.array[float] x: data to differentiate. May be multidimensional; see :code:`axis`.
:param float dt: step size
:param list[float] or float params: (**deprecated**, prefer :code:`high_freq_cutoff`)
:param dict options: (**deprecated**, prefer :code:`even_extension`
and :code:`pad_to_zero_dxdt`) a dictionary consisting of {'even_extension': (bool), 'pad_to_zero_dxdt': (bool)}
:param float high_freq_cutoff: The high frequency cutoff as a multiple of the Nyquist frequency: Should be between 0
and 1. Frequencies below this threshold will be kept, and at and above will be zeroed.
:param bool even_extension: if True, extend the data with an even extension so signal starts and ends at the same value.
Expand All @@ -24,16 +19,6 @@ def spectraldiff(x, dt, params=None, options=None, high_freq_cutoff=None, even_e
:return: - **x_hat** (np.array) -- estimated (smoothed) x
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
if params is not None: # Warning to support old interface for a while. Remove these lines along with params in a future release.
warn("`params` and `options` parameters will be removed in a future version. Use `high_freq_cutoff`, "
"`even_extension`, and `pad_to_zero_dxdt` instead.", DeprecationWarning)
high_freq_cutoff = params[0] if isinstance(params, list) else params
if options is not None:
if 'even_extension' in options: even_extension = options['even_extension']
if 'pad_to_zero_dxdt' in options: pad_to_zero_dxdt = options['pad_to_zero_dxdt']
elif high_freq_cutoff is None:
raise ValueError("`high_freq_cutoff` must be given.")

if np.any(np.isnan(x)): raise ValueError("`x` may not contain NaN. Missing values spread through the FFT to make the whole spectrum NaN.")
if not np.isscalar(dt): raise ValueError("`dt` must be a scalar. The FFT assumes uniformly sampled data.")

Expand Down
57 changes: 0 additions & 57 deletions pynumdiff/finite_difference.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""This module implements some common finite difference schemes.
This is handy for this module https://web.media.mit.edu/~crtaylor/calculator.html"""
from warnings import warn
import numpy as np
from pynumdiff.utils import utility

Expand Down Expand Up @@ -71,59 +70,3 @@ def finitediff(x, dt, num_iterations=1, order=2, axis=0):

return np.moveaxis(x_hat, 0, axis), np.moveaxis(dxdt_hat, 0, axis) # reorder axes back to original


def first_order(x, dt, params=None, options={}, num_iterations=1):
"""First-order difference method\n
**Deprecated**, prefer :code:`finitediff` with order 1 instead.

:param np.array[float] x: data to differentiate
:param float dt: step size
:param list[float] or float params: (**deprecated**, prefer :code:`num_iterations`)
:param dict options: (**deprecated**, prefer :code:`num_iterations`) a dictionary consisting of {'iterate': (bool)}
:param int num_iterations: number of iterations. If >1, the derivative is integrated with trapezoidal
rule, that result is finite-differenced again, and the cycle is repeated num_iterations-1 times

:return: - **x_hat** (np.array) -- original x if :code:`num_iterations=1`, else smoothed x that yielded dxdt_hat
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
warn("`first_order` in past releases was actually calculating a second-order FD. Use `second_order` to achieve "
"approximately the same behavior. Note that odd-order methods have asymmetrical stencils, which causes "
"horizontal drift in the answer, especially when iterating.", DeprecationWarning)
if params is not None and 'iterate' in options:
warn("`params` and `options` parameters will be removed in a future version. Use `num_iterations` instead.", DeprecationWarning)
num_iterations = params[0] if isinstance(params, list) else params

warn("`first_order` is deprecated. Call `finitediff` with order 1 instead.", DeprecationWarning)
return finitediff(x, dt, num_iterations, 1)


def second_order(x, dt, num_iterations=1):
"""Second-order centered difference method, with special endpoint formulas.\n
**Deprecated**, prefer :code:`finitediff` with order 2 instead.

:param np.array[float] x: data to differentiate
:param float dt: step size
:param int num_iterations: number of iterations. If >1, the derivative is integrated with trapezoidal
rule, that result is finite-differenced again, and the cycle is repeated num_iterations-1 times

:return: - **x_hat** (np.array) -- original x if :code:`num_iterations=1`, else smoothed x that yielded dxdt_hat
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
warn("`second_order` is deprecated. Call `finitediff` with order 2 instead.", DeprecationWarning)
return finitediff(x, dt, num_iterations, 2)


def fourth_order(x, dt, num_iterations=1):
"""Fourth-order centered difference method, with special endpoint formulas.\n
**Deprecated**, prefer :code:`finitediff` with order 4 instead.

:param np.array[float] x: data to differentiate
:param float dt: step size
:param int num_iterations: number of iterations. If >1, the derivative is integrated with trapezoidal
rule, that result is finite-differenced again, and the cycle is repeated num_iterations-1 times

:return: - **x_hat** (np.array) -- original x if :code:`num_iterations=1`, else smoothed x that yielded dxdt_hat
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
warn("`fourth_order` is deprecated. Call `finitediff` with order 4 instead.", DeprecationWarning)
return finitediff(x, dt, num_iterations, 4)
93 changes: 1 addition & 92 deletions pynumdiff/kalman_smooth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""This module implements constant-derivative model-based smoothers based on Kalman filtering and its generalization."""
from warnings import warn
import numpy as np
from scipy.linalg import expm, sqrtm
try: import cvxpy
Expand Down Expand Up @@ -100,7 +99,7 @@ def rts_smooth(A, xhat_pre, xhat_post, P_pre, P_post, compute_P_smooth=True):

def rtsdiff(x, dt_or_t, order, log_qr_ratio, forwardbackward=False, axis=0, circular=False):
"""Perform Rauch-Tung-Striebel smoothing with a naive constant derivative model. Makes use of :code:`kalman_filter`
and :code:`rts_smooth`, which are made public. :code:`constant_X` methods in this module call this function.
and :code:`rts_smooth`, which are made public.

:param np.array[float] x: data series to differentiate. May contain NaN values (missing data); NaNs are excluded
from fitting and imputed by dynamical model evolution. May be multidimensional; see :code:`axis`.
Expand Down Expand Up @@ -180,96 +179,6 @@ def rtsdiff(x, dt_or_t, order, log_qr_ratio, forwardbackward=False, axis=0, circ
return x_hat, dxdt_hat


def constant_velocity(x, dt, params=None, options=None, r=None, q=None, forwardbackward=True):
"""Run a forward-backward constant velocity RTS Kalman smoother to estimate the derivative.\n
**Deprecated**, prefer :code:`rtsdiff` with order 1 instead.

:param np.array[float] x: data series to differentiate
:param float dt: step size
:param list[float] params: (**deprecated**, prefer :code:`r` and :code:`q`)
:param options: (**deprecated**, prefer :code:`forwardbackward`)
a dictionary consisting of {'forwardbackward': (bool)}
:param float r: variance of the signal noise
:param float q: variance of the constant velocity model
:param bool forwardbackward: indicates whether to run smoother forwards and backwards
(usually achieves better estimate at end points)

:return: - **x_hat** (np.array) -- estimated (smoothed) x
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
if params is not None: # boilerplate backwards compatibility code
warn("`params` and `options` parameters will be removed in a future version. Use `r`, "
"`q`, and `forwardbackward` instead.", DeprecationWarning)
r, q = params
if options is not None:
if 'forwardbackward' in options: forwardbackward = options['forwardbackward']
elif r is None or q is None:
raise ValueError("`q` and `r` must be given.")

warn("`constant_velocity` is deprecated. Call `rtsdiff` with order 1 instead.", DeprecationWarning)
return rtsdiff(x, dt, 1, np.log10(q/r), forwardbackward)


def constant_acceleration(x, dt, params=None, options=None, r=None, q=None, forwardbackward=True):
"""Run a forward-backward constant acceleration RTS Kalman smoother to estimate the derivative.\n
**Deprecated**, prefer :code:`rtsdiff` with order 2 instead.

:param np.array[float] x: data series to differentiate
:param float dt: step size
:param list[float] params: (**deprecated**, prefer :code:`r` and :code:`q`)
:param options: (**deprecated**, prefer :code:`forwardbackward`)
a dictionary consisting of {'forwardbackward': (bool)}
:param float r: variance of the signal noise
:param float q: variance of the constant acceleration model
:param bool forwardbackward: indicates whether to run smoother forwards and backwards
(usually achieves better estimate at end points)

:return: - **x_hat** (np.array) -- estimated (smoothed) x
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
if params is not None: # boilerplate backwards compatibility code
warn("`params` and `options` parameters will be removed in a future version. Use `r`, "
"`q`, and `forwardbackward` instead.", DeprecationWarning)
r, q = params
if options is not None:
if 'forwardbackward' in options: forwardbackward = options['forwardbackward']
elif r is None or q is None:
raise ValueError("`q` and `r` must be given.")

warn("`constant_acceleration` is deprecated. Call `rtsdiff` with order 2 instead.", DeprecationWarning)
return rtsdiff(x, dt, 2, np.log10(q/r), forwardbackward)


def constant_jerk(x, dt, params=None, options=None, r=None, q=None, forwardbackward=True):
"""Run a forward-backward constant jerk RTS Kalman smoother to estimate the derivative.\n
**Deprecated**, prefer :code:`rtsdiff` with order 3 instead.

:param np.array[float] x: data series to differentiate
:param float dt: step size
:param list[float] params: (**deprecated**, prefer :code:`r` and :code:`q`)
:param options: (**deprecated**, prefer :code:`forwardbackward`)
a dictionary consisting of {'forwardbackward': (bool)}
:param float r: variance of the signal noise
:param float q: variance of the constant jerk model
:param bool forwardbackward: indicates whether to run smoother forwards and backwards
(usually achieves better estimate at end points)

:return: - **x_hat** (np.array) -- estimated (smoothed) x
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
if params is not None: # boilerplate backwards compatibility code
warn("`params` and `options` parameters will be removed in a future version. Use `r`, "
"`q`, and `forwardbackward` instead.", DeprecationWarning)
r, q = params
if options is not None:
if 'forwardbackward' in options: forwardbackward = options['forwardbackward']
elif r is None or q is None:
raise ValueError("`q` and `r` must be given.")

warn("`constant_jerk` is deprecated. Call `rtsdiff` with order 3 instead.", DeprecationWarning)
return rtsdiff(x, dt, 3, np.log10(q/r), forwardbackward)


def robustdiff(x, dt_or_t, order, log_q, log_r, proc_huberM=6, meas_huberM=0, axis=0):
"""Perform outlier-robust differentiation by solving the *maximum a posteriori* optimization problem:
:math:`\\text{argmin}_{\\{x_n\\}} \\sum_{n=0}^{N-1} V(R^{-1/2}(y_n - C x_n)) + \\sum_{n=1}^{N-1} J(Q_{n-1}^{-1/2}(x_n - A_{n-1} x_{n-1}))`,
Expand Down
38 changes: 1 addition & 37 deletions pynumdiff/linear_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,19 @@
import numpy as np

from pynumdiff.finite_difference import finitediff
from pynumdiff.polynomial_fit import savgoldiff as _savgoldiff # patch through
from pynumdiff.polynomial_fit import polydiff as _polydiff # patch through
from pynumdiff.basis_fit import spectraldiff as _spectraldiff # patch through
from pynumdiff.utils import utility

try: import cvxpy
except ImportError: pass


def savgoldiff(*args, **kwargs): # pragma: no cover pylint: disable=missing-function-docstring
warn("`savgoldiff` has moved to `polynomial_fit.savgoldiff` and will be removed from "
"`linear_model` in a future release.", DeprecationWarning)
return _savgoldiff(*args, **kwargs)

def polydiff(*args, **kwargs): # pragma: no cover pylint: disable=missing-function-docstring
warn("`polydiff` has moved to `polynomial_fit.polydiff` and will be removed from "
"`linear_model` in a future release.", DeprecationWarning)
return _polydiff(*args, **kwargs)

def spectraldiff(*args, **kwargs): # pragma: no cover pylint: disable=missing-function-docstring
warn("`spectraldiff` has moved to `basis_fit.spectraldiff` and will be removed from "
"`linear_model` in a future release.", DeprecationWarning)
return _spectraldiff(*args, **kwargs)


_PROBLEM_CACHE = {} # (order, window length) -> a parametrized CVXPY problem, so identically shaped windows reuse it

def lineardiff(x, dt, params=None, options=None, order=None, gamma=None, window_size=None,
step_size=None, kernel='friedrichs', solver='CLARABEL', axis=0):
def lineardiff(x, dt, order, gamma, window_size=None, step_size=None, kernel='friedrichs', solver='CLARABEL', axis=0):
"""Fit a linear dynamical system to windows of the data, then differentiate that model.

:param np.array[float] x: data to differentiate. May be multidimensional; see :code:`axis`.
:param float dt: step size
:param list[int, float, int] params: (**deprecated**, prefer :code:`order`, :code:`gamma`, and :code:`window_size`)
:param dict options: (**deprecated**, prefer :code:`window_size`, :code:`step_size`, :code:`kernel`, and
:code:`solver`) a dictionary consisting of {'sliding': (bool), 'step_size': (int), 'kernel_name': (str), 'solver': (str)}
:param int>0 order: number of states in the linear system, equivalently how many times :code:`x` is integrated
:param float gamma: regularization term, in multiples of the data's own scale, so a given value means the same
thing whatever the units. See #222
Expand All @@ -55,19 +32,6 @@ def lineardiff(x, dt, params=None, options=None, order=None, gamma=None, window_
:return: - **x_hat** (np.array) -- estimated (smoothed) x
- **dxdt_hat** (np.array) -- estimated derivative of x
"""
if params is not None:
warn("`params` and `options` parameters will be removed in a future version. Use `order`, "
"`gamma`, and `window_size` instead.", DeprecationWarning)
order, gamma = params[:2]
if len(params) > 2: window_size = params[2]
if options is not None:
if 'sliding' in options and not options['sliding']: window_size = None
if 'step_size' in options: step_size = options['step_size']
if 'kernel_name' in options: kernel = options['kernel_name']
if 'solver' in options: solver = options['solver']
elif order is None or gamma is None:
raise ValueError("`order` and `gamma` must be given.")

if np.any(np.isnan(x)): raise ValueError("`x` may not contain NaN. CVXPY cannot form a problem with missing data.")
if not np.isscalar(dt): raise ValueError("`dt` must be a scalar. The integrals of x are accumulated at a constant step.")

Expand Down
Loading