File size: 3,773 Bytes
8664fba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import random
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from dataclasses import dataclass

GRID_WIDTH = 10
GRID_HEIGHT = 10
AGENT_NR = 5  # Number of points

@dataclass
class Agent:
    cost_measurements: list
    acc_measurements: list

    @property
    def cost_mean(self):
        return np.mean(self.cost_measurements)

    @property
    def acc_mean(self):
        return np.mean(self.acc_measurements)

    @property
    def cost_conf_interval(self):
        if len(self.cost_measurements) > 1:
            return stats.sem(self.cost_measurements) * stats.t.ppf((1 + 0.95) / 2., len(self.cost_measurements)-1)
        else:
            return 0

    @property
    def acc_conf_interval(self):
        if len(self.acc_measurements) > 1:
            return stats.sem(self.acc_measurements) * stats.t.ppf((1 + 0.95) / 2., len(self.acc_measurements)-1)
        else:
            return 0

    def __repr__(self):
        return f"Agent(cost={self.cost_mean:.2f}±{self.cost_conf_interval:.2f}, acc={self.acc_mean:.2f}±{self.acc_conf_interval:.2f})"

def generate_agent() -> Agent:
    cost_mean = random.randint(2, GRID_WIDTH-2)
    acc_mean = random.randint(2, GRID_HEIGHT-2)
    x_samples = [random.gauss(cost_mean, 0.5) for _ in range(10)]  # Gaussian distributed x
    y_samples = [random.gauss(acc_mean, 0.5) for _ in range(10)]  # Gaussian distributed y
    return Agent(x_samples, y_samples)

def draw_grid() -> None:
    plt.axis([-1, GRID_WIDTH + 1, -1, GRID_HEIGHT + 1])
    plt.grid(visible=True, which='major', color='0.75', linestyle='--')
    plt.xticks(range(0, GRID_WIDTH+1, 1))
    plt.yticks(range(0, GRID_HEIGHT+1, 1))
    plt.xlim(0, GRID_WIDTH)
    plt.ylim(0, GRID_HEIGHT)

def draw_frontier(points: list[Agent], upper_hull: list[Agent]) -> None:
    draw_grid()
    for point in points:
        plt.errorbar(point.cost_mean, point.acc_mean, xerr=point.cost_conf_interval, yerr=point.acc_conf_interval, fmt='o', color='blue', ecolor='gray', capsize=0)
    plt.plot([p.cost_mean for p in upper_hull], [p.acc_mean for p in upper_hull], linestyle='-', color='black', label='Pareto Frontier')
    plt.legend(['Pareto Frontier', 'Agents'], loc='upper left')
    plt.xlabel('Cost')
    plt.ylabel('Accuracy')
    plt.show()
    plt.close()

def cross(point_o: Agent, point_a: Agent, point_b: Agent) -> int:
    return (point_a.cost_mean - point_o.cost_mean) * (point_b.acc_mean - point_o.acc_mean) - (point_a.acc_mean - point_o.acc_mean) * (point_b.cost_mean - point_o.cost_mean)

def compute_hull_side(points: list[Agent]) -> list[Agent]:
    hull: list[Agent] = []
    for p in points:
        while len(hull) >= 2 and cross(hull[-2], hull[-1], p) <= 0:
            hull.pop()
        hull.append(p)
    return hull

def is_pareto_efficient(others, candidate):
    for other in others:
        if (other.cost_mean <= candidate.cost_mean and other.acc_mean >= candidate.acc_mean) and \
           (other.cost_mean < candidate.cost_mean or other.acc_mean > candidate.acc_mean):
            return False
    return True

def compute_pareto_frontier(points: list[Agent]) -> list[Agent]:
    points = sorted(list(points), key=lambda p: (p.cost_mean, p.acc_mean))
    if len(points) <= 1:
        return points

    upper_convex_hull = compute_hull_side(list(reversed(points)))
    pareto_frontier = [agent for agent in upper_convex_hull if is_pareto_efficient(upper_convex_hull, agent)]

    print("Agents on Pareto frontier:")
    for agent in pareto_frontier:
        print(agent)

    draw_frontier(points, pareto_frontier)

    return pareto_frontier

# Main script to generate points and compute the Pareto frontier
points = [generate_agent() for _ in range(AGENT_NR)]
pareto_frontier = compute_pareto_frontier(points)