Skip to content

Expanded derivatives #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Aug 30, 2020
Merged
Show file tree
Hide file tree
Changes from 10 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
19 changes: 15 additions & 4 deletions examples/1_feature_overview.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Pass in pre-computed derivatives"
"### Pass in pre-computed derivatives\n",
"Rather than using one of PySINDy's built-in differentiators, you can compute numerical derivatives using a method of your choice then pass them directly to the `fit` method. This option also enables you to use derivative data obtained directly from experiments."
]
},
{
Expand Down Expand Up @@ -751,7 +752,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Drop end points from finite difference computation"
"### Drop end points from finite difference computation\n",
"Many methods of numerical differentiation exhibit poor performance near the endpoints of the data. The `FiniteDifference` and `SmoothedFiniteDifference` methods allow one to easily drop the endpoints for improved accuracy."
]
},
{
Expand Down Expand Up @@ -786,7 +788,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Smoothed finite difference"
"### Smoothed finite difference\n",
"This method, designed for noisy data, applies a smoother (the default is `scipy.signal.savgol_filter`) before performing differentiation."
]
},
{
Expand Down Expand Up @@ -817,6 +820,14 @@
"model.print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### More differentiation options\n",
"PySINDy provides many other differentiation methods (powered by the [derivative](https://pypi.org/project/derivative/) package). They are explored in detail in [this notebook](https://github.com/dynamicslab/pysindy/blob/master/examples/5_differentiation.ipynb)."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1485,7 +1496,7 @@
"width": "296.475px"
},
"toc_section_display": true,
"toc_window_display": true
"toc_window_display": false
},
"varInspector": {
"cols": {
Expand Down
92 changes: 74 additions & 18 deletions examples/4_scikit_learn_compatibility.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:35.177225Z",
"start_time": "2020-07-14T21:07:33.573951Z"
"end_time": "2020-08-22T22:47:55.509529Z",
"start_time": "2020-08-22T22:47:54.323781Z"
}
},
"outputs": [],
Expand All @@ -44,8 +44,8 @@
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:35.285747Z",
"start_time": "2020-07-14T21:07:35.184412Z"
"end_time": "2020-08-22T22:47:56.708081Z",
"start_time": "2020-08-22T22:47:56.618948Z"
}
},
"outputs": [],
Expand Down Expand Up @@ -95,8 +95,8 @@
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:44.101945Z",
"start_time": "2020-07-14T21:07:35.293352Z"
"end_time": "2020-08-22T22:50:47.967603Z",
"start_time": "2020-08-22T22:50:40.520544Z"
}
},
"outputs": [
Expand Down Expand Up @@ -135,6 +135,62 @@
"search.best_estimator_.print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some extra care must be taken when working with differentiation methods from the `derivative` package (i.e. those accessed via the `SINDyDerivative` class). See the example below."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2020-08-22T22:59:18.558877Z",
"start_time": "2020-08-22T22:58:57.908732Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Best parameters: {'differentiation_method__kwargs': {'kind': 'spline', 's': 0.01}, 'optimizer__threshold': 0.1}\n",
"x0' = -10.000 x0 + 10.000 x1\n",
"x1' = 28.003 x0 + -1.001 x1 + -1.000 x0 x2\n",
"x2' = -2.667 x2 + 1.000 x0 x1\n"
]
}
],
"source": [
"model = ps.SINDy(\n",
" t_default=dt,\n",
" differentiation_method=ps.SINDyDerivative(kind='spline', s=1e-2)\n",
")\n",
"\n",
"param_grid = {\n",
" \"optimizer__threshold\": [0.001, 0.01, 0.1],\n",
" \"differentiation_method__kwargs\": [\n",
" {'kind': 'spline', 's': 1e-2},\n",
" {'kind': 'spline', 's': 1e-1},\n",
" {'kind': 'finite_difference', 'k': 1},\n",
" {'kind': 'finite_difference', 'k': 2},\n",
" ]\n",
"}\n",
"\n",
"# This part is identical to what we did before\n",
"search = GridSearchCV(\n",
" model,\n",
" param_grid,\n",
" cv=TimeSeriesSplit(n_splits=5)\n",
")\n",
"search.fit(x_train)\n",
"\n",
"print(\"Best parameters:\", search.best_params_)\n",
"search.best_estimator_.print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -145,11 +201,11 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:44.133790Z",
"start_time": "2020-07-14T21:07:44.116536Z"
"end_time": "2020-08-22T22:59:50.283609Z",
"start_time": "2020-08-22T22:59:50.261397Z"
}
},
"outputs": [],
Expand Down Expand Up @@ -203,11 +259,11 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:50.098150Z",
"start_time": "2020-07-14T21:07:44.137586Z"
"end_time": "2020-08-22T22:59:56.316952Z",
"start_time": "2020-08-22T22:59:52.131651Z"
}
},
"outputs": [
Expand Down Expand Up @@ -256,11 +312,11 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:50.552501Z",
"start_time": "2020-07-14T21:07:50.107361Z"
"end_time": "2020-08-22T22:59:59.313750Z",
"start_time": "2020-08-22T22:59:58.992166Z"
}
},
"outputs": [
Expand All @@ -284,11 +340,11 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2020-07-14T21:07:50.660764Z",
"start_time": "2020-07-14T21:07:50.577603Z"
"end_time": "2020-08-22T23:00:00.375038Z",
"start_time": "2020-08-22T23:00:00.333557Z"
}
},
"outputs": [
Expand Down
702 changes: 702 additions & 0 deletions examples/5_differentation.ipynb

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions pysindy/differentiation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
from .base import BaseDifferentiation
from .derivative import SINDyDerivative
from .finite_difference import FiniteDifference
from .smoothed_finite_difference import SmoothedFiniteDifference


__all__ = [
"BaseDifferentiation",
"SINDyDerivative",
"FiniteDifference",
"SmoothedFiniteDifference",
]
72 changes: 72 additions & 0 deletions pysindy/differentiation/derivative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Wrapper classes for differentiation methods from the `derivative` package.

Some default values used here may differ from those used in `derivative`.
"""
from derivative import dxdt
from numpy import arange
from sklearn.base import BaseEstimator

from pysindy.utils.base import validate_input


class SINDyDerivative(BaseEstimator):
"""
Wrapper class for differentiation classes from the ``derivative`` package.

Imbues the class with ``_differentiate`` and ``__call__`` methods which are
used by PySINDy.

Parameters
----------
derivative_kws: dictionary, optional
Keyword arguments to be passed to the ``derivative.dxdt`` method.

Notes
-----
See the `derivative documentation <https://derivative.readthedocs.io/en/latest/>`_
for acceptable keywords.
"""

def __init__(self, **kwargs):
self.kwargs = kwargs

def set_params(self, **params):
"""
Set the parameters of this estimator.
Modification of the pysindy method to allow unknown kwargs. This allows using
the full range of derivative parameters that are not defined as member variables
in sklearn grid search.

Returns
-------
self
"""
if not params:
# Simple optimization to gain speed (inspect is slow)
return self
else:
self.kwargs.update(params)

return self

def get_params(self, deep=True):
"""Get parameters."""
params = super().get_params(deep)

if isinstance(self.kwargs, dict):
params.update(self.kwargs)

return params

def _differentiate(self, x, t=1):
if isinstance(t, (int, float)):
if t < 0:
raise ValueError("t must be a positive constant or an array")
t = arange(x.shape[0]) * t

return dxdt(x, t, axis=0, **self.kwargs)

def __call__(self, x, t=1):
x = validate_input(x, t=t)
return self._differentiate(x, t)
20 changes: 12 additions & 8 deletions pysindy/pysindy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import warnings
from typing import Sequence

from derivative.differentiation import Derivative
from numpy import concatenate
from numpy import isscalar
from numpy import ndim
Expand All @@ -15,14 +16,15 @@
from sklearn.pipeline import Pipeline
from sklearn.utils.validation import check_is_fitted

from pysindy.differentiation import FiniteDifference
from pysindy.feature_library import PolynomialLibrary
from pysindy.optimizers import SINDyOptimizer
from pysindy.optimizers import STLSQ
from pysindy.utils.base import drop_nan_rows
from pysindy.utils.base import equations
from pysindy.utils.base import validate_control_variables
from pysindy.utils.base import validate_input
from .differentiation import FiniteDifference
from .differentiation import SINDyDerivative
from .feature_library import PolynomialLibrary
from .optimizers import SINDyOptimizer
from .optimizers import STLSQ
from .utils.base import drop_nan_rows
from .utils.base import equations
from .utils.base import validate_control_variables
from .utils.base import validate_input


class SINDy(BaseEstimator):
Expand Down Expand Up @@ -156,6 +158,8 @@ def __init__(
self.feature_library = feature_library
if differentiation_method is None:
differentiation_method = FiniteDifference()
elif isinstance(differentiation_method, Derivative):
differentiation_method = SINDyDerivative(differentiation_method)
self.differentiation_method = differentiation_method
if not isinstance(t_default, float) and not isinstance(t_default, int):
raise ValueError("t_default must be a positive number")
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
scikit-learn[alldeps]>=0.23
numpy
scipy
derivative
Loading