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.
- openreservoircomputing-0.1.0.dist-info/METADATA +168 -0
- openreservoircomputing-0.1.0.dist-info/RECORD +28 -0
- openreservoircomputing-0.1.0.dist-info/WHEEL +5 -0
- openreservoircomputing-0.1.0.dist-info/licenses/LICENSE +201 -0
- openreservoircomputing-0.1.0.dist-info/top_level.txt +1 -0
- orc/__init__.py +29 -0
- orc/classifier/__init__.py +6 -0
- orc/classifier/base.py +6 -0
- orc/classifier/models.py +6 -0
- orc/classifier/train.py +6 -0
- orc/control/__init__.py +15 -0
- orc/control/base.py +362 -0
- orc/control/models.py +139 -0
- orc/control/train.py +90 -0
- orc/data/__init__.py +27 -0
- orc/data/integrators.py +753 -0
- orc/drivers.py +851 -0
- orc/embeddings.py +459 -0
- orc/forecaster/__init__.py +20 -0
- orc/forecaster/base.py +487 -0
- orc/forecaster/models.py +416 -0
- orc/forecaster/train.py +301 -0
- orc/readouts.py +742 -0
- orc/tuning/__init__.py +6 -0
- orc/utils/__init__.py +6 -0
- orc/utils/numerics.py +115 -0
- orc/utils/regressions.py +73 -0
- orc/utils/visualization.py +193 -0
orc/drivers.py
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
"""Define base class for reservoir drivers and implement common architectures."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
import equinox as eqx
|
|
7
|
+
import jax
|
|
8
|
+
import jax.numpy as jnp
|
|
9
|
+
import jax.random
|
|
10
|
+
from jax.experimental import sparse
|
|
11
|
+
from jaxtyping import Array, Float
|
|
12
|
+
|
|
13
|
+
from orc.utils import max_eig_arnoldi
|
|
14
|
+
|
|
15
|
+
jax.config.update("jax_enable_x64", True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DriverBase(eqx.Module, ABC):
|
|
19
|
+
"""
|
|
20
|
+
Base class dictating API for all implemented reservoir drivers.
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
res_dim : int
|
|
25
|
+
Reservoir dimensionxe
|
|
26
|
+
dtype : Float
|
|
27
|
+
Dtype for model, jnp.float64 or jnp.float32.
|
|
28
|
+
|
|
29
|
+
Methods
|
|
30
|
+
-------
|
|
31
|
+
advance(proj_vars, res_state)
|
|
32
|
+
Advance reservoir according to proj_vars.
|
|
33
|
+
batch_advance(proj_vars, res_state)
|
|
34
|
+
Advance batch of reservoir states according to proj_vars.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
res_dim: int
|
|
38
|
+
dtype: Float
|
|
39
|
+
|
|
40
|
+
def __init__(self, res_dim, dtype=jnp.float64):
|
|
41
|
+
"""Ensure reservoir dim and dtype are correct type."""
|
|
42
|
+
self.res_dim = res_dim
|
|
43
|
+
if not isinstance(res_dim, int):
|
|
44
|
+
raise TypeError("Reservoir dimension res_dim must be an integer.")
|
|
45
|
+
self.dtype = dtype
|
|
46
|
+
if not (dtype == jnp.float64 or dtype == jnp.float32):
|
|
47
|
+
raise TypeError("dtype must be jnp.float64 or jnp.float32.")
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def advance(self, proj_vars: Array, res_state: Array) -> Array:
|
|
51
|
+
"""Advance the reservoir given projected inputs and current state.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
proj_vars : Array
|
|
56
|
+
Projected inputs to reservoir.
|
|
57
|
+
res_state : Array
|
|
58
|
+
Initial reservoir state.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
Array
|
|
63
|
+
Updated reservoir state, (shape=(res_dim,)).
|
|
64
|
+
"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@eqx.filter_jit
|
|
68
|
+
def batch_advance(self, proj_vars: Array, res_state: Array) -> Array:
|
|
69
|
+
"""
|
|
70
|
+
Batch advance the reservoir given projected inputs and current state.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
proj_vars : Array
|
|
75
|
+
Reservoir projected inputs.
|
|
76
|
+
res_state : Array
|
|
77
|
+
Reservoir state.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
Array
|
|
82
|
+
Updated reservoir state.
|
|
83
|
+
"""
|
|
84
|
+
return eqx.filter_vmap(self.advance)(proj_vars, res_state)
|
|
85
|
+
|
|
86
|
+
def __call__(self, proj_vars: Array, res_state: Array) -> Array:
|
|
87
|
+
"""Advance reservoir state.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
proj_vars : Array
|
|
92
|
+
Reservoir projected inputs, (shape=(chunks, res_dim) or
|
|
93
|
+
shape=(seq_len, chunks, res_dim)).
|
|
94
|
+
res_state : Array
|
|
95
|
+
Current reservoir state, (shape=(chunks, res_dim) or
|
|
96
|
+
shape=(seq_len, chunks, res_dim)).
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
Array
|
|
101
|
+
Sequence of reservoir states, (shape=(chunks, res_dim,) or
|
|
102
|
+
shape=(seq_len, chunks, res_dim)).
|
|
103
|
+
"""
|
|
104
|
+
if self.chunks > 0:
|
|
105
|
+
if len(proj_vars.shape) == 2:
|
|
106
|
+
to_ret = self.advance(proj_vars, res_state)
|
|
107
|
+
elif len(proj_vars.shape) == 3:
|
|
108
|
+
to_ret = self.batch_advance(proj_vars, res_state)
|
|
109
|
+
else:
|
|
110
|
+
if len(proj_vars.shape) == 1:
|
|
111
|
+
to_ret = self.advance(proj_vars, res_state)
|
|
112
|
+
elif len(proj_vars.shape) == 2:
|
|
113
|
+
to_ret = self.batch_advance(proj_vars, res_state)
|
|
114
|
+
return to_ret
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ParallelESNDriver(DriverBase):
|
|
118
|
+
"""Standard implementation of ESN reservoir with tanh nonlinearity.
|
|
119
|
+
|
|
120
|
+
Attributes
|
|
121
|
+
----------
|
|
122
|
+
res_dim : int
|
|
123
|
+
Reservoir dimension.
|
|
124
|
+
wr : Array
|
|
125
|
+
Reservoir update matrix, (shape=(chunks, res_dim, res_dim,)).
|
|
126
|
+
leak : float
|
|
127
|
+
Leak rate parameter.
|
|
128
|
+
spectral_radius : float
|
|
129
|
+
Spectral radius of wr.
|
|
130
|
+
density : float
|
|
131
|
+
Density of wr.
|
|
132
|
+
bias : float
|
|
133
|
+
Additive bias in tanh nonlinearity.
|
|
134
|
+
chunks: int
|
|
135
|
+
Number of parallel reservoirs.
|
|
136
|
+
mode : str
|
|
137
|
+
Mode of reservoir update, either "discrete" or "continuous".
|
|
138
|
+
time_const : float
|
|
139
|
+
Time constant for continuous mode.
|
|
140
|
+
dtype : Float
|
|
141
|
+
Dtype, default jnp.float64.
|
|
142
|
+
|
|
143
|
+
Methods
|
|
144
|
+
-------
|
|
145
|
+
advance(proj_vars, res_state)
|
|
146
|
+
Updated reservoir state.
|
|
147
|
+
__call__(proj_vars, res_state)
|
|
148
|
+
Batched or single update to reservoir state.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
res_dim: int
|
|
152
|
+
leak: float
|
|
153
|
+
spectral_radius: float
|
|
154
|
+
density: float
|
|
155
|
+
bias: float
|
|
156
|
+
dtype: Float
|
|
157
|
+
wr: Array
|
|
158
|
+
chunks: int
|
|
159
|
+
mode: str
|
|
160
|
+
time_const: float
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
res_dim: int,
|
|
165
|
+
leak: float = 0.6,
|
|
166
|
+
spectral_radius: float = 0.8,
|
|
167
|
+
density: float = 0.02,
|
|
168
|
+
bias: float = 1.6,
|
|
169
|
+
dtype: Float = jnp.float64,
|
|
170
|
+
chunks: int = 1,
|
|
171
|
+
mode: str = "discrete",
|
|
172
|
+
time_const: float = 50.0,
|
|
173
|
+
*,
|
|
174
|
+
seed: int,
|
|
175
|
+
use_sparse_eigs: bool = True,
|
|
176
|
+
eigenval_batch_size: int = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Initialize weight matrices.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
res_dim : int
|
|
183
|
+
Reservoir dimension.
|
|
184
|
+
leak : float
|
|
185
|
+
Leak rate parameter.
|
|
186
|
+
spectral_radius : float
|
|
187
|
+
Spectral radius of wr.
|
|
188
|
+
density : float
|
|
189
|
+
Density of wr.
|
|
190
|
+
bias : float
|
|
191
|
+
Additive bias in tanh nonlinearity.
|
|
192
|
+
chunks: int
|
|
193
|
+
Number of parallel reservoirs.
|
|
194
|
+
mode : str
|
|
195
|
+
Mode of reservoir update, either "discrete" or "continuous".
|
|
196
|
+
time_const : float
|
|
197
|
+
Time constant for continuous mode. Ignored in discrete mode.
|
|
198
|
+
dtype : Float
|
|
199
|
+
Dtype, default jnp.float64.
|
|
200
|
+
seed : int
|
|
201
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
202
|
+
use_sparse_eigs : bool
|
|
203
|
+
Whether to use sparse eigensolver for setting the spectral radius of wr.
|
|
204
|
+
Default is True, which is recommended to save memory and compute time. If
|
|
205
|
+
False, will use dense eigensolver which may be more accurate.
|
|
206
|
+
eigenval_batch_size : int
|
|
207
|
+
Size of batches when batch_eigenvals. Default is None, which means no
|
|
208
|
+
batch eigenvalue computation.
|
|
209
|
+
"""
|
|
210
|
+
super().__init__(res_dim=res_dim, dtype=dtype)
|
|
211
|
+
self.res_dim = res_dim
|
|
212
|
+
self.leak = leak
|
|
213
|
+
self.spectral_radius = spectral_radius
|
|
214
|
+
self.density = density
|
|
215
|
+
self.bias = bias
|
|
216
|
+
self.dtype = dtype
|
|
217
|
+
self.mode = mode
|
|
218
|
+
self.time_const = time_const
|
|
219
|
+
key = jax.random.key(seed)
|
|
220
|
+
if spectral_radius <= 0:
|
|
221
|
+
raise ValueError("Spectral radius must be positive.")
|
|
222
|
+
if leak < 0 or leak > 1:
|
|
223
|
+
raise ValueError("Leak rate must satisfy 0 < leak < 1.")
|
|
224
|
+
if density < 0 or density > 1:
|
|
225
|
+
raise ValueError("Density must satisfy 0 < density < 1.")
|
|
226
|
+
if mode not in ["discrete", "continuous"]:
|
|
227
|
+
raise ValueError("Mode must be either 'discrete' or 'continuous'.")
|
|
228
|
+
if time_const <= 0:
|
|
229
|
+
raise ValueError("Time constant must be positive.")
|
|
230
|
+
subkey, wr_key = jax.random.split(key)
|
|
231
|
+
|
|
232
|
+
# check res_dim size for eigensolver choice
|
|
233
|
+
if res_dim < 100 and use_sparse_eigs:
|
|
234
|
+
use_sparse_eigs = False
|
|
235
|
+
warnings.warn(
|
|
236
|
+
"Reservoir dimension is less than 100, using dense "
|
|
237
|
+
"eigensolver for spectral radius.",
|
|
238
|
+
stacklevel=2,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# generate all wr matricies
|
|
242
|
+
sp_mat = sparse.random_bcoo(
|
|
243
|
+
key=wr_key,
|
|
244
|
+
shape=(chunks, res_dim, res_dim),
|
|
245
|
+
n_batch=1,
|
|
246
|
+
nse=density,
|
|
247
|
+
dtype=dtype,
|
|
248
|
+
generator=jax.random.normal,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
self.wr = _spec_rad_normalization(
|
|
252
|
+
sp_mat,
|
|
253
|
+
spectral_radius=spectral_radius,
|
|
254
|
+
eigenval_batch_size=eigenval_batch_size,
|
|
255
|
+
use_sparse_eigs=use_sparse_eigs,
|
|
256
|
+
chunks=chunks,
|
|
257
|
+
)
|
|
258
|
+
self.chunks = chunks
|
|
259
|
+
self.dtype = dtype
|
|
260
|
+
|
|
261
|
+
@eqx.filter_jit
|
|
262
|
+
def advance(self, proj_vars: Array, res_state: Array) -> Array:
|
|
263
|
+
"""Advance the reservoir state.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
proj_vars : Array
|
|
268
|
+
Reservoir projected inputs, (shape=(chunks, res_dim,)).
|
|
269
|
+
res_state : Array
|
|
270
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
271
|
+
|
|
272
|
+
Returns
|
|
273
|
+
-------
|
|
274
|
+
res_next : Array
|
|
275
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
276
|
+
"""
|
|
277
|
+
if proj_vars.shape != (self.chunks, self.res_dim):
|
|
278
|
+
raise ValueError(f"Incorrect proj_var dimension, got {proj_vars.shape}")
|
|
279
|
+
if self.mode == "continuous":
|
|
280
|
+
return self.time_const * (
|
|
281
|
+
-res_state
|
|
282
|
+
+ _sparse_ops(
|
|
283
|
+
self.wr, res_state, proj_vars, self.bias * jnp.ones_like(proj_vars)
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
else:
|
|
287
|
+
return (
|
|
288
|
+
self.leak
|
|
289
|
+
* _sparse_ops(
|
|
290
|
+
self.wr, res_state, proj_vars, self.bias * jnp.ones_like(proj_vars)
|
|
291
|
+
)
|
|
292
|
+
+ (1 - self.leak) * res_state
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class ESNDriver(ParallelESNDriver):
|
|
297
|
+
"""Standard implementation of single ESN reservoir with tanh nonlinearity.
|
|
298
|
+
|
|
299
|
+
Attributes
|
|
300
|
+
----------
|
|
301
|
+
res_dim : int
|
|
302
|
+
Reservoir dimension.
|
|
303
|
+
wr : Array
|
|
304
|
+
Reservoir update matrix, (shape=(1, res_dim, res_dim,)).
|
|
305
|
+
leak : float
|
|
306
|
+
Leak rate parameter.
|
|
307
|
+
spectral_radius : float
|
|
308
|
+
Spectral radius of wr.
|
|
309
|
+
density : float
|
|
310
|
+
Density of wr.
|
|
311
|
+
bias : float
|
|
312
|
+
Additive bias in tanh nonlinearity.
|
|
313
|
+
mode : str
|
|
314
|
+
Mode of reservoir update, either "discrete" or "continuous".
|
|
315
|
+
time_const : float
|
|
316
|
+
Time constant for continuous mode.
|
|
317
|
+
dtype : Float
|
|
318
|
+
Dtype, default jnp.float64.
|
|
319
|
+
|
|
320
|
+
Methods
|
|
321
|
+
-------
|
|
322
|
+
advance(proj_vars, res_state)
|
|
323
|
+
Updated reservoir state.
|
|
324
|
+
__call__(proj_vars, res_state)
|
|
325
|
+
Batched or single update to reservoir state.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def __init__(
|
|
329
|
+
self,
|
|
330
|
+
res_dim: int,
|
|
331
|
+
leak: float = 0.6,
|
|
332
|
+
spectral_radius: float = 0.8,
|
|
333
|
+
density: float = 0.02,
|
|
334
|
+
bias: float = 1.6,
|
|
335
|
+
dtype: Float = jnp.float64,
|
|
336
|
+
mode: str = "discrete",
|
|
337
|
+
time_const: float = 50.0,
|
|
338
|
+
*,
|
|
339
|
+
seed: int,
|
|
340
|
+
use_sparse_eigs: bool = True,
|
|
341
|
+
eigenval_batch_size: int = None,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Initialize weight matrices.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
res_dim : int
|
|
348
|
+
Reservoir dimension.
|
|
349
|
+
leak : float
|
|
350
|
+
Leak rate parameter.
|
|
351
|
+
spectral_radius : float
|
|
352
|
+
Spectral radius of wr.
|
|
353
|
+
density : float
|
|
354
|
+
Density of wr.
|
|
355
|
+
bias : float
|
|
356
|
+
Additive bias in tanh nonlinearity.
|
|
357
|
+
mode : str
|
|
358
|
+
Mode of reservoir update, either "discrete" or "continuous".
|
|
359
|
+
time_const : float
|
|
360
|
+
Time constant for continuous mode. Ignored in discrete mode.
|
|
361
|
+
dtype : Float
|
|
362
|
+
Dtype, default jnp.float64.
|
|
363
|
+
seed : int
|
|
364
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
365
|
+
use_sparse_eigs : bool
|
|
366
|
+
Whether to use sparse eigensolver for setting the spectral radius of wr.
|
|
367
|
+
Default is True, which is recommended to save memory and compute time. If
|
|
368
|
+
False, will use dense eigensolver which may be more accurate.
|
|
369
|
+
eigenval_batch_size : int
|
|
370
|
+
Size of batches when batch_eigenvals. Default is None, which means no
|
|
371
|
+
batch eigenvalue computation.
|
|
372
|
+
"""
|
|
373
|
+
super().__init__(
|
|
374
|
+
res_dim=res_dim,
|
|
375
|
+
leak=leak,
|
|
376
|
+
spectral_radius=spectral_radius,
|
|
377
|
+
density=density,
|
|
378
|
+
bias=bias,
|
|
379
|
+
dtype=dtype,
|
|
380
|
+
mode=mode,
|
|
381
|
+
time_const=time_const,
|
|
382
|
+
seed=seed,
|
|
383
|
+
use_sparse_eigs=use_sparse_eigs,
|
|
384
|
+
eigenval_batch_size=eigenval_batch_size,
|
|
385
|
+
chunks=1,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
@eqx.filter_jit
|
|
389
|
+
def advance(self, proj_vars: Array, res_state: Array) -> Array:
|
|
390
|
+
"""Advance the reservoir state.
|
|
391
|
+
|
|
392
|
+
Parameters
|
|
393
|
+
----------
|
|
394
|
+
proj_vars : Array
|
|
395
|
+
Reservoir projected inputs, (shape=(res_dim,)).
|
|
396
|
+
res_state : Array
|
|
397
|
+
Reservoir state, (shape=(res_dim,)).
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
res_next : Array
|
|
402
|
+
Reservoir state, (shape=(res_dim,)).
|
|
403
|
+
"""
|
|
404
|
+
res_next = super().advance(proj_vars.reshape(1, -1), res_state.reshape(1, -1))
|
|
405
|
+
res_next = jnp.squeeze(res_next)
|
|
406
|
+
return res_next
|
|
407
|
+
|
|
408
|
+
def __call__(self, proj_vars: Array, res_state: Array) -> Array:
|
|
409
|
+
"""Advance reservoir state.
|
|
410
|
+
|
|
411
|
+
Parameters
|
|
412
|
+
----------
|
|
413
|
+
proj_vars : Array
|
|
414
|
+
Reservoir projected inputs, (shape=(res_dim,) or
|
|
415
|
+
shape=(seq_len, res_dim)).
|
|
416
|
+
res_state : Array
|
|
417
|
+
Current reservoir state, (shape=(res_dim,) or
|
|
418
|
+
shape=(seq_len, res_dim)).
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
Array
|
|
423
|
+
Sequence of reservoir states, (shape=(res_dim,) or
|
|
424
|
+
shape=(seq_len, res_dim)).
|
|
425
|
+
"""
|
|
426
|
+
res_next = super().__call__(proj_vars[..., None, :], res_state[..., None, :])
|
|
427
|
+
res_next = jnp.squeeze(res_next)
|
|
428
|
+
return res_next
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class ParallelTaylorDriver(DriverBase):
|
|
432
|
+
"""ESN driver with tanh nonlinearity, Taylor expanded.
|
|
433
|
+
|
|
434
|
+
This class defines a driver according to the Taylor series expansion of
|
|
435
|
+
ParallelESNDriver including the first ``n_terms`` terms with the leak rate
|
|
436
|
+
leak=0. Only discrete time dynamics are supported.
|
|
437
|
+
|
|
438
|
+
Attributes
|
|
439
|
+
----------
|
|
440
|
+
n_terms : int
|
|
441
|
+
Number of terms in Taylor series to include.
|
|
442
|
+
res_dim : int
|
|
443
|
+
Reservoir dimension.
|
|
444
|
+
wr : Array
|
|
445
|
+
Reservoir update matrix, (shape=(chunks, res_dim, res_dim,)).
|
|
446
|
+
spectral_radius : float
|
|
447
|
+
Spectral radius of wr.
|
|
448
|
+
density : float
|
|
449
|
+
Density of wr.
|
|
450
|
+
bias : float
|
|
451
|
+
Additive bias in tanh nonlinearity.
|
|
452
|
+
chunks: int
|
|
453
|
+
Number of parallel reservoirs.
|
|
454
|
+
dtype : Float
|
|
455
|
+
Dtype, default jnp.float64.
|
|
456
|
+
|
|
457
|
+
Methods
|
|
458
|
+
-------
|
|
459
|
+
advance(proj_vars, res_state)
|
|
460
|
+
Updated reservoir state.
|
|
461
|
+
advance_full(proj_vars, res_state, terms)
|
|
462
|
+
Updated reservoir state advanced according to full tanh nonlinearity.
|
|
463
|
+
__call__(proj_vars, res_state)
|
|
464
|
+
Batched or single update to reservoir state.
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
n_terms: int
|
|
468
|
+
res_dim: int
|
|
469
|
+
spectral_radius: float
|
|
470
|
+
density: float
|
|
471
|
+
bias: float
|
|
472
|
+
dtype: Float
|
|
473
|
+
wr: Array
|
|
474
|
+
chunks: int
|
|
475
|
+
|
|
476
|
+
def __init__(
|
|
477
|
+
self,
|
|
478
|
+
n_terms: int,
|
|
479
|
+
res_dim: int,
|
|
480
|
+
spectral_radius: float = 0.8,
|
|
481
|
+
density: float = 0.02,
|
|
482
|
+
bias: float = 1.6,
|
|
483
|
+
dtype: Float = jnp.float64,
|
|
484
|
+
chunks: int = 1,
|
|
485
|
+
*,
|
|
486
|
+
seed: int,
|
|
487
|
+
use_sparse_eigs: bool = True,
|
|
488
|
+
eigenval_batch_size: int = None,
|
|
489
|
+
) -> None:
|
|
490
|
+
"""Initialize weight matrices.
|
|
491
|
+
|
|
492
|
+
Parameters
|
|
493
|
+
----------
|
|
494
|
+
n_terms : int
|
|
495
|
+
Number of terms to use in Taylor expansion.
|
|
496
|
+
res_dim : int
|
|
497
|
+
Reservoir dimension.
|
|
498
|
+
spectral_radius : float
|
|
499
|
+
Spectral radius of wr.
|
|
500
|
+
density : float
|
|
501
|
+
Density of wr.
|
|
502
|
+
bias : float
|
|
503
|
+
Additive bias in tanh nonlinearity.
|
|
504
|
+
chunks: int
|
|
505
|
+
Number of parallel reservoirs.
|
|
506
|
+
dtype : Float
|
|
507
|
+
Dtype, default jnp.float64.
|
|
508
|
+
seed : int
|
|
509
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
510
|
+
use_sparse_eigs : bool
|
|
511
|
+
Whether to use sparse eigensolver for setting the spectral radius of wr.
|
|
512
|
+
Default is True, which is recommended to save memory and compute time. If
|
|
513
|
+
False, will use dense eigensolver which may be more accurate.
|
|
514
|
+
eigenval_batch_size : int
|
|
515
|
+
Size of batches when batch_eigenvals. Default is None, which means no
|
|
516
|
+
batch eigenvalue computation.
|
|
517
|
+
"""
|
|
518
|
+
super().__init__(res_dim=res_dim, dtype=dtype)
|
|
519
|
+
self.n_terms = n_terms
|
|
520
|
+
self.res_dim = res_dim
|
|
521
|
+
self.spectral_radius = spectral_radius
|
|
522
|
+
self.density = density
|
|
523
|
+
self.bias = bias
|
|
524
|
+
self.dtype = dtype
|
|
525
|
+
key = jax.random.key(seed)
|
|
526
|
+
if spectral_radius <= 0:
|
|
527
|
+
raise ValueError("Spectral radius must be positive.")
|
|
528
|
+
if density < 0 or density > 1:
|
|
529
|
+
raise ValueError("Density must satisfy 0 < density < 1.")
|
|
530
|
+
if n_terms > 5:
|
|
531
|
+
raise ValueError("Taylor expansion is only supported up to 5th order.")
|
|
532
|
+
subkey, wr_key = jax.random.split(key)
|
|
533
|
+
|
|
534
|
+
# check res_dim size for eigensolver choice
|
|
535
|
+
if res_dim < 100 and use_sparse_eigs:
|
|
536
|
+
use_sparse_eigs = False
|
|
537
|
+
warnings.warn(
|
|
538
|
+
"Reservoir dimension is less than 100, using dense "
|
|
539
|
+
"eigensolver for spectral radius.",
|
|
540
|
+
stacklevel=2,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# generate all wr matricies
|
|
544
|
+
sp_mat = sparse.random_bcoo(
|
|
545
|
+
key=wr_key,
|
|
546
|
+
shape=(chunks, res_dim, res_dim),
|
|
547
|
+
n_batch=1,
|
|
548
|
+
nse=density,
|
|
549
|
+
dtype=dtype,
|
|
550
|
+
generator=jax.random.normal,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
self.wr = _spec_rad_normalization(
|
|
554
|
+
sp_mat,
|
|
555
|
+
spectral_radius=spectral_radius,
|
|
556
|
+
eigenval_batch_size=eigenval_batch_size,
|
|
557
|
+
use_sparse_eigs=use_sparse_eigs,
|
|
558
|
+
chunks=chunks,
|
|
559
|
+
)
|
|
560
|
+
self.chunks = chunks
|
|
561
|
+
self.dtype = dtype
|
|
562
|
+
|
|
563
|
+
@eqx.filter_jit
|
|
564
|
+
def advance(self, proj_vars: Array, res_state: Array) -> Array:
|
|
565
|
+
"""Advance the reservoir state.
|
|
566
|
+
|
|
567
|
+
Parameters
|
|
568
|
+
----------
|
|
569
|
+
proj_vars : Array
|
|
570
|
+
Reservoir projected inputs, (shape=(chunks, res_dim,)).
|
|
571
|
+
res_state : Array
|
|
572
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
573
|
+
|
|
574
|
+
Returns
|
|
575
|
+
-------
|
|
576
|
+
res_next : Array
|
|
577
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
578
|
+
"""
|
|
579
|
+
if proj_vars.shape != (self.chunks, self.res_dim):
|
|
580
|
+
raise ValueError(f"Incorrect proj_var dimension, got {proj_vars.shape}")
|
|
581
|
+
|
|
582
|
+
t = jnp.tanh(self.bias)
|
|
583
|
+
s = 1 - t**2
|
|
584
|
+
deltaz = sparse.sparsify(jax.vmap(jnp.matmul))(self.wr, res_state) + proj_vars
|
|
585
|
+
const = t * jnp.ones((self.chunks, self.res_dim), dtype=self.dtype)
|
|
586
|
+
linear_term = s * deltaz
|
|
587
|
+
quadratic_term = (t**3 - t) * deltaz**2
|
|
588
|
+
cubic_term = (-(t**4) + (4 / 3) * t**2 - (1 / 3)) * deltaz**3
|
|
589
|
+
quartic_term = (t / 3) * (3 * t**4 - 5 * t**2 + 2) * deltaz**4
|
|
590
|
+
quintic_term = (-(t**6) + 2 * t**4 - (17 / 15) * t**2 + (2 / 15)) * deltaz**5
|
|
591
|
+
stacked = jnp.stack(
|
|
592
|
+
[
|
|
593
|
+
const,
|
|
594
|
+
linear_term,
|
|
595
|
+
quadratic_term,
|
|
596
|
+
cubic_term,
|
|
597
|
+
quartic_term,
|
|
598
|
+
quintic_term,
|
|
599
|
+
],
|
|
600
|
+
axis=0,
|
|
601
|
+
)
|
|
602
|
+
return jnp.sum(stacked[: self.n_terms + 1], axis=0)
|
|
603
|
+
|
|
604
|
+
@eqx.filter_jit
|
|
605
|
+
def advance_full(self, proj_vars: Array, res_state: Array) -> Array:
|
|
606
|
+
"""Advance the reservoir state according to full tanh dynamics.
|
|
607
|
+
|
|
608
|
+
Parameters
|
|
609
|
+
----------
|
|
610
|
+
proj_vars : Array
|
|
611
|
+
Reservoir projected inputs, (shape=(chunks, res_dim,)).
|
|
612
|
+
res_state : Array
|
|
613
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
res_next : Array
|
|
618
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
619
|
+
"""
|
|
620
|
+
if proj_vars.shape != (self.chunks, self.res_dim):
|
|
621
|
+
raise ValueError(f"Incorrect proj_var dimension, got {proj_vars.shape}")
|
|
622
|
+
|
|
623
|
+
return _sparse_ops(
|
|
624
|
+
self.wr, res_state, proj_vars, self.bias * jnp.ones_like(proj_vars)
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class TaylorDriver(ParallelTaylorDriver):
|
|
629
|
+
"""ESN driver with tanh nonlinearity, Taylor expanded.
|
|
630
|
+
|
|
631
|
+
This class defines a driver according to the Taylor series expansion of
|
|
632
|
+
ParallelESNDriver including the first ``n_terms`` terms with the leak rate
|
|
633
|
+
leak=0. Only discrete time dynamics are supported.
|
|
634
|
+
|
|
635
|
+
Attributes
|
|
636
|
+
----------
|
|
637
|
+
n_terms : int
|
|
638
|
+
Number of terms in Taylor series to include.
|
|
639
|
+
res_dim : int
|
|
640
|
+
Reservoir dimension.
|
|
641
|
+
wr : Array
|
|
642
|
+
Reservoir update matrix, (shape=(chunks, res_dim, res_dim,)).
|
|
643
|
+
spectral_radius : float
|
|
644
|
+
Spectral radius of wr.
|
|
645
|
+
density : float
|
|
646
|
+
Density of wr.
|
|
647
|
+
bias : float
|
|
648
|
+
Additive bias in tanh nonlinearity.
|
|
649
|
+
dtype : Float
|
|
650
|
+
Dtype, default jnp.float64.
|
|
651
|
+
|
|
652
|
+
Methods
|
|
653
|
+
-------
|
|
654
|
+
advance(proj_vars, res_state)
|
|
655
|
+
Updated reservoir state.
|
|
656
|
+
__call__(proj_vars, res_state)
|
|
657
|
+
Batched or single update to reservoir state.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
def __init__(
|
|
661
|
+
self,
|
|
662
|
+
n_terms: int,
|
|
663
|
+
res_dim: int,
|
|
664
|
+
spectral_radius: float = 0.8,
|
|
665
|
+
density: float = 0.02,
|
|
666
|
+
bias: float = 1.6,
|
|
667
|
+
dtype: Float = jnp.float64,
|
|
668
|
+
*,
|
|
669
|
+
seed: int,
|
|
670
|
+
use_sparse_eigs: bool = True,
|
|
671
|
+
eigenval_batch_size: int = None,
|
|
672
|
+
) -> None:
|
|
673
|
+
"""Initialize weight matrices.
|
|
674
|
+
|
|
675
|
+
Parameters
|
|
676
|
+
----------
|
|
677
|
+
n_terms : int
|
|
678
|
+
Number of terms to use in Taylor expansion.
|
|
679
|
+
res_dim : int
|
|
680
|
+
Reservoir dimension.
|
|
681
|
+
spectral_radius : float
|
|
682
|
+
Spectral radius of wr.
|
|
683
|
+
density : float
|
|
684
|
+
Density of wr.
|
|
685
|
+
bias : float
|
|
686
|
+
Additive bias in tanh nonlinearity.
|
|
687
|
+
dtype : Float
|
|
688
|
+
Dtype, default jnp.float64.
|
|
689
|
+
seed : int
|
|
690
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
691
|
+
use_sparse_eigs : bool
|
|
692
|
+
Whether to use sparse eigensolver for setting the spectral radius of wr.
|
|
693
|
+
Default is True, which is recommended to save memory and compute time. If
|
|
694
|
+
False, will use dense eigensolver which may be more accurate.
|
|
695
|
+
eigenval_batch_size : int
|
|
696
|
+
Size of batches when batch_eigenvals. Default is None, which means no
|
|
697
|
+
batch eigenvalue computation.
|
|
698
|
+
"""
|
|
699
|
+
super().__init__(
|
|
700
|
+
n_terms=n_terms,
|
|
701
|
+
res_dim=res_dim,
|
|
702
|
+
spectral_radius=spectral_radius,
|
|
703
|
+
density=density,
|
|
704
|
+
bias=bias,
|
|
705
|
+
dtype=dtype,
|
|
706
|
+
seed=seed,
|
|
707
|
+
use_sparse_eigs=use_sparse_eigs,
|
|
708
|
+
eigenval_batch_size=eigenval_batch_size,
|
|
709
|
+
chunks=1,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
@eqx.filter_jit
|
|
713
|
+
def advance(self, proj_vars: Array, res_state: Array) -> Array:
|
|
714
|
+
"""Advance the reservoir state.
|
|
715
|
+
|
|
716
|
+
Parameters
|
|
717
|
+
----------
|
|
718
|
+
proj_vars : Array
|
|
719
|
+
Reservoir projected inputs, (shape=(res_dim,)).
|
|
720
|
+
res_state : Array
|
|
721
|
+
Reservoir state, (shape=(res_dim,)).
|
|
722
|
+
|
|
723
|
+
Returns
|
|
724
|
+
-------
|
|
725
|
+
res_next : Array
|
|
726
|
+
Reservoir state, (shape=(res_dim,)).
|
|
727
|
+
"""
|
|
728
|
+
res_next = super().advance(proj_vars.reshape(1, -1), res_state.reshape(1, -1))
|
|
729
|
+
res_next = jnp.squeeze(res_next)
|
|
730
|
+
return res_next
|
|
731
|
+
|
|
732
|
+
def __call__(self, proj_vars: Array, res_state: Array) -> Array:
|
|
733
|
+
"""Advance reservoir state.
|
|
734
|
+
|
|
735
|
+
Parameters
|
|
736
|
+
----------
|
|
737
|
+
proj_vars : Array
|
|
738
|
+
Reservoir projected inputs, (shape=(res_dim,) or
|
|
739
|
+
shape=(seq_len, res_dim)).
|
|
740
|
+
res_state : Array
|
|
741
|
+
Current reservoir state, (shape=(res_dim,) or
|
|
742
|
+
shape=(seq_len, res_dim)).
|
|
743
|
+
|
|
744
|
+
Returns
|
|
745
|
+
-------
|
|
746
|
+
Array
|
|
747
|
+
Sequence of reservoir states, (shape=(res_dim,) or
|
|
748
|
+
shape=(seq_len, res_dim)).
|
|
749
|
+
"""
|
|
750
|
+
res_next = super().__call__(proj_vars[..., None, :], res_state[..., None, :])
|
|
751
|
+
res_next = jnp.squeeze(res_next)
|
|
752
|
+
return res_next
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
@sparse.sparsify
|
|
756
|
+
@jax.vmap
|
|
757
|
+
def _sparse_ops(wr: Array, res_state: Array, proj_vars: Array, bias: Array):
|
|
758
|
+
"""Dense operation to sparsify for advancing reservoir."""
|
|
759
|
+
return jnp.tanh(wr @ res_state + proj_vars + bias)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _spec_rad_normalization(
|
|
763
|
+
sp_mat: Array,
|
|
764
|
+
spectral_radius: float,
|
|
765
|
+
eigenval_batch_size: int | None = None,
|
|
766
|
+
use_sparse_eigs: bool = True,
|
|
767
|
+
chunks: int = 1,
|
|
768
|
+
):
|
|
769
|
+
"""Spectral radius normalization for jax sparse.bcoo matrices with n_batch=1."""
|
|
770
|
+
if eigenval_batch_size is not None:
|
|
771
|
+
batch_size = min(eigenval_batch_size, chunks)
|
|
772
|
+
eigs_list = []
|
|
773
|
+
|
|
774
|
+
for i in range(0, chunks, batch_size):
|
|
775
|
+
end_idx = min(i + batch_size, chunks)
|
|
776
|
+
batch_sp_mat = sp_mat[i:end_idx]
|
|
777
|
+
|
|
778
|
+
if use_sparse_eigs:
|
|
779
|
+
batch_eigs = jnp.abs(jax.vmap(max_eig_arnoldi)(batch_sp_mat))
|
|
780
|
+
else:
|
|
781
|
+
batch_dense_mat = sparse.bcoo_todense(batch_sp_mat)
|
|
782
|
+
batch_eigs = jnp.max(
|
|
783
|
+
jnp.abs(jnp.linalg.eigvals(batch_dense_mat)), axis=1
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
eigs_list.append(batch_eigs)
|
|
787
|
+
|
|
788
|
+
eigs = jnp.concatenate(eigs_list, axis=0)
|
|
789
|
+
else:
|
|
790
|
+
if use_sparse_eigs:
|
|
791
|
+
eigs = jnp.abs(jax.vmap(max_eig_arnoldi)(sp_mat))
|
|
792
|
+
else:
|
|
793
|
+
dense_mat = sparse.bcoo_todense(sp_mat)
|
|
794
|
+
eigs = jnp.max(jnp.abs(jnp.linalg.eigvals(dense_mat)), axis=1)
|
|
795
|
+
sp_mat = spectral_radius * (sp_mat / eigs[:, None, None])
|
|
796
|
+
return sp_mat
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
class GRUDriver(DriverBase):
|
|
800
|
+
"""Gated Recurrent Unit (GRU) based reservoir driver.
|
|
801
|
+
|
|
802
|
+
This driver uses an Equinox GRUCell as the reservoir dynamics.
|
|
803
|
+
|
|
804
|
+
Attributes
|
|
805
|
+
----------
|
|
806
|
+
res_dim : int
|
|
807
|
+
Reservoir dimension.
|
|
808
|
+
gru : eqx.Module
|
|
809
|
+
Equinox GRUCell module for reservoir updates.
|
|
810
|
+
dtype : Float
|
|
811
|
+
Dtype for model, jnp.float64 or jnp.float32.
|
|
812
|
+
|
|
813
|
+
Methods
|
|
814
|
+
-------
|
|
815
|
+
advance(res_state, in_state)
|
|
816
|
+
Advance reservoir state using GRU dynamics.
|
|
817
|
+
"""
|
|
818
|
+
|
|
819
|
+
gru: eqx.Module
|
|
820
|
+
chunks: int = 0
|
|
821
|
+
|
|
822
|
+
def __init__(self, res_dim, *, seed):
|
|
823
|
+
"""Initialize GRU-based reservoir driver.
|
|
824
|
+
|
|
825
|
+
Parameters
|
|
826
|
+
----------
|
|
827
|
+
res_dim : int
|
|
828
|
+
Reservoir dimension.
|
|
829
|
+
seed : int
|
|
830
|
+
Random seed for initializing GRU weights. Default is 0.
|
|
831
|
+
"""
|
|
832
|
+
super().__init__(res_dim=res_dim)
|
|
833
|
+
key = jax.random.key(seed)
|
|
834
|
+
self.gru = eqx.nn.GRUCell(res_dim, res_dim, key=key)
|
|
835
|
+
|
|
836
|
+
def advance(self, res_state, in_state):
|
|
837
|
+
"""Advance the reservoir state using GRU dynamics.
|
|
838
|
+
|
|
839
|
+
Parameters
|
|
840
|
+
----------
|
|
841
|
+
res_state : Array
|
|
842
|
+
Current reservoir state, (shape=(res_dim,)).
|
|
843
|
+
in_state : Array
|
|
844
|
+
Projected inputs to reservoir, (shape=(res_dim,)).
|
|
845
|
+
|
|
846
|
+
Returns
|
|
847
|
+
-------
|
|
848
|
+
Array
|
|
849
|
+
Updated reservoir state, (shape=(res_dim,)).
|
|
850
|
+
"""
|
|
851
|
+
return self.gru(in_state, res_state)
|