1use super::*;
2
3use std::path::Path;
4
5use crate::estimate::{ConfidenceInterval, Estimate};
6use crate::stats::bivariate::regression::Slope;
7use crate::stats::bivariate::Data;
8
9pub(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
113pub(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