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 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 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 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 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_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 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_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_axis(
t: Tensor,
axis_idx: Int,
) -> Result(Tensor, error.TensorError)
Mean along a specific axis
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 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 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 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 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_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 sub(
a: Tensor,
b: Tensor,
) -> Result(Tensor, error.TensorError)
Element-wise subtraction
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 to_contiguous(t: Tensor) -> Tensor
Convert strided tensor back to regular (materializes the view)
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 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)