millwright 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {millwright-0.2.0 → millwright-0.2.1}/CHANGELOG.md +26 -0
  2. {millwright-0.2.0 → millwright-0.2.1}/Cargo.lock +2 -1
  3. {millwright-0.2.0 → millwright-0.2.1}/Cargo.toml +6 -1
  4. {millwright-0.2.0 → millwright-0.2.1}/PKG-INFO +1 -1
  5. {millwright-0.2.0 → millwright-0.2.1}/docs/deploy.html +1 -0
  6. {millwright-0.2.0 → millwright-0.2.1}/index.html +12 -12
  7. {millwright-0.2.0 → millwright-0.2.1}/pyproject.toml +1 -1
  8. {millwright-0.2.0 → millwright-0.2.1}/src/monitor.rs +9 -0
  9. millwright-0.2.1/src/onnx.rs +1075 -0
  10. {millwright-0.2.0 → millwright-0.2.1}/src/pipeline.rs +29 -34
  11. {millwright-0.2.0 → millwright-0.2.1}/src/registry.rs +37 -0
  12. {millwright-0.2.0 → millwright-0.2.1}/src/selection/mod.rs +27 -0
  13. {millwright-0.2.0 → millwright-0.2.1}/src/selection/search.rs +9 -0
  14. {millwright-0.2.0 → millwright-0.2.1}/src/serve.rs +12 -0
  15. {millwright-0.2.0 → millwright-0.2.1}/src/traits.rs +11 -0
  16. {millwright-0.2.0 → millwright-0.2.1}/src/transform.rs +15 -0
  17. millwright-0.2.1/tests/hero_snippet.rs +51 -0
  18. millwright-0.2.1/tests/onnx_export.rs +151 -0
  19. millwright-0.2.0/src/onnx.rs +0 -327
  20. {millwright-0.2.0 → millwright-0.2.1}/.github/workflows/ci.yml +0 -0
  21. {millwright-0.2.0 → millwright-0.2.1}/.github/workflows/release-crate.yml +0 -0
  22. {millwright-0.2.0 → millwright-0.2.1}/.github/workflows/release-python.yml +0 -0
  23. {millwright-0.2.0 → millwright-0.2.1}/.gitignore +0 -0
  24. {millwright-0.2.0 → millwright-0.2.1}/CNAME +0 -0
  25. {millwright-0.2.0 → millwright-0.2.1}/CONTRIBUTING.md +0 -0
  26. {millwright-0.2.0 → millwright-0.2.1}/GUIDE.md +0 -0
  27. {millwright-0.2.0 → millwright-0.2.1}/LICENSE +0 -0
  28. {millwright-0.2.0 → millwright-0.2.1}/README.md +0 -0
  29. {millwright-0.2.0 → millwright-0.2.1}/RELEASING.md +0 -0
  30. {millwright-0.2.0 → millwright-0.2.1}/benches/throughput.rs +0 -0
  31. {millwright-0.2.0 → millwright-0.2.1}/docs/data.html +0 -0
  32. {millwright-0.2.0 → millwright-0.2.1}/docs/index.html +0 -0
  33. {millwright-0.2.0 → millwright-0.2.1}/docs/insight.html +0 -0
  34. {millwright-0.2.0 → millwright-0.2.1}/docs/pipelines.html +0 -0
  35. {millwright-0.2.0 → millwright-0.2.1}/docs/python.html +0 -0
  36. {millwright-0.2.0 → millwright-0.2.1}/docs/site.css +0 -0
  37. {millwright-0.2.0 → millwright-0.2.1}/examples/automl.rs +0 -0
  38. {millwright-0.2.0 → millwright-0.2.1}/examples/backends.rs +0 -0
  39. {millwright-0.2.0 → millwright-0.2.1}/examples/explore.rs +0 -0
  40. {millwright-0.2.0 → millwright-0.2.1}/examples/insight.rs +0 -0
  41. {millwright-0.2.0 → millwright-0.2.1}/examples/operations.rs +0 -0
  42. {millwright-0.2.0 → millwright-0.2.1}/examples/portability.rs +0 -0
  43. {millwright-0.2.0 → millwright-0.2.1}/examples/specialized.rs +0 -0
  44. {millwright-0.2.0 → millwright-0.2.1}/examples/spine.rs +0 -0
  45. {millwright-0.2.0 → millwright-0.2.1}/examples/trust.rs +0 -0
  46. {millwright-0.2.0 → millwright-0.2.1}/examples/workflow.rs +0 -0
  47. {millwright-0.2.0 → millwright-0.2.1}/guide.html +0 -0
  48. {millwright-0.2.0 → millwright-0.2.1}/scripts/release.sh +0 -0
  49. {millwright-0.2.0 → millwright-0.2.1}/src/anomaly.rs +0 -0
  50. {millwright-0.2.0 → millwright-0.2.1}/src/automl.rs +0 -0
  51. {millwright-0.2.0 → millwright-0.2.1}/src/backends/chronos.rs +0 -0
  52. {millwright-0.2.0 → millwright-0.2.1}/src/backends/incremental.rs +0 -0
  53. {millwright-0.2.0 → millwright-0.2.1}/src/backends/linfa.rs +0 -0
  54. {millwright-0.2.0 → millwright-0.2.1}/src/backends/mod.rs +0 -0
  55. {millwright-0.2.0 → millwright-0.2.1}/src/backends/smartcore.rs +0 -0
  56. {millwright-0.2.0 → millwright-0.2.1}/src/balance.rs +0 -0
  57. {millwright-0.2.0 → millwright-0.2.1}/src/calibration.rs +0 -0
  58. {millwright-0.2.0 → millwright-0.2.1}/src/diagnostics.rs +0 -0
  59. {millwright-0.2.0 → millwright-0.2.1}/src/ensemble.rs +0 -0
  60. {millwright-0.2.0 → millwright-0.2.1}/src/error.rs +0 -0
  61. {millwright-0.2.0 → millwright-0.2.1}/src/evaluate.rs +0 -0
  62. {millwright-0.2.0 → millwright-0.2.1}/src/explain.rs +0 -0
  63. {millwright-0.2.0 → millwright-0.2.1}/src/frame.rs +0 -0
  64. {millwright-0.2.0 → millwright-0.2.1}/src/lib.rs +0 -0
  65. {millwright-0.2.0 → millwright-0.2.1}/src/logistic.rs +0 -0
  66. {millwright-0.2.0 → millwright-0.2.1}/src/profile.rs +0 -0
  67. {millwright-0.2.0 → millwright-0.2.1}/src/python.rs +0 -0
  68. {millwright-0.2.0 → millwright-0.2.1}/src/rng.rs +0 -0
  69. {millwright-0.2.0 → millwright-0.2.1}/src/selection/cv.rs +0 -0
  70. {millwright-0.2.0 → millwright-0.2.1}/src/selection/scoring.rs +0 -0
  71. {millwright-0.2.0 → millwright-0.2.1}/src/table.rs +0 -0
  72. {millwright-0.2.0 → millwright-0.2.1}/src/viz.rs +0 -0
  73. {millwright-0.2.0 → millwright-0.2.1}/tests/ensemble_zoo.rs +0 -0
  74. {millwright-0.2.0 → millwright-0.2.1}/tests/golden.rs +0 -0
  75. {millwright-0.2.0 → millwright-0.2.1}/tests/real_data.rs +0 -0
@@ -6,6 +6,32 @@ All notable changes to Millwright are recorded here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.1] - 2026-08-23
10
+
11
+ ### Added
12
+ - **Exported forests now serve in Millwright.** `InferenceModel` runs linear / NN
13
+ ONNX graphs through tract as before, and evaluates the ONNX-ML tree-ensemble
14
+ ops tract doesn't implement (from an exported `RandomForest`) with a small
15
+ native interpreter. A model exported by Millwright always round-trips back into
16
+ `InferenceModel`/`Server` — forests included — while the artifact stays portable
17
+ to any ONNX runtime.
18
+ - **Imputers and one-hot encoders are ONNX-exportable.** A `SimpleImputer` step
19
+ exports as `Where(IsNaN(x), fill, x)`; a `OneHotEncoder` step exports as a
20
+ per-column `Gather` → `Round` → `Equal` → `Cast` → `Concat` expansion (with the
21
+ graph input re-declared at the raw feature width). So the whole realistic
22
+ `impute → one-hot → scale → model` pipeline exports and serves as one graph —
23
+ through tract (linear) or the native interpreter (behind a forest), verified
24
+ identical to the in-memory pipeline. The pipeline export generalized from
25
+ folding one affine map to splicing an ordered chain of transformer "prefixes"
26
+ (new `Transformer::onnx_prefix`). Only steps with no ONNX form now error.
27
+ - **`SearchResult::export_onnx`** — a `GridSearch`/`RandomSearch` winner can now
28
+ be exported to ONNX (it could only `predict` before). Backed by a new
29
+ object-safe `Estimator::to_onnx_proto` on `Pipeline`.
30
+ - **`Server::from_registry(&reg, name, tag)`** and
31
+ **`DriftMonitor::from_registry(&version)`** — serve a tagged artifact straight
32
+ from a registry, and build a PSI monitor from the version's stored reference
33
+ distribution.
34
+
9
35
  ## [0.2.0] - 2026-08-23
10
36
 
11
37
  ### Added
@@ -1935,7 +1935,7 @@ dependencies = [
1935
1935
 
1936
1936
  [[package]]
1937
1937
  name = "millwright"
1938
- version = "0.2.0"
1938
+ version = "0.2.1"
1939
1939
  dependencies = [
1940
1940
  "axum",
1941
1941
  "chronos-ts",
@@ -1956,6 +1956,7 @@ dependencies = [
1956
1956
  "plotters",
1957
1957
  "plotters-statistical",
1958
1958
  "polars",
1959
+ "prost 0.13.5",
1959
1960
  "pyo3",
1960
1961
  "rayon",
1961
1962
  "regression-diagnostics",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "millwright"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  edition = "2021"
5
5
  rust-version = "1.95"
6
6
  description = "A unified ML framework for Rust — proven Rust crates, assembled into one machine."
@@ -57,6 +57,10 @@ polars = { version = "=0.55.2", default-features = false, features = ["csv", "pa
57
57
  pyo3 = { version = "0.24", features = ["extension-module", "abi3-py39"], optional = true }
58
58
  ndarray = { version = "0.16", optional = true }
59
59
  rayon = { version = "1", optional = true }
60
+ # Decode a saved ONNX file back into the proto, to natively evaluate ONNX-ML ops
61
+ # (tree ensembles) that tract does not implement. Same version onnx-export-rs
62
+ # uses, so the generated proto types decode cleanly.
63
+ prost = { version = "0.13", optional = true }
60
64
  plotters = { version = "0.3.7", default-features = false, features = ["svg_backend", "all_series"], optional = true }
61
65
  tract-onnx = { version = "0.23", optional = true }
62
66
  axum = { version = "0.8", optional = true }
@@ -146,6 +150,7 @@ onnx = [
146
150
  "onnx-export-rs/smartcore-compat",
147
151
  "dep:tract-onnx",
148
152
  "dep:ndarray",
153
+ "dep:prost",
149
154
  "smartcore-backend",
150
155
  "smartcore/serde",
151
156
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: millwright
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Intended Audience :: Science/Research
@@ -71,6 +71,7 @@ reg.tag(<span class="s">"demand"</span>, &amp;v1.id, <span class="s">"prod"</spa
71
71
  .with_monitor(<span class="f">DriftMonitor</span>::psi(&amp;reference)?)
72
72
  .serve(<span class="s">"0.0.0.0:8080"</span>).<span class="k">await</span>?; <span class="c">// POST /predict, GET /metrics</span></pre>
73
73
  <p class="run">cargo run --example operations --features "onnx registry monitor serve"</p>
74
+ <div class="callout"><b>Serving any model.</b> The <code class="inl">Server</code> runs linear / NN ONNX graphs through <code class="inl">tract</code>, and evaluates ONNX-ML tree ensembles (a forest) with a small native interpreter — so a model exported by Millwright always serves in Millwright, and the artifact stays portable to any ONNX runtime.</div>
74
75
  </div>
75
76
  </section>
76
77
 
@@ -381,7 +381,7 @@ grid! {
381
381
  <span class="c">// 1 — compose a pipeline: preprocessing + a model, one object</span>
382
382
  <span class="k">let</span> pipe = <span class="f">Pipeline</span>::new()
383
383
  .step(<span class="s">"impute"</span>, <span class="f">SimpleImputer</span>::median())
384
- .step(<span class="s">"encode"</span>, <span class="f">OneHot</span>::infer())
384
+ .step(<span class="s">"encode"</span>, <span class="f">OneHotEncoder</span>::infer())
385
385
  .step(<span class="s">"scale"</span>, <span class="f">StandardScaler</span>::new())
386
386
  .balance(<span class="f">Smote</span>::default()) <span class="c">// imbalance-rs · train-time only</span>
387
387
  .estimator(<span class="s">"rf"</span>, <span class="f">RandomForest</span>::new());
@@ -392,15 +392,15 @@ grid! {
392
392
  .scoring(<span class="f">Metric</span>::F1)
393
393
  .fit(&amp;train)?;
394
394
 
395
- <span class="c">// 3 — assess &amp; explain</span>
396
- <span class="k">let</span> report = model.evaluate(&amp;test)?; <span class="c">// metrics + regression-diagnostics</span>
397
- <span class="k">let</span> shap = model.explain(<span class="f">Explainer</span>::kernel(), &amp;test)?; <span class="c">// shap-rs</span>
395
+ <span class="c">// 3 — assess &amp; explain the best model</span>
396
+ <span class="k">let</span> report = model.evaluate(&amp;test)?; <span class="c">// accuracy · precision · recall · f1</span>
397
+ <span class="k">let</span> shap = model.explain(&amp;<span class="f">Explainer</span>::kernel(), test.features())?; <span class="c">// shap-rs</span>
398
398
 
399
399
  <span class="c">// 4 — ship it: one ONNX artifact, served, with drift on every request</span>
400
400
  model.export_onnx(<span class="s">"churn.onnx"</span>)?; <span class="c">// onnx-export-rs</span>
401
401
 
402
- <span class="f">Server</span>::from_onnx(<span class="s">"churn.onnx"</span>) <span class="c">// tract inference</span>
403
- .with_monitor(<span class="f">DriftMonitor</span>::psi(&amp;train)) <span class="c">// driftwatch</span>
402
+ <span class="f">Server</span>::from_onnx(<span class="s">"churn.onnx"</span>)? <span class="c">// forests run natively in Millwright</span>
403
+ .with_monitor(<span class="f">DriftMonitor</span>::psi(&amp;reference)?) <span class="c">// driftwatch · training preds</span>
404
404
  .route(<span class="s">"/predict"</span>)
405
405
  .serve(<span class="s">"0.0.0.0:8080"</span>).<span class="k">await</span>?; <span class="c">// axum</span></pre>
406
406
  <p class="tiny" style="margin-top:1rem">The <code class="inl">timeseries</code> feature swaps the estimator for a <span class="mono">chronos-ts</span> auto-ARIMA forecaster behind the same <code class="inl">fit</code>/<code class="inl">predict</code> contract; <code class="inl">incremental</code> swaps <code class="inl">.fit()</code> for <code class="inl">.partial_fit()</code> over batches that never fully load into memory.</p>
@@ -638,17 +638,17 @@ pipe.export_onnx(<span class="s">"churn.onnx"</span>) <span class=
638
638
  <div>
639
639
  <h3>Register, serve, roll back</h3>
640
640
  <pre><span class="c">// version a trained pipeline — artifact + ONNX + metrics + lineage</span>
641
- <span class="k">let</span> v = <span class="f">Registry</span>::local(<span class="s">"./models"</span>)
642
- .register(<span class="s">"churn"</span>, &amp;model)?
643
- .tag(<span class="s">"prod"</span>); <span class="c">// a movable pointer</span>
641
+ <span class="k">let</span> reg = <span class="f">Registry</span>::local(<span class="s">"./models"</span>);
642
+ <span class="k">let</span> v = reg.register(<span class="s">"churn"</span>, &amp;model, meta)?; <span class="c">// meta: metrics + reference dist</span>
643
+ reg.tag(<span class="s">"churn"</span>, &amp;v.id, <span class="s">"prod"</span>)?; <span class="c">// a movable pointer</span>
644
644
 
645
645
  <span class="c">// serve straight from the registry; the monitor uses the stored reference</span>
646
- <span class="f">Server</span>::from_registry(<span class="s">"churn"</span>, <span class="s">"prod"</span>)
647
- .with_monitor(<span class="f">DriftMonitor</span>::from_registry(&amp;v))
646
+ <span class="f">Server</span>::from_registry(&amp;reg, <span class="s">"churn"</span>, <span class="s">"prod"</span>)?
647
+ .with_monitor(<span class="f">DriftMonitor</span>::from_registry(&amp;v)?)
648
648
  .serve(<span class="s">"0.0.0.0:8080"</span>).<span class="k">await</span>?;
649
649
 
650
650
  <span class="c">// when drift fires: retrain on the recorded lineage — or revert in one line</span>
651
- <span class="f">Registry</span>::local(<span class="s">"./models"</span>).rollback(<span class="s">"churn"</span>, <span class="s">"prod"</span>)?;</pre>
651
+ reg.rollback(<span class="s">"churn"</span>, <span class="s">"prod"</span>)?;</pre>
652
652
  </div>
653
653
  <div>
654
654
  <h3>Closing the loop</h3>
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "millwright"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "A unified ML framework for Rust — Python bindings over the Rust core."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -46,6 +46,15 @@ impl DriftMonitor {
46
46
  })
47
47
  }
48
48
 
49
+ /// Build a monitor from the reference distribution a registry [`Version`]
50
+ /// stored at registration time.
51
+ ///
52
+ /// [`Version`]: crate::registry::Version
53
+ #[cfg(feature = "registry")]
54
+ pub fn from_registry(version: &crate::registry::Version) -> Result<Self> {
55
+ Self::psi(&version.metadata.reference)
56
+ }
57
+
49
58
  /// Record a batch of live predictions.
50
59
  pub fn observe(&self, predictions: &[f64]) {
51
60
  self.live