MUSE Pipeline Reference Manual  0.18.1
muse_astro.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set sw=2 sts=2 et cin: */
3 /*
4  * This file is part of the MUSE Instrument Pipeline
5  * Copyright (C) 2005-2014 European Southern Observatory
6  * (C) 2000-2002 European Southern Observatory
7  * (C) 2002-2006 European Southern Observatory
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 /*----------------------------------------------------------------------------*
29  * Includes *
30  *----------------------------------------------------------------------------*/
31 #include <cpl.h>
32 #include <math.h>
33 #include <string.h>
34 
35 #include "muse_astro.h"
36 
37 #include "muse_pfits.h"
38 
39 /*----------------------------------------------------------------------------*
40  * Debugging Macros *
41  * Set these to 1 or higher for (lots of) debugging output *
42  *----------------------------------------------------------------------------*/
43 #define MUSE_ASTRO_AIRMASS_APPROX 2 /* airmass approximation to use: *
44  * 0 Young & Irvine (1967) *
45  * 1 Young (1994) *
46  * 2 Hardie (1962) */
47 
48 /*----------------------------------------------------------------------------*/
57 /*----------------------------------------------------------------------------*/
58 
61 /*----------------------------------------------------------------------------*/
77 /*----------------------------------------------------------------------------*/
78 static double
79 muse_astro_get_zenith_distance(double aHourAngle, double aDelta,
80  double aLatitude)
81 {
82  double p0 = sin(aLatitude) * sin(aDelta),
83  p1 = cos(aLatitude) * cos(aDelta),
84  z = p0 + cos(aHourAngle) * p1;
85  return fabs(z) < FLT_EPSILON ? 0. : z;
86 }
87 
88 #if MUSE_ASTRO_AIRMASS_APPROX == 0
89 /*----------------------------------------------------------------------------*/
101 /*----------------------------------------------------------------------------*/
102 static double
103 muse_astro_get_airmass_youngirvine(double aSecZ)
104 {
105  return aSecZ * (1. - 0.0012 * (pow(aSecZ, 2) - 1.));
106 }
107 #endif /* MUSE_ASTRO_AIRMASS_APPROX == 0 */
108 
109 #if MUSE_ASTRO_AIRMASS_APPROX == 1
110 /*----------------------------------------------------------------------------*/
124 /*----------------------------------------------------------------------------*/
125 static double
126 muse_astro_get_airmass_young(double aCosZt)
127 {
128  return (1.002432 * aCosZt*aCosZt + 0.148386 * aCosZt + 0.0096467)
129  / (aCosZt*aCosZt*aCosZt + 0.149864 * aCosZt*aCosZt + 0.0102963 * aCosZt
130  + 0.000303978);
131 }
132 #endif /* MUSE_ASTRO_AIRMASS_APPROX == 1 */
133 
134 #if MUSE_ASTRO_AIRMASS_APPROX == 2
135 /*----------------------------------------------------------------------------*/
148 /*----------------------------------------------------------------------------*/
149 static double
150 muse_astro_get_airmass_hardie(double aSecZ)
151 {
152  double secm1 = aSecZ - 1;
153  return aSecZ - 0.0018167 * secm1 - 0.002875 * secm1*secm1
154  - 0.0008083 * secm1*secm1*secm1;
155 }
156 #endif /* MUSE_ASTRO_AIRMASS_APPROX == 2 */
157 
158 /*----------------------------------------------------------------------------*/
196 /*----------------------------------------------------------------------------*/
197 double
198 muse_astro_compute_airmass(double aRA, double aDEC, double aLST,
199  double aExptime, double aLatitude)
200 {
201  cpl_ensure(aRA >= 0. && aRA < 360. && aDEC >= -90. && aDEC <= 90. &&
202  aLST >= 0. && aLST < 86400. && aLatitude >= -90. && aLatitude <= 90.,
203  CPL_ERROR_ILLEGAL_INPUT, -1.);
204  cpl_ensure(aExptime >= 0., CPL_ERROR_ILLEGAL_INPUT, -1.);
205 
206  /* Compute hour angle of the observation in degrees. */
207  double HA = aLST * 15./3600. - aRA;
208  /* Range adjustments. Angle between line of sight and the meridian is needed. */
209  if (HA < -180.) {
210  HA += 360.;
211  }
212  if (HA > 180.) {
213  HA -= 360.;
214  }
215 
216  /* Convert angles from degrees to radians. */
217  double delta = aDEC * CPL_MATH_RAD_DEG,
218  latitude = aLatitude * CPL_MATH_RAD_DEG,
219  hourangle = HA * CPL_MATH_RAD_DEG;
220 
221  /* Calculate airmass of the observation using the approximation given *
222  * by Young (1994). For non-zero exposure times these airmass values are *
223  * averaged using the weights given by Stetson. */
224  double cosz = muse_astro_get_zenith_distance(hourangle, delta, latitude);
225 #if MUSE_ASTRO_AIRMASS_APPROX == 2
226  double z = acos(cosz) * CPL_MATH_DEG_RAD;
227  const double zlimit = 80.;
228  if (z > zlimit) {
229  cpl_msg_warning(__func__, "Zenith angle %f > %f!", z, zlimit);
230  }
231 #endif
232  if (cosz == 0. || fabs(1. / cosz) < FLT_EPSILON ||
233  acos(cosz) > CPL_MATH_PI_2) {
234  cpl_msg_error(__func__, "Airmass computation unsuccessful. Object is below "
235  "the horizon at start (z = %f).", acos(cosz) * CPL_MATH_DEG_RAD);
236  cpl_error_set(__func__, CPL_ERROR_ILLEGAL_OUTPUT);
237  return -1.;
238  }
239 
240  double airmass = 0.;
241 #if MUSE_ASTRO_AIRMASS_APPROX == 0
242  airmass = muse_astro_get_airmass_youngirvine(1. / cosz);
243 #endif
244 #if MUSE_ASTRO_AIRMASS_APPROX == 1
245  airmass = muse_astro_get_airmass_young(cosz);
246 #endif
247 #if MUSE_ASTRO_AIRMASS_APPROX == 2
248  airmass = muse_astro_get_airmass_hardie(1. / cosz);
249 #endif
250 #if MUSE_ASTRO_AIRMASS_APPROX > 2
251 #error set MUSE_ASTRO_AIRMASS_APPROX to 0, 1, 2
252 #endif
253 
254  /* if the exposure time is larger than zero, weight in airmass *
255  * at mid and end exposure according to Stetson's weights (which *
256  * are also used in IRAF/astcalc for the eairmass function */
257  if (aExptime > 0.) {
258  const double weights[] = {1./6., 2./3., 1./6.};
259  const int nweights = sizeof(weights) / sizeof(double);
260 
261  double timeStep = aExptime / (nweights - 1) * 15./3600. * CPL_MATH_RAD_DEG;
262  airmass *= weights[0];
263 
264  int i;
265  for (i = 1; i < nweights; i++) {
266  cosz = muse_astro_get_zenith_distance(hourangle + i * timeStep, delta, latitude);
267 #if MUSE_ASTRO_AIRMASS_APPROX == 2
268  z = acos(cosz) * CPL_MATH_DEG_RAD;
269  if (z > zlimit) {
270  cpl_msg_warning(__func__, "Zenith angle %f > %f!", z, zlimit);
271  }
272 #endif
273  if (cosz == 0. || fabs(1. / cosz) < FLT_EPSILON ||
274  acos(cosz) > CPL_MATH_PI_2) {
275  cpl_msg_error(__func__, "Airmass computation unsuccessful at timeStep. "
276  "Object is below the horizon at %s exposure (z=%f).",
277  i == 1 ? "mid" : "end", acos(cosz) * CPL_MATH_DEG_RAD);
278  cpl_error_set(__func__, CPL_ERROR_ILLEGAL_OUTPUT);
279  return -1.;
280  }
281 
282 #if MUSE_ASTRO_AIRMASS_APPROX == 0
283  airmass += weights[i] * muse_astro_get_airmass_youngirvine(1. / cosz);
284 #endif
285 #if MUSE_ASTRO_AIRMASS_APPROX == 1
286  airmass += weights[i] * muse_astro_get_airmass_young(cosz);
287 #endif
288 #if MUSE_ASTRO_AIRMASS_APPROX == 2
289  airmass += weights[i] * muse_astro_get_airmass_hardie(1. / cosz);
290 #endif
291  } /* for i (weights) */
292  } /* if aExptime */
293 
294 #if MUSE_ASTRO_AIRMASS_APPROX == 0
295  /* Accuracy limit for airmass approximation of Young & Irvine */
296  const double airmasslimit = 4.;
297  if (airmass > airmasslimit) {
298  cpl_msg_warning(__func__, "Airmass larger than %f", airmasslimit);
299  }
300 #endif
301 
302  return airmass;
303 } /* muse_astro_compute_airmass() */
304 
305 /*----------------------------------------------------------------------------*/
320 /*----------------------------------------------------------------------------*/
321 double
322 muse_astro_airmass(cpl_propertylist *aHeader)
323 {
324  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, -1.);
325 
326  cpl_errorstate prestate = cpl_errorstate_get();
327  double airm1 = muse_pfits_get_airmass_start(aHeader),
328  airm2 = muse_pfits_get_airmass_end(aHeader);
329  cpl_errorstate_set(prestate); /* we don't really care if these are missing */
330 
331  /* Try to read all necessary FITS headers. Reset them to something invalid *
332  * if they cannot be read, so that we get a real error message from *
333  * muse_astro_compute_airmass(), just for GEOLAT we don't care, because *
334  * muse_pfits knows a good default. */
335  prestate = cpl_errorstate_get();
336  double ra = muse_pfits_get_ra(aHeader);
337  if (!cpl_errorstate_is_equal(prestate)) {
338  ra = -1000.;
339  }
340  prestate = cpl_errorstate_get();
341  double dec = muse_pfits_get_dec(aHeader);
342  if (!cpl_errorstate_is_equal(prestate)) {
343  dec = -1000.;
344  }
345  prestate = cpl_errorstate_get();
346  double lst = muse_pfits_get_lst(aHeader);
347  if (!cpl_errorstate_is_equal(prestate)) {
348  lst = -1000.;
349  }
350  prestate = cpl_errorstate_get();
351  double exptime = muse_pfits_get_exptime(aHeader);
352  if (!cpl_errorstate_is_equal(prestate)) {
353  exptime = -1.;
354  }
355 
356  double airmass = muse_astro_compute_airmass(ra, dec, lst, exptime,
357  muse_pfits_get_geolat(aHeader));
358 
359  /* airmass computation failed, use simple average */
360  if (airmass < 0. && airm1 != 0. && airm2 != 0.) {
361  /* comparison doesn't make sense in this case, warn and return immediately */
362  double average = (airm1 + airm2) / 2.;
363  cpl_msg_warning(__func__, "airmass computation unsuccessful (%s), using simple "
364  "average of start and end values (%f)", cpl_error_get_message(),
365  average);
366  return average;
367  }
368 
369  /* compare computed and header values */
370  cpl_msg_debug(__func__, "airmass=%f (header %f, %f)", airmass, airm1, airm2);
371  if (airm1 != 0. && airm2 != 0.) {
372  cpl_boolean check = airmass > fmin(airm1, airm2) - 0.005
373  && airmass < fmax(airm1, airm2) + 0.005;
374  if (!check) {
375  cpl_msg_warning(__func__, "Computed airmass %.3f is NOT in the range "
376  "recorded in the FITS header (%f, %f)", airmass,
377  airm1, airm2);
378  }
379  }
380 
381  return airmass;
382 } /* muse_astro_airmass() */
383 
384 /*----------------------------------------------------------------------------*/
402 /*----------------------------------------------------------------------------*/
403 double
404 muse_astro_posangle(const cpl_propertylist *aHeader)
405 {
406  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, 0.);
407  double posang = muse_pfits_get_drot_posang(aHeader);
408  const char *mode = muse_pfits_get_drot_mode(aHeader);
409  if (mode && !strncmp(mode, "SKY", 4)) {
410  posang *= -1.;
411  } else if (mode && strncmp(mode, "STAT", 5)) { /* not STAT, which we ignore */
412  cpl_msg_warning(__func__, "Derotator mode is neither SKY nor STAT! "
413  "Effective position angle may be wrong!");
414  } else if (!mode) { /* MODE missing */
415  cpl_msg_warning(__func__, "Derotator mode is not given! Effective position "
416  "angle may be wrong!");
417  }
418  return posang;
419 } /* muse_astro_posangle() */
420 
421 /*----------------------------------------------------------------------------*/
438 /*----------------------------------------------------------------------------*/
439 double
440 muse_astro_parangle(const cpl_propertylist *aHeader)
441 {
442  cpl_ensure(aHeader, CPL_ERROR_NULL_INPUT, 0.);
443  cpl_errorstate es = cpl_errorstate_get();
444  double p1 = muse_pfits_get_parang_start(aHeader),
445  p2 = muse_pfits_get_parang_end(aHeader);
446  if (!cpl_errorstate_is_equal(es)) {
447  cpl_msg_warning(__func__, "One or both TEL.PARANG keywords are missing!");
448  }
449 
450  double parang = (p1 + p2) / 2.;
451  if (fabs(p1 - p2) < 90.) {
452  /* are not going through the meridian with a 360 deg flip in *
453  * PARANG, so it should be safe to just average the two values */
454  return parang;
455  }
456  /* Flip in PARANG while observing this object, special (pretty *
457  * convoluted) handling needed: Both absolute values should be close to *
458  * 180., so compute the absolute distance from 180, the average of the *
459  * distance to 180 with the respective sign, and subtract the average *
460  * of those 180. The final sign is taken from the value whose distance *
461  * to the from respectively signed 180 is larger. */
462  double d1 = copysign(180. - fabs(p1), p1),
463  d2 = copysign(180. - fabs(p2), p2);
464  parang = 180. - fabs((d1 + d2) / 2.);
465  if (fabs(d1) > fabs(d2)) {
466  parang = copysign(parang, p1);
467  } else {
468  parang = copysign(parang, p2);
469  } /* else */
470  return parang;
471 } /* muse_astro_parangle() */
472 
473 /*----------------------------------------------------------------------------*/
485 /*----------------------------------------------------------------------------*/
486 double
487 muse_astro_angular_distance(double aRA1, double aDEC1, double aRA2, double aDEC2)
488 {
489  /* first convert all inputs to radians */
490  double ra1 = aRA1 * CPL_MATH_RAD_DEG,
491  dec1 = aDEC1 * CPL_MATH_RAD_DEG,
492  ra2 = aRA2 * CPL_MATH_RAD_DEG,
493  dec2 = aDEC2 * CPL_MATH_RAD_DEG;
494  /* compute formula components */
495  double dra = ra2 - ra1,
496  cosdra = cos(dra),
497  sindec1 = sin(dec1),
498  cosdec1 = cos(dec1),
499  sindec2 = sin(dec2),
500  cosdec2 = cos(dec2);
501  /* compute the two terms in the nominator */
502  double t1 = cosdec2 * sin(dra),
503  t2 = cosdec1 * sindec2 - sindec1 * cosdec2 * cosdra,
504  dsigma = atan2(sqrt(t1*t1 + t2*t2),
505  sindec1 * sindec2 + cosdec1 * cosdec2 * cosdra);
506  return dsigma * CPL_MATH_DEG_RAD;
507 } /* muse_astro_angular_distance() */
508 
double muse_pfits_get_ra(const cpl_propertylist *aHeaders)
find out the right ascension
Definition: muse_pfits.c:206
double muse_pfits_get_airmass_start(const cpl_propertylist *aHeaders)
find out the airmass at start of exposure
Definition: muse_pfits.c:732
double muse_astro_parangle(const cpl_propertylist *aHeader)
Properly average parallactic angle values of one exposure.
Definition: muse_astro.c:440
double muse_pfits_get_drot_posang(const cpl_propertylist *aHeaders)
find out the MUSE derotator position angle (in degrees)
Definition: muse_pfits.c:948
double muse_pfits_get_geolat(const cpl_propertylist *aHeaders)
find out the telescope's latitude
Definition: muse_pfits.c:666
const char * muse_pfits_get_drot_mode(const cpl_propertylist *aHeaders)
find out the MUSE derotator mode
Definition: muse_pfits.c:930
double muse_astro_compute_airmass(double aRA, double aDEC, double aLST, double aExptime, double aLatitude)
Compute the effective airmass of an observation.
Definition: muse_astro.c:198
double muse_pfits_get_dec(const cpl_propertylist *aHeaders)
find out the declination
Definition: muse_pfits.c:224
double muse_pfits_get_parang_start(const cpl_propertylist *aHeaders)
find out the parallactic angle at start of exposure (in degrees)
Definition: muse_pfits.c:894
double muse_astro_posangle(const cpl_propertylist *aHeader)
Derive the position angle of an observation from information in a FITS header.
Definition: muse_astro.c:404
double muse_pfits_get_airmass_end(const cpl_propertylist *aHeaders)
find out the airmass at end of exposure
Definition: muse_pfits.c:750
double muse_pfits_get_exptime(const cpl_propertylist *aHeaders)
find out the exposure time
Definition: muse_pfits.c:278
double muse_pfits_get_lst(const cpl_propertylist *aHeaders)
find out the local siderial time
Definition: muse_pfits.c:242
double muse_astro_angular_distance(double aRA1, double aDEC1, double aRA2, double aDEC2)
Compute angular distance in the sky between two positions.
Definition: muse_astro.c:487
double muse_pfits_get_parang_end(const cpl_propertylist *aHeaders)
find out the parallactic angle at end of exposure (in degrees)
Definition: muse_pfits.c:912
double muse_astro_airmass(cpl_propertylist *aHeader)
Derive the effective airmass of an observation from information in a FITS header. ...
Definition: muse_astro.c:322