Matrices is a matrix computation and linear algebra library for Dart and Flutter. It focuses on high performance and script-style ergonomics, providing NumPy/MATLAB-like construction, indexing, broadcasting, and operator semantics, while keeping explicit 32-bit and 64-bit precision choices.
Project features:
- Uses Dart's built-in
SIMDAPIs to accelerate computation, without depending on FFI or platform-specific binary runtimes. - Uses contiguous typed-data storage to avoid performance and semantic issues from nested-list structures.
- Explicit
Matrix64/Matrix32andVector64/Vector32APIs. - Covers common dense matrices, CSR sparse matrices, direct decompositions, advanced decompositions, iterative methods, and Krylov methods.
import 'package:matrices/matrices.dart';
void main() {
final a = mat([
[1, 2, 3],
[4, 5, 6],
]);
final b = mat([
[7, 8],
[9, 10],
[11, 12],
]);
final x = vec([1, 1, 1]);
print(a * b); // Matrix64 matrix multiplication
print(a * x); // Matrix64 * Vector64
print(a + 1); // scalar broadcasting
print(a + x); // row-vector broadcasting
print(a.transpose);
}Explicit 32-bit path:
final a32 = mat32([
[1, 2],
[3, 4],
]);
final x32 = vec32([1, 1]);
print(a32 * x32); // Vector32
print(a32 * a32); // Matrix32
print(a32.toFloat64()); // Matrix64| Type | Storage | Use |
|---|---|---|
Matrix64 |
Float64List |
Default high-precision dense matrix |
Matrix32 |
Float32List |
Throughput and memory-oriented dense matrix |
Vector64 |
Float64List |
Default high-precision vector |
Vector32 |
Float32List |
Throughput-oriented float32 vector |
Matrix |
typedef Matrix = Matrix64 |
Script-friendly short name |
Vector |
typedef Vector = Vector64 |
Script-friendly short name |
Matrix and Vector are 64-bit aliases, not separate implementations. Public
APIs and documentation should prefer Matrix64 / Vector64 when precision must
be explicit. Use Matrix32 / Vector32 for float32. The package does not
silently downgrade precision.
Default constructors use full words, with short aliases for script-style code. Examples use short aliases for interactive ergonomics.
| Default API | Short alias | Return type |
|---|---|---|
matrix([...]) |
mat([...]) |
Matrix64 |
matrix32([...]) |
mat32([...]) |
Matrix32 |
vector([...]) |
vec([...]) |
Vector64 |
vector32([...]) |
vec32([...]) |
Vector32 |
Helper constructors follow the same pattern, for example vectorZeros /
vecZeros and vectorLinspace32 / vecLinspace32.
64-bit matrices:
final a = Matrix64([
[1, 2],
[3, 4],
]);
final b = Matrix([
[1, 2],
[3, 4],
]); // Matrix is an alias for Matrix64
final z = zeros(2, 3);
final o = ones(2, 3);
final f = full(2, 3, 9);
final r = rand(2, 3, seed: 42);
final i = eye(4);
final d = diag([1, 2, 3]);
final grid = arange(0, 12, columns: 4);
final samples = linspace(0, 1, 5);32-bit matrices:
final a32 = Matrix32([
[1, 2],
[3, 4],
]);
final z32 = zeros32(2, 3);
final o32 = ones32(2, 3);
final f32 = full32(2, 3, 1.5);
final r32 = rand32(1024, 1024, seed: 7);
final i32 = eye32(4);
final d32 = diag32([1, 2, 3]);
final grid32 = arange32(0, 12, columns: 4);
final samples32 = linspace32(0, 1, 5);Other constructors:
final fromRows = Matrix64.fromRows([[1, 2], [3, 4]]);
final fromColumns = Matrix64.fromColumns([[1, 3], [2, 4]]);
final fromFlat = Matrix64.fromFlat([1, 2, 3, 4], 2, 2);
final fromBytes = Matrix64.fromByteData(
Float64List.fromList([1, 2, 3, 4]).buffer.asByteData(),
2,
2,
);
final spd = Matrix64.randomSPD(4, seed: 1);final a = arange(0, 9, columns: 3);
print(a(1, 2)); // element access
print(a[1][2]); // row view access
print(a.row(0)); // Vector64
print(a.column(1)); // Vector64
a.set(0, 0, 99);
a[1][1] = 42;
final rows = a.toRows();
final values = a.values; // Float64List copy
final unsafe = a.unsafeValuesView; // internal storage view for advanced use
final json = a.toJson();
final restored = Matrix64.fromJson(json);Rows are fixed-length views. Vector64 and Vector32 are fixed-length linear
algebra objects that implement Iterable<double>; they are not growable
List<double> objects.
final a = mat([
[1, 2],
[3, 4],
]);
final b = mat([
[5, 6],
[7, 8],
]);
final x = vec([1, 1]);
print(a + b); // elementwise addition
print(a - 1); // scalar broadcasting
print(a * 2); // scalar multiplication
print(a * b); // matrix multiplication
print(a * x); // matrix-vector multiplication
print(x * a); // vector-matrix multiplication
print(a + x); // row-vector broadcasting
print(a.hadamard(b)); // Hadamard elementwise multiplication
print(a / 2);The meaning of * depends on the right-hand side:
matrix * matrixis matrix multiplication.matrix * vectoris matrix-vector multiplication.vector * matrixis row-vector matrix multiplication.matrix * numberandvector * numberare scalar multiplication.- Use
hadamardfor elementwise matrix multiplication.
Invalid shapes throw ArgumentError immediately. The library does not silently
reshape, pad, or truncate data.
final a = arange(0, 12, columns: 4);
print(a.transpose);
print(a.t);
print(a.reshape(4, 3));
print(a.slice(rowStart: 1, rowEnd: 3, columnStart: 1));
print(a.sample(rowIndices: [0, 2], columnIndices: [1, 3]));
print(a.vstack(a));
print(a.hstack(a));
print(a.flatten());Transforms and statistics:
final centered = a.mapColumns((column) => column - column.mean);
final selected = a.filterRows((row, index) => row.sum > 10);
final sorted = a.sort((row) => row.sum, direction: SortDirection.desc);
print(a.mean);
print(a.meanByAxis(Axis.columns));
print(a.variance(Axis.rows));
print(a.deviation(Axis.rows));
print(a.norm());final x = vec([1, 2, 3]);
final y = vec([4, 5, 6]);
final x32 = vec32([1, 2, 3, 4]);
final y32 = vec32([5, 6, 7, 8]);
print(x.dot(y));
print(x.norm());
print(x.distanceTo(y, Distance.euclidean));
print(x.cosine(y));
print(x.normalize());
print(x.subvector(1));
print(x.unique());
print(x32.dot(y32));
print(x32 + y32);Helper constructors:
final z = vecZeros(3);
final o = vecOnes(3);
final f = vecFull(3, 2);
final r = randVec(3, seed: 1);
final range = vecRange(0, 10, step: 2);
final line = vecLinspace(0, 1, 5);
final z32 = vecZeros32(4);
final o32 = vecOnes32(4);final a = mat([
[4, 7],
[2, 6],
]);
print(a.determinant);
print(a.inverse);
print(a.trace);
print(a.rank);
print(a.rref());
final rhs = mat([
[1],
[0],
]);
print(a.solve(rhs));Square-matrix entry point:
final s = SquareMatrix.fromList([
[4, 7],
[2, 6],
]);
print(s.determinant);
print(s.inverse);
print(s.logAbsDeterminant);Direct solve, determinant, and inverse use pivoting. Singular matrices throw at working precision. Ill-conditioned problems should pass explicit tolerances.
final a = mat([
[4, 1],
[1, 3],
]);
final lu = a.lu();
final qr = a.qr();
final cholesky = a.cholesky();
final eigen = a.eigenSymmetric();
final svd = a.svd();
print(lu.solve(mat([[1], [2]])));
print(qr.q * qr.r);
print(cholesky.lower * cholesky.lower.transpose);
print(eigen.values);
print(svd.singularValues);High-level APIs:
final design = mat([
[1, 1],
[1, 2],
[1, 3],
]);
final observed = mat([
[1],
[2],
[2],
]);
print(design.leastSquares(observed));
print(design.pseudoInverse());
final pca = design.pca(components: 1);
print(pca.components);
print(pca.explainedVariance);| API | Applies to | Typical use |
|---|---|---|
lu() |
square matrices | Direct solve, determinant, pivoted factorization |
qr() |
tall or full-rank square matrices | Least squares, orthogonalization |
cholesky() |
symmetric positive definite matrices | SPD solve and factorization |
eigenSymmetric() |
real symmetric matrices | Full eigenvalues and eigenvectors |
eigen() |
symmetric full solve or power iteration | High-level eigen interface |
svd() |
rectangular or square matrices | Low-rank analysis, pseudoinverse |
pseudoInverse() |
rectangular or square matrices | Moore-Penrose pseudoinverse |
leastSquares(rhs) |
overdetermined or full-rank systems | Least squares |
pca() |
observation x feature matrix | Principal component analysis |
Matrix32 exposes the same high-level API names. Matrix-valued results keep
32-bit storage; scalar accumulations may use Dart double temporaries where the
language requires it.
final a = mat([
[4, 1],
[1, 3],
]);
final b = vec([1, 2]);
print(a.jacobi(b).solution);
print(a.gaussSeidel(b).solution);
print(a.sor(b, omega: 1.1).solution);
print(a.conjugateGradient(b).solution);
print(a.gmres(b, restart: 20).solution);
print(a.arnoldi(b, 2).hessenberg);
print(a.powerIteration().eigenvalue);Result objects include convergence status, iteration count, residual norm, and
the computed solution or eigenpair. Iterative methods are sensitive to initial
guess, tolerance, conditioning, and expected convergence properties. Production
code should set tolerance and maxIterations explicitly.
final sparse = SparseMatrix.fromRows([
[1, 0, 2],
[0, 0, 3],
[4, 0, 0],
]);
print(sparse.nnz);
print(sparse.mv(vec([1, 2, 3])));
print(sparse.matmul(eye(3)));
print(sparse.transpose().toDense());
final fromTriplets = SparseMatrix.fromTriplets(3, 3, [
SparseEntry(0, 0, 1),
SparseEntry(0, 2, 2),
SparseEntry(2, 1, 4),
]);
final compact = mat([
[1, 0],
[0, 2],
]).toSparse();Sparse matrices use CSR storage. The sparse API is separate from
Matrix64 / Matrix32 dense kernels and is intended for matrices where nonzero
entries are a small fraction of total entries.
Recommended choices:
- Use
Matrix64/Vector64by default. - Use
Matrix32/Vector32for large throughput-oriented workloads when float32 error is acceptable. - Set explicit tolerances for ill-conditioned matrices, rank decisions, near-singular systems, and iterative solvers.
Matrix32 and Vector32 use Float32x4 on suitable hot paths, including
matrix multiplication, matrix-vector multiplication, vector-matrix
multiplication, elementwise arithmetic, scalar arithmetic, dot products, sums,
and norms. Matrix64 / Vector64 use Float64List and Float64x2-oriented
kernels.
API scope:
- Covers common dense-matrix workflows: construction, indexing, shape operations, arithmetic, statistics, JSON, decompositions, and solves.
- Adds QR, SVD, pseudoinverse, least squares, PCA, iterative methods, Krylov methods, and CSR sparse matrices.
- Does not include native BLAS/LAPACK, GPU execution, autodiff, distributed matrices, or full complex nonsymmetric eigensolvers.
Benchmark scripts use an AOT runner by default. JIT is for smoke/debug runs and must not be used for published performance claims.
Focused matrix multiplication comparison:
dart run test/benchmark_matmul.dart 256,512,1024 3 1 build/performance_report.mdBroader benchmark suite:
dart run test/benchmark_suite.dart 100,256,512,1000 3 1 build/benchmark_suite.mdThe suite covers construction, scalar/elementwise operations, broadcasting, square and rectangular matrix multiplication, matrix-vector, vector-matrix, transpose, direct solves, vectors, sparse matrices, decompositions, least squares, statistics, and iterative/Krylov algorithms.
Generated reports:
Performance claims must name:
- Dart SDK and command line.
- AOT/JIT mode.
- Precision: float32 or float64.
- Matrix size and shape.
- Compared package version and interface.
- Iterations, warmups, and sample count.
Routine checks:
dart format --output=none --set-exit-if-changed lib test
dart analyze
dart testOptional performance regression:
MATRICES_PERF_REGRESSION=1 dart test test/performance_regression_test.dartThe test suite covers:
- Construction, indexing, shape validation, and error paths.
Matrix64/Matrix32/Vector64/Vector32arithmetic.- Matrix multiplication against reference implementations.
- LU, QR, Cholesky, determinant, inverse, and solve.
- Symmetric eigen, SVD, pseudoinverse, least squares, PCA.
- Jacobi, Gauss-Seidel, SOR, CG, GMRES, Arnoldi, power iteration.
- CSR sparse construction, conversion, transpose, serialization, vector multiply, and dense multiply.
- Randomized property tests, ill-conditioned Hilbert residuals, near-singular tolerance behavior, and float32 tolerances.
Recommended quality gates should at least include format checks, static analysis, tests, benchmark smoke, coverage artifacts, and API reference generation.
Matrices fails fast on shape and numerical preconditions:
mat([[1, 2]]) * mat([[1, 2]]); // ArgumentError
mat([[1, 2], [2, 4]]).inverse; // StateError
mat([[1, 2], [3, 4]]).cholesky(); // StateErrorThe package does not silently reshape, pad, truncate, or change precision.