Upload folder using huggingface_hub
Browse files- README.md +15 -5
- index.html +337 -18
- meta.json +1 -0
- shim.js +183 -0
- weights.bin +3 -0
README.md
CHANGED
|
@@ -1,10 +1,20 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: tinycast
|
| 3 |
+
emoji: 📉
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: Tiny models vs transformers on the ETTh1 benchmark
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# tinycast
|
| 13 |
+
|
| 14 |
+
Do you need a transformer to forecast? In-browser demo of tiny forecasting
|
| 15 |
+
models (NLinear, DLinear + baselines) on the ETTh1 benchmark — inference runs
|
| 16 |
+
as plain JavaScript matrix products, no backend. The ETTh1 CSV is fetched
|
| 17 |
+
directly from [ETDataset](https://github.com/zhouhaoyi/ETDataset) at load time.
|
| 18 |
+
|
| 19 |
+
Source, training code and full evaluation (TCN, LSTM, published transformer
|
| 20 |
+
comparison): https://github.com/UsmarHaider/tinycast
|
index.html
CHANGED
|
@@ -1,19 +1,338 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<title>tinycast — do you need a transformer to forecast?</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
color-scheme: light;
|
| 10 |
+
--surface: #fcfcfb; --plane: #f9f9f7;
|
| 11 |
+
--ink: #0b0b0b; --ink-2: #52514e; --muted: #898781;
|
| 12 |
+
--grid: #e1e0d9; --axis: #c3c2b7; --ring: rgba(11,11,11,.10);
|
| 13 |
+
--s1: #2a78d6; --s2: #eb6834; --s3: #1baf7a; --s4: #eda100; --s5: #e87ba4; --s6: #008300;
|
| 14 |
+
}
|
| 15 |
+
@media (prefers-color-scheme: dark) {
|
| 16 |
+
:root:not([data-theme="light"]) {
|
| 17 |
+
color-scheme: dark;
|
| 18 |
+
--surface: #1a1a19; --plane: #0d0d0d;
|
| 19 |
+
--ink: #ffffff; --ink-2: #c3c2b7; --muted: #898781;
|
| 20 |
+
--grid: #2c2c2a; --axis: #383835; --ring: rgba(255,255,255,.10);
|
| 21 |
+
--s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500; --s5: #d55181; --s6: #008300;
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
* { box-sizing: border-box; }
|
| 25 |
+
body {
|
| 26 |
+
margin: 0; background: var(--plane); color: var(--ink);
|
| 27 |
+
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
| 28 |
+
}
|
| 29 |
+
.wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 48px; }
|
| 30 |
+
header h1 { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
|
| 31 |
+
header h1 .accent { color: var(--s1); }
|
| 32 |
+
header p.tag { margin: 6px 0 0; color: var(--ink-2); max-width: 64ch; }
|
| 33 |
+
.card {
|
| 34 |
+
background: var(--surface); border: 1px solid var(--ring); border-radius: 12px;
|
| 35 |
+
padding: 16px 18px; margin-top: 18px;
|
| 36 |
+
}
|
| 37 |
+
.controls { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; }
|
| 38 |
+
.seg { display: inline-flex; border: 1px solid var(--ring); border-radius: 8px; overflow: hidden; }
|
| 39 |
+
.seg button {
|
| 40 |
+
border: 0; background: transparent; color: var(--ink-2); padding: 6px 14px;
|
| 41 |
+
font: inherit; cursor: pointer;
|
| 42 |
+
}
|
| 43 |
+
.seg button.on { background: var(--s1); color: #fff; }
|
| 44 |
+
.controls label { color: var(--ink-2); font-size: 13px; }
|
| 45 |
+
input[type=range] { width: 190px; accent-color: var(--s1); vertical-align: middle; }
|
| 46 |
+
.btn {
|
| 47 |
+
border: 1px solid var(--ring); background: transparent; color: var(--ink);
|
| 48 |
+
border-radius: 8px; padding: 6px 14px; font: inherit; cursor: pointer;
|
| 49 |
+
}
|
| 50 |
+
.btn:hover { border-color: var(--s1); color: var(--s1); }
|
| 51 |
+
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
| 52 |
+
.chip {
|
| 53 |
+
display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--ring);
|
| 54 |
+
border-radius: 999px; padding: 3px 12px; font-size: 13px; color: var(--ink-2);
|
| 55 |
+
background: transparent; cursor: pointer;
|
| 56 |
+
}
|
| 57 |
+
.chip .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--c); }
|
| 58 |
+
.chip.off { opacity: .38; }
|
| 59 |
+
.chip.fixed { cursor: default; }
|
| 60 |
+
.chip .dot.dash { border-radius: 1px; height: 3px; }
|
| 61 |
+
#chartbox { position: relative; margin-top: 6px; }
|
| 62 |
+
svg { display: block; width: 100%; height: auto; }
|
| 63 |
+
.tip {
|
| 64 |
+
position: absolute; pointer-events: none; display: none; background: var(--surface);
|
| 65 |
+
border: 1px solid var(--ring); border-radius: 8px; padding: 8px 10px; font-size: 12px;
|
| 66 |
+
box-shadow: 0 4px 14px rgba(0,0,0,.12); min-width: 150px; z-index: 2;
|
| 67 |
+
}
|
| 68 |
+
.tip .row { display: flex; justify-content: space-between; gap: 12px; }
|
| 69 |
+
.tip .row .val { font-variant-numeric: tabular-nums; color: var(--ink); }
|
| 70 |
+
.tip .row .name { color: var(--ink-2); }
|
| 71 |
+
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-top: 18px; }
|
| 72 |
+
.tile { background: var(--surface); border: 1px solid var(--ring); border-radius: 12px; padding: 14px 16px; }
|
| 73 |
+
.tile .k { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
|
| 74 |
+
.tile .v { font-size: 26px; margin-top: 2px; }
|
| 75 |
+
.tile .s { font-size: 12px; color: var(--ink-2); }
|
| 76 |
+
.tablecard { overflow-x: auto; }
|
| 77 |
+
table { border-collapse: collapse; width: 100%; font-size: 13.5px; margin-top: 4px; }
|
| 78 |
+
th, td { text-align: right; padding: 6px 10px; border-bottom: 1px solid var(--grid); font-variant-numeric: tabular-nums; }
|
| 79 |
+
th:first-child, td:first-child { text-align: left; }
|
| 80 |
+
th { color: var(--muted); font-weight: 600; }
|
| 81 |
+
td.best { color: var(--s1); font-weight: 700; }
|
| 82 |
+
tr.pub td { color: var(--ink-2); }
|
| 83 |
+
tr.sep td { border-top: 2px solid var(--axis); }
|
| 84 |
+
.note { color: var(--muted); font-size: 12.5px; margin-top: 8px; }
|
| 85 |
+
footer { margin-top: 26px; color: var(--muted); font-size: 13px; }
|
| 86 |
+
footer a { color: var(--s1); text-decoration: none; }
|
| 87 |
+
#err { display: none; margin-top: 18px; border: 1px solid #d03b3b; color: #d03b3b;
|
| 88 |
+
border-radius: 10px; padding: 10px 14px; }
|
| 89 |
+
</style>
|
| 90 |
+
</head>
|
| 91 |
+
<body>
|
| 92 |
+
<div class="wrap">
|
| 93 |
+
<header>
|
| 94 |
+
<h1>tiny<span class="accent">cast</span></h1>
|
| 95 |
+
<p class="tag">Do you need a transformer to forecast? Five tiny models — none bigger than
|
| 96 |
+
80k parameters — against the ETTh1 long-horizon benchmark, evaluated exactly like the papers.
|
| 97 |
+
Pick a test window and see for yourself.</p>
|
| 98 |
+
</header>
|
| 99 |
+
|
| 100 |
+
<div id="err"></div>
|
| 101 |
+
|
| 102 |
+
<div class="card">
|
| 103 |
+
<div class="controls">
|
| 104 |
+
<span class="seg" id="horizon-seg"></span>
|
| 105 |
+
<label>window <input type="range" id="win" min="0" max="100" value="0">
|
| 106 |
+
<span id="winlab" style="font-variant-numeric:tabular-nums"></span></label>
|
| 107 |
+
<button class="btn" id="rand">Try a sample</button>
|
| 108 |
+
<span id="t0" style="color:var(--muted);font-size:13px"></span>
|
| 109 |
+
</div>
|
| 110 |
+
<div class="chips" id="chips"></div>
|
| 111 |
+
<div id="chartbox">
|
| 112 |
+
<svg id="chart" viewBox="0 0 940 400" role="img" aria-label="Forecast chart"></svg>
|
| 113 |
+
<div class="tip" id="tip"></div>
|
| 114 |
+
</div>
|
| 115 |
+
<div class="note">Oil temperature (°C) of electricity transformer 1, hourly. Left of the
|
| 116 |
+
divider: the last week of history the models see. Right: what each predicts vs. what happened.</div>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
<div class="tiles" id="tiles"></div>
|
| 120 |
+
|
| 121 |
+
<div class="card tablecard">
|
| 122 |
+
<strong>Test MSE (standardized, multivariate) — lower is better</strong>
|
| 123 |
+
<table id="results"></table>
|
| 124 |
+
<div class="note">Published rows are the numbers reported in Zeng et al. 2023
|
| 125 |
+
(<em>Are Transformers Effective for Time Series Forecasting?</em>, AAAI). Same dataset,
|
| 126 |
+
same split, same metric. Best per horizon in blue.</div>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<footer>
|
| 130 |
+
Built by <a href="https://github.com/UsmarHaider">Usmar Haider</a> ·
|
| 131 |
+
<a href="https://github.com/UsmarHaider/tinycast">source & write-up on GitHub</a> ·
|
| 132 |
+
data: <a href="https://github.com/zhouhaoyi/ETDataset">ETDataset</a>
|
| 133 |
+
</footer>
|
| 134 |
+
</div>
|
| 135 |
+
|
| 136 |
+
<script src="shim.js"></script>
|
| 137 |
+
<script>
|
| 138 |
+
"use strict";
|
| 139 |
+
const SERIES = [
|
| 140 |
+
{ key: "nlinear", label: "NLinear", css: "--s1", on: true },
|
| 141 |
+
{ key: "seasonal_naive", label: "Seasonal naive", css: "--s2", on: true },
|
| 142 |
+
{ key: "dlinear", label: "DLinear", css: "--s3", on: false },
|
| 143 |
+
{ key: "tcn", label: "TCN", css: "--s4", on: true },
|
| 144 |
+
{ key: "lstm", label: "LSTM", css: "--s5", on: false },
|
| 145 |
+
{ key: "persistence", label: "Persistence", css: "--s6", on: false },
|
| 146 |
+
];
|
| 147 |
+
const NICE = { persistence: "Persistence", seasonal_naive: "Seasonal naive", linear: "Linear",
|
| 148 |
+
nlinear: "NLinear", dlinear: "DLinear", tcn: "TCN", lstm: "LSTM" };
|
| 149 |
+
const state = { horizon: 96, index: 0, meta: null, sample: null, hist_show: 168 };
|
| 150 |
+
|
| 151 |
+
const $ = (id) => document.getElementById(id);
|
| 152 |
+
const css = (v) => getComputedStyle(document.documentElement).getPropertyValue(v).trim();
|
| 153 |
+
|
| 154 |
+
async function api(path) {
|
| 155 |
+
const r = await fetch(path);
|
| 156 |
+
if (!r.ok) throw new Error(path + " → " + r.status);
|
| 157 |
+
return r.json();
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
function fail(msg) { const e = $("err"); e.style.display = "block"; e.textContent = msg; }
|
| 161 |
+
|
| 162 |
+
function segButtons() {
|
| 163 |
+
const seg = $("horizon-seg");
|
| 164 |
+
seg.innerHTML = "";
|
| 165 |
+
for (const h of state.meta.horizons) {
|
| 166 |
+
const b = document.createElement("button");
|
| 167 |
+
b.textContent = h + " h";
|
| 168 |
+
b.className = h === state.horizon ? "on" : "";
|
| 169 |
+
b.onclick = () => { state.horizon = h; clampIndex(); refresh(); };
|
| 170 |
+
seg.appendChild(b);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
function clampIndex() {
|
| 175 |
+
const n = state.meta.n_windows[String(state.horizon)];
|
| 176 |
+
state.index = Math.min(state.index, n - 1);
|
| 177 |
+
$("win").max = n - 1;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
function chips(available) {
|
| 181 |
+
const box = $("chips");
|
| 182 |
+
box.innerHTML = "";
|
| 183 |
+
const hist = document.createElement("span");
|
| 184 |
+
hist.className = "chip fixed";
|
| 185 |
+
hist.innerHTML = '<span class="dot" style="--c:var(--ink)"></span>history';
|
| 186 |
+
const act = document.createElement("span");
|
| 187 |
+
act.className = "chip fixed";
|
| 188 |
+
act.innerHTML = '<span class="dot dash" style="--c:var(--ink)"></span>actual';
|
| 189 |
+
box.append(hist, act);
|
| 190 |
+
for (const s of SERIES) {
|
| 191 |
+
if (!available.includes(s.key)) continue;
|
| 192 |
+
const b = document.createElement("button");
|
| 193 |
+
b.className = "chip" + (s.on ? "" : " off");
|
| 194 |
+
b.innerHTML = `<span class="dot" style="--c:var(${s.css})"></span>${s.label}`;
|
| 195 |
+
b.onclick = () => { s.on = !s.on; draw(); };
|
| 196 |
+
box.appendChild(b);
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
function draw() {
|
| 201 |
+
const d = state.sample;
|
| 202 |
+
if (!d) return;
|
| 203 |
+
const H = d.horizon, histN = state.hist_show;
|
| 204 |
+
const hist = d.history_ot.slice(-histN);
|
| 205 |
+
const W = 940, Hgt = 400, m = { t: 16, r: 14, b: 30, l: 46 };
|
| 206 |
+
const iw = W - m.l - m.r, ih = Hgt - m.t - m.b;
|
| 207 |
+
const x = (i) => m.l + ((i + histN) / (histN + H - 1)) * iw; // i in [-histN, H-1]
|
| 208 |
+
const active = SERIES.filter((s) => s.on && d.forecasts[s.key]);
|
| 209 |
+
let vals = hist.concat(d.actual_ot);
|
| 210 |
+
for (const s of active) vals = vals.concat(d.forecasts[s.key]);
|
| 211 |
+
const lo = Math.min(...vals), hi = Math.max(...vals);
|
| 212 |
+
const pad = (hi - lo) * 0.08 + 0.01;
|
| 213 |
+
const y = (v) => m.t + (1 - (v - (lo - pad)) / (hi - lo + 2 * pad)) * ih;
|
| 214 |
+
|
| 215 |
+
const line = (pts, color, dash, wdt) => {
|
| 216 |
+
const dstr = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join("");
|
| 217 |
+
return `<path d="${dstr}" fill="none" stroke="${color}" stroke-width="${wdt || 2}"` +
|
| 218 |
+
(dash ? ` stroke-dasharray="${dash}"` : "") + ` stroke-linejoin="round"/>`;
|
| 219 |
+
};
|
| 220 |
+
let g = "";
|
| 221 |
+
// gridlines + y labels
|
| 222 |
+
const ticks = 5;
|
| 223 |
+
for (let i = 0; i <= ticks; i++) {
|
| 224 |
+
const v = lo - pad + ((hi - lo + 2 * pad) * i) / ticks;
|
| 225 |
+
g += `<line x1="${m.l}" x2="${W - m.r}" y1="${y(v)}" y2="${y(v)}" stroke="${css("--grid")}" stroke-width="1"/>`;
|
| 226 |
+
g += `<text x="${m.l - 8}" y="${y(v) + 4}" text-anchor="end" font-size="11" fill="${css("--muted")}">${v.toFixed(1)}°</text>`;
|
| 227 |
+
}
|
| 228 |
+
// x labels every 48h
|
| 229 |
+
for (let t = -histN; t <= H; t += 48) {
|
| 230 |
+
g += `<text x="${x(t)}" y="${Hgt - 8}" text-anchor="middle" font-size="11" fill="${css("--muted")}">${t === 0 ? "now" : (t > 0 ? "+" + t : t) + "h"}</text>`;
|
| 231 |
+
}
|
| 232 |
+
// forecast-start divider
|
| 233 |
+
g += `<line x1="${x(0)}" x2="${x(0)}" y1="${m.t}" y2="${Hgt - m.b}" stroke="${css("--axis")}" stroke-width="1" stroke-dasharray="2 3"/>`;
|
| 234 |
+
// history + actual
|
| 235 |
+
g += line(hist.map((v, i) => [x(i - histN), y(v)]), css("--ink"), null, 2);
|
| 236 |
+
g += line(d.actual_ot.map((v, i) => [x(i), y(v)]), css("--ink"), "5 4", 2);
|
| 237 |
+
// forecasts
|
| 238 |
+
for (const s of active) g += line(d.forecasts[s.key].map((v, i) => [x(i), y(v)]), css(s.css), null, 2);
|
| 239 |
+
$("chart").innerHTML = g + `<rect id="hit" x="${m.l}" y="${m.t}" width="${iw}" height="${ih}" fill="transparent"/>`;
|
| 240 |
+
hover(x, histN, H, hist, active);
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
function hover(xf, histN, H, hist, active) {
|
| 244 |
+
const svg = $("chart"), tip = $("tip"), box = $("chartbox");
|
| 245 |
+
const d = state.sample;
|
| 246 |
+
svg.onmousemove = (ev) => {
|
| 247 |
+
const pt = svg.createSVGPoint(); pt.x = ev.clientX; pt.y = ev.clientY;
|
| 248 |
+
const p = pt.matrixTransform(svg.getScreenCTM().inverse());
|
| 249 |
+
const t = Math.round(((p.x - 46) / (940 - 46 - 14)) * (histN + H - 1) - histN);
|
| 250 |
+
if (t < -histN || t >= H) { tip.style.display = "none"; return; }
|
| 251 |
+
let rows = `<div class="row"><span class="name">${t >= 0 ? "+" + t + "h" : t + "h"}</span></div>`;
|
| 252 |
+
if (t < 0) {
|
| 253 |
+
rows += `<div class="row"><span class="name">history</span><span class="val">${hist[t + histN].toFixed(1)}°C</span></div>`;
|
| 254 |
+
} else {
|
| 255 |
+
rows += `<div class="row"><span class="name">actual</span><span class="val">${d.actual_ot[t].toFixed(1)}°C</span></div>`;
|
| 256 |
+
for (const s of active)
|
| 257 |
+
rows += `<div class="row"><span class="name" style="color:var(${s.css})">${s.label}</span><span class="val">${d.forecasts[s.key][t].toFixed(1)}°C</span></div>`;
|
| 258 |
+
}
|
| 259 |
+
tip.innerHTML = rows;
|
| 260 |
+
tip.style.display = "block";
|
| 261 |
+
const r = box.getBoundingClientRect();
|
| 262 |
+
const tx = ev.clientX - r.left + 14, ty = ev.clientY - r.top - 10;
|
| 263 |
+
tip.style.left = Math.min(tx, r.width - tip.offsetWidth - 6) + "px";
|
| 264 |
+
tip.style.top = ty + "px";
|
| 265 |
+
};
|
| 266 |
+
svg.onmouseleave = () => { tip.style.display = "none"; };
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
function tiles() {
|
| 270 |
+
const res = state.meta.results, box = $("tiles");
|
| 271 |
+
const ours = res.models, pub = res.published || {};
|
| 272 |
+
const h = "96";
|
| 273 |
+
const nl = ours.nlinear?.[h]?.mse, inf = pub["Informer (2021)"]?.[h]?.mse;
|
| 274 |
+
const tls = [];
|
| 275 |
+
if (nl != null && inf != null)
|
| 276 |
+
tls.push(["NLinear vs Informer, h=96", Math.round((1 - nl / inf) * 100) + "% lower MSE",
|
| 277 |
+
`${nl.toFixed(3)} vs ${inf.toFixed(3)}`]);
|
| 278 |
+
tls.push(["Winning model size", "32k params", "one 336×96 matrix + bias"]);
|
| 279 |
+
const otm = res.ot_mae_celsius_h96;
|
| 280 |
+
if (otm) tls.push(["Oil-temp error, 4 days out", otm.mae_c + " °C MAE", NICE[otm.model] + ", horizon 96 h"]);
|
| 281 |
+
const sn = ours.seasonal_naive?.[h]?.mse;
|
| 282 |
+
if (sn != null && inf != null && sn < inf)
|
| 283 |
+
tls.push(["Seasonal naive vs Informer", "baseline wins", `${sn.toFixed(3)} vs ${inf.toFixed(3)} MSE`]);
|
| 284 |
+
box.innerHTML = tls.map(([k, v, s]) =>
|
| 285 |
+
`<div class="tile"><div class="k">${k}</div><div class="v">${v}</div><div class="s">${s}</div></div>`).join("");
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
function table() {
|
| 289 |
+
const res = state.meta.results, horizons = res.horizons;
|
| 290 |
+
const rows = [];
|
| 291 |
+
const ordered = ["persistence", "seasonal_naive", "linear", "nlinear", "dlinear", "tcn", "lstm"];
|
| 292 |
+
for (const k of ordered) if (res.models[k]) rows.push([NICE[k] + " (ours)", res.models[k], false]);
|
| 293 |
+
for (const [k, v] of Object.entries(res.published || {})) rows.push([k, v, true]);
|
| 294 |
+
const best = {};
|
| 295 |
+
for (const h of horizons) {
|
| 296 |
+
best[h] = Math.min(...rows.map(([, v]) => v[String(h)]?.mse ?? Infinity));
|
| 297 |
+
}
|
| 298 |
+
let html = "<tr><th>model</th>" + horizons.map((h) => `<th>h=${h}</th>`).join("") + "</tr>";
|
| 299 |
+
let sep = false;
|
| 300 |
+
for (const [name, v, pub] of rows) {
|
| 301 |
+
const cls = (pub ? "pub" : "") + (pub && !sep ? " sep" : "");
|
| 302 |
+
if (pub) sep = true;
|
| 303 |
+
html += `<tr class="${cls}"><td>${name}</td>` + horizons.map((h) => {
|
| 304 |
+
const c = v[String(h)];
|
| 305 |
+
if (!c) return "<td>–</td>";
|
| 306 |
+
return `<td class="${c.mse === best[h] ? "best" : ""}">${c.mse.toFixed(3)}</td>`;
|
| 307 |
+
}).join("") + "</tr>";
|
| 308 |
+
}
|
| 309 |
+
$("results").innerHTML = html;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
async function refresh() {
|
| 313 |
+
segButtons();
|
| 314 |
+
const n = state.meta.n_windows[String(state.horizon)];
|
| 315 |
+
$("win").max = n - 1; $("win").value = state.index;
|
| 316 |
+
$("winlab").textContent = state.index + " / " + (n - 1);
|
| 317 |
+
try {
|
| 318 |
+
state.sample = await api(`api/sample?index=${state.index}&horizon=${state.horizon}`);
|
| 319 |
+
} catch (e) { fail("Could not load sample: " + e.message); return; }
|
| 320 |
+
$("t0").textContent = "forecast starts " + state.sample.t0;
|
| 321 |
+
chips(Object.keys(state.sample.forecasts));
|
| 322 |
+
draw();
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
(async function init() {
|
| 326 |
+
try { state.meta = await api("api/meta"); } catch (e) { fail("Could not reach the API: " + e.message); return; }
|
| 327 |
+
const q = new URLSearchParams(location.search);
|
| 328 |
+
const n = state.meta.n_windows[String(state.horizon)];
|
| 329 |
+
state.index = q.has("sample") ? Math.min(+q.get("sample"), n - 1) : 480 % n;
|
| 330 |
+
$("win").oninput = (e) => { state.index = +e.target.value; refresh(); };
|
| 331 |
+
$("rand").onclick = () => { state.index = Math.floor(Math.random() * n); refresh(); };
|
| 332 |
+
tiles(); table();
|
| 333 |
+
await refresh();
|
| 334 |
+
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", draw);
|
| 335 |
+
})();
|
| 336 |
+
</script>
|
| 337 |
+
</body>
|
| 338 |
</html>
|
meta.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"tensors": {"nlinear_96": {"w": {"offset": 0, "shape": [96, 336]}, "b": {"offset": 32256, "shape": [96]}}, "nlinear_336": {"w": {"offset": 32352, "shape": [336, 336]}, "b": {"offset": 145248, "shape": [336]}}, "dlinear_96": {"wt": {"offset": 145584, "shape": [96, 336]}, "bt": {"offset": 177840, "shape": [96]}, "ws": {"offset": 177936, "shape": [96, 336]}, "bs": {"offset": 210192, "shape": [96]}}, "dlinear_336": {"wt": {"offset": 210288, "shape": [336, 336]}, "bt": {"offset": 323184, "shape": [336]}, "ws": {"offset": 323520, "shape": [336, 336]}, "bs": {"offset": 436416, "shape": [336]}}}, "seq_len": 336, "horizons": [96, 336], "moving_avg": 25, "channels": ["HUFL", "HULL", "MUFL", "MULL", "LUFL", "LULL", "OT"], "target_index": 6, "scaler_mean": [7.937742245659508, 2.0210386567335163, 5.079770601157927, 0.7461858799957015, 2.781762386375555, 0.7884531235540096, 17.1282616982271], "scaler_std": [5.812749409143771, 2.0901046504076, 5.518793579036245, 1.926379274132982, 1.0235226594952191, 0.6302366362251923, 9.176491024944333], "test_start_row": 11184, "test_end_row": 14400, "data_url": "https://raw.githubusercontent.com/zhouhaoyi/ETDataset/main/ETT-small/ETTh1.csv", "results": {"seq_len": 336, "horizons": [96, 192, 336, 720], "models": {"persistence": {"96": {"mse": 1.2944, "mae": 0.7132}, "192": {"mse": 1.3249, "mae": 0.7331}, "336": {"mse": 1.3299, "mae": 0.746}, "720": {"mse": 1.3351, "mae": 0.755}}, "seasonal_naive": {"96": {"mse": 0.5122, "mae": 0.4333}, "192": {"mse": 0.5808, "mae": 0.4692}, "336": {"mse": 0.6499, "mae": 0.5008}, "720": {"mse": 0.6554, "mae": 0.5141}}, "linear": {"96": {"mse": 0.3952, "mae": 0.4191}, "192": {"mse": 0.447, "mae": 0.4569}, "336": {"mse": 0.4897, "mae": 0.4894}, "720": {"mse": 0.5275, "mae": 0.5292}}, "nlinear": {"96": {"mse": 0.3998, "mae": 0.4156}, "192": {"mse": 0.4228, "mae": 0.4288}, "336": {"mse": 0.4497, "mae": 0.4437}, "720": {"mse": 0.4552, "mae": 0.4646}}, "dlinear": {"96": {"mse": 0.395, "mae": 0.4184}, "192": {"mse": 0.4352, "mae": 0.4434}, "336": {"mse": 0.4739, "mae": 0.4711}, "720": {"mse": 0.5015, "mae": 0.5108}}, "tcn": {"96": {"mse": 0.3848, "mae": 0.414}, "192": {"mse": 0.4378, "mae": 0.4427}, "336": {"mse": 0.485, "mae": 0.4702}, "720": {"mse": 0.524, "mae": 0.5071}}, "lstm": {"96": {"mse": 0.4097, "mae": 0.4317}, "192": {"mse": 0.4541, "mae": 0.4557}, "336": {"mse": 0.4603, "mae": 0.4608}, "720": {"mse": 0.5274, "mae": 0.5129}}}, "published": {"Informer (2021)": {"96": {"mse": 0.865, "mae": 0.713}, "192": {"mse": 1.008, "mae": 0.792}, "336": {"mse": 1.107, "mae": 0.809}, "720": {"mse": 1.181, "mae": 0.865}}, "Autoformer (2021)": {"96": {"mse": 0.449, "mae": 0.459}, "192": {"mse": 0.5, "mae": 0.482}, "336": {"mse": 0.521, "mae": 0.496}, "720": {"mse": 0.514, "mae": 0.512}}, "FEDformer (2022)": {"96": {"mse": 0.376, "mae": 0.419}, "192": {"mse": 0.42, "mae": 0.448}, "336": {"mse": 0.459, "mae": 0.465}, "720": {"mse": 0.506, "mae": 0.507}}, "DLinear (paper)": {"96": {"mse": 0.375, "mae": 0.399}, "192": {"mse": 0.405, "mae": 0.416}, "336": {"mse": 0.439, "mae": 0.443}, "720": {"mse": 0.472, "mae": 0.49}}, "NLinear (paper)": {"96": {"mse": 0.374, "mae": 0.394}, "192": {"mse": 0.408, "mae": 0.415}, "336": {"mse": 0.429, "mae": 0.427}, "720": {"mse": 0.44, "mae": 0.453}}}, "ot_mae_celsius_h96": {"model": "tcn", "mae_c": 1.575}}}
|
shim.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* tinycast static-Space engine.
|
| 2 |
+
*
|
| 3 |
+
* In the browser it overrides window.fetch for the app's own /api/* routes and
|
| 4 |
+
* answers them client-side: weights come from weights.bin/meta.json in the
|
| 5 |
+
* Space, the raw ETTh1 CSV comes straight from GitHub (CORS: *), and the
|
| 6 |
+
* linear models run as plain matrix-vector products below. Under Node it just
|
| 7 |
+
* exports the math so the Python test suite can check JS/PyTorch parity.
|
| 8 |
+
*/
|
| 9 |
+
(function (root) {
|
| 10 |
+
"use strict";
|
| 11 |
+
|
| 12 |
+
// ---------- pure forecasting math (mirrors tinycast/models.py) ----------
|
| 13 |
+
|
| 14 |
+
function movingAverage(col, kernel) {
|
| 15 |
+
const L = col.length;
|
| 16 |
+
const left = Math.floor(kernel / 2);
|
| 17 |
+
const right = kernel - 1 - left;
|
| 18 |
+
const padded = new Float64Array(L + kernel - 1);
|
| 19 |
+
for (let i = 0; i < left; i++) padded[i] = col[0];
|
| 20 |
+
for (let i = 0; i < L; i++) padded[left + i] = col[i];
|
| 21 |
+
for (let i = 0; i < right; i++) padded[left + L + i] = col[L - 1];
|
| 22 |
+
const out = new Float64Array(L);
|
| 23 |
+
let sum = 0;
|
| 24 |
+
for (let i = 0; i < kernel; i++) sum += padded[i];
|
| 25 |
+
out[0] = sum / kernel;
|
| 26 |
+
for (let i = 1; i < L; i++) {
|
| 27 |
+
sum += padded[i + kernel - 1] - padded[i - 1];
|
| 28 |
+
out[i] = sum / kernel;
|
| 29 |
+
}
|
| 30 |
+
return out;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
function matVec(w, rows, cols, v, b) {
|
| 34 |
+
const out = new Float64Array(rows);
|
| 35 |
+
for (let r = 0; r < rows; r++) {
|
| 36 |
+
let s = b ? b[r] : 0;
|
| 37 |
+
const off = r * cols;
|
| 38 |
+
for (let c = 0; c < cols; c++) s += w[off + c] * v[c];
|
| 39 |
+
out[r] = s;
|
| 40 |
+
}
|
| 41 |
+
return out;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function nlinearForward(t, col, H) {
|
| 45 |
+
const L = col.length;
|
| 46 |
+
const last = col[L - 1];
|
| 47 |
+
const centered = new Float64Array(L);
|
| 48 |
+
for (let i = 0; i < L; i++) centered[i] = col[i] - last;
|
| 49 |
+
const out = matVec(t.w.data, H, L, centered, t.b.data);
|
| 50 |
+
for (let h = 0; h < H; h++) out[h] += last;
|
| 51 |
+
return out;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
function dlinearForward(t, col, H, kernel) {
|
| 55 |
+
const L = col.length;
|
| 56 |
+
const trend = movingAverage(col, kernel);
|
| 57 |
+
const seasonal = new Float64Array(L);
|
| 58 |
+
for (let i = 0; i < L; i++) seasonal[i] = col[i] - trend[i];
|
| 59 |
+
const out = matVec(t.wt.data, H, L, trend, t.bt.data);
|
| 60 |
+
const s = matVec(t.ws.data, H, L, seasonal, t.bs.data);
|
| 61 |
+
for (let h = 0; h < H; h++) out[h] += s[h];
|
| 62 |
+
return out;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
function persistence(col, H) {
|
| 66 |
+
return new Float64Array(H).fill(col[col.length - 1]);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
function seasonalNaive(col, H, period) {
|
| 70 |
+
period = period || 24;
|
| 71 |
+
const out = new Float64Array(H);
|
| 72 |
+
const start = col.length - period;
|
| 73 |
+
for (let h = 0; h < H; h++) out[h] = col[start + (h % period)];
|
| 74 |
+
return out;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
const math = { movingAverage, matVec, nlinearForward, dlinearForward, persistence, seasonalNaive };
|
| 78 |
+
|
| 79 |
+
if (typeof module !== "undefined" && module.exports) {
|
| 80 |
+
module.exports = math; // Node: parity tests only
|
| 81 |
+
return;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// ---------- browser: data loading + fetch override ----------
|
| 85 |
+
|
| 86 |
+
const realFetch = root.fetch.bind(root);
|
| 87 |
+
let enginePromise = null;
|
| 88 |
+
|
| 89 |
+
async function loadEngine() {
|
| 90 |
+
const meta = await (await realFetch("meta.json")).json();
|
| 91 |
+
const buf = await (await realFetch("weights.bin")).arrayBuffer();
|
| 92 |
+
const tensors = {};
|
| 93 |
+
for (const [model, parts] of Object.entries(meta.tensors)) {
|
| 94 |
+
tensors[model] = {};
|
| 95 |
+
for (const [name, t] of Object.entries(parts)) {
|
| 96 |
+
const size = t.shape.reduce((a, b) => a * b, 1);
|
| 97 |
+
tensors[model][name] = { data: new Float32Array(buf, t.offset * 4, size), shape: t.shape };
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
const csv = await (await realFetch(meta.data_url)).text();
|
| 101 |
+
const lines = csv.trim().split("\n");
|
| 102 |
+
const header = lines[0].split(",");
|
| 103 |
+
const otCol = header.indexOf("OT");
|
| 104 |
+
const mean = meta.scaler_mean[meta.target_index];
|
| 105 |
+
const std = meta.scaler_std[meta.target_index];
|
| 106 |
+
const dates = [];
|
| 107 |
+
const ot = new Float64Array(meta.test_end_row - meta.test_start_row);
|
| 108 |
+
for (let r = meta.test_start_row; r < meta.test_end_row; r++) {
|
| 109 |
+
const cells = lines[r + 1].split(",");
|
| 110 |
+
dates.push(cells[0]);
|
| 111 |
+
ot[r - meta.test_start_row] = (parseFloat(cells[otCol]) - mean) / std;
|
| 112 |
+
}
|
| 113 |
+
return { meta, tensors, ot, dates, mean, std };
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
function engine() {
|
| 117 |
+
if (!enginePromise) enginePromise = loadEngine();
|
| 118 |
+
return enginePromise;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
async function handle(url) {
|
| 122 |
+
const e = await engine();
|
| 123 |
+
const { meta } = e;
|
| 124 |
+
const L = meta.seq_len;
|
| 125 |
+
const nWindows = (h) => e.ot.length - L - h + 1;
|
| 126 |
+
|
| 127 |
+
const u = new URL(url, location.href);
|
| 128 |
+
if (u.pathname.endsWith("/api/meta") || u.pathname.endsWith("api/meta")) {
|
| 129 |
+
const n = {};
|
| 130 |
+
for (const h of meta.horizons) n[String(h)] = nWindows(h);
|
| 131 |
+
return {
|
| 132 |
+
horizons: meta.horizons,
|
| 133 |
+
n_windows: n,
|
| 134 |
+
models: ["persistence", "seasonal_naive", "nlinear", "dlinear"],
|
| 135 |
+
results: meta.results,
|
| 136 |
+
};
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
const index = parseInt(u.searchParams.get("index") || "0", 10);
|
| 140 |
+
const H = parseInt(u.searchParams.get("horizon") || "96", 10);
|
| 141 |
+
if (!meta.horizons.includes(H)) throw new Error("horizon not exported: " + H);
|
| 142 |
+
if (index < 0 || index >= nWindows(H)) throw new Error("index out of range");
|
| 143 |
+
|
| 144 |
+
const col = e.ot.subarray(index, index + L);
|
| 145 |
+
const actual = e.ot.subarray(index + L, index + L + H);
|
| 146 |
+
const toC = (a) => Array.from(a, (z) => Math.round((z * e.std + e.mean) * 1000) / 1000);
|
| 147 |
+
const forecasts = {
|
| 148 |
+
persistence: persistence(col, H),
|
| 149 |
+
seasonal_naive: seasonalNaive(col, H),
|
| 150 |
+
nlinear: nlinearForward(e.tensors["nlinear_" + H], col, H),
|
| 151 |
+
dlinear: dlinearForward(e.tensors["dlinear_" + H], col, H, meta.moving_avg),
|
| 152 |
+
};
|
| 153 |
+
const mae = {};
|
| 154 |
+
for (const [k, f] of Object.entries(forecasts)) {
|
| 155 |
+
let s = 0;
|
| 156 |
+
for (let h = 0; h < H; h++) s += Math.abs(f[h] - actual[h]);
|
| 157 |
+
mae[k] = Math.round((s / H) * e.std * 1000) / 1000;
|
| 158 |
+
}
|
| 159 |
+
const out = {};
|
| 160 |
+
for (const [k, f] of Object.entries(forecasts)) out[k] = toC(f);
|
| 161 |
+
return {
|
| 162 |
+
index,
|
| 163 |
+
n_windows: nWindows(H),
|
| 164 |
+
horizon: H,
|
| 165 |
+
t0: e.dates[index + L],
|
| 166 |
+
history_ot: toC(col),
|
| 167 |
+
actual_ot: toC(actual),
|
| 168 |
+
forecasts: out,
|
| 169 |
+
window_mae_c: mae,
|
| 170 |
+
};
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
root.fetch = function (url, opts) {
|
| 174 |
+
const u = String(url);
|
| 175 |
+
if (u.startsWith("api/") || u.startsWith("/api/")) {
|
| 176 |
+
return handle(u).then(
|
| 177 |
+
(data) => new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" } }),
|
| 178 |
+
(err) => new Response(String(err && err.message), { status: 500 })
|
| 179 |
+
);
|
| 180 |
+
}
|
| 181 |
+
return realFetch(url, opts);
|
| 182 |
+
};
|
| 183 |
+
})(typeof window !== "undefined" ? window : globalThis);
|
weights.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9f63b761d52d915c9c6182e1c9352bd648e71b9b53030a6fea9186dddddd9684
|
| 3 |
+
size 1747008
|