OpenReservoirComputing 0.1.3a0__tar.gz → 0.2.0__tar.gz

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.
Files changed (92) hide show
  1. {openreservoircomputing-0.1.3a0/src/OpenReservoirComputing.egg-info → openreservoircomputing-0.2.0}/PKG-INFO +2 -2
  2. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/README.md +1 -1
  3. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/classifier.md +5 -0
  4. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/control.md +5 -0
  5. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/forecaster.md +5 -0
  6. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/examples/classification.ipynb +4 -4
  7. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/examples/continuous_rc.ipynb +7 -7
  8. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/examples/control.ipynb +5 -5
  9. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/examples/ks.ipynb +1 -1
  10. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0/docs}/examples/lorenz.ipynb +1 -1
  11. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0/docs}/examples/rc_background.ipynb +16 -6
  12. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/user-guide/drivers.md +9 -0
  13. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/user-guide/models.md +129 -8
  14. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/user-guide/readouts.md +49 -14
  15. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/examples/classification.ipynb +4 -4
  16. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/examples/continuous_rc.ipynb +7 -7
  17. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/examples/control.ipynb +5 -5
  18. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/examples/ks.ipynb +1 -1
  19. openreservoircomputing-0.2.0/examples/lorenz.ipynb +264 -0
  20. openreservoircomputing-0.2.0/examples/rc_background.ipynb +420 -0
  21. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0/src/OpenReservoirComputing.egg-info}/PKG-INFO +2 -2
  22. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/classifier/__init__.py +2 -1
  23. openreservoircomputing-0.2.0/src/orc/classifier/train.py +152 -0
  24. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/control/__init__.py +2 -1
  25. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/control/models.py +18 -0
  26. openreservoircomputing-0.2.0/src/orc/control/train.py +160 -0
  27. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/drivers.py +40 -1
  28. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/embeddings.py +3 -0
  29. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/forecaster/__init__.py +2 -0
  30. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/forecaster/base.py +2 -24
  31. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/forecaster/train.py +112 -160
  32. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/readouts.py +186 -1
  33. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/classifier/test_classifier_models.py +96 -0
  34. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/control/test_control_models.py +73 -0
  35. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/forecaster/test_forecast_models.py +110 -0
  36. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/test_readouts.py +174 -0
  37. openreservoircomputing-0.1.3a0/docs/examples/lorenz.ipynb +0 -256
  38. openreservoircomputing-0.1.3a0/docs/examples/rc_background.ipynb +0 -417
  39. openreservoircomputing-0.1.3a0/src/orc/classifier/train.py +0 -83
  40. openreservoircomputing-0.1.3a0/src/orc/control/train.py +0 -90
  41. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/.github/CODEOWNERS +0 -0
  42. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/.github/workflows/docs.yml +0 -0
  43. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/.github/workflows/publish.yml +0 -0
  44. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/.github/workflows/tests.yml +0 -0
  45. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/.gitignore +0 -0
  46. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/LICENSE +0 -0
  47. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/data.md +0 -0
  48. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/drivers.md +0 -0
  49. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/embeddings.md +0 -0
  50. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/readouts.md +0 -0
  51. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/api/utils.md +0 -0
  52. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/contributing.md +0 -0
  53. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/examples/data_library.ipynb +0 -0
  54. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/getting-started/installation.md +0 -0
  55. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/getting-started/quickstart.md +0 -0
  56. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/imgs/ORC_logo_cropped.png +0 -0
  57. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/imgs/control_diagram.png +0 -0
  58. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/imgs/readme_example_forecast.png +0 -0
  59. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/index.md +0 -0
  60. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/javascripts/mathjax.js +0 -0
  61. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/stylesheets/extra.css +0 -0
  62. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/user-guide/data.md +0 -0
  63. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/user-guide/embeddings.md +0 -0
  64. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/docs/user-guide/overview.md +0 -0
  65. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/examples/data_library.ipynb +0 -0
  66. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/imgs/ORC_logo_cropped.png +0 -0
  67. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/imgs/control_diagram.png +0 -0
  68. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/imgs/readme_example_forecast.png +0 -0
  69. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/mkdocs.yml +0 -0
  70. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/pyproject.toml +0 -0
  71. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/setup.cfg +0 -0
  72. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/OpenReservoirComputing.egg-info/SOURCES.txt +0 -0
  73. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/OpenReservoirComputing.egg-info/dependency_links.txt +0 -0
  74. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/OpenReservoirComputing.egg-info/requires.txt +0 -0
  75. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/OpenReservoirComputing.egg-info/top_level.txt +0 -0
  76. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/__init__.py +0 -0
  77. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/classifier/base.py +0 -0
  78. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/classifier/models.py +0 -0
  79. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/control/base.py +0 -0
  80. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/data/__init__.py +0 -0
  81. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/data/integrators.py +0 -0
  82. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/forecaster/models.py +0 -0
  83. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/utils/__init__.py +0 -0
  84. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/utils/numerics.py +0 -0
  85. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/utils/regressions.py +0 -0
  86. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/src/orc/utils/visualization.py +0 -0
  87. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/data/test_integrators.py +0 -0
  88. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/test_drivers.py +0 -0
  89. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/test_embeddings.py +0 -0
  90. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/utils/test_numerics.py +0 -0
  91. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/utils/test_regressions.py +0 -0
  92. {openreservoircomputing-0.1.3a0 → openreservoircomputing-0.2.0}/tests/utils/test_visualization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenReservoirComputing
3
- Version: 0.1.3a0
3
+ Version: 0.2.0
4
4
  Summary: GPU accelerated implementations of common RC architectures
5
5
  Author-email: "Jan P. Williams" <jmpw1@uw.edu>, Dima Tretiak <dtretiak@uw.edu>
6
6
  Maintainer-email: "Jan P. Williams" <jmpw1@uw.edu>
@@ -111,7 +111,7 @@ t_test = t[split_idx:]
111
111
 
112
112
  # Initialize and train the ESN
113
113
  esn = orc.forecaster.ESNForecaster(data_dim=3, res_dim=400)
114
- esn, R = orc.forecaster.train_ESNForecaster(esn, U_train)
114
+ esn, R = orc.forecaster.train_RCForecaster(esn, U_train)
115
115
 
116
116
  # Forecast!
117
117
  U_pred = esn.forecast(fcast_len=U_test.shape[0], res_state=R[-1]) # feed in the last reservoir state seen in training
@@ -52,7 +52,7 @@ t_test = t[split_idx:]
52
52
 
53
53
  # Initialize and train the ESN
54
54
  esn = orc.forecaster.ESNForecaster(data_dim=3, res_dim=400)
55
- esn, R = orc.forecaster.train_ESNForecaster(esn, U_train)
55
+ esn, R = orc.forecaster.train_RCForecaster(esn, U_train)
56
56
 
57
57
  # Forecast!
58
58
  U_pred = esn.forecast(fcast_len=U_test.shape[0], res_state=R[-1]) # feed in the last reservoir state seen in training
@@ -20,6 +20,11 @@ The `orc.classifier` subpackage provides reservoir computing models for time ser
20
20
 
21
21
  ## Training Functions
22
22
 
23
+ ::: orc.classifier.train.train_RCClassifier
24
+ options:
25
+ show_root_heading: true
26
+ show_source: true
27
+
23
28
  ::: orc.classifier.train.train_ESNClassifier
24
29
  options:
25
30
  show_root_heading: true
@@ -20,6 +20,11 @@ The `orc.control` subpackage provides reservoir computing models for dynamical s
20
20
 
21
21
  ## Training Functions
22
22
 
23
+ ::: orc.control.train.train_RCController
24
+ options:
25
+ show_root_heading: true
26
+ show_source: true
27
+
23
28
  ::: orc.control.train.train_ESNController
24
29
  options:
25
30
  show_root_heading: true
@@ -38,6 +38,11 @@ The `orc.forecaster` subpackage provides reservoir computing models for time ser
38
38
 
39
39
  ## Training Functions
40
40
 
41
+ ::: orc.forecaster.train.train_RCForecaster
42
+ options:
43
+ show_root_heading: true
44
+ show_source: true
45
+
41
46
  ::: orc.forecaster.train.train_ESNForecaster
42
47
  options:
43
48
  show_root_heading: true
@@ -207,7 +207,7 @@
207
207
  }
208
208
  ],
209
209
  "source": [
210
- "trained_classifier = orc.classifier.train_ESNClassifier(\n",
210
+ "trained_classifier = orc.classifier.train_RCClassifier(\n",
211
211
  " classifier,\n",
212
212
  " train_seqs=train_seqs,\n",
213
213
  " labels=train_labels,\n",
@@ -245,7 +245,7 @@
245
245
  "probs = jax.vmap(trained_classifier.classify)(train_seqs)\n",
246
246
  "\n",
247
247
  "\n",
248
- "train_preds = jnp.array(jnp.argmax(probs, axis=1))\n",
248
+ "train_preds = jnp.argmax(probs, axis=1)\n",
249
249
  "train_acc = jnp.mean(train_preds == train_labels)\n",
250
250
  "print(f\"Training accuracy: {train_acc:.1%}\")"
251
251
  ]
@@ -277,7 +277,7 @@
277
277
  "source": [
278
278
  "probs = jax.vmap(trained_classifier.classify)(test_seqs)\n",
279
279
  "\n",
280
- "test_preds = jnp.array(jnp.argmax(probs, axis=1))\n",
280
+ "test_preds = jnp.argmax(probs, axis=1)\n",
281
281
  "test_acc = jnp.mean(test_preds == test_labels)\n",
282
282
  "print(f\"Test accuracy: {test_acc:.1%}\")"
283
283
  ]
@@ -356,7 +356,7 @@
356
356
  " state_repr=\"mean\",\n",
357
357
  ")\n",
358
358
  "\n",
359
- "trained_mean = orc.classifier.train_ESNClassifier(\n",
359
+ "trained_mean = orc.classifier.train_RCClassifier(\n",
360
360
  " classifier_mean,\n",
361
361
  " train_seqs=train_seqs,\n",
362
362
  " labels=train_labels,\n",
@@ -98,18 +98,18 @@
98
98
  },
99
99
  {
100
100
  "cell_type": "code",
101
- "execution_count": 3,
101
+ "execution_count": 4,
102
102
  "id": "13d6be2b",
103
103
  "metadata": {},
104
104
  "outputs": [],
105
105
  "source": [
106
106
  "esn = orc.forecaster.CESNForecaster(data_dim=U_train.shape[1], res_dim=1000, time_const=40.0)\n",
107
- "esn, R = orc.forecaster.train_CESNForecaster(model=esn, train_seq=U_train, t_train=t_train)"
107
+ "esn, R = orc.forecaster.train_RCForecaster(model=esn, train_seq=U_train, ts=t_train)"
108
108
  ]
109
109
  },
110
110
  {
111
111
  "cell_type": "code",
112
- "execution_count": 4,
112
+ "execution_count": 5,
113
113
  "id": "6d3630b8",
114
114
  "metadata": {},
115
115
  "outputs": [
@@ -146,7 +146,7 @@
146
146
  },
147
147
  {
148
148
  "cell_type": "code",
149
- "execution_count": 5,
149
+ "execution_count": 6,
150
150
  "id": "8e549270",
151
151
  "metadata": {},
152
152
  "outputs": [],
@@ -159,7 +159,7 @@
159
159
  },
160
160
  {
161
161
  "cell_type": "code",
162
- "execution_count": 6,
162
+ "execution_count": 7,
163
163
  "id": "9aaa840e",
164
164
  "metadata": {},
165
165
  "outputs": [
@@ -196,7 +196,7 @@
196
196
  },
197
197
  {
198
198
  "cell_type": "code",
199
- "execution_count": 7,
199
+ "execution_count": 8,
200
200
  "id": "ad1c9f18",
201
201
  "metadata": {},
202
202
  "outputs": [
@@ -226,7 +226,7 @@
226
226
  },
227
227
  {
228
228
  "cell_type": "code",
229
- "execution_count": 8,
229
+ "execution_count": 9,
230
230
  "id": "618df66f",
231
231
  "metadata": {},
232
232
  "outputs": [
@@ -190,13 +190,13 @@
190
190
  },
191
191
  {
192
192
  "cell_type": "code",
193
- "execution_count": 5,
193
+ "execution_count": 4,
194
194
  "id": "65a44d34",
195
195
  "metadata": {},
196
196
  "outputs": [],
197
197
  "source": [
198
198
  "control_model = orc.control.ESNController(data_dim=2, control_dim=1, res_dim=1000)\n",
199
- "control_model, R = orc.control.train_ESNController(control_model, outputs_damped[:-1], u[1:])"
199
+ "control_model, R = orc.control.train_RCController(control_model, outputs_damped[:-1], u[1:])"
200
200
  ]
201
201
  },
202
202
  {
@@ -217,7 +217,7 @@
217
217
  },
218
218
  {
219
219
  "cell_type": "code",
220
- "execution_count": 6,
220
+ "execution_count": 5,
221
221
  "id": "28b6b864",
222
222
  "metadata": {},
223
223
  "outputs": [],
@@ -257,7 +257,7 @@
257
257
  },
258
258
  {
259
259
  "cell_type": "code",
260
- "execution_count": 7,
260
+ "execution_count": 6,
261
261
  "id": "39b5601a",
262
262
  "metadata": {},
263
263
  "outputs": [],
@@ -267,7 +267,7 @@
267
267
  },
268
268
  {
269
269
  "cell_type": "code",
270
- "execution_count": 8,
270
+ "execution_count": 7,
271
271
  "id": "be2c5103",
272
272
  "metadata": {},
273
273
  "outputs": [
@@ -164,7 +164,7 @@
164
164
  "metadata": {},
165
165
  "outputs": [],
166
166
  "source": [
167
- "esn, R = orc.forecaster.train_ESNForecaster(\n",
167
+ "esn, R = orc.forecaster.train_RCForecaster(\n",
168
168
  " esn,\n",
169
169
  " U_train,\n",
170
170
  " beta=beta\n",
@@ -143,7 +143,7 @@
143
143
  "# init + train ESN\n",
144
144
  "NR = 1000\n",
145
145
  "esn = orc.forecaster.ESNForecaster(data_dim=3, res_dim=NR, seed=0)\n",
146
- "esn, R = orc.forecaster.train_ESNForecaster(esn, U_train)"
146
+ "esn, R = orc.forecaster.train_RCForecaster(esn, U_train)"
147
147
  ]
148
148
  },
149
149
  {
@@ -163,14 +163,14 @@
163
163
  "class Readout(orc.readouts.ReadoutBase):\n",
164
164
  " res_dim: int\n",
165
165
  " out_dim: int\n",
166
- " W_O: jnp.ndarray\n",
166
+ " wout: jnp.ndarray\n",
167
167
  " \n",
168
168
  " def __init__(self, out_dim, res_dim):\n",
169
169
  " super().__init__(out_dim, res_dim)\n",
170
- " self.W_O = jnp.zeros((out_dim, res_dim))\n",
170
+ " self.wout = jnp.zeros((out_dim, res_dim))\n",
171
171
  " \n",
172
172
  " def readout(self, res_state):\n",
173
- " return self.W_O @ res_state"
173
+ " return self.wout @ res_state"
174
174
  ]
175
175
  },
176
176
  {
@@ -299,7 +299,7 @@
299
299
  "# set transient to discard\n",
300
300
  "spinup = 200\n",
301
301
  "\n",
302
- "# learn W_O\n",
302
+ "# learn wout\n",
303
303
  "readout_mat = ridge_regression(forced_seq[spinup:], target_seq[spinup:], beta=1e-7)"
304
304
  ]
305
305
  },
@@ -318,10 +318,20 @@
318
318
  "source": [
319
319
  "# define where in the forecaster model we need to update\n",
320
320
  "def where(model: Forecaster):\n",
321
- " return model.readout.W_O\n",
321
+ " return model.readout.wout\n",
322
322
  "model = eqx.tree_at(where, model, readout_mat)"
323
323
  ]
324
324
  },
325
+ {
326
+ "cell_type": "code",
327
+ "execution_count": 10,
328
+ "metadata": {},
329
+ "outputs": [],
330
+ "source": [
331
+ "# Alternatively\n",
332
+ "model, forced_seq = orc.forecaster.train_RCForecaster(model, train_seq=U_train, spinup=spinup, beta=1e-7)"
333
+ ]
334
+ },
325
335
  {
326
336
  "cell_type": "markdown",
327
337
  "metadata": {},
@@ -331,7 +341,7 @@
331
341
  },
332
342
  {
333
343
  "cell_type": "code",
334
- "execution_count": 10,
344
+ "execution_count": 11,
335
345
  "metadata": {},
336
346
  "outputs": [
337
347
  {
@@ -12,12 +12,16 @@ The `DriverBase` class defines the core interface that all driver implementation
12
12
 
13
13
  ### Key Attributes
14
14
  - `res_dim`: Reservoir dimension
15
+ - `chunks`: Number of parallel reservoirs (default `0`, meaning no chunks dimension)
15
16
  - `dtype`: JAX array dtype (jnp.float32 or jnp.float64)
16
17
 
17
18
  ### Core Methods
18
19
  - `advance(proj_vars, res_state)`: Advance single reservoir state by one time step
19
20
  - `batch_advance(proj_vars, res_state)`: Advance batch of reservoir states by one time step
20
21
  - `__call__(proj_vars, res_state)`: Flexible interface supporting both single and batch updates
22
+ - `default_state()`: Create a zero initial reservoir state with the appropriate shape —
23
+ `(chunks, res_dim)` when `chunks > 0`, or `(res_dim,)` for single-reservoir drivers.
24
+ Used by the unified training functions when no `initial_res_state` is provided.
21
25
 
22
26
  ## ParallelESNDriver
23
27
 
@@ -142,6 +146,11 @@ class CustomDriver(DriverBase):
142
146
  ### Requirements
143
147
  - Inherit from `DriverBase`
144
148
  - Implement the abstract `advance()` method
149
+ - Override `default_state()` if your driver uses a non-standard state shape (e.g., a single
150
+ reservoir driver should return `jnp.zeros((res_dim,), dtype=self.dtype)`). The base class
151
+ implementation returns `(chunks, res_dim)` when `chunks > 0` and `(res_dim,)` otherwise.
152
+ The unified training functions (`train_RCForecaster`, etc.) call this method to construct
153
+ the initial reservoir state when none is provided by the caller.
145
154
  - Choose your parallel reservoir support level:
146
155
 
147
156
  #### Single Reservoir Only
@@ -176,9 +176,116 @@ model = CESNForecaster(
176
176
  )
177
177
  ```
178
178
 
179
+ ## ESN Controller
180
+
181
+ The `ESNController` implements an Echo State Network for control tasks, where the model tracks a reference trajectory by optimizing a control input sequence.
182
+
183
+ ### Basic Usage
184
+
185
+ ```python
186
+ from orc.control import ESNController, train_ESNController
187
+ import jax.numpy as jnp
188
+
189
+ # Create ESN controller
190
+ model = ESNController(
191
+ data_dim=3, # System input/output dimension
192
+ control_dim=1, # Control input dimension
193
+ res_dim=500, # Reservoir dimension
194
+ seed=42
195
+ )
196
+
197
+ # Train with input and control sequences
198
+ trained_model, res_states = train_ESNController(
199
+ model,
200
+ train_seq, # shape: (seq_len, data_dim)
201
+ control_seq, # shape: (seq_len, control_dim)
202
+ target_seq=target_seq,
203
+ spinup=100,
204
+ beta=8e-8
205
+ )
206
+
207
+ # Compute optimal control to track a reference trajectory
208
+ control_opt = trained_model.compute_control(
209
+ control_seq=initial_guess, # shape: (fcast_len, control_dim)
210
+ res_state=res_states[-1],
211
+ ref_traj=reference # shape: (fcast_len, data_dim)
212
+ )
213
+ ```
214
+
215
+ ### Control Penalty Weights
216
+
217
+ The controller optimizes a penalty function with three terms, each weighted by a tunable parameter:
218
+
219
+ - `alpha_1` (default 100): **Trajectory deviation** — penalizes squared error between the controlled forecast and the reference trajectory. Higher values enforce tighter tracking.
220
+
221
+ - `alpha_2` (default 1): **Control magnitude** — penalizes the squared norm of control inputs. Higher values favor smaller control effort.
222
+
223
+ - `alpha_3` (default 5): **Control smoothness** — penalizes the squared norm of consecutive control input differences. Higher values produce smoother control signals.
224
+
225
+ ```python
226
+ # Custom penalty weights
227
+ model = ESNController(
228
+ data_dim=3,
229
+ control_dim=1,
230
+ res_dim=500,
231
+ alpha_1=200, # Stricter trajectory tracking
232
+ alpha_2=0.5, # Allow larger control inputs
233
+ alpha_3=10, # Enforce smoother control
234
+ seed=42
235
+ )
236
+ ```
237
+
238
+ ### Key Parameters
239
+
240
+ In addition to the standard ESN parameters (`leak_rate`, `bias`, `embedding_scaling`, `Wr_density`, `Wr_spectral_radius`), the controller accepts:
241
+
242
+ - `control_dim`: Dimension of the control input
243
+ - `alpha_1`, `alpha_2`, `alpha_3`: Control penalty weights (see above)
244
+
245
+ For detailed API documentation, see the [Control API Reference](../api/control.md).
246
+
179
247
  ## Training Functions
180
248
 
181
- Both ESN models use ridge regression for training the readout layer.
249
+ All models use ridge regression for training the readout layer. ORC provides unified training functions that work with any model inheriting from the base classes, as well as convenience wrappers for each specific model type.
250
+
251
+ ### Unified Training Functions (Recommended)
252
+
253
+ The primary training API uses generic functions that work with any `RCForecasterBase`, `RCClassifierBase`, or `RCControllerBase` subclass, including custom models:
254
+
255
+ ```python
256
+ from orc.forecaster import train_RCForecaster
257
+ from orc.classifier import train_RCClassifier
258
+ from orc.control import train_RCController
259
+
260
+ # Forecaster: works with ESNForecaster, CESNForecaster, EnsembleESNForecaster,
261
+ # and any custom RCForecasterBase subclass
262
+ trained_model, res_states = train_RCForecaster(model, train_seq, target_seq=target_seq)
263
+
264
+ # Classifier: works with ESNClassifier and custom RCClassifierBase subclasses
265
+ trained_model = train_RCClassifier(model, train_seqs, labels)
266
+
267
+ # Controller: works with ESNController and custom RCControllerBase subclasses
268
+ trained_model, res_states = train_RCController(model, train_seq, control_seq)
269
+ ```
270
+
271
+ For continuous-time models, pass the time vector via `**force_kwargs`:
272
+
273
+ ```python
274
+ # CESNForecaster via the unified function
275
+ trained_model, res_states = train_RCForecaster(
276
+ model, train_seq, target_seq=target_seq, ts=t_train
277
+ )
278
+ ```
279
+
280
+ ### Model-Specific Convenience Functions
281
+
282
+ The legacy model-specific functions remain available and delegate to the unified functions above:
283
+
284
+ - `train_ESNForecaster` — equivalent to `train_RCForecaster` with an `ESNForecaster` type check
285
+ - `train_CESNForecaster` — equivalent to `train_RCForecaster(..., ts=t_train)` with a `CESNForecaster` type check
286
+ - `train_EnsembleESNForecaster` — equivalent to `train_RCForecaster` with an `EnsembleESNForecaster` type check
287
+ - `train_ESNClassifier` — equivalent to `train_RCClassifier` with an `ESNClassifier` type check
288
+ - `train_ESNController` — equivalent to `train_RCController` with an `ESNController` type check
182
289
 
183
290
  ### Training Parameters
184
291
 
@@ -187,19 +294,22 @@ Both ESN models use ridge regression for training the readout layer.
187
294
 
188
295
  - `beta`: Tikhonov regularization parameter (typically 1e-8 to 1e-6)
189
296
 
190
- - `initial_res_state`: Custom initial reservoir state (default: zeros)
297
+ - `initial_res_state`: Custom initial reservoir state (default: uses `model.driver.default_state()`)
191
298
 
192
299
  **Memory Management:**
193
- - `batch_size`: Process parallel reservoirs in batches to reduce memory usage
300
+ - `batch_size`: Process parallel reservoirs in batches to reduce memory usage (available for all three unified functions)
301
+
302
+ **Extra Force Arguments:**
303
+ - `**force_kwargs`: Additional keyword arguments forwarded to `model.force()` (e.g., `ts=t_train` for continuous models)
194
304
 
195
305
  ### Advanced Training
196
306
 
197
307
  ```python
198
308
  # Batched training for memory efficiency with many parallel reservoirs
199
- trained_model, res_states = train_ESNForecaster(
309
+ trained_model, res_states = train_RCForecaster(
200
310
  model,
201
311
  train_seq,
202
- target_seq,
312
+ target_seq=target_seq,
203
313
  spinup=100,
204
314
  beta=1e-7,
205
315
  batch_size=10 # Process 10 parallel reservoirs at a time
@@ -249,9 +359,9 @@ The models module demonstrates the power of ORC's modular design:
249
359
  To create your own model, follow the ESN pattern:
250
360
 
251
361
  ```python
252
- from orc.rc import RCForecasterBase
362
+ from orc.forecaster import RCForecasterBase, train_RCForecaster
253
363
  from orc.drivers import CustomDriver
254
- from orc.embeddings import CustomEmbedding
364
+ from orc.embeddings import CustomEmbedding
255
365
  from orc.readouts import CustomReadout
256
366
 
257
367
  class CustomForecaster(RCForecasterBase):
@@ -260,11 +370,22 @@ class CustomForecaster(RCForecasterBase):
260
370
  driver = CustomDriver(res_dim=res_dim, **kwargs)
261
371
  embedding = CustomEmbedding(in_dim=data_dim, res_dim=res_dim, **kwargs)
262
372
  readout = CustomReadout(out_dim=data_dim, res_dim=res_dim, **kwargs)
263
-
373
+
264
374
  # Initialize base class
265
375
  super().__init__(driver, readout, embedding, **kwargs)
376
+
377
+ # Train using the unified function — no model-specific training code needed
378
+ trained_model, res_states = train_RCForecaster(
379
+ CustomForecaster(data_dim=3, res_dim=500),
380
+ train_seq,
381
+ target_seq=target_seq,
382
+ spinup=100,
383
+ beta=8e-8,
384
+ )
266
385
  ```
267
386
 
387
+ The unified `train_RCForecaster` function delegates shape handling to the readout's `prepare_train`, `prepare_target`, and `set_wout` methods and the driver's `default_state` method, so custom components that implement those methods integrate automatically with training. See the [Readouts](readouts.md) and [Drivers](drivers.md) user guides for details.
388
+
268
389
  ## Performance Considerations
269
390
 
270
391
  - Use `jnp.float64` for numerical precision (default)
@@ -13,13 +13,26 @@ The `ReadoutBase` class defines the core interface that all readout implementati
13
13
  ### Key Attributes
14
14
  - `out_dim`: Output dimension
15
15
  - `res_dim`: Reservoir dimension
16
+ - `chunks`: Number of parallel reservoirs (default `0`, meaning no chunks dimension)
16
17
  - `dtype`: JAX array dtype (jnp.float32 or jnp.float64)
17
18
 
18
19
  ### Core Methods
20
+
21
+ **Inference:**
19
22
  - `readout(res_state)`: Map a single reservoir state to output
20
- - `batch_readout(res_state)`: Map multiple reservoir states to outputs efficiently
23
+ - `batch_readout(res_state)`: Map multiple reservoir states to outputs efficiently
21
24
  - `__call__(res_state)`: Flexible interface supporting both single and batch inputs
22
25
 
26
+ **Training interface** (used by `train_RCForecaster`, `train_RCClassifier`, `train_RCController`):
27
+ - `prepare_train(res_seq)`: Prepare reservoir states for ridge regression. Default: identity
28
+ (returns `res_seq` unchanged). Override to apply nonlinear transforms or reshape for
29
+ chunk structure.
30
+ - `prepare_target(target_seq)`: Prepare target sequence for ridge regression. Default:
31
+ identity. Override to reshape for chunk structure (e.g., adding or repeating the chunks axis).
32
+ - `set_wout(cmat)`: Return a **new** readout instance with the updated weight matrix `cmat`
33
+ stored in `wout`. Override if your readout uses a different attribute name for the output
34
+ weights.
35
+
23
36
  ## ParallelLinearReadout Example
24
37
 
25
38
  The `ParallelLinearReadout` class demonstrates a standard linear transformation from reservoir states to outputs with support for parallel reservoirs.
@@ -72,7 +85,7 @@ The readout weights (`wout`) are initialized to zero and trained externally usin
72
85
 
73
86
  ## Custom Readouts
74
87
 
75
- The primary purpose of the readout system is to enable you to create your own readout layers tailored to your specific needs. While RC architectures often are trained via regression, this also provies more flexibility for more complicated readout architectures and training protocols.
88
+ The primary purpose of the readout system is to enable you to create your own readout layers tailored to your specific needs. While RC architectures often are trained via regression, this also provides more flexibility for more complicated readout architectures and training protocols.
76
89
 
77
90
  ```python
78
91
  from orc.readouts import ReadoutBase
@@ -80,26 +93,39 @@ import equinox as eqx
80
93
  import jax.numpy as jnp
81
94
 
82
95
  class CustomReadout(ReadoutBase):
83
- # Define your parameters
96
+ # Define your parameters — `chunks` is inherited from ReadoutBase
84
97
  wout: Array
85
- chunks: int
86
-
98
+
87
99
  def __init__(self, out_dim, res_dim, chunks=1, **kwargs):
88
- super().__init__(out_dim, res_dim)
89
- self.chunks = chunks
90
- # Initialize your parameters (e.g., weights, nonlinearities)
91
- self.wout = jnp.zeros((chunks, int(out_dim/chunks), res_dim))
92
-
100
+ super().__init__(out_dim, res_dim, chunks=chunks)
101
+ # Initialize weights
102
+ self.wout = jnp.zeros((chunks, int(out_dim / chunks), res_dim))
103
+
93
104
  def readout(self, res_state):
94
105
  # Implement your custom readout logic
95
- # Apply transformations, nonlinearities, etc.
96
106
  transformed_state = your_transformation(res_state)
97
107
  return jnp.ravel(eqx.filter_vmap(jnp.matmul)(self.wout, transformed_state))
108
+
109
+ # Training interface — override to integrate with train_RCForecaster etc.
110
+ def prepare_train(self, res_seq):
111
+ # Apply any custom transform and/or reshape for chunk structure
112
+ return your_transformation(res_seq) # shape: (seq_len, chunks, res_dim)
113
+
114
+ def prepare_target(self, target_seq):
115
+ # Reshape target to match chunks structure expected by ridge regression
116
+ return target_seq.reshape(target_seq.shape[0], self.chunks, -1)
117
+
118
+ # set_wout is inherited and works as long as the weight attribute is named `wout`
98
119
  ```
99
120
 
100
121
  ### Requirements for Custom Readouts
101
122
  - Inherit from `ReadoutBase`
102
123
  - Implement the abstract `readout()` method
124
+ - Do **not** redefine `chunks` as a class attribute — it is inherited from `ReadoutBase`
125
+ - Override `prepare_train`, `prepare_target`, and/or `set_wout` to integrate with the
126
+ unified training functions (`train_RCForecaster`, `train_RCClassifier`,
127
+ `train_RCController`). The base class defaults (identity transforms) are sufficient
128
+ for simple single-reservoir readouts with a `wout` attribute.
103
129
  - Choose your parallel reservoir support level:
104
130
 
105
131
  #### Single Reservoir Only
@@ -137,10 +163,19 @@ def __call__(self, res_state):
137
163
  ## Design Considerations
138
164
 
139
165
  ### Training Integration
140
- - Readout weights are learned separately via ridge regression (see training utilities)
141
- - The readout layer structure determines the feature space for regression
142
- - More complex readouts can capture richer output relationships but may require more training data or different training procedures.
143
166
 
167
+ The unified training functions (`train_RCForecaster`, `train_RCClassifier`,
168
+ `train_RCController`) interact with readout layers entirely through the training interface:
169
+
170
+ 1. `prepare_train(res_seq)` — called to transform/reshape reservoir states before ridge
171
+ regression
172
+ 2. `prepare_target(target_seq)` — called to reshape targets to match the expected ridge
173
+ regression input
174
+ 3. `set_wout(cmat)` — called after solving regression to install the learned weights
175
+
176
+ Custom readouts that override these three methods integrate automatically with training
177
+ without any changes to the training code. Readouts whose `chunks > 0` receive the batched
178
+ ridge regression path; readouts with `chunks == 0` use the standard scalar ridge regression.
144
179
 
145
180
  ### Integration with RC Models
146
181
  - Readout layers receive reservoir states from drivers
@@ -207,7 +207,7 @@
207
207
  }
208
208
  ],
209
209
  "source": [
210
- "trained_classifier = orc.classifier.train_ESNClassifier(\n",
210
+ "trained_classifier = orc.classifier.train_RCClassifier(\n",
211
211
  " classifier,\n",
212
212
  " train_seqs=train_seqs,\n",
213
213
  " labels=train_labels,\n",
@@ -245,7 +245,7 @@
245
245
  "probs = jax.vmap(trained_classifier.classify)(train_seqs)\n",
246
246
  "\n",
247
247
  "\n",
248
- "train_preds = jnp.array(jnp.argmax(probs, axis=1))\n",
248
+ "train_preds = jnp.argmax(probs, axis=1)\n",
249
249
  "train_acc = jnp.mean(train_preds == train_labels)\n",
250
250
  "print(f\"Training accuracy: {train_acc:.1%}\")"
251
251
  ]
@@ -277,7 +277,7 @@
277
277
  "source": [
278
278
  "probs = jax.vmap(trained_classifier.classify)(test_seqs)\n",
279
279
  "\n",
280
- "test_preds = jnp.array(jnp.argmax(probs, axis=1))\n",
280
+ "test_preds = jnp.argmax(probs, axis=1)\n",
281
281
  "test_acc = jnp.mean(test_preds == test_labels)\n",
282
282
  "print(f\"Test accuracy: {test_acc:.1%}\")"
283
283
  ]
@@ -356,7 +356,7 @@
356
356
  " state_repr=\"mean\",\n",
357
357
  ")\n",
358
358
  "\n",
359
- "trained_mean = orc.classifier.train_ESNClassifier(\n",
359
+ "trained_mean = orc.classifier.train_RCClassifier(\n",
360
360
  " classifier_mean,\n",
361
361
  " train_seqs=train_seqs,\n",
362
362
  " labels=train_labels,\n",