use std::process::Child; use crate::stats::bivariate::regression::Slope; use criterion_plot::prelude::*; use super::*; use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext}; use crate::stats::bivariate::Data; use crate::estimate::{ConfidenceInterval, Estimate}; use crate::measurement::ValueFormatter; fn regression_figure( formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option, ) -> Figure { let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap(); let slope_dist = measurements.distributions.slope.as_ref().unwrap(); let (lb, ub) = slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level); let data = &measurements.data; let (max_iters, typical) = (data.x().max(), data.y().max()); let mut scaled_y: Vec = data.y().iter().cloned().collect(); let unit = formatter.scale_values(typical, &mut scaled_y); let scaled_y = Sample::new(&scaled_y); let point_estimate = Slope::fit(&measurements.data).0; let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters]; let _ = formatter.scale_values(typical, &mut scaled_points); let [point, lb, ub] = scaled_points; let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; let x_scale = 10f64.powi(-exponent); let x_label = if exponent == 0 { "Iterations".to_owned() } else { format!("Iterations (x 10^{})", exponent) }; let mut figure = Figure::new(); figure .set(Font(DEFAULT_FONT)) .set(size.unwrap_or(SIZE)) .configure(Axis::BottomX, |a| { a.configure(Grid::Major, |g| g.show()) .set(Label(x_label)) .set(ScaleFactor(x_scale)) }) .configure(Axis::LeftY, |a| { a.configure(Grid::Major, |g| g.show()) .set(Label(format!("Total sample time ({})", unit))) }) .plot( Points { x: data.x().as_ref(), y: scaled_y.as_ref(), }, |c| { c.set(DARK_BLUE) .set(Label("Sample")) .set(PointSize(0.5)) .set(PointType::FilledCircle) }, ) .plot( Lines { x: &[0., max_iters], y: &[0., point], }, |c| { c.set(DARK_BLUE) .set(LINEWIDTH) .set(Label("Linear regression")) .set(LineType::Solid) }, ) .plot( FilledCurve { x: &[0., max_iters], y1: &[0., lb], y2: &[0., ub], }, |c| { c.set(DARK_BLUE) .set(Label("Confidence interval")) .set(Opacity(0.25)) }, ); figure } pub(crate) fn regression( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option, ) -> Child { let mut figure = regression_figure(formatter, measurements, size); figure.set(Title(gnuplot_escape(id.as_title()))); figure.configure(Key, |k| { k.set(Justification::Left) .set(Order::SampleText) .set(Position::Inside(Vertical::Top, Horizontal::Left)) }); let path = context.report_path(id, "regression.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() } pub(crate) fn regression_small( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option, ) -> Child { let mut figure = regression_figure(formatter, measurements, size); figure.configure(Key, |k| k.hide()); let path = context.report_path(id, "regression_small.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() } fn regression_comparison_figure( formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, comparison: &ComparisonData, base_data: &Data<'_, f64, f64>, size: Option, ) -> Figure { let data = &measurements.data; let max_iters = base_data.x().max().max(data.x().max()); let typical = base_data.y().max().max(data.y().max()); let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; let x_scale = 10f64.powi(-exponent); let x_label = if exponent == 0 { "Iterations".to_owned() } else { format!("Iterations (x 10^{})", exponent) }; let Estimate { confidence_interval: ConfidenceInterval { lower_bound: base_lb, upper_bound: base_ub, .. }, point_estimate: base_point, .. } = comparison.base_estimates.slope.as_ref().unwrap(); let Estimate { confidence_interval: ConfidenceInterval { lower_bound: lb, upper_bound: ub, .. }, point_estimate: point, .. } = measurements.absolute_estimates.slope.as_ref().unwrap(); let mut points = [ base_lb * max_iters, base_point * max_iters, base_ub * max_iters, lb * max_iters, point * max_iters, ub * max_iters, ]; let unit = formatter.scale_values(typical, &mut points); let [base_lb, base_point, base_ub, lb, point, ub] = points; let mut figure = Figure::new(); figure .set(Font(DEFAULT_FONT)) .set(size.unwrap_or(SIZE)) .configure(Axis::BottomX, |a| { a.configure(Grid::Major, |g| g.show()) .set(Label(x_label)) .set(ScaleFactor(x_scale)) }) .configure(Axis::LeftY, |a| { a.configure(Grid::Major, |g| g.show()) .set(Label(format!("Total sample time ({})", unit))) }) .configure(Key, |k| { k.set(Justification::Left) .set(Order::SampleText) .set(Position::Inside(Vertical::Top, Horizontal::Left)) }) .plot( FilledCurve { x: &[0., max_iters], y1: &[0., base_lb], y2: &[0., base_ub], }, |c| c.set(DARK_RED).set(Opacity(0.25)), ) .plot( FilledCurve { x: &[0., max_iters], y1: &[0., lb], y2: &[0., ub], }, |c| c.set(DARK_BLUE).set(Opacity(0.25)), ) .plot( Lines { x: &[0., max_iters], y: &[0., base_point], }, |c| { c.set(DARK_RED) .set(LINEWIDTH) .set(Label("Base sample")) .set(LineType::Solid) }, ) .plot( Lines { x: &[0., max_iters], y: &[0., point], }, |c| { c.set(DARK_BLUE) .set(LINEWIDTH) .set(Label("New sample")) .set(LineType::Solid) }, ); figure } pub(crate) fn regression_comparison( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, comparison: &ComparisonData, base_data: &Data<'_, f64, f64>, size: Option, ) -> Child { let mut figure = regression_comparison_figure(formatter, measurements, comparison, base_data, size); figure.set(Title(gnuplot_escape(id.as_title()))); let path = context.report_path(id, "both/regression.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() } pub(crate) fn regression_comparison_small( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, comparison: &ComparisonData, base_data: &Data<'_, f64, f64>, size: Option, ) -> Child { let mut figure = regression_comparison_figure(formatter, measurements, comparison, base_data, size); figure.configure(Key, |k| k.hide()); let path = context.report_path(id, "relative_regression_small.svg"); debug_script(&path, &figure); figure.set(Output(path)).draw().unwrap() }