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@entropy.erl
Raw

src/viva_math@entropy.erl

-module(viva_math@entropy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_math/entropy.gleam").
-export([shannon/1, shannon_normalized/1, kl_divergence/2, kl_divergence_smoothed/3, symmetric_kl/2, jensen_shannon/2, cross_entropy/2, binary_cross_entropy/2, mutual_information/3, conditional_entropy/2, relative_entropy_rate/2, hybrid_shannon/3, kl_divergence_with_sensitivity/3, renyi/2, tsallis/2, fisher_information_gaussian/1, differential_entropy_gaussian/1]).
-export_type([kl_sensitivity/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(
" Entropy and information theory functions.\n"
"\n"
" Based on Shannon (1948) and Kullback-Leibler (1951).\n"
" Used for memory consolidation scoring and uncertainty quantification.\n"
"\n"
" References:\n"
" - Shannon (1948) \"A Mathematical Theory of Communication\"\n"
" - Cover & Thomas (2006) \"Elements of Information Theory\"\n"
).
-type kl_sensitivity() :: standard |
{arousal_weighted, float()} |
{custom_gamma, float()}.
-file("src/viva_math/entropy.gleam", 27).
?DOC(
" Shannon entropy: H(X) = -Σ p(x) log₂ p(x)\n"
"\n"
" Measures uncertainty/information content of a distribution.\n"
" Higher entropy = more uncertainty.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" shannon([0.5, 0.5]) // -> 1.0 (maximum for 2 outcomes)\n"
" shannon([1.0, 0.0]) // -> 0.0 (no uncertainty)\n"
" shannon([0.25, 0.25, 0.25, 0.25]) // -> 2.0\n"
" ```\n"
).
-spec shannon(list(float())) -> float().
shannon(Probabilities) ->
Terms = gleam@list:fold(Probabilities, [], fun(Acc, P) -> case P =< +0.0 of
true ->
Acc;
false ->
case gleam_community@maths:logarithm_2(P) of
{ok, Log_p} ->
[+0.0 - (P * Log_p) | Acc];
{error, _} ->
Acc
end
end end),
viva_math@precision:neumaier_sum(Terms).
-file("src/viva_math/entropy.gleam", 274).
-spec int_to_float(integer()) -> float().
int_to_float(N) ->
case N of
0 ->
+0.0;
1 ->
1.0;
2 ->
2.0;
_ ->
Half = N div 2,
Remainder = N - (Half * 2),
(int_to_float(Half) * 2.0) + int_to_float(Remainder)
end.
-file("src/viva_math/entropy.gleam", 47).
?DOC(
" Normalized Shannon entropy (0 to 1 range).\n"
"\n"
" Divides by log₂(n) where n is number of outcomes.\n"
).
-spec shannon_normalized(list(float())) -> float().
shannon_normalized(Probabilities) ->
N = erlang:length(Probabilities),
case N =< 1 of
true ->
+0.0;
false ->
H = shannon(Probabilities),
case gleam_community@maths:logarithm_2(int_to_float(N)) of
{ok, Max_h} ->
case Max_h =:= +0.0 of
true ->
+0.0;
false ->
case Max_h of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> H / Gleam@denominator
end
end;
{error, _} ->
+0.0
end
end.
-file("src/viva_math/entropy.gleam", 71).
?DOC(
" KL Divergence: D_KL(P || Q) = Σ p(x) log(p(x) / q(x))\n"
"\n"
" Measures how much P diverges from Q (not symmetric!).\n"
" P is the \"true\" distribution, Q is the approximation.\n"
"\n"
" Returns Error if distributions have different lengths or Q has zeros where P is non-zero.\n"
).
-spec kl_divergence(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
kl_divergence(P, Q) ->
case erlang:length(P) =:= erlang:length(Q) of
false ->
{error, nil};
true ->
Pairs = gleam@list:zip(P, Q),
Result = gleam@list:fold(
Pairs,
{ok, +0.0},
fun(Acc, Pair) -> case Acc of
{error, nil} ->
{error, nil};
{ok, Sum} ->
{Pi, Qi} = Pair,
case Pi =< +0.0 of
true ->
{ok, Sum};
false ->
case Qi =< +0.0 of
true ->
{error, nil};
false ->
case gleam_community@maths:natural_logarithm(
case Qi of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Pi / Gleam@denominator
end
) of
{ok, Log_ratio} ->
{ok, Sum + (Pi * Log_ratio)};
{error, _} ->
{error, nil}
end
end
end
end end
),
Result
end.
-file("src/viva_math/entropy.gleam", 111).
?DOC(
" KL divergence with additive smoothing.\n"
"\n"
" Adds `eps` to every q_i before normalising; useful when q contains zeros\n"
" where p > 0 (which would otherwise return `Error`). Standard practice in\n"
" language modelling and probabilistic ML.\n"
"\n"
" Recommended `eps ∈ [1e-12, 1e-6]`. Larger values bias the result more.\n"
).
-spec kl_divergence_smoothed(list(float()), list(float()), float()) -> {ok,
float()} |
{error, nil}.
kl_divergence_smoothed(P, Q, Eps) ->
case {erlang:length(P) =:= erlang:length(Q), Eps < +0.0} of
{false, _} ->
{error, nil};
{_, true} ->
{error, nil};
{true, false} ->
Smoothed_unnorm = gleam@list:map(Q, fun(Qi) -> Qi + Eps end),
Total = gleam@list:fold(
Smoothed_unnorm,
+0.0,
fun(Acc, X) -> Acc + X end
),
case Total =< +0.0 of
true ->
{error, nil};
false ->
Q_norm = gleam@list:map(
Smoothed_unnorm,
fun(X@1) -> case Total of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> X@1 / Gleam@denominator
end end
),
kl_divergence(P, Q_norm)
end
end.
-file("src/viva_math/entropy.gleam", 137).
?DOC(
" Symmetric KL Divergence (Jensen-Shannon divergence without the 1/2).\n"
"\n"
" D_sym(P, Q) = D_KL(P || Q) + D_KL(Q || P)\n"
).
-spec symmetric_kl(list(float()), list(float())) -> {ok, float()} | {error, nil}.
symmetric_kl(P, Q) ->
case {kl_divergence(P, Q), kl_divergence(Q, P)} of
{{ok, D1}, {ok, D2}} ->
{ok, D1 + D2};
{_, _} ->
{error, nil}
end.
-file("src/viva_math/entropy.gleam", 148).
?DOC(
" Jensen-Shannon Divergence: JS(P, Q) = (D_KL(P || M) + D_KL(Q || M)) / 2\n"
" where M = (P + Q) / 2\n"
"\n"
" This is symmetric and bounded [0, 1] when using log₂.\n"
).
-spec jensen_shannon(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
jensen_shannon(P, Q) ->
case erlang:length(P) =:= erlang:length(Q) of
false ->
{error, nil};
true ->
M = begin
_pipe = gleam@list:zip(P, Q),
gleam@list:map(
_pipe,
fun(Pair) ->
{Pi, Qi} = Pair,
(Pi + Qi) / 2.0
end
)
end,
case {kl_divergence(P, M), kl_divergence(Q, M)} of
{{ok, D_pm}, {ok, D_qm}} ->
{ok, (D_pm + D_qm) / 2.0};
{_, _} ->
{error, nil}
end
end.
-file("src/viva_math/entropy.gleam", 172).
?DOC(
" Cross-entropy: H(P, Q) = -Σ p(x) log q(x)\n"
"\n"
" Used in machine learning loss functions.\n"
" H(P, Q) = H(P) + D_KL(P || Q)\n"
).
-spec cross_entropy(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
cross_entropy(P, Q) ->
case erlang:length(P) =:= erlang:length(Q) of
false ->
{error, nil};
true ->
Pairs = gleam@list:zip(P, Q),
Result = gleam@list:fold(
Pairs,
{ok, +0.0},
fun(Acc, Pair) -> case Acc of
{error, nil} ->
{error, nil};
{ok, Sum} ->
{Pi, Qi} = Pair,
case Pi =< +0.0 of
true ->
{ok, Sum};
false ->
case Qi =< +0.0 of
true ->
{error, nil};
false ->
case gleam_community@maths:natural_logarithm(
Qi
) of
{ok, Log_q} ->
{ok, Sum - (Pi * Log_q)};
{error, _} ->
{error, nil}
end
end
end
end end
),
Result
end.
-file("src/viva_math/entropy.gleam", 206).
?DOC(
" Binary cross-entropy for single probability.\n"
"\n"
" H(p, q) = -[p log(q) + (1-p) log(1-q)]\n"
).
-spec binary_cross_entropy(float(), float()) -> {ok, float()} | {error, nil}.
binary_cross_entropy(P, Q) ->
Q_clamped = gleam@float:max(gleam@float:min(Q, 0.999999), 0.000001),
Q_inv = 1.0 - Q_clamped,
P_inv = 1.0 - P,
case {gleam_community@maths:natural_logarithm(Q_clamped),
gleam_community@maths:natural_logarithm(Q_inv)} of
{{ok, Log_q}, {ok, Log_q_inv}} ->
Result = (P * Log_q) + (P_inv * Log_q_inv),
{ok, +0.0 - Result};
{_, _} ->
{error, nil}
end.
-file("src/viva_math/entropy.gleam", 225).
?DOC(
" Mutual Information: I(X; Y) = H(X) + H(Y) - H(X, Y)\n"
"\n"
" Measures shared information between two variables.\n"
" Takes marginal distributions and joint distribution as input.\n"
).
-spec mutual_information(list(float()), list(float()), list(list(float()))) -> float().
mutual_information(Px, Py, Pxy) ->
Hx = shannon(Px),
Hy = shannon(Py),
Pxy_flat = lists:append(Pxy),
Hxy = shannon(Pxy_flat),
(Hx + Hy) - Hxy.
-file("src/viva_math/entropy.gleam", 243).
?DOC(
" Conditional entropy: H(X|Y) = H(X, Y) - H(Y)\n"
"\n"
" Uncertainty in X given knowledge of Y.\n"
).
-spec conditional_entropy(list(float()), list(list(float()))) -> float().
conditional_entropy(Px, Pxy) ->
Hx = shannon(Px),
Pxy_flat = lists:append(Pxy),
Hxy = shannon(Pxy_flat),
Hxy - Hx.
-file("src/viva_math/entropy.gleam", 253).
?DOC(
" Relative entropy rate for sequences.\n"
"\n"
" Used for measuring \"surprise\" in temporal data.\n"
).
-spec relative_entropy_rate(list(float()), list(float())) -> {ok, float()} |
{error, nil}.
relative_entropy_rate(Observed, Expected) ->
case erlang:length(Observed) =:= erlang:length(Expected) of
false ->
{error, nil};
true ->
N = erlang:length(Observed),
case N =:= 0 of
true ->
{ok, +0.0};
false ->
case kl_divergence(Observed, Expected) of
{ok, Kl} ->
{ok, case int_to_float(N) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Kl / Gleam@denominator
end};
{error, nil} ->
{error, nil}
end
end
end.
-file("src/viva_math/entropy.gleam", 315).
?DOC(" Clamp value to [0, 1]\n").
-spec clamp_unit(float()) -> float().
clamp_unit(Value) ->
case Value < +0.0 of
true ->
+0.0;
false ->
case Value > 1.0 of
true ->
1.0;
false ->
Value
end
end.
-file("src/viva_math/entropy.gleam", 303).
?DOC(
" Hybrid entropy for mixed emotional states.\n"
"\n"
" H_hybrid(X) = α × H(X₁) + (1 - α) × H(X₂)\n"
"\n"
" Proposed by DeepSeek R1 for modeling hybrid emotions.\n"
" α ∈ [0, 1] controls the blend between two emotional distributions.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" hybrid_shannon([0.5, 0.5], [0.7, 0.3], 0.5) // Blend of two emotions\n"
" ```\n"
).
-spec hybrid_shannon(list(float()), list(float()), float()) -> float().
hybrid_shannon(Probs1, Probs2, Alpha) ->
H1 = shannon(Probs1),
H2 = shannon(Probs2),
Clamped_alpha = clamp_unit(Alpha),
(Clamped_alpha * H1) + ((1.0 - Clamped_alpha) * H2).
-file("src/viva_math/entropy.gleam", 389).
-spec weighted_mean_helper(list(float()), integer(), float(), float()) -> float().
weighted_mean_helper(Probs, Index, Sum, Weight_sum) ->
case Probs of
[] ->
case Weight_sum =:= +0.0 of
true ->
+0.0;
false ->
case Weight_sum of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Sum / Gleam@denominator
end
end;
[P | Rest] ->
Idx_float = int_to_float(Index),
weighted_mean_helper(
Rest,
Index + 1,
Sum + (P * Idx_float),
Weight_sum + P
)
end.
-file("src/viva_math/entropy.gleam", 385).
?DOC(
" Calculate weighted mean of a distribution.\n"
" Assumes indices 0, 1, 2, ... as values.\n"
).
-spec weighted_mean(list(float())) -> float().
weighted_mean(Probs) ->
weighted_mean_helper(Probs, 0, +0.0, +0.0).
-file("src/viva_math/entropy.gleam", 376).
?DOC(" Calculate squared difference of distribution means.\n").
-spec mean_difference_squared(list(float()), list(float())) -> float().
mean_difference_squared(P, Q) ->
Mean_p = weighted_mean(P),
Mean_q = weighted_mean(Q),
Diff = Mean_p - Mean_q,
Diff * Diff.
-file("src/viva_math/entropy.gleam", 344).
?DOC(
" KL divergence with sensitivity parameter.\n"
"\n"
" D_KL^γ(P || Q) = γ × (μ₁ - μ₂)² + D_KL(P || Q)\n"
"\n"
" Proposed by DeepSeek R1 for arousal-modulated divergence.\n"
" Higher γ = more sensitive to mean differences.\n"
).
-spec kl_divergence_with_sensitivity(
list(float()),
list(float()),
kl_sensitivity()
) -> {ok, float()} | {error, nil}.
kl_divergence_with_sensitivity(P, Q, Sensitivity) ->
Gamma = case Sensitivity of
standard ->
+0.0;
{arousal_weighted, Arousal} ->
Abs_arousal = case Arousal >= +0.0 of
true ->
Arousal;
false ->
+0.0 - Arousal
end,
Abs_arousal;
{custom_gamma, G} ->
G
end,
case kl_divergence(P, Q) of
{error, nil} ->
{error, nil};
{ok, Standard_kl} ->
Mean_diff_sq = mean_difference_squared(P, Q),
{ok, (Gamma * Mean_diff_sq) + Standard_kl}
end.
-file("src/viva_math/entropy.gleam", 448).
?DOC(" Power function using exp(α × ln(x))\n").
-spec power(float(), float()) -> float().
power(Base, Exponent) ->
case Base =< +0.0 of
true ->
+0.0;
false ->
case gleam_community@maths:natural_logarithm(Base) of
{ok, Ln_base} ->
gleam_community@maths:exponential(Exponent * Ln_base);
{error, _} ->
+0.0
end
end.
-file("src/viva_math/entropy.gleam", 421).
?DOC(
" Renyi entropy of order α.\n"
"\n"
" H_α(X) = (1/(1-α)) × log(Σ p(x)^α)\n"
"\n"
" Generalizes Shannon entropy (α → 1 gives Shannon).\n"
" α = 0: Hartley entropy (log of support size)\n"
" α = 2: Collision entropy\n"
" α → ∞: Min-entropy\n"
).
-spec renyi(list(float()), float()) -> {ok, float()} | {error, nil}.
renyi(Probabilities, Alpha) ->
case Alpha =:= 1.0 of
true ->
{ok, shannon(Probabilities)};
false ->
Sum_p_alpha = gleam@list:fold(
Probabilities,
+0.0,
fun(Acc, P) -> case P =< +0.0 of
true ->
Acc;
false ->
Acc + power(P, Alpha)
end end
),
case Sum_p_alpha =< +0.0 of
true ->
{error, nil};
false ->
case gleam_community@maths:logarithm_2(Sum_p_alpha) of
{ok, Log_sum} ->
{ok, case (1.0 - Alpha) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Log_sum / Gleam@denominator
end};
{error, _} ->
{error, nil}
end
end
end.
-file("src/viva_math/entropy.gleam", 469).
?DOC(
" Tsallis entropy S_q(X) = (1 - Σ pᵢ^q) / (q - 1).\n"
"\n"
" Generalises Shannon (q → 1) and Rényi. q < 1 emphasises rare events,\n"
" q > 1 emphasises modes. Used in non-extensive statistical mechanics and\n"
" to model heavy-tailed emotional distributions.\n"
).
-spec tsallis(list(float()), float()) -> {ok, float()} | {error, nil}.
tsallis(Probabilities, Q) ->
Q_close_to_1 = case Q - 1.0 of
D when D < +0.0 ->
(+0.0 - D) < 1.0e-9;
D@1 ->
D@1 < 1.0e-9
end,
case Q_close_to_1 of
true ->
{ok, shannon(Probabilities)};
false ->
Sum_p_q = gleam@list:fold(
Probabilities,
+0.0,
fun(Acc, P) -> case P =< +0.0 of
true ->
Acc;
false ->
Acc + power(P, Q)
end end
),
{ok, case (Q - 1.0) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> (1.0 - Sum_p_q) / Gleam@denominator
end}
end.
-file("src/viva_math/entropy.gleam", 494).
?DOC(
" Fisher information for a Gaussian distribution: I(σ) = 1 / σ².\n"
"\n"
" Measures how much information an observation carries about the mean.\n"
).
-spec fisher_information_gaussian(float()) -> {ok, float()} | {error, nil}.
fisher_information_gaussian(Sigma) ->
case Sigma =< +0.0 of
true ->
{error, nil};
false ->
{ok, case (Sigma * Sigma) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end}
end.
-file("src/viva_math/entropy.gleam", 502).
?DOC(" Differential entropy of a Gaussian: h(N(μ, σ²)) = ½ ln(2πeσ²).\n").
-spec differential_entropy_gaussian(float()) -> {ok, float()} | {error, nil}.
differential_entropy_gaussian(Sigma) ->
case Sigma =< +0.0 of
true ->
{error, nil};
false ->
Two_pi_e = 17.07946844534713,
case gleam_community@maths:natural_logarithm(
(Two_pi_e * Sigma) * Sigma
) of
{ok, Ln_val} ->
{ok, 0.5 * Ln_val};
{error, _} ->
{error, nil}
end
end.