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