Packages

Core mathematical functions for VIVA - sentient digital life. PAD emotions, Cusp catastrophe, Free Energy Principle, attractor dynamics.

Current section

Files

Jump to
viva_math src viva_math@vector.erl
Raw

src/viva_math@vector.erl

-module(viva_math@vector).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_math/vector.gleam").
-export([zero/0, splat/1, from_list/1, to_list/1, add/2, sub/2, scale/2, 'div'/2, negate/1, multiply/2, dot/2, cross/2, length_squared/1, length/1, distance/2, distance_squared/2, normalize/1, lerp/3, clamp/3, clamp_pad/1, map/2, is_close/3, weighted_average/2, min/2, max/2, sum/1, average/1, pad/3, pleasure/1, arousal/1, dominance/1]).
-export_type([vec3/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(
" Vector operations for 3D space (PAD model).\n"
"\n"
" Vec3 is the fundamental type for emotional state in VIVA:\n"
" - Pleasure (x): [-1, 1] - sadness ↔ joy\n"
" - Arousal (y): [-1, 1] - calm ↔ excitement\n"
" - Dominance (z): [-1, 1] - submission ↔ control\n"
).
-type vec3() :: {vec3, float(), float(), float()}.
-file("src/viva_math/vector.gleam", 18).
?DOC(" Create a zero vector.\n").
-spec zero() -> vec3().
zero() ->
{vec3, +0.0, +0.0, +0.0}.
-file("src/viva_math/vector.gleam", 23).
?DOC(" Create a vector with all components set to the same value.\n").
-spec splat(float()) -> vec3().
splat(Value) ->
{vec3, Value, Value, Value}.
-file("src/viva_math/vector.gleam", 29).
?DOC(
" Create a Vec3 from a list of 3 floats.\n"
" Returns Error if list doesn't have exactly 3 elements.\n"
).
-spec from_list(list(float())) -> {ok, vec3()} | {error, nil}.
from_list(Values) ->
case Values of
[X, Y, Z] ->
{ok, {vec3, X, Y, Z}};
_ ->
{error, nil}
end.
-file("src/viva_math/vector.gleam", 37).
?DOC(" Convert Vec3 to a list of floats.\n").
-spec to_list(vec3()) -> list(float()).
to_list(V) ->
[erlang:element(2, V), erlang:element(3, V), erlang:element(4, V)].
-file("src/viva_math/vector.gleam", 42).
?DOC(" Add two vectors.\n").
-spec add(vec3(), vec3()) -> vec3().
add(A, B) ->
{vec3,
erlang:element(2, A) + erlang:element(2, B),
erlang:element(3, A) + erlang:element(3, B),
erlang:element(4, A) + erlang:element(4, B)}.
-file("src/viva_math/vector.gleam", 47).
?DOC(" Subtract vector b from vector a.\n").
-spec sub(vec3(), vec3()) -> vec3().
sub(A, B) ->
{vec3,
erlang:element(2, A) - erlang:element(2, B),
erlang:element(3, A) - erlang:element(3, B),
erlang:element(4, A) - erlang:element(4, B)}.
-file("src/viva_math/vector.gleam", 52).
?DOC(" Multiply vector by scalar.\n").
-spec scale(vec3(), float()) -> vec3().
scale(V, S) ->
{vec3,
erlang:element(2, V) * S,
erlang:element(3, V) * S,
erlang:element(4, V) * S}.
-file("src/viva_math/vector.gleam", 57).
?DOC(" Divide vector by scalar. Returns Error if scalar is zero.\n").
-spec 'div'(vec3(), float()) -> {ok, vec3()} | {error, nil}.
'div'(V, S) ->
case S =:= +0.0 of
true ->
{error, nil};
false ->
{ok, {vec3, case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:element(2, V) / Gleam@denominator
end, case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:element(3, V) / Gleam@denominator@1
end, case S of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> erlang:element(4, V) / Gleam@denominator@2
end}}
end.
-file("src/viva_math/vector.gleam", 65).
?DOC(" Negate a vector.\n").
-spec negate(vec3()) -> vec3().
negate(V) ->
{vec3,
+0.0 - erlang:element(2, V),
+0.0 - erlang:element(3, V),
+0.0 - erlang:element(4, V)}.
-file("src/viva_math/vector.gleam", 70).
?DOC(" Component-wise multiplication (Hadamard product).\n").
-spec multiply(vec3(), vec3()) -> vec3().
multiply(A, B) ->
{vec3,
erlang:element(2, A) * erlang:element(2, B),
erlang:element(3, A) * erlang:element(3, B),
erlang:element(4, A) * erlang:element(4, B)}.
-file("src/viva_math/vector.gleam", 75).
?DOC(" Dot product of two vectors.\n").
-spec dot(vec3(), vec3()) -> float().
dot(A, B) ->
((erlang:element(2, A) * erlang:element(2, B)) + (erlang:element(3, A) * erlang:element(
3,
B
)))
+ (erlang:element(4, A) * erlang:element(4, B)).
-file("src/viva_math/vector.gleam", 80).
?DOC(" Cross product of two vectors.\n").
-spec cross(vec3(), vec3()) -> vec3().
cross(A, B) ->
{vec3,
(erlang:element(3, A) * erlang:element(4, B)) - (erlang:element(4, A) * erlang:element(
3,
B
)),
(erlang:element(4, A) * erlang:element(2, B)) - (erlang:element(2, A) * erlang:element(
4,
B
)),
(erlang:element(2, A) * erlang:element(3, B)) - (erlang:element(3, A) * erlang:element(
2,
B
))}.
-file("src/viva_math/vector.gleam", 89).
?DOC(" Squared length of a vector (avoids sqrt for comparisons).\n").
-spec length_squared(vec3()) -> float().
length_squared(V) ->
dot(V, V).
-file("src/viva_math/vector.gleam", 94).
?DOC(" Length (magnitude) of a vector.\n").
-spec length(vec3()) -> float().
length(V) ->
Squared = length_squared(V),
case gleam@float:square_root(Squared) of
{ok, Result} ->
Result;
{error, _} ->
+0.0
end.
-file("src/viva_math/vector.gleam", 103).
?DOC(" Euclidean distance between two vectors.\n").
-spec distance(vec3(), vec3()) -> float().
distance(A, B) ->
length(sub(A, B)).
-file("src/viva_math/vector.gleam", 108).
?DOC(" Squared distance between two vectors (avoids sqrt).\n").
-spec distance_squared(vec3(), vec3()) -> float().
distance_squared(A, B) ->
length_squared(sub(A, B)).
-file("src/viva_math/vector.gleam", 114).
?DOC(
" Normalize a vector to unit length.\n"
" Returns zero vector if input has zero length.\n"
).
-spec normalize(vec3()) -> vec3().
normalize(V) ->
Len = length(V),
case Len =:= +0.0 of
true ->
zero();
false ->
{vec3, case Len of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:element(2, V) / Gleam@denominator
end, case Len of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> erlang:element(3, V) / Gleam@denominator@1
end, case Len of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> erlang:element(4, V) / Gleam@denominator@2
end}
end.
-file("src/viva_math/vector.gleam", 123).
?DOC(" Linear interpolation between two vectors.\n").
-spec lerp(vec3(), vec3(), float()) -> vec3().
lerp(A, B, T) ->
{vec3,
viva_math@common:lerp(erlang:element(2, A), erlang:element(2, B), T),
viva_math@common:lerp(erlang:element(3, A), erlang:element(3, B), T),
viva_math@common:lerp(erlang:element(4, A), erlang:element(4, B), T)}.
-file("src/viva_math/vector.gleam", 132).
?DOC(" Clamp each component to [min, max] range.\n").
-spec clamp(vec3(), float(), float()) -> vec3().
clamp(V, Min, Max) ->
{vec3,
viva_math@common:clamp(erlang:element(2, V), Min, Max),
viva_math@common:clamp(erlang:element(3, V), Min, Max),
viva_math@common:clamp(erlang:element(4, V), Min, Max)}.
-file("src/viva_math/vector.gleam", 141).
?DOC(" Clamp vector to PAD range [-1, 1] for all components.\n").
-spec clamp_pad(vec3()) -> vec3().
clamp_pad(V) ->
clamp(V, -1.0, 1.0).
-file("src/viva_math/vector.gleam", 146).
?DOC(" Apply a function to each component.\n").
-spec map(vec3(), fun((float()) -> float())) -> vec3().
map(V, F) ->
{vec3,
F(erlang:element(2, V)),
F(erlang:element(3, V)),
F(erlang:element(4, V))}.
-file("src/viva_math/vector.gleam", 151).
?DOC(" Check if two vectors are approximately equal.\n").
-spec is_close(vec3(), vec3(), float()) -> boolean().
is_close(A, B, Tolerance) ->
Dx = gleam@float:absolute_value(erlang:element(2, A) - erlang:element(2, B)),
Dy = gleam@float:absolute_value(erlang:element(3, A) - erlang:element(3, B)),
Dz = gleam@float:absolute_value(erlang:element(4, A) - erlang:element(4, B)),
((Dx =< Tolerance) andalso (Dy =< Tolerance)) andalso (Dz =< Tolerance).
-file("src/viva_math/vector.gleam", 160).
?DOC(
" Compute weighted average of vectors.\n"
" weights and vectors must have same length.\n"
).
-spec weighted_average(list(vec3()), list(float())) -> {ok, vec3()} |
{error, nil}.
weighted_average(Vectors, Weights) ->
case erlang:length(Vectors) =:= erlang:length(Weights) of
false ->
{error, nil};
true ->
Sum_weights = gleam@list:fold(
Weights,
+0.0,
fun(Acc, W) -> Acc + W end
),
case Sum_weights =:= +0.0 of
true ->
{error, nil};
false ->
Weighted = begin
_pipe = gleam@list:zip(Vectors, Weights),
_pipe@1 = gleam@list:map(
_pipe,
fun(Pair) ->
{V, W@1} = Pair,
scale(V, W@1)
end
),
gleam@list:fold(_pipe@1, zero(), fun add/2)
end,
{ok, scale(Weighted, case Sum_weights of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end)}
end
end.
-file("src/viva_math/vector.gleam", 186).
?DOC(" Component-wise minimum of two vectors.\n").
-spec min(vec3(), vec3()) -> vec3().
min(A, B) ->
{vec3,
gleam@float:min(erlang:element(2, A), erlang:element(2, B)),
gleam@float:min(erlang:element(3, A), erlang:element(3, B)),
gleam@float:min(erlang:element(4, A), erlang:element(4, B))}.
-file("src/viva_math/vector.gleam", 191).
?DOC(" Component-wise maximum of two vectors.\n").
-spec max(vec3(), vec3()) -> vec3().
max(A, B) ->
{vec3,
gleam@float:max(erlang:element(2, A), erlang:element(2, B)),
gleam@float:max(erlang:element(3, A), erlang:element(3, B)),
gleam@float:max(erlang:element(4, A), erlang:element(4, B))}.
-file("src/viva_math/vector.gleam", 196).
?DOC(" Sum of all components.\n").
-spec sum(vec3()) -> float().
sum(V) ->
(erlang:element(2, V) + erlang:element(3, V)) + erlang:element(4, V).
-file("src/viva_math/vector.gleam", 201).
?DOC(" Average of all components.\n").
-spec average(vec3()) -> float().
average(V) ->
sum(V) / 3.0.
-file("src/viva_math/vector.gleam", 208).
?DOC(" Create a PAD vector (Pleasure, Arousal, Dominance).\n").
-spec pad(float(), float(), float()) -> vec3().
pad(Pleasure, Arousal, Dominance) ->
_pipe = {vec3, Pleasure, Arousal, Dominance},
clamp_pad(_pipe).
-file("src/viva_math/vector.gleam", 213).
?DOC(" Get Pleasure component (x).\n").
-spec pleasure(vec3()) -> float().
pleasure(V) ->
erlang:element(2, V).
-file("src/viva_math/vector.gleam", 218).
?DOC(" Get Arousal component (y).\n").
-spec arousal(vec3()) -> float().
arousal(V) ->
erlang:element(3, V).
-file("src/viva_math/vector.gleam", 223).
?DOC(" Get Dominance component (z).\n").
-spec dominance(vec3()) -> float().
dominance(V) ->
erlang:element(4, V).