Code for reproducing the experiments in ResCP: Reservoir Conformal Prediction for Time Series Forecasting, published at ICLR 2026.
Authors: Roberto Neglia, Andrea Cini, Michael M. Bronstein, Filippo Maria Bianchi.
Reservoir Conformal Prediction (ResCP) is a novel, training-free, scalable, localized conformal prediction (CP) method for time series where local similarity is gauged by relying on reservoir states. By sampling more residuals associated with similar states, we condition the estimates on specific dynamics of interest in the time series. This allows us to generate prediction intervals (PIs) that are more informative and adaptive to local patterns in the data, and we empirically demonstrate its effectiveness across various real-world datasets. We also prove that, under reasonable assumptions, ResCP achieves asymptotic conditional coverage.
ResCP applies on top of any point forecaster — it only needs residuals on a disjoint calibration set. This repository contains two variants:
| Variant | Entry point | Description |
|---|---|---|
| ResCP | run_RExCP_sampling.py |
Training-free. Samples calibration residuals proportionally to reservoir-state similarity and takes empirical quantiles. |
| ResCQR | run_RExCP_qr.py |
Quantile-regression readout trained on top of the (frozen, random) reservoir states. |
conda env create -f environment.yaml
conda activate rescp_envThe environment pins CUDA 12.4 builds of PyTorch 2.4.1 and installs
tsl from source. Experiments run on CPU
as well, just slower.
All entry points import both src.* and reservoir_conformal_prediction.src.*, so the
repository root must be on PYTHONPATH:
export PYTHONPATH="$(pwd):$PYTHONPATH"The two run_*.sh scripts set this up themselves.
Everything needed is bundled under data/ — no downloads required.
| Dataset | Path | Shape | Target |
|---|---|---|---|
| Solar (NSRDB, 50 locations) | data/solar/ |
26,304 hourly steps × 50 nodes | dhi |
| Beijing air quality (12 stations) | data/air_quality/ |
35,064 hourly steps × 12 nodes | PM10 |
| ACEA electricity load (Rome) | data/elec_rome/ |
137,376 steps @ 10 min × 1 node | consumption |
| Exchange rate (8 countries) | downloaded by tsl on first use |
7,588 daily steps × 8 nodes | rate |
Solar is resampled from 30 min to hourly at load time. Beijing is the only dataset with missing values in the target (6,449 entries, 1.53%); those are forward/backward filled so the tensors stay finite, and the dataset mask marks them invalid so they are excluded from the training loss and from all reported metrics.
Pre-trained base models, their residuals (residuals.h5) and the calibration/test index
splits (indices.npz) are bundled under reservoir_conformal_prediction/logs/base/, so
both scripts run on a fresh clone with no setup:
cd reservoir_conformal_prediction
./run_rexcp_sampling.sh # ResCP — all 4 datasets × 3 base models
./run_rexcp_qr.sh # ResCQR — all 4 datasets × 3 base modelsTo evaluate against your own base models, point LOGS at your own output directory:
LOGS=/my/logs/base ./run_rexcp_sampling.shSingle runs are plain Hydra invocations:
python run_RExCP_sampling.py \
src_dir=logs/base/beijing/rnn/2025-07-16/17-50-09 \
dataset=beijing \
conformal_predictor=rexcp_sampling_beijing_rnn \
sampler=beijing_rnn \
+clip_residuals=TrueThe ESN hyperparameters reported in Table 4 of the paper (spectral radius, leak rate,
input scaling, temperature, sliding window) live in
configs/conformal_predictor/ and configs/sampler/, one file per
dataset/base-model pair.
The three point predictors — a single-layer GRU, a decoder-only Transformer, and
ARIMA(3,1,3) — are trained by run_base_model.py:
cd reservoir_conformal_prediction
python run_base_model.py dataset=beijing model=rnn
python run_base_model.py dataset=solar model=transformer
python run_base_model.py dataset=elec model=arimaEach run writes a new directory under logs/base/<dataset>/<model>/<date>/<time>/
containing residuals.h5 (calibration + test residuals and the target mask),
indices.npz (the splits), config.yaml, and a checkpoint. Feed that directory to the
conformal scripts as src_dir=.
The bundled checkpoints let you recompute residuals without retraining (ARIMA is fitted directly on the data, so it has none):
python run_base_model.py dataset=beijing model=rnn \
+load_model_path=logs/base/beijing/rnn/2025-07-16/17-50-09/epoch=22-step=2300.ckptSplits follow the paper: 40% train, then a calibration split whose first 25% is held out for early stopping, and a 20% test tail.
data/ raw datasets (see table above)
reservoir_conformal_prediction/
├── run_base_model.py trains the point forecasters, dumps residuals
├── run_RExCP_sampling.py ResCP (training-free)
├── run_RExCP_qr.py ResCQR (quantile-regression readout)
├── configs/ Hydra configs
│ ├── training/ base-model training
│ ├── dataset/ conformal_predictor/ sampler/
├── logs/base/ bundled pre-trained runs
└── src/
├── reservoir_conformal_residual_sampler.py core ResCP sampler
├── torch_reservoir_computing/ ESN implementation
└── lib/
├── datasets/ Solar, Beijing, ACEA loaders
├── nn/ encoders (reservoir), decoders, base models
├── engines/ Lightning predictors
└── metrics/ masked coverage, PI width, Winkler score
-
Coverage gap
$\Delta C = 100 · (1(y_t \in \hat{C}(ŷ_t)) - (1-\alpha))$ -
PI width
$q̂^{1−\alpha/2} − q̂^{\alpha/2}$ -
Winkler score — PI width plus a
$2\alpha$ -weighted penalty for observations outside the interval
All three are masked: invalid (missing) target entries never contribute.
@inproceedings{neglia2026rescp,
title = {{ResCP}: Reservoir Conformal Prediction for Time Series Forecasting},
author = {Neglia, Roberto and Cini, Andrea and
Bronstein, Michael M. and Bianchi, Filippo Maria},
booktitle = {International Conference on Learning Representations (ICLR)},
year = {2026}
}Code released under the MIT License.
Dataset licences are those of the original sources: the Beijing air quality data comes from the UCI Machine Learning Repository (Zhang et al., 2017), the Solar data from the US National Solar Radiation Database (Sengupta et al., 2018) in the 50-series form used by Auer et al. (2023), and the ACEA load data from Bianchi et al. (2015).

