viva_tensor/tensor

Tensor - N-dimensional arrays for numerical computing

“All you need is shapes” - a tensor library author, probably

This is the PUBLIC FACADE over core/tensor.gleam. Why two modules? Because opaque types are great until you need pattern matching in tests. This module gives you the nice API; core/ gives you the guarantees.

Design: NumPy-inspired with strides for zero-copy views. Uses Erlang :array for O(1) access + strides for efficient transpose/reshape. For matrices > 100x100, enable the NIF. The difference is 100-1000x.

Types

Conv2D configuration

pub type Conv2dConfig {
  Conv2dConfig(
    kernel_h: Int,
    kernel_w: Int,
    stride_h: Int,
    stride_w: Int,
    padding_h: Int,
    padding_w: Int,
  )
}

Constructors

  • Conv2dConfig(
      kernel_h: Int,
      kernel_w: Int,
      stride_h: Int,
      stride_w: Int,
      padding_h: Int,
      padding_w: Int,
    )

Tensor with NumPy-style strides for zero-copy views

  • storage: contiguous data buffer (Erlang array for O(1) access)
  • shape: dimensions [d0, d1, …, dn]
  • strides: bytes to skip for each dimension [s0, s1, …, sn]
  • offset: starting position in storage (for views/slices)

NCHW vs NHWC: we use NCHW because that’s what the cool kids (PyTorch) do. TensorFlow uses NHWC by default, but they also use Python so…

pub type Tensor {
  Tensor(data: List(Float), shape: List(Int))
  StridedTensor(
    storage: ffi.ErlangArray,
    shape: List(Int),
    strides: List(Int),
    offset: Int,
  )
}

Constructors

  • Tensor(data: List(Float), shape: List(Int))
  • StridedTensor(
      storage: ffi.ErlangArray,
      shape: List(Int),
      strides: List(Int),
      offset: Int,
    )

Re-export TensorError so other modules can reference tensor.TensorError

pub type TensorError =
  error.TensorError

Values

pub fn add(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Element-wise addition

pub fn add_broadcast(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Element-wise addition with broadcasting

pub fn add_scalar(t: Tensor, s: Float) -> Tensor

Add constant

pub fn argmax(t: Tensor) -> Int

Argmax - index of largest element

pub fn argmin(t: Tensor) -> Int

Argmin - index of smallest element

pub fn avg_pool2d(
  input: Tensor,
  pool_h: Int,
  pool_w: Int,
  stride_h: Int,
  stride_w: Int,
) -> Result(Tensor, error.TensorError)

Average pooling 2D with O(1) array access

pub fn broadcast_shape(
  a: List(Int),
  b: List(Int),
) -> Result(List(Int), error.TensorError)

Compute broadcast shape

pub fn broadcast_to(
  t: Tensor,
  target_shape: List(Int),
) -> Result(Tensor, error.TensorError)

Broadcast tensor to target shape. Zero-copy when possible, materialized when necessary.

pub fn can_broadcast(a: List(Int), b: List(Int)) -> Bool

Check if two shapes can be broadcast together

pub fn clamp(t: Tensor, min_val: Float, max_val: Float) -> Tensor

Clamp values to [min, max]

pub fn clone(t: Tensor) -> Tensor

Clone tensor (creates a copy)

pub fn cols(t: Tensor) -> Int

Return number of columns (for matrices)

pub fn concat(tensors: List(Tensor)) -> Tensor

Concatenate vectors (1D)

pub fn concat_axis(
  tensors: List(Tensor),
  axis: Int,
) -> Result(Tensor, error.TensorError)

Concatenate tensors along a specific axis For [2,3] and [2,3] tensors: concat_axis([a, b], 0) -> [4,3] For [2,3] and [2,3] tensors: concat_axis([a, b], 1) -> [2,6]

pub fn conv2d(
  input: Tensor,
  kernel: Tensor,
  config: Conv2dConfig,
) -> Result(Tensor, error.TensorError)

2D Convolution with optimized O(1) array access.

Output size formula: O = floor((I - K + 2P) / S) + 1 where I = input size, K = kernel size, P = padding, S = stride

Input shapes supported:

  • [H, W] with kernel [KH, KW] -> [H_out, W_out]
  • [C, H, W] with kernel [C, KH, KW] -> [H_out, W_out]
  • [N, C_in, H, W] with kernel [C_out, C_in, KH, KW] -> [N, C_out, H_out, W_out]
pub fn conv2d_config() -> Conv2dConfig

Default conv2d config (3x3 kernel, stride 1, no padding)

pub fn conv2d_same(kernel_h: Int, kernel_w: Int) -> Conv2dConfig

Conv2d config with “same” padding (output same size as input)

pub fn dim(
  t: Tensor,
  axis: Int,
) -> Result(Int, error.TensorError)

Specific dimension

pub fn div(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Element-wise division

pub fn dot(
  a: Tensor,
  b: Tensor,
) -> Result(Float, error.TensorError)

Dot product of two vectors: a . b = sum(a_i * b_i)

pub fn expand_dims(t: Tensor, axis: Int) -> Tensor

Expand tensor to add batch dimension (alias for unsqueeze)

pub fn fill(shape: List(Int), value: Float) -> Tensor

Create tensor filled with value

pub fn flatten(t: Tensor) -> Tensor

Flatten to 1D

pub fn from_list(data: List(Float)) -> Tensor

Create tensor from list (1D)

pub fn from_list2d(
  rows: List(List(Float)),
) -> Result(Tensor, error.TensorError)

Create 2D tensor (matrix) from list of lists

pub fn get(
  t: Tensor,
  index: Int,
) -> Result(Float, error.TensorError)

Access element by linear index

pub fn get2d(
  t: Tensor,
  row: Int,
  col: Int,
) -> Result(Float, error.TensorError)

Access 2D element

pub fn get2d_fast(
  t: Tensor,
  row: Int,
  col: Int,
) -> Result(Float, error.TensorError)

Get 2D element with O(1) access

pub fn get_col(
  t: Tensor,
  col_idx: Int,
) -> Result(Tensor, error.TensorError)

Get matrix column as vector

pub fn get_data(t: Tensor) -> List(Float)

Extract data as list from any tensor variant

pub fn get_fast(
  t: Tensor,
  index: Int,
) -> Result(Float, error.TensorError)

Get element with O(1) access for StridedTensor

pub fn get_row(
  t: Tensor,
  row_idx: Int,
) -> Result(Tensor, error.TensorError)

Get matrix row as vector

pub fn global_avg_pool2d(
  input: Tensor,
) -> Result(Tensor, error.TensorError)

Global average pooling - reduces spatial dimensions to 1x1. The modern replacement for flatten+dense in classification heads. Lin et al. (2013) - Network In Network

Input: [N, C, H, W] -> Output: [N, C, 1, 1]

pub fn he_init(fan_in: Int, fan_out: Int) -> Tensor

He/Kaiming initialization (for ReLU networks). He et al. (2015) - Delving Deep into Rectifiers

std = sqrt(2 / fan_in) W ~ Normal(0, std)

pub fn is_contiguous(t: Tensor) -> Bool

Check if tensor is contiguous in memory

pub fn map(t: Tensor, f: fn(Float) -> Float) -> Tensor

Apply function to each element

pub fn map_indexed(
  t: Tensor,
  f: fn(Float, Int) -> Float,
) -> Tensor

Apply function with index

pub fn matmul(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Matrix-matrix multiplication: [m, n] @ [n, p] -> [m, p] C_ij = sum_k(A_ik * B_kj)

This is O(mnp) - for large matrices, use the NIF backend.

pub fn matmul_vec(
  mat: Tensor,
  vec: Tensor,
) -> Result(Tensor, error.TensorError)

Matrix-vector multiplication: [m, n] @ [n] -> [m] C_i = sum_j(A_ij * x_j)

pub fn matrix(
  rows: Int,
  cols: Int,
  data: List(Float),
) -> Result(Tensor, error.TensorError)

Create matrix (2D tensor) with explicit dimensions

pub fn max(t: Tensor) -> Float

Maximum value

pub fn max_pool2d(
  input: Tensor,
  pool_h: Int,
  pool_w: Int,
  stride_h: Int,
  stride_w: Int,
) -> Result(Tensor, error.TensorError)

Max pooling 2D with O(1) array access Input: [H, W] or [N, C, H, W] Output: [H_out, W_out] or [N, C, H_out, W_out]

pub fn mean(t: Tensor) -> Float

Mean: E[X] = (1/n) * sum(x_i)

pub fn mean_axis(
  t: Tensor,
  axis_idx: Int,
) -> Result(Tensor, error.TensorError)

Mean along a specific axis

pub fn min(t: Tensor) -> Float

Minimum value

pub fn mul(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Element-wise multiplication (Hadamard product) Not to be confused with matmul. Named after Jacques Hadamard (1865-1963).

pub fn mul_broadcast(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Element-wise multiplication with broadcasting

pub fn negate(t: Tensor) -> Tensor

Negation

pub fn norm(t: Tensor) -> Float

L2 norm: ||x||_2 = sqrt(sum(x_i^2))

pub fn normalize(t: Tensor) -> Tensor

Normalize to unit length: x / ||x||_2

pub fn ones(shape: List(Int)) -> Tensor

Create tensor of ones

pub fn outer(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Outer product: [m] x [n] -> [m, n] C_ij = a_i * b_j

pub fn pad2d(
  t: Tensor,
  pad_h: Int,
  pad_w: Int,
) -> Result(Tensor, error.TensorError)

Pad a 2D tensor with zeros Input: [H, W], Output: [H + 2pad_h, W + 2pad_w]

pub fn pad4d(
  t: Tensor,
  pad_h: Int,
  pad_w: Int,
) -> Result(Tensor, error.TensorError)

Pad a 4D tensor (batch) with zeros Input: [N, C, H, W], Output: [N, C, H + 2pad_h, W + 2pad_w]

pub fn product(t: Tensor) -> Float

Product of all elements

pub fn random_normal(
  shape: List(Int),
  mean_val: Float,
  std_val: Float,
) -> Tensor

Tensor with normal random values via Box-Muller transform. Box & Muller (1958) - A Note on the Generation of Random Normal Deviates

pub fn random_uniform(shape: List(Int)) -> Tensor

Tensor with uniform random values [0, 1)

pub fn rank(t: Tensor) -> Int

Number of dimensions (rank)

pub fn reshape(
  t: Tensor,
  new_shape: List(Int),
) -> Result(Tensor, error.TensorError)

Reshape tensor - same data, different shape The total number of elements must match.

pub fn rows(t: Tensor) -> Int

Return number of rows (for matrices)

pub fn scale(t: Tensor, s: Float) -> Tensor

Scale by constant

pub fn shape(t: Tensor) -> List(Int)

Get tensor shape

pub fn size(t: Tensor) -> Int

Total number of elements

pub fn slice(
  t: Tensor,
  start: List(Int),
  lengths: List(Int),
) -> Result(Tensor, error.TensorError)

Slice tensor: extract sub-tensor from start to start+lengths slice(t, [1], [3]) extracts elements at indices 1, 2, 3

pub fn squeeze(t: Tensor) -> Tensor

Remove dimensions of size 1 (squeeze operation)

pub fn squeeze_axis(
  t: Tensor,
  axis: Int,
) -> Result(Tensor, error.TensorError)

Remove dimension at specific axis if it’s 1

pub fn stack(
  tensors: List(Tensor),
  axis: Int,
) -> Result(Tensor, error.TensorError)

Stack tensors along a new axis For [3] and [3] tensors: stack([a, b], 0) -> [2, 3] For [3] and [3] tensors: stack([a, b], 1) -> [3, 2]

pub fn std(t: Tensor) -> Float

Standard deviation: sqrt(Var(X))

pub fn sub(
  a: Tensor,
  b: Tensor,
) -> Result(Tensor, error.TensorError)

Element-wise subtraction

pub fn sum(t: Tensor) -> Float

Sum all elements

pub fn sum_axis(
  t: Tensor,
  axis_idx: Int,
) -> Result(Tensor, error.TensorError)

Sum along a specific axis For a [2, 3] tensor, sum_axis(, 0) gives [3], sum_axis(, 1) gives [2]

pub fn take_first(t: Tensor, n: Int) -> Tensor

Take first N elements along first axis

pub fn take_last(t: Tensor, n: Int) -> Tensor

Take last N elements along first axis

pub fn to_contiguous(t: Tensor) -> Tensor

Convert strided tensor back to regular (materializes the view)

pub fn to_list(t: Tensor) -> List(Float)

Convert to list

pub fn to_list2d(
  t: Tensor,
) -> Result(List(List(Float)), error.TensorError)

Convert matrix to list of lists

pub fn to_strided(t: Tensor) -> Tensor

Convert regular tensor to strided (O(n) once, then O(1) access)

pub fn transpose(t: Tensor) -> Result(Tensor, error.TensorError)

Matrix transpose: A^T where (A^T)_ij = A_ji

pub fn transpose_strided(
  t: Tensor,
) -> Result(Tensor, error.TensorError)

ZERO-COPY TRANSPOSE - just swap strides and shape! This is the magic of strided tensors: transpose is O(1).

pub fn unsqueeze(t: Tensor, axis: Int) -> Tensor

Add dimension of size 1 at specified axis (unsqueeze operation)

pub fn variance(t: Tensor) -> Float

Variance: Var(X) = E[X^2] - E[X]^2 (computational form) We use the two-pass algorithm for numerical stability.

pub fn vector(data: List(Float)) -> Tensor

Create vector (1D tensor)

pub fn xavier_init(fan_in: Int, fan_out: Int) -> Tensor

Xavier/Glorot initialization for weights. Glorot & Bengio (2010) - Understanding the difficulty of training deep feedforward NNs

limit = sqrt(6 / (fan_in + fan_out)) W ~ Uniform(-limit, limit)

pub fn zeros(shape: List(Int)) -> Tensor

Create tensor of zeros

Search Document