Coverage for python/lsst/summit/utils/m1m3/plots/inertia_compensation_system.py: 19%
98 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-17 08:53 +0000
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-17 08:53 +0000
1import logging
3import matplotlib.pyplot as plt
4import pandas as pd
5from astropy.time import Time
7from lsst.summit.utils.type_utils import M1M3ICSAnalysis
9__all__ = [
10 "plot_hp_data",
11 "mark_slew_begin_end",
12 "mark_padded_slew_begin_end",
13 "customize_fig",
14 "customize_hp_plot",
15 "add_hp_limits",
16 "plot_velocity_data",
17 "plot_torque_data",
18 "plot_stable_region",
19 "plot_hp_measured_data",
20 "HP_BREAKAWAY_LIMIT",
21 "HP_FATIGUE_LIMIT",
22 "HP_OPERATIONAL_LIMIT",
23 "FIGURE_WIDTH",
24 "FIGURE_HEIGHT",
25]
27# Approximate value for breakaway
28HP_BREAKAWAY_LIMIT: float = 3000 # [N]
30# limit that can still damage the mirror with fatigue
31HP_FATIGUE_LIMIT: float = 900 # [N]
33# desired operational limit
34HP_OPERATIONAL_LIMIT: float = 450 # [N]
36FIGURE_WIDTH = 10
37FIGURE_HEIGHT = 7
40def plot_hp_data(ax: plt.Axes, data: pd.Series | list, label: str) -> plt.Line2D:
41 """
42 Plot hardpoint data on the given axes.
44 Parameters
45 ----------
46 ax : `plt.Axes`
47 The axes on which the data is plotted.
48 topic : `str`
49 The topic of the data.
50 data : `Series` or `list`
51 The data points to be plotted.
52 label : `str`
53 The label for the plotted data.
55 Returns
56 -------
57 lines : `plt.Line2D`
58 The plotted data as a Line2D object.
59 """
60 line = ax.plot(data, "-", label=label, lw=0.5)
61 # Make this function consistent with others by returning single Line2D
62 return line[0]
65def mark_slew_begin_end(ax: plt.Axes, slew_begin: Time, slew_end: Time) -> plt.Line2D:
66 """
67 Mark the beginning and the end of a slew with vertical lines on the given
68 axes.
70 Parameters
71 ----------
72 ax : `matplotlib.axes._axes.Axes`
73 The axes where the vertical lines are drawn.
74 slew_begin : `astropy.time.Time`
75 The slew beginning time.
76 slew_end : `astropy.time.Time`
77 The slew ending time.
79 Returns
80 -------
81 line : `matplotlib.lines.Line2D`
82 The Line2D object representing the line drawn at the slew end.
83 """
84 _ = ax.axvline(slew_begin.datetime, lw=0.5, ls="--", c="k", zorder=-1)
85 line = ax.axvline(slew_end.datetime, lw=0.5, ls="--", c="k", zorder=-1, label="Slew Start/Stop")
86 return line
89def mark_padded_slew_begin_end(ax: plt.Axes, begin: Time, end: Time) -> plt.Line2D:
90 """
91 Mark the padded beginning and the end of a slew with vertical lines.
93 Parameters
94 ----------
95 ax : `matplotlib.axes._axes.Axes`
96 The axes where the vertical lines are drawn.
97 begin : `astropy.time.Time`
98 The padded slew beginning time.
99 end : `astropy.time.Time`
100 The padded slew ending time.
102 Returns
103 -------
104 line : `matplotlib.lines.Line2D`
105 The Line2D object representing the line drawn at the padded slew end.
106 """
107 _ = ax.axvline(begin.datetime, alpha=0.5, lw=0.5, ls="-", c="k", zorder=-1)
108 line = ax.axvline(
109 end.datetime,
110 alpha=0.5,
111 lw=0.5,
112 ls="-",
113 c="k",
114 zorder=-1,
115 label="Padded Slew Start/Stop",
116 )
117 return line
120def customize_fig(fig: plt.Figure, dataset: M1M3ICSAnalysis):
121 """
122 Add a title to a figure and adjust its subplots spacing
124 Paramters
125 ---------
126 fig : `matplotlib.pyplot.Figure`
127 Figure to be custoized.
128 dataset : `M1M3ICSAnalysis`
129 The dataset object containing the data to be plotted and metadata.
130 """
131 t_fmt = "%Y%m%d %H:%M:%S"
132 fig.suptitle(
133 f"HP Measured Data\n "
134 f"DayObs {dataset.event.dayObs} "
135 f"SeqNum {dataset.event.seqNum} "
136 f"v{dataset.event.version}\n "
137 f"{dataset.df.index[0].strftime(t_fmt)} - "
138 f"{dataset.df.index[-1].strftime(t_fmt)}"
139 )
141 fig.subplots_adjust(hspace=0)
144def customize_hp_plot(ax: plt.Axes, lines: list[plt.Line2D]) -> None:
145 """
146 Customize the appearance of the hardpoint plot.
148 Parameters
149 ----------
150 ax : `matplotlib.axes._axes.Axes`
151 The axes of the plot to be customized.
152 lines : `list`
153 The list of Line2D objects representing the plotted data lines.
154 """
155 limit_lines = add_hp_limits(ax)
156 lines.extend(limit_lines)
158 ax.set_xlabel("Time [UTC]")
159 ax.set_ylabel("HP Measured\n Forces [N]")
160 ax.set_ylim(-3100, 3100)
161 ax.grid(linestyle=":", alpha=0.2)
164def add_hp_limits(ax: plt.Axes):
165 """
166 Add horizontal lines to represent the breakaway limits, the fatigue limits,
167 and the operational limits.
169 This was first discussed on Slack. From Doug Neil we got:
171 > A fracture statistics estimate of the fatigue limit of a borosilicate
172 > glass. The fatigue limit of borosilicate glass is 0.21 MPa (~30 psi).
173 > This implies that repeated loads of 30% of our breakaway limit would
174 > eventually produce failure. To ensure that the system is safe for the
175 > life of the project we should provide a factor of safety of at least two.
176 > I recommend a 30% repeated load limit, and a project goal to keep the
177 > stress below 15% of the breakaway during normal operations.
179 Parameters
180 ----------
181 ax : `plt.Axes`
182 The axes on which the velocity data is plotted.
183 """
184 hp_limits = {
185 "HP Breakaway Limit": {"pos_limit": HP_BREAKAWAY_LIMIT, "neg_limit": -HP_BREAKAWAY_LIMIT, "ls": "-"},
186 "Repeated Load Limit (30% breakaway)": {
187 "pos_limit": HP_FATIGUE_LIMIT,
188 "neg_limit": -HP_FATIGUE_LIMIT,
189 "ls": "--",
190 },
191 "Normal Ops Limit (15% breakaway)": {
192 "pos_limit": HP_OPERATIONAL_LIMIT,
193 "neg_limit": -HP_OPERATIONAL_LIMIT,
194 "ls": ":",
195 },
196 }
198 kwargs = dict(alpha=0.5, lw=1.0, c="r", zorder=-1)
199 line_list = []
201 for key, sub_dict in hp_limits.items():
202 ax.axhline(sub_dict["pos_limit"], ls=sub_dict["ls"], **kwargs)
203 line = ax.axhline(sub_dict["neg_limit"], ls=sub_dict["ls"], label=key, **kwargs)
204 line_list.append(line)
206 return line_list
209def plot_velocity_data(ax: plt.Axes, dataset: M1M3ICSAnalysis) -> None:
210 """
211 Plot the azimuth and elevation velocities on the given axes.
213 Parameters
214 ----------
215 ax : `matplotlib.axes._axes.Axes`
216 The axes on which the velocity data is plotted.
217 dataset : `M1M3ICSAnalysis`
218 The dataset object containing the data to be plotted and metadata.
219 """
220 ax.plot(dataset.df["az_actual_velocity"], color="royalblue", label="Az Velocity")
221 ax.plot(dataset.df["el_actual_velocity"], color="teal", label="El Velocity")
222 ax.grid(linestyle=":", alpha=0.2)
223 ax.set_ylabel("Actual Velocity\n [deg/s]")
224 ax.legend(ncol=2, fontsize="x-small")
227def plot_torque_data(ax: plt.Axes, dataset: M1M3ICSAnalysis) -> None:
228 """
229 Plot the azimuth and elevation torques on the given axes.
231 Parameters
232 ----------
233 ax : `matplotlib.axes._axes.Axes`
234 The axes on which the torque data is plotted.
235 dataset : `M1M3ICSAnalysis`
236 The dataset object containing the data to be plotted and metadata.
237 """
238 ax.plot(dataset.df["az_actual_torque"], color="firebrick", label="Az Torque")
239 ax.plot(dataset.df["el_actual_torque"], color="salmon", label="El Torque")
240 ax.grid(linestyle=":", alpha=0.2)
241 ax.set_ylabel("Actual Torque\n [kN.m]")
242 ax.legend(ncol=2, fontsize="x-small")
245def plot_stable_region(
246 fig: plt.Figure, begin: Time, end: Time, label: str = "", color: str = "b"
247) -> plt.Polygon:
248 """
249 Highlight a stable region on the plot with a colored span.
251 Parameters
252 ----------
253 fig : `plt.Figure`
254 The figure containing the axes on which the stable region is
255 highlighted.
256 begin : `astropy.time.Time`
257 The beginning time of the stable region.
258 end : `astropy.time.Time`
259 The ending time of the stable region.
260 label : `str`, optional
261 The label for the highlighted region.
262 color : `str`, optional
263 The color of the highlighted region.
265 Returns
266 -------
267 polygon : `matplotlib.patches.Polygon`
268 The Polygon object representing the highlighted region.
269 """
270 for ax in fig.axes[1:]:
271 span = ax.axvspan(begin.datetime, end.datetime, fc=color, alpha=0.1, zorder=-2, label=label)
272 return span
275def plot_hp_measured_data(
276 dataset: M1M3ICSAnalysis,
277 fig: plt.Figure,
278 commands: dict[Time, str] | None = None,
279 log: logging.Logger | None = None,
280) -> plt.Figure:
281 """
282 Create and plot hardpoint measured data, velocity, and torque on subplots.
283 This plot was designed for a figure with `figsize=(10, 7)` and `dpi=120`.
285 Parameters
286 ----------
287 dataset : `M1M3ICSAnalysis`
288 The dataset object containing the data to be plotted and metadata.
289 fig : `plt.Figure`
290 The figure to be plotted on.
291 commands : `dict`, optional
292 A dictionary times at which commands were issued, and with the values
293 as the command strings themselves.
294 log : `logging.Logger`, optional
295 The logger object to log progress.
296 """
297 log = log.getChild(__name__) if log is not None else logging.getLogger(__name__)
299 # Start clean
300 fig.clear()
302 # Add subplots
303 gs = fig.add_gridspec(4, 1, height_ratios=[1, 2, 1, 1])
305 ax_label = fig.add_subplot(gs[0])
306 ax_hp = fig.add_subplot(gs[1])
307 ax_tor = fig.add_subplot(gs[2], sharex=ax_hp)
308 ax_vel = fig.add_subplot(gs[3], sharex=ax_hp)
310 # Remove frame from axis dedicated to label
311 ax_label.axis("off")
313 # Plotting
314 line_list: list[plt.Line2D] = []
315 for hp in range(dataset.number_of_hardpoints):
316 topic = dataset.measured_forces_topics[hp]
317 line = plot_hp_data(ax_hp, dataset.df[topic], f"HP{hp+1}")
318 line_list.append(line)
320 slew_begin = Time(dataset.event.begin, scale="utc")
321 slew_end = Time(dataset.event.end, scale="utc")
323 mark_slew_begin_end(ax_hp, slew_begin, slew_end)
324 mark_slew_begin_end(ax_vel, slew_begin, slew_end)
325 line = mark_slew_begin_end(ax_tor, slew_begin, slew_end)
326 line_list.append(line)
328 mark_padded_slew_begin_end(ax_hp, slew_begin - dataset.outer_pad, slew_end + dataset.outer_pad)
329 mark_padded_slew_begin_end(ax_vel, slew_begin - dataset.outer_pad, slew_end + dataset.outer_pad)
330 line = mark_padded_slew_begin_end(ax_tor, slew_begin - dataset.outer_pad, slew_end + dataset.outer_pad)
331 line_list.append(line)
333 plot_velocity_data(ax_vel, dataset)
334 plot_torque_data(ax_tor, dataset)
336 lineColors = [p["color"] for p in plt.rcParams["axes.prop_cycle"]] # cycle through the colors
337 colorCounter = 0
338 if commands is not None:
339 for commandTime, command in commands.items():
340 command = command.replace("lsst.sal.", "")
341 for ax in (ax_hp, ax_tor, ax_vel): # so that the line spans all plots
342 line = ax.axvline(
343 commandTime.utc.datetime,
344 c=lineColors[colorCounter],
345 ls="--",
346 alpha=0.75,
347 label=f"{command}",
348 )
349 line_list.append(line) # put it in the legend
350 colorCounter += 1 # increment color so each line is different
352 customize_hp_plot(ax_hp, line_list)
354 handles, labels = ax_hp.get_legend_handles_labels()
355 ax_label.legend(handles, labels, loc="center", frameon=False, ncol=4, fontsize="x-small")
357 customize_fig(fig, dataset)
359 return fig