返回 DeepSeek-Reasonix
pareto.go
根目录 / cmd / e2ebench / pareto.go
1 package main
2
3 import (
4 "fmt"
5 "path/filepath"
6 "strings"
7 )
8
9 // paretoPoint is one arm on the accuracy-vs-TTCS plane. The product question
10 // is Pareto position, not averages: an arm beaten on both axes at once by the
11 // same competitor is the unambiguous alarm.
12 type paretoPoint struct {
13 label string
14 acc float64 // solved %
15 ttcsMs int64 // median time to correct solution
16 solved, ran int
17 dominatedBy string
18 }
19
20 func newParetoPoint(path string, s armStats) paretoPoint {
21 return paretoPoint{
22 label: strings.TrimSuffix(filepath.Base(path), ".json"),
23 acc: solveRate(s.Solved, s.Ran),
24 ttcsMs: median(s.TTCS),
25 solved: s.Solved,
26 ran: s.Ran,
27 }
28 }
29
30 // markDominated flags each point beaten on both axes by another (strictly on
31 // at least one). Points without a solve have no TTCS and cannot dominate.
32 func markDominated(points []paretoPoint) {
33 for i := range points {
34 for j := range points {
35 if i == j || points[j].solved == 0 || points[i].solved == 0 {
36 continue
37 }
38 betterAcc := points[j].acc >= points[i].acc
39 betterTime := points[j].ttcsMs <= points[i].ttcsMs
40 strict := points[j].acc > points[i].acc || points[j].ttcsMs < points[i].ttcsMs
41 if betterAcc && betterTime && strict {
42 points[i].dominatedBy = points[j].label
43 break
44 }
45 }
46 }
47 }
48
49 func paretoSection(points []paretoPoint) string {
50 if len(points) < 2 {
51 return ""
52 }
53 markDominated(points)
54 var b strings.Builder
55 b.WriteString("### Pareto: accuracy vs TTCS\n\n")
56 b.WriteString("```\n" + paretoChart(points) + "```\n\n")
57 for _, p := range points {
58 switch {
59 case p.solved == 0:
60 fmt.Fprintf(&b, "- `%s`: no solves — off the chart\n", p.label)
61 case p.dominatedBy != "":
62 fmt.Fprintf(&b, "- ⚠️ `%s` is **dominated** by `%s`: at least as accurate and no slower — the unambiguous alarm\n", p.label, p.dominatedBy)
63 default:
64 fmt.Fprintf(&b, "- ✅ `%s` is on the Pareto frontier (%s solved, TTCS median %s)\n", p.label, pct(p.solved, p.ran), dur(p.ttcsMs))
65 }
66 }
67 b.WriteString("\n")
68 return b.String()
69 }
70
71 const (
72 paretoRows = 9
73 paretoCols = 46
74 )
75
76 // paretoChart renders the accuracy/TTCS scatter as fixed-width ASCII, letters
77 // keyed to a legend line. Dominated arms render as ✗ at their position.
78 func paretoChart(points []paretoPoint) string {
79 charted := make([]paretoPoint, 0, len(points))
80 for _, p := range points {
81 if p.solved > 0 {
82 charted = append(charted, p)
83 }
84 }
85 if len(charted) == 0 {
86 return "(no solved runs to chart)\n"
87 }
88 xmin, xmax, ymin := paretoBounds(charted)
89 grid := make([][]rune, paretoRows)
90 for r := range grid {
91 grid[r] = []rune(strings.Repeat(" ", paretoCols))
92 }
93 legend := make([]string, 0, len(charted))
94 for i, p := range charted {
95 col := 0
96 if xmax > xmin {
97 col = int(float64(p.ttcsMs-xmin) / float64(xmax-xmin) * float64(paretoCols-1))
98 }
99 row := int((100 - p.acc) / (100 - ymin) * float64(paretoRows-1))
100 marker := rune('A' + i)
101 if p.dominatedBy != "" {
102 marker = '✗'
103 }
104 grid[clampInt(row, 0, paretoRows-1)][clampInt(col, 0, paretoCols-1)] = marker
105 legend = append(legend, fmt.Sprintf("%c=%s", 'A'+i, p.label))
106 }
107 var b strings.Builder
108 b.WriteString("Accuracy\n")
109 for r, line := range grid {
110 switch r {
111 case 0:
112 fmt.Fprintf(&b, "%5s |%s\n", "100%", string(line))
113 case paretoRows - 1:
114 fmt.Fprintf(&b, "%5s |%s\n", fmt.Sprintf("%.0f%%", ymin), string(line))
115 default:
116 fmt.Fprintf(&b, " |%s\n", string(line))
117 }
118 }
119 fmt.Fprintf(&b, " +%s→ TTCS\n", strings.Repeat("-", paretoCols))
120 fmt.Fprintf(&b, " %-*s%s\n", paretoCols-len(dur(xmax)), dur(xmin), dur(xmax))
121 fmt.Fprintf(&b, " %s\n", strings.Join(legend, " "))
122 return b.String()
123 }
124
125 // paretoBounds pads the time axis and floors the accuracy axis one decade
126 // under the worst arm so points sit inside the frame, not on its edges.
127 func paretoBounds(points []paretoPoint) (xmin, xmax int64, ymin float64) {
128 xmin, xmax, ymin = points[0].ttcsMs, points[0].ttcsMs, points[0].acc
129 for _, p := range points[1:] {
130 xmin = min(xmin, p.ttcsMs)
131 xmax = max(xmax, p.ttcsMs)
132 ymin = min(ymin, p.acc)
133 }
134 pad := max((xmax-xmin)/10, 500)
135 xmin = max(xmin-pad, 0)
136 xmax += pad
137 ymin = max(float64(int(ymin/10)*10-10), 0)
138 if ymin >= 100 {
139 ymin = 90
140 }
141 return xmin, xmax, ymin
142 }
143
144 func clampInt(v, lo, hi int) int {
145 return min(max(v, lo), hi)
146 }
147
147 lines GO