File size: 2,767 Bytes
b89c27d
 
 
 
 
 
 
 
 
 
 
 
 
 
275f68e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b89c27d
275f68e
 
 
 
 
 
 
 
b89c27d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275f68e
b89c27d
275f68e
b89c27d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import Plot from 'react-plotly.js'

const PLOTLY_BASE = {
  font: { family: 'Inter', color: '#f3f0e8', size: 12 },
  paper_bgcolor: '#2a2824',
  plot_bgcolor: '#1f1d1a',
  hoverlabel: { bgcolor: '#f3f0e8', font: { color: '#1f1d1a' } },
}
const AXIS = {
  gridcolor: '#403b34', zerolinecolor: '#554e45',
  showline: true, linecolor: '#554e45',
  tickfont: { color: '#b5ada0' },
}

// Per-component weights from rewards.py. Breakdown bars show weighted
// contributions (sum → total reward) rather than raw component values, so
// the chart is directly readable as "which components are hurting / helping".
const WEIGHTS = {
  r_regret:      1.0,
  r_convergence: 0.3,
  r_robustness:  0.3,
  r_novelty:     0.1,
  r_budget:     -0.05,   // subtractive
  r_eval_failures: -0.5, // subtractive
}

const DISPLAY_NAMES = {
  r_regret:        'regret (×1.0)',
  r_convergence:   'convergence (×0.3)',
  r_robustness:    'robustness (×0.3)',
  r_novelty:       'novelty (×0.1)',
  r_budget:        'budget (−0.05×)',
  r_eval_failures: 'eval crashes (−0.5×)',
}

export function RewardBreakdown({ breakdown, total }) {
  // Weighted contributions — sum to the terminal reward.
  const entries = Object.entries(WEIGHTS).map(([k, w]) => ({
    key: k,
    label: DISPLAY_NAMES[k],
    value: (breakdown[k] ?? 0) * w,
  }))
  const names = entries.map(e => e.label)
  const vs    = entries.map(e => e.value)
  const colors = vs.map(v => v >= 0 ? '#3d6b4c' : '#a0483a')
  const labels = vs.map(v => (v >= 0 ? '+' : '') + v.toFixed(3))

  return (
    <Plot
      data={[{
        type: 'bar', orientation: 'h',
        y: names, x: vs,
        marker: { color: colors, line: { color: '#1f1d1a', width: 1 } },
        text: labels, textposition: 'outside', cliponaxis: false,
        textfont: { color: '#f3f0e8', size: 11 },
        hovertemplate: '%{y}<br>contribution=%{x:+.3f}<extra></extra>',
      }]}
      layout={{
        ...PLOTLY_BASE,
        title: {
          text: `Reward breakdown  ·  total = ${(total >= 0 ? '+' : '') + total.toFixed(3)}`,
          x: 0.02, xanchor: 'left',
          font: { size: 14, color: '#f3f0e8' },
        },
        height: 260, margin: { l: 160, r: 50, t: 50, b: 30 },
        xaxis: {
          title: 'contribution to total reward',
          range: [Math.min(...vs, 0) - 0.15, Math.max(...vs, 0) + 0.15],
          ...AXIS,
        },
        yaxis: { autorange: 'reversed', ...AXIS },
        showlegend: false, bargap: 0.25,
        shapes: [{
          type: 'line', x0: 0, x1: 0, y0: -0.5, y1: names.length - 0.5,
          line: { color: '#554e45', width: 1 },
        }],
      }}
      config={{ displayModeBar: false, responsive: true }}
      style={{ width: '100%' }}
      useResizeHandler
    />
  )
}