diff --git a/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOM-1593_force_actuator_error_elevation.ipynb b/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOM-1593_force_actuator_error_elevation.ipynb new file mode 100644 index 00000000..3bea76d3 --- /dev/null +++ b/notebooks/tel_and_site/subsys_req_ver/m1m3/SITCOM-1593_force_actuator_error_elevation.ipynb @@ -0,0 +1,584 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5223f18-41bc-436a-838e-acdbce522a8d", + "metadata": {}, + "source": [ + "# [SITCOM-1593] - M1M3 Force actuator analysis for different elevations\n", + "\n", + "Following [SITCOM-1593], we want to plot the forces actuator errors as a function of elevation. \n", + "\n", + "Given a time range, this notebook will plot the average primaryCylinderFollowingError as well as the maximum and minimum, for different elevations. \n", + "\n", + "[SITCOM-1593]: https://rubinobs.atlassian.net/browse/SITCOM-1593" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b70e4c91-237b-4b63-ab48-81d86ea93133", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68135d46-80ca-49a9-91e1-30ac7d1f4494", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "from astropy.time import Time\n", + "from pathlib import Path\n", + "\n", + "from lsst.summit.utils.efdUtils import EfdClient, getEfdData, makeEfdClient\n", + "from lsst.sitcom.vandv import m1m3\n", + "from lsst.ts.xml.tables.m1m3 import FATable\n", + "from lsst.ts.xml.enums.MTM1M3 import DetailedStates\n", + "\n", + "from collections import defaultdict\n", + "from bokeh.plotting import figure, show\n", + "from bokeh.models import HoverTool, TapTool, Legend, ColumnDataSource, Range1d\n", + "from bokeh.layouts import column\n", + "from bokeh.io import output_notebook\n", + "\n", + "N_ACTUATORS = 156\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1fda21fd-90d9-466a-8022-a8eb3831a857", + "metadata": {}, + "outputs": [], + "source": [ + "# resampling value for the loaded dataframe in seconds\n", + "resample_in_sec = 60 \n", + "# choose the number of bins in which we want to group the elevation_resampled values (x axis)\n", + "n_bins = 30 " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a660089-7aa0-4d1b-b657-dacd2303246b", + "metadata": {}, + "outputs": [], + "source": [ + "client = makeEfdClient()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dddfb070-89b4-4abf-814b-736fe51b1f9d", + "metadata": {}, + "outputs": [], + "source": [ + "start = Time(\"2024-11-09 02:15:0Z\", scale=\"utc\")\n", + "end = Time(\"2024-11-09 05:15:0Z\", scale=\"utc\")\n", + "topic = f\"lsst.sal.MTM1M3.forceActuatorData\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2489fed7-a3bb-44fe-bf9a-3ad707de3745", + "metadata": {}, + "outputs": [], + "source": [ + "plot_name = \"Force Actuator Error vs Elevation\"\n", + "plot_path = Path(\"./plots\")\n", + "plot_path.mkdir(exist_ok=True, parents=True)" + ] + }, + { + "cell_type": "markdown", + "id": "e62d1b6d-29d6-4415-ac25-01619208383b", + "metadata": {}, + "source": [ + "### retrieve data from EFD" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "389431a5-8495-4d82-8355-7c839a3fd85d", + "metadata": {}, + "outputs": [], + "source": [ + "# Tested with up to 3h of data in USDF, used 16GB node\n", + "# Retrieve Force Actuator information\n", + "primary_FA_error = [f\"primaryCylinderFollowingError{i}\" for i in range(N_ACTUATORS)]\n", + "df_primary_FA_error = getEfdData(\n", + " client,\"lsst.sal.MTM1M3.forceActuatorData\", columns=primary_FA_error, begin=start, end=end\n", + ") \n", + "# Retrieve elevations\n", + "elevations = getEfdData(\n", + " client,\"lsst.sal.MTMount.elevation\", columns=\"actualPosition\", begin=start, end=end\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "98f19de6-8fed-449b-9ad3-5a709c7fed43", + "metadata": {}, + "source": [ + "### resample the data into more manageable time chunks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67d33502-7e11-4f3a-88b3-ed9c91964c44", + "metadata": {}, + "outputs": [], + "source": [ + "# take the mean value for each of the actuators in {resample_in_sec} second samples\n", + "df_primary_FA_error_resampled_mean = df_primary_FA_error.resample(\n", + " f\"{resample_in_sec}s\"\n", + ").mean()\n", + "\n", + "# take the maximum value for each of the actuators in those {resample_in_sec} second samples\n", + "df_primary_FA_error_resampled_max = df_primary_FA_error.resample(\n", + " f\"{resample_in_sec}s\"\n", + ").max()\n", + "\n", + "# take the minimum value for each of the actuators in those {resample_in_sec} second samples\n", + "df_primary_FA_error_resampled_min = df_primary_FA_error.resample(\n", + " f\"{resample_in_sec}s\"\n", + ").min()\n", + "\n", + "# mean value of elevation in the time period ({resample_in_sec} seconds)\n", + "elevations_resampled = (\n", + " elevations[\"actualPosition\"].resample(f\"{resample_in_sec}s\").mean()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "71849f60-1fd9-4c7b-94fb-7496de04b9c6", + "metadata": {}, + "source": [ + "### obtain average, maximum and minimum across the 156 actuators" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "369e3969-f1cc-4602-bfaa-03f99e50e55b", + "metadata": {}, + "outputs": [], + "source": [ + "average_across_actuators_resampled = df_primary_FA_error_resampled_mean.mean(axis=1)\n", + "max_across_actuators_resampled = df_primary_FA_error_resampled_max.max(axis=1)\n", + "min_across_actuators_resampled = df_primary_FA_error_resampled_min.min(axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "5c16c817-b727-4311-803a-041f79b89cd2", + "metadata": {}, + "source": [ + "### make a scatter plot of force actuator errors for different elevations\n", + "Note that some elevation values might be repeated or slightly offset from other, similar values as the sequence of elevation stops can change a lot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd246647-a50c-4da7-b510-e5fcb1d2b4b9", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "bin_edges = np.linspace(\n", + " np.min(elevations_resampled), np.max(elevations_resampled), n_bins + 1\n", + ")\n", + "bin_indices = np.digitize(elevations_resampled, bin_edges)\n", + "\n", + "bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2\n", + "\n", + "plt.scatter(\n", + " elevations_resampled,\n", + " average_across_actuators_resampled,\n", + " alpha=0.3,\n", + " color=\"blue\",\n", + " label=f\"Average value per {resample_in_sec}s period\",\n", + " s=5,\n", + ")\n", + "\n", + "plt.scatter(\n", + " elevations_resampled,\n", + " max_across_actuators_resampled,\n", + " alpha=0.3,\n", + " color=\"orange\",\n", + " label=f\"Largest value per {resample_in_sec}s period\",\n", + " s=5,\n", + ")\n", + "\n", + "plt.scatter(\n", + " elevations_resampled,\n", + " min_across_actuators_resampled,\n", + " alpha=0.3,\n", + " color=\"red\",\n", + " label=f\"Lowest value per {resample_in_sec}s period\",\n", + " s=5,\n", + ")\n", + "\n", + "plt.title(plot_name, y=1.08)\n", + "t0 = pd.to_datetime(start.datetime, utc=True)\n", + "t1 = pd.to_datetime(end.datetime, utc=True)\n", + "plt.suptitle(f\"{t0} - {t1}\", y=0.93, fontsize=10, color=\"gray\")\n", + "plt.ylabel(\"primaryCylinderFollowingError (N)\")\n", + "plt.xlabel(\"elevation (degrees)\")\n", + "plt.legend()\n", + "plt.savefig(plot_path / \"sitcom-1593_fa_error_vs_elevation_scatter.png\")" + ] + }, + { + "cell_type": "markdown", + "id": "1a4788bd-4905-466b-94cf-c1bf03f0f3ed", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-21T12:45:50.801083Z", + "iopub.status.busy": "2024-11-21T12:45:50.800786Z", + "iopub.status.idle": "2024-11-21T12:45:50.965985Z", + "shell.execute_reply": "2024-11-21T12:45:50.965568Z", + "shell.execute_reply.started": "2024-11-21T12:45:50.801067Z" + } + }, + "source": [ + "### histogram the actuator ID that hits the maximum and minimum record most often" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adf9d44e-7969-4e9f-a5b1-4bb55cc8be48", + "metadata": {}, + "outputs": [], + "source": [ + "# get the actuator ID which has a maximum or minimum value in the resampled data set\n", + "# with maximum and minimum values of each actuator\n", + "\n", + "#first determine an array storing the actuator_id values\n", + "aid = np.empty(len(FATable))\n", + "for i in range(len(FATable)):\n", + " aid[i] = FATable[i].actuator_id\n", + "maxbin = int(np.max(aid))\n", + "minbin = int(np.min(aid))\n", + "\n", + "max_actuators = np.argmax(df_primary_FA_error_resampled_max, axis=1)\n", + "max_actuators_id = np.empty(len(max_actuators))\n", + "min_actuators = np.argmin(df_primary_FA_error_resampled_min, axis=1)\n", + "min_actuators_id = np.empty(len(min_actuators))\n", + "for i in range(len(max_actuators)):\n", + " max_actuators_id[i] = FATable[max_actuators[i]].actuator_id\n", + " min_actuators_id[i] = FATable[min_actuators[i]].actuator_id\n", + "\n", + "minhist = plt.hist(\n", + " min_actuators_id, bins=maxbin-minbin, range=[minbin, maxbin], color=\"red\", label=\"Largest negative errors\"\n", + ")\n", + "maxhist = plt.hist(\n", + " max_actuators_id, bins=maxbin-minbin, range=[minbin, maxbin], color=\"orange\", label=\"Largest positive errors\"\n", + ") \n", + "\n", + "plt.xlabel(\"Actuator ID\")\n", + "plt.yscale('log')\n", + "plt.grid(axis=\"y\",which=\"minor\")\n", + "plt.title(\"Actuator IDs with largest positive/negative Force Actuator Errors\")\n", + "plt.legend()\n", + "plt.savefig(plot_path / \"sitcom-1593_actuator_id_hist.png\")" + ] + }, + { + "cell_type": "markdown", + "id": "858d9042-de2b-40d3-a5b2-379cc95a58e7", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-21T12:49:26.616959Z", + "iopub.status.busy": "2024-11-21T12:49:26.616647Z", + "iopub.status.idle": "2024-11-21T12:49:26.655054Z", + "shell.execute_reply": "2024-11-21T12:49:26.654679Z", + "shell.execute_reply.started": "2024-11-21T12:49:26.616942Z" + } + }, + "source": [ + "### add interactivity to the plot\n", + "plot the same histogram with interactive hover tooltips\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b26a78d1-609f-4dd1-9f33-0d2872d5293e", + "metadata": {}, + "outputs": [], + "source": [ + "# one histogram for largest positive values of error\n", + "\n", + "histmax, edges = np.histogram(max_actuators_id, bins=maxbin-minbin, range=[minbin, maxbin])\n", + "bin_centers = (edges[:-1] + edges[1:]) / 2\n", + "source_max = ColumnDataSource(data={ 'bin_centers': bin_centers, 'top': histmax, \n", + " 'left': edges[:-1], 'right': edges[1:],})\n", + "p = figure(\n", + " title=\"Actuator IDs with largest positive Force Actuator Errors\",\n", + " y_range=Range1d(start=1, end=np.max(histmax)+10),\n", + " tools=\"\", x_axis_label=\"Actuator ID\", y_axis_type=\"log\"\n", + ")\n", + "\n", + "histplot_max = p.quad(\n", + " source=source_max, bottom=1, \n", + " fill_color=\"red\", line_color=None, alpha=0.5, \n", + " legend_label=\"IDs with largest positive errors\"\n", + ")\n", + "\n", + "hover = HoverTool(tooltips=[(\"Count\", \"@top\"), (\"ID\", \"@left\")])\n", + "p.add_tools(hover)\n", + "p.legend.location = \"top_right\"\n", + "output_notebook()\n", + "show(p)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09f5f21d", + "metadata": {}, + "outputs": [], + "source": [ + "# another for largest negative values of error\n", + "\n", + "maxbin = int(np.max(aid))\n", + "minbin = int(np.min(aid))\n", + "histmin, edges = np.histogram(min_actuators_id, bins=maxbin-minbin, range=[minbin, maxbin])\n", + "bin_centers = (edges[:-1] + edges[1:]) / 2\n", + "source_min = ColumnDataSource(data={ 'bin_centers': bin_centers, 'top': histmin, \n", + " 'left': edges[:-1], 'right': edges[1:],})\n", + "\n", + "p = figure(\n", + " title=\"Actuator IDs with largest negative Force Actuator Errors\",\n", + " y_range=Range1d(start=1, end=np.max(histmin)+10),\n", + " tools=\"\", x_axis_label=\"Actuator ID\", y_axis_type=\"log\"\n", + ")\n", + "\n", + "histplot_max = p.quad(\n", + " source=source_min, bottom=1, \n", + " fill_color=\"orange\", line_color=None, alpha=0.5, \n", + " legend_label=\"IDs with largest negative errors\"\n", + ")\n", + "\n", + "hover = HoverTool(tooltips=[(\"Count\", \"@top\"), (\"ID\", \"@left\")])\n", + "p.add_tools(hover)\n", + "p.legend.location = \"top_right\"\n", + "output_notebook()\n", + "show(p)" + ] + }, + { + "cell_type": "markdown", + "id": "1dffc732-490d-44af-8f2f-9932fd9d379f", + "metadata": {}, + "source": [ + "### plot all actuator force error behavior as a function of elevation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "acf3e7f6-17cf-4d9d-919a-0f9f51493ed2", + "metadata": {}, + "outputs": [], + "source": [ + "binned_primary_FA_error_resampled_mean = [None] * N_ACTUATORS\n", + "binned_primary_FA_error_resampled_max = [None] * N_ACTUATORS\n", + "binned_primary_FA_error_resampled_min = [None] * N_ACTUATORS\n", + "bin_edges = np.linspace(\n", + " np.min(elevations_resampled), np.max(elevations_resampled), n_bins + 1\n", + ")\n", + "bin_indices = np.digitize(elevations_resampled, bin_edges)\n", + "\n", + "for j in range(N_ACTUATORS):\n", + " binned_primary_FA_error_resampled_mean[j] = [\n", + " df_primary_FA_error_resampled_mean[bin_indices == i][\n", + " f\"primaryCylinderFollowingError{j}\"\n", + " ].mean()\n", + " for i in range(1, len(bin_edges))\n", + " ]\n", + " binned_primary_FA_error_resampled_max[j] = [\n", + " df_primary_FA_error_resampled_max[bin_indices == i][\n", + " f\"primaryCylinderFollowingError{j}\"\n", + " ].mean()\n", + " for i in range(1, len(bin_edges))\n", + " ]\n", + " binned_primary_FA_error_resampled_min[j] = [\n", + " df_primary_FA_error_resampled_min[bin_indices == i][\n", + " f\"primaryCylinderFollowingError{j}\"\n", + " ].mean()\n", + " for i in range(1, len(bin_edges))\n", + " ]\n", + " \n", + "bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5bc24bf6-9fe7-447e-9337-023a4d42292c", + "metadata": {}, + "outputs": [], + "source": [ + "for j in range(N_ACTUATORS):\n", + " plt.plot(\n", + " bin_centers,\n", + " binned_primary_FA_error_resampled_mean[j],\n", + " alpha=0.1,\n", + " color=\"gray\",\n", + " )\n", + "\n", + " plt.plot(\n", + " bin_centers,\n", + " binned_primary_FA_error_resampled_min[j],\n", + " alpha=0.1,\n", + " color=\"red\",\n", + " )\n", + "\n", + " plt.plot(\n", + " bin_centers,\n", + " binned_primary_FA_error_resampled_max[j],\n", + " alpha=0.1,\n", + " color=\"orange\",\n", + " )\n", + "\n", + "plt.title(f\"{plot_name}: all actuators\", y=1.08)\n", + "t0 = pd.to_datetime(start.datetime, utc=True)\n", + "t1 = pd.to_datetime(end.datetime, utc=True)\n", + "plt.suptitle(f\"{t0} - {t1}\", y=0.93, fontsize=10, color=\"gray\")\n", + "plt.ylabel(\"primaryCylinderFollowingError (N)\")\n", + "plt.xlabel(\"elevation (degrees)\")\n", + "plt.legend()\n", + "plt.savefig(plot_path / \"sitcom-1593_fa_error_vs_elevation_line.png\")" + ] + }, + { + "cell_type": "markdown", + "id": "1752d1ab-0e0c-47bc-bf54-32a9e96c167e", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-21T12:49:26.616959Z", + "iopub.status.busy": "2024-11-21T12:49:26.616647Z", + "iopub.status.idle": "2024-11-21T12:49:26.655054Z", + "shell.execute_reply": "2024-11-21T12:49:26.654679Z", + "shell.execute_reply.started": "2024-11-21T12:49:26.616942Z" + } + }, + "source": [ + "### add interactivity to the plot\n", + "So that we can identify with hover the actuator IDs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "038fe76d-c651-4d05-b102-95dcec6d8ee6", + "metadata": {}, + "outputs": [], + "source": [ + "fadata_mean = defaultdict(list)\n", + "fadata_max = defaultdict(list)\n", + "fadata_min = defaultdict(list)\n", + "\n", + "fa_mean = np.array(binned_primary_FA_error_resampled_mean)\n", + "fa_max = np.array(binned_primary_FA_error_resampled_max)\n", + "fa_min = np.array(binned_primary_FA_error_resampled_min)\n", + "\n", + "for i in range(N_ACTUATORS):\n", + " actuator_id = FATable[i].actuator_id\n", + " fadata_mean[\"elevation\"].append(bin_centers)\n", + " fadata_mean[\"force error\"].append(fa_mean[i])\n", + " fadata_mean[\"FA\"].append(f\"actuator_id {actuator_id}\")\n", + " fadata_max[\"elevation\"].append(bin_centers)\n", + " fadata_max[\"force error\"].append(fa_max[i])\n", + " fadata_max[\"FA\"].append(f\"actuator_id {actuator_id}\")\n", + " fadata_min[\"elevation\"].append(bin_centers)\n", + " fadata_min[\"force error\"].append(fa_min[i])\n", + " fadata_min[\"FA\"].append(f\"actuator_id {actuator_id}\")\n", + "\n", + "hover_opts = dict(tooltips=[(\"FA\", \"@FA\")], show_arrow=False, line_policy=\"next\")\n", + "line_opts_mean = dict(\n", + " line_width=1,\n", + " line_color=\"grey\",\n", + " line_alpha=0.1,\n", + " hover_line_alpha=1.0,\n", + " source=fadata_mean,\n", + ")\n", + "line_opts_max = dict(\n", + " line_width=1,\n", + " line_color=\"orange\",\n", + " line_alpha=0.1,\n", + " hover_line_alpha=1.0,\n", + " source=fadata_max,\n", + ")\n", + "line_opts_min = dict(\n", + " line_width=1,\n", + " line_color=\"red\",\n", + " line_alpha=0.1,\n", + " hover_line_alpha=1.0,\n", + " source=fadata_min,\n", + ")\n", + "\n", + "p = figure(\n", + " title=f\"{plot_name}: all actuators\",\n", + " x_axis_label=\"elevation (degrees)\",\n", + " y_axis_label=\"primaryCylinderFollowingError (N)\",\n", + " tools=[HoverTool(**hover_opts), TapTool()],\n", + ")\n", + "\n", + "p.multi_line(xs=\"elevation\", ys=\"force error\", **line_opts_mean)\n", + "p.multi_line(xs=\"elevation\", ys=\"force error\", **line_opts_max)\n", + "p.multi_line(xs=\"elevation\", ys=\"force error\", **line_opts_min)\n", + "\n", + "output_notebook()\n", + "show(p)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b2b913b-7656-48ff-a5f2-c5062c4ce67e", + "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 +}