use super::{bricks, metrics::Metrics, quantizer, signals, variant};
use std::fmt::Write as _;
#[derive(Debug, Clone, Copy, Default)]
pub struct Overrides {
pub step: Option<f32>,
pub bias: Option<f32>,
}
#[derive(Debug, Clone)]
pub struct Row {
pub signal: String,
pub metrics: Metrics,
}
#[derive(Debug, Clone)]
pub struct Report {
pub brick: String,
pub variant: String,
pub params: String,
pub rows: Vec<Row>,
pub mean: Metrics,
}
pub fn run(brick_id: &str, variant_name: &str, ov: Overrides) -> Result<Report, String> {
let brick = bricks::by_id(brick_id)
.ok_or_else(|| format!("unknown brick '{brick_id}' (try `mp3lab bricks`)"))?;
match brick.id {
"N4" => run_quantizer(variant_name, ov),
other => Err(format!(
"brick {other} is not runnable yet (status: {}). Next buildable: {}",
brick.status.name(),
bricks::next_unbuilt().map_or("—", |b| b.id),
)),
}
}
fn run_quantizer(variant_name: &str, ov: Overrides) -> Result<Report, String> {
let mut cfg = variant::find(quantizer::VARIANTS, variant_name).ok_or_else(|| {
format!(
"unknown variant '{variant_name}' for N4. Available: {}",
variant::names(quantizer::VARIANTS)
)
})?;
if let Some(s) = ov.step {
cfg.step = s;
}
if let Some(b) = ov.bias {
cfg.bias = b;
}
let rows: Vec<Row> = signals::corpus()
.iter()
.map(|s| Row {
signal: s.name.clone(),
metrics: quantizer::eval(cfg, s),
})
.collect();
let mean = Metrics::mean(&rows.iter().map(|r| r.metrics).collect::<Vec<_>>());
Ok(Report {
brick: "N4".to_string(),
variant: variant_name.to_string(),
params: format!(
"step={} bias={} max_level={}",
cfg.step, cfg.bias, cfg.max_level
),
rows,
mean,
})
}
impl Report {
#[must_use]
pub fn to_text(&self) -> String {
let mut s = format!(
"experiment: {} / {}\nparams: {}\n\n",
self.brick, self.variant, self.params
);
for r in &self.rows {
let _ = writeln!(s, " {:<18} {}", r.signal, r.metrics.summary());
}
let _ = writeln!(s, " {:<18} {}", "── mean ──", self.mean.summary());
s
}
#[must_use]
pub fn to_json(&self) -> String {
let rows: Vec<String> = self
.rows
.iter()
.map(|r| {
format!(
" {{\"signal\":\"{}\",\"max_abs_err\":{:.8},\"rmse\":{:.8},\"psnr_db\":{}}}",
r.signal,
r.metrics.max_abs_err,
r.metrics.rmse,
r.metrics.psnr_json()
)
})
.collect();
format!(
"{{\n \"brick\": \"{}\",\n \"variant\": \"{}\",\n \"params\": \"{}\",\n \"mean\": {{\"max_abs_err\":{:.8},\"rmse\":{:.8},\"psnr_db\":{}}},\n \"rows\": [\n{}\n ]\n}}\n",
self.brick,
self.variant,
self.params,
self.mean.max_abs_err,
self.mean.rmse,
self.mean.psnr_json(),
rows.join(",\n"),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quantizer_experiment_is_repeatable() {
let a = run("N4", "iso", Overrides::default()).unwrap();
let b = run("N4", "iso", Overrides::default()).unwrap();
assert_eq!(a.to_json(), b.to_json());
assert_eq!(a.rows.len(), signals::corpus().len());
}
#[test]
fn unbuilt_brick_errs_not_panics() {
let r = run("L1", "whatever", Overrides::default());
assert!(r.is_err());
}
#[test]
fn override_changes_result() {
let preset = run("N4", "iso", Overrides::default()).unwrap();
let overridden = run(
"N4",
"iso",
Overrides {
bias: Some(0.0),
step: None,
},
)
.unwrap();
assert_ne!(preset.to_json(), overridden.to_json());
}
}