Code accompanying the paper "" (NeurIPS 2026 submission).
We study whether ensembling TimeKAN with five time-series foundation models (TTM, Timer, TimesFM, UniTS, Moirai) improves long-horizon forecasting on standard LTSF benchmarks (ETTh1/2, ETTm1/2, Weather, Electricity), and analyze when and why the resulting hybrid succeeds or fails.
.
├── benchmark_ltsf_full.py # Train TimeKAN + run FM inference (canonical hparams)
├── benchmark_ensemble_ctx512.py # Main ensembling: TimeKAN + FM stacking (OLS / NNLS)
├── build_predictions_npz.py # Bridges benchmark caches → npz format for the above
├── run_nnls_decorrselect.py # NNLS oracle, DecorrSelect, Diebold-Mariano tests
├── threshold_sensitivity.py # Decorrelation percentile sweep (Pearson r, DecorrScore)
├── analyze_sample_errors.py # Per-sample error coupling analysis
├── make_figures.py # Generate the 4 paper figures from cached CSVs/JSON
├── envs/ # Per-env package hint files
│ ├── timekan.txt # TimeKAN training + orchestration
│ ├── ttm.txt # TTM
│ ├── timesfm-moirai.txt # TimesFM + Moirai
│ ├── units.txt # UniTS
│ └── timer.txt # Timer
│
├── UpdatedFMs/ # FM inference wrappers (one per FM)
│ ├── ttm_infer.py
│ ├── timer_infer.py
│ ├── timesfm_infer.py
│ ├── units_infer.py
│ ├── moirai_infer.py
│ └── UniTS/ # git submodule → mims-harvard/UniTS
│
├── TimeKAN/ # git submodule → huangst21/TimeKAN
│
└── paper_results/ensemble_ctx512/
├── ensemble_unconstrained.csv # All MSE/MAE results (288 rows)
├── ols_weights_unconstrained.csv # Per-(dataset, horizon, FM) OLS weights
├── pivot_mse_unconstrained.csv # MSE pivot for side-by-side comparison
├── pivot_mae_unconstrained.csv
├── decorrselect_results.json # NNLS / DecorrSelect / DM stats
├── paper_tables/ # Final tables used in the paper
├── sample_error_analysis/ # Decorrelation analysis outputs
└── figures/ # Output of make_figures.py (PNG + PDF)
git clone --recurse-submodules https://github.com/fnafees/TimeKanEnsemble.git
cd timekan-ensemble-paper
If you already cloned without --recurse-submodules:
git submodule update --init --recursive
Five conda envs are required because the FMs' upstream packages have mutually incompatible dependency pins (notably transformers versions and torch versions):
| Env name (suggested) | Used for | Key packages | Hint file |
|---|---|---|---|
timekan | TimeKAN training, ensembling, figures, analysis | torch 1.13, sktime | envs/timekan.txt |
ttm | TTM inference | tsfm_public | envs/ttm.txt |
timesfm-moirai | TimesFM and Moirai inference | timesfm, uni2ts, transformers (mainline) | envs/timesfm-moirai.txt |
units | UniTS inference | timm, torch | envs/units.txt |
timer | Timer inference | custom transformers fork | envs/timer.txt |
Create each env, then pip install -r envs/<env>.txt as a starting point and resolve any GPU/CUDA-version conflicts locally. The hint files are pip freeze snapshots from the dev machine, not strict lockfiles.
Then export the python interpreter paths so the orchestrating scripts can launch FM subprocesses in the right env:
export TIMEKAN_PY=/path/to/conda/envs/timekan/bin/python
export FRONTIER_PY=/path/to/conda/envs/ttm/bin/python # TTM only
export ENS_PY=/path/to/conda/envs/timesfm-moirai/bin/python # TimesFM + Moirai
export UNITS_PY=/path/to/conda/envs/units/bin/python # UniTS
export TIMER_PY=/path/to/conda/envs/timer/bin/python # Timer
If unset, scripts fall back to sys.executable. The orchestrating scripts themselves (benchmark_ltsf_full.py, benchmark_ensemble_ctx512.py, make_figures.py, run_nnls_decorrselect.py, build_predictions_npz.py, threshold_sensitivity.py, analyze_sample_errors.py) should always be invoked with $TIMEKAN_PY. The env vars above tell those orchestrators which interpreter to use when they spawn FM-inference subprocesses.
The historical env-var names (FRONTIER_PY, ENS_PY, etc.) are kept for code-stability; the FM-to-env mapping is hardcoded as FM_ENVS in benchmark_ensemble_ctx512.py if you need to verify it.
UniTS requires the public units_x128_pretrain_checkpoint.pth. Download it from the UniTS HuggingFace page and place it at:
UpdatedFMs/units_x128_pretrain_checkpoint.pth
Standard LTSF datasets used in the paper:
TimeKAN/dataset/ETT-small/)weather/weather.csv and electricity/electricity.csv files; place under TimeKAN/dataset/weather/ and TimeKAN/dataset/electricity/ respectively)Both benchmark_ltsf_full.py and benchmark_ensemble_ctx512.py read from TimeKAN/dataset/ via the TimeKAN data loader.
SEED=2021 is set at the top of benchmark_ltsf_full.py and benchmark_ensemble_ctx512.py. On the same hardware, two runs of benchmark_ltsf_full.py --datasets ETTh1 --pred-lens 96 produce identical losses to all printed digits (verified on Quadro RTX 8000, CUDA 12.x, torch 2.x).
torch.use_deterministic_algorithms(True) is not set — some FM ops (e.g., scatter operations in TimesFM) are not deterministic-safe. Different GPU architectures may produce drift in the 4th decimal of MSE.
The cached CSVs and JSON in paper_results/ensemble_ctx512/ were produced from one specific full sweep on Quadro RTX 8000. A fresh re-train will produce slightly different absolute numbers (typically 1–3% variation) due to non-deterministic CUDA ops; e.g., the cached TimeKAN ETTh1 H=96 MSE is 0.3746, while a fresh re-train on the same hardware produces 0.3669. Reviewers should expect qualitative match (model rankings, OLS-vs-NNLS gap, sign of decorrelation correlations), not bitwise equality.
Heads up: Re-running
benchmark_ensemble_ctx512.pyappends/updates rows in the cachedpaper_results/ensemble_ctx512/ensemble_unconstrained.csv. If you want to preserve the canonical CSV as a reference, copy it aside first (e.g.,cp ensemble_unconstrained.csv ensemble_unconstrained.canonical.csv) before re-running.
run_nnls_decorrselect.py + threshold_sensitivity.py + analyze_sample_errors.py: <5 min combined.make_figures.py: <30 s.All commands assume the env vars from §2 are exported. As a quick check:
echo "$TIMEKAN_PY" "$FRONTIER_PY" "$ENS_PY" "$UNITS_PY" "$TIMER_PY"
# Each should print an absolute path to a `python` binary; none should be empty.
$TIMEKAN_PY benchmark_ltsf_full.py \
--datasets ETTh1,ETTh2,ETTm1,ETTm2,Weather,Electricity \
--pred-lens 96,192,336,720
This trains TimeKAN with the canonical hyperparameters used in the paper and saves checkpoints to TimeKAN/checkpoints/{setting}/checkpoint.pth. Note that the bundled TimeKAN scripts in TimeKAN/scripts/<dataset>/ use slightly different hyperparameters (des='Exp') and are not used in this paper.
$TIMEKAN_PY benchmark_ensemble_ctx512.py \
--fms TTM,Timer,TimesFM,UniTS,Moirai \
--datasets ETTh1,ETTh2,ETTm1,ETTm2,Weather,Electricity \
--pred-lens 96,192,336,720
This appends/updates rows in paper_results/ensemble_ctx512/ensemble_unconstrained.csv (see the heads-up note in §5 about preserving the canonical CSV).
$TIMEKAN_PY build_predictions_npz.py # Build per-config .npz from caches
$TIMEKAN_PY run_nnls_decorrselect.py \
--pred_dir ./predictions \
--threshold_sweep \
--output_json decorrselect_results.json
$TIMEKAN_PY threshold_sensitivity.py
$TIMEKAN_PY make_figures.py
Outputs the 4 figures (PNG + PDF) to paper_results/ensemble_ctx512/figures/.
A fast end-to-end check that exercises TimeKAN training → FM inference subprocess → OLS stacking → figures. Requires $TIMEKAN_PY and $FRONTIER_PY (TTM env) exported per §2; other FM env vars are not needed since this only runs TTM.
# (a) Train TimeKAN on ETTh1 H=96
$TIMEKAN_PY benchmark_ltsf_full.py --models TimeKAN --datasets ETTh1 --pred-lens 96
# Expected: final mse: ≈ 0.367 (deterministic on the same hardware with SEED=2021)
# (b) Run TTM-only ensemble for the same combo
$TIMEKAN_PY benchmark_ensemble_ctx512.py --fms TTM --datasets ETTh1 --pred-lens 96
# (c) Structural verification — Hybrid-TTM should beat both TimeKAN and raw TTM
$TIMEKAN_PY - <<'PY'
import pandas as pd
df = pd.read_csv('paper_results/ensemble_ctx512/ensemble_unconstrained.csv')
sub = df[(df.Dataset=='ETTh1') & (df.Horizon==96)].drop_duplicates('Model', keep='last')
tk = sub[sub.Model=='TimeKAN'].MSE.iloc[0]
ht = sub[sub.Model=='Hybrid-TTM'].MSE.iloc[0]
ttm = sub[sub.Model=='TTM'].MSE.iloc[0]
print(f'TimeKAN MSE = {tk:.4f}')
print(f'TTM MSE = {ttm:.4f}')
print(f'Hybrid-TTM MSE = {ht:.4f}')
assert ht < tk and ht < ttm, 'Ensemble should beat both components'
print('OK ensemble pipeline reproduces qualitative result')
PY
# (d) Generate figures
$TIMEKAN_PY make_figures.py
A successful run will print OK ensemble pipeline reproduces qualitative result in step (c) and emit 8 files (4 figures × {png, pdf}) in step (d). Note: a fresh re-train produces slightly different absolute MSEs than the cached CSVs due to non-deterministic CUDA ops; we verify qualitative match (ensembling improves over both components), not bitwise equality.
6 commits
Python
100.0%
Code accompanying the paper "" (NeurIPS 2026 submission).
We study whether ensembling TimeKAN with five time-series foundation models (TTM, Timer, TimesFM, UniTS, Moirai) improves long-horizon forecasting on standard LTSF benchmarks (ETTh1/2, ETTm1/2, Weather, Electricity), and analyze when and why the resulting hybrid succeeds or fails.
.
├── benchmark_ltsf_full.py # Train TimeKAN + run FM inference (canonical hparams)
├── benchmark_ensemble_ctx512.py # Main ensembling: TimeKAN + FM stacking (OLS / NNLS)
├── build_predictions_npz.py # Bridges benchmark caches → npz format for the above
├── run_nnls_decorrselect.py # NNLS oracle, DecorrSelect, Diebold-Mariano tests
├── threshold_sensitivity.py # Decorrelation percentile sweep (Pearson r, DecorrScore)
├── analyze_sample_errors.py # Per-sample error coupling analysis
├── make_figures.py # Generate the 4 paper figures from cached CSVs/JSON
├── envs/ # Per-env package hint files
│ ├── timekan.txt # TimeKAN training + orchestration
│ ├── ttm.txt # TTM
│ ├── timesfm-moirai.txt # TimesFM + Moirai
│ ├── units.txt # UniTS
│ └── timer.txt # Timer
│
├── UpdatedFMs/ # FM inference wrappers (one per FM)
│ ├── ttm_infer.py
│ ├── timer_infer.py
│ ├── timesfm_infer.py
│ ├── units_infer.py
│ ├── moirai_infer.py
│ └── UniTS/ # git submodule → mims-harvard/UniTS
│
├── TimeKAN/ # git submodule → huangst21/TimeKAN
│
└── paper_results/ensemble_ctx512/
├── ensemble_unconstrained.csv # All MSE/MAE results (288 rows)
├── ols_weights_unconstrained.csv # Per-(dataset, horizon, FM) OLS weights
├── pivot_mse_unconstrained.csv # MSE pivot for side-by-side comparison
├── pivot_mae_unconstrained.csv
├── decorrselect_results.json # NNLS / DecorrSelect / DM stats
├── paper_tables/ # Final tables used in the paper
├── sample_error_analysis/ # Decorrelation analysis outputs
└── figures/ # Output of make_figures.py (PNG + PDF)
git clone --recurse-submodules https://github.com/fnafees/TimeKanEnsemble.git
cd timekan-ensemble-paper
If you already cloned without --recurse-submodules:
git submodule update --init --recursive
Five conda envs are required because the FMs' upstream packages have mutually incompatible dependency pins (notably transformers versions and torch versions):
| Env name (suggested) | Used for | Key packages | Hint file |
|---|---|---|---|
timekan | TimeKAN training, ensembling, figures, analysis | torch 1.13, sktime | envs/timekan.txt |
ttm | TTM inference | tsfm_public | envs/ttm.txt |
timesfm-moirai | TimesFM and Moirai inference | timesfm, uni2ts, transformers (mainline) | envs/timesfm-moirai.txt |
units | UniTS inference | timm, torch | envs/units.txt |
timer | Timer inference | custom transformers fork | envs/timer.txt |
Create each env, then pip install -r envs/<env>.txt as a starting point and resolve any GPU/CUDA-version conflicts locally. The hint files are pip freeze snapshots from the dev machine, not strict lockfiles.
Then export the python interpreter paths so the orchestrating scripts can launch FM subprocesses in the right env:
export TIMEKAN_PY=/path/to/conda/envs/timekan/bin/python
export FRONTIER_PY=/path/to/conda/envs/ttm/bin/python # TTM only
export ENS_PY=/path/to/conda/envs/timesfm-moirai/bin/python # TimesFM + Moirai
export UNITS_PY=/path/to/conda/envs/units/bin/python # UniTS
export TIMER_PY=/path/to/conda/envs/timer/bin/python # Timer
If unset, scripts fall back to sys.executable. The orchestrating scripts themselves (benchmark_ltsf_full.py, benchmark_ensemble_ctx512.py, make_figures.py, run_nnls_decorrselect.py, build_predictions_npz.py, threshold_sensitivity.py, analyze_sample_errors.py) should always be invoked with $TIMEKAN_PY. The env vars above tell those orchestrators which interpreter to use when they spawn FM-inference subprocesses.
The historical env-var names (FRONTIER_PY, ENS_PY, etc.) are kept for code-stability; the FM-to-env mapping is hardcoded as FM_ENVS in benchmark_ensemble_ctx512.py if you need to verify it.
UniTS requires the public units_x128_pretrain_checkpoint.pth. Download it from the UniTS HuggingFace page and place it at:
UpdatedFMs/units_x128_pretrain_checkpoint.pth
Standard LTSF datasets used in the paper:
TimeKAN/dataset/ETT-small/)weather/weather.csv and electricity/electricity.csv files; place under TimeKAN/dataset/weather/ and TimeKAN/dataset/electricity/ respectively)Both benchmark_ltsf_full.py and benchmark_ensemble_ctx512.py read from TimeKAN/dataset/ via the TimeKAN data loader.
SEED=2021 is set at the top of benchmark_ltsf_full.py and benchmark_ensemble_ctx512.py. On the same hardware, two runs of benchmark_ltsf_full.py --datasets ETTh1 --pred-lens 96 produce identical losses to all printed digits (verified on Quadro RTX 8000, CUDA 12.x, torch 2.x).
torch.use_deterministic_algorithms(True) is not set — some FM ops (e.g., scatter operations in TimesFM) are not deterministic-safe. Different GPU architectures may produce drift in the 4th decimal of MSE.
The cached CSVs and JSON in paper_results/ensemble_ctx512/ were produced from one specific full sweep on Quadro RTX 8000. A fresh re-train will produce slightly different absolute numbers (typically 1–3% variation) due to non-deterministic CUDA ops; e.g., the cached TimeKAN ETTh1 H=96 MSE is 0.3746, while a fresh re-train on the same hardware produces 0.3669. Reviewers should expect qualitative match (model rankings, OLS-vs-NNLS gap, sign of decorrelation correlations), not bitwise equality.
Heads up: Re-running
benchmark_ensemble_ctx512.pyappends/updates rows in the cachedpaper_results/ensemble_ctx512/ensemble_unconstrained.csv. If you want to preserve the canonical CSV as a reference, copy it aside first (e.g.,cp ensemble_unconstrained.csv ensemble_unconstrained.canonical.csv) before re-running.
run_nnls_decorrselect.py + threshold_sensitivity.py + analyze_sample_errors.py: <5 min combined.make_figures.py: <30 s.All commands assume the env vars from §2 are exported. As a quick check:
echo "$TIMEKAN_PY" "$FRONTIER_PY" "$ENS_PY" "$UNITS_PY" "$TIMER_PY"
# Each should print an absolute path to a `python` binary; none should be empty.
$TIMEKAN_PY benchmark_ltsf_full.py \
--datasets ETTh1,ETTh2,ETTm1,ETTm2,Weather,Electricity \
--pred-lens 96,192,336,720
This trains TimeKAN with the canonical hyperparameters used in the paper and saves checkpoints to TimeKAN/checkpoints/{setting}/checkpoint.pth. Note that the bundled TimeKAN scripts in TimeKAN/scripts/<dataset>/ use slightly different hyperparameters (des='Exp') and are not used in this paper.
$TIMEKAN_PY benchmark_ensemble_ctx512.py \
--fms TTM,Timer,TimesFM,UniTS,Moirai \
--datasets ETTh1,ETTh2,ETTm1,ETTm2,Weather,Electricity \
--pred-lens 96,192,336,720
This appends/updates rows in paper_results/ensemble_ctx512/ensemble_unconstrained.csv (see the heads-up note in §5 about preserving the canonical CSV).
$TIMEKAN_PY build_predictions_npz.py # Build per-config .npz from caches
$TIMEKAN_PY run_nnls_decorrselect.py \
--pred_dir ./predictions \
--threshold_sweep \
--output_json decorrselect_results.json
$TIMEKAN_PY threshold_sensitivity.py
$TIMEKAN_PY make_figures.py
Outputs the 4 figures (PNG + PDF) to paper_results/ensemble_ctx512/figures/.
A fast end-to-end check that exercises TimeKAN training → FM inference subprocess → OLS stacking → figures. Requires $TIMEKAN_PY and $FRONTIER_PY (TTM env) exported per §2; other FM env vars are not needed since this only runs TTM.
# (a) Train TimeKAN on ETTh1 H=96
$TIMEKAN_PY benchmark_ltsf_full.py --models TimeKAN --datasets ETTh1 --pred-lens 96
# Expected: final mse: ≈ 0.367 (deterministic on the same hardware with SEED=2021)
# (b) Run TTM-only ensemble for the same combo
$TIMEKAN_PY benchmark_ensemble_ctx512.py --fms TTM --datasets ETTh1 --pred-lens 96
# (c) Structural verification — Hybrid-TTM should beat both TimeKAN and raw TTM
$TIMEKAN_PY - <<'PY'
import pandas as pd
df = pd.read_csv('paper_results/ensemble_ctx512/ensemble_unconstrained.csv')
sub = df[(df.Dataset=='ETTh1') & (df.Horizon==96)].drop_duplicates('Model', keep='last')
tk = sub[sub.Model=='TimeKAN'].MSE.iloc[0]
ht = sub[sub.Model=='Hybrid-TTM'].MSE.iloc[0]
ttm = sub[sub.Model=='TTM'].MSE.iloc[0]
print(f'TimeKAN MSE = {tk:.4f}')
print(f'TTM MSE = {ttm:.4f}')
print(f'Hybrid-TTM MSE = {ht:.4f}')
assert ht < tk and ht < ttm, 'Ensemble should beat both components'
print('OK ensemble pipeline reproduces qualitative result')
PY
# (d) Generate figures
$TIMEKAN_PY make_figures.py
A successful run will print OK ensemble pipeline reproduces qualitative result in step (c) and emit 8 files (4 figures × {png, pdf}) in step (d). Note: a fresh re-train produces slightly different absolute MSEs than the cached CSVs due to non-deterministic CUDA ops; we verify qualitative match (ensembling improves over both components), not bitwise equality.
6 commits
Python
100.0%