Skip to content

Commit 18437b2

Browse files
authored
Merge pull request #148 from florisvb/splines-irregular-steps
Irregular $\Delta t$ for `splinediff`
2 parents 43f0c5a + 5597aef commit 18437b2

3 files changed

Lines changed: 168 additions & 184 deletions

File tree

README.md

Lines changed: 65 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,70 @@ Python methods for numerical differentiation of noisy data, including multi-obje
1919
<img src="https://joss.theoj.org/papers/102257ee4b0142bf49bc18d7c810e9d5/status.svg"></a>
2020
</p>
2121

22-
## Table of contents
23-
- [PyNumDiff](#pynumdiff)
24-
- [Table of contents](#table-of-contents)
25-
- [Introduction](#introduction)
26-
- [Structure](#structure)
27-
- [Citation](#citation)
28-
- [PyNumDiff python package:](#pynumdiff-python-package)
29-
- [Optimization algorithm:](#optimization-algorithm)
30-
- [Getting Started](#getting-started)
31-
- [Prerequisite](#prerequisite)
32-
- [Installing](#installing)
33-
- [Usage](#usage)
34-
- [Code snippets](#code-snippets)
35-
- [Notebook examples](#notebook-examples)
36-
- [Important notes](#important-notes)
37-
- [Running the tests](#running-the-tests)
38-
- [License](#license)
39-
4022
## Introduction
4123

42-
PyNumDiff is a Python package that implements various methods for computing numerical derivatives of noisy data, which
43-
can be a critical step in developing dynamic models or designing control. There are four different families of methods
44-
implemented in this repository: smoothing followed by finite difference calculation, local approximation with linear
45-
models, Kalman filtering based methods and total variation regularization methods. Most of these methods have multiple
46-
parameters involved to tune. We take a principled approach and propose a multi-objective optimization framework for
47-
choosing parameters that minimize a loss function to balance the faithfulness and smoothness of the derivative estimate.
48-
For more details, refer to [this paper](https://doi.org/10.1109/ACCESS.2020.3034077).
24+
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:
25+
26+
1. convolutional smoothing followed by finite difference calculation
27+
2. polynomial-fit-based methods
28+
3. iterated finite differencing
29+
4. total variation regularization of a finite difference derivative
30+
5. Kalman (RTS) smoothing
31+
6. Fourier spectral with tricks
32+
7. linear local approximation with linear model
33+
34+
Most of these methods have multiple parameters, so we take a principled approach and propose a multi-objective optimization framework for choosing parameters that minimize a loss function to balance the faithfulness and smoothness of the derivative estimate. For more details, refer to [this paper](https://doi.org/10.1109/ACCESS.2020.3034077).
35+
36+
## Installing
37+
38+
Dependencies are listed in [pyproject.toml](https://github.com/florisvb/PyNumDiff/blob/master/pyproject.toml). They include the usual suspects like `numpy` and `scipy`, but also optionally `cvxpy`.
39+
40+
The code is compatible with >=Python 3.10. Install from PyPI with `pip install pynumdiff`, from source with `pip install git+https://github.com/florisvb/PyNumDiff`, or from local download with `pip install .`. Call `pip install pynumdiff[advanced]` to automatically install optional dependencies from the advanced list, like [CVXPY](https://www.cvxpy.org).
41+
42+
## Usage
43+
44+
For more details, read our [Sphinx documentation](https://pynumdiff.readthedocs.io/master/). The basic pattern of all differentiation methods is:
4945

50-
## Structure
46+
```python
47+
somethingdiff(x, dt, **kwargs)
48+
```
49+
50+
where `x` is data, `dt` is a step size, and various keyword arguments control the behavior. Some methods support variable step size, in which case `dt` can also receive an array of values to denote sample locations.
51+
52+
You can provide the parameters:
53+
```python
54+
from pynumdiff.submodule import method
55+
56+
x_hat, dxdt_hat = method(x, dt, param1=val1, param2=val2, ...)
57+
```
58+
59+
Or you can find parameter by calling the multi-objective optimization algorithm from the `optimize` module:
60+
```python
61+
from pynumdiff.optimize import optimize
62+
63+
# estimate cutoff_frequency by (a) counting the number of true peaks per second in the data or (b) look at power spectra and choose cutoff
64+
tvgamma = np.exp(-1.6*np.log(cutoff_frequency) -0.71*np.log(dt) - 5.1) # see https://ieeexplore.ieee.org/abstract/document/9241009
65+
66+
params, val = optimize(somethingdiff, x, dt, tvgamma=tvgamma, # smoothness hyperparameter which defaults to None if dxdt_truth given
67+
dxdt_truth=None, # give ground truth data if available, in which case tvgamma goes unused
68+
search_space_updates={'param1':[vals], 'param2':[vals], ...})
69+
70+
print('Optimal parameters: ', params)
71+
x_hat, dxdt_hat = somethingdiff(x, dt, **params)
72+
```
73+
If no `search_space_updates` is given, a default search space is used. See the top of `_optimize.py`.
74+
75+
The following heuristic works well for choosing `tvgamma`, where `cutoff_frequency` is the highest frequency content of the signal in your data, and `dt` is the timestep: `tvgamma=np.exp(-1.6*np.log(cutoff_frequency)-0.71*np.log(dt)-5.1)`. Larger values of `tvgamma` produce smoother derivatives. The value of `tvgamma` is largely universal across methods, making it easy to compare method results. Be aware the optimization is a fairly heavy process.
76+
77+
### Notebook examples
78+
79+
Much more extensive usage is demonstrated in Jupyter notebooks:
80+
* Differentiation with different methods: [1_basic_tutorial.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/1_basic_tutorial.ipynb)
81+
* Parameter Optimization with known ground truth (only for demonstration purpose): [2a_optimizing_parameters_with_dxdt_known.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2a_optimizing_parameters_with_dxdt_known.ipynb)
82+
* Parameter Optimization with unknown ground truth: [2b_optimizing_parameters_with_dxdt_unknown.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2b_optimizing_parameters_with_dxdt_unknown.ipynb)
83+
* Automatic method suggestion: [3_automatic_method_suggestion.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/3_automatic_method_suggestion.ipynb)
84+
85+
## Repo Structure
5186

5287
- `.github/workflows` contains `.yaml` that configures our GitHub Actions continuous integration (CI) runs.
5388
- `docs/` contains `make` files and `.rst` files to govern the way `sphinx` builds documentation, either locally by navigating to this folder and calling `make html` or in the cloud by `readthedocs.io`.
@@ -82,7 +117,6 @@ See CITATION.cff file as well as the following references.
82117
journal = {Journal of Open Source Software}
83118
}
84119

85-
86120
### Optimization algorithm:
87121

88122
@article{ParamOptimizationDerivatives2020,
@@ -93,86 +127,16 @@ See CITATION.cff file as well as the following references.
93127
year={2020}
94128
}
95129

96-
## Getting Started
97-
98-
### Prerequisite
99-
100-
PyNumDiff requires common packages like `numpy`, `scipy`, and `matplotlib`. For a full list, you can check the file [pyproject.toml](https://github.com/florisvb/PyNumDiff/blob/master/pyproject.toml)
101-
102-
In addition, it also requires certain additional packages for select functions, though these are not required for a successful install of PyNumDiff:
103-
- Total Variation Regularization methods: [`cvxpy`](http://www.cvxpy.org/install/index.html)
104-
- `pytest` for unittests
105-
106-
### Installing
107-
108-
The code is compatible with >=Python 3.5. It can be installed using pip or directly from the source code. Basic installation options include:
109-
110-
* From PyPI using pip: `pip install pynumdiff`.
111-
* From source using pip git+: `pip install git+https://github.com/florisvb/PyNumDiff`
112-
* From local source code using setup.py: Run `pip install .` from inside this directory. See below for example.
113-
114-
Call `pip install pynumdiff[advanced]` to automatically install [CVXPY](https://www.cvxpy.org) along with PyNumDiff. <em>Note: Some CVXPY solvers require a license, like ECOS and MOSEK. The latter offers a [free academic license](https://www.mosek.com/products/academic-licenses/).</em>
115-
116-
## Usage
117-
118-
**PyNumDiff** uses [Sphinx](http://www.sphinx-doc.org/en/master/) for code documentation, so read more details about the API usage [there](https://pynumdiff.readthedocs.io/master/).
119-
120-
### Code snippets
121-
122-
* Basic Usage: you provide the parameters
123-
```python
124-
from pynumdiff.submodule import method
125-
126-
x_hat, dxdt_hat = method(x, dt, param1=val1, param2=val2, ...)
127-
```
128-
* Intermediate usage: automated parameter selection through multi-objective optimization
129-
```python
130-
from pynumdiff.optimize import optimize
131-
132-
params, val = optimize(method, x, dt, search_space={'param1':[vals], 'param2':[vals], ...},
133-
tvgamma=tvgamma, # hyperparameter, defaults to None if dxdt_truth given
134-
dxdt_truth=None) # or give ground truth data, in which case tvgamma unused
135-
print('Optimal parameters: ', params)
136-
x_hat, dxdt_hat = method(x, dt, **params)
137-
```
138-
If no `search_space` is given, a default one is used.
139-
140-
* Advanced usage: automated parameter selection through multi-objective optimization using a user-defined cutoff frequency
141-
```python
142-
# cutoff_freq: estimate by (a) counting the number of true peaks per second in the data or (b) look at power spectra and choose cutoff
143-
log_gamma = -1.6*np.log(cutoff_frequency) -0.71*np.log(dt) - 5.1 # see: https://ieeexplore.ieee.org/abstract/document/9241009
144-
tvgamma = np.exp(log_gamma)
145-
146-
params, val = optimize(method, x, dt, search_space={'param1':[options], 'param2':[options], ...},
147-
tvgamma=tvgamma)
148-
print('Optimal parameters: ', params)
149-
x_hat, dxdt_hat = method(x, dt, **params)
150-
```
151-
152-
### Notebook examples
153-
154-
We will frequently update simple examples for demo purposes, and here are currently exisiting ones:
155-
* Differentiation with different methods: [1_basic_tutorial.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/1_basic_tutorial.ipynb)
156-
* Parameter Optimization with known ground truth (only for demonstration purpose): [2a_optimizing_parameters_with_dxdt_known.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2a_optimizing_parameters_with_dxdt_known.ipynb)
157-
* Parameter Optimization with unknown ground truth: [2b_optimizing_parameters_with_dxdt_unknown.ipynb](https://github.com/florisvb/PyNumDiff/blob/master/examples/2b_optimizing_parameters_with_dxdt_unknown.ipynb)
158-
159-
### Important notes
160-
161-
* Larger values of `tvgamma` produce smoother derivatives
162-
* The value of `tvgamma` is largely universal across methods, making it easy to compare method results
163-
* The optimization is not fast. Run it on subsets of your data if you have a lot of data. It will also be much faster with faster differentiation methods, like `savgoldiff` and `butterdiff`.
164-
* The following heuristic works well for choosing `tvgamma`, where `cutoff_frequency` is the highest frequency content of the signal in your data, and `dt` is the timestep: `tvgamma=np.exp(-1.6*np.log(cutoff_frequency)-0.71*np.log(dt)-5.1)`
165-
166-
### Running the tests
130+
## Running the tests
167131

168132
We are using GitHub Actions for continuous intergration testing.
169133

170-
To run tests locally, type:
134+
Run tests locally by navigating to the repo in a terminal and calling
171135
```bash
172-
> pytest pynumdiff
136+
> pytest -s
173137
```
174138

175-
Add the flag `--plot` to see plots of the methods against test functions. Add the flag `--bounds` to print log error bounds (useful when changing method behavior).
139+
Add the flag `--plot` to see plots of the methods against test functions. Add the flag `--bounds` to print $\log$ error bounds (useful when changing method behavior).
176140

177141
## License
178142

pynumdiff/polynomial_fit/_polynomial_fit.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ def splinediff(x, dt, params=None, options={}, degree=3, s=None, num_iterations=
1010
scipy.interpolate.UnivariateSpline.
1111
1212
:param np.array[float] x: data to differentiate
13-
:param float dt: step size
13+
:param float or array[float] dt: This function supports variable step size. This parameter is either the constant
14+
step size if given as a single float, or data locations if given as an array of same length as :code:`x`.
1415
:param list params: (**deprecated**, prefer :code:`degree`, :code:`cutoff_freq`, and :code:`num_iterations`)
1516
:param dict options: (**deprecated**, prefer :code:`num_iterations`) an empty dictionary or {'iterate': (bool)}
1617
:param int degree: polynomial degree of the spline. A kth degree spline can be differentiated k times.
@@ -29,7 +30,11 @@ def splinediff(x, dt, params=None, options={}, degree=3, s=None, num_iterations=
2930
if 'iterate' in options and options['iterate']:
3031
num_iterations = params[2]
3132

32-
t = np.arange(len(x))*dt
33+
if isinstance(dt, (np.ndarray, list)): # support variable step size for this function
34+
if len(x) != len(dt): raise ValueError("If `dt` is given as array-like, must have same length as `x`.")
35+
t = dt
36+
else:
37+
t = np.arange(len(x))*dt
3338

3439
x_hat = x
3540
for _ in range(num_iterations):

0 commit comments

Comments
 (0)