Skip to main content

cbtop/brick/
score.rs

1//! ComputeBrick Scoring Framework (section 29 of compute-block-tui-cbtop.md)
2
3use std::fmt;
4
5use super::widget::Color;
6use super::Brick;
7
8// ============================================================================
9// ComputeBrick Scoring Framework (§29 of compute-block-tui-cbtop.md)
10// ============================================================================
11
12/// ComputeBrick quality score (0-100)
13///
14/// Scoring categories per §29.1:
15/// - Performance: 40 pts (GFLOP/s throughput)
16/// - Efficiency: 25 pts (backend utilization)
17/// - Correctness: 20 pts (assertions, numerical accuracy)
18/// - Stability: 15 pts (CV < 5%)
19///
20/// Reference: [Hennessy & Patterson, 2017] "Computer Architecture: A Quantitative Approach"
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct BrickScore {
23    /// Performance score (0-40): GFLOP/s throughput vs theoretical peak
24    pub performance: u8,
25    /// Efficiency score (0-25): Backend utilization, memory efficiency
26    pub efficiency: u8,
27    /// Correctness score (0-20): Assertions passing, numerical accuracy
28    pub correctness: u8,
29    /// Stability score (0-15): CV < 5%, reproducibility
30    pub stability: u8,
31}
32
33impl BrickScore {
34    /// Create a new BrickScore with explicit values
35    pub const fn new(performance: u8, efficiency: u8, correctness: u8, stability: u8) -> Self {
36        Self {
37            performance: if performance > 40 { 40 } else { performance },
38            efficiency: if efficiency > 25 { 25 } else { efficiency },
39            correctness: if correctness > 20 { 20 } else { correctness },
40            stability: if stability > 15 { 15 } else { stability },
41        }
42    }
43
44    /// Create a perfect score (100/100)
45    pub const fn perfect() -> Self {
46        Self::new(40, 25, 20, 15)
47    }
48
49    /// Create a zero score (0/100)
50    pub const fn zero() -> Self {
51        Self::new(0, 0, 0, 0)
52    }
53
54    /// Total score (0-100)
55    pub const fn total(&self) -> u8 {
56        self.performance + self.efficiency + self.correctness + self.stability
57    }
58
59    /// Letter grade based on total score (F501-F505 criteria)
60    pub fn grade(&self) -> BrickGrade {
61        match self.total() {
62            90..=100 => BrickGrade::A,
63            80..=89 => BrickGrade::B,
64            70..=79 => BrickGrade::C,
65            60..=69 => BrickGrade::D,
66            _ => BrickGrade::F,
67        }
68    }
69
70    /// Performance score as percentage (0.0-1.0)
71    pub fn performance_pct(&self) -> f64 {
72        self.performance as f64 / 40.0
73    }
74
75    /// Efficiency score as percentage (0.0-1.0)
76    pub fn efficiency_pct(&self) -> f64 {
77        self.efficiency as f64 / 25.0
78    }
79
80    /// Correctness score as percentage (0.0-1.0)
81    pub fn correctness_pct(&self) -> f64 {
82        self.correctness as f64 / 20.0
83    }
84
85    /// Stability score as percentage (0.0-1.0)
86    pub fn stability_pct(&self) -> f64 {
87        self.stability as f64 / 15.0
88    }
89
90    /// Calculate performance score from GFLOP/s vs theoretical peak
91    /// Per §29.2: `min(40, (actual / theoretical) * 40)`
92    pub fn score_performance(actual_gflops: f64, theoretical_gflops: f64) -> u8 {
93        if theoretical_gflops <= 0.0 {
94            return 0;
95        }
96        let ratio = actual_gflops / theoretical_gflops;
97        (ratio * 40.0).min(40.0) as u8
98    }
99
100    /// Calculate performance score from speedup vs scalar baseline
101    /// Per §29.2: `log2(speedup) * 5` capped at 20
102    pub fn score_speedup(speedup: f64) -> u8 {
103        if speedup <= 1.0 {
104            return 0;
105        }
106        (speedup.log2() * 5.0).min(20.0) as u8
107    }
108
109    /// Calculate stability score from Coefficient of Variation
110    /// Per §29.5: CV < 5% = 8 pts, CV < 10% = 4 pts, else 0
111    pub fn score_cv(cv_percent: f64) -> u8 {
112        if cv_percent < 5.0 {
113            8
114        } else if cv_percent < 10.0 {
115            4
116        } else {
117            0
118        }
119    }
120
121    /// Render a progress bar for a component (TUI display)
122    pub fn render_bar(value: u8, max: u8, width: usize) -> String {
123        let ratio = value as f64 / max as f64;
124        let filled = (ratio * width as f64).round() as usize;
125        let empty = width.saturating_sub(filled);
126        format!("{}{}", "█".repeat(filled), "░".repeat(empty))
127    }
128}
129
130impl Default for BrickScore {
131    fn default() -> Self {
132        Self::zero()
133    }
134}
135
136impl fmt::Display for BrickScore {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        write!(f, "{}/100 ({})", self.total(), self.grade().description())
139    }
140}
141
142/// Letter grade for BrickScore
143///
144/// Note: Ordering is reversed (A > B > C > D > F) to match intuitive grade comparison.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum BrickGrade {
147    /// Failing (<60)
148    F,
149    /// Needs Improvement (60-69)
150    D,
151    /// Acceptable (70-79)
152    C,
153    /// Good (80-89)
154    B,
155    /// Excellent (90-100)
156    A,
157}
158
159impl PartialOrd for BrickGrade {
160    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
161        Some(self.cmp(other))
162    }
163}
164
165impl Ord for BrickGrade {
166    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
167        // Order by grade quality: A > B > C > D > F
168        let self_val = match self {
169            Self::A => 4,
170            Self::B => 3,
171            Self::C => 2,
172            Self::D => 1,
173            Self::F => 0,
174        };
175        let other_val = match other {
176            Self::A => 4,
177            Self::B => 3,
178            Self::C => 2,
179            Self::D => 1,
180            Self::F => 0,
181        };
182        self_val.cmp(&other_val)
183    }
184}
185
186impl BrickGrade {
187    /// Human-readable description
188    pub const fn description(&self) -> &'static str {
189        match self {
190            Self::A => "Excellent",
191            Self::B => "Good",
192            Self::C => "Acceptable",
193            Self::D => "Needs Improvement",
194            Self::F => "Failing",
195        }
196    }
197
198    /// Single character representation
199    pub const fn letter(&self) -> char {
200        match self {
201            Self::A => 'A',
202            Self::B => 'B',
203            Self::C => 'C',
204            Self::D => 'D',
205            Self::F => 'F',
206        }
207    }
208
209    /// Color for TUI display (Andon-style)
210    pub const fn color(&self) -> Color {
211        match self {
212            Self::A | Self::B => Color::ANDON_GREEN,
213            Self::C => Color::ANDON_YELLOW,
214            Self::D | Self::F => Color::ANDON_RED,
215        }
216    }
217}
218
219impl fmt::Display for BrickGrade {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        write!(f, "{}", self.letter())
222    }
223}
224
225/// Trait extension for scoring ComputeBricks
226///
227/// Any Brick that implements Scorable can report its quality score.
228pub trait Scorable: Brick {
229    /// Calculate the current quality score
230    fn score(&self) -> BrickScore;
231
232    /// Generate a detailed score report (TUI-friendly)
233    fn score_report(&self) -> String {
234        let score = self.score();
235        let perf_bar = BrickScore::render_bar(score.performance, 40, 20);
236        let eff_bar = BrickScore::render_bar(score.efficiency, 25, 20);
237        let corr_bar = BrickScore::render_bar(score.correctness, 20, 20);
238        let stab_bar = BrickScore::render_bar(score.stability, 15, 20);
239
240        format!(
241            "╭──────────────────────────────────────────────────────╮\n\
242             │      ComputeBrick Score: {:<24} │\n\
243             ├──────────────────────────────────────────────────────┤\n\
244             │ Performance: {:>5}/40  {} {:>3.0}% │\n\
245             │ Efficiency:  {:>5}/25  {} {:>3.0}% │\n\
246             │ Correctness: {:>5}/20  {} {:>3.0}% │\n\
247             │ Stability:   {:>5}/15  {} {:>3.0}% │\n\
248             ├──────────────────────────────────────────────────────┤\n\
249             │ TOTAL SCORE: {:>6}/100                 Grade: {} │\n\
250             ╰──────────────────────────────────────────────────────╯",
251            self.brick_name(),
252            score.performance,
253            perf_bar,
254            score.performance_pct() * 100.0,
255            score.efficiency,
256            eff_bar,
257            score.efficiency_pct() * 100.0,
258            score.correctness,
259            corr_bar,
260            score.correctness_pct() * 100.0,
261            score.stability,
262            stab_bar,
263            score.stability_pct() * 100.0,
264            score.total(),
265            score.grade()
266        )
267    }
268}