Current section

Files

Jump to
macula_tweann src selection_utils.erl
Raw

src/selection_utils.erl

%% @doc Selection utilities for evolutionary algorithms.
%%
%% Provides selection mechanisms used throughout the evolution process:
%% - Roulette wheel selection (fitness-proportionate)
%% - Random uniform selection
%% - Weighted selection
%%
%% @author Macula.io
%% @copyright 2025 Macula.io, Apache-2.0
-module(selection_utils).
%% Suppress supertype warnings - specs are intentionally general for API flexibility
-dialyzer({nowarn_function, [random_select/1]}).
-export([
roulette_wheel/1,
random_select/1,
weighted_select/2
]).
%% @doc Roulette wheel selection based on weights.
%%
%% Selects an element with probability proportional to its weight.
%% Higher weight = higher probability of selection.
%%
%% Algorithm:
%% 1. Calculate total weight
%% 2. Generate random value in [0, total)
%% 3. Accumulate weights until random value exceeded
%%
%% @param WeightedItems list of {Item, Weight} tuples
%% @returns selected Item
-spec roulette_wheel([{term(), number()}]) -> term().
roulette_wheel([]) ->
error({selection_failed, empty_list});
roulette_wheel(WeightedItems) ->
TotalWeight = lists:sum([W || {_, W} <- WeightedItems]),
case TotalWeight of
0 ->
%% All zero weights - fall back to random selection
{Item, _} = lists:nth(rand:uniform(length(WeightedItems)), WeightedItems),
Item;
_ ->
RandomValue = rand:uniform() * TotalWeight,
select_by_accumulated_weight(WeightedItems, RandomValue, 0)
end.
%% @private Accumulate weights until we exceed target
-spec select_by_accumulated_weight([{term(), number()}], number(), number()) -> term().
select_by_accumulated_weight([{Item, Weight} | Rest], Target, Acc) ->
NewAcc = Acc + Weight,
case NewAcc >= Target of
true -> Item;
false -> select_by_accumulated_weight(Rest, Target, NewAcc)
end;
select_by_accumulated_weight([], _, _) ->
error({selection_failed, accumulation_error}).
%% @doc Uniformly select a random element from a list.
%%
%% Each element has equal probability of selection.
%%
%% @param Items list of items to select from
%% @returns randomly selected Item
-spec random_select([term()]) -> term().
random_select([]) ->
error({selection_failed, empty_list});
random_select(Items) ->
Index = rand:uniform(length(Items)),
lists:nth(Index, Items).
%% @doc Select element with custom weight function.
%%
%% Applies a weight function to each element, then performs
%% roulette wheel selection.
%%
%% @param Items list of items
%% @param WeightFun function that returns weight for an item
%% @returns selected Item
-spec weighted_select([term()], fun((term()) -> number())) -> term().
weighted_select([], _WeightFun) ->
error({selection_failed, empty_list});
weighted_select(Items, WeightFun) ->
WeightedItems = [{Item, WeightFun(Item)} || Item <- Items],
roulette_wheel(WeightedItems).