Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shape operations

All shape-transformation methods return new independent owned tensors. The numeric core copies data internally; no view lifetime is ever exposed.

Reshape

#![allow(unused)]
fn main() {
let t = Tensor::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[2, 3]);

// Panic zone
let r = t.reshape(&[3, 2]);      // shape [3, 2], same flat order

// Result zone
let r = t.try_reshape(&[3, 2])?; // MattenError::Shape on mismatch
}

Only the element count matters — reshape never fails because of memory layout. Flat data order (row-major) is preserved unchanged.

The easiest way to read reshape is: keep the flat row-major tape, then place cuts in different positions.

shape [2, 3]

[ 1  2  3 ]
[ 4  5  6 ]

flat tape: 1  2  3  4  5  6

reshape [3, 2]

[ 1  2 ]
[ 3  4 ]
[ 5  6 ]
// Any compatible shape works
let flat  = t.reshape(&[6]);        // [6]
let col   = t.reshape(&[6, 1]);     // [6, 1]
let cube  = t.reshape(&[1, 2, 3]);  // [1, 2, 3]

Panic message on mismatch:

matten shape error in reshape: cannot reshape tensor with 6 elements
    from shape [2, 3] into shape [4, 2] requiring 8 elements

Flatten

#![allow(unused)]
fn main() {
let t = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
let f = t.flatten();   // shape [4]

// A scalar becomes shape [1]
let s = Tensor::scalar(7.0).flatten();  // shape [1]
}

Flatten is the same row-major tape without any row/column grouping:

[ 1  2 ]
[ 3  4 ]  ->  [1 2 3 4]

Transpose

transpose() reverses the axis order. t() is an alias.

#![allow(unused)]
fn main() {
// 2-D: swap rows and columns
let m  = Tensor::new(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[2, 3]);
let mt = m.transpose();
// shape [3, 2], data [1.0, 4.0, 2.0, 5.0, 3.0, 6.0]

// Higher rank: axes are fully reversed
// [d0, d1, d2] → [d2, d1, d0]
let t3  = Tensor::new((1..=24).map(|x| x as f64).collect(), &[2, 3, 4]);
let t3t = t3.transpose();  // shape [4, 3, 2]
}

For a matrix, transpose swaps the coordinate meaning:

input shape [2, 3]          transpose shape [3, 2]

coord [0,0] = 1             coord [0,0] = 1
coord [0,1] = 2             coord [1,0] = 2
coord [0,2] = 3             coord [2,0] = 3
coord [1,0] = 4             coord [0,1] = 4
coord [1,1] = 5             coord [1,1] = 5
coord [1,2] = 6             coord [2,1] = 6

[ 1  2  3 ]                 [ 1  4 ]
[ 4  5  6 ]       ->        [ 2  5 ]
                             [ 3  6 ]

Transposing twice is the identity:

assert_eq!(t.transpose().transpose(), t);

Transposing a scalar panics — there are no axes to reverse.

Swap axes

#![allow(unused)]
fn main() {
let t = Tensor::new((1..=24).map(|x| x as f64).collect(), &[2, 3, 4]);
let s = t.swap_axes(0, 2);  // shape [4, 3, 2]
}

transpose() reverses every axis; swap_axes(a, b) swaps only the two axes you name:

shape [2, 3, 4]
axes    0  1  2

transpose()       -> shape [4, 3, 2]   axes 2 1 0
swap_axes(0, 2)   -> shape [4, 3, 2]   axes 2 1 0
swap_axes(0, 1)   -> shape [3, 2, 4]   axes 1 0 2
swap_axes(1, 2)   -> shape [2, 4, 3]   axes 0 2 1

Swapping an axis with itself is a no-op. Out-of-range axes panic:

matten shape error in swap_axes: axis 5 is out of range for rank-3 tensor

Squeeze and expand_dims (RFC-038)

use matten::Tensor;

// squeeze: drop every length-1 axis (data order unchanged)
let t = Tensor::new(vec![1.0, 2.0, 3.0], &[1, 3, 1]);
let s = t.squeeze();           // shape [3]

// an all-ones shape squeezes to a scalar
let one = Tensor::new(vec![5.0], &[1, 1]).squeeze();  // shape []

// expand_dims: insert a length-1 axis at `axis` (0..=ndim)
let v = Tensor::from_vec(vec![1.0, 2.0, 3.0]);
let row = v.expand_dims(0);    // [1, 3]
let col = v.expand_dims(1);    // [3, 1]

// Result zone: axis > ndim is an InvalidArgument
let r = v.try_expand_dims(axis)?;

squeeze removes all length-1 axes and never fails (a scalar stays a scalar). expand_dims accepts axis in 0..=ndim; an out-of-range axis panics, while try_expand_dims returns MattenError::InvalidArgument. Both clone data and reject dynamic tensors (call try_numeric() first).

Element access

#![allow(unused)]
fn main() {
use matten::Tensor;
let t = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);

t.get(&[0, 1]);  // Some(2.0)
t.get(&[5, 0]);  // None — out of bounds
t.get(&[0]);     // None — rank mismatch

// Scalar element
Tensor::scalar(99.0).get(&[]);  // Some(99.0)
assert_eq!(t.get(&[0, 1]), Some(2.0));
assert_eq!(t.get(&[5, 0]), None);
assert_eq!(t.get(&[0]), None);
assert_eq!(Tensor::scalar(99.0).get(&[]), Some(99.0));
}

get returns Option<f64> and never panics.

Mutable element access (RFC-104)

get_mut and get_flat_mut mirror get and get_flat exactly — same argument shape, same Option return, same panic-on-dynamic guard — but return a mutable reference instead of a copy, so read-modify-write is one expression instead of two lookups:

#![allow(unused)]
fn main() {
use matten::Tensor;
let mut t = Tensor::new(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);

*t.get_mut(&[0, 1]).unwrap() += 1.0;
assert_eq!(t.get(&[0, 1]), Some(3.0));

assert_eq!(t.get_mut(&[5, 0]), None); // out of bounds -- tensor unchanged
}

There is no set/set_flat: t.set(coord, v) is one line over get_mut (*t.get_mut(coord).unwrap() = v), so it was left out rather than added as redundant sugar. IndexMut (t[[i, j]] = v), iter_mut, and as_mut_slice are deliberately not in this cut either — get_mut covers the read-modify-write case with an Option rather than a panic path; the others are cheap to add later on top of get_mut and not cheap to withdraw.

On a dynamic tensor, use get_element_mut instead — get_mut/get_flat_mut panic on dynamic input, the same guard get/get_flat use.

Numeric Tensor ownership note

Every method above clones or physically reorders data into a fresh contiguous buffer. This keeps the API lifetime-free and predictable, at the cost of higher allocation than a view-based library. When this matters for large data, migrate to ndarray or nalgebra using tensor.into_vec().

See also

To join several tensors into one — along an existing axis (concatenate) or a new axis (stack) — see Shape composition.