criterion/plot/gnuplot_backend/
regression.rs

1use std::process::Child;
2
3use crate::stats::bivariate::regression::Slope;
4use criterion_plot::prelude::*;
5
6use super::*;
7use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext};
8use crate::stats::bivariate::Data;
9
10use crate::estimate::{ConfidenceInterval, Estimate};
11
12use crate::measurement::ValueFormatter;
13
14fn regression_figure(
15    formatter: &dyn ValueFormatter,
16    measurements: &MeasurementData<'_>,
17    size: Option<Size>,
18) -> Figure {
19    let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap();
20    let slope_dist = measurements.distributions.slope.as_ref().unwrap();
21    let (lb, ub) =
22        slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level);
23
24    let data = &measurements.data;
25    let (max_iters, typical) = (data.x().max(), data.y().max());
26    let mut scaled_y: Vec<f64> = data.y().iter().cloned().collect();
27    let unit = formatter.scale_values(typical, &mut scaled_y);
28    let scaled_y = Sample::new(&scaled_y);
29
30    let point_estimate = Slope::fit(&measurements.data).0;
31    let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters];
32    let _ = formatter.scale_values(typical, &mut scaled_points);
33    let [point, lb, ub] = scaled_points;
34
35    let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
36    let x_scale = 10f64.powi(-exponent);
37
38    let x_label = if exponent == 0 {
39        "Iterations".to_owned()
40    } else {
41        format!("Iterations (x 10^{})", exponent)
42    };
43
44    let mut figure = Figure::new();
45    figure
46        .set(Font(DEFAULT_FONT))
47        .set(size.unwrap_or(SIZE))
48        .configure(Axis::BottomX, |a| {
49            a.configure(Grid::Major, |g| g.show())
50                .set(Label(x_label))
51                .set(ScaleFactor(x_scale))
52        })
53        .configure(Axis::LeftY, |a| {
54            a.configure(Grid::Major, |g| g.show())
55                .set(Label(format!("Total sample time ({})", unit)))
56        })
57        .plot(
58            Points {
59                x: data.x().as_ref(),
60                y: scaled_y.as_ref(),
61            },
62            |c| {
63                c.set(DARK_BLUE)
64                    .set(Label("Sample"))
65                    .set(PointSize(0.5))
66                    .set(PointType::FilledCircle)
67            },
68        )
69        .plot(
70            Lines {
71                x: &[0., max_iters],
72                y: &[0., point],
73            },
74            |c| {
75                c.set(DARK_BLUE)
76                    .set(LINEWIDTH)
77                    .set(Label("Linear regression"))
78                    .set(LineType::Solid)
79            },
80        )
81        .plot(
82            FilledCurve {
83                x: &[0., max_iters],
84                y1: &[0., lb],
85                y2: &[0., ub],
86            },
87            |c| {
88                c.set(DARK_BLUE)
89                    .set(Label("Confidence interval"))
90                    .set(Opacity(0.25))
91            },
92        );
93    figure
94}
95
96pub(crate) fn regression(
97    id: &BenchmarkId,
98    context: &ReportContext,
99    formatter: &dyn ValueFormatter,
100    measurements: &MeasurementData<'_>,
101    size: Option<Size>,
102) -> Child {
103    let mut figure = regression_figure(formatter, measurements, size);
104    figure.set(Title(gnuplot_escape(id.as_title())));
105    figure.configure(Key, |k| {
106        k.set(Justification::Left)
107            .set(Order::SampleText)
108            .set(Position::Inside(Vertical::Top, Horizontal::Left))
109    });
110
111    let path = context.report_path(id, "regression.svg");
112    debug_script(&path, &figure);
113    figure.set(Output(path)).draw().unwrap()
114}
115
116pub(crate) fn regression_small(
117    id: &BenchmarkId,
118    context: &ReportContext,
119    formatter: &dyn ValueFormatter,
120    measurements: &MeasurementData<'_>,
121    size: Option<Size>,
122) -> Child {
123    let mut figure = regression_figure(formatter, measurements, size);
124    figure.configure(Key, |k| k.hide());
125
126    let path = context.report_path(id, "regression_small.svg");
127    debug_script(&path, &figure);
128    figure.set(Output(path)).draw().unwrap()
129}
130
131fn regression_comparison_figure(
132    formatter: &dyn ValueFormatter,
133    measurements: &MeasurementData<'_>,
134    comparison: &ComparisonData,
135    base_data: &Data<'_, f64, f64>,
136    size: Option<Size>,
137) -> Figure {
138    let data = &measurements.data;
139    let max_iters = base_data.x().max().max(data.x().max());
140    let typical = base_data.y().max().max(data.y().max());
141
142    let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
143    let x_scale = 10f64.powi(-exponent);
144
145    let x_label = if exponent == 0 {
146        "Iterations".to_owned()
147    } else {
148        format!("Iterations (x 10^{})", exponent)
149    };
150
151    let Estimate {
152        confidence_interval:
153            ConfidenceInterval {
154                lower_bound: base_lb,
155                upper_bound: base_ub,
156                ..
157            },
158        point_estimate: base_point,
159        ..
160    } = comparison.base_estimates.slope.as_ref().unwrap();
161
162    let Estimate {
163        confidence_interval:
164            ConfidenceInterval {
165                lower_bound: lb,
166                upper_bound: ub,
167                ..
168            },
169        point_estimate: point,
170        ..
171    } = measurements.absolute_estimates.slope.as_ref().unwrap();
172
173    let mut points = [
174        base_lb * max_iters,
175        base_point * max_iters,
176        base_ub * max_iters,
177        lb * max_iters,
178        point * max_iters,
179        ub * max_iters,
180    ];
181    let unit = formatter.scale_values(typical, &mut points);
182    let [base_lb, base_point, base_ub, lb, point, ub] = points;
183
184    let mut figure = Figure::new();
185    figure
186        .set(Font(DEFAULT_FONT))
187        .set(size.unwrap_or(SIZE))
188        .configure(Axis::BottomX, |a| {
189            a.configure(Grid::Major, |g| g.show())
190                .set(Label(x_label))
191                .set(ScaleFactor(x_scale))
192        })
193        .configure(Axis::LeftY, |a| {
194            a.configure(Grid::Major, |g| g.show())
195                .set(Label(format!("Total sample time ({})", unit)))
196        })
197        .configure(Key, |k| {
198            k.set(Justification::Left)
199                .set(Order::SampleText)
200                .set(Position::Inside(Vertical::Top, Horizontal::Left))
201        })
202        .plot(
203            FilledCurve {
204                x: &[0., max_iters],
205                y1: &[0., base_lb],
206                y2: &[0., base_ub],
207            },
208            |c| c.set(DARK_RED).set(Opacity(0.25)),
209        )
210        .plot(
211            FilledCurve {
212                x: &[0., max_iters],
213                y1: &[0., lb],
214                y2: &[0., ub],
215            },
216            |c| c.set(DARK_BLUE).set(Opacity(0.25)),
217        )
218        .plot(
219            Lines {
220                x: &[0., max_iters],
221                y: &[0., base_point],
222            },
223            |c| {
224                c.set(DARK_RED)
225                    .set(LINEWIDTH)
226                    .set(Label("Base sample"))
227                    .set(LineType::Solid)
228            },
229        )
230        .plot(
231            Lines {
232                x: &[0., max_iters],
233                y: &[0., point],
234            },
235            |c| {
236                c.set(DARK_BLUE)
237                    .set(LINEWIDTH)
238                    .set(Label("New sample"))
239                    .set(LineType::Solid)
240            },
241        );
242    figure
243}
244
245pub(crate) fn regression_comparison(
246    id: &BenchmarkId,
247    context: &ReportContext,
248    formatter: &dyn ValueFormatter,
249    measurements: &MeasurementData<'_>,
250    comparison: &ComparisonData,
251    base_data: &Data<'_, f64, f64>,
252    size: Option<Size>,
253) -> Child {
254    let mut figure =
255        regression_comparison_figure(formatter, measurements, comparison, base_data, size);
256    figure.set(Title(gnuplot_escape(id.as_title())));
257
258    let path = context.report_path(id, "both/regression.svg");
259    debug_script(&path, &figure);
260    figure.set(Output(path)).draw().unwrap()
261}
262
263pub(crate) fn regression_comparison_small(
264    id: &BenchmarkId,
265    context: &ReportContext,
266    formatter: &dyn ValueFormatter,
267    measurements: &MeasurementData<'_>,
268    comparison: &ComparisonData,
269    base_data: &Data<'_, f64, f64>,
270    size: Option<Size>,
271) -> Child {
272    let mut figure =
273        regression_comparison_figure(formatter, measurements, comparison, base_data, size);
274    figure.configure(Key, |k| k.hide());
275
276    let path = context.report_path(id, "relative_regression_small.svg");
277    debug_script(&path, &figure);
278    figure.set(Output(path)).draw().unwrap()
279}