Current section
Files
Jump to
Current section
Files
src/viva_math@vecn.erl
-module(viva_math@vecn).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_math/vecn.gleam").
-export([zeros/1, ones/1, splat/2, add/2, sub/2, multiply/2, scale/2, add_scalar/2, dot/2, length_squared/1, length/1, distance/2, normalize/1, negate/1, clamp/3, lerp/3, sum/1, mean/1, zip_with/3, euclidean_distance/2, manhattan_distance/2, cosine_similarity/2, lp_norm/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(
" N-dimensional vectors as `List(Float)`.\n"
"\n"
" Pure functional, no NIF, no broadcasting beyond scalar/vector. For heavy\n"
" linear algebra, defer to `viva_tensor`.\n"
"\n"
" Most functions return `Result` when shapes mismatch.\n"
).
-file("src/viva_math/vecn.gleam", 15).
-spec zeros(integer()) -> list(float()).
zeros(N) ->
gleam@list:repeat(+0.0, N).
-file("src/viva_math/vecn.gleam", 19).
-spec ones(integer()) -> list(float()).
ones(N) ->
gleam@list:repeat(1.0, N).
-file("src/viva_math/vecn.gleam", 23).
-spec splat(float(), integer()) -> list(float()).
splat(Value, N) ->
gleam@list:repeat(Value, N).
-file("src/viva_math/vecn.gleam", 28).
?DOC(" Element-wise add. Errors on length mismatch.\n").
-spec add(list(float()), list(float())) -> {ok, list(float())} | {error, nil}.
add(A, B) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:map(
gleam@list:zip(A, B),
fun(P) -> erlang:element(1, P) + erlang:element(2, P) end
)}
end.
-file("src/viva_math/vecn.gleam", 35).
-spec sub(list(float()), list(float())) -> {ok, list(float())} | {error, nil}.
sub(A, B) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:map(
gleam@list:zip(A, B),
fun(P) -> erlang:element(1, P) - erlang:element(2, P) end
)}
end.
-file("src/viva_math/vecn.gleam", 42).
-spec multiply(list(float()), list(float())) -> {ok, list(float())} |
{error, nil}.
multiply(A, B) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:map(
gleam@list:zip(A, B),
fun(P) -> erlang:element(1, P) * erlang:element(2, P) end
)}
end.
-file("src/viva_math/vecn.gleam", 50).
?DOC(" Scalar multiplication.\n").
-spec scale(list(float()), float()) -> list(float()).
scale(V, S) ->
gleam@list:map(V, fun(X) -> X * S end).
-file("src/viva_math/vecn.gleam", 54).
-spec add_scalar(list(float()), float()) -> list(float()).
add_scalar(V, S) ->
gleam@list:map(V, fun(X) -> X + S end).
-file("src/viva_math/vecn.gleam", 58).
-spec dot(list(float()), list(float())) -> {ok, float()} | {error, nil}.
dot(A, B) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:fold(
gleam@list:zip(A, B),
+0.0,
fun(Acc, P) ->
Acc + (erlang:element(1, P) * erlang:element(2, P))
end
)}
end.
-file("src/viva_math/vecn.gleam", 65).
-spec length_squared(list(float())) -> float().
length_squared(V) ->
gleam@list:fold(V, +0.0, fun(Acc, X) -> Acc + (X * X) end).
-file("src/viva_math/vecn.gleam", 78).
-spec length_hypot(list(float()), float()) -> float().
length_hypot(Rest, Acc) ->
case Rest of
[] ->
Acc;
[X | Tail] ->
length_hypot(Tail, viva_math@scalar:hypot(Acc, X))
end.
-file("src/viva_math/vecn.gleam", 70).
?DOC(" Length using progressive `hypot` reduction to avoid overflow.\n").
-spec length(list(float())) -> float().
length(V) ->
case V of
[] ->
+0.0;
[X] ->
viva_math@scalar:safe_sqrt(X * X);
[A, B | Rest] ->
length_hypot(Rest, viva_math@scalar:hypot(A, B))
end.
-file("src/viva_math/vecn.gleam", 85).
-spec distance(list(float()), list(float())) -> {ok, float()} | {error, nil}.
distance(A, B) ->
case sub(A, B) of
{ok, Diff} ->
{ok, length(Diff)};
{error, _} ->
{error, nil}
end.
-file("src/viva_math/vecn.gleam", 92).
-spec normalize(list(float())) -> list(float()).
normalize(V) ->
L = length(V),
case L =:= +0.0 of
true ->
V;
false ->
scale(V, case L of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end)
end.
-file("src/viva_math/vecn.gleam", 100).
-spec negate(list(float())) -> list(float()).
negate(V) ->
gleam@list:map(V, fun(X) -> +0.0 - X end).
-file("src/viva_math/vecn.gleam", 104).
-spec clamp(list(float()), float(), float()) -> list(float()).
clamp(V, Lo, Hi) ->
gleam@list:map(V, fun(X) -> viva_math@scalar:clamp(X, Lo, Hi) end).
-file("src/viva_math/vecn.gleam", 108).
-spec lerp(list(float()), list(float()), float()) -> {ok, list(float())} |
{error, nil}.
lerp(A, B, T) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:map(
gleam@list:zip(A, B),
fun(P) ->
viva_math@scalar:lerp(
erlang:element(1, P),
erlang:element(2, P),
T
)
end
)}
end.
-file("src/viva_math/vecn.gleam", 115).
-spec sum(list(float())) -> float().
sum(V) ->
gleam@list:fold(V, +0.0, fun(Acc, X) -> Acc + X end).
-file("src/viva_math/vecn.gleam", 119).
-spec mean(list(float())) -> {ok, float()} | {error, nil}.
mean(V) ->
case V of
[] ->
{error, nil};
_ ->
{ok, case erlang:float(erlang:length(V)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> sum(V) / Gleam@denominator
end}
end.
-file("src/viva_math/vecn.gleam", 127).
?DOC(" Component-wise zip with a custom binary function.\n").
-spec zip_with(list(float()), list(float()), fun((float(), float()) -> float())) -> {ok,
list(float())} |
{error, nil}.
zip_with(A, B, F) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:map(
gleam@list:zip(A, B),
fun(P) -> F(erlang:element(1, P), erlang:element(2, P)) end
)}
end.
-file("src/viva_math/vecn.gleam", 140).
?DOC(
" Euclidean (L₂) distance — alias for `distance/2` for API parity with\n"
" `gleam_community_maths` and downstream packages migrated off it.\n"
).
-spec euclidean_distance(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
euclidean_distance(A, B) ->
distance(A, B).
-file("src/viva_math/vecn.gleam", 145).
?DOC(" Manhattan (L₁) distance: `Σ |aᵢ − bᵢ|`.\n").
-spec manhattan_distance(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
manhattan_distance(A, B) ->
case erlang:length(A) =:= erlang:length(B) of
false ->
{error, nil};
true ->
{ok,
gleam@list:fold(
gleam@list:zip(A, B),
+0.0,
fun(Acc, P) ->
Acc + gleam@float:absolute_value(
erlang:element(1, P) - erlang:element(2, P)
)
end
)}
end.
-file("src/viva_math/vecn.gleam", 160).
?DOC(
" Cosine similarity: `(a · b) / (‖a‖ · ‖b‖)`.\n"
"\n"
" Returns `Error(Nil)` if either vector has zero length or lengths differ.\n"
).
-spec cosine_similarity(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
cosine_similarity(A, B) ->
case dot(A, B) of
{error, _} ->
{error, nil};
{ok, D} ->
Na = length(A),
Nb = length(B),
case (Na =:= +0.0) orelse (Nb =:= +0.0) of
true ->
{error, nil};
false ->
{ok, case (Na * Nb) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> D / Gleam@denominator
end}
end
end.
-file("src/viva_math/vecn.gleam", 189).
?DOC(
" General Lₚ norm: `(Σ |xᵢ|ᵖ)^(1/p)`.\n"
"\n"
" Defined for `p ≥ 1` (where it satisfies the triangle inequality and is a\n"
" true norm). For `0 < p < 1` the formula still computes but the result is\n"
" only a pseudo-norm (triangle inequality fails). The function does not\n"
" reject `0 < p < 1` so callers can opt into the pseudo-norm regime.\n"
"\n"
" **Domain**: `p > 0` strictly. `p ≤ 0` returns `Error(Nil)` because\n"
" `1/p` is undefined or infinite, and the underlying `pow` raises\n"
" `badarith` on Erlang while returning `Infinity` on JavaScript — i.e. the\n"
" behaviour would diverge across targets, which is unacceptable for a\n"
" dual-target library.\n"
"\n"
" Special cases: `p = 2.0` collapses to Euclidean (`length`), `p = 1.0` to\n"
" Manhattan. Returns `Ok(0.0)` for the empty vector.\n"
).
-spec lp_norm(list(float()), float()) -> {ok, float()} | {error, nil}.
lp_norm(V, P) ->
case P =< +0.0 of
true ->
{error, nil};
false ->
case V of
[] ->
{ok, +0.0};
_ ->
Powed = gleam@list:fold(
V,
+0.0,
fun(Acc, X) ->
Acc + math:pow(gleam@float:absolute_value(X), P)
end
),
{ok, math:pow(Powed, case P of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end)}
end
end.