1use super::*;
2use crate::measurement::ValueFormatter;
3use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext};
4use plotters::data;
5use plotters::style::RGBAColor;
6use std::path::Path;
7
8pub(crate) fn pdf_comparison_figure(
9 path: &Path,
10 title: Option<&str>,
11 formatter: &dyn ValueFormatter,
12 measurements: &MeasurementData<'_>,
13 comparison: &ComparisonData,
14 size: Option<(u32, u32)>,
15) {
16 let base_avg_times = Sample::new(&comparison.base_avg_times);
17 let typical = base_avg_times.max().max(measurements.avg_times.max());
18 let mut scaled_base_avg_times: Vec<f64> = comparison.base_avg_times.clone();
19 let unit = formatter.scale_values(typical, &mut scaled_base_avg_times);
20 let scaled_base_avg_times = Sample::new(&scaled_base_avg_times);
21
22 let mut scaled_new_avg_times: Vec<f64> = (&measurements.avg_times as &Sample<f64>)
23 .iter()
24 .cloned()
25 .collect();
26 let _ = formatter.scale_values(typical, &mut scaled_new_avg_times);
27 let scaled_new_avg_times = Sample::new(&scaled_new_avg_times);
28
29 let base_mean = scaled_base_avg_times.mean();
30 let new_mean = scaled_new_avg_times.mean();
31
32 let (base_xs, base_ys, base_y_mean) =
33 kde::sweep_and_estimate(scaled_base_avg_times, KDE_POINTS, None, base_mean);
34 let (xs, ys, y_mean) =
35 kde::sweep_and_estimate(scaled_new_avg_times, KDE_POINTS, None, new_mean);
36
37 let x_range = data::fitting_range(base_xs.iter().chain(xs.iter()));
38 let y_range = data::fitting_range(base_ys.iter().chain(ys.iter()));
39
40 let size = size.unwrap_or(SIZE);
41 let root_area = SVGBackend::new(&path, (size.0, size.1)).into_drawing_area();
42
43 let mut cb = ChartBuilder::on(&root_area);
44
45 if let Some(title) = title {
46 cb.caption(title, (DEFAULT_FONT, 20));
47 }
48
49 let mut chart = cb
50 .margin((5).percent())
51 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
52 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
53 .build_cartesian_2d(x_range, y_range.clone())
54 .unwrap();
55
56 chart
57 .configure_mesh()
58 .disable_mesh()
59 .y_desc("Density (a.u.)")
60 .x_desc(format!("Average Time ({})", unit))
61 .x_label_formatter(&|&x| pretty_print_float(x, true))
62 .y_label_formatter(&|&y| pretty_print_float(y, true))
63 .x_labels(5)
64 .draw()
65 .unwrap();
66
67 chart
68 .draw_series(AreaSeries::new(
69 base_xs.iter().zip(base_ys.iter()).map(|(x, y)| (*x, *y)),
70 y_range.start,
71 DARK_RED.mix(0.5).filled(),
72 ))
73 .unwrap()
74 .label("Base PDF")
75 .legend(|(x, y)| Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_RED.mix(0.5).filled()));
76
77 chart
78 .draw_series(AreaSeries::new(
79 xs.iter().zip(ys.iter()).map(|(x, y)| (*x, *y)),
80 y_range.start,
81 DARK_BLUE.mix(0.5).filled(),
82 ))
83 .unwrap()
84 .label("New PDF")
85 .legend(|(x, y)| {
86 Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.5).filled())
87 });
88
89 chart
90 .draw_series(std::iter::once(PathElement::new(
91 vec![(base_mean, 0.0), (base_mean, base_y_mean)],
92 DARK_RED.filled().stroke_width(2),
93 )))
94 .unwrap()
95 .label("Base Mean")
96 .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], DARK_RED));
97
98 chart
99 .draw_series(std::iter::once(PathElement::new(
100 vec![(new_mean, 0.0), (new_mean, y_mean)],
101 DARK_BLUE.filled().stroke_width(2),
102 )))
103 .unwrap()
104 .label("New Mean")
105 .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], DARK_BLUE));
106
107 if title.is_some() {
108 chart.configure_series_labels().draw().unwrap();
109 }
110}
111
112pub(crate) fn pdf_small(
113 id: &BenchmarkId,
114 context: &ReportContext,
115 formatter: &dyn ValueFormatter,
116 measurements: &MeasurementData<'_>,
117 size: Option<(u32, u32)>,
118) {
119 let avg_times = &*measurements.avg_times;
120 let typical = avg_times.max();
121 let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect();
122 let unit = formatter.scale_values(typical, &mut scaled_avg_times);
123 let scaled_avg_times = Sample::new(&scaled_avg_times);
124 let mean = scaled_avg_times.mean();
125
126 let (xs, ys, mean_y) = kde::sweep_and_estimate(scaled_avg_times, KDE_POINTS, None, mean);
127 let xs_ = Sample::new(&xs);
128 let ys_ = Sample::new(&ys);
129
130 let y_limit = ys_.max() * 1.1;
131
132 let path = context.report_path(id, "pdf_small.svg");
133
134 let size = size.unwrap_or(SIZE);
135 let root_area = SVGBackend::new(&path, (size.0, size.1)).into_drawing_area();
136
137 let mut chart = ChartBuilder::on(&root_area)
138 .margin((5).percent())
139 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
140 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
141 .build_cartesian_2d(xs_.min()..xs_.max(), 0.0..y_limit)
142 .unwrap();
143
144 chart
145 .configure_mesh()
146 .disable_mesh()
147 .y_desc("Density (a.u.)")
148 .x_desc(format!("Average Time ({})", unit))
149 .x_label_formatter(&|&x| pretty_print_float(x, true))
150 .y_label_formatter(&|&y| pretty_print_float(y, true))
151 .x_labels(5)
152 .draw()
153 .unwrap();
154
155 chart
156 .draw_series(AreaSeries::new(
157 xs.iter().zip(ys.iter()).map(|(x, y)| (*x, *y)),
158 0.0,
159 DARK_BLUE.mix(0.25).filled(),
160 ))
161 .unwrap();
162
163 chart
164 .draw_series(std::iter::once(PathElement::new(
165 vec![(mean, 0.0), (mean, mean_y)],
166 DARK_BLUE.filled().stroke_width(2),
167 )))
168 .unwrap();
169}
170
171pub(crate) fn pdf(
172 id: &BenchmarkId,
173 context: &ReportContext,
174 formatter: &dyn ValueFormatter,
175 measurements: &MeasurementData<'_>,
176 size: Option<(u32, u32)>,
177) {
178 let avg_times = &measurements.avg_times;
179 let typical = avg_times.max();
180 let mut scaled_avg_times: Vec<f64> = (avg_times as &Sample<f64>).iter().cloned().collect();
181 let unit = formatter.scale_values(typical, &mut scaled_avg_times);
182 let scaled_avg_times = Sample::new(&scaled_avg_times);
183
184 let mean = scaled_avg_times.mean();
185
186 let iter_counts = measurements.iter_counts();
187 let &max_iters = iter_counts
188 .iter()
189 .max_by_key(|&&iters| iters as u64)
190 .unwrap();
191 let exponent = (max_iters.log10() / 3.).floor() as i32 * 3;
192 let y_scale = 10f64.powi(-exponent);
193
194 let y_label = if exponent == 0 {
195 "Iterations".to_owned()
196 } else {
197 format!("Iterations (x 10^{})", exponent)
198 };
199
200 let (xs, ys) = kde::sweep(scaled_avg_times, KDE_POINTS, None);
201 let (lost, lomt, himt, hist) = avg_times.fences();
202 let mut fences = [lost, lomt, himt, hist];
203 let _ = formatter.scale_values(typical, &mut fences);
204 let [lost, lomt, himt, hist] = fences;
205
206 let path = context.report_path(id, "pdf.svg");
207
208 let xs_ = Sample::new(&xs);
209
210 let size = size.unwrap_or(SIZE);
211 let root_area = SVGBackend::new(&path, (size.0, size.1)).into_drawing_area();
212
213 let range = data::fitting_range(ys.iter());
214
215 let mut chart = ChartBuilder::on(&root_area)
216 .margin((5).percent())
217 .caption(id.as_title(), (DEFAULT_FONT, 20))
218 .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60))
219 .set_label_area_size(LabelAreaPosition::Right, (5).percent_width().min(60))
220 .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40))
221 .build_cartesian_2d(xs_.min()..xs_.max(), 0.0..max_iters)
222 .unwrap()
223 .set_secondary_coord(xs_.min()..xs_.max(), 0.0..range.end);
224
225 chart
226 .configure_mesh()
227 .disable_mesh()
228 .y_desc(y_label)
229 .x_desc(format!("Average Time ({})", unit))
230 .x_label_formatter(&|&x| pretty_print_float(x, true))
231 .y_label_formatter(&|&y| pretty_print_float(y * y_scale, true))
232 .draw()
233 .unwrap();
234
235 chart
236 .configure_secondary_axes()
237 .y_desc("Density (a.u.)")
238 .x_label_formatter(&|&x| pretty_print_float(x, true))
239 .y_label_formatter(&|&y| pretty_print_float(y, true))
240 .draw()
241 .unwrap();
242
243 chart
244 .draw_secondary_series(AreaSeries::new(
245 xs.iter().zip(ys.iter()).map(|(x, y)| (*x, *y)),
246 0.0,
247 DARK_BLUE.mix(0.5).filled(),
248 ))
249 .unwrap()
250 .label("PDF")
251 .legend(|(x, y)| {
252 Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.5).filled())
253 });
254
255 chart
256 .draw_series(std::iter::once(PathElement::new(
257 vec![(mean, 0.0), (mean, max_iters)],
258 DARK_BLUE,
259 )))
260 .unwrap()
261 .label("Mean")
262 .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], DARK_BLUE));
263
264 chart
265 .draw_series(vec![
266 PathElement::new(vec![(lomt, 0.0), (lomt, max_iters)], DARK_ORANGE),
267 PathElement::new(vec![(himt, 0.0), (himt, max_iters)], DARK_ORANGE),
268 PathElement::new(vec![(lost, 0.0), (lost, max_iters)], DARK_RED),
269 PathElement::new(vec![(hist, 0.0), (hist, max_iters)], DARK_RED),
270 ])
271 .unwrap();
272 use crate::stats::univariate::outliers::tukey::Label;
273
274 let mut draw_data_point_series =
275 |filter: &dyn Fn(&Label) -> bool, color: RGBAColor, name: &str| {
276 chart
277 .draw_series(
278 avg_times
279 .iter()
280 .zip(scaled_avg_times.iter())
281 .zip(iter_counts.iter())
282 .filter_map(|(((_, label), t), i)| {
283 if filter(&label) {
284 Some(Circle::new((*t, *i), POINT_SIZE, color.filled()))
285 } else {
286 None
287 }
288 }),
289 )
290 .unwrap()
291 .label(name)
292 .legend(move |(x, y)| Circle::new((x + 10, y), POINT_SIZE, color.filled()));
293 };
294
295 draw_data_point_series(
296 &|l| !l.is_outlier(),
297 DARK_BLUE.to_rgba(),
298 "\"Clean\" sample",
299 );
300 draw_data_point_series(
301 &|l| l.is_mild(),
302 RGBColor(255, 127, 0).to_rgba(),
303 "Mild outliers",
304 );
305 draw_data_point_series(&|l| l.is_severe(), DARK_RED.to_rgba(), "Severe outliers");
306 chart.configure_series_labels().draw().unwrap();
307}
308