From ba592f872d28073d7adfb6e8781741b73f082fee Mon Sep 17 00:00:00 2001 From: Bruno Quint Date: Fri, 1 Nov 2024 20:32:34 -0700 Subject: [PATCH 1/3] Add notebooks previously living in notebooks_vandv --- ...alysis_actuators_and_actuators_movie.ipynb | 551 ++++++ ..._force_actuator_snapshot_during_slew.ipynb | 255 +++ ..._inertia_compensation_single_slew_hp.ipynb | 1547 +++++++++++++++++ ...MTN-092_m1m3_ics_historical_analysis.ipynb | 523 ++++++ 4 files changed, 2876 insertions(+) create mode 100644 notebooks/SITCOMTN-092_analysis_actuators_and_actuators_movie.ipynb create mode 100644 notebooks/SITCOMTN-092_force_actuator_snapshot_during_slew.ipynb create mode 100644 notebooks/SITCOMTN-092_inertia_compensation_single_slew_hp.ipynb create mode 100644 notebooks/SITCOMTN-092_m1m3_ics_historical_analysis.ipynb diff --git a/notebooks/SITCOMTN-092_analysis_actuators_and_actuators_movie.ipynb b/notebooks/SITCOMTN-092_analysis_actuators_and_actuators_movie.ipynb new file mode 100644 index 0000000..8551946 --- /dev/null +++ b/notebooks/SITCOMTN-092_analysis_actuators_and_actuators_movie.ipynb @@ -0,0 +1,551 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# M1M3 actuator movies\n", + "Craig Lage - 20-Apr-23 \\\n", + "The 17 tons of mirror are supported by 156 pneumatic actuators where 44 are single-axis and provide support only on the axial direction, 100 are dual-axis providing support in the axial and lateral direction, and 12 are dual-axis providing support in the axial and cross lateral directions. \\\n", + "Positioning is provided by 6 hard points in a hexapod configuration which moves the mirror to a fixed operational position that shall be maintained during telescope operations. The remaining optical elements will be moved relative to this position in order to align the telescope optics. Support and optical figure correction is provided by 112 dual axis and 44 single axis pneumatic actuators. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "execution": { + "iopub.execute_input": "2023-05-15T20:34:58.081853Z", + "iopub.status.busy": "2023-05-15T20:34:58.081628Z", + "iopub.status.idle": "2023-05-15T20:34:58.084496Z", + "shell.execute_reply": "2023-05-15T20:34:58.084062Z", + "shell.execute_reply.started": "2023-05-15T20:34:58.081838Z" + }, + "tags": [] + }, + "source": [ + "## SITCOM-763 - This is the ticket that we are addressing with this notebook\n", + "M1M3 should be raised for this test. (Check this tbc)\n", + "\n", + "We tested the M1M3 force balance system by applying external force over the surrogate. The force was applied by stepping on random position on the surrogate surface.\n", + "\n", + "Date: 18.04.23 15.30 - 15.40h CLT.\n", + "\n", + "We looked at the M1M3 EUI at the measured forces at the Actuator 2D map.\n", + "\n", + "The expected result was that we see smooth gradients depending on where people were stepping.\n", + "\n", + "We see the movement in the applied actuator forces. See attached video.\n", + "\n", + "For detailed offline analysis: Create an animation of the 2D map for this period with a fixed color scale. Use the nominal position as a reference position." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare the notebook" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Directory to store the data\n", + "dir_name = \"/home/c/cslage/u/MTM1M3/movies/\"\n", + "\n", + "# Times to make the plot\n", + "start = \"2023-04-18 16:10:00Z\"\n", + "end = \"2023-04-18 16:15:00Z\"\n", + "\n", + "autoScale = True\n", + "# The following are only used if autoScale = False\n", + "zmin = 0.0 # In nt\n", + "zmax = 2000.0 # In nt\n", + "lateral_max = 1500.0 # In nt\n", + "\n", + "# The following average the first 100 data points\n", + "# and subtract these from the measurements\n", + "# If subtractBasline = False, the unmodified values will be plotted\n", + "subtract_baseline = True\n", + "baseline_t0 = 0.0\n", + "baseline_t1 = 100.0\n", + "\n", + "# The following allows you to plot only every nth data point\n", + "# If this value is 1, a frame will be made for every data point\n", + "# Of course, this takes longer\n", + "# If this value is 50, it will make a frame every second\n", + "frame_n = 50" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import asyncio\n", + "import glob\n", + "import os\n", + "import shlex\n", + "import subprocess\n", + "import sys\n", + "from pathlib import Path\n", + "from datetime import datetime\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import numpy as np\n", + "\n", + "from astropy.time import Time, TimeDelta\n", + "\n", + "from matplotlib.colors import LightSource\n", + "\n", + "from lsst.ts.xml.tables.m1m3 import FAOrientation, FATable, FAType\n", + "\n", + "from lsst_efd_client import EfdClient" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up the necessary subroutines" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def actuator_layout(ax):\n", + " \"\"\" Plot a visualization of the actuator locations and types\n", + " Parameters\n", + " ----------\n", + " ax : a matplotlib.axes object\n", + "\n", + " Returns\n", + " -------\n", + " No return, only the ax object which was input\n", + " \"\"\"\n", + " ax.set_xlabel(\"X position (m)\")\n", + " ax.set_ylabel(\"Y position (m)\")\n", + " ax.set_title(\"M1M3 Actuator positions and type\\nHardpoints are approximate\", fontsize=18)\n", + " types = [\n", + " [FAType.SAA, FAOrientation.NA, 'o', 'Z', 'b'],\n", + " [FAType.DAA, FAOrientation.Y_PLUS, '^', '+Y','g'],\n", + " [FAType.DAA, FAOrientation.Y_MINUS, 'v', '-Y', 'cyan'],\n", + " [FAType.DAA, FAOrientation.X_PLUS, '>', '+X', 'r'],\n", + " [FAType.DAA, FAOrientation.X_MINUS, '<', '-X', 'r'],\n", + " ]\n", + " for [type, orient, marker, label, color] in types:\n", + " xs = []\n", + " ys = []\n", + " for fa in FATable:\n", + " x = fa.x_position\n", + " y = fa.y_position\n", + " if fa.actuator_type == type and \\\n", + " fa.orientation == orient:\n", + " xs.append(x)\n", + " ys.append(y)\n", + " else:\n", + " continue\n", + " ax.scatter(xs, ys, marker=marker, color=color, s=200, label=label)\n", + "\n", + " # Now plot approximate hardpoint location\n", + " Rhp = 3.1 # Radius in meters\n", + " for i in range(6):\n", + " theta = 2.0 * np.pi / 6.0 * float(i)\n", + " if i == 0:\n", + " ax.scatter(Rhp * np.cos(theta), Rhp * np.sin(theta), marker='o', color='magenta', \\\n", + " s=200, label='HP')\n", + " else:\n", + " ax.scatter(Rhp * np.cos(theta), Rhp * np.sin(theta), marker='o', color='magenta', \\\n", + " s=200, label='_nolegend_')\n", + " ax.legend(loc='lower left', fontsize=9)\n", + " return\n", + " \n", + " \n", + "def bar_chart_z(df, df_zero, ax, index, zmin, zmax):\n", + " \"\"\" Plot a 3D bar chart of the actuator Z forces\n", + " Parameters\n", + " ----------\n", + " df: pandas dataframe\n", + " The pandas dataframe object with the force actuator data\n", + " \n", + " df_zero: pandas dataframe\n", + " The pandas dataframe object of the quiescent force data\n", + " which will be subtracted off from the force actuator data\n", + " \n", + " ax : a matplotlib.axes object\n", + "\n", + " index: 'int'\n", + " The index of the movie frame\n", + " \n", + " zmin: 'float'\n", + " The minimum force value for the plot\n", + " \n", + " zmax: 'float'\n", + " The maximum force value for the plot\n", + "\n", + " Returns\n", + " -------\n", + " No return, only the ax object which was input\n", + " \"\"\"\n", + "\n", + " ax.set_xlabel(\"X position (m)\")\n", + " ax.set_ylabel(\"Y position (m)\")\n", + " ax.set_zlabel(\"Force (nt)\")\n", + " ax.set_title(\"M1M3 Actuator Z forces\", fontsize=18)\n", + "\n", + " light_source = LightSource(azdeg=180, altdeg=78)\n", + " grey_color = '0.9'\n", + " colors = []\n", + " xs = []\n", + " ys = []\n", + " for fa in FATable:\n", + " x = fa.x_position\n", + " y = fa.y_position\n", + " xs.append(x)\n", + " ys.append(y)\n", + " if fa.actuator_type == FAType.SAA:\n", + " colors.append('blue'); colors.append('blue')\n", + " colors.append(grey_color); colors.append(grey_color)\n", + " colors.append(grey_color); colors.append(grey_color)\n", + " else:\n", + " if fa.orientation in [FAOrientation.Y_PLUS, FAOrientation.Y_MINUS]:\n", + " colors.append('green'); colors.append('green')\n", + " colors.append(grey_color); colors.append(grey_color)\n", + " colors.append(grey_color); colors.append(grey_color)\n", + " else:\n", + " colors.append('red'); colors.append('red')\n", + " colors.append(grey_color); colors.append(grey_color)\n", + " colors.append(grey_color); colors.append(grey_color)\n", + "\n", + " zs = np.zeros([len(FATable)])\n", + " for fa in FATable:\n", + " name=f\"zForce{fa.index}\"\n", + " zs[fa.index] = df.iloc[index][name] - df_zero.iloc[0][name]\n", + "\n", + " dxs = 0.2 * np.ones([len(FATable)])\n", + " dys = 0.2 * np.ones([len(FATable)])\n", + " bottom = np.zeros([len(FATable)])\n", + " ax.bar3d(xs, ys, bottom, dxs, dys, zs, shade=True, alpha=0.5, \\\n", + " lightsource=light_source, color=colors)\n", + " ax.set_zlim(zmin, zmax)\n", + " ax.view_init(elev=30., azim=225)\n", + " return\n", + " \n", + "\n", + "def heat_map_z(df, df_zero, ax, index, zmin, zmax):\n", + " \"\"\" Plot a \"heat map\" of the actuator Z forces\n", + " Parameters\n", + " ----------\n", + " df: pandas dataframe\n", + " The pandas dataframe object with the force actuator data\n", + " \n", + " df_zero: pandas dataframe\n", + " The pandas dataframe object of the quiescent force data\n", + " which will be subtracted off from the force actuator data\n", + " \n", + " ax : a matplotlib.axes object\n", + "\n", + " index: 'int'\n", + " The index of the movie frame\n", + " \n", + " zmin: 'float'\n", + " The minimum force value for the plot\n", + " \n", + " zmax: 'float'\n", + " The maximum force value for the plot\n", + "\n", + " Returns\n", + " -------\n", + " No return, only the ax object which was input\n", + " \"\"\"\n", + " \n", + " ax.set_xlabel(\"X position (m)\")\n", + " ax.set_ylabel(\"Y position (m)\")\n", + " ax.set_title(\"M1M3 Actuator Z forces (nt)\", fontsize=18)\n", + "\n", + " types = [\n", + " [FAType.SAA, FAOrientation.NA, 'o', 'Z'],\n", + " [FAType.DAA, FAOrientation.Y_PLUS, '^', '+Y'],\n", + " [FAType.DAA, FAOrientation.Y_MINUS, 'v', '-Y'],\n", + " [FAType.DAA, FAOrientation.X_PLUS, '>', '+X'],\n", + " [FAType.DAA, FAOrientation.X_MINUS, '<', '-X'],\n", + " ]\n", + "\n", + " for [type, orient, marker, label] in types:\n", + " xs = []\n", + " ys = []\n", + " zs = []\n", + " for fa in FATable:\n", + " x = fa.x_position\n", + " y = fa.y_position\n", + " if fa.actuator_type == type and \\\n", + " fa.orientation == orient:\n", + " xs.append(x)\n", + " ys.append(y)\n", + " name=f\"zForce{fa.index}\"\n", + " zs.append(df.iloc[index][name] - df_zero.iloc[0][name])\n", + " im = ax.scatter(xs, ys, marker=marker, c=zs, cmap='RdBu_r', \\\n", + " vmin=zmin, vmax=zmax, s=50, label=label)\n", + " plt.colorbar(im, ax=ax,fraction=0.055, pad=0.02, cmap='RdBu_r') \n", + " return\n", + " \n", + " \n", + " \n", + "def lateral_forces(df, df_zero, ax, index, force_max):\n", + " \"\"\" Plot a 2D whisker plot of the actuator X and Y forces\n", + " Parameters\n", + " ----------\n", + " df: pandas dataframe\n", + " The pandas dataframe object with the force actuator data\n", + " \n", + " df_zero: pandas dataframe\n", + " The pandas dataframe object of the quiescent force data\n", + " which will be subtracted off from the force actuator data\n", + " \n", + " ax : a matplotlib.axes object\n", + "\n", + " index: 'int'\n", + " The index of the movie frame\n", + " \n", + " force_max: 'float'\n", + " maximum force values for scaling the whisker arrows\n", + "\n", + " Returns\n", + " -------\n", + " No return, only the ax object which was input\n", + " \"\"\"\n", + " \n", + " ax.set_xlabel(\"X position (m)\")\n", + " ax.set_ylabel(\"Y position (m)\")\n", + " ax.set_title(\"M1M3 lateral forces (nt)\", fontsize=18)\n", + " ax.set_xlim(-4.5,4.5)\n", + " ax.set_ylim(-4.5,4.5)\n", + " types = [\n", + " [FAType.DAA, FAOrientation.Y_PLUS, '^', '+Y','g'],\n", + " [FAType.DAA, FAOrientation.Y_MINUS, 'v', '-Y', 'cyan'],\n", + " [FAType.DAA, FAOrientation.X_PLUS, '>', '+X', 'r'],\n", + " [FAType.DAA, FAOrientation.X_MINUS, '<', '-X', 'r'],\n", + " ]\n", + " for [type, orient, marker, label, color] in types:\n", + " xs = []\n", + " ys = []\n", + " arrow_xs = []\n", + " arrow_ys = []\n", + " for fa in FATable:\n", + " x = fa.x_position\n", + " y = fa.y_position\n", + " if fa.actuator_type == type and \\\n", + " fa.orientation == orient:\n", + " xs.append(x)\n", + " ys.append(y)\n", + " if orient == FAOrientation.X_PLUS:\n", + " name = f\"xForce{fa.x_index}\"\n", + " arrow_xs.append(df.iloc[index][name] / force_max)\n", + " arrow_ys.append(0.0)\n", + " elif orient == FAOrientation.X_MINUS:\n", + " name = f\"xForce{fa.x_index}\"\n", + " arrow_xs.append(-df.iloc[index][name] / force_max)\n", + " arrow_ys.append(0.0)\n", + " elif orient == FAOrientation.Y_PLUS:\n", + " name = f\"yForce{fa.y_index}\"\n", + " arrow_xs.append(0.0)\n", + " arrow_ys.append(df.iloc[index][name] / force_max)\n", + " else:\n", + " name = f\"yForce{fa.y_index}\"\n", + " arrow_xs.append(0.0)\n", + " arrow_ys.append(-df.iloc[index][name] / force_max)\n", + " else:\n", + " continue\n", + " ax.scatter(xs, ys, marker=marker, color=color, s=50, label=label)\n", + " for ii in range(len(xs)):\n", + " ax.arrow(xs[ii], ys[ii], arrow_xs[ii], arrow_ys[ii], color=color)\n", + "\n", + " ax.plot([-4.0,-3.0], [-4.0,-4.0], color='g')\n", + " ax.text(-4.0, -4.3, f\"{force_max} nt\")\n", + " return\n", + "\n", + "\n", + "def get_zero_values_and_limits(df, subtract_baseline, t0, t1):\n", + " \"\"\" Plot a 2D whisker plot of the actuator X and Y forces\n", + " Parameters\n", + " ----------\n", + " df: pandas dataframe\n", + " The pandas dataframe object with the force actuator data.\n", + " \n", + " subtract_baseline : 'bool'\n", + " Determines whether or not to subtract off a baseline value from the plots.\n", + "\n", + " t0: 'float'\n", + " The time from the beginning of the dataframe when the baseline\n", + " quiescent period (which will be subtracted off) begins.\n", + "\n", + " t1: 'float'\n", + " The time from the beginning of the dataframe when the baseline\n", + " quiescent period (which will be subtracted off) ends.\n", + "\n", + " Returns\n", + " -------\n", + " zmin: 'float'\n", + " The minimum force value for the Z force plots\n", + "\n", + " zmax: 'float'\n", + " The maximum force value for the Z force plots\n", + " \n", + " lateral_max: 'float'\n", + " The maximum force value for the X,Y whisker plots\n", + " \n", + "\n", + " df_zero: pandas dataframe\n", + " The pandas dataframe object of the quiescent force data\n", + " which will be subtracted off from the force actuator data \n", + " \n", + " \"\"\"\n", + " df_zero = df.head(1)\n", + " for column_name in df_zero.columns:\n", + " try:\n", + " if subtract_baseline:\n", + " df_zero.iloc[0, df_zero.columns.get_loc(column_name)] = \\\n", + " np.median(df[column_name].values[t0:t1])\n", + " else:\n", + " df_zero.iloc[0, df_zero.columns.get_loc(column_name)] = 0.0\n", + " except:\n", + " continue\n", + " # Now calculate the limits \n", + " zmin = 0.0; ymin = 0.0; xmin = 0.0; zmax = 0.0; ymax = 0.0; xmax = 0.0\n", + " for fa in FATable:\n", + " name = f\"zForce{fa.z_index}\"\n", + " zmin = min(zmin, np.min(df[name] - df_zero.iloc[0][name]))\n", + " zmax = max(zmax, np.max(df[name] - df_zero.iloc[0][name]))\n", + " if fa.y_index is not None:\n", + " name = f\"yForce{fa.y_index}\"\n", + " ymin = min(ymin, np.min(df[name] - df_zero.iloc[0][name]))\n", + " ymax = max(ymax, np.max(df[name] - df_zero.iloc[0][name]))\n", + " if fa.x_index is not None:\n", + " name = f\"xForce{fa.x_index}\"\n", + " xmin = min(xmin, np.min(df[name] - df_zero.iloc[0][name]))\n", + " xmax = max(xmax, np.max(df[name] - df_zero.iloc[0][name]))\n", + "\n", + " lateral_max = max(xmax, ymax, -xmin, -ymin)\n", + " return [round(zmin), round(zmax), round(lateral_max), df_zero]\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Now generate the frames\n", + "### This will take some time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "client = EfdClient('usdf_efd')\n", + "\n", + "forces = await client.select_time_series(\"lsst.sal.MTM1M3.forceActuatorData\", \"*\", \n", + " Time(start, scale='utc'), Time(end, scale='utc'))\n", + "timestamp = forces.index[0].isoformat().split('.')[0].replace('-','').replace(':','')\n", + "os.makedirs(Path(dir_name) / f\"movie_{timestamp}\", exist_ok=True)\n", + "[auto_zmin, auto_zmax, auto_lateral_max, forces_zero] = \\\n", + " get_zero_values_and_limits(forces, subtract_baseline, baseline_t0, baseline_t1)\n", + "if autoScale:\n", + " zmin = auto_zmin\n", + " zmax = auto_zmax\n", + " lateral_max = auto_lateral_max\n", + "\n", + "# Build the individual frames\n", + "fig = plt.figure(figsize=(16,16))\n", + "for n in range(0, len(forces), frame_n):\n", + " ax1 = fig.add_subplot(2,2,1)\n", + " actuator_layout(ax1)\n", + " ax2 = fig.add_subplot(2,2,2, projection='3d')\n", + " bar_chart_z(forces, forces_zero, ax2, n, zmin, zmax)\n", + " ax3 = fig.add_subplot(2,2,3)\n", + " lateral_forces(forces, forces_zero, ax3, n, lateral_max)\n", + " ax4 = fig.add_subplot(2,2,4)\n", + " heat_map_z(forces, forces_zero, ax4, n, zmin, zmax)\n", + " plt.savefig(f\"{dir_name}/movie_{timestamp}/Frame_{n:05d}.png\")\n", + " plt.clf()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "len(forces)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Now build the movie" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "print(f\"\\033[1mThe movie name will be: {dir_name}movie_{timestamp}/m1m3_movie.mp4\\033[0m\")\n", + "\n", + "command = f\"ffmpeg -pattern_type glob -i '{dir_name}movie_{timestamp}/*.png' -f mp4 -vcodec libx264 -pix_fmt yuv420p -framerate 50 -y {dir_name}movie_{timestamp}/m1m3_movie.mp4\"\n", + "args = shlex.split(command)\n", + "build_movie = subprocess.Popen(args)\n", + "build_movie.wait()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.10.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/SITCOMTN-092_force_actuator_snapshot_during_slew.ipynb b/notebooks/SITCOMTN-092_force_actuator_snapshot_during_slew.ipynb new file mode 100644 index 0000000..b269fe2 --- /dev/null +++ b/notebooks/SITCOMTN-092_force_actuator_snapshot_during_slew.ipynb @@ -0,0 +1,255 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5223f18-41bc-436a-838e-acdbce522a8d", + "metadata": {}, + "source": [ + "# [SITCOMTN-092] - M1M3 Inertia Compensation Performance - Force Actuators Snapshot\n", + "\n", + "Following [SITCOM-1115], we want to have snapshots of the forces applied to the force actuators during a slew. \n", + "\n", + "Thinking of a design, this will consist of a function that will receive:\n", + "- a topic associated with one of the forces applied to the force actuators\n", + "- a dayObs\n", + "- a slewID\n", + "\n", + "Refer to the [README.md] file for details on how to set up this repository in your environment. \n", + "\n", + "[lsst-sitcom/summit_utils]: https://github.com/lsst-sitcom/summit_utils\n", + "[README.md]: https://github.com/lsst-sitcom/notebooks_vandv/blob/develop/README.md\n", + "[SITCOM-1115]: https://jira.lsstcorp.org/browse/SITCOM-1115\n", + "[SITCOMTN-092]: https://sitcomtn-092.lsst.io/\n", + "\n", + "## Notebook Setup\n", + "\n", + "We start setting up the notebook's variables that are propagated in our analysis. \n", + "Here is a short description about each of them:\n", + "\n", + "```\n", + "day_obs : int\n", + " The associated day_obs of the slew event we are interested in.\n", + "slew_id : int\n", + " The associated slew event number. Starts at 0 every night.\n", + "m1m3_topic : str\n", + " M1M3 telemetry that we want to use for plots.\n", + " See the notes below for more details.\n", + "summary_function : str\n", + " A string used to represent a statistical function that we will\n", + " apply to the telemetry of each force actuator over the time window\n", + " associated with the TMA event. Current options are:\n", + " mean, min, max, std\n", + "```\n", + "The available options for `m1m3_topic` are:\n", + "\n", + "- [appliedAccelerationForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedaccelerationforces)\n", + "- [appliedAzimuthForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedazimuthforces)\n", + "- [appliedBalanceForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedbalanceforces)\n", + "- [appliedCylinderForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedcylinderforces)\n", + "- [appliedElevationForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedelevationforces)\n", + "- [appliedForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedforces)\n", + "- [appliedThermalForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedthermalforces)\n", + "- [appliedVelocityForces](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#appliedvelocityforces)\n", + "- [forceActuatorData](https://ts-xml.lsst.io/sal_interfaces/MTM1M3.html#forceactuatordata)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dddfb070-89b4-4abf-814b-736fe51b1f9d", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs = 20231212\n", + "slew_id = 300\n", + "m1m3_topic = \"forceActuatorData\"\n", + "summary_function = \"mean\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef2659a0-eb1a-4a08-9648-4ff0c0361045", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext lab_black\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a7241bb-0d00-485a-b10c-6726c8e31804", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from astropy.time import Time\n", + "from pathlib import Path\n", + "\n", + "from lsst.summit.utils.efdUtils import EfdClient, getEfdData\n", + "from lsst.summit.utils.tmaUtils import getCommandsDuringEvent, TMAEvent, TMAEventMaker\n", + "from lsst.sitcom.vandv.logger import create_logger\n", + "from lsst.sitcom.vandv import m1m3\n", + "from lsst.ts.xml.tables.m1m3 import FATable\n", + "\n", + "log = create_logger(\"SITCOMTN-092\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2489fed7-a3bb-44fe-bf9a-3ad707de3745", + "metadata": {}, + "outputs": [], + "source": [ + "plot_name = \"SITCOMTN-092: Force Actuators Snapshot\"\n", + "\n", + "plot_path = Path(\"./plots\")\n", + "plot_path.mkdir(exist_ok=True, parents=True)\n", + "\n", + "event_maker = TMAEventMaker()" + ] + }, + { + "cell_type": "markdown", + "id": "1b893dfb-1553-498e-b3c8-12cca04d6776", + "metadata": {}, + "source": [ + "## Helper Functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eabad634-0b9b-4911-bcfc-f2da42e698b6", + "metadata": {}, + "outputs": [], + "source": [ + "topic = f\"lsst.sal.MTM1M3.{m1m3_topic}\"\n", + "\n", + "if summary_function.strip().lower() == \"mean\":\n", + " func = np.mean\n", + "elif summary_function.strip().lower() == \"min\":\n", + " func = np.min\n", + "elif summary_function.strip().lower() == \"max\":\n", + " func = np.max\n", + "elif summary_function.strip().lower() == \"std\":\n", + " func = np.std" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48f067fe-b183-4f80-af9d-93d59f840d24", + "metadata": {}, + "outputs": [], + "source": [ + "# Refrieve the relevant event\n", + "evt = event_maker.getEvent(day_obs, slew_id)\n", + "if evt is None:\n", + " raise ValueError(f\"Cannot find slew {slew_id} on day-obs {day_obs}\")\n", + "\n", + "log.debug(\n", + " f\"Found event - day_obs={evt.dayObs} seq_num={evt.seqNum} \"\n", + " f\"type={evt.type.name} end={evt.endReason.name}\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65baeda7-c16f-4676-ad32-3b9474633b4e", + "metadata": {}, + "outputs": [], + "source": [ + "# Query data\n", + "df = getEfdData(\n", + " event_maker.client,\n", + " topic,\n", + " event=evt,\n", + " warn=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "009851cc-ff95-4ede-a0cf-bd4f5f27b508", + "metadata": {}, + "outputs": [], + "source": [ + "# Clean up the data\n", + "cols = [c for c in df.columns if (\"xForce\" in c or \"yForce\" in c or \"zForce\" in c)]\n", + "series = df[cols].apply(func)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18938e74-60b8-486a-9535-788cee6ea367", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the snapshot\n", + "%matplotlib inline\n", + "fig, (ax_z, ax_y, ax_x) = plt.subplots(num=plot_name, figsize=(14, 5), ncols=3)\n", + "\n", + "ax_z = m1m3.snapshot_forces_fa_map(\n", + " ax_z, series, prefix=\"zForce\", title=f\"{m1m3_topic} - Z\"\n", + ")\n", + "ax_y = m1m3.snapshot_forces_fa_map(\n", + " ax_y, series, prefix=\"yForce\", title=f\"{m1m3_topic} - Y\"\n", + ")\n", + "ax_x = m1m3.snapshot_forces_fa_map(\n", + " ax_x, series, prefix=\"xForce\", title=f\"{m1m3_topic} - X\"\n", + ")\n", + "\n", + "fig.suptitle(f\"{plot_name}\\n day_obs={day_obs}, slew_id={slew_id}\")\n", + "fig.tight_layout()\n", + "fig.savefig(plot_path / f\"{plot_name}.png\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "379d32ae-43eb-4341-8a28-c5e6f295b148", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d1f7922-2e09-4294-862c-daeb20d5b7a1", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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 +} diff --git a/notebooks/SITCOMTN-092_inertia_compensation_single_slew_hp.ipynb b/notebooks/SITCOMTN-092_inertia_compensation_single_slew_hp.ipynb new file mode 100644 index 0000000..1e87bf1 --- /dev/null +++ b/notebooks/SITCOMTN-092_inertia_compensation_single_slew_hp.ipynb @@ -0,0 +1,1547 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5816c9f6-113f-4882-ac8a-2bad48b929cc", + "metadata": {}, + "source": [ + "# [SITCOMTN-092] - M1M3 Inertia Compensation Performance with Hard Points Forces\n", + "\n", + "We need plots and metrics to evaluate the M1M3 Inertia Compensation System (ICS) performance as described in [SITCOM-989]. \n", + "For more information on the data analysis and success criteria, please see the [SITCOMTN-092] tech note. \n", + "\n", + "Examples of plots are:\n", + "\n", + "* Hardpoint Load Cell Forces Minima and Maxima during slews as a function of time.\n", + "* Correlate the plots above with accelerations, velocities, and positions.\n", + "* (any other ideas?)\n", + "\n", + "Petr asked to analyze the data obtained when slewing the telescope around 80 deg in elevation with and without inertia forces. \n", + "The two datasets below that he used as an example contain movement from -100 deg in azimuth to 100 deg in a single slew. \n", + "In both cases, we are using 30% motion settings in azimuth. \n", + "\n", + "* [M1M3 TMA Inertial forces Chronograph Dashboard on 2023-08-02 22:02 - 2023-08-02 22:04 UTC]\n", + "* [M1M3 TMA Inertial forces Chronograph Dashboard on 2023-07-28 02:15 - 2023-07-28 02:17 UTC]\n", + "\n", + "Added a new dataset containing similar data but with a 50% azimuth motion settings. \n", + "\n", + "* [M1M3 TMA Inertial forces Chronograph Dashboard on 2023-08-03 03:20 - 2023-08-03 03:22 UTC]\n", + "\n", + "\n", + "The bulk analysis has been moved to [lsst-sitcom/summit_utils]. \n", + "You will need to have it cloned and use the `tickets/DM-41232` branch until it is done. \n", + "Once the ticket is complete and the PR is merged, use `sitcom-performance-analysis` or `develop`. \n", + "Refer to the [README.md] file for details on how to set up this repository in your environment. \n", + "\n", + "\n", + "[lsst-sitcom/summit_utils]: https://github.com/lsst-sitcom/summit_utils\n", + "[README.md]: https://github.com/lsst-sitcom/notebooks_vandv/blob/develop/README.md\n", + "[SITCOM-989]: https://jira.lsstcorp.org/browse/SITCOM-989\n", + "[SITCOMTN-092]: https://sitcomtn-092.lsst.io/\n", + "\n", + "\n", + "[M1M3 TMA Inertial forces Chronograph Dashboard on 2023-08-02 22:02 - 2023-08-02 22:04 UTC]: https://summit-lsp.lsst.codes/chronograf/sources/1/dashboards/252?redirect=%2Flogin%3Fredirect%3D%252Fsources%252F1%252Fdashboards%252F252%253Frefresh%253D30s%2526tempVars%255BDownsample%255D%253DDefault%2526tempVars%255BFunction%255D%253Draw%2526lower%253Dnow%2528%2529%252520-%25252015m%2526zoomedLower%253D2023-08-02T21%25253A23%25253A19.366Z%2526zoomedUpper%253D2023-08-02T21%25253A23%25253A23.843Z&refresh=Paused&tempVars%5BDownsample%5D=Default&tempVars%5BFunction%5D=mean%28%29&lower=2023-08-02T20%3A00%3A00.000Z&upper=2023-08-03T02%3A00%3A00.000Z&zoomedLower=2023-08-02T22%3A02%3A24.799Z&zoomedUpper=2023-08-02T22%3A04%3A02.450Zhttps://summit-lsp.lsst.codes/chronograf/sources/1/dashboards/252?redirect=%2Flogin%3Fredirect%3D%252Fsources%252F1%252Fdashboards%252F252%253Frefresh%253D30s%2526tempVars%255BDownsample%255D%253DDefault%2526tempVars%255BFunction%255D%253Draw%2526lower%253Dnow%2528%2529%252520-%25252015m%2526zoomedLower%253D2023-08-02T21%25253A23%25253A19.366Z%2526zoomedUpper%253D2023-08-02T21%25253A23%25253A23.843Z&refresh=Paused&tempVars%5BDownsample%5D=Default&tempVars%5BFunction%5D=mean%28%29&lower=2023-08-02T20%3A00%3A00.000Z&upper=2023-08-03T02%3A00%3A00.000Z&zoomedLower=2023-08-02T22%3A02%3A24.799Z&zoomedUpper=2023-08-02T22%3A04%3A02.450Z\n", + "\n", + "\n", + "[M1M3 TMA Inertial forces Chronograph Dashboard on 2023-07-28 02:15 - 2023-07-28 02:17 UTC]:https://summit-lsp.lsst.codes/chronograf/sources/1/dashboards/252?redirect=%2Flogin%3Fredirect%3D%252Fsources%252F1%252Fdashboards%252F252%253Frefresh%253D30s%2526tempVars%255BDownsample%255D%253DDefault%2526tempVars%255BFunction%255D%253Draw%2526lower%253Dnow%2528%2529%252520-%25252015m%2526zoomedLower%253D2023-08-02T21%25253A23%25253A19.366Z%2526zoomedUpper%253D2023-08-02T21%25253A23%25253A23.843Z&refresh=Paused&tempVars%5BDownsample%5D=Default&tempVars%5BFunction%5D=mean%28%29&lower=2023-07-28T02%3A00%3A00.000Z&upper=2023-07-28T03%3A30%3A00.000Z&zoomedLower=2023-07-28T02%3A15%3A45.730Z&zoomedUpper=2023-07-28T02%3A17%3A11.966Z\n", + "\n", + "[M1M3 TMA Inertial forces Chronograph Dashboard on 2023-08-03 03:20 - 2023-08-03 03:22 UTC]:https://summit-lsp.lsst.codes/chronograf/sources/1/dashboards/252?redirect=%2Flogin%3Fredirect%3D%252Fsources%252F1%252Fdashboards%252F252%253Frefresh%253D30s%2526tempVars%255BDownsample%255D%253DDefault%2526tempVars%255BFunction%255D%253Draw%2526lower%253Dnow%2528%2529%252520-%25252015m%2526zoomedLower%253D2023-08-02T21%25253A23%25253A19.366Z%2526zoomedUpper%253D2023-08-02T21%25253A23%25253A23.843Z&refresh=Paused&tempVars%5BDownsample%5D=5Hz&tempVars%5BFunction%5D=mean%28%29&lower=2023-08-03T03%3A20%3A00.000Z&upper=2023-08-03T03%3A22%3A00.000Z" + ] + }, + { + "cell_type": "markdown", + "id": "fbc394ad-1b3e-45de-b0ee-62732a411ef4", + "metadata": {}, + "source": [ + "## Notebook Preparation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "931c530c-a5bf-483e-a3d7-c3d3cf6b8bef", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "%load_ext lab_black\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6402f4cc-296b-48d8-99cc-9a1f85992c99", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "from astropy.time import Time\n", + "from pathlib import Path\n", + "\n", + "import awkward as ak\n", + "import awkward_pandas as akpd\n", + "\n", + "from operator import attrgetter\n", + "\n", + "# This notebooks requires `summit_utils` with the `tickets/DM-41232` branch.\n", + "# Once this branch is merged, use `develop` or `sitcom-performance-analysis` instead.\n", + "from lsst.summit.utils.m1m3 import inertia_compensation_system as m1m3_ics\n", + "from lsst.summit.utils.m1m3.plots import inertia_compensation_system as m1m3_ics_plots\n", + "from lsst.summit.utils.blockUtils import BlockParser\n", + "from lsst.summit.utils.efdUtils import makeEfdClient, getEfdData\n", + "from lsst.summit.utils.tmaUtils import (\n", + " getCommandsDuringEvent,\n", + " TMAEvent,\n", + " TMAEventMaker,\n", + " TMAState,\n", + ")\n", + "from lsst.summit.utils.utils import setupLogging\n", + "from lsst.sitcom.vandv.logger import create_logger\n", + "\n", + "setupLogging()" + ] + }, + { + "cell_type": "markdown", + "id": "4532f599-5201-49b4-8899-4374291d5be5", + "metadata": {}, + "source": [ + "## Create Event Maker\n", + "\n", + "We want to create a single instance of the `TMAEventMaker` object. \n", + "Each instance might be quite heavy. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68db14d4-41c9-4cd7-a606-53cd703d3e43", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot_path = Path(\"./plots\")\n", + "plot_path.mkdir(exist_ok=True, parents=True)\n", + "\n", + "log = create_logger(\"m1m3_ics_slew\")\n", + "log.setLevel(\"DEBUG\")\n", + "log.propagate = True\n", + "\n", + "event_maker = TMAEventMaker()\n", + "efd_client = makeEfdClient()" + ] + }, + { + "cell_type": "markdown", + "id": "05995898-fdca-4105-a543-0ab79956df83", + "metadata": {}, + "source": [ + "## Helper functions\n", + "### analyze_m1m3_ics_slew_event" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec7ce176-c389-4fdc-a525-4c320b4ac3fc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def analyze_m1m3_ics_slew_event(begin, end, event_maker, log, path=None):\n", + " \"\"\"\n", + " Plot the ICS performance analysis in a single slew.\n", + " Three axes are created. The top representes the hard point forces.\n", + " The second shows the velocity in azimuth and elevation.\n", + " The thierd shows the torques in azimuth and elevation.\n", + "\n", + " Parameters\n", + " ----------\n", + " begin : str\n", + " Approximate time of when the slew began in UTC using iso format.\n", + " end : str\n", + " Approximate time of when the slew ended in UTC using iso format.\n", + " event_maker :\n", + " TMA event maker\n", + " log :\n", + " Logger\n", + " path : Path, optional\n", + " Path to store plots\n", + " \"\"\"\n", + " time_begin = Time(begin, format=\"isot\", scale=\"utc\")\n", + " time_end = Time(end, format=\"isot\", scale=\"utc\")\n", + " time_half = time_begin + (time_end - time_begin) * 0.5\n", + "\n", + " event = event_maker.findEvent(time_half)\n", + " print(\n", + " f\"Slew happened from {begin=} to {end=} \"\n", + " f\"and has sequence number {event.seqNum} \"\n", + " f\"and observation day {event.dayObs}\"\n", + " )\n", + "\n", + " data = m1m3_ics.M1M3ICSAnalysis(event, event_maker.client, log=log)\n", + " name = f\"ics_performance_ics_hp{data.stats.ics_enabled}_{data.stats.day_obs}_sn{data.stats.seq_num}_v{data.stats.version}\"\n", + "\n", + " commands = getCommandsDuringEvent(\n", + " event_maker.client, event, hardpoint_commands_to_plot\n", + " )\n", + "\n", + " fig = plt.figure(num=name, figsize=(10, 5), dpi=120)\n", + " fig = m1m3_ics_plots.plot_hp_measured_data(\n", + " data, log=data.log, fig=fig, commands=commands\n", + " )\n", + "\n", + " if path:\n", + " fig.savefig(str(path / f\"{name}\"))\n", + "\n", + " # plt.show()\n", + "\n", + " return data" + ] + }, + { + "cell_type": "markdown", + "id": "577c452f-dc47-40c7-aa07-ebf880b5a2c4", + "metadata": {}, + "source": [ + "### print_block_events_and_azel_diff" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b526be74-b797-48ad-aed0-10874d74fe11", + "metadata": {}, + "outputs": [], + "source": [ + "def print_block_events_and_azel_diff(day_obs, block_id, _print=False):\n", + " block_parser = BlockParser(day_obs)\n", + " events = event_maker.getEvents(day_obs)\n", + "\n", + " block_events = set()\n", + " seq_num_list = block_parser.getSeqNums(block_id)\n", + "\n", + " for seq_num in seq_num_list:\n", + " found = block_parser.getEventsForBlock(events, block_id, seq_num)\n", + " block_events.update(found)\n", + "\n", + " block_events = sorted(list(block_events), key=attrgetter(\"seqNum\"))\n", + " good_events = []\n", + "\n", + " for evt in block_events:\n", + " az = getEfdData(\n", + " client=efd_client,\n", + " topic=\"lsst.sal.MTMount.azimuth\",\n", + " columns=[\"actualPosition\"],\n", + " event=evt,\n", + " )\n", + "\n", + " el = getEfdData(\n", + " client=efd_client,\n", + " topic=\"lsst.sal.MTMount.elevation\",\n", + " columns=[\"actualPosition\"],\n", + " event=evt,\n", + " )\n", + "\n", + " az_diff = az.actualPosition.iloc[-1] - az.actualPosition.iloc[0]\n", + " el_diff = el.actualPosition.iloc[-1] - el.actualPosition.iloc[0]\n", + "\n", + " if _print:\n", + " print(f\"{evt.seqNum}, {az_diff:8.2f}, {el_diff:8.2f}\")\n", + "\n", + " if abs(az_diff - 10) < 1 or abs(el_diff - 12) < 1:\n", + " good_events.append(evt)\n", + "\n", + " return good_events" + ] + }, + { + "cell_type": "markdown", + "id": "1fb58920-2461-4425-acc0-9b05feea99e2", + "metadata": {}, + "source": [ + "### get_events_for_blocks_in_a_day" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "001fbcee-0c0d-450b-89c1-1317ce12975b", + "metadata": {}, + "outputs": [], + "source": [ + "def get_events_for_blocks_in_a_day(day_obs, block_id_list):\n", + " \"\"\"\n", + " Retrieves all the TMA events in a `day_obs` that belong to the blocks\n", + " listed in the `block_id_list`.\n", + "\n", + " Parameters\n", + " ----------\n", + " day_obs : int\n", + " YYYYMMDD representation of a day obs.\n", + " block_id_list : list of int\n", + " A list containing the BLOCK indexes. E.g.: for BLOCK-146, use 146.\n", + "\n", + " Returns\n", + " -------\n", + " block_events : set\n", + " Events associated with any of the blocks in `block_id_list`\n", + " in `day_obs`.\n", + " \"\"\"\n", + " block_parser = BlockParser(day_obs)\n", + " events = event_maker.getEvents(day_obs)\n", + "\n", + " block_events = set()\n", + "\n", + " for block_id in block_id_list:\n", + " seq_num_list = block_parser.getSeqNums(block_id)\n", + "\n", + " for seq_num in seq_num_list:\n", + " found = block_parser.getEventsForBlock(events, block_id, seq_num)\n", + "\n", + " block_events.update(found)\n", + "\n", + " block_events = sorted(list(block_events), key=attrgetter(\"seqNum\"))\n", + " return block_events" + ] + }, + { + "cell_type": "markdown", + "id": "e2193973-8497-474e-8923-9321d2bf1468", + "metadata": {}, + "source": [ + "### get_hp_minmax_during_events" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84600eea-877d-43d9-80c6-e55d8da7520a", + "metadata": {}, + "outputs": [], + "source": [ + "def get_hp_minmax_during_events(\n", + " block_events, event_type=TMAState.SLEWING, verbose=False\n", + "):\n", + "\n", + " df = pd.DataFrame()\n", + "\n", + " for evt in block_events:\n", + "\n", + " if evt.type != event_type:\n", + " continue\n", + "\n", + " if evt.endReason == TMAState.FAULT:\n", + " print(f\"Event {evt.seqNum} on {evt.dayObs} faulted. Ignoring it.\")\n", + " continue\n", + "\n", + " az = getEfdData(\n", + " client=efd_client,\n", + " topic=\"lsst.sal.MTMount.azimuth\",\n", + " columns=[\"actualPosition\"],\n", + " event=evt,\n", + " )\n", + "\n", + " el = getEfdData(\n", + " client=efd_client,\n", + " topic=\"lsst.sal.MTMount.elevation\",\n", + " columns=[\"actualPosition\"],\n", + " event=evt,\n", + " )\n", + "\n", + " measured_forces = getEfdData(\n", + " client=efd_client,\n", + " topic=\"lsst.sal.MTM1M3.hardpointActuatorData\",\n", + " columns=[f\"measuredForce{i}\" for i in range(6)],\n", + " event=evt,\n", + " )\n", + "\n", + " try:\n", + " az_diff = az.actualPosition.iloc[-1] - az.actualPosition.iloc[0]\n", + " el_diff = el.actualPosition.iloc[-1] - el.actualPosition.iloc[0]\n", + " except AttributeError:\n", + " continue\n", + "\n", + " if verbose:\n", + " print(\n", + " f\"{evt.blockInfos[0].blockNumber}, \"\n", + " f\"{evt.seqNum}, \"\n", + " f\"{az_diff:8.2f}, \"\n", + " f\"{el_diff:8.2f}, \"\n", + " f\"{measured_forces.min().min():8.2f}, \"\n", + " f\"{measured_forces.max().max():8.2f} \"\n", + " )\n", + "\n", + " my_dict = dict(\n", + " seq_num=evt.seqNum,\n", + " block_id=evt.blockInfos[0].blockNumber,\n", + " delta_az=az_diff,\n", + " delta_el=el_diff,\n", + " min_forces=measured_forces.min().min(),\n", + " max_forces=measured_forces.max().max(),\n", + " )\n", + "\n", + " my_df = pd.DataFrame([my_dict])\n", + " df = pd.concat([df, my_df], ignore_index=True)\n", + "\n", + " return df" + ] + }, + { + "cell_type": "markdown", + "id": "b2d6771f-8078-4243-95e4-36f64e232e94", + "metadata": {}, + "source": [ + "### histogram_during_slews" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82bbf9ac-1f5a-4987-8204-b5cd35ab8ae2", + "metadata": {}, + "outputs": [], + "source": [ + "def histogram_during_slews(my_df, day_obs):\n", + "\n", + " fig, (min_ax, max_ax) = plt.subplots(figsize=(10, 5), ncols=2, sharey=True)\n", + "\n", + " block_ids = my_df.block_id.unique()\n", + " sub_dfs = [my_df[my_df.block_id == block_id] for block_id in block_ids]\n", + " labels = [\n", + " f\"BLOCK-{block_id} - total {my_df[my_df.block_id == block_id].index.size}\"\n", + " for block_id in block_ids\n", + " ]\n", + "\n", + " min_ax.hist(\n", + " [df[\"min_forces\"] for df in sub_dfs],\n", + " ec=\"white\",\n", + " alpha=0.75,\n", + " label=labels,\n", + " log=True,\n", + " )\n", + " max_ax.hist(\n", + " [df[\"max_forces\"] for df in sub_dfs],\n", + " ec=\"white\",\n", + " alpha=0.75,\n", + " label=labels,\n", + " log=True,\n", + " )\n", + "\n", + " min_ax.grid(alpha=0.3)\n", + " min_ax.set_xlabel(\"Minimum measured forces on\\n the hardpoints during a slew [N]\")\n", + " min_ax.set_ylabel(\"Number of slews\")\n", + " min_ax.axvline(-450, ls=\":\", c=\"black\", alpha=0.5, label=\"Operational limit\", lw=2)\n", + " min_ax.axvline(-900, ls=\"--\", c=\"red\", alpha=0.5, label=\"Fatigue limit\", lw=2)\n", + " min_ax.legend()\n", + "\n", + " max_ax.grid(alpha=0.3)\n", + " max_ax.set_xlabel(\"Maximum measured forces on\\n the hardpoints during a slew [N]\")\n", + " # max_ax.set_ylabel(\"Number of slews\")\n", + " max_ax.axvline(450, ls=\":\", c=\"black\", alpha=0.5, label=\"Operational limit\", lw=2)\n", + " max_ax.axvline(900, ls=\"--\", c=\"red\", alpha=0.5, label=\"Fatigue limit\", lw=2)\n", + " max_ax.legend()\n", + "\n", + " fig.suptitle(\n", + " f\"Histogram with the number of slews with\\n\"\n", + " f\"different minimum and maximum measured forces on the hardpoints.\\n\"\n", + " f\"DayObs {day_obs}, total of {my_df.index.size} slews\",\n", + " )\n", + "\n", + " fig.tight_layout()\n", + " fig.savefig(f\"./plots/histogram_hp_minmax_dayobs_{day_obs}.png\")\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "c23f96de-2a96-4500-ae59-c1d3d3054ac4", + "metadata": {}, + "source": [ + "## Analyze M1M3 ICS per Slew Event\n", + "\n", + "The three cases below shows how each slew event is analyzed. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7232f539-0f5e-41f7-b996-a21691738f03", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slew_data = {\n", + " # First data obtained at 30% motion settings and ICS disabled\n", + " \"20230727_ics_False_30\": dict(\n", + " begin=\"2023-07-28T02:17:15\", end=\"2023-07-28T02:17:55\"\n", + " ),\n", + " # Second data obtained at 30% motion settings and ICS enabled\n", + " \"20230802_ics_True_30\": dict(\n", + " begin=\"2023-08-02T22:02:30\", end=\"2023-08-02T22:04:00\"\n", + " ),\n", + " # Third data obtained at 50% motion settings and ICS enabled\n", + " \"20230802_ics_True_50\": dict(\n", + " begin=\"2023-08-03T03:20:30\", end=\"2023-08-03T03:21:20\"\n", + " ),\n", + " # More recent data obtained at Full Performance and ICS enabled\n", + " \"20231129_ics_True_100\": dict(\n", + " begin=\"2023-11-30T08:46:44\", end=\"2023-11-30T08:47:45\"\n", + " ),\n", + "}\n", + "\n", + "hardpoint_commands_to_plot = [\n", + " \"lsst.sal.MTM1M3.command_setSlewFlag\",\n", + " \"lsst.sal.MTM1M3.command_enableHardpointCorrections\",\n", + " \"lsst.sal.MTM1M3.command_clearSlewFlag\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "615c94ce-b77b-4afd-820b-fe37f5a181ca", + "metadata": {}, + "source": [ + "### Case 1 - ICS Disabled and 30% TMA Performance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c510e63-2ef5-4480-a7ff-990749fee22d", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " slew_data[\"20230727_ics_False_30\"][\"begin\"],\n", + " slew_data[\"20230727_ics_False_30\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "data.stats" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca125d38-a640-4d3d-8b9f-09cf73fd2c4d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "a2588f28-ddb5-445e-b12b-8116cec5a091", + "metadata": {}, + "source": [ + "### Case 2 - ICS Enabled and 30% TMA Performance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "974c2eae-751d-4d85-b115-ca34452f7664", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " slew_data[\"20230802_ics_True_30\"][\"begin\"],\n", + " slew_data[\"20230802_ics_True_30\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)\n", + "print(data.stats.forces)" + ] + }, + { + "cell_type": "markdown", + "id": "73614717-096a-4a66-8261-7004bf8b7bec", + "metadata": {}, + "source": [ + "### Case 3 - ICS Enabled and 50% TMA Performance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a94ba52b-2e6c-428d-9816-242f406dd86f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " slew_data[\"20230802_ics_True_50\"][\"begin\"],\n", + " slew_data[\"20230802_ics_True_50\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)" + ] + }, + { + "cell_type": "markdown", + "id": "80d1f50e-775e-463b-aa39-1ac35fc4de17", + "metadata": {}, + "source": [ + "### Case 4 - ICS Enabled and 100% TMA Performance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "999f9a6f-edf8-45cb-9a66-1fe06876f564", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " slew_data[\"20231129_ics_True_100\"][\"begin\"],\n", + " slew_data[\"20231129_ics_True_100\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)" + ] + }, + { + "cell_type": "markdown", + "id": "fdf97514-2ab1-4e07-9c39-f83a16cdaa05", + "metadata": {}, + "source": [ + "## Block Anaysis\n", + "\n", + "Here we will provide a bit more of details on the performance of slews obtained within specific blocks. \n", + "The events above, when looked individually, do not tell us much on the performance. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9aab12f1-a45d-471d-b331-077eb0c7f847", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the day_obs list\n", + "day_obs_list = [20230727, 20230802, 20231129, 20240109]\n", + "\n", + "# For each day_obs in the list determine which blocks were run and put\n", + "# the list of blocks into the block_list.\n", + "block_list = []\n", + "\n", + "for day_obs in day_obs_list:\n", + " block_parser = BlockParser(day_obs)\n", + " blocks = block_parser.getBlockNums()\n", + " block_list.append(blocks)\n", + "\n", + "# Put the variable length nested list into an awkward array and then\n", + "# put that into a pandas dataframe with the awkward array extension\n", + "# so that the list of blocks is shown in a column.\n", + "blocks = ak.Array({\"day_obs\": day_obs_list, \"blocks\": block_list})\n", + "series = akpd.from_awkward(blocks)\n", + "pandas_df = series.ak.to_columns(extract_all=True)\n", + "pandas_df" + ] + }, + { + "cell_type": "markdown", + "id": "6e3e8a61-9d7b-416b-a9de-12c8233bc619", + "metadata": {}, + "source": [ + "Here we have a better idea of the blocks obtained on those days. BLOCK-5 is an invalid block.\n", + "\n", + "- [BLOCK-5] - Invalid. It does not even have a JSON file.\n", + "- [BLOCK-79] - Long and short movements in elevation only and in azimuth only. Similar to gateway tests. ICS data collection.\n", + "- [BLOCK-81] - Long slews in Azimuth for different elevation angles. ICS data collection.\n", + "- [BLOCK-82] - Long slews in Azimuth for different elevation angles. We ran this block multiple times this day: first with 30%El/30%Az, second with 30%El/40%Az, finally 30%El/50%Az.\n", + "- [BLOCK-13] - M1M3 Bump Test\n", + "- [BLOCK-121] - Large movements in azimuth at Zenith and near the horizon\n", + "- [BLOCK-137] - Soak tests\n", + "- [BLOCK-184] - Short and Long Az/El/combined Slews with Az Jerk = 20, El Jerk = 10\n", + "- [BLOCK-186] - Short and Long Az/El/combined Slews with 70%, Az Jerk = 2 and El Jerk = 1\n", + "\n", + "[BLOCK-5]: https://jira.lsstcorp.org/browse/BLOCK-5\n", + "[BLOCK-13]: https://jira.lsstcorp.org/browse/BLOCK-13\n", + "[BLOCK-79]: https://jira.lsstcorp.org/browse/BLOCK-79\n", + "[BLOCK-81]: https://jira.lsstcorp.org/browse/BLOCK-81\n", + "[BLOCK-82]: https://jira.lsstcorp.org/browse/BLOCK-82\n", + "[BLOCK-121]: https://jira.lsstcorp.org/browse/BLOCK-121\n", + "[BLOCK-137]: https://jira.lsstcorp.org/browse/BLOCK-137\n", + "[BLOCK-184]: https://rubinobs.atlassian.net/browse/BLOCK-184\n", + "[BLOCK-186]: https://rubinobs.atlassian.net/browse/BLOCK-186" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ec1ad03-ede2-4f46-90cc-d6611dac902e", + "metadata": {}, + "outputs": [], + "source": [ + "events_block_81 = print_block_events_and_azel_diff(20230727, 81)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33e162f5-badd-4e03-a705-64a189bb4424", + "metadata": {}, + "outputs": [], + "source": [ + "for evt in events_block_81:\n", + " data = analyze_m1m3_ics_slew_event(\n", + " evt.begin.isot,\n", + " evt.end.isot,\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae7c5961-ea4b-4da3-aae5-3b85096ef33b", + "metadata": {}, + "outputs": [], + "source": [ + "events_block_82 = print_block_events_and_azel_diff(20230802, 82)" + ] + }, + { + "cell_type": "markdown", + "id": "2bb8e913-b203-4de1-ad2a-a31c2faa30b8", + "metadata": {}, + "source": [ + "Now we can compare apples with apples. Let's compare the following data:\n", + "\n", + "```\n", + "BLOCK-81 - Seq Num 49 - Delta Az = 9.98, Delta El = 0\n", + "BLOCK-82 - Seq Num 27 - Delta Az = 9.99, Delta El = 0\n", + "\n", + "BLOCK-81 - Seq Num 57 - Delta Az = 0, Delta El = -11.99\n", + "BLOCK-82 - Seq Num 43 - Delta Az = 0, Delta El = -11.99\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e23ee16-7682-4eba-9f85-666dfa15224c", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs_list = [20230727, 20230802]\n", + "block_id_list = [81, 82]\n", + "seq_num_list_of_list = [[84, 57], [27, 43]]\n", + "block_data = {}\n", + "\n", + "for day_obs, block_id, seq_num_list in zip(\n", + " day_obs_list, block_id_list, seq_num_list_of_list\n", + "):\n", + " all_events = event_maker.getEvents(day_obs)\n", + "\n", + " for seq_num in seq_num_list:\n", + " print(day_obs, block_id, seq_num)\n", + " event = event_maker.getEvent(day_obs, seq_num)\n", + "\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{seq_num}\": {\n", + " \"begin\": event.begin.isot,\n", + " \"end\": event.end.isot,\n", + " }\n", + " }\n", + "\n", + " block_data.update(my_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "f88b458a-786f-47f9-9dbf-162bf614dad4", + "metadata": {}, + "source": [ + "### BLOCK-81 - Seq Num 49 - Delta Az = 9.98, Delta El = 0 - ICS-OFF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe44c51b-cd08-4234-9722-7b27ab5ddfc1", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20230727_81_84\"][\"begin\"],\n", + " block_data[\"20230727_81_84\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)" + ] + }, + { + "cell_type": "markdown", + "id": "3754a2ca-2e48-4ec0-9b5b-82463bee24c2", + "metadata": {}, + "source": [ + "### BLOCK-82 - Seq Num 27 - Delta Az = 9.98, Delta El = 0 - ICS-ON" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2f18740-a463-4233-80e7-f073555a6fb7", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20230802_82_27\"][\"begin\"],\n", + " block_data[\"20230802_82_27\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3c7c1ada-90fe-42cb-b30b-3c6b55444992", + "metadata": {}, + "source": [ + "### BLOCK-81 - Seq Num 57 - Delta Az = 0, Delta El = -11.99\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0121253-2cb1-44e1-bbe0-bfbabb356767", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20230727_81_57\"][\"begin\"],\n", + " block_data[\"20230727_81_57\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11b30bc1-01ba-461e-9563-faee2926e594", + "metadata": {}, + "source": [ + "### BLOCK-82 - Seq Num 43 - Delta Az = 0, Delta El = -11.99" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2396e357-854d-4cba-bd42-763d4681543f", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20230802_82_43\"][\"begin\"],\n", + " block_data[\"20230802_82_43\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "885ea2c0-9618-447e-a485-691a38e477b4", + "metadata": {}, + "source": [ + "### BLOCK-121 - Gyro Data Collection\n", + "\n", + "Data obtained on [2023-11-29].\n", + "\n", + "[BLOCK-121]: https://rubinobs.atlassian.net/browse/BLOCK-121\n", + "[2023-11-29]: https://summit-lsp.lsst.codes/rolex?log_date=2023-11-29" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75119028-9d04-49b8-9bd0-e2c536bd6802", + "metadata": {}, + "outputs": [], + "source": [ + "events_block_121 = print_block_events_and_azel_diff(20231129, 121)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88687245-5478-443e-a895-185ca24fdf0d", + "metadata": {}, + "outputs": [], + "source": [ + "block_id = 121\n", + "day_obs = 20231129\n", + "seq_num_list = [89, 97, 106]\n", + "block_data = {}\n", + "\n", + "all_events = event_maker.getEvents(day_obs)\n", + "\n", + "for seq_num in seq_num_list:\n", + " print(day_obs, block_id, seq_num)\n", + " event = event_maker.getEvent(day_obs, seq_num)\n", + "\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{seq_num}\": {\n", + " \"begin\": event.begin.isot,\n", + " \"end\": event.end.isot,\n", + " }\n", + " }\n", + "\n", + " block_data.update(my_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c34da460-ed55-48a4-af21-9df5fd1be9ee", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20231129_121_89\"][\"begin\"],\n", + " block_data[\"20231129_121_89\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee618bff-2c57-44dd-b029-dc97dda0d717", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20231129_121_97\"][\"begin\"],\n", + " block_data[\"20231129_121_97\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca80bcf7-3d35-4961-8ec2-76732793fd24", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_data[\"20231129_121_106\"][\"begin\"],\n", + " block_data[\"20231129_121_106\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)" + ] + }, + { + "cell_type": "markdown", + "id": "1cf0533a-aca4-475a-9bcc-18d78dfdb386", + "metadata": {}, + "source": [ + "### [BLOCK-167] - ICS - Acc On, Bal Off, Vel On, Booster Valves On\n", + "\n", + "Executed on [2023-12-14]\n", + "\n", + "[BLOCK-167]: https://rubinobs.atlassian.net/browse/BLOCK-167\n", + "[2023-12-14]: https://summit-lsp.lsst.codes/rolex?log_date=2023-12-14" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0ccb387-62f4-405c-a6b5-b0b01e224976", + "metadata": {}, + "outputs": [], + "source": [ + "list_of_events = get_events_for_blocks_in_a_day(20231214, [167])\n", + "\n", + "for evt in list_of_events:\n", + " print(evt.seqNum, evt.begin.isot, evt.end.isot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ae96aa0-4211-48b0-99f6-af69dba04114", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs = 20231214\n", + "block_id = 167\n", + "seq_num_list = [101]\n", + "block_167_data = {}\n", + "\n", + "all_events = event_maker.getEvents(day_obs)\n", + "\n", + "for evt in list_of_events:\n", + " print(day_obs, block_id, evt.seqNum)\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{evt.seqNum}\": {\n", + " \"begin\": evt.begin.isot,\n", + " \"end\": evt.end.isot,\n", + " }\n", + " }\n", + "\n", + " block_167_data.update(my_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5671ad65-fb06-45f5-874a-48973202104a", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_167_data[\"20231214_167_101\"][\"begin\"],\n", + " block_167_data[\"20231214_167_101\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be0f5ba3-5f22-4bbf-9ca7-b622321a03fa", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_167_data[\"20231214_167_102\"][\"begin\"],\n", + " block_167_data[\"20231214_167_102\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2041d180-9dd3-43e2-ad6f-7cd727c64c4e", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_167_data[\"20231214_167_103\"][\"begin\"],\n", + " block_167_data[\"20231214_167_103\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9ed12251-0036-4786-bba9-f1a1aef2abb7", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_167_data[\"20231214_167_658\"][\"begin\"],\n", + " block_167_data[\"20231214_167_658\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed10864e-3da0-411b-a656-60058426d83e", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_167_data[\"20231214_167_661\"][\"begin\"],\n", + " block_167_data[\"20231214_167_661\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7ac2dce3-9159-4495-8439-c2f34cedeeb5", + "metadata": {}, + "source": [ + "### [BLOCK-168] - ICS - Acc Off, Bal Off, Vel Off, Booster Valves On\n", + "\n", + "Executed on [2023-12-15]\n", + "\n", + "[2023-12-14]: https://summit-lsp.lsst.codes/rolex?log_date=2023-12-14\n", + "[BLOCK-168]: https://rubinobs.atlassian.net/browse/BLOCK-168" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88ae84fe-732f-4286-bfd7-b158877873d7", + "metadata": {}, + "outputs": [], + "source": [ + "list_of_events = get_events_for_blocks_in_a_day(20231214, [167])\n", + "\n", + "for evt in list_of_events:\n", + " print(evt.seqNum)" + ] + }, + { + "cell_type": "markdown", + "id": "2c8be345-0e5b-47bb-b948-4ab32a69af36", + "metadata": {}, + "source": [ + "### [BLOCK-127] and [BLOCK-178] - Short and long slews at 90% and 40%\n", + "\n", + "I want to evaluate the performance of the inertia compensation system at the end of the campaign. \n", + "I looked at the last datasets and 2024-01-05 was one of the last days with a decent amount of data. \n", + "I found that it contains many blocks, and I decided to work on some exploratory analysis. \n", + "This is what you will see here. I will start reviewing which blocks were executed. \n", + "I find that [BLOCK-127] - Dynamic Test 90% motion settings and [BLOCk-178] - M1M3 Accelerometer/Gyro Test 40-70% (limited Az) are the most interesting for such analysis. \n", + "\n", + "Ran in many nights. Including:\n", + "* [2024-01-05] - 40% Performance\n", + "\n", + "[BLOCK-127]: https://rubinobs.atlassian.net/browse/BLOCK-146\n", + "[BLOCK-178]: https://rubinobs.atlassian.net/browse/BLOCK-146\n", + "[2023-12-04]: https://summit-lsp.lsst.codes/rolex?log_date=2023-12-04\n", + "[2024-01-05]: https://summit-lsp.lsst.codes/rolex?log_date=2024-01-05" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13728728-4502-409f-a3aa-3a06095731d3", + "metadata": {}, + "outputs": [], + "source": [ + "# day_obs = 20231204\n", + "day_obs = 20240105 # the data is confusing\n", + "# day_obs = 20240104 # Not enough data\n", + "# day_obs = 20240103 #" + ] + }, + { + "cell_type": "markdown", + "id": "624bef8a-c509-495b-a352-05ead01c687f", + "metadata": {}, + "source": [ + "---\n", + "What are the blocks in that day?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb85f99c-c269-447a-aa89-4c6af21d10e8", + "metadata": {}, + "outputs": [], + "source": [ + "block_parser = BlockParser(day_obs)\n", + "block_id_list = block_parser.getBlockNums()\n", + "print(block_id_list)" + ] + }, + { + "cell_type": "markdown", + "id": "02a4f62f-5392-4908-9d0e-b8b1abbe91fd", + "metadata": {}, + "source": [ + "---\n", + "How many tma events per block?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d72b5d4-5450-4ed6-b682-3de9d0f41be3", + "metadata": {}, + "outputs": [], + "source": [ + "for block_id in block_id_list:\n", + " block_events = get_events_for_blocks_in_a_day(day_obs, [block_id])\n", + " print(f\"BLOCK-{block_id} - {len(block_events)} \")" + ] + }, + { + "cell_type": "markdown", + "id": "3015b15e-cd8c-4c75-9186-e508123d30d0", + "metadata": {}, + "source": [ + "---\n", + "How many tma slew events per block?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8c1af13-48a7-4ca6-a8f7-a516bbbc6e26", + "metadata": {}, + "outputs": [], + "source": [ + "for block_id in block_id_list:\n", + " block_events = get_events_for_blocks_in_a_day(day_obs, [block_id])\n", + " block_events = [evt for evt in block_events if evt.type == TMAState.SLEWING]\n", + " print(f\"BLOCK-{block_id} - {len(block_events)} \")" + ] + }, + { + "cell_type": "markdown", + "id": "2fb8cbc8-b7b0-4c0e-837a-0f9c61946f1b", + "metadata": {}, + "source": [ + "---\n", + "[BLOCK-127] corresponds to m1m3 dynamic tests at 90%, which should have long and short slews. \n", + "[BLOCK-178] contains gyro data collection slews at 40%, which is similar. \n", + "\n", + "[BLOCK-127]: https://rubinobs.atlassian.net/browse/BLOCK-127\n", + "[BLOCK-178]: https://rubinobs.atlassian.net/browse/BLOCK-178" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "286e73f9-d1c6-4ef4-bf63-40acd277c37a", + "metadata": {}, + "outputs": [], + "source": [ + "# list_of_day_obs = [20240103]\n", + "list_of_day_obs = [20240105]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2f8ba32-b251-4ea6-862a-6365e8c45b01", + "metadata": {}, + "outputs": [], + "source": [ + "events = []\n", + "for day_obs in list_of_day_obs:\n", + " print(f\" querying {day_obs}\")\n", + " block_events = get_events_for_blocks_in_a_day(\n", + " day_obs, [146, 178]\n", + " ) # 127, 146, 178 / 146, 176, 178\n", + " print(\" found \", len(block_events), \"for dayobs \", day_obs)\n", + " events.extend(block_events)\n", + "\n", + "print(\"total events found: \", len(block_events))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85b9f454-bbb5-4ea5-b7a9-713bb34b1633", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7dfa2c9b-2a07-4816-b8fc-c2c21171b907", + "metadata": {}, + "outputs": [], + "source": [ + "df = get_hp_minmax_during_events(block_events)\n", + "print(\"confirm number of rows in the dataframe: \", df.index.size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98f11d24-28a6-4730-9dca-af3fa7b44024", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "histogram_during_slews(df, day_obs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec60c9ab-4d93-4248-92ba-dd64fb2bc90c", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs = 20240103\n", + "block_id = 178\n", + "seq_num_list = [116, 197, 198]\n", + "blocks_20240103 = {}\n", + "\n", + "list_of_events = get_events_for_blocks_in_a_day(day_obs, [block_id])\n", + "\n", + "for evt in list_of_events:\n", + " # print(day_obs, block_id, evt.seqNum)\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{evt.seqNum}\": {\n", + " \"begin\": evt.begin.isot,\n", + " \"end\": evt.end.isot,\n", + " }\n", + " }\n", + "\n", + " blocks_20240103.update(my_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "503f4a0c-0496-407e-a97c-7c62c7fd781f", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " blocks_20240103[\"20240103_178_197\"][\"begin\"],\n", + " blocks_20240103[\"20240103_178_197\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e893101-c6ae-4909-8c2c-eeae71770225", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs = 20240103\n", + "block_id = 146\n", + "seq_num_list = [300]\n", + "\n", + "list_of_events = get_events_for_blocks_in_a_day(day_obs, [block_id])\n", + "\n", + "for evt in list_of_events:\n", + "\n", + " if evt.type != TMAState.SLEWING:\n", + " continue\n", + "\n", + " print(day_obs, block_id, evt.seqNum)\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{evt.seqNum}\": {\n", + " \"begin\": evt.begin.isot,\n", + " \"end\": evt.end.isot,\n", + " }\n", + " }\n", + "\n", + " blocks_20240103.update(my_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac43afcb-98b7-4591-a730-cc22f5b67571", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " blocks_20240103[\"20240103_146_314\"][\"begin\"],\n", + " blocks_20240103[\"20240103_146_314\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb6a8cda-fd28-4a95-869b-6e5c8b693165", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs = 20240105\n", + "block_id = 146\n", + "blocks_20240105 = {}\n", + "\n", + "list_of_events = get_events_for_blocks_in_a_day(day_obs, [block_id])\n", + "\n", + "for evt in list_of_events:\n", + "\n", + " if evt.type != TMAState.SLEWING:\n", + " continue\n", + "\n", + " print(day_obs, block_id, evt.seqNum)\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{evt.seqNum}\": {\n", + " \"begin\": evt.begin.isot,\n", + " \"end\": evt.end.isot,\n", + " }\n", + " }\n", + "\n", + " blocks_20240105.update(my_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9fdaa9ea-7a19-4ecc-8369-cf9fecb6c018", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " blocks_20240105[\"20240105_146_899\"][\"begin\"],\n", + " blocks_20240105[\"20240105_146_899\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")\n", + "\n", + "print(data.stats)" + ] + }, + { + "cell_type": "markdown", + "id": "d851ae23-80ff-4caf-a19c-b9c948a7143a", + "metadata": {}, + "source": [ + "### [BLOCK-186] - Short and long slews at full performance\n", + "\n", + "[BLOCK-186]: https://rubinobs.atlassian.net/browse/BLOCK-186" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f5dd5c8b-ff9a-4c89-adb9-e79b6dfd26f9", + "metadata": {}, + "outputs": [], + "source": [ + "day_obs = 20240109\n", + "block_id = 184\n", + "seq_num_list = [310]\n", + "block_184_data = {}\n", + "\n", + "list_of_events = get_events_for_blocks_in_a_day(day_obs, [block_id])\n", + "\n", + "for evt in list_of_events:\n", + " # print(day_obs, block_id, evt.seqNum)\n", + " my_dict = {\n", + " f\"{day_obs}_{block_id}_{evt.seqNum}\": {\n", + " \"begin\": evt.begin.isot,\n", + " \"end\": evt.end.isot,\n", + " }\n", + " }\n", + "\n", + " block_184_data.update(my_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8dc28110-b6d9-4594-bdbd-0a833dc955cc", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "data = analyze_m1m3_ics_slew_event(\n", + " block_184_data[\"20240109_184_310\"][\"begin\"],\n", + " block_184_data[\"20240109_184_310\"][\"end\"],\n", + " event_maker,\n", + " log,\n", + " plot_path,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ad020d7-7095-4f2e-b9de-bf2fb2da5329", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/SITCOMTN-092_m1m3_ics_historical_analysis.ipynb b/notebooks/SITCOMTN-092_m1m3_ics_historical_analysis.ipynb new file mode 100644 index 0000000..6e14f00 --- /dev/null +++ b/notebooks/SITCOMTN-092_m1m3_ics_historical_analysis.ipynb @@ -0,0 +1,523 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4f9debea-aa94-4f87-8d9b-0c74c3d648dd", + "metadata": {}, + "source": [ + "# [SITCOMTN-092] - M1M3 Inertia Compensation System - Historical Analysis\n", + "\n", + "This notebook analyses all the slews during a `dayObs`, which starts at 9h CLT each day. \n", + "The more detailed analysis on each slew can be found in [SITCOMTN-092_analysis_inertia_compensation.ipynb] notebook.\n", + "\n", + "Update the `day_obs_list` variable to select a new date to analyze. \n", + "\n", + "The notebook first shows a correlation plots as an initial data exploring that we are keeping for historical reasons. \n", + "Then, it shows the most extreme forces measured on each hard point as a function of the maximum velocities in elevation and azimuth. \n", + "\n", + "For more detailes on the analysis, please check [SITCOMTN-092].\n", + "\n", + "## Expected Performance\n", + "\n", + "The Inertia Compensation System should offload forces from the Hard Points. \n", + "This means that the most extreme forces for each hard point should be as near to zero as possible. \n", + "The [El Only Motion Analysis] and [Az Only Motion Analysis] sections display plots containing the most extreme hardpoint force values versus the maximum velocity measured in each axis. \n", + "This reflects the TMA performance settings. \n", + "\n", + "The `multiaxis_plots` functions are useful to compare the performance when the ICS is enabled versus disabled. \n", + "Use it comparing data from [20230728] and [20230802]. \n", + "They are commented out now since the plots use too much space. \n", + "Uncomment them to see a comparison between ICS enabled and ICS disabled. \n", + "\n", + "Data from [20231115] shows that the maximum values in elevation are below 1000 N. \n", + "In azimuth, those values reach up to ~ 1700 N. \n", + "Considering that the breakaway limit is 3000 N, going higher velocities could imply in forces that are too close to the breakaway limit. \n", + "This means that the ICS needs improvement. \n", + "\n", + "- [ ] To do: Update `multiaxis_plots` to make them more compact\n", + "- [ ] To do: Update `multiaxis_plots` to allow changing the plot limits. \n", + "- [ ] To do: Add or replace `measuredForceMax` with the most extreme values (min/max)\n", + "\n", + "\n", + "## Data Summary\n", + "\n", + "[20230728] :\n", + "- BLOCK-82, a set of different slews in azimuth at fixed elevation followed but a set of different slews in elevation at fixed azimuth at 30% maximum motion settings. \n", + "- BLOCK-5, use the Scheduler for observation simulations using M1M3 in closed look. Assuming 30% motions settings.\n", + "\n", + "[20230802] : \n", + "- We ran BLOCK-82 with different maximum motion settings.\n", + " - 20% max in both axes\n", + " - 30% max in both axes\n", + " - 30% max in elevation and 40% max in azimuth\n", + " - 30% max in elevation and 50% max in azimuth\n", + " \n", + "(!) Why did we go only until 30% max in elevation again? \n", + "\n", + "[20231115] :\n", + "- All tests with Inertia Compensation System turned on and telemetry from MTMount only.\n", + "- We ran gateway tests at 10%, 20%, 30% and 40%.\n", + "- We ran dynamic tests at 30% and 40%.\n", + "- We ran M2 Open Loop tests at 1%, 3% and 5%. \n", + "- Check the night log for BLOCKs ids.\n", + "\n", + "[20230728]: https://confluence.lsstcorp.org/display/LSSTCOM/23.07.28+-+M1M3+Test+Log\n", + "[20230802]: https://confluence.lsstcorp.org/display/LSSTCOM/23.08.02+-+M1M3+Test+Log\n", + "[20231115]: https://confluence.lsstcorp.org/pages/viewpage.action?pageId=239404701\n", + "\n", + "[SITCOMTN-092_analysis_inertia_compensation.ipynb]: https://github.com/lsst-sitcom/notebooks_vandv/blob/develop/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOMTN-092_analysis_inertia_compensation.ipynb\n", + "[SITCOMTN-092]: https://sitcomtn-092.lsst.io/https://sitcomtn-092.lsst.io/" + ] + }, + { + "cell_type": "markdown", + "id": "1669ee8a-fc22-4a9e-aa92-7a5dbb8ba908", + "metadata": {}, + "source": [ + "## Prepare Notebook" + ] + }, + { + "cell_type": "markdown", + "id": "dc9b7d0c-c73a-429f-b956-73e3a6dbc4e1", + "metadata": {}, + "source": [ + "For this notebook you will need to have `summit_utils` installed and running with the proper version. \n", + "The current version for `summit_utils` is `tickets/DM-41232` until that ticket is merged/done. \n", + "Otherwise, use `sitcom-performance-analysis` or `develop` branches. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bee1fe0d-6384-4121-80df-9706bcdb6455", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%load_ext lab_black\n", + "%load_ext autoreload\n", + "%autoreload 2\n", + "import pandas as pd\n", + "import re\n", + "from pathlib import Path\n", + "\n", + "from lsst.ts.xml.tables.m1m3 import HP_COUNT\n", + "\n", + "from lsst.summit.utils.tmaUtils import TMAEventMaker\n", + "from lsst.summit.utils.m1m3 import inertia_compensation_system as m1m3_ics\n", + "from lsst.sitcom.vandv.logger import create_logger\n", + "from lsst.sitcom.vandv.m1m3.sitcomtn092 import (\n", + " correlation_map,\n", + " merge_csvs,\n", + " multiaxis_plots,\n", + " singleaxis_plots,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dab82b3f-2673-4b91-a3e4-b0beed85e982", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "day_obs_list = [\n", + " 20230728,\n", + " # 20230802,\n", + " # 20231115,\n", + "]\n", + "\n", + "file_pattern = \"m1m3_ics_{day_obs}.csv\"\n", + "output_file = Path(\"m1m3_ics.csv\")\n", + "data_folder = Path(\"./data\")\n", + "plot_folder = Path(\"./plots\")" + ] + }, + { + "cell_type": "markdown", + "id": "b8df862c-c851-4fa5-a6dd-6b10d531dab2", + "metadata": {}, + "source": [ + "## Generate tables with historical data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c1bb2eb-a1fc-45f3-963c-e4478602b82b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "event_maker = TMAEventMaker()\n", + "data_folder.mkdir(parents=True, exist_ok=True)\n", + "plot_folder.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "28a9cd5a-8db2-4689-b732-15170cef0e07", + "metadata": {}, + "source": [ + "🔴 The following cell will take a long time to be executed (> 10 min). 🔴 \n", + " \n", + "It analyzes each slew on a `obsDay` and saves the results into a CSV file. \n", + "If the file already exists, this will be fast. \n", + "\n", + "If the analysis is done, the amount of output on this cell might break the plots. \n", + "The log below is now set to `ERROR` to minimize the output. \n", + "Change it to `WARNING`, `INFO` or `DEBUG` for more information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5931ed42-b3b5-453a-af46-b1c9bf13b714", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "log = create_logger(\"m1m3_ics_stats\")\n", + "log.setLevel(\"ERROR\")\n", + "log.propagate = False\n", + "\n", + "for day_obs in day_obs_list:\n", + " file_path = Path(data_folder) / file_pattern.format(day_obs=day_obs)\n", + " if file_path.exists():\n", + " print(f\"File exists: {file_path}\\n Skipping data processing.\")\n", + " continue\n", + " else:\n", + " temp_df = m1m3_ics.evaluate_m1m3_ics_day_obs(day_obs, event_maker, log=log)\n", + " temp_df.to_csv(file_path)\n", + " del temp_df" + ] + }, + { + "cell_type": "markdown", + "id": "b1f8f6a3-32af-43aa-b7e3-e76aadec1a31", + "metadata": {}, + "source": [ + "## Merge datasets into a single one" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6bd48d02-20f4-4934-b9d7-ec8d7009609e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df = merge_csvs(data_folder, file_pattern, day_obs_list)\n", + "df.to_csv(data_folder / output_file)" + ] + }, + { + "cell_type": "markdown", + "id": "9573c5bc-9c12-45e1-a38b-c388b761290e", + "metadata": {}, + "source": [ + "## What data can be correlated with the HP forces?\n", + "\n", + "For this, I will start with a correlation map. \n", + "This migh give me an idea of what is related to what. \n", + "I will start by replacing the boolean values of `ics_enabled` with numerical ones to allow gathering correlation. \n", + "Then, I will temporarily drop the columns that I know don't have any correlation with the ICS performance. \n", + "Finally, I will display the correlation map that will give us some impressions on the possible correlations. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ac5d672-d71f-42ac-9171-30e551743e46", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df[\"ics_enabled\"] = df[\"ics_enabled\"].apply(lambda x: 1 if x else -1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00955bb5-44f6-4735-b348-af9828723edd", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "subset_of_columns = [\n", + " \"time_duration\",\n", + " \"az_start\",\n", + " \"az_end\",\n", + " \"az_extreme_vel\",\n", + " \"az_extreme_torque\",\n", + " \"az_diff\",\n", + " \"el_start\",\n", + " \"el_end\",\n", + " \"el_extreme_vel\",\n", + " \"el_extreme_torque\",\n", + " \"el_diff\",\n", + " \"ics_enabled\",\n", + "]\n", + "\n", + "subset_of_columns.extend([f\"measuredForceMin{hp}\" for hp in range(HP_COUNT)])\n", + "subset_of_columns.extend([f\"measuredForceMax{hp}\" for hp in range(HP_COUNT)])\n", + "subset_of_columns.extend([f\"measuredForceMean{hp}\" for hp in range(HP_COUNT)])\n", + "subset_of_columns.extend([f\"measuredForceStd{hp}\" for hp in range(HP_COUNT)])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb9f2bf1-3431-4373-858c-612e8ce29f97", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "correlation_map(df, subset_of_columns, lines=[1, 6, 11, 12, 18, 24, 30])" + ] + }, + { + "cell_type": "markdown", + "id": "20f6a0a4-6993-4e12-8bb9-accb5e5428de", + "metadata": {}, + "source": [ + "It is a bit hard to see all the correlations in the set above. So let,s create a smaller set:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd3d6b83-507d-46c6-8004-5565e955c4bc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "correlation_map(\n", + " df,\n", + " [\n", + " \"az_extreme_vel\",\n", + " \"az_extreme_torque\",\n", + " \"el_extreme_vel\",\n", + " \"el_extreme_torque\",\n", + " ]\n", + " + [f\"measuredForceMin{hp}\" for hp in range(HP_COUNT)]\n", + " + [f\"measuredForceMax{hp}\" for hp in range(HP_COUNT)],\n", + " lines=[4, 10, 16],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ddf9cfab-429d-4605-9dde-1dc23c154e0f", + "metadata": {}, + "source": [ + "We need to perform a deeper study on the heat maps above. \n", + "Different nights present different correlations. \n", + "They seem to consistent but we do not have a clear conclusion right " + ] + }, + { + "cell_type": "markdown", + "id": "0d13f051-276d-4e0b-87da-ed2ae301b915", + "metadata": {}, + "source": [ + "## Expected performance during constant speed\n", + "\n", + "Ideally, the Inertia Compensation System should offload all the forces from the Hard-Points to the Force Balance System when accelerating or when moving at constant velocity. Let's have a look at the first case first. Let me create a plot of the `measuredForceMean` values versus `az_extreme_torque` and `el_extreme_torque`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ded65447-bb20-4fdd-8f1f-00d780511ef5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "df[\"abs_az_extreme_vel\"] = df[\"az_extreme_vel\"].abs()\n", + "df[\"abs_az_extreme_torque\"] = df[\"az_extreme_torque\"].abs()\n", + "df[\"abs_el_extreme_vel\"] = df[\"el_extreme_vel\"].abs()\n", + "df[\"abs_el_extreme_torque\"] = df[\"el_extreme_torque\"].abs()" + ] + }, + { + "cell_type": "markdown", + "id": "b7bebc1e-88a1-4aa5-8eb9-a5877cca7f94", + "metadata": {}, + "source": [ + "During the study, we identified an outlier that had `abs_el_extreme_vel` and `abs_el_extreme_torque` that had values inconsistently high. Since it was a single point, I will ignore it for now:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b135d0fe-d099-45b0-b243-c9f8503d6de6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "print(f\"Data-frame size before filtering: {df.index.size}\")\n", + "df = df[df[\"abs_el_extreme_vel\"] < 10]\n", + "print(f\"Data-frame size after filtering: {df.index.size}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78ae11dd-b265-4f61-9f5a-fa6d1d738f24", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "el_only_df = df[df[\"abs_az_extreme_vel\"] < 0.02]\n", + "az_only_df = df[df[\"abs_el_extreme_vel\"] < 0.02]\n", + "\n", + "print(f\"Total number of slews: {df.index.size}\")\n", + "print(f\"Number of elevation-only slews: {el_only_df.index.size}\")\n", + "print(f\"Number of azimuth-only slews: {az_only_df.index.size}\")\n", + "print(f\"Sum of the two above: {el_only_df.index.size + az_only_df.index.size}\")" + ] + }, + { + "cell_type": "markdown", + "id": "a884d9c5-fa67-479a-b4a4-0aa5d1b97ed0", + "metadata": {}, + "source": [ + "## El Only Motion Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54753ba8-61fe-45d7-ae0a-4def6fdf05ff", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "# multiaxis_plots(el_only_df, \"abs_el_extreme_vel\", \"measuredForceMean\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86868194-789a-4b02-b75e-25acf64c57ac", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "# multiaxis_plots(el_only_df, \"abs_el_extreme_torque\", \"measuredForceMax\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a98d0780-0615-4a0f-a8c1-936d38b81174", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "only_ics_enabled = el_only_df[el_only_df[\"ics_enabled\"] > 0]\n", + "singleaxis_plots(only_ics_enabled, \"abs_el_extreme_torque\", \"measuredForceMax\")" + ] + }, + { + "cell_type": "markdown", + "id": "b7fef2ec-282e-4f19-ae1d-5ca97b4aac7b", + "metadata": {}, + "source": [ + "## Az Only Motion Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "564705ad-44de-4f80-9eef-1a8e5dd7c349", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "# multiaxis_plots(az_only_df, \"abs_az_extreme_vel\", \"measuredForceMean\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "245d7ef1-d486-4dbc-a81e-53a41fd4eba9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "# multiaxis_plots(az_only_df, \"abs_az_extreme_torque\", \"measuredForceMax\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8f1cc10-ed4c-4be3-b831-368debbf973e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "only_ics_enabled = az_only_df[az_only_df[\"ics_enabled\"] > 0]\n", + "singleaxis_plots(only_ics_enabled, \"abs_az_extreme_torque\", \"measuredForceMax\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33e3d7c8-24fa-448a-b05f-13f5169d23eb", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From d9a4fc07bedf2f682b6455649e4b9385b0724270 Mon Sep 17 00:00:00 2001 From: Bruno Quint Date: Sun, 3 Nov 2024 04:25:46 +0000 Subject: [PATCH 2/3] Update .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index da408c0..126cc87 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ _build/ .tox/ venv/ .venv/ + +# Ignore Jupyter Checkpoints +**/.ipynb_checkpoints + +# Ignore Python Cache +**/__pycache__ From a2e5ac227c42d9c822f046ac3c84f29ed7bf6475 Mon Sep 17 00:00:00 2001 From: Bruno Quint Date: Sun, 3 Nov 2024 04:26:35 +0000 Subject: [PATCH 3/3] Add analysis for BLOCK-T227 on 2024-10-26 --- notebooks/BLOCK-T227_analysis_slew.ipynb | 1226 +++++++++++++++++ notebooks/BLOCK_T227_utils.py | 415 ++++++ .../histogram_hp_minmax_dayobs_20241026.png | Bin 0 -> 54631 bytes 3 files changed, 1641 insertions(+) create mode 100644 notebooks/BLOCK-T227_analysis_slew.ipynb create mode 100644 notebooks/BLOCK_T227_utils.py create mode 100644 notebooks/plots/histogram_hp_minmax_dayobs_20241026.png diff --git a/notebooks/BLOCK-T227_analysis_slew.ipynb b/notebooks/BLOCK-T227_analysis_slew.ipynb new file mode 100644 index 0000000..2f81181 --- /dev/null +++ b/notebooks/BLOCK-T227_analysis_slew.ipynb @@ -0,0 +1,1226 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# BLOCK-T227 Dynamic Tests Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:00.065786Z", + "iopub.status.busy": "2024-11-03T04:23:00.065669Z", + "iopub.status.idle": "2024-11-03T04:23:00.067991Z", + "shell.execute_reply": "2024-11-03T04:23:00.067545Z", + "shell.execute_reply.started": "2024-11-03T04:23:00.065773Z" + } + }, + "outputs": [], + "source": [ + "block_id = \"BLOCK-T227\"\n", + "day_obs = 20241026" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:01.129462Z", + "iopub.status.busy": "2024-11-03T04:23:01.128949Z", + "iopub.status.idle": "2024-11-03T04:23:07.281151Z", + "shell.execute_reply": "2024-11-03T04:23:07.280698Z", + "shell.execute_reply.started": "2024-11-03T04:23:01.129447Z" + } + }, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import os\n", + "import pandas as pd\n", + "import re\n", + "\n", + "from astropy.time import Time\n", + "from astropy import units as u\n", + "from datetime import datetime\n", + "\n", + "from lsst.summit.utils.blockUtils import BlockParser\n", + "from lsst.summit.utils.efdUtils import getDayObsStartTime, makeEfdClient\n", + "from lsst.summit.utils.tmaUtils import TMAEvent, TMAEventMaker, TMAState\n", + "\n", + "import BLOCK_T227_utils as block\n", + "\n", + "# Create a client to access the Engineering Facility Database\n", + "efd_client = makeEfdClient()\n", + "\n", + "# Create an object that mines TMA Slew Events\n", + "event_maker = TMAEventMaker()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:07.282361Z", + "iopub.status.busy": "2024-11-03T04:23:07.281913Z", + "iopub.status.idle": "2024-11-03T04:23:07.323244Z", + "shell.execute_reply": "2024-11-03T04:23:07.322865Z", + "shell.execute_reply.started": "2024-11-03T04:23:07.282347Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Query data for 20241026\n", + " starts at 2024-10-26T12:00:00.000 and\n", + " ends at 2024-10-27T12:00:00.000\n", + "\n" + ] + } + ], + "source": [ + "day_obs = int(day_obs)\n", + "start_time = getDayObsStartTime(day_obs)\n", + "end_time = start_time + 1 * u.day\n", + "\n", + "print(\n", + " f\"\\nQuery data for {day_obs}\"\n", + " f\"\\n starts at {start_time.isot} and\"\n", + " f\"\\n ends at {end_time.isot}\\n\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Querying data\n", + "\n", + "Here I am trying to query the data based on information stored in the EFD. I am assuming that I do not have any information other than the test case id and the execution date. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:09.443977Z", + "iopub.status.busy": "2024-11-03T04:23:09.443524Z", + "iopub.status.idle": "2024-11-03T04:23:09.517597Z", + "shell.execute_reply": "2024-11-03T04:23:09.517121Z", + "shell.execute_reply.started": "2024-11-03T04:23:09.443962Z" + } + }, + "outputs": [], + "source": [ + "# Query blocks status\n", + "df_block_status = block.query_block_status(efd_client, start_time, end_time, block_id)\n", + "\n", + "# Select relevant columns\n", + "df_block_status = df_block_status[[\"id\", \"status\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:10.324194Z", + "iopub.status.busy": "2024-11-03T04:23:10.323885Z", + "iopub.status.idle": "2024-11-03T04:23:10.365912Z", + "shell.execute_reply": "2024-11-03T04:23:10.365515Z", + "shell.execute_reply.started": "2024-11-03T04:23:10.324174Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idstatus
2024-10-27 07:06:37.934694+00:00BLOCK-T227STARTED
2024-10-27 07:16:07.463834+00:00BLOCK-T227STARTED
2024-10-27 07:48:42.601261+00:00BLOCK-T227STARTED
2024-10-27 07:51:08.578495+00:00BLOCK-T227STARTED
2024-10-27 08:28:22.354645+00:00BLOCK-T227STARTED
2024-10-27 08:55:48.736711+00:00BLOCK-T227EXECUTING
\n", + "
" + ], + "text/plain": [ + " id status\n", + "2024-10-27 07:06:37.934694+00:00 BLOCK-T227 STARTED\n", + "2024-10-27 07:16:07.463834+00:00 BLOCK-T227 STARTED\n", + "2024-10-27 07:48:42.601261+00:00 BLOCK-T227 STARTED\n", + "2024-10-27 07:51:08.578495+00:00 BLOCK-T227 STARTED\n", + "2024-10-27 08:28:22.354645+00:00 BLOCK-T227 STARTED\n", + "2024-10-27 08:55:48.736711+00:00 BLOCK-T227 EXECUTING" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_block_status" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The DataFrame above shows me that the BLOCK started five times. However, there not much more information in it. I ommitted several columns that did not seem useful for this analysis. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:11.215879Z", + "iopub.status.busy": "2024-11-03T04:23:11.215586Z", + "iopub.status.idle": "2024-11-03T04:23:11.323918Z", + "shell.execute_reply": "2024-11-03T04:23:11.323484Z", + "shell.execute_reply.started": "2024-11-03T04:23:11.215864Z" + } + }, + "outputs": [], + "source": [ + "# Query script status \n", + "df_script_status = block.query_script_states(efd_client, start_time, end_time, block_id)\n", + "\n", + "# Select most relevant columns\n", + "df_script_status = df_script_status[[\"blockId\", \"lastCheckpoint\", \"salIndex\", \"state\"]]\n", + "\n", + "# Convert `state`, which is an int, into a string from readability\n", + "df_script_status[\"state_name\"] = df_script_status[\"state\"].apply(block.convert_script_state)\n", + "\n", + "# For each `blockId`, drop duplicates to it is easier to read\n", + "df_script_status = df_script_status.drop_duplicates(subset=[\"blockId\", \"state_name\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:12.049586Z", + "iopub.status.busy": "2024-11-03T04:23:12.049301Z", + "iopub.status.idle": "2024-11-03T04:23:12.087321Z", + "shell.execute_reply": "2024-11-03T04:23:12.086948Z", + "shell.execute_reply.started": "2024-11-03T04:23:12.049573Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
blockIdlastCheckpointsalIndexstatestate_name
2024-10-27 07:06:47.111542+00:00BT227_O_20241026_0000011002712CONFIGURED
2024-10-27 07:06:48.237087+00:00BT227_O_20241026_0000011002703RUNNING
2024-10-27 07:06:55.359559+00:00BT227_O_20241026_000001None: azel grid 153.0/34.0 1/11002707FAILING
2024-10-27 07:07:01.564360+00:00BT227_O_20241026_000001None: azel grid 153.0/34.0 1/110027010FAILED
2024-10-27 07:16:16.510233+00:00BT227_O_20241026_0000021002832CONFIGURED
\n", + "
" + ], + "text/plain": [ + " blockId \\\n", + "2024-10-27 07:06:47.111542+00:00 BT227_O_20241026_000001 \n", + "2024-10-27 07:06:48.237087+00:00 BT227_O_20241026_000001 \n", + "2024-10-27 07:06:55.359559+00:00 BT227_O_20241026_000001 \n", + "2024-10-27 07:07:01.564360+00:00 BT227_O_20241026_000001 \n", + "2024-10-27 07:16:16.510233+00:00 BT227_O_20241026_000002 \n", + "\n", + " lastCheckpoint salIndex \\\n", + "2024-10-27 07:06:47.111542+00:00 100271 \n", + "2024-10-27 07:06:48.237087+00:00 100270 \n", + "2024-10-27 07:06:55.359559+00:00 None: azel grid 153.0/34.0 1/1 100270 \n", + "2024-10-27 07:07:01.564360+00:00 None: azel grid 153.0/34.0 1/1 100270 \n", + "2024-10-27 07:16:16.510233+00:00 100283 \n", + "\n", + " state state_name \n", + "2024-10-27 07:06:47.111542+00:00 2 CONFIGURED \n", + "2024-10-27 07:06:48.237087+00:00 3 RUNNING \n", + "2024-10-27 07:06:55.359559+00:00 7 FAILING \n", + "2024-10-27 07:07:01.564360+00:00 10 FAILED \n", + "2024-10-27 07:16:16.510233+00:00 2 CONFIGURED " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_script_status.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here I am getting more information. With the DataFrame above I can confirm that the block failed four times before it passed. I can use the table above to investigate with more details why the block execution failed. This is out of the scope of this notebook. However, here are a few queries that we could try if we wanted to investigate more. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:12.990868Z", + "iopub.status.busy": "2024-11-03T04:23:12.990578Z", + "iopub.status.idle": "2024-11-03T04:23:13.048944Z", + "shell.execute_reply": "2024-11-03T04:23:13.048520Z", + "shell.execute_reply.started": "2024-11-03T04:23:12.990854Z" + } + }, + "outputs": [], + "source": [ + "# Query Scripts description\n", + "df_script_description = block.query_script_description(efd_client, start_time, end_time, block_id)\n", + "\n", + "# Filter most useful columns\n", + "df_script_description = df_script_description[[\"classname\", \"description\", \"salIndex\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:13.870448Z", + "iopub.status.busy": "2024-11-03T04:23:13.870251Z", + "iopub.status.idle": "2024-11-03T04:23:13.910101Z", + "shell.execute_reply": "2024-11-03T04:23:13.909697Z", + "shell.execute_reply.started": "2024-11-03T04:23:13.870435Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
classnamedescriptionsalIndex
2024-10-26 15:34:51.197572+00:00MTSchedulerAddBlockLoad block to the MAIN_TEL Scheduler100122
2024-10-26 15:34:59.530994+00:00CheckHardpointCheck M1M3 Hardpoint100123
2024-10-26 17:04:13.445158+00:00SetSummaryStatePut CSCs into specified states100124
2024-10-26 17:12:37.906339+00:00MTSchedulerAddBlockLoad block to the MAIN_TEL Scheduler100125
2024-10-26 17:12:45.992582+00:00CheckActuatorsBump Test on M1M3 Actuators100126
\n", + "
" + ], + "text/plain": [ + " classname \\\n", + "2024-10-26 15:34:51.197572+00:00 MTSchedulerAddBlock \n", + "2024-10-26 15:34:59.530994+00:00 CheckHardpoint \n", + "2024-10-26 17:04:13.445158+00:00 SetSummaryState \n", + "2024-10-26 17:12:37.906339+00:00 MTSchedulerAddBlock \n", + "2024-10-26 17:12:45.992582+00:00 CheckActuators \n", + "\n", + " description \\\n", + "2024-10-26 15:34:51.197572+00:00 Load block to the MAIN_TEL Scheduler \n", + "2024-10-26 15:34:59.530994+00:00 Check M1M3 Hardpoint \n", + "2024-10-26 17:04:13.445158+00:00 Put CSCs into specified states \n", + "2024-10-26 17:12:37.906339+00:00 Load block to the MAIN_TEL Scheduler \n", + "2024-10-26 17:12:45.992582+00:00 Bump Test on M1M3 Actuators \n", + "\n", + " salIndex \n", + "2024-10-26 15:34:51.197572+00:00 100122 \n", + "2024-10-26 15:34:59.530994+00:00 100123 \n", + "2024-10-26 17:04:13.445158+00:00 100124 \n", + "2024-10-26 17:12:37.906339+00:00 100125 \n", + "2024-10-26 17:12:45.992582+00:00 100126 " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_script_description.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The DataFrame above shows us the name of the script associated with a `salIndex` and its description. This adds some information to what happened during a block execution. No needed for this notebook, but keeping it here for now just for the record. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:14.777752Z", + "iopub.status.busy": "2024-11-03T04:23:14.777456Z", + "iopub.status.idle": "2024-11-03T04:23:14.837509Z", + "shell.execute_reply": "2024-11-03T04:23:14.837168Z", + "shell.execute_reply.started": "2024-11-03T04:23:14.777739Z" + } + }, + "outputs": [], + "source": [ + "# Query scripts configuration\n", + "df_script_configuration = block.query_script_configuration(efd_client, start_time, end_time)\n", + "\n", + "# Filter most useful columns \n", + "df_script_configuration = df_script_configuration[[\"blockId\", \"config\", \"logLevel\", \"salIndex\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:15.578258Z", + "iopub.status.busy": "2024-11-03T04:23:15.577975Z", + "iopub.status.idle": "2024-11-03T04:23:15.620375Z", + "shell.execute_reply": "2024-11-03T04:23:15.620079Z", + "shell.execute_reply.started": "2024-11-03T04:23:15.578244Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
blockIdconfiglogLevelsalIndex
2024-10-26 15:34:51.198247+00:00id: BLOCK-T14510100122
2024-10-26 15:34:59.531484+00:00BT145_O_20241026_000001program: BLOCK-T145\\nreason: BLOCK-T145\\n20100123
2024-10-26 17:04:13.445954+00:00data:\\n- [MTRotator, ENABLED]10100124
2024-10-26 17:12:37.907130+00:00id: BLOCK-T14410100125
2024-10-26 17:12:45.993099+00:00BT144_O_20241026_000001program: BLOCK-T144\\nreason: BLOCK-T144\\n20100126
\n", + "
" + ], + "text/plain": [ + " blockId \\\n", + "2024-10-26 15:34:51.198247+00:00 \n", + "2024-10-26 15:34:59.531484+00:00 BT145_O_20241026_000001 \n", + "2024-10-26 17:04:13.445954+00:00 \n", + "2024-10-26 17:12:37.907130+00:00 \n", + "2024-10-26 17:12:45.993099+00:00 BT144_O_20241026_000001 \n", + "\n", + " config \\\n", + "2024-10-26 15:34:51.198247+00:00 id: BLOCK-T145 \n", + "2024-10-26 15:34:59.531484+00:00 program: BLOCK-T145\\nreason: BLOCK-T145\\n \n", + "2024-10-26 17:04:13.445954+00:00 data:\\n- [MTRotator, ENABLED] \n", + "2024-10-26 17:12:37.907130+00:00 id: BLOCK-T144 \n", + "2024-10-26 17:12:45.993099+00:00 program: BLOCK-T144\\nreason: BLOCK-T144\\n \n", + "\n", + " logLevel salIndex \n", + "2024-10-26 15:34:51.198247+00:00 10 100122 \n", + "2024-10-26 15:34:59.531484+00:00 20 100123 \n", + "2024-10-26 17:04:13.445954+00:00 10 100124 \n", + "2024-10-26 17:12:37.907130+00:00 10 100125 \n", + "2024-10-26 17:12:45.993099+00:00 20 100126 " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_script_configuration.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The configurations might be interesting to check. Especially in the case of using `RunCommand` scripts, which forward commands to the CSCs. \n", + "\n", + "However, let's go back to the original purpose of this notebook which is to try to get what are the successful runs of BLOCK-T227. " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:16.468069Z", + "iopub.status.busy": "2024-11-03T04:23:16.467800Z", + "iopub.status.idle": "2024-11-03T04:23:16.510919Z", + "shell.execute_reply": "2024-11-03T04:23:16.510506Z", + "shell.execute_reply.started": "2024-11-03T04:23:16.468056Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
blockIdlastCheckpointsalIndexstatestate_name
2024-10-27 08:28:31.464246+00:00BT227_O_20241026_0000051003212CONFIGURED
2024-10-27 08:28:32.518807+00:00BT227_O_20241026_0000051003203RUNNING
2024-10-27 08:28:46.631056+00:00BT227_O_20241026_000005None: azel grid 34.0/34.0 1/11003205ENDING
2024-10-27 08:28:46.632370+00:00BT227_O_20241026_000005None: azel grid 34.0/34.0 1/11003208DONE
\n", + "
" + ], + "text/plain": [ + " blockId \\\n", + "2024-10-27 08:28:31.464246+00:00 BT227_O_20241026_000005 \n", + "2024-10-27 08:28:32.518807+00:00 BT227_O_20241026_000005 \n", + "2024-10-27 08:28:46.631056+00:00 BT227_O_20241026_000005 \n", + "2024-10-27 08:28:46.632370+00:00 BT227_O_20241026_000005 \n", + "\n", + " lastCheckpoint salIndex \\\n", + "2024-10-27 08:28:31.464246+00:00 100321 \n", + "2024-10-27 08:28:32.518807+00:00 100320 \n", + "2024-10-27 08:28:46.631056+00:00 None: azel grid 34.0/34.0 1/1 100320 \n", + "2024-10-27 08:28:46.632370+00:00 None: azel grid 34.0/34.0 1/1 100320 \n", + "\n", + " state state_name \n", + "2024-10-27 08:28:31.464246+00:00 2 CONFIGURED \n", + "2024-10-27 08:28:32.518807+00:00 3 RUNNING \n", + "2024-10-27 08:28:46.631056+00:00 5 ENDING \n", + "2024-10-27 08:28:46.632370+00:00 8 DONE " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Identify blockIds with at least one state_name equals to \"FAILED\"\n", + "failed_block_ids = df_script_status[df_script_status[\"state_name\"] == \"FAILED\"][\"blockId\"].unique()\n", + "\n", + "# Filter out rows with those blockIds\n", + "df_script_status_filtered = df_script_status[~df_script_status[\"blockId\"].isin(failed_block_ids)]\n", + "\n", + "df_script_status_filtered" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Based on the information above, it seems that this block ran a single script. I know that this block contains several `move_p2p` scripts. \n", + "\n", + "Another way of trying to query information about blocks would be using the `BlockParser` class from `summit_utils`. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:18.003195Z", + "iopub.status.busy": "2024-11-03T04:23:18.002899Z", + "iopub.status.idle": "2024-11-03T04:23:18.489169Z", + "shell.execute_reply": "2024-11-03T04:23:18.488861Z", + "shell.execute_reply.started": "2024-11-03T04:23:18.003180Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[]\n" + ] + } + ], + "source": [ + "block_parser = BlockParser(dayObs=20241026)\n", + "block_numbers = block_parser.getBlockNums()\n", + "print(block_numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, it seems it is not working. Something might be broked halfway. So let's return to timestamps. \n", + "\n", + "The `df_script_status_filtered` is the closest to what I expected. The timestamps associated with the `CONFIGURED` state name can be used to define the beginning of this BLOCK execution. However, it seems unrealistic that this block ends in less than a minute after this BLOCK starts. Since this telemetry is unrealiable, I will use the timestamp recorded in OLE." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:19.474927Z", + "iopub.status.busy": "2024-11-03T04:23:19.474665Z", + "iopub.status.idle": "2024-11-03T04:23:19.510246Z", + "shell.execute_reply": "2024-11-03T04:23:19.509936Z", + "shell.execute_reply.started": "2024-11-03T04:23:19.474914Z" + } + }, + "outputs": [], + "source": [ + "# This works as I expect - from one of the indexes of the dataframes above\n", + "start_time = Time(df_script_status_filtered.index[0])\n", + "\n", + "# I cannot find the end time information. So I am getting it from OLE.\n", + "end_time = Time(\"2024-10-27T09:03:18.763994\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since `block_parser` is not working as expected, I will have to find slew events withing the range above manually." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:20.954244Z", + "iopub.status.busy": "2024-11-03T04:23:20.953837Z", + "iopub.status.idle": "2024-11-03T04:23:21.283953Z", + "shell.execute_reply": "2024-11-03T04:23:21.283621Z", + "shell.execute_reply.started": "2024-11-03T04:23:20.954227Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
lsst.summit.utils.tmaUtils INFO: Retrieving mount data for 20241026 from the EFD
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "events = event_maker.getEvents(day_obs)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:22.293116Z", + "iopub.status.busy": "2024-11-03T04:23:22.292820Z", + "iopub.status.idle": "2024-11-03T04:23:22.342631Z", + "shell.execute_reply": "2024-11-03T04:23:22.342248Z", + "shell.execute_reply.started": "2024-11-03T04:23:22.293100Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 17 TMA Slew Events\n" + ] + } + ], + "source": [ + "# We are working with lists \n", + "mask_begin = np.array([evt.begin >= start_time for evt in events])\n", + "mask_end = np.array([evt.end <= end_time for evt in events])\n", + "mask = mask_begin & mask_end\n", + "\n", + "events = np.array(events)\n", + "filtered_events = events[mask]\n", + "\n", + "print(f\"Found {filtered_events.size} TMA Slew Events\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T03:49:11.487970Z", + "iopub.status.busy": "2024-11-03T03:49:11.487640Z", + "iopub.status.idle": "2024-11-03T03:49:11.522969Z", + "shell.execute_reply": "2024-11-03T03:49:11.522678Z", + "shell.execute_reply.started": "2024-11-03T03:49:11.487956Z" + } + }, + "source": [ + "The cell above shows how, somehow, we are not grabbing all the relevant information from the Script and Scheduler events. Something is missing. Let's keep going. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:23.645994Z", + "iopub.status.busy": "2024-11-03T04:23:23.645727Z", + "iopub.status.idle": "2024-11-03T04:23:25.553096Z", + "shell.execute_reply": "2024-11-03T04:23:25.552692Z", + "shell.execute_reply.started": "2024-11-03T04:23:23.645980Z" + } + }, + "outputs": [], + "source": [ + "df_hp_forces = block.get_hp_minmax_forces(efd_client, filtered_events, verbose=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:27.470514Z", + "iopub.status.busy": "2024-11-03T04:23:27.470035Z", + "iopub.status.idle": "2024-11-03T04:23:27.509922Z", + "shell.execute_reply": "2024-11-03T04:23:27.509623Z", + "shell.execute_reply.started": "2024-11-03T04:23:27.470499Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
seq_numdelta_azdelta_elmin_forcesmax_forces
01325.223579e-012.777878e-07-37.01570147.153450
11331.093094e-071.199936e+01-28.820343192.332001
21342.716174e-07-1.199953e+01-196.52340720.624985
31352.399929e+01-6.746274e-07-37.24953532.542400
4136-2.399961e+01-3.042437e-07-27.21408741.932915
51374.968611e-073.499543e+00-19.395681123.917244
6138-1.501624e-07-3.499490e+00-98.65789817.312695
71393.499415e+008.651104e-07-42.79302645.825710
8140-3.499553e+00-6.613994e-07-32.14547039.474308
91418.479694e+008.479638e+00-57.640686162.856857
10142-8.479757e+008.479706e+00-49.308739187.329330
11143-8.479665e+00-8.479708e+00-198.95713857.913864
121448.479665e+00-8.479622e+00-163.49910047.220482
131452.469731e+002.469770e+00-52.962502106.729599
14146-2.469640e+002.469690e+00-51.935795123.241203
15147-2.469556e+00-2.469514e+00-115.16258251.016106
161482.469666e+00-2.469621e+00-122.26095646.311100
\n", + "
" + ], + "text/plain": [ + " seq_num delta_az delta_el min_forces max_forces\n", + "0 132 5.223579e-01 2.777878e-07 -37.015701 47.153450\n", + "1 133 1.093094e-07 1.199936e+01 -28.820343 192.332001\n", + "2 134 2.716174e-07 -1.199953e+01 -196.523407 20.624985\n", + "3 135 2.399929e+01 -6.746274e-07 -37.249535 32.542400\n", + "4 136 -2.399961e+01 -3.042437e-07 -27.214087 41.932915\n", + "5 137 4.968611e-07 3.499543e+00 -19.395681 123.917244\n", + "6 138 -1.501624e-07 -3.499490e+00 -98.657898 17.312695\n", + "7 139 3.499415e+00 8.651104e-07 -42.793026 45.825710\n", + "8 140 -3.499553e+00 -6.613994e-07 -32.145470 39.474308\n", + "9 141 8.479694e+00 8.479638e+00 -57.640686 162.856857\n", + "10 142 -8.479757e+00 8.479706e+00 -49.308739 187.329330\n", + "11 143 -8.479665e+00 -8.479708e+00 -198.957138 57.913864\n", + "12 144 8.479665e+00 -8.479622e+00 -163.499100 47.220482\n", + "13 145 2.469731e+00 2.469770e+00 -52.962502 106.729599\n", + "14 146 -2.469640e+00 2.469690e+00 -51.935795 123.241203\n", + "15 147 -2.469556e+00 -2.469514e+00 -115.162582 51.016106\n", + "16 148 2.469666e+00 -2.469621e+00 -122.260956 46.311100" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_hp_forces" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alright, those values make sense. Now let's plot the histogram. There is not much data. But we should see something. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-03T04:23:29.904440Z", + "iopub.status.busy": "2024-11-03T04:23:29.904157Z", + "iopub.status.idle": "2024-11-03T04:23:30.420375Z", + "shell.execute_reply": "2024-11-03T04:23:30.420027Z", + "shell.execute_reply.started": "2024-11-03T04:23:29.904426Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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pWky7xeZ/zKPKp6YmCgmTJggnJychI6OjihbtqyYNGmSSEpKUppu2bJlAoA0IntOtm7dKipVqiR0dXVVRs7eu3evqFu3rjAwMBDGxsbC399fnDp1SqWOSZMmCQcHB6GlpaW0LU6fPi28vb2FkZGRKF26tBgwYIC4cOGCyrqpM3p5bmLK7ejluflcExMThbm5uWjUqJFadSuoe4xk/gyEEOLly5dixIgRwtXVVejq6gpLS0vxxRdfiMmTJ4v4+Hiladu3by8AiC1btkhlKSkpwtjYWGhpaYn//vtPKk9KShJDhgwRnp6ewszMTBgaGgp3d3cxffp0kZCQkKv1IyIi9ciEyDR0LhERlRgxMTFwdnbGN998g5CQEE2HQxrUuXNn3L9/H1FRUZoOhYiIiD7A7uVERCXQkydPcO/ePSxYsABaWloYOXKkpkMiDRJCICIiAr/++qumQyEiIqJM2OgmIiqB1q5di5kzZ8LFxQVbtmxR65nB9OmSyWSIiYnRdBhERESUBXYvJyIiIiIiIiokfGQYERERERERUSFho5uIiIiIiIiokLDRTURERERERFRI2OgmIiIiIiIiKiRsdBMREREREREVEja6iYiIiIiIiAoJG91EREREREREhYSNbiIiIiIiIqJCwkY3ERERERERUSFho5uIiIiIiIiokLDRTURERERERFRI2OgmIiIiIiIiKiRsdBMREREREREVEja6iYiIiIiIiAoJG91EREREREREhYSNbiIiIiIiIqJCwkY3ERERERERUSFho5uIiIiIiIiokOhoOoBPmVwux7Nnz2BqagqZTKbpcIiIiPJECIG4uDg4ODhASyv/5+uZH4mI6FOgbn5ko7sQPXv2DE5OTpoOg4iIqEA8fvwYZcqUyXc9zI9ERPQp+Vh+ZKO7EJmamgLI+BDMzMzyX+FPPwFxcYCpKTB8eP7rKwJyuRwvX75E6dKlC+TqCGWN27locDsXDW7nopGb7RwbGwsnJycpr+VXbvMj94miwe1cNLidCx+3cdEokdu5gNtT6uZHNroLkaLLnJmZWcE0ug0MgJSUjH8Lor4iIJfLkZSUBDMzs5JzMJZA3M5Fg9u5aHA7F428bOeC6gqe2/zIfaJocDsXDW7nwsdtXDRK5HYupPbUx/JjCdk6RERERERERCUPG91EREREREREhYTdy4nok7NmzRrEx8fD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jWWGDh2KIUOGYPDgwQgKCkKdOnXQsWPHEsddlIEDB2LSpEl4++234efnh5SUFISFhcHc3LzQdcLDw9GnTx/069cPrVq1wq1btxAREVHk85iammLSpElo1qwZOnToAGNjY6xevbrAZd3d3bFv3z7k5uYiJCQEPj4+iIqKgp2dHYyM/vf237FjR+Tm5iofIuzt7dG4cWM4OzuX6qjN44YMGYK6deuWyVhEVP5YG1kbS4K1sWisjVWHSkr64xYiqvCmTZuGX375RbnnJpWt559/Hm5ubvj222/1HYrOqFQq/Pzzz+jdu3eJx4iNjcWYMWN0dqSFiKg4WBt1i7VRO6yNlQsvpEZEVAYePHiAL774AiEhITA2NsaqVauwfft2xMXF6Ts0nevfvz8cHR1LdKqotbU1cnJyijzqQUREFRNrI2sjPcKmm4ioDKhUKmzatAmzZs1CZmYmGjZsiLVr1+K5557Td2g6lZycDAAwNjYu0fp5R5JKuj4RERku1kbWRnqEp5cTERERERER6QgvpEZERERERESkI2y6iYiIiIiIiHSETTcRERERERGRjrDpJiIiIiIiItIRNt1EREREREREOsKmm4iIiIiIiEhH2HQTERERERER6QibbiIiIiIiIiIdYdNNREREREREpCP/D+RMSpCQ7RrEAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline \n", + "block.plot_histogram_hp_minmax_forces(df_hp_forces, day_obs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/BLOCK_T227_utils.py b/notebooks/BLOCK_T227_utils.py new file mode 100644 index 0000000..9db36d3 --- /dev/null +++ b/notebooks/BLOCK_T227_utils.py @@ -0,0 +1,415 @@ +import re +import os +import pandas as pd + +from matplotlib import pyplot as plt +from lsst.summit.utils.efdUtils import getEfdData +from lsst.ts.idl.enums.Script import ScriptState +from lsst.summit.utils.tmaUtils import TMAState + +__all__ = [ + "convert_script_state", + "filter_by_block_id", + "get_hp_minmax_forces", + "query_script_configuration", + "query_script_description", + "query_script_states", + "query_block_status", +] + + +def convert_script_state(state): + """ + Convert the Script state to a string. + + Parameters + ---------- + state : int + The Script state. + + Returns + ------- + str + The string representation of the Script state. + """ + return ScriptState(state).name + + +def filter_by_block_id(df, block_name): + """ + Filter a DataFrame by the block name. + + Parameters + ---------- + df : pandas.DataFrame + The DataFrame to filter. + block_name : str + The block name to filter by. + + Returns + ------- + pandas.DataFrame + The filtered DataFrame. + """ + assert "blockId" in df.columns, "The DataFrame must have a 'blockId' column." + block_number = int(re.search(r"\d+", block_name).group()) + block_id = f"{block_number:03d}" + temp_df = df[df.blockId.str.contains(block_id)] + return temp_df + + +def get_hp_minmax_forces(efd_client, tma_slew_events, event_type=TMAState.SLEWING, verbose=False): + """ + Retrieve the minimum and maximum hardpoint measured forces during TMA + slewing events. + + Parameters: + efd_client : object + The EFD client used to query data. + tma_slew_events : list + A list of TMA slewing events to process. + event_type : TMAState, optional + The type of event to filter for (default is TMAState.SLEWING). + verbose : bool, optional + If True, print detailed information about each event + (default is False). + + Returns: + pd.DataFrame + A DataFrame containing the sequence number, azimuth difference, + elevation difference, minimum forces, and maximum forces for each + valid event. + """ + df = pd.DataFrame() + + for evt in tma_slew_events: + + if evt.type != event_type: + continue + + if evt.endReason == TMAState.FAULT: + print(f"Event {evt.seqNum} on {evt.dayObs} faulted. Ignoring it.") + continue + + az = query_mtmount_azimuth(efd_client, evt) + el = query_mtmount_elevation(efd_client, evt) + forces = query_m1m3_hp_measured_forces(efd_client, evt) + + try: + az_diff = az.actualPosition.iloc[-1] - az.actualPosition.iloc[0] + el_diff = el.actualPosition.iloc[-1] - el.actualPosition.iloc[0] + except AttributeError: + continue + + if verbose: + print( + f"{evt.seqNum}, " + f"{az_diff:8.2f}, " + f"{el_diff:8.2f}, " + f"{forces.min().min():8.2f}, " + f"{forces.max().max():8.2f} " + ) + + my_dict = dict( + seq_num=evt.seqNum, + delta_az=az_diff, + delta_el=el_diff, + min_forces=forces.min().min(), + max_forces=forces.max().max(), + ) + + my_df = pd.DataFrame([my_dict]) + df = pd.concat([df, my_df], ignore_index=True) + + return df + + +def plot_histogram_hp_minmax_forces(df, day_obs): + """ + Plots histograms of the minimum and maximum forces measured on hardpoints + during slews. + Parameters + ---------- + df : pandas.DataFrame + DataFrame containing the minimum and maximum forces data. It should + have columns 'min_forces' and 'max_forces'. + day_obs : int or str + The observation day identifier to be included in the plot title and + filename. + Returns + ------- + None + """ + fig, (min_ax, max_ax) = plt.subplots(figsize=(10, 5), ncols=2, sharey=True) + + min_ax.hist( + df["min_forces"], + ec="white", + alpha=0.75, + label="Minimum forces", + log=True, + ) + max_ax.hist( + df["max_forces"], + ec="white", + alpha=0.75, + label="Maximum forces", + log=True, + ) + + min_ax.grid(alpha=0.3) + min_ax.set_xlabel("Minimum measured forces on\n the hardpoints during a slew [N]") + min_ax.set_ylabel("Number of slews") + min_ax.axvline(-450, ls=":", c="black", alpha=0.5, label="Operational limit", lw=2) + min_ax.axvline(-900, ls="--", c="red", alpha=0.5, label="Fatigue limit", lw=2) + min_ax.legend() + + max_ax.grid(alpha=0.3) + max_ax.set_xlabel("Maximum measured forces on\n the hardpoints during a slew [N]") + # max_ax.set_ylabel("Number of slews") + max_ax.axvline(450, ls=":", c="black", alpha=0.5, label="Operational limit", lw=2) + max_ax.axvline(900, ls="--", c="red", alpha=0.5, label="Fatigue limit", lw=2) + max_ax.legend() + + fig.suptitle( + f"Histogram with the number of slews with\n" + f"different minimum and maximum measured forces on the hardpoints.\n" + f"DayObs {day_obs}, total of {df.index.size} slews", + ) + + os.makedirs("./plots", exist_ok=True) + fig.tight_layout() + fig.savefig(f"./plots/histogram_hp_minmax_dayobs_{day_obs}.png") + plt.show() + + +def query_m1m3_hp_measured_forces(efd_client, tma_slew_event): + """ + Query the EFD for the measured forces of the M1M3 component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + tma_slew_event : TMAEvent + The TMA event for the slew. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the measured forces of the M1M3 component. + """ + df_forces = getEfdData( + efd_client, + "lsst.sal.MTM1M3.hardpointActuatorData", + columns=[f"measuredForce{i}" for i in range(6)], + event=tma_slew_event, + ) + + return df_forces + + +def query_mtmount_azimuth(efd_client, tma_slew_event): + """ + Query the EFD for the azimuth of the MTMount component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + tma_slew_event : TMAEvent + The TMA event for the slew. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the azimuth of the MTMount component. + """ + df_azimuth = getEfdData( + efd_client, + "lsst.sal.MTMount.azimuth", + columns=["actualPosition"], + event=tma_slew_event, + ) + + return df_azimuth + + +def query_mtmount_elevation(efd_client, tma_slew_event): + """ + Query the EFD for the elevation of the MTMount component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + tma_slew_event : TMAEvent + The TMA event for the slew. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the elevation of the MTMount component. + """ + df_elevation = getEfdData( + efd_client, + "lsst.sal.MTMount.elevation", + columns=["actualPosition"], + event=tma_slew_event, + ) + + return df_elevation + + +def query_script_configuration(efd_client, start_day_obs, end_day_obs): + """ + Query the EFD for the configuration of the Script SAL component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + start_day_obs : int + The first day of observations to query. + end_day_obs : int + The last day of observations to query. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the configuration of the Script SAL component. + """ + df_configuration = getEfdData( + efd_client, + "lsst.sal.Script.command_configure", + columns="*", + begin=start_day_obs, + end=end_day_obs, + ) + + return df_configuration + + +def query_script_description(efd_client, start_day_obs, end_day_obs, block_id=None): + """ + Query the EFD for the description of the Script SAL component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + start_day_obs : int + The first day of observations to query. + end_day_obs : int + The last day of observations to query. + block_id : str, optional + The block ID to filter by. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the description of the Script SAL component. + """ + df_description = getEfdData( + efd_client, + "lsst.sal.Script.logevent_description", + columns="*", + begin=start_day_obs, + end=end_day_obs, + ) + + return df_description + + +def query_script_log_message(efd_client, start_day_obs, end_day_obs): + """ + Query the EFD for the log messages of the Script SAL component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + start_day_obs : int + The first day of observations to query. + end_day_obs : int + The last day of observations to query. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the log messages of the Script SAL component. + """ + df_log_message = getEfdData( + efd_client, + "lsst.sal.Script.logevent_logMessage", + columns="*", + begin=start_day_obs, + end=end_day_obs, + ) + + return df_log_message + + +def query_script_states(efd_client, start_day_obs, end_day_obs, block_id=None): + """ + Query the EFD for the state of the Script SAL component. + + Parameters + ---------- + efd_client : EfdClient + The EFD client to use for querying. + start_day_obs : int + The first day of observations to query. + end_day_obs : int + The last day of observations to query. + block_id : str, optional + The block ID to filter by. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the state of the Script SAL component. + """ + df_state = getEfdData( + efd_client, + "lsst.sal.Script.logevent_state", + columns="*", + begin=start_day_obs, + end=end_day_obs, + ) + + if block_id is not None: + df_state = filter_by_block_id(df_state, block_id) + + return df_state + + +def query_block_status(client, start_day_obs, end_day_obs, block_name): + """ + Query the EFD for the block status. + + Parameters + ---------- + client : EfdClient + The EFD client to use for querying. + start_day_obs : int + The first day of observations to query. + end_day_obs : int + The last day of observations to query. + block_name : str + The name of the block to query. + + Returns + ------- + pandas.DataFrame + A DataFrame containing the block status. + """ + df_block = getEfdData( + client, + "lsst.sal.Scheduler.logevent_blockStatus", + columns="*", + begin=start_day_obs, + end=end_day_obs, + ) + df_block = df_block[df_block.id == block_name] + return df_block diff --git a/notebooks/plots/histogram_hp_minmax_dayobs_20241026.png b/notebooks/plots/histogram_hp_minmax_dayobs_20241026.png new file mode 100644 index 0000000000000000000000000000000000000000..3bd22eadf1532b1c68eb802b395cbf3b6bc9c480 GIT binary patch literal 54631 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 {w9-K(#jgNPWgPmVt{lYEeXy9p^!e^BsC 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 literal 0 HcmV?d00001