criterion/plot/gnuplot_backend/
pdf.rs

1use super::*;
2use crate::kde;
3use crate::measurement::ValueFormatter;
4use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext};
5use std::process::Child;
6
7pub(crate) fn pdf(
8    id: &BenchmarkId,
9    context: &ReportContext,
10    formatter: &dyn ValueFormatter,
11    measurements: &MeasurementData<'_>,
12    size: Option<Size>,
13) -> Child {
14    let avg_times = &measurements.avg_times;
15    let typical = avg_times.max();
16    let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect();
17    let unit = formatter.scale_values(typical, &mut scaled_avg_times);
18    let scaled_avg_times = Sample::new(&scaled_avg_times);
19
20    let mean = scaled_avg_times.mean();
21
22    let iter_counts = measurements.iter_counts();
23    let &max_iters = iter_counts
24        .iter()
25        .max_by_key(|&&iters| iters as u64)
26        .unwrap();
27    let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
28    let y_scale = 10f64.powi(-exponent);
29
30    let y_label = if exponent == 0 {
31        "Iterations".to_owned()
32    } else {
33        format!("Iterations (x 10^{})", exponent)
34    };
35
36    let (xs, ys) = kde::sweep(scaled_avg_times, KDE_POINTS, None);
37    let (lost, lomt, himt, hist) = avg_times.fences();
38    let mut fences = [lost, lomt, himt, hist];
39    let _ = formatter.scale_values(typical, &mut fences);
40    let [lost, lomt, himt, hist] = fences;
41
42    let vertical = &[0., max_iters];
43    let zeros = iter::repeat(0);
44
45    let mut figure = Figure::new();
46    figure
47        .set(Font(DEFAULT_FONT))
48        .set(size.unwrap_or(SIZE))
49        .configure(Axis::BottomX, |a| {
50            let xs_ = Sample::new(&xs);
51            a.set(Label(format!("Average time ({})", unit)))
52                .set(Range::Limits(xs_.min(), xs_.max()))
53        })
54        .configure(Axis::LeftY, |a| {
55            a.set(Label(y_label))
56                .set(Range::Limits(0., max_iters * y_scale))
57                .set(ScaleFactor(y_scale))
58        })
59        .configure(Axis::RightY, |a| a.set(Label("Density (a.u.)")))
60        .configure(Key, |k| {
61            k.set(Justification::Left)
62                .set(Order::SampleText)
63                .set(Position::Outside(Vertical::Top, Horizontal::Right))
64        })
65        .plot(
66            FilledCurve {
67                x: &*xs,
68                y1: &*ys,
69                y2: zeros,
70            },
71            |c| {
72                c.set(Axes::BottomXRightY)
73                    .set(DARK_BLUE)
74                    .set(Label("PDF"))
75                    .set(Opacity(0.25))
76            },
77        )
78        .plot(
79            Lines {
80                x: &[mean, mean],
81                y: vertical,
82            },
83            |c| {
84                c.set(DARK_BLUE)
85                    .set(LINEWIDTH)
86                    .set(LineType::Dash)
87                    .set(Label("Mean"))
88            },
89        )
90        .plot(
91            Points {
92                x: avg_times
93                    .iter()
94                    .zip(scaled_avg_times.iter())
95                    .filter_map(
96                        |((_, label), t)| {
97                            if label.is_outlier() {
98                                None
99                            } else {
100                                Some(t)
101                            }
102                        },
103                    ),
104                y: avg_times
105                    .iter()
106                    .zip(iter_counts.iter())
107                    .filter_map(
108                        |((_, label), i)| {
109                            if label.is_outlier() {
110                                None
111                            } else {
112                                Some(i)
113                            }
114                        },
115                    ),
116            },
117            |c| {
118                c.set(DARK_BLUE)
119                    .set(Label("\"Clean\" sample"))
120                    .set(PointType::FilledCircle)
121                    .set(POINT_SIZE)
122            },
123        )
124        .plot(
125            Points {
126                x: avg_times
127                    .iter()
128                    .zip(scaled_avg_times.iter())
129                    .filter_map(
130                        |((_, label), t)| {
131                            if label.is_mild() {
132                                Some(t)
133                            } else {
134                                None
135                            }
136                        },
137                    ),
138                y: avg_times
139                    .iter()
140                    .zip(iter_counts.iter())
141                    .filter_map(
142                        |((_, label), i)| {
143                            if label.is_mild() {
144                                Some(i)
145                            } else {
146                                None
147                            }
148                        },
149                    ),
150            },
151            |c| {
152                c.set(DARK_ORANGE)
153                    .set(Label("Mild outliers"))
154                    .set(POINT_SIZE)
155                    .set(PointType::FilledCircle)
156            },
157        )
158        .plot(
159            Points {
160                x: avg_times
161                    .iter()
162                    .zip(scaled_avg_times.iter())
163                    .filter_map(
164                        |((_, label), t)| {
165                            if label.is_severe() {
166                                Some(t)
167                            } else {
168                                None
169                            }
170                        },
171                    ),
172                y: avg_times
173                    .iter()
174                    .zip(iter_counts.iter())
175                    .filter_map(
176                        |((_, label), i)| {
177                            if label.is_severe() {
178                                Some(i)
179                            } else {
180                                None
181                            }
182                        },
183                    ),
184            },
185            |c| {
186                c.set(DARK_RED)
187                    .set(Label("Severe outliers"))
188                    .set(POINT_SIZE)
189                    .set(PointType::FilledCircle)
190            },
191        )
192        .plot(
193            Lines {
194                x: &[lomt, lomt],
195                y: vertical,
196            },
197            |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash),
198        )
199        .plot(
200            Lines {
201                x: &[himt, himt],
202                y: vertical,
203            },
204            |c| c.set(DARK_ORANGE).set(LINEWIDTH).set(LineType::Dash),
205        )
206        .plot(
207            Lines {
208                x: &[lost, lost],
209                y: vertical,
210            },
211            |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash),
212        )
213        .plot(
214            Lines {
215                x: &[hist, hist],
216                y: vertical,
217            },
218            |c| c.set(DARK_RED).set(LINEWIDTH).set(LineType::Dash),
219        );
220    figure.set(Title(gnuplot_escape(id.as_title())));
221
222    let path = context.report_path(id, "pdf.svg");
223    debug_script(&path, &figure);
224    figure.set(Output(path)).draw().unwrap()
225}
226
227pub(crate) fn pdf_small(
228    id: &BenchmarkId,
229    context: &ReportContext,
230    formatter: &dyn ValueFormatter,
231    measurements: &MeasurementData<'_>,
232    size: Option<Size>,
233) -> Child {
234    let avg_times = &*measurements.avg_times;
235    let typical = avg_times.max();
236    let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect();
237    let unit = formatter.scale_values(typical, &mut scaled_avg_times);
238    let scaled_avg_times = Sample::new(&scaled_avg_times);
239    let mean = scaled_avg_times.mean();
240
241    let (xs, ys, mean_y) = kde::sweep_and_estimate(scaled_avg_times, KDE_POINTS, None, mean);
242    let xs_ = Sample::new(&xs);
243    let ys_ = Sample::new(&ys);
244
245    let y_limit = ys_.max() * 1.1;
246    let zeros = iter::repeat(0);
247
248    let mut figure = Figure::new();
249    figure
250        .set(Font(DEFAULT_FONT))
251        .set(size.unwrap_or(SIZE))
252        .configure(Axis::BottomX, |a| {
253            a.set(Label(format!("Average time ({})", unit)))
254                .set(Range::Limits(xs_.min(), xs_.max()))
255        })
256        .configure(Axis::LeftY, |a| {
257            a.set(Label("Density (a.u.)"))
258                .set(Range::Limits(0., y_limit))
259        })
260        .configure(Axis::RightY, |a| a.hide())
261        .configure(Key, |k| k.hide())
262        .plot(
263            FilledCurve {
264                x: &*xs,
265                y1: &*ys,
266                y2: zeros,
267            },
268            |c| {
269                c.set(Axes::BottomXRightY)
270                    .set(DARK_BLUE)
271                    .set(Label("PDF"))
272                    .set(Opacity(0.25))
273            },
274        )
275        .plot(
276            Lines {
277                x: &[mean, mean],
278                y: &[0., mean_y],
279            },
280            |c| c.set(DARK_BLUE).set(LINEWIDTH).set(Label("Mean")),
281        );
282
283    let path = context.report_path(id, "pdf_small.svg");
284    debug_script(&path, &figure);
285    figure.set(Output(path)).draw().unwrap()
286}
287
288fn pdf_comparison_figure(
289    formatter: &dyn ValueFormatter,
290    measurements: &MeasurementData<'_>,
291    comparison: &ComparisonData,
292    size: Option<Size>,
293) -> Figure {
294    let base_avg_times = Sample::new(&comparison.base_avg_times);
295    let typical = base_avg_times.max().max(measurements.avg_times.max());
296    let mut scaled_base_avg_times: Vec<f64> = comparison.base_avg_times.clone();
297    let unit = formatter.scale_values(typical, &mut scaled_base_avg_times);
298    let scaled_base_avg_times = Sample::new(&scaled_base_avg_times);
299
300    let mut scaled_new_avg_times: Vec<f64> = (&measurements.avg_times as &Sample<f64>)
301        .iter()
302        .cloned()
303        .collect();
304    let _ = formatter.scale_values(typical, &mut scaled_new_avg_times);
305    let scaled_new_avg_times = Sample::new(&scaled_new_avg_times);
306
307    let base_mean = scaled_base_avg_times.mean();
308    let new_mean = scaled_new_avg_times.mean();
309
310    let (base_xs, base_ys, base_y_mean) =
311        kde::sweep_and_estimate(scaled_base_avg_times, KDE_POINTS, None, base_mean);
312    let (xs, ys, y_mean) =
313        kde::sweep_and_estimate(scaled_new_avg_times, KDE_POINTS, None, new_mean);
314
315    let zeros = iter::repeat(0);
316
317    let mut figure = Figure::new();
318    figure
319        .set(Font(DEFAULT_FONT))
320        .set(size.unwrap_or(SIZE))
321        .configure(Axis::BottomX, |a| {
322            a.set(Label(format!("Average time ({})", unit)))
323        })
324        .configure(Axis::LeftY, |a| a.set(Label("Density (a.u.)")))
325        .configure(Axis::RightY, |a| a.hide())
326        .configure(Key, |k| {
327            k.set(Justification::Left)
328                .set(Order::SampleText)
329                .set(Position::Outside(Vertical::Top, Horizontal::Right))
330        })
331        .plot(
332            FilledCurve {
333                x: &*base_xs,
334                y1: &*base_ys,
335                y2: zeros.clone(),
336            },
337            |c| c.set(DARK_RED).set(Label("Base PDF")).set(Opacity(0.5)),
338        )
339        .plot(
340            Lines {
341                x: &[base_mean, base_mean],
342                y: &[0., base_y_mean],
343            },
344            |c| c.set(DARK_RED).set(Label("Base Mean")).set(LINEWIDTH),
345        )
346        .plot(
347            FilledCurve {
348                x: &*xs,
349                y1: &*ys,
350                y2: zeros,
351            },
352            |c| c.set(DARK_BLUE).set(Label("New PDF")).set(Opacity(0.5)),
353        )
354        .plot(
355            Lines {
356                x: &[new_mean, new_mean],
357                y: &[0., y_mean],
358            },
359            |c| c.set(DARK_BLUE).set(Label("New Mean")).set(LINEWIDTH),
360        );
361    figure
362}
363
364pub(crate) fn pdf_comparison(
365    id: &BenchmarkId,
366    context: &ReportContext,
367    formatter: &dyn ValueFormatter,
368    measurements: &MeasurementData<'_>,
369    comparison: &ComparisonData,
370    size: Option<Size>,
371) -> Child {
372    let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size);
373    figure.set(Title(gnuplot_escape(id.as_title())));
374    let path = context.report_path(id, "both/pdf.svg");
375    debug_script(&path, &figure);
376    figure.set(Output(path)).draw().unwrap()
377}
378
379pub(crate) fn pdf_comparison_small(
380    id: &BenchmarkId,
381    context: &ReportContext,
382    formatter: &dyn ValueFormatter,
383    measurements: &MeasurementData<'_>,
384    comparison: &ComparisonData,
385    size: Option<Size>,
386) -> Child {
387    let mut figure = pdf_comparison_figure(formatter, measurements, comparison, size);
388    figure.configure(Key, |k| k.hide());
389    let path = context.report_path(id, "relative_pdf_small.svg");
390    debug_script(&path, &figure);
391    figure.set(Output(path)).draw().unwrap()
392}