LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
refraction.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2018 LSST/AURA.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 from astropy import units
24 from astropy.units import cds
25 import numpy as np
26 
27 import lsst.geom
28 from lsst.afw.coord.weather import Weather
29 
30 __all__ = ["refraction", "differentialRefraction"]
31 
32 # The differential refractive index is the difference of the refractive index from 1.,
33 # multiplied by 1E8 to simplify the notation and equations.
34 deltaRefractScale = 1.0E8
35 
36 
37 def refraction(wavelength, elevation, observatory, weather=None):
38  """Calculate overall refraction under atmospheric and observing conditions.
39 
40  Parameters
41  ----------
42  wavelength : `float`
43  wavelength is in nm (valid for 230.2 < wavelength < 2058.6)
44  elevation : `lsst.geom.Angle`
45  Elevation of the observation, as an Angle.
46  observatory : `lsst.afw.coord.Observatory`
47  Class containing the longitude, latitude,
48  and altitude of the observatory.
49  weather : `lsst.afw.coord.Weather`, optional
50  Class containing the measured temperature, pressure, and humidity
51  at the observatory during an observation
52  If omitted, typical conditions for the observatory's elevation will be calculated.
53 
54  Returns
55  -------
56  refraction : `lsst.geom.Angle`
57  The angular refraction for light of the given wavelength,
58  under the given observing conditions.
59 
60  Notes
61  -----
62  The calculation is taken from [1]_.
63 
64  References
65  ----------
66  .. [1] R. C. Stone, "An Accurate Method for Computing Atmospheric
67  Refraction," Publications of the Astronomical Society of the Pacific,
68  vol. 108, p. 1051, 1996.
69  """
70  if wavelength < 230.2:
71  raise ValueError("Refraction calculation is valid for wavelengths between 230.2 and 2058.6 nm.")
72  if wavelength > 2058.6:
73  raise ValueError("Refraction calculation is valid for wavelengths between 230.2 and 2058.6 nm.")
74  latitude = observatory.getLatitude()
75  altitude = observatory.getElevation()
76  if weather is None:
77  weather = defaultWeather(altitude*units.meter)
78  reducedN = deltaN(wavelength, weather)/deltaRefractScale
79  temperature = extractTemperature(weather, useKelvin=True)
80  atmosScaleheightRatio = 4.5908E-6*temperature.to_value(units.Kelvin)
81 
82  # Account for oblate Earth
83  # This replicates equation 10 of Stone 1996
84  relativeGravity = (1. + 0.005302*np.sin(latitude.asRadians())**2. -
85  0.00000583*np.sin(2.*latitude.asRadians())**2. - 0.000000315*altitude)
86 
87  # Calculate the tangent of the zenith angle.
88  tanZ = np.tan(np.pi/2. - elevation.asRadians())
89  atmosTerm1 = reducedN*relativeGravity*(1. - atmosScaleheightRatio)
90  atmosTerm2 = reducedN*relativeGravity*(atmosScaleheightRatio - reducedN/2.)
91  result = float(atmosTerm1*tanZ + atmosTerm2*tanZ**3.)*lsst.geom.radians
92  return result
93 
94 
95 def differentialRefraction(wavelength, wavelengthRef, elevation, observatory, weather=None):
96  """Calculate the differential refraction between two wavelengths.
97 
98  Parameters
99  ----------
100  wavelength : `float`
101  wavelength is in nm (valid for 230.2 < wavelength < 2058.6)
102  wavelengthRef : `float`
103  Reference wavelength, typically the effective wavelength of a filter.
104  elevation : `lsst.geom.Angle`
105  Elevation of the observation, as an Angle.
106  observatory : `lsst.afw.coord.Observatory`
107  Class containing the longitude, latitude,
108  and altitude of the observatory.
109  weather : `lsst.afw.coord.Weather`, optional
110  Class containing the measured temperature, pressure, and humidity
111  at the observatory during an observation
112  If omitted, typical conditions for the observatory's elevation will be calculated.
113 
114  Returns
115  -------
116  differentialRefraction : `lsst.geom.Angle`
117  The refraction at `wavelength` minus the refraction at `wavelengthRef`.
118  """
119  refractionStart = refraction(wavelength, elevation, observatory, weather=weather)
120  refractionEnd = refraction(wavelengthRef, elevation, observatory, weather=weather)
121  return refractionStart - refractionEnd
122 
123 
124 def deltaN(wavelength, weather):
125  """Calculate the differential refractive index of air.
126 
127  Parameters
128  ----------
129  wavelength : `float`
130  wavelength is in nanometers
131  weather : `lsst.afw.coord.Weather`
132  Class containing the measured temperature, pressure, and humidity
133  at the observatory during an observation
134 
135  Returns
136  -------
137  deltaN : `float`
138  The difference of the refractive index of air from 1.,
139  calculated as (n_air - 1)*10^8
140 
141  Notes
142  -----
143  The differential refractive index is the difference of
144  the refractive index from 1., multiplied by 1E8 to simplify
145  the notation and equations. Calculated as (n_air - 1)*10^8
146 
147  This replicates equation 14 of [1]_
148 
149  References
150  ----------
151  .. [1] R. C. Stone, "An Accurate Method for Computing Atmospheric
152  Refraction," Publications of the Astronomical Society of the Pacific,
153  vol. 108, p. 1051, 1996.
154  """
155  waveNum = 1E3/wavelength # want wave number in units 1/micron
156  dryAirTerm = 2371.34 + (683939.7/(130. - waveNum**2.)) + (4547.3/(38.9 - waveNum**2.))
157  wetAirTerm = 6487.31 + 58.058*waveNum**2. - 0.71150*waveNum**4. + 0.08851*waveNum**6.
158  return (dryAirTerm*densityFactorDry(weather) +
159  wetAirTerm*densityFactorWater(weather))
160 
161 
162 def densityFactorDry(weather):
163  """Calculate dry air pressure term to refractive index calculation.
164 
165  Parameters
166  ----------
167  weather : `lsst.afw.coord.Weather`
168  Class containing the measured temperature, pressure, and humidity
169  at the observatory during an observation
170 
171  Returns
172  -------
173  densityFactor : `float`
174  Returns the relative density of dry air
175  at the given pressure and temperature.
176 
177  Notes
178  -----
179  This replicates equation 15 of [1]_
180 
181  References
182  ----------
183  .. [1] R. C. Stone, "An Accurate Method for Computing Atmospheric
184  Refraction," Publications of the Astronomical Society of the Pacific,
185  vol. 108, p. 1051, 1996.
186  """
187  temperature = extractTemperature(weather, useKelvin=True)
188  waterVaporPressure = humidityToPressure(weather)
189  airPressure = weather.getAirPressure()*units.pascal
190  dryPressure = airPressure - waterVaporPressure
191  eqn = dryPressure.to_value(cds.mbar)*(57.90E-8 - 9.3250E-4/temperature.to_value(units.Kelvin) +
192  0.25844/temperature.to_value(units.Kelvin)**2.)
193  densityFactor = (1. + eqn)*dryPressure.to_value(cds.mbar)/temperature.to_value(units.Kelvin)
194  return densityFactor
195 
196 
197 def densityFactorWater(weather):
198  """Calculate water vapor pressure term to refractive index calculation.
199 
200  Parameters
201  ----------
202  weather : `lsst.afw.coord.Weather`
203  Class containing the measured temperature, pressure, and humidity
204  at the observatory during an observation
205 
206  Returns
207  -------
208  densityFactor : `float`
209  Returns the relative density of water vapor
210  at the given pressure and temperature.
211 
212  Notes
213  -----
214  This replicates equation 16 of [1]_
215 
216  References
217  ----------
218  .. [1] R. C. Stone, "An Accurate Method for Computing Atmospheric
219  Refraction," Publications of the Astronomical Society of the Pacific,
220  vol. 108, p. 1051, 1996.
221  """
222  temperature = extractTemperature(weather, useKelvin=True)
223  waterVaporPressure = humidityToPressure(weather)
224  densityEqn1 = (-2.37321E-3 + 2.23366/temperature.to_value(units.Kelvin) -
225  710.792/temperature.to_value(units.Kelvin)**2. +
226  7.75141E-4/temperature.to_value(units.Kelvin)**3.)
227  densityEqn2 = waterVaporPressure.to_value(cds.mbar)*(1. + 3.7E-4*waterVaporPressure.to_value(cds.mbar))
228  relativeDensity = waterVaporPressure.to_value(cds.mbar)/temperature.to_value(units.Kelvin)
229  densityFactor = (1 + densityEqn2*densityEqn1)*relativeDensity
230 
231  return densityFactor
232 
233 
234 def humidityToPressure(weather):
235  """Convert humidity and temperature to water vapor pressure.
236 
237  Parameters
238  ----------
239  weather : `lsst.afw.coord.Weather`
240  Class containing the measured temperature, pressure, and humidity
241  at the observatory during an observation
242 
243  Returns
244  -------
245  pressure : `astropy.units.Quantity`
246  The water vapor pressure in Pascals
247  calculated from the given humidity and temperature.
248 
249  Notes
250  -----
251  This replicates equations 18 & 20 of [1]_
252 
253  References
254  ----------
255  .. [1] R. C. Stone, "An Accurate Method for Computing Atmospheric
256  Refraction," Publications of the Astronomical Society of the Pacific,
257  vol. 108, p. 1051, 1996.
258  """
259  humidity = weather.getHumidity()
260  x = np.log(humidity/100.0)
261  temperature = extractTemperature(weather)
262  temperatureEqn1 = (temperature + 238.3*units.Celsius)*x + 17.2694*temperature
263  temperatureEqn2 = (temperature + 238.3*units.Celsius)*(17.2694 - x) - 17.2694*temperature
264  dewPoint = 238.3*temperatureEqn1/temperatureEqn2
265  waterVaporPressure = (4.50874 + 0.341724*dewPoint + 0.0106778*dewPoint**2 + 0.184889E-3*dewPoint**3 +
266  0.238294E-5*dewPoint**4 + 0.203447E-7*dewPoint**5)*133.32239*units.pascal
267 
268  return waterVaporPressure
269 
270 
271 def extractTemperature(weather, useKelvin=False):
272  """Thin wrapper to return the measured temperature from an observation.
273 
274  Parameters
275  ----------
276  weather : `lsst.afw.coord.Weather`
277  Class containing the measured temperature, pressure, and humidity
278  at the observatory during an observation
279  useKelvin : bool, optional
280  Set to True to return the temperature in Kelvin instead of Celsius
281  This is needed because Astropy can't easily convert
282  between Kelvin and Celsius.
283 
284  Returns
285  -------
286  temperature : `astropy.units.Quantity`
287  The temperature in Celsius, unless `useKelvin` is set.
288  """
289  temperature = weather.getAirTemperature()*units.Celsius
290  if useKelvin:
291  temperature = temperature.to(units.Kelvin, equivalencies=units.temperature())
292  return temperature
293 
294 
295 def defaultWeather(altitude):
296  """Set default local weather conditions if they are missing.
297 
298  Parameters
299  ----------
300  weather : `lsst.afw.coord.Weather`
301  Class containing the measured temperature, pressure, and humidity
302  at the observatory during an observation
303  altitude : `astropy.units.Quantity`
304  The altitude of the observatory, in meters.
305 
306  Returns
307  -------
308  default : `lsst.afw.coord.Weather`
309  Updated Weather class with any `nan` values replaced by defaults.
310  """
311  if isinstance(altitude, units.quantity.Quantity):
312  altitude2 = altitude
313  else:
314  altitude2 = altitude*units.meter
315  p0 = 101325.*units.pascal # sea level air pressure
316  g = 9.80665*units.meter/units.second**2 # typical gravitational acceleration at sea level
317  R0 = 8.31447*units.Joule/(units.mol*units.Kelvin) # gas constant
318  T0 = 19.*units.Celsius # Typical sea-level temperature
319  lapseRate = -6.5*units.Celsius/units.km # Typical rate of change of temperature with altitude
320  M = 0.0289644*units.kg/units.mol # molar mass of dry air
321 
322  temperature = T0 + lapseRate*altitude2
323  temperatureK = temperature.to(units.Kelvin, equivalencies=units.temperature())
324  pressure = p0*np.exp(-(g*M*altitude2)/(R0*temperatureK))
325  humidity = 40. # Typical humidity at many observatory sites.
326  weather = Weather((temperature/units.Celsius).value, (pressure/units.pascal).value, humidity)
327  return weather
def densityFactorWater(weather)
Definition: refraction.py:197
def humidityToPressure(weather)
Definition: refraction.py:234
def defaultWeather(altitude)
Definition: refraction.py:295
def refraction(wavelength, elevation, observatory, weather=None)
Definition: refraction.py:37
def densityFactorDry(weather)
Definition: refraction.py:162
def differentialRefraction(wavelength, wavelengthRef, elevation, observatory, weather=None)
Definition: refraction.py:95
def deltaN(wavelength, weather)
Definition: refraction.py:124
def extractTemperature(weather, useKelvin=False)
Definition: refraction.py:271