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/control/base.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""Defines base classes for Reservoir Computer Controllers."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
|
|
5
|
+
import equinox as eqx
|
|
6
|
+
import jax
|
|
7
|
+
import jax.numpy as jnp
|
|
8
|
+
import jax.scipy.optimize
|
|
9
|
+
from jaxtyping import Array, Float
|
|
10
|
+
|
|
11
|
+
from orc.drivers import DriverBase
|
|
12
|
+
from orc.embeddings import EmbedBase
|
|
13
|
+
from orc.readouts import ReadoutBase
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RCControllerBase(eqx.Module, ABC):
|
|
17
|
+
"""Base class for reservoir computer controllers.
|
|
18
|
+
|
|
19
|
+
Defines the interface for the reservoir computer controller which includes
|
|
20
|
+
the driver, readout and embedding layers. Unlike the forecaster, the controller
|
|
21
|
+
handles an additional control input at each time step.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
driver : DriverBase
|
|
26
|
+
Driver layer of the reservoir computer.
|
|
27
|
+
readout : ReadoutBase
|
|
28
|
+
Readout layer of the reservoir computer.
|
|
29
|
+
embedding : EmbedBase
|
|
30
|
+
Embedding layer of the reservoir computer. Should accept concatenated
|
|
31
|
+
[input, control] vectors.
|
|
32
|
+
in_dim : int
|
|
33
|
+
Dimension of the system input data.
|
|
34
|
+
control_dim : int
|
|
35
|
+
Dimension of the control input.
|
|
36
|
+
out_dim : int
|
|
37
|
+
Dimension of the output data.
|
|
38
|
+
res_dim : int
|
|
39
|
+
Dimension of the reservoir.
|
|
40
|
+
dtype : type
|
|
41
|
+
Data type of the reservoir computer (jnp.float64 is highly recommended).
|
|
42
|
+
alpha_1 : float
|
|
43
|
+
Weight for trajectory deviation penalty in control optimization.
|
|
44
|
+
alpha_2 : float
|
|
45
|
+
Weight for control magnitude penalty in control optimization.
|
|
46
|
+
alpha_3 : float
|
|
47
|
+
Weight for control derivative penalty in control optimization.
|
|
48
|
+
seed : int
|
|
49
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Methods
|
|
53
|
+
-------
|
|
54
|
+
force(in_seq, control_seq, res_state)
|
|
55
|
+
Teacher forces the reservoir with input and control sequences.
|
|
56
|
+
apply_control(control_seq, res_state)
|
|
57
|
+
Apply a predefined control sequence in closed-loop.
|
|
58
|
+
set_readout(readout)
|
|
59
|
+
Replaces the readout layer of the reservoir computer.
|
|
60
|
+
set_embedding(embedding)
|
|
61
|
+
Replaces the embedding layer of the reservoir computer.
|
|
62
|
+
compute_penalty(control_seq, res_state, ref_traj)
|
|
63
|
+
Compute the control penalty for a given control sequence.
|
|
64
|
+
compute_control(control_seq, res_state, ref_traj)
|
|
65
|
+
Compute optimal control sequence to track a reference trajectory.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
driver: DriverBase
|
|
69
|
+
readout: ReadoutBase
|
|
70
|
+
embedding: EmbedBase
|
|
71
|
+
in_dim: int
|
|
72
|
+
control_dim: int
|
|
73
|
+
out_dim: int
|
|
74
|
+
res_dim: int
|
|
75
|
+
dtype: Float = jnp.float64
|
|
76
|
+
alpha_1: float = 100
|
|
77
|
+
alpha_2: float = 1
|
|
78
|
+
alpha_3: float = 5
|
|
79
|
+
seed: int = 0
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
driver: DriverBase,
|
|
84
|
+
readout: ReadoutBase,
|
|
85
|
+
embedding: EmbedBase,
|
|
86
|
+
in_dim: int,
|
|
87
|
+
control_dim: int,
|
|
88
|
+
dtype: Float = jnp.float64,
|
|
89
|
+
alpha_1: float = 100,
|
|
90
|
+
alpha_2: float = 1,
|
|
91
|
+
alpha_3: float = 5,
|
|
92
|
+
seed: int = 0,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Initialize RCController Base.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
driver : DriverBase
|
|
99
|
+
Driver layer of the reservoir computer.
|
|
100
|
+
readout : ReadoutBase
|
|
101
|
+
Readout layer of the reservoir computer.
|
|
102
|
+
embedding : EmbedBase
|
|
103
|
+
Embedding layer of the reservoir computer.
|
|
104
|
+
in_dim : int
|
|
105
|
+
Dimension of the system input data.
|
|
106
|
+
control_dim : int
|
|
107
|
+
Dimension of the control input.
|
|
108
|
+
dtype : type
|
|
109
|
+
Data type of the reservoir computer (jnp.float64 is highly recommended).
|
|
110
|
+
alpha_1 : float
|
|
111
|
+
Weight for trajectory deviation penalty in control optimization.
|
|
112
|
+
alpha_2 : float
|
|
113
|
+
Weight for control magnitude penalty in control optimization.
|
|
114
|
+
alpha_3 : float
|
|
115
|
+
Weight for control derivative penalty in control optimization.
|
|
116
|
+
seed : int
|
|
117
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
118
|
+
"""
|
|
119
|
+
self.driver = driver
|
|
120
|
+
self.readout = readout
|
|
121
|
+
self.embedding = embedding
|
|
122
|
+
self.in_dim = in_dim
|
|
123
|
+
self.control_dim = control_dim
|
|
124
|
+
self.out_dim = self.readout.out_dim
|
|
125
|
+
self.res_dim = self.driver.res_dim
|
|
126
|
+
self.dtype = dtype
|
|
127
|
+
self.alpha_1 = alpha_1
|
|
128
|
+
self.alpha_2 = alpha_2
|
|
129
|
+
self.alpha_3 = alpha_3
|
|
130
|
+
self.seed = seed
|
|
131
|
+
|
|
132
|
+
@eqx.filter_jit
|
|
133
|
+
def force(self, in_seq: Array, control_seq: Array, res_state: Array) -> Array:
|
|
134
|
+
"""Teacher forces the reservoir with input and control sequences.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
in_seq: Array
|
|
139
|
+
Input sequence to force the reservoir, (shape=(seq_len, in_dim)).
|
|
140
|
+
control_seq: Array
|
|
141
|
+
Control sequence to force the reservoir, (shape=(seq_len, control_dim)).
|
|
142
|
+
res_state : Array
|
|
143
|
+
Initial reservoir state, (shape=(res_dim,)).
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
Array
|
|
148
|
+
Forced reservoir sequence, (shape=(seq_len, res_dim)).
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def scan_fn(state, in_vars):
|
|
152
|
+
in_state, control_state = in_vars
|
|
153
|
+
# Concatenate input and control for embedding
|
|
154
|
+
combined_input = jnp.concatenate([in_state, control_state])
|
|
155
|
+
proj_vars = self.embedding.embed(combined_input)
|
|
156
|
+
res_state = self.driver.advance(proj_vars, state)
|
|
157
|
+
return (res_state, res_state)
|
|
158
|
+
|
|
159
|
+
_, res_seq = jax.lax.scan(scan_fn, res_state, (in_seq, control_seq))
|
|
160
|
+
return res_seq
|
|
161
|
+
|
|
162
|
+
def __call__(self, in_seq: Array, control_seq: Array, res_state: Array) -> Array:
|
|
163
|
+
"""Teacher forces the reservoir, wrapper for `force` method.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
in_seq: Array
|
|
168
|
+
Input sequence to force the reservoir, (shape=(seq_len, in_dim)).
|
|
169
|
+
control_seq: Array
|
|
170
|
+
Control sequence to force the reservoir, (shape=(seq_len, control_dim)).
|
|
171
|
+
res_state : Array
|
|
172
|
+
Initial reservoir state, (shape=(res_dim,)).
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
Array
|
|
177
|
+
Forced reservoir sequence, (shape=(seq_len, res_dim)).
|
|
178
|
+
"""
|
|
179
|
+
return self.force(in_seq, control_seq, res_state)
|
|
180
|
+
|
|
181
|
+
@eqx.filter_jit
|
|
182
|
+
def apply_control(
|
|
183
|
+
self, control_seq: Array, res_state: Array
|
|
184
|
+
) -> tuple[Array, Array]:
|
|
185
|
+
"""Apply a predefined control sequence in closed-loop.
|
|
186
|
+
|
|
187
|
+
The readout feeds back as the next input: u(t+1) = readout(x(t)).
|
|
188
|
+
Control c(t) comes from the provided control_seq.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
control_seq : Array
|
|
193
|
+
Control sequence to apply, (shape=(fcast_len, control_dim)).
|
|
194
|
+
res_state : Array
|
|
195
|
+
Initial reservoir state, (shape=(res_dim,)).
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
Array
|
|
200
|
+
Controlled output trajectory with shape=(fcast_len, out_dim)).
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def scan_fn(state, control_vars):
|
|
204
|
+
# Get output from current reservoir state
|
|
205
|
+
out_state = self.readout(state)
|
|
206
|
+
# Concatenate output (as next input) with control
|
|
207
|
+
combined_input = jnp.concatenate([out_state, control_vars])
|
|
208
|
+
# Embed and advance reservoir
|
|
209
|
+
proj_vars = self.embedding(combined_input)
|
|
210
|
+
next_res_state = self.driver(proj_vars, state)
|
|
211
|
+
return (next_res_state, self.readout(next_res_state))
|
|
212
|
+
|
|
213
|
+
res_state, state_seq = jax.lax.scan(scan_fn, res_state, control_seq)
|
|
214
|
+
return state_seq
|
|
215
|
+
|
|
216
|
+
def set_readout(self, readout: ReadoutBase) -> "RCControllerBase":
|
|
217
|
+
"""Replace readout layer.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
readout : ReadoutBase
|
|
222
|
+
New readout layer.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
RCControllerBase
|
|
227
|
+
Updated model with new readout layer.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def where(m: "RCControllerBase"):
|
|
231
|
+
return m.readout
|
|
232
|
+
|
|
233
|
+
new_model = eqx.tree_at(where, self, readout)
|
|
234
|
+
return new_model
|
|
235
|
+
|
|
236
|
+
def set_embedding(self, embedding: EmbedBase) -> "RCControllerBase":
|
|
237
|
+
"""Replace embedding layer.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
embedding : EmbedBase
|
|
242
|
+
New embedding layer.
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
RCControllerBase
|
|
247
|
+
Updated model with new embedding layer.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def where(m: "RCControllerBase"):
|
|
251
|
+
return m.embedding
|
|
252
|
+
|
|
253
|
+
new_model = eqx.tree_at(where, self, embedding)
|
|
254
|
+
return new_model
|
|
255
|
+
|
|
256
|
+
def compute_penalty(
|
|
257
|
+
self,
|
|
258
|
+
control_seq: Array,
|
|
259
|
+
res_state: Array,
|
|
260
|
+
ref_traj: Array,
|
|
261
|
+
) -> Float:
|
|
262
|
+
"""Compute the control penalty for a given control sequence.
|
|
263
|
+
|
|
264
|
+
The penalty consists of three terms:
|
|
265
|
+
- Deviation penalty: squared error between forecast and reference trajectory
|
|
266
|
+
- Magnitude penalty: squared norm of control inputs
|
|
267
|
+
- Derivative penalty: squared norm of control input differences
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
control_seq : Array
|
|
272
|
+
Control sequence to evaluate, (shape=(fcast_len, control_dim)).
|
|
273
|
+
res_state : Array
|
|
274
|
+
Initial reservoir state, (shape=(res_dim,)).
|
|
275
|
+
ref_traj : Array
|
|
276
|
+
Reference trajectory to track, (shape=(fcast_len, out_dim)).
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
Float
|
|
281
|
+
Total penalty value (scalar).
|
|
282
|
+
"""
|
|
283
|
+
fcast = self.apply_control(control_seq, res_state)
|
|
284
|
+
deviation = fcast - ref_traj
|
|
285
|
+
dev_penalty = jnp.sum(deviation**2) * self.alpha_1
|
|
286
|
+
mag_penalty = jnp.sum(control_seq**2) * self.alpha_2
|
|
287
|
+
deriv_penalty = jnp.sum(jnp.diff(control_seq, axis=0) ** 2) * self.alpha_3
|
|
288
|
+
return dev_penalty + mag_penalty + deriv_penalty
|
|
289
|
+
|
|
290
|
+
@eqx.filter_jit
|
|
291
|
+
def compute_control(
|
|
292
|
+
self,
|
|
293
|
+
control_seq: Array,
|
|
294
|
+
res_state: Array,
|
|
295
|
+
ref_traj: Array,
|
|
296
|
+
) -> Array:
|
|
297
|
+
"""Compute optimal control sequence to track a reference trajectory.
|
|
298
|
+
|
|
299
|
+
Uses BFGS optimization to find a control sequence that minimizes the
|
|
300
|
+
penalty function (deviation from reference + control magnitude + control
|
|
301
|
+
smoothness).
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
control_seq : Array
|
|
306
|
+
Initial guess for control sequence, (shape=(fcast_len, control_dim)).
|
|
307
|
+
res_state : Array
|
|
308
|
+
Initial reservoir state, (shape=(res_dim,)).
|
|
309
|
+
ref_traj : Array
|
|
310
|
+
Reference trajectory to track, (shape=(fcast_len, out_dim)).
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
Array
|
|
315
|
+
Optimized control sequence, (shape=(fcast_len, control_dim)).
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def loss_fn(control_seq):
|
|
319
|
+
control_seq = control_seq.reshape(-1, self.control_dim)
|
|
320
|
+
return self.compute_penalty(control_seq, res_state, ref_traj)
|
|
321
|
+
|
|
322
|
+
# TODO: Implement optimization to allow finer grained control of tolerances
|
|
323
|
+
# linesearch = optax.scale_by_backtracking_linesearch(
|
|
324
|
+
# max_backtracking_steps=30,
|
|
325
|
+
# decrease_factor=0.5,
|
|
326
|
+
# )
|
|
327
|
+
# solver = optax.lbfgs(linesearch=linesearch)
|
|
328
|
+
|
|
329
|
+
# if not use_builtin_solver:
|
|
330
|
+
# solver = optax.lbfgs()
|
|
331
|
+
|
|
332
|
+
# @jax.jit
|
|
333
|
+
# def run_lbfgs(x0):
|
|
334
|
+
# value_and_grad_fn = jax.value_and_grad(loss_fn)
|
|
335
|
+
|
|
336
|
+
# def step(carry, _):
|
|
337
|
+
# x, state = carry
|
|
338
|
+
# value, grad = value_and_grad_fn(x)
|
|
339
|
+
# updates, state = solver.update(
|
|
340
|
+
# grad,
|
|
341
|
+
# state,
|
|
342
|
+
# x,
|
|
343
|
+
# value=value,
|
|
344
|
+
# grad=grad,
|
|
345
|
+
# value_fn=loss_fn)
|
|
346
|
+
# x = optax.apply_updates(x, updates)
|
|
347
|
+
# return (x, state), None
|
|
348
|
+
|
|
349
|
+
# init_state = solver.init(x0)
|
|
350
|
+
# (x_final, final_state), _ = jax.lax.scan(step, (x0, init_state),
|
|
351
|
+
# None,
|
|
352
|
+
# length=max_iter)
|
|
353
|
+
# return x_final, final_state
|
|
354
|
+
|
|
355
|
+
# control_opt, state = run_lbfgs(control_seq)
|
|
356
|
+
|
|
357
|
+
optimize_results = jax.scipy.optimize.minimize(
|
|
358
|
+
loss_fn, control_seq.reshape(-1), method="BFGS"
|
|
359
|
+
)
|
|
360
|
+
control_opt = optimize_results.x.reshape(-1, self.control_dim)
|
|
361
|
+
|
|
362
|
+
return control_opt
|
orc/control/models.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Discrete ESN controller implementation."""
|
|
2
|
+
|
|
3
|
+
import jax
|
|
4
|
+
import jax.numpy as jnp
|
|
5
|
+
|
|
6
|
+
from orc.control.base import RCControllerBase
|
|
7
|
+
from orc.drivers import ESNDriver
|
|
8
|
+
from orc.embeddings import LinearEmbedding
|
|
9
|
+
from orc.readouts import LinearReadout, QuadraticReadout
|
|
10
|
+
|
|
11
|
+
jax.config.update("jax_enable_x64", True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ESNController(RCControllerBase):
|
|
15
|
+
"""
|
|
16
|
+
Basic implementation of ESN for control tasks.
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
res_dim : int
|
|
21
|
+
Reservoir dimension.
|
|
22
|
+
data_dim : int
|
|
23
|
+
System input/output dimension.
|
|
24
|
+
control_dim : int
|
|
25
|
+
Control input dimension.
|
|
26
|
+
driver : ESNDriver
|
|
27
|
+
Driver implementing the Echo State Network dynamics.
|
|
28
|
+
readout : ReadoutBase
|
|
29
|
+
Trainable linear readout layer.
|
|
30
|
+
embedding : LinearEmbedding
|
|
31
|
+
Untrainable linear embedding layer for [input, control] concatenation.
|
|
32
|
+
|
|
33
|
+
Methods
|
|
34
|
+
-------
|
|
35
|
+
force(in_seq, control_seq, res_state)
|
|
36
|
+
Teacher forces the reservoir with input and control sequences.
|
|
37
|
+
apply_control(control_seq, fcast_len, res_state)
|
|
38
|
+
Apply a predefined control sequence in closed-loop.
|
|
39
|
+
set_readout(readout)
|
|
40
|
+
Replace readout layer.
|
|
41
|
+
set_embedding(embedding)
|
|
42
|
+
Replace embedding layer.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
res_dim: int
|
|
46
|
+
data_dim: int
|
|
47
|
+
control_dim: int
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
data_dim: int,
|
|
52
|
+
control_dim: int,
|
|
53
|
+
res_dim: int,
|
|
54
|
+
leak_rate: float = 0.6,
|
|
55
|
+
bias: float = 1.6,
|
|
56
|
+
embedding_scaling: float = 0.08,
|
|
57
|
+
Wr_density: float = 0.02,
|
|
58
|
+
Wr_spectral_radius: float = 0.8,
|
|
59
|
+
dtype: type = jnp.float64,
|
|
60
|
+
seed: int = 0,
|
|
61
|
+
quadratic: bool = False,
|
|
62
|
+
use_sparse_eigs: bool = True,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Initialize the ESN controller.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
data_dim : int
|
|
70
|
+
Dimension of the system input/output data.
|
|
71
|
+
control_dim : int
|
|
72
|
+
Dimension of the control input.
|
|
73
|
+
res_dim : int
|
|
74
|
+
Dimension of the reservoir adjacency matrix Wr.
|
|
75
|
+
leak_rate : float
|
|
76
|
+
Integration leak rate of the reservoir dynamics.
|
|
77
|
+
bias : float
|
|
78
|
+
Bias term for the reservoir dynamics.
|
|
79
|
+
embedding_scaling : float
|
|
80
|
+
Scaling factor for the embedding layer.
|
|
81
|
+
Wr_density : float
|
|
82
|
+
Density of the reservoir adjacency matrix Wr.
|
|
83
|
+
Wr_spectral_radius : float
|
|
84
|
+
Largest eigenvalue of the reservoir adjacency matrix Wr.
|
|
85
|
+
dtype : type
|
|
86
|
+
Data type of the model (jnp.float64 is highly recommended).
|
|
87
|
+
seed : int
|
|
88
|
+
Random seed for generating the PRNG key for the reservoir computer.
|
|
89
|
+
quadratic : bool
|
|
90
|
+
Use quadratic nonlinearity in output, default False.
|
|
91
|
+
use_sparse_eigs : bool
|
|
92
|
+
Whether to use sparse eigensolver for setting the spectral radius of wr.
|
|
93
|
+
Default is True, which is recommended to save memory and compute time. If
|
|
94
|
+
False, will use dense eigensolver which may be more accurate.
|
|
95
|
+
"""
|
|
96
|
+
# Initialize the random key and reservoir dimension
|
|
97
|
+
self.res_dim = res_dim
|
|
98
|
+
self.seed = seed
|
|
99
|
+
self.data_dim = data_dim
|
|
100
|
+
self.control_dim = control_dim
|
|
101
|
+
key = jax.random.PRNGKey(seed)
|
|
102
|
+
key_driver, key_readout, key_embedding = jax.random.split(key, 3)
|
|
103
|
+
|
|
104
|
+
# Embedding accepts concatenated [input, control] vectors
|
|
105
|
+
# Total input dimension is data_dim + control_dim
|
|
106
|
+
embedding = LinearEmbedding(
|
|
107
|
+
in_dim=data_dim + control_dim,
|
|
108
|
+
res_dim=res_dim,
|
|
109
|
+
seed=key_embedding[0],
|
|
110
|
+
scaling=embedding_scaling,
|
|
111
|
+
)
|
|
112
|
+
driver = ESNDriver(
|
|
113
|
+
res_dim=res_dim,
|
|
114
|
+
seed=key_driver[0],
|
|
115
|
+
leak=leak_rate,
|
|
116
|
+
bias=bias,
|
|
117
|
+
density=Wr_density,
|
|
118
|
+
spectral_radius=Wr_spectral_radius,
|
|
119
|
+
dtype=dtype,
|
|
120
|
+
use_sparse_eigs=use_sparse_eigs,
|
|
121
|
+
)
|
|
122
|
+
if quadratic:
|
|
123
|
+
readout = QuadraticReadout(
|
|
124
|
+
out_dim=data_dim, res_dim=res_dim, seed=key_readout[0]
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
readout = LinearReadout(
|
|
128
|
+
out_dim=data_dim, res_dim=res_dim, seed=key_readout[0]
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
super().__init__(
|
|
132
|
+
driver=driver,
|
|
133
|
+
readout=readout,
|
|
134
|
+
embedding=embedding,
|
|
135
|
+
in_dim=data_dim,
|
|
136
|
+
control_dim=control_dim,
|
|
137
|
+
dtype=dtype,
|
|
138
|
+
seed=seed,
|
|
139
|
+
)
|
orc/control/train.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Training functions for reservoir computer controllers."""
|
|
2
|
+
|
|
3
|
+
import equinox as eqx
|
|
4
|
+
import jax.numpy as jnp
|
|
5
|
+
from jaxtyping import Array
|
|
6
|
+
|
|
7
|
+
from orc.control.models import ESNController
|
|
8
|
+
from orc.readouts import NonlinearReadout
|
|
9
|
+
from orc.utils.regressions import ridge_regression
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def train_ESNController(
|
|
13
|
+
model: ESNController,
|
|
14
|
+
train_seq: Array,
|
|
15
|
+
control_seq: Array,
|
|
16
|
+
target_seq: Array = None,
|
|
17
|
+
spinup: int = 0,
|
|
18
|
+
initial_res_state: Array = None,
|
|
19
|
+
beta: float = 8e-8,
|
|
20
|
+
) -> tuple[ESNController, Array]:
|
|
21
|
+
"""Training function for ESNController.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
model : ESNController
|
|
26
|
+
ESNController model to train.
|
|
27
|
+
train_seq : Array
|
|
28
|
+
Training input sequence for reservoir, (shape=(seq_len, data_dim)).
|
|
29
|
+
control_seq : Array
|
|
30
|
+
Control input sequence for reservoir, (shape=(seq_len, control_dim)).
|
|
31
|
+
target_seq : Array
|
|
32
|
+
Target sequence for training reservoir, (shape=(seq_len, data_dim)).
|
|
33
|
+
If None, defaults to train_seq[1:].
|
|
34
|
+
initial_res_state : Array
|
|
35
|
+
Initial reservoir state, (shape=(res_dim,)).
|
|
36
|
+
spinup : int
|
|
37
|
+
Initial transient of reservoir states to discard.
|
|
38
|
+
beta : float
|
|
39
|
+
Tikhonov regularization parameter.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
model : ESNController
|
|
44
|
+
Trained ESN controller model.
|
|
45
|
+
res_seq : Array
|
|
46
|
+
Training sequence of reservoir states.
|
|
47
|
+
"""
|
|
48
|
+
# Check that model is an ESN Controller
|
|
49
|
+
if not isinstance(model, ESNController):
|
|
50
|
+
raise TypeError("Model must be an ESNController.")
|
|
51
|
+
|
|
52
|
+
# check that train_seq and control_seq have the same length
|
|
53
|
+
if train_seq.shape[0] != control_seq.shape[0]:
|
|
54
|
+
raise ValueError("train_seq and control_seq must have the same length.")
|
|
55
|
+
|
|
56
|
+
# check that spinup is less than the length of the training sequence
|
|
57
|
+
if spinup >= train_seq.shape[0]:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"spinup must be less than the length of the training sequence."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if initial_res_state is None:
|
|
63
|
+
initial_res_state = jnp.zeros(model.res_dim, dtype=model.dtype)
|
|
64
|
+
|
|
65
|
+
if target_seq is None:
|
|
66
|
+
tot_seq = train_seq
|
|
67
|
+
tot_control_seq = control_seq
|
|
68
|
+
target_seq = train_seq[1:, :]
|
|
69
|
+
train_seq = train_seq[:-1, :]
|
|
70
|
+
control_seq = control_seq[:-1, :]
|
|
71
|
+
else:
|
|
72
|
+
tot_seq = jnp.vstack((train_seq, target_seq[-1:]))
|
|
73
|
+
tot_control_seq = control_seq
|
|
74
|
+
|
|
75
|
+
tot_res_seq = model.force(tot_seq, tot_control_seq, initial_res_state)
|
|
76
|
+
res_seq = tot_res_seq[:-1]
|
|
77
|
+
if isinstance(model.readout, NonlinearReadout):
|
|
78
|
+
res_seq_train = model.readout.nonlinear_transform(res_seq)
|
|
79
|
+
else:
|
|
80
|
+
res_seq_train = res_seq
|
|
81
|
+
|
|
82
|
+
cmat = ridge_regression(res_seq_train[spinup:], target_seq[spinup:], beta)
|
|
83
|
+
cmat = cmat.reshape(1, cmat.shape[0], cmat.shape[1])
|
|
84
|
+
|
|
85
|
+
def where(m):
|
|
86
|
+
return m.readout.wout
|
|
87
|
+
|
|
88
|
+
model = eqx.tree_at(where, model, cmat)
|
|
89
|
+
|
|
90
|
+
return model, tot_res_seq
|
orc/data/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Implementations of data generation and handling functions."""
|
|
2
|
+
|
|
3
|
+
from orc.data import integrators
|
|
4
|
+
from orc.data.integrators import (
|
|
5
|
+
KS_1D,
|
|
6
|
+
colpitts,
|
|
7
|
+
double_pendulum,
|
|
8
|
+
hyper_lorenz63,
|
|
9
|
+
hyper_xu,
|
|
10
|
+
lorenz63,
|
|
11
|
+
lorenz96,
|
|
12
|
+
rossler,
|
|
13
|
+
sakaraya,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"integrators",
|
|
18
|
+
"lorenz63",
|
|
19
|
+
"rossler",
|
|
20
|
+
"sakaraya",
|
|
21
|
+
"colpitts",
|
|
22
|
+
"hyper_lorenz63",
|
|
23
|
+
"hyper_xu",
|
|
24
|
+
"double_pendulum",
|
|
25
|
+
"lorenz96",
|
|
26
|
+
"KS_1D",
|
|
27
|
+
]
|