Skip to content

Metrics

policy_arena.metrics

compute_cooperation_rate(model)

Fraction of all actions this round that were COOPERATE.

Source code in src/policy_arena/metrics/cooperation.py
16
17
18
19
20
21
def compute_cooperation_rate(model: mesa.Model) -> float:
    """Fraction of all actions this round that were COOPERATE."""
    actions: list[Action] = getattr(model, "_round_all_actions", [])
    if not actions:
        return 0.0
    return sum(1 for a in actions if a == Action.COOPERATE) / len(actions)

compute_strategy_entropy(model)

Shannon entropy over actions this round (reads model._round_all_actions).

Source code in src/policy_arena/metrics/entropy.py
64
65
66
67
def compute_strategy_entropy(model: mesa.Model) -> float:
    """Shannon entropy over actions this round (reads model._round_all_actions)."""
    actions = getattr(model, "_round_all_actions", [])
    return shannon_entropy(actions)

shannon_entropy(values)

Shannon entropy H = -sum(p * log2(p)) over the distribution of values.

Returns 0.0 for empty sequences or single-value sequences.

Source code in src/policy_arena/metrics/entropy.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def shannon_entropy(values: Sequence[Hashable]) -> float:
    """Shannon entropy H = -sum(p * log2(p)) over the distribution of values.

    Returns 0.0 for empty sequences or single-value sequences.
    """
    if not values:
        return 0.0
    counts = Counter(values)
    total = len(values)
    entropy = 0.0
    for count in counts.values():
        p = count / total
        if p > 0:
            entropy -= p * math.log2(p)
    return entropy

gini_coefficient(values)

Compute the Gini coefficient of a list of values.

Returns a value in [0, 1] where 0 = perfect equality, 1 = maximum inequality. Returns 0.0 for empty or all-zero inputs.

Source code in src/policy_arena/metrics/gini.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def gini_coefficient(values: list[float]) -> float:
    """Compute the Gini coefficient of a list of values.

    Returns a value in [0, 1] where 0 = perfect equality, 1 = maximum inequality.
    Returns 0.0 for empty or all-zero inputs.
    """
    if not values:
        return 0.0
    n = len(values)
    if n == 1:
        return 0.0
    sorted_vals = sorted(values)
    total = sum(sorted_vals)
    if total == 0:
        return 0.0
    cumulative = 0.0
    weighted_sum = 0.0
    for i, v in enumerate(sorted_vals):
        cumulative += v
        weighted_sum += (2 * (i + 1) - n - 1) * v
    return weighted_sum / (n * total)

compute_nash_distance(model)

Fraction of this round's pairwise interactions deviating from NE.

Reads model._round_actions: list of (agent_i_id, agent_j_id, action_i, action_j).

Source code in src/policy_arena/metrics/nash_distance.py
19
20
21
22
23
24
25
26
27
28
29
30
def compute_nash_distance(model: mesa.Model) -> float:
    """Fraction of this round's pairwise interactions deviating from NE.

    Reads `model._round_actions`: list of (agent_i_id, agent_j_id, action_i, action_j).
    """
    interactions: list[tuple] = getattr(model, "_round_interactions", [])
    if not interactions:
        return 0.0

    ne_profile = (Action.DEFECT, Action.DEFECT)
    deviations = sum(1 for _, _, a_i, a_j in interactions if (a_i, a_j) != ne_profile)
    return deviations / len(interactions)

reciprocity_index(history_a, history_b)

Compute reciprocity between two agents' action histories.

Measures how often agents match each other's previous action. Returns a value in [-1, 1]: +1 = perfect reciprocity (always copies opponent's last move) 0 = no correlation -1 = perfect anti-reciprocity (always does opposite)

Only meaningful for histories of length >= 2.

Source code in src/policy_arena/metrics/reciprocity.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def reciprocity_index(
    history_a: list[Action],
    history_b: list[Action],
) -> float:
    """Compute reciprocity between two agents' action histories.

    Measures how often agents match each other's previous action.
    Returns a value in [-1, 1]:
      +1 = perfect reciprocity (always copies opponent's last move)
      0 = no correlation
      -1 = perfect anti-reciprocity (always does opposite)

    Only meaningful for histories of length >= 2.
    """
    if len(history_a) < 2 or len(history_b) < 2:
        return 0.0

    n = min(len(history_a), len(history_b))
    matches = 0
    total = 0

    for t in range(1, n):
        # Did A at time t match B at time t-1?
        a_matches_prev_b = history_a[t] == history_b[t - 1]
        # Did B at time t match A at time t-1?
        b_matches_prev_a = history_b[t] == history_a[t - 1]

        if a_matches_prev_b:
            matches += 1
        else:
            matches -= 1
        if b_matches_prev_a:
            matches += 1
        else:
            matches -= 1
        total += 2

    return matches / total if total > 0 else 0.0

compute_individual_regret(agent, payoff_matrix)

Compute regret for a single agent over its full interaction history.

Args: agent: The agent whose regret to compute. payoff_matrix: Maps (my_action, opponent_action) -> (my_payoff, opponent_payoff).

Returns: Non-negative regret value (0 = played optimally in hindsight).

Source code in src/policy_arena/metrics/regret.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def compute_individual_regret(
    agent: PDAgent,
    payoff_matrix: dict[tuple[Action, Action], tuple[float, float]],
) -> float:
    """Compute regret for a single agent over its full interaction history.

    Args:
        agent: The agent whose regret to compute.
        payoff_matrix: Maps (my_action, opponent_action) -> (my_payoff, opponent_payoff).

    Returns:
        Non-negative regret value (0 = played optimally in hindsight).
    """
    all_opponent_actions: list[Action] = []
    for opp_actions in agent._opponent_history.values():
        all_opponent_actions.extend(opp_actions)

    if not all_opponent_actions:
        return 0.0

    actual_payoff = agent.cumulative_payoff

    best_fixed_payoff = float("-inf")
    for candidate_action in Action:
        hypothetical = sum(
            payoff_matrix[(candidate_action, opp_action)][0]
            for opp_action in all_opponent_actions
        )
        best_fixed_payoff = max(best_fixed_payoff, hypothetical)

    return max(0.0, best_fixed_payoff - actual_payoff)

compute_social_welfare(model)

Total payoffs this round as fraction of theoretical max.

Reads model._round_total_payoff and model._round_max_payoff.

Source code in src/policy_arena/metrics/social_welfare.py
16
17
18
19
20
21
22
23
24
25
def compute_social_welfare(model: mesa.Model) -> float:
    """Total payoffs this round as fraction of theoretical max.

    Reads `model._round_total_payoff` and `model._round_max_payoff`.
    """
    total = getattr(model, "_round_total_payoff", 0.0)
    maximum = getattr(model, "_round_max_payoff", 1.0)
    if maximum == 0:
        return 0.0
    return total / maximum