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/embeddings.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"""Define base class for embedding layers and implement common architectures."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
import equinox as eqx
|
|
6
|
+
import jax
|
|
7
|
+
import jax.numpy as jnp
|
|
8
|
+
from jaxtyping import Array, Float
|
|
9
|
+
|
|
10
|
+
jax.config.update("jax_enable_x64", True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EmbedBase(eqx.Module, ABC):
|
|
14
|
+
"""
|
|
15
|
+
Base class dictating API for all implemented embedding layers.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
in_dim : int
|
|
20
|
+
Input dimension.
|
|
21
|
+
res_dim : int
|
|
22
|
+
Reservoir dimension.
|
|
23
|
+
dtype : Float
|
|
24
|
+
Dtype of JAX arrays, jnp.float32 or jnp.float64.
|
|
25
|
+
|
|
26
|
+
Methods
|
|
27
|
+
-------
|
|
28
|
+
embed(in_state)
|
|
29
|
+
Embed input into reservoir dimension.
|
|
30
|
+
batch_embed(in_state)
|
|
31
|
+
Embed multiple inputs into reservoir dimension.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
in_dim: int
|
|
35
|
+
res_dim: int
|
|
36
|
+
dtype: Float
|
|
37
|
+
|
|
38
|
+
def __init__(self, in_dim, res_dim, dtype=jnp.float64):
|
|
39
|
+
"""Ensure in dim, res dim, and dtype are correct type."""
|
|
40
|
+
self.res_dim = res_dim
|
|
41
|
+
self.in_dim = in_dim
|
|
42
|
+
self.dtype = dtype
|
|
43
|
+
if not isinstance(res_dim, int):
|
|
44
|
+
raise TypeError("Reservoir dimension res_dim must be an integer.")
|
|
45
|
+
if not isinstance(in_dim, int):
|
|
46
|
+
raise TypeError("Reservoir dimension res_dim must be an integer.")
|
|
47
|
+
self.dtype = dtype
|
|
48
|
+
if not (dtype == jnp.float64 or dtype == jnp.float32):
|
|
49
|
+
raise TypeError("dtype must be jnp.float64 of jnp.float32.")
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def embed(
|
|
53
|
+
self,
|
|
54
|
+
in_state: Array,
|
|
55
|
+
) -> Array:
|
|
56
|
+
"""Embed input signal to reservoir dimension.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
in_state : Array
|
|
61
|
+
Input state, (shape=(in_dim,)).
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
Array
|
|
66
|
+
Embedded input state to reservoir dimension.
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@eqx.filter_jit
|
|
71
|
+
def batch_embed(
|
|
72
|
+
self,
|
|
73
|
+
in_state: Array,
|
|
74
|
+
) -> Array:
|
|
75
|
+
"""Batch apply embedding from input states.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
in_state : Array
|
|
80
|
+
Input states.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
Array
|
|
85
|
+
Embedded input states to reservoir, (shape=(batch_dim, res_dim,)).
|
|
86
|
+
"""
|
|
87
|
+
return eqx.filter_vmap(self.embed)(in_state)
|
|
88
|
+
|
|
89
|
+
def __call__(
|
|
90
|
+
self,
|
|
91
|
+
in_state: Array,
|
|
92
|
+
) -> Array:
|
|
93
|
+
"""Embed state to reservoir dimensions.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
in_state : Array
|
|
98
|
+
Input state, (shape=(in_dim,) or shape=(seq_len, in_dim)).
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
Array
|
|
103
|
+
Embedded input to reservoir, (shape=(chunks, res_dim,) or
|
|
104
|
+
shape=(seq_len, chunks, res_dim)).
|
|
105
|
+
"""
|
|
106
|
+
if len(in_state.shape) == 1:
|
|
107
|
+
to_ret = self.embed(in_state)
|
|
108
|
+
elif len(in_state.shape) == 2:
|
|
109
|
+
to_ret = self.batch_embed(in_state)
|
|
110
|
+
return to_ret
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ParallelLinearEmbedding(EmbedBase):
|
|
114
|
+
"""Linear embedding layer.
|
|
115
|
+
|
|
116
|
+
Attributes
|
|
117
|
+
----------
|
|
118
|
+
in_dim : int
|
|
119
|
+
Reservoir input dimension.
|
|
120
|
+
res_dim : int
|
|
121
|
+
Reservoir dimension.
|
|
122
|
+
scaling : float
|
|
123
|
+
Min/max values of input matrix.
|
|
124
|
+
win : Array
|
|
125
|
+
Input matrix.
|
|
126
|
+
chunks : int
|
|
127
|
+
Number of parallel reservoirs.
|
|
128
|
+
locality : int
|
|
129
|
+
Adjacent reservoir overlap.
|
|
130
|
+
periodic : bool
|
|
131
|
+
Assume periodic BCs when decomposing the input state to parallel network
|
|
132
|
+
inputs. If False, the input is padded with boundary values at the edges
|
|
133
|
+
(i.e., edge values are extended to the locality region), which may not
|
|
134
|
+
match the true spatial dynamics. If True, the input is padded by connecting
|
|
135
|
+
smoothly the end and beginning of the signal ensuring continuity. Default is
|
|
136
|
+
True.
|
|
137
|
+
|
|
138
|
+
Methods
|
|
139
|
+
-------
|
|
140
|
+
__call__(in_state)
|
|
141
|
+
Embed input state to reservoir dimension.
|
|
142
|
+
localize(in_state, periodic=True)
|
|
143
|
+
Decompose input_state to parallel network inputs.
|
|
144
|
+
moving_window(a)
|
|
145
|
+
Helper function for localize.
|
|
146
|
+
embed(in_state)
|
|
147
|
+
Embed single input state to reservoir dimension.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
in_dim: int
|
|
151
|
+
res_dim: int
|
|
152
|
+
scaling: float
|
|
153
|
+
win: Array
|
|
154
|
+
dtype: Float
|
|
155
|
+
chunks: int
|
|
156
|
+
locality: int
|
|
157
|
+
chunk_size: int
|
|
158
|
+
periodic: bool
|
|
159
|
+
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
in_dim: int,
|
|
163
|
+
res_dim: int,
|
|
164
|
+
scaling: float,
|
|
165
|
+
dtype: Float = jnp.float64,
|
|
166
|
+
chunks: int = 1,
|
|
167
|
+
locality: int = 0,
|
|
168
|
+
periodic: bool = True,
|
|
169
|
+
*,
|
|
170
|
+
seed: int,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Instantiate linear embedding.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
in_dim : int
|
|
177
|
+
Input dimension to reservoir.
|
|
178
|
+
res_dim : int
|
|
179
|
+
Reservoir dimension.
|
|
180
|
+
scaling : float
|
|
181
|
+
Min/max values of input matrix.
|
|
182
|
+
seed : int
|
|
183
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
184
|
+
dtype : Float
|
|
185
|
+
Dtype of model, jnp.float64 or jnp.float32.
|
|
186
|
+
periodic : bool
|
|
187
|
+
Assume periodic BCs when decomposing the input state to parallel network
|
|
188
|
+
inputs. If False, the input is padded with boundary values at the edges
|
|
189
|
+
(i.e., edge values are extended to the locality region), which may not
|
|
190
|
+
match the true spatial dynamics. If True, the input is padded by connecting
|
|
191
|
+
smoothly the end and beginning of the signal ensuring continuity. Default is
|
|
192
|
+
True.
|
|
193
|
+
"""
|
|
194
|
+
super().__init__(in_dim=in_dim, res_dim=res_dim, dtype=dtype)
|
|
195
|
+
self.scaling = scaling
|
|
196
|
+
self.dtype = dtype
|
|
197
|
+
key = jax.random.key(seed)
|
|
198
|
+
self.chunk_size = int(in_dim / chunks)
|
|
199
|
+
|
|
200
|
+
if in_dim % chunks:
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"The number of chunks {chunks} must evenly divide in_dim {in_dim}."
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
self.win = jax.random.uniform(
|
|
206
|
+
key,
|
|
207
|
+
(chunks, res_dim, self.chunk_size + 2 * locality),
|
|
208
|
+
minval=-scaling,
|
|
209
|
+
maxval=scaling,
|
|
210
|
+
dtype=dtype,
|
|
211
|
+
)
|
|
212
|
+
self.locality = locality
|
|
213
|
+
self.chunks = chunks
|
|
214
|
+
self.periodic = periodic
|
|
215
|
+
|
|
216
|
+
@eqx.filter_jit
|
|
217
|
+
def moving_window(self, a):
|
|
218
|
+
"""Generate window to compute localized states."""
|
|
219
|
+
size = int(self.in_dim / self.chunks + 2 * self.locality)
|
|
220
|
+
starts = jnp.arange(len(a) - size + 1)[: self.chunks] * int(
|
|
221
|
+
self.in_dim / self.chunks
|
|
222
|
+
)
|
|
223
|
+
return eqx.filter_vmap(
|
|
224
|
+
lambda start: jax.lax.dynamic_slice(a, (start,), (size,))
|
|
225
|
+
)(starts)
|
|
226
|
+
|
|
227
|
+
@eqx.filter_jit
|
|
228
|
+
def localize(self, in_state: Array) -> Array:
|
|
229
|
+
"""Generate parallel reservoir inputs from input state.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
in_state : Array
|
|
234
|
+
Input state, (shape=(in_dim,))
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
Array
|
|
239
|
+
Parallel reservoir inputs, (shape=(chunks, chunk_size + 2*locality))
|
|
240
|
+
"""
|
|
241
|
+
if len(in_state.shape) != 1:
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"Only 1-dimensional localization is currently supported, detected a "
|
|
244
|
+
f"{len(in_state.shape)}D field."
|
|
245
|
+
)
|
|
246
|
+
aug_state = jnp.hstack(
|
|
247
|
+
[in_state[-self.locality :], in_state, in_state[: self.locality]]
|
|
248
|
+
)
|
|
249
|
+
if not self.periodic:
|
|
250
|
+
aug_state = aug_state.at[: self.locality].set(aug_state[self.locality])
|
|
251
|
+
aug_state = aug_state.at[-self.locality :].set(aug_state[-self.locality])
|
|
252
|
+
return self.moving_window(aug_state)
|
|
253
|
+
|
|
254
|
+
@eqx.filter_jit
|
|
255
|
+
def embed(self, in_state: Array) -> Array:
|
|
256
|
+
"""Embed single state to reservoir dimensions.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
in_state : Array
|
|
261
|
+
Input state, (shape=(in_dim,)).
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
Array
|
|
266
|
+
Embedded input to reservoir, (shape=(chunks, res_dim,)).
|
|
267
|
+
"""
|
|
268
|
+
if in_state.shape != (self.in_dim,):
|
|
269
|
+
raise ValueError("Incorrect dimension for input state.")
|
|
270
|
+
localized_states = self.localize(in_state)
|
|
271
|
+
|
|
272
|
+
return eqx.filter_vmap(jnp.matmul)(self.win, localized_states)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class LinearEmbedding(ParallelLinearEmbedding):
|
|
276
|
+
"""Linear embedding layer.
|
|
277
|
+
|
|
278
|
+
Attributes
|
|
279
|
+
----------
|
|
280
|
+
in_dim : int
|
|
281
|
+
Reservoir input dimension.
|
|
282
|
+
res_dim : int
|
|
283
|
+
Reservoir dimension.
|
|
284
|
+
scaling : float
|
|
285
|
+
Min/max values of input matrix.
|
|
286
|
+
win : Array
|
|
287
|
+
Input matrix.
|
|
288
|
+
|
|
289
|
+
Methods
|
|
290
|
+
-------
|
|
291
|
+
__call__(in_state)
|
|
292
|
+
Embed input state to reservoir dimension.
|
|
293
|
+
embed(in_state)
|
|
294
|
+
Embed single input state to reservoir dimension.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
in_dim: int,
|
|
300
|
+
res_dim: int,
|
|
301
|
+
scaling: float,
|
|
302
|
+
dtype: Float = jnp.float64,
|
|
303
|
+
*,
|
|
304
|
+
seed: int,
|
|
305
|
+
) -> None:
|
|
306
|
+
"""Instantiate linear embedding.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
in_dim : int
|
|
311
|
+
Input dimension to reservoir.
|
|
312
|
+
res_dim : int
|
|
313
|
+
Reservoir dimension.
|
|
314
|
+
scaling : float
|
|
315
|
+
Min/max values of input matrix.
|
|
316
|
+
seed : int
|
|
317
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
318
|
+
dtype : Float
|
|
319
|
+
Dtype of model, jnp.float64 or jnp.float32.
|
|
320
|
+
periodic : bool
|
|
321
|
+
Assume periodic BCs when decomposing the input state to parallel network
|
|
322
|
+
inputs. If False, the input is padded with boundary values at the edges
|
|
323
|
+
(i.e., edge values are extended to the locality region), which may not
|
|
324
|
+
match the true spatial dynamics. If True, the input is padded by connecting
|
|
325
|
+
smoothly the end and beginning of the signal ensuring continuity. Default is
|
|
326
|
+
True.
|
|
327
|
+
"""
|
|
328
|
+
super().__init__(
|
|
329
|
+
in_dim=in_dim,
|
|
330
|
+
res_dim=res_dim,
|
|
331
|
+
scaling=scaling,
|
|
332
|
+
dtype=dtype,
|
|
333
|
+
chunks=1,
|
|
334
|
+
locality=0,
|
|
335
|
+
periodic=True,
|
|
336
|
+
seed=seed,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
@eqx.filter_jit
|
|
340
|
+
def embed(self, in_state: Array) -> Array:
|
|
341
|
+
"""Embed single state to reservoir dimensions.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
in_state : Array
|
|
346
|
+
Input state, (shape=(in_dim,)).
|
|
347
|
+
|
|
348
|
+
Returns
|
|
349
|
+
-------
|
|
350
|
+
Array
|
|
351
|
+
Embedded input to reservoir, (shape=(res_dim,)).
|
|
352
|
+
"""
|
|
353
|
+
return super().embed(in_state).reshape(-1)
|
|
354
|
+
|
|
355
|
+
def __call__(self, in_state: Array) -> Array:
|
|
356
|
+
"""Embed state to reservoir dimensions.
|
|
357
|
+
|
|
358
|
+
Parameters
|
|
359
|
+
----------
|
|
360
|
+
in_state : Array
|
|
361
|
+
Input state, (shape=(in_dim,) or shape=(seq_len, in_dim)).
|
|
362
|
+
|
|
363
|
+
Returns
|
|
364
|
+
-------
|
|
365
|
+
Array
|
|
366
|
+
Embedded input to reservoir, (shape=(res_dim,) or
|
|
367
|
+
shape=(seq_len, res_dim)).
|
|
368
|
+
"""
|
|
369
|
+
return jnp.squeeze(super().__call__(in_state))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class EnsembleLinearEmbedding(EmbedBase):
|
|
373
|
+
"""Ensemble linear embedding layer.
|
|
374
|
+
|
|
375
|
+
Attributes
|
|
376
|
+
----------
|
|
377
|
+
in_dim : int
|
|
378
|
+
Reservoir input dimension.
|
|
379
|
+
res_dim : int
|
|
380
|
+
Reservoir dimension.
|
|
381
|
+
scaling : float
|
|
382
|
+
Min/max values of input matrix.
|
|
383
|
+
win : Array
|
|
384
|
+
Input matrix.
|
|
385
|
+
chunks : int
|
|
386
|
+
Number of parallel reservoirs.
|
|
387
|
+
|
|
388
|
+
Methods
|
|
389
|
+
-------
|
|
390
|
+
__call__(in_state)
|
|
391
|
+
Embed input state to reservoir dimension.
|
|
392
|
+
embed(in_state)
|
|
393
|
+
Embed single input state to reservoir dimension.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
in_dim: int
|
|
397
|
+
res_dim: int
|
|
398
|
+
scaling: float
|
|
399
|
+
win: Array
|
|
400
|
+
dtype: Float
|
|
401
|
+
chunks: int
|
|
402
|
+
|
|
403
|
+
def __init__(
|
|
404
|
+
self,
|
|
405
|
+
in_dim: int,
|
|
406
|
+
res_dim: int,
|
|
407
|
+
scaling: float,
|
|
408
|
+
chunks: int = 5,
|
|
409
|
+
dtype: Float = jnp.float64,
|
|
410
|
+
*,
|
|
411
|
+
seed: int,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""Instantiate linear embedding.
|
|
414
|
+
|
|
415
|
+
Parameters
|
|
416
|
+
----------
|
|
417
|
+
in_dim : int
|
|
418
|
+
Input dimension to reservoir.
|
|
419
|
+
res_dim : int
|
|
420
|
+
Reservoir dimension.
|
|
421
|
+
scaling : float
|
|
422
|
+
Min/max values of input matrix.
|
|
423
|
+
seed : int
|
|
424
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
425
|
+
dtype : Float
|
|
426
|
+
Dtype of model, jnp.float64 or jnp.float32.
|
|
427
|
+
"""
|
|
428
|
+
super().__init__(in_dim=in_dim, res_dim=res_dim, dtype=dtype)
|
|
429
|
+
self.scaling = scaling
|
|
430
|
+
self.dtype = dtype
|
|
431
|
+
key = jax.random.key(seed)
|
|
432
|
+
|
|
433
|
+
self.win = jax.random.uniform(
|
|
434
|
+
key,
|
|
435
|
+
(chunks, res_dim, in_dim),
|
|
436
|
+
minval=-scaling,
|
|
437
|
+
maxval=scaling,
|
|
438
|
+
dtype=dtype,
|
|
439
|
+
)
|
|
440
|
+
self.chunks = chunks
|
|
441
|
+
|
|
442
|
+
@eqx.filter_jit
|
|
443
|
+
def embed(self, in_state: Array) -> Array:
|
|
444
|
+
"""Embed single state to reservoir dimensions.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
in_state : Array
|
|
449
|
+
Input state, (shape=(in_dim,)).
|
|
450
|
+
|
|
451
|
+
Returns
|
|
452
|
+
-------
|
|
453
|
+
Array
|
|
454
|
+
Embedded input to reservoir, (shape=(chunks, res_dim,)).
|
|
455
|
+
"""
|
|
456
|
+
if in_state.shape != (self.in_dim,):
|
|
457
|
+
raise ValueError("Incorrect dimension for input state.")
|
|
458
|
+
|
|
459
|
+
return self.win @ in_state
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Forecasting with Reservoir Computers."""
|
|
2
|
+
|
|
3
|
+
from orc.forecaster.base import CRCForecasterBase, RCForecasterBase
|
|
4
|
+
from orc.forecaster.models import CESNForecaster, EnsembleESNForecaster, ESNForecaster
|
|
5
|
+
from orc.forecaster.train import (
|
|
6
|
+
train_CESNForecaster,
|
|
7
|
+
train_EnsembleESNForecaster,
|
|
8
|
+
train_ESNForecaster,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"RCForecasterBase",
|
|
13
|
+
"CRCForecasterBase",
|
|
14
|
+
"ESNForecaster",
|
|
15
|
+
"CESNForecaster",
|
|
16
|
+
"EnsembleESNForecaster",
|
|
17
|
+
"train_ESNForecaster",
|
|
18
|
+
"train_CESNForecaster",
|
|
19
|
+
"train_EnsembleESNForecaster",
|
|
20
|
+
]
|