criterion/plot/gnuplot_backend/
regression.rs

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