Skip to content

Rust · privacy-governed · open research

Forecast the signal.
Show your uncertainty,
not just your answer.

RuForecast is an independent multivariate forecasting engine for ambient and RF-derived sensing signals — built to predict a range of plausible futures, not a single confident guess, and to prove every claim it makes.

3
Rust crates
core · model · train
7
forecast quantiles
per prediction
CPU/CUDA/WGPU
Burn backends,
one codebase
0
unproven accuracy
claims made

What it does

One model, several coupled signals, honest ranges.

Feed RuForecast a window of recent readings across several correlated variates — heart rate, breathing rate, signal quality, presence, motion — and it predicts what each one does next, as a distribution, not a point.

Multivariate, coupled

Variates are modeled together, not one signal at a time — permutation-equivariant attention lets it handle any number of correlated inputs.

Quantiles, not one number

Every prediction ships as seven ordered quantiles — an honest spread of plausible outcomes, ready for a real 80% interval, not a false-confidence point estimate.

Missing data, handled

Real sensors drop frames. RuForecast's masking is a first-class input, not a preprocessing afterthought — gaps are modeled, not imputed away silently.

Typed privacy policy

Every training run carries a declared privacy class, tenant scope, retention window and consent record in its own type — not a comment, an enforced field.

How it's built

A clean-room architecture, Burn-native, backend-neutral.

No borrowed model weights, no wrapped third-party checkpoint. Masked patch tokens, gated depthwise temporal mixing, permutation-equivariant variate attention, and an ordered-quantile head — composed from generic primitives, described in full in the project's own ADRs.

ruforecast-coreSeries, splits, metrics, privacy policy — Burn-free contracts
ruforecast-modelThe network itself — masked patches, temporal mixing, quantile head
ruforecast-trainLocal + fal.ai orchestration, the ruforecast CLI
context window dashed = dropped frame patchify patch tokens masked temporal mix per-signal gating cross-variate variate attention decode 7 quantiles
The real forward path, left to right: a masked context window becomes patch tokens; a gated depthwise convolution mixes each signal's own history; permutation-equivariant attention cross-references signals regardless of their order; an ordered-quantile head decodes seven plausible futures per step, not one.

Three backends, one model

CPU (ndarray) for portability and CI, CUDA for Linux/NVIDIA training, WGPU for cross-platform GPU inference — same architecture, feature-gated at compile time.

#![forbid(unsafe_code)]

The model crate carries zero unsafe blocks. Configuration and artifact validation are exposed even with Burn disabled entirely, so the rest of the workspace never has to pull in a GPU toolchain to type-check against it.

How improvements get trusted

A hyperparameter can't promote itself.

RuForecast pairs with two governed pipelines built alongside it: Darwin Mode searches the hyperparameter space; Autogenous cryptographically verifies any candidate before it's trusted — never the other way around.

parent genome Darwin mutates candidate A candidate B candidate C score × N corpora signed receipt signed receipt signed receipt beats parent by margin? rejected rejected rejected promoted not yet reached
The real gate, drawn as it currently stands: every candidate Darwin has proposed so far has been independently re-scored on fresh, out-of-search corpora by Autogenous and rejected for not clearing its parent by a real margin. The "promoted" path exists and is enforced — it just hasn't been earned yet.

Darwin Mode — search

A numeric-genome evolution engine mutates and scores candidate hyperparameter sets against held-out weighted quantile loss, evaluated across multiple independent synthetic corpora — not one fixed dataset a search could quietly overfit to.

Autogenous — verify

A candidate that "wins" a search still needs signed evaluation receipts from independent judges, on out-of-search data, beating its parent by a real margin, before a promotion gate will even consider it. No generated candidate promotes itself.

Usage

Train and evaluate locally, in three commands.

The ruforecast CLI trains from typed local JSONL, scores against last-value and seasonal-naive baselines, and reports weighted quantile loss, per-horizon breakdown, interval coverage, and missingness — real numbers, not a demo mode.

# build the CLI
cargo build --release -p ruforecast-train --features cpu,cli --bin ruforecast

# generate a synthetic dataset and train a candidate
./ruforecast prepare-synthetic-dataset --directory ./run --train-windows 24
./ruforecast train-local --request ./run/train-local.toml \
  --dataset-root ./run --output ./run/artifacts

# score it against the trivial baselines it has to beat
./ruforecast evaluate --candidate ./run/artifacts/*/model.mpk \
  --test-jsonl ./run/test.jsonl

Benefits

What you actually get.

No opaque forecasts

Uncertainty is a first-class output, not an afterthought — downstream systems can act on a range, abstain on low confidence, or flag a widening interval as a signal in itself.

Governed by construction

Privacy class, retention, and consent are typed fields checked before training runs, not policy documents someone has to remember to follow.

Verifiable, not just tested

A hyperparameter improvement carries a signed, replayable receipt — anyone can independently re-check that a promoted candidate really did beat its parent.

Runs where your hardware is

The same model compiles for a CI runner's CPU, a training rig's CUDA GPU, or a portable WGPU target — pick the backend, not a different codebase.

Research status — read this before you trust a number

No configuration has yet been shown to reliably beat trivial forecasting baselines out-of-sample.

Two independent hyperparameter searches this project ran — each cryptographically verified against fresh, out-of-search synthetic data — found that at the current small training-corpus scale, dataset noise dominates any real hyperparameter effect. Neither search's "winner" held up under independent verification.

WQL 0 lower is better ↓ corpus A 0.099 0.178 corpus B 0.109 0.314 trivial baseline searched candidate
Real numbers from the second, post-fix search round, re-scored on out-of-search data — the candidate lost on both corpora it was independently checked against.
  • This is reported here on purpose, not buried in an issue tracker — the project's standing rule is that every accuracy claim is tagged MEASURED, CLAIMED, or SYNTHETIC, and nothing is claimed as proven until it is.
  • The credible next lever was more real training data, not further search on a small synthetic fixture — that ran (see the real-data update below).
  • Full evidence, both search rounds, and the retraction are recorded in docs/benchmarks/ruforecast.md.

Update: real household data (2026-09-01) — same conclusion, now confirmed on real data too.

The "more real training data" lever above is no longer just a plan — it ran. 6,390 real 1 Hz vital-signs samples from a live household sensor (88.75% real coverage, gaps handled honestly, never fabricated) were split two genuinely independent ways and each independently, cryptographically verified through the real Autogenous regression-candidate promotion path — not self-reported.

judge (real split) model WQL best baseline WQL margin vs. 0.01 bar
70% train / 90s embargo / 30% test (27 windows) 0.0514 0.0563 +0.0049 — below margin, rejected
50% train / 90s embargo / 50% test (46 windows) 0.0670 0.0543 −0.0128 — lost outright

Signed verdict: REJECT. The first split nominally beat the baseline, but by less than the required non-inferiority margin — it doesn't clear the bar on its own terms. The second split lost outright. Same shape of result as every synthetic search on this page: a win on one split does not survive independent verification on another.

This isn't evidence that real data can't help — it's evidence that 6,390 samples from one household is not yet enough to separate a genuine effect from split-dependent noise. Full numbers, both splits, and the signed rejection are recorded in docs/benchmarks/ruforecast.md.

Update: real public dataset, cross-entity holdout (2026-09-02) — same conclusion, on 53 different real people.

Every real-data test above used one household with only a temporal holdout — same sensor, different time windows. This test is the first genuine cross-entity holdout: 53 real ICU patients from the BIDMC PPG and Respiration Dataset (PhysioNet, Open Data Commons Attribution License v1.0, public and openly licensed), split by patient so the held-out test set contains real people the model never saw during training. 25,546 real 1 Hz rows, heart rate + respiratory rate + SpO2; 3 of 53 patients excluded honestly for genuine sensor-dropout gaps in the source recordings, not fabricated.

judge (real patient split) model WQL best baseline WQL result
34 train / 16 test patients (contiguous split) 0.01964 0.01159 worse, +69%
24 train / 26 test patients (interleaved split) 0.07022 0.01002 worse, +601%

Same conclusion, now on real, public, multi-subject clinical data with genuine cross-patient generalization. The margin is decisive on both independent splits, not a near-miss — the hypothesis that a larger, genuinely diverse real dataset (many different people, not one household) might change the picture does not hold at this model configuration either.

Honest scope note: unlike the household-data entry above, this result was not additionally run through Autogenous's signed promotion path — it is real, independent, out-of-sample evidence (two disjoint real patient partitions), but without a cryptographically signed verdict. Full numbers and methodology recorded in docs/benchmarks/ruforecast.md.