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@core@ops.erl
-module(viva_tensor@core@ops).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_tensor/core/ops.gleam").
-export([map/2, map_indexed/2, add/2, sub/2, mul/2, 'div'/2, scale/2, add_scalar/2, negate/1, abs/1, square/1, sqrt/1, exp/1, log/1, pow/2, sum/1, product/1, mean/1, max/1, min/1, argmax/1, argmin/1, variance/1, std/1, norm/1, normalize/1, dot/2, matmul_vec/2, matmul/2, dot_fast/2, matmul_fast/2, dot_auto/2, matmul_auto/2, backend_info/0, all_backends_info/0, sum_auto/1, add_auto/2, sub_auto/2, mul_auto/2, scale_auto/2, transpose/1, outer/2, clamp/3, relu/1, sigmoid/1, tanh/1, softmax/1, can_broadcast/2, broadcast_shape/2, broadcast_to/2, add_broadcast/2, mul_broadcast/2]).
-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 operations - where the actual computation happens.\n"
"\n"
" Design philosophy: correctness first, then optimize the hot paths.\n"
" The naive O(n³) matmul is fine for small matrices. For large ones,\n"
" we delegate to BLAS via NIF (Apple Accelerate on macOS, OpenBLAS elsewhere).\n"
"\n"
" Broadcasting follows NumPy semantics exactly because (1) it's well-documented,\n"
" (2) everyone expects it, and (3) I tried inventing my own rules once. Never again.\n"
"\n"
" Historical note: Hadamard product (element-wise mul) is named after\n"
" Jacques Hadamard, who used it in his 1893 theorem on determinants.\n"
" Most people just call it \"element-wise multiplication\" now.\n"
).
-file("src/viva_tensor/core/ops.gleam", 26).
?DOC(" Map a function over all elements. The workhorse of tensor ops.\n").
-spec map(viva_tensor@core@tensor:tensor(), fun((float()) -> float())) -> viva_tensor@core@tensor:tensor().
map(T, F) ->
Data = viva_tensor@core@tensor:to_list(T),
New_data = gleam@list:map(Data, F),
case viva_tensor@core@tensor:new(New_data, viva_tensor@core@tensor:shape(T)) of
{ok, Result} ->
Result;
{error, _} ->
T
end.
-file("src/viva_tensor/core/ops.gleam", 36).
?DOC(" Like map but you also get the index. Useful for positional encoding.\n").
-spec map_indexed(
viva_tensor@core@tensor:tensor(),
fun((float(), integer()) -> float())
) -> viva_tensor@core@tensor:tensor().
map_indexed(T, F) ->
Data = viva_tensor@core@tensor:to_list(T),
New_data = gleam@list:index_map(Data, fun(X, I) -> F(X, I) end),
case viva_tensor@core@tensor:new(New_data, viva_tensor@core@tensor:shape(T)) of
{ok, Result} ->
Result;
{error, _} ->
T
end.
-file("src/viva_tensor/core/ops.gleam", 46).
?DOC(" Element-wise add. Shapes must match (use add_broadcast for different shapes).\n").
-spec add(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
add(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> X + Y end),
viva_tensor@core@tensor:new(Data, viva_tensor@core@tensor:shape(A));
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 59).
?DOC(" a - b, element-wise.\n").
-spec sub(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
sub(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> X - Y end),
viva_tensor@core@tensor:new(Data, viva_tensor@core@tensor:shape(A));
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 72).
?DOC(" Hadamard product (element-wise multiply). Not to be confused with matmul!\n").
-spec mul(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
mul(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Data = gleam@list:map2(A_data, B_data, fun(X, Y) -> X * Y end),
viva_tensor@core@tensor:new(Data, viva_tensor@core@tensor:shape(A));
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 85).
?DOC(" a / b element-wise. Watch out for division by zero (you get Infinity).\n").
-spec 'div'(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
'div'(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(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),
viva_tensor@core@tensor:new(Data, viva_tensor@core@tensor:shape(A));
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 98).
?DOC(" Scalar multiplication. The s stands for scalar, not \"slow\".\n").
-spec scale(viva_tensor@core@tensor:tensor(), float()) -> viva_tensor@core@tensor:tensor().
scale(T, S) ->
map(T, fun(X) -> X * S end).
-file("src/viva_tensor/core/ops.gleam", 103).
?DOC(" Add constant to all elements. Useful for bias terms.\n").
-spec add_scalar(viva_tensor@core@tensor:tensor(), float()) -> viva_tensor@core@tensor:tensor().
add_scalar(T, S) ->
map(T, fun(X) -> X + S end).
-file("src/viva_tensor/core/ops.gleam", 108).
?DOC(" Negate all elements (multiply by -1).\n").
-spec negate(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
negate(T) ->
scale(T, -1.0).
-file("src/viva_tensor/core/ops.gleam", 113).
?DOC(" Element-wise absolute value\n").
-spec abs(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
abs(T) ->
map(T, fun(X) -> viva_tensor@core@ffi:abs(X) end).
-file("src/viva_tensor/core/ops.gleam", 118).
?DOC(" Element-wise square\n").
-spec square(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
square(T) ->
map(T, fun(X) -> X * X end).
-file("src/viva_tensor/core/ops.gleam", 123).
?DOC(" Element-wise square root\n").
-spec sqrt(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
sqrt(T) ->
map(T, fun(X) -> viva_tensor@core@ffi:sqrt(X) end).
-file("src/viva_tensor/core/ops.gleam", 128).
?DOC(" Element-wise exponential\n").
-spec exp(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
exp(T) ->
map(T, fun(X) -> viva_tensor@core@ffi:exp(X) end).
-file("src/viva_tensor/core/ops.gleam", 133).
?DOC(" Element-wise natural log\n").
-spec log(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
log(T) ->
map(T, fun(X) -> viva_tensor@core@ffi:log(X) end).
-file("src/viva_tensor/core/ops.gleam", 138).
?DOC(" Element-wise power\n").
-spec pow(viva_tensor@core@tensor:tensor(), float()) -> viva_tensor@core@tensor:tensor().
pow(T, Exponent) ->
map(T, fun(X) -> viva_tensor@core@ffi:pow(X, Exponent) end).
-file("src/viva_tensor/core/ops.gleam", 152).
?DOC(" Sum all elements. O(n) time, O(1) space.\n").
-spec sum(viva_tensor@core@tensor:tensor()) -> float().
sum(T) ->
Data = viva_tensor@core@tensor:to_list(T),
gleam@list:fold(Data, +0.0, fun(Acc, X) -> Acc + X end).
-file("src/viva_tensor/core/ops.gleam", 158).
?DOC(" Product of all elements\n").
-spec product(viva_tensor@core@tensor:tensor()) -> float().
product(T) ->
Data = viva_tensor@core@tensor:to_list(T),
gleam@list:fold(Data, 1.0, fun(Acc, X) -> Acc * X end).
-file("src/viva_tensor/core/ops.gleam", 164).
?DOC(" Mean of all elements\n").
-spec mean(viva_tensor@core@tensor:tensor()) -> float().
mean(T) ->
S = sum(T),
N = erlang:float(viva_tensor@core@tensor: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/core/ops.gleam", 174).
?DOC(" Maximum value\n").
-spec max(viva_tensor@core@tensor:tensor()) -> float().
max(T) ->
Data = viva_tensor@core@tensor:to_list(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/core/ops.gleam", 183).
?DOC(" Minimum value\n").
-spec min(viva_tensor@core@tensor:tensor()) -> float().
min(T) ->
Data = viva_tensor@core@tensor:to_list(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/core/ops.gleam", 192).
?DOC(" Index of max element. Returns 0 for empty tensors (debatable choice).\n").
-spec argmax(viva_tensor@core@tensor:tensor()) -> integer().
argmax(T) ->
Data = viva_tensor@core@tensor:to_list(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/core/ops.gleam", 212).
?DOC(" Index of minimum element\n").
-spec argmin(viva_tensor@core@tensor:tensor()) -> integer().
argmin(T) ->
Data = viva_tensor@core@tensor:to_list(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/core/ops.gleam", 231).
?DOC(" Variance of all elements. Single pass: computes sum and sum_sq simultaneously.\n").
-spec variance(viva_tensor@core@tensor:tensor()) -> float().
variance(T) ->
Data = viva_tensor@core@tensor:to_list(T),
N = erlang:float(viva_tensor@core@tensor:size(T)),
case N > +0.0 of
true ->
{Total, Total_sq} = gleam@list:fold(
Data,
{+0.0, +0.0},
fun(Acc, X) ->
{S, Sq} = Acc,
{S + X, Sq + (X * X)}
end
),
M = case N of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Total / Gleam@denominator
end,
(case N of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> Total_sq / Gleam@denominator@1
end) - (M * M);
false ->
+0.0
end.
-file("src/viva_tensor/core/ops.gleam", 250).
?DOC(" Standard deviation\n").
-spec std(viva_tensor@core@tensor:tensor()) -> float().
std(T) ->
viva_tensor@core@ffi:sqrt(variance(T)).
-file("src/viva_tensor/core/ops.gleam", 255).
?DOC(" L2 norm\n").
-spec norm(viva_tensor@core@tensor:tensor()) -> float().
norm(T) ->
Data = viva_tensor@core@tensor:to_list(T),
Sum_sq = gleam@list:fold(Data, +0.0, fun(Acc, X) -> Acc + (X * X) end),
viva_tensor@core@ffi:sqrt(Sum_sq).
-file("src/viva_tensor/core/ops.gleam", 262).
?DOC(" Normalize to unit length\n").
-spec normalize(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@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/core/ops.gleam", 278).
?DOC(
" Dot product: Σ(a_i * b_i). The foundation of all neural networks, really.\n"
" Uses array-based access for large vectors, list-based for small ones.\n"
).
-spec dot(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
float()} |
{error, viva_tensor@core@error:tensor_error()}.
dot(A, B) ->
case ((viva_tensor@core@tensor:rank(A) =:= 1) andalso (viva_tensor@core@tensor:rank(
B
)
=:= 1))
andalso (viva_tensor@core@tensor:size(A) =:= viva_tensor@core@tensor:size(B)) of
true ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(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,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 297).
?DOC(
" Matrix-vector multiplication: [m, n] @ [n] -> [m]\n"
" Uses array-based O(1) access for O(m*n) total instead of O(m*n^2).\n"
).
-spec matmul_vec(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
matmul_vec(Mat, Vec) ->
case {viva_tensor@core@tensor:shape(Mat),
viva_tensor@core@tensor:shape(Vec)} of
{[M, N], [Vec_n]} when N =:= Vec_n ->
Mat_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(Mat)
),
Vec_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(Vec)
),
Result_data = begin
_pipe = gleam@list:range(0, M - 1),
gleam@list:map(
_pipe,
fun(Row_idx) ->
Start = Row_idx * N,
_pipe@1 = gleam@list:range(0, N - 1),
gleam@list:fold(
_pipe@1,
+0.0,
fun(Acc, K) ->
Acc + (viva_tensor@core@ffi:array_get(
Mat_arr,
Start + K
)
* viva_tensor@core@ffi:array_get(Vec_arr, K))
end
)
end
)
end,
viva_tensor@core@tensor:new(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/core/ops.gleam", 325).
?DOC(
" Matrix multiplication. The operation that launched a thousand GPUs.\n"
" C[i,j] = Σ_k A[i,k] * B[k,j]\n"
"\n"
" Uses array-based O(1) access for O(mnp) total.\n"
" For serious work, use matmul_auto() which delegates to BLAS.\n"
).
-spec matmul(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
matmul(A, B) ->
case {viva_tensor@core@tensor:shape(A), viva_tensor@core@tensor:shape(B)} of
{[M, N], [N2, P]} when N =:= N2 ->
A_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(A)
),
B_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(B)
),
Result_data = begin
_pipe = gleam@list:range(0, M - 1),
gleam@list:flat_map(
_pipe,
fun(I) ->
Row_start = I * N,
_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) ->
Acc + (viva_tensor@core@ffi:array_get(
A_arr,
Row_start + K
)
* viva_tensor@core@ffi:array_get(
B_arr,
(K * P) + J
))
end
) end
)
end
)
end,
viva_tensor@core@tensor:new(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/core/ops.gleam", 356).
?DOC(" Faster dot using Erlang arrays. ~2-3x speedup for large vectors.\n").
-spec dot_fast(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, float()} | {error, viva_tensor@core@error:tensor_error()}.
dot_fast(A, B) ->
case ((viva_tensor@core@tensor:rank(A) =:= 1) andalso (viva_tensor@core@tensor:rank(
B
)
=:= 1))
andalso (viva_tensor@core@tensor:size(A) =:= viva_tensor@core@tensor:size(B)) of
true ->
A_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(A)
),
B_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(B)
),
{ok, viva_tensor@core@ffi:array_dot(A_arr, B_arr)};
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 372).
?DOC(" Faster matmul using arrays. Use this for matrices > 50x50.\n").
-spec matmul_fast(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
matmul_fast(A, B) ->
case {viva_tensor@core@tensor:shape(A), viva_tensor@core@tensor:shape(B)} of
{[M, K], [K2, N]} when K =:= K2 ->
A_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(A)
),
B_arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(B)
),
Result_arr = viva_tensor@core@ffi:array_matmul(
A_arr,
B_arr,
M,
N,
K
),
Result_list = viva_tensor@core@ffi:array_to_list(Result_arr),
viva_tensor@core@tensor:new(Result_list, [M, N]);
{[_, K@1], [K2@1, _]} ->
{error, {shape_mismatch, [K@1, -1], [K2@1, -1]}};
{_, _} ->
{error, {dimension_error, <<"Expected two matrices"/utf8>>}}
end.
-file("src/viva_tensor/core/ops.gleam", 444).
-spec dot_with_zig_fallback(
list(float()),
list(float()),
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {{ok, float()} | {error, viva_tensor@core@error:tensor_error()}, binary()}.
dot_with_zig_fallback(A_list, B_list, A, B) ->
case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_dot(A_list, B_list) of
{ok, R} ->
{{ok, R}, <<"zig"/utf8>>};
{error, _} ->
{dot_fast(A, B), <<"erlang"/utf8>>}
end;
false ->
{dot_fast(A, B), <<"erlang"/utf8>>}
end.
-file("src/viva_tensor/core/ops.gleam", 428).
-spec dot_list_fallback(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {{ok, float()} | {error, viva_tensor@core@error:tensor_error()}, binary()}.
dot_list_fallback(A, B) ->
A_list = viva_tensor@core@tensor:to_list(A),
B_list = viva_tensor@core@tensor:to_list(B),
case viva_tensor@core@ffi:is_nif_loaded() of
true ->
case viva_tensor@core@ffi:nif_dot(A_list, B_list) of
{ok, R} ->
{{ok, R}, <<"accelerate"/utf8>>};
{error, _} ->
dot_with_zig_fallback(A_list, B_list, A, B)
end;
false ->
dot_with_zig_fallback(A_list, B_list, A, B)
end.
-file("src/viva_tensor/core/ops.gleam", 404).
?DOC(
" Smart dot - delegates to fastest available backend at runtime.\n"
" Instrumented: records latency and backend selection metrics.\n"
).
-spec dot_auto(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, float()} | {error, viva_tensor@core@error:tensor_error()}.
dot_auto(A, B) ->
case ((viva_tensor@core@tensor:rank(A) =:= 1) andalso (viva_tensor@core@tensor:rank(
B
)
=:= 1))
andalso (viva_tensor@core@tensor:size(A) =:= viva_tensor@core@tensor:size(B)) of
true ->
T0 = viva_tensor@core@ffi:now_microseconds(),
{Result, Backend} = case {viva_tensor@core@tensor:native_ref(A),
viva_tensor@core@tensor:native_ref(B)} of
{{ok, Ref_a}, {ok, Ref_b}} ->
case viva_tensor@core@ffi:nt_dot(Ref_a, Ref_b) of
{ok, R} ->
{{ok, R}, <<"native"/utf8>>};
{error, _} ->
dot_list_fallback(A, B)
end;
{_, _} ->
dot_list_fallback(A, B)
end,
viva_tensor@telemetry:record_dot(
viva_tensor@core@tensor:size(A),
viva_tensor@core@ffi:now_microseconds() - T0,
Backend
),
Result;
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 504).
-spec matmul_with_zig_fallback(
list(float()),
list(float()),
integer(),
integer(),
integer(),
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {{ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()},
binary()}.
matmul_with_zig_fallback(A_list, B_list, M, N, K, A, B) ->
case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_matmul(A_list, B_list, M, N, K) of
{ok, Result_list} ->
{viva_tensor@core@tensor:new(Result_list, [M, N]),
<<"zig"/utf8>>};
{error, _} ->
{matmul_fast(A, B), <<"erlang"/utf8>>}
end;
false ->
{matmul_fast(A, B), <<"erlang"/utf8>>}
end.
-file("src/viva_tensor/core/ops.gleam", 485).
-spec matmul_list_fallback(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor(),
integer(),
integer(),
integer()
) -> {{ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()},
binary()}.
matmul_list_fallback(A, B, M, N, K) ->
A_list = viva_tensor@core@tensor:to_list(A),
B_list = viva_tensor@core@tensor:to_list(B),
case viva_tensor@core@ffi:is_nif_loaded() of
true ->
case viva_tensor@core@ffi:nif_matmul(A_list, B_list, M, N, K) of
{ok, Result_list} ->
{viva_tensor@core@tensor:new(Result_list, [M, N]),
<<"accelerate"/utf8>>};
{error, _} ->
matmul_with_zig_fallback(A_list, B_list, M, N, K, A, B)
end;
false ->
matmul_with_zig_fallback(A_list, B_list, M, N, K, A, B)
end.
-file("src/viva_tensor/core/ops.gleam", 464).
?DOC(
" Smart matmul. Can be 1400x faster than pure Gleam for 500x500 matrices.\n"
" Instrumented: logs backend selection and records latency metrics.\n"
).
-spec matmul_auto(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
matmul_auto(A, B) ->
case {viva_tensor@core@tensor:shape(A), viva_tensor@core@tensor:shape(B)} of
{[M, K], [K2, N]} when K =:= K2 ->
T0 = viva_tensor@core@ffi:now_microseconds(),
{Result, Backend} = case {viva_tensor@core@tensor:native_ref(A),
viva_tensor@core@tensor:native_ref(B)} of
{{ok, Ref_a}, {ok, Ref_b}} ->
case viva_tensor@core@ffi:nt_matmul(Ref_a, Ref_b, M, N, K) of
{ok, Ref_c} ->
{{ok,
viva_tensor@core@tensor:from_native_ref(
Ref_c,
[M, N]
)},
<<"native"/utf8>>};
{error, _} ->
matmul_list_fallback(A, B, M, N, K)
end;
{_, _} ->
matmul_list_fallback(A, B, M, N, K)
end,
viva_tensor@telemetry:record_matmul(
M,
N,
K,
viva_tensor@core@ffi:now_microseconds() - T0,
Backend
),
Result;
{[_, K@1], [K2@1, _]} ->
{error, {shape_mismatch, [K@1, -1], [K2@1, -1]}};
{_, _} ->
{error, {dimension_error, <<"Expected two matrices"/utf8>>}}
end.
-file("src/viva_tensor/core/ops.gleam", 527).
?DOC(
" Get current backend info string\n"
" Shows best available backend for tensor operations\n"
).
-spec backend_info() -> binary().
backend_info() ->
case viva_tensor@core@ffi:zig_is_loaded() of
true ->
<<(viva_tensor@core@ffi:zig_backend_info())/binary,
" + NIF Resources"/utf8>>;
false ->
case viva_tensor@core@ffi:is_nif_loaded() of
true ->
viva_tensor@core@ffi:nif_backend_info();
false ->
<<"Pure Erlang (O(1) array access)"/utf8>>
end
end.
-file("src/viva_tensor/core/ops.gleam", 539).
?DOC(" Get detailed status of all backends\n").
-spec all_backends_info() -> binary().
all_backends_info() ->
Zig_status = case viva_tensor@core@ffi:zig_is_loaded() of
true ->
<<"✓ "/utf8, (viva_tensor@core@ffi:zig_backend_info())/binary>>;
false ->
<<"✗ Not available"/utf8>>
end,
Accel_status = case viva_tensor@core@ffi:is_nif_loaded() of
true ->
<<"✓ "/utf8, (viva_tensor@core@ffi:nif_backend_info())/binary>>;
false ->
<<"✗ Not available"/utf8>>
end,
<<<<<<<<"Zig SIMD: "/utf8, Zig_status/binary>>/binary,
"\nApple Accelerate: "/utf8>>/binary,
Accel_status/binary>>/binary,
"\nPure Erlang: ✓ Always available"/utf8>>.
-file("src/viva_tensor/core/ops.gleam", 585).
-spec sum_with_accel_fallback(list(float())) -> float().
sum_with_accel_fallback(Data) ->
case viva_tensor@core@ffi:is_nif_loaded() of
true ->
case viva_tensor@core@ffi:nif_sum(Data) of
{ok, Result} ->
Result;
{error, _} ->
gleam@list:fold(Data, +0.0, fun(Acc, X) -> Acc + X end)
end;
false ->
gleam@list:fold(Data, +0.0, fun(Acc@1, X@1) -> Acc@1 + X@1 end)
end.
-file("src/viva_tensor/core/ops.gleam", 573).
-spec sum_list_fallback(viva_tensor@core@tensor:tensor()) -> float().
sum_list_fallback(T) ->
Data = viva_tensor@core@tensor:to_list(T),
case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_sum(Data) of
{ok, R} ->
R;
{error, _} ->
sum_with_accel_fallback(Data)
end;
false ->
sum_with_accel_fallback(Data)
end.
-file("src/viva_tensor/core/ops.gleam", 558).
?DOC(
" Auto-selecting sum reduction.\n"
" Priority: Zig SIMD > Apple Accelerate > Pure Erlang.\n"
" Instrumented: records operation latency.\n"
).
-spec sum_auto(viva_tensor@core@tensor:tensor()) -> float().
sum_auto(T) ->
T0 = viva_tensor@core@ffi:now_microseconds(),
Result = case viva_tensor@core@tensor:native_ref(T) of
{ok, Ref} ->
case viva_tensor@core@ffi:nt_sum(Ref) of
{ok, R} ->
R;
{error, _} ->
sum_list_fallback(T)
end;
{error, _} ->
sum_list_fallback(T)
end,
viva_tensor@telemetry:record_op(
<<"sum"/utf8>>,
viva_tensor@core@ffi:now_microseconds() - T0
),
Result.
-file("src/viva_tensor/core/ops.gleam", 613).
-spec add_list_fallback(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
add_list_fallback(A, B) ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Result_data = case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_add(A_data, B_data) of
{ok, R} ->
R;
{error, _} ->
gleam@list:map2(A_data, B_data, fun(X, Y) -> X + Y end)
end;
false ->
gleam@list:map2(A_data, B_data, fun(X@1, Y@1) -> X@1 + Y@1 end)
end,
viva_tensor@core@tensor:new(Result_data, viva_tensor@core@tensor:shape(A)).
-file("src/viva_tensor/core/ops.gleam", 598).
?DOC(" Auto-selecting element-wise add. Delegates to Zig SIMD or Accelerate.\n").
-spec add_auto(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
add_auto(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
case {viva_tensor@core@tensor:native_ref(A),
viva_tensor@core@tensor:native_ref(B)} of
{{ok, Ref_a}, {ok, Ref_b}} ->
case viva_tensor@core@ffi:nt_add(Ref_a, Ref_b) of
{ok, Ref_c} ->
{ok,
viva_tensor@core@tensor:from_native_ref(
Ref_c,
viva_tensor@core@tensor:shape(A)
)};
{error, _} ->
add_list_fallback(A, B)
end;
{_, _} ->
add_list_fallback(A, B)
end;
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 643).
-spec sub_list_fallback(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
sub_list_fallback(A, B) ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Result_data = case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_scale(B_data, -1.0) of
{ok, Neg_b} ->
case viva_tensor@core@ffi:zig_add(A_data, Neg_b) of
{ok, R} ->
R;
{error, _} ->
gleam@list:map2(
A_data,
B_data,
fun(X, Y) -> X - Y end
)
end;
{error, _} ->
gleam@list:map2(
A_data,
B_data,
fun(X@1, Y@1) -> X@1 - Y@1 end
)
end;
false ->
gleam@list:map2(A_data, B_data, fun(X@2, Y@2) -> X@2 - Y@2 end)
end,
viva_tensor@core@tensor:new(Result_data, viva_tensor@core@tensor:shape(A)).
-file("src/viva_tensor/core/ops.gleam", 628).
?DOC(" Auto-selecting element-wise subtract.\n").
-spec sub_auto(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
sub_auto(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
case {viva_tensor@core@tensor:native_ref(A),
viva_tensor@core@tensor:native_ref(B)} of
{{ok, Ref_a}, {ok, Ref_b}} ->
case viva_tensor@core@ffi:nt_sub(Ref_a, Ref_b) of
{ok, Ref_c} ->
{ok,
viva_tensor@core@tensor:from_native_ref(
Ref_c,
viva_tensor@core@tensor:shape(A)
)};
{error, _} ->
sub_list_fallback(A, B)
end;
{_, _} ->
sub_list_fallback(A, B)
end;
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 677).
-spec mul_list_fallback(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
mul_list_fallback(A, B) ->
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Result_data = case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_mul(A_data, B_data) of
{ok, R} ->
R;
{error, _} ->
gleam@list:map2(A_data, B_data, fun(X, Y) -> X * Y end)
end;
false ->
gleam@list:map2(A_data, B_data, fun(X@1, Y@1) -> X@1 * Y@1 end)
end,
viva_tensor@core@tensor:new(Result_data, viva_tensor@core@tensor:shape(A)).
-file("src/viva_tensor/core/ops.gleam", 662).
?DOC(" Auto-selecting element-wise multiply. Delegates to Zig SIMD when available.\n").
-spec mul_auto(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
mul_auto(A, B) ->
case viva_tensor@core@tensor:shape(A) =:= viva_tensor@core@tensor:shape(B) of
true ->
case {viva_tensor@core@tensor:native_ref(A),
viva_tensor@core@tensor:native_ref(B)} of
{{ok, Ref_a}, {ok, Ref_b}} ->
case viva_tensor@core@ffi:nt_mul(Ref_a, Ref_b) of
{ok, Ref_c} ->
{ok,
viva_tensor@core@tensor:from_native_ref(
Ref_c,
viva_tensor@core@tensor:shape(A)
)};
{error, _} ->
mul_list_fallback(A, B)
end;
{_, _} ->
mul_list_fallback(A, B)
end;
false ->
{error,
{shape_mismatch,
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(B)}}
end.
-file("src/viva_tensor/core/ops.gleam", 720).
-spec scale_with_accel_fallback(list(float()), float()) -> list(float()).
scale_with_accel_fallback(Data, S) ->
case viva_tensor@core@ffi:is_nif_loaded() of
true ->
case viva_tensor@core@ffi:nif_scale(Data, S) of
{ok, Result} ->
Result;
{error, _} ->
gleam@list:map(Data, fun(X) -> X * S end)
end;
false ->
gleam@list:map(Data, fun(X@1) -> X@1 * S end)
end.
-file("src/viva_tensor/core/ops.gleam", 704).
-spec scale_list_fallback(viva_tensor@core@tensor:tensor(), float()) -> viva_tensor@core@tensor:tensor().
scale_list_fallback(T, S) ->
Data = viva_tensor@core@tensor:to_list(T),
Result_data = case viva_tensor@core@ffi:zig_is_loaded() of
true ->
case viva_tensor@core@ffi:zig_scale(Data, S) of
{ok, R} ->
R;
{error, _} ->
scale_with_accel_fallback(Data, S)
end;
false ->
scale_with_accel_fallback(Data, S)
end,
case viva_tensor@core@tensor:new(
Result_data,
viva_tensor@core@tensor:shape(T)
) of
{ok, Result} ->
Result;
{error, _} ->
T
end.
-file("src/viva_tensor/core/ops.gleam", 692).
?DOC(" Auto-selecting scalar multiplication. Delegates to Zig SIMD or Accelerate.\n").
-spec scale_auto(viva_tensor@core@tensor:tensor(), float()) -> viva_tensor@core@tensor:tensor().
scale_auto(T, S) ->
case viva_tensor@core@tensor:native_ref(T) of
{ok, Ref} ->
case viva_tensor@core@ffi:nt_scale(Ref, S) of
{ok, Ref_c} ->
viva_tensor@core@tensor:from_native_ref(
Ref_c,
viva_tensor@core@tensor:shape(T)
);
{error, _} ->
scale_list_fallback(T, S)
end;
{error, _} ->
scale_list_fallback(T, S)
end.
-file("src/viva_tensor/core/ops.gleam", 733).
?DOC(" Matrix transpose. Uses array-based O(1) access for O(m*n) total.\n").
-spec transpose(viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
transpose(T) ->
case viva_tensor@core@tensor:shape(T) of
[M, N] ->
Arr = viva_tensor@core@ffi:list_to_array(
viva_tensor@core@tensor:to_list(T)
),
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:map(
_pipe@1,
fun(I) ->
viva_tensor@core@ffi:array_get(Arr, (I * N) + J)
end
) end
)
end,
viva_tensor@core@tensor:new(Result_data, [N, M]);
_ ->
{error, {dimension_error, <<"Transpose requires 2D tensor"/utf8>>}}
end.
-file("src/viva_tensor/core/ops.gleam", 750).
?DOC(" Outer product: [m] @ [n] -> [m, n]\n").
-spec outer(viva_tensor@core@tensor:tensor(), viva_tensor@core@tensor:tensor()) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
outer(A, B) ->
case (viva_tensor@core@tensor:rank(A) =:= 1) andalso (viva_tensor@core@tensor:rank(
B
)
=:= 1) of
true ->
M = viva_tensor@core@tensor:size(A),
N = viva_tensor@core@tensor:size(B),
A_data = viva_tensor@core@tensor:to_list(A),
B_data = viva_tensor@core@tensor:to_list(B),
Result_data = gleam@list:flat_map(
A_data,
fun(Ai) -> gleam@list:map(B_data, fun(Bj) -> Ai * Bj end) end
),
viva_tensor@core@tensor:new(Result_data, [M, N]);
false ->
{error,
{dimension_error, <<"Outer product requires two vectors"/utf8>>}}
end.
-file("src/viva_tensor/core/ops.gleam", 768).
?DOC(" Clamp to [min, max]. Useful for gradient clipping.\n").
-spec clamp(viva_tensor@core@tensor:tensor(), float(), float()) -> viva_tensor@core@tensor: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/core/ops.gleam", 773).
?DOC(" ReLU activation\n").
-spec relu(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
relu(T) ->
map(T, fun(X) -> gleam@float:max(X, +0.0) end).
-file("src/viva_tensor/core/ops.gleam", 778).
?DOC(" Sigmoid activation\n").
-spec sigmoid(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
sigmoid(T) ->
map(T, fun(X) -> case (1.0 + viva_tensor@core@ffi:exp(+0.0 - X)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end end).
-file("src/viva_tensor/core/ops.gleam", 783).
?DOC(" Tanh activation\n").
-spec tanh(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
tanh(T) ->
map(T, fun(X) -> viva_tensor@core@ffi:tanh(X) end).
-file("src/viva_tensor/core/ops.gleam", 797).
?DOC(
" Softmax: exp(x_i) / Σexp(x_j). Converts logits to probabilities.\n"
"\n"
" The \"subtract max\" trick prevents overflow. Without it:\n"
" softmax([1000, 1001, 1002]) = [exp(1000)/..., ...] = [Inf/Inf, Inf/Inf, Inf/Inf] = NaN\n"
" With it:\n"
" softmax([1000, 1001, 1002]) = softmax([0, 1, 2]) = [0.09, 0.24, 0.67] ✓\n"
"\n"
" Mathematically equivalent because softmax(x) = softmax(x - c) for any c.\n"
"\n"
" TODO: add axis parameter, this only works on 1D vectors right now\n"
).
-spec softmax(viva_tensor@core@tensor:tensor()) -> viva_tensor@core@tensor:tensor().
softmax(T) ->
Data = viva_tensor@core@tensor:to_list(T),
Max_val = max(T),
Shifted = gleam@list:map(
Data,
fun(X) -> viva_tensor@core@ffi:exp(X - Max_val) end
),
Sum_exp = gleam@list:fold(Shifted, +0.0, fun(Acc, X@1) -> Acc + X@1 end),
Result = gleam@list:map(Shifted, fun(X@2) -> case Sum_exp of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> X@2 / Gleam@denominator
end end),
case viva_tensor@core@tensor:new(Result, viva_tensor@core@tensor:shape(T)) of
{ok, R} ->
R;
{error, _} ->
T
end.
-file("src/viva_tensor/core/ops.gleam", 822).
?DOC(" Check if shapes can 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/core/ops.gleam", 839).
?DOC(" Compute broadcast shape\n").
-spec broadcast_shape(list(integer()), list(integer())) -> {ok, list(integer())} |
{error, viva_tensor@core@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/core/ops.gleam", 939).
-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/core/ops.gleam", 960).
-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/core/ops.gleam", 951).
-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/core/ops.gleam", 970).
-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/core/ops.gleam", 906).
-spec broadcast_data(viva_tensor@core@tensor: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 = viva_tensor@core@tensor:shape(T),
Src_rank = erlang:length(Src_shape),
Target_rank = erlang:length(Target_shape),
Data = viva_tensor@core@tensor:to_list(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/core/ops.gleam", 887).
?DOC(" Broadcast tensor to target shape\n").
-spec broadcast_to(viva_tensor@core@tensor:tensor(), list(integer())) -> {ok,
viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
broadcast_to(T, Target_shape) ->
Src_shape = viva_tensor@core@tensor:shape(T),
case can_broadcast(Src_shape, Target_shape) of
false ->
{error, {broadcast_error, Src_shape, Target_shape}};
true ->
case Src_shape =:= Target_shape of
true ->
{ok, T};
false ->
Data = broadcast_data(T, Target_shape),
viva_tensor@core@tensor:new(Data, Target_shape)
end
end.
-file("src/viva_tensor/core/ops.gleam", 865).
?DOC(" Element-wise addition with broadcasting\n").
-spec add_broadcast(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
add_broadcast(A, B) ->
gleam@result:'try'(
broadcast_shape(
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(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/core/ops.gleam", 876).
?DOC(" Element-wise multiplication with broadcasting\n").
-spec mul_broadcast(
viva_tensor@core@tensor:tensor(),
viva_tensor@core@tensor:tensor()
) -> {ok, viva_tensor@core@tensor:tensor()} |
{error, viva_tensor@core@error:tensor_error()}.
mul_broadcast(A, B) ->
gleam@result:'try'(
broadcast_shape(
viva_tensor@core@tensor:shape(A),
viva_tensor@core@tensor:shape(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
).