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.