Skip to content

Commit 6f3b882

Browse files
authored
fix!: Remove t_default and require t passed explicitly in SINDY.fit() and SINDY.score() (#628)
Previously, `t_default` was a convenience argument for people making demonstration notebooks. It didn't do anything unique. But by existing in __init__ and not fit(), it broke the distinction between data-dependent and data-independent arguments. But at the end of the day, it was just a duplicated feature.
1 parent 5eb0d65 commit 6f3b882

File tree

8 files changed

+1043
-717
lines changed

8 files changed

+1043
-717
lines changed

examples/1_feature_overview/example.ipynb

Lines changed: 980 additions & 525 deletions
Large diffs are not rendered by default.

examples/1_feature_overview/example.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ def ignore_specific_warnings():
170170
# Since we used a uniform timestep when defining `t_train` we can tell set a default timestep to be used whenever `t` isn't passed in.
171171

172172
# In[10]:
173-
model = ps.SINDy(t_default=dt)
174-
model.fit(x_train)
173+
model = ps.SINDy()
174+
model.fit(x_train, t=dt)
175175
model.print()
176176

177177
# %% [markdown]
@@ -255,7 +255,7 @@ def f(x):
255255
for i in range(1, n_steps):
256256
x_train_map[i] = f(x_train_map[i - 1]) + eps * np.random.randn()
257257
model = ps.SINDy(discrete_time=True)
258-
model.fit(x_train_map)
258+
model.fit(x_train_map, t=1)
259259

260260
model.print()
261261

@@ -1219,7 +1219,7 @@ def u_fun(t):
12191219
model = ps.SINDy(
12201220
feature_library=lib, optimizer=opt, feature_names=["x", "r"], discrete_time=True
12211221
)
1222-
model.fit(xs_train, u=rs_train)
1222+
model.fit(xs_train, u=rs_train, t=1)
12231223
model.print()
12241224

12251225
# %% [markdown]
@@ -1407,7 +1407,7 @@ def u_fun(t):
14071407
)
14081408
optimizer = ps.STLSQ(threshold=8, alpha=1e-3, normalize_columns=True)
14091409
model = ps.SINDy(feature_library=generalized_library, optimizer=optimizer)
1410-
model.fit(data, x_dot=data_dot)
1410+
model.fit(data, x_dot=data_dot, t=1)
14111411

14121412
# Note scale of phi is large so some coefficients >> 1
14131413
# --> would want to rescale phi with eps_0 for a harder problem

pysindy/deeptime/deeptime.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ class SINDyEstimator(SINDy):
3636
Names for the input features (e.g. ``['x', 'y', 'z']``). If None, will use
3737
``['x0', 'x1', ...]``.
3838
39-
t_default : float, optional (default 1)
40-
Default value for the time step.
41-
4239
discrete_time : boolean, optional (default False)
4340
If True, dynamical system is treated as a map. Rather than predicting
4441
derivatives, the right hand side functions step the system forward by
@@ -65,15 +62,13 @@ def __init__(
6562
feature_library=None,
6663
differentiation_method=None,
6764
feature_names=None,
68-
t_default=1,
6965
discrete_time=False,
7066
):
7167
super(SINDyEstimator, self).__init__(
7268
optimizer=optimizer,
7369
feature_library=feature_library,
7470
differentiation_method=differentiation_method,
7571
feature_names=feature_names,
76-
t_default=t_default,
7772
discrete_time=discrete_time,
7873
)
7974
self._model = None
@@ -102,7 +97,6 @@ def fit(self, x, **kwargs):
10297
feature_library=self.model.steps[0][1],
10398
optimizer=self.model.steps[-1][1],
10499
feature_names=self.feature_names,
105-
t_default=self.t_default,
106100
discrete_time=self.discrete_time,
107101
n_control_features_=self.n_control_features_,
108102
)
@@ -166,9 +160,6 @@ class SINDyModel(SINDy):
166160
Names for the input features (e.g. ``['x', 'y', 'z']``). If None, will use
167161
``['x0', 'x1', ...]``.
168162
169-
t_default : float, optional (default 1)
170-
Default value for the time step.
171-
172163
discrete_time : boolean, optional (default False)
173164
If True, dynamical system is treated as a map. Rather than predicting
174165
derivatives, the right hand side functions step the system forward by
@@ -193,15 +184,13 @@ def __init__(
193184
feature_library,
194185
optimizer,
195186
feature_names=None,
196-
t_default=1,
197187
discrete_time=False,
198188
n_control_features_=0,
199189
):
200190
super(SINDyModel, self).__init__(
201191
feature_library=feature_library,
202192
optimizer=optimizer,
203193
feature_names=feature_names,
204-
t_default=t_default,
205194
discrete_time=discrete_time,
206195
)
207196
self.n_control_features_ = n_control_features_

pysindy/pysindy.py

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,6 @@ class SINDy(_BaseSINDy):
199199
Names for the input features (e.g. ``['x', 'y', 'z']``). If None, will use
200200
``['x0', 'x1', ...]``.
201201
202-
t_default : float, optional (default 1)
203-
Default value for the time step.
204-
205202
discrete_time : boolean, optional (default False)
206203
If True, dynamical system is treated as a map. Rather than predicting
207204
derivatives, the right hand side functions step the system forward by
@@ -291,7 +288,6 @@ def __init__(
291288
feature_library: Optional[BaseFeatureLibrary] = None,
292289
differentiation_method: Optional[BaseDifferentiation] = None,
293290
feature_names: Optional[list[str]] = None,
294-
t_default: float = 1,
295291
discrete_time: bool = False,
296292
):
297293
if optimizer is None:
@@ -303,19 +299,13 @@ def __init__(
303299
if differentiation_method is None:
304300
differentiation_method = FiniteDifference(axis=-2)
305301
self.differentiation_method = differentiation_method
306-
if not isinstance(t_default, float) and not isinstance(t_default, int):
307-
raise ValueError("t_default must be a positive number")
308-
elif t_default <= 0:
309-
raise ValueError("t_default must be a positive number")
310-
else:
311-
self.t_default = t_default
312302
self.feature_names = feature_names
313303
self.discrete_time = discrete_time
314304

315305
def fit(
316306
self,
317307
x,
318-
t=None,
308+
t,
319309
x_dot=None,
320310
u=None,
321311
):
@@ -329,16 +319,14 @@ def fit(
329319
x should be a list containing data for each trajectory. Individual
330320
trajectories may contain different numbers of samples.
331321
332-
t: float, numpy array of shape (n_samples,), or list of numpy arrays, optional \
333-
(default None)
322+
t: float, numpy array of shape (n_samples,), or list of numpy arrays
334323
If t is a float, it specifies the timestep between each sample.
335324
If array-like, it specifies the time at which each sample was
336325
collected.
337326
In this case the values in t must be strictly increasing.
338327
In the case of multi-trajectory training data, t may also be a list
339328
of arrays containing the collection times for each individual
340329
trajectory.
341-
If None, the default time step ``t_default`` will be used.
342330
343331
x_dot: array-like or list of array-like, shape (n_samples, n_input_features), \
344332
optional (default None)
@@ -362,9 +350,6 @@ def fit(
362350
self: a fitted :class:`SINDy` instance
363351
"""
364352

365-
if t is None:
366-
t = self.t_default
367-
368353
if not _check_multiple_trajectories(x, x_dot, u):
369354
x, t, x_dot, u = _adapt_to_multiple_trajectories(x, t, x_dot, u)
370355
x, x_dot, u = _comprehend_and_validate_inputs(
@@ -482,7 +467,7 @@ def print(self, lhs=None, precision=3, **kwargs):
482467
names = f"{lhs[i]}"
483468
print(f"{names} = {eqn}", **kwargs)
484469

485-
def score(self, x, t=None, x_dot=None, u=None, metric=r2_score, **metric_kws):
470+
def score(self, x, t, x_dot=None, u=None, metric=r2_score, **metric_kws):
486471
"""
487472
Returns a score for the time derivative prediction produced by the model.
488473
@@ -491,12 +476,10 @@ def score(self, x, t=None, x_dot=None, u=None, metric=r2_score, **metric_kws):
491476
x: array-like or list of array-like, shape (n_samples, n_input_features)
492477
Samples from which to make predictions.
493478
494-
t: float, numpy array of shape (n_samples,), or list of numpy arrays, optional \
495-
(default None)
479+
t: float, numpy array of shape (n_samples,), or list of numpy arrays
496480
Time step between samples or array of collection times. Optional,
497481
used to compute the time derivatives of the samples if x_dot is not
498482
provided.
499-
If None, the default time step ``t_default`` will be used.
500483
501484
x_dot: array-like or list of array-like, shape (n_samples, n_input_features), \
502485
optional (default None)
@@ -527,9 +510,6 @@ def score(self, x, t=None, x_dot=None, u=None, metric=r2_score, **metric_kws):
527510
Metric function value for the model prediction of x_dot.
528511
"""
529512

530-
if t is None:
531-
t = self.t_default
532-
533513
if not _check_multiple_trajectories(x, x_dot, u):
534514
x, t, x_dot, u = _adapt_to_multiple_trajectories(x, t, x_dot, u)
535515
x, x_dot, u = _comprehend_and_validate_inputs(
@@ -597,7 +577,7 @@ def _process_trajectories(self, x, t, x_dot):
597577
)
598578
return x, x_dot
599579

600-
def differentiate(self, x, t=None):
580+
def differentiate(self, x, t):
601581
"""
602582
Apply the model's differentiation method
603583
(:code:`self.differentiation_method`) to data.
@@ -607,10 +587,8 @@ def differentiate(self, x, t=None):
607587
x: array-like or list of array-like, shape (n_samples, n_input_features)
608588
Data to be differentiated.
609589
610-
t: int, numpy array of shape (n_samples,), or list of numpy arrays, optional \
611-
(default None)
590+
t: int, numpy array of shape (n_samples,), or list of numpy arrays
612591
Time step between samples or array of collection times.
613-
If None, the default time step ``t_default`` will be used.
614592
615593
Returns
616594
-------
@@ -622,8 +600,7 @@ def differentiate(self, x, t=None):
622600
"SINDy.differentiate is deprecated. "
623601
"Call the differentiation_method parameter"
624602
)
625-
if t is None:
626-
t = self.t_default
603+
627604
if self.discrete_time:
628605
raise RuntimeError("No differentiation implemented for discrete time model")
629606
if not _check_multiple_trajectories(x, None, None):

test/test_feature_library.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -643,22 +643,22 @@ def test_parameterized_library(diffuse_multiple_trajectories):
643643

644644

645645
# Helper function for testing PDE libraries
646-
def pde_library_helper(library, u):
646+
def pde_library_helper(library, u, t):
647647
base_opt = STLSQ(normalize_columns=True, alpha=1e-10, threshold=0)
648648
model = SINDy(optimizer=base_opt, feature_library=library)
649-
model.fit(u)
649+
model.fit(u, t)
650650
assert np.any(base_opt.coef_ != 0.0)
651651

652652

653653
def test_1D_pdes(data_1d_random_pde):
654-
_, spatial_grid, u, _ = data_1d_random_pde
654+
t, spatial_grid, u, _ = data_1d_random_pde
655655
pde_lib = PDELibrary(
656656
function_library=PolynomialLibrary(degree=2, include_bias=False),
657657
derivative_order=4,
658658
spatial_grid=spatial_grid,
659659
include_bias=True,
660660
)
661-
pde_library_helper(pde_lib, u)
661+
pde_library_helper(pde_lib, u, t)
662662

663663

664664
def test_1D_weak_pdes():
@@ -676,7 +676,7 @@ def test_1D_weak_pdes():
676676
H_xt=2,
677677
include_bias=True,
678678
)
679-
pde_library_helper(pde_lib, u)
679+
pde_library_helper(pde_lib, u, t)
680680

681681

682682
def test_sindypi_library(data_lorenz):

test/test_optimizers/test_optimizers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def test_sr3_quadratic_library(params):
436436
# Test SR3
437437
opt = SR3(**params)
438438
model = SINDy(optimizer=opt, feature_library=sindy_library)
439-
model.fit(x)
439+
model.fit(x, t=1)
440440
check_is_fitted(model)
441441

442442

@@ -472,7 +472,7 @@ def test_constrained_sr3_quadratic_library(params):
472472
# Test constrained SR3 without constraints
473473
opt = ConstrainedSR3(**params)
474474
model = SINDy(optimizer=opt, feature_library=sindy_library)
475-
model.fit(x)
475+
model.fit(x, t=1)
476476
check_is_fitted(model)
477477

478478
# rerun with identity constraints
@@ -487,7 +487,7 @@ def test_constrained_sr3_quadratic_library(params):
487487
constraint_lhs=constraint_matrix, constraint_rhs=constraint_rhs, **params
488488
)
489489
model = SINDy(optimizer=opt, feature_library=sindy_library)
490-
model.fit(x)
490+
model.fit(x, t=1)
491491
check_is_fitted(model)
492492
assert np.allclose((model.coefficients().flatten())[:p], 0.0)
493493

@@ -1081,7 +1081,7 @@ def test_ssr_criteria(data_lorenz):
10811081
x, t = data_lorenz
10821082
opt = SSR(normalize_columns=True, criteria="model_residual", kappa=1e-3)
10831083
model = SINDy(optimizer=opt)
1084-
model.fit(x)
1084+
model.fit(x, t)
10851085
assert np.shape(opt.coef_) == (3, 10)
10861086

10871087

0 commit comments

Comments
 (0)