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.
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)