use super::*; use crate::kde; use crate::measurement::ValueFormatter; use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext}; use std::process::Child; pub(crate) fn pdf( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option, ) -> Child { let avg_times = &measurements.avg_times; let typical = avg_times.max(); let mut scaled_avg_times: Vec = (avg_times as &Sample).iter().cloned().collect(); let unit = formatter.scale_values(typical, &mut scaled_avg_times); let scaled_avg_times = Sample::new(&scaled_avg_times); let mean = scaled_avg_times.mean(); let iter_counts = measurements.iter_counts(); let &max_iters = iter_counts .iter() .max_by_key(|&&iters| iters as u64) .unwrap(); let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; let y_scale = 10f64.powi(-exponent); let y_label = if exponent == 0 { "Iterations".to_owned() } else { format!("Iterations (x 10^{})", exponent) }; let (xs, ys) = kde::sweep(scaled_avg_times, KDE_POINTS, None); let (lost, lomt, himt, hist) = avg_times.fences(); let mut fences = [lost, lomt, himt, hist]; let _ = formatter.scale_values(typical, &mut fences); let [lost, lomt, himt, hist] = fences; let vertical = &[0., max_iters]; let zeros = iter::repeat(0); let mut figure = Figure::new(); figure .set(Font(DEFAULT_FONT)) .set(size.unwrap_or(SIZE)) .configure(Axis::BottomX, |a| { let xs_ = Sample::new(&xs); a.set(Label(format!("Average time ({})", unit))) .set(Range::Limits(xs_.min(), xs_.max())) }) .configure(Axis::LeftY, |a| { a.set(Label(y_label)) .set(Range::Limits(0., max_iters * y_scale)) .set(ScaleFactor(y_scale)) }) .configure(Axis::RightY, |a| a.set(Label("Density (a.u.)"))) .configure(Key, |k| { k.set(Justification::Left) .set(Order::SampleText) .set(Position::Outside(Vertical::Top, Horizontal::Right)) }) .plot( FilledCurve { x: &*xs, y1: &*ys, y2: zeros, }, |c| { c.set(Axes::BottomXRightY) .set(DARK_BLUE) .set(Label("PDF")) .set(Opacity(0.25)) }, ) .plot( Lines { x: &[mean, mean], y: vertical, }, |c| { c.set(DARK_BLUE) .set(LINEWIDTH) .set(LineType::Dash) .set(Label("Mean")) }, ) .plot( Points { x: avg_times .iter() .zip(scaled_avg_times.iter()) .filter_map( |((_, label), t)| { if label.is_outlier() { None } else { Some(t) } }, ), y: avg_times .iter() .zip(iter_counts.iter()) .filter_map( |((_, label), i)| { if label.is_outlier() { None } else { Some(i) } }, ), }, |c| { c.set(DARK_BLUE) .set(Label("\"Clean\" sample")) .set(PointType::FilledCircle) .set(POINT_SIZE) }, ) .plot( Points { x: avg_times .iter() .zip(scaled_avg_times.iter()) .filter_map( |((_, label), t)| { if label.is_mild() { Some(t) } else { None } }, ), y: avg_times .iter() .zip(iter_counts.iter()) .filter_map( |((_, label), i)| { if label.is_mild() { Some(i) } else { None } }, ), }, |c| { c.set(DARK_ORANGE) .set(Label("Mild outliers")) .set(POINT_SIZE) .set(PointType::FilledCircle) }, ) .plot( Points { x: avg_times .iter() .zip(scaled_avg_times.iter()) .filter_map( |((_, label), t)| { if label.is_severe() { Some(t) } else { None } }, ), y: avg_times .iter() .zip(iter_counts.iter()) .filter_map( |((_, label), i)| { if label.is_severe() { Some(i) } else { None } }, ), }, |c| { c.set(DARK_RED) .set(Label("Severe outliers")) .set(POINT_SIZE) .set(PointType::FilledCircle) }, ) .plot( Lines { x: &[lomt, lomt], y: vertical, }, |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash), ) .plot( Lines { x: &[himt, himt], y: vertical, }, |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash), ) .plot( Lines { x: &[lost, lost], y: vertical, }, |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash), ) .plot( Lines { x: &[hist, hist], y: vertical, }, |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash), ); figure.set(Title(gnuplot_escape(id.as_title()))); let path = context.report_path(id, "pdf.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() } pub(crate) fn pdf_small( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option, ) -> Child { let avg_times = &*measurements.avg_times; let typical = avg_times.max(); let mut scaled_avg_times: Vec = (avg_times as &Sample).iter().cloned().collect(); let unit = formatter.scale_values(typical, &mut scaled_avg_times); let scaled_avg_times = Sample::new(&scaled_avg_times); let mean = scaled_avg_times.mean(); let (xs, ys, mean_y) = kde::sweep_and_estimate(scaled_avg_times, KDE_POINTS, None, mean); let xs_ = Sample::new(&xs); let ys_ = Sample::new(&ys); let y_limit = ys_.max() * 1.1; let zeros = iter::repeat(0); let mut figure = Figure::new(); figure .set(Font(DEFAULT_FONT)) .set(size.unwrap_or(SIZE)) .configure(Axis::BottomX, |a| { a.set(Label(format!("Average time ({})", unit))) .set(Range::Limits(xs_.min(), xs_.max())) }) .configure(Axis::LeftY, |a| { a.set(Label("Density (a.u.)")) .set(Range::Limits(0., y_limit)) }) .configure(Axis::RightY, |a| a.hide()) .configure(Key, |k| k.hide()) .plot( FilledCurve { x: &*xs, y1: &*ys, y2: zeros, }, |c| { c.set(Axes::BottomXRightY) .set(DARK_BLUE) .set(Label("PDF")) .set(Opacity(0.25)) }, ) .plot( Lines { x: &[mean, mean], y: &[0., mean_y], }, |c| c.set(DARK_BLUE).set(LINEWIDTH).set(Label("Mean")), ); let path = context.report_path(id, "pdf_small.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() } fn pdf_comparison_figure( formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, comparison: &ComparisonData, size: Option, ) -> Figure { let base_avg_times = Sample::new(&comparison.base_avg_times); let typical = base_avg_times.max().max(measurements.avg_times.max()); let mut scaled_base_avg_times: Vec = comparison.base_avg_times.clone(); let unit = formatter.scale_values(typical, &mut scaled_base_avg_times); let scaled_base_avg_times = Sample::new(&scaled_base_avg_times); let mut scaled_new_avg_times: Vec = (&measurements.avg_times as &Sample) .iter() .cloned() .collect(); let _ = formatter.scale_values(typical, &mut scaled_new_avg_times); let scaled_new_avg_times = Sample::new(&scaled_new_avg_times); let base_mean = scaled_base_avg_times.mean(); let new_mean = scaled_new_avg_times.mean(); let (base_xs, base_ys, base_y_mean) = kde::sweep_and_estimate(scaled_base_avg_times, KDE_POINTS, None, base_mean); let (xs, ys, y_mean) = kde::sweep_and_estimate(scaled_new_avg_times, KDE_POINTS, None, new_mean); let zeros = iter::repeat(0); let mut figure = Figure::new(); figure .set(Font(DEFAULT_FONT)) .set(size.unwrap_or(SIZE)) .configure(Axis::BottomX, |a| { a.set(Label(format!("Average time ({})", unit))) }) .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)"))) .configure(Axis::RightY, |a| a.hide()) .configure(Key, |k| { k.set(Justification::Left) .set(Order::SampleText) .set(Position::Outside(Vertical::Top, Horizontal::Right)) }) .plot( FilledCurve { x: &*base_xs, y1: &*base_ys, y2: zeros.clone(), }, |c| c.set(DARK_RED).set(Label("Base PDF")).set(Opacity(0.5)), ) .plot( Lines { x: &[base_mean, base_mean], y: &[0., base_y_mean], }, |c| c.set(DARK_RED).set(Label("Base Mean")).set(LINEWIDTH), ) .plot( FilledCurve { x: &*xs, y1: &*ys, y2: zeros, }, |c| c.set(DARK_BLUE).set(Label("New PDF")).set(Opacity(0.5)), ) .plot( Lines { x: &[new_mean, new_mean], y: &[0., y_mean], }, |c| c.set(DARK_BLUE).set(Label("New Mean")).set(LINEWIDTH), ); figure } pub(crate) fn pdf_comparison( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, comparison: &ComparisonData, size: Option, ) -> Child { let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size); figure.set(Title(gnuplot_escape(id.as_title()))); let path = context.report_path(id, "both/pdf.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() } pub(crate) fn pdf_comparison_small( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, comparison: &ComparisonData, size: Option, ) -> Child { let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size); figure.configure(Key, |k| k.hide()); let path = context.report_path(id, "relative_pdf_small.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() }