Metadata-Version: 2.4
Name: esnfed
Version: 1.4.0
Summary: Echo State Networks for Federated Learning: reservoirs, topologies and federated strategies
Project-URL: Homepage, https://daibeal.github.io/esnfed/
Project-URL: Documentation, https://daibeal.github.io/esnfed/
Project-URL: Repository, https://github.com/daibeal/esnfed
Project-URL: Issues, https://github.com/daibeal/esnfed/issues
Author-email: Dairon Andres Benites Aldaz <dairon.aldaz@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: echo state networks,ensemble learning,federated learning,machine learning,recurrent neural networks,reservoir computing,time series
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: networkx>=3.0
Requires-Dist: numpy>=1.21
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: jupyter-server>=2.0; extra == 'docs'
Requires-Dist: jupyterlite-core>=0.3; extra == 'docs'
Requires-Dist: jupyterlite-pyodide-kernel>=0.3; extra == 'docs'
Requires-Dist: mkdocs-jupyter>=0.24; extra == 'docs'
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Provides-Extra: experiments
Requires-Dist: matplotlib>=3.5; extra == 'experiments'
Requires-Dist: pandas>=1.3; extra == 'experiments'
Requires-Dist: scikit-learn>=1.0; extra == 'experiments'
Requires-Dist: scipy>=1.7; extra == 'experiments'
Provides-Extra: fast
Requires-Dist: numba>=0.58; extra == 'fast'
Requires-Dist: scipy>=1.7; extra == 'fast'
Provides-Extra: flower
Requires-Dist: flwr>=1.7; extra == 'flower'
Provides-Extra: llm
Requires-Dist: transformers>=4.30; extra == 'llm'
Provides-Extra: reservoirpy
Requires-Dist: reservoirpy>=0.3; extra == 'reservoirpy'
Provides-Extra: viz
Requires-Dist: kaleido; extra == 'viz'
Requires-Dist: matplotlib>=3.5; extra == 'viz'
Requires-Dist: plotly>=5.0; extra == 'viz'
Requires-Dist: seaborn>=0.12; extra == 'viz'
Description-Content-Type: text/markdown

# esnfed — a federated reservoir computing toolkit

`esnfed` is a small, dependency-light Python library for training **Echo State
Networks** (reservoir computing) in a **federated** setting — where several
parties jointly train a model without sharing their raw data. It accompanies the
Final Degree Project *Ensemble of Recurrent Networks for Federated Learning*
(ETSINF, Universitat Politècnica de València).

The core library depends only on **NumPy** and **NetworkX**.

> **Why this and not ReservoirPy?** [ReservoirPy](https://reservoirpy.readthedocs.io)
> is the mature, full-featured library for *building and tuning* reservoir
> computing models, and `esnfed` does **not** try to replace it. `esnfed`
> focuses on the part ReservoirPy does not cover: **federating** reservoir
> models across parties. You can design a reservoir in ReservoirPy and federate
> it here in one line (see *Interoperability* below).

## Gallery

All four plots below are produced by `esnfed.viz` (see *Visualising your ESN*).

| Reservoir topology | Eigenvalue spectrum |
|:---:|:---:|
| ![Reservoir topology graph](https://raw.githubusercontent.com/daibeal/esnfed/main/docs/images/reservoir.png) | ![Eigenvalue spectrum](https://raw.githubusercontent.com/daibeal/esnfed/main/docs/images/spectrum.png) |
| **Reservoir activations** | **Forecast vs. actual** |
| ![Reservoir state activations](https://raw.githubusercontent.com/daibeal/esnfed/main/docs/images/states.png) | ![Forecast vs actual](https://raw.githubusercontent.com/daibeal/esnfed/main/docs/images/forecast.png) |

## Features

- **Echo State Network** with leaky-integrator neurons and a closed-form ridge
  readout (`EchoStateNetwork`).
- **Federated strategies**:
  - `federated_ridge` — *exact* federated training for a shared reservoir
    (clients exchange only ridge sufficient statistics; provably equal to pooled
    training, in one communication round);
  - `fedavg` — iterative FedAvg on the readout;
  - `ensemble_predict` — prediction ensemble for *heterogeneous* reservoirs;
  - `structural_alignment` — interpolate reservoirs toward a shared structure.
- **Reservoir topologies**: Erdős–Rényi, small-world, scale-free, ring.
- **Data**: synthetic benchmarks (NARMA-10, Mackey-Glass, Lorenz) **and** real
  data — a bundled counterparty-risk series (the TED spread) plus generic
  loaders (`from_array`, `load_csv`, `load_fred`).
- **Interoperability**: adapters for [ReservoirPy](https://reservoirpy.readthedocs.io)
  reservoirs and an example integration with the
  [Flower](https://flower.ai) federated-learning framework.

## Installation

```bash
pip install esnfed                  # core (numpy + networkx)
pip install "esnfed[viz]"           # + plotly/matplotlib/seaborn plots
pip install "esnfed[experiments]"   # + matplotlib/pandas/scipy/scikit-learn
pip install "esnfed[reservoirpy]"   # + ReservoirPy interop
pip install "esnfed[flower]"        # + Flower integration
```

## Quick start

### Federated counterparty-risk forecasting (real data)

Several institutions jointly forecast the **TED spread** (a classic gauge of
interbank/counterparty credit risk) without sharing their data. Exact federated
ridge equals pooled training in a single round:

```python
from esnfed import datasets, federated, topologies, metrics

u, y = datasets.load_ted_spread()                 # bundled real series (FRED)
u_tr, y_tr, u_te, y_te = datasets.split(u, y, 0.7)
parts = datasets.partition_iid(u_tr, y_tr, n_clients=8)   # 8 "institutions"

W = topologies.random_reservoir(200, density=0.1, rng=0)
esn_kw = dict(spectral_radius=0.9, leaking_rate=0.5, washout=100, ridge=1e-6)
clients, ref = federated.make_shared_clients(W, parts, input_seed=0, esn_kwargs=esn_kw)

W_out = federated.federated_ridge(clients, ref)   # one round, exact, private
Z_test = ref.harvest(u_te)[ref.washout:]
print("federated NRMSE:", metrics.nrmse(y_te[ref.washout:], Z_test @ W_out))
```

Need another series? `datasets.load_fred("BAMLH0A0HYM2")` pulls a high-yield
credit spread from FRED; `datasets.from_array(my_series)` or
`datasets.load_csv("my.csv")` turn any series into a forecasting task.

### Train a single ESN

```python
import numpy as np
from esnfed import EchoStateNetwork, datasets, topologies, metrics

u, y = datasets.narma10(3000, rng=0)
u_tr, y_tr, u_te, y_te = datasets.split(u, y)
W = topologies.random_reservoir(200, density=0.1, rng=0)
esn = EchoStateNetwork(1, 1, W, spectral_radius=0.9, washout=100).fit(u_tr, y_tr)
print("NRMSE:", metrics.nrmse(y_te[100:], esn.predict(u_te)[100:]))
```

## Interoperability

### ReservoirPy — design there, federate here

```python
from reservoirpy.nodes import Reservoir
from esnfed import interop, datasets, federated

res = Reservoir(200, sr=0.9, lr=0.5, input_dim=1)   # design/tune in ReservoirPy
esn = interop.to_esn(res, n_inputs=1)               # -> esnfed EchoStateNetwork
W = interop.reservoir_matrix(res)                   # ... then federate it
```

### Flower — a real FL framework

`examples/flower_federated_ridge.py` shows the exact federated-ridge scheme as a
Flower `NumPyClient` + custom `Strategy`: each client's `fit` returns its ridge
sufficient statistics and the server sums them and solves once. The Flower-routed
result is identical to `federated_ridge` to numerical precision.

## Visualising your ESN

The optional `esnfed.viz` module (`pip install "esnfed[viz]"`) provides four
plots. The default backend is **Plotly** (interactive); pass
`backend="matplotlib"` or `backend="seaborn"` for static figures. Each function
returns the native figure object.

```python
from esnfed import EchoStateNetwork, topologies, viz

W = topologies.small_world_reservoir(100, k=6, p=0.1, rng=0)
esn = EchoStateNetwork(1, 1, W, spectral_radius=0.9).fit(u_tr, y_tr)

viz.plot_reservoir(esn).show()           # connectivity graph (degree-coloured)
viz.plot_spectrum(esn).show()            # eigenvalues + unit circle + ρ
viz.plot_states(esn, u_te).show()        # reservoir activations over time
viz.plot_forecast(y_te, esn.predict(u_te), washout=100).show()
viz.save(viz.plot_spectrum(esn), "spectrum.html")   # or .png (needs kaleido)
```

| Function | Shows |
|----------|-------|
| `plot_reservoir` | reservoir connectivity as a network graph |
| `plot_spectrum` | eigenvalues in the complex plane (echo state property) |
| `plot_states` | a sample of reservoir activations over time |
| `plot_forecast` | predicted vs. actual with NRMSE |

## Modules

| Module | Contents |
|--------|----------|
| `esnfed.esn` | `EchoStateNetwork`, ridge helpers |
| `esnfed.topologies` | reservoir generators + `graph_metrics` |
| `esnfed.datasets` | NARMA-10, Mackey-Glass, Lorenz; `from_array`, `load_csv`, `load_ted_spread`, `load_fred` |
| `esnfed.metrics` | `nrmse`, `rmse`, `mse`, `r2_score` |
| `esnfed.federated` | `Client`, `federated_ridge`, `fedavg`, `ensemble_predict`, `structural_alignment` |
| `esnfed.interop` | ReservoirPy adapters (`to_esn`, `reservoir_matrix`) |
| `esnfed.viz` | `plot_reservoir`, `plot_spectrum`, `plot_states`, `plot_forecast` |

## Reproducing the research experiments

```bash
pip install -e ".[experiments,reservoirpy,flower,viz]"
python -m pytest                    # 53 tests
python experiments/run_all.py       # synthetic-benchmark figures and tables
python experiments/exp6_finance.py  # federated counterparty-risk (TED spread)
```

## Data attribution

The bundled TED spread is sourced from the Federal Reserve Bank of St. Louis
(FRED, series `TEDRATE`) and is used for demonstration under FRED's terms.

## Citation

```bibtex
@thesis{benites2026esnfed,
  author = {Benites Aldaz, Dairon Andres},
  title  = {Ensemble of Recurrent Networks for Federated Learning},
  school = {Universitat Politecnica de Valencia (ETSINF)},
  year   = {2026},
  type   = {Bachelor's thesis},
}
```

## License

MIT — see [LICENSE](LICENSE).
