Skip to main content

latentplant

ci license

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

latentplant-0.8.0.tar.gz (134.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

latentplant-0.8.0-py3-none-any.whl (97.1 kB view details)

Uploaded Python 3

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

Hashes for latentplant-0.8.0.tar.gz
Algorithm Hash digest
SHA256 cffe1318e38ba70f58f3549b060a213555506cace5f2a8ef8c51f6bbd91539b1
MD5 7ecdd5118c2968f3f4cd4c7dfa13e84e
BLAKE2b-256 e0c54a83418aa5c16befbd00b8133ed19a216ba5f55c86b5b9cc3d98a0393753

See more details on using hashes here.

Provenance

The following attestation bundles were made for latentplant-0.8.0.tar.gz:

Publisher: release.yml on fsantibanezleal/CAOS_LatentPlant

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for latentplant-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d4ea0ea07a9c46a94527ebbeb8b9447e22d7d9112fb75c97464efda70a8e4061
MD5 746c900b7ddfed77f66e576f51a5ac01
BLAKE2b-256 7fa14b4494a1449bcf73759b0bd1e2fb0caf31bf82be288faf98c6925f34462f

See more details on using hashes here.

Provenance

The following attestation bundles were made for latentplant-0.8.0-py3-none-any.whl:

Publisher: release.yml on fsantibanezleal/CAOS_LatentPlant

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.10.0

2 files

This release

0.8.0 This release

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page