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/readouts.py
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
"""Define base class for readout layers and implement common architectures."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
import equinox as eqx
|
|
7
|
+
import jax.numpy as jnp
|
|
8
|
+
from jaxtyping import Array, Float
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReadoutBase(eqx.Module, ABC):
|
|
12
|
+
"""
|
|
13
|
+
Base class dictating API for all implemented readout layers.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
out_dim : int
|
|
18
|
+
Dimension of reservoir output.
|
|
19
|
+
res_dim : int
|
|
20
|
+
Reservoir dimension.
|
|
21
|
+
dtype : Float
|
|
22
|
+
Dtype of JAX arrays, jnp.float32 or jnp.float64.
|
|
23
|
+
|
|
24
|
+
Methods
|
|
25
|
+
-------
|
|
26
|
+
readout(res_state)
|
|
27
|
+
Map from reservoir state to output state.
|
|
28
|
+
batch_readout(res_state)
|
|
29
|
+
Map from reservoir states to output states.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
out_dim: int
|
|
33
|
+
res_dim: int
|
|
34
|
+
dtype: Float
|
|
35
|
+
|
|
36
|
+
def __init__(self, out_dim, res_dim, dtype=jnp.float64):
|
|
37
|
+
"""Ensure in dim, res dim, and dtype are correct type."""
|
|
38
|
+
self.res_dim = res_dim
|
|
39
|
+
self.out_dim = out_dim
|
|
40
|
+
self.dtype = dtype
|
|
41
|
+
if not isinstance(res_dim, int):
|
|
42
|
+
raise TypeError("Reservoir dimension res_dim must be an integer.")
|
|
43
|
+
if not isinstance(out_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 of jnp.float32.")
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def readout(
|
|
51
|
+
self,
|
|
52
|
+
res_state: Array,
|
|
53
|
+
) -> Array:
|
|
54
|
+
"""Readout from reservoir state.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
res_state : Array
|
|
59
|
+
Reservoir state.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
Array
|
|
64
|
+
Output from reservoir state.
|
|
65
|
+
"""
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def batch_readout(
|
|
69
|
+
self,
|
|
70
|
+
res_state: Array,
|
|
71
|
+
) -> Array:
|
|
72
|
+
"""Batch apply readout from reservoir states.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
res_state : Array
|
|
77
|
+
Reservoir state.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
Array
|
|
82
|
+
Output from reservoir states.
|
|
83
|
+
"""
|
|
84
|
+
return eqx.filter_vmap(self.readout)(res_state)
|
|
85
|
+
|
|
86
|
+
def __call__(
|
|
87
|
+
self,
|
|
88
|
+
res_state: Array,
|
|
89
|
+
) -> Array:
|
|
90
|
+
"""Call either readout or batch_readout depending on dimensions.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
res_state : Array
|
|
95
|
+
Reservoir state, (shape=(chunks, res_dim) or
|
|
96
|
+
shape=(seq_len, chunks, res_dim)).
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
Array
|
|
101
|
+
Output state, (out_dim,) or shape=(seq_len, out_dim)).
|
|
102
|
+
"""
|
|
103
|
+
if self.chunks > 0:
|
|
104
|
+
if len(res_state.shape) == 2:
|
|
105
|
+
to_ret = self.readout(res_state)
|
|
106
|
+
elif len(res_state.shape) == 3:
|
|
107
|
+
to_ret = self.batch_readout(res_state)
|
|
108
|
+
else:
|
|
109
|
+
if len(res_state.shape) == 1:
|
|
110
|
+
to_ret = self.readout(res_state)
|
|
111
|
+
elif len(res_state.shape) == 2:
|
|
112
|
+
to_ret = self.batch_readout(res_state)
|
|
113
|
+
return to_ret
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ParallelLinearReadout(ReadoutBase):
|
|
117
|
+
"""Linear readout layer.
|
|
118
|
+
|
|
119
|
+
Attributes
|
|
120
|
+
----------
|
|
121
|
+
out_dim : int
|
|
122
|
+
Dimension of reservoir output.
|
|
123
|
+
res_dim : int
|
|
124
|
+
Reservoir dimension.
|
|
125
|
+
chunks : int
|
|
126
|
+
Number of parallel reservoirs.
|
|
127
|
+
wout : Array
|
|
128
|
+
Output matrix.
|
|
129
|
+
dtype : Float
|
|
130
|
+
Dtype, default jnp.float64.
|
|
131
|
+
|
|
132
|
+
Methods
|
|
133
|
+
-------
|
|
134
|
+
readout(res_state)
|
|
135
|
+
Map from reservoir state to output state.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
out_dim: int
|
|
139
|
+
res_dim: int
|
|
140
|
+
wout: Array
|
|
141
|
+
chunks: int
|
|
142
|
+
dtype: Float
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
out_dim: int,
|
|
147
|
+
res_dim: int,
|
|
148
|
+
chunks: int = 1,
|
|
149
|
+
dtype: Float = jnp.float64,
|
|
150
|
+
*,
|
|
151
|
+
seed: int = 0,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Initialize readout layer to zeros.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
out_dim : int
|
|
158
|
+
Dimension of reservoir output.
|
|
159
|
+
res_dim : int
|
|
160
|
+
Reservoir dimension.
|
|
161
|
+
chunks : int
|
|
162
|
+
Number of parallel resrevoirs.
|
|
163
|
+
dtype : Float
|
|
164
|
+
Dtype, default jnp.float64.
|
|
165
|
+
seed : int
|
|
166
|
+
Not used for ParallelLinearReadout, here to maintain consistent
|
|
167
|
+
interface.
|
|
168
|
+
"""
|
|
169
|
+
super().__init__(out_dim=out_dim, res_dim=res_dim, dtype=dtype)
|
|
170
|
+
self.out_dim = out_dim
|
|
171
|
+
self.res_dim = res_dim
|
|
172
|
+
self.wout = jnp.zeros((chunks, int(out_dim / chunks), res_dim), dtype=dtype)
|
|
173
|
+
self.dtype = dtype
|
|
174
|
+
self.chunks = chunks
|
|
175
|
+
|
|
176
|
+
@eqx.filter_jit
|
|
177
|
+
def readout(self, res_state: Array) -> Array:
|
|
178
|
+
"""Readout from reservoir state.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
res_state : Array
|
|
183
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
Array
|
|
188
|
+
Output from reservoir, (shape=(out_dim,)).
|
|
189
|
+
"""
|
|
190
|
+
if res_state.shape[1] != self.res_dim:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
"Incorrect reservoir dimension for instantiated output map."
|
|
193
|
+
)
|
|
194
|
+
return jnp.ravel(eqx.filter_vmap(jnp.matmul)(self.wout, res_state))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class LinearReadout(ParallelLinearReadout):
|
|
198
|
+
"""Linear readout layer.
|
|
199
|
+
|
|
200
|
+
Attributes
|
|
201
|
+
----------
|
|
202
|
+
out_dim : int
|
|
203
|
+
Dimension of reservoir output.
|
|
204
|
+
res_dim : int
|
|
205
|
+
Reservoir dimension.
|
|
206
|
+
wout : Array
|
|
207
|
+
Output matrix.
|
|
208
|
+
dtype : Float
|
|
209
|
+
Dtype, default jnp.float64.
|
|
210
|
+
|
|
211
|
+
Methods
|
|
212
|
+
-------
|
|
213
|
+
readout(res_state)
|
|
214
|
+
Map from reservoir state to output state.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
def __init__(
|
|
218
|
+
self,
|
|
219
|
+
out_dim: int,
|
|
220
|
+
res_dim: int,
|
|
221
|
+
dtype: Float = jnp.float64,
|
|
222
|
+
*,
|
|
223
|
+
seed: int = 0,
|
|
224
|
+
) -> None:
|
|
225
|
+
"""Initialize readout layer to zeros.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
out_dim : int
|
|
230
|
+
Dimension of reservoir output.
|
|
231
|
+
res_dim : int
|
|
232
|
+
Reservoir dimension.
|
|
233
|
+
chunks : int
|
|
234
|
+
Number of parallel resrevoirs.
|
|
235
|
+
dtype : Float
|
|
236
|
+
Dtype, default jnp.float64.
|
|
237
|
+
seed : int
|
|
238
|
+
Not used for ParallelLinearReadout, here to maintain consistent
|
|
239
|
+
interface.
|
|
240
|
+
"""
|
|
241
|
+
super().__init__(
|
|
242
|
+
out_dim=out_dim,
|
|
243
|
+
res_dim=res_dim,
|
|
244
|
+
chunks=1,
|
|
245
|
+
dtype=dtype,
|
|
246
|
+
seed=seed,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
@eqx.filter_jit
|
|
250
|
+
def readout(self, res_state: Array) -> Array:
|
|
251
|
+
"""Readout from reservoir state.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
res_state : Array
|
|
256
|
+
Reservoir state, (shape=(res_dim,)).
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
Array
|
|
261
|
+
Output from reservoir, (shape=(out_dim,)).
|
|
262
|
+
"""
|
|
263
|
+
return super().readout(res_state.reshape(1, -1)).reshape(-1)
|
|
264
|
+
|
|
265
|
+
def __call__(self, res_state: Array) -> Array:
|
|
266
|
+
"""Call either readout or batch_readout depending on dimensions.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
res_state : Array
|
|
271
|
+
Reservoir state, (shape=(res_dim) or
|
|
272
|
+
shape=(seq_len, res_dim)).
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
Array
|
|
277
|
+
Output state, (out_dim,) or shape=(seq_len, out_dim)).
|
|
278
|
+
"""
|
|
279
|
+
return jnp.squeeze(super().__call__(res_state[..., None, :]))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class ParallelNonlinearReadout(ReadoutBase):
|
|
283
|
+
"""Readout layer with user specified nonlinearities.
|
|
284
|
+
|
|
285
|
+
Attributes
|
|
286
|
+
----------
|
|
287
|
+
out_dim : int
|
|
288
|
+
Dimension of reservoir output.
|
|
289
|
+
res_dim : int
|
|
290
|
+
Reservoir dimension.
|
|
291
|
+
chunks : int
|
|
292
|
+
Number of parallel reservoirs.
|
|
293
|
+
wout : Array
|
|
294
|
+
Output matrix.
|
|
295
|
+
nonlin_list : list
|
|
296
|
+
List containing user specified nonlinearities.
|
|
297
|
+
dtype : Float
|
|
298
|
+
Dtype, default jnp.float64.
|
|
299
|
+
|
|
300
|
+
Methods
|
|
301
|
+
-------
|
|
302
|
+
nonlinear_transform(res_state)
|
|
303
|
+
Nonlinear transform that acts entrywise on reservoir state.
|
|
304
|
+
readout(res_state)
|
|
305
|
+
Map from reservoir state to output state.
|
|
306
|
+
__call__(res_state)
|
|
307
|
+
Map from reservoir state to output state, handles batch and single outputs.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
out_dim: int
|
|
311
|
+
res_dim: int
|
|
312
|
+
wout: Array
|
|
313
|
+
chunks: int
|
|
314
|
+
nonlin_list: list
|
|
315
|
+
dtype: Float
|
|
316
|
+
|
|
317
|
+
def __init__(
|
|
318
|
+
self,
|
|
319
|
+
out_dim: int,
|
|
320
|
+
res_dim: int,
|
|
321
|
+
nonlin_list: list[Callable],
|
|
322
|
+
chunks: int = 1,
|
|
323
|
+
dtype: Float = jnp.float64,
|
|
324
|
+
*,
|
|
325
|
+
seed: int = 0,
|
|
326
|
+
) -> None:
|
|
327
|
+
"""Initialize readout layer to zeros.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
out_dim : int
|
|
332
|
+
Dimension of reservoir output.
|
|
333
|
+
res_dim : int
|
|
334
|
+
Reservoir dimension.
|
|
335
|
+
nonlin_list : list[Callable]
|
|
336
|
+
List containing user specified entrywise nonlinearities. Each entry should
|
|
337
|
+
be a function mapping a scalar value to another scalar value, e.g.
|
|
338
|
+
lambda x : x ** 2 or lambda x : jnp.sin(x).
|
|
339
|
+
chunks : int
|
|
340
|
+
Number of parallel reservoirs.
|
|
341
|
+
dtype : Float
|
|
342
|
+
Dtype, default jnp.float64.
|
|
343
|
+
seed : int
|
|
344
|
+
Not used for ParallelNonlinearReadout, here to maintain consistent
|
|
345
|
+
interface.
|
|
346
|
+
"""
|
|
347
|
+
super().__init__(out_dim=out_dim, res_dim=res_dim, dtype=dtype)
|
|
348
|
+
self.out_dim = out_dim
|
|
349
|
+
self.res_dim = res_dim
|
|
350
|
+
self.wout = jnp.zeros((chunks, int(out_dim / chunks), res_dim), dtype=dtype)
|
|
351
|
+
self.dtype = dtype
|
|
352
|
+
self.chunks = chunks
|
|
353
|
+
self.nonlin_list = nonlin_list
|
|
354
|
+
|
|
355
|
+
def nonlinear_transform(self, res_state: Array) -> Array:
|
|
356
|
+
"""Perform nonlinear transformation on reservoir state.
|
|
357
|
+
|
|
358
|
+
Let tot_list be the list consisting of nonlin_list prepended by the identity
|
|
359
|
+
mapping. Let n be the length of tot_list. Then, nonlinear_transform acts such
|
|
360
|
+
that for all 0 <= k < chunks and 0 <= j < j * n:
|
|
361
|
+
res_state[k, j * n] <- res_state[k, j*n]
|
|
362
|
+
res_state[k, j * n + 1] <- f_0(res_state[k, j * n + 1])
|
|
363
|
+
...
|
|
364
|
+
res_state[k, j * n + n - 1] <- f_{n-1}(res_state[k, j * n + n - 1])
|
|
365
|
+
where f_i is the i-th entry of nonlin_list.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
res_state : Array
|
|
370
|
+
Reservoir state, (shape=(..., res_dim,)).
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
Array
|
|
375
|
+
Transformed reservoir state.
|
|
376
|
+
"""
|
|
377
|
+
num_nonlins = len(self.nonlin_list)
|
|
378
|
+
transformed_res_state = res_state
|
|
379
|
+
for idx in range(num_nonlins):
|
|
380
|
+
transformed_res_state = transformed_res_state.at[
|
|
381
|
+
..., idx + 1 :: num_nonlins + 1
|
|
382
|
+
].set(
|
|
383
|
+
self.nonlin_list[idx](
|
|
384
|
+
transformed_res_state[..., idx + 1 :: num_nonlins + 1]
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
return transformed_res_state
|
|
388
|
+
|
|
389
|
+
@eqx.filter_jit
|
|
390
|
+
def readout(self, res_state: Array) -> Array:
|
|
391
|
+
"""Readout from reservoir state.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
res_state : Array
|
|
396
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
Array
|
|
401
|
+
Output from reservoir, (shape=(out_dim,)).
|
|
402
|
+
"""
|
|
403
|
+
if res_state.shape[1] != self.res_dim:
|
|
404
|
+
raise ValueError(
|
|
405
|
+
"Incorrect reservoir dimension for instantiated output map."
|
|
406
|
+
)
|
|
407
|
+
transformed_res_state = self.nonlinear_transform(res_state)
|
|
408
|
+
return jnp.ravel(eqx.filter_vmap(jnp.matmul)(self.wout, transformed_res_state))
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class NonlinearReadout(ParallelNonlinearReadout):
|
|
412
|
+
"""Readout layer with user specified nonlinearities.
|
|
413
|
+
|
|
414
|
+
Attributes
|
|
415
|
+
----------
|
|
416
|
+
out_dim : int
|
|
417
|
+
Dimension of reservoir output.
|
|
418
|
+
res_dim : int
|
|
419
|
+
Reservoir dimension.
|
|
420
|
+
chunks : int
|
|
421
|
+
Number of parallel reservoirs.
|
|
422
|
+
wout : Array
|
|
423
|
+
Output matrix.
|
|
424
|
+
nonlin_list : list
|
|
425
|
+
List containing user specified nonlinearities.
|
|
426
|
+
dtype : Float
|
|
427
|
+
Dtype, default jnp.float64.
|
|
428
|
+
|
|
429
|
+
Methods
|
|
430
|
+
-------
|
|
431
|
+
nonlinear_transform(res_state)
|
|
432
|
+
Nonlinear transform that acts entrywise on reservoir state.
|
|
433
|
+
readout(res_state)
|
|
434
|
+
Map from reservoir state to output state.
|
|
435
|
+
__call__(res_state)
|
|
436
|
+
Map from reservoir state to output state, handles batch and single outputs.
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
def __init__(
|
|
440
|
+
self,
|
|
441
|
+
out_dim: int,
|
|
442
|
+
res_dim: int,
|
|
443
|
+
nonlin_list: list[Callable],
|
|
444
|
+
dtype: Float = jnp.float64,
|
|
445
|
+
*,
|
|
446
|
+
seed: int = 0,
|
|
447
|
+
) -> None:
|
|
448
|
+
"""Initialize readout layer to zeros.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
out_dim : int
|
|
453
|
+
Dimension of reservoir output.
|
|
454
|
+
res_dim : int
|
|
455
|
+
Reservoir dimension.
|
|
456
|
+
nonlin_list : list[Callable]
|
|
457
|
+
List containing user specified entrywise nonlinearities. Each entry should
|
|
458
|
+
be a function mapping a scalar value to another scalar value, e.g.
|
|
459
|
+
lambda x : x ** 2 or lambda x : jnp.sin(x).
|
|
460
|
+
dtype : Float
|
|
461
|
+
Dtype, default jnp.float64.
|
|
462
|
+
seed : int
|
|
463
|
+
Not used for ParallelNonlinearReadout, here to maintain consistent
|
|
464
|
+
interface.
|
|
465
|
+
"""
|
|
466
|
+
super().__init__(
|
|
467
|
+
out_dim=out_dim,
|
|
468
|
+
res_dim=res_dim,
|
|
469
|
+
nonlin_list=nonlin_list,
|
|
470
|
+
chunks=1,
|
|
471
|
+
dtype=dtype,
|
|
472
|
+
seed=seed,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
def nonlinear_transform(self, res_state: Array) -> Array:
|
|
476
|
+
"""Perform nonlinear transformation on reservoir state.
|
|
477
|
+
|
|
478
|
+
Let tot_list be the list consisting of nonlin_list prepended by the identity
|
|
479
|
+
mapping. Let n be the length of tot_list. Then, nonlinear_transform acts such
|
|
480
|
+
that for all 0 <= k < chunks and 0 <= j < j * n:
|
|
481
|
+
res_state[k, j * n] <- res_state[k, j*n]
|
|
482
|
+
res_state[k, j * n + 1] <- f_0(res_state[k, j * n + 1])
|
|
483
|
+
...
|
|
484
|
+
res_state[k, j * n + n - 1] <- f_{n-1}(res_state[k, j * n + n - 1])
|
|
485
|
+
where f_i is the i-th entry of nonlin_list.
|
|
486
|
+
|
|
487
|
+
Parameters
|
|
488
|
+
----------
|
|
489
|
+
res_state : Array
|
|
490
|
+
Reservoir state, (shape=(..., res_dim,)).
|
|
491
|
+
|
|
492
|
+
Returns
|
|
493
|
+
-------
|
|
494
|
+
Array
|
|
495
|
+
Transformed reservoir state.
|
|
496
|
+
"""
|
|
497
|
+
num_nonlins = len(self.nonlin_list)
|
|
498
|
+
transformed_res_state = res_state
|
|
499
|
+
for idx in range(num_nonlins):
|
|
500
|
+
transformed_res_state = transformed_res_state.at[
|
|
501
|
+
..., idx + 1 :: num_nonlins + 1
|
|
502
|
+
].set(
|
|
503
|
+
self.nonlin_list[idx](
|
|
504
|
+
transformed_res_state[..., idx + 1 :: num_nonlins + 1]
|
|
505
|
+
)
|
|
506
|
+
)
|
|
507
|
+
return transformed_res_state
|
|
508
|
+
|
|
509
|
+
@eqx.filter_jit
|
|
510
|
+
def readout(self, res_state: Array) -> Array:
|
|
511
|
+
"""Readout from reservoir state.
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
res_state : Array
|
|
516
|
+
Reservoir state, (shape=(res_dim,)).
|
|
517
|
+
|
|
518
|
+
Returns
|
|
519
|
+
-------
|
|
520
|
+
Array
|
|
521
|
+
Output from reservoir, (shape=(out_dim,)).
|
|
522
|
+
"""
|
|
523
|
+
return super().readout(res_state.reshape(1, -1)).reshape(-1)
|
|
524
|
+
|
|
525
|
+
def __call__(self, res_state: Array) -> Array:
|
|
526
|
+
"""Call either readout or batch_readout depending on dimensions.
|
|
527
|
+
|
|
528
|
+
Parameters
|
|
529
|
+
----------
|
|
530
|
+
res_state : Array
|
|
531
|
+
Reservoir state, (shape=(res_dim) or
|
|
532
|
+
shape=(seq_len, res_dim)).
|
|
533
|
+
|
|
534
|
+
Returns
|
|
535
|
+
-------
|
|
536
|
+
Array
|
|
537
|
+
Output state, (out_dim,) or shape=(seq_len, out_dim)).
|
|
538
|
+
"""
|
|
539
|
+
return jnp.squeeze(super().__call__(res_state[..., None, :]))
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
class ParallelQuadraticReadout(ParallelNonlinearReadout):
|
|
543
|
+
"""Quadratic readout layer.
|
|
544
|
+
|
|
545
|
+
Attributes
|
|
546
|
+
----------
|
|
547
|
+
out_dim : int
|
|
548
|
+
Dimension of reservoir output.
|
|
549
|
+
res_dim : int
|
|
550
|
+
Reservoir dimension.
|
|
551
|
+
chunks : int
|
|
552
|
+
Number of parallel reservoirs.
|
|
553
|
+
wout : Array
|
|
554
|
+
Output matrix.
|
|
555
|
+
dtype : Float
|
|
556
|
+
Dtype, default jnp.float64.
|
|
557
|
+
|
|
558
|
+
Methods
|
|
559
|
+
-------
|
|
560
|
+
nonlinear_transform(res_state)
|
|
561
|
+
Quadratic transform that acts entrywise on reservoir state.
|
|
562
|
+
readout(res_state)
|
|
563
|
+
Map from reservoir state to output state with quadratic nonlinearity.
|
|
564
|
+
__call__(res_state)
|
|
565
|
+
Map from reservoir state to output state with quadratic nonlinearity,
|
|
566
|
+
handles batch and single outputs.
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
out_dim: int
|
|
570
|
+
res_dim: int
|
|
571
|
+
wout: Array
|
|
572
|
+
chunks: int
|
|
573
|
+
dtype: Float
|
|
574
|
+
|
|
575
|
+
def __init__(
|
|
576
|
+
self,
|
|
577
|
+
out_dim: int,
|
|
578
|
+
res_dim: int,
|
|
579
|
+
chunks: int = 1,
|
|
580
|
+
dtype: Float = jnp.float64,
|
|
581
|
+
*,
|
|
582
|
+
seed: int = 0,
|
|
583
|
+
) -> None:
|
|
584
|
+
"""Initialize readout layer to zeros.
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
out_dim : int
|
|
589
|
+
Dimension of reservoir output.
|
|
590
|
+
res_dim : int
|
|
591
|
+
Reservoir dimension.
|
|
592
|
+
chunks : int
|
|
593
|
+
Number of parallel resrevoirs.
|
|
594
|
+
dtype : Float
|
|
595
|
+
Dtype, default jnp.float64.
|
|
596
|
+
seed : int
|
|
597
|
+
Not used for ParallelQuadraticReadout, here to maintain consistent
|
|
598
|
+
interface.
|
|
599
|
+
"""
|
|
600
|
+
super().__init__(
|
|
601
|
+
out_dim=out_dim,
|
|
602
|
+
res_dim=res_dim,
|
|
603
|
+
dtype=dtype,
|
|
604
|
+
nonlin_list=[lambda x: x**2],
|
|
605
|
+
chunks=chunks,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
class QuadraticReadout(NonlinearReadout):
|
|
610
|
+
"""Quadratic readout layer.
|
|
611
|
+
|
|
612
|
+
Attributes
|
|
613
|
+
----------
|
|
614
|
+
out_dim : int
|
|
615
|
+
Dimension of reservoir output.
|
|
616
|
+
res_dim : int
|
|
617
|
+
Reservoir dimension.
|
|
618
|
+
wout : Array
|
|
619
|
+
Output matrix.
|
|
620
|
+
dtype : Float
|
|
621
|
+
Dtype, default jnp.float64.
|
|
622
|
+
|
|
623
|
+
Methods
|
|
624
|
+
-------
|
|
625
|
+
nonlinear_transform(res_state)
|
|
626
|
+
Quadratic transform that acts entrywise on reservoir state.
|
|
627
|
+
readout(res_state)
|
|
628
|
+
Map from reservoir state to output state with quadratic nonlinearity.
|
|
629
|
+
__call__(res_state)
|
|
630
|
+
Map from reservoir state to output state with quadratic nonlinearity,
|
|
631
|
+
handles batch and single outputs.
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
def __init__(
|
|
635
|
+
self,
|
|
636
|
+
out_dim: int,
|
|
637
|
+
res_dim: int,
|
|
638
|
+
dtype: Float = jnp.float64,
|
|
639
|
+
*,
|
|
640
|
+
seed: int = 0,
|
|
641
|
+
) -> None:
|
|
642
|
+
"""Initialize readout layer to zeros.
|
|
643
|
+
|
|
644
|
+
Parameters
|
|
645
|
+
----------
|
|
646
|
+
out_dim : int
|
|
647
|
+
Dimension of reservoir output.
|
|
648
|
+
res_dim : int
|
|
649
|
+
Reservoir dimension.
|
|
650
|
+
dtype : Float
|
|
651
|
+
Dtype, default jnp.float64.
|
|
652
|
+
seed : int
|
|
653
|
+
Not used for ParallelQuadraticReadout, here to maintain consistent
|
|
654
|
+
interface.
|
|
655
|
+
"""
|
|
656
|
+
super().__init__(
|
|
657
|
+
out_dim=out_dim,
|
|
658
|
+
res_dim=res_dim,
|
|
659
|
+
dtype=dtype,
|
|
660
|
+
nonlin_list=[lambda x: x**2],
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
class EnsembleLinearReadout(ReadoutBase):
|
|
665
|
+
"""Esembled linear readout layer.
|
|
666
|
+
|
|
667
|
+
Attributes
|
|
668
|
+
----------
|
|
669
|
+
out_dim : int
|
|
670
|
+
Dimension of reservoir output.
|
|
671
|
+
res_dim : int
|
|
672
|
+
Reservoir dimension.
|
|
673
|
+
chunks : int
|
|
674
|
+
Number of parallel reservoirs.
|
|
675
|
+
wout : Array
|
|
676
|
+
Output matrix.
|
|
677
|
+
dtype : Float
|
|
678
|
+
Dtype, default jnp.float64.
|
|
679
|
+
|
|
680
|
+
Methods
|
|
681
|
+
-------
|
|
682
|
+
readout(res_state)
|
|
683
|
+
Map from reservoir state to output state.
|
|
684
|
+
"""
|
|
685
|
+
|
|
686
|
+
out_dim: int
|
|
687
|
+
res_dim: int
|
|
688
|
+
wout: Array
|
|
689
|
+
chunks: int
|
|
690
|
+
dtype: Float
|
|
691
|
+
|
|
692
|
+
def __init__(
|
|
693
|
+
self,
|
|
694
|
+
out_dim: int,
|
|
695
|
+
res_dim: int,
|
|
696
|
+
chunks: int = 5,
|
|
697
|
+
dtype: Float = jnp.float64,
|
|
698
|
+
*,
|
|
699
|
+
seed: int = 0,
|
|
700
|
+
) -> None:
|
|
701
|
+
"""Initialize readout layer to zeros.
|
|
702
|
+
|
|
703
|
+
Parameters
|
|
704
|
+
----------
|
|
705
|
+
out_dim : int
|
|
706
|
+
Dimension of reservoir output.
|
|
707
|
+
res_dim : int
|
|
708
|
+
Reservoir dimension.
|
|
709
|
+
chunks : int
|
|
710
|
+
Number of parallel resrevoirs.
|
|
711
|
+
dtype : Float
|
|
712
|
+
Dtype, default jnp.float64.
|
|
713
|
+
seed : int
|
|
714
|
+
Not used for ParallelLinearReadout, here to maintain consistent
|
|
715
|
+
interface.
|
|
716
|
+
"""
|
|
717
|
+
super().__init__(out_dim=out_dim, res_dim=res_dim, dtype=dtype)
|
|
718
|
+
self.out_dim = out_dim
|
|
719
|
+
self.res_dim = res_dim
|
|
720
|
+
self.wout = jnp.zeros((chunks, out_dim, res_dim), dtype=dtype)
|
|
721
|
+
self.dtype = dtype
|
|
722
|
+
self.chunks = chunks
|
|
723
|
+
|
|
724
|
+
@eqx.filter_jit
|
|
725
|
+
def readout(self, res_state: Array) -> Array:
|
|
726
|
+
"""Readout from reservoir state.
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
----------
|
|
730
|
+
res_state : Array
|
|
731
|
+
Reservoir state, (shape=(chunks, res_dim,)).
|
|
732
|
+
|
|
733
|
+
Returns
|
|
734
|
+
-------
|
|
735
|
+
Array
|
|
736
|
+
Output from reservoir, (shape=(out_dim,)).
|
|
737
|
+
"""
|
|
738
|
+
if res_state.shape[1] != self.res_dim:
|
|
739
|
+
raise ValueError(
|
|
740
|
+
"Incorrect reservoir dimension for instantiated output map."
|
|
741
|
+
)
|
|
742
|
+
return jnp.mean(eqx.filter_vmap(jnp.matmul)(self.wout, res_state), axis=0)
|