1 | use super::*; |
2 | |
3 | use std::path::Path; |
4 | |
5 | use crate::estimate::{ConfidenceInterval, Estimate}; |
6 | use crate::stats::bivariate::regression::Slope; |
7 | use crate::stats::bivariate::Data; |
8 | |
9 | pub(crate) fn regression_figure( |
10 | title: Option<&str>, |
11 | path: &Path, |
12 | formatter: &dyn ValueFormatter, |
13 | measurements: &MeasurementData<'_>, |
14 | size: Option<(u32, u32)>, |
15 | ) { |
16 | let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap(); |
17 | let slope_dist = measurements.distributions.slope.as_ref().unwrap(); |
18 | let (lb, ub) = |
19 | slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level); |
20 | |
21 | let data = &measurements.data; |
22 | let (max_iters, typical) = (data.x().max(), data.y().max()); |
23 | let mut scaled_y: Vec<f64> = data.y().iter().cloned().collect(); |
24 | let unit = formatter.scale_values(typical, &mut scaled_y); |
25 | let scaled_y = Sample::new(&scaled_y); |
26 | |
27 | let point_estimate = Slope::fit(&measurements.data).0; |
28 | let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters]; |
29 | let _ = formatter.scale_values(typical, &mut scaled_points); |
30 | let [point, lb, ub] = scaled_points; |
31 | |
32 | let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; |
33 | |
34 | let x_scale = 10f64.powi(-exponent); |
35 | let x_label = if exponent == 0 { |
36 | "Iterations" .to_owned() |
37 | } else { |
38 | format!("Iterations (x 10^{})" , exponent) |
39 | }; |
40 | |
41 | let size = size.unwrap_or(SIZE); |
42 | let root_area = SVGBackend::new(path, size).into_drawing_area(); |
43 | |
44 | let mut cb = ChartBuilder::on(&root_area); |
45 | if let Some(title) = title { |
46 | cb.caption(title, (DEFAULT_FONT, 20)); |
47 | } |
48 | |
49 | let x_range = plotters::data::fitting_range(data.x().iter()); |
50 | let y_range = plotters::data::fitting_range(scaled_y.iter()); |
51 | |
52 | let mut chart = cb |
53 | .margin((5).percent()) |
54 | .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60)) |
55 | .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40)) |
56 | .build_cartesian_2d(x_range, y_range) |
57 | .unwrap(); |
58 | |
59 | chart |
60 | .configure_mesh() |
61 | .x_desc(x_label) |
62 | .y_desc(format!("Total sample time ({})" , unit)) |
63 | .x_label_formatter(&|x| pretty_print_float(x * x_scale, true)) |
64 | .light_line_style(TRANSPARENT) |
65 | .draw() |
66 | .unwrap(); |
67 | |
68 | chart |
69 | .draw_series( |
70 | data.x() |
71 | .iter() |
72 | .zip(scaled_y.iter()) |
73 | .map(|(x, y)| Circle::new((*x, *y), POINT_SIZE, DARK_BLUE.filled())), |
74 | ) |
75 | .unwrap() |
76 | .label("Sample" ) |
77 | .legend(|(x, y)| Circle::new((x + 10, y), POINT_SIZE, DARK_BLUE.filled())); |
78 | |
79 | chart |
80 | .draw_series(std::iter::once(PathElement::new( |
81 | vec![(0.0, 0.0), (max_iters, point)], |
82 | DARK_BLUE, |
83 | ))) |
84 | .unwrap() |
85 | .label("Linear regression" ) |
86 | .legend(|(x, y)| { |
87 | PathElement::new( |
88 | vec![(x, y), (x + 20, y)], |
89 | DARK_BLUE.filled().stroke_width(2), |
90 | ) |
91 | }); |
92 | |
93 | chart |
94 | .draw_series(std::iter::once(Polygon::new( |
95 | vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)], |
96 | DARK_BLUE.mix(0.25).filled(), |
97 | ))) |
98 | .unwrap() |
99 | .label("Confidence interval" ) |
100 | .legend(|(x, y)| { |
101 | Rectangle::new([(x, y - 5), (x + 20, y + 5)], DARK_BLUE.mix(0.25).filled()) |
102 | }); |
103 | |
104 | if title.is_some() { |
105 | chart |
106 | .configure_series_labels() |
107 | .position(SeriesLabelPosition::UpperLeft) |
108 | .draw() |
109 | .unwrap(); |
110 | } |
111 | } |
112 | |
113 | pub(crate) fn regression_comparison_figure( |
114 | title: Option<&str>, |
115 | path: &Path, |
116 | formatter: &dyn ValueFormatter, |
117 | measurements: &MeasurementData<'_>, |
118 | comparison: &ComparisonData, |
119 | base_data: &Data<'_, f64, f64>, |
120 | size: Option<(u32, u32)>, |
121 | ) { |
122 | let data = &measurements.data; |
123 | let max_iters = base_data.x().max().max(data.x().max()); |
124 | let typical = base_data.y().max().max(data.y().max()); |
125 | |
126 | let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; |
127 | let x_scale = 10f64.powi(-exponent); |
128 | |
129 | let x_label = if exponent == 0 { |
130 | "Iterations" .to_owned() |
131 | } else { |
132 | format!("Iterations (x 10^{})" , exponent) |
133 | }; |
134 | |
135 | let Estimate { |
136 | confidence_interval: |
137 | ConfidenceInterval { |
138 | lower_bound: base_lb, |
139 | upper_bound: base_ub, |
140 | .. |
141 | }, |
142 | point_estimate: base_point, |
143 | .. |
144 | } = comparison.base_estimates.slope.as_ref().unwrap(); |
145 | |
146 | let Estimate { |
147 | confidence_interval: |
148 | ConfidenceInterval { |
149 | lower_bound: lb, |
150 | upper_bound: ub, |
151 | .. |
152 | }, |
153 | point_estimate: point, |
154 | .. |
155 | } = measurements.absolute_estimates.slope.as_ref().unwrap(); |
156 | |
157 | let mut points = [ |
158 | base_lb * max_iters, |
159 | base_point * max_iters, |
160 | base_ub * max_iters, |
161 | lb * max_iters, |
162 | point * max_iters, |
163 | ub * max_iters, |
164 | ]; |
165 | let unit = formatter.scale_values(typical, &mut points); |
166 | let [base_lb, base_point, base_ub, lb, point, ub] = points; |
167 | |
168 | let y_max = point.max(base_point); |
169 | |
170 | let size = size.unwrap_or(SIZE); |
171 | let root_area = SVGBackend::new(path, size).into_drawing_area(); |
172 | |
173 | let mut cb = ChartBuilder::on(&root_area); |
174 | if let Some(title) = title { |
175 | cb.caption(title, (DEFAULT_FONT, 20)); |
176 | } |
177 | |
178 | let mut chart = cb |
179 | .margin((5).percent()) |
180 | .set_label_area_size(LabelAreaPosition::Left, (5).percent_width().min(60)) |
181 | .set_label_area_size(LabelAreaPosition::Bottom, (5).percent_height().min(40)) |
182 | .build_cartesian_2d(0.0..max_iters, 0.0..y_max) |
183 | .unwrap(); |
184 | |
185 | chart |
186 | .configure_mesh() |
187 | .x_desc(x_label) |
188 | .y_desc(format!("Total sample time ({})" , unit)) |
189 | .x_label_formatter(&|x| pretty_print_float(x * x_scale, true)) |
190 | .light_line_style(TRANSPARENT) |
191 | .draw() |
192 | .unwrap(); |
193 | |
194 | chart |
195 | .draw_series(vec![ |
196 | PathElement::new(vec![(0.0, 0.0), (max_iters, base_point)], DARK_RED).into_dyn(), |
197 | Polygon::new( |
198 | vec![(0.0, 0.0), (max_iters, base_lb), (max_iters, base_ub)], |
199 | DARK_RED.mix(0.25).filled(), |
200 | ) |
201 | .into_dyn(), |
202 | ]) |
203 | .unwrap() |
204 | .label("Base Sample" ) |
205 | .legend(|(x, y)| { |
206 | PathElement::new(vec![(x, y), (x + 20, y)], DARK_RED.filled().stroke_width(2)) |
207 | }); |
208 | |
209 | chart |
210 | .draw_series(vec![ |
211 | PathElement::new(vec![(0.0, 0.0), (max_iters, point)], DARK_BLUE).into_dyn(), |
212 | Polygon::new( |
213 | vec![(0.0, 0.0), (max_iters, lb), (max_iters, ub)], |
214 | DARK_BLUE.mix(0.25).filled(), |
215 | ) |
216 | .into_dyn(), |
217 | ]) |
218 | .unwrap() |
219 | .label("New Sample" ) |
220 | .legend(|(x, y)| { |
221 | PathElement::new( |
222 | vec![(x, y), (x + 20, y)], |
223 | DARK_BLUE.filled().stroke_width(2), |
224 | ) |
225 | }); |
226 | |
227 | if title.is_some() { |
228 | chart |
229 | .configure_series_labels() |
230 | .position(SeriesLabelPosition::UpperLeft) |
231 | .draw() |
232 | .unwrap(); |
233 | } |
234 | } |
235 | |