A family-car multidimensional array (tensor) library for Rust — for learning, teaching, small workflows, and early prototypes, with a small, optional ecosystem of companion crates around it.
This repository is a Cargo workspace, where core matten stays small and dependency-light.
| Crate | Version | Status | What it is |
|---|---|---|---|
matten |
0.46.x family | stable (v0.x) | The core f64 tensor library: construction, shape ops, broadcasting, slicing, reductions, matmul, JSON/CSV boundary APIs, and an optional dynamic ingestion on-ramp. |
matten-ndarray |
0.46.x family | production-ready | Conversion bridge between matten::Tensor and ndarray::ArrayD<f64>. |
matten-mlprep |
0.46.x family | production-ready | Transparent, deterministic preprocessing helpers (standardize, min-max scale, bias column, train/test split). |
matten-data |
0.46.x family | production-ready | Small, honest CSV→tensor ingestion (Table with explicit missing-value handling and strict numeric conversion). Deliberately not a dataframe library. |
matten-stats |
0.46.x family | production-ready candidate | Small, explicit scalar statistics (covariance, covariance_population, correlation, quantile, skewness, kurtosis) over matten::Tensor. Estimator conventions differ per function, matching each function's own ecosystem default. |
All crates share one family version (RFC-030): matching numbers mean a
matched, compatible set. A crate's maturity is the Status column, not its
version number — a crate may sit at the shared family version and still be beta.
Internally, companion crates inherit a broad core requirement from
[workspace.dependencies] for maintenance (matten = "0" plus the workspace path),
but user-facing examples still pin the matched family explicitly (RFC-064).
These labels describe stability within matten's documented scope (PoC,
learning, and small workflows): production-ready means dependable for that
scope, not a performance or scale claim — matten optimizes for time to a
runnable PoC, not benchmark leadership. Full rung definitions are in the
maturity ladder.
matten is a developer-experience-first multidimensional array (tensor) library
for Rust. It makes learning-oriented, teaching-oriented, early-stage numerical,
and data-exploration work feel close to NumPy/Pandas ergonomics while staying
native Rust: one concrete Tensor type, no visible lifetimes, no generic dtype
puzzles, and human-readable failures.
It deliberately favors developer experience over peak performance, and is not
a replacement for ndarray, nalgebra, or candle on hot paths.
matten-ndarray converts between matten's numeric Tensor and ndarray's
dynamic-dimension ArrayD<f64>, and nothing else. It is the first companion
crate in the matten workspace and exists to let you hand data off to the
ndarray ecosystem when you outgrow matten's family-car scope.
It adds no dependency to core matten, wraps none of the ndarray API, and
exposes no view or lifetime types.
matten-mlprep provides a handful of plain functions for preparing numeric
tensors before handing them to an external tool. There is no model training, no
autograd, no optimizer, and no hidden randomness — every function is a pure,
deterministic transform you can read and reason about.
It depends only on core matten (no default features); it adds no
ndarray, candle, or rand dependency.
Use core matten to get a NumPy-like tensor going quickly in Rust without
generics, lifetimes, or view-type puzzles. It is intentionally approachable for
learning tensor shapes, teaching small numerical transformations, and building a
readable first version before handing off to a heavier ecosystem. Reach for a
companion crate only when you need to cross a boundary — e.g. matten-ndarray
to hand data to the ndarray ecosystem. Core stays a family car; companions are
the trailer hitch.
[dependencies]
matten = "0.46.0"use matten::Tensor;
let a = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
assert_eq!(a.shape(), &[2, 2]);
assert_eq!(a.ndim(), 2);
// Boundary-style construction is recoverable instead of panicking:
use matten::MattenError;
let bad = Tensor::try_new(vec![1.0, 2.0, 3.0], &[2, 2]);
assert!(matches!(bad, Err(MattenError::Shape { .. })));More examples are here.
Optional dynamic on-ramp — ingest heterogeneous, possibly-messy values, then land in a
clean f64 tensor under an explicit policy (off by default; see the dynamic
guide):
[dependencies]
matten = { version = "0.46.0", features = ["dynamic"] }use matten::{Element, NumericPolicy, Tensor};
// Heterogeneous, possibly-messy inputs — ints, floats, and a missing value:
let raw = vec![Element::Int(1), Element::Float(2.5), Element::None, Element::Int(4)];
let dynamic = Tensor::from_elements(raw, &[2, 2]);
assert!(dynamic.is_dynamic());
// Land in a clean f64 tensor under an explicit policy (here: missing -> 0.0):
let numeric = dynamic.try_numeric_with(NumericPolicy::default().none_as(0.0))?;
assert_eq!(numeric.as_slice(), &[1.0, 2.5, 0.0, 4.0]);
# Ok::<(), matten::MattenError>(())[dependencies]
matten = "0.46.0"
matten-ndarray = "0.46.0"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
# Ok::<(), matten_ndarray::MattenNdarrayError>(())More examples are here.
[dependencies]
matten = "0.46.0"
matten-mlprep = "0.46.0"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)?;
# Ok::<(), matten_mlprep::MattenMlprepError>(())More examples are here.
- One primary type. Users work through
matten::Tensor. The public root also exposesMattenErrorandDataFormat; the dynamicElementengine is a feature-gated dynamic on-ramp; it is off by default. - Two error zones. Local convenience APIs panic with actionable messages for
fast PoC feedback; every external boundary returns
Result<_, MattenError>and never panics on ordinary invalid input.MattenErrorderives onlyDebug, so match it by variant, not==. - Convenient by default, lean on request.
default = ["serde", "json", "csv"]for a smooth first run;default-features = falsefor the lean core. - Safe Rust only. The crate is
#![forbid(unsafe_code)].
- Both directions copy. No zero-copy is claimed; that would need layout guarantees out of scope for an experimental bridge.
- Logical order is preserved.
from_arraydconverts a non-standard-layoutArrayD(transposed / sliced) by its logical element order, never the raw backing buffer. - Zero-sized axes are rejected. Core
mattendoes not support zero-length dimensions, sofrom_arraydreturns an error for them. - Dynamic tensors are rejected, not panicked. With the
dynamicfeature, passing a dynamic (Element) tensor returnsMattenNdarrayError::DynamicTensor; convert it withTensor::try_numeric()first. - Supported
ndarray: the0.17minor (CI targets0.17.2).
- Convention: rank-2 only,
rows = samples,columns = features. No silent transposition; a non-2D input is an error. - Population std.
standardize_columnsdivides byn(like scikit-learn'sStandardScaler). - Constant columns error, not silently zero. A zero-variance / zero-range
column returns
MattenMlprepError::ZeroVariance { column }so you handle it deliberately. add_bias_columnprepends the1.0column (intercept at index 0).train_test_splitis ordered and deterministic —first floor(n*ratio)rows are train, the rest are test. No shuffle. (A seeded variant is planned; see RFC-024 §6.)- Dynamic tensors are rejected, not panicked (with the
dynamicfeature).
- Documentation: nabbisen.github.io/matten (mdBook).
- Playground: try broadcasting, reshape, axis reductions, and
matmulin the browser — nabbisen.github.io/matten/playground.html. - Roadmap:
ROADMAP.md(canonical for v0.16+). - Design decisions:
rfcs/— see RFC-022 (boundary policy), RFC-025 (bridge policy), RFC-030 (family versioning).
Licensed under the Apache License, Version 2.0. See LICENSE and
NOTICE.