| 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<Size>, |
| ) -> 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<f64> = 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<Size>, |
| ) -> 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<Size>, |
| ) -> 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<Size>, |
| ) -> 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<Size>, |
| ) -> 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<Size>, |
| ) -> 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() |
| } |