Coverage for python/lsst/summit/utils/m1m3/plots/inertia_compensation_system.py: 17%

105 statements  

« prev     ^ index     » next       coverage.py v7.4.2, created at 2024-02-23 15:45 +0000

1import logging 

2 

3import matplotlib.pyplot as plt 

4import pandas as pd 

5from astropy.time import Time 

6from lsst.summit.utils.type_utils import M1M3ICSAnalysis 

7 

8__all__ = [ 

9 'plot_hp_data', 

10 'mark_slew_begin_end', 

11 'mark_padded_slew_begin_end', 

12 'customize_fig', 

13 'customize_hp_plot', 

14 'add_hp_limits', 

15 'plot_velocity_data', 

16 'plot_torque_data', 

17 'plot_stable_region', 

18 'plot_hp_measured_data', 

19 'HP_BREAKAWAY_LIMIT', 

20 'HP_FATIGUE_LIMIT', 

21 'HP_OPERATIONAL_LIMIT', 

22 'FIGURE_WIDTH', 

23 'FIGURE_HEIGHT', 

24] 

25 

26# Approximate value for breakaway 

27HP_BREAKAWAY_LIMIT = 3000 # [N] 

28 

29# limit that can still damage the mirror with fatigue 

30HP_FATIGUE_LIMIT = 900 # [N] 

31 

32# desired operational limit 

33HP_OPERATIONAL_LIMIT = 450 # [N] 

34 

35FIGURE_WIDTH = 10 

36FIGURE_HEIGHT = 7 

37 

38 

39def plot_hp_data(ax: plt.Axes, data: pd.Series | list, label: str) -> list[plt.Line2D]: 

40 """ 

41 Plot hardpoint data on the given axes. 

42 

43 Parameters 

44 ---------- 

45 ax : `matplotlib.axes._axes.Axes` 

46 The axes on which the data is plotted. 

47 topic : `str` 

48 The topic of the data. 

49 data : `Series` or `list` 

50 The data points to be plotted. 

51 label : `str` 

52 The label for the plotted data. 

53 

54 Returns 

55 ------- 

56 lines : `list[Line2D]` 

57 A list containing the Line2D objects representing the plotted data 

58 lines. 

59 """ 

60 line = ax.plot(data, "-", label=label, lw=0.5) 

61 return line 

62 

63 

64def mark_slew_begin_end(ax: plt.Axes, slew_begin: Time, slew_end: Time) -> plt.Line2D: 

65 """ 

66 Mark the beginning and the end of a slew with vertical lines on the given 

67 axes. 

68 

69 Parameters 

70 ---------- 

71 ax : `matplotlib.axes._axes.Axes` 

72 The axes where the vertical lines are drawn. 

73 slew_begin : `astropy.time.Time` 

74 The slew beginning time. 

75 slew_end : `astropy.time.Time` 

76 The slew ending time. 

77 

78 Returns 

79 ------- 

80 line : `matplotlib.lines.Line2D` 

81 The Line2D object representing the line drawn at the slew end. 

82 """ 

83 _ = ax.axvline(slew_begin.datetime, lw=0.5, ls="--", c="k", zorder=-1) 

84 line = ax.axvline( 

85 slew_end.datetime, lw=0.5, ls="--", c="k", zorder=-1, label="Slew Start/Stop" 

86 ) 

87 return line 

88 

89 

90def mark_padded_slew_begin_end(ax: plt.Axes, begin: Time, end: Time) -> plt.Line2D: 

91 """ 

92 Mark the padded beginning and the end of a slew with vertical lines. 

93 

94 Parameters 

95 ---------- 

96 ax : `matplotlib.axes._axes.Axes` 

97 The axes where the vertical lines are drawn. 

98 begin : `astropy.time.Time` 

99 The padded slew beginning time. 

100 end : `astropy.time.Time` 

101 The padded slew ending time. 

102 

103 Returns 

104 ------- 

105 line : `matplotlib.lines.Line2D` 

106 The Line2D object representing the line drawn at the padded slew end. 

107 """ 

108 _ = ax.axvline(begin.datetime, alpha=0.5, lw=0.5, ls="-", c="k", zorder=-1) 

109 line = ax.axvline( 

110 end.datetime, 

111 alpha=0.5, 

112 lw=0.5, 

113 ls="-", 

114 c="k", 

115 zorder=-1, 

116 label="Padded Slew Start/Stop", 

117 ) 

118 return line 

119 

120 

121def customize_fig(fig: plt.Figure, dataset: M1M3ICSAnalysis): 

122 """ 

123 Add a title to a figure and adjust its subplots spacing 

124 

125 Paramters 

126 --------- 

127 fig : `matplotlib.pyplot.Figure` 

128 Figure to be custoized. 

129 dataset : `M1M3ICSAnalysis` 

130 The dataset object containing the data to be plotted and metadata. 

131 """ 

132 t_fmt = "%Y%m%d %H:%M:%S" 

133 fig.suptitle( 

134 f"HP Measured Data\n " 

135 f"DayObs {dataset.event.dayObs} " 

136 f"SeqNum {dataset.event.seqNum} " 

137 f"v{dataset.event.version}\n " 

138 f"{dataset.df.index[0].strftime(t_fmt)} - " 

139 f"{dataset.df.index[-1].strftime(t_fmt)}" 

140 ) 

141 

142 fig.subplots_adjust(hspace=0) 

143 

144 

145def customize_hp_plot( 

146 ax: plt.Axes, dataset: M1M3ICSAnalysis, lines: list[plt.Line2D] 

147) -> None: 

148 """ 

149 Customize the appearance of the hardpoint plot. 

150 

151 Parameters 

152 ---------- 

153 ax : `matplotlib.axes._axes.Axes` 

154 The axes of the plot to be customized. 

155 dataset : `M1M3ICSAnalysis` 

156 The dataset object containing the data to be plotted and metadata. 

157 lines : `list` 

158 The list of Line2D objects representing the plotted data lines. 

159 """ 

160 limit_lines = add_hp_limits(ax) 

161 lines.extend(limit_lines) 

162 

163 ax.set_xlabel("Time [UTC]") 

164 ax.set_ylabel("HP Measured\n Forces [N]") 

165 ax.set_ylim(-3100, 3100) 

166 ax.grid(":", alpha=0.2) 

167 

168 

169def add_hp_limits(ax: plt.Axes): 

170 """ 

171 Add horizontal lines to represent the breakaway limits, the fatigue limits, 

172 and the operational limits. 

173 

174 This was first discussed on Slack. From Doug Neil we got: 

175 

176 > A fracture statistics estimate of the fatigue limit of a borosilicate 

177 > glass. The fatigue limit of borosilicate glass is 0.21 MPa (~30 psi). 

178 > This implies that repeated loads of 30% of our breakaway limit would 

179 > eventually produce failure. To ensure that the system is safe for the 

180 > life of the project we should provide a factor of safety of at least two. 

181 > I recommend a 30% repeated load limit, and a project goal to keep the 

182 > stress below 15% of the breakaway during normal operations. 

183 

184 Parameters 

185 ---------- 

186 ax : `matplotlib.axes._axes.Axes` 

187 The axes on which the velocity data is plotted. 

188 """ 

189 hp_limits = { 

190 "HP Breakaway Limit": { 

191 "pos_limit": HP_BREAKAWAY_LIMIT, 

192 "neg_limit": -HP_BREAKAWAY_LIMIT, 

193 "ls": "-" 

194 }, 

195 "Repeated Load Limit (30% breakaway)": { 

196 "pos_limit": HP_FATIGUE_LIMIT, 

197 "neg_limit": -HP_FATIGUE_LIMIT, 

198 "ls": "--" 

199 }, 

200 "Normal Ops Limit (15% breakaway)": { 

201 "pos_limit": HP_OPERATIONAL_LIMIT, 

202 "neg_limit": -HP_OPERATIONAL_LIMIT, 

203 "ls": ":" 

204 } 

205 } 

206 

207 kwargs = dict(alpha=0.5, lw=1.0, c="r", zorder=-1) 

208 lines = [] 

209 

210 for key, sub_dict in hp_limits.items(): 

211 ax.axhline(sub_dict["pos_limit"], ls=sub_dict["ls"], **kwargs) 

212 line = ax.axhline(sub_dict["neg_limit"], ls=sub_dict["ls"], label=key, **kwargs) 

213 lines.append(line) 

214 

215 return lines 

216 

217 

218def plot_velocity_data(ax: plt.Axes, dataset: M1M3ICSAnalysis) -> None: 

219 """ 

220 Plot the azimuth and elevation velocities on the given axes. 

221 

222 Parameters 

223 ---------- 

224 ax : `matplotlib.axes._axes.Axes` 

225 The axes on which the velocity data is plotted. 

226 dataset : `M1M3ICSAnalysis` 

227 The dataset object containing the data to be plotted and metadata. 

228 """ 

229 ax.plot(dataset.df["az_actual_velocity"], color="royalblue", label="Az Velocity") 

230 ax.plot(dataset.df["el_actual_velocity"], color="teal", label="El Velocity") 

231 ax.grid(":", alpha=0.2) 

232 ax.set_ylabel("Actual Velocity\n [deg/s]") 

233 ax.legend(ncol=2, fontsize="x-small") 

234 

235 

236def plot_torque_data(ax: plt.Axes, dataset: M1M3ICSAnalysis) -> None: 

237 """ 

238 Plot the azimuth and elevation torques on the given axes. 

239 

240 Parameters 

241 ---------- 

242 ax : `matplotlib.axes._axes.Axes` 

243 The axes on which the torque data is plotted. 

244 dataset : `M1M3ICSAnalysis` 

245 The dataset object containing the data to be plotted and metadata. 

246 """ 

247 ax.plot(dataset.df["az_actual_torque"], color="firebrick", label="Az Torque") 

248 ax.plot(dataset.df["el_actual_torque"], color="salmon", label="El Torque") 

249 ax.grid(":", alpha=0.2) 

250 ax.set_ylabel("Actual Torque\n [kN.m]") 

251 ax.legend(ncol=2, fontsize="x-small") 

252 

253 

254def plot_stable_region( 

255 fig: plt.figure, begin: Time, end: Time, label: str = "", color: str = "b" 

256) -> plt.Polygon: 

257 """ 

258 Highlight a stable region on the plot with a colored span. 

259 

260 Parameters 

261 ---------- 

262 fig : `matplotlib.figure.Figure` 

263 The figure containing the axes on which the stable region is 

264 highlighted. 

265 begin : `astropy.time.Time` 

266 The beginning time of the stable region. 

267 end : `astropy.time.Time` 

268 The ending time of the stable region. 

269 label : `str`, optional 

270 The label for the highlighted region. 

271 color : `str`, optional 

272 The color of the highlighted region. 

273 

274 Returns 

275 ------- 

276 polygon : `matplotlib.patches.Polygon` 

277 The Polygon object representing the highlighted region. 

278 """ 

279 for ax in fig.axes[1:]: 

280 span = ax.axvspan( 

281 begin.datetime, end.datetime, fc=color, alpha=0.1, zorder=-2, label=label 

282 ) 

283 return span 

284 

285 

286def plot_hp_measured_data( 

287 dataset: M1M3ICSAnalysis, 

288 fig: plt.figure, 

289 commands: dict[str, Time] = None, 

290 log: None | logging.Logger = None, 

291) -> None: 

292 """ 

293 Create and plot hardpoint measured data, velocity, and torque on subplots. 

294 This plot was designed for a figure with `figsize=(10, 7)` and `dpi=120`. 

295 

296 Parameters 

297 ---------- 

298 dataset : `M1M3ICSAnalysis` 

299 The dataset object containing the data to be plotted and metadata. 

300 fig : `matplotlib.figure.Figure` 

301 The figure to be plotted on. 

302 log : `logging.Logger`, optional 

303 The logger object to log progress. 

304 """ 

305 log = log.getChild(__name__) if log is not None else logging.getLogger(__name__) 

306 

307 # Start clean 

308 fig.clear() 

309 

310 # Add subplots 

311 gs = fig.add_gridspec(4, 1, height_ratios=[1, 2, 1, 1]) 

312 

313 ax_label = fig.add_subplot(gs[0]) 

314 ax_hp = fig.add_subplot(gs[1]) 

315 ax_tor = fig.add_subplot(gs[2], sharex=ax_hp) 

316 ax_vel = fig.add_subplot(gs[3], sharex=ax_hp) 

317 

318 # Remove frame from axis dedicated to label 

319 ax_label.axis('off') 

320 

321 # Plotting 

322 lines = [] 

323 for hp in range(dataset.number_of_hardpoints): 

324 topic = dataset.measured_forces_topics[hp] 

325 line = plot_hp_data(ax_hp, dataset.df[topic], f"HP{hp+1}") 

326 lines.extend(line) 

327 

328 slew_begin = Time(dataset.event.begin, scale="utc") 

329 slew_end = Time(dataset.event.end, scale="utc") 

330 

331 mark_slew_begin_end(ax_hp, slew_begin, slew_end) 

332 mark_slew_begin_end(ax_vel, slew_begin, slew_end) 

333 line = mark_slew_begin_end(ax_tor, slew_begin, slew_end) 

334 lines.append(line) 

335 

336 mark_padded_slew_begin_end( 

337 ax_hp, slew_begin - dataset.outer_pad, slew_end + dataset.outer_pad 

338 ) 

339 mark_padded_slew_begin_end( 

340 ax_vel, slew_begin - dataset.outer_pad, slew_end + dataset.outer_pad 

341 ) 

342 line = mark_padded_slew_begin_end( 

343 ax_tor, slew_begin - dataset.outer_pad, slew_end + dataset.outer_pad 

344 ) 

345 lines.append(line) 

346 

347 stable_begin, stable_end = dataset.find_stable_region() 

348 stat_begin, stat_end = ( 

349 stable_begin + dataset.inner_pad, 

350 stable_end - dataset.inner_pad, 

351 ) 

352 

353 plot_velocity_data(ax_vel, dataset) 

354 plot_torque_data(ax_tor, dataset) 

355 span_stable = plot_stable_region(fig, stable_begin, stable_end, "Stable", color="k") 

356 span_with_padding = plot_stable_region( 

357 fig, stat_begin, stat_end, "Stable w/ Padding", color="b" 

358 ) 

359 lines.extend([span_stable, span_with_padding]) 

360 

361 lineColors = [p['color'] for p in plt.rcParams['axes.prop_cycle']] # cycle through the colors 

362 colorCounter = 0 

363 if commands is not None: 

364 for command, commandTime in commands.items(): 

365 # if commands weren't found, the item is set to None. This is 

366 # common for events so handle it gracefully and silently. The 

367 # command finding code logs about lack of commands found so no need 

368 # to mention here. 

369 if commandTime is None: 

370 continue 

371 command = command.replace("lsst.sal.", "") 

372 for ax in (ax_hp, ax_tor, ax_vel): # so that the line spans all plots 

373 line = ax.axvline(commandTime.utc.datetime, c=lineColors[colorCounter], 

374 ls='--', alpha=0.75, label=f'{command}') 

375 lines.append(line) # put it in the legend 

376 colorCounter += 1 # increment color so each line is different 

377 

378 customize_hp_plot(ax_hp, dataset, lines) 

379 

380 handles, labels = ax_hp.get_legend_handles_labels() 

381 ax_label.legend(handles, labels, loc='center', frameon=False, ncol=4, fontsize="x-small") 

382 

383 customize_fig(fig, dataset) 

384 

385 return fig