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}
280