Skip to main content

millwright

A unified ML framework for Rust — ten crates, one lifecycle.

"Ten crates" is the ecosystem this project assembles — plotters-statistical, model-selection-rs, imbalance-rs, regression-diagnostics, hyperopt-rs, shap-rs, driftwatch, onnx-export-rs, incremental-rs, chronos-ts — riding the established smartcore / linfa / polars stack.

Status: Phases 0–8 — done

Phase 0 · the spine

fit · transform · predict · Pipeline end to end over a real backend:

  • Frame / Dataset — the contiguous, row-major f64 boundary type (src/frame.rs).
  • The four traits — object-safe Transformer, Estimator, Predictor, ProbaPredictor, plus a blanket Model (src/traits.rs).
  • The first backend — a smartcore adapter (RandomForest, LinearRegression) converting Frame → DenseMatrix at the edge only (src/backends/smartcore.rs).
  • Pipeline — named steps + a final model, "step__param" addressing; pipelines nest (src/pipeline.rs).

Phase 1 · prep & select — a real, tunable, ensemble-ready workflow

  • Preprocessing (src/transform.rs, core): SimpleImputer, StandardScaler, MinMaxScaler, OneHotEncoder, Winsorize (clip outliers), PowerTransform (Yeo-Johnson), ColumnTransformer (per-subset transforms), and the supervised TargetEncoder.
  • Balancing (src/balance.rs, via imbalance-rs): Smote, RandomOverSampler as train-time Balancers — Pipeline::balance(...), applied only during fit.
  • Model selection (src/selection/, via model-selection-rs): KFold / StratifiedKFold, a Metric enum (accuracy, F1, MAE, MSE, RMSE, R²), and GridSearch / RandomSearch over a whole pipeline, tuned by path. grid! macro included.
  • Ensembles (src/ensemble.rs, core): Voting (hard/soft), Bagging, and leak-free Stacking riding the same CV engine — all Models themselves, so they compose, tune, and nest.

Phase 2 · backends & HPO — two backends, one contract

  • The second backend (src/backends/linfa.rs, via linfa, feature linfa-backend): KMeans, GaussianMixture, Dbscan (as a new Clusterer contract) and Pca (as a Transformer) — each converting Frame → ndarray at the edge, proving the boundary conversion against a whole other engine.
  • Bayesian search (src/selection/, via hyperopt-rs, feature hpo): BayesSearch runs TPE search over a SearchSpace and returns the same SearchResult as grid/random search — one search API, three strategies.

Phase 3 · insight — trust the model, not just run it

  • Evaluation reports (src/evaluate.rs, core): model.evaluate(&test) bundles task-appropriate metrics into a Report (accuracy/precision/recall/F1 or MAE/MSE/RMSE/R²).
  • Regression diagnostics (src/diagnostics.rs, via regression-diagnostics, feature diagnostics): Diagnostics::of(&data) runs OLS and exposes summary(), R², per-column VIF, residuals, and Cook's distance.
  • Explainability (src/explain.rs, via shap-rs, feature explain): model.explain(&Explainer::kernel(), &frame) gives per-row SHAP values and global importance, plus permutation_importance(...).
  • Report figures (src/viz.rs, via plotters-statistical, feature viz): viz::roc_svg(...) and viz::residuals_svg(...) render self-contained SVGs (pure-Rust backend, no system fonts).
  • Probabilities (src/logistic.rs, core): LogisticRegression is a native, probability-capable classifier — the first real ProbaPredictor.
  • Calibration (src/calibration.rs, feature calibration): PlattScaling / IsotonicRegression and reliability_curve, plus CalibratedClassifier, which wraps any ProbaPredictor and returns calibrated probabilities.
  • Anomaly detection (src/anomaly.rs, feature anomaly): Mahalanobis and KnnScore, unified behind an OutlierDetector trait.

Phase 4 · portability & Python — train once; run in Rust, Python, or any ONNX runtime

  • ONNX export (src/onnx.rs, via onnx-export-rs, feature onnx): model.export_onnx(path) for RandomForest (ONNX-ML tree ensemble) and LinearRegression; whole-pipeline export folds affine scalers into the estimator's graph as one .onnx.
  • Inference (via tract, feature onnx): InferenceModel::load(path) loads and runs any ONNX file. tract executes the linear/affine/pipeline graphs (a full round-trip); tree-ensemble ONNX-ML artifacts run in external runtimes like onnxruntime.
  • Python bindings (src/python.rs, via pyo3, feature python): a Pipeline class over the same Rust core, built with maturin into an abi3 wheel.
import millwright as mw
pipe = mw.Pipeline()
pipe.standard_scaler()
pipe.random_forest(n_trees=100, max_depth=8)
pipe.fit(rows, labels)          # list[list[float]], list[float]
preds = pipe.predict(rows)      # runs the Rust engine

Build the Python module (from a virtualenv):

maturin develop --features python

Phase 5 · operations — past where scikit-learn stops

  • Registry (src/registry.rs, feature registry): Registry::local(path) versions a model's ONNX artifact, content-addressed (identical models dedupe), with metadata + reference distribution, movable tags, and rollback.
  • Drift monitor (src/monitor.rs, via driftwatch, feature monitor): DriftMonitor::psi(reference) watches the prediction stream — observe + report give live PSI and a drift verdict.
  • Server (src/serve.rs, via axum, feature serve): Server::from_onnx exposes POST /predict (validated) over the tract runtime; with a monitor attached, every request feeds it and GET /metrics reports drift.
Server::from_onnx(reg.onnx_path("churn", "prod")?)?
    .route("/predict")
    .with_monitor(DriftMonitor::psi(&reference)?)
    .serve("0.0.0.0:8080").await?;

Phase 6 · specialized — the long tail of real workloads

Same contract, different data shapes — each gets its own trait.

  • Time series (src/backends/chronos.rs, via chronos-ts, feature timeseries): AutoArima implements a Forecasterfit(&series) then forecast(steps).
  • Out-of-core (src/backends/incremental.rs, via incremental-rs, feature incremental): IncrementalLinear implements PartialFit + Predictorpartial_fit(&batch) learns one batch at a time.

These two crates pin ndarray 0.15 while the rest of the stack uses 0.16; Cargo links both, and the boundary conversion happens only inside these adapters — the "two ndarray worlds" the design settles, now exercised for real.

Phase 7 · synthesis — auto-sklearn, but the output actually deploys

  • AutoML (src/automl.rs, feature automl): AutoML::classifier() / regressor() searches preprocessing × model × hyperparameters under a Budget (trials or minutes), auto-ensembles the top candidates, and returns a ranked leaderboard plus the best fitted model. No new crate — it orchestrates the model-selection, ensemble, and backend machinery already built. A single-pipeline winner flows straight into export_onnx, so unlike a TPOT object the result deploys.
let result = AutoML::classifier()
    .budget(Budget::trials(40))
    .metric(Metric::F1)
    .cv(StratifiedKFold::new(5))
    .fit(&train)?;
println!("{}", result.leaderboard());
result.export_onnx("model.onnx")?;   // deployable

Phase 8 · harden → 1.0 — a framework you can bet on

Pin, prove, document — owning the one real risk of assembling young, single-author engine crates.

  • Exact-version pins (Cargo.toml): every engine — the ecosystem crates plus the smartcore and linfa families — is pinned to an exact =x.y.z, so a stray cargo update can't move a fragile engine under the stable trait contract. General infrastructure (serde, tokio, axum, …) stays on caret ranges to avoid forcing conflicts downstream.
  • Committed Cargo.lock: the whole ~300-package graph is reproducible; CI builds with --locked.
  • Golden-output tests (tests/golden.rs): lock the numeric behaviour of the engines on fixed inputs — exact for the deterministic paths (OLS, affine transforms, metric formulas), well-separated class labels for the stochastic ones. An engine bump that moves a number shows up as a diff.
  • Feature-matrix CI (.github/workflows/ci.yml): fmt, clippy -D warnings, docs, and the test suite across the feature matrix — from --no-default-features through each feature to full — plus Windows/macOS, the runnable examples, a benchmark compile-check, a cargo publish --dry-run, and a maturin wheel. The MSRV (rust-version = 1.95, dep-dictated) is enforced by cargo for consumers.
  • The tutorial (GUIDE.md + guide.html): the design brief's lifecycle, re-cast as a hands-on guide.

Ingest & EDA — the lifecycle starts where the data does

The front of the lifecycle, behind the eda feature (via polars).

  • Table (src/table.rs): a dtype-aware, polars-backed table — Table::from_csv / from_parquet read real string/categorical/datetime/null columns. It lowers to the numeric world: table.to_frame() and table.into_dataset("target") (categoricals label-encoded, nulls → NaN), so Frame stays the numeric boundary everything else already speaks.
  • Profile (src/profile.rs): Profile::of(&table) returns a typed EDA — overview, per-column numeric/categorical profiles, missingness, Pearson correlations (high-|r| pairs flagged), IQR outliers, and target relationship (class balance or feature-target correlation). It renders a self-contained to_html(path) report, lists alerts() that name the fix, and — the loop scikit-learn can't close — suggest_pipeline() drafts the preprocessing from those findings; you just add the model.
let table = Table::from_csv("customers.csv")?;
let profile = Profile::of_with_target(&table, "churned")?;
profile.to_html("eda.html")?;

let train = table.into_dataset("churned")?;
let mut pipe = profile.suggest_pipeline()      // impute · encode · scale, from the alerts
    .estimator("rf", RandomForest::new());
pipe.fit(&train)?;

Quickstart

use millwright::grid;
use millwright::prelude::*;

let pipe = Pipeline::new()
    .step("impute", SimpleImputer::median())
    .step("scale", StandardScaler::new())
    .balance(Smote::new())                 // train-time only
    .estimator("rf", RandomForest::new());

let search = GridSearch::new(pipe, grid! { "rf__max_depth" => [4, 8, 16] })
    .cv(StratifiedKFold::new(5))
    .scoring(Metric::F1)
    .fit(&train)?;

println!("best F1 = {:.3}", search.best_score());
let preds = search.predict(&test)?;

Run the end-to-end examples:

cargo run --example spine
cargo run --example explore --features "eda smartcore-backend"
cargo run --example trust --features "calibration anomaly"
cargo run --example workflow
cargo run --example backends --features "smartcore-backend linfa-backend hpo"
cargo run --example insight --features "smartcore-backend diagnostics explain viz"
cargo run --example portability --features "smartcore-backend onnx"
cargo run --example operations --features "smartcore-backend onnx registry monitor serve"
cargo run --example specialized --features "timeseries incremental"
cargo run --example automl --features "smartcore-backend automl onnx"

Building on Windows

The default toolchain is MSVC. If a Unix link.exe (e.g. from Git/Laragon) is ahead of MSVC's on PATH, linking fails with an "extra operand" error. Build from a Developer Command Prompt / PowerShell for VS 2022, or run vcvars64.bat first, so the MSVC linker is found before the shadowing one.

Roadmap

Phases 0–8 are done — the full lifecycle plus 1.0 hardening (exact-version pins, a committed lockfile, golden-output tests, and a feature-matrix CI). The design brief lays out the arc; the tutorial (GUIDE.md) is the how.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

millwright-0.1.0.tar.gz (170.7 kB view details)

Uploaded Source

Built Distributions

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

millwright-0.1.0-cp39-abi3-win_amd64.whl (276.5 kB view details)

Uploaded CPython 3.9+Windows x86-64

millwright-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (424.8 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

millwright-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (415.7 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

millwright-0.1.0-cp39-abi3-macosx_11_0_arm64.whl (375.0 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

millwright-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl (379.9 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file millwright-0.1.0.tar.gz.

File metadata

  • Download URL: millwright-0.1.0.tar.gz
  • Upload date:
  • Size: 170.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.14.1

File hashes

Hashes for millwright-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a3eaeabceb16f3793fbe708c651bae6dad6cef559805bb2a78b5d6fc6f1ccdfd
MD5 60b6282424bd2248b456b5deb80e0e73
BLAKE2b-256 0a4aaa80ab60c8001502872ea47c70e14b9619322bc712045d4667f5f441b149

See more details on using hashes here.

File details

Details for the file millwright-0.1.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for millwright-0.1.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 07de405a1c3ffc3eb060554e5a2b2e45e204a0794be6a86292711a6cc138601a
MD5 d8dd9ee42699234a9584058d4b992e60
BLAKE2b-256 53b06221bba8d2c263c4cd0451ae4c9cb412266f78d37d21b6c0d050ecedb5b7

See more details on using hashes here.

File details

Details for the file millwright-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for millwright-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 f1691d928c512e01fb852a543ee12e1b98c3503b44fb2e736a98395de18ad359
MD5 7141d46a162be887108405b4585e1e61
BLAKE2b-256 843e6166885860642f0d01d008eff031bb327edddc8a47f1a9519010a1953c01

See more details on using hashes here.

File details

Details for the file millwright-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for millwright-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 94c46d4c831a1d2c2cb62355333d7d34005edb2be4b2a150e3a06ddf13223af8
MD5 a2634ed4c7bb5e83c1f0ec49bae6fd92
BLAKE2b-256 698497a7ae9d636a2b239924eda96fe14131b89dc77bc9f73d8f409de145fd88

See more details on using hashes here.

File details

Details for the file millwright-0.1.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for millwright-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8aeb62ebcb7e0bd6160b4f50575afcd479a5a67ae38788af779fb62c2024231a
MD5 134590e073249e839010655ac275c489
BLAKE2b-256 36c41a1fc18e95cab55c33a3d082530e89789685451e6751c7358eefe8c8c881

See more details on using hashes here.

File details

Details for the file millwright-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for millwright-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 3a74b70b6ed68036821221256f1f9d3547c17e8e2bd569cb4e61c2dc03f66f16
MD5 ae24833c91b83d9eff28b92a29c8257c
BLAKE2b-256 ccd6dc9f4014c8217a9240f702c5a91f2f499fe4f59f9a81efe2eec135bfbd9a

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.1

6 files

0.2.0

6 files

0.1.1

6 files

This release

0.1.0 This release

6 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page