Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ Python methods for numerical differentiation of noisy data, including multi-obje

PyNumDiff is a Python package that implements various methods for computing numerical derivatives of noisy data, which can be a critical step in developing dynamic models or designing control. There are seven different families of methods implemented in this repository:

1. convolutional smoothing followed by finite difference calculation
2. polynomial fit methods
3. basis function fit methods
4. iterated finite differencing
1. prefiltering followed by finite difference calculation
2. iterated finite differencing
3. polynomial fit methods
4. basis function fit methods
5. total variation regularization of a finite difference derivative
6. generalized Kalman smoothing
7. local approximation with linear model
Expand Down
9 changes: 8 additions & 1 deletion docs/source/smooth_finite_difference.rst

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now manually documenting things in here so they show up in the order I want.

Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ smooth_finite_difference
========================

.. automodule:: pynumdiff.smooth_finite_difference
:members:
:no-members:

.. 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
384 changes: 127 additions & 257 deletions examples/1_basic_tutorial.ipynb

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of sections belonged to now-deprecated methods. They can get absorbed into one.

Large diffs are not rendered by default.

249 changes: 64 additions & 185 deletions examples/2a_optimizing_parameters_with_dxdt_known.ipynb

Large diffs are not rendered by default.

252 changes: 65 additions & 187 deletions examples/2b_optimizing_parameters_with_dxdt_unknown.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/4_performance_analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@
"\t\t\t(splinediff, 'SplineDiff'),\n",
"\t\t\t(polydiff, 'PolyDiff'),\n",
"\t\t\t(savgoldiff, 'SavGolDiff'),\n",
"\t\t (spectraldiff, 'SpectralDiff'),\n",
"\t\t\t(spectraldiff, 'SpectralDiff'),\n",
"\t\t\t(rbfdiff, 'RBF'),\n",
"\t\t\t(finitediff, 'IteratedFD'), # skip first_order, because it's not going to be the best\n",
"\t\t\t(tvrdiff, 'TVR'),\n",
"\t\t\t(smooth_acceleration, 'SmoothAccelTVR'), # skip in plotting?\n",
"\t\t\t(rtsdiff, 'RTS'),\n",
"\t\t (robustdiff, 'RobustDiff')]\n",
"\t\t\t(robustdiff, 'RobustDiff')]\n",
"sims = [(pi_cruise_control, 'Cruise Control'),\n",
"\t\t(sine, 'Sum of Sines'),\n",
"\t\t(triangle, 'Triangles'),\n",
Expand Down
2 changes: 1 addition & 1 deletion pynumdiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .linear_model import lineardiff

from .finite_difference import finitediff, first_order, second_order, fourth_order
from .smooth_finite_difference import meandiff, mediandiff, gaussiandiff, friedrichsdiff, butterdiff
from .smooth_finite_difference import kerneldiff, meandiff, mediandiff, gaussiandiff, friedrichsdiff, butterdiff
from .polynomial_fit import splinediff, polydiff, savgoldiff
from .basis_fit import spectraldiff, rbfdiff
from .total_variation_regularization import iterative_velocity
Expand Down
12 changes: 9 additions & 3 deletions pynumdiff/finite_difference/_finite_difference.py

@pavelkomarov pavelkomarov Oct 16, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added deprecation messages to the docs, as well as warnings for runtime.

Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def finitediff(x, dt, num_iterations, order):


def first_order(x, dt, params=None, options={}, num_iterations=1):
"""First-order difference method
"""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
Expand All @@ -85,11 +86,13 @@ def first_order(x, dt, params=None, options={}, num_iterations=1):
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.
"""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
Expand All @@ -100,11 +103,13 @@ def second_order(x, dt, num_iterations=1):
- **x_hat** -- original x if :code:`num_iterations=1`, else smoothed x that yielded dxdt_hat
- **dxdt_hat** -- 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.
"""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
Expand All @@ -115,4 +120,5 @@ def fourth_order(x, dt, num_iterations=1):
- **x_hat** -- original x if :code:`num_iterations=1`, else smoothed x that yielded dxdt_hat
- **dxdt_hat** -- estimated derivative of x
"""
warn("`fourth_order` is deprecated. Call `finitediff` with order 4 instead.", DeprecationWarning)
return finitediff(x, dt, num_iterations, 4)
12 changes: 9 additions & 3 deletions pynumdiff/kalman_smooth/_kalman_smooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ def rtsdiff(x, _t, order, qr_ratio, forwardbackward):


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.
"""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
Expand All @@ -195,11 +196,13 @@ def constant_velocity(x, dt, params=None, options=None, r=None, q=None, forwardb
elif r == None or q == 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, 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.
"""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
Expand All @@ -224,11 +227,13 @@ def constant_acceleration(x, dt, params=None, options=None, r=None, q=None, forw
elif r == None or q == 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, 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.
"""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
Expand All @@ -253,6 +258,7 @@ def constant_jerk(x, dt, params=None, options=None, r=None, q=None, forwardbackw
elif r == None or q == 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, q/r, forwardbackward)


Expand Down
51 changes: 28 additions & 23 deletions pynumdiff/optimize/_optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from ..utils import evaluate
from ..finite_difference import finitediff, first_order, second_order, fourth_order
from ..smooth_finite_difference import mediandiff, meandiff, gaussiandiff, friedrichsdiff, butterdiff
from ..smooth_finite_difference import kerneldiff, mediandiff, meandiff, gaussiandiff, friedrichsdiff, butterdiff
from ..polynomial_fit import polydiff, savgoldiff, splinediff
from ..basis_fit import spectraldiff, rbfdiff
from ..total_variation_regularization import tvrdiff, velocity, acceleration, jerk, iterative_velocity, smooth_acceleration, jerk_sliding
Expand All @@ -19,10 +19,25 @@

# Map from method -> (search_space, bounds_low_hi)
method_params_and_bounds = {
meandiff: ({'window_size': [5, 15, 30, 50],
kerneldiff: ({'kernel': {'mean', 'median', 'gaussian', 'friedrichs'},
'window_size': [5, 15, 30, 50],
'num_iterations': [1, 5, 10]},
{'window_size': (1, 1e6),
'num_iterations': (1, 100)}),
meandiff: ({'window_size': [5, 15, 30, 50], # Deprecated method
'num_iterations': [1, 5, 10]},
{'window_size': (1, 1e6),
'num_iterations': (1, 100)}),
butterdiff: ({'filter_order': set(i for i in range(1,11)), # categorical to save us from doing double work by guessing between orders
'cutoff_freq': [0.0001, 0.001, 0.005, 0.01, 0.1, 0.5],
'num_iterations': [1, 5, 10]},
{'cutoff_freq': (1e-4, 1-1e-2),
'num_iterations': (1, 1000)}),
finitediff: ({'num_iterations': [5, 10, 30, 50],
'order': {2, 4}}, # order is categorical here, because it can't be 3
{'num_iterations': (1, 1000)}),
first_order: ({'num_iterations': [5, 10, 30, 50]}, # Separated because optimizing over this one is rare due to shifted answer
{'num_iterations': (1, 1000)}),
polydiff: ({'step_size': [1, 2, 5],
'kernel': {'friedrichs', 'gaussian'}, # categorical
'degree': [2, 3, 5, 7],
Expand All @@ -49,26 +64,16 @@
'lmbd': [1e-3, 1e-2, 1e-1]},
{'sigma': (1e-3, 1e3),
'lmbd': (1e-4, 0.5)}),
finitediff: ({'num_iterations': [5, 10, 30, 50],
'order': {2, 4}}, # order is categorical here, because it can't be 3
{'num_iterations': (1, 1000)}),
first_order: ({'num_iterations': [5, 10, 30, 50]},
{'num_iterations': (1, 1000)}),
butterdiff: ({'filter_order': set(i for i in range(1,11)), # categorical to save us from doing double work by guessing between orders
'cutoff_freq': [0.0001, 0.001, 0.005, 0.01, 0.1, 0.5],
'num_iterations': [1, 5, 10]},
{'cutoff_freq': (1e-4, 1-1e-2),
'num_iterations': (1, 1000)}),
tvrdiff: ({'gamma': [1e-2, 1e-1, 1, 10, 100, 1000],
'order': {1, 2, 3}}, # warning: order 1 hacks the loss function when tvgamma is used, tends to win but is usually suboptimal choice in terms of true RMSE
{'gamma': (1e-4, 1e7)}),
velocity: ({'gamma': [1e-2, 1e-1, 1, 10, 100, 1000]},
velocity: ({'gamma': [1e-2, 1e-1, 1, 10, 100, 1000]}, # Deprecated method
{'gamma': (1e-4, 1e7)}),
iterative_velocity: ({'num_iterations': [1, 5, 10],
'gamma': [1e-2, 1e-1, 1, 10, 100, 1000],
'scale': 'small'},
{'num_iterations': (1, 100), # gets expensive with more iterations
'gamma': (1e-4, 1e7)}),
iterative_velocity: ({'scale': 'small', # Rare to optimize this one, because it's longer-running than convex version
'num_iterations': [1, 5, 10],
'gamma': [1e-2, 1e-1, 1, 10, 100, 1000]},
{'num_iterations': (1, 100), # gets expensive with more iterations
'gamma': (1e-4, 1e7)}),
smooth_acceleration: ({'gamma': [1e-2, 1e-1, 1, 10, 100, 1000],
'window_size': [3, 10, 30, 50, 90, 130]},
{'gamma': (1e-4, 1e7),
Expand All @@ -77,7 +82,7 @@
'order': {1, 2, 3}, # for this few options, the optimization works better if this is categorical
'qr_ratio': [10**k for k in range(-9, 10, 2)] + [1e12, 1e16]},
{'qr_ratio': [1e-10, 1e20]}), # qr_ratio is usually >>1
constant_velocity: ({'q': [1e-8, 1e-4, 1e-1, 1e1, 1e4, 1e8],
constant_velocity: ({'q': [1e-8, 1e-4, 1e-1, 1e1, 1e4, 1e8], # Deprecated method
'r': [1e-8, 1e-4, 1e-1, 1e1, 1e4, 1e8],
'forwardbackward': {True, False}},
{'q': (1e-10, 1e10),
Expand All @@ -95,14 +100,14 @@
'gamma': (1e-3, 1000),
'window_size': (15, 1000)})
} # Methods with nonunique parameter sets are aliased in the dictionary below
for method in [second_order, fourth_order]:
for method in [second_order, fourth_order]: # Deprecated, redundant methods

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized basically all these methods I'm aliasing down here are redundant. An argument to truly consider them deprecated and remove them eventually.

method_params_and_bounds[method] = method_params_and_bounds[first_order]
for method in [mediandiff, gaussiandiff, friedrichsdiff]:
for method in [mediandiff, gaussiandiff, friedrichsdiff]: # Deprecated methods
method_params_and_bounds[method] = method_params_and_bounds[meandiff]
for method in [acceleration, jerk]:
for method in [acceleration, jerk]: # Deprecated, redundant methods
method_params_and_bounds[method] = method_params_and_bounds[velocity]
method_params_and_bounds[jerk_sliding] = method_params_and_bounds[smooth_acceleration]
for method in [constant_acceleration, constant_jerk]:
for method in [constant_acceleration, constant_jerk]: # Deprecated, redundant methods
method_params_and_bounds[method] = method_params_and_bounds[constant_velocity]


Expand Down
7 changes: 2 additions & 5 deletions pynumdiff/smooth_finite_difference/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"""Apply smoothing method before finite difference.
"""
from ._smooth_finite_difference import mediandiff, meandiff, gaussiandiff, friedrichsdiff, butterdiff, splinediff

__all__ = ['mediandiff', 'meandiff', 'gaussiandiff', 'friedrichsdiff', 'butterdiff'] # So automodule from the .rst finds them

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__all__ can go away because I'm now listing things in the corresponding .rst.

# splinediff is still included in the imports list so backwards compatibility isn't broken, but excluded
# from the __all__ list so sphinx doesn't try to document it from this module.
from ._smooth_finite_difference import kerneldiff, mediandiff, meandiff, gaussiandiff, friedrichsdiff, butterdiff, splinediff
# splinediff is still included in the imports list so backwards compatibility isn't broken
Loading