1 | use std::process::Child; |
---|---|

2 | |

3 | use crate::stats::bivariate::regression::Slope; |

4 | use criterion_plot::prelude::*; |

5 | |

6 | use super::*; |

7 | use crate::report::{BenchmarkId, ComparisonData, MeasurementData, ReportContext}; |

8 | use crate::stats::bivariate::Data; |

9 | |

10 | use crate::estimate::{ConfidenceInterval, Estimate}; |

11 | |

12 | use crate::measurement::ValueFormatter; |

13 | |

14 | fn regression_figure( |

15 | formatter: &dyn ValueFormatter, |

16 | measurements: &MeasurementData<'_>, |

17 | size: Option<Size>, |

18 | ) -> Figure { |

19 | let slope_estimate = measurements.absolute_estimates.slope.as_ref().unwrap(); |

20 | let slope_dist = measurements.distributions.slope.as_ref().unwrap(); |

21 | let (lb, ub) = |

22 | slope_dist.confidence_interval(slope_estimate.confidence_interval.confidence_level); |

23 | |

24 | let data = &measurements.data; |

25 | let (max_iters, typical) = (data.x().max(), data.y().max()); |

26 | let mut scaled_y: Vec<f64> = data.y().iter().cloned().collect(); |

27 | let unit = formatter.scale_values(typical, &mut scaled_y); |

28 | let scaled_y = Sample::new(&scaled_y); |

29 | |

30 | let point_estimate = Slope::fit(&measurements.data).0; |

31 | let mut scaled_points = [point_estimate * max_iters, lb * max_iters, ub * max_iters]; |

32 | let _ = formatter.scale_values(typical, &mut scaled_points); |

33 | let [point, lb, ub] = scaled_points; |

34 | |

35 | let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; |

36 | let x_scale = 10f64.powi(-exponent); |

37 | |

38 | let x_label = if exponent == 0 { |

39 | "Iterations".to_owned() |

40 | } else { |

41 | format!("Iterations (x 10^{})", exponent) |

42 | }; |

43 | |

44 | let mut figure = Figure::new(); |

45 | figure |

46 | .set(Font(DEFAULT_FONT)) |

47 | .set(size.unwrap_or(SIZE)) |

48 | .configure(Axis::BottomX, |a| { |

49 | a.configure(Grid::Major, |g| g.show()) |

50 | .set(Label(x_label)) |

51 | .set(ScaleFactor(x_scale)) |

52 | }) |

53 | .configure(Axis::LeftY, |a| { |

54 | a.configure(Grid::Major, |g| g.show()) |

55 | .set(Label(format!("Total sample time ({})", unit))) |

56 | }) |

57 | .plot( |

58 | Points { |

59 | x: data.x().as_ref(), |

60 | y: scaled_y.as_ref(), |

61 | }, |

62 | |c| { |

63 | c.set(DARK_BLUE) |

64 | .set(Label("Sample")) |

65 | .set(PointSize(0.5)) |

66 | .set(PointType::FilledCircle) |

67 | }, |

68 | ) |

69 | .plot( |

70 | Lines { |

71 | x: &[0., max_iters], |

72 | y: &[0., point], |

73 | }, |

74 | |c| { |

75 | c.set(DARK_BLUE) |

76 | .set(LINEWIDTH) |

77 | .set(Label("Linear regression")) |

78 | .set(LineType::Solid) |

79 | }, |

80 | ) |

81 | .plot( |

82 | FilledCurve { |

83 | x: &[0., max_iters], |

84 | y1: &[0., lb], |

85 | y2: &[0., ub], |

86 | }, |

87 | |c| { |

88 | c.set(DARK_BLUE) |

89 | .set(Label("Confidence interval")) |

90 | .set(Opacity(0.25)) |

91 | }, |

92 | ); |

93 | figure |

94 | } |

95 | |

96 | pub(crate) fn regression( |

97 | id: &BenchmarkId, |

98 | context: &ReportContext, |

99 | formatter: &dyn ValueFormatter, |

100 | measurements: &MeasurementData<'_>, |

101 | size: Option<Size>, |

102 | ) -> Child { |

103 | let mut figure = regression_figure(formatter, measurements, size); |

104 | figure.set(Title(gnuplot_escape(id.as_title()))); |

105 | figure.configure(Key, |k| { |

106 | k.set(Justification::Left) |

107 | .set(Order::SampleText) |

108 | .set(Position::Inside(Vertical::Top, Horizontal::Left)) |

109 | }); |

110 | |

111 | let path = context.report_path(id, "regression.svg"); |

112 | debug_script(&path, &figure); |

113 | figure.set(Output(path)).draw().unwrap() |

114 | } |

115 | |

116 | pub(crate) fn regression_small( |

117 | id: &BenchmarkId, |

118 | context: &ReportContext, |

119 | formatter: &dyn ValueFormatter, |

120 | measurements: &MeasurementData<'_>, |

121 | size: Option<Size>, |

122 | ) -> Child { |

123 | let mut figure = regression_figure(formatter, measurements, size); |

124 | figure.configure(Key, |k| k.hide()); |

125 | |

126 | let path = context.report_path(id, "regression_small.svg"); |

127 | debug_script(&path, &figure); |

128 | figure.set(Output(path)).draw().unwrap() |

129 | } |

130 | |

131 | fn regression_comparison_figure( |

132 | formatter: &dyn ValueFormatter, |

133 | measurements: &MeasurementData<'_>, |

134 | comparison: &ComparisonData, |

135 | base_data: &Data<'_, f64, f64>, |

136 | size: Option<Size>, |

137 | ) -> Figure { |

138 | let data = &measurements.data; |

139 | let max_iters = base_data.x().max().max(data.x().max()); |

140 | let typical = base_data.y().max().max(data.y().max()); |

141 | |

142 | let exponent = (max_iters.log10() / 3.).floor() as i32 * 3; |

143 | let x_scale = 10f64.powi(-exponent); |

144 | |

145 | let x_label = if exponent == 0 { |

146 | "Iterations".to_owned() |

147 | } else { |

148 | format!("Iterations (x 10^{})", exponent) |

149 | }; |

150 | |

151 | let Estimate { |

152 | confidence_interval: |

153 | ConfidenceInterval { |

154 | lower_bound: base_lb, |

155 | upper_bound: base_ub, |

156 | .. |

157 | }, |

158 | point_estimate: base_point, |

159 | .. |

160 | } = comparison.base_estimates.slope.as_ref().unwrap(); |

161 | |

162 | let Estimate { |

163 | confidence_interval: |

164 | ConfidenceInterval { |

165 | lower_bound: lb, |

166 | upper_bound: ub, |

167 | .. |

168 | }, |

169 | point_estimate: point, |

170 | .. |

171 | } = measurements.absolute_estimates.slope.as_ref().unwrap(); |

172 | |

173 | let mut points = [ |

174 | base_lb * max_iters, |

175 | base_point * max_iters, |

176 | base_ub * max_iters, |

177 | lb * max_iters, |

178 | point * max_iters, |

179 | ub * max_iters, |

180 | ]; |

181 | let unit = formatter.scale_values(typical, &mut points); |

182 | let [base_lb, base_point, base_ub, lb, point, ub] = points; |

183 | |

184 | let mut figure = Figure::new(); |

185 | figure |

186 | .set(Font(DEFAULT_FONT)) |

187 | .set(size.unwrap_or(SIZE)) |

188 | .configure(Axis::BottomX, |a| { |

189 | a.configure(Grid::Major, |g| g.show()) |

190 | .set(Label(x_label)) |

191 | .set(ScaleFactor(x_scale)) |

192 | }) |

193 | .configure(Axis::LeftY, |a| { |

194 | a.configure(Grid::Major, |g| g.show()) |

195 | .set(Label(format!("Total sample time ({})", unit))) |

196 | }) |

197 | .configure(Key, |k| { |

198 | k.set(Justification::Left) |

199 | .set(Order::SampleText) |

200 | .set(Position::Inside(Vertical::Top, Horizontal::Left)) |

201 | }) |

202 | .plot( |

203 | FilledCurve { |

204 | x: &[0., max_iters], |

205 | y1: &[0., base_lb], |

206 | y2: &[0., base_ub], |

207 | }, |

208 | |c| c.set(DARK_RED).set(Opacity(0.25)), |

209 | ) |

210 | .plot( |

211 | FilledCurve { |

212 | x: &[0., max_iters], |

213 | y1: &[0., lb], |

214 | y2: &[0., ub], |

215 | }, |

216 | |c| c.set(DARK_BLUE).set(Opacity(0.25)), |

217 | ) |

218 | .plot( |

219 | Lines { |

220 | x: &[0., max_iters], |

221 | y: &[0., base_point], |

222 | }, |

223 | |c| { |

224 | c.set(DARK_RED) |

225 | .set(LINEWIDTH) |

226 | .set(Label("Base sample")) |

227 | .set(LineType::Solid) |

228 | }, |

229 | ) |

230 | .plot( |

231 | Lines { |

232 | x: &[0., max_iters], |

233 | y: &[0., point], |

234 | }, |

235 | |c| { |

236 | c.set(DARK_BLUE) |

237 | .set(LINEWIDTH) |

238 | .set(Label("New sample")) |

239 | .set(LineType::Solid) |

240 | }, |

241 | ); |

242 | figure |

243 | } |

244 | |

245 | pub(crate) fn regression_comparison( |

246 | id: &BenchmarkId, |

247 | context: &ReportContext, |

248 | formatter: &dyn ValueFormatter, |

249 | measurements: &MeasurementData<'_>, |

250 | comparison: &ComparisonData, |

251 | base_data: &Data<'_, f64, f64>, |

252 | size: Option<Size>, |

253 | ) -> Child { |

254 | let mut figure = |

255 | regression_comparison_figure(formatter, measurements, comparison, base_data, size); |

256 | figure.set(Title(gnuplot_escape(id.as_title()))); |

257 | |

258 | let path = context.report_path(id, "both/regression.svg"); |

259 | debug_script(&path, &figure); |

260 | figure.set(Output(path)).draw().unwrap() |

261 | } |

262 | |

263 | pub(crate) fn regression_comparison_small( |

264 | id: &BenchmarkId, |

265 | context: &ReportContext, |

266 | formatter: &dyn ValueFormatter, |

267 | measurements: &MeasurementData<'_>, |

268 | comparison: &ComparisonData, |

269 | base_data: &Data<'_, f64, f64>, |

270 | size: Option<Size>, |

271 | ) -> Child { |

272 | let mut figure = |

273 | regression_comparison_figure(formatter, measurements, comparison, base_data, size); |

274 | figure.configure(Key, |k| k.hide()); |

275 | |

276 | let path = context.report_path(id, "relative_regression_small.svg"); |

277 | debug_script(&path, &figure); |

278 | figure.set(Output(path)).draw().unwrap() |

279 | } |

280 |