engin-core
Open toolkit for fermentation scale-up: a mechanistic fed-batch simulator, a Gaussian-process titer model with conformally calibrated uncertainty, an active-learning next-batch recommender, and reproducible benchmarks. Built on mature libraries — scipy (integration), scikit-learn (GP), MAPIE (conformal) — with only the domain models hand-written.
Part of the engin-suite strain-to-scale monorepo.
Why
Most bioprocess optimizers give point predictions or intervals that quietly lie. The trap is easy to fall into: form a 90% interval from the model's uncertainty alone and assume normality, and it covers only ~55% of held-out runs — badly overconfident. engin-core forecasts titer with honest coverage and recommends the runs worth doing next.
Calibrated uncertainty is the whole point of the wedge: a scale-up decision needs
P(hit $/kg target), not a number with no error bar.
Install
Not published to PyPI yet — pip install engin-core will not find anything.
Install from a checkout of the monorepo:
git clone https://github.com/enginbio/engin-suite
cd engin-suite
pip install -e "packages/engin-core[dev]"
Quickstart
import numpy as np
from engin_core import (
simulate_unit, fit_gp, split_conformal_multiplier,
recommend_batch, ard_importance, unit_to_physical,
)
rng = np.random.default_rng(0)
U = rng.random((120, 5)) # unit-cube DoE (5 design knobs)
y = simulate_unit(U) # titer (g/L) from the simulator
tr, ca, te = slice(0, 70), slice(70, 100), slice(100, 120)
gp = fit_gp(U[tr], y[tr]) # fit the GP titer model
# Calibrate a 90% interval on a held-out set (split conformal):
mc, sdc = gp.predict(U[ca])
q90 = split_conformal_multiplier(y[ca], mc, sdc, level=0.90)
mean, sd = gp.predict(U[te]) # forecast: mean ± q90*sd is a 90% PI
X_next, m_next, sd_next, ei = recommend_batch(gp, float(y[tr].max()), k=8)
print("recommended next runs (physical units):\n", unit_to_physical(X_next))
print("titer drivers (ARD):", np.round(ard_importance(gp), 2))
Running this in a loop? recommend_batch draws a fresh candidate pool on each
call, because seed defaults to None (ADR 0011). Pass seed=<int> when you want
a bit-reproducible recommendation — but pass a different one each round, not a
fixed one, or every round searches the same fixed set of candidate points and the
campaign converges to the best point in that set rather than the best design.
Full end-to-end demo (writes plots, a DoE CSV, and a DoE round-reduction memo):
python examples/run_demo.py
What's in the box
| Module | What it does |
|---|---|
engin_core.simulator |
Fed-batch Monod/Luedeking-Piret bioreactor with product inhibition — a non-monotonic titer landscape with a real interior optimum. Bespoke equations, integrated by scipy solve_ivp (piecewise across the feed/induction switches). 5 knobs: feed_rate, feed_start, Sf, induction_time, S0. |
engin_core.gp |
scikit-learn ARD-RBF Gaussian Process + split-conformal (heteroscedastic, sd-scaled) and MAPIE-backed interval calibration + prob_at_least. |
engin_core.recommend |
Expected-Improvement next-batch recommender with a diversity filter. |
engin_core.sensitivity |
ARD inverse-lengthscale sensitivity — which knobs actually move titer. |
The calibration story (why conformal)
A raw GP interval is overconfident here because a space-filling DoE is "easier"
than the future query points the model is asked about (covariate shift), and the
epistemic sd ignores observation noise entirely. Split-conformal calibrates the
interval multiplier on a same-distribution held-out set using the finite-sample
conformal quantile — distribution-free, no normality assumption. The score is the classical
normalized nonconformity measure of Papadopoulos, Gammerman and Vovk —
|y − ŷ| / σ, with σ taken from the model's own predictive sd — and the
multiplier form keeps intervals riding the GP's per-point sd.
This is related to, but not the same as, MAPIE's ResidualNormalisedScore, which
estimates σ with a separate learned model fitted to log-residuals. Normalizing
by the GP's own sd needs no second model, which matters at the sample sizes this
targets.
Reproduce (python benchmarks/benchmark.py, mean over 20 seeds):
| 90% interval built from… | Coverage (target 0.90) |
|---|---|
| epistemic-only, Gaussian ×1.645 | 0.59 — naive, overconfident (seed-dependent; read as ~0.55–0.62) |
| total (model + noise), Gaussian ×1.645 | 0.86 — assumes normality, drifts per seed |
| total, split-conformal ×q90 | 0.96 — honest |
Benchmarks
python benchmarks/benchmark.py (mean over 20 seeds, held-out):
- Forecast: RMSE ≈ 3.8 g/L, R² ≈ 0.96.
- Calibration: see table above.
- Active-learning lift (best true titer of an 8-run batch vs the best true titer in the initial DoE): EI ≈ +18% vs random ≈ −23% — one recommended batch moves the frontier; a random batch does not.
The single-seed examples/run_demo.py slice: R² 0.97, RMSE ~4 g/L, feed_rate
the dominant driver, and one active-learning round lifting best titer +28%
(86→110 g/L).
Development
pip install -e ".[dev]"
pytest # tests
ruff check . # lint
License
Apache-2.0. Patent grant included, matching the BayBE/BioSTEAM ecosystem.
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 engin_core-0.1.1.tar.gz.
File metadata
- Download URL: engin_core-0.1.1.tar.gz
- Upload date:
- Size: 126.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
354734760ee056759a397a7afc3f5ee09740c9f88213ffe2767b64b6e3c132b0
|
|
| MD5 |
afea25e505627ba7dd11c7ff25fd3179
|
|
| BLAKE2b-256 |
5a1d8856963963d2df8fe7a726e3c0f866ba63bd80d28dbb0794c2e1ffa21c8e
|
Provenance
The following attestation bundles were made for engin_core-0.1.1.tar.gz:
Publisher:
release.yml on enginbio/engin-suite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
engin_core-0.1.1.tar.gz -
Subject digest:
354734760ee056759a397a7afc3f5ee09740c9f88213ffe2767b64b6e3c132b0 - Sigstore transparency entry: 2559223109
- Sigstore integration time:
-
Permalink:
enginbio/engin-suite@87173621d8d0fee3e09073ff2f07622e61efde39 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/enginbio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@87173621d8d0fee3e09073ff2f07622e61efde39 -
Trigger Event:
push
-
Statement type:
File details
Details for the file engin_core-0.1.1-py3-none-any.whl.
File metadata
- Download URL: engin_core-0.1.1-py3-none-any.whl
- Upload date:
- Size: 88.9 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 |
eaaef8bc90976105daa62c2132338c71854cbaa0f8e67ff42d075bb28a2c6930
|
|
| MD5 |
4b11e72e90506b26be9b7a8362b9f3ad
|
|
| BLAKE2b-256 |
377ff818f04e3fa489c9d9178d535002618f475f120cb551e2fd482424bd443d
|
Provenance
The following attestation bundles were made for engin_core-0.1.1-py3-none-any.whl:
Publisher:
release.yml on enginbio/engin-suite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
engin_core-0.1.1-py3-none-any.whl -
Subject digest:
eaaef8bc90976105daa62c2132338c71854cbaa0f8e67ff42d075bb28a2c6930 - Sigstore transparency entry: 2559223606
- Sigstore integration time:
-
Permalink:
enginbio/engin-suite@87173621d8d0fee3e09073ff2f07622e61efde39 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/enginbio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@87173621d8d0fee3e09073ff2f07622e61efde39 -
Trigger Event:
push
-
Statement type: