Skip to content

Commit 4149ef8

Browse files
committed
create initial version of notebook
1 parent c9cee31 commit 4149ef8

File tree

1 file changed

+336
-0
lines changed

1 file changed

+336
-0
lines changed

sitcomtn-179.ipynb

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "d7d70254-1e00-47f6-843b-e93d03ebb845",
6+
"metadata": {},
7+
"source": [
8+
"# Characterizing the Influence of Wind Speed and Direction on Portable and Fixed DIMM seeing measurements\n",
9+
"\n",
10+
"This is the companion notebook associated to TechNote sitcomtn-179"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "4dc15ebc-b48f-4e3c-a290-665fb7ae2340",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"%matplotlib inline\n",
21+
"%load_ext autoreload\n",
22+
"%autoreload 2\n",
23+
"\n",
24+
"import os \n",
25+
"import pandas as pd\n",
26+
"import numpy as np\n",
27+
"\n",
28+
"from astropy.time import Time\n",
29+
"import matplotlib.pyplot as plt\n",
30+
"\n",
31+
"from lsst_efd_client import EfdClient\n",
32+
"from lsst.summit.utils.efdUtils import getEfdData"
33+
]
34+
},
35+
{
36+
"cell_type": "code",
37+
"execution_count": null,
38+
"id": "bbcbfc14-803d-4c52-8823-27979a847aee",
39+
"metadata": {},
40+
"outputs": [],
41+
"source": [
42+
"# Some initialization\n",
43+
"resample = '3min' # DIMMs are providing measurements every ~1 minutes, we resample this to decrease the noise \n",
44+
"EFD_client = EfdClient(\"usdf_efd\")"
45+
]
46+
},
47+
{
48+
"cell_type": "code",
49+
"execution_count": null,
50+
"id": "fb64dd84-9913-4cfb-8b15-2fda871d00fa",
51+
"metadata": {},
52+
"outputs": [],
53+
"source": [
54+
"# Create directory for plots if it doesn't already exist\n",
55+
"if not os.path.isdir(\"plots\"):\n",
56+
" os.mkdir(\"plots\")"
57+
]
58+
},
59+
{
60+
"cell_type": "markdown",
61+
"id": "3f29a4d3-dfe2-4407-be62-f51a31418d08",
62+
"metadata": {},
63+
"source": [
64+
"## Some interesting DIMM data taking periods corresponding to different wind speeds and directions\n",
65+
"\n",
66+
" - 2025-11-27 -28: Wind speed > 4 m/s - Wind from NE\n",
67+
" - 2025-12-02 -03: Wind speed < 4 m/s = Wind direction mostly from NE - Somme occurrences of very low wind speed\n",
68+
" - 2025-12-03 -04: Decreasing wind speed - Wind direction from S\n",
69+
" - 2025-12-28 -29: Low wind speed - Changing direction\n",
70+
" - 2026-01-01 -02: Both DIMM disagree - Wind speed between 1 and 5 m/s - Wind direction from SSE\n",
71+
" - 2026-01-03 -04: Wind stops and change of direction"
72+
]
73+
},
74+
{
75+
"cell_type": "code",
76+
"execution_count": null,
77+
"id": "019cca4d-cd8e-4bd6-946a-423517ef14eb",
78+
"metadata": {},
79+
"outputs": [],
80+
"source": [
81+
"t_start = Time(\"2026-01-03 22:00:00\")\n",
82+
"t_end = Time(\"2026-01-04 12:00:00\")\n",
83+
"\n",
84+
"df_dimm_meas = getEfdData(\n",
85+
" EFD_client, \"lsst.sal.DIMM.logevent_dimmMeasurement\", begin=t_start, end=t_end)\n",
86+
"df_dimm_meas.dropna(inplace=True)\n",
87+
"df_dimm_meas = df_dimm_meas[df_dimm_meas[\"fwhm\"]<5] # Drop abnormally high fwhm\n",
88+
"\n",
89+
"df_ess_airflow = getEfdData(\n",
90+
" EFD_client, \"lsst.sal.ESS.airFlow\", begin=t_start, end=t_end\n",
91+
")\n",
92+
"\n",
93+
"# Determine the time window where we actually get DIMM measurements\n",
94+
"start_dimm = df_dimm_meas.index[0]\n",
95+
"end_dimm = df_dimm_meas.index[-1]\n",
96+
"\n",
97+
"# Get wind measurements - salIndex 301 corresponds to the weather tower located on the calibration hill\n",
98+
"airflow = df_ess_airflow[start_dimm:end_dimm][df_ess_airflow[\"salIndex\"] == 301]"
99+
]
100+
},
101+
{
102+
"cell_type": "code",
103+
"execution_count": null,
104+
"id": "8e649bf8-c008-4716-af77-f5fb012c3052",
105+
"metadata": {},
106+
"outputs": [],
107+
"source": [
108+
"# Define cut_1 and cut_2 to select DIMM-1 and DIMM-2 according to the salIndex\n",
109+
"cut_1 = df_dimm_meas[\"salIndex\"] == 1\n",
110+
"cut_2 = df_dimm_meas[\"salIndex\"] == 2\n",
111+
"\n",
112+
"# Determine the time window where we actually get DIMM measurements\n",
113+
"start_dimm = df_dimm_meas.index[0]\n",
114+
"end_dimm = df_dimm_meas.index[-1]\n",
115+
"\n",
116+
"# Compute resampled wind direction correctly (handling 0-360 boundary)\n",
117+
"sin_mean = airflow['direction'].apply(lambda x: np.sin(np.radians(x))).resample(resample).mean()\n",
118+
"cos_mean = airflow['direction'].apply(lambda x: np.cos(np.radians(x))).resample(resample).mean()\n",
119+
"wind_direction = (np.degrees(np.arctan2(sin_mean, cos_mean)) + 360) % 360\n",
120+
"\n",
121+
"# Build the combined dataframe\n",
122+
"df = pd.concat([\n",
123+
" airflow[\"speed\"].resample(resample).mean() .rename(\"wind_speed\"),\n",
124+
" wind_direction .rename(\"wind_direction\"),\n",
125+
" df_dimm_meas[\"fwhm\"][cut_1].resample(resample).mean() .rename(\"dimm_1_fwhm\"),\n",
126+
" df_dimm_meas[\"fwhm\"][cut_2].resample(resample).mean() .rename(\"dimm_2_fwhm\"),\n",
127+
" df_dimm_meas[\"secz\"][cut_1].resample(resample).mean() .rename(\"dimm_1_airmass\"),\n",
128+
" df_dimm_meas[\"secz\"][cut_2].resample(resample).mean() .rename(\"dimm_2_airmass\"),\n",
129+
"], axis=1)\n",
130+
"\n",
131+
"# Drop rows where dimm_1 or dimm_2 have no values\n",
132+
"df = df.dropna(subset=[\"dimm_1_fwhm\", \"dimm_2_fwhm\"], how=\"any\")\n",
133+
"\n",
134+
"t_min = df.index[0]\n",
135+
"t_max = df.index[-1]"
136+
]
137+
},
138+
{
139+
"cell_type": "code",
140+
"execution_count": null,
141+
"id": "139c8f0f-fa3e-4f10-acf4-91242b12908f",
142+
"metadata": {},
143+
"outputs": [],
144+
"source": [
145+
"fig, ax = plt.subplots(2, 1, dpi=128, figsize=(8, 6))\n",
146+
"\n",
147+
"ax[0].scatter(df.index, df[\"dimm_1_fwhm\"], marker=\"o\", s=2, color=\"red\", label=\"DIMM 1\")\n",
148+
"ax[0].scatter(df.index, df[\"dimm_2_fwhm\"], marker=\"o\", s=2, color=\"blue\", label=\"DIMM 2\")\n",
149+
"ax[0].set_xlabel(\"Time\")\n",
150+
"ax[0].set_ylabel(\"FWHM (arcsec)\")\n",
151+
"ax[0].legend()\n",
152+
"\n",
153+
"Q0 = ax[1].quiver(df.index, df[\"wind_speed\"], df[\"wind_speed\"] , df[\"wind_speed\"], \n",
154+
" angles=270.0-df[\"wind_direction\"], scale=15, scale_units=\"y\", width=0.0005, color=\"green\") \n",
155+
"qk0 = ax[1].quiverkey(Q0, 0.1, 0.9, 5, '5 m/s', labelpos='E', coordinates='axes', color=\"green\")\n",
156+
"ax[1].set_xlabel(\"Time\")\n",
157+
"ax[1].set_ylabel(\"Wind Speed (m/s)\")\n",
158+
"\n",
159+
"# Add date\n",
160+
"date_start = t_min.strftime(\"%Y-%m-%d\")\n",
161+
"date_end = t_max.strftime(\"%Y-%m-%d\")\n",
162+
"\n",
163+
"fig.suptitle(f\"FWHM(DIMM_1) -- FWHM(DIMM_2) (arcsec) - Wind speed (m/s) - {date_start} to {date_end}\")\n",
164+
"\n",
165+
"fig.tight_layout()\n",
166+
"plt.savefig(f\"plots/dimm-1_dimm2-wind_{date_start}-{date_end}.png\")"
167+
]
168+
},
169+
{
170+
"cell_type": "code",
171+
"execution_count": null,
172+
"id": "4cebf87e-55db-4053-8321-36ab95f114c1",
173+
"metadata": {},
174+
"outputs": [],
175+
"source": [
176+
"fig, ax = plt.subplots(1, 1, dpi=128, figsize=(8, 5))\n",
177+
"\n",
178+
"ax.scatter(df[\"wind_speed\"], df[\"dimm_1_fwhm\"] - df[\"dimm_2_fwhm\"], marker=\"o\", s=10, color=\"green\")\n",
179+
"ax.set_ylabel(\"Dimm_1 - Dimm_2 (arcsec)\")\n",
180+
"ax.set_xlabel(\"Wind speed (m/s)\")\n",
181+
"#ax.set_xlim([0., 12.])\n",
182+
"ax.set_ylim([-0.5, 1])\n",
183+
"\n",
184+
"# Add date\n",
185+
"date_start = t_min.strftime(\"%Y-%m-%d\")\n",
186+
"date_end = t_max.strftime(\"%Y-%m-%d\")\n",
187+
"\n",
188+
"fig.suptitle(f\"{date_start} to {date_end}\")\n",
189+
"\n",
190+
"fig.tight_layout()\n",
191+
"plt.savefig(f\"plots/dimm_1-dimm_2-Vs-wind_res_{resample}_{date_start}-{date_end}.png\")"
192+
]
193+
},
194+
{
195+
"cell_type": "code",
196+
"execution_count": null,
197+
"id": "3e40d470-efe2-444b-b810-2dbdc8e0a5aa",
198+
"metadata": {},
199+
"outputs": [],
200+
"source": [
201+
"# Plot DIMM-1 - DIMM-2 in polar coordinates as a function of wind_direction\n",
202+
"# color scale is wind_speed\n",
203+
"\n",
204+
"wind_direction_rad = np.deg2rad(df[\"wind_direction\"])\n",
205+
"\n",
206+
"fig = plt.figure(figsize=(10,7))\n",
207+
"ax = fig.add_subplot(111, polar=True)\n",
208+
"\n",
209+
"# Compass-like orientation\n",
210+
"ax.set_theta_zero_location(\"N\") # 0° at North\n",
211+
"ax.set_theta_direction(-1) # Clockwise\n",
212+
"\n",
213+
"# Set grid labels in degrees instead of radians\n",
214+
"ax.set_thetagrids(range(0, 360, 30)) # every 30°\n",
215+
"\n",
216+
"\n",
217+
"# Plot histogram bars on polar axis\n",
218+
"sc = ax.scatter(wind_direction_rad, df[\"dimm_1_fwhm\"]-df[\"dimm_2_fwhm\"],\n",
219+
" c=df[\"wind_speed\"], cmap='viridis', s=20)\n",
220+
"ax.set_rlabel_position(90)\n",
221+
"\n",
222+
"# Add a colorbar\n",
223+
"cbar = plt.colorbar(sc, ax=ax, pad=0.1)\n",
224+
"cbar.set_label(\"Wind speed (m/s)\")\n",
225+
"\n",
226+
"# Add date\n",
227+
"date_start = t_min.strftime(\"%Y-%m-%d\")\n",
228+
"date_end = t_max.strftime(\"%Y-%m-%d\")\n",
229+
"\n",
230+
"fig.suptitle(f\"FWHM(DIMM_1)- FWHM(DIMM_2) (arcsec) / {date_start} to {date_end}\")\n",
231+
"fig.tight_layout()\n",
232+
"plt.savefig(f\"plots/dimm_diff_polar_{date_start}-{date_end}.png\")"
233+
]
234+
},
235+
{
236+
"cell_type": "code",
237+
"execution_count": null,
238+
"id": "d7610af4-50b2-4352-800d-75ae76e39a52",
239+
"metadata": {},
240+
"outputs": [],
241+
"source": [
242+
"# Plot DIMM-1 and DIMM-2 FWHM in polar coordinates as a function of wind direction\n",
243+
"# color scale is wind_speed\n",
244+
"\n",
245+
"wind_direction_rad = np.deg2rad(df[\"wind_direction\"])\n",
246+
"\n",
247+
"fig = plt.figure(figsize=(15,12))\n",
248+
"\n",
249+
"ax = []\n",
250+
"pos = [221, 222]\n",
251+
"for i in [0,1]:\n",
252+
" ax.append(fig.add_subplot(int(pos[i]), polar=True))\n",
253+
" \n",
254+
" # Compass-like orientation\n",
255+
" ax[i].set_theta_zero_location(\"N\") # 0° at North\n",
256+
" ax[i].set_theta_direction(-1) # Clockwise\n",
257+
" \n",
258+
" # Set grid labels in degrees instead of radians\n",
259+
" ax[i].set_thetagrids(range(0, 360, 30)) # every 30°\n",
260+
" \n",
261+
" \n",
262+
" # Plot histogram bars on polar axis\n",
263+
" sc = ax[i].scatter(wind_direction_rad, df[f\"dimm_{i+1}_fwhm\"],\n",
264+
" c=df[\"wind_speed\"], cmap='viridis', s=20)\n",
265+
" ax[i].set_rlim(0.4, 1.5) \n",
266+
" ax[i].set_rlabel_position(90)\n",
267+
" ax[i].set_title(f\"FWHM DIMM-{i+1} (arcsec)\")\n",
268+
" \n",
269+
" # Add a colorbar\n",
270+
" cbar = plt.colorbar(sc, ax=ax[i], pad=0.1)\n",
271+
" cbar.set_label(\"Wind speed (m/s)\")\n",
272+
" \n",
273+
" # Add date\n",
274+
" date_start = t_min.strftime(\"%Y-%m-%d\")\n",
275+
" date_end = t_max.strftime(\"%Y-%m-%d\")\n",
276+
" \n",
277+
" fig.suptitle(f\"{date_start} to {date_end}\")\n",
278+
"fig.tight_layout(rect=[0, 0, 1, 0.97])\n",
279+
"plt.savefig(f\"plots/DIMM-1_DIMM-2_polar_{date_start}-{date_end}.png\")"
280+
]
281+
},
282+
{
283+
"cell_type": "markdown",
284+
"id": "1a7fa860-2852-4df9-ad95-fa48dc7c7a90",
285+
"metadata": {},
286+
"source": [
287+
"## Check boresight of bot DIMM\n",
288+
"\n",
289+
"Both DIMM are operating independently so there is no reason for them to point to the same target. \n",
290+
"\n",
291+
"Unfortunately we cannot get the reference star coordinates in the EFD (values are wrong). \n",
292+
"\n",
293+
"In order to compare both DIMM pointings we use the airmass as a proxy and we assume that if the airmass is the same for both DIMM then they\n",
294+
"are very likely pointing to the same reference star."
295+
]
296+
},
297+
{
298+
"cell_type": "code",
299+
"execution_count": null,
300+
"id": "89839252-7f37-4a4c-8b72-90bc4ccc3496",
301+
"metadata": {},
302+
"outputs": [],
303+
"source": [
304+
"fig, ax = plt.subplots(1, 1, dpi=128, figsize=(12, 5))\n",
305+
"ax.scatter(df_dimm_meas.index[cut_1], df_dimm_meas[\"secz\"][cut_1], marker=\"o\", s=2, color=\"red\", label=\"DIMM 1\", alpha=0.5)\n",
306+
"ax.scatter(df_dimm_meas.index[cut_2], df_dimm_meas[\"secz\"][cut_2]+0.004, marker=\"o\", s=2, color=\"blue\", label=\"DIMM 2\", alpha=0.5)\n",
307+
"ax.set_xlabel(\"Time\")\n",
308+
"ax.set_ylabel(\"Airmass\")\n",
309+
"ax.legend()\n",
310+
"fig.tight_layout(rect=[0, 0, 1, 0.97])\n",
311+
"plt.savefig(f\"plots/airmass_{t_start.strftime(\"%Y-%m-%d\")}-{t_end.strftime(\"%Y-%m-%d\")}.png\")"
312+
]
313+
}
314+
],
315+
"metadata": {
316+
"kernelspec": {
317+
"display_name": "LSST",
318+
"language": "python",
319+
"name": "lsst"
320+
},
321+
"language_info": {
322+
"codemirror_mode": {
323+
"name": "ipython",
324+
"version": 3
325+
},
326+
"file_extension": ".py",
327+
"mimetype": "text/x-python",
328+
"name": "python",
329+
"nbconvert_exporter": "python",
330+
"pygments_lexer": "ipython3",
331+
"version": "3.12.11"
332+
}
333+
},
334+
"nbformat": 4,
335+
"nbformat_minor": 5
336+
}

0 commit comments

Comments
 (0)