millwright 0.1.0__tar.gz → 0.2.0__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 (78) hide show
  1. millwright-0.2.0/.github/workflows/release-crate.yml +50 -0
  2. {millwright-0.1.0 → millwright-0.2.0}/.github/workflows/release-python.yml +5 -5
  3. millwright-0.2.0/CHANGELOG.md +118 -0
  4. millwright-0.2.0/CONTRIBUTING.md +55 -0
  5. {millwright-0.1.0 → millwright-0.2.0}/Cargo.lock +2 -1
  6. {millwright-0.1.0 → millwright-0.2.0}/Cargo.toml +8 -5
  7. millwright-0.2.0/GUIDE.md +41 -0
  8. {millwright-0.1.0 → millwright-0.2.0}/PKG-INFO +19 -9
  9. {millwright-0.1.0 → millwright-0.2.0}/README.md +18 -8
  10. millwright-0.2.0/RELEASING.md +94 -0
  11. millwright-0.2.0/docs/data.html +114 -0
  12. millwright-0.2.0/docs/deploy.html +134 -0
  13. millwright-0.2.0/docs/index.html +156 -0
  14. millwright-0.2.0/docs/insight.html +102 -0
  15. millwright-0.2.0/docs/pipelines.html +187 -0
  16. millwright-0.2.0/docs/python.html +106 -0
  17. millwright-0.2.0/docs/site.css +152 -0
  18. millwright-0.2.0/guide.html +9 -0
  19. {millwright-0.1.0 → millwright-0.2.0}/index.html +30 -26
  20. {millwright-0.1.0 → millwright-0.2.0}/pyproject.toml +1 -1
  21. millwright-0.2.0/scripts/release.sh +54 -0
  22. {millwright-0.1.0 → millwright-0.2.0}/src/automl.rs +136 -14
  23. millwright-0.2.0/src/backends/smartcore.rs +624 -0
  24. {millwright-0.1.0 → millwright-0.2.0}/src/ensemble.rs +231 -9
  25. {millwright-0.1.0 → millwright-0.2.0}/src/lib.rs +2 -2
  26. {millwright-0.1.0 → millwright-0.2.0}/src/onnx.rs +56 -0
  27. {millwright-0.1.0 → millwright-0.2.0}/src/profile.rs +232 -15
  28. millwright-0.2.0/src/python.rs +1010 -0
  29. {millwright-0.1.0 → millwright-0.2.0}/src/selection/cv.rs +15 -10
  30. {millwright-0.1.0 → millwright-0.2.0}/src/selection/mod.rs +39 -0
  31. {millwright-0.1.0 → millwright-0.2.0}/src/selection/scoring.rs +35 -1
  32. {millwright-0.1.0 → millwright-0.2.0}/src/table.rs +24 -0
  33. {millwright-0.1.0 → millwright-0.2.0}/src/traits.rs +4 -4
  34. millwright-0.2.0/tests/ensemble_zoo.rs +42 -0
  35. millwright-0.1.0/CHANGELOG.md +0 -63
  36. millwright-0.1.0/GUIDE.md +0 -621
  37. millwright-0.1.0/RELEASING.md +0 -61
  38. millwright-0.1.0/guide.html +0 -574
  39. millwright-0.1.0/src/backends/smartcore.rs +0 -290
  40. millwright-0.1.0/src/python.rs +0 -179
  41. {millwright-0.1.0 → millwright-0.2.0}/.github/workflows/ci.yml +0 -0
  42. {millwright-0.1.0 → millwright-0.2.0}/.gitignore +0 -0
  43. {millwright-0.1.0 → millwright-0.2.0}/CNAME +0 -0
  44. {millwright-0.1.0 → millwright-0.2.0}/LICENSE +0 -0
  45. {millwright-0.1.0 → millwright-0.2.0}/benches/throughput.rs +0 -0
  46. {millwright-0.1.0 → millwright-0.2.0}/examples/automl.rs +0 -0
  47. {millwright-0.1.0 → millwright-0.2.0}/examples/backends.rs +0 -0
  48. {millwright-0.1.0 → millwright-0.2.0}/examples/explore.rs +0 -0
  49. {millwright-0.1.0 → millwright-0.2.0}/examples/insight.rs +0 -0
  50. {millwright-0.1.0 → millwright-0.2.0}/examples/operations.rs +0 -0
  51. {millwright-0.1.0 → millwright-0.2.0}/examples/portability.rs +0 -0
  52. {millwright-0.1.0 → millwright-0.2.0}/examples/specialized.rs +0 -0
  53. {millwright-0.1.0 → millwright-0.2.0}/examples/spine.rs +0 -0
  54. {millwright-0.1.0 → millwright-0.2.0}/examples/trust.rs +0 -0
  55. {millwright-0.1.0 → millwright-0.2.0}/examples/workflow.rs +0 -0
  56. {millwright-0.1.0 → millwright-0.2.0}/src/anomaly.rs +0 -0
  57. {millwright-0.1.0 → millwright-0.2.0}/src/backends/chronos.rs +0 -0
  58. {millwright-0.1.0 → millwright-0.2.0}/src/backends/incremental.rs +0 -0
  59. {millwright-0.1.0 → millwright-0.2.0}/src/backends/linfa.rs +0 -0
  60. {millwright-0.1.0 → millwright-0.2.0}/src/backends/mod.rs +0 -0
  61. {millwright-0.1.0 → millwright-0.2.0}/src/balance.rs +0 -0
  62. {millwright-0.1.0 → millwright-0.2.0}/src/calibration.rs +0 -0
  63. {millwright-0.1.0 → millwright-0.2.0}/src/diagnostics.rs +0 -0
  64. {millwright-0.1.0 → millwright-0.2.0}/src/error.rs +0 -0
  65. {millwright-0.1.0 → millwright-0.2.0}/src/evaluate.rs +0 -0
  66. {millwright-0.1.0 → millwright-0.2.0}/src/explain.rs +0 -0
  67. {millwright-0.1.0 → millwright-0.2.0}/src/frame.rs +0 -0
  68. {millwright-0.1.0 → millwright-0.2.0}/src/logistic.rs +0 -0
  69. {millwright-0.1.0 → millwright-0.2.0}/src/monitor.rs +0 -0
  70. {millwright-0.1.0 → millwright-0.2.0}/src/pipeline.rs +0 -0
  71. {millwright-0.1.0 → millwright-0.2.0}/src/registry.rs +0 -0
  72. {millwright-0.1.0 → millwright-0.2.0}/src/rng.rs +0 -0
  73. {millwright-0.1.0 → millwright-0.2.0}/src/selection/search.rs +0 -0
  74. {millwright-0.1.0 → millwright-0.2.0}/src/serve.rs +0 -0
  75. {millwright-0.1.0 → millwright-0.2.0}/src/transform.rs +0 -0
  76. {millwright-0.1.0 → millwright-0.2.0}/src/viz.rs +0 -0
  77. {millwright-0.1.0 → millwright-0.2.0}/tests/golden.rs +0 -0
  78. {millwright-0.1.0 → millwright-0.2.0}/tests/real_data.rs +0 -0
@@ -0,0 +1,50 @@
1
+ name: release-crate
2
+
3
+ # Publish the Rust crate to crates.io when a version tag is pushed
4
+ # (`git tag v0.1.1 && git push --tags`), using crates.io Trusted Publishing
5
+ # (OIDC) — no stored token. A manual run (workflow_dispatch) does a `--dry-run`.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ publish:
16
+ name: publish to crates.io
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ id-token: write # required for crates.io Trusted Publishing (OIDC)
21
+ steps:
22
+ - uses: actions/checkout@v5
23
+ - uses: dtolnay/rust-toolchain@stable
24
+ - uses: Swatinem/rust-cache@v2
25
+
26
+ - name: check tag matches Cargo.toml version
27
+ if: startsWith(github.ref, 'refs/tags/')
28
+ run: |
29
+ tag="${GITHUB_REF_NAME#v}"
30
+ version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
31
+ echo "tag=$tag Cargo.toml=$version"
32
+ if [ "$tag" != "$version" ]; then
33
+ echo "::error::tag v$tag does not match Cargo.toml version $version"
34
+ exit 1
35
+ fi
36
+
37
+ - name: authenticate to crates.io (OIDC)
38
+ if: startsWith(github.ref, 'refs/tags/')
39
+ id: auth
40
+ uses: rust-lang/crates-io-auth-action@v1
41
+
42
+ - name: publish
43
+ if: startsWith(github.ref, 'refs/tags/')
44
+ env:
45
+ CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
46
+ run: cargo publish --locked
47
+
48
+ - name: dry-run (manual runs only)
49
+ if: ${{ !startsWith(github.ref, 'refs/tags/') }}
50
+ run: cargo publish --locked --dry-run
@@ -99,6 +99,8 @@ jobs:
99
99
  needs: [linux, windows, macos, sdist]
100
100
  # Only publish on a version tag; a manual run just builds the wheels above.
101
101
  if: startsWith(github.ref, 'refs/tags/')
102
+ permissions:
103
+ id-token: write # PyPI Trusted Publishing (OIDC) — no stored token
102
104
  steps:
103
105
  - uses: actions/download-artifact@v4
104
106
  with:
@@ -106,9 +108,7 @@ jobs:
106
108
  merge-multiple: true
107
109
  path: dist
108
110
  - name: publish
109
- uses: PyO3/maturin-action@v1
110
- env:
111
- MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
111
+ uses: pypa/gh-action-pypi-publish@release/v1
112
112
  with:
113
- command: upload
114
- args: --non-interactive --skip-existing dist/*
113
+ packages-dir: dist
114
+ skip-existing: true
@@ -0,0 +1,118 @@
1
+ # Changelog
2
+
3
+ All notable changes to Millwright are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project aims at
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] - 2026-08-23
10
+
11
+ ### Added
12
+ - **rayon parallelism.** Cross-validation folds and search candidates now
13
+ evaluate in parallel: `cross_val_score` is fold-parallel, `Bagging` fits its
14
+ base estimators in parallel, and `AutoML::parallel()` adds candidate-level
15
+ parallelism. The core contract traits gained `Send + Sync` bounds (every
16
+ concrete model already satisfied them). Results stay seed-reproducible.
17
+ - **`Boosting`** — SAMME adaptive boosting over any weak learner (an
18
+ `alpha`-weighted vote of models each reweighted toward the last round's
19
+ mistakes), joining `Voting`/`Bagging`/`Stacking`.
20
+ - **Three more models over the smartcore backend:** `Knn` (k-nearest-neighbours),
21
+ `Svc` (support vector classifier, linear or RBF, one-vs-one for multiclass),
22
+ and `NaiveBayes` (Gaussian). All implement the same `Estimator`/`Predictor`
23
+ contract, so they drop into pipelines, ensembles, and search unchanged — and
24
+ they are exposed to Python too (`mw.Knn`, `mw.Svc` / `mw.Svc.rbf()`,
25
+ `mw.NaiveBayes`, plus `pipe.knn()` / `pipe.svc()` / `pipe.naive_bayes()`).
26
+ - **Python: a scikit-learn-shaped object API.** `mw.Frame` with `from_pandas` /
27
+ `from_numpy` / `from_rows` ingest; composable transformer/estimator objects
28
+ (`StandardScaler`, `MinMaxScaler`, `SimpleImputer`, `OneHotEncoder`,
29
+ `RandomForest`, `LinearRegression`) added via `pipe.step(name, obj)` /
30
+ `pipe.estimator(name, obj)`; `fit`/`predict`/`evaluate` accept a `Frame`.
31
+ - **Python: `pipeline.explain(...)`** returns SHAP feature importance
32
+ (`Explainer.kernel()` configurable), and **`pipeline.export_onnx(path)`**
33
+ writes the fitted pipeline to a single ONNX file. The `python` wheel now
34
+ bundles the `model-selection`, `explain`, and `onnx` engines.
35
+ - **Python: `GridSearch` / `KFold` / `StratifiedKFold`** over a pipeline, with
36
+ a `SearchResult` (`best_score`, `best_params()`, `predict()`).
37
+ - **`InferenceModel` is now an `Estimator` + `Predictor`**, so a pre-trained
38
+ ONNX model (from scikit-learn, PyTorch, …) can be dropped into a `Pipeline`
39
+ as a frozen estimator behind Millwright's preprocessing — in Rust and, via
40
+ `mw.OnnxModel(path)`, from Python.
41
+ - **Python: `mw.Table` + `mw.Profile`** — dtype-aware CSV/Parquet ingest and
42
+ automated EDA (`Profile.of(table_or_frame).to_html(path)`). The wheel now
43
+ bundles the `eda` (polars) engine, so it is larger than the pure-model build.
44
+ - **`Table::from_frame`** (Rust): build a numeric `Table` from a `Frame`, so the
45
+ numeric world can round-trip back into the typed one (e.g. to profile it).
46
+ - **Richer EDA in `Profile`** — excess `kurtosis` and z-score outlier counts per
47
+ numeric column; a **Spearman** rank-correlation matrix beside the Pearson one;
48
+ a **co-missing** map (columns whose null patterns correlate); high-cardinality
49
+ categoricals now suggest `TargetEncoder`; and `suggest_pipeline` adds a
50
+ train-time **SMOTE** balancer on class imbalance (with `preprocessing`).
51
+ - **AutoML is seeded by EDA.** With the `eda` engine on, the search fixes its
52
+ preprocessing to `Profile::suggest_pipeline()` and varies only the model,
53
+ pruning the space (it falls back to a scaler sweep without `eda`).
54
+
55
+ ### Fixed
56
+ - **Cross-validated F1 no longer returns `NaN`.** A fold whose predictions
57
+ contain no true positives left smartcore's F1 evaluating `0/0`; a single NaN
58
+ fold poisoned the CV mean and a search's `best_score`. An undefined F1 is now
59
+ 0.0 (scikit-learn's `zero_division=0` convention), so `GridSearch(...).scoring(F1)`
60
+ yields finite, comparable scores.
61
+
62
+ ## [0.1.1]
63
+
64
+ ### Added
65
+
66
+ - **`LogisticRegression`** — a native, core binary classifier with genuine
67
+ `predict_proba`: the framework's first real `ProbaPredictor`.
68
+ - **`calibration` feature** — `PlattScaling`, `IsotonicRegression`,
69
+ `reliability_curve`, and `CalibratedClassifier`, which wraps any
70
+ `ProbaPredictor` and returns calibrated probabilities.
71
+ - **`anomaly` feature** — `Mahalanobis` and `KnnScore`, unified behind an
72
+ `OutlierDetector` trait.
73
+ - **`eda` feature** — a polars-backed, dtype-aware `Table` (CSV/Parquet ingest
74
+ that lowers to the numeric `Frame`) and a typed `Profile` with an HTML report,
75
+ actionable alerts, and `suggest_pipeline()`.
76
+ - **Transformers** — `Winsorize`, `PowerTransform` (Yeo-Johnson),
77
+ `ColumnTransformer`, and the supervised `TargetEncoder`.
78
+ - **Convenience** — `Frame::from_csv` (dependency-free numeric loader),
79
+ `Table::head`.
80
+ - **Python** — `min_max_scaler`, `simple_imputer`, `one_hot`,
81
+ `linear_regression`, and `evaluate()`.
82
+ - **Examples** — `explore` (ingest → profile → pipeline) and `trust`
83
+ (calibration → reliability → anomaly detection).
84
+ - **Benchmarks** — `benches/throughput.rs` (criterion): the boundary conversion
85
+ and core fit/predict, backing the "Rust speed" claim.
86
+ - **One-hot ingest** — `Table::to_frame_with` / `into_dataset_with` and a
87
+ `CategoryEncoding` enum: lower nominal categories to 0/1 indicator columns
88
+ instead of ordinal codes.
89
+ - **Schema-aware preprocessing** — `Frame` carries a per-column `Dtype`; `Table`
90
+ marks categoricals as it lowers; scalers / `Winsorize` / `PowerTransform` pass
91
+ categorical columns through untouched, and `OneHotEncoder` encodes by dtype
92
+ rather than a value heuristic when the schema is known.
93
+ - **Real-data validation** — an end-to-end integration test on Quinlan's
94
+ PlayTennis (`tests/real_data.rs`): CSV → profile → suggested pipeline → fit.
95
+
96
+ ### Changed
97
+
98
+ - Exact-version pins on every engine crate; `Cargo.lock` committed; a
99
+ feature-matrix CI (fmt, clippy `-D warnings`, docs, matrix, OS, examples,
100
+ benches, publish dry-run, wheel).
101
+ - `selection.rs` split into `selection/{scoring,cv,search}`.
102
+ - MSRV is **1.95** (dep-dictated — `sysinfo` via tract, and polars); enforced by
103
+ cargo via `rust-version` rather than a dedicated CI job (which would break on
104
+ every transitive bump). The default install needs 1.85.
105
+ - De-staled the crate and module docs (no more "Phase 0 · the spine").
106
+
107
+ ### Fixed
108
+
109
+ - Golden tests and the crate doctest build under every feature subset (they were
110
+ unconditionally referencing backend-gated types).
111
+ - Float sorts use `f64::total_cmp`, closing a NaN-driven panic class.
112
+
113
+ ## [0.1.0]
114
+
115
+ - Phases 0–8: the `Frame`/trait spine and smartcore backend, preprocessing and
116
+ model selection, a second backend (linfa) and HPO, evaluation/diagnostics/
117
+ explainability, ONNX export and inference, serving + drift monitoring + a model
118
+ registry, time-series and out-of-core estimators, AutoML, and 1.0 hardening.
@@ -0,0 +1,55 @@
1
+ # Contributing to Millwright
2
+
3
+ Thanks for your interest! Millwright assembles proven Rust crates into one
4
+ composable ML lifecycle. Bug reports, small fixes, and focused features are all
5
+ welcome.
6
+
7
+ ## Getting set up
8
+
9
+ ```bash
10
+ git clone https://github.com/mi7plus/millwright
11
+ cd millwright
12
+ cargo test # default features
13
+ ```
14
+
15
+ Rust **1.95+** is required (the floor is dictated by transitive engine deps).
16
+
17
+ ## Before you open a PR
18
+
19
+ The CI is a feature matrix, so run the same checks locally:
20
+
21
+ ```bash
22
+ cargo fmt --all --check
23
+ cargo clippy --features full --all-targets -- -D warnings
24
+ cargo test --features full
25
+ cargo test --no-default-features # the bare-core build must pass too
26
+ cargo doc --no-deps --features full # with RUSTDOCFLAGS="-D warnings"
27
+ ```
28
+
29
+ - **Every capability is a cargo feature.** New functionality behind a young
30
+ single-author crate goes behind its own feature; the core stays lean. See the
31
+ feature list in `Cargo.toml`.
32
+ - **Engines are pinned to exact versions** (`=x.y.z`) and `Cargo.lock` is
33
+ committed — bump them deliberately, one line, one commit.
34
+ - **Add tests.** Prefer a `#[test]` next to the code; lock numeric behaviour in
35
+ `tests/golden.rs` when it matters. Feature-gate tests that need a backend.
36
+ - **Run the examples** you touch: `cargo run --features full --example <name>`.
37
+
38
+ ## Try it out
39
+
40
+ - Examples: [`examples/`](examples) — one runnable program per feature group.
41
+ - Benchmarks: `cargo bench --features smartcore-backend`.
42
+ - Docs/tutorial: <https://millwright-rs.dev/docs/>.
43
+
44
+ ## Releasing
45
+
46
+ Maintainers: bump `Cargo.toml` + `pyproject.toml`, roll `CHANGELOG.md`, and tag
47
+ `vX.Y.Z` — the workflows publish to crates.io and PyPI via OIDC. The helper
48
+ `scripts/release.sh X.Y.Z` does the mechanical steps. See
49
+ [`RELEASING.md`](RELEASING.md).
50
+
51
+ ## Scope & conduct
52
+
53
+ Millwright is a thin facade over proven engines — it orchestrates, it doesn't
54
+ reimplement numerics. Please keep PRs focused, and be kind and constructive in
55
+ issues and reviews.
@@ -1935,7 +1935,7 @@ dependencies = [
1935
1935
 
1936
1936
  [[package]]
1937
1937
  name = "millwright"
1938
- version = "0.1.0"
1938
+ version = "0.2.0"
1939
1939
  dependencies = [
1940
1940
  "axum",
1941
1941
  "chronos-ts",
@@ -1957,6 +1957,7 @@ dependencies = [
1957
1957
  "plotters-statistical",
1958
1958
  "polars",
1959
1959
  "pyo3",
1960
+ "rayon",
1960
1961
  "regression-diagnostics",
1961
1962
  "serde",
1962
1963
  "serde_json",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "millwright"
3
- version = "0.1.0"
3
+ version = "0.2.0"
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."
@@ -56,6 +56,7 @@ polars = { version = "=0.55.2", default-features = false, features = ["csv", "pa
56
56
  # --- general infrastructure: caret ranges, reproduced via Cargo.lock ---
57
57
  pyo3 = { version = "0.24", features = ["extension-module", "abi3-py39"], optional = true }
58
58
  ndarray = { version = "0.16", optional = true }
59
+ rayon = { version = "1", optional = true }
59
60
  plotters = { version = "0.3.7", default-features = false, features = ["svg_backend", "all_series"], optional = true }
60
61
  tract-onnx = { version = "0.23", optional = true }
61
62
  axum = { version = "0.8", optional = true }
@@ -90,11 +91,13 @@ smartcore-backend = ["dep:smartcore"]
90
91
  preprocessing = ["dep:imbalance-rs", "dep:ndarray"]
91
92
 
92
93
  # Cross-validation splitters, scoring, and grid/random search over a pipeline.
93
- model-selection = ["dep:model-selection-rs", "dep:ndarray"]
94
+ # CV folds and search candidates are evaluated in parallel via rayon.
95
+ model-selection = ["dep:model-selection-rs", "dep:ndarray", "dep:rayon"]
94
96
 
95
97
  # Voting / bagging / stacking meta-estimators, composed over the four traits.
96
- # Leak-free stacking uses the model-selection CV engine when it is enabled.
97
- ensemble = []
98
+ # Leak-free stacking uses the model-selection CV engine when it is enabled;
99
+ # bagging fits its base estimators in parallel via rayon.
100
+ ensemble = ["dep:rayon"]
98
101
 
99
102
  # Ingest & EDA — the front of the lifecycle. A polars-backed, dtype-aware
100
103
  # `Table` (CSV/Parquet in, real string/categorical/datetime/null columns) that
@@ -151,7 +154,7 @@ onnx = [
151
154
  # Deliberately NOT part of `full`: pyo3's `extension-module` defers libpython
152
155
  # symbols, so a plain `cargo test`/`clippy` cannot link a test binary with it.
153
156
  # Build & test this feature through maturin instead (see pyproject.toml).
154
- python = ["dep:pyo3", "smartcore-backend"]
157
+ python = ["dep:pyo3", "smartcore-backend", "model-selection", "explain", "onnx", "eda"]
155
158
 
156
159
  # Phase 5 · OPERATIONS — past where scikit-learn stops.
157
160
  # A versioned model registry: content-addressed ONNX artifact + metadata +
@@ -0,0 +1,41 @@
1
+ # The Millwright Guide
2
+
3
+ *A unified ML framework for Rust — ten crates, one lifecycle.*
4
+
5
+ The full hands-on tutorial now lives as a browsable, multi-page site:
6
+
7
+ ### → **[millwright-rs.dev/docs/](https://millwright-rs.dev/docs/)** (source in [`docs/`](docs/index.html))
8
+
9
+ It walks the whole lifecycle, one topic per page: **Data & EDA** · **Pipelines &
10
+ Models** · **Insight** (evaluate, explain, calibrate, detect) · **Deploy**
11
+ (ONNX, serving, registry, AutoML) · **Python**.
12
+
13
+ ## Quickstart
14
+
15
+ ```toml
16
+ [dependencies]
17
+ millwright = "0.1" # or features = ["full"] for the whole lifecycle
18
+ ```
19
+
20
+ ```rust
21
+ use millwright::prelude::*;
22
+
23
+ // features as rows + a target -> a Dataset
24
+ let train = Dataset::new(x, y)?;
25
+
26
+ // standardize, then a random forest — one composable object
27
+ let mut pipe = Pipeline::new()
28
+ .step("scale", StandardScaler::new())
29
+ .estimator("rf", RandomForest::new());
30
+
31
+ pipe.fit(&train)?;
32
+ let preds = pipe.predict(&test)?;
33
+ ```
34
+
35
+ ## Where to go
36
+
37
+ - **Tutorial:** <https://millwright-rs.dev/docs/> — the full, hands-on walk-through.
38
+ - **API reference:** [docs.rs/millwright](https://docs.rs/millwright).
39
+ - **Design brief (the *why*):** <https://millwright-rs.dev/>.
40
+ - **Examples:** [`examples/`](examples) — a runnable program for each feature group.
41
+ - **Python:** [`pip install millwright`](https://pypi.org/project/millwright/).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: millwright
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Intended Audience :: Science/Research
@@ -16,10 +16,19 @@ Project-URL: Repository, https://github.com/mi7plus/millwright
16
16
 
17
17
  # millwright
18
18
 
19
+ [![crates.io](https://img.shields.io/crates/v/millwright.svg)](https://crates.io/crates/millwright)
20
+ [![docs.rs](https://img.shields.io/docsrs/millwright)](https://docs.rs/millwright)
21
+ [![PyPI](https://img.shields.io/pypi/v/millwright.svg)](https://pypi.org/project/millwright/)
22
+ [![CI](https://github.com/mi7plus/millwright/actions/workflows/ci.yml/badge.svg)](https://github.com/mi7plus/millwright/actions/workflows/ci.yml)
23
+ [![downloads](https://img.shields.io/crates/d/millwright.svg)](https://crates.io/crates/millwright)
24
+ [![license](https://img.shields.io/crates/l/millwright.svg)](LICENSE)
25
+
19
26
  A unified ML framework for Rust — *ten crates, one lifecycle.*
20
27
 
21
- - **Tutorial:** [`GUIDE.md`](GUIDE.md) — the hands-on walk through the whole
22
- lifecycle (also as a page: [`guide.html`](guide.html)).
28
+ - **Tutorial:** the hands-on docs site at <https://millwright-rs.dev/docs/>
29
+ (source in [`docs/`](docs/index.html); a short [`GUIDE.md`](GUIDE.md) has the
30
+ quickstart).
31
+ - **Contributing:** see [`CONTRIBUTING.md`](CONTRIBUTING.md).
23
32
  - **Design brief:** the *why*, at **<https://millwright-rs.dev/>**.
24
33
 
25
34
  > *"Ten crates"* is the ecosystem this project assembles —
@@ -108,7 +117,12 @@ A unified ML framework for Rust — *ten crates, one lifecycle.*
108
117
  (a full round-trip); tree-ensemble ONNX-ML artifacts run in external runtimes
109
118
  like onnxruntime.
110
119
  - **Python bindings** (`src/python.rs`, via [`pyo3`], feature `python`): a
111
- `Pipeline` class over the same Rust core, built with maturin into an abi3 wheel.
120
+ `Pipeline` class over the same Rust core, shipped on [PyPI](https://pypi.org/project/millwright/)
121
+ as an abi3 wheel.
122
+
123
+ ```bash
124
+ pip install millwright
125
+ ```
112
126
 
113
127
  ```python
114
128
  import millwright as mw
@@ -119,11 +133,7 @@ pipe.fit(rows, labels) # list[list[float]], list[float]
119
133
  preds = pipe.predict(rows) # runs the Rust engine
120
134
  ```
121
135
 
122
- Build the Python module (from a virtualenv):
123
-
124
- ```bash
125
- maturin develop --features python
126
- ```
136
+ To build from source (contributors), from a virtualenv: `maturin develop --features python`.
127
137
 
128
138
  [`regression-diagnostics`]: https://crates.io/crates/regression-diagnostics
129
139
  [`shap-rs`]: https://crates.io/crates/shap-rs
@@ -1,9 +1,18 @@
1
1
  # millwright
2
2
 
3
+ [![crates.io](https://img.shields.io/crates/v/millwright.svg)](https://crates.io/crates/millwright)
4
+ [![docs.rs](https://img.shields.io/docsrs/millwright)](https://docs.rs/millwright)
5
+ [![PyPI](https://img.shields.io/pypi/v/millwright.svg)](https://pypi.org/project/millwright/)
6
+ [![CI](https://github.com/mi7plus/millwright/actions/workflows/ci.yml/badge.svg)](https://github.com/mi7plus/millwright/actions/workflows/ci.yml)
7
+ [![downloads](https://img.shields.io/crates/d/millwright.svg)](https://crates.io/crates/millwright)
8
+ [![license](https://img.shields.io/crates/l/millwright.svg)](LICENSE)
9
+
3
10
  A unified ML framework for Rust — *ten crates, one lifecycle.*
4
11
 
5
- - **Tutorial:** [`GUIDE.md`](GUIDE.md) — the hands-on walk through the whole
6
- lifecycle (also as a page: [`guide.html`](guide.html)).
12
+ - **Tutorial:** the hands-on docs site at <https://millwright-rs.dev/docs/>
13
+ (source in [`docs/`](docs/index.html); a short [`GUIDE.md`](GUIDE.md) has the
14
+ quickstart).
15
+ - **Contributing:** see [`CONTRIBUTING.md`](CONTRIBUTING.md).
7
16
  - **Design brief:** the *why*, at **<https://millwright-rs.dev/>**.
8
17
 
9
18
  > *"Ten crates"* is the ecosystem this project assembles —
@@ -92,7 +101,12 @@ A unified ML framework for Rust — *ten crates, one lifecycle.*
92
101
  (a full round-trip); tree-ensemble ONNX-ML artifacts run in external runtimes
93
102
  like onnxruntime.
94
103
  - **Python bindings** (`src/python.rs`, via [`pyo3`], feature `python`): a
95
- `Pipeline` class over the same Rust core, built with maturin into an abi3 wheel.
104
+ `Pipeline` class over the same Rust core, shipped on [PyPI](https://pypi.org/project/millwright/)
105
+ as an abi3 wheel.
106
+
107
+ ```bash
108
+ pip install millwright
109
+ ```
96
110
 
97
111
  ```python
98
112
  import millwright as mw
@@ -103,11 +117,7 @@ pipe.fit(rows, labels) # list[list[float]], list[float]
103
117
  preds = pipe.predict(rows) # runs the Rust engine
104
118
  ```
105
119
 
106
- Build the Python module (from a virtualenv):
107
-
108
- ```bash
109
- maturin develop --features python
110
- ```
120
+ To build from source (contributors), from a virtualenv: `maturin develop --features python`.
111
121
 
112
122
  [`regression-diagnostics`]: https://crates.io/crates/regression-diagnostics
113
123
  [`shap-rs`]: https://crates.io/crates/shap-rs
@@ -0,0 +1,94 @@
1
+ # Releasing
2
+
3
+ Millwright ships as one Rust crate (crates.io) and one Python wheel (PyPI), from
4
+ the same source.
5
+
6
+ ## The easy way
7
+
8
+ With `main` green and `CHANGELOG.md`'s `[Unreleased]` section filled in, run:
9
+
10
+ ```bash
11
+ bash scripts/release.sh 0.1.2
12
+ ```
13
+
14
+ It bumps both manifests, rolls the changelog, syncs `Cargo.lock`, verifies the
15
+ package, commits, tags `v0.1.2`, and (after a prompt) pushes — which triggers the
16
+ publish workflows below. The rest of this file is the manual equivalent.
17
+
18
+ ## Preflight (manual)
19
+
20
+ 1. `main` is green in CI and `CHANGELOG.md`'s `[Unreleased]` section is current.
21
+ 2. Bump the version in **both** `Cargo.toml` and `pyproject.toml`, move the
22
+ changelog's `[Unreleased]` entries under the new version, and commit.
23
+ 3. Prove the packaged crate builds exactly as crates.io will build it:
24
+
25
+ ```bash
26
+ cargo publish --dry-run --locked
27
+ ```
28
+
29
+ ## Rust crate → crates.io
30
+
31
+ Automated by the **`release-crate.yml`** workflow, which publishes on a `v*` tag
32
+ via **Trusted Publishing (OIDC)** — no token is stored anywhere. One-time setup,
33
+ on crates.io (crate → Settings → Trusted Publishing → add GitHub):
34
+
35
+ | field | value |
36
+ | ------------------- | ------------------ |
37
+ | Repository owner | `mi7plus` |
38
+ | Repository name | `millwright` |
39
+ | Workflow filename | `release-crate.yml`|
40
+ | Environment | *(leave blank)* |
41
+
42
+ Then a tag (below) publishes; a manual run (Actions → release-crate → Run
43
+ workflow) does a `--dry-run`. The workflow also fails fast if the tag doesn't
44
+ match `Cargo.toml`'s version. To publish by hand instead: `cargo login <token>`
45
+ then `cargo publish --locked`.
46
+
47
+ docs.rs builds the documentation automatically with the `full` feature set (see
48
+ `[package.metadata.docs.rs]` in `Cargo.toml`); the `python` feature is excluded
49
+ there because it needs a Python interpreter.
50
+
51
+ ## Python wheel → PyPI
52
+
53
+ The recommended path is the **`release-python.yml`** workflow, which builds
54
+ Linux (x86_64 + aarch64), macOS (x86_64 + aarch64), and Windows wheels plus an
55
+ sdist, and publishes them together via **Trusted Publishing (OIDC)** — no stored
56
+ token. One-time setup, on PyPI:
57
+
58
+ - **Existing project:** PyPI → Your projects → `millwright` → Manage →
59
+ Publishing → Add a new publisher (GitHub Actions).
60
+ - **Before the first publish:** account → Publishing → add a *pending* publisher.
61
+
62
+ | field | value |
63
+ | ------------------- | ------------------- |
64
+ | Owner | `mi7plus` |
65
+ | Repository name | `millwright` |
66
+ | Workflow name | `release-python.yml`|
67
+ | Environment | *(leave blank)* |
68
+
69
+ *(A pending publisher also needs the PyPI Project Name, `millwright`.)*
70
+
71
+ Then every release is just a tag:
72
+
73
+ ```bash
74
+ git tag vX.Y.Z && git push --tags
75
+ ```
76
+
77
+ The workflow builds all platforms and publishes on the tag. A manual run
78
+ (Actions → release-python → Run workflow) builds the wheels *without* publishing,
79
+ for a dry run.
80
+
81
+ To publish a single platform's wheel by hand instead (only installable on that
82
+ OS), from a Developer PowerShell/Prompt with a Python toolchain:
83
+
84
+ ```bash
85
+ maturin publish # needs MATURIN_PYPI_TOKEN set
86
+ ```
87
+
88
+ ## Notes
89
+
90
+ - The crate pins its engines to exact versions and commits `Cargo.lock`, so a
91
+ publish is fully reproducible.
92
+ - MSRV is declared in `Cargo.toml` (`rust-version`) and enforced by cargo for
93
+ consumers; it is dictated by transitive engine deps, so expect it to rise over
94
+ time.