1 use super::*;
2 use crate::estimate::Estimate;
3 use crate::estimate::Statistic;
4 use crate::measurement::ValueFormatter;
5 use crate::report::{BenchmarkId, MeasurementData, ReportContext};
6 use crate::stats::Distribution;
7 
abs_distribution( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, statistic: Statistic, distribution: &Distribution<f64>, estimate: &Estimate, size: Option<(u32, u32)>, )8 fn abs_distribution(
9     id: &BenchmarkId,
10     context: &ReportContext,
11     formatter: &dyn ValueFormatter,
12     statistic: Statistic,
13     distribution: &Distribution<f64>,
14     estimate: &Estimate,
15     size: Option<(u32, u32)>,
16 ) {
17     let ci = &estimate.confidence_interval;
18     let typical = ci.upper_bound;
19     let mut ci_values = [ci.lower_bound, ci.upper_bound, estimate.point_estimate];
20     let unit = formatter.scale_values(typical, &mut ci_values);
21     let (lb, ub, point) = (ci_values[0], ci_values[1], ci_values[2]);
22 
23     let start = lb - (ub - lb) / 9.;
24     let end = ub + (ub - lb) / 9.;
25     let mut scaled_xs: Vec<f64> = distribution.iter().cloned().collect();
26     let _ = formatter.scale_values(typical, &mut scaled_xs);
27     let scaled_xs_sample = Sample::new(&scaled_xs);
28     let (kde_xs, ys) = kde::sweep(scaled_xs_sample, KDE_POINTS, Some((start, end)));
29 
30     // interpolate between two points of the KDE sweep to find the Y position at the point estimate.
31     let n_point = kde_xs
32         .iter()
33         .position(|&x| x >= point)
34         .unwrap_or(kde_xs.len() - 1)
35         .max(1); // Must be at least the second element or this will panic
36     let slope = (ys[n_point] - ys[n_point - 1]) / (kde_xs[n_point] - kde_xs[n_point - 1]);
37     let y_point = ys[n_point - 1] + (slope * (point - kde_xs[n_point - 1]));
38 
39     let start = kde_xs
40         .iter()
41         .enumerate()
42         .find(|&(_, &x)| x >= lb)
43         .unwrap()
44         .0;
45     let end = kde_xs
46         .iter()
47         .enumerate()
48         .rev()
49         .find(|&(_, &x)| x <= ub)
50         .unwrap()
51         .0;
52     let len = end - start;
53 
54     let kde_xs_sample = Sample::new(&kde_xs);
55 
56     let path = context.report_path(id, &format!("{}.svg", statistic));
57     let root_area = SVGBackend::new(&path, size.unwrap_or(SIZE)).into_drawing_area();
58 
59     let x_range = plotters::data::fitting_range(kde_xs_sample.iter());
60     let mut y_range = plotters::data::fitting_range(ys.iter());
61 
62     y_range.end *= 1.1;
63 
64     let mut chart = ChartBuilder::on(&root_area)
65         .margin((5).percent())
66         .caption(
67             format!("{}:{}", id.as_title(), statistic),
68             (DEFAULT_FONT, 20),
69         )
70         .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
71         .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
72         .build_cartesian_2d(x_range, y_range)
73         .unwrap();
74 
75     chart
76         .configure_mesh()
77         .disable_mesh()
78         .x_desc(format!("Average time ({})", unit))
79         .y_desc("Density (a.u.)")
80         .x_label_formatter(&|&v| pretty_print_float(v, true))
81         .y_label_formatter(&|&v| pretty_print_float(v, true))
82         .draw()
83         .unwrap();
84 
85     chart
86         .draw_series(LineSeries::new(
87             kde_xs.iter().zip(ys.iter()).map(|(&x, &y)| (x, y)),
88             &DARK_BLUE,
89         ))
90         .unwrap()
91         .label("Bootstrap distribution")
92         .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &DARK_BLUE));
93 
94     chart
95         .draw_series(AreaSeries::new(
96             kde_xs
97                 .iter()
98                 .zip(ys.iter())
99                 .skip(start)
100                 .take(len)
101                 .map(|(&x, &y)| (x, y)),
102             0.0,
103             DARK_BLUE.mix(0.25).filled().stroke_width(3),
104         ))
105         .unwrap()
106         .label("Confidence interval")
107         .legend(|(x, y)| {
108             Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled())
109         });
110 
111     chart
112         .draw_series(std::iter::once(PathElement::new(
113             vec![(point, 0.0), (point, y_point)],
114             DARK_BLUE.filled().stroke_width(3),
115         )))
116         .unwrap()
117         .label("Point estimate")
118         .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &DARK_BLUE));
119 
120     chart
121         .configure_series_labels()
122         .position(SeriesLabelPosition::UpperRight)
123         .draw()
124         .unwrap();
125 }
126 
abs_distributions( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option<(u32, u32)>, )127 pub(crate) fn abs_distributions(
128     id: &BenchmarkId,
129     context: &ReportContext,
130     formatter: &dyn ValueFormatter,
131     measurements: &MeasurementData<'_>,
132     size: Option<(u32, u32)>,
133 ) {
134     crate::plot::REPORT_STATS
135         .iter()
136         .filter_map(|stat| {
137             measurements.distributions.get(*stat).and_then(|dist| {
138                 measurements
139                     .absolute_estimates
140                     .get(*stat)
141                     .map(|est| (*stat, dist, est))
142             })
143         })
144         .for_each(|(statistic, distribution, estimate)| {
145             abs_distribution(
146                 id,
147                 context,
148                 formatter,
149                 statistic,
150                 distribution,
151                 estimate,
152                 size,
153             )
154         })
155 }
156 
rel_distribution( id: &BenchmarkId, context: &ReportContext, statistic: Statistic, distribution: &Distribution<f64>, estimate: &Estimate, noise_threshold: f64, size: Option<(u32, u32)>, )157 fn rel_distribution(
158     id: &BenchmarkId,
159     context: &ReportContext,
160     statistic: Statistic,
161     distribution: &Distribution<f64>,
162     estimate: &Estimate,
163     noise_threshold: f64,
164     size: Option<(u32, u32)>,
165 ) {
166     let ci = &estimate.confidence_interval;
167     let (lb, ub) = (ci.lower_bound, ci.upper_bound);
168 
169     let start = lb - (ub - lb) / 9.;
170     let end = ub + (ub - lb) / 9.;
171     let (xs, ys) = kde::sweep(distribution, KDE_POINTS, Some((start, end)));
172     let xs_ = Sample::new(&xs);
173 
174     // interpolate between two points of the KDE sweep to find the Y position at the point estimate.
175     let point = estimate.point_estimate;
176     let n_point = xs
177         .iter()
178         .position(|&x| x >= point)
179         .unwrap_or(ys.len() - 1)
180         .max(1);
181     let slope = (ys[n_point] - ys[n_point - 1]) / (xs[n_point] - xs[n_point - 1]);
182     let y_point = ys[n_point - 1] + (slope * (point - xs[n_point - 1]));
183 
184     let start = xs.iter().enumerate().find(|&(_, &x)| x >= lb).unwrap().0;
185     let end = xs
186         .iter()
187         .enumerate()
188         .rev()
189         .find(|&(_, &x)| x <= ub)
190         .unwrap()
191         .0;
192     let len = end - start;
193 
194     let x_min = xs_.min();
195     let x_max = xs_.max();
196 
197     let (fc_start, fc_end) = if noise_threshold < x_min || -noise_threshold > x_max {
198         let middle = (x_min + x_max) / 2.;
199 
200         (middle, middle)
201     } else {
202         (
203             if -noise_threshold < x_min {
204                 x_min
205             } else {
206                 -noise_threshold
207             },
208             if noise_threshold > x_max {
209                 x_max
210             } else {
211                 noise_threshold
212             },
213         )
214     };
215     let y_range = plotters::data::fitting_range(ys.iter());
216     let path = context.report_path(id, &format!("change/{}.svg", statistic));
217     let root_area = SVGBackend::new(&path, size.unwrap_or(SIZE)).into_drawing_area();
218 
219     let mut chart = ChartBuilder::on(&root_area)
220         .margin((5).percent())
221         .caption(
222             format!("{}:{}", id.as_title(), statistic),
223             (DEFAULT_FONT, 20),
224         )
225         .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
226         .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
227         .build_cartesian_2d(x_min..x_max, y_range.clone())
228         .unwrap();
229 
230     chart
231         .configure_mesh()
232         .disable_mesh()
233         .x_desc("Relative change (%)")
234         .y_desc("Density (a.u.)")
235         .x_label_formatter(&|&v| pretty_print_float(v, true))
236         .y_label_formatter(&|&v| pretty_print_float(v, true))
237         .draw()
238         .unwrap();
239 
240     chart
241         .draw_series(LineSeries::new(
242             xs.iter().zip(ys.iter()).map(|(x, y)| (*x, *y)),
243             &DARK_BLUE,
244         ))
245         .unwrap()
246         .label("Bootstrap distribution")
247         .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &DARK_BLUE));
248 
249     chart
250         .draw_series(AreaSeries::new(
251             xs.iter()
252                 .zip(ys.iter())
253                 .skip(start)
254                 .take(len)
255                 .map(|(x, y)| (*x, *y)),
256             0.0,
257             DARK_BLUE.mix(0.25).filled().stroke_width(3),
258         ))
259         .unwrap()
260         .label("Confidence interval")
261         .legend(|(x, y)| {
262             Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled())
263         });
264 
265     chart
266         .draw_series(std::iter::once(PathElement::new(
267             vec![(point, 0.0), (point, y_point)],
268             DARK_BLUE.filled().stroke_width(3),
269         )))
270         .unwrap()
271         .label("Point estimate")
272         .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &DARK_BLUE));
273 
274     chart
275         .draw_series(std::iter::once(Rectangle::new(
276             [(fc_start, y_range.start), (fc_end, y_range.end)],
277             DARK_RED.mix(0.1).filled(),
278         )))
279         .unwrap()
280         .label("Noise threshold")
281         .legend(|(x, y)| {
282             Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_RED.mix(0.25).filled())
283         });
284     chart
285         .configure_series_labels()
286         .position(SeriesLabelPosition::UpperRight)
287         .draw()
288         .unwrap();
289 }
290 
rel_distributions( id: &BenchmarkId, context: &ReportContext, _measurements: &MeasurementData<'_>, comparison: &ComparisonData, size: Option<(u32, u32)>, )291 pub(crate) fn rel_distributions(
292     id: &BenchmarkId,
293     context: &ReportContext,
294     _measurements: &MeasurementData<'_>,
295     comparison: &ComparisonData,
296     size: Option<(u32, u32)>,
297 ) {
298     crate::plot::CHANGE_STATS.iter().for_each(|&statistic| {
299         rel_distribution(
300             id,
301             context,
302             statistic,
303             comparison.relative_distributions.get(statistic),
304             comparison.relative_estimates.get(statistic),
305             comparison.noise_threshold,
306             size,
307         )
308     });
309 }
310