latentplant
Learned world models for industrial process time series. An action-conditioned model of process dynamics that can be rolled forward in imagination, carries calibrated uncertainty, and supports what-if queries, planning and regime-change detection.
What a world model is here, and why it is not a forecaster
A forecaster maps history to a future. A world model maps history and a proposed action sequence to a distribution over futures:
rollout(obs_ctx, act_ctx, exo_ctx, act_fut, exo_fut, n_samples=64)
act_fut is an argument. That single difference is what makes counterfactual questions
expressible ("what if we raise the amine dose for the next four hours") and what makes planning
possible at all. A model that ignores its action input is a forecaster wearing a costume, so the
test suite asserts direction-of-effect on a system with known gain.
The disciplines baked in
Rollout-first evaluation. One-step teacher-forced error is not a world-model metric: on any
autocorrelated process series it flatters a model that has learned nothing. Everything in
latentplant.metrics is indexed by horizon, and break_even_horizon reports where the model
stops beating persistence. That integer belongs next to every headline number.
Two uncertainties, reported separately. uncertainty_split returns aleatoric (process and
sensor noise, irreducible) and epistemic (the model does not know this region) as different
arrays. A noisy tag and an unfamiliar operating point look identical in a single band and mean
completely different things; the epistemic band is what a regime-change alarm should watch.
Cadence is declared, not guessed. An hourly lab assay carried on 20-second rows repeats about
180 times. A model trained without masking those repeats learns to copy the previous row and
scores beautifully. PlantSchema takes update_seconds per tag and stale_mask marks the
repeats so the loss can ignore them.
Leakage-safe by construction. Episodes never cross an acquisition gap, windows never cross an episode, splits are chronological, and standardization is fitted on train only.
Install
pip install latentplant # core: numpy + torch
pip install "latentplant[onnx]" # ONNX export for a browser inference lane
pip install "latentplant[baselines]" # sysidentpy / pysindy baseline adapters
Quick start
import numpy as np
from latentplant import (PlantSchema, TagSpec, ProbabilisticEnsemble,
split_on_gaps, make_windows, chronological_split)
from latentplant.metrics import rollout_report
schema = PlantSchema(
tags=(
TagSpec("silica_conc", "target", "pct", 0.0, 10.0, update_seconds=3600.0),
TagSpec("pulp_level", "observation", "pct", 0.0, 100.0),
TagSpec("amina_flow", "action", "m3/h", 0.0, 800.0),
TagSpec("iron_feed", "exogenous", "pct"),
),
row_seconds=20.0,
)
episodes = split_on_gaps(schema, values, timestamps) # a plant stop ends an episode
windows = make_windows(schema, episodes, context=24, horizon=12)
train, val, test = chronological_split(windows)
wm = ProbabilisticEnsemble(n_obs=2, n_act=1, n_exo=1, n_members=5)
wm.fit(train, epochs=60, seed=0)
roll = wm.rollout(test.obs_ctx, test.act_ctx, test.exo_ctx,
test.act_fut, test.exo_fut, n_samples=64, seed=0)
lo, hi = roll.interval(0.9)
print(rollout_report(roll.samples, test.obs_fut, test.obs_ctx, test.mask_fut))
Counterfactual A/B from the same anchor:
more, less = wm.counterfactual(ctx_obs, ctx_act, ctx_exo, action_a, action_b, exo_fut)
lift = more.mean - less.mean
On real plant data this supports direction-of-effect claims only: logged actions come from a closed loop, so observational data does not identify interventions without assumptions. On a simulator the ground truth exists and the imagination-to-reality gap is measured instead.
Plan against the model, and measure what the plan was worth:
from latentplant import Planner, cvar_cost, target_tracking_cost, imagination_gap
planner = Planner(model=wm, bounds=action_bounds, horizon=24, method="cem")
plan = planner.plan(obs_ctx[:1], act_ctx[:1], exo_ctx[:1], exo_fut[:1],
cvar_cost(target_tracking_cost(target), alpha=0.2),
action_rate_penalty=2.0)
realized = env.execute(plan.actions) # only possible where truth exists
print(imagination_gap(plan, realized, persistence_from=obs_ctx[0]))
Fix an overconfident model's intervals without retraining it:
from latentplant import fit_conformal, calibration_report
conf = fit_conformal(cal_roll, cal.obs_fut, level=0.9, mask=cal.mask_fut)
print(calibration_report(conf, test_roll, test.obs_fut, mask=test.mask_fut))
Documentation
Full docs in docs/: concepts (what a world model is, the plant contract,
windows and leakage), models (probabilistic ensemble, RSSM, ensembled RSSM), evaluation (rollout
metrics, conformal calibration) and planning (imagination, the imagination-to-reality gap).
Status
0.03.000, alpha. Shipping now: the ingestion contract with the multi-rate observation operator, episodes/windows/leakage-safe splits, the probabilistic ensemble (PETS-class, TS-inf propagation), the vector RSSM, the ensembled RSSM with the aleatoric/epistemic split, the rollout metric suite, split-conformal interval calibration, and CEM/MPPI planning with the imagination-to-reality gap. On the roadmap: ONNX export for a browser inference lane, a Gymnasium imagination env, and baseline adapters.
Cite
Santibañez-Leal, F. (2026). Action-Conditioned Latent Dynamics for Mineral Processing: A World-Model Engine, and a Measured Account of Where It Earns Its Cost. Preprint v0.01, CC BY 4.0. 10.5281/zenodo.22135521 (concept DOI, always latest: 10.5281/zenodo.22135520).
The paper reports what this engine actually achieves on mineral-processing data, in three
directions: a recurrent latent tracks a PDE concentration field it never observes and is
overconfident doing it, a linear model wins on a near-linear grinding circuit, and on a real
flotation historian the whole class fails for a reason that is a property of the record rather
than of the method. Source in manuscripts/.
Not to be confused with
phenoforge, the sibling engine, which fits and
ensembles closed-form phenomenological equations. latentplant learns latent dynamics from
data. Same industry, different object.
References
The design follows: Chua et al. 2018, Deep RL in a Handful of Trials with Probabilistic Dynamics Models, arXiv:1805.12114 (probabilistic ensembles, TS-inf); Lakshminarayanan et al. 2017, Simple and Scalable Predictive Uncertainty Estimation using Deep Ensembles, arXiv:1612.01474; Hafner et al. 2019, Learning Latent Dynamics for Planning from Pixels, arXiv:1811.04551 (RSSM); Hafner et al. 2023, Mastering Diverse Domains through World Models, arXiv:2301.04104; Janner et al. 2019, When to Trust Your Model, arXiv:1906.08253 (rollout horizon and compounding error); Che et al. 2016, Recurrent Neural Networks for Multivariate Time Series with Missing Values, arXiv:1606.01865 (cadence and masking); Romano et al. 2019, Conformalized Quantile Regression, arXiv:1905.03222 (interval calibration); Williams et al. 2017, Information Theoretic Model Predictive Control, arXiv:1707.02342 (MPPI); Levine et al. 2020, Offline Reinforcement Learning: Tutorial, Review, and Perspectives, arXiv:2005.01643 (why the planner refuses to issue setpoints).
Developed by Felipe Santibanez-Leal. MIT licensed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file latentplant-0.8.0.tar.gz.
File metadata
- Download URL: latentplant-0.8.0.tar.gz
- Upload date:
- Size: 134.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cffe1318e38ba70f58f3549b060a213555506cace5f2a8ef8c51f6bbd91539b1
|
|
| MD5 |
7ecdd5118c2968f3f4cd4c7dfa13e84e
|
|
| BLAKE2b-256 |
e0c54a83418aa5c16befbd00b8133ed19a216ba5f55c86b5b9cc3d98a0393753
|
Provenance
The following attestation bundles were made for latentplant-0.8.0.tar.gz:
Publisher:
release.yml on fsantibanezleal/CAOS_LatentPlant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
latentplant-0.8.0.tar.gz -
Subject digest:
cffe1318e38ba70f58f3549b060a213555506cace5f2a8ef8c51f6bbd91539b1 - Sigstore transparency entry: 2657415015
- Sigstore integration time:
-
Permalink:
fsantibanezleal/CAOS_LatentPlant@72b106798bcd803dfd8a8b5b0cf05deee438e22e -
Branch / Tag:
refs/tags/v0.08.000 - Owner: https://github.com/fsantibanezleal
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@72b106798bcd803dfd8a8b5b0cf05deee438e22e -
Trigger Event:
push
-
Statement type:
File details
Details for the file latentplant-0.8.0-py3-none-any.whl.
File metadata
- Download URL: latentplant-0.8.0-py3-none-any.whl
- Upload date:
- Size: 97.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4ea0ea07a9c46a94527ebbeb8b9447e22d7d9112fb75c97464efda70a8e4061
|
|
| MD5 |
746c900b7ddfed77f66e576f51a5ac01
|
|
| BLAKE2b-256 |
7fa14b4494a1449bcf73759b0bd1e2fb0caf31bf82be288faf98c6925f34462f
|
Provenance
The following attestation bundles were made for latentplant-0.8.0-py3-none-any.whl:
Publisher:
release.yml on fsantibanezleal/CAOS_LatentPlant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
latentplant-0.8.0-py3-none-any.whl -
Subject digest:
d4ea0ea07a9c46a94527ebbeb8b9447e22d7d9112fb75c97464efda70a8e4061 - Sigstore transparency entry: 2657415038
- Sigstore integration time:
-
Permalink:
fsantibanezleal/CAOS_LatentPlant@72b106798bcd803dfd8a8b5b0cf05deee438e22e -
Branch / Tag:
refs/tags/v0.08.000 - Owner: https://github.com/fsantibanezleal
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@72b106798bcd803dfd8a8b5b0cf05deee438e22e -
Trigger Event:
push
-
Statement type: