1 use std::iter;
2 use std::process::Child;
3 
4 use crate::stats::univariate::Sample;
5 use crate::stats::Distribution;
6 use criterion_plot::prelude::*;
7 
8 use super::*;
9 use crate::estimate::Statistic;
10 use crate::kde;
11 use crate::measurement::ValueFormatter;
12 use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext};
13 use crate::Estimate;
14 
abs_distribution( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, statistic: Statistic, distribution: &Distribution<f64>, estimate: &Estimate, size: Option<Size>, ) -> Child15 fn abs_distribution(
16     id: &BenchmarkId,
17     context: &ReportContext,
18     formatter: &dyn ValueFormatter,
19     statistic: Statistic,
20     distribution: &Distribution<f64>,
21     estimate: &Estimate,
22     size: Option<Size>,
23 ) -> Child {
24     let ci = estimate.confidence_interval;
25     let typical = ci.upper_bound;
26     let mut ci_values = [ci.lower_bound, ci.upper_bound, estimate.point_estimate];
27     let unit = formatter.scale_values(typical, &mut ci_values);
28     let (lb, ub, point) = (ci_values[0], ci_values[1], ci_values[2]);
29 
30     let start = lb - (ub - lb) / 9.;
31     let end = ub + (ub - lb) / 9.;
32     let mut scaled_xs: Vec<f64> = distribution.iter().cloned().collect();
33     let _ = formatter.scale_values(typical, &mut scaled_xs);
34     let scaled_xs_sample = Sample::new(&scaled_xs);
35     let (kde_xs, ys) = kde::sweep(scaled_xs_sample, KDE_POINTS, Some((start, end)));
36 
37     let n_point = kde_xs
38         .iter()
39         .position(|&x| x >= point)
40         .unwrap_or(kde_xs.len() - 1);
41     let n_point = n_point.max(1); // Must be at least the second element or this will panic
42     let slope = (ys[n_point] - ys[n_point - 1]) / (kde_xs[n_point] - kde_xs[n_point - 1]);
43     let y_point = ys[n_point - 1] + (slope * (point - kde_xs[n_point - 1]));
44 
45     let zero = iter::repeat(0);
46 
47     let start = kde_xs
48         .iter()
49         .enumerate()
50         .find(|&(_, &x)| x >= lb)
51         .unwrap()
52         .0;
53     let end = kde_xs
54         .iter()
55         .enumerate()
56         .rev()
57         .find(|&(_, &x)| x <= ub)
58         .unwrap()
59         .0;
60     let len = end - start;
61 
62     let kde_xs_sample = Sample::new(&kde_xs);
63 
64     let mut figure = Figure::new();
65     figure
66         .set(Font(DEFAULT_FONT))
67         .set(size.unwrap_or(SIZE))
68         .set(Title(format!(
69             "{}: {}",
70             escape_underscores(id.as_title()),
71             statistic
72         )))
73         .configure(Axis::BottomX, |a| {
74             a.set(Label(format!("Average time ({})", unit)))
75                 .set(Range::Limits(kde_xs_sample.min(), kde_xs_sample.max()))
76         })
77         .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)")))
78         .configure(Key, |k| {
79             k.set(Justification::Left)
80                 .set(Order::SampleText)
81                 .set(Position::Outside(Vertical::Top, Horizontal::Right))
82         })
83         .plot(
84             Lines {
85                 x: &*kde_xs,
86                 y: &*ys,
87             },
88             |c| {
89                 c.set(DARK_BLUE)
90                     .set(LINEWIDTH)
91                     .set(Label("Bootstrap distribution"))
92                     .set(LineType::Solid)
93             },
94         )
95         .plot(
96             FilledCurve {
97                 x: kde_xs.iter().skip(start).take(len),
98                 y1: ys.iter().skip(start),
99                 y2: zero,
100             },
101             |c| {
102                 c.set(DARK_BLUE)
103                     .set(Label("Confidence interval"))
104                     .set(Opacity(0.25))
105             },
106         )
107         .plot(
108             Lines {
109                 x: &[point, point],
110                 y: &[0., y_point],
111             },
112             |c| {
113                 c.set(DARK_BLUE)
114                     .set(LINEWIDTH)
115                     .set(Label("Point estimate"))
116                     .set(LineType::Dash)
117             },
118         );
119 
120     let path = context.report_path(id, &format!("{}.svg", statistic));
121     debug_script(&path, &figure);
122     figure.set(Output(path)).draw().unwrap()
123 }
124 
abs_distributions( id: &BenchmarkId, context: &ReportContext, formatter: &dyn ValueFormatter, measurements: &MeasurementData<'_>, size: Option<Size>, ) -> Vec<Child>125 pub(crate) fn abs_distributions(
126     id: &BenchmarkId,
127     context: &ReportContext,
128     formatter: &dyn ValueFormatter,
129     measurements: &MeasurementData<'_>,
130     size: Option<Size>,
131 ) -> Vec<Child> {
132     measurements
133         .distributions
134         .iter()
135         .map(|(&statistic, distribution)| {
136             abs_distribution(
137                 id,
138                 context,
139                 formatter,
140                 statistic,
141                 distribution,
142                 &measurements.absolute_estimates[&statistic],
143                 size,
144             )
145         })
146         .collect::<Vec<_>>()
147 }
148 
rel_distribution( id: &BenchmarkId, context: &ReportContext, statistic: Statistic, distribution: &Distribution<f64>, estimate: &Estimate, noise_threshold: f64, size: Option<Size>, ) -> Child149 fn rel_distribution(
150     id: &BenchmarkId,
151     context: &ReportContext,
152     statistic: Statistic,
153     distribution: &Distribution<f64>,
154     estimate: &Estimate,
155     noise_threshold: f64,
156     size: Option<Size>,
157 ) -> Child {
158     let ci = estimate.confidence_interval;
159     let (lb, ub) = (ci.lower_bound, ci.upper_bound);
160 
161     let start = lb - (ub - lb) / 9.;
162     let end = ub + (ub - lb) / 9.;
163     let (xs, ys) = kde::sweep(distribution, KDE_POINTS, Some((start, end)));
164     let xs_ = Sample::new(&xs);
165 
166     let point = estimate.point_estimate;
167     let n_point = xs
168         .iter()
169         .position(|&x| x >= point)
170         .unwrap_or(ys.len() - 1)
171         .max(1);
172     let slope = (ys[n_point] - ys[n_point - 1]) / (xs[n_point] - xs[n_point - 1]);
173     let y_point = ys[n_point - 1] + (slope * (point - xs[n_point - 1]));
174 
175     let one = iter::repeat(1);
176     let zero = iter::repeat(0);
177 
178     let start = xs.iter().enumerate().find(|&(_, &x)| x >= lb).unwrap().0;
179     let end = xs
180         .iter()
181         .enumerate()
182         .rev()
183         .find(|&(_, &x)| x <= ub)
184         .unwrap()
185         .0;
186     let len = end - start;
187 
188     let x_min = xs_.min();
189     let x_max = xs_.max();
190 
191     let (fc_start, fc_end) = if noise_threshold < x_min || -noise_threshold > x_max {
192         let middle = (x_min + x_max) / 2.;
193 
194         (middle, middle)
195     } else {
196         (
197             if -noise_threshold < x_min {
198                 x_min
199             } else {
200                 -noise_threshold
201             },
202             if noise_threshold > x_max {
203                 x_max
204             } else {
205                 noise_threshold
206             },
207         )
208     };
209 
210     let mut figure = Figure::new();
211 
212     figure
213         .set(Font(DEFAULT_FONT))
214         .set(size.unwrap_or(SIZE))
215         .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)")))
216         .configure(Key, |k| {
217             k.set(Justification::Left)
218                 .set(Order::SampleText)
219                 .set(Position::Outside(Vertical::Top, Horizontal::Right))
220         })
221         .set(Title(format!(
222             "{}: {}",
223             escape_underscores(id.as_title()),
224             statistic
225         )))
226         .configure(Axis::BottomX, |a| {
227             a.set(Label("Relative change (%)"))
228                 .set(Range::Limits(x_min * 100., x_max * 100.))
229                 .set(ScaleFactor(100.))
230         })
231         .plot(Lines { x: &*xs, y: &*ys }, |c| {
232             c.set(DARK_BLUE)
233                 .set(LINEWIDTH)
234                 .set(Label("Bootstrap distribution"))
235                 .set(LineType::Solid)
236         })
237         .plot(
238             FilledCurve {
239                 x: xs.iter().skip(start).take(len),
240                 y1: ys.iter().skip(start),
241                 y2: zero.clone(),
242             },
243             |c| {
244                 c.set(DARK_BLUE)
245                     .set(Label("Confidence interval"))
246                     .set(Opacity(0.25))
247             },
248         )
249         .plot(
250             Lines {
251                 x: &[point, point],
252                 y: &[0., y_point],
253             },
254             |c| {
255                 c.set(DARK_BLUE)
256                     .set(LINEWIDTH)
257                     .set(Label("Point estimate"))
258                     .set(LineType::Dash)
259             },
260         )
261         .plot(
262             FilledCurve {
263                 x: &[fc_start, fc_end],
264                 y1: one,
265                 y2: zero,
266             },
267             |c| {
268                 c.set(Axes::BottomXRightY)
269                     .set(DARK_RED)
270                     .set(Label("Noise threshold"))
271                     .set(Opacity(0.1))
272             },
273         );
274 
275     let path = context.report_path(id, &format!("change/{}.svg", statistic));
276     debug_script(&path, &figure);
277     figure.set(Output(path)).draw().unwrap()
278 }
279 
rel_distributions( id: &BenchmarkId, context: &ReportContext, _measurements: &MeasurementData<'_>, comparison: &ComparisonData, size: Option<Size>, ) -> Vec<Child>280 pub(crate) fn rel_distributions(
281     id: &BenchmarkId,
282     context: &ReportContext,
283     _measurements: &MeasurementData<'_>,
284     comparison: &ComparisonData,
285     size: Option<Size>,
286 ) -> Vec<Child> {
287     comparison
288         .relative_distributions
289         .iter()
290         .map(|(&statistic, distribution)| {
291             rel_distribution(
292                 id,
293                 context,
294                 statistic,
295                 distribution,
296                 &comparison.relative_estimates[&statistic],
297                 comparison.noise_threshold,
298                 size,
299             )
300         })
301         .collect::<Vec<_>>()
302 }
303