Packages

Elixir implementation of the GEPA (Genetic-Pareto) optimizer that combines LLM-powered reflection with Pareto search to evolve text-based system components.

Current section

Files

Jump to
gepa_ex lib gepa utils.ex
Raw

lib/gepa/utils.ex

defmodule GEPA.Utils do
@moduledoc """
Utility functions for GEPA optimization.
Includes functions for:
- Pareto front analysis
- Program domination detection
- Merge proposer utilities
"""
@doc """
Checks if a program is dominated by other programs across all Pareto fronts.
A program is dominated if, on every validation front it appears on,
there exists at least one program from the `dominating_programs` set.
## Parameters
- `program` - Program ID to check
- `dominating_programs` - Set of potentially dominating program IDs
- `pareto_front` - Map of val_id => MapSet of program IDs on Pareto front
## Returns
- `true` if program is dominated on all its fronts
- `false` if program is alone on at least one front
## Examples
pareto_front = %{
0 => MapSet.new([0, 1]),
1 => MapSet.new([0, 2])
}
# Program 0 appears on fronts 0 and 1
# If programs 1 and 2 are considered dominating:
# - On front 0: program 1 exists (dominated)
# - On front 1: program 2 exists (dominated)
# Result: program 0 is dominated
GEPA.Utils.is_dominated?(0, MapSet.new([1, 2]), pareto_front)
# => true
"""
@spec is_dominated?(
program :: non_neg_integer(),
dominating_programs :: MapSet.t(non_neg_integer()),
pareto_front :: %{term() => MapSet.t(non_neg_integer())}
) :: boolean()
# Public API kept for compatibility with the original port and tests.
# credo:disable-for-next-line Credo.Check.Readability.PredicateFunctionNames
def is_dominated?(program, dominating_programs, pareto_front) do
# Find all fronts this program appears on
program_fronts =
pareto_front
|> Enum.reduce([], fn {val_id, front}, acc ->
if is_struct(front, MapSet) and MapSet.member?(front, program) do
[{val_id, front} | acc]
else
acc
end
end)
|> Enum.reverse()
# If program doesn't appear on any front, consider it dominated
if Enum.empty?(program_fronts) do
true
else
# Check if on EVERY front this program appears, there's a dominating program
Enum.all?(program_fronts, fn {_val_id, front} ->
# Does this front contain at least one dominating program?
MapSet.intersection(front, dominating_programs)
|> MapSet.size() > 0
end)
end
end
@doc """
Removes dominated programs from Pareto fronts.
Iteratively removes programs that are dominated across all fronts they appear on.
A program is dominated if better programs exist on every front.
## Parameters
- `pareto_front` - Map of val_id => MapSet of program indices
- `scores` - Map of program_idx => score (higher is better)
## Returns
- New Pareto front map with dominated programs removed
## Algorithm
1. Find all programs on any front
2. Sort by score (ascending)
3. Iteratively check if each can be dominated by higher-scoring programs
4. Remove dominated programs from all fronts
"""
@spec remove_dominated_programs(
pareto_front :: %{term() => MapSet.t(non_neg_integer())},
scores :: %{non_neg_integer() => float()}
) :: %{term() => MapSet.t(non_neg_integer())}
def remove_dominated_programs(pareto_front, _scores) when pareto_front == %{} do
%{}
end
def remove_dominated_programs(pareto_front, scores) do
# Count frequency of each program across all fronts
all_programs =
pareto_front
|> Map.values()
|> Enum.reduce(MapSet.new(), fn front, acc ->
if is_struct(front, MapSet) do
MapSet.union(acc, front)
else
acc
end
end)
|> MapSet.to_list()
# Sort programs by score (ascending) - check weakest first
sorted_programs = Enum.sort_by(all_programs, &Map.get(scores, &1, 0))
# Iteratively find dominated programs
dominated = find_dominated_programs(sorted_programs, pareto_front, scores, [])
dominated_set = MapSet.new(dominated)
new_pareto_front =
pareto_front
|> Enum.map(fn {val_id, front} ->
new_front =
if is_struct(front, MapSet) do
MapSet.difference(front, dominated_set)
else
front
end
{val_id, new_front}
end)
|> Enum.into(%{})
new_pareto_front
end
# Iteratively find dominated programs
defp find_dominated_programs([], _pareto_front, _scores, dominated), do: dominated
defp find_dominated_programs([program | rest], pareto_front, scores, dominated) do
# Skip if already dominated
if Enum.member?(dominated, program) do
find_dominated_programs(rest, pareto_front, scores, dominated)
else
# Only consider programs with STRICTLY HIGHER scores as potential dominators
program_score = Map.get(scores, program, 0)
potential_dominators =
rest
|> Enum.filter(fn p ->
not Enum.member?(dominated, p) and Map.get(scores, p, 0) > program_score
end)
|> MapSet.new()
# A program is dominated only if better programs exist on ALL its fronts
if MapSet.size(potential_dominators) > 0 and
is_dominated?(program, potential_dominators, pareto_front) do
# This program is dominated
find_dominated_programs(rest, pareto_front, scores, [program | dominated])
else
# This program is NOT dominated - it's a dominator
find_dominated_programs(rest, pareto_front, scores, dominated)
end
end
end
@doc """
Finds programs that dominate others on the Pareto front.
This is the main entry point used by the merge proposer.
## Parameters
- `pareto_front_programs` - Map of val_id => set of program indices
- `program_scores` - Map of program_idx => aggregate score
## Returns
- List of program indices that are dominators (not dominated by others)
## Examples
pareto_front = %{
0 => MapSet.new([0, 1, 2]),
1 => MapSet.new([1, 2])
}
scores = %{0 => 0.5, 1 => 0.7, 2 => 0.9}
GEPA.Utils.find_dominator_programs(pareto_front, scores)
# => [2, 1] (sorted by score, descending)
"""
@spec find_dominator_programs(
pareto_front_programs :: %{term() => MapSet.t(non_neg_integer())},
program_scores :: %{non_neg_integer() => float()}
) :: [non_neg_integer()]
def find_dominator_programs(pareto_front_programs, _program_scores)
when pareto_front_programs == %{} do
[]
end
def find_dominator_programs(pareto_front_programs, program_scores) do
# Remove dominated programs from fronts
cleaned_fronts = remove_dominated_programs(pareto_front_programs, program_scores)
# Collect all unique programs from cleaned fronts
dominators =
cleaned_fronts
|> Map.values()
|> Enum.reduce(MapSet.new(), fn front, acc ->
if is_struct(front, MapSet) do
MapSet.union(acc, front)
else
acc
end
end)
|> MapSet.to_list()
# Return sorted by score (descending)
Enum.sort_by(dominators, fn p -> -Map.get(program_scores, p, 0) end)
end
end