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