OpenReservoirComputing 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,416 @@
1
+ """Discrete and continuous ESN implementations with standard driver."""
2
+
3
+ import diffrax
4
+ import jax
5
+ import jax.numpy as jnp
6
+
7
+ from orc.drivers import ParallelESNDriver
8
+ from orc.embeddings import EnsembleLinearEmbedding, ParallelLinearEmbedding
9
+ from orc.forecaster.base import CRCForecasterBase, RCForecasterBase
10
+ from orc.readouts import (
11
+ EnsembleLinearReadout,
12
+ ParallelLinearReadout,
13
+ ParallelQuadraticReadout,
14
+ )
15
+
16
+ jax.config.update("jax_enable_x64", True)
17
+
18
+
19
+ class ESNForecaster(RCForecasterBase):
20
+ """
21
+ Basic implementation of ESN for forecasting.
22
+
23
+ Attributes
24
+ ----------
25
+ res_dim : int
26
+ Reservoir dimension.
27
+ data_dim : int
28
+ Input/output dimension.
29
+ driver : ParallelESNDriver
30
+ Driver implmenting the Echo State Network dynamics.
31
+ readout : BaseReadout
32
+ Trainable linear readout layer.
33
+ embedding : ParallelLinearEmbedding
34
+ Untrainable linear embedding layer.
35
+
36
+ Methods
37
+ -------
38
+ force(in_seq, res_state)
39
+ Teacher forces the reservoir with sequence in_seq and init. cond. res_state.
40
+ forecast(fcast_len, res_state)
41
+ Perform a forecast of fcast_len steps from res_state.
42
+ forecast_from_IC(fcast_len, spinup_data)
43
+ Forecast from a sequence of spinup data.
44
+ set_readout(readout)
45
+ Replace readout layer.
46
+ set_embedding(embedding)
47
+ Replace embedding layer.
48
+ """
49
+
50
+ res_dim: int
51
+ data_dim: int
52
+
53
+ def __init__(
54
+ self,
55
+ data_dim: int,
56
+ res_dim: int,
57
+ leak_rate: float = 0.6,
58
+ bias: float = 1.6,
59
+ embedding_scaling: float = 0.08,
60
+ Wr_density: float = 0.02,
61
+ Wr_spectral_radius: float = 0.8,
62
+ dtype: type = jnp.float64,
63
+ seed: int = 0,
64
+ chunks: int = 1,
65
+ locality: int = 0,
66
+ quadratic: bool = False,
67
+ periodic: bool = True,
68
+ use_sparse_eigs: bool = True,
69
+ ) -> None:
70
+ """
71
+ Initialize the ESN model.
72
+
73
+ Parameters
74
+ ----------
75
+ data_dim : int
76
+ Dimension of the input data.
77
+ res_dim : int
78
+ Dimension of the reservoir adjacency matrix Wr.
79
+ leak_rate : float
80
+ Integration leak rate of the reservoir dynamics.
81
+ bias : float
82
+ Bias term for the reservoir dynamics.
83
+ embedding_scaling : float
84
+ Scaling factor for the embedding layer.
85
+ Wr_density : float
86
+ Density of the reservoir adjacency matrix Wr.
87
+ Wr_spectral_radius : float
88
+ Largest eigenvalue of the reservoir adjacency matrix Wr.
89
+ dtype : type
90
+ Data type of the model (jnp.float64 is highly recommended).
91
+ seed : int
92
+ Random seed for generating the PRNG key for the reservoir computer.
93
+ chunks : int
94
+ Number of parallel reservoirs, must evenly divide data_dim.
95
+ locality : int
96
+ Overlap in adjacent parallel reservoirs.
97
+ quadratic : bool
98
+ Use quadratic nonlinearity in output, default False.
99
+ periodic : bool
100
+ Periodic BCs for embedding layer.
101
+ use_sparse_eigs : bool
102
+ Whether to use sparse eigensolver for setting the spectral radius of wr.
103
+ Default is True, which is recommended to save memory and compute time. If
104
+ False, will use dense eigensolver which may be more accurate.
105
+ """
106
+ # Initialize the random key and reservoir dimension
107
+ self.res_dim = res_dim
108
+ self.seed = seed
109
+ self.data_dim = data_dim
110
+ key = jax.random.PRNGKey(seed)
111
+ key_driver, key_readout, key_embedding = jax.random.split(key, 3)
112
+
113
+ # init in embedding, driver and readout
114
+ embedding = ParallelLinearEmbedding(
115
+ in_dim=data_dim,
116
+ res_dim=res_dim,
117
+ seed=key_embedding[0],
118
+ scaling=embedding_scaling,
119
+ chunks=chunks,
120
+ locality=locality,
121
+ periodic=periodic,
122
+ )
123
+ driver = ParallelESNDriver(
124
+ res_dim=res_dim,
125
+ seed=key_driver[0],
126
+ leak=leak_rate,
127
+ bias=bias,
128
+ density=Wr_density,
129
+ spectral_radius=Wr_spectral_radius,
130
+ chunks=chunks,
131
+ dtype=dtype,
132
+ use_sparse_eigs=use_sparse_eigs,
133
+ )
134
+ if quadratic:
135
+ readout = ParallelQuadraticReadout(
136
+ out_dim=data_dim, res_dim=res_dim, seed=key_readout[0], chunks=chunks
137
+ )
138
+ else:
139
+ readout = ParallelLinearReadout(
140
+ out_dim=data_dim, res_dim=res_dim, seed=key_readout[0], chunks=chunks
141
+ )
142
+
143
+ super().__init__(
144
+ driver=driver,
145
+ readout=readout,
146
+ embedding=embedding,
147
+ dtype=dtype,
148
+ seed=seed,
149
+ )
150
+ self.chunks = chunks
151
+
152
+
153
+ class CESNForecaster(CRCForecasterBase):
154
+ """
155
+ Basic implementation of a Continuous ESN for forecasting.
156
+
157
+ Attributes
158
+ ----------
159
+ res_dim : int
160
+ Reservoir dimension.
161
+ data_dim : int
162
+ Input/output dimension.
163
+ driver : ParallelESNDriver
164
+ Driver implementing the Echo State Network dynamics
165
+ in continuous time.
166
+ readout : BaseReadout
167
+ Trainable linear readout layer.
168
+ embedding : ParallelLinearEmbedding
169
+ Untrainable linear embedding layer.
170
+
171
+ Methods
172
+ -------
173
+ force(in_seq, res_state)
174
+ Teacher forces the reservoir with sequence in_seq and init. cond. res_state.
175
+ forecast(fcast_len, res_state)
176
+ Perform a forecast of fcast_len steps from res_state.
177
+ forecast_from_IC(fcast_len, spinup_data)
178
+ Forecast from a sequence of spinup data.
179
+ set_readout(readout)
180
+ Replace readout layer.
181
+ set_embedding(embedding)
182
+ Replace embedding layer.
183
+ """
184
+
185
+ res_dim: int
186
+ data_dim: int
187
+
188
+ def __init__(
189
+ self,
190
+ data_dim: int,
191
+ res_dim: int,
192
+ time_const: float = 50.0,
193
+ bias: float = 1.6,
194
+ embedding_scaling: float = 0.08,
195
+ Wr_density: float = 0.02,
196
+ Wr_spectral_radius: float = 0.8,
197
+ dtype: type = jnp.float64,
198
+ seed: int = 0,
199
+ chunks: int = 1,
200
+ locality: int = 0,
201
+ quadratic: bool = False,
202
+ periodic: bool = True,
203
+ use_sparse_eigs: bool = True,
204
+ solver: diffrax.AbstractSolver = None,
205
+ stepsize_controller: diffrax.AbstractAdaptiveStepSizeController = None,
206
+ ) -> None:
207
+ """
208
+ Initialize the CESN model.
209
+
210
+ Parameters
211
+ ----------
212
+ data_dim : int
213
+ Dimension of the input data.
214
+ res_dim : int
215
+ Dimension of the reservoir adjacency matrix Wr.
216
+ time_const : float
217
+ Time constant of the reservoir dynamics.
218
+ bias : float
219
+ Bias term for the reservoir dynamics.
220
+ embedding_scaling : float
221
+ Scaling factor for the embedding layer.
222
+ Wr_density : float
223
+ Density of the reservoir adjacency matrix Wr.
224
+ Wr_spectral_radius : float
225
+ Largest eigenvalue of the reservoir adjacency matrix Wr.
226
+ dtype : type
227
+ Data type of the model (jnp.float64 is highly recommended).
228
+ seed : int
229
+ Random seed for generating the PRNG key for the reservoir computer.
230
+ chunks : int
231
+ Number of parallel reservoirs, must evenly divide data_dim.
232
+ locality : int
233
+ Overlap in adjacent parallel reservoirs.
234
+ quadratic : bool
235
+ Use quadratic nonlinearity in output, default False.
236
+ periodic : bool
237
+ Periodic BCs for embedding layer.
238
+ use_sparse_eigs : bool
239
+ Whether to use sparse eigensolver for setting the spectral radius of wr.
240
+ Default is True, which is recommended to save memory and compute time. If
241
+ False, will use dense eigensolver which may be more accurate.
242
+ """
243
+ # Initialize the random key and reservoir dimension
244
+ self.res_dim = res_dim
245
+ self.seed = seed
246
+ self.data_dim = data_dim
247
+ key = jax.random.PRNGKey(seed)
248
+ key_driver, key_readout, key_embedding = jax.random.split(key, 3)
249
+
250
+ # init in embedding, driver and readout
251
+ embedding = ParallelLinearEmbedding(
252
+ in_dim=data_dim,
253
+ res_dim=res_dim,
254
+ seed=key_embedding[0],
255
+ scaling=embedding_scaling,
256
+ chunks=chunks,
257
+ locality=locality,
258
+ periodic=periodic,
259
+ )
260
+ driver = ParallelESNDriver(
261
+ res_dim=res_dim,
262
+ seed=key_driver[0],
263
+ time_const=time_const,
264
+ bias=bias,
265
+ density=Wr_density,
266
+ spectral_radius=Wr_spectral_radius,
267
+ chunks=chunks,
268
+ mode="continuous",
269
+ dtype=dtype,
270
+ use_sparse_eigs=use_sparse_eigs,
271
+ )
272
+ if quadratic:
273
+ readout = ParallelQuadraticReadout(
274
+ out_dim=data_dim, res_dim=res_dim, seed=key_readout[0], chunks=chunks
275
+ )
276
+ else:
277
+ readout = ParallelLinearReadout(
278
+ out_dim=data_dim, res_dim=res_dim, seed=key_readout[0], chunks=chunks
279
+ )
280
+
281
+ if solver is None:
282
+ solver = diffrax.Tsit5()
283
+ if stepsize_controller is None:
284
+ stepsize_controller = diffrax.PIDController(
285
+ rtol=1e-3, atol=1e-6, icoeff=1.0
286
+ )
287
+
288
+ super().__init__(
289
+ driver=driver,
290
+ readout=readout,
291
+ embedding=embedding,
292
+ dtype=dtype,
293
+ seed=seed,
294
+ solver=solver,
295
+ stepsize_controller=stepsize_controller,
296
+ )
297
+ self.chunks = chunks
298
+
299
+
300
+ class EnsembleESNForecaster(RCForecasterBase):
301
+ """
302
+ Ensembled ESNs for forecasting.
303
+
304
+ Attributes
305
+ ----------
306
+ res_dim : int
307
+ Reservoir dimension.
308
+ data_dim : int
309
+ Input/output dimension.
310
+ driver : ParallelESNDriver
311
+ Driver implmenting the Echo State Network dynamics.
312
+ readout : EnsembleLinearReadout
313
+ Trainable linear readout layer.
314
+ embedding : EnsembleLinearEmbedding
315
+ Untrainable linear embedding layer.
316
+
317
+ Methods
318
+ -------
319
+ force(in_seq, res_state)
320
+ Teacher forces the reservoir with sequence in_seq and init. cond. res_state.
321
+ forecast(fcast_len, res_state)
322
+ Perform a forecast of fcast_len steps from res_state.
323
+ forecast_from_IC(fcast_len, spinup_data)
324
+ Forecast from a sequence of spinup data.
325
+ set_readout(readout)
326
+ Replace readout layer.
327
+ set_embedding(embedding)
328
+ Replace embedding layer.
329
+ """
330
+
331
+ res_dim: int
332
+ data_dim: int
333
+
334
+ def __init__(
335
+ self,
336
+ data_dim: int,
337
+ res_dim: int,
338
+ leak_rate: float = 0.6,
339
+ bias: float = 1.6,
340
+ embedding_scaling: float = 0.08,
341
+ Wr_density: float = 0.02,
342
+ Wr_spectral_radius: float = 0.8,
343
+ dtype: type = jnp.float64,
344
+ seed: int = 0,
345
+ chunks: int = 1,
346
+ use_sparse_eigs: bool = True,
347
+ ) -> None:
348
+ """
349
+ Initialize the ESN model.
350
+
351
+ Parameters
352
+ ----------
353
+ data_dim : int
354
+ Dimension of the input data.
355
+ res_dim : int
356
+ Dimension of the reservoir adjacency matrix Wr.
357
+ leak_rate : float
358
+ Integration leak rate of the reservoir dynamics.
359
+ bias : float
360
+ Bias term for the reservoir dynamics.
361
+ embedding_scaling : float
362
+ Scaling factor for the embedding layer.
363
+ Wr_density : float
364
+ Density of the reservoir adjacency matrix Wr.
365
+ Wr_spectral_radius : float
366
+ Largest eigenvalue of the reservoir adjacency matrix Wr.
367
+ dtype : type
368
+ Data type of the model (jnp.float64 is highly recommended).
369
+ seed : int
370
+ Random seed for generating the PRNG key for the reservoir computer.
371
+ chunks : int
372
+ Number of parallel reservoirs, must evenly divide data_dim.
373
+ use_sparse_eigs : bool
374
+ Whether to use sparse eigensolver for setting the spectral radius of wr.
375
+ Default is True, which is recommended to save memory and compute time. If
376
+ False, will use dense eigensolver which may be more accurate.
377
+ """
378
+ # Initialize the random key and reservoir dimension
379
+ self.res_dim = res_dim
380
+ self.seed = seed
381
+ self.data_dim = data_dim
382
+ key = jax.random.PRNGKey(seed)
383
+ key_driver, key_readout, key_embedding = jax.random.split(key, 3)
384
+
385
+ # init in embedding, driver and readout
386
+ embedding = EnsembleLinearEmbedding(
387
+ in_dim=data_dim,
388
+ res_dim=res_dim,
389
+ seed=key_embedding[0],
390
+ scaling=embedding_scaling,
391
+ chunks=chunks,
392
+ )
393
+ driver = ParallelESNDriver(
394
+ res_dim=res_dim,
395
+ seed=key_driver[0],
396
+ leak=leak_rate,
397
+ bias=bias,
398
+ density=Wr_density,
399
+ spectral_radius=Wr_spectral_radius,
400
+ chunks=chunks,
401
+ dtype=dtype,
402
+ use_sparse_eigs=use_sparse_eigs,
403
+ )
404
+
405
+ readout = EnsembleLinearReadout(
406
+ out_dim=data_dim, res_dim=res_dim, seed=key_readout[0], chunks=chunks
407
+ )
408
+
409
+ super().__init__(
410
+ driver=driver,
411
+ readout=readout,
412
+ embedding=embedding,
413
+ dtype=dtype,
414
+ seed=seed,
415
+ )
416
+ self.chunks = chunks
@@ -0,0 +1,301 @@
1
+ """Training functions for reservoir computer forecasters."""
2
+
3
+ import equinox as eqx
4
+ import jax.numpy as jnp
5
+ from jaxtyping import Array
6
+
7
+ from orc.forecaster.models import CESNForecaster, EnsembleESNForecaster, ESNForecaster
8
+ from orc.readouts import ParallelNonlinearReadout
9
+ from orc.utils.regressions import (
10
+ _solve_all_ridge_reg,
11
+ _solve_all_ridge_reg_batched,
12
+ )
13
+
14
+
15
+ def train_ESNForecaster(
16
+ model: ESNForecaster,
17
+ train_seq: Array,
18
+ target_seq: Array = None,
19
+ spinup: int = 0,
20
+ initial_res_state: Array = None,
21
+ beta: float = 8e-8,
22
+ batch_size: int = None,
23
+ ) -> tuple[ESNForecaster, Array]:
24
+ """Training function for ESNForecaster.
25
+
26
+ Parameters
27
+ ----------
28
+ model : ESNForecaster
29
+ ESNForecaster model to train.
30
+ train_seq : Array
31
+ Training input sequence for reservoir, (shape=(seq_len, data_dim)).
32
+ target_seq : Array
33
+ Target sequence for training reservoir, (shape=(seq_len, data_dim)).
34
+ initial_res_state : Array
35
+ Initial reservoir state, (shape=(chunks, res_dim,)).
36
+ spinup : int
37
+ Initial transient of reservoir states to discard.
38
+ beta : float
39
+ Tikhonov regularization parameter.
40
+ batch_size : int, optional
41
+ Number of parallel reservoirs to process in each batch for ridge regression.
42
+ If None (default), processes all reservoirs at once. Use smaller values
43
+ to reduce memory usage for large numbers of parallel reservoirs.
44
+
45
+ Returns
46
+ -------
47
+ model : ESNForecaster
48
+ Trained ESN model.
49
+ res_seq : Array
50
+ Training sequence of reservoir states.
51
+ """
52
+ # Check that model is an ESN
53
+ if not isinstance(model, ESNForecaster):
54
+ raise TypeError("Model must be an ESNForecaster.")
55
+
56
+ # check that spinup is less than the length of the training sequence
57
+ if spinup >= train_seq.shape[0]:
58
+ raise ValueError(
59
+ "spinup must be less than the length of the training sequence."
60
+ )
61
+
62
+ if initial_res_state is None:
63
+ initial_res_state = jnp.zeros(
64
+ (
65
+ model.embedding.chunks,
66
+ model.res_dim,
67
+ ),
68
+ dtype=model.dtype,
69
+ )
70
+
71
+ if target_seq is None:
72
+ tot_seq = train_seq
73
+ target_seq = train_seq[1:, :]
74
+ train_seq = train_seq[:-1, :]
75
+ else:
76
+ tot_seq = jnp.vstack((train_seq, target_seq[-1:]))
77
+
78
+ tot_res_seq = model.force(tot_seq, initial_res_state)
79
+ res_seq = tot_res_seq[:-1]
80
+ if isinstance(model.readout, ParallelNonlinearReadout):
81
+ res_seq_train = model.readout.nonlinear_transform(res_seq)
82
+ else:
83
+ res_seq_train = res_seq
84
+
85
+ if batch_size is None:
86
+ cmat = _solve_all_ridge_reg(
87
+ res_seq_train[spinup:],
88
+ target_seq[spinup:].reshape(
89
+ res_seq[spinup:].shape[0], res_seq.shape[1], -1
90
+ ),
91
+ beta,
92
+ )
93
+ else:
94
+ cmat = _solve_all_ridge_reg_batched(
95
+ res_seq_train[spinup:],
96
+ target_seq[spinup:].reshape(
97
+ res_seq[spinup:].shape[0], res_seq.shape[1], -1
98
+ ),
99
+ beta,
100
+ batch_size,
101
+ )
102
+
103
+ def where(m):
104
+ return m.readout.wout
105
+
106
+ model = eqx.tree_at(where, model, cmat)
107
+
108
+ return model, tot_res_seq
109
+
110
+
111
+ def train_CESNForecaster(
112
+ model: CESNForecaster,
113
+ train_seq: Array,
114
+ t_train: Array,
115
+ target_seq: Array = None,
116
+ spinup: int = 0,
117
+ initial_res_state: Array = None,
118
+ beta: float = 8e-8,
119
+ batch_size: int = None,
120
+ ) -> tuple[CESNForecaster, Array]:
121
+ """Training function for CESNForecaster.
122
+
123
+ Parameters
124
+ ----------
125
+ model : CESNForecaster
126
+ CESNForecaster model to train.
127
+ train_seq : Array
128
+ Training input sequence for reservoir, (shape=(seq_len, data_dim)).
129
+ t_train : Array
130
+ time vector corresponding to the training sequence, (shape=(seq_len,)).
131
+ target_seq : Array
132
+ Target sequence for training reservoir, (shape=(seq_len, data_dim)).
133
+ initial_res_state : Array
134
+ Initial reservoir state, (shape=(chunks, res_dim,)).
135
+ spinup : int
136
+ Initial transient of reservoir states to discard.
137
+ beta : float
138
+ Tikhonov regularization parameter.
139
+ batch_size : int, optional
140
+ Number of parallel reservoirs to process in each batch for ridge regression.
141
+ If None (default), processes all reservoirs at once. Use smaller values
142
+ to reduce memory usage for large numbers of parallel reservoirs.
143
+
144
+ Returns
145
+ -------
146
+ model : CESNForecaster
147
+ Trained CESN model.
148
+ res_seq : Array
149
+ Training sequence of reservoir states.
150
+ """
151
+ # check that model is continuous
152
+ if not isinstance(model, CESNForecaster):
153
+ raise TypeError("Model must be a CESNForecaster.")
154
+
155
+ # check that train_seq and t_train have the same length
156
+ if train_seq.shape[0] != t_train.shape[0]:
157
+ raise ValueError("train_seq and t_train must have the same length.")
158
+
159
+ # check that spinup is less than the length of the training sequence
160
+ if spinup >= train_seq.shape[0]:
161
+ raise ValueError(
162
+ "spinup must be less than the length of the training sequence."
163
+ )
164
+
165
+ if initial_res_state is None:
166
+ initial_res_state = jnp.zeros(
167
+ (
168
+ model.embedding.chunks,
169
+ model.res_dim,
170
+ ),
171
+ dtype=model.dtype,
172
+ )
173
+
174
+ if target_seq is None:
175
+ tot_seq = train_seq
176
+ target_seq = train_seq[1:, :]
177
+ train_seq = train_seq[:-1, :]
178
+ else:
179
+ tot_seq = jnp.vstack((train_seq, target_seq[-1:]))
180
+
181
+ tot_res_seq = model.force(tot_seq, initial_res_state, ts=t_train)
182
+ res_seq = tot_res_seq[:-1]
183
+ if isinstance(model.readout, ParallelNonlinearReadout):
184
+ res_seq_train = eqx.filter_vmap(model.readout.nonlinear_transform)(res_seq)
185
+ else:
186
+ res_seq_train = res_seq
187
+
188
+ if batch_size is None:
189
+ cmat = _solve_all_ridge_reg(
190
+ res_seq_train[spinup:],
191
+ target_seq[spinup:].reshape(
192
+ res_seq[spinup:].shape[0], res_seq.shape[1], -1
193
+ ),
194
+ beta,
195
+ )
196
+ else:
197
+ cmat = _solve_all_ridge_reg_batched(
198
+ res_seq_train[spinup:],
199
+ target_seq[spinup:].reshape(
200
+ res_seq[spinup:].shape[0], res_seq.shape[1], -1
201
+ ),
202
+ beta,
203
+ batch_size,
204
+ )
205
+
206
+ def where(m):
207
+ return m.readout.wout
208
+
209
+ model = eqx.tree_at(where, model, cmat)
210
+
211
+ return model, tot_res_seq
212
+
213
+
214
+ def train_EnsembleESNForecaster(
215
+ model: EnsembleESNForecaster,
216
+ train_seq: Array,
217
+ target_seq: Array | None = None,
218
+ spinup: int = 0,
219
+ initial_res_state: Array | None = None,
220
+ beta: float = 8e-8,
221
+ batch_size: int | None = None,
222
+ ) -> tuple[ESNForecaster, Array]:
223
+ """Training function for ESNForecaster.
224
+
225
+ Parameters
226
+ ----------
227
+ model : ESNForecaster
228
+ ESNForecaster model to train.
229
+ train_seq : Array
230
+ Training input sequence for reservoir, (shape=(seq_len, data_dim)).
231
+ target_seq : Array
232
+ Target sequence for training reservoir, (shape=(seq_len, data_dim)).
233
+ initial_res_state : Array
234
+ Initial reservoir state, (shape=(chunks, res_dim,)).
235
+ spinup : int
236
+ Initial transient of reservoir states to discard.
237
+ beta : float
238
+ Tikhonov regularization parameter.
239
+ batch_size : int, optional
240
+ Number of parallel reservoirs to process in each batch for ridge regression.
241
+ If None (default), processes all reservoirs at once. Use smaller values
242
+ to reduce memory usage for large numbers of parallel reservoirs.
243
+
244
+ Returns
245
+ -------
246
+ model : ESNForecaster
247
+ Trained ESN model.
248
+ res_seq : Array
249
+ Training sequence of reservoir states.
250
+ """
251
+ # Check that model is an ESN
252
+ if not isinstance(model, EnsembleESNForecaster):
253
+ raise TypeError("Model must be an EnsembleESNForecaster.")
254
+
255
+ # check that spinup is less than the length of the training sequence
256
+ if spinup >= train_seq.shape[0]:
257
+ raise ValueError(
258
+ "spinup must be less than the length of the training sequence."
259
+ )
260
+
261
+ if initial_res_state is None:
262
+ initial_res_state = jnp.zeros(
263
+ (
264
+ model.embedding.chunks,
265
+ model.res_dim,
266
+ ),
267
+ dtype=model.dtype,
268
+ )
269
+
270
+ if target_seq is None:
271
+ tot_seq = train_seq
272
+ target_seq = train_seq[1:, :]
273
+ train_seq = train_seq[:-1, :]
274
+ else:
275
+ tot_seq = jnp.vstack((train_seq, target_seq[-1:]))
276
+
277
+ tot_res_seq = model.force(tot_seq, initial_res_state)
278
+ res_seq = tot_res_seq[:-1]
279
+ res_seq_train = res_seq
280
+
281
+ repeated_target_seq = jnp.repeat(target_seq[:, None, :], model.chunks, axis=1)
282
+ if batch_size is None:
283
+ cmat = _solve_all_ridge_reg(
284
+ res_seq_train[spinup:],
285
+ repeated_target_seq[spinup:],
286
+ beta,
287
+ )
288
+ else:
289
+ cmat = _solve_all_ridge_reg_batched(
290
+ res_seq_train[spinup:],
291
+ repeated_target_seq[spinup:],
292
+ beta,
293
+ batch_size,
294
+ )
295
+
296
+ def where(m):
297
+ return m.readout.wout
298
+
299
+ model = eqx.tree_at(where, model, cmat)
300
+
301
+ return model, tot_res_seq