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