Coverage for python/lsst/summit/utils/m1m3/plots/inertia_compensation_system.py: 17%
105 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-18 12:40 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-18 12:40 +0000
1import logging
3import matplotlib.pyplot as plt
4import pandas as pd
5from astropy.time import Time
6from lsst.summit.utils.type_utils import M1M3ICSAnalysis
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]
26# Approximate value for breakaway
27HP_BREAKAWAY_LIMIT = 3000 # [N]
29# limit that can still damage the mirror with fatigue
30HP_FATIGUE_LIMIT = 900 # [N]
32# desired operational limit
33HP_OPERATIONAL_LIMIT = 450 # [N]
35FIGURE_WIDTH = 10
36FIGURE_HEIGHT = 7
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.
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.
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
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.
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.
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
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.
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.
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
121def customize_fig(fig: plt.Figure, dataset: M1M3ICSAnalysis):
122 """
123 Add a title to a figure and adjust its subplots spacing
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 )
142 fig.subplots_adjust(hspace=0)
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.
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)
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)
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.
174 This was first discussed on Slack. From Doug Neil we got:
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.
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 }
207 kwargs = dict(alpha=0.5, lw=1.0, c="r", zorder=-1)
208 lines = []
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)
215 return lines
218def plot_velocity_data(ax: plt.Axes, dataset: M1M3ICSAnalysis) -> None:
219 """
220 Plot the azimuth and elevation velocities on the given axes.
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")
236def plot_torque_data(ax: plt.Axes, dataset: M1M3ICSAnalysis) -> None:
237 """
238 Plot the azimuth and elevation torques on the given axes.
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")
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.
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.
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
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`.
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__)
307 # Start clean
308 fig.clear()
310 # Add subplots
311 gs = fig.add_gridspec(4, 1, height_ratios=[1, 2, 1, 1])
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)
318 # Remove frame from axis dedicated to label
319 ax_label.axis('off')
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)
328 slew_begin = Time(dataset.event.begin, scale="utc")
329 slew_end = Time(dataset.event.end, scale="utc")
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)
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)
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 )
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])
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
378 customize_hp_plot(ax_hp, dataset, lines)
380 handles, labels = ax_hp.get_legend_handles_labels()
381 ax_label.legend(handles, labels, loc='center', frameon=False, ncol=4, fontsize="x-small")
383 customize_fig(fig, dataset)
385 return fig