viva_tensor/core/tensor
Core Tensor module - the heart of viva_tensor.
Why opaque? Learned the hard way that letting users construct Tensor(data: [1,2,3], shape: [2,2]) leads to 3am debugging sessions. Algebraic data types are great until someone violates your invariants.
The strided representation comes straight from how NumPy does it internally (see: https://numpy.org/doc/stable/reference/arrays.ndarray.html#internal-memory-layout) Basically: instead of copying data for transpose, just swap the strides. O(1) vs O(n). The kind of trick that makes you feel smart.
Fun fact: Erlang’s :array module uses a tree structure (not contiguous memory), so our “O(1)” access is actually O(log32 n). Close enough for jazz.
let a = tensor.zeros([2, 3])
let b = tensor.ones([2, 3])
use c <- result.try(tensor.add(a, b))
Types
Values
pub fn arange(start: Float, end: Float, step: Float) -> Tensor
Create tensor with values from start to end (exclusive)
pub fn dim(
t: Tensor,
axis: Int,
) -> Result(Int, error.TensorError)
Get specific dimension size
pub fn eye(n: Int) -> Tensor
Identity matrix. The multiplicative identity of matrix algebra. IA = AI = A. One of the few things in linear algebra that’s intuitive.
pub fn from_list2d(
rows: List(List(Float)),
) -> Result(Tensor, error.TensorError)
Create 2D tensor (matrix) from list of lists
pub fn from_native_ref(
ref: ffi.NativeTensorRef,
shape: List(Int),
) -> Tensor
Wrap a NIF resource ref as a Tensor
pub fn get(
t: Tensor,
index: Int,
) -> Result(Float, error.TensorError)
Get element by flat index. For strided tensors, computes the real offset.
pub fn get2d(
t: Tensor,
row: Int,
col: Int,
) -> Result(Float, error.TensorError)
Get element by 2D coordinates
pub fn get_col(
t: Tensor,
col_idx: Int,
) -> Result(Tensor, error.TensorError)
Get matrix column as vector
pub fn get_row(
t: Tensor,
row_idx: Int,
) -> Result(Tensor, error.TensorError)
Get matrix row as vector
pub fn he_init(
fan_in fan_in: Int,
fan_out fan_out: Int,
) -> Tensor
He init (2015 paper: “Delving Deep into Rectifiers”) std = sqrt(2/fan_in) accounts for ReLU killing half the activations. The “2” is not arbitrary - it comes from E[ReLU(x)²] = Var(x)/2 for x~N(0,σ²)
pub fn is_contiguous(t: Tensor) -> Bool
Check if tensor has contiguous memory layout
pub fn linspace(start: Float, end: Float, num: Int) -> Tensor
Create linearly spaced tensor
pub fn matrix(
rows rows: Int,
cols cols: Int,
data data: List(Float),
) -> Result(Tensor, error.TensorError)
Create matrix with explicit dimensions
pub fn native_fill(
shape: List(Int),
value: Float,
) -> Result(Tensor, error.TensorError)
Create native tensor filled with value
pub fn native_from_list(
data: List(Float),
shape: List(Int),
) -> Result(Tensor, error.TensorError)
Create native tensor from list data
pub fn native_ones(
shape: List(Int),
) -> Result(Tensor, error.TensorError)
Create native tensor of ones
pub fn native_ref(t: Tensor) -> Result(ffi.NativeTensorRef, Nil)
Extract native ref (for passing to NIF resource ops)
pub fn native_zeros(
shape: List(Int),
) -> Result(Tensor, error.TensorError)
Create native tensor of zeros (data in C memory)
pub fn new(
data: List(Float),
shape: List(Int),
) -> Result(Tensor, error.TensorError)
Create tensor with validation. This is the “safe” constructor.
pub fn random_normal(
shape shape: List(Int),
mean mean: Float,
std std: Float,
) -> Tensor
Normal distribution via Box-Muller transform (1958). Could use Ziggurat for ~3x speedup but Box-Muller is elegant and “premature optimization is the root of all evil” - Knuth
pub fn random_uniform(shape: List(Int)) -> Tensor
Uniform random in [0, 1). Seeds from system entropy on first call.
pub fn to_strided(t: Tensor) -> Tensor
Convert to strided (backed by Erlang :array for O(1) random access)
pub fn transpose_strided(
t: Tensor,
) -> Result(Tensor, error.TensorError)
Zero-copy transpose (just swap strides and shape)
pub fn xavier_init(
fan_in fan_in: Int,
fan_out fan_out: Int,
) -> Tensor
Xavier/Glorot init (2010 paper: “Understanding the difficulty of training deep FFNs”) The limit = sqrt(6 / (fan_in + fan_out)) keeps variance stable across layers. Use this for tanh/sigmoid. For ReLU, use he_init instead.