diff --git a/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOM-1171-analysis-failed-M1M3-lowering.ipynb b/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOM-1171-analysis-failed-M1M3-lowering.ipynb new file mode 100644 index 00000000..60527d34 --- /dev/null +++ b/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOM-1171-analysis-failed-M1M3-lowering.ipynb @@ -0,0 +1,889 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "df58a53e-6d9f-4954-8871-2d0ca500a27e", + "metadata": {}, + "source": [ + "# [SITCOM-1171] - Investigate M1M3 lowering failing to reach 0 position on the first try\n", + "\n", + "Notebook containing data analysis for the event reported on [SITCOM-1171].\n", + "\n", + "The M1M3 surrogate mirror had to be lowered twice on 15th January 2024, as the cell was getting ready for removal from TMA.\n", + "\n", + "The first M1M3 lowering started around 12:17:45 UTC. The mirror's position as measured by the laser tracker was reported 10mm off in the Y direction once lowered. The surrogate mirror was raised and lowered again, starting around 12:29:18 UTC. The second try was successful, with the M1M3 being lowered correctly.\n", + "\n", + "\n", + "## Goal:\n", + "---\n", + "\n", + "The goal is to identify what might cause the problem and fix the code so the lowering of the M1M3 at the zenith will always result in the mirror resting at the same position.\n", + "\n", + "\n", + "## Methodology\n", + "---\n", + "We will query data for different telemetry to analyze what might have caused the observed offset.\n", + "\n", + "## Results\n", + "---\n", + "The offset reported by the laser tracker seems to not be real. IMS data and Hardpoint actuator data do not show the reported offset.\n", + "\n", + "[SITCOM-1171]: https://jira.lsstcorp.org/browse/SITCOM-1171\n" + ] + }, + { + "cell_type": "markdown", + "id": "823ef8a8-a626-405d-b036-ff165208901f", + "metadata": { + "execution": { + "iopub.execute_input": "2024-02-28T13:00:37.307280Z", + "iopub.status.busy": "2024-02-28T13:00:37.306970Z", + "iopub.status.idle": "2024-02-28T13:00:37.310583Z", + "shell.execute_reply": "2024-02-28T13:00:37.310048Z", + "shell.execute_reply.started": "2024-02-28T13:00:37.307265Z" + } + }, + "source": [ + "## Preparing the Notebook\n", + "\n", + "----\n", + "\n", + "### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59d3232d-e6c5-45a1-9f87-4cf2ba39e77f", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c18b9c1d-4d47-4941-9567-899f22e12ed4", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import os\n", + "import astropy.units as u\n", + "from astropy.time import Time, TimeDelta\n", + "\n", + "\n", + "from lsst.ts.xml.enums.MTM1M3 import DetailedStates\n", + "from lsst.sitcom import vandv\n" + ] + }, + { + "cell_type": "markdown", + "id": "ade271da-51be-464c-83da-3582ddd03fca", + "metadata": {}, + "source": [ + "## Helper function and classes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4264f1b-f93f-4b0d-8b69-8760bcc9d15c", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "class RaisingLoweringEvent:\n", + " \"\"\"Class to handle the telemetry associated with a raising or lowering event\n", + " \"\"\"\n", + "\n", + " # Used to the define what is considered as a zenith position\n", + " MIN_EL_ZENITH = 88\n", + " MAX_EL_ZENITH = 92\n", + "\n", + " def __init__(self, t_start, t_end, event_type):\n", + " \"\"\"A rasing or lowering event is defined by a start and end time and a type\n", + " of event.\n", + "\n", + " Parameters\n", + " ----------\n", + " t_start : Time.Time\n", + " Start time of the event\n", + " t_end : Time.Time\n", + " End time of the event\n", + " event_type : lsst.ts.xml.enums.MTM1M3.DetailedStates\n", + " Type of event. Can be RAISING, LOWERING, RAISINGENGINEERING or LOWERINGENGINEERING.\n", + " \"\"\"\n", + "\n", + " self.t_start = t_start\n", + " self.t_end = t_end\n", + "\n", + " event_type = int(event_type)\n", + " self.event_type = DetailedStates(event_type)\n", + "\n", + "\n", + " async def at_zenith(self):\n", + " \"\"\"Check if the event happened at zenith.\n", + "\n", + " Returns\n", + " -------\n", + " bool\n", + " True if the event happened at zenith, False otherwise\n", + " \"\"\"\n", + " # Query the elevation of the telescope during the event\n", + " el = await self.get_elevation()\n", + " # Check if the elevation is within the zenith range\n", + " return el >= self.MIN_EL_ZENITH and el <= self.MAX_EL_ZENITH\n", + "\n", + "\n", + " async def get_elevation(self):\n", + " \"\"\"Queries the mean elevation of the telescope during the event.\n", + " It should be constant during the event.\n", + "\n", + " Returns\n", + " -------\n", + " float\n", + " Elevation of the telescope during the event\n", + " \"\"\"\n", + " # start and end time of the event\n", + " t_start_el = self.t_start\n", + " t_end_el = self.t_end\n", + "\n", + " # Query the elevation of the telescope during the raising/lowering event\n", + " # from the EDF\n", + " df_el = await client.select_time_series(\n", + " \"lsst.sal.MTM1M3.inclinometerData\",\n", + " \"inclinometerAngle\",\n", + " t_start_el,\n", + " t_end_el,\n", + " )\n", + "\n", + " # find another way to verify data\n", + " # This should mean that there is no available data for these dates\n", + " if not \"inclinometerAngle\" in df_el:\n", + " print(\"No inclinometer data\")\n", + " return None\n", + " # returns the mean elevation during the event\n", + " el = np.mean(df_el[\"inclinometerAngle\"])\n", + " return el\n", + "\n", + "\n", + " async def query_data_after_event(self, client, table, columns, time_delta_sec=10):\n", + " \"\"\"Queries telemetry data from the EFD after the end of the event, using a\n", + " given time window.\n", + "\n", + " Parameters\n", + " ----------\n", + " client : lsst_efd_client.EfdClient\n", + " Client to interact with the EFD\n", + " table : str\n", + " Name of the table (topic) to query from the EFD\n", + " columns : list of str or str\n", + " List of columns (fields) to query from the table (topic)\n", + " time_delta_sec : int, optional\n", + " Time window to query data after the event is finished, in seconds,\n", + " by default 10\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " Dataframe containing the telemetry data\n", + " \"\"\"\n", + "\n", + " # We want to query data from the end of the event to a given time window\n", + " t_end = self.t_end + TimeDelta(time_delta_sec, format=\"sec\")\n", + " df = await client.select_time_series(\n", + " table,\n", + " columns,\n", + " self.t_end,\n", + " t_end)\n", + " return df\n", + "\n", + "\n", + " async def query_data_during_event(self, client, table, columns):\n", + " \"\"\"Queries telemetry data from the EFD during the event.\n", + "\n", + " Parameters\n", + " ----------\n", + " client : lsst_efd_client.EfdClient\n", + " Client to interact with the EFD\n", + " table : str\n", + " Name of the table (topic) to query from the EFD\n", + " columns : list of str or str\n", + " List of columns (fields) to query from the table (topic)\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " Dataframe containing the telemetry data\n", + " \"\"\"\n", + " df = await client.select_time_series(\n", + " table,\n", + " columns,\n", + " self.t_start,\n", + " self.t_end)\n", + " return df\n", + "\n", + "\n", + "\n", + "async def find_events(time_start, time_end, detailed_state=DetailedStates.LOWERING):\n", + " \"\"\"Given a time window, find all the events of a given type of state.\n", + " e.g. all the lowering events, all the raising events, etc.\n", + "\n", + " Parameters\n", + " ----------\n", + " time_start : Time.Time\n", + " Start time of the time window\n", + " time_end : Time.Time\n", + " End time of the time window\n", + " detailed_state : lsst.ts.xml.enums.MTM1M3.DetailedStates, optional\n", + " Detailed states to find in the time window, by default DetailedStates.LOWERING\n", + "\n", + " Returns\n", + " -------\n", + " pd.DataFrame\n", + " Dataframe containing the detailed state events\n", + " \"\"\"\n", + " state = int(detailed_state)\n", + "\n", + " query = (\n", + " \"SELECT detailedState \"\n", + " 'FROM \"efd\".\"autogen\".\"lsst.sal.MTM1M3.logevent_detailedState\" '\n", + " f\"WHERE time > '{time_start.isot}+00:00' AND time < '{time_end.isot}+00:00' \"\n", + " f\"AND detailedState = {state} \"\n", + " f\"ORDER BY time ASC \"\n", + " )\n", + " df_states = await client.influx_client.query(query)\n", + " return df_states\n", + "\n", + "\n", + "async def find_next_state(timestamp):\n", + " \"\"\"Given a timestamp, find the next state of the MTM1M3 detailed state.\n", + "\n", + " Parameters\n", + " ----------\n", + " timestamp : Time.Time\n", + " Timestamp to query the next state\n", + "\n", + " Returns\n", + " -------\n", + " tuple: Time.Time, lsst.ts.xml.enums.MTM1M3.DetailedStates\n", + " Timestamp of the next state and the next state\n", + " \"\"\"\n", + "\n", + " # We use a small delta to make sure we get the next state\n", + " timestamp += TimeDelta(0.1, format=\"sec\")\n", + "\n", + " # Query the next state of the MTM1M3 detailed state\n", + " query = (\n", + " \"SELECT detailedState \"\n", + " 'FROM \"efd\".\"autogen\".\"lsst.sal.MTM1M3.logevent_detailedState\" '\n", + " f\"WHERE time > '{timestamp.isot}+00:00' ORDER BY time ASC LIMIT 1\"\n", + " )\n", + " ret = await client.influx_client.query(query)\n", + "\n", + " # We should have only one result\n", + " next_state = ret[\"detailedState\"].iloc[0]\n", + " timestamp_next_state = ret[\"detailedState\"].index[0]\n", + "\n", + " return timestamp_next_state, next_state\n", + "\n", + "\n", + "async def find_finished_rising_lowering_events(time_start, time_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=False):\n", + " \"\"\"Find all the finished raising or lowering events in a given time window.\n", + " This is needed because not all the events are finished when they are executed.\n", + " e.g. A lowering event can stop at 50% mirror supported percent and be resumed later.\n", + " We would like to only consider the events that are finished at 0% .\n", + "\n", + " Finished events are considered as the following:\n", + " - Lowering event that finished at PARKED\n", + " - LoweringEngineering event that finished at PARKEDENGINEERING\n", + " - Raising event that finished at ACTIVE\n", + " - RaisingEngineering event that finished at ACTIVEENGINEERING\n", + "\n", + "\n", + " Parameters\n", + " ----------\n", + " time_start : Time.Time\n", + " Start time of the time window\n", + " time_end : Time.Time\n", + " End time of the time window\n", + " detailed_state : lsst.ts.xml.enums.MTM1M3.DetailedStates, optional\n", + " Detailed states to find in the time window, by default DetailedStates.LOWERING,\n", + " only_at_zenith : bool, optional\n", + " If True, only finds events that are executed\n", + " with the telescope pointing at the zenith, by default False\n", + "\n", + " Returns\n", + " -------\n", + " list of RaisingLoweringEvent\n", + " List of finished raising or lowering events\n", + " \"\"\"\n", + " # Find all the events of a given type of state in the time window\n", + " df_states = await find_events(time_start, time_end, detailed_state=detailed_state)\n", + "\n", + " events = []\n", + "\n", + "\n", + " for event in df_states.iterrows():\n", + " # The current state of the event\n", + " current_state = event[1].iloc[0]\n", + " timestamp = Time(event[0])\n", + " # Find the next state of the event\n", + " timestamp_next_state, next_state = await find_next_state(timestamp)\n", + "\n", + " event_finished = False\n", + " # Check if the event is finished\n", + " if current_state == DetailedStates.LOWERING and next_state == DetailedStates.PARKED:\n", + " event_finished = True\n", + " elif current_state == DetailedStates.LOWERINGENGINEERING and next_state == DetailedStates.PARKEDENGINEERING:\n", + " event_finished = True\n", + " elif current_state == DetailedStates.RAISING and next_state == DetailedStates.ACTIVE:\n", + " event_finished = True\n", + " elif current_state == DetailedStates.RAISINGENGINEERING and next_state == DetailedStates.ACTIVEENGINEERING:\n", + " event_finished = True\n", + " else:\n", + " pass\n", + " #print(current_state, next_state)\n", + "\n", + " if event_finished:\n", + " # We create a rasing lowering event object\n", + " event_obj = RaisingLoweringEvent(Time(timestamp), Time(timestamp_next_state), current_state)\n", + " # Filter by events at zenith\n", + " if (only_at_zenith and await event_obj.at_zenith()) or not only_at_zenith:\n", + " events.append(event_obj)\n", + "\n", + " return events\n", + "\n", + "\n", + "\n", + "async def plot_telemetry_after_finished_event(client, events_list, table, column, time_delta_sec=20,\n", + " unit_column=None, unit_plot=None, y_min=None, y_max=None,\n", + " fig=None, axs=None, save_plot=True, plot_filename=None, save_path=\".\",\n", + " highlight_timestamp=None):\n", + " \"\"\"Plots the telemetry data from the EFD after the end of a list of events.\n", + " All events are plotted on the same plot and aligned to the time of the end\n", + " of the events.\n", + " Two plots axis are created. The left one shows a time series of the telemetry\n", + " after the definition of the end of the events. The right one shows an histogram\n", + " of the distribution of the telemetry values at the end of the events.\n", + "\n", + "\n", + " Parameters\n", + " ----------\n", + " client : lsst_efd_client.EfdClient\n", + " Client to interact with the EFD\n", + " events_list : list of RaisingLoweringEvent\n", + " List of events to plot the telemetry data after the end of the event\n", + " table : str\n", + " Name of the table (topic) to query from the EFD\n", + " column : str\n", + " Name of the column (field) to query from the table (topic)\n", + " time_delta_sec : int, optional\n", + " Length of the time window to query and plot after the end of the events, by default 20\n", + " unit_column : astropy.units, optional\n", + " Default units of the column. by default None\n", + " unit_plot : astropy.units, optional\n", + " Unit to plot. Data are trasformed to this unit if possible (from m to mm)\n", + " and this will be displayed in the plot labels, by default None.\n", + " y_min : float, optional\n", + " Min y axis value, by default None\n", + " y_max : float, optional\n", + " Max y axis value, by default None\n", + " fig : matplotlib.figure.Figure, optional\n", + " Figure used for plotting, by default None. If None, a new figure is created.\n", + " axs : list of matplotlib.axes.Axes (len 2), optional\n", + " List of axes to make the plots, by default None. If None, new axes are created.\n", + " save_plot : bool, optional\n", + " Whether the plot is saved to disk or not. by default True\n", + " plot_filename : str, optional\n", + " Plot filename. Overwrites default filename, by default None. If None\n", + " the default filename is used.\n", + " save_path : str, optional\n", + " Path where the plots are saved to, by default \".\"\n", + " highlight_timestamp : Time.Time, optional\n", + " If an event occurred at this exact time, it will be highlighted in the plots\n", + " , by default None.\n", + "\n", + " Returns\n", + " -------\n", + " fig, axs\n", + " Figure and axes used for the plot. Useful if the user wants to modify the plot.\n", + "\n", + " Raises\n", + " ------\n", + " ValueError\n", + " If the number of passed axes is not 2\n", + " \"\"\"\n", + "\n", + " # Creates the plots if not provided\n", + " if fig is None or axs is None:\n", + " fig, axs = plt.subplots(1, 2, sharey=True, figsize=(10, 5))\n", + " if len(axs) != 2:\n", + " raise ValueError(f\"Axes should be a list of 2 axes objetcs\")\n", + "\n", + " ax = axs[0]\n", + "\n", + " # Factor for unit conversion\n", + " factor = 1\n", + " if unit_column is not None and unit_plot is not None:\n", + " factor = (1*unit_column).to_value(unit_plot)\n", + "\n", + " h_line_value = None\n", + " end_values = []\n", + " for event in events_list:\n", + " # Query the telemetry data after the end of the event\n", + " df = await event.query_data_after_event(client, table, column, time_delta_sec=time_delta_sec)\n", + " # Time since the end of the event\n", + " sec = (df.index - df.index[0])/np.timedelta64(1, \"s\")\n", + " # Quantity to plot, transformed to the desired unit\n", + " quantity = np.array(df[column]*factor)\n", + " # Plot parameters\n", + " plot_kwargs = {\"c\": \"k\", \"alpha\":0.5, \"linewidth\":1, \"label\": None}\n", + " if highlight_timestamp and event.t_start == highlight_timestamp:\n", + " # Highlight the event if it occurred at the exact time\n", + " plot_kwargs[\"c\"] = \"r\"\n", + " plot_kwargs[\"alpha\"] = 1\n", + " plot_kwargs[\"label\"] = f\"Event on {highlight_timestamp}\"\n", + " h_line_value = quantity[-1]\n", + " # plot the telemetry data\n", + " ax.plot(sec, quantity, zorder=len(events_list), **plot_kwargs)\n", + " # Store the last value of the telemetry data\n", + " end_values.append(quantity[-1])\n", + "\n", + " ax.set_xlabel(\"Time since end of event (s)\")\n", + " unit_string = \"\"\n", + " if unit_column is not None and unit_plot is not None:\n", + " unit_string = f\"[{unit_plot}]\"\n", + "\n", + " label = f\"{table}\\n{column} {unit_string}\"\n", + " ax.set_ylabel(label)\n", + " if y_min is not None and y_max is not None:\n", + " ax.set_ylim(y_min, y_max)\n", + " ax.legend()\n", + "\n", + " # in the right plot, we plot an histogram of the telemetry data at the end of the events\n", + " ax = axs[1]\n", + " bins = 100\n", + " if y_min is not None and y_max is not None:\n", + " bins = np.linspace(y_min, y_max, bins)\n", + " ax.hist(end_values, bins=bins, orientation=\"horizontal\")\n", + " if h_line_value:\n", + " pass\n", + " #ax.axhline(h_line_value, c='r', label=f\"Event on {highlight_timestamp}\")\n", + " ax.set_xlabel(\"counts\")\n", + " events_types = set([event.event_type.name for event in events_list])\n", + " events = \"-\".join(events_types)\n", + " ax.legend()\n", + " fig.suptitle(events)\n", + "\n", + " fig.tight_layout()\n", + "\n", + " if save_plot:\n", + " if not os.path.exists(save_path):\n", + " os.makedirs(save_path)\n", + " if plot_filename is None:\n", + " # Default filename\n", + " plot_filename = f\"{column}_{table}_after_end_of_event_{events}.png\"\n", + "\n", + " plot_filename = os.path.join(save_path, plot_filename)\n", + " fig.savefig(plot_filename, dpi=200)\n", + "\n", + " return fig, ax\n", + "\n", + "async def plot_telemetry_while_moving(client, events_list, table, column, unit_column=None, unit_plot=None, y_min=None, y_max=None, fig=None, ax=None,\n", + " save_plot=True, save_path=\".\", plot_filename=None, highlight_timestamp=None):\n", + " \"\"\"Plots the telemetry data from the EFD during a list of events.\n", + "\n", + " Parameters\n", + " ----------\n", + " client : lsst_efd_client.EfdClient\n", + " Client to interact with the EFD\n", + " events_list : list of RaisingLoweringEvent\n", + " List of events to plot the telemetry data after the end of the event\n", + " table : str\n", + " Name of the table (topic) to query from the EFD\n", + " column : str\n", + " Name of the column (field) to query from the table (topic)\n", + " unit_column : astropy.units, optional\n", + " Default units of the column. by default None\n", + " unit_plot : astropy.units, optional\n", + " Unit to plot. Data are trasformed to this unit if possible (from m to mm)\n", + " and this will be displayed in the plot labels, by default None.\n", + " y_min : float, optional\n", + " Min y axis value, by default None\n", + " y_max : float, optional\n", + " Max y axis value, by default None\n", + " fig : matplotlib.figure.Figure, optional\n", + " Figure used for plotting, by default None. If None, a new figure is created.\n", + " ax : matplotlib.axes.Axes (len 2), optional\n", + " Ax to plot on. If None, new axes are created.\n", + " save_plot : bool, optional\n", + " Whether the plot is saved to disk or not. by default True\n", + " plot_filename : str, optional\n", + " Plot filename. Overwrites default filename, by default None. If None\n", + " the default filename is used.\n", + " save_path : str, optional\n", + " Path where the plots are saved to, by default \".\"\n", + " highlight_timestamp : Time.Time, optional\n", + " If an event occurred at this exact time, it will be highlighted in the plots\n", + " , by default None.\n", + "\n", + " Returns\n", + " -------\n", + " fig, ax\n", + " Figure and axes used for the plot. Useful if the user wants to modify the plot.\n", + "\n", + " \"\"\"\n", + "\n", + " # Creates the plots if not provided\n", + " if fig is None or ax is None:\n", + " fig, ax = plt.subplots(figsize=(5, 5))\n", + "\n", + " # Factor for unit conversion\n", + " factor = 1\n", + " if unit_column is not None and unit_plot is not None:\n", + " factor = (1*unit_column).to_value(unit_plot)\n", + "\n", + " for event in events_list:\n", + " # Query the telemetry data during the event\n", + " df = await event.query_data_during_event(client, table, column)\n", + " # Time since the end of the event\n", + " sec = (df.index - df.index[-1])/np.timedelta64(1, \"s\")\n", + " # Quantity to plot, transformed to the desired unit\n", + " quantity = np.array(df[column]*factor)\n", + "\n", + " # Plot parameters\n", + " plot_kwargs = {\"c\": \"k\", \"alpha\":0.5, \"linewidth\":1, \"label\": None}\n", + " if highlight_timestamp and event.t_start == highlight_timestamp:\n", + " # Highlight the event if it occurred at the exact time\n", + " plot_kwargs[\"c\"] = \"r\"\n", + " plot_kwargs[\"alpha\"] = 1\n", + " plot_kwargs[\"label\"] = f\"Event on {highlight_timestamp}\"\n", + " # plot the telemetry data\n", + " ax.plot(sec, quantity, zorder=len(events_list), **plot_kwargs)\n", + "\n", + "\n", + " ax.set_xlabel(\"Time since end of event (s)\")\n", + " unit_string = \"\"\n", + " if unit_column is not None and unit_plot is not None:\n", + " unit_string = f\"[{unit_plot}]\"\n", + "\n", + " label = f\"{table}\\n{column} {unit_string}\"\n", + " ax.set_ylabel(label)\n", + " if y_min is not None and y_max is not None:\n", + " ax.set_ylim(y_min, y_max)\n", + "\n", + " events_types = set([event.event_type.name for event in events_list])\n", + " events = \"-\".join(events_types)\n", + " fig.suptitle(events)\n", + " ax.legend()\n", + " fig.tight_layout()\n", + "\n", + " if save_plot:\n", + " if not os.path.exists(save_path):\n", + " os.makedirs(save_path)\n", + " if plot_filename is None:\n", + " # Default filename\n", + " plot_filename = f\"{column}_{table}_during_event_{events}.png\"\n", + "\n", + " plot_filename = os.path.join(save_path, plot_filename)\n", + " fig.savefig(plot_filename, dpi=200)\n", + "\n", + " return fig, ax\n" + ] + }, + { + "cell_type": "markdown", + "id": "eda1f830", + "metadata": {}, + "source": [ + "## Initial Analysis\n", + "\n", + "We will start by querying lowering profiles for the weightSupportedPercent data\n", + "to verify we are actually getting finished lowering events." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d52ebc17", + "metadata": {}, + "outputs": [], + "source": [ + "# Create the EFD client\n", + "client = vandv.efd.create_efd_client()\n", + "\n", + "# This is the timestamp of the failed lowering event\n", + "t = np.datetime64(\"2024-01-15 12:17:16.272362+00:00\")\n", + "t = Time(t)\n", + "\n", + "\n", + "# Query data from 1 year ago to now\n", + "t_start = Time(\"2023-07-01T00:00\", format=\"isot\", scale=\"utc\")\n", + "t_end = Time(\"2024-02-28T00:00\", format=\"isot\", scale=\"utc\")\n", + "lowering_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERINGENGINEERING, only_at_zenith=True)\n", + "lowering_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=True)\n", + "lowering_events.extend(lowering_events_eng)\n", + "\n", + "# Plot the supported percent profiles during lowering events\n", + "table = \"lsst.sal.MTM1M3.logevent_raisingLoweringInfo\"\n", + "column = \"weightSupportedPercent\"\n", + "await plot_telemetry_while_moving(client, lowering_events, table, column, highlight_timestamp=t, save_plot=False)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3da39599-1cb5-4847-9cb0-1b2400fee334", + "metadata": {}, + "source": [ + "## Lowering Profiles\n", + "\n", + "The first thing we notice is that there is a difference in the lowering speeds. There seem to be two groups of lowering profiles. Manual examination shows that before and after 2024-01-11 the profiles are differents, indicating a change in the configuration. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bdc335e-e899-4acc-b09f-70b3d5dee327", + "metadata": {}, + "outputs": [], + "source": [ + "# We plot the lowering profiles before and after the mentioned date\n", + "t_start = Time(\"2023-07-01T00:00\", format=\"isot\", scale=\"utc\")\n", + "t_end = Time(\"2024-01-12T00:00\", format=\"isot\", scale=\"utc\")\n", + "lowering_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERINGENGINEERING, only_at_zenith=True)\n", + "lowering_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=True)\n", + "lowering_events.extend(lowering_events_eng)\n", + "\n", + "# Plot the supported percent profiles during lowering events\n", + "table = \"lsst.sal.MTM1M3.logevent_raisingLoweringInfo\"\n", + "column = \"weightSupportedPercent\"\n", + "await plot_telemetry_while_moving(client, lowering_events, table, column, highlight_timestamp=t, save_plot=False)\n", + "\n", + "t_start = Time(\"2024-01-12T00:00\", format=\"isot\", scale=\"utc\")\n", + "t_end = Time(\"2024-02-28T00:00\", format=\"isot\", scale=\"utc\")\n", + "lowering_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERINGENGINEERING, only_at_zenith=True)\n", + "lowering_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=True)\n", + "lowering_events.extend(lowering_events_eng)\n", + "await plot_telemetry_while_moving(client, lowering_events, table, column, highlight_timestamp=t, save_plot=False)\n" + ] + }, + { + "cell_type": "markdown", + "id": "07a62ba3", + "metadata": {}, + "source": [ + "If the lowering profile changed in a date near the reported event, can this be related? We find that the reported event is the first event after 2024-01-12." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9480e4e8", + "metadata": {}, + "outputs": [], + "source": [ + "df = await find_events(Time(\"2024-01-11T00:00\", format=\"isot\", scale=\"utc\"), Time(\"2024-01-17T00:00\", format=\"isot\", scale=\"utc\"),\n", + " detailed_state=DetailedStates.LOWERINGENGINEERING)\n", + "print(df)\n", + "\n", + "df = await find_events(Time(\"2024-01-11T00:00\", format=\"isot\", scale=\"utc\"), Time(\"2024-01-17T00:00\", format=\"isot\", scale=\"utc\"),\n", + " detailed_state=DetailedStates.LOWERING)\n", + "print(df)" + ] + }, + { + "cell_type": "markdown", + "id": "dff38f13-2bd0-49a7-9004-2c0fcf6bb09c", + "metadata": {}, + "source": [ + "## Position telemetry analysis\n", + "\n", + "Now that we know that the lowering speed changed on a given date. For a fair comparison, we plot telemetry data before this date and after this date in different plots.\n", + "\n", + "The telemetry we plot here is \n", + "\n", + "- lsst.sal.MTM1M3.imsData: \"xPosition\", \"yPosition\", \"zPosition\", \"xRotation\", \"yRotation\", \"zRotation\"\n", + "- lsst.sal.MTM1M3.hardpointActuatorData: \"xPosition\", \"yPosition\", \"zPosition\", \"xRotation\", \"yRotation\", \"zRotation\", \"measuredForce0\", \"measuredForce1\", \"measuredForce2\", \"measuredForce3\", \"measuredForce4\", \"measuredForce5\"\n", + "- sst.sal.MTM1M3.logevent_raisingLoweringInfo: \"weightSupportedPercent\"\n", + "\n", + "Although the most relevant quantities are the yPosition from the IMS and the hardpoint data, as the 10 mm offset was reported for the y position.\n", + "\n", + "Several plots will be created in directories named after the different start-end dates.\n", + "\n", + "**The execution of the cells below can take several minutes, be aware of that**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e131f122-57da-4047-8fd7-2ec52c1c06d5", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "plots_dict = {'lsst.sal.MTM1M3.imsData': {\"columns\": [\"xPosition\", \"yPosition\", \"zPosition\", \"xRotation\", \"yRotation\", \"zRotation\"],\n", + " \"units_column\":[u.m, u.m, u.m, u.deg, u.deg, u.deg],\n", + " \"units_plot\":[u.mm, u.mm, u.mm, u.deg, u.deg, u.deg] },\n", + " 'lsst.sal.MTM1M3.hardpointActuatorData': {\"columns\": [\"xPosition\", \"yPosition\", \"zPosition\", \"xRotation\", \"yRotation\", \"zRotation\", \"measuredForce0\", \"measuredForce1\", \"measuredForce2\", \"measuredForce3\", \"measuredForce4\", \"measuredForce5\"],\n", + " \"units_column\":[u.m, u.m, u.m, u.deg, u.deg, u.deg, u.N, u.N, u.N, u.N, u.N, u.N],\n", + " \"units_plot\":[u.mm, u.mm, u.mm, u.deg, u.deg, u.deg, u.N, u.N, u.N, u.N, u.N, u.N] },\n", + " 'lsst.sal.MTM1M3.logevent_raisingLoweringInfo': {\"columns\": [\"weightSupportedPercent\"],\n", + " \"units_column\":[None],\n", + " \"units_plot\":[None] },\n", + "\n", + " }\n", + "\n", + "\n", + "# Events in first configuration\n", + "t_start = Time(\"2023-07-01T00:00\", format=\"isot\", scale=\"utc\")\n", + "t_end = Time(\"2024-01-12T00:00\", format=\"isot\", scale=\"utc\")\n", + "lowering_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERINGENGINEERING, only_at_zenith=True)\n", + "lowering_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=True)\n", + "lowering_events.extend(lowering_events_eng)\n", + "\n", + "\n", + "#raising_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.RAISINGENGINEERING, only_at_zenith=True)\n", + "#raising_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.RAISING, only_at_zenith=True)\n", + "#raising_events.extend(raising_events_eng)\n", + "\n", + "save_path = f\"{t_start.datetime.strftime('%d-%m-%Y')}_{t_end.datetime.strftime('%d-%m-%Y')}\"\n", + "\n", + "for table in plots_dict:\n", + " columns = plots_dict[table][\"columns\"]\n", + " units_columns = plots_dict[table][\"units_column\"]\n", + " units_plots = plots_dict[table][\"units_plot\"]\n", + " for column, unit_column, unit_plot in zip(columns, units_columns, units_plots):\n", + " print(f\"making plots for {table} {column}\")\n", + " await plot_telemetry_while_moving(client, lowering_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " #await plot_telemetry_while_moving(client, raising_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + "\n", + " if column == \"weightSupportedPercent\":\n", + " continue\n", + " await plot_telemetry_after_finished_event(client, lowering_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " #await plot_telemetry_after_finished_event(client, raising_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbddc7ec-5b47-44d3-9fac-0373b4ddd430", + "metadata": {}, + "outputs": [], + "source": [ + "# Events in second configuration\n", + "t_start = Time(\"2024-01-12T00:00\", format=\"isot\", scale=\"utc\")\n", + "t_end = Time(\"2024-02-28T00:00\", format=\"isot\", scale=\"utc\")\n", + "lowering_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERINGENGINEERING, only_at_zenith=True)\n", + "lowering_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=True)\n", + "lowering_events.extend(lowering_events_eng)\n", + "\n", + "\n", + "#raising_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.RAISINGENGINEERING, only_at_zenith=True)\n", + "#raising_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.RAISING, only_at_zenith=True)\n", + "#raising_events.extend(raising_events_eng)\n", + "save_path = f\"{t_start.datetime.strftime('%d-%m-%Y')}_{t_end.datetime.strftime('%d-%m-%Y')}\"\n", + "\n", + "for table in plots_dict:\n", + " columns = plots_dict[table][\"columns\"]\n", + " units_columns = plots_dict[table][\"units_column\"]\n", + " units_plots = plots_dict[table][\"units_plot\"]\n", + " for column, unit_column, unit_plot in zip(columns, units_columns, units_plots):\n", + " print(f\"making plots for {table} {column}\")\n", + " await plot_telemetry_while_moving(client, lowering_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " #await plot_telemetry_while_moving(client, raising_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " if column == \"weightSupportedPercent\":\n", + " continue\n", + " #await plot_telemetry_after_finished_event(client, raising_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " await plot_telemetry_after_finished_event(client, lowering_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ada4f5b-8e9d-4f60-9820-7f1f9a005662", + "metadata": {}, + "outputs": [], + "source": [ + "# Events including both configurations\n", + "\n", + "t_start = Time(\"2023-07-01T00:00\", format=\"isot\", scale=\"utc\")\n", + "t_end = Time(\"2024-02-28T00:00\", format=\"isot\", scale=\"utc\")\n", + "lowering_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERINGENGINEERING, only_at_zenith=True)\n", + "lowering_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.LOWERING, only_at_zenith=True)\n", + "lowering_events.extend(lowering_events_eng)\n", + "\n", + "\n", + "#raising_events_eng = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.RAISINGENGINEERING, only_at_zenith=True)\n", + "#raising_events = await find_finished_rising_lowering_events(t_start, t_end, detailed_state=DetailedStates.RAISING, only_at_zenith=True)\n", + "#raising_events.extend(raising_events_eng)\n", + "\n", + "save_path = f\"{t_start.datetime.strftime('%d-%m-%Y')}_{t_end.datetime.strftime('%d-%m-%Y')}\"\n", + "\n", + "for table in plots_dict:\n", + " columns = plots_dict[table][\"columns\"]\n", + " units_columns = plots_dict[table][\"units_column\"]\n", + " units_plots = plots_dict[table][\"units_plot\"]\n", + " for column, unit_column, unit_plot in zip(columns, units_columns, units_plots):\n", + " print(f\"making plots for {table} {column}\")\n", + " await plot_telemetry_while_moving(client, lowering_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " #await plot_telemetry_while_moving(client, raising_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + "\n", + " if column == \"weightSupportedPercent\":\n", + " continue\n", + " await plot_telemetry_after_finished_event(client, lowering_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n", + " #await plot_telemetry_after_finished_event(client, raising_events, table, column, unit_column=unit_column, unit_plot=unit_plot, highlight_timestamp=t, save_path=save_path)\n" + ] + }, + { + "cell_type": "markdown", + "id": "0b309224-2109-4ccc-a871-4b1ea7e035eb", + "metadata": {}, + "source": [ + "## Results\n", + "\n", + "We are paying special attention to the final profiles and values for the lowering events in the yPosition, and considering the systematic offset between IMS and hardpoint ([SITCOM 760]). We find that the final position for the event reported in [SITCOM-1171] is not as off as the reported value of 10 mm, and is similar to the final position of other lowering events within 1 mm. This can be observed in the plots for just the second lowering configuration (since 12-01-2024) and both configurations (since 11-07-2023 to 28-02-2024). \n", + "\n", + "Important differences in other telemetries are also not observed, neither in x, y, or z positions from IMS and Hardpoint data nor in the measured forces in the hardpoints.\n", + "\n", + "As a preliminary conclusion, we find that the reported offset in the mirror position for the lowering event on 15th January 2024 around 12:17:45 UTC did not happen, or at least is not registered in any telemetry available in the EDF. A possible alternative is that the laser tracker could have reported a wrong measurement. More investigation on this side is needed.\n", + "\n", + "When looking at the lowering profiles there seem to be two different profiles. We find that one profile can be isolated to happen before around 2024-01-12, and the other after around 2024-01-12, suggesting a change in the lowering configuration. Nevertheless, the ending position in the same for both lowering profiles. If the configuration change was executed after 2024-01-12T13:07, then the reported event would be the first lowering event after this change. This, combined to the LaserTracker VM clock not being synchronized ([IT-5073]) around these dates, could be related to this event.\n", + "\n", + "\n", + "[SITCOM-1171]: https://jira.lsstcorp.org/browse/SITCOM-1171\n", + "[SITCOM-760]: https://jira.lsstcorp.org/browse/SITCOM-760\n", + "[IT-5073]: https://jira.lsstcorp.org/browse/IT-5073" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "LSST", + "language": "python", + "name": "lsst" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}