criterion/plot/plotters_backend/
regression.rs1use super::*;
2
3use std::path::Path;
4
5use crate::estimate::{ConfidenceInterval, Estimate};
6use crate::stats::bivariate::regression::Slope;
7
8pub(crate) fn regression_figure(
9 title: Option<&str>,
10 path: &Path,
11 formatter: &dyn ValueFormatter,
12 measurements: &MeasurementData<'_>,
13 size: Option<(u32, u32)>,
14) {
15 let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap();
16 let slope_dist = measurements.distributions.slope.as_ref().unwrap();
17 let (lb, ub) =
18 slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level);
19
20 let data = &measurements.data;
21 let (max_iters, typical) = (data.x().max(), data.y().max());
22 let mut scaled_y: Vec<f64> = data.y().iter().cloned().collect();
23 let unit = formatter.scale_values(typical, &mut scaled_y);
24 let scaled_y = Sample::new(&scaled_y);
25
26 let point_estimate = Slope::fit(&measurements.data).0;
27 let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters];
28 let _ = formatter.scale_values(typical, &mut scaled_points);
29 let [point, lb, ub] = scaled_points;
30
31 let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
32
33 let x_scale = 10f64.powi(-exponent);
34 let x_label = if exponent == 0 {
35 "Iterations".to_owned()
36 } else {
37 format!("Iterations (x 10^{})", exponent)
38 };
39
40 let size = size.unwrap_or(SIZE);
41 let root_area = SVGBackend::new(path, size).into_drawing_area();
42
43 let mut cb = ChartBuilder::on(&root_area);
44 if let Some(title) = title {
45 cb.caption(title, (DEFAULT_FONT, 20));
46 }
47
48 let x_range = plotters::data::fitting_range(data.x().iter());
49 let y_range = plotters::data::fitting_range(scaled_y.iter());
50
51 let mut chart = cb
52 .margin((5).percent())
53 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
54 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
55 .build_cartesian_2d(x_range, y_range)
56 .unwrap();
57
58 chart
59 .configure_mesh()
60 .x_desc(x_label)
61 .y_desc(format!("Total sample time ({})", unit))
62 .x_label_formatter(&|x| pretty_print_float(x * x_scale, true))
63 .light_line_style(TRANSPARENT)
64 .draw()
65 .unwrap();
66
67 chart
68 .draw_series(
69 data.x()
70 .iter()
71 .zip(scaled_y.iter())
72 .map(|(x, y)| Circle::new((*x, *y), POINT_SIZE, DARK_BLUE.filled())),
73 )
74 .unwrap()
75 .label("Sample")
76 .legend(|(x, y)| Circle::new((x + 10, y), POINT_SIZE, DARK_BLUE.filled()));
77
78 chart
79 .draw_series(std::iter::once(PathElement::new(
80 vec![(0.0, 0.0), (max_iters, point)],
81 DARK_BLUE,
82 )))
83 .unwrap()
84 .label("Linear regression")
85 .legend(|(x, y)| {
86 PathElement::new(
87 vec![(x, y), (x + 20, y)],
88 DARK_BLUE.filled().stroke_width(2),
89 )
90 });
91
92 chart
93 .draw_series(std::iter::once(Polygon::new(
94 vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)],
95 DARK_BLUE.mix(0.25).filled(),
96 )))
97 .unwrap()
98 .label("Confidence interval")
99 .legend(|(x, y)| {
100 Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled())
101 });
102
103 if title.is_some() {
104 chart
105 .configure_series_labels()
106 .position(SeriesLabelPosition::UpperLeft)
107 .draw()
108 .unwrap();
109 }
110}
111
112pub(crate) fn regression_comparison_figure(
113 title: Option<&str>,
114 path: &Path,
115 formatter: &dyn ValueFormatter,
116 measurements: &MeasurementData<'_>,
117 comparison: &ComparisonData,
118 base_data: &Data<'_, f64, f64>,
119 size: Option<(u32, u32)>,
120) {
121 let data = &measurements.data;
122 let max_iters = base_data.x().max().max(data.x().max());
123 let typical = base_data.y().max().max(data.y().max());
124
125 let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
126 let x_scale = 10f64.powi(-exponent);
127
128 let x_label = if exponent == 0 {
129 "Iterations".to_owned()
130 } else {
131 format!("Iterations (x 10^{})", exponent)
132 };
133
134 let Estimate {
135 confidence_interval:
136 ConfidenceInterval {
137 lower_bound: base_lb,
138 upper_bound: base_ub,
139 ..
140 },
141 point_estimate: base_point,
142 ..
143 } = comparison.base_estimates.slope.as_ref().unwrap();
144
145 let Estimate {
146 confidence_interval:
147 ConfidenceInterval {
148 lower_bound: lb,
149 upper_bound: ub,
150 ..
151 },
152 point_estimate: point,
153 ..
154 } = measurements.absolute_estimates.slope.as_ref().unwrap();
155
156 let mut points = [
157 base_lb * max_iters,
158 base_point * max_iters,
159 base_ub * max_iters,
160 lb * max_iters,
161 point * max_iters,
162 ub * max_iters,
163 ];
164 let unit = formatter.scale_values(typical, &mut points);
165 let [base_lb, base_point, base_ub, lb, point, ub] = points;
166
167 let y_max = point.max(base_point);
168
169 let size = size.unwrap_or(SIZE);
170 let root_area = SVGBackend::new(path, size).into_drawing_area();
171
172 let mut cb = ChartBuilder::on(&root_area);
173 if let Some(title) = title {
174 cb.caption(title, (DEFAULT_FONT, 20));
175 }
176
177 let mut chart = cb
178 .margin((5).percent())
179 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
180 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
181 .build_cartesian_2d(0.0..max_iters, 0.0..y_max)
182 .unwrap();
183
184 chart
185 .configure_mesh()
186 .x_desc(x_label)
187 .y_desc(format!("Total sample time ({})", unit))
188 .x_label_formatter(&|x| pretty_print_float(x * x_scale, true))
189 .light_line_style(TRANSPARENT)
190 .draw()
191 .unwrap();
192
193 chart
194 .draw_series(vec![
195 PathElement::new(vec![(0.0, 0.0), (max_iters, base_point)], DARK_RED).into_dyn(),
196 Polygon::new(
197 vec![(0.0, 0.0), (max_iters, base_lb), (max_iters, base_ub)],
198 DARK_RED.mix(0.25).filled(),
199 )
200 .into_dyn(),
201 ])
202 .unwrap()
203 .label("Base Sample")
204 .legend(|(x, y)| {
205 PathElement::new(vec![(x, y), (x + 20, y)], DARK_RED.filled().stroke_width(2))
206 });
207
208 chart
209 .draw_series(vec![
210 PathElement::new(vec![(0.0, 0.0), (max_iters, point)], DARK_BLUE).into_dyn(),
211 Polygon::new(
212 vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)],
213 DARK_BLUE.mix(0.25).filled(),
214 )
215 .into_dyn(),
216 ])
217 .unwrap()
218 .label("New Sample")
219 .legend(|(x, y)| {
220 PathElement::new(
221 vec![(x, y), (x + 20, y)],
222 DARK_BLUE.filled().stroke_width(2),
223 )
224 });
225
226 if title.is_some() {
227 chart
228 .configure_series_labels()
229 .position(SeriesLabelPosition::UpperLeft)
230 .draw()
231 .unwrap();
232 }
233}