| use super::*; |
| |
| use std::path::Path; |
| |
| use crate::estimate::{ConfidenceInterval, Estimate}; |
| use crate::stats::bivariate::regression::Slope; |
| use crate::stats::bivariate::Data; |
| |
| pub(crate) fn regression_figure( |
| title: Option<&str>, |
| path: &Path, |
| formatter: &dyn ValueFormatter, |
| measurements: &MeasurementData<'_>, |
| size: Option<(u32, u32)>, |
| ) { |
| 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 size = size.unwrap_or(SIZE); |
| let root_area = SVGBackend::new(path, size).into_drawing_area(); |
| |
| let mut cb = ChartBuilder::on(&root_area); |
| if let Some(title) = title { |
| cb.caption(title, (DEFAULT_FONT, 20)); |
| } |
| |
| let x_range = plotters::data::fitting_range(data.x().iter()); |
| let y_range = plotters::data::fitting_range(scaled_y.iter()); |
| |
| let mut chart = cb |
| .margin((5).percent()) |
| .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60)) |
| .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40)) |
| .build_cartesian_2d(x_range, y_range) |
| .unwrap(); |
| |
| chart |
| .configure_mesh() |
| .x_desc(x_label) |
| .y_desc(format!("Total sample time ({})", unit)) |
| .x_label_formatter(&|x| pretty_print_float(x * x_scale, true)) |
| .light_line_style(&TRANSPARENT) |
| .draw() |
| .unwrap(); |
| |
| chart |
| .draw_series( |
| data.x() |
| .iter() |
| .zip(scaled_y.iter()) |
| .map(|(x, y)| Circle::new((*x, *y), POINT_SIZE, DARK_BLUE.filled())), |
| ) |
| .unwrap() |
| .label("Sample") |
| .legend(|(x, y)| Circle::new((x + 10, y), POINT_SIZE, DARK_BLUE.filled())); |
| |
| chart |
| .draw_series(std::iter::once(PathElement::new( |
| vec![(0.0, 0.0), (max_iters, point)], |
| &DARK_BLUE, |
| ))) |
| .unwrap() |
| .label("Linear regression") |
| .legend(|(x, y)| { |
| PathElement::new( |
| vec![(x, y), (x + 20, y)], |
| DARK_BLUE.filled().stroke_width(2), |
| ) |
| }); |
| |
| chart |
| .draw_series(std::iter::once(Polygon::new( |
| vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)], |
| DARK_BLUE.mix(0.25).filled(), |
| ))) |
| .unwrap() |
| .label("Confidence interval") |
| .legend(|(x, y)| { |
| Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled()) |
| }); |
| |
| if title.is_some() { |
| chart |
| .configure_series_labels() |
| .position(SeriesLabelPosition::UpperLeft) |
| .draw() |
| .unwrap(); |
| } |
| } |
| |
| pub(crate) fn regression_comparison_figure( |
| title: Option<&str>, |
| path: &Path, |
| formatter: &dyn ValueFormatter, |
| measurements: &MeasurementData<'_>, |
| comparison: &ComparisonData, |
| base_data: &Data<'_, f64, f64>, |
| size: Option<(u32, u32)>, |
| ) { |
| 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 y_max = point.max(base_point); |
| |
| let size = size.unwrap_or(SIZE); |
| let root_area = SVGBackend::new(path, size).into_drawing_area(); |
| |
| let mut cb = ChartBuilder::on(&root_area); |
| if let Some(title) = title { |
| cb.caption(title, (DEFAULT_FONT, 20)); |
| } |
| |
| let mut chart = cb |
| .margin((5).percent()) |
| .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60)) |
| .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40)) |
| .build_cartesian_2d(0.0..max_iters, 0.0..y_max) |
| .unwrap(); |
| |
| chart |
| .configure_mesh() |
| .x_desc(x_label) |
| .y_desc(format!("Total sample time ({})", unit)) |
| .x_label_formatter(&|x| pretty_print_float(x * x_scale, true)) |
| .light_line_style(&TRANSPARENT) |
| .draw() |
| .unwrap(); |
| |
| chart |
| .draw_series(vec![ |
| PathElement::new(vec![(0.0, 0.0), (max_iters, base_point)], &DARK_RED).into_dyn(), |
| Polygon::new( |
| vec![(0.0, 0.0), (max_iters, base_lb), (max_iters, base_ub)], |
| DARK_RED.mix(0.25).filled(), |
| ) |
| .into_dyn(), |
| ]) |
| .unwrap() |
| .label("Base Sample") |
| .legend(|(x, y)| { |
| PathElement::new(vec![(x, y), (x + 20, y)], DARK_RED.filled().stroke_width(2)) |
| }); |
| |
| chart |
| .draw_series(vec![ |
| PathElement::new(vec![(0.0, 0.0), (max_iters, point)], &DARK_BLUE).into_dyn(), |
| Polygon::new( |
| vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)], |
| DARK_BLUE.mix(0.25).filled(), |
| ) |
| .into_dyn(), |
| ]) |
| .unwrap() |
| .label("New Sample") |
| .legend(|(x, y)| { |
| PathElement::new( |
| vec![(x, y), (x + 20, y)], |
| DARK_BLUE.filled().stroke_width(2), |
| ) |
| }); |
| |
| if title.is_some() { |
| chart |
| .configure_series_labels() |
| .position(SeriesLabelPosition::UpperLeft) |
| .draw() |
| .unwrap(); |
| } |
| } |