Packages
Tensor library for Gleam/BEAM with a pure Gleam API, zero-copy views, and optional native acceleration
Retired package: Release invalid
Current section
Files
Jump to
Current section
Files
src/viva_tensor@tensor.erl
-module(viva_tensor@tensor).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_tensor/tensor.gleam").
-export([zeros/1, ones/1, fill/2, from_list/1, from_list2d/1, vector/1, matrix/3, size/1, rank/1, rows/1, cols/1, can_broadcast/2, broadcast_shape/2, dim/2, is_contiguous/1, random_uniform/1, random_normal/3, xavier_init/2, he_init/2, to_strided/1, transpose_strided/1, get_data/1, get/2, get2d/3, get_row/2, get_col/2, map/2, map_indexed/2, add/2, sub/2, mul/2, 'div'/2, scale/2, add_scalar/2, negate/1, sum/1, product/1, mean/1, max/1, min/1, argmax/1, argmin/1, variance/1, std/1, sum_axis/2, mean_axis/2, dot/2, matmul_vec/2, matmul/2, transpose/1, outer/2, to_list/1, to_list2d/1, clone/1, reshape/2, flatten/1, concat/1, concat_axis/2, take_first/2, take_last/2, slice/3, norm/1, normalize/1, clamp/3, squeeze/1, squeeze_axis/2, unsqueeze/2, stack/2, expand_dims/2, to_contiguous/1, get_fast/2, get2d_fast/3, broadcast_to/2, add_broadcast/2, mul_broadcast/2]).
-export_type([erlang_array/0, tensor/0, tensor_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Tensor - N-dimensional arrays for numerical computing\n"
"\n"
" Design: NumPy-inspired with strides for zero-copy views.\n"
" Uses Erlang :array for O(1) access + strides for efficient transpose/reshape.\n"
).
-type erlang_array() :: any().
-type tensor() :: {tensor, list(float()), list(integer())} |
{strided_tensor,
erlang_array(),
list(integer()),
list(integer()),
integer()}.
-type tensor_error() :: {shape_mismatch, list(integer()), list(integer())} |
{invalid_shape, binary()} |
{dimension_error, binary()} |
{broadcast_error, list(integer()), list(integer())}.
-file("src/viva_tensor/tensor.gleam", 46).
?DOC(" Create tensor of zeros\n").
-spec zeros(list(integer())) -> tensor().
zeros(Shape) ->
Size = gleam@list:fold(Shape, 1, fun(Acc, Dim) -> Acc * Dim end),
{tensor, gleam@list:repeat(+0.0, Size), Shape}.
-file("src/viva_tensor/tensor.gleam", 52).
?DOC(" Create tensor of ones\n").
-spec ones(list(integer())) -> tensor().
ones(Shape) ->
Size = gleam@list:fold(Shape, 1, fun(Acc, Dim) -> Acc * Dim end),
{tensor, gleam@list:repeat(1.0, Size), Shape}.
-file("src/viva_tensor/tensor.gleam", 58).
?DOC(" Create tensor filled with value\n").
-spec fill(list(integer()), float()) -> tensor().
fill(Shape, Value) ->
Size = gleam@list:fold(Shape, 1, fun(Acc, Dim) -> Acc * Dim end),
{tensor, gleam@list:repeat(Value, Size), Shape}.
-file("src/viva_tensor/tensor.gleam", 64).
?DOC(" Create tensor from list (1D)\n").
-spec from_list(list(float())) -> tensor().
from_list(Data) ->
{tensor, Data, [erlang:length(Data)]}.
-file("src/viva_tensor/tensor.gleam", 69).
?DOC(" Create 2D tensor (matrix) from list of lists\n").
-spec from_list2d(list(list(float()))) -> {ok, tensor()} |
{error, tensor_error()}.
from_list2d(Rows) ->
case Rows of
[] ->
{ok, {tensor, [], [0, 0]}};
[First | Rest] ->
Cols = erlang:length(First),
Valid = gleam@list:all(
Rest,
fun(Row) -> erlang:length(Row) =:= Cols end
),
case Valid of
false ->
{error,
{invalid_shape, <<"Rows have different lengths"/utf8>>}};
true ->
Data = lists:append(Rows),
Num_rows = erlang:length(Rows),
{ok, {tensor, Data, [Num_rows, Cols]}}
end
end.
-file("src/viva_tensor/tensor.gleam", 89).
?DOC(" Create vector (1D tensor)\n").
-spec vector(list(float())) -> tensor().
vector(Data) ->
from_list(Data).
-file("src/viva_tensor/tensor.gleam", 94).
?DOC(" Create matrix (2D tensor) with explicit dimensions\n").
-spec matrix(integer(), integer(), list(float())) -> {ok, tensor()} |
{error, tensor_error()}.
matrix(Rows, Cols, Data) ->
Expected_size = Rows * Cols,
Actual_size = erlang:length(Data),
case Expected_size =:= Actual_size of
true ->
{ok, {tensor, Data, [Rows, Cols]}};
false ->
{error,
{invalid_shape,
<<<<<<"Expected "/utf8,
(erlang:integer_to_binary(Expected_size))/binary>>/binary,
" elements, got "/utf8>>/binary,
(erlang:integer_to_binary(Actual_size))/binary>>}}
end.
-file("src/viva_tensor/tensor.gleam", 140).
?DOC(" Total number of elements\n").
-spec size(tensor()) -> integer().
size(T) ->
case T of
{tensor, Data, _} ->
erlang:length(Data);
{strided_tensor, _, Shape, _, _} ->
gleam@list:fold(Shape, 1, fun(Acc, Dim) -> Acc * Dim end)
end.
-file("src/viva_tensor/tensor.gleam", 149).
?DOC(" Number of dimensions (rank)\n").
-spec rank(tensor()) -> integer().
rank(T) ->
erlang:length(erlang:element(3, T)).
-file("src/viva_tensor/tensor.gleam", 162).
?DOC(" Return number of rows (for matrices)\n").
-spec rows(tensor()) -> integer().
rows(T) ->
case erlang:element(3, T) of
[R | _] ->
R;
[] ->
0
end.
-file("src/viva_tensor/tensor.gleam", 170).
?DOC(" Return number of columns (for matrices)\n").
-spec cols(tensor()) -> integer().
cols(T) ->
case erlang:element(3, T) of
[_, C | _] ->
C;
[N] ->
N;
[] ->
0
end.
-file("src/viva_tensor/tensor.gleam", 503).
?DOC(" Remove element at index from list\n").
-spec remove_at_index(list(DWI), integer()) -> list(DWI).
remove_at_index(Lst, Idx) ->
_pipe = Lst,
_pipe@1 = gleam@list:index_map(_pipe, fun(Item, I) -> {Item, I} end),
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(Pair) -> erlang:element(2, Pair) /= Idx end
),
gleam@list:map(_pipe@2, fun(Pair@1) -> erlang:element(1, Pair@1) end).
-file("src/viva_tensor/tensor.gleam", 1062).
?DOC(" Check if two shapes can be broadcast together\n").
-spec can_broadcast(list(integer()), list(integer())) -> boolean().
can_broadcast(A, B) ->
{Longer, Shorter} = case erlang:length(A) >= erlang:length(B) of
true ->
{A, B};
false ->
{B, A}
end,
Diff = erlang:length(Longer) - erlang:length(Shorter),
Padded = lists:append(gleam@list:repeat(1, Diff), Shorter),
_pipe = gleam@list:zip(Longer, Padded),
gleam@list:all(
_pipe,
fun(Pair) ->
{Dim_a, Dim_b} = Pair,
((Dim_a =:= Dim_b) orelse (Dim_a =:= 1)) orelse (Dim_b =:= 1)
end
).
-file("src/viva_tensor/tensor.gleam", 1079).
?DOC(" Compute broadcast shape\n").
-spec broadcast_shape(list(integer()), list(integer())) -> {ok, list(integer())} |
{error, tensor_error()}.
broadcast_shape(A, B) ->
case can_broadcast(A, B) of
false ->
{error, {broadcast_error, A, B}};
true ->
Max_rank = gleam@int:max(erlang:length(A), erlang:length(B)),
Diff_a = Max_rank - erlang:length(A),
Diff_b = Max_rank - erlang:length(B),
Padded_a = lists:append(gleam@list:repeat(1, Diff_a), A),
Padded_b = lists:append(gleam@list:repeat(1, Diff_b), B),
Result_shape = begin
_pipe = gleam@list:zip(Padded_a, Padded_b),
gleam@list:map(
_pipe,
fun(Pair) ->
{Dim_a, Dim_b} = Pair,
gleam@int:max(Dim_a, Dim_b)
end
)
end,
{ok, Result_shape}
end.
-file("src/viva_tensor/tensor.gleam", 1301).
-spec list_at_int(list(integer()), integer()) -> {ok, integer()} | {error, nil}.
list_at_int(Lst, Index) ->
case Index < 0 of
true ->
{error, nil};
false ->
_pipe = Lst,
_pipe@1 = gleam@list:drop(_pipe, Index),
gleam@list:first(_pipe@1)
end.
-file("src/viva_tensor/tensor.gleam", 154).
?DOC(" Specific dimension\n").
-spec dim(tensor(), integer()) -> {ok, integer()} | {error, tensor_error()}.
dim(T, Axis) ->
_pipe = list_at_int(erlang:element(3, T), Axis),
gleam@result:map_error(
_pipe,
fun(_) ->
{dimension_error,
<<<<"Axis "/utf8, (erlang:integer_to_binary(Axis))/binary>>/binary,
" out of bounds"/utf8>>}
end
).
-file("src/viva_tensor/tensor.gleam", 1311).
-spec list_at_float(list(float()), integer()) -> {ok, float()} | {error, nil}.
list_at_float(Lst, Index) ->
case Index < 0 of
true ->
{error, nil};
false ->
_pipe = Lst,
_pipe@1 = gleam@list:drop(_pipe, Index),
gleam@list:first(_pipe@1)
end.
-file("src/viva_tensor/tensor.gleam", 1329).
-spec flat_to_multi(integer(), list(integer())) -> list(integer()).
flat_to_multi(Flat, Shape) ->
Reversed = lists:reverse(Shape),
{Indices, _} = gleam@list:fold(
Reversed,
{[], Flat},
fun(Acc, Dim) ->
{Idxs, Remaining} = Acc,
Idx = case Dim of
0 -> 0;
Gleam@denominator -> Remaining rem Gleam@denominator
end,
Next = case Dim of
0 -> 0;
Gleam@denominator@1 -> Remaining div Gleam@denominator@1
end,
{[Idx | Idxs], Next}
end
),
Indices.
-file("src/viva_tensor/tensor.gleam", 1341).
-spec compute_strides(list(integer())) -> list(integer()).
compute_strides(Shape) ->
Reversed = lists:reverse(Shape),
{Strides, _} = gleam@list:fold(
Reversed,
{[], 1},
fun(Acc, Dim) ->
{S, Running} = Acc,
{[Running | S], Running * Dim}
end
),
Strides.
-file("src/viva_tensor/tensor.gleam", 511).
?DOC(" Compute flat index when summing along an axis\n").
-spec compute_index_with_axis(list(integer()), integer(), integer(), integer()) -> integer().
compute_index_with_axis(Shape, Out_idx, Axis_idx, Axis_pos) ->
Strides = compute_strides(Shape),
_ = erlang:length(Shape),
Shape_without_axis = remove_at_index(Shape, Axis_idx),
Strides_without_axis = compute_strides(Shape_without_axis),
Out_coords = begin
_pipe = gleam@list:range(0, erlang:length(Shape_without_axis) - 1),
gleam@list:map(
_pipe,
fun(I) ->
Stride = case begin
_pipe@1 = gleam@list:drop(Strides_without_axis, I),
gleam@list:first(_pipe@1)
end of
{ok, S} ->
S;
{error, _} ->
1
end,
case (case begin
_pipe@2 = gleam@list:drop(Shape_without_axis, I),
gleam@list:first(_pipe@2)
end of
{ok, D} ->
D;
{error, _} ->
1
end) of
0 -> 0;
Gleam@denominator@1 -> (case Stride of
0 -> 0;
Gleam@denominator -> Out_idx div Gleam@denominator
end) rem Gleam@denominator@1
end
end
)
end,
{Before, After} = gleam@list:split(Out_coords, Axis_idx),
Full_coords = lists:append([Before, [Axis_pos], After]),
_pipe@3 = gleam@list:zip(Full_coords, Strides),
gleam@list:fold(
_pipe@3,
0,
fun(Acc, Pair) ->
{Coord, Stride@1} = Pair,
Acc + (Coord * Stride@1)
end
).
-file("src/viva_tensor/tensor.gleam", 1250).
?DOC(" Check if tensor is contiguous in memory\n").
-spec is_contiguous(tensor()) -> boolean().
is_contiguous(T) ->
case T of
{tensor, _, _} ->
true;
{strided_tensor, _, Shape, Strides, _} ->
Expected_strides = compute_strides(Shape),
Strides =:= Expected_strides
end.
-file("src/viva_tensor/tensor.gleam", 1384).
-spec multi_to_flat(list(integer()), list(integer())) -> integer().
multi_to_flat(Indices, Shape) ->
Strides = compute_strides(Shape),
_pipe = gleam@list:zip(Indices, Strides),
gleam@list:fold(
_pipe,
0,
fun(Acc, Pair) ->
{Idx, Stride} = Pair,
Acc + (Idx * Stride)
end
).
-file("src/viva_tensor/tensor.gleam", 1009).
?DOC(" Tensor with uniform random values [0, 1)\n").
-spec random_uniform(list(integer())) -> tensor().
random_uniform(Shape) ->
Size_val = gleam@list:fold(Shape, 1, fun(Acc, Dim) -> Acc * Dim end),
Data = begin
_pipe = gleam@list:range(1, Size_val),
gleam@list:map(_pipe, fun(_) -> rand:uniform() end)
end,
{tensor, Data, Shape}.
-file("src/viva_tensor/tensor.gleam", 1018).
?DOC(" Tensor with normal random values (approx via Box-Muller)\n").
-spec random_normal(list(integer()), float(), float()) -> tensor().
random_normal(Shape, Mean_val, Std_val) ->
Size_val = gleam@list:fold(Shape, 1, fun(Acc, Dim) -> Acc * Dim end),
Data = begin
_pipe = gleam@list:range(1, Size_val),
gleam@list:map(
_pipe,
fun(_) ->
U1 = gleam@float:max(rand:uniform(), 0.0001),
U2 = rand:uniform(),
Z = math:sqrt(-2.0 * math:log(U1)) * math:cos(
(2.0 * 3.14159265359) * U2
),
Mean_val + (Z * Std_val)
end
)
end,
{tensor, Data, Shape}.
-file("src/viva_tensor/tensor.gleam", 1038).
?DOC(" Xavier initialization for weights\n").
-spec xavier_init(integer(), integer()) -> tensor().
xavier_init(Fan_in, Fan_out) ->
Limit = math:sqrt(case erlang:float(Fan_in + Fan_out) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 6.0 / Gleam@denominator
end),
Data = begin
_pipe = gleam@list:range(1, Fan_in * Fan_out),
gleam@list:map(
_pipe,
fun(_) ->
R = rand:uniform(),
((R * 2.0) * Limit) - Limit
end
)
end,
{tensor, Data, [Fan_out, Fan_in]}.
-file("src/viva_tensor/tensor.gleam", 1051).
?DOC(" He initialization (for ReLU)\n").
-spec he_init(integer(), integer()) -> tensor().
he_init(Fan_in, Fan_out) ->
Std_val = math:sqrt(case erlang:float(Fan_in) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 2.0 / Gleam@denominator
end),
random_normal([Fan_out, Fan_in], +0.0, Std_val).
-file("src/viva_tensor/tensor.gleam", 1325).
-spec list_to_array(list(float())) -> erlang_array().
list_to_array(Lst) ->
viva_tensor_ffi:list_to_array(Lst).
-file("src/viva_tensor/tensor.gleam", 1199).
?DOC(" Convert regular tensor to strided (O(n) once, then O(1) access)\n").
-spec to_strided(tensor()) -> tensor().
to_strided(T) ->
case T of
{strided_tensor, _, _, _, _} ->
T;
{tensor, Data, Shape} ->
Storage = list_to_array(Data),
Strides = compute_strides(Shape),
{strided_tensor, Storage, Shape, Strides, 0}
end.
-file("src/viva_tensor/tensor.gleam", 1222).
?DOC(" ZERO-COPY TRANSPOSE - just swap strides and shape!\n").
-spec transpose_strided(tensor()) -> {ok, tensor()} | {error, tensor_error()}.
transpose_strided(T) ->
case T of
{tensor, _, Shape} ->
case Shape of
[_, _] ->
Strided = to_strided(T),
transpose_strided(Strided);
_ ->
{error,
{dimension_error,
<<"Transpose requires 2D tensor"/utf8>>}}
end;
{strided_tensor, Storage, Shape@1, Strides, Offset} ->
case {Shape@1, Strides} of
{[M, N], [S0, S1]} ->
{ok, {strided_tensor, Storage, [N, M], [S1, S0], Offset}};
{_, _} ->
{error,
{dimension_error,
<<"Transpose requires 2D tensor"/utf8>>}}
end
end.
-file("src/viva_tensor/tensor.gleam", 1321).
-spec array_get(erlang_array(), integer()) -> float().
array_get(Arr, Index) ->
viva_tensor_ffi:array_get(Arr, Index).
-file("src/viva_tensor/tensor.gleam", 119).
?DOC(" Extract data as list from any tensor variant\n").
-spec get_data(tensor()) -> list(float()).
get_data(T) ->
case T of
{tensor, Data, _} ->
Data;
{strided_tensor, Storage, Shape, Strides, Offset} ->
Total_size = gleam@list:fold(
Shape,
1,
fun(Acc, Dim) -> Acc * Dim end
),
_pipe = gleam@list:range(0, Total_size - 1),
gleam@list:map(
_pipe,
fun(Flat_idx) ->
Indices = flat_to_multi(Flat_idx, Shape),
Idx = begin
_pipe@1 = gleam@list:zip(Indices, Strides),
gleam@list:fold(
_pipe@1,
Offset,
fun(Acc@1, Pair) ->
{I, S} = Pair,
Acc@1 + (I * S)
end
)
end,
array_get(Storage, Idx)
end
)
end.
-file("src/viva_tensor/tensor.gleam", 183).
?DOC(" Access element by linear index\n").
-spec get(tensor(), integer()) -> {ok, float()} | {error, tensor_error()}.
get(T, Index) ->
case T of
{tensor, Data, _} ->
_pipe = list_at_float(Data, Index),
gleam@result:map_error(
_pipe,
fun(_) ->
{dimension_error,
<<<<"Index "/utf8,
(erlang:integer_to_binary(Index))/binary>>/binary,
" out of bounds"/utf8>>}
end
);
{strided_tensor, Storage, Shape, Strides, Offset} ->
Indices = flat_to_multi(Index, Shape),
Flat_idx = begin
_pipe@1 = gleam@list:zip(Indices, Strides),
gleam@list:fold(
_pipe@1,
Offset,
fun(Acc, Pair) ->
{I, S} = Pair,
Acc + (I * S)
end
)
end,
{ok, array_get(Storage, Flat_idx)}
end.
-file("src/viva_tensor/tensor.gleam", 204).
?DOC(" Access 2D element\n").
-spec get2d(tensor(), integer(), integer()) -> {ok, float()} |
{error, tensor_error()}.
get2d(T, Row, Col) ->
case erlang:element(3, T) of
[_, Num_cols] ->
Index = (Row * Num_cols) + Col,
get(T, Index);
_ ->
{error, {dimension_error, <<"Tensor is not 2D"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 215).
?DOC(" Get matrix row as vector\n").
-spec get_row(tensor(), integer()) -> {ok, tensor()} | {error, tensor_error()}.
get_row(T, Row_idx) ->
case erlang:element(3, T) of
[Num_rows, Num_cols] ->
case (Row_idx >= 0) andalso (Row_idx < Num_rows) of
true ->
Data = get_data(T),
Start = Row_idx * Num_cols,
Row_data = begin
_pipe = Data,
_pipe@1 = gleam@list:drop(_pipe, Start),
gleam@list:take(_pipe@1, Num_cols)
end,
{ok, from_list(Row_data)};
false ->
{error,
{dimension_error, <<"Row index out of bounds"/utf8>>}}
end;
_ ->
{error, {dimension_error, <<"Tensor is not 2D"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 236).
?DOC(" Get matrix column as vector\n").
-spec get_col(tensor(), integer()) -> {ok, tensor()} | {error, tensor_error()}.
get_col(T, Col_idx) ->
case erlang:element(3, T) of
[Num_rows, Num_cols] ->
case (Col_idx >= 0) andalso (Col_idx < Num_cols) of
true ->
Col_data = begin
_pipe = gleam@list:range(0, Num_rows - 1),
gleam@list:filter_map(
_pipe,
fun(Row) -> get2d(T, Row, Col_idx) end
)
end,
{ok, from_list(Col_data)};
false ->
{error,
{dimension_error, <<"Column index out of bounds"/utf8>>}}
end;
_ ->
{error, {dimension_error, <<"Tensor is not 2D"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 258).
?DOC(" Apply function to each element\n").
-spec map(tensor(), fun((float()) -> float())) -> tensor().
map(T, F) ->
Data = get_data(T),
{tensor, gleam@list:map(Data, F), erlang:element(3, T)}.
-file("src/viva_tensor/tensor.gleam", 264).
?DOC(" Apply function with index\n").
-spec map_indexed(tensor(), fun((float(), integer()) -> float())) -> tensor().
map_indexed(T, F) ->
Data = get_data(T),
{tensor,
gleam@list:index_map(Data, fun(X, I) -> F(X, I) end),
erlang:element(3, T)}.
-file("src/viva_tensor/tensor.gleam", 270).
?DOC(" Element-wise addition\n").
-spec add(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
add(A, B) ->
case erlang:element(3, A) =:= erlang:element(3, B) of
true ->
A_data = get_data(A),
B_data = get_data(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> X + Y end),
{ok, {tensor, Data, erlang:element(3, A)}};
false ->
{error,
{shape_mismatch, erlang:element(3, A), erlang:element(3, B)}}
end.
-file("src/viva_tensor/tensor.gleam", 283).
?DOC(" Element-wise subtraction\n").
-spec sub(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
sub(A, B) ->
case erlang:element(3, A) =:= erlang:element(3, B) of
true ->
A_data = get_data(A),
B_data = get_data(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> X - Y end),
{ok, {tensor, Data, erlang:element(3, A)}};
false ->
{error,
{shape_mismatch, erlang:element(3, A), erlang:element(3, B)}}
end.
-file("src/viva_tensor/tensor.gleam", 296).
?DOC(" Element-wise multiplication (Hadamard)\n").
-spec mul(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
mul(A, B) ->
case erlang:element(3, A) =:= erlang:element(3, B) of
true ->
A_data = get_data(A),
B_data = get_data(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> X * Y end),
{ok, {tensor, Data, erlang:element(3, A)}};
false ->
{error,
{shape_mismatch, erlang:element(3, A), erlang:element(3, B)}}
end.
-file("src/viva_tensor/tensor.gleam", 309).
?DOC(" Element-wise division\n").
-spec 'div'(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
'div'(A, B) ->
case erlang:element(3, A) =:= erlang:element(3, B) of
true ->
A_data = get_data(A),
B_data = get_data(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> case Y of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> X / Gleam@denominator
end end),
{ok, {tensor, Data, erlang:element(3, A)}};
false ->
{error,
{shape_mismatch, erlang:element(3, A), erlang:element(3, B)}}
end.
-file("src/viva_tensor/tensor.gleam", 322).
?DOC(" Scale by constant\n").
-spec scale(tensor(), float()) -> tensor().
scale(T, S) ->
map(T, fun(X) -> X * S end).
-file("src/viva_tensor/tensor.gleam", 327).
?DOC(" Add constant\n").
-spec add_scalar(tensor(), float()) -> tensor().
add_scalar(T, S) ->
map(T, fun(X) -> X + S end).
-file("src/viva_tensor/tensor.gleam", 332).
?DOC(" Negation\n").
-spec negate(tensor()) -> tensor().
negate(T) ->
scale(T, -1.0).
-file("src/viva_tensor/tensor.gleam", 341).
?DOC(" Sum all elements\n").
-spec sum(tensor()) -> float().
sum(T) ->
Data = get_data(T),
gleam@list:fold(Data, +0.0, fun(Acc, X) -> Acc + X end).
-file("src/viva_tensor/tensor.gleam", 347).
?DOC(" Product of all elements\n").
-spec product(tensor()) -> float().
product(T) ->
Data = get_data(T),
gleam@list:fold(Data, 1.0, fun(Acc, X) -> Acc * X end).
-file("src/viva_tensor/tensor.gleam", 353).
?DOC(" Mean\n").
-spec mean(tensor()) -> float().
mean(T) ->
S = sum(T),
N = erlang:float(size(T)),
case N > +0.0 of
true ->
case N of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> S / Gleam@denominator
end;
false ->
+0.0
end.
-file("src/viva_tensor/tensor.gleam", 363).
?DOC(" Maximum value\n").
-spec max(tensor()) -> float().
max(T) ->
Data = get_data(T),
case Data of
[] ->
+0.0;
[First | Rest] ->
gleam@list:fold(
Rest,
First,
fun(Acc, X) -> gleam@float:max(Acc, X) end
)
end.
-file("src/viva_tensor/tensor.gleam", 372).
?DOC(" Minimum value\n").
-spec min(tensor()) -> float().
min(T) ->
Data = get_data(T),
case Data of
[] ->
+0.0;
[First | Rest] ->
gleam@list:fold(
Rest,
First,
fun(Acc, X) -> gleam@float:min(Acc, X) end
)
end.
-file("src/viva_tensor/tensor.gleam", 381).
?DOC(" Argmax - index of largest element\n").
-spec argmax(tensor()) -> integer().
argmax(T) ->
Data = get_data(T),
case Data of
[] ->
0;
[First | Rest] ->
{Idx, _, _} = gleam@list:fold(
Rest,
{0, First, 1},
fun(Acc, X) ->
{Best_idx, Best_val, Curr_idx} = Acc,
case X > Best_val of
true ->
{Curr_idx, X, Curr_idx + 1};
false ->
{Best_idx, Best_val, Curr_idx + 1}
end
end
),
Idx
end.
-file("src/viva_tensor/tensor.gleam", 400).
?DOC(" Argmin - index of smallest element\n").
-spec argmin(tensor()) -> integer().
argmin(T) ->
Data = get_data(T),
case Data of
[] ->
0;
[First | Rest] ->
{Idx, _, _} = gleam@list:fold(
Rest,
{0, First, 1},
fun(Acc, X) ->
{Best_idx, Best_val, Curr_idx} = Acc,
case X < Best_val of
true ->
{Curr_idx, X, Curr_idx + 1};
false ->
{Best_idx, Best_val, Curr_idx + 1}
end
end
),
Idx
end.
-file("src/viva_tensor/tensor.gleam", 419).
?DOC(" Variance of all elements\n").
-spec variance(tensor()) -> float().
variance(T) ->
Data = get_data(T),
M = mean(T),
Squared_diffs = gleam@list:map(
Data,
fun(X) ->
Diff = X - M,
Diff * Diff
end
),
N = erlang:float(size(T)),
case N > +0.0 of
true ->
case N of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> gleam@list:fold(
Squared_diffs,
+0.0,
fun(Acc, X@1) -> Acc + X@1 end
)
/ Gleam@denominator
end;
false ->
+0.0
end.
-file("src/viva_tensor/tensor.gleam", 435).
?DOC(" Standard deviation\n").
-spec std(tensor()) -> float().
std(T) ->
math:sqrt(variance(T)).
-file("src/viva_tensor/tensor.gleam", 441).
?DOC(
" Sum along a specific axis\n"
" For a [2, 3] tensor, sum_axis(_, 0) gives [3], sum_axis(_, 1) gives [2]\n"
).
-spec sum_axis(tensor(), integer()) -> {ok, tensor()} | {error, tensor_error()}.
sum_axis(T, Axis_idx) ->
R = rank(T),
case (Axis_idx >= 0) andalso (Axis_idx < R) of
false ->
{error, {dimension_error, <<"Invalid axis index"/utf8>>}};
true ->
case erlang:element(3, T) of
[] ->
{error, {dimension_error, <<"Cannot reduce scalar"/utf8>>}};
[_] ->
{ok, {tensor, [sum(T)], [1]}};
_ ->
Axis_size = case begin
_pipe = gleam@list:drop(erlang:element(3, T), Axis_idx),
gleam@list:first(_pipe)
end of
{ok, S} ->
S;
{error, _} ->
1
end,
New_shape = remove_at_index(erlang:element(3, T), Axis_idx),
New_size = gleam@list:fold(
New_shape,
1,
fun(Acc, D) -> Acc * D end
),
Data = get_data(T),
Result = begin
_pipe@1 = gleam@list:range(0, New_size - 1),
gleam@list:map(
_pipe@1,
fun(Out_idx) ->
_pipe@2 = gleam@list:range(0, Axis_size - 1),
gleam@list:fold(
_pipe@2,
+0.0,
fun(Acc@1, Axis_pos) ->
In_idx = compute_index_with_axis(
erlang:element(3, T),
Out_idx,
Axis_idx,
Axis_pos
),
Val = case begin
_pipe@3 = gleam@list:drop(
Data,
In_idx
),
gleam@list:first(_pipe@3)
end of
{ok, V} ->
V;
{error, _} ->
+0.0
end,
Acc@1 + Val
end
)
end
)
end,
{ok, {tensor, Result, New_shape}}
end
end.
-file("src/viva_tensor/tensor.gleam", 485).
?DOC(" Mean along a specific axis\n").
-spec mean_axis(tensor(), integer()) -> {ok, tensor()} | {error, tensor_error()}.
mean_axis(T, Axis_idx) ->
R = rank(T),
case (Axis_idx >= 0) andalso (Axis_idx < R) of
false ->
{error, {dimension_error, <<"Invalid axis index"/utf8>>}};
true ->
Axis_size = case begin
_pipe = gleam@list:drop(erlang:element(3, T), Axis_idx),
gleam@list:first(_pipe)
end of
{ok, S} ->
S;
{error, _} ->
1
end,
case sum_axis(T, Axis_idx) of
{error, E} ->
{error, E};
{ok, Summed} ->
{ok, scale(Summed, case erlang:float(Axis_size) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end)}
end
end.
-file("src/viva_tensor/tensor.gleam", 557).
?DOC(" Dot product of two vectors\n").
-spec dot(tensor(), tensor()) -> {ok, float()} | {error, tensor_error()}.
dot(A, B) ->
case ((rank(A) =:= 1) andalso (rank(B) =:= 1)) andalso (size(A) =:= size(B)) of
true ->
A_data = get_data(A),
B_data = get_data(B),
Products = gleam@list:map2(A_data, B_data, fun(X, Y) -> X * Y end),
{ok,
gleam@list:fold(Products, +0.0, fun(Acc, X@1) -> Acc + X@1 end)};
false ->
{error,
{shape_mismatch, erlang:element(3, A), erlang:element(3, B)}}
end.
-file("src/viva_tensor/tensor.gleam", 570).
?DOC(" Matrix-vector multiplication: [m, n] @ [n] -> [m]\n").
-spec matmul_vec(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
matmul_vec(Mat, Vec) ->
case {erlang:element(3, Mat), erlang:element(3, Vec)} of
{[M, N], [Vec_n]} when N =:= Vec_n ->
Mat_data = get_data(Mat),
Vec_data = get_data(Vec),
Result_data = begin
_pipe = gleam@list:range(0, M - 1),
gleam@list:map(
_pipe,
fun(Row_idx) ->
Start = Row_idx * N,
Row = begin
_pipe@1 = Mat_data,
_pipe@2 = gleam@list:drop(_pipe@1, Start),
gleam@list:take(_pipe@2, N)
end,
_pipe@3 = gleam@list:map2(
Row,
Vec_data,
fun(A, B) -> A * B end
),
gleam@list:fold(
_pipe@3,
+0.0,
fun(Acc, X) -> Acc + X end
)
end
)
end,
{ok, {tensor, Result_data, [M]}};
{[_, N@1], [Vec_n@1]} ->
{error, {shape_mismatch, [N@1], [Vec_n@1]}};
{_, _} ->
{error, {dimension_error, <<"Expected matrix and vector"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 594).
?DOC(" Matrix-matrix multiplication: [m, n] @ [n, p] -> [m, p]\n").
-spec matmul(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
matmul(A, B) ->
case {erlang:element(3, A), erlang:element(3, B)} of
{[M, N], [N2, P]} when N =:= N2 ->
Result_data = begin
_pipe = gleam@list:range(0, M - 1),
gleam@list:flat_map(
_pipe,
fun(I) -> _pipe@1 = gleam@list:range(0, P - 1),
gleam@list:map(
_pipe@1,
fun(J) -> _pipe@2 = gleam@list:range(0, N - 1),
gleam@list:fold(
_pipe@2,
+0.0,
fun(Acc, K) ->
A_ik = case get2d(A, I, K) of
{ok, V} ->
V;
{error, _} ->
+0.0
end,
B_kj = case get2d(B, K, J) of
{ok, V@1} ->
V@1;
{error, _} ->
+0.0
end,
Acc + (A_ik * B_kj)
end
) end
) end
)
end,
{ok, {tensor, Result_data, [M, P]}};
{[_, N@1], [N2@1, _]} ->
{error, {shape_mismatch, [N@1, -1], [N2@1, -1]}};
{_, _} ->
{error, {dimension_error, <<"Expected two matrices"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 624).
?DOC(" Matrix transpose\n").
-spec transpose(tensor()) -> {ok, tensor()} | {error, tensor_error()}.
transpose(T) ->
case erlang:element(3, T) of
[M, N] ->
Result_data = begin
_pipe = gleam@list:range(0, N - 1),
gleam@list:flat_map(
_pipe,
fun(J) -> _pipe@1 = gleam@list:range(0, M - 1),
gleam@list:filter_map(
_pipe@1,
fun(I) -> get2d(T, I, J) end
) end
)
end,
{ok, {tensor, Result_data, [N, M]}};
_ ->
{error, {dimension_error, <<"Transpose requires 2D tensor"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 640).
?DOC(" Outer product: [m] @ [n] -> [m, n]\n").
-spec outer(tensor(), tensor()) -> {ok, tensor()} | {error, tensor_error()}.
outer(A, B) ->
case (rank(A) =:= 1) andalso (rank(B) =:= 1) of
true ->
M = size(A),
N = size(B),
A_data = get_data(A),
B_data = get_data(B),
Result_data = gleam@list:flat_map(
A_data,
fun(Ai) -> gleam@list:map(B_data, fun(Bj) -> Ai * Bj end) end
),
{ok, {tensor, Result_data, [M, N]}};
false ->
{error,
{dimension_error, <<"Outer product requires two vectors"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 660).
?DOC(" Convert to list\n").
-spec to_list(tensor()) -> list(float()).
to_list(T) ->
get_data(T).
-file("src/viva_tensor/tensor.gleam", 665).
?DOC(" Convert matrix to list of lists\n").
-spec to_list2d(tensor()) -> {ok, list(list(float()))} | {error, tensor_error()}.
to_list2d(T) ->
case erlang:element(3, T) of
[Num_rows, Num_cols] ->
Data = get_data(T),
Rows_list = begin
_pipe = gleam@list:range(0, Num_rows - 1),
gleam@list:map(
_pipe,
fun(I) ->
Start = I * Num_cols,
_pipe@1 = Data,
_pipe@2 = gleam@list:drop(_pipe@1, Start),
gleam@list:take(_pipe@2, Num_cols)
end
)
end,
{ok, Rows_list};
_ ->
{error, {dimension_error, <<"Tensor is not 2D"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 684).
?DOC(" Clone tensor\n").
-spec clone(tensor()) -> tensor().
clone(T) ->
Data = get_data(T),
{tensor, Data, erlang:element(3, T)}.
-file("src/viva_tensor/tensor.gleam", 690).
?DOC(" Reshape tensor\n").
-spec reshape(tensor(), list(integer())) -> {ok, tensor()} |
{error, tensor_error()}.
reshape(T, New_shape) ->
Old_size = size(T),
New_size = gleam@list:fold(New_shape, 1, fun(Acc, Dim) -> Acc * Dim end),
case Old_size =:= New_size of
true ->
Data = get_data(T),
{ok, {tensor, Data, New_shape}};
false ->
{error,
{invalid_shape,
<<<<<<<<"Cannot reshape: size mismatch ("/utf8,
(erlang:integer_to_binary(Old_size))/binary>>/binary,
" vs "/utf8>>/binary,
(erlang:integer_to_binary(New_size))/binary>>/binary,
")"/utf8>>}}
end.
-file("src/viva_tensor/tensor.gleam", 711).
?DOC(" Flatten to 1D\n").
-spec flatten(tensor()) -> tensor().
flatten(T) ->
Data = get_data(T),
{tensor, Data, [size(T)]}.
-file("src/viva_tensor/tensor.gleam", 717).
?DOC(" Concatenate vectors (1D)\n").
-spec concat(list(tensor())) -> tensor().
concat(Tensors) ->
Data = gleam@list:flat_map(Tensors, fun(T) -> get_data(T) end),
from_list(Data).
-file("src/viva_tensor/tensor.gleam", 725).
?DOC(
" Concatenate tensors along a specific axis\n"
" For [2,3] and [2,3] tensors: concat_axis([a, b], 0) -> [4,3]\n"
" For [2,3] and [2,3] tensors: concat_axis([a, b], 1) -> [2,6]\n"
).
-spec concat_axis(list(tensor()), integer()) -> {ok, tensor()} |
{error, tensor_error()}.
concat_axis(Tensors, Axis) ->
case Tensors of
[] ->
{error, {invalid_shape, <<"Cannot concatenate empty list"/utf8>>}};
[Single] ->
{ok, Single};
[First | Rest] ->
Base_shape = erlang:element(3, First),
R = erlang:length(Base_shape),
case (Axis >= 0) andalso (Axis < R) of
false ->
{error,
{dimension_error,
<<"Invalid axis for concatenation"/utf8>>}};
true ->
Shapes_ok = gleam@list:all(
Rest,
fun(T) ->
T_shape = erlang:element(3, T),
case erlang:length(T_shape) =:= R of
false ->
false;
true ->
_pipe = gleam@list:zip(Base_shape, T_shape),
_pipe@1 = gleam@list:index_map(
_pipe,
fun(Pair, I) -> {Pair, I} end
),
gleam@list:all(
_pipe@1,
fun(X) ->
{{Dim_a, Dim_b}, I@1} = X,
(I@1 =:= Axis) orelse (Dim_a =:= Dim_b)
end
)
end
end
),
case Shapes_ok of
false ->
{error,
{invalid_shape,
<<"Shapes must match except on concat axis"/utf8>>}};
true ->
Concat_dim = gleam@list:fold(
Tensors,
0,
fun(Acc, T@1) ->
case begin
_pipe@2 = gleam@list:drop(
erlang:element(3, T@1),
Axis
),
gleam@list:first(_pipe@2)
end of
{ok, D} ->
Acc + D;
{error, _} ->
Acc
end
end
),
New_shape = begin
_pipe@3 = Base_shape,
gleam@list:index_map(
_pipe@3,
fun(D@1, I@2) -> case I@2 =:= Axis of
true ->
Concat_dim;
false ->
D@1
end end
)
end,
case Axis =:= 0 of
true ->
Data = gleam@list:flat_map(
Tensors,
fun(T@2) -> get_data(T@2) end
),
{ok, {tensor, Data, New_shape}};
false ->
Total_size = gleam@list:fold(
New_shape,
1,
fun(Acc@1, D@2) -> Acc@1 * D@2 end
),
_ = compute_strides(New_shape),
Result = begin
_pipe@4 = gleam@list:range(
0,
Total_size - 1
),
gleam@list:map(
_pipe@4,
fun(Flat_idx) ->
Indices = flat_to_multi(
Flat_idx,
New_shape
),
Axis_idx = case begin
_pipe@5 = gleam@list:drop(
Indices,
Axis
),
gleam@list:first(_pipe@5)
end of
{ok, I@3} ->
I@3;
{error, _} ->
0
end,
{Tensor_idx, Local_axis_idx, _} = gleam@list:fold(
Tensors,
{-1, Axis_idx, 0},
fun(Acc@2, T@3) ->
{Found_t,
Remaining,
T_idx} = Acc@2,
case Found_t >= 0 of
true ->
Acc@2;
false ->
T_axis_size = case begin
_pipe@6 = gleam@list:drop(
erlang:element(
3,
T@3
),
Axis
),
gleam@list:first(
_pipe@6
)
end of
{ok, D@3} ->
D@3;
{error, _} ->
0
end,
case Remaining < T_axis_size of
true ->
{T_idx,
Remaining,
T_idx};
false ->
{-1,
Remaining
- T_axis_size,
T_idx
+ 1}
end
end
end
),
Local_indices = begin
_pipe@7 = Indices,
gleam@list:index_map(
_pipe@7,
fun(Idx, I@4) ->
case I@4 =:= Axis of
true ->
Local_axis_idx;
false ->
Idx
end
end
)
end,
case begin
_pipe@8 = gleam@list:drop(
Tensors,
Tensor_idx
),
gleam@list:first(_pipe@8)
end of
{ok, T@4} ->
T_strides = compute_strides(
erlang:element(
3,
T@4
)
),
Local_flat = begin
_pipe@9 = gleam@list:zip(
Local_indices,
T_strides
),
gleam@list:fold(
_pipe@9,
0,
fun(A, P) ->
A + (erlang:element(
1,
P
)
* erlang:element(
2,
P
))
end
)
end,
T_data = get_data(T@4),
case begin
_pipe@10 = gleam@list:drop(
T_data,
Local_flat
),
gleam@list:first(
_pipe@10
)
end of
{ok, V} ->
V;
{error, _} ->
+0.0
end;
{error, _} ->
+0.0
end
end
)
end,
{ok, {tensor, Result, New_shape}}
end
end
end
end.
-file("src/viva_tensor/tensor.gleam", 900).
?DOC(" Take first N elements along first axis\n").
-spec take_first(tensor(), integer()) -> tensor().
take_first(T, N) ->
Data = get_data(T),
case erlang:element(3, T) of
[] ->
T;
[First_dim | Rest_dims] ->
Take_n = gleam@int:min(N, First_dim),
Stride = gleam@list:fold(Rest_dims, 1, fun(Acc, D) -> Acc * D end),
New_data = gleam@list:take(Data, Take_n * Stride),
New_shape = [Take_n | Rest_dims],
{tensor, New_data, New_shape}
end.
-file("src/viva_tensor/tensor.gleam", 916).
?DOC(" Take last N elements along first axis\n").
-spec take_last(tensor(), integer()) -> tensor().
take_last(T, N) ->
Data = get_data(T),
case erlang:element(3, T) of
[] ->
T;
[First_dim | Rest_dims] ->
Take_n = gleam@int:min(N, First_dim),
Stride = gleam@list:fold(Rest_dims, 1, fun(Acc, D) -> Acc * D end),
Skip = (First_dim - Take_n) * Stride,
New_data = gleam@list:drop(Data, Skip),
New_shape = [Take_n | Rest_dims],
{tensor, New_data, New_shape}
end.
-file("src/viva_tensor/tensor.gleam", 934).
?DOC(
" Slice tensor: extract sub-tensor from start to start+lengths\n"
" slice(t, [1], [3]) extracts elements at indices 1, 2, 3\n"
).
-spec slice(tensor(), list(integer()), list(integer())) -> {ok, tensor()} |
{error, tensor_error()}.
slice(T, Start, Lengths) ->
Data = get_data(T),
R = rank(T),
case (erlang:length(Start) =:= R) andalso (erlang:length(Lengths) =:= R) of
false ->
{error,
{dimension_error,
<<"Slice dimensions must match tensor rank"/utf8>>}};
true ->
case R of
1 ->
S = case gleam@list:first(Start) of
{ok, V} ->
V;
{error, _} ->
0
end,
Len = case gleam@list:first(Lengths) of
{ok, V@1} ->
V@1;
{error, _} ->
0
end,
Sliced = begin
_pipe = Data,
_pipe@1 = gleam@list:drop(_pipe, S),
gleam@list:take(_pipe@1, Len)
end,
{ok, {tensor, Sliced, [Len]}};
_ ->
New_size = gleam@list:fold(
Lengths,
1,
fun(Acc, D) -> Acc * D end
),
Result = begin
_pipe@2 = gleam@list:range(0, New_size - 1),
gleam@list:map(
_pipe@2,
fun(Flat_idx) ->
Local_indices = flat_to_multi(Flat_idx, Lengths),
Global_indices = gleam@list:map2(
Local_indices,
Start,
fun(L, S@1) -> L + S@1 end
),
Global_flat = multi_to_flat(
Global_indices,
erlang:element(3, T)
),
case begin
_pipe@3 = gleam@list:drop(Data, Global_flat),
gleam@list:first(_pipe@3)
end of
{ok, V@2} ->
V@2;
{error, _} ->
+0.0
end
end
)
end,
{ok, {tensor, Result, Lengths}}
end
end.
-file("src/viva_tensor/tensor.gleam", 984).
?DOC(" L2 norm\n").
-spec norm(tensor()) -> float().
norm(T) ->
Data = get_data(T),
Sum_sq = gleam@list:fold(Data, +0.0, fun(Acc, X) -> Acc + (X * X) end),
math:sqrt(Sum_sq).
-file("src/viva_tensor/tensor.gleam", 991).
?DOC(" Normalize to unit length\n").
-spec normalize(tensor()) -> tensor().
normalize(T) ->
N = norm(T),
case N > 0.0001 of
true ->
scale(T, case N of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end);
false ->
T
end.
-file("src/viva_tensor/tensor.gleam", 1000).
?DOC(" Clamp values\n").
-spec clamp(tensor(), float(), float()) -> tensor().
clamp(T, Min_val, Max_val) ->
map(T, fun(X) -> gleam@float:min(gleam@float:max(X, Min_val), Max_val) end).
-file("src/viva_tensor/tensor.gleam", 1144).
?DOC(" Remove dimensions of size 1\n").
-spec squeeze(tensor()) -> tensor().
squeeze(T) ->
Data = get_data(T),
New_shape = gleam@list:filter(erlang:element(3, T), fun(D) -> D /= 1 end),
Final_shape = case New_shape of
[] ->
[1];
_ ->
New_shape
end,
{tensor, Data, Final_shape}.
-file("src/viva_tensor/tensor.gleam", 1155).
?DOC(" Remove dimension at specific axis if it's 1\n").
-spec squeeze_axis(tensor(), integer()) -> {ok, tensor()} |
{error, tensor_error()}.
squeeze_axis(T, Axis) ->
case list_at_int(erlang:element(3, T), Axis) of
{error, _} ->
{error, {dimension_error, <<"Axis out of bounds"/utf8>>}};
{ok, D} ->
case D =:= 1 of
false ->
{error,
{invalid_shape, <<"Dimension at axis is not 1"/utf8>>}};
true ->
Data = get_data(T),
New_shape = begin
_pipe = erlang:element(3, T),
_pipe@1 = gleam@list:index_map(
_pipe,
fun(Dim, I) -> {Dim, I} end
),
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(Pair) -> erlang:element(2, Pair) /= Axis end
),
gleam@list:map(
_pipe@2,
fun(Pair@1) -> erlang:element(1, Pair@1) end
)
end,
{ok, {tensor, Data, New_shape}}
end
end.
-file("src/viva_tensor/tensor.gleam", 1176).
?DOC(" Add dimension of size 1 at specified axis\n").
-spec unsqueeze(tensor(), integer()) -> tensor().
unsqueeze(T, Axis) ->
Data = get_data(T),
Rnk = erlang:length(erlang:element(3, T)),
Insert_at = case Axis < 0 of
true ->
(Rnk + Axis) + 1;
false ->
Axis
end,
{Before, After} = gleam@list:split(erlang:element(3, T), Insert_at),
New_shape = lists:append([Before, [1], After]),
{tensor, Data, New_shape}.
-file("src/viva_tensor/tensor.gleam", 861).
?DOC(
" Stack tensors along a new axis\n"
" For [3] and [3] tensors: stack([a, b], 0) -> [2, 3]\n"
" For [3] and [3] tensors: stack([a, b], 1) -> [3, 2]\n"
).
-spec stack(list(tensor()), integer()) -> {ok, tensor()} |
{error, tensor_error()}.
stack(Tensors, Axis) ->
case Tensors of
[] ->
{error, {invalid_shape, <<"Cannot stack empty list"/utf8>>}};
[First | Rest] ->
Base_shape = erlang:element(3, First),
Shapes_ok = gleam@list:all(
Rest,
fun(T) -> erlang:element(3, T) =:= Base_shape end
),
case Shapes_ok of
false ->
{error, {shape_mismatch, Base_shape, []}};
true ->
N_tensors = erlang:length(Tensors),
R = erlang:length(Base_shape),
Insert_axis = case Axis < 0 of
true ->
(R + Axis) + 1;
false ->
Axis
end,
case (Insert_axis >= 0) andalso (Insert_axis =< R) of
false ->
{error,
{dimension_error,
<<"Invalid axis for stacking"/utf8>>}};
true ->
{Before, After} = gleam@list:split(
Base_shape,
Insert_axis
),
_ = lists:append([Before, [N_tensors], After]),
Unsqueezed = begin
_pipe = Tensors,
gleam@list:map(
_pipe,
fun(T@1) -> unsqueeze(T@1, Insert_axis) end
)
end,
concat_axis(Unsqueezed, Insert_axis)
end
end
end.
-file("src/viva_tensor/tensor.gleam", 1190).
?DOC(" Expand tensor to add batch dimension\n").
-spec expand_dims(tensor(), integer()) -> tensor().
expand_dims(T, Axis) ->
unsqueeze(T, Axis).
-file("src/viva_tensor/tensor.gleam", 1211).
?DOC(" Convert strided tensor back to regular (materializes the view)\n").
-spec to_contiguous(tensor()) -> tensor().
to_contiguous(T) ->
case T of
{tensor, _, _} ->
T;
{strided_tensor, _, _, _, _} ->
Data = get_data(T),
{tensor, Data, erlang:element(3, T)}
end.
-file("src/viva_tensor/tensor.gleam", 1261).
?DOC(" Get element with O(1) access for StridedTensor\n").
-spec get_fast(tensor(), integer()) -> {ok, float()} | {error, tensor_error()}.
get_fast(T, Index) ->
case T of
{tensor, Data, _} ->
_pipe = list_at_float(Data, Index),
gleam@result:map_error(
_pipe,
fun(_) ->
{dimension_error,
<<<<"Index "/utf8,
(erlang:integer_to_binary(Index))/binary>>/binary,
" out of bounds"/utf8>>}
end
);
{strided_tensor, Storage, Shape, Strides, Offset} ->
Indices = flat_to_multi(Index, Shape),
Flat_idx = begin
_pipe@1 = gleam@list:zip(Indices, Strides),
gleam@list:fold(
_pipe@1,
Offset,
fun(Acc, Pair) ->
{Idx, Stride} = Pair,
Acc + (Idx * Stride)
end
)
end,
{ok, array_get(Storage, Flat_idx)}
end.
-file("src/viva_tensor/tensor.gleam", 1282).
?DOC(" Get 2D element with O(1) access\n").
-spec get2d_fast(tensor(), integer(), integer()) -> {ok, float()} |
{error, tensor_error()}.
get2d_fast(T, Row, Col) ->
case T of
{tensor, _, _} ->
get2d(T, Row, Col);
{strided_tensor, Storage, Shape, Strides, Offset} ->
case {Shape, Strides} of
{[_, _], [S0, S1]} ->
Flat_idx = (Offset + (Row * S0)) + (Col * S1),
{ok, array_get(Storage, Flat_idx)};
{_, _} ->
{error, {dimension_error, <<"Tensor is not 2D"/utf8>>}}
end
end.
-file("src/viva_tensor/tensor.gleam", 1351).
-spec broadcast_data(tensor(), list(integer())) -> list(float()).
broadcast_data(T, Target_shape) ->
Target_size = gleam@list:fold(
Target_shape,
1,
fun(Acc, Dim) -> Acc * Dim end
),
Src_shape = erlang:element(3, T),
Src_rank = erlang:length(Src_shape),
Target_rank = erlang:length(Target_shape),
Data = get_data(T),
Diff = Target_rank - Src_rank,
Padded_shape = lists:append(gleam@list:repeat(1, Diff), Src_shape),
_pipe = gleam@list:range(0, Target_size - 1),
gleam@list:map(
_pipe,
fun(Flat_idx) ->
Target_indices = flat_to_multi(Flat_idx, Target_shape),
Src_indices = begin
_pipe@1 = gleam@list:zip(Target_indices, Padded_shape),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Pair) ->
{Idx, Dim@1} = Pair,
case Dim@1 =:= 1 of
true ->
0;
false ->
Idx
end
end
),
gleam@list:drop(_pipe@2, Diff)
end,
Src_flat = multi_to_flat(Src_indices, Src_shape),
case list_at_float(Data, Src_flat) of
{ok, V} ->
V;
{error, _} ->
+0.0
end
end
).
-file("src/viva_tensor/tensor.gleam", 1105).
?DOC(" Broadcast tensor to target shape\n").
-spec broadcast_to(tensor(), list(integer())) -> {ok, tensor()} |
{error, tensor_error()}.
broadcast_to(T, Target_shape) ->
case can_broadcast(erlang:element(3, T), Target_shape) of
false ->
{error, {broadcast_error, erlang:element(3, T), Target_shape}};
true ->
case erlang:element(3, T) =:= Target_shape of
true ->
{ok, T};
false ->
Data = broadcast_data(T, Target_shape),
{ok, {tensor, Data, Target_shape}}
end
end.
-file("src/viva_tensor/tensor.gleam", 1124).
?DOC(" Element-wise addition with broadcasting\n").
-spec add_broadcast(tensor(), tensor()) -> {ok, tensor()} |
{error, tensor_error()}.
add_broadcast(A, B) ->
gleam@result:'try'(
broadcast_shape(erlang:element(3, A), erlang:element(3, B)),
fun(Result_shape) ->
gleam@result:'try'(
broadcast_to(A, Result_shape),
fun(A_bc) ->
gleam@result:'try'(
broadcast_to(B, Result_shape),
fun(B_bc) -> add(A_bc, B_bc) end
)
end
)
end
).
-file("src/viva_tensor/tensor.gleam", 1132).
?DOC(" Element-wise multiplication with broadcasting\n").
-spec mul_broadcast(tensor(), tensor()) -> {ok, tensor()} |
{error, tensor_error()}.
mul_broadcast(A, B) ->
gleam@result:'try'(
broadcast_shape(erlang:element(3, A), erlang:element(3, B)),
fun(Result_shape) ->
gleam@result:'try'(
broadcast_to(A, Result_shape),
fun(A_bc) ->
gleam@result:'try'(
broadcast_to(B, Result_shape),
fun(B_bc) -> mul(A_bc, B_bc) end
)
end
)
end
).