This is the last of four short posts about matten. The earlier posts covered why the library exists, the numeric core, and the dynamic ingestion feature. This one covers the companion crates and, more importantly, when the right answer is to stop using matten.
Three companion crates
The matten workspace publishes three small companion crates alongside the core.
Each has a narrow, closed scope. None of them adds a dependency to the core matten crate.
matten-ndarray — the bridge to ndarray
matten-ndarray converts between matten::Tensor and ndarray::ArrayD<f64>. That is all it does.
use matten::Tensor;
use matten_ndarray::{from_arrayd, to_arrayd};
let t = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
let arr = to_arrayd(&t)?; // Tensor -> ArrayD<f64>
let back = from_arrayd(arr)?; // ArrayD<f64> -> Tensor
This exists for the handoff moment: when a proof of concept has grown to the point where a hot path needs ndarray's performance, the flat Vec<f64> data moves across without re-encoding. Status: production-ready.
matten-mlprep — small preprocessing helpers
matten-mlprep provides a handful of plain, deterministic functions for preparing a numeric tensor before feeding it somewhere else.
use matten::Tensor;
use matten_mlprep::{add_bias_column, standardize_columns, train_test_split};
let x = Tensor::new(vec![1.0, 3.0, 5.0, 7.0], &[4, 1]);
let z = standardize_columns(&x)?; // zero mean, unit std per column
let z = add_bias_column(&z)?; // prepend a 1.0 intercept column
let (train, test) = train_test_split(&z, 0.75)?;
minmax_scale_columns is also available. There is no model training, no autograd, no randomness, and no hidden state. When you need those things, reach for a real ML crate — this one deliberately stops at the preprocessing step. Status: production-ready candidate.
matten-data — CSV to Tensor via named columns
matten-data handles the step between a CSV file with a header row and a clean numeric tensor, letting you select columns by name.
use matten_data::Table;
let csv = "sales,cost,note\n10,2,a\n20,,b\n30,4,c";
let tensor = Table::from_csv_str(csv)?
.select_columns(["sales", "cost"])? // pick numeric columns by name
.fill_missing(0.0)? // clean missing values explicitly
.try_numeric()? // convert
.to_tensor()?; // -> matten::Tensor, shape [3, 2]
This crate is CSV-only by design. If you need JSON ingestion, that lives in the core matten crate (from_json, from_json_dynamic). Status: production-ready candidate.
When to move on
This is probably the more useful part of this post.
matten is designed for early-stage work: trying out an algorithm, sketching a data pipeline, getting a Rust numerical idea off the ground quickly. At some point a project may outgrow that role, and it is worth being specific about when that is.
Move a hot path to ndarray or nalgebra when benchmark numbers start to matter for that part of the code. A 64×64 matrix multiply in matten currently runs at roughly 7–9× the time of the equivalent in ndarray or nalgebra on the same machine. For a one-off computation that is usually fine; for something called in a tight loop it is not. The matten-ndarray bridge handles the data handoff.
Move to ndarray directly when you need strided views, zero-copy slicing, or BLAS integration. matten's slice API copies data; ndarray's views do not.
Move to a dedicated ML framework (Candle, Burn, tch-rs) when you need autograd, model training, or GPU acceleration. matten-mlprep covers preprocessing; it is not a substitute for any of those.
Stay with matten for as long as the problem is small, the data fits comfortably in memory, and developer time is more constrained than runtime.
The library is still pre-1.0. The numeric core is stable in practice, the companion crates are production-ready candidates, and v1 will come when the developers / maintainers are confident it is ready — not on a schedule. Feedback and issues are welcome on the repository.
Links: crates.io · docs.rs · mdBook · repository
Top comments (0)