diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf2b7b..5f4f55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- +## [0.8.0] — Tensors & Randomness + +### Added +- `src/tensor.rs` — New N-dimensional generic `Tensor` implementation + - Row-major flat storage with stride-based indexing + - Generic over any type `T` (f32, f64, i32, etc.) + - Support for `+`, `-`, scalar `*`, and `.hadamard()` + - Recursive `Display` implementation for any rank +- **Randomness Support** (for both `Matrix` and `Tensor`) + - `.rand()` / `.rand_seeded()` — Uniform distribution + - `.randn()` / `.randn_seeded()` — Standard normal distribution (floats only) + - Deterministic seeded constructors for reproducible benchmarks and tests +- `rand` and `rand_distr` dependencies + +### Changed +- `Matrix::det` and `Matrix::inverse` updated with better internal pivot selection +- Internal `new_unchecked` constructor for `Matrix` to avoid redundant checks in internal math routines + +--- + ## [0.7.0] — Parallel Execution ### Added diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 0000000..287770c --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,169 @@ +# NumRS Examples + +This guide provides full, runnable examples of how to use NumRS for common linear algebra tasks. + +--- + +## 1. Solving Linear Systems ($Ax = b$) + +To solve a system of linear equations like: +$3x + y = 10$ +$2x + 4y = 20$ + +We can represent this as $Ax = b$ and solve for $x$ using $x = A^{-1}b$. + +```rust +use numrs::{ns_array, Matrix}; + +fn main() { + let a = ns_array![[3.0, 1.0], [2.0, 4.0]]; + let b = ns_array![[10.0], [20.0]]; + + // Solve x = A⁻¹ * b + let a_inv = a.inverse().expect("Matrix must be invertible"); + let x = &a_inv * &b; + + println!("Solution x:\n{}", x); + // Expected: x ≈ 2, y ≈ 4 +} +``` + +--- + +## 2. Markov Chain Simulation + +Simulate a simple 2-state Markov chain (e.g., Weather: Sunny/Rainy). + +```rust +use numrs::{ns_array, Matrix}; + +fn main() { + // Transition matrix: + // Sunny Rainy + // Sunny [ 0.9, 0.1 ] + // Rainy [ 0.5, 0.5 ] + let p = ns_array![[0.9, 0.1], [0.5, 0.5]]; + + // Initial state: 100% Sunny + let mut state = ns_array![[1.0, 0.0]]; + + println!("Initial state: {}", state); + + for i in 1..=5 { + state = &state * &p; + println!("After step {}: {}", i, state); + } +} +``` + +--- + +## 3. Least Squares Fitting + +Find the best-fit line $y = mx + c$ for a set of points $(1, 2), (2, 3), (3, 5), (4, 4)$. +The normal equation is $x = (A^T A)^{-1} A^T b$. + +```rust +use numrs::{ns_array, Matrix}; + +fn main() { + // Design matrix A (ones in second column for intercept c) + let a = ns_array![ + [1.0, 1.0], + [2.0, 1.0], + [3.0, 1.0], + [4.0, 1.0] + ]; + + // Observation vector b + let b = ns_array![[2.0], [3.0], [5.0], [4.0]]; + + let at = a.transpose(); + let at_a_inv = (&at * &a).inverse().expect("Matrix not invertible"); + let weights = &(&at_a_inv * &at) * &b; + + println!("Weights (m, c):\n{}", weights); +} +``` + +--- + +## 4. Graph Path Counting + +Given an adjacency matrix $A$ of a graph, $(A^k)_{ij}$ gives the number of paths of length $k$ from node $i$ to node $j$. + +```rust +use numrs::{ns_array, Matrix}; + +fn main() { + // A directed graph: 0 -> 1, 1 -> 2, 2 -> 0, 2 -> 1 + let a = ns_array![ + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 1.0, 0.0] + ]; + + // Paths of length 3 + let a2 = &a * &a; + let a3 = &a2 * &a; + + println!("Number of paths of length 3:\n{}", a3); +} +``` + +--- + +## 5. 2D Transformations + +Applying rotation and scaling to a 2D point. + +```rust +use numrs::{ns_array, Matrix}; + +fn main() { + let point = ns_array![[1.0], [0.0]]; // Point (1, 0) + + // 90-degree rotation matrix + let angle = std::f64::consts::FRAC_PI_2; + let rotation = ns_array![ + [angle.cos(), -angle.sin()], + [angle.sin(), angle.cos()] + ]; + + // Scaling matrix (2x) + let scale = ns_array![ + [2.0, 0.0], + [0.0, 2.0] + ]; + + let transformed = &scale * &(&rotation * &point); + println!("Transformed point:\n{}", transformed); +} +``` + +--- + +## 6. Hill Cipher (Simplified) + +Encryption using a key matrix. Note: For traditional Hill Cipher, operations are performed modulo 26. Since NumRS uses `f64`, this example demonstrates the linear algebra core. + +```rust +use numrs::{ns_array, Matrix}; + +fn main() { + // Key matrix (must be invertible) + let key = ns_array![[6.0, 24.0, 1.0], [13.0, 16.0, 10.0], [20.0, 17.0, 15.0]]; + + // Plaintext "ACT" -> [0, 2, 19] + let plaintext = ns_array![[0.0], [2.0], [19.0]]; + + // Encrypt: C = K * P + let ciphertext = &key * &plaintext; + println!("Raw ciphertext:\n{}", ciphertext); + + // Decrypt: P = K⁻¹ * C + let key_inv = key.inverse().expect("Key must be invertible"); + let decrypted = &key_inv * &ciphertext; + println!("Decrypted plaintext:\n{}", decrypted); +} +``` diff --git a/README.md b/README.md index a04cc61..f1b3c76 100644 --- a/README.md +++ b/README.md @@ -1,166 +1,58 @@ # NumRS 🦀 -A lightweight linear algebra library built from scratch in Rust — -with an intuitive macro syntax, colored terminal output, and clean modular design. +A lightweight, high-performance linear algebra and tensor library built from scratch in Rust. NumRS provides an intuitive API, automatic parallelism for large matrices, and a clean modular design. --- -## Features +## Key Features -- `ns_array!` macro — write matrices like Python -- Colored terminal display with per-column alignment -- Full operator support for both owned and reference types (`+` `-` `*`) -- Scalar multiplication in both directions (`matrix * 2.0` and `2.0 * matrix`) -- Utility constructors: `zeros`, `ones`, `eye`, `fill` -- Direct element access via `matrix[(i, j)]` -- Mathematical operations: transpose, Hadamard, norm, determinant, inverse -- **Automatic parallelism** via Rayon for large matrices +- **2D Matrix & N-D Tensor**: Specialized `Matrix` for linear algebra and generic `Tensor` for multi-dimensional data. +- **Intuitive Syntax**: Create matrices with the `ns_array!` macro — just like Python/NumPy. +- **Automatic Parallelism**: Silently scales across CPU cores using Rayon for large-scale computations. +- **Robust Math**: Transpose, Determinant, Inverse (Gauss-Jordan), Norm, and Hadamard product. +- **Beautiful Output**: Colored terminal display with automatic column alignment. +- **Reference-First Operators**: Clean arithmetic (`a + b`, `a * b`) with zero unnecessary clones. --- ## Quick Start -```bash -git clone https://github.com/sinamsv/NumRS.git -``` - -```bash -cargo run -``` - ---- - -## API Reference - -### Constructors - -| Method | Description | -|---------------------------|------------------------------------| -| `ns_array![[...], [...]]` | Create matrix from literal values | -| `Matrix::zeros(r, c)` | Matrix of zeros | -| `Matrix::ones(r, c)` | Matrix of ones | -| `Matrix::eye(n)` | n×n identity matrix | -| `Matrix::fill(r, c, v)` | Matrix filled with a specific value| - -### Operators - -| Expression | Returns | Description | -|---------------|------------------------------|-------------------------------------| -| `&a + &b` | `Matrix` | Matrix addition (panics on error) | -| `a.try_add(&b)`| `Result`| Safe matrix addition | -| `&a - &b` | `Matrix` | Matrix subtraction (panics on error)| -| `a.try_sub(&b)`| `Result`| Safe matrix subtraction | -| `&a * &b` | `Matrix` | Matrix multiplication (panics) | -| `a.try_mul(&b)`| `Result`| Safe matrix multiplication | -| `&a * 2.0` | `Matrix` | Scalar multiplication | -| `2.0 * &a` | `Matrix` | Scalar multiplication (commutative) | - -All operators are also available in owned form (`a + b`, `a * b`, etc.) -so you can choose based on whether you need to reuse the original. - -### Indexing - -```rust -let val = matrix[(i, j)]; // read element -matrix[(i, j)] = 3.14; // write element -``` - -### Mathematical Operations - -| Method | Returns | Description | -|------------------|-------------------------------|------------------------------------------| -| `.transpose()` | `Matrix` | Swap rows and columns | -| `.hadamard(&b)` | `Result` | Element-wise multiplication | -| `.norm()` | `f64` | Frobenius norm | -| `.det()` | `Result` | Determinant (square matrices only) | -| `.inverse()` | `Result` | Inverse via Gauss-Jordan | - ---- - -## Error Handling - -NumRS uses a custom `MatrixError` enum for robust error handling. While standard operators (`+`, `-`, `*`) will panic with a descriptive message on invalid input (like shape mismatches), the library provides `try_*` variants and `Result`-returning methods for safe contexts. - ```rust -use numrs::{ns_array, MatrixError}; +use numrs::{ns_array, Matrix}; -let a = ns_array![[1, 2]]; -let b = ns_array![[1, 2, 3]]; +fn main() { + let a = ns_array![[1, 2], [3, 4]]; + let b = Matrix::eye(2); -match a.try_add(&b) { - Err(MatrixError::ShapeMismatch { expected, found }) => { - println!("Error: expected {:?}, got {:?}", expected, found); - } - _ => unreachable!() + let result = &a * &b; + println!("A x I =\n{}", result); } ``` --- -## Parallel Execution +## Documentation & Learning -NumRS automatically parallelizes operations on large matrices using [Rayon](https://github.com/rayon-rs/rayon). - -- **Threshold**: Operations switch to parallel mode when the number of elements reaches `PAR_THRESHOLD` (currently 1024). -- **Tuning**: You can find this constant in `src/parallel.rs`. -- **Operations**: Addition, subtraction, multiplication, transposition, Hadamard product, and Frobenius norm are all parallelized. - ---- - -## Project Structure - -``` -src/ -├── main.rs # Entry point and demos -├── lib.rs # Crate root and exports -├── matrix.rs # Matrix struct, Display, ns_array! macro, constructors -├── math.rs # Mathematical operations -├── error.rs # MatrixError enum and Error implementation -├── parallel.rs # Parallelization utilities and thresholds -└── ops/ - ├── mod.rs - ├── add.rs # Add trait and try_add - ├── sub.rs # Sub trait and try_sub - ├── mul.rs # Mul trait (matrix × matrix) and try_mul - ├── scalar.rs # Mul and f64 * Matrix - └── index.rs # Index and IndexMut traits -``` - ---- - -## What You Can Build With This - -| Use Case | Operations needed | -|---------------------------|------------------------------------| -| Solve linear systems Ax=b | `inverse()` + `*` | -| Hill Cipher (cryptography)| `*`, `det()`, `inverse()` | -| 2D/3D transformations | `*`, constructors | -| Markov chain simulation | `*` repeatedly on state vector | -| Least squares fitting | `transpose()`, `inverse()`, `*` | -| Graph path counting | Matrix powers via `*` | +- **[Usage Guide](USAGE.md)** — Detailed API documentation for all modules. +- **[Examples](EXAMPLES.md)** — Practical recipes for solving linear systems, Markov chains, transformations, and more. +- **[Changelog](CHANGELOG.md)** — History of releases and recent changes. --- ## Roadmap - [x] Matrix struct with colored Display -- [x] Add, Sub, Mul operators (owned + reference) -- [x] Scalar multiplication -- [x] Constructors: zeros, ones, eye, fill -- [x] Index / IndexMut -- [x] Transpose, Hadamard, Norm, Determinant, Inverse +- [x] Full operator support (Add, Sub, Mul, Scalar) +- [x] Constructors (zeros, ones, eye, rand) +- [x] Determinant and Inverse - [x] Automatic parallelism via Rayon -- [x] Comprehensive error handling with `MatrixError` +- [x] Comprehensive error handling +- [/] **Tensor Support** (In Development) - [ ] Eigenvalues and eigenvectors - [ ] LU / QR decomposition -- [ ] Tensor support -- [ ] ML layer: ReLU, softmax, sigmoid, gradient tracking +- [ ] ML layer: ReLU, softmax, gradient tracking --- -## Documentation - -- [USAGE.md](USAGE.md) -- [CHANGELOG.md](CHANGELOG.md) ## License diff --git a/USAGE.md b/USAGE.md index 603bf2b..907c71a 100644 --- a/USAGE.md +++ b/USAGE.md @@ -2,34 +2,25 @@ This guide explains everything you need to know to use NumRS: what each constructor, operator, and method does, how the two core types relate to -each other, and what isn't built yet. Every example below was run against -the actual source to confirm it compiles and produces the output shown. +each other, and the internal design decisions. --- ## What is NumRS? NumRS is a linear algebra and tensor library built from scratch in Rust, -with two core types: +designed for both ease of use and performance. | Type | Dimensions | Element type | What it's for | |-------------|------------|---------------|--------------------------------------------------| | `Matrix` | 2D only | `f64` only | Linear algebra: solving systems, transforms, crypto math | | `Tensor` | N-D, any rank | generic `T` | N-dimensional data — the basis for future AI/tensor work | -Both share the same underlying idea: a single flat `Vec` holding the data -in **row-major** order, instead of nested `Vec>`. This is faster -(one heap allocation, cache-friendly) and is the standard layout used by -NumPy, ndarray, and most numerical libraries. - -`Matrix` and `Tensor` are **independent types** — `Tensor` was added -without changing a single line of `Matrix`'s public API. - --- ## Installation -NumRS isn't published to crates.io yet. Use it as a path or git dependency: +NumRS can be included as a path or git dependency in your `Cargo.toml`: ```toml [dependencies] @@ -37,272 +28,88 @@ numrs = { path = "../NumRS" } # or: numrs = { git = "https://github.com/sinamsv/NumRS.git" } ``` -Its own dependencies (already in `Cargo.toml`) are: - -| Crate | Why it's there | -|--------------|--------------------------------------------------------------| -| `colored` | Colored terminal output for `Display` (both `Matrix` and `Tensor`) | -| `num-traits` | Gives `Tensor` generic access to `T::zero()` / `T::one()` so it isn't locked to `f64` | - --- -## Quick Start +## Detailed API Documentation -```rust -use numrs::{Matrix, Tensor, ns_array}; +### 1. `numrs::matrix` — 2D Matrix Operations -fn main() { - // A 2D matrix - let a = ns_array![[1, 2, 3], [4, 5, 6]]; - println!("{}", a); +The `Matrix` type is the workhorse for 2D linear algebra. It uses a flat `Vec` in **row-major** order. - // A 3D tensor of i64 - let t: Tensor = Tensor::zeros(&[2, 2, 2]); - println!("{}", t); -} -``` +#### Constructors +- `ns_array![[1,2],[3,4]]`: Macro for intuitive matrix creation. +- `Matrix::new(rows, cols, data)`: Creates a matrix from flat data. Panics if `rows * cols != data.len()`. +- `Matrix::zeros(rows, cols)`, `Matrix::ones(rows, cols)`, `Matrix::fill(rows, cols, val)`: Utility constructors. +- `Matrix::eye(n)`: Creates an $n \times n$ identity matrix. +- `Matrix::rand(rows, cols)`, `Matrix::randn(rows, cols)`: Random matrices (Uniform and Normal). +- `Matrix::rand_seeded(rows, cols, seed)`, `Matrix::randn_seeded(rows, cols, seed)`: Deterministic random matrices. ---- +#### Arithmetic & Operators +NumRS uses a **reference-first** operator model. All operators (`+`, `-`, `*`) work on: +- `&Matrix op &Matrix` +- `&Matrix op Matrix` +- `Matrix op &Matrix` +- `Matrix op Matrix` -## Part 1 — `Matrix` +**Note:** Standard operators panic on dimension mismatch with a descriptive `MatrixError` message. For non-panicking alternatives, see "Safe Variants". -`Matrix` is a 2D, `f64`-only type with a full set of linear algebra -operations. Internally, element `(i, j)` lives at `data[i * cols + j]`. +#### Safe Variants +If you prefer `Result` over panics, use the `try_*` methods: +- `a.try_add(&b)` -> `Result` +- `a.try_sub(&b)` -> `Result` +- `a.try_mul(&b)` -> `Result` -### 1.1 Creating a Matrix +### 2. `numrs::tensor` — N-Dimensional Tensors -| Call | Result | -|------------------------------------|------------------------------------------------------| -| `ns_array![[1,2],[3,4]]` | Matrix from literal rows, Python-like syntax | -| `Matrix::new(rows, cols, data)` | Matrix from an explicit flat `Vec` — panics if `rows*cols != data.len()` | -| `Matrix::zeros(rows, cols)` | All elements `0.0` | -| `Matrix::ones(rows, cols)` | All elements `1.0` | -| `Matrix::fill(rows, cols, v)` | All elements set to `v` | -| `Matrix::eye(n)` | n×n identity matrix | +`Tensor` generalizes the row-major storage to N dimensions. +#### Usage ```rust -let a = ns_array![[1, 2, 3], [4, 5, 6]]; // 2×3 -let z = Matrix::zeros(2, 3); -let id = Matrix::eye(3); +let t: Tensor = Tensor::zeros(&[2, 3, 4]); // A 3D tensor +let val = t[&[0, 1, 2]]; // Stride-based indexing ``` -### 1.2 Reading and writing elements - -```rust -let mut m = Matrix::zeros(2, 2); -m[(0, 1)] = 5.0; // write -let v = m[(0, 1)]; // read -> 5.0 -``` -Implemented via `Index`/`IndexMut` on the tuple `(row, col)`. Out-of-bounds -access panics with a descriptive message. - -### 1.3 Arithmetic operators - -| Expression | Meaning | -|-------------|------------------------------------------------------------------| -| `&a + &b` | Element-wise addition — shapes must match | -| `&a - &b` | Element-wise subtraction — shapes must match | -| `&a * &b` | **Matrix multiplication** (dot product) — `a.cols` must equal `b.rows` | -| `&a * 2.0` | Scalar multiplication | -| `2.0 * &a` | Scalar multiplication, commutative form | - -Every operator above also works in **owned form** (`a + b`, `a * b`, ...), -and in all four combinations of owned/reference. References are the -primary implementation; owned versions just delegate to them so you never -pay for a hidden `.clone()`. +#### Supported Operations +- **Basic Arithmetic**: `+`, `-` between tensors of the same shape. +- **Scalar**: `tensor * scalar` (multiplies every element). +- **Hadamard**: `a.hadamard(&b)` for element-wise multiplication. +- **Random**: `.rand()`, `.randn()`, and seeded variants similar to `Matrix`. -```rust -let sum = &a + &a; -let scaled = &a * 2.0; -let same = 2.0 * &a; // commutative — same result as above -``` +### 3. `numrs::math` — Advanced Mathematics -### 1.4 Math operations +The `math` module provides complex operations for `Matrix`: +- `.transpose()`: Returns a new transposed matrix. +- `.det()`: Calculates the determinant using Gaussian elimination with partial pivoting. +- `.inverse()`: Calculates the Gauss-Jordan inverse. Returns `Result`. +- `.norm()`: Calculates the Frobenius norm. -| Method | Returns | Description | -|--------------------|-------------------|--------------------------------------------------------| -| `.transpose()` | `Matrix` | Swaps rows and columns | -| `.hadamard(&b)` | `Matrix` | Element-wise multiplication — shapes must match exactly (different from `*`, which is matrix multiplication) | -| `.norm()` | `f64` | Frobenius norm: √(sum of all squared elements) | -| `.det()` | `f64` | Determinant via Gaussian elimination with partial pivoting. Panics if not square; returns `0.0` if singular | -| `.inverse()` | `Option` | Gauss-Jordan inverse. Panics if not square; `None` if singular (no panic) | +### 4. `numrs::error` — Error Handling -```rust -let m = ns_array![[3, 1], [2, 4]]; -let det = m.det(); // 10.0 -let inv = m.inverse(); // Some(Matrix) -``` +NumRS uses the `MatrixError` enum to categorize failures: +- `ShapeMismatch`: Element-wise operation on incompatible shapes. +- `DimensionMismatch`: Matrix multiplication shape error. +- `NonSquare`: Operation requires a square matrix (e.g., `det`, `inverse`). +- `NonInvertible`: Matrix is singular. +- `IndexOutOfBounds`: Accessing an element outside the matrix/tensor bounds. -### 1.5 Printing a Matrix +### 5. `numrs::parallel` — Automatic Parallelism -`Matrix` implements `Display`, so `println!("{}", m)` renders a colored, -column-aligned table: -- Cyan box-drawing brackets (`┌ │ └`, or `[ ]` for a single row) -- Negative values in yellow, zeros dimmed, positive values white -- Each column is sized to its widest value, values shown to 3 decimals +NumRS automatically uses [Rayon](https://github.com/rayon-rs/rayon) to parallelize operations on large matrices. +- **Threshold**: Operations switch to parallel mode when the number of elements $\ge 1024$. +- **Parallelized Ops**: Addition, Subtraction, Multiplication, Transposition, Hadamard, and Norm. +- **Serial Ops**: Determinant and Inverse (due to the sequential nature of Gaussian elimination). --- -## Part 2 — `Tensor` - -`Tensor` generalizes the same row-major idea to **any rank** (0, 1, 2, -3, ... dimensions) and **any numeric type** `T` (e.g. `f32`, `f64`, `i32`, -`i64`, `u32`, ...) — not just `f64`. - -### 2.1 Shape, strides, and rank - -A `Tensor` doesn't store `rows`/`cols`; it stores a `shape: Vec` -(one entry per dimension) and a matching `strides: Vec` that tells -you how far to jump in the flat data for each dimension. For shape -`[2, 3, 4]`: - -``` -shape = [2, 3, 4] -strides = [12, 4, 1] // 4 = 4*1 (size of last dim), 12 = 3*4 -``` - -This is exactly the `i * cols + j` scheme `Matrix` uses, just extended to -N dimensions. `rank()` is simply `shape.len()` — how many dimensions the -tensor has. - -### 2.2 Creating a Tensor - -| Call | Requires | Result | -|---------------------------------------|----------------|---------------------------------------| -| `Tensor::from_vec(shape, data)` | — | Tensor from explicit shape + flat data; panics if sizes don't match | -| `Tensor::zeros(shape)` | `T: Zero` | All elements `T::zero()` | -| `Tensor::ones(shape)` | `T: One` | All elements `T::one()` | -| `Tensor::fill(shape, value)` | `T: Clone` | All elements set to `value` | +## Design Principles -```rust -let t: Tensor = Tensor::from_vec(&[2, 3], vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); -let tz: Tensor = Tensor::zeros(&[2, 2, 2]); // a 3D tensor -let to: Tensor = Tensor::ones(&[3]); // a 1D tensor of i64 -``` - -`Zero`/`One` come from the `num-traits` crate and are already implemented -for every Rust numeric primitive, so `zeros`/`ones` work out of the box -for `f32`, `f64`, `i8`...`i128`, `u8`...`u128`, `isize`, `usize`. - -### 2.3 Inspecting a Tensor - -| Method | Returns | Meaning | -|------------------|--------------|------------------------------------------| -| `.rank()` | `usize` | Number of dimensions | -| `.shape()` | `&[usize]` | Size of each dimension | -| `.strides()` | `&[usize]` | Flat-index jump per dimension | -| `.len()` | `usize` | Total number of elements | -| `.is_empty()` | `bool` | `true` if there are no elements | -| `.as_slice()` | `&[T]` | The raw underlying flat data | - -### 2.4 Reading and writing elements - -```rust -let mut t: Tensor = Tensor::zeros(&[2, 2]); -t[&[1, 1]] = 42.0; // write -let v = t[&[1, 1]]; // read -> 42.0 -``` -The index is a slice with **one number per dimension** — `&[1, 1]` for a -2D tensor, `&[0, 2, 1]` for a 3D one, and so on. Array literals like -`&[1, 1]` coerce to `&[usize]` automatically. Passing the wrong number of -indices, or an out-of-bounds one, panics with a descriptive message. - -### 2.5 Arithmetic operators - -| Expression | Requires | Meaning | -|-------------------|---------------------------|----------------------------------------------------| -| `&a + &b` | `T: Copy + Add` | Element-wise addition — shapes must match exactly | -| `&a - &b` | `T: Copy + Sub` | Element-wise subtraction — shapes must match exactly | -| `&a * scalar` | `T: Copy + Mul` | Scalar multiplication | -| `a.hadamard(&b)` | `T: Copy + Mul` | Element-wise multiplication — shapes must match exactly | - -`+` and `-` also work in owned form and all four ownership combinations, -same as `Matrix`. **One asymmetry to know about:** scalar multiplication -currently only works as `tensor * scalar`, not `scalar * tensor` — unlike -`Matrix`, the commutative form (`2.0 * &t`) isn't implemented yet. - -```rust -let a: Tensor = Tensor::from_vec(&[2, 2], vec![1, 2, 3, 4]); -let b: Tensor = Tensor::ones(&[2, 2]); -let sum = &a + &b; // [[2,3],[4,5]] -let prod = &a * 10; // [[10,20],[30,40]] — works -// let bad = 10 * &a; // does NOT compile yet -``` - -There is no `*` between two tensors (no matrix-multiplication or -tensor-contraction equivalent yet) and no `det`/`inverse`/`norm` — those -remain `Matrix`-only for now. - -### 2.6 Which types can I use? - -Any `T` that implements the traits a given operation needs. In practice, -every signed/unsigned integer and float primitive works for everything -except `Display`-based printing of negative-aware coloring, which needs -`PartialOrd` (also true for all of them). A few examples that work today: -`f32`, `f64`, `i32`, `i64`, `u32`, `u64`. - -### 2.7 Printing a Tensor - -`Tensor` implements `Display` recursively, so it works for **any -rank** — unlike a fixed 2D table. A 3D tensor of shape `[2,2,2]` prints -as nested brackets: - -``` -Tensor [2, 2, 2] -[ - [ - [0, 0], - [0, 0] - ], - [ - [0, 0], - [0, 0] - ] -] -``` - -Same coloring rule as `Matrix` (negative → yellow, zero → dimmed, -positive → white). **One difference from `Matrix` to know about:** -because `T` is generic, `Display` can't assume `{:.3}` formatting — -numbers print using `T`'s own natural formatting (e.g. `4.5`, not -`4.500`), and columns are not padded/aligned the way `Matrix`'s are. +1. **Row-Major Storage**: Contiguous memory layout for cache efficiency. +2. **Zero-Cost Ownership**: Owned operators delegate to reference implementations. +3. **Predictable Performance**: Automatic parallelism for large workloads, single-threaded for small ones to avoid overhead. +4. **Type Safety**: `Tensor` is generic, while `Matrix` is specialized for `f64` linear algebra. --- -## Shared design principles - -- **Row-major flat storage** for both types — no nested `Vec`s, one - contiguous allocation. -- **Reference-first operators** — `&Matrix op &Matrix` / `&Tensor op - &Tensor` hold the real logic; every owned variant just delegates, so - you choose owned vs. borrowed based on whether you need the original - afterward, never because of a performance penalty. -- **Panics, not `Result`, for invalid input** (shape mismatches, - out-of-bounds indices, non-square matrices for `det`/`inverse`). This - is a known gap — see below. -- **`Tensor` never touches `Matrix`'s code.** They're separate types on - purpose, so adding `Tensor` couldn't break anything that already - depended on `Matrix`. - ---- +## Examples -## Known limitations (today) - -- No custom error type — invalid shapes/indices panic instead of - returning a `Result`. -- No parallelism — every operation is single-threaded. -- `Tensor` has no `reshape`, `permute` (the N-D equivalent of - `transpose`), broadcasting, or tensor contraction/matmul yet. -- `Tensor` has no `det`, `inverse`, or `norm` — those exist only on - `Matrix`. -- `Tensor` scalar multiplication is one-directional (`tensor * scalar` - only). -- `Tensor`'s `Display` isn't column-aligned like `Matrix`'s. -- No conversions between `Matrix` and `Tensor` yet. -- No LU/QR decomposition, eigenvalues, or GPU acceleration yet. - -## What's next - -See `README.md` for the full roadmap. The current focus after `Tensor` -is adding shape-manipulation methods (`reshape`, `permute`) before moving -on to decompositions and hardware acceleration. +For full code examples of solving linear systems, Markov chains, and more, see [EXAMPLES.md](EXAMPLES.md).