24 Commits
Author SHA1 Message Date
yosata f4e77ffaf7 Ready for v1.4 release. 2021-03-10 11:52:14 +01:00
yosata 7771c59268 Added TPH dependency to Edlen and BonschPotulski functions. 2021-03-10 11:50:00 +01:00
yosata 160818cdf7 Merge of local branch with master branch - readying for 1.3.1 release. 2020-11-15 18:05:37 +01:00
yosata a3521a2426 Small bugfixes in refractionModels.py 2020-11-15 18:04:39 +01:00
Joost van den Born f4b37bd9bf Update README.md 2020-09-08 10:38:24 +02:00
Joost van den Born b804b15405 Update README.md 2020-09-08 10:33:28 +02:00
yosata 7a084d1635 Upated setup.py for v1.3 release 2020-09-07 21:29:56 +02:00
Joost van den Born f1aa2ee903 Update README.md 2020-09-07 21:24:43 +02:00
yosata 8f9a0bbbea Added Filippenko model, updated B&S refractivity 2020-09-07 21:20:09 +02:00
yosata cce60766fc included data files into installation 2020-08-07 13:32:24 +02:00
yosata 2b1835c814 fixed minor bug 2020-08-05 15:59:25 +02:00
yosata 1abb7af4e9 updated README 2020-08-05 11:39:48 +02:00
yosata c1f94e4d95 ported slalib.sla_refro, some refactoring 2020-08-05 11:37:49 +02:00
yosata 83d0c9ea45 Fixed typo in setup.py 2020-08-03 11:14:10 +02:00
yosata 502183889b Refactored some variable names and updated descriptions 2020-08-03 11:07:42 +02:00
yosata e9e4b4ccd6 Fixed merging conflict 2020-07-29 15:26:42 +02:00
yosata b024ae5d3a Added Mathars exponential refraction model 2020-07-29 15:21:14 +02:00
Joost van den Born a21288554b Update README.md with publication reference 2020-07-01 11:35:28 +02:00
yosata e5e20e3584 Updated setup.py for v1.0 release. 2020-06-24 17:05:04 +02:00
Joost van den Born 9b24c2248b Merge branch 'born-master-patch-60157' into 'master'
Update README.md

See merge request micado/atmosphericmodel!2
2020-06-24 16:52:55 +02:00
Joost van den Born 43bc8e5788 Update README.md 2020-06-24 16:52:44 +02:00
Joost van den Born 9df4333d51 Merge branch 'born-master-patch-02871' into 'master'
Update README.md

See merge request micado/atmosphericmodel!1
2020-06-24 16:50:33 +02:00
Joost van den Born e76ff46d5a Update README.md 2020-06-24 16:50:08 +02:00
yosata 8d50899db6 Updated documentation, descriptions for v1.0 release. 2020-06-24 16:47:25 +02:00
36 changed files with 19717 additions and 541 deletions

No files matched your search

+3 -2
View File
@@ -5,6 +5,7 @@ from .ciddorModel import *
from .dispersionModels import *
from .refractionModels import *
from .refractionModels import *
from .examples import *
from .misc import *
from .refractivityModels import *
from .neoslalib import *
from .observatories import *
+207 -18
View File
@@ -16,6 +16,53 @@ from scipy import integrate
'''
class Observatory:
'''
Class used for all function to calculate the refractive index
and the corresponding uncertainties.
...
Attributes
----------
dT : float (optional)
Uncertainty in the atmospheric temperature in Kelvin
dP : float (optional)
Uncertainty in the atmospheric pressure in Pascal
dH : float (optional)
Uncertainty in the atmospheric relative humidity
dCO2: float (optional)
Uncertianty in the atmospheric CO2 density in parts
per million.
dl : float (optional)
Uncertainty in the wavelengths in micron
Methods
-------
n_as(l)
Returns the refractive index for standard atmospheric conditions.
n_axs(l, xc)
Same as n_as(l), but with a different CO2 density given by xc
n_ws(l)
Returns the refractive index of air for standard moist contions.
f(p,T)
Returns the enchancement factor of water vapour in air.
svp(T)
Returns the saturation vapour pressure in Pa.
svp2(T)
Returns the saturation vapour pressure in Pa.
From IAPWS formula, based on documentation by NIST toolbox.
xw(p, T, RH)
Returns the molar fraction of water vapour in moist air.
Z(p, T, RH, xw='')
Returns the compressibility of moist air
rho(p, T, RH, xc, xw='')
Returns the density of atmospheric air at the given parameters.
n_tph(l, T, p, RH, xc)
Returns the refractive index for any atmospheric conditions.
dn_tph(l, T, p, RH, xc, dl=None, dT=None, dP=None, dRH=None, dCO2=None)
Returns the uncertainty in the refractive index for any atmospheric
conditions, given the parameters.
'''
def __init__(self, dT=0.2, dP=20, dH=0.02, dCO2=20, dl=0.001):
self.dT = dT # in K
self.dP = dP # in Pa
@@ -28,6 +75,17 @@ class Observatory:
Formula that calculate the refractive index for standard atmospheric
conditions, i.e. 15 C, 101,325 Pa, 0% RH, 450 ppm CO2.
It returns n_as, not (n_as - 1) * 1e8!
Parameters
----------
l : float
Wavelength of light in microns
Returns
-------
n : float
Refractive index of air at standard atmospheric conditions.
'''
_s = 1 / l
_k0 = 238.0185 # um^-2
@@ -42,6 +100,18 @@ class Observatory:
Formula that calculate the refractive index for standard atmospheric
conditions, i.e. 15 C, 101,325 Pa, 0% RH, but with xc ppm of CO2.
It returns n_axs, not (n_axs - 1)
Parameters
----------
l : float
Wavelength of light in microns
xc : float
CO2 density in parts per million
Returns
-------
n : float
Refractive index of air
'''
_n_as = self.n_as(l)
return ( (_n_as - 1) * (1 + 0.534e-6 * (xc - 450) ) ) + 1
@@ -52,6 +122,16 @@ class Observatory:
Formula that calculates the refractivity of air for standard moist conditions
as defined by B&S (1939), i.e. 20 C, 1333 Pa
Returns n_ws, not (n_ws - 1) * 1e8
Parameters
----------
l : float
Wavelength of light in microns
Returns
-------
n : float
Refractive index of standard moist air.
'''
_s = 1 / l
_cf = 1.022
@@ -65,8 +145,18 @@ class Observatory:
def f(self, p, T):
'''
Enhancement factor of water vapour in air
t in degrees celsius
p in Pascal (Pa)
Parameters
----------
p : float
Atmospheric pressure in Pascal
T : float
Atmospheric temperature in Kelvin
Returns
-------
f : float
Enhancement factor of water vapour in air
'''
_t = T - 273.15
_a = 1.00062
@@ -78,6 +168,16 @@ class Observatory:
def svp(self, T):
'''
Saturation vapour pressure in Pa.
Parameters
----------
T : float
Atmospheric temperature in Kelvin
Returns
-------
svp : float
Saturation vapour pressure
'''
_A = 1.2378847e-5 # K^-2
_B = -1.9121316e-2 # K^-2
@@ -88,7 +188,18 @@ class Observatory:
def svp2(self, T):
'''
Calculation of svp from IAPWS formula, based on documentation by NIST toolbox.
Calculation of svp from IAPWS formula, based on documentation
by NIST toolbox.
Parameters
----------
T : float
Atmospheric temperature in Kelvin
Returns
-------
svp : float
Saturation vapour pressure
'''
K1 = 1.16705214528e+03
K2 = -7.24213167032e+05
@@ -111,6 +222,20 @@ class Observatory:
def xw(self, p, T, RH):
'''
Molar fraction of water vapour in moist air.
Parameters
----------
p : float
Atmospheric pressure in Pascal
T : float
Atmospheric temperature in Kelvin
RH : float
Atmospheric relative humidity (0<=RH<=1)
Returns
-------
xw : float
Molar fraction of water vapour in moist air
'''
_f = self.f(p, T)
_svp = self.svp(T)
@@ -120,6 +245,20 @@ class Observatory:
def Z(self, p, T, RH, xw=''):
'''
Compressibility of moist air
Parameters
----------
p : float
Atmospheric pressure in Pascal
T : float
Atmospheric temperature in Kelvin
RH : float
Atmospheric relative humidity (0<=RH<=1)
Returns
-------
Z : float
Compressibility of moist air
'''
_a0 = 1.58123e-6 # K Pa^-1
_a1 = -2.9331e-8 # Pa^-1
@@ -142,10 +281,25 @@ class Observatory:
def rho(self, p, T, RH, xc, xw=''):
'''
Calculates the density of air for the given parameters.
T in Kelvin
p in Pascal
RH between 0 and 1
xc in ppm
Parameters
----------
p : float
Atmospheric pressure in Pascal
T : float
Atmospheric temperature in Kelvin
RH : float
Atmospheric relative humidity (0<=RH<=1)
xc : float
CO2 density in parts per million
xw : str or float
Placeholder for molar fraction of water vapour in moist air.
Used to pass the function along where it should go.
Returns
-------
rho : float
Density of air
'''
_Ma = 1e-3 * (28.9635 + 12.011e-6 * (xc - 400) ) # Molar mass in kg/mol of dry air containing xc ppm of CO2
_Mw = 0.018015 # kg/mol
@@ -161,6 +315,25 @@ class Observatory:
def n_tph(self, l, T, p, RH, xc):
'''
Calculates the refraction of the atmosphere for any conditions.
Follows the equations by Ciddor (1996).
Parameters
----------
l : float
Wavelength of light in microns
p : float
Atmospheric pressure in Pascal
T : float
Atmospheric temperature in Kelvin
RH : float
Atmospheric relative humidity (0<=RH<=1)
xc : float
CO2 density in parts per million
Returns
-------
n : float
Refractive index of air
'''
_n_a = self.n_axs(l, xc)
_n_w = self.n_ws(l)
@@ -194,17 +367,33 @@ class Observatory:
This involves almost endless use of the chain rule and evaluation of both the moist
air and dry air components. Note: dry air does not contain any water, i.e. H=0, x_w=0, etc.
Parameters:
l - Wavelength in micron (0.4 - 2.5 micron)
T - Temperature in Kelvin
p - Pressure in Pascals
RH - Relative humidity (no units, don't use percentage but value 0<x<1)
xc - CO2 density in ppm
dl - Uncertainty in the blue wavelength
dT - Uncertainty in the temperature, default: 0.2 K
dP - Uncertainty in the pressure, default: 20 Pa
dRH - Uncertainty in the relative humidity, default: 0.02
dCO2- Uncertainty in the CO2 density, default: 20
Parameters
----------
l : float
Wavelength of light in microns
p : float
Atmospheric pressure in Pascal
T : float
Atmospheric temperature in Kelvin
RH : float
Atmospheric relative humidity (0<=RH<=1)
xc : float
CO2 density in parts per million
dl : float
Uncertainty in the wavelength in microns
dT : float (default: 0.2)
Uncertainty in the atmospheric temperature in Kelvin
dP : float (default: 20)
Uncertainty in the atmospheric pressure in Pascal
dRH : float (default: 0.02)
Uncertainty in the atmospheric relative humidity
dCO2: float (default: 20)
Uncertainty in the atmospheric CO2 density in parts per million
Returns
-------
dn : float
Uncertainty in the refractive index of air
'''
if dP == None:
+590 -199
View File
@@ -3,6 +3,7 @@ import numpy as np
from scipy import integrate
import os
from .ciddorModel import Observatory
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@@ -13,33 +14,100 @@ from .ciddorModel import Observatory
The actual refraction is calculated by any of the following models:
- Plane parallel atmosphere
- Cassini spherical homogeneous atmosphere
- Mathar's Barometric exponential model
- Other methods described in Corbard et al., 2019.
'''
class dispersion(Observatory):
'''
Class that sets the observer location.
These location attributes are then used to calculate the
appropriate atmospheric dispersion.
See also:
Observatory()
refraction()
...
Attributes
-----------
lat : float
Latitude of the observer in degrees
h : float
Altitude above mean sea level of the observer in meters
Other Observatory() attributes
Methods
-------
cassini(n1, n2, zenith, reduced_height=None)
Returns the atmospheric dispersion assuming Cassini's homegeneous
atmosphere model.
cassiniError(z, l1, l2, T, p, RH, xc, dl1=0, dl2=0, dT=0.2, dP=20,
dRH=0.02, dCO2=20, dz=0, lat=None, h=None)
Returns the uncertainty in the atmospheric dispersion, assuming
Cassini's homegeneous atmosphere model and the given parameters.
corbard(n1, n2, zenith, reduced_height=None)
Returns the atmospheric dispersion following the method described
in Corbard et al., 2019.
gammaCoefficients(n1, reduced_height=None)
Helper function for matharExponential(), that calculates the gamma
coefficients.
H_isotherm(T)
Sets the reduced height for the conditions at the observer, assuming
an isothermal atmosphere.
matharExponential(n1, n2, zenith, reduced_height=None)
Returns the atmospheric dispersion assuming Mathar's barometric
exponential model.
oriani(n1, n2, zenith, reduced_height=None)
Returns the atmospheric dispersion following the classic version
of Oriani's theorem.
planeParallel(n1, n2, zenith)
Returns the atmospheric dispersion assuming a plane parallel
atmosphere.
psi(x)
Submethod for corbard().
refractionIntegral(n1, n2, zenith, R0=None, useStandardAtmosphere=True, heightData=None, rhoData=None)
Returns the atmospheric dispersion following the refraction integral.
setReducedHeight(p, rho)
Sets the reduced atmospheric height.
set_H(p, rho)
Sets the reduced height for the conditions at the observer, assuming
an adiabatic atmosphere.
set_rc()
Sets the local radius of curvature of the Earth at the observer.
tan5(n1, n2, zenith, reduced_height=None)
Returns the atmospheric dispersion following Oriani's theorem,
including a fifth order term in the expansion.
Other Observatory() methods
'''
def __init__(self, lat, h):
Observatory.__init__(self)
self.h = h # height above sea level in meters
self.lat = lat # Latitude of the observer in degrees
self.rc = self.set_rc() * 1000 # Radius of curvature of the earth at the observer
def setReducedHeight(self, P, rho):
self.H = self.set_H(P, rho) # Reduced height of the atmosphere assuming ideal gas law
def planeParallel(self, n1, n2, zenith):
'''
Refraction based on a flat atmosphere, easily derived from Snell's law.
'''
_R1 = np.arcsin(n1 * np.sin( np.deg2rad(zenith) )) - np.deg2rad(zenith)
_R2 = np.arcsin(n2 * np.sin( np.deg2rad(zenith) )) - np.deg2rad(zenith)
return np.degrees(_R1) - np.degrees(_R2)
def cassini(self, n1, n2, zenith, reduced_height=None):
'''
Refraction of spherical atmosphere, derived from geometry using Sine law
Parameters
----------
n1 : float
Refractive index at the shortest wavelength of interest
n2 : float
Refractive index at the longest wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
if reduced_height:
H = reduced_height
@@ -51,194 +119,63 @@ class dispersion(Observatory):
return np.degrees(_R1) - np.degrees(_R2)
def tan5(self, n1, n2, zenith, reduced_height=None):
def cassiniError(self, zenith, l1, l2, T, p, RH, xc, dl1=0, dl2=0, dT=0.2, dP=20,
dRH=0.02, dCO2=20, dz=0, lat=None, h=None):
'''
Same as oriani's theorem, but including a higher order term.
Found in Corbard et al., 2019.
'''
if reduced_height:
H = reduced_height
else:
H = self.H
Calculates the uncertainty in the atmospheric dispersion,
following the Cassini model, by using fully propagated error analysis.
This involves almost endless use of the chain rule and evaluation
of both the moist air and dry air components.
Note: dry air does not contain any water, i.e. H=0, x_w=0, etc.
_rList = []
for _n in [n1, n2]:
_a = _n - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3 + 3 * _a * (_b - _a/2)**2 * np.tan( np.deg2rad(zenith) )**5
_rList.append(np.degrees(_R))
return _rList[0] - _rList[1]
Not taken into account is the errror propagation in the scale height
of the atmosphere, from my results this should give a small or even
negligible difference.
Parameters
----------
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
l1 : float
Wavelength of light in microns, at the shortest wavelength
of interest
l2 : float
Wavelength of light in microns, at the longest wavelength
of interest
T : float
Atmospheric temperature in Kelvin
p : float
Atmospheric pressure in Pascal
RH : float
Atmospheric relative humidity (0<=RH<=1)
xc : float
CO2 density in parts per million
dl1 : float
Uncertainty in the wavelength in microns, at the shortest
wavelength of interest
dl2 : float
Uncertainty in the wavelength in microns, at the longest
wavelength of interest
dT : float (default: 0.2)
Uncertainty in the atmospheric temperature in Kelvin
dP : float (default: 20)
Uncertainty in the atmospheric pressure in Pascal
dRH : float (default: 0.02)
Uncertainty in the atmospheric relative humidity
dCO2: float (default: 20)
Uncertainty in the atmospheric CO2 density in parts per million
dz : float (default: 0)
Uncertainty in the zenith angle, in degrees.
lat : float (default: None)
If entered, overrides the latitude attribute of the class.
h : float (default=None)
If entered, overrides the altitude attribute of the class.
def oriani(self, n1, n2, zenith, reduced_height=None):
'''
Classic refraction formula with two tan(z) terms. This does not assume any
information about the structure of the atmosphere. Found with Oriani's theorem.
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_rList = []
for _n in [n1, n2]:
_a = _n - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3
_rList.append(np.degrees(_R))
return _rList[0] - _rList[1]
def corbard(self, n1, n2, zenith, reduced_height=None):
'''
Corbard et al., 2019, mention an additional formula based on the error function.
Oriani's theorem can be derived from this equation by 'keeping only the three first
terms of its asymptotic expansion'.
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_rList = []
for _n in [n1, n2]:
_a = _n - 1
_b = H / self.rc
_R = _a * ( (2 - _a) / (np.sqrt( 2*_b - _a )) ) * np.sin( np.deg2rad(zenith) ) * self.psi( (np.cos(np.deg2rad(zenith))) / np.sqrt(2*_b - _a) )
_rList.append(np.degrees(_R))
return _rList[0] - _rList[1]
def refractionIntegral(self, n1, n2, z0, R0=None, useStandardAtmosphere=True, heightData=None, rhoData=None):
'''
Using data that gives temperature, pressure and density as a function of height,
the gladstone-dale relation is invoked to calculate the path of a light ray
along the atmosphere. This is allows us to also include atmospheric activity in
different atmosphere layers.
Recommended use with the US Standard Atmosphere from 1976.
USSA1976 data generated from code supplied by http://www.pdas.com/atmos.html
The method described in eq. 3 in Auer & Standish (2000) is used for the calculation.
'''
if not R0:
R0 = self.rc
if useStandardAtmosphere:
_datafileLocation = os.path.join(os.path.dirname(__file__), 'data/1976USSA.txt')
_h, _, _, _, _Temp, _Press, _Dens,_,_,_ = np.genfromtxt(_datafileLocation, unpack=True, skip_header=6)
else:
_Dens = rhoData
_h = heightData
_rList = []
z0 = np.deg2rad(z0)
# Integration of the refraction integral over the atmosphere layers.
for _n in [n1, n2]:
_Dens = np.asarray(_Dens)
_h = np.asarray(_h)
_R = R0 + _h * 1000
# Gladstone-Dale relation between refractive index and density
_n_h = 1 + (_n - 1) / _Dens[0] * _Dens
# Calculation of the zenith angle for given height and refractive index, from the refractive invariant
_z = np.arcsin( (_n * R0) / (_n_h * _R) * np.sin(z0) )
# Calculation of log space derivative of n and r
_lnn = np.log(_n_h)
_lnr = np.log(_R)
_dlnn = np.asarray([_lnn[i+1] - _lnn[i] for i in range(len(_lnn)-1)])
_dlnr = np.asarray([_lnr[i+1] - _lnr[i] for i in range(len(_lnr)-1)])
_dz = np.asarray([(_z[i] - _z[i+1]) for i in range(len(_z)-1)])
# Calculation of the integrand in eq. 3 in Auer & Standish
_Integrand = -1*(_dlnn/_dlnr) / (1 + _dlnn/_dlnr) * _dz
_rList.append(np.degrees(np.sum(_Integrand)))
return _rList[0] - _rList[1]
def psi(self, x):
_f = lambda a: np.exp(-a**2)
_int = integrate.quad(_f, x, np.inf)
return np.exp(x**2) * _int[0]
def set_rc(self):
'''
Returns the radius of the curvature at the observer in km,
taking into account the latitude of the observer and the corresponding
ellipsoidality of the earth.
'''
_A = 6378.137 # km
_B = 6356.752 # km
_phi = np.deg2rad(self.lat)
_rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
return _rc0 + self.h/1000
def set_H(self, P, rho):
'''
Calculates the reduced height of the atmosphere, assuming ideal gas law.
'''
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
_g = _g0_local - (3.0877e-6 - 4.3e-9 * np.sin(_phi)**2) * self.h + 7.2e-13 * self.h**2
return P / (rho * _g)
def H_isotherm(self, T):
'''
Calculates the reduced height of the atmosphere, assuming an isotherm distribution.
'''
_kb = 1.38064852e-23 # kg m2 s-2 K-1
_m = 4.809651698e-26 # kg (weight per molecule of air, assuming dry air)
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
return (_kb*T) / (_m * _g0_local)
def cassiniError(self, z, l1, l2, T, p, RH, xc, dl1=0, dl2=0, dT=0.2, dP=20, dRH=0.02, dCO2=20, dz=0, lat=None, h=None):
'''
Calculates the uncertainty in the atmospheric dispersion, following the Cassini model,
by using fully propagated error analysis.
This involves almost endless use of the chain rule and evaluation of both the moist
air and dry air components. Note: dry air does not contain any water, i.e. H=0, x_w=0, etc.
Not taken into account is the errror propagation in the scale height of the atmosphere or
the zenith error, from my results this should give a small or even negligible difference.
Parameters:
z - Zenith angle in degrees
l1 - Bluest wavelength in micron (0.4 - 2.5 micron)
l2 - Reddest wavelength in micron (0.4 - 2.5 micron)
T - Temperature in Kelvin
p - Pressure in Pascals
RH - Relative humidity (no units, don't use percentage but value 0<x<1)
xc - CO2 density in ppm
dl1 - Uncertainty in the blue wavelength
dl2 - Uncertainty in the red wavelength
dT - Uncertainty in the temperature, default: 0.2 K
dP - Uncertainty in the pressure, default: 20 Pa
dRH - Uncertainty in the relative humidity, default: 0.02
dCO2- Uncertainty in the CO2 density, default: 20
lat - [Optional] Latitude of the observatory in degrees
h - [Optional] Height in m of the observatory
Returns
-------
dRR : float
Uncertainty in the atmospheric dispersion in degrees.
'''
if lat == None:
lat = self.lat
@@ -291,7 +228,7 @@ class dispersion(Observatory):
_rho_obs = self.rho(p, T, RH, xc)
self.rc = self.set_rc() * 1000
self.H = self.set_H(p, _rho_obs)
_alpha = self.rc * np.sin(np.deg2rad(z)) / (self.rc + self.H)
_alpha = self.rc * np.sin(np.deg2rad(zenith)) / (self.rc + self.H)
_p_a = p - RH*self.svp(T)
_p_w = RH*self.svp(T)
_x_w_a = self.xw(_p_a, T, _RH_a)
@@ -438,7 +375,7 @@ class dispersion(Observatory):
# Partial derivatives for derivation to z
_zenith = np.deg2rad(z)
_zenith = np.deg2rad(zenith)
_dz = np.deg2rad(dz)
_R = self.rc
_H = self.H
@@ -448,3 +385,457 @@ class dispersion(Observatory):
_dispersionError = np.sqrt( (_dddl1 * dl1)**2 + (_dddl2 * dl2)**2 + (_dddT * dT)**2 + (_dddP * dP)**2 + (_dddRH * dRH)**2 + (_dddCO2 * dCO2)**2 + (_dddz * _dz)**2)
return np.degrees(_dispersionError)
def corbard(self, n1, n2, zenith, reduced_height=None):
'''
Corbard et al., 2019, mention an additional formula based on
the error function. Oriani's theorem can be derived from this
equation by 'keeping only the three first terms of its
asymptotic expansion'.
Parameters
----------
n1 : float
Refractive index at the shortest wavelength of interest
n2 : float
Refractive index at the longest wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_rList = []
for _n in [n1, n2]:
_a = _n - 1
_b = H / self.rc
_R = _a * ( (2 - _a) / (np.sqrt( 2*_b - _a )) ) * np.sin( np.deg2rad(zenith) ) * self.psi( (np.cos(np.deg2rad(zenith))) / np.sqrt(2*_b - _a) )
_rList.append(np.degrees(_R))
return _rList[0] - _rList[1]
def gammaCoefficients(self, n1, reduced_height=None):
'''
Submethod for matharExponential(). Calculates the gamma
coefficients.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest at the observer
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
g : array
List of values, corresponding to the gamma coefficients.
g[0] corresponds to g_1, g[1] to g_3, g[2] to g_5, and so on.
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_chi0 = n1**2 - 1
_Khat = H / self.rc
_g1 = (1 - 5/24 * _Khat * _chi0**2 - 3/4 * _chi0 - 3/8 * _Khat**2 * _chi0 \
- _Khat - 6 * _Khat**3 + 5/8 * _chi0**2 + 63/128 * _chi0**4 + 3/8 \
* _Khat * _chi0 + 35/256 * _Khat * _chi0**3 + 2 * _Khat**2 + 5/36 \
* _Khat**2 * _chi0**2 - 35/64 * _chi0**3 + 9/16 * _Khat**3 * _chi0 \
- 120 * _Khat**5 + 24 * _Khat**4 - 231 / 512 * _chi0**5 - 63 / 640 \
* _Khat * _chi0**4 - 35 / 512 * _Khat**2 * _chi0**3 - 5 / 36 * _Khat**3\
* _chi0**2 - 9 / 8 * _Khat**4 * _chi0 )
_g3 = (35/96 * _chi0**3 - 5/12 * _chi0**2 + 333 * _Khat**4 * _chi0 - 2400 \
* _Khat**5 + 3605/4608 * _Khat**2 * _chi0**3 + 63/256 * _Khat * _chi0**4\
- 819/16 * _Khat**3 * _chi0 - 35/18 * _Khat**2 * _chi0**2 - 35/96 \
* _Khat * _chi0**3 + 69/8 * _Khat**2 * _chi0 + 5/8 * _Khat * _chi0**2 \
- 3/2 * _Khat * _chi0 + 1355/216 * _Khat**3 * _chi0**2 + 10 * _Khat**2 \
+ 336 * _Khat**4 - 2 * _Khat + _chi0/2 - 21/64 * _chi0**4 - 54 * _Khat**3 \
+ 77/256 * _chi0**5 ) * 1/2
_g5 = (-96 * _Khat**3 + 8 * _Khat**2 - 7/24 * _chi0**3 + 65/4 * _Khat**2 \
* _chi0**2 + 35/48 * _Khat * _chi0**3 + 30 * _Khat**2 * _chi0 - 5/3 \
* _Khat * _chi0**2 - 3 * _Khat * _chi0 + 20091/8 * _Khat**4 * _chi0 \
- 32605/216 * _Khat**3 * _chi0**2 - 35/9 * _Khat**2 * _chi0**3 + 984 \
* _Khat**4 + _chi0**2/3 - 10200 * _Khat**5 - 77/320 * _chi0**5 + 21/80 \
* _chi0**4 - 1089/4 * _Khat**3 * _chi0 - 7/16 * _Khat * _chi0**4 ) * 3/8
_g7 = (-17520 * _Khat**5 - 9/40 * _chi0**4 - 8755/12 * _Khat**3 * _chi0**2 \
+ 1056 * _Khat**4 + _chi0**3/4 + 23517/4 * _Khat**4 * _chi0 - 11/3 \
* _Khat * _chi0**2 + 21 * _Khat**2 * _chi0 - 7/4 * _Khat * _chi0**3 \
+ 505/9 * _Khat**2 * _chi0**2 - 801/2 * _Khat**3 * _chi0 + 33/160 \
* _chi0**5 - 48 * _Khat**3 + 63/80 * _Khat * _chi0**4 + 2345/96 \
* _Khat**2 * _chi0**3) * 5/16
_g9 = (_chi0**4 / 5 + 384 * _Khat**4 - 13440 * _Khat**5 + 5562 * _Khat**4 \
* _chi0 + 3125/36 * _Khat**2 * _chi0**3 - 11/60 * _chi0**5 - 25/6 \
* _Khat * _chi0**3 - 26950/27 * _Khat**3 * _chi0**2 + 340/9 * _Khat**2 \
* _chi0**2 - 180 * _Khat**3 * _chi0 - 9/5 * _Khat * _chi0**4) * 35/128
g = np.array([_g1, _g3, _g5, _g7, _g9])
g*= n1*_chi0/2
return g
def H_isotherm(self, T):
'''
Calculates the reduced height of the atmosphere,
assuming an isotherm distribution.
Parameters
----------
T : float
Atmospheric temperature in Kelvin
Returns
-------
H : float
Reduced height of the atmosphere in meters
'''
_kb = 1.38064852e-23 # kg m2 s-2 K-1
_m = 4.809651698e-26 # kg (weight per molecule of air, assuming dry air)
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
return (_kb*T) / (_m * _g0_local)
def matharExponential(self, n1, n2, zenith, reduced_height=None):
'''
Model on atmospheric refraction described by Richard Mathar in
"A Barometric Exponential Model of the Atmosphere’s Refractive Index: ZenithAngles and Second Order Aberration in the Entrance Pupil" (2020)
ArXiv id: 2004.11808 (https://arxiv.org/abs/2004.11808)
The code in this function is adapted from eqs 3, 16, 17 and 18 in this
work. It is an adaptation of the RefrExpo.cxx C++ file written by Mathar.
This model assumes the refractive index, n, is related to the susceptibility,
chi, following: n(r) = sqrt(1+chi(r)) (eq. 9).
R. Mathar then assumes that the susceptibility is proportional to the
air density and reduced height, following eq. 10:
chi(r) = chi0 * exp(-(r-rho)/K).
The refraction is described using a Taylor expansion; R in terms of
the odd powers of the tangent of the observed zenith angle (eq. 3), not
unlike Oriani's theorem. This requires coefficients g_i.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest at the observer
n2 : float
Refractive index at the longest wavelength of interest at the
observer
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
dR : float
Atmospheric refraction in degrees
'''
if reduced_height:
_H = reduced_height
else:
_H = self.H
_zenith = np.deg2rad(zenith)
_rc = self.rc # rho
# Atmospheric refraction for the first wavelength
_gamma1 = self.gammaCoefficients(n1, reduced_height=_H)
_R1 = sum([_gamma1[i]*np.tan(_zenith)**(2*i+1) for i in range(len(_gamma1))])
# Atmospheric refraction for the second wavelength
_gamma2 = self.gammaCoefficients(n2, reduced_height=_H)
_R2 = sum([_gamma2[i]*np.tan(_zenith)**(2*i+1) for i in range(len(_gamma2))])
# The atmospheric dispersion in radians
_dR = _R1 - _R2
return np.degrees(_dR)
def oriani(self, n1, n2, zenith, reduced_height=None):
'''
Classic refraction formula with two tan(z) terms.
This does not assume any information about the structure
of the atmosphere. Found with Oriani's theorem.
Parameters
----------
n1 : float
Refractive index at the shortest wavelength of interest
n2 : float
Refractive index at the longest wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_rList = []
for _n in [n1, n2]:
_a = _n - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3
_rList.append(np.degrees(_R))
return _rList[0] - _rList[1]
def planeParallel(self, n1, n2, zenith):
'''
Refraction based on a flat atmosphere, easily derived from Snell's law.
Parameters
----------
n1 : float
Refractive index at the shortest wavelength of interest
n2 : float
Refractive index at the longest wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
_R1 = np.arcsin(n1 * np.sin( np.deg2rad(zenith) )) - np.deg2rad(zenith)
_R2 = np.arcsin(n2 * np.sin( np.deg2rad(zenith) )) - np.deg2rad(zenith)
return np.degrees(_R1) - np.degrees(_R2)
def psi(self, x):
'''
Submethod for corbard(). Described explicitly in Corbard et al.,2019.
Parameters
----------
x : float
Input of the function
Returns
-------
y : float
Output of the function
'''
_f = lambda a: np.exp(-a**2)
_int = integrate.quad(_f, x, np.inf)
return np.exp(x**2) * _int[0]
def refractionIntegral(self, n1, n2, zenith, R0=None, useStandardAtmosphere=True,
heightData=None, rhoData=None):
'''
Using data that gives temperature, pressure and density as a
function of height, the gladstone-dale relation is invoked to
calculate the path of a light ray along the atmosphere.
This is allows us to also include atmospheric activity in
different atmosphere layers.
Recommended use with the US Standard Atmosphere from 1976.
USSA1976 data generated from code supplied by http://www.pdas.com/atmos.html
The method described in eq. 3 in Auer & Standish (2000) is used for the
calculation.
Parameters
----------
n1 : float
Refractive index at the shortest wavelength of interest
n2 : float
Refractive index at the longest wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
R0 : float (optional)
Local radius of curvature of the Earth in km.
useStandardAtmosphere : bool
Flag to use the included USSA1976 data
heightData : 1D Array
If "useStandardAtmosphere" is False, the function will ask
for alternative atmospheric data to integrate over. This
gives the altitudinal points
rhoData : 1D Array
If useStandardAtmosphere is False, the function will ask
for alternative atmospheric data to integrate over. This
gives the atmospheric density at the points defined by
"heightData".
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
if not R0:
R0 = self.rc
if useStandardAtmosphere:
_datafileLocation = os.path.join(os.path.dirname(__file__), 'data/1976USSA.txt')
_h, _, _, _, _Temp, _Press, _Dens,_,_,_ = np.genfromtxt(_datafileLocation, unpack=True, skip_header=6)
else:
_Dens = rhoData
_h = heightData
_rList = []
zenith = np.deg2rad(zenith)
# Integration of the refraction integral over the atmosphere layers.
for _n in [n1, n2]:
_Dens = np.asarray(_Dens)
_h = np.asarray(_h)
_R = R0 + _h * 1000
# Gladstone-Dale relation between refractive index and density
_n_h = 1 + (_n - 1) / _Dens[0] * _Dens
# Calculation of the zenith angle for given height and refractive index, from the refractive invariant
_z = np.arcsin( (_n * R0) / (_n_h * _R) * np.sin(zenith) )
# Calculation of log space derivative of n and r
_lnn = np.log(_n_h)
_lnr = np.log(_R)
_dlnn = np.asarray([_lnn[i+1] - _lnn[i] for i in range(len(_lnn)-1)])
_dlnr = np.asarray([_lnr[i+1] - _lnr[i] for i in range(len(_lnr)-1)])
_dz = np.asarray([(_z[i] - _z[i+1]) for i in range(len(_z)-1)])
# Calculation of the integrand in eq. 3 in Auer & Standish
_Integrand = -1*(_dlnn/_dlnr) / (1 + _dlnn/_dlnr) * _dz
_rList.append(np.degrees(np.sum(_Integrand)))
return _rList[0] - _rList[1]
def setReducedHeight(self, p, rho):
'''
Sets the reduced atmospheric height.
Parameters
----------
p : float
Atmospheric pressure in Pascal
rho : float
Density of the atmospheric air at the observer
'''
self.H = self.set_H(p, rho) # Reduced height of the atmosphere assuming ideal gas law
def set_H(self, p, rho):
'''
Calculates the reduced height of the atmosphere, assuming ideal gas law.
Parameters
----------
p : float
Atmospheric pressure in Pascal
rho : float
Density of the atmospheric air at the observer
Returns
-------
H : float
Reduced height of the atmosphere in meters
'''
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
_g = _g0_local - (3.0877e-6 - 4.3e-9 * np.sin(_phi)**2) * self.h + 7.2e-13 * self.h**2
return p / (rho * _g)
def set_rc(self):
'''
Returns the radius of the curvature at the observer in km,
taking into account the latitude of the observer and the corresponding
ellipsoidality of the earth.
Returns
-------
rc : float
Local radius of curvature of the Earth in kilometers.
'''
_A = 6378.137 # km
_B = 6356.752 # km
_phi = np.deg2rad(self.lat)
_rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
return _rc0 + self.h/1000
def tan5(self, n1, n2, zenith, reduced_height=None):
'''
Same as oriani's theorem, but including a higher order term.
Found in Corbard et al., 2019.
Parameters
----------
n1 : float
Refractive index at the shortest wavelength of interest
n2 : float
Refractive index at the longest wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_rList = []
for _n in [n1, n2]:
_a = _n - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3 + 3 * _a * (_b - _a/2)**2 * np.tan( np.deg2rad(zenith) )**5
_rList.append(np.degrees(_R))
return _rList[0] - _rList[1]
-106
View File
@@ -1,106 +0,0 @@
#!/usr/bin/env Python3
from .ciddorModel import Observatory
from .refractionModels import refraction
from .dispersionModels import dispersion
'''
Author: Joost van den Born
Some functions for quick testing.
'''
def quick_refractive_index(l, conditions='STANDARD'):
if conditions == 'STANDARD':
# Parameters at Cerro Armazones
T = 288.15 # k
P = 101325 # Pa
H = 0.0
xc = 450 # ppm
lat = 0 # degrees
h = 0 # m
elif conditions == 'CERRO_ARMAZONES':
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l
n = at.n_tph(l=l, T=T, p=P, RH=H, xc=xc)
return n
def quick_refraction(l, z, conditions='STANDARD'):
if conditions == 'STANDARD':
# Parameters at Cerro Armazones
T = 288.15 # k
P = 101325 # Pa
H = 0.0
xc = 450 # ppm
lat = 0 # degrees
h = 0 # m
elif conditions == 'CERRO_ARMAZONES':
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l
n = at.n_tph(l=l, T=T, p=P, RH=H, xc=xc)
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
# Initializing refraction model and setting the reduced height
ref = refraction(lat, h)
ref.setReducedHeight(P, rho)
return ref.cassini(n, z)
def quick_dispersion(l1, l2, z, conditions='STANDARD'):
if conditions == 'STANDARD':
# Parameters at Cerro Armazones
T = 288.15 # k
P = 101325 # Pa
H = 0.0
xc = 450 # ppm
lat = 0 # degrees
h = 0 # m
elif conditions == 'CERRO_ARMAZONES':
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l1 and l2
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
# Initializing refraction model and setting the reduced height
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
return disp.cassini(n1, n2, z)
+395
View File
@@ -0,0 +1,395 @@
#!/usr/bin/env Python3
from .ciddorModel import Observatory
from .refractionModels import refraction
from .dispersionModels import dispersion
from .neoslalib import sla_refro
from .observatories import observatories
from .refractivityModels import Edlen1953
import numpy as np
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: Some functions for quick testing.
'''
def get_conditions(location):
'''
Returns the mean conditions at various site. Currently
supported locations:
- 'STANDARD'
- 'CERRO_ARMAZONES'
- 'CERRO_PARANAL'
- 'LA_PALMA'
- 'LA_SILLA'
- 'LAS_CAMPANAS'
- 'MAUNA_KEA'
Parameters
----------
location : string
String denoting the desired location
Returns
-------
observatory : dict
Dictionary containing the atmospheric parameters
'''
observatory = observatories[location]
return observatory
def Filippenko1982(l1, l2, zenith, conditions='STANDARD', T=288.15, p=101325,
RH=0.0, xc=450, lat=0, h=0, f=None):
'''
Calculates the refraction following the 1982 paper by
Alexei V. Filippenko.
The refraction is based on a flat atmosphere and the refractivity
values are calculated from a modified Edlen (1953) equation.
Parameters
----------
l1 : float
Shortest wavelength of interest in micron.
l2 : float
Longest wavelength of interest in micron.
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
conditions : string
Refers to the standard conditions of the
observatory. See get_conditions() for more info.
If set to 'None', the conditions specified
after will be used.
T : float (optional)
Temperature in Kelvin
p : float (optional)
Atmospheric pressure in Pa
RH : float (optional)
Relative humidity
xc : float (optional)
CO2 density in parts per million
lat : float (optional)
Latitude of the observer in degrees
h : float (optional)
Altitude of the observer in meters
f : float
Water vapour pressure (default=None). If None, it is
calculated automatically.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if conditions is not None:
T, p, RH, xc, lat, h, _ = get_conditions(conditions).values()
n1 = Edlen1953(l1, T=T, p=p, RH=RH, f=f)
n2 = Edlen1953(l2, T=T, p=p, RH=RH, f=f)
dR = (n1 - n2) * np.tan(np.deg2rad(zenith))
return np.degrees(dR)
def quick_refractive_index(l, conditions='STANDARD', T=288.15, p=101325,
RH=0.0, xc=450, lat=0, h=0):
'''
Function to quickly calculate the refractive index
of atmospheric air at reference conditions, given
a wavelength l.
Parameters
----------
l : float
Wavelength in microns
conditions: string
Refers to the standard conditions of the
observatory. See get_conditions() for more info.
If set to 'None', the conditions specified
after will be used.
T : float (optional)
Temperature in Kelvin
p : float (optional)
Atmospheric pressure in Pa
RH : float (optional)
Relative humidity
xc : float (optional)
CO2 density in parts per million
lat : float (optional)
Latitude of the observer in degrees
h : float (optional)
Altitude of the observer in meters
Returns
-------
n : float
Refractive index for the given conditions.
'''
if conditions is not None:
T, p, RH, xc, lat, h, _ = get_conditions(conditions).values()
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l
n = at.n_tph(l=l, T=T, p=p, RH=RH, xc=xc)
return n
def quick_refraction(l, zenith, conditions='STANDARD', T=288.15, p=101325,
RH=0.0, xc=450, lat=0, h=0):
'''
Function to quickly calculate the refraction
of atmospheric air at reference conditions,
given a wavelength l.
Parameters
----------
l : float
Wavelength in microns
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
conditions : string
Refers to the standard conditions of the
observatory. See get_conditions() for more info.
If set to 'None', the conditions specified
after will be used.
T : float (optional)
Temperature in Kelvin
p : float (optional)
Atmospheric pressure in Pa
RH : float (optional)
Relative humidity
xc : float (optional)
CO2 density in parts per million
lat : float (optional)
Latitude of the observer in degrees
h : float (optional)
Altitude of the observer in meters
Returns
-------
R : float
Refraction in degrees
'''
if conditions is not None:
T, p, RH, xc, lat, h, _ = get_conditions(conditions).values()
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l
n = at.n_tph(l=l, T=T, p=p, RH=RH, xc=xc)
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=p, T=T, RH=RH, xc=xc)
# Initializing refraction model and setting the reduced height
ref = refraction(lat, h)
ref.setReducedHeight(p, rho)
return ref.cassini(n, zenith)
def quick_dispersion(l1, l2, zenith, conditions='STANDARD', T=288.15,
p=101325, RH=0.0, xc=450, lat=0, h=0):
'''
Function to quickly calculate the dispersion
of atmospheric air at reference conditions,
given a wavelength l.
Parameters
----------
l1 : float
Shortest wavelength of interest in micron.
l2 : float
Longest wavelength of interest in micron.
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
conditions : string
Refers to the standard conditions of the
observatory. See get_conditions() for more info.
If set to 'None', the conditions specified
after will be used.
T : float (optional)
Temperature in Kelvin
p : float (optional)
Atmospheric pressure in Pa
RH : float (optional)
Relative humidity
xc : float (optional)
CO2 density in parts per million
lat : float (optional)
Latitude of the observer in degrees
h : float (optional)
Altitude of the observer in meters
Returns
-------
dR : float
Atmospheric dispersion in degrees
'''
if conditions is not None:
T, p, RH, xc, lat, h, _ = get_conditions(conditions).values()
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l1 and l2
n1 = at.n_tph(l=l1, T=T, p=p, RH=RH, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=p, RH=RH, xc=xc)
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=p, T=T, RH=RH, xc=xc)
# Initializing refraction model and setting the reduced height
disp = dispersion(lat, h)
disp.setReducedHeight(p, rho)
return disp.cassini(n1, n2, zenith)
def slalib_refraction(l, zenith, conditions='STANDARD', T=288.15, p=101325,
RH=0.0, xc=450, lat=0, h=0, tlr=0.0065, eps=1e-8, use_pyslalib=False):
'''
Calculates the atmospheric refraction assuming the SLALIB
package. SLALIB uses the same atmospheric model as ZEMAX
based on the works of Hohenkerk & Sinclair and Seidelmann.
They take the B&S refraction formula as a basis.
The pySLALIB FORTRAN has been ported, so pyslalib is not
needed, but can still be used. There are minor numerical
differences, likely due to precision or declaration issues.
Parameters
----------
l : float
Wavelength in microns
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
conditions : string
Refers to the standard conditions of the
observatory. See get_conditions() for more info.
If set to 'None', the conditions specified
after will be used.
T : float (optional)
Temperature in Kelvin
p : float (optional)
Atmospheric pressure in Pa
RH : float (optional)
Relative humidity
xc : float (optional)
CO2 density in parts per million
lat : float (optional)
Latitude of the observer in degrees (default=0)
h : float (optional)
Altitude of the observer in meters
tlr : float
Temperature lapse rate (default=0.0065)
eps : float
Precision at which the calculation is aborted (default=1e-8)
use_pyslalib : bool
Flag that indicates that the user wants to use the pySlalib
package. If False, the internal python port of the original
FORTRAN code will be used. (default=False).
Returns
-------
R : float
Refraction in degrees
'''
if conditions is not None:
T, p, RH, xc, lat, h, _ = get_conditions(conditions).values()
# Converting units to the units that sla_refro() requires.
_zenith = np.deg2rad(zenith) # From degrees to radians
_P = p / 100 # From Pa to mbar
_phi = np.deg2rad(lat) # From degrees to radians
R = []
if type(l) == float or type(l) == int:
l = [l]
if use_pyslalib:
try:
from pyslalib import slalib
except ImportError as e:
print('pySLALIB package not found! Try installing it first.')
for wl in l:
# If using original slalib (e.g. PySLALIB)
Refrac = slalib.sla_refro(_zenith, h, T, _P, RH, wl, phi, tlr, eps)
R.append(np.degrees(Refrac))
# If using internal slalib port
else:
for wl in l:
Refrac = sla_refro(_zenith, h, T, _P, RH, wl, phi, tlr, eps)
R.append(np.degrees(Refrac))
if len(l) == 1:
return R[0]
else:
return np.asarray(R)
def slalib_dispersion(l1, l2, zenith, conditions='STANDARD', T=288.15, p=101325,
RH=0.0, xc=450, lat=0, h=0, tlr=0.0065, eps=1e-8, use_pyslalib=False):
'''
Calculates the atmospheric refraction assuming the SLALIB
package. SLALIB uses the same atmospheric model as ZEMAX
based on the works of Hohenkerk & Sinclair and Seidelmann.
They take the B&S refraction formula as a basis.
The pySLALIB FORTRAN has been ported, so pyslalib is not
needed, but can still be used. There are minor numerical
differences, likely due to precision or declaration issues.
Parameters
----------
l1 : float
Shortest wavelength of interest in micron.
l2 : float
Longest wavelength of interest in micron.
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
conditions : string
Refers to the standard conditions of the
observatory. See get_conditions() for more info.
If set to 'None', the conditions specified
after will be used.
T : float (optional)
Temperature in Kelvin
p : float (optional)
Atmospheric pressure in Pa
RH : float (optional)
Relative humidity
xc : float (optional)
CO2 density in parts per million
lat : float (optional)
Latitude of the observer in degrees (default=0)
h : float (optional)
Altitude of the observer in meters
tlr : float
Temperature lapse rate (default=0.0065)
eps : float
Precision at which the calculation is aborted (default=1e-8)
use_pyslalib : bool
Flag that indicates that the user wants to use the pySlalib
package. If False, the internal python port of the original
FORTRAN code will be used. (default=False).
Returns
-------
dR : float
Refraction in degrees
'''
R1 = slalib_refraction(l1, zenith, conditions, T, p, RH, xc,\
lat, h, tlr, eps, use_pyslalib)
R2 = slalib_refraction(l2, zenith, conditions, T, p, RH, xc,\
lat, h, tlr, eps, use_pyslalib)
dR = R1 - R2
return dR
+754
View File
@@ -0,0 +1,754 @@
#!/usr/bin/env Python3
import numpy as np
def sla_atms(RT, TT, DNT, GAMAL, R):
'''
Internal routine used by sla_refro(), ported from the SLALIB
FORTRAN code (Copyright P.T.Wallace. All rights reserved.)
The method is also described in "Explanatory Supplement
Astronomical Almanac" by P.K. Seidelmann, 1992, section 3.281.
Refractive index and derivative with respect to height for the
stratosphere.
Parameters
----------
RT : float
Height of tropopause from center of the Earth (meters)
TT : float
Temperature at the tropopause (K)
DNT : float
Refractive index at the tropopause
GAMAL : float
Constant of the atmospheric model = G*MD/R
R : float
Current distance from the centre of the Earth (metre)
Returns
-------
DN : float
Refractive index at R
RDNDR : float
R * rate the refractive index is changing at R
'''
_B = GAMAL / TT
_W = (DNT - 1) * np.exp( -_B * (R - RT) )
DN = 1+_W
RDNDR = -R * _B * _W
return DN, RDNDR
def sla_atmt(R0, T0, ALPHA, GAMM2, DELM2, C1, C2, C3, C4, C5, C6, R):
'''
Internal routine used by sla_refro(), ported from the SLALIB
FORTRAN code (Copyright P.T.Wallace. All rights reserved.)
The method is also described in "Explanatory Supplement
Astronomical Almanac" by P.K. Seidelmann, 1992, section 3.281.
Refractive index and derivative with respect to height for the
troposphere.
Parameters
----------
R0 : float
Height of observer from center of the Earth (meter)
T0 : float
Temperature at the observer (K)
ALPHA : float
Alpha
GAMM2 : float
Gamma minus 2 - see HMNAO paper
DELM2 : float
Delta minus 2
C1 : float
Useful term
C2 : float
Useful term
C3 : float
Useful term
C4 : float
Useful term
C5 : float
Useful term
C6 : float
Useful term
R : float
Current distance from the center of the Earth (meters)
Returns
-------
T : float
Temperature at R (K)
DN : float
Refractive index at R
RDNDR : float
R * rate the refractive index is changing at R
Note that in the optical case C5 and C6 are zero.
'''
T = max(min(T0 - ALPHA * (R - R0), 320), 100)
TT0 = T / T0
TT0GM2 = TT0**GAMM2
TT0DM2 = TT0**DELM2
DN = 1 + ( C1 * TT0GM2 - (C2 - C5 / T) * TT0DM2) * TT0
RDNDR = R * (-C3 * TT0GM2 + (C4 - C6 / TT0) * TT0DM2)
return T, DN, RDNDR
def sla_drange(a):
'''
Helper function for sla_refro().
'''
drange = a % (2*np.pi)
if drange >= np.pi:
drange -= np.abs(2*np.pi)*np.sign(a)
return drange
def sla_refi(dn, rdndr):
'''
Helper function for sla_refro(), that calculates
the refraction integrand.
'''
return rdndr/(dn+rdndr)
def sla_refro(ZOBS, HM, TDK, PMB, RH, WL, PHI, TLR, EPS):
'''
This is a direct port from the original FORTRAN SLA_REFRO()
routine.
Calculates Atmospheric refraction for radio and
optical/IR wavelengths, ported from the original SLALIB
FORTRAN code (Copyright P.T.Wallace. All rights reserved.).
The method is also described in "Explanatory Supplement
Astronomical Almanac" by P.K. Seidelmann, 1992, section 3.281.
Parameters
----------
ZOBS : float
Observed zenith distance of the source (radian)
HM : float
Height of the observer above sea level (meter)
TDK : float
Ambient temperature at the observer (K)
PMB : float
Pressure at the observer (millibar)
RH : float
Relative humidity at the observer (range 0-1)
WL : float
Effective wavelength of the source (micrometre)
PHI : float
Latitude of the observer (radian, astronomical)
TLR : float
Temperature lapse rate in the troposphere (K/metre)
EPS : float
Precision required to terminate iteration (radian)
Returns
-------
REF : float
Refraction: in vacuo ZD minus observed ZD (radian)
Notes
-----
1 A suggested value for the TLR argument is 0.0065D0. The
refraction is significantly affected by TLR, and if studies
of the local atmosphere have been carried out a better TLR
value may be available. The sign of the supplied TLR value
is ignored.
2 A suggested value for the EPS argument is 1D-8. The result is
usually at least two orders of magnitude more computationally
precise than the supplied EPS value.
3 The routine computes the refraction for zenith distances up
to and a little beyond 90 deg using the method of Hohenkerk
and Sinclair (NAO Technical Notes 59 and 63, subsequently adopted
in the Explanatory Supplement, 1992 edition - see section 3.281).
4 The code is a development of the optical/IR refraction subroutine
AREF of C.Hohenkerk (HMNAO, September 1984), with extensions to
support the radio case. Apart from merely cosmetic changes, the
following modifications to the original HMNAO optical/IR refraction
code have been made:
. The angle arguments have been changed to radians.
. Any value of ZOBS is allowed (see note 6, below).
. Other argument values have been limited to safe values.
. Murray's values for the gas constants have been used
(Vectorial Astrometry, Adam Hilger, 1983).
. The numerical integration phase has been rearranged for
extra clarity.
. A better model for Ps(T) has been adopted (taken from
Gill, Atmosphere-Ocean Dynamics, Academic Press, 1982).
. More accurate expressions for Pwo have been adopted
(again from Gill 1982).
. The formula for the water vapour pressure, given the
saturation pressure and the relative humidity, is from
Crane (1976), expression 2.5.5.
. Provision for radio wavelengths has been added using
expressions devised by A.T.Sinclair, RGO (private
communication 1989). The refractivity model currently
used is from J.M.Rueger, "Refractive Index Formulae for
Electronic Distance Measurement with Radio and Millimetre
Waves", in Unisurv Report S-68 (2002), School of Surveying
and Spatial Information Systems, University of New South
Wales, Sydney, Australia.
. The optical refractivity for dry air is from Resolution 3 of
the International Association of Geodesy adopted at the XXIIth
General Assembly in Birmingham, UK, 1999.
. Various small changes have been made to gain speed.
5 The radio refraction is chosen by specifying WL > 100 micrometres.
Because the algorithm takes no account of the ionosphere, the
accuracy deteriorates at low frequencies, below about 30 MHz.
6 Before use, the value of ZOBS is expressed in the range +/- pi.
If this ranged ZOBS is -ve, the result REF is computed from its
absolute value before being made -ve to match. In addition, if
it has an absolute value greater than 93 deg, a fixed REF value
equal to the result for ZOBS = 93 deg is returned, appropriately
signed.
7 As in the original Hohenkerk and Sinclair algorithm, fixed values
of the water vapour polytrope exponent, the height of the
tropopause, and the height at which refraction is negligible are
used.
8 The radio refraction has been tested against work done by
Iain Coulson, JACH, (private communication 1995) for the
James Clerk Maxwell Telescope, Mauna Kea. For typical conditions,
agreement at the 0.1 arcsec level is achieved for moderate ZD,
worsening to perhaps 0.5-1.0 arcsec at ZD 80 deg. At hot and
humid sea-level sites the accuracy will not be as good.
9 It should be noted that the relative humidity RH is formally
defined in terms of "mixing ratio" rather than pressures or
densities as is often stated. It is the mass of water per unit
mass of dry air divided by that for saturated air at the same
temperature and pressure (see Gill 1982).
10 The algorithm is designed for observers in the troposphere. The
supplied temperature, pressure and lapse rate are assumed to be
for a point in the troposphere and are used to define a model
atmosphere with the tropopause at 11km altitude and a constant
temperature above that. However, in practice, the refraction
values returned for stratospheric observers, at altitudes up to
25km, are quite usable.
'''
# Fixed parameters
D93 = np.deg2rad(93)# 93 degrees in radians
GCR = 8314.32 # Universal gas constant
DMD = 28.9644 # Molecular weight of dry air
DMW = 18.0152 # Molecular weight of water vapour
S = 6378120 # Mean Earth radius (metre)
DELTA = 18.36 # Exponent of temperature dependence of water vapour pressure
HT = 11000 # Height of tropopause (metre)
HS = 80000 # Upper limit for refractive effects (metre)
ISMAX = 16384 # Numerical integration: maximum number of strips.
# Transform ZOBS into the normal range.
ZOBS1 = sla_drange(ZOBS)
ZOBS2 = min(abs(ZOBS1),D93)
# Keep other arguments within safe bounds.
HMOK = min(max(HM,-1E3),HS)
TDKOK = min(max(TDK,100),500)
PMBOK = min(max(PMB,0),10000)
RHOK = min(max(RH,0),1)
WLOK = max(WL,0.1)
ALPHA = min(max(abs(TLR),0.001),0.01)
# Tolerance for iteration.
TOL = min(max(abs(EPS),1E-12),0.1)/2
# Decide whether optical/IR or radio case - switch at 100 microns.
OPTIC = (WLOK<=100)
# Set up model atmosphere parameters defined at the observer.
WLSQ = WLOK*WLOK
# Local gravity, can be made consistent with AstroAtmosphere
GB = 9.784*(1-0.0026*np.cos(PHI+PHI)-0.00000028*HMOK)
if OPTIC:
# Refractivity from Barrell & Sears (1939)!
A = (287.6155+(1.62887+0.01360/WLSQ)/WLSQ)*273.15E-6/1013.25
else:
A = 77.6890E-6
GAMAL = (GB*DMD)/GCR
GAMMA = GAMAL/ALPHA
GAMM2 = GAMMA-2
DELM2 = DELTA-2
TDC = TDKOK-273.15
PSAT = 10**((0.7859+0.03477*TDC)/(1+0.00412*TDC))\
*(1+PMBOK*(4.5E-6+6E-10*TDC*TDC))
if PMBOK > 0:
PWO = RHOK*PSAT/(1-(1-RHOK)*PSAT/PMBOK)
else:
PWO = 0
W = PWO*(1-DMW/DMD)*GAMMA/(DELTA-GAMMA)
C1 = A*(PMBOK+W)/TDKOK
if OPTIC:
C2 = (A*W+11.2684E-6*PWO)/TDKOK
else:
C2 = (A*W+6.3938E-6*PWO)/TDKOK
C3 = (GAMMA-1)*ALPHA*C1/TDKOK
C4 = (DELTA-1)*ALPHA*C2/TDKOK
if OPTIC:
C5 = 0
C6 = 0
else:
C5 = 375463E-6*PWO/TDKOK
C6 = C5*DELM2*ALPHA/(TDKOK*TDKOK)
# Conditions at the observer.
R0 = S+HMOK
TEMPO, DN0, RDNDR0 = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,R0)
SK0 = DN0*R0*np.sin(ZOBS2)
F0 = sla_refi(DN0,RDNDR0)
# Conditions in the troposphere at the tropopause.
RT = S+max(HT,HMOK)
TT, DNT, RDNDRT = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,RT)
SINE = SK0/(RT*DNT)
ZT = np.arctan2(SINE,np.sqrt(max(1-SINE*SINE,0)))
FT = sla_refi(DNT,RDNDRT)
# Conditions in the stratosphere at the tropopause.
DNTS,RDNDRP = sla_atms(RT,TT,DNT,GAMAL,RT)
SINE = SK0/(RT*DNTS)
ZTS = np.arctan2(SINE,np.sqrt(max(1-SINE*SINE,0)))
FTS = sla_refi(DNTS,RDNDRP)
# Conditions at the stratosphere limit.
RS = S+HS
DNS,RDNDRS = sla_atms(RT,TT,DNT,GAMAL,RS)
SINE = SK0/(RS*DNS)
ZS = np.arctan2(SINE,np.sqrt(max(1-SINE*SINE,0)))
FS = sla_refi(DNS,RDNDRS)
# Variable initialization to avoid compiler warning.
REFT = 0
# Integrate the refraction integral in two parts; first in the
# troposphere (K=1), then in the stratosphere (K=2).
for K in [1,2]:
# Initialize previous refraction to ensure at least two iterations.
REFOLD = 1
# Start off with 8 strips.
IS = 8
# Start Z, Z range, and start and end values.
if K==1:
Z0 = ZOBS2
ZRANGE = ZT-Z0
FB = F0
FF = FT
else:
Z0 = ZTS
ZRANGE = ZS-Z0
FB = FTS
FF = FS
# Sums of odd and even values.
FO = 0
FE = 0
# First time through the loop we have to do every point.
N = 1
# Start of iteration loop (terminates at specified precision).
LOOP = True
while LOOP:
# Strip width.
H = ZRANGE/IS
# Initialize distance from Earth centre for quadrature pass.
if K==1:
R = R0
else:
R = RT
# One pass (no need to compute evens after first time).
# DO I=1,IS-1,N
for I in np.arange(1, IS-1, N):
# Sine of observed zenith distance.
SZ = np.sin(Z0+H*I)
# Find R (to the nearest metre, maximum four iterations).
if (SZ>1E-20):
W = SK0/SZ
RG = R
DR = 1E6
J = 0
while (abs(DR)>1 and J<4):
J+=1
if K==1:
TG,DN,RDNDR = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,RG)
else:
DN,RDNDR = sla_atms(RT,TT,DNT,GAMAL,RG)
DR = (RG*DN-W)/(DN+RDNDR)
RG = RG-DR
R = RG
# Find the refractive index and integrand at R.
if K==1:
T,DN,RDNDR = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,R)
else:
DN,RDNDR = sla_atms(RT,TT,DNT,GAMAL,R)
F = sla_refi(DN,RDNDR)
# Accumulate odd and (first time only) even values.
if N==1 and (I%2)==0:
FE = FE+F
else:
FO = FO+F
# Evaluate the integrand using Simpson's Rule.
REFP = H*(FB+4*FO+2*FE+FF)/3
# Has the required precision been achieved (or can't be)?
if (abs(REFP-REFOLD)>TOL and IS<ISMAX):
# No: prepare for next iteration.
# Save current value for convergence test.
REFOLD = REFP
# Double the number of strips.
IS = IS+IS
# Sum of all current values = sum of next pass's even values.
FE = FE+FO
# Prepare for new odd values.
FO = 0
# Skip even values next time.
N = 2
else:
# Yes: save troposphere component and terminate the loop.
if (K==1):
REFT = REFP
LOOP = False
# Result.
REF = REFT+REFP
if (ZOBS1<0):
REF = -REF
return REF
# =============================================================================
# WORK IN PROGRESS; Implementation of the slalib model for
# any refractive index model.
# Main issue is in the calculation of dn/dr for any model
# of the refractive index. One possibility is following
# the Corbard error function model, which assumes the
# atmospheric density follows the equation rho(r)=exp(-u/a).
# But then the SLA_REFRO routine would not have to be modified
# and one would simply use the refraction.corbard() function.
# =============================================================================
#
# def get_rc(lat):
# '''
# Returns the radius of the curvature at the observer in km,
# taking into account the latitude of the observer and the corresponding
# ellipsoidality of the earth.
# Returns
# -------
# rc : float
# Local radius of curvature of the Earth in kilometers.
# '''
# _A = 6378.137 # km
# _B = 6356.752 # km
# _phi = np.deg2rad(lat)
# _rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
# return _rc0*1000
# def get_local_g(lat, h):
# '''
# Returns the local gravitic acceleration for a given latitude
# and altitude.
# Parameters
# ----------
# lat : float
# Latitude in degrees
# h : float
# Altitude above sea level in meters
# Returns
# -------
# g : float
# Local acceleration due to gravity in m/s^2
# '''
# _phi = np.deg2rad(lat)
# _c1 = 5.2790414e-3
# _c2 = 2.32718e-5
# _c3 = 1.262e-7
# _c4 = 7e-10
# _g0 = 9.780327 # m/s^2
# _g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
# _g = _g0_local - (3.0877e-6 - 4.3e-9 * np.sin(_phi)**2) * h + 7.2e-13 * h**2
# return _g
# def aa_refro(zenith, l, h, T, p, RH, xc, lat, TLR, EPS):
# '''
# Modified version of sla_refro(), updating some constants
# and changing the units on input and output parameters.
# The function calculates the atmospheric refraction
# optical/IR wavelengths (not radio!), ported from the original
# SLALIB FORTRAN code (Copyright P.T.Wallace. All rights
# reserved.).
# The method is also described in "Explanatory Supplement
# Astronomical Almanac" by P.K. Seidelmann, 1992, section 3.281.
# See also the description of sla_refro().
# Parameters
# ----------
# zenith : float
# Observed zenith distance of the source (degrees)
# l : float
# Effective wavelength of the source (micrometre)
# h : float
# Height of the observer above sea level (meter)
# T : float
# Ambient temperature at the observer (K)
# p : float
# Pressure at the observer (Pascal)
# RH : float
# Relative humidity at the observer (range 0-1)
# xc : float
# CO2 density in parts per million
# lat : float
# Latitude of the observer (degrees)
# TLR : float
# Temperature lapse rate in the troposphere (K/metre)
# EPS : float
# Precision required to terminate iteration (radian)
# Returns
# -------
# REF : float
# Refraction: in vacuo ZD minus observed ZD (radian)
# '''
# # Converting some units for use later
# ZOBS = np.deg2rad(zenith)
# HM = h
# TDK = T
# PMB = p / 100
# WL = l
# PHI = np.deg2rad(lat)
# # Fixed parameters
# D93 = np.deg2rad(93)# 93 degrees in radians
# GCR = 8314.510 # Universal gas constant
# DMD = 28.9635 + 12.011e-6 * (xc - 400) # Molar mass in g/mol of dry air containing xc ppm of CO2
# DMW = 18.0152 # Molecular weight of water vapour
# S = get_rc(lat) # Mean Earth radius (meters)
# DELTA = 18.36 # Exponent of temperature dependence of water vapour pressure
# HT = 11000 # Height of tropopause (metre)
# HS = 80000 # Upper limit for refractive effects (meters)
# ISMAX = 16384 # Numerical integration: maximum number of strips.
# # Transform ZOBS into the normal range.
# ZOBS1 = sla_drange(ZOBS)
# ZOBS2 = min(abs(ZOBS1),D93)
# # Keep other arguments within safe bounds.
# HMOK = min(max(HM,-1E3),HS)
# TDKOK = min(max(TDK,100),500)
# PMBOK = min(max(PMB,0),10000)
# RHOK = min(max(RH,0),1)
# WLOK = max(WL,0.1)
# ALPHA = min(max(abs(TLR),0.001),0.01)
# # Tolerance for iteration.
# TOL = min(max(abs(EPS),1E-12),0.1)/2
# # Set up model atmosphere parameters defined at the observer.
# WLSQ = WLOK*WLOK
# # Local gravity
# GB = get_local_g(lat, h)
# # Refractivity of air
# A = (287.6155+(1.62887+0.01360/WLSQ)/WLSQ)*273.15E-6/1013.25
# GAMAL = (GB * DMD) / GCR
# GAMMA = GAMAL / ALPHA
# GAMM2 = GAMMA - 2
# DELM2 = DELTA - 2
# TDC = TDKOK - 273.15
# PSAT = 10**( (0.7859+0.03477*TDC) / (1+0.00412*TDC) )\
# * (1 + PMBOK * (4.5E-6 + 6E-10 * TDC*TDC) )
# if PMBOK > 0:
# PWO = RHOK*PSAT/(1-(1-RHOK)*PSAT/PMBOK)
# else:
# PWO = 0
# W = PWO*(1-DMW/DMD)*GAMMA/(DELTA-GAMMA)
# C1 = A*(PMBOK+W)/TDKOK
# C2 = (A*W+11.2684E-6*PWO)/TDKOK
# C3 = (GAMMA-1)*ALPHA*C1/TDKOK
# C4 = (DELTA-1)*ALPHA*C2/TDKOK
# C5 = 0
# C6 = 0
# # Conditions at the observer.
# R0 = S+HMOK
# TEMPO, DN0, RDNDR0 = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,R0)
# SK0 = DN0*R0*np.sin(ZOBS2)
# F0 = sla_refi(DN0,RDNDR0)
# # Conditions in the troposphere at the tropopause.
# RT = S+max(HT,HMOK)
# TT, DNT, RDNDRT = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,RT)
# SINE = SK0/(RT*DNT)
# ZT = np.arctan2(SINE,np.sqrt(max(1-SINE*SINE,0)))
# FT = sla_refi(DNT,RDNDRT)
# # Conditions in the stratosphere at the tropopause.
# DNTS,RDNDRP = sla_atms(RT,TT,DNT,GAMAL,RT)
# SINE = SK0/(RT*DNTS)
# ZTS = np.arctan2(SINE,np.sqrt(max(1-SINE*SINE,0)))
# FTS = sla_refi(DNTS,RDNDRP)
# # Conditions at the stratosphere limit.
# RS = S+HS
# DNS,RDNDRS = sla_atms(RT,TT,DNT,GAMAL,RS)
# SINE = SK0/(RS*DNS)
# ZS = np.arctan2(SINE,np.sqrt(max(1-SINE*SINE,0)))
# FS = sla_refi(DNS,RDNDRS)
# # Variable initialization to avoid compiler warning.
# REFT = 0
# # Integrate the refraction integral in two parts; first in the
# # troposphere (K=1), then in the stratosphere (K=2).
# for K in [1,2]:
# # Initialize previous refraction to ensure at least two iterations.
# REFOLD = 1
# # Start off with 8 strips.
# IS = 8
# # Start Z, Z range, and start and end values.
# if K==1:
# Z0 = ZOBS2
# ZRANGE = ZT-Z0
# FB = F0
# FF = FT
# else:
# Z0 = ZTS
# ZRANGE = ZS-Z0
# FB = FTS
# FF = FS
# # Sums of odd and even values.
# FO = 0
# FE = 0
# # First time through the loop we have to do every point.
# N = 1
# # Start of iteration loop (terminates at specified precision).
# LOOP = True
# while LOOP:
# # Strip width.
# H = ZRANGE/IS
# # Initialize distance from Earth centre for quadrature pass.
# if K==1:
# R = R0
# else:
# R = RT
# # One pass (no need to compute evens after first time).
# # DO I=1,IS-1,N
# for I in np.arange(1, IS-1, N):
# # Sine of observed zenith distance.
# SZ = np.sin(Z0+H*I)
# # Find R (to the nearest metre, maximum four iterations).
# if (SZ>1E-20):
# W = SK0/SZ
# RG = R
# DR = 1E6
# J = 0
# while (abs(DR)>1 and J<4):
# J+=1
# if K==1:
# TG,DN,RDNDR = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,RG)
# else:
# DN,RDNDR = sla_atms(RT,TT,DNT,GAMAL,RG)
# DR = (RG*DN-W)/(DN+RDNDR)
# RG = RG-DR
# R = RG
# # Find the refractive index and integrand at R.
# if K==1:
# T,DN,RDNDR = sla_atmt(R0,TDKOK,ALPHA,GAMM2,DELM2,C1,C2,C3,C4,C5,C6,R)
# else:
# DN,RDNDR = sla_atms(RT,TT,DNT,GAMAL,R)
# F = sla_refi(DN,RDNDR)
# # Accumulate odd and (first time only) even values.
# if N==1 and (I%2)==0:
# FE = FE+F
# else:
# FO = FO+F
# # Evaluate the integrand using Simpson's Rule.
# REFP = H*(FB+4*FO+2*FE+FF)/3
# # Has the required precision been achieved (or can't be)?
# if (abs(REFP-REFOLD)>TOL and IS<ISMAX):
# # No: prepare for next iteration.
# # Save current value for convergence test.
# REFOLD = REFP
# # Double the number of strips.
# IS = IS+IS
# # Sum of all current values = sum of next pass's even values.
# FE = FE+FO
# # Prepare for new odd values.
# FO = 0
# # Skip even values next time.
# N = 2
# else:
# # Yes: save troposphere component and terminate the loop.
# if (K==1):
# REFT = REFP
# LOOP = False
# # Result.
# REF = REFT+REFP
# if (ZOBS1<0):
# REF = -REF
# return REF
+85
View File
@@ -0,0 +1,85 @@
observatories = {
'STANDARD': {
'T': 288.15,
'p': 101325,
'RH': 0.0,
'xc': 450,
'latitude': 0,
'altitude': 0,
'comment': 'Standard atmospheric conditions'
},
'CERRO_ARMAZONES': {
'T': 279.65,
'p': 71200,
'RH': 0.22,
'xc': 450,
'latitude': -24.5983,
'altitude': 3064,
'comment': 'Default conditions for Cerro Armazones, site of the ELT.\nValues are taken from the optical model of MICADO.'
},
'CERRO_PARANAL': {
'T': 285.95,
'p': 74300,
'RH': 0.15,
'xc': 450,
'latitude': -24.6230,
'altitude': 2635,
'comment': 'Average conditions at Cerro Paranal, site of the VLT.\nValues are taken from https://www.eso.org/sci/facilities/paranal/astroclimate/site.html and https://academic.oup.com/mnras/article/399/2/783/1061377'
},
'LA_SILLA': {
'T': 284.65,
'p': 77220,
'RH': 0.44,
'xc': 450,
'latitude': -29.25,
'altitude': 2400,
'comment': 'Average conditions of the La Silla Observatory.\nValues based on https://www.eso.org/gen-fac/pubs/astclim/lasilla/diffrefr.html'
},
'LAS_CAMPANAS': {
'T': 285.85,
'p': 76400,
'RH': 0.0,
'xc': 450,
'latitude': -29.016,
'altitude': 2380,
'comment': 'Mean conditions at Las Campanas observatory, site of the GMT.\nValues based on https://www.gmto.org/Resources/GMT-ID-01466-Chapter_5_Site_Evaluation.pdf and http://weather.lco.cl/'
},
'LA_PALMA': {
'T': 281.95,
'p': 77500,
'RH': 0.36,
'xc': 450,
'latitude': 28.764,
'altitude': 2396,
'comment': 'Average conditions for Roque de los Muchachos Observatory on La Palma, Spain.\nValues taken from https://academic.oup.com/mnras/article/399/2/783/1061377'
},
'MAUNA_KEA': {
'T': 274.45,
'p': 61400,
'RH': 0.48,
'xc': 450,
'latitude': 19.826,
'altitude': 4205,
'comment': 'Average conditions at the Mauna Kea Observatory.\nValues taken from http://www.soest.hawaii.edu/MET/Faculty/businger/mauna_kea/daSilvaThesisFinal.pdf, Ch. 3. and https://www.eso.org/gen-fac/pubs/astclim/espas/espas_reports/ESPAS-MaunaKea.pdf'
}
# 'MT_GRAHAM': {
# 'T': 278.15,
# 'p': 68500,
# 'RH': 0.0,
# 'xc': 450,
# 'latitude': 32.701,
# 'altitude': 3191,
# 'comment': 'Mean conditions at Mount Graham International Observatory,\
# site of the LBT. Values are estimates.'
# },
# 'FRED_LAWRENCE_WHIPPLE': {
# 'T': 288.15,
# 'p': 74300,
# 'RH': 0.0,
# 'xc': 450,
# 'latitude': 31.681,
# 'altitude': 2616,
# 'comment': 'Mean conditions at Fred Lawrence Whipple Observatory, site of\
# the MMT. Values are estimates.'
# }
}
+455 -94
View File
@@ -3,20 +3,82 @@ import numpy as np
from scipy import integrate
import os
from .ciddorModel import Observatory
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: This script offers a complete model of the atmospheric refraction
and dispersion. It uses the Ciddor1996 paper to calculate the
index of refraction for a wide range of conditions, with corresponding
error propagation if needed.
index of refraction for a wide range of conditions, with corresponding error propagation if needed.
The actual refraction is calculated by any of the following models:
- Plane parallel atmosphere
- Cassini spherical homogeneous atmosphere
- Mathar's Barometric exponential model
- Other methods described in Corbard et al., 2019.
'''
class refraction(Observatory):
'''
Class that sets the observer location.
These location attributes are then used to calculate the
appropriate atmospheric refraction.
See also:
Observatory()
dispersion()
...
Attributes
-----------
lat : float
Latitude of the observer in degrees
h : float
Altitude above mean sea level of the observer in meters
Other Observatory() attributes
Methods
-------
cassini(n1, n2, zenith, reduced_height=None)
Returns the atmospheric refraction assuming Cassini's homegeneous
atmosphere model.
cassiniError(z, l1, l2, T, p, RH, xc, dl1=0, dl2=0, dT=0.2, dP=20,
dRH=0.02, dCO2=20, dz=0, lat=None, h=None)
Returns the uncertainty in the atmospheric refraction, assuming
Cassini's homegeneous atmosphere model and the given parameters.
corbard(n1, n2, zenith, reduced_height=None)
Returns the atmospheric refraction following the method described
in Corbard et al., 2019.
gammaCoefficients(n1, reduced_height=None)
Helper function for matharExponential(), that calculates the gamma
coefficients.
H_isotherm(T)
Sets the reduced height for the conditions at the observer, assuming
an isothermal atmosphere.
matharExponential(n1, zenith, reduced_height=None)
Returns the atmospheric refraction assuming Mathar's barometric
exponential model.
oriani(n1, n2, zenith, reduced_height=None)
Returns the atmospheric refraction following the classic version
of Oriani's theorem.
planeParallel(n1, n2, zenith)
Returns the atmospheric refraction assuming a plane parallel
atmosphere.
psi(x)
Submethod for corbard().
refractionIntegral(n1, n2, zenith, R0=None, useStandardAtmosphere=True, heightData=None, rhoData=None)
Returns the atmospheric refraction following the refraction integral.
setReducedHeight(p, rho)
Sets the reduced atmospheric height.
set_H(p, rho)
Sets the reduced height for the conditions at the observer, assuming
an adiabatic atmosphere.
set_rc()
Sets the local radius of curvature of the Earth at the observer.
tan5(n1, n2, zenith, reduced_height=None)
Returns the atmospheric refraction following Oriani's theorem,
including a fifth order term in the expansion.
Other Observatory() methods
'''
def __init__(self, lat, h):
Observatory.__init__(self)
self.h = h # height above sea level in meters
@@ -24,22 +86,26 @@ class refraction(Observatory):
self.rc = self.set_rc() * 1000 # Radius of curvature of the earth at the observer
# self.H2 = self.H_isotherm(T) # Reduced height of isotherm atmosphere
def setReducedHeight(self, P, rho):
self.H = self.set_H(P, rho) # Reduced height of the atmosphere assuming ideal gas law
def planeParallel(self, n, zenith):
'''
Refraction based on a flat atmosphere, easily derived from Snell's law.
'''
_R = np.arcsin(n * np.sin( np.deg2rad(zenith) )) - np.deg2rad(zenith)
return np.degrees(_R)
def cassini(self, n1, zenith, reduced_height=None):
'''
Refraction of spherical atmosphere, derived from geometry using Sine law
Parameters
----------
n1 : float
Refractive index at the wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if reduced_height:
H = reduced_height
@@ -50,39 +116,46 @@ class refraction(Observatory):
return np.degrees(_R1)
def tan5(self, n1, zenith, reduced_height=None):
def cassiniError(self, n, dn, zenith, dz=0, reduced_height=None):
'''
Same as oriani's theorem, but including a higher order term.
Found in Corbard et al., 2019.
Calculates the uncertainty in the refraction angle in degrees.
See Observatory().n_tph() and Observatory().dn_tph() on how to
calculate the refractive index and it's uncertainty.
Parameters
----------
n : float
Refractive index of air
dn : float
Uncertainty in the refractive index of air
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
dz : float (optional)
Uncertainty in the zenith angle, in degrees.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
dR : float
Uncertainty in the atmospheric refraction in degrees.
'''
if reduced_height:
H = reduced_height
_H = reduced_height
else:
H = self.H
_H = self.H
_zenith = np.deg2rad(zenith)
_dz = np.deg2rad(dz)
_R = self.rc
_dRdn = (_R * np.sin(_zenith)) / ((_R + _H) * np.sqrt(1 - ((n**2 * _R**2 * np.sin(_zenith)**2)/(_R + _H)**2)))
_a = n1 - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3 + 3 * _a * (_b - _a/2)**2 * np.tan( np.deg2rad(zenith) )**5
return np.degrees(_R)
_dRdz = (_R * np.cos(_zenith)) / (_R + _H) * ( n / np.sqrt(1-(n**2 * _R**2 * np.sin(_zenith)**2)/(_R + _H)**2) - 1 / np.sqrt(1-(_R**2 * np.sin(_zenith)**2)/(_R + _H)**2) )
_dR = np.sqrt( (_dRdn * dn)**2 + (_dRdz * _dz)**2)
def oriani(self, n1, zenith, reduced_height=None):
'''
Classic refraction formula with two tan(z) terms. This does not assume any
information about the structure of the atmosphere. Found with Oriani's theorem.
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_a = n1 - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3
return np.degrees(_R)
return np.degrees(_dR)
def corbard(self, n1, zenith, reduced_height=None):
@@ -90,6 +163,22 @@ class refraction(Observatory):
Corbard et al., 2019, mention an additional formula based on the error function.
Oriani's theorem can be derived from this equation by 'keeping only the three first
terms of its asymptotic expansion'.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if reduced_height:
H = reduced_height
@@ -102,15 +191,268 @@ class refraction(Observatory):
return np.degrees(_R)
def refractionIntegral(self, n0, z0, R0=None, useStandardAtmosphere=True, heightData=None, rhoData=None):
def gammaCoefficients(self, n1, reduced_height=None):
'''
Using data that gives temperature, pressure and density as a function of height,
the gladstone-dale relation is invoked to calculate the path of a light ray
along the atmosphere. This is allows us to also include atmospheric activity in
Submethod for matharExponential(). Calculates the gamma
coefficients.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest at the observer
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
g : array
List of values, corresponding to the gamma coefficients.
g[0] corresponds to g_1, g[1] to g_3, g[2] to g_5, and so on.
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_chi0 = n1**2 - 1
_Khat = H / self.rc
_g1 = (1 - 5/24 * _Khat * _chi0**2 - 3/4 * _chi0 - 3/8 * _Khat**2 * _chi0 \
- _Khat - 6 * _Khat**3 + 5/8 * _chi0**2 + 63/128 * _chi0**4 + 3/8 \
* _Khat * _chi0 + 35/256 * _Khat * _chi0**3 + 2 * _Khat**2 + 5/36 \
* _Khat**2 * _chi0**2 - 35/64 * _chi0**3 + 9/16 * _Khat**3 * _chi0 \
- 120 * _Khat**5 + 24 * _Khat**4 - 231 / 512 * _chi0**5 - 63 / 640 \
* _Khat * _chi0**4 - 35 / 512 * _Khat**2 * _chi0**3 - 5 / 36 * _Khat**3\
* _chi0**2 - 9 / 8 * _Khat**4 * _chi0 )
_g3 = (35/96 * _chi0**3 - 5/12 * _chi0**2 + 333 * _Khat**4 * _chi0 - 2400 \
* _Khat**5 + 3605/4608 * _Khat**2 * _chi0**3 + 63/256 * _Khat * _chi0**4\
- 819/16 * _Khat**3 * _chi0 - 35/18 * _Khat**2 * _chi0**2 - 35/96 \
* _Khat * _chi0**3 + 69/8 * _Khat**2 * _chi0 + 5/8 * _Khat * _chi0**2 \
- 3/2 * _Khat * _chi0 + 1355/216 * _Khat**3 * _chi0**2 + 10 * _Khat**2 \
+ 336 * _Khat**4 - 2 * _Khat + _chi0/2 - 21/64 * _chi0**4 - 54 * _Khat**3 \
+ 77/256 * _chi0**5 ) * 1/2
_g5 = (-96 * _Khat**3 + 8 * _Khat**2 - 7/24 * _chi0**3 + 65/4 * _Khat**2 \
* _chi0**2 + 35/48 * _Khat * _chi0**3 + 30 * _Khat**2 * _chi0 - 5/3 \
* _Khat * _chi0**2 - 3 * _Khat * _chi0 + 20091/8 * _Khat**4 * _chi0 \
- 32605/216 * _Khat**3 * _chi0**2 - 35/9 * _Khat**2 * _chi0**3 + 984 \
* _Khat**4 + _chi0**2/3 - 10200 * _Khat**5 - 77/320 * _chi0**5 + 21/80 \
* _chi0**4 - 1089/4 * _Khat**3 * _chi0 - 7/16 * _Khat * _chi0**4 ) * 3/8
_g7 = (-17520 * _Khat**5 - 9/40 * _chi0**4 - 8755/12 * _Khat**3 * _chi0**2 \
+ 1056 * _Khat**4 + _chi0**3/4 + 23517/4 * _Khat**4 * _chi0 - 11/3 \
* _Khat * _chi0**2 + 21 * _Khat**2 * _chi0 - 7/4 * _Khat * _chi0**3 \
+ 505/9 * _Khat**2 * _chi0**2 - 801/2 * _Khat**3 * _chi0 + 33/160 \
* _chi0**5 - 48 * _Khat**3 + 63/80 * _Khat * _chi0**4 + 2345/96 \
* _Khat**2 * _chi0**3) * 5/16
_g9 = (_chi0**4 / 5 + 384 * _Khat**4 - 13440 * _Khat**5 + 5562 * _Khat**4 \
* _chi0 + 3125/36 * _Khat**2 * _chi0**3 - 11/60 * _chi0**5 - 25/6 \
* _Khat * _chi0**3 - 26950/27 * _Khat**3 * _chi0**2 + 340/9 * _Khat**2 \
* _chi0**2 - 180 * _Khat**3 * _chi0 - 9/5 * _Khat * _chi0**4) * 35/128
g = np.array([_g1, _g3, _g5, _g7, _g9])
g*= n1*_chi0/2
return g
def H_isotherm(self, T):
'''
Calculates the reduced height of the atmosphere,
assuming an isotherm distribution.
Parameters
----------
T : float
Atmospheric temperature in Kelvin
Returns
-------
H : float
Reduced height of the atmosphere in meters
'''
_kb = 1.38064852e-23 # kg m2 s-2 K-1
_m = 4.809651698e-26 # kg (weight per molecule of air, assuming dry air)
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
return (_kb*T) / (_m * _g0_local)
def matharExponential(self, n1, zenith, reduced_height=None):
'''
Model on atmospheric refraction described by Richard Mathar in
"A Barometric Exponential Model of the Atmosphere’s Refractive Index: ZenithAngles and Second Order Aberration in the Entrance Pupil" (2020)
ArXiv id: 2004.11808 (https://arxiv.org/abs/2004.11808)
The code in this function is adapted from eqs 3, 16, 17 and 18 in this
work. It is an adaptation of the RefrExpo.cxx C++ file written by Mathar.
This model assumes the refractive index, n, is related to the susceptibility,
chi, following: n(r) = sqrt(1+chi(r)) (eq. 9).
R. Mathar then assumes that the susceptibility is proportional to the
air density and reduced height, following eq. 10:
chi(r) = chi0 * exp(-(r-rho)/K).
The refraction is described using a Taylor expansion; R in terms of
the odd powers of the tangent of the observed zenith angle (eq. 3), not
unlike Oriani's theorem. This requires coefficients g_i.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest at the observer
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if reduced_height:
_H = reduced_height
else:
_H = self.H
_zenith = np.deg2rad(zenith)
_rc = self.rc # rho
# Atmospheric refraction in radians
_gamma = self.gammaCoefficients(n1, reduced_height=_H)
_R = sum([_gamma[i]*np.tan(_zenith)**(2*i+1) for i in range(len(_gamma))])
return np.degrees(_R)
def oriani(self, n1, zenith, reduced_height=None):
'''
Classic refraction formula with two tan(z) terms. This does not
assume any information about the structure of the atmosphere.
Found with Oriani's theorem.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if reduced_height:
H = reduced_height
else:
H = self.H
_a = n1 - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3
return np.degrees(_R)
def planeParallel(self, n1, zenith):
'''
Refraction based on a flat atmosphere, easily derived from Snell's law.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
_R = np.arcsin(n1 * np.sin( np.deg2rad(zenith) )) - np.deg2rad(zenith)
return np.degrees(_R)
def psi(self, x):
'''
Submethod for corbard(). Described explicitly in Corbard et al.,2019.
Parameters
----------
x : float
Input of the function
Returns
-------
y : float
Output of the function
'''
_f = lambda a: np.exp(-a**2)
_int = integrate.quad(_f, x, np.inf)
return np.exp(x**2) * _int[0]
def refractionIntegral(self, n1, zenith, R0=None, useStandardAtmosphere=True,
heightData=None, rhoData=None):
'''
Using data that gives temperature, pressure and density as a
function of height, the gladstone-dale relation is invoked to
calculate the path of a light ray along the atmosphere.
This is allows us to also include atmospheric activity in
different atmosphere layers.
Recommended use with the US Standard Atmosphere from 1976.
USSA1976 data generated from code supplied by http://www.pdas.com/atmos.html
The method described in eq. 3 in Auer & Standish (2000) is used for the
calculation.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest at the observer
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
R0 : float (optional)
Local radius of curvature of the Earth in km.
useStandardAtmosphere : bool
Flag to use the included USSA1976 data
heightData : 1D Array
If "useStandardAtmosphere" is False, the function will ask
for alternative atmospheric data to integrate over. This
gives the altitudinal points
rhoData : 1D Array
If useStandardAtmosphere is False, the function will ask
for alternative atmospheric data to integrate over. This
gives the atmospheric density at the points defined by
"heightData".
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if not R0:
R0 = self.rc
@@ -121,16 +463,16 @@ class refraction(Observatory):
_Dens = rhoData
_h = heightData
z0 = np.deg2rad(z0)
zenith = np.deg2rad(zenith)
_Dens = np.asarray(_Dens)
_h = np.asarray(_h)
_R = R0 + _h * 1000
# Gladstone-Dale relation between refractive index and density
_n_h = 1 + (n0 - 1) / _Dens[0] * _Dens
_n_h = 1 + (n1 - 1) / _Dens[0] * _Dens
# Calculation of the zenith angle for given height and refractive index, from the refractive invariant
_z = np.arcsin( (n0 * R0) / (_n_h * _R) * np.sin(z0) )
_z = np.arcsin( (n1 * R0) / (_n_h * _R) * np.sin(zenith) )
# Calculation of log space derivative of n and r
_lnn = np.log(_n_h)
@@ -146,32 +488,35 @@ class refraction(Observatory):
return np.degrees(np.sum(_Integrand))
def psi(self, x):
_f = lambda a: np.exp(-a**2)
_int = integrate.quad(_f, x, np.inf)
return np.exp(x**2) * _int[0]
def set_rc(self):
def setReducedHeight(self, p, rho):
'''
Returns the radius of the curvature at the observer in km,
taking into account the latitude of the observer and the corresponding
ellipsoidality of the earth.
Sets the reduced atmospheric height.
Parameters
----------
p : float
Atmospheric pressure in Pascal
rho : float
Density of the atmospheric air at the observer
'''
_A = 6378.137 # km
_B = 6356.752 # km
_phi = np.deg2rad(self.lat)
_rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
return _rc0 + self.h/1000
self.H = self.set_H(p, rho) # Reduced height of the atmosphere assuming ideal gas law
def set_H(self, P, rho):
def set_H(self, p, rho):
'''
Calculates the reduced height of the atmosphere, assuming ideal gas law.
Parameters
----------
p : float
Atmospheric pressure in Pascal
rho : float
Density of the atmospheric air at the observer
Returns
-------
H : float
Reduced height of the atmosphere in meters
'''
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
@@ -181,40 +526,56 @@ class refraction(Observatory):
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
_g = _g0_local - (3.0877e-6 - 4.3e-9 * np.sin(_phi)**2) * self.h + 7.2e-13 * self.h**2
return P / (rho * _g)
return p / (rho * _g)
def H_isotherm(self, T):
'''
Calculates the reduced height of the atmosphere, assuming an isotherm distribution.
'''
_kb = 1.38064852e-23 # kg m2 s-2 K-1
_m = 4.809651698e-26 # kg (weight per molecule of air, assuming dry air)
def set_rc(self):
'''
Returns the radius of the curvature at the observer in km,
taking into account the latitude of the observer and the corresponding
ellipsoidality of the earth.
Returns
-------
rc : float
Local radius of curvature of the Earth in kilometers.
'''
_A = 6378.137 # km
_B = 6356.752 # km
_phi = np.deg2rad(self.lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
return (_kb*T) / (_m * _g0_local)
_rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
return _rc0 + self.h/1000
def cassiniError(self, n, dn, zenith, dz=0, reduced_height=None):
def tan5(self, n1, zenith, reduced_height=None):
'''
Calculates the uncertainty in the refraction angle in degrees.
Same as oriani's theorem, but including a higher order term.
Found in Corbard et al., 2019.
Parameters
----------
n1 : float
Refractive index at the wavelength of interest
zenith : float
Angle of observation in degrees.
zenith = 0 means the observation is directly overhead.
reduced_height : float (optional)
Reduced height of the atmosphere. Default value is calculated
from the object attributes.
Returns
-------
R : float
Atmospheric refraction in degrees
'''
if reduced_height:
_H = reduced_height
H = reduced_height
else:
_H = self.H
_zenith = np.deg2rad(zenith)
_dz = np.deg2rad(dz)
_R = self.rc
_dRdn = (_R * np.sin(_zenith)) / ((_R + _H) * np.sqrt(1 - ((n**2 * _R**2 * np.sin(_zenith)**2)/(_R + _H)**2)))
H = self.H
_dRdz = (_R * np.cos(_zenith)) / (_R + _H) * ( n / np.sqrt(1-(n**2 * _R**2 * np.sin(_zenith)**2)/(_R + _H)**2) - 1 / np.sqrt(1-(_R**2 * np.sin(_zenith)**2)/(_R + _H)**2) )
_a = n1 - 1
_b = H / self.rc
_R = _a * (1 - _b) * np.tan( np.deg2rad(zenith) ) - _a * (_b - _a/2) * np.tan( np.deg2rad(zenith) )**3 + 3 * _a * (_b - _a/2)**2 * np.tan( np.deg2rad(zenith) )**5
return np.degrees(_R)
_dR = np.sqrt( (_dRdn * dn)**2 + (_dRdz * _dz)**2)
return np.degrees(_dR)
+353 -84
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env Python3
import numpy as np
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@@ -10,28 +9,41 @@ import numpy as np
atmospheric dispersion, given a set of conditions.
'''
def BarrellAndSears(l, T=288.15, p=101325, H=0):
def BarrellAndSears(l, T=288.15, p=101325, RH=0, f=None):
'''
From Barrell & Sears, 1939
Equation 7.7, page 52
For atmospheric air (i.e. CO2 = 300 ppm)
This equation is applicable for the ranges T=10-30C and p=720-800 mmHg.
Input:
l - Wavelength in microns
T - Temperature in Kelvin
p - Atmospheric pressure in Pa
H - fractional humidity (0=<H=<1)
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pascal
RH : float
Relative humidity (0=<H=<1)
f : float
Water vapour pressure in Pascal
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
if f is None:
f = RH * svp(T)
f_mmHg = hPa2mmHg(f/100)
T = K2C(T)
P = hPA2mmHg(p/100)
R = (0.378125 + 0.0021414 / l**2 + 0.00001793 / l**4) * (P*(1 + (1.049 - 0.0157*T) * P * 1e-6)/(1 + 0.003661*T)) - (0.0624 - 0.000680 / l**2) * H / (1 + 0.003661*T)
P = hPa2mmHg(p/100)
R = (0.378125 + 0.0021414 / l**2 + 0.00001793 / l**4) * (P*(1 + (1.049 - 0.0157*T) * P * 1e-6)/(1 + 0.003661*T)) - (0.0624 - 0.000680 / l**2) * f_mmHg / (1 + 0.003661*T)
n = R/1e6 + 1
return n
def Ciddor(l, CO2=450):
'''
From Ciddor, 1996
@@ -39,11 +51,17 @@ def Ciddor(l, CO2=450):
At default conditions only. For more flexibility use
Observatory.n_tph()
Input:
l - Wavelength in microns
Parameters
----------
l : float
Wavelength in microns
CO2 : float
CO2 density in parts per million
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
s = 1 / l
R = 5792105 / (238.0185 - s**2) + 167917 / (57.362 - s**2)
@@ -52,90 +70,180 @@ def Ciddor(l, CO2=450):
n = R/1e8 + 1
return n
def Edlen1953(l):
def Edlen1953(l, T=288.15, p=101325, RH=0, f=None):
'''
From Edlen, 1953
Equation 2
At standard conditions only
At standard conditions only.
Following Filippenko (1982), eqs. 2 & 3 we also include
the modification proposed by Barrell in 1951.
Input:
l - Wavelength in microns
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pascal
RH : float
Relative humidity (0=<H=<1)
f : float
Water vapor pressure in Pascal
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
s = 1 / l
R = 6432.8 + 2949810 / (146 - s**2) + 25540/(41-s**2)
n = R/1e8 + 1
n_standard = R/1e8 + 1
pHg = hPa2mmHg(p/100)
Tc = K2C(T)
if f is None:
f = RH * svp(T)
f_mmHg = hPa2mmHg(f/100)
# Changes in temperature and pressure, from Barrell (1951)
n_tp = R/1e8 * (pHg * (1 + (1.049 - 0.0157*Tc) * 1e-6 * pHg ) ) / ( 720.883 * (1 + 0.003661 * Tc)) + 1
# Correction for water vapour pressure
n_svp = (n_tp - 1) * 1e6 - ( 0.0624 - 0.000680 * s**2 ) / (1 + 0.003661 * Tc) * f_mmHg
n = n_svp/1e6 + 1
return n
def Edlen1966(l):
def Edlen1966(l, T=288.15, p=101325, RH=0, CO2=300, f=None):
'''
From Edlen, 1966
Equation 1
Equations 1, 12, 17, 22
At standard conditions only
Input:
l - Wavelength in microns
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pascal
RH : float
Relative humidity (0=<H=<1)
CO2 : float
CO2 density in parts per million
f : float
Water vapor pressure in Pascal
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
s = 1 / l
R = 8342.13 + 2406030 / (130 - s**2) + 15997 / (38.9 - s**2)
n = R/1e8 + 1
# Influence of temperature and pressure, eq. 12
Tc = K2C(T)
Ptorr = Pa2Torr(p)
R_tp = ( Ptorr * R/1e8 ) / 720.775 * (1 + Ptorr * (0.817 - 0.0133 * Tc) * 1e-6) / ( 1 + 0.0036610 * Tc)
# Allow for a CO2 fraction, eq. 17, but we include T and P dependence.
# This is okay from "Asthe refractivity of CO, is about 50% higher than
# that of air this CO,-content increases the refractivity over that of
# CO2-free air by approximately 0.5 x 0.0003 x (n-1)s" and "As the
# density factor of CO, is not much different from that of air this ratio r
# can be taken as independent of t and p ."
R_x = R_tp * (1 + 0.540 * (CO2/1e6 - 0.0003))
n_x = R_x + 1
# Correction for water vapour, eq. 22
if f is None:
f = RH * svp(T)
ftorr = Pa2Torr(f)
n = n_x - ftorr * (5.7224 - 0.0457 * s**2) * 1e-8
return n
def Owens(l, T=288.15, p=101325, H=0):
def Owens(l, T=288.15, p=101325, RH=0):
'''
From Owens, 1967
Equation 32
Note that the pressure P and H are in torricelli (Torr),
however, this is almost precisely 1 Torr = 1 mmHg
Input:
l - wavelength in micron
T - Temperature in Kelvin
p - Atmospheric pressure in Pa
H - Humdity (0<=H<=1)
Parameters
----------
l : float
wavelength in micron
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pa
RH : float
Relative humdity (0<=H<=1)
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
P = hPA2mmHg(p/100)
P = hPa2mmHg(p/100)
T = K2C(T)
s = 1 / l
R = 8342.13 + 2406030 / (130 - s**2) + 15997 / (38.9 - s**2) * (P/720.775) * ((1 + P * (0.817 - 0.0133) * 1e-6)/(1 + 0.0036610*T)) - H * (5.722 - 0.0457*s**2)
R = 8342.13 + 2406030 / (130 - s**2) + 15997 / (38.9 - s**2) * (P/720.775) * ((1 + P * (0.817 - 0.0133) * 1e-6)/(1 + 0.0036610*T)) - RH * (5.722 - 0.0457*s**2)
n = R/1e8 + 1
return n
def BonschPotulski(l, T=288.15, p=101325, CO2=450):
def BonschPotulski(l, T=288.15, p=101325, RH=0, CO2=400, f=None):
'''
From Bonsch & Potulski, 1998
Equations 6, 7, 8
Equations 6, 7, 8 and 9a
Input:
l - Wavelength in microns
T - Temperature in Kelvin
p - Atmospheric pressure in Pa
CO2 - Particles per million of CO2
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pa
RH : float
Relative humidity (0<=H<=1)
CO2 : float
Particles per million of CO2
f : float
Water vapor pressure in Pascal
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
x = CO2 / 1e6
T = K2C(T)
Tc = K2C(T)
s = 1 / l
# Rs = 8092.33 + 2333983 / (130 - s**2) + 15518 / (38.9 - s**2)
Rs = 8091.37 + 2333983 / (130 - s**2) + 15518 / (38.9 - s**2)
Rs = Rs / 1e8
Rx = Rs * (1 + 0.5327 * (x - 0.0004))
Rtp = Rx * p / 93214.60 * (1 + 1e-8 * (0.5953 - 0.009876 * T)*p) / (1 + 0.0036610 * T)
n = Rtp + 1
Rtp = Rx * p / 93214.60 * (1 + 1e-8 * (0.5953 - 0.009876 * Tc)*p) / (1 + 0.0036610 * Tc)
if f is None:
f = RH * svp(T)
n_tp = Rtp + 1
n = n_tp - f * (3.8020 - 0.0384 * s**2) * 1e-10
return n
def BirchDowns(l):
'''
From Birch & Downs, 1993 and 1994 (correction)
@@ -143,17 +251,22 @@ def BirchDowns(l):
Only at standard conditions
Input:
l - wavelength in micron
Parameters
----------
l : float
wavelength in micron
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
s = 1 / l
R = 8342.54 + 2406147 / (130 - s**2) + 15998 / (38.9-s**2)
n = R/1e8 + 1
return n
def PeckReeder(l):
'''
From Peck & Reeder, 1972
@@ -161,11 +274,15 @@ def PeckReeder(l):
Only at standard conditions
Input:
l - wavelength in micron
Parameters
----------
l : float
wavelength in micron
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
s = 1 / l
R = 8060.51 + 2480990 / (132.274 - s**2) + 17455.7 / (39.32957 - s**2)
@@ -173,25 +290,106 @@ def PeckReeder(l):
return n
def slalib(l, T=288.15, p=101325, H=0, h=0, lat=-24, tlr=0.0065, eps=1e-10):
def HohenkerkAndSinclair(l, T=288.15, p=101325, RH=0, h=0, lat=0):
'''
Calcalation of the refractive index following Hohenkerk & Sinclair
(1985), which is the same one as used in SLALIB.
This equation is based on the Barrell & Sears equation, but with
slightly different values. The refractive index values here seem
to be a bit higher than the original Barrel and Sears values.
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pa
RH : float
fractional humidity (0=<H=<1)
h : float
Altitude of the observer (default=0 m)
lat : float
Latitude of the observer (default=-24)
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
# Some constants
alpha = 0.0065
delta = 18.36
R = 8314.36
Md = 28.966
Mw = 18.016
# Pressure in millibars
P0 = p/100
# Local acceleration due to gravity
g_local = 9.784 * (1 - 0.0026 * np.cos(2*np.deg2rad(lat))) - 0.00000028*h
gamma = g_local * Md / (R * alpha)
# Partial pressure of water vapour at observer
Pw = RH * (T/247.1)**18.36
# Refractivity * 1e6 at reference conditions
A = (287.604 + 1.6288*l**-2 + 0.0136*l**-4) * 273.15/1013.25
# A = (287.5522 + 1.6288*l**-2 + 0.0136*l**-4) * 273.15/1013.25
P = (P0 + (1 - Mw/Md) * gamma/(delta - gamma) * Pw )\
- (1 - Mw/Md) * gamma / (delta-gamma) * Pw
# Refractive index at given conditions
n = 1 + 1e-6 * (A * P - 11.2684*Pw)/T
return n
def slalib(l, T=288.15, p=101325, RH=0, h=0, lat=-24, tlr=0.0065, eps=1e-10):
'''
NOTE: This function still works for now, but is considered DEPRECATED.
Use BarrellAndSears() or HohenkerkAndSinclair() instead.
SLALIB uses the same atmospheric model as ZEMAX
based on the works of Hohenkerk & Sinclair and Seidelmann.
They take the B&S refraction formula as a basis.
Requires the pySLALIB package to work.
Input:
l - Wavelength in microns
T - Temperature in Kelvin
p - Atmospheric pressure in Pa
H - fractional humidity (0=<H=<1)
h - Altitude of the observer (default=0 m)
lat - Latitude of the observer (default=-24)
tlr - temperature lapse rate (default=0.0065)
eps - precision at which the calculation is aborted (default=1e-10)
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pa
RH : float
Relative humidity (0=<H=<1)
h : float
Altitude of the observer (default=0 m)
lat : float
Latitude of the observer (default=-24)
tlr : float
temperature lapse rate (default=0.0065)
eps : float
precision at which the calculation is aborted (default=1e-10)
Returns:
n - Refractive index of atmospheric air at the given conditions
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
Notes
-----
1. I don't understand why this function works. It calculates the
refraction and somehow returns the refractive index. Perhaps
this is a very particular set of input parameters that make it
work. It does closely follow BarrellAndSears() for standard
conditions.
'''
try:
from pyslalib import slalib
@@ -205,27 +403,37 @@ def slalib(l, T=288.15, p=101325, H=0, h=0, lat=-24, tlr=0.0065, eps=1e-10):
if type(l) == float:
l = [l]
for wl in l:
R.append(slalib.sla_refro(zObs, h, T, p, H, wl, lat, tlr, eps))
R.append(slalib.sla_refro(zObs, h, T, p, RH, wl, lat, tlr, eps))
if len(l) == 1:
return R[0]+1
else:
return np.asarray(R)+1
def Mathar(l, T=288.15, p=101325, H=0):
def Mathar(l, T=288.15, p=101325, RH=0):
'''
Refractivity model by Mathar (2007), valid above 1.4 microns
Code taken and modified from:
Code taken from
https://github.com/polyanskiy/refractiveindex.info-scripts/blob/master/scripts/Mathar%202007%20-%20Air%201.3-2.5um.py
(better known from refractiveindex.info). The code was subsequently
minimally modified to make it suitable for this package.
Input:
l - Wavelength in microns
T - Temperature in Kelvin
p - Atmospheric pressure in Pa
H - fractional humidity (0=<H=<1)
Returns:
n - Refractive index of atmospheric air at the given conditions
Parameters
----------
l : float
Wavelength in microns
T : float
Temperature in Kelvin
p : float
Atmospheric pressure in Pa
H : float
fractional humidity (0=<H=<1)
Returns
-------
n : float
Refractive index of atmospheric air at the given conditions
'''
if l < 1.3 or l > 24:
print('Using the Mathar model at this wavelength (l={:.3f}) is not recommended!'.format(l))
@@ -326,12 +534,12 @@ def Mathar(l, T=288.15, p=101325, H=0):
σref = 1e4/20 # cm^−1, reference wavenumber
Tref = 273.15+17.5 # K, reference temperature
pref = 75000 # Pa, reference pressure
Href = 10 #%, reference humdity in percent
Href = 10 # %, reference humdity in percent
########################################################################
s = 1e4/l # cm^-1
n = 1
H = H*100
H = RH*100
for j in range(0, 6):
n += ( cref[j] + cT[j]*(1/T-1/Tref) + cTT[j]*(1/T-1/Tref)**2
@@ -343,14 +551,75 @@ def Mathar(l, T=288.15, p=101325, H=0):
return n
# Other necessary conversion or calculations
def K2C(K):
'''
Kelvin to Celsius conversion
Parameters
----------
K : float
Temperature in Kelvin
Returns
-------
C : float
Temperature in Celsius
'''
return K - 273.15
def hPA2mmHg(hPa):
def hPa2mmHg(hPa):
'''
hPa to mmHg conversion
Parameters
----------
hPa : float
Atmospheric pressure in hPa (hecto Pascal)
Returns
-------
mmHg : float
Atmospheric pressure in mmHg
'''
return 0.7500616827 * hPa
def Pa2Torr(Pa):
'''
Pa to Torr conversion
Parameters
----------
Pa : float
Atmospheric pressure in Pa (Pascal)
Returns
-------
Torr : float
Atmospheric pressure in Torr
'''
return Pa * 760/101325
def svp(T):
'''
Saturation vapour pressure in Pa.
Parameters
----------
T : float
Atmospheric temperature in Kelvin
Returns
-------
svp : float
Saturation vapour pressure in Pa
'''
_A = 1.2378847e-5 # K^-2
_B = -1.9121316e-2 # K^-2
_C = 33.93711047
_D = -6.3431645e3 # K^-2
return np.exp(_A*T**2 + _B*T + _C + _D/T)
+1
View File
@@ -0,0 +1 @@
include AstroAtmosphere/data/1976USSA.txt
+171 -36
View File
@@ -1,66 +1,134 @@
# AstroAtmosphere
**Note:**
A manuscript of this atmospheric code is in preparation and will be submitted to the Monthly Notices of the Royal Astronomical Society (MNRAS) journal before the end of the year (2019).
If you use this code for your project, please acknowledge this by making a reference to this paper in due course. You can contact me, the author, for the submission and review status.
It would be appreciated if a reference to the following work, for which this package was originally build, is included whenever this code is used for a publication:
*Quantification of the expected residual dispersion of the MICADO Near-IR imaging instrument*, van den Born & Jellema, 2020, MNRAS, DOI: [10.1093/mnras/staa1870](https://doi.org/10.1093/mnras/staa1870).
Author info: *Joost van den Born*, *born@astron.nl*
**Author info**: *Joost van den Born*, *born@astron.nl*
## Example
*Below you can find a set of models describing the atmospheric refraction and dispersion.*
A minimal working example to calculate the atmospheric dispersion at Cerro Armazones between 1.49 and 1.78 micron.
**Contents:**
- AstroAtmosphere : Python package that can be used to calculate the atmospheric refraction and dispersion, the main content of this repository.
- atmospheric refraction example: jupyter notebook example
- `setup.py` : Install script to install the AstroAtmosphere package locally.
```python
from AstroAtmosphere import *
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
z0 = 30
# Initializing dispersion model
at = Observatory()
# Calculating indices of refraction for l1 and l2
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
# Initializing refraction model and setting the reduced height
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
# Calculation of the atmopheric dipsersion
atm_dispersion = disp.cassini(n1, n2, z0) * 3.6e6
print ('The dispersion is %.03f milli arc seconds' %(atm_dispersion))
```
## Installation
The package is available through Git-Lab and through PyPI. The package may be installed using the `setup.py` file, but easier is to install using `pip`.
```bash
pip install AstroAtmosphere
```
**On the contents of the AstroAtmosphere package**
## Contents of the AstroAtmosphere package
The package contains the following files:
- `/data/1976USSA.txt` : Data file of the 1976 US Standard Atmosphere.
- `ciddorModel.py` : Contains the `Observatory()` class to calculate the refractive index of atmospheric air, using the Ciddor (1996) model.
- `dispersionModels.py` : Contains various atmospheric models that can be used to calculate the atmospheric dispersion
- `examples.py` : Contains several quick functions, e.g. to quickly calculate the refractive index.
- `refractionModels.py` : Contains various atmospheric models that can be used to calculate the atmospheric refraction
- `misc.py` : Contains several miscellaneous functions, e.g. to quickly calculate the refractive index.
- `neoslalib.py` : Contains a Python native port of the `pyslalib.SLA_REFRO()` routine, translated from the original FORTRAN code.
- `observatories.py` : Contains a dictionary with the average atmospheric conditions for various large observatories around the world.
- `refractionModels.py` : Contains various atmospheric models that can be used to calculate the atmospheric refraction
- `refractivityModels.py` : Contains various functions from literature to calculate the refractive index. Some examples of works included are Ciddor (1996), Barrell & Sears (1939), SLALIB, Owens (1967) and Edlen (1966).
**On the dispersion model:**
Dispersion model from Ciddor, 1996, calculates the index of refraction of air using the following parameters:
- Temperature
- Pressure
- Relative humidity
- CO2 density
## The Ciddor refractivity model
The refractivity model from Ciddor, 1996, calculates the index of refraction of air using the following parameters:
- T - Temperature in Kelvin
- p - Pressure in Pascal
- RH - Relative humidity in fractional units
- xc - CO2 density in parts per million
It is invoked by first initializing an `Observatory()` object. The atmospheric conditions can be parsed into the `n_tph()` function.
```python
at = Observatory()
n = at.n_tph(l=0.55, T=273.15, p=101325, RH=0.2, xc=300)
```
Basic functionality of other refractivity models is included as well and is discussed below.
## The atmospheric refraction and atmospheric dispersion models
If the atmospheric refraction is denoted as R(n), then atmospheric dispersion is defined as R(n1) - R(n2). Thus they are very similar in function and use.
**On the atmospheric refraction / atmospheric dispersion models:**
Available models:
- Full integration using the refractive integral (requires detailed information about the atmosphere, e.g. `AstroAtmosphere/data/1976USSA.txt`)
- Plane-parallel atmosphere refraction model
- Cassini's homogeneous atmosphere refraction model
- Oriani's theorem ( Atan(z) + Btan^3(z) )
- Oriani's theorem expanded ( Atan(z) + Btan^3(z) + Ctan^5(z) )
- Error function refraction model
The available models in this package:
- `refractionIntegral()` - Full integration using the refractive integral (requires detailed information about the atmosphere, e.g. `AstroAtmosphere/data/1976USSA.txt`)
- `planeParallel()` - Plane-parallel atmosphere refraction model
- `cassini()` - Cassini's homogeneous atmosphere refraction model
- `oriani()` - Oriani's theorem ( Atan(z) + Btan^3(z) )
- `tan5()` - Oriani's theorem expanded ( Atan(z) + Btan^3(z) + Ctan^5(z) )
- `corbard()` - Error function refraction model
- `matharExponential()` - Barometric exponential model by R. Mathar.
The package also includes a python native port of the `SLA_REFRO()` FORTRAN routine, ported from pyslalib. See below for more information.
Input parameters for the spherical atmosphere models (all but the first):
- Temperature [K]
- Pressure [Pa]
- Relative humidity
- CO2 density [ppm]
- Latitude [deg]
- Height above sea level [m]
- Wavelength(s) [um]
- Zenith angle [deg]
- T - Temperature [K]
- p - Pressure [Pa]
- RH - Relative humidity
- xc - CO2 density [ppm]
- lat - Latitude [deg]
- h - Height above sea level [m]
- l - Wavelength(s) [um]
- z - Zenith angle [deg]
An example - To calculate the atmospheric refraction we set up a `refraction()` object. This requires the latitude and altitude of the observer. The atmospheric dispersion is calculated similarly, but by exchanging the `refraction()` object by a `dispersion()` object and parsing two refractive indices into the `cassini()` function.
```python
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=101325, T=273.15, RH=0.2, xc=300)
# Initializing refraction model and setting the reduced height
ref = refraction(lat=30, h=2200)
ref.setReducedHeight(p=101325, rho=rho)
# Calculating the atmospheric refraction
atm_ref = ref.cassini(n, zenith=30)
```
**Analytical error propagation**
## Analytical error propagation
Full analytical error propagation is available for the Ciddor dispersion model (`Observatory.dn_tph()`), for the Cassini refraction model (`refraction.cassiniError()`) and for the Cassini dispersion model (`dispersion.cassiniError()`).
The results agree well with a Monte Carlo simulation, suggesting that if additional errors have not been considered, they are of neglible importance.
@@ -97,3 +165,70 @@ Cassini dispersion model:
| dCO2 | CO2 density | ppm |
| dz | Zenith angle | deg |
An example of its use is given in the code snippet here:
```python
# Initializing dispersion object
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
# Atmospheric dispersion
dispersion_obs = disp.cassini(n1, n2, zenith_obs)
# Uncertainty in the atmospheric dispersion
dispersion_unc = disp.cassiniError(zenith_obs, l1, l2, T, P, H, xc,
dl1=0.001, dl2=0.001, dT=0.2, dP=30, dRH=0.03, dCO2=25, dz=1/3600) * 3.6e6
```
## Other refractivity models
The package also includes limited functionality of other refractivity models. These are not as extensive, work only under standard atmospheric conditions, or over a limited wavelength range, compared to the Ciddor model.
Included are
| Model name | Function | Remarks |
| ---------- | -------- | ------- |
| Barrell & Sears (1939) | `BarrellAndSears()` | Outdated model, especially poor for short wavelengths. |
| Ciddor (1996) | `Ciddor()` | Only at standard atmosphere. CO2 density can be changed. |
| Edlen (1953) | `Edlen1953()` | The equation by Edlèn, 1953, combined with modification from Barrel (1951), to include temperature, pressure and humidity dependence. |
| Edlen (1966) | `Edlen1966()` | Only at standard atmosphere. Function requires wavelength only. |
| Owens (1967) | `Owens()` | - |
| Bonsch & Potulski (1998) | `BonschPotulski()` | Function does not include humidity |
| Birch & Downs (1994) | `BirchDowns()` | Only at standard atmosphere. Function requires wavelength only. |
| Peck & Reeder (1972) | `PeckReeder()` | Only at standard atmosphere. Function requires wavelength only. |
| Hohenkerk & Sinclair (1985) | `HohenkerkAndSinclair()` | Based on Barrell & Sears equation. Generally, gives slightly larger values.|
| Hohenkerk & Sinclair (1985) | `slalib()` | Uses pySLALIB to retrieve the refractive index at standard atmospheric conditions.|
The last function extracts the refractive index from the `pyslalib.sla_refro()` function and thus requires [pyslalib](https://pypi.org/project/pySLALIB/). This is an optional dependency for the setup of AstroAtmosphere.
## Quick functions
Some functions can make use of the `observatories` dictionary as input. These include default average conditions of the atmosphere at these locations. When these are used, only the wavelength and zenith angle are required to calculate the refraction and dispersion.
```python
# Quick refractive index
nq = quick_refractive_index(l=1.49, conditions='STANDARD')
# Quick refraction
rq = quick_refraction(l1, zenith_obs, conditions='STANDARD')
# Quick dispersion
dq = quick_dispersion(l1, l2, zenith_obs, conditions='STANDARD')
```
Presently, several observatories are known. These can all be found in the `observatories` dictionary. Contact the author to add additional standard conditions.
| Observatory | Flag | Notes |
| --------------------------------------------- | ----------------- | --------------------------------------------- |
| Standard conditions | `STANDARD` | |
| Cerro Armazones | `CERRO_ARMAZONES` | Location of the future ELT |
| Cerro Paranal | `CERRO_PARANAL` | Location of the VLT, VISTA, VST, NGTS and SSO |
| Roque de los Muchachos Observatory (La Palma) | `LA_PALMA` | GTC, INT, WHT, NOT, and others. |
| La Silla Observatory | `LA_SILLA` | ESO 3.6m telescope, NTG, BlackGEM and others. |
| Las Campanas Observatory | `LAS_CAMPANAS` | Site of the future GMT. |
## Other
This package also contains a python native port of the `SLA_REFRO()` routine of the pyslalib package, which was originally written in FORTRAN by P.T. Wallace. It can be called directly using `sla_refro()`. When using the same units as the other refraction functions, you might prefer using `slalib_refraction()` or `slalib_dispersion()`.
Finally, the dispersion model used by Alexei Filippenko in 1982 is also included. This model is a combination of the (modified) Edlèn equation and a plane-parallel atmospheric geometry. This calculation can be accessed by calling the `Filippenko1982()` function.
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env Python3
import numpy as np
from scipy.optimize import fsolve
class ADC:
def __init__(self, wedge=(1.6554, 6.28927, -1.48306)):
self.p1 = prism('S-LAH71', 15, wedge[0], wedge[1])
self.p2 = prism('S-FPL51', 15, wedge[1], wedge[2])
self.p3 = prism('S-FPL51', 15, wedge[2], wedge[1])
self.p4 = prism('S-LAH71', 15, wedge[1], wedge[0])
self.theta = 90
self.FR = 0
def setOrientation(self, theta):
theta = float(theta)
self.p1.setPrismOrientation(theta)
self.p2.setPrismOrientation(theta)
self.p3.setPrismOrientation(180-1*theta)
self.p4.setPrismOrientation(180-1*theta)
self.theta = theta
def setFieldRotation(self, FR):
FR = float(FR)
self.p1.setPrismOrientation(self.theta-FR)
self.p2.setPrismOrientation(self.theta-FR)
self.p3.setPrismOrientation(180-1*self.theta-FR)
self.p4.setPrismOrientation(180-1*self.theta-FR)
self.FR = FR
def calculateDispersion(self, l1, l2, hx=0, hy=0, refraction=False):
'''
Calculates the dispersion for the ADC using the current position,
for a normalized on sky field angle (hx, hy). Default field position
is in the center of the FoV.
'''
_prismList = [self.p1, self.p2, self.p3, self.p4]
_d1x_list, _d1y_list = [], []
_pupilRatio = 0.07943/38.542
hx = hx * 7.5e-3
hy = hy * 7.5e-3
ix = hx * 1/_pupilRatio
iy = hy * 1/_pupilRatio
for l in [l1,l2]:
_deltaListX = np.zeros(len(_prismList))
_deltaListY = np.zeros(len(_prismList))
for p in range(4):
_n_g = _prismList[p].indexOfRefraction(l)
_b1x = _prismList[p].tilt1x
_b1y = _prismList[p].tilt1y
_b2x = _prismList[p].tilt2x
_b2y = _prismList[p].tilt2y
if p==0:
_t_iz_n_x = ix # Zero before first prism
_t_iz_n_y = iy # Zero before first prism
# X direction
_t_i1_n_x = _t_iz_n_x
_d1x = deviationCalculation3(_t_i1_n_x, _b1x, _b2x, _n_g)
_t_tz_n_x = _t_iz_n_x - _d1x
_t_iz_n_x = _t_tz_n_x # Set output to new input (for the next prism)
# Y direction
_t_i1_n_y = _t_iz_n_y
_d1y = deviationCalculation3(_t_i1_n_y, _b1y, _b2y, _n_g)
_t_tz_n_y = _t_iz_n_y - _d1y
_t_iz_n_y = _t_tz_n_y # Set output to new input (for the next prism)
# Adding the deviations to the list with delta terms
_deltaListX[p] = _d1x
_deltaListY[p] = _d1y
_d1x_list.append(_deltaListX.sum())
_d1y_list.append(_deltaListY.sum())
self.dispX = ( _d1x_list[1] - _d1x_list[0] ) * 3600 * 1000 * _pupilRatio
self.dispY = ( _d1y_list[1] - _d1y_list[0] ) * 3600 * 1000 * _pupilRatio
if refraction:
rx = ( _d1x_list[0] ) * 3600 * 1000 * _pupilRatio
ry = ( _d1y_list[0] ) * 3600 * 1000 * _pupilRatio
return rx, ry
return self.dispX, self.dispY
def optimize(self, inputDispersion, l1, l2, x0=50):
_solver = fsolve(optimizeFunction, x0=x0, args=(self, inputDispersion, l1, l2))
return _solver[0]
class prism:
def __init__(self, glass, thickness, tilt1, tilt2=None):
self.glass = glass
self.thickness = thickness
self.tilt1x = tilt1
self.tilt1y = 0
self.tilt2x = tilt2
self.tilt2y = 0
self.theta = 90
# Remember original tilts for reset:
self.tilt1x_OG = tilt1
self.tilt1y_OG = 0
self.tilt2x_OG = tilt2
self.tilt2y_OG = 0
def indexOfRefraction(self, l):
if self.glass == 'S-FPL51':
a = [1.170105050E+00, 4.757107830E-02, 7.638324450E-01]
b = [6.162039240E-03, 2.633728760E-02, 1.418826420E+02]
elif self.glass == 'S-LAH71':
a = [1.982800310E+00, 3.167584500E-01, 2.444726460E+00]
b = [1.189874560E-02, 5.271560010E-02, 2.132206970E+02]
return dispersionFormula(l, a, b)
def setPrismOrientation(self, theta):
self.tilt1x, self.tilt1y = tiltRotation(self.tilt1x_OG, self.tilt1y_OG, theta)
self.tilt2x, self.tilt2y = tiltRotation(self.tilt2x_OG, self.tilt2y_OG, theta)
self.theta = theta
def tiltRotation(tx,ty, theta):
theta = np.deg2rad(theta-90)
tx_new = tx * np.cos(theta) - ty * np.sin(theta)
ty_new = tx * np.sin(theta) + ty * np.cos(theta)
return tx_new, ty_new
def dispersionFormula(l, a, b):
n = np.sqrt( (a[0]* l**2)/(l**2 - b[0]) + (a[1] * l**2) / (l**2 - b[1]) + (a[2] * l**2) / (l**2 - b[2]) + 1)
return n
# def deviationCalculation(t_i1, b1, b2, n):
# Returns deviation of the ray after the prism with apex angle alpha
# Units: degrees
# DO NOT USE!
# alpha = b2 - b1
# delta = t_i1 + np.arcsin( np.sin(np.deg2rad(alpha)) * np.sqrt(n**2-(np.sin(np.deg2rad(t_i1)))**2) - np.sin(np.deg2rad(t_i1)) * np.cos(np.deg2rad(alpha)) )*180/np.pi - alpha
# return delta
def deviationCalculation2(t_i1, b1, b2, n):
# Returns deviation of the ray after the prism with apex angle alpha
# only now using Snell's law only
# Units: degrees
t_i1 = np.deg2rad(t_i1)
b1 = np.deg2rad(b1)
b2 = np.deg2rad(b2)
t_i = b1 - t_i1
t_f = np.arcsin( 1 / n * np.sin( t_i ) )
t_f_z = b1 - t_f
t_i = b2 - t_f_z
t_f = np.arcsin( n / 1 * np.sin( t_i ) )
t_f_z = b2 - t_f
delta = t_i1 - t_f_z
delta = np.degrees(delta)
return delta
def deviationCalculation3(t_i1, b1, b2, n):
# Using the formula from Hagen & Tkazcyk (2011)
# Gives exactly the same results as deviationCaclulation2(), above
t_i1 = np.deg2rad(t_i1)
b1 = np.deg2rad(b1)
b2 = np.deg2rad(b2)
alpha = b2 - b1
delta = t_i1 - b2 - np.arcsin( n * np.sin( np.arcsin(1/n * np.sin(t_i1 - b1) ) - alpha ) )
return np.degrees(delta)
def optimizeFunction(theta, adcObject, inputDispersion, l1, l2, FR=None):
if FR:
FRdeg = np.deg2rad(FR) # 30 degrees field rotation
inputDispersionX = np.cos(FRdeg) * inputDispersion
inputDispersionY = np.sin(FRdeg) * inputDispersion
adcObject.setOrientation(theta)
ADCdisp = adcObject.calculateDispersion(l1,l2)
return np.sqrt((ADCdisp[1] - inputDispersionY)**2 + (ADCdisp[0] - inputDispersionX)**2)
else:
adcObject.setOrientation(theta)
ADCdisp = adcObject.calculateDispersion(l1,l2)
ADCdisp = ADCdisp[1]
return ADCdisp - inputDispersion
def angleFunc(x, a):
'''
Function to fit the ideal angle on
'''
return np.arccos(a * np.tan(np.deg2rad(x))) * 180/np.pi
File diff suppressed because it is too large. Load diff
+156
View File
@@ -0,0 +1,156 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Atmospheric Dispersion Corrector model\n",
"\n",
"This notebook explains how you can use the ADC.py analytic ADC model for some quick simulations. For the model to be representative, the following assumptions must apply:\n",
"* The ADC consists of two amici prisms, totaling 4 glass prisms in total\n",
"* The ADC is located in the pupil plane of the instrument (or very close to it)\n",
"* The rotation of the prisms is defined as follows: for $\\theta=0$ the ADC is in it's maximum dispersive configuration. For any other rotation angle, the top prism set rotates by $\\theta$ degrees and the bottom prism set also rotates by $\\theta$ degrees in opposite direction. The total relative angle is thus $2\\theta$. From this also follows that the minimum dispersion magnitude is reached at $\\theta=90$ degrees.\n",
"\n",
"The ADC.py model is really not made for very general use, so it will likely require some editing in the source code by the user to make it work for a different instrument than MICADO.\n",
"\n",
"\n",
"We first start by importing the necessary files. Here we'll use the `AstroAtmosphere` package to calculate the atmospheric dispersion, but a manual input is also possible."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import ADC\n",
"import AstroAtmosphere"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we calculate the atmospheric dispersion for a given set of conditions."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Atmospheric conditions\n",
"T = 288.15 \t# k\n",
"P = 101325 \t# Pa\n",
"H = 0.3\n",
"xc = 400 \t# ppm\n",
"lat = -25 \t\t# degrees\n",
"h = 2500 \t# m\n",
"l1 = 1.49 \t\t# micron, start H-band\n",
"l2 = 1.78 \t\t# micron, end H-band\n",
"zenith = 30 \t# degrees\n",
"\n",
"# Initializing dispersion model\n",
"at = AstroAtmosphere.Observatory()\n",
"rho = at.rho(p=P, T=T, RH=H, xc=xc)\n",
"disp = AstroAtmosphere.dispersion(lat, h)\n",
"disp.setReducedHeight(P, rho)\n",
"\n",
"# Calculation of indices of refraction at l1 and l2\n",
"n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)\n",
"n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)\n",
"\n",
"# Calculation of atmospheric dispersion in milli arc sec\n",
"dispersion = disp.cassini(n1, n2, zenith)*3.6e6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we initialize the ADC class. We can calculate the dispersion in sky angles, i.e. in terms of the Field of View of the telescope, for a given rotation of the ADC prisms. In this case, if we set $\\theta=30$ degrees, we find that the ADC produces 58 milli arc seconds of dispersion in H-band."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The dispersion of the ADC for 30 degrees of rotation: 58.011 mas\n"
]
}
],
"source": [
"# Initialize ADC object\n",
"adc = ADC.ADC()\n",
"\n",
"# Calculate the dispersion for a given rotation\n",
"theta = 30 # degrees\n",
"adc.setOrientation(theta)\n",
"adcDispersion = adc.calculateDispersion(l1,l2)[1]\n",
"print(\n",
" 'The dispersion of the ADC for {:.0f} degrees of rotation: {:.3f} mas'\\\n",
" .format(theta, adcDispersion))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, we're often much more interested in the optimum positioning of the ADC given an amount of atmospheric dispersion. This is done as follows."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The atmospheric dispersion: 24.771 mas\n",
"The optimum ADC angle in this case: 68.1 degrees\n",
"The residual dispersion: 0.000 mas\n"
]
}
],
"source": [
"# Optimize for atmospheric dispersion, given wavelength range {l1,l2}\n",
"adcOptimumAngle = adc.optimize(dispersion, l1, l2) \n",
"adcDispersionOptimized = adc.calculateDispersion(l1,l2)[1]\n",
"\n",
"print('The atmospheric dispersion: {:.3f} mas'.format(dispersion) )\n",
"print('The optimum ADC angle in this case: {:.1f} degrees'.format(adcOptimumAngle))\n",
"print('The residual dispersion: {:.3f} mas'.format(dispersion - adcDispersionOptimized))\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"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.7.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env Python3
import numpy as np
from AstroAtmosphere import *
import matplotlib.pyplot as plt
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: This script uses the equations of the paper to calculate the
residual dispersion due to the finite step size of the ADC.
'''
def angleFunc(x, a):
'''
Function to fit the ideal angle on
'''
return np.arccos(a * np.tan(np.deg2rad(x))) * 180/np.pi
def angleFuncDeriv(x, a):
return (a * (1+np.tan(np.deg2rad(x))**2)) / np.sqrt(1 - a**2 * np.tan(np.deg2rad(x))**2)
def cassiniDispersionDeriv(n1, n2, zenith, alpha):
a1 = n1 * alpha * np.cos(np.deg2rad(zenith)) / np.sqrt(1 - n1**2 * alpha**2 * np.sin(np.deg2rad(zenith))**2)
a2 = n2 * alpha * np.cos(np.deg2rad(zenith)) / np.sqrt(1 - n2**2 * alpha**2 * np.sin(np.deg2rad(zenith))**2)
return a1 - a2
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 30 # degrees
at = dispersion(lat, h)
at.setReducedHeight(P, at.rho(P, T, H, xc))
n1 = at.n_tph(l1, T, P, H, xc)
n2 = at.n_tph(l2, T, P, H, xc)
alpha = at.rc / (at.rc+at.h)
# c_fH = 0.499413 # From Zemax
c_fH = 0.46816061517198737 # Completely analytical cf
# f_fH = 0.46462883308144837 # Best fit to analytical ADC model
dispersion = at.cassini(n1, n2, zenith)*3.6e6
dispersionDeriv = cassiniDispersionDeriv(n1, n2, zenith, alpha)*3.6e6
optAngle = angleFunc(zenith, c_fH)
optAngleDeriv = angleFuncDeriv(zenith, c_fH)
maxDev = optAngleDeriv / dispersionDeriv
print('Zenith = %s degrees' %zenith)
print('Dispersion in mas: ', dispersion)
print('Dispersion/degree zenith at z=%s: %s mas/degree z' %(zenith, dispersionDeriv))
print('Optimum angle ADC: %s' %(optAngle))
print('Optimum angle ADC/degree zenith at z=%s: %s' %(zenith, optAngleDeriv))
print('Max deviation per mas dispersion: %s degrees' %(maxDev))
print('At designed stepsize at most %s mas of residual dispersion' %(1/maxDev*(360/1262.5)))
# Plotting as a function of zenith angle
dataList = []
for zenith in np.linspace(5,65,100):
dispersion = at.cassini(n1, n2, zenith)*3.6e6
dispersionDeriv = cassiniDispersionDeriv(n1, n2, zenith, alpha)*3.6e6
optAngle = angleFunc(zenith, c_fH)
optAngleDeriv = angleFuncDeriv(zenith, c_fH)
maxDev = optAngleDeriv / dispersionDeriv
dataList.append(1/maxDev*(360/1262.5))
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(np.linspace(5,65,100), dataList)
ax.grid(True, alpha=0.2)
ax.set_xlabel('Zenith (degrees)')
ax.set_ylabel('Residual dispersion (mas)')
plt.show()
@@ -0,0 +1,21 @@
NARR data provided by the NOAA/OAR/ESRL PSL, Boulder, Colorado, USA, from their Web
site at https://psl.noaa.gov/
The original "air.201806.nc" datafile was downloaded from
https://psl.noaa.gov/data/gridded/data.narr.pressure.html
Specific download link (~650 MB):
ftp://ftp.cdc.noaa.gov/Datasets/NARR/pressure/air.201806.nc [Last accessed on
2020-06-17]
The following files are a direct slice of this data:
- export_dist.txt
- export_heights.txt
- export_press_atmosphere.txt
- export_temp_atmosphere.txt
This data file was not included here, because it has a very niche use for the
AstroAtmosphere package and is a large file. The tradeoff seems to me to not very
favourable, and therefore it is omitted.
@@ -0,0 +1,97 @@
NCEP North American Regional Reanalysis (NARR) gridded weather data.
Data from https://www.esrl.noaa.gov/psd/data/gridded/data.narr.pressure.html
###############
# Header info #
###############
$ ncdump -h air.201806.nc
netcdf air.201806 {
dimensions:
time = UNLIMITED ; // (240 currently)
level = 29 ;
y = 277 ;
x = 349 ;
variables:
double time(time) ;
time:axis = "T" ;
time:coordinate_defines = "point" ;
time:delta_t = "0000-00-00 03:00:00" ;
time:long_name = "Time" ;
time:standard_name = "time" ;
time:units = "hours since 1800-1-1 00:00:0.0" ;
time:actual_range = 1914576., 1915293. ;
float level(level) ;
level:GRIB_id = 100s ;
level:GRIB_name = "hPa" ;
level:actual_range = 1000.f, 100.f ;
level:axis = "Z" ;
level:coordinate_defines = "point" ;
level:long_name = "Level" ;
level:positive = "down" ;
level:standard_name = "level" ;
level:units = "millibar" ;
float lat(y, x) ;
lat:axis = "Y" ;
lat:coordinate_defines = "point" ;
lat:long_name = "Latitude" ;
lat:standard_name = "latitude" ;
lat:units = "degrees_north" ;
float lon(y, x) ;
lon:axis = "X" ;
lon:coordinate_defines = "point" ;
lon:long_name = "Longitude" ;
lon:standard_name = "longitude" ;
lon:units = "degrees_east" ;
float y(y) ;
y:long_name = "northward distance from southwest corner of domain in projection coordinates" ;
y:standard_name = "projection_y_coordinate" ;
y:units = "m" ;
float x(x) ;
x:long_name = "eastward distance from southwest corner of domain in projection coordinates" ;
x:standard_name = "projection_x_coordinate" ;
x:units = "m" ;
int Lambert_Conformal ;
Lambert_Conformal:false_easting = 5632642.22547 ;
Lambert_Conformal:false_northing = 4612545.65137 ;
Lambert_Conformal:grid_mapping_name = "lambert_conformal_conic" ;
Lambert_Conformal:latitude_of_projection_origin = 50. ;
Lambert_Conformal:longitude_of_central_meridian = -107. ;
Lambert_Conformal:standard_parallel = 50., 50. ;
float air(time, level, y, x) ;
air:GRIB_id = 11s ;
air:GRIB_name = "TMP" ;
air:_FillValue = 9.96921e+36f ;
air:coordinates = "lat lon" ;
air:dataset = "NARR 3-hourly" ;
air:grid_mapping = "Lambert_Conformal" ;
air:level_desc = "Pressure Levels" ;
air:long_name = "3-hourly Air Temperature on Pressure Levels" ;
air:parent_stat = "Other" ;
air:standard_name = "air_temperature" ;
air:statistic = "Individual Obs" ;
air:units = "degK" ;
air:valid_range = 137.5f, 362.5f ;
air:var_desc = "Air temperature" ;
air:missing_value = -9.96921e+36f ;
air:actual_range = 187.1279f, 320.1487f ;
// global attributes:
:Conventions = "CF-1.2" ;
:centerlat = 50. ;
:centerlon = -107. ;
:comments = "" ;
:institution = "National Centers for Environmental Prediction" ;
:latcorners = 1.000001f, 0.897945f, 46.3544f, 46.63433f ;
:loncorners = -145.5f, -68.32005f, -2.569891f, 148.6418f ;
:platform = "Model" ;
:standardpar1 = 50. ;
:standardpar2 = 50.000001 ;
:title = "8x Daily NARR" ;
:history = "created Mon Jul 18 21:01:07 MDT 2016 by NOAA/ESRL/PSD" ;
:dataset_title = "NCEP North American Regional Reanalysis (NARR)" ;
:references = "https://www.esrl.noaa.gov/psd/data/gridded/data.narr.html" ;
:source = "http://www.emc.ncep.noaa.gov/mmb/rreanl/index.html" ;
}
@@ -0,0 +1 @@
[0.0, 0.23179527183433432, 0.46359054366866864, 0.6953858155030029, 0.9271810873373373, 1.1589763591716715, 1.3907716310060059, 1.6225669028403402, 1.8543621746746746, 2.086157446509009, 2.317952718343343, 2.5497479901776776, 2.7815432620120117, 3.0133385338463463, 3.2451338056806804, 3.476929077515015, 3.708724349349349, 3.9405196211836833, 4.172314893018018, 4.404110164852352, 4.635905436686686, 4.867700708521021, 5.099495980355355, 5.33129125218969, 5.5630865240240235, 5.794881795858358, 6.026677067692693, 6.258472339527026, 6.490267611361361, 6.7220628831956954, 6.95385815503003, 7.185653426864364, 7.417448698698698, 7.649243970533033, 7.8810392423673665, 8.112834514201701, 8.344629786036036, 8.57642505787037, 8.808220329704705, 9.04001560153904, 9.271810873373372, 9.503606145207707, 9.735401417042041, 9.967196688876376, 10.19899196071071, 10.430787232545045, 10.66258250437938, 10.894377776213712, 11.126173048048047, 11.357968319882382, 11.589763591716716, 11.82155886355105, 12.053354135385385, 12.28514940721972, 12.516944679054053, 12.748739950888387, 12.980535222722722, 13.212330494557056, 13.444125766391391, 13.675921038225725, 13.90771631006006, 14.139511581894393, 14.371306853728727, 14.603102125563062, 14.834897397397397, 15.066692669231731, 15.298487941066066, 15.5302832129004, 15.762078484734733, 15.993873756569068, 16.225669028403402, 16.45746430023774, 16.68925957207207, 16.921054843906404, 17.15285011574074, 17.384645387575073, 17.61644065940941, 17.848235931243742, 18.08003120307808, 18.31182647491241, 18.543621746746744, 18.77541701858108, 19.007212290415413, 19.23900756224975, 19.470802834084083, 19.70259810591842, 19.93439337775275, 20.166188649587085, 20.39798392142142, 20.629779193255754, 20.86157446509009, 21.093369736924423, 21.32516500875876, 21.556960280593092, 21.788755552427425, 22.02055082426176, 22.252346096096094, 22.48414136793043, 22.715936639764763, 22.9477319115991, 23.179527183433432, 23.411322455267765, 23.6431177271021, 23.874912998936434, 24.10670827077077, 24.338503542605103, 24.57029881443944, 24.802094086273772, 25.033889358108105, 25.26568462994244, 25.497479901776774, 25.72927517361111, 25.961070445445444, 26.19286571727978, 26.424660989114113, 26.656456260948445, 26.888251532782782, 27.120046804617115, 27.35184207645145, 27.583637348285784, 27.81543262012012, 28.047227891954453, 28.279023163788786, 28.510818435623122, 28.742613707457455, 28.97440897929179, 29.206204251126124, 29.43799952296046, 29.669794794794793, 29.901590066629126, 30.133385338463462, 30.365180610297795, 30.59697588213213, 30.828771153966464, 31.0605664258008, 31.292361697635133, 31.524156969469466, 31.755952241303802, 31.987747513138135, 32.21954278497247, 32.451338056806804, 32.68313332864114, 32.91492860047548, 33.146723872309806, 33.37851914414414, 33.61031441597848, 33.84210968781281, 34.073904959647145, 34.30570023148148, 34.53749550331582, 34.76929077515015, 35.00108604698448, 35.23288131881882, 35.46467659065315, 35.696471862487485, 35.92826713432182, 36.16006240615616, 36.39185767799049, 36.62365294982482, 36.85544822165916, 37.08724349349349, 37.319038765327825, 37.55083403716216, 37.7826293089965, 38.01442458083083, 38.24621985266516, 38.4780151244995, 38.70981039633383, 38.941605668168165, 39.1734009400025, 39.40519621183684, 39.63699148367117, 39.8687867555055, 40.10058202733984, 40.33237729917417, 40.564172571008505, 40.79596784284284, 41.02776311467718, 41.25955838651151, 41.491353658345844, 41.72314893018018, 41.95494420201451, 42.186739473848846, 42.41853474568318, 42.65033001751752, 42.88212528935185, 43.113920561186184, 43.34571583302052, 43.57751110485485, 43.809306376689186, 44.04110164852352, 44.27289692035786, 44.50469219219219, 44.736487464026524, 44.96828273586086, 45.20007800769519, 45.431873279529526, 45.66366855136386, 45.8954638231982, 46.12725909503253, 46.359054366866864, 46.5908496387012, 46.82264491053553, 47.054440182369866, 47.2862354542042, 47.51803072603854, 47.74982599787287, 47.981621269707205, 48.21341654154154, 48.44521181337587, 48.67700708521021, 48.90880235704454, 49.14059762887888, 49.37239290071321, 49.604188172547545, 49.83598344438188, 50.06777871621621, 50.29957398805055, 50.53136925988488, 50.76316453171922, 50.99495980355355, 51.226755075387885, 51.45855034722222, 51.69034561905655, 51.92214089089089, 52.15393616272522, 52.38573143455956, 52.61752670639389, 52.849321978228225, 53.08111725006256, 53.31291252189689, 53.54470779373123, 53.776503065565564, 54.0082983373999, 54.24009360923423, 54.471888881068566, 54.7036841529029, 54.93547942473723, 55.16727469657157, 55.399069968405904, 55.63086524024024, 55.86266051207457, 56.094455783908906, 56.32625105574324, 56.55804632757757, 56.78984159941191, 57.021636871246244, 57.25343214308058, 57.48522741491491, 57.717022686749246, 57.94881795858358, 58.18061323041791, 58.41240850225225, 58.644203774086584, 58.87599904592092, 59.10779431775525, 59.339589589589586, 59.57138486142392, 59.80318013325825, 60Line truncated
@@ -0,0 +1 @@
[0.0, 0.21553884711779447, 0.43107769423558895, 0.6466165413533834, 0.8621553884711779, 1.0776942355889725, 1.2932330827067668, 1.5087719298245612, 1.7243107769423558, 1.9398496240601504, 2.155388471177945, 2.370927318295739, 2.5864661654135337, 2.8020050125313283, 3.0175438596491224, 3.233082706766917, 3.4486215538847116, 3.664160401002506, 3.8796992481203008, 4.095238095238095, 4.31077694235589, 4.526315789473684, 4.741854636591478, 4.957393483709273, 5.172932330827067, 5.3884711779448615, 5.6040100250626566, 5.819548872180451, 6.035087719298245, 6.25062656641604, 6.466165413533834, 6.681704260651629, 6.897243107769423, 7.112781954887217, 7.328320802005012, 7.5438596491228065, 7.7593984962406015, 7.974937343358396, 8.19047619047619, 8.406015037593985, 8.62155388471178, 8.837092731829573, 9.052631578947368, 9.268170426065163, 9.483709273182956, 9.699248120300751, 9.914786967418546, 10.13032581453634, 10.345864661654135, 10.56140350877193, 10.776942355889723, 10.992481203007518, 11.208020050125313, 11.423558897243106, 11.639097744360901, 11.854636591478696, 12.07017543859649, 12.285714285714285, 12.50125313283208, 12.716791979949875, 12.932330827067668, 13.147869674185463, 13.363408521303258, 13.578947368421051, 13.794486215538846, 14.010025062656641, 14.225563909774435, 14.44110275689223, 14.656641604010025, 14.872180451127818, 15.087719298245613, 15.303258145363408, 15.518796992481203, 15.734335839598996, 15.949874686716791, 16.165413533834585, 16.38095238095238, 16.596491228070175, 16.81203007518797, 17.027568922305765, 17.24310776942356, 17.45864661654135, 17.674185463659146, 17.88972431077694, 18.105263157894736, 18.32080200501253, 18.536340852130326, 18.751879699248118, 18.967418546365913, 19.182957393483708, 19.398496240601503, 19.614035087719298, 19.829573934837093, 20.045112781954884, 20.26065162907268, 20.476190476190474, 20.69172932330827, 20.907268170426065, 21.12280701754386, 21.338345864661655, 21.553884711779446, 21.76942355889724, 21.984962406015036, 22.20050125313283, 22.416040100250626, 22.63157894736842, 22.847117794486213, 23.062656641604008, 23.278195488721803, 23.493734335839598, 23.709273182957393, 23.924812030075188, 24.14035087719298, 24.355889724310774, 24.57142857142857, 24.786967418546364, 25.00250626566416, 25.218045112781954, 25.43358395989975, 25.64912280701754, 25.864661654135336, 26.08020050125313, 26.295739348370926, 26.51127819548872, 26.726817042606516, 26.942355889724308, 27.157894736842103, 27.373433583959898, 27.588972431077693, 27.804511278195488, 28.020050125313283, 28.235588972431078, 28.45112781954887, 28.666666666666664, 28.88220551378446, 29.097744360902254, 29.31328320802005, 29.528822055137844, 29.744360902255636, 29.95989974937343, 30.175438596491226, 30.39097744360902, 30.606516290726816, 30.82205513784461, 31.037593984962406, 31.253132832080198, 31.468671679197993, 31.684210526315788, 31.899749373433583, 32.11528822055138, 32.33082706766917, 32.54636591478697, 32.76190476190476, 32.97744360902256, 33.19298245614035, 33.40852130325814, 33.62406015037594, 33.83959899749373, 34.05513784461153, 34.27067669172932, 34.48621553884712, 34.70175438596491, 34.9172932330827, 35.1328320802005, 35.34837092731829, 35.56390977443609, 35.77944862155388, 35.994987468671674, 36.21052631578947, 36.426065162907264, 36.64160401002506, 36.857142857142854, 37.07268170426065, 37.288220551378444, 37.503759398496236, 37.719298245614034, 37.934837092731826, 38.150375939849624, 38.365914786967416, 38.581453634085214, 38.796992481203006, 39.0125313283208, 39.228070175438596, 39.44360902255639, 39.659147869674186, 39.87468671679198, 40.09022556390977, 40.30576441102757, 40.52130325814536, 40.73684210526316, 40.95238095238095, 41.16791979949875, 41.38345864661654, 41.59899749373433, 41.81453634085213, 42.03007518796992, 42.24561403508772, 42.46115288220551, 42.67669172932331, 42.8922305764411, 43.10776942355889, 43.32330827067669, 43.53884711779448, 43.75438596491228, 43.96992481203007, 44.185463659147864, 44.40100250626566, 44.616541353383454, 44.83208020050125, 45.047619047619044, 45.26315789473684, 45.478696741854634, 45.694235588972425, 45.909774436090224, 46.125313283208015, 46.340852130325814, 46.556390977443606, 46.771929824561404, 46.987468671679196, 47.20300751879699, 47.418546365914786, 47.63408521303258, 47.849624060150376, 48.06516290726817, 48.28070175438596, 48.49624060150376, 48.71177944862155, 48.92731829573935, 49.14285714285714, 49.35839598997494, 49.57393483709273, 49.78947368421052, 50.00501253132832, 50.22055137844611, 50.43609022556391, 50.6516290726817, 50.8671679197995, 51.08270676691729, 51.29824561403508, 51.51378446115288, 51.72932330827067, 51.94486215538847, 52.16040100250626, 52.37593984962406, 52.59147869674185, 52.807017543859644, 53.02255639097744, 53.238095238095234, 53.45363408521303, 53.669172932330824, 53.884711779448615, 54.100250626566414, 54.315789473684205, 54.531328320802004, 54.746867167919795, 54.962406015037594, 55.177944862155385, 55.39348370Line truncated
@@ -0,0 +1 @@
[[100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 99999.99999999999, 99999.99999999999, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.00000000001, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.00000000001, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.00000000001, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 99999.99999999999, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100000.0, 100Line truncated
@@ -0,0 +1 @@
[[284.050537109375, 284.05081584207153, 284.0510945747681, 284.0513733074647, 284.0516520401612, 284.0519307728577, 284.0522095055543, 284.05248823825076, 284.05276697094735, 284.05304570364393, 284.05332443634046, 284.053603169037, 284.0538819017336, 284.05416063443016, 284.0544393671267, 284.05471809982316, 284.05499683251975, 284.0552755652163, 284.0555542979128, 284.05583303060934, 284.0561117633059, 284.05639049600245, 284.05666922869904, 284.0569479613956, 284.05722669409215, 284.0575054267887, 284.0577841594852, 284.05806289218174, 284.05834162487827, 284.0586203575748, 284.0588990902713, 284.05917782296797, 284.0594565556645, 284.059735288361, 284.06001402105755, 284.06029275375414, 284.06057148645067, 284.06085021914726, 284.0611289518438, 284.0614076845403, 284.06168641723684, 284.06196514993337, 284.06224388262996, 284.0625226153265, 284.062801348023, 284.0630800807196, 284.06335881341613, 284.0636375461127, 284.06391627880924, 284.0641950115058, 284.06447374420236, 284.06475247689883, 284.0650312095954, 284.06530994229195, 284.06558867498853, 284.06586740768506, 284.0661461403816, 284.0664248730782, 284.0667036057747, 284.06698233847123, 284.0672610711678, 284.06753980386435, 284.0678185365609, 284.0680972692574, 284.06837600195394, 284.0686547346505, 284.06893346734705, 284.06921220004364, 284.06949093274017, 284.0697696654367, 284.0700483981333, 284.0703271308298, 284.0706058635264, 284.07088459622287, 284.0711633289194, 284.071442061616, 284.0717207943125, 284.0719995270091, 284.0722782597056, 284.07255699240216, 284.0728357250987, 284.07311445779527, 284.07339319049186, 284.0736719231884, 284.0739506558849, 284.07422938858144, 284.07450812127803, 284.0747868539745, 284.0750655866711, 284.0753443193677, 284.0756230520642, 284.07590178476073, 284.07618051745726, 284.0764592501538, 284.0767379828504, 284.0770167155469, 284.0772954482435, 284.07757418094, 284.07785291363655, 284.0781316463331, 284.07841037902966, 284.0786891117262, 284.0789678444228, 284.0792465771193, 284.07952530981584, 284.07980404251236, 284.08008277520895, 284.0803615079055, 284.080640240602, 284.0809189732986, 284.0811977059951, 284.08147643869165, 284.0817551713882, 284.08203390408477, 284.0823126367813, 284.0825913694779, 284.08287010217435, 284.08314883487094, 284.08342756756747, 284.08370630026405, 284.0839850329606, 284.0842637656571, 284.0845424983537, 284.0848212310502, 284.08509996374676, 284.08537869644334, 284.0856574291398, 284.0859361618364, 284.08621489453293, 284.0864936272295, 284.08677235992604, 284.0870510926226, 284.08732982531916, 284.0876085580157, 284.0878872907122, 284.0881660234088, 284.08844475610533, 284.08872348880186, 284.0890022214984, 284.089280954195, 284.0895596868915, 284.08983841958803, 284.0901171522846, 284.09039588498115, 284.0906746176777, 284.09095335037426, 284.0912320830708, 284.0915108157673, 284.09178954846385, 284.09206828116044, 284.09234701385697, 284.0926257465535, 284.0929044792501, 284.0931832119466, 284.09346194464314, 284.0937406773397, 284.09401941003625, 284.09429814273284, 284.0945768754293, 284.0948556081259, 284.0951343408224, 284.095413073519, 284.09569180621554, 284.09597053891207, 284.0962492716086, 284.0965280043052, 284.0968067370017, 284.0970854696983, 284.09736420239483, 284.106526646489, 284.1175328703515, 284.1285390942141, 284.13954531807667, 284.15055154193925, 284.1615577658018, 284.17256398966435, 284.1835702135269, 284.19457643738946, 284.205582661252, 284.21658888511456, 284.22759510897714, 284.23860133283966, 284.24960755670224, 284.2606137805648, 284.2716200044274, 284.2826262282899, 284.2936324521525, 284.3046386760151, 284.31564489987767, 284.3266511237402, 284.33765734760277, 284.3486635714653, 284.35966979532793, 284.37067601919046, 284.38168224305304, 284.39268846691556, 284.40369469077814, 284.4147009146407, 284.4257071385033, 284.4367133623658, 284.4477195862284, 284.458725810091, 284.4697320339535, 284.4807382578161, 284.49174448167867, 284.5027507055412, 284.51375692940377, 284.52476315326635, 284.53576937712893, 284.54677560099145, 284.55778182485403, 284.5687880487166, 284.5797942725792, 284.5908004964417, 284.6018067203043, 284.6128129441669, 284.6238191680294, 284.634825391892, 284.64583161575456, 284.6568378396171, 284.66784406347966, 284.67885028734224, 284.68985651120477, 284.70086273506735, 284.7118689589299, 284.7228751827925, 284.73388140665503, 284.7448876305176, 284.7558938543802, 284.76690007824277, 284.7779063021053, 284.7889125259679, 284.7999187498304, 284.810924973693, 284.82193119755556, 284.8329374214181, 284.84394364528066, 284.8549498691432, 284.86595609300576, 284.87696231686834, 284.8879685407309, 284.89897476459345, 284.909980988456, 284.9209872123186, 284.9319934361812, 284.9429996600437, 284.9540058839063, 284.96501210776887, 284.9760183316314, 284.987024555494, 284.99803077935655, 285.0090370032191, 285.0200432270817, 285.03104945094424, 285.04205567480676, 285.05306189866934, 285.0640681225319, 285.07507434639444, Line truncated
@@ -0,0 +1,168 @@
#!/usr/bin/env Python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib import cm
from matplotlib.colors import LogNorm
from AstroAtmosphere import *
from scipy import interpolate
import json
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: This script uses the NCEP NARR data to model a real structured atmosphere
from data with a resolution of 2.5 km per data point. We interpolate this
data to a higher resolution.
Then a full integration over this atmosphere is done and compared to the
values for the refraction and dispersion if we had only taken the atmospheric
conditions directly at the observatory.
'''
# Load a variable from a json text file.
def loadVariableFromTXT(filename):
variableFile = open(filename, 'r')
variable = json.load(variableFile)
variableFile.close()
return np.asarray(variable)
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
# h, _, _, _, Temp, Press, Dens, vSound, _,_ = np.genfromtxt('si1py_modified.txt', unpack=True, skip_header=6)
dist = loadVariableFromTXT('export_dist.txt')
heights = loadVariableFromTXT('export_heights.txt')
press = loadVariableFromTXT('export_press_atmosphere.txt')
temp = loadVariableFromTXT('export_temp_atmosphere.txt')
print("Loading complete!")
# Conditions at Cerro Armazones
T = temp[0,0] # k
P = press[0,0]# Pa
H = 0.0
xc = 314 # ppm
lat = -24.5983 # degrees
h0 = 0 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 60 # degrees
# Calculate the index of refraction
at = Observatory()
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
RHlist = np.asarray([H]*len(press[:,0]))
xclist = np.asarray([xc]*len(press[:,0]))
Dens = at.rho(p=press[:,0], T=temp[:,0], RH=RHlist, xc=xclist)
refr = refraction(lat, h0)
refr.setReducedHeight(P, rho)
disp = dispersion(lat, h0)
disp.setReducedHeight(P, rho)
# We must create a cube of weather data:
# We assume a flat atmosphere for now, so that h does not need to be adjusted
# x_max = 200 km, gives zenith_max = ~66.7 degrees
# Every 20 km in horizontal direction a new perturbation and interpolation
# for every 500 m.
x_max = dist[-1] # km
Temp_cube_n = []
Press_cube_n = []
for x in range(len(dist)):
Temp_cube_n.append(temp[:,0])
Press_cube_n.append(press[:,0])
Temp_cube_n = np.asarray(Temp_cube_n).T
Press_cube_n = np.asarray(Press_cube_n).T
fig = plt.figure()
ax1 = fig.add_subplot(211)
im = ax1.pcolormesh(dist,heights, temp, cmap=cm.coolwarm)
fig.colorbar(im, ax=ax1, label='Temperature (K)')
ax1.set_aspect(1)
ax1.set_ylabel('Height (km)')
ax2 = fig.add_subplot(212)
im2 = ax2.pcolormesh(dist, heights, press, cmap=cm.coolwarm, norm=LogNorm())
ax2.set_aspect(1)
ax2.set_xlabel('Horizontal distance (km)')
ax2.set_ylabel('Height (km)')
fig.colorbar(im2, ax=ax2, label='Pressure (Pa)')
plt.tight_layout()
plt.show(block=False)
# Now for a given zenith angle we need to find the temp and press along the line of sight
f_Temp_2d = interpolate.interp2d(dist, heights, temp, kind='linear')
f_Press_2d = interpolate.interp2d(dist, heights, press, kind='linear')
f_Temp_2d_n = interpolate.interp2d(dist, heights, Temp_cube_n, kind='linear')
f_Press_2d_n = interpolate.interp2d(dist, heights, Press_cube_n, kind='linear')
print('Interpolation complete!')
zenithRange = np.linspace(30,65,10)
refrDiff, dispDiff = [], []
for zenith in zenithRange:
z = np.deg2rad(zenith)
y_los = heights
x_los = np.linspace(0, heights.max()*np.tan(z), len(heights))
Temp_los, Press_los = [], []
Temp_los_n, Press_los_n = [], []
for i in range(len(heights)):
Temp_los.append(f_Temp_2d(x_los[i], y_los[i])[0])
Press_los.append(f_Press_2d(x_los[i], y_los[i])[0])
Temp_los_n.append(f_Temp_2d_n(x_los[i], y_los[i])[0])
Press_los_n.append(f_Press_2d_n(x_los[i], y_los[i])[0])
Temp_los = np.asarray(Temp_los)
Press_los = np.asarray(Press_los)
Temp_los_n = np.asarray(Temp_los_n)
Press_los_n = np.asarray(Press_los_n)
rho_los = at.rho(p=Press_los, T=Temp_los, RH=H, xc=xc)
rho_los_n = at.rho(p=Press_los_n, T=Temp_los_n, RH=H, xc=xc)
refrInt = refr.refractionIntegral(n1, z, useStandardAtmosphere=False, heightData=heights, rhoData=rho_los_n)*3600*1000
refrInt_pert = refr.refractionIntegral(n1, z, useStandardAtmosphere=False, heightData=heights, rhoData=rho_los)*3600*1000
refrDiff.append(refrInt_pert - refrInt)
dispInt = disp.refractionIntegral(n1, n2, z, useStandardAtmosphere=False, heightData=heights, rhoData=rho_los_n)*3.6e6
dispInt_pert = disp.refractionIntegral(n1, n2, z, useStandardAtmosphere=False, heightData=heights, rhoData=rho_los)*3.6e6
dispDiff.append(dispInt_pert - dispInt)
fig3 = plt.figure(figsize=(9,7))
ax1 = fig3.add_subplot(211)
ax2 = fig3.add_subplot(212)
ax1.tick_params(axis ='both', direction='in', which='both', labelsize=16)
ax2.tick_params(axis ='both', direction='in', which='both', labelsize=16)
ax1.grid(True, alpha=0.3)#, which='major')
ax2.grid(True, alpha=0.3)#, which='major')
ax1.plot(zenithRange, np.log10(np.abs(refrDiff)), 'k-', linewidth=2)
ax2.plot(zenithRange, np.log10(np.abs(dispDiff)), 'k-', linewidth=2)
ax1.set_xlabel('Zenith (degrees)', fontsize=18)
ax2.set_xlabel('Zenith (degrees)', fontsize=18)
ax1.set_ylabel('$\log_{10}($|$R - R_{o}$|$)$ (mas)', fontsize=18)
ax2.set_ylabel('$\log_{10}($|$\Delta R - \Delta R_{o}$|$)$ (mas)', fontsize=18)
plt.tight_layout()
plt.show()
@@ -0,0 +1,251 @@
#!/usr/bin/env Python3
import numpy as np
import sys
from AstroAtmosphere import *
import matplotlib.pyplot as plt
from netCDF4 import Dataset
from pdb import set_trace
from matplotlib import cm
from scipy.interpolate import interp1d, interp2d
from matplotlib.colors import LogNorm
import json
# Saves a variable, preferably a list or string to json text file.
# Useful for long simulations, where you're making a plot
def saveVariableToTXT(variable, filename):
variableFile = open(filename, 'w')
if type(variable) == np.ndarray:
json.dump(variable.tolist(), variableFile)
elif type(variable) in [list, str, float]:
json.dump(variable, variableFile)
else:
print('Not a list, array or string. Check variable type.')
variableFile.close()
return
# Load a variable from a json text file.
def loadVariableFromTXT(filename):
variableFile = open(filename, 'r')
variable = json.load(variableFile)
variableFile.close()
return variable
def calculateDistanceResolution(lat, lon):
# Calculate local radius of the earth
_A = 6378.137 # km
_B = 6356.752 # km
_phi = np.deg2rad(lat)
_rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
# Calculate circumference of the earth at constant latitude.
latR = 2 * np.pi * np.cos(_phi) * _rc0
distRes = latR/360 * lon
return distRes
def H_isotherm(T, lat):
'''
Calculates the reduced height of the atmosphere, assuming an isotherm distribution.
'''
_kb = 1.38064852e-23 # kg m2 s-2 K-1
_m = 4.809651698e-26 # kg (weight per molecule of air, assuming dry air)
_phi = np.deg2rad(lat)
_c1 = 5.2790414e-3
_c2 = 2.32718e-5
_c3 = 1.262e-7
_c4 = 7e-10
_g0 = 9.780327 # m/s^2
_g0_local = _g0 * (1 + _c1 * np.sin(_phi)**2 + _c2 * np.sin(_phi)**4 + _c3 * np.sin(_phi)**6 + _c4 * np.sin(_phi)**8)
return (_kb*T) / (_m * _g0_local)
def calculatePressure(Th, P0, h, lat):
redH = H_isotherm(Th, lat)
Ph = P0 * np.exp(-h/redH)
return Ph
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
# Import the real data
nc = Dataset('air.201806.nc', 'r')
latitudes = np.asarray(nc.variables['lat'][:])
longitudes = np.asarray(nc.variables['lon'][:])
air_temp = nc.variables['air']
air_press = np.asarray(nc.variables['level'])
at = Observatory()
# Parameters at Cerro Armazones
T = air_temp[0,:,0,0][0] # k
P = air_press[0] # Pa
H = 0
xc = 450 # ppm
lat = 24.44768
h = 0 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
rho = at.rho(p=P, T=T, RH=H, xc=xc)
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
air_dens = at.rho(p=air_press, T=air_temp[0,:,0,0], RH=np.asarray([0]*len(air_press)), xc=np.asarray([0]*len(air_press)))
heights = disp.calcHeight(air_press, air_temp[0,:,0,0], RH=np.asarray([0]*len(air_press)), xc=np.asarray([0]*len(air_press)), P0=air_press[0]) / 1e3
distance_lat = calculateDistanceResolution(latitudes[100], longitudes[100,:]) - calculateDistanceResolution(latitudes[100,0], longitudes[100,0])
temp_selection_ground = air_temp[0,0,100,:]
temp_selection_upper = air_temp[0,-1,100,:]
print(distance_lat[1])
#########################################################
# Extrapolating using the U.S. Standard Atmosphere 1976.
# The end point of the real data is used to shift the USSA data accordingly (linearly)
h_ussa1976, _, _, _, air_temp_ussa1976, air_press_ussa1976,_,_,_,_ = np.genfromtxt('../US1976 Standard Atmosphere/si1py_modified.txt', unpack=True, skip_header=6)
temp_ip = interp1d(h_ussa1976, air_temp_ussa1976)
press_ip = interp1d(h_ussa1976, air_press_ussa1976)
ussa1976_start = temp_ip(heights[-1])
diff_temp_list = temp_selection_upper - ussa1976_start
start_i = np.argmax(h_ussa1976 > heights[-1])
ussa1976_temp_array = np.tile(air_temp_ussa1976[start_i:], (len(temp_selection_upper),1))
ussa1976_temp_array = np.asarray([ussa1976_temp_array[i] + diff_temp_list[i] for i in range(len(diff_temp_list))]).T
# Combining the (masked) arrays
new_heights = np.append(heights, h_ussa1976[start_i:])
tempData = np.append(air_temp[0,:,100,:], ussa1976_temp_array, axis=0)
tempData_mask = np.append(air_temp[0,:,100,:].mask, np.zeros(ussa1976_temp_array.shape), axis=0)
tempData = np.ma.masked_array(tempData, mask=tempData_mask)
# Now the same for the pressure data
# Combining the (masked) arrays
start_i = np.argmax(h_ussa1976 > heights[-1])
new_heights = np.append(heights, h_ussa1976[start_i:])
pressData = []
for i in range(len(temp_selection_upper)):
calc_press_data = calculatePressure(Th=ussa1976_temp_array[:,i], P0=air_press[0], h=h_ussa1976[start_i:]*1000, lat=lat)
pressData_i = np.append(air_press*100, calc_press_data*100, axis=0)
pressData.append(pressData_i)
pressData = np.asarray(pressData).T
#########################################################
# We'd now like to export a subset of the atmospheric data for a distance of ~200 km.
# Let's use interpolation to create a uniform gridded dataset
export_heights = np.linspace(0,new_heights.max(),400)
export_dist = np.linspace(0,distance_lat[136]-distance_lat[130],1000)
atmosphere_temp_func = interp2d(distance_lat[130:136]-distance_lat[130], new_heights, tempData[:,130:136], kind='linear')
export_temp_atmosphere = atmosphere_temp_func(export_dist, export_heights)
atmosphere_press_func = interp2d(distance_lat[130:136]-distance_lat[130], new_heights, pressData[:,130:136], kind='linear')
export_press_atmosphere = atmosphere_press_func(export_dist, export_heights)
# saveVariableToTXT(export_heights, 'export_heights.txt')
# saveVariableToTXT(export_dist, 'export_dist.txt')
# saveVariableToTXT(export_temp_atmosphere, 'export_temp_atmosphere.txt')
# saveVariableToTXT(export_press_atmosphere, 'export_press_atmosphere.txt')
# Plot the real data + extrapolated standard atmosphere.
# fig = plt.figure()
# ax = fig.add_subplot(211)
# ax2 = fig.add_subplot(212)
# im1 = ax.pcolormesh(export_dist, export_heights, export_temp_atmosphere, cmap=cm.coolwarm)
# im2 = ax2.pcolormesh(export_dist, export_heights, export_press_atmosphere, cmap=cm.coolwarm, norm=LogNorm())
# ax2.set_xlabel('Lateral distance (km)')
# ax.set_ylabel('Height (km)')
# ax2.set_ylabel('Height (km)')
# cbar = fig.colorbar(im1, ax=ax, label='Temperature (K)')
# lbls = cbar.get_ticks()
# cbar.ax.invert_yaxis()
# cbar.set_ticks(lbls[::-1])
# cbar2 = fig.colorbar(im2, ax=ax2, label='Pressure (Pa)')
# lbls = cbar2.get_ticks()
# cbar2.ax.invert_yaxis()
# cbar2.set_ticks(lbls[::-1])
# plt.tight_layout()
# plt.show(block=False)
# set_trace()
# # Plot the real data + extrapolated standard atmosphere.
# fig = plt.figure()
# ax = fig.add_subplot(211)
# ax2 = fig.add_subplot(212)
# im1 = ax.imshow(tempData[:,130:137], cmap=cm.coolwarm, interpolation= 'bicubic', aspect='auto', origin='lower')
# # im1 = ax.pcolormesh(distance_lat[130:137], new_heights, tempData[:,130:137], cmap=cm.coolwarm)
# im2 = ax2.pcolormesh(distance_lat, new_heights, pressData, cmap=cm.coolwarm, norm=LogNorm())
# ax2.set_xlabel('Lateral distance (km)')
# ax.set_ylabel('Height (km)')
# ax2.set_ylabel('Height (km)')
# cbar = fig.colorbar(im1, ax=ax, label='Temperature (K)')
# lbls = cbar.get_ticks()
# cbar.ax.invert_yaxis()
# cbar.set_ticks(lbls[::-1])
# cbar2 = fig.colorbar(im2, ax=ax2, label='Pressure (Pa)')
# lbls = cbar2.get_ticks()
# cbar2.ax.invert_yaxis()
# cbar2.set_ticks(lbls[::-1])
# plt.tight_layout()
# plt.show(block=False)
# Plot the real data and properties
fig = plt.figure(figsize=(9,4))
fig2 = plt.figure()
ax1 = fig2.add_subplot(221)
ax2 = fig2.add_subplot(222)
ax3 = fig2.add_subplot(223)
ax4 = fig.add_subplot(121)
ax5 = fig2.add_subplot(224)
ax6 = fig.add_subplot(122)
ax1.grid(alpha=0.3)
ax2.grid(alpha=0.3)
ax3.grid(alpha=0.3)
ax5.grid(alpha=0.3)
ax1.plot(air_temp[0,:,0,0], heights)
ax1.set_xlabel('Temperature (K)')
ax1.set_ylabel('Height (km)')
ax2.plot(air_press, heights)
ax2.set_xlabel('Pressure (Pa)')
ax2.set_ylabel('Height (km)')
ax3.plot(air_dens, heights)
ax3.set_xlabel('Density (g cm$^{-3}$)')
ax3.set_ylabel('Height (km)')
im = ax4.imshow(np.rot90(air_temp[0,0,:,:].T), aspect=1, cmap='jet')
ax4.set_yticks([])
ax4.set_xticks([])
# fig.colorbar(im, ax=ax4, label='Temperature (K)')
ax5.plot(distance_lat, temp_selection_ground, 'b-', label='ground')
ax5.plot(distance_lat, temp_selection_upper, 'r-', label='upper atmosphere')
ax5.legend()
ax5.set_ylabel('Temperature (K)')
ax5.set_xlabel('Distance (km)')
im2 = ax6.imshow(air_temp[0,:,100,:], aspect='auto', origin='lower', interpolation='bilinear', extent=[0,distance_lat[-1],0,heights[-1] ], cmap='jet')
ax6.set_xlabel('Distance (km)', fontsize=18)
ax6.set_ylabel('Height (km)', fontsize=18)
cbar = fig.colorbar(im2, ax=ax6)#, label='Temperature (K)')
cbar.ax.tick_params(labelsize=16)
cbar.set_label('Temperature (K)', size=18)
lbls = cbar.get_ticks()
cbar.ax.invert_yaxis()
cbar.set_ticks(lbls[::-1])
ax6.tick_params(axis ='both', direction='in', labelsize=16)
plt.tight_layout()
plt.show()
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python
""" Tables.py - Make tables of atmospheric properties (Python 3)
Adapted by
Richard J. Kwan, Lightsaber Computing
from original programs by
Ralph L. Carmichael, Public Domain Aeronautical Software
Revision History
Date Vers Person Statement of Changes
2004 Oct 04 1.0 RJK Initial program
2017 Jun 04 1.1 RLC All indents are with spaces; all prints in ( )
New version for Python 3 that does integer div with //
"""
import sys, math
version = "1.1 (2017 Jun 04)"
greeting = "Tables - A Python program to compute atmosphere tables"
author = "Ralph L. Carmichael, Public Domain Aeronautical Software"
modifier = "Richard J. Kwan, Lightsaber Computing"
farewell = "Four files added to your directory."
finalmess = "Normal termination of tables."
# P H Y S I C A L C O N S T A N T S
FT2METERS = 0.3048 # mult. ft. to get meters (exact)
KELVIN2RANKINE = 1.8 # mult deg K to get deg R
PSF2NSM = 47.880258 # mult lb/sq.ft to get sq.m
SCF2KCM = 515.379 # mult slugs/cu.ft to get kg/cu.m
TZERO = 288.15 # sea-level temperature, kelvins
PZERO = 101325.0 # sea-level pressure, N/sq.m
RHOZERO = 1.225 # sea-level density, kg/cu.m
AZERO = 340.294 # speed of sound at S.L. m/sec
BETAVISC = 1.458E-6 # viscosity constant
SUTH = 110.4 # Sutherland's constant, kelvins
def LongUSTable():
Itxt = open('us1py.prt', 'w')
Itxt.write(' alt sigma delta theta ')
Itxt.write(' temp press dens a visc k.visc\n')
Itxt.write(' Kft ')
Itxt.write(' degR lb/sq.ft s/cu.ft fps s/ft-s sq.ft/s\n')
for i in range(-1, 57):
altKm=5*i*FT2METERS
(sigma, delta, theta) = Atmosphere(altKm)
Itxt.write("%4i %9.3E %9.3E %6.4f " %
(5*i, sigma, delta, theta))
temp=(KELVIN2RANKINE*TZERO)*theta
pressure=(PZERO/PSF2NSM)*delta
density=(RHOZERO/SCF2KCM)*sigma
asound=(AZERO/FT2METERS)*math.sqrt(theta)
Itxt.write("%5.1f %8.3E %9.3E %6.1f" %
(temp, pressure, density, asound))
viscosity=(1.0/PSF2NSM)*MetricViscosity(theta)
kinematicViscosity=viscosity/density
Itxt.write("%6.3f %8.2E\n" %
(1.0E6*viscosity, kinematicViscosity))
Itxt.close()
def ShortUSTable():
Itxt = open('us2py.prt', 'w')
Itxt.write(' alt sigma delta theta ')
Itxt.write(' temp press dens a visc k.visc ratio\n')
Itxt.write(' Kft ')
Itxt.write(' degR psf s/cu.ft fps s/ft-sec sq.ft/s 1/ft\n')
for i in range(-1, 66):
altKm=i*FT2METERS
(sigma, delta, theta) = SimpleAtmosphere(altKm)
Itxt.write("%4i %6.4f %6.4f %6.4f" %
(i, sigma, delta, theta))
temp=KELVIN2RANKINE*TZERO*theta
pressure=PZERO*delta/47.88
density=RHOZERO*sigma/515.379
asound=(AZERO/FT2METERS)*math.sqrt(theta);
Itxt.write("%6.1f %6.1f %9.7f %6.1f" %
(temp, pressure, density, asound))
viscosity=(1.0/PSF2NSM)*MetricViscosity(theta)
kinematicViscosity=viscosity/density
vratio=asound/kinematicViscosity
Itxt.write("%6.3f %8.2E %4.2f\n"%
(1.0E6*viscosity, kinematicViscosity, 1.0E-6*vratio))
Itxt.close()
def LongSITable():
Itxt = open('si1py.prt', 'w')
Itxt.write(' alt sigma delta theta ')
Itxt.write(' temp press dens a visc k.visc\n')
Itxt.write(' Km ')
Itxt.write(' K N/sq.m kg/cu.m m/sec kg/m-s sq.m/s\n')
for i in range(-4,173):
altKm=0.5*i
(sigma, delta, theta) = Atmosphere(altKm)
Itxt.write("%4.1f %8.4E %8.4E %6.4f" %
(0.5*i, sigma,delta,theta))
temp=TZERO*theta
pressure=PZERO*delta
density=RHOZERO*sigma
asound=AZERO*math.sqrt(theta)
Itxt.write("%6.1f %8.5E %8.3E %5.1f" %
(temp,pressure,density,asound))
viscosity=MetricViscosity(theta)
kinematicViscosity=viscosity/density
Itxt.write("%6.2f %8.2E\n" %
(1.0E6*viscosity, kinematicViscosity))
Itxt.close()
def ShortSITable():
Itxt = open('si2py.prt', 'w')
Itxt.write(' alt sigma delta theta ')
Itxt.write(' temp press dens a visc k.visc ratio\n')
Itxt.write(' Km ')
Itxt.write(' K N/sq.m kcm m/sec kg/m-s sq.m/s 1/m\n')
for i in range(-1, 41):
altKm=0.5*i
(sigma, delta, theta) = SimpleAtmosphere(altKm)
Itxt.write("%4.1f %6.4f %6.4f %6.4f" %
(altKm, sigma, delta, theta))
temp=TZERO*theta
pressure=PZERO*delta
density=RHOZERO*sigma
asound=AZERO*math.sqrt(theta)
Itxt.write("%6.1f %6.0f %5.3f %5.1f" %
(temp, pressure,density,asound))
viscosity=MetricViscosity(theta)
kinematicViscosity=viscosity/density
vratio=asound/kinematicViscosity
Itxt.write("%6.2f %8.2E %5.2f\n" %
(1.0E6*viscosity, kinematicViscosity, 1.0E-6*vratio))
Itxt.close()
def Atmosphere(alt):
""" Compute temperature, density, and pressure in standard atmosphere.
Correct to 86 km. Only approximate thereafter.
Input:
alt geometric altitude, km.
Return: (sigma, delta, theta)
sigma density/sea-level standard density
delta pressure/sea-level standard pressure
theta temperature/sea-level std. temperature
"""
REARTH = 6369.0 # radius of the Earth (km)
GMR = 34.163195
NTAB = 8 # length of tables
htab = [ 0.0, 11.0, 20.0, 32.0, 47.0,
51.0, 71.0, 84.852 ]
ttab = [ 288.15, 216.65, 216.65, 228.65, 270.65,
270.65, 214.65, 186.946 ]
ptab = [ 1.0, 2.2336110E-1, 5.4032950E-2, 8.5666784E-3, 1.0945601E-3,
6.6063531E-4, 3.9046834E-5, 3.68501E-6 ]
gtab = [ -6.5, 0.0, 1.0, 2.8, 0, -2.8, -2.0, 0.0 ]
h = alt*REARTH/(alt+REARTH) # geometric to geopotential altitude
i=0; j=len(htab)-1
while (j > i+1):
k = (i+j)//2 # this is floor division in Python 3
if h < htab[k]:
j = k
else:
i = k
tgrad = gtab[i] # temp. gradient of local layer
tbase = ttab[i] # base temp. of local layer
deltah=h-htab[i] # height above local base
tlocal=tbase+tgrad*deltah # local temperature
theta = tlocal/ttab[0] # temperature ratio
if 0.0 == tgrad:
delta=ptab[i]*math.exp(-GMR*deltah/tbase)
else:
delta=ptab[i]*math.pow(tbase/tlocal, GMR/tgrad)
sigma = delta/theta
return ( sigma, delta, theta )
def SimpleAtmosphere(alt):
""" Compute temperature, density, and pressure in simplified
standard atmosphere.
Correct to 20 km. Only approximate thereafter.
Input:
alt geometric altitude, km.
Return: (sigma, delta, theta)
sigma density/sea-level standard density
delta pressure/sea-level standard pressure
theta temperature/sea-level std. temperature
"""
REARTH = 6369.0 # radius of the Earth (km)
GMR = 34.163195 # gas constant
h = alt*REARTH/(alt+REARTH) # geometric to geopotential altitude
if h<11.0: # troposphere
theta=(288.15-6.5*h)/288.15
delta=math.pow(theta, GMR/6.5)
else: # stratosphere
theta=216.65/288.15
delta=0.2233611*math.exp(-GMR*(h-11.0)/216.65)
sigma = delta/theta
return ( sigma, delta, theta )
def MetricViscosity(theta):
t=theta*TZERO
return BETAVISC*math.sqrt(t*t*t)/(t+SUTH)
def main():
print ("Executing ", sys.argv[0])
print (greeting)
print (author)
if modifier != "":
print ("Modified by ", modifier)
print (" version ", version)
LongUSTable()
ShortUSTable()
LongSITable()
ShortSITable()
print (farewell)
print (finalmess)
main()
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env Python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib import cm
from matplotlib.colors import LogNorm
import sys
from AstroAtmosphere import *
from scipy import interpolate
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: This script uses the US Standard Atmosphere and perturbs it
at several points. Then full integration is used to calculate
the atmospheric dispersion. This is then compared to when we
would have only used the atmospheric conditions directly at
the observatory. This is done to assess whether we should
monitor the weather at every height along the line of sight of
the telescope.
'''
def perturbAtmosphere(h, Temp_perturbed, Press_perturbed):
for _i in range(len(h)):
if h[_i] >= 1.0 and h[_i] <= 2.0:
Tnoise = np.random.normal(0,5,1)
Temp_perturbed[_i] = Temp_perturbed[_i] + Tnoise
Pnoise = np.random.normal(0,1000,1)
Press_perturbed[_i] = Press_perturbed[_i] + Pnoise
if h[_i] >= 7.0 and h[_i] <= 10.0:
Tnoise = np.random.normal(0,5,1)
Temp_perturbed[_i] = Temp_perturbed[_i] + Tnoise
Pnoise = np.random.normal(0,500,1)
Press_perturbed[_i] = Press_perturbed[_i] + Pnoise
if h[_i] >= 25.0 and h[_i] <= 30.0:
Tnoise = np.random.normal(0,10,1)
Temp_perturbed[_i] = Temp_perturbed[_i] + Tnoise
Pnoise = np.random.normal(0,250,1)
Press_perturbed[_i] = Press_perturbed[_i] + Pnoise
if h[_i] >= 50.0 and h[_i] <= 60.0:
Tnoise = np.random.normal(0,15,1)
Temp_perturbed[_i] = Temp_perturbed[_i] + Tnoise
Pnoise = np.random.normal(0,4,1)
Press_perturbed[_i] = Press_perturbed[_i] + Pnoise
return Temp_perturbed, Press_perturbed
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
h, _, _, _, Temp, Press, Dens, vSound, _,_ = np.genfromtxt('si1py_modified.txt', unpack=True, skip_header=6)
# Conditions at Cerro Armazones
T = 288.15 # k
P = 101324 # Pa
H = 0.0
xc = 314 # ppm
lat = -24.5983 # degrees
h0 = 0 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 60 # degrees
# Calculate the index of refraction
at = Observatory()
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
refr = refraction(lat, h0)
refr.setReducedHeight(P, rho)
disp = dispersion(lat, h0)
disp.setReducedHeight(P, rho)
# We must create a cube of weather data:
# We assume a flat atmosphere for now, so that h does not need to be adjusted
# x_max = 200 km, gives zenith_max = ~66.7 degrees
# Every 20 km in horizontal direction a new perturbation and interpolation
# for every 500 m.
x_max = 200 # km
Temp_cube = []
Press_cube = []
Temp_cube_n = []
Press_cube_n = []
Temp_cube.append(Temp)
Press_cube.append(Press)
Temp_cube_n.append(Temp)
Press_cube_n.append(Press)
for x in range(int(x_max/(2))):
# Adding noise
# 1-2 km: 2 degree noise, 100 Pa noise
# 7-10 km: 3 degree noise, 150 Pa noise
# 25-30 km: 5 degree noise, 250 Pa noise
Temp_perturbed = Temp.copy()
Press_perturbed = Press.copy()
Temp_perturbed, Press_perturbed = perturbAtmosphere(h, Temp_perturbed, Press_perturbed)
Temp_cube.append(Temp_perturbed)
Press_cube.append(Press_perturbed)
Temp_cube_n.append(Temp)
Press_cube_n.append(Press)
f_Temp_n = interpolate.interp1d(np.linspace(0,x_max, int(x_max/2)+1), Temp_cube_n, axis=0)
f_Press_n = interpolate.interp1d(np.linspace(0,x_max, int(x_max/2)+1), Press_cube_n, axis=0)
f_Temp = interpolate.interp1d(np.linspace(0,x_max, int(x_max/2)+1), Temp_cube, axis=0)
f_Press = interpolate.interp1d(np.linspace(0,x_max, int(x_max/2)+1), Press_cube, axis=0)
Temp_interpolated_n = f_Temp_n(np.linspace(0,x_max, int(x_max/0.5)+1) )
Press_interpolated_n = f_Press_n(np.linspace(0,x_max, int(x_max/0.5)+1) )
Temp_interpolated = f_Temp(np.linspace(0,x_max, int(x_max/0.5)+1) )
Press_interpolated = f_Press(np.linspace(0,x_max, int(x_max/0.5)+1) )
x, y = np.mgrid[0:200:(int(x_max/0.5)+1)*1j, 0:np.max(h):len(h)*1j]
fig = plt.figure()
ax1 = fig.add_subplot(211)
im = ax1.pcolor(x,y, Temp_interpolated, cmap=cm.coolwarm)
fig.colorbar(im, ax=ax1, label='Temperature (K)')
ax1.set_aspect(1)
ax1.set_ylabel('Height (km)')
ax2 = fig.add_subplot(212)
im2 = ax2.pcolor(x,y, Press_interpolated, cmap=cm.coolwarm, norm=LogNorm())
ax2.set_aspect(1)
ax2.set_xlabel('Horizontal distance (km)')
ax2.set_ylabel('Height (km)')
fig.colorbar(im2, ax=ax2, label='Pressure (Pa)')
plt.tight_layout()
plt.savefig('2d_atmosphere.png', bbox_inches='tight')
plt.savefig('2d_atmosphere.pdf', bbox_inches='tight')
plt.show(block=False)
# Now for a given zenith angle we need to find the temp and press along the line of sight
x = np.linspace(0,200,(int(x_max/0.5)+1))
y = h
f_Temp_2d_n = interpolate.interp2d(y,x,Temp_interpolated_n, kind='linear')
f_Press_2d_n = interpolate.interp2d(y,x,Press_interpolated_n, kind='linear')
f_Temp_2d = interpolate.interp2d(y,x,Temp_interpolated, kind='linear')
f_Press_2d = interpolate.interp2d(y,x,Press_interpolated, kind='linear')
fig2 = plt.figure()
ax = fig2.add_subplot(121)
ax.grid(True, alpha=0.3)
ax.plot(Temp, h, 'k-')
ax2 = fig2.add_subplot(122)
ax2.grid(True, alpha=0.3)
ax2.plot(Press, h, 'k-')
zenithRange = np.linspace(30,65,100)
refrDiff, dispDiff = [], []
for zenith in zenithRange:
z = np.deg2rad(zenith)
y_los = np.linspace(0, 86, len(h))
x_los = np.linspace(0, 86*np.tan(z), len(h))
Temp_los, Press_los = [], []
Temp_los_n, Press_los_n = [], []
for i in range(len(h)):
Temp_los.append(f_Temp_2d(y_los[i], x_los[i])[0])
Press_los.append(f_Press_2d(y_los[i], x_los[i])[0])
Temp_los_n.append(f_Temp_2d_n(y_los[i], x_los[i])[0])
Press_los_n.append(f_Press_2d_n(y_los[i], x_los[i])[0])
Temp_los = np.asarray(Temp_los)
Press_los = np.asarray(Press_los)
Temp_los_n = np.asarray(Temp_los_n)
Press_los_n = np.asarray(Press_los_n)
rho_los = at.rho(p=Press_los, T=Temp_los, RH=H, xc=xc)
rho_los_n = at.rho(p=Press_los_n, T=Temp_los_n, RH=H, xc=xc)
ax.plot(Temp_los, h, label='z=%i' %zenith)
ax2.plot(Press_los, h, label='z=%i' %zenith)
refrInt = refr.refractionIntegral(n1, z, useStandardAtmosphere=False, heightData=h, rhoData=rho_los_n)*3600*1000
refrInt_pert = refr.refractionIntegral(n1, z, useStandardAtmosphere=False, heightData=h, rhoData=rho_los)*3600*1000
refrDiff.append(refrInt_pert - refrInt)
dispInt = disp.refractionIntegral(n1, n2, z, useStandardAtmosphere=False, heightData=h, rhoData=rho_los_n)*3.6e6
dispInt_pert = disp.refractionIntegral(n1, n2, z, useStandardAtmosphere=False, heightData=h, rhoData=rho_los)*3.6e6
dispDiff.append(dispInt_pert - dispInt)
ax.legend()
ax2.legend()
# fig3 = plt.figure()
# ax1 = fig3.add_subplot(211)
# ax2 = fig3.add_subplot(212)
# ax1.tick_params(axis ='both', direction='in')
# ax2.tick_params(axis ='both', direction='in')
# ax1.grid(True, alpha=0.3)
# ax2.grid(True, alpha=0.3)
# ax1.semilogy(zenithRange, np.abs(refrDiff), 'k-')
# ax2.semilogy(zenithRange, np.abs(dispDiff), 'k-')
# ax1.set_xlabel('Zenith (degrees)')
# ax2.set_xlabel('Zenith (degrees)')
# ax1.set_ylabel('|$R_{p} - R_{SA}$| (mas)')
# ax2.set_ylabel('|$\Delta R_{p} - \Delta R_{SA}$| (mas)')
# plt.tight_layout()
# plt.savefig('differential_USSA_for_different_z.png', bbox_inches='tight')
# plt.savefig('differential_USSA_for_different_z.pdf', bbox_inches='tight')
# plt.show()
fig3 = plt.figure(figsize=(9,7))
ax1 = fig3.add_subplot(211)
ax2 = fig3.add_subplot(212)
ax1.tick_params(axis ='both', direction='in', which='both', labelsize=16)
ax2.tick_params(axis ='both', direction='in', which='both', labelsize=16)
ax1.grid(True, alpha=0.3)
ax2.grid(True, alpha=0.3)
ax1.plot(zenithRange, np.log10(np.abs(refrDiff)), 'k-', linewidth=2)
ax2.plot(zenithRange, np.log10(np.abs(dispDiff)), 'k-', linewidth=2)
ax1.set_xlabel('Zenith (degrees)', fontsize=18)
ax2.set_xlabel('Zenith (degrees)', fontsize=18)
ax1.set_ylabel('$\log_{10}($|$R - R_{o}$|$)$ (mas)', fontsize=18)
ax2.set_ylabel('$\log_{10}($|$\Delta R - \Delta R_{o}$|$)$ (mas)', fontsize=18)
plt.tight_layout()
# plt.savefig('differential_USSA_for_different_z.png', bbox_inches='tight')
# plt.savefig('differential_USSA_for_different_z.pdf', bbox_inches='tight')
plt.show()
# Comparison of different air columns.
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env Python3
import numpy as np
from pdb import set_trace
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import sys
from AstroAtmosphere import *
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: We plot the total refraction as a function of height.
We find that most refraction happens in the lower atmosphere
i.e. <10 km.
'''
def refractionIntegral2(n, R, z0, n0, R0):
z0 = np.deg2rad(z0)
return (n0 * R0 * np.sin(z0))/(n*np.sqrt((n*R)**2 - (n0 * R0 * np.sin(z0))**2 ))
def n_for_heights(n0, z0, R0=None, useStandardAtmosphere=True, heightData=None, rhoData=None):
'''
Using data that gives temperature, pressure and density as a function of height,
the gladstone-dale relation is invoked to calculate the path of a light ray
along the atmosphere. This is allows us to also include atmospheric activity in
different atmosphere layers.
Recommended use with the US Standard Atmosphere from 1976.
USSA1976 data generated from code supplied by http://www.pdas.com/atmos.html
'''
if useStandardAtmosphere:
_h, _, _, _, _Temp, _Press, _Dens,_,_,_ = np.genfromtxt('si1py_modified.txt', unpack=True, skip_header=6)
else:
_Dens = rhoData
_h = heightData
z0 = np.deg2rad(z0)
_Dens = np.asarray(_Dens)
_h = np.asarray(_h)
_R = R0 + _h * 1000
# Gladstone-Dale relation between refractive index and density
_n_h = 1 + (n0 - 1) / _Dens[0] * _Dens
_z_h = np.arcsin( (n0 * R0) / (_n_h * _R) * np.sin(z0) )
return _h, _n_h, np.degrees(_z_h)
h, _, _, _, Temp, Press, Dens, vSound, _,_ = np.genfromtxt('si1py_modified.txt', unpack=True, skip_header=6)
# Conditions
T = 288.15 # k
P = 101324 # Pa
H = 0.0
xc = 314 # ppm
lat = -24.5983 # degrees
h0 = 0 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 60 # degrees
# Calculate the index of refraction
at = Observatory()
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
refr = refraction(lat, h0)
refr.setReducedHeight(P, rho)
disp = dispersion(lat, h0)
disp.setReducedHeight(P, rho)
fig = plt.figure(figsize=(5,4))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.set_xlabel('Height (km)')
ax.set_ylabel('Normalized cumulative refraction')
ax.set_xlim([0, h[-1]])
for z in [30]:
refList = []
dispList = []
for i in range(len(h)):
refList.append(refr.refractionIntegral(n1, z, useStandardAtmosphere=False, heightData=h[:i+1], rhoData=Dens[:i+1])*3600)
dispList.append(disp.refractionIntegral(n1, n2, z, useStandardAtmosphere=False, heightData=h[:i+1], rhoData=Dens[:i+1])*3.6e6)
ax.plot(h, np.asarray(refList)/refList[-1], 'k-', label='$\lambda=1.49$ $\mu$m, z=%i$^{\\circ}$' %z)
ax.set_ylim([0,1.1])
plt.tight_layout()
plt.show()
+179
View File
@@ -0,0 +1,179 @@
alt sigma delta theta temp press dens a visc k.visc
Km K N/sq.m kg/cu.m m/sec kg/m-s sq.m/s
-2.0 1.2067E+00 1.2611E+00 1.0451 301.2 1.27783E+05 1.478E+00 347.9 18.51 1.25E-05
-1.5 1.1522E+00 1.1912E+00 1.0338 297.9 1.20696E+05 1.411E+00 346.0 18.36 1.30E-05
-1.0 1.0996E+00 1.1244E+00 1.0226 294.7 1.13931E+05 1.347E+00 344.1 18.21 1.35E-05
-0.5 1.0489E+00 1.0607E+00 1.0113 291.4 1.07478E+05 1.285E+00 342.2 18.05 1.40E-05
0.0 1.0000E+00 1.0000E+00 1.0000 288.1 1.01325E+05 1.225E+00 340.3 17.89 1.46E-05
0.5 9.5288E-01 9.4213E-01 0.9887 284.9 9.54613E+04 1.167E+00 338.4 17.74 1.52E-05
1.0 9.0748E-01 8.8701E-01 0.9774 281.7 8.98763E+04 1.112E+00 336.4 17.58 1.58E-05
1.5 8.6376E-01 8.3454E-01 0.9662 278.4 8.45597E+04 1.058E+00 334.5 17.42 1.65E-05
2.0 8.2168E-01 7.8462E-01 0.9549 275.2 7.95014E+04 1.007E+00 332.5 17.26 1.71E-05
2.5 7.8119E-01 7.3715E-01 0.9436 271.9 7.46917E+04 9.570E-01 330.6 17.10 1.79E-05
3.0 7.4225E-01 6.9204E-01 0.9324 268.7 7.01211E+04 9.093E-01 328.6 16.94 1.86E-05
3.5 7.0482E-01 6.4920E-01 0.9211 265.4 6.57804E+04 8.634E-01 326.6 16.78 1.94E-05
4.0 6.6885E-01 6.0854E-01 0.9098 262.2 6.16604E+04 8.193E-01 324.6 16.61 2.03E-05
4.5 6.3432E-01 5.6997E-01 0.8986 258.9 5.77525E+04 7.770E-01 322.6 16.45 2.12E-05
5.0 6.0117E-01 5.3341E-01 0.8873 255.7 5.40482E+04 7.364E-01 320.5 16.28 2.21E-05
5.5 5.6936E-01 4.9878E-01 0.8760 252.4 5.05393E+04 6.975E-01 318.5 16.12 2.31E-05
6.0 5.3887E-01 4.6600E-01 0.8648 249.2 4.72176E+04 6.601E-01 316.5 15.95 2.42E-05
6.5 5.0964E-01 4.3499E-01 0.8535 245.9 4.40754E+04 6.243E-01 314.4 15.78 2.53E-05
7.0 4.8165E-01 4.0568E-01 0.8423 242.7 4.11052E+04 5.900E-01 312.3 15.61 2.65E-05
7.5 4.5485E-01 3.7799E-01 0.8310 239.5 3.82996E+04 5.572E-01 310.2 15.44 2.77E-05
8.0 4.2921E-01 3.5185E-01 0.8198 236.2 3.56515E+04 5.258E-01 308.1 15.27 2.90E-05
8.5 4.0470E-01 3.2721E-01 0.8085 233.0 3.31541E+04 4.958E-01 306.0 15.10 3.05E-05
9.0 3.8128E-01 3.0398E-01 0.7973 229.7 3.08006E+04 4.671E-01 303.8 14.93 3.20E-05
9.5 3.5891E-01 2.8211E-01 0.7860 226.5 2.85846E+04 4.397E-01 301.7 14.75 3.36E-05
10.0 3.3756E-01 2.6153E-01 0.7748 223.3 2.64998E+04 4.135E-01 299.5 14.58 3.53E-05
10.5 3.1720E-01 2.4219E-01 0.7635 220.0 2.45401E+04 3.886E-01 297.4 14.40 3.71E-05
11.0 2.9780E-01 2.2403E-01 0.7523 216.8 2.26998E+04 3.648E-01 295.2 14.22 3.90E-05
11.5 2.7545E-01 2.0710E-01 0.7519 216.7 2.09847E+04 3.374E-01 295.1 14.22 4.21E-05
12.0 2.5464E-01 1.9146E-01 0.7519 216.7 1.93993E+04 3.119E-01 295.1 14.22 4.56E-05
12.5 2.3541E-01 1.7699E-01 0.7519 216.7 1.79340E+04 2.884E-01 295.1 14.22 4.93E-05
13.0 2.1763E-01 1.6363E-01 0.7519 216.7 1.65795E+04 2.666E-01 295.1 14.22 5.33E-05
13.5 2.0119E-01 1.5127E-01 0.7519 216.7 1.53275E+04 2.465E-01 295.1 14.22 5.77E-05
14.0 1.8600E-01 1.3985E-01 0.7519 216.7 1.41702E+04 2.279E-01 295.1 14.22 6.24E-05
14.5 1.7196E-01 1.2929E-01 0.7519 216.7 1.31005E+04 2.107E-01 295.1 14.22 6.75E-05
15.0 1.5898E-01 1.1953E-01 0.7519 216.7 1.21117E+04 1.948E-01 295.1 14.22 7.30E-05
15.5 1.4698E-01 1.1051E-01 0.7519 216.7 1.11976E+04 1.801E-01 295.1 14.22 7.90E-05
16.0 1.3589E-01 1.0217E-01 0.7519 216.7 1.03527E+04 1.665E-01 295.1 14.22 8.54E-05
16.5 1.2564E-01 9.4465E-02 0.7519 216.7 9.57164E+03 1.539E-01 295.1 14.22 9.24E-05
17.0 1.1616E-01 8.7339E-02 0.7519 216.7 8.84961E+03 1.423E-01 295.1 14.22 9.99E-05
17.5 1.0740E-01 8.0752E-02 0.7519 216.7 8.18215E+03 1.316E-01 295.1 14.22 1.08E-04
18.0 9.9302E-02 7.4662E-02 0.7519 216.7 7.56512E+03 1.216E-01 295.1 14.22 1.17E-04
18.5 9.1815E-02 6.9032E-02 0.7519 216.7 6.99471E+03 1.125E-01 295.1 14.22 1.26E-04
19.0 8.4893E-02 6.3828E-02 0.7519 216.7 6.46738E+03 1.040E-01 295.1 14.22 1.37E-04
19.5 7.8494E-02 5.9017E-02 0.7519 216.7 5.97989E+03 9.616E-02 295.1 14.22 1.48E-04
20.0 7.2578E-02 5.4569E-02 0.7519 216.7 5.52921E+03 8.891E-02 295.1 14.22 1.60E-04
20.5 6.6979E-02 5.0460E-02 0.7534 217.1 5.11291E+03 8.205E-02 295.4 14.24 1.74E-04
21.0 6.1807E-02 4.6670E-02 0.7551 217.6 4.72884E+03 7.571E-02 295.7 14.27 1.88E-04
21.5 5.7045E-02 4.3173E-02 0.7568 218.1 4.37446E+03 6.988E-02 296.0 14.29 2.05E-04
22.0 5.2660E-02 3.9945E-02 0.7585 218.6 4.04741E+03 6.451E-02 296.4 14.32 2.22E-04
22.5 4.8622E-02 3.6965E-02 0.7603 219.1 3.74551E+03 5.956E-02 296.7 14.35 2.41E-04
23.0 4.4902E-02 3.4214E-02 0.7620 219.6 3.46678E+03 5.500E-02 297.0 14.38 2.61E-04
23.5 4.1474E-02 3.1674E-02 0.7637 220.1 3.20939E+03 5.081E-02 297.4 14.40 2.83E-04
24.0 3.8316E-02 2.9328E-02 0.7654 220.6 2.97167E+03 4.694E-02 297.7 14.43 3.07E-04
24.5 3.5404E-02 2.7161E-02 0.7672 221.1 2.75206E+03 4.337E-02 298.1 14.46 3.33E-04
25.0 3.2721E-02 2.5158E-02 0.7689 221.6 2.54915E+03 4.008E-02 298.4 14.48 3.61E-04
25.5 3.0246E-02 2.3308E-02 0.7706 222.0 2.36163E+03 3.705E-02 298.7 14.51 3.92E-04
26.0 2.7964E-02 2.1597E-02 0.7723 222.5 2.18831E+03 3.426E-02 299.1 14.54 4.24E-04
26.5 2.5858E-02 2.0016E-02 0.7740 223.0 2.02807E+03 3.168E-02 299.4 14.57 4.60E-04
27.0 2.3916E-02 1.8553E-02 0.7758 223.5 1.87991E+03 2.930E-02 299.7 14.59 4.98E-04
27.5 2.2124E-02 1.7201E-02 0.7775 224.0 1.74289E+03 2.710E-02 300.1 14.62 5.39E-04
28.0 2.0470E-02 1.5950E-02 0.7792 224.5 1.61614E+03 2.508E-02 300.4 14.65 5.84E-04
28.5 1.8943E-02 1.4793E-02 0.7809 225.0 1.49888E+03 2.320E-02 300.7 14.67 6.32E-04
29.0 1.7533E-02 1.3722E-02 0.7826 225.5 1.39037E+03 2.148E-02 301.0 14.70 6.84E-04
29.5 1.6231E-02 1.2731E-02 0.7844 226.0 1.28994E+03 1.988E-02 301.4 14.73 7.41E-04
30.0 1.5028E-02 1.1813E-02 0.7861 226.5 1.19698E+03 1.841E-02 301.7 14.75 8.01E-04
30.5 1.3917E-02 1.0964E-02 0.7878 227.0 1.11092E+03 1.705E-02 302.0 14.78 8.67E-04
31.0 1.2891E-02 1.0177E-02 0.7895 227.5 1.03122E+03 1.579E-02 302.4 14.81 9.38E-04
31.5 1.1942E-02 9.4488E-03 0.7912 228.0 9.57402E+02 1.463E-02 302.7 14.83 1.01E-03
32.0 1.1065E-02 8.7740E-03 0.7930 228.5 8.89024E+02 1.355E-02 303.0 14.86 1.10E-03
32.5 1.0228E-02 8.1493E-03 0.7968 229.6 8.25726E+02 1.253E-02 303.8 14.92 1.19E-03
33.0 9.4469E-03 7.5724E-03 0.8016 231.0 7.67272E+02 1.157E-02 304.7 14.99 1.30E-03
33.5 8.7297E-03 7.0395E-03 0.8064 232.4 7.13279E+02 1.069E-02 305.6 15.07 1.41E-03
34.0 8.0709E-03 6.5470E-03 0.8112 233.7 6.63380E+02 9.887E-03 306.5 15.14 1.53E-03
34.5 7.4654E-03 6.0917E-03 0.8160 235.1 6.17243E+02 9.145E-03 307.4 15.21 1.66E-03
35.0 6.9085E-03 5.6705E-03 0.8208 236.5 5.74564E+02 8.463E-03 308.3 15.29 1.81E-03
35.5 6.3961E-03 5.2807E-03 0.8256 237.9 5.35066E+02 7.835E-03 309.2 15.36 1.96E-03
36.0 5.9245E-03 4.9198E-03 0.8304 239.3 4.98495E+02 7.257E-03 310.1 15.43 2.13E-03
36.5 5.4901E-03 4.5854E-03 0.8352 240.7 4.64618E+02 6.725E-03 311.0 15.51 2.31E-03
37.0 5.0899E-03 4.2756E-03 0.8400 242.1 4.33223E+02 6.235E-03 311.9 15.58 2.50E-03
37.5 4.7209E-03 3.9883E-03 0.8448 243.4 4.04115E+02 5.783E-03 312.8 15.65 2.71E-03
38.0 4.3806E-03 3.7218E-03 0.8496 244.8 3.77116E+02 5.366E-03 313.7 15.72 2.93E-03
38.5 4.0666E-03 3.4746E-03 0.8544 246.2 3.52061E+02 4.982E-03 314.6 15.79 3.17E-03
39.0 3.7767E-03 3.2450E-03 0.8592 247.6 3.28801E+02 4.626E-03 315.4 15.87 3.43E-03
39.5 3.5089E-03 3.0318E-03 0.8640 249.0 3.07198E+02 4.298E-03 316.3 15.94 3.71E-03
40.0 3.2616E-03 2.8337E-03 0.8688 250.4 2.87125E+02 3.995E-03 317.2 16.01 4.01E-03
40.5 3.0329E-03 2.6496E-03 0.8736 251.7 2.68467E+02 3.715E-03 318.1 16.08 4.33E-03
41.0 2.8214E-03 2.4783E-03 0.8784 253.1 2.51116E+02 3.456E-03 318.9 16.15 4.67E-03
41.5 2.6257E-03 2.3190E-03 0.8832 254.5 2.34974E+02 3.216E-03 319.8 16.22 5.04E-03
42.0 2.4445E-03 2.1708E-03 0.8880 255.9 2.19952E+02 2.995E-03 320.7 16.29 5.44E-03
42.5 2.2768E-03 2.0327E-03 0.8928 257.3 2.05965E+02 2.789E-03 321.5 16.36 5.87E-03
43.0 2.1214E-03 1.9041E-03 0.8976 258.6 1.92937E+02 2.599E-03 322.4 16.43 6.32E-03
43.5 1.9774E-03 1.7843E-03 0.9024 260.0 1.80799E+02 2.422E-03 323.3 16.50 6.81E-03
44.0 1.8438E-03 1.6727E-03 0.9072 261.4 1.69484E+02 2.259E-03 324.1 16.57 7.34E-03
44.5 1.7199E-03 1.5685E-03 0.9120 262.8 1.58932E+02 2.107E-03 325.0 16.64 7.90E-03
45.0 1.6050E-03 1.4714E-03 0.9168 264.2 1.49090E+02 1.966E-03 325.8 16.71 8.50E-03
45.5 1.4983E-03 1.3808E-03 0.9216 265.5 1.39905E+02 1.835E-03 326.7 16.78 9.14E-03
46.0 1.3992E-03 1.2961E-03 0.9263 266.9 1.31330E+02 1.714E-03 327.5 16.85 9.83E-03
46.5 1.3071E-03 1.2171E-03 0.9311 268.3 1.23322E+02 1.601E-03 328.4 16.92 1.06E-02
47.0 1.2215E-03 1.1433E-03 0.9359 269.7 1.15841E+02 1.496E-03 329.2 16.99 1.14E-02
47.5 1.1437E-03 1.0743E-03 0.9393 270.6 1.08849E+02 1.401E-03 329.8 17.04 1.22E-02
48.0 1.0748E-03 1.0095E-03 0.9393 270.6 1.02287E+02 1.317E-03 329.8 17.04 1.29E-02
48.5 1.0100E-03 9.4865E-04 0.9393 270.6 9.61215E+01 1.237E-03 329.8 17.04 1.38E-02
49.0 9.4912E-04 8.9147E-04 0.9393 270.6 9.03286E+01 1.163E-03 329.8 17.04 1.47E-02
49.5 8.9193E-04 8.3776E-04 0.9393 270.6 8.48858E+01 1.093E-03 329.8 17.04 1.56E-02
50.0 8.3819E-04 7.8728E-04 0.9393 270.6 7.97716E+01 1.027E-03 329.8 17.04 1.66E-02
50.5 7.8770E-04 7.3986E-04 0.9393 270.6 7.49663E+01 9.649E-04 329.8 17.04 1.77E-02
51.0 7.4026E-04 6.9530E-04 0.9393 270.6 7.04511E+01 9.068E-04 329.8 17.04 1.88E-02
51.5 6.9630E-04 6.5342E-04 0.9384 270.4 6.62082E+01 8.530E-04 329.7 17.02 2.00E-02
52.0 6.5758E-04 6.1395E-04 0.9336 269.0 6.22084E+01 8.055E-04 328.8 16.96 2.10E-02
52.5 6.2085E-04 5.7668E-04 0.9289 267.7 5.84321E+01 7.605E-04 328.0 16.89 2.22E-02
53.0 5.8599E-04 5.4150E-04 0.9241 266.3 5.48678E+01 7.178E-04 327.1 16.82 2.34E-02
53.5 5.5293E-04 5.0831E-04 0.9193 264.9 5.15047E+01 6.773E-04 326.3 16.75 2.47E-02
54.0 5.2158E-04 4.7700E-04 0.9145 263.5 4.83322E+01 6.389E-04 325.4 16.68 2.61E-02
54.5 4.9187E-04 4.4748E-04 0.9098 262.1 4.53405E+01 6.025E-04 324.6 16.61 2.76E-02
55.0 4.6370E-04 4.1964E-04 0.9050 260.8 4.25201E+01 5.680E-04 323.7 16.54 2.91E-02
55.5 4.3702E-04 3.9341E-04 0.9002 259.4 3.98620E+01 5.354E-04 322.9 16.47 3.08E-02
56.0 4.1175E-04 3.6869E-04 0.8954 258.0 3.73576E+01 5.044E-04 322.0 16.40 3.25E-02
56.5 3.8782E-04 3.4541E-04 0.8907 256.6 3.49988E+01 4.751E-04 321.2 16.33 3.44E-02
57.0 3.6516E-04 3.2349E-04 0.8859 255.3 3.27778E+01 4.473E-04 320.3 16.26 3.64E-02
57.5 3.4373E-04 3.0286E-04 0.8811 253.9 3.06871E+01 4.211E-04 319.4 16.19 3.85E-02
58.0 3.2344E-04 2.8344E-04 0.8763 252.5 2.87198E+01 3.962E-04 318.6 16.12 4.07E-02
58.5 3.0426E-04 2.6518E-04 0.8716 251.1 2.68692E+01 3.727E-04 317.7 16.05 4.31E-02
59.0 2.8612E-04 2.4800E-04 0.8668 249.8 2.51289E+01 3.505E-04 316.8 15.98 4.56E-02
59.5 2.6897E-04 2.3186E-04 0.8620 248.4 2.34929E+01 3.295E-04 315.9 15.91 4.83E-02
60.0 2.5276E-04 2.1668E-04 0.8573 247.0 2.19554E+01 3.096E-04 315.1 15.84 5.11E-02
60.5 2.3746E-04 2.0243E-04 0.8525 245.6 2.05110E+01 2.909E-04 314.2 15.77 5.42E-02
61.0 2.2300E-04 1.8904E-04 0.8477 244.3 1.91546E+01 2.732E-04 313.3 15.69 5.75E-02
61.5 2.0935E-04 1.7647E-04 0.8430 242.9 1.78811E+01 2.565E-04 312.4 15.62 6.09E-02
62.0 1.9647E-04 1.6468E-04 0.8382 241.5 1.66860E+01 2.407E-04 311.5 15.55 6.46E-02
62.5 1.8431E-04 1.5361E-04 0.8334 240.2 1.55648E+01 2.258E-04 310.7 15.48 6.86E-02
63.0 1.7285E-04 1.4323E-04 0.8287 238.8 1.45132E+01 2.117E-04 309.8 15.41 7.28E-02
63.5 1.6204E-04 1.3351E-04 0.8239 237.4 1.35275E+01 1.985E-04 308.9 15.33 7.72E-02
64.0 1.5185E-04 1.2439E-04 0.8191 236.0 1.26036E+01 1.860E-04 308.0 15.26 8.20E-02
64.5 1.4225E-04 1.1585E-04 0.8144 234.7 1.17382E+01 1.743E-04 307.1 15.19 8.72E-02
65.0 1.3321E-04 1.0785E-04 0.8096 233.3 1.09277E+01 1.632E-04 306.2 15.12 9.26E-02
65.5 1.2470E-04 1.0036E-04 0.8048 231.9 1.01690E+01 1.528E-04 305.3 15.04 9.85E-02
66.0 1.1668E-04 9.3354E-05 0.8001 230.5 9.45909E+00 1.429E-04 304.4 14.97 1.05E-01
66.5 1.0914E-04 8.6800E-05 0.7953 229.2 8.79502E+00 1.337E-04 303.5 14.90 1.11E-01
67.0 1.0204E-04 8.0672E-05 0.7906 227.8 8.17408E+00 1.250E-04 302.6 14.82 1.19E-01
67.5 9.5371E-05 7.4944E-05 0.7858 226.4 7.59372E+00 1.168E-04 301.7 14.75 1.26E-01
68.0 8.9101E-05 6.9593E-05 0.7811 225.1 7.05149E+00 1.091E-04 300.7 14.67 1.34E-01
68.5 8.3209E-05 6.4595E-05 0.7763 223.7 6.54509E+00 1.019E-04 299.8 14.60 1.43E-01
69.0 7.7675E-05 5.9929E-05 0.7715 222.3 6.07234E+00 9.515E-05 298.9 14.53 1.53E-01
69.5 7.2478E-05 5.5576E-05 0.7668 221.0 5.63120E+00 8.879E-05 298.0 14.45 1.63E-01
70.0 6.7601E-05 5.1515E-05 0.7620 219.6 5.21972E+00 8.281E-05 297.1 14.38 1.74E-01
70.5 6.3026E-05 4.7728E-05 0.7573 218.2 4.83607E+00 7.721E-05 296.1 14.30 1.85E-01
71.0 5.8734E-05 4.4199E-05 0.7525 216.8 4.47851E+00 7.195E-05 295.2 14.23 1.98E-01
71.5 5.4712E-05 4.0912E-05 0.7478 215.5 4.14543E+00 6.702E-05 294.3 14.15 2.11E-01
72.0 5.0905E-05 3.7852E-05 0.7436 214.3 3.83531E+00 6.236E-05 293.4 14.08 2.26E-01
72.5 4.7296E-05 3.5007E-05 0.7402 213.3 3.54709E+00 5.794E-05 292.8 14.03 2.42E-01
73.0 4.3927E-05 3.2365E-05 0.7368 212.3 3.27939E+00 5.381E-05 292.1 13.98 2.60E-01
73.5 4.0786E-05 2.9912E-05 0.7334 211.3 3.03083E+00 4.996E-05 291.4 13.92 2.79E-01
74.0 3.7856E-05 2.7635E-05 0.7300 210.3 2.80012E+00 4.637E-05 290.7 13.87 2.99E-01
74.5 3.5125E-05 2.5522E-05 0.7266 209.4 2.58605E+00 4.303E-05 290.1 13.81 3.21E-01
75.0 3.2580E-05 2.3563E-05 0.7232 208.4 2.38749E+00 3.991E-05 289.4 13.76 3.45E-01
75.5 3.0209E-05 2.1746E-05 0.7198 207.4 2.20338E+00 3.701E-05 288.7 13.70 3.70E-01
76.0 2.8001E-05 2.0061E-05 0.7164 206.4 2.03272E+00 3.430E-05 288.0 13.65 3.98E-01
76.5 2.5946E-05 1.8501E-05 0.7131 205.5 1.87458E+00 3.178E-05 287.4 13.59 4.28E-01
77.0 2.4033E-05 1.7055E-05 0.7097 204.5 1.72810E+00 2.944E-05 286.7 13.54 4.60E-01
77.5 2.2253E-05 1.5716E-05 0.7063 203.5 1.59247E+00 2.726E-05 286.0 13.48 4.95E-01
78.0 2.0597E-05 1.4477E-05 0.7029 202.5 1.46692E+00 2.523E-05 285.3 13.43 5.32E-01
78.5 1.9058E-05 1.3331E-05 0.6995 201.6 1.35076E+00 2.335E-05 284.6 13.37 5.73E-01
79.0 1.7627E-05 1.2270E-05 0.6961 200.6 1.24331E+00 2.159E-05 283.9 13.32 6.17E-01
79.5 1.6298E-05 1.1290E-05 0.6927 199.6 1.14396E+00 1.996E-05 283.2 13.26 6.64E-01
80.0 1.5063E-05 1.0384E-05 0.6893 198.6 1.05213E+00 1.845E-05 282.5 13.21 7.16E-01
80.5 1.3917E-05 9.5464E-06 0.6860 197.7 9.67291E-01 1.705E-05 281.8 13.15 7.71E-01
81.0 1.2853E-05 8.7731E-06 0.6826 196.7 8.88933E-01 1.574E-05 281.1 13.10 8.32E-01
81.5 1.1866E-05 8.0591E-06 0.6792 195.7 8.16590E-01 1.454E-05 280.4 13.04 8.97E-01
82.0 1.0950E-05 7.4002E-06 0.6758 194.7 7.49827E-01 1.341E-05 279.7 12.98 9.68E-01
82.5 1.0101E-05 6.7924E-06 0.6724 193.8 6.88237E-01 1.237E-05 279.0 12.93 1.04E+00
83.0 9.3145E-06 6.2318E-06 0.6690 192.8 6.31441E-01 1.141E-05 278.3 12.87 1.13E+00
83.5 8.5856E-06 5.7151E-06 0.6657 191.8 5.79087E-01 1.052E-05 277.6 12.82 1.22E+00
84.0 7.9106E-06 5.2391E-06 0.6623 190.8 5.30848E-01 9.690E-06 276.9 12.76 1.32E+00
84.5 7.2857E-06 4.8006E-06 0.6589 189.9 4.86416E-01 8.925E-06 276.2 12.70 1.42E+00
85.0 6.7074E-06 4.3968E-06 0.6555 188.9 4.45509E-01 8.217E-06 275.5 12.65 1.54E+00
85.5 6.1724E-06 4.0253E-06 0.6521 187.9 4.07863E-01 7.561E-06 274.8 12.59 1.67E+00
86.0 5.6777E-06 3.6835E-06 0.6488 186.9 3.73232E-01 6.955E-06 274.1 12.53 1.80E+00
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env Python3
import numpy as np
import matplotlib.pyplot as plt
from AstroAtmosphere import *
from ADC import *
import sys
import scipy.optimize as opt
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: This script finds the filter constant using
- Direct application of equation 18 and 19
- The minimization algorithm implemented in the ADC class
and compares these two.
'''
class filter:
def __init__(self, name, l1, l2):
self.name = name
self.lmin = np.min([l1,l2])
self.lmax = np.max([l1,l2])
self.bandwidth = self.lmax - self.lmin
self.optimumAngle = None
self.residualDispersion = None
self.optimumAngleForAllZ = None
self.cf = None
def analytic_optimum_position_ADC(self, z, l1, l2, dispersion_object, adc_object):
disp = dispersion_object
adc = adc_object
_R = disp.rc / (disp.rc + disp.H)
n1 = at.n_tph(l=self.lmin, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=self.lmax, T=T, p=P, RH=H, xc=xc)
dn_atm = n2 - n1
_dispersion = np.deg2rad(disp.cassini(n1,n2,z))
_A1 = np.deg2rad(adc.p1.tilt2x_OG - adc.p1.tilt1x_OG)
_A2 = np.deg2rad(adc.p2.tilt2x_OG - adc.p2.tilt1x_OG)
_A3 = -1 * np.deg2rad(adc.p3.tilt2x_OG - adc.p3.tilt1x_OG) # -1 because doublet gets flipped after setting rotation
_A4 = -1 * np.deg2rad(adc.p4.tilt2x_OG - adc.p4.tilt1x_OG) # -1 because doublet gets flipped after setting rotation
_dn_p1 = adc.p1.indexOfRefraction(l1) - adc.p1.indexOfRefraction(l2)
_dn_p2 = adc.p2.indexOfRefraction(l1) - adc.p2.indexOfRefraction(l2)
_pupilRatio = 0.07943/38.542
_Psi = (_dn_p1 * (_A1 + _A4) + _dn_p2 * (_A2 + _A3)) * _pupilRatio
_cf = -1/_pupilRatio * (dn_atm)/((_dn_p1 * (_A1 + _A4) + _dn_p2 * (_A2 + _A3)))
_thetaOpt = np.arccos(1/_Psi * _dispersion)
return np.degrees(_thetaOpt), _cf
def ADCposAnalytical(z, Cf):
return np.arccos(Cf*np.tan(np.deg2rad(z)))*180/np.pi
if __name__ == "__main__":
Iband = filter('I-band',0.8,0.934)
Jband = filter('J-band',1.15,1.345)
Hband = filter('H-band',1.49,1.78)
Kband = filter('K-band',1.97,2.32)
##########################################################
# Atmospheric model
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
# Initializing dispersion model
at = Observatory()
# Density of the atmosphere (following CIPM-81/91 equations)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
# Initializing dispersion object
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
##########################################################
adc = ADC()
# Unlock this when the above works
# zRange = range(2,62)
zRange = np.arange(5,65.1,0.1)
optimumAngleFilterList = []
fig = plt.figure()
ax = fig.add_subplot(111)
for f in [Hband, Iband, Jband, Kband]:
# For new filters or if the zenith values are different, the new values are calculated
print ('Starting %s...' %f.name )
# Calculating indices of refraction for l1 and l2
n1 = at.n_tph(l=f.lmin, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=f.lmax, T=T, p=P, RH=H, xc=xc)
dispersion_obs = disp.cassini(n1, n2, zRange)*3.6e6
adc.setOrientation(0)
theta_opt = [adc.optimize(disp, f.lmin, f.lmax) for disp in dispersion_obs]
f.optimumAngleForAllZ = theta_opt
# Plotting the data
ax.plot(zRange, f.optimumAngleForAllZ, label=f.name)
for f in [Hband, Iband, Jband, Kband]:
popt, pcov = opt.curve_fit(ADCposAnalytical, zRange[:-50], f.optimumAngleForAllZ[:-50], p0=0.5)
f.cf = popt[0]
ax.plot(zRange, ADCposAnalytical(zRange, f.cf), 'k--', label='best fit %s' %f.name)
theta_opt_analytical, cf_analytical = f.analytic_optimum_position_ADC(zRange, f.lmin, f.lmax, disp, adc)
ax.plot(zRange, ADCposAnalytical(zRange, cf_analytical), 'b--', label='analytical cf %s' %f.name)
print('{} - Best fit: {} - Analytical: {}'.format(f.name, f.cf, cf_analytical))
# Plot properties
ax.set_title('Optimum rotation angle of the first ADC prism', fontsize=9)
ax.set_ylabel('Rotation angle (deg)', fontsize=9)
ax.set_xlabel('Zenith angle (deg)', fontsize=9)
ax.grid(True, alpha=0.5)
ax.legend(fontsize=9)
plt.show()
+299
View File
@@ -0,0 +1,299 @@
#!/usr/bin/env Python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
from AstroAtmosphere import *
import pandas as pd
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: Modeling the atmospheric refraction using multiple
models of the atmosphere and then comparing them.
Models included:
- Full integration method
- Plane-parallel atmosphere
- Cassini's model
- Tan5 model (A*tan(z) + B*tan^3(z) + C*tan^5(z))
- Oriani's theorem (A*tan(z) + B*tan^3(z))
- Erf model (from Corbard et al., 2019)
- SLALIB
One of the figures and all three tables are used in the paper.
'''
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
# Conditions at Cerro Armazones
# T = 279.65 # k
# P = 71200 # Pa
# H = 0.22
# xc = 450 # ppm
# Conditions for the standard atmosphere
T = 288.15 # k
P = 101325 # Pa
H = 0.0
xc = 314 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 30 # degrees
# Calculate the index of refraction
at = Observatory()
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
refractionAnalysis = True
dispersionAnalysis = True
tableCompare = True
errorTable = True
if dispersionAnalysis:
# Initiate the refraction class
ref = dispersion(lat, h)
ref.setReducedHeight(P, rho)
# Compare the refraction models to check if the output is okay
print ('Plane - parallel atmosphere dispersion: %s mas' %(ref.planeParallel(n1, n2, zenith) * 3.6e6))
print ('Cassini dispersion: %s mas' %(ref.cassini(n1, n2, zenith) * 3.6e6))
print ('Tan5 dispersion: %s mas' %(ref.tan5(n1, n2, zenith) * 3.6e6))
print ('Oriani dispersion: %s mas' %(ref.oriani(n1, n2, zenith) * 3.6e6))
print ('Corbard dispersion: %s mas' %(ref.corbard(n1, n2, zenith)*3.6e6))
print ('Full integration: %s mas' %(ref.refractionIntegral(n1, n2, zenith)*3.6e6))
print('-'*35)
# Now we calculate the dispersion over a wide range of zenith angles
# for ideal gas atmosphere
zenithRange = np.linspace(5, 85, 1000)
ppList = ref.planeParallel(n1, n2, zenithRange) * 3.6e6
cassiniList = ref.cassini(n1, n2, zenithRange) * 3.6e6
tan5List = ref.tan5(n1, n2, zenithRange) * 3.6e6
orianiList = ref.oriani(n1, n2, zenithRange) * 3.6e6
corbardList = [ref.corbard(n1, n2, z) * 3.6e6 for z in zenithRange]
fullIntegrationList = [ref.refractionIntegral(n1, n2, z) * 3.6e6 for z in zenithRange]
# for isothermal atmosphere
cassiniList_isotherm = ref.cassini(n1, n2, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e6
tan5List_isotherm = ref.tan5(n1, n2, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e6
orianiList_isotherm = ref.oriani(n1, n2, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e6
corbardList_isotherm = [ref.corbard(n1, n2, z, reduced_height=ref.H_isotherm(T)) * 3.6e6 for z in zenithRange]
# check when the variation between the different models pass different values
dispersionWidth = []
widthList = []
for z in range(len(zenithRange)):
compList = [cassiniList[z], tan5List[z], orianiList[z], corbardList[z], cassiniList_isotherm[z], tan5List_isotherm[z], orianiList_isotherm[z], corbardList_isotherm[z], fullIntegrationList[z]]
width = np.max(compList) - np.min(compList)
widthList.append(width)
dispersionWidth.append((zenithRange[z], width))
masBorder = np.argmax(np.asarray(widthList)>1)
p1masBorder = np.argmax(np.asarray(widthList)>0.1)
p01masBorder = np.argmax(np.asarray(widthList)>0.01)
p001masBorder = np.argmax(np.asarray(widthList)>0.001)
plotAbsolute = 1
if plotAbsolute:
# Plotting it all, with vertical lines where the variation between the models exceed 1, 0.1, 0.01 and 0.001 mas.
fig = plt.figure(figsize=(9,5))
ax = fig.add_subplot(111)
ax.set_xlim([30,85])
ax.set_xlabel('Zenith (degrees)')
ax.set_ylabel('Dispersion (mas)')
ax.grid(True, alpha=0.3)
ax.axvline(dispersionWidth[masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[masBorder][0]-0.5, 402, '1 mas', ha='center', va='bottom', rotation='vertical')
ax.axvline(dispersionWidth[p1masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[p1masBorder][0]-0.5, 402, '0.1 mas', ha='center', va='bottom', rotation='vertical')
ax.axvline(dispersionWidth[p01masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[p01masBorder][0]-0.5, 402, '10 $\mu$as', ha='center', va='bottom', rotation='vertical')
ax.axvline(dispersionWidth[p001masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[p001masBorder][0]-0.5, 402, '1 $\mu$as', ha='center', va='bottom', rotation='vertical')
ax.plot(zenithRange, ppList, 'k-', label='Plane-parallel', alpha=0.2)
ax.plot(zenithRange, cassiniList, 'g-', label='Cassini')
ax.plot(zenithRange, tan5List, 'b-', label='$Atan(z) - Btan^3(z) + Ctan^5(z)$')
ax.plot(zenithRange, orianiList, 'y-', label='$Atan(z) - Btan^3(z)$')
ax.plot(zenithRange, corbardList, 'r-', label='Error function')
ax.plot(zenithRange, fullIntegrationList, 'c-', label='Full integration')
ax.plot(zenithRange, cassiniList_isotherm, 'g--', label='Cassini (isotherm)')
ax.plot(zenithRange, tan5List_isotherm, 'b--', label='$Atan(z) - Btan^3(z) + Ctan^5(z)$ (isotherm)')
ax.plot(zenithRange, orianiList_isotherm, 'y--', label='$Atan(z) - Btan^3(z)$ (isotherm)')
ax.plot(zenithRange, corbardList_isotherm, 'r--', label='Error function (isotherm)')
ax.tick_params(axis ='both', direction='in')
ax.legend()
plt.tight_layout()
# plt.show()
plotRelative = 1
if plotRelative:
# RELATIVE TO FULL INTEGRATION
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
ax.set_xlim([30,85])
ax.set_xlabel('Zenith (degrees)', fontsize=18)
ax.set_ylabel(r'$\log_{10}(|(\Delta R - \Delta R_{int})|$ mas$^{-1})$', fontsize=18)
ax.axhline(np.log10(0.4), linestyle='-', linewidth=1, color='red')
ax.text(30.5, np.log10(0.4)+0.05, 'Residual dispersion requirement', fontsize=12)
ax.grid(True, which='major', alpha=0.3)
ax.grid(True, which='minor', alpha=0.2)
ax.plot(zenithRange, np.log10(np.abs(ppList-fullIntegrationList)), color='k', linestyle='-', linewidth=2, label='Plane-parallel')
ax.plot(zenithRange, np.log10(np.abs(cassiniList-fullIntegrationList)), color='g', linestyle=(0, (1, 1)), linewidth=2, label='Cassini')
ax.plot(zenithRange, np.log10(np.abs(orianiList-fullIntegrationList)), color='y', linestyle='-.', linewidth=2, label='Oriani')
ax.plot(zenithRange, np.log10(np.abs(tan5List-fullIntegrationList)), color='b', linestyle=(0, (5, 1)), linewidth=2, label='Oriani incl. fifth order')
ax.plot(zenithRange, np.log10(np.abs(np.asarray(corbardList)-fullIntegrationList)), color='r', linestyle=(0, (3, 1, 1, 1, 1, 1)), linewidth=2, label='Error function')
ax.plot(zenithRange, np.log10(np.abs(cassiniList-fullIntegrationList)), color='g', linestyle=(0, (1, 1)), linewidth=2)
ax.set_ylim(top=2.6, bottom=-7.2)
ax.tick_params(axis ='both', direction='in', labelsize=16)
ax.legend(fontsize=14, labelspacing=0.3, columnspacing=0.5, handletextpad=0.3, ncol=1, loc='lower right')
plt.tight_layout()
# plt.savefig('./figures/dispersionModels_with_SLALIB_minimal2.pdf', transparent=True, bbox_inches='tight')
# plt.savefig('./figures/dispersionModels_with_SLALIB_minimal2.png', transparent=True, bbox_inches='tight')
if refractionAnalysis:
# Initiate the refraction class
ref = refraction(lat, h)
ref.setReducedHeight(P, rho)
# Compare the refraction models to check if the output is okay
print ('Plane - parallel atmosphere refraction: %s arcsec' %(ref.planeParallel(n1, zenith) * 3.6e3))
print ('Cassini refraction: %s arcsec' %(ref.cassini(n1, zenith) * 3.6e3))
print ('Tan5 refraction: %s arcsec' %(ref.tan5(n1, zenith) * 3.6e3))
print ('Oriani refraction: %s arcsec' %(ref.oriani(n1, zenith) * 3.6e3))
print ('Corbard refraction: %s arcsec' %(ref.corbard(n1, zenith)*3.6e3))
print ('Full integration: %s mas' %(ref.refractionIntegral(n1, zenith)*3.6e3))
print('-'*35)
# Now we calculate the dispersion over a wide range of zenith angles
# for ideal gas atmosphere
zenithRange = np.linspace(0, 88, 1000)
ppList = ref.planeParallel(n1, zenithRange) * 3.6e3
cassiniList = ref.cassini(n1, zenithRange) * 3.6e3
tan5List = ref.tan5(n1, zenithRange) * 3.6e3
orianiList = ref.oriani(n1, zenithRange) * 3.6e3
corbardList = [ref.corbard(n1, z) * 3.6e3 for z in zenithRange]
fullIntegrationList = [ref.refractionIntegral(n1, z) * 3.6e3 for z in zenithRange]
# for isothermal atmosphere
cassiniList_isotherm = ref.cassini(n1, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e3
tan5List_isotherm = ref.tan5(n1, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e3
orianiList_isotherm = ref.oriani(n1, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e3
corbardList_isotherm = [ref.corbard(n1, z, reduced_height=ref.H_isotherm(T)) * 3.6e3 for z in zenithRange]
# Plotting it all, with vertical lines where the variation between the models exceed 1, 0.1, 0.01 and 0.001 arcsec.
fig = plt.figure(figsize=(9,5))
ax = fig.add_subplot(111)
ax.set_xlim([80,88])
ax.set_xlabel('Zenith (degrees)')
ax.set_ylabel('Refraction (arcsec)')
ax.grid(True, alpha=0.3)
ax.plot(zenithRange, ppList, 'k-', label='Plane-parallel', alpha=0.2)
ax.plot(zenithRange, cassiniList, 'g-', label='Cassini')
ax.plot(zenithRange, tan5List, 'b-', label='$Atan(z) - Btan^3(z) + Ctan^5(z)$')
ax.plot(zenithRange, orianiList, 'y-', label='$Atan(z) - Btan^3(z)$')
ax.plot(zenithRange, corbardList, 'r-', label='Error function')
ax.plot(zenithRange, fullIntegrationList, 'c-', label='Full integration')
ax.tick_params(axis ='both', direction='in')
ax.legend()
plt.tight_layout()
if tableCompare:
refTableData = []
dispTableData = []
ref = refraction(lat, h)
ref.setReducedHeight(P, rho)
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
zenithRange = [10, 30, 50, 60, 70, 80, 85]
headerList = np.array(['z', 'Plane-parallel', 'Cassini', 'tan3', 'tan5', 'Error function', 'Full integration'])
refTableData.append(headerList)
dispTableData.append(headerList)
for z in zenithRange:
ppValue = round(ref.planeParallel(n1, z) * 3.6e3, 3)
cassiniValue = round(ref.cassini(n1, z) * 3.6e3, 3)
tan3Value = round(ref.oriani(n1, z) * 3.6e3, 3)
tan5Value = round(ref.tan5(n1, z) * 3.6e3, 3)
erfValue = round(ref.corbard(n1, z) * 3.6e3, 3)
fullIntValue = round(ref.refractionIntegral(n1, z) * 3.6e3, 3)
refTableData.append([z, ppValue, cassiniValue, tan3Value, tan5Value, erfValue, fullIntValue])
ppValue = round(disp.planeParallel(n1, n2, z) * 3.6e6, 3)
cassiniValue = round(disp.cassini(n1, n2, z) * 3.6e6, 3)
tan3Value = round(disp.oriani(n1, n2, z) * 3.6e6, 3)
tan5Value = round(disp.tan5(n1, n2, z) * 3.6e6, 3)
erfValue = round(disp.corbard(n1, n2, z) * 3.6e6, 3)
fullIntValue = round(disp.refractionIntegral(n1, n2, z) * 3.6e6, 3)
dispTableData.append([z, ppValue, cassiniValue, tan3Value, tan5Value, erfValue, fullIntValue])
dfr = pd.DataFrame(np.asarray(refTableData))
dfd = pd.DataFrame(np.asarray(dispTableData))
print('-'*35,'\nREFRACTION COMPARISON:\n')
print(dfr.to_string(index=False))
print('-'*35)
print('-'*35,'\nDISPERSION COMPARISON:\n')
print(dfd.to_string(index=False))
print('-'*35)
# print(dfr.to_latex(index=False))
# print(dfd.to_latex(index=False))
if errorTable:
ref = dispersion(lat, h)
ref.setReducedHeight(P, rho)
dispTableData = []
headerList = np.array(['s_P', 's_T', 's_H', 's_disp', 's_disp(dl=0)'])
dispTableData.append(headerList)
# dPlist = [2, 20, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 200]
# dTlist = [0, 0, 0, 0.02, 0.2, 2, 0, 0, 0, 0, 0, 0, 0.02, 0.2, 2 ]
# dRHlist= [0, 0, 0, 0, 0, 0, 0.002, 0.02, 0.2, 0, 0, 0, 0.002, 0.02, 0.2]
# dCO2list=[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 200, 2, 20, 200]
dPlist = [0, 10, 10, 20, 100]
dTlist = [0, 0.1, 0.2, 0.2, 1.0]
dRHlist= [0, 0.01, 0.01, 0.02, 0.05]
dCO2list=[0, 20, 20, 20, 20]
for i in range(len(dPlist)):
# nerr = at.dn_tph(l1, T, P, H, xc, dl=0, dT=dTlist[i], dP=dPlist[i], dRH=dRHlist[i], dCO2=dCO2list[i])
disp_error = ref.cassiniError(zenith, l1, l2, T, P, H, xc, dl1=0.001, dl2=0.001, dT=dTlist[i], dP=dPlist[i], dRH=dRHlist[i],
dCO2=dCO2list[i], dz=0) * 3.6e9
disp_error_dl0 = ref.cassiniError(zenith, l1, l2, T, P, H, xc, dl1=0, dl2=0, dT=dTlist[i], dP=dPlist[i], dRH=dRHlist[i],
dCO2=dCO2list[i], dz=0) * 3.6e9
dispTableData.append([dPlist[i], dTlist[i], dRHlist[i], disp_error, disp_error_dl0])
dfd = pd.DataFrame(np.asarray(dispTableData))
print('-'*35,'\nUNCERTAINTY ANALYSIS:\n')
print(dfd.to_string(index=False))
print('-'*35)
plt.show()
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env Python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
from AstroAtmosphere import *
from pyslalib import slalib
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: This script was used to compare the impact of using the old
B&S equations, and the particular implementation of SLALIB.
The rest of the script is almost a one-to-one copy of other
script that compared the atmospheric models.
Modeling the atmospheric refraction using multiple models of
the atmosphere and then comparing them.
Models included:
- Full integration method
- Plane-parallel atmosphere
- Cassini's model
- Tan5 model (A*tan(z) + B*tan^3(z) + C*tan^5(z))
- Oriani's theorem (A*tan(z) + B*tan^3(z))
- Erf model (from Corbard et al., 2019)
- SLALIB
'''
class atmosphereSLALIB:
def __init__(self, T=279.65, P=712, H=0.22):
self.name = 'SLALIB atmosphere'
self.T = T
self.P = P
self.H = H
self.h = 3064
self.lat = -24.5983
self.TLR = 0.0065
self.prec = 1e-8
def atmModel(self, A, B, zenith):
zenith = np.radians(zenith)
return A * np.tan(zenith) + B * (np.tan(zenith)**3)
def dispersion(self, l1, l2, z):
A1, B1 = slalib.sla_refco(self.h, self.T, self.P, self.H, l1, np.deg2rad(self.lat), self.TLR, self.prec)
A2, B2 = slalib.sla_atmdsp(self.T, self.P, self.H, l1, A1, B1, l2)
D1 = np.degrees(self.atmModel(A1, B1, z)) * 3600
D2 = np.degrees(self.atmModel(A2, B2, z)) * 3600
dispersionValue = ((D1 - D2) * 1000)
return dispersionValue
def refractivity(self, l=None):
'''
SLALIB uses the same atmospheric model as ZEMAX
based on the works of Hohenkerk & Sinclair and Seidelmann.
They take the B&S refraction formula as a basis.
'''
zObs = np.deg2rad(45.06563)
h = 0
T = self.T
P = self.P
H = self.H
lat = 24
tlr = 0.0065
eps = 1e-10
if not l:
l = self.lrange
R = []
if type(l) == float:
l = [l]
for wl in l:
R.append(slalib.sla_refro(zObs, h, T, P, H, wl, lat, tlr, eps)+1)
return np.asarray(R)
if __name__ == '__main__':
plt.rc('font', family='serif')
# plt.rc('xtick', labelsize='x-small')
# plt.rc('ytick', labelsize='x-small')
plt.rc('axes', labelsize='large')
# Standard atmospheric conditions
# T = 279.65 # k
# P = 71200 # Pa
# H = 0.22
# xc = 450 # ppm
T = 288.15 # k
P = 101325 # Pa
H = 0.0
xc = 314 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 30 # degrees
# Calculate the index of refraction
at = Observatory()
n1 = at.n_tph(l=l1, T=T, p=P, RH=H, xc=xc)
n2 = at.n_tph(l=l2, T=T, p=P, RH=H, xc=xc)
rho = at.rho(p=P, T=T, RH=H, xc=xc)
#######################################################
# DIRECT COMPARISON
ref = dispersion(lat, h)
ref.setReducedHeight(P, rho)
print('Ciddor refractivity: ', n1, n2)
print ('Cassini dispersion: %s mas' %(ref.cassini(n1, n2, zenith) * 3.6e6))
n1c = n1
n2c = n2
refraction = refraction(lat, h)
refraction.setReducedHeight(P, rho)
refractionValueCiddor = refraction.cassini(n1c, zenith) * 3.6e6 # in mas
# SLALIB
atSLALIB = atmosphereSLALIB(T, P/100, H)
n1 = atSLALIB.refractivity(l1)
n2 = atSLALIB.refractivity(l2)
print('B&S refractivity: ', n1, n2)
print ('SLALIB model: %s mas' %(atSLALIB.dispersion(l1, l2, zenith) ) )
# SLALIB refraction:
A1, B1 = slalib.sla_refco(atSLALIB.h, atSLALIB.T, atSLALIB.P, atSLALIB.H, l1, np.deg2rad(atSLALIB.lat), atSLALIB.TLR, atSLALIB.prec)
refractionValueSLALIB = np.degrees(atSLALIB.atmModel(A1, B1, zenith)) * 3.6e6 # in mas
print('Differential dispersion = {} mas'.format(atSLALIB.dispersion(l1, l2, zenith) - ref.cassini(n1c, n2c, zenith) * 3.6e6) )
print('Differential refraction = {} mas'.format( refractionValueSLALIB - refractionValueCiddor ) )
#######################################################
refractionAnalysis = True
dispersionAnalysis = True
tableCompare = True
if dispersionAnalysis:
# Initiate the refraction class
ref = dispersion(lat, h)
ref.setReducedHeight(P, rho)
# Compare the refraction models to check if the output is okay
print ('Plane - parallel atmosphere dispersion: %s mas' %(ref.planeParallel(n1, n2, zenith) * 3.6e6))
print ('Cassini dispersion: %s mas' %(ref.cassini(n1, n2, zenith) * 3.6e6))
print ('Tan5 dispersion: %s mas' %(ref.tan5(n1, n2, zenith) * 3.6e6))
print ('Oriani dispersion: %s mas' %(ref.oriani(n1, n2, zenith) * 3.6e6))
print ('Corbard dispersion: %s mas' %(ref.corbard(n1, n2, zenith)*3.6e6))
print ('Full integration: %s mas' %(ref.refractionIntegral(n1, n2, zenith)*3.6e6))
print ('SLALIB model: %s mas' %(atSLALIB.dispersion(l1, l2, zenith) ) )
# Now we calculate the dispersion over a wide range of zenith angles
# for ideal gas atmosphere
zenithRange = np.linspace(30, 85, 1000)
ppList = ref.planeParallel(n1, n2, zenithRange) * 3.6e6
cassiniList = ref.cassini(n1, n2, zenithRange) * 3.6e6
tan5List = ref.tan5(n1, n2, zenithRange) * 3.6e6
orianiList = ref.oriani(n1, n2, zenithRange) * 3.6e6
corbardList = [ref.corbard(n1, n2, z)[0] * 3.6e6 for z in zenithRange]
fullIntegrationList = np.asarray([ref.refractionIntegral(n1, n2, z) * 3.6e6 for z in zenithRange])
slalibList = atSLALIB.dispersion(l1, l2, zenithRange)
# for isothermal atmosphere
cassiniList_isotherm = ref.cassini(n1, n2, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e6
tan5List_isotherm = ref.tan5(n1, n2, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e6
orianiList_isotherm = ref.oriani(n1, n2, zenithRange, reduced_height=ref.H_isotherm(T)) * 3.6e6
corbardList_isotherm = [ref.corbard(n1, n2, z, reduced_height=ref.H_isotherm(T))[0] * 3.6e6 for z in zenithRange]
# check when the variation between the different models pass different values
dispersionWidth = []
widthList = []
for z in range(len(zenithRange)):
compList = [cassiniList[z], tan5List[z], orianiList[z], corbardList[z], cassiniList_isotherm[z], tan5List_isotherm[z], orianiList_isotherm[z], corbardList_isotherm[z], slalibList[z]]#, fullIntegrationList[z]]
width = np.max(compList) - np.min(compList)
widthList.append(width)
dispersionWidth.append((zenithRange[z], width))
masBorder = np.argmax(np.asarray(widthList)>1)
p1masBorder = np.argmax(np.asarray(widthList)>0.1)
p01masBorder = np.argmax(np.asarray(widthList)>0.01)
p001masBorder = np.argmax(np.asarray(widthList)>0.001)
plotAbsolute = 1
if plotAbsolute:
# Plotting it all, with vertical lines where the variation between the models exceed 1, 0.1, 0.01 and 0.001 mas.
fig = plt.figure(figsize=(9,5))
ax = fig.add_subplot(111)
ax.set_xlim([30,85])
ax.set_xlabel('Zenith (degrees)')
ax.set_ylabel('Dispersion (mas)')
ax.grid(True, alpha=0.3)
ax.axvline(dispersionWidth[masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[masBorder][0]-0.5, 402, '1 mas', ha='center', va='bottom', rotation='vertical')
ax.axvline(dispersionWidth[p1masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[p1masBorder][0]-0.5, 402, '0.1 mas', ha='center', va='bottom', rotation='vertical')
ax.axvline(dispersionWidth[p01masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[p01masBorder][0]-0.5, 402, '10 $\mu$as', ha='center', va='bottom', rotation='vertical')
ax.axvline(dispersionWidth[p001masBorder][0], linestyle='--', color='k', linewidth=0.5)
ax.text(dispersionWidth[p001masBorder][0]-0.5, 402, '1 $\mu$as', ha='center', va='bottom', rotation='vertical')
ax.plot(zenithRange, ppList, 'k-', label='Plane-parallel', alpha=0.2)
ax.plot(zenithRange, cassiniList, 'g-', label='Cassini')
ax.plot(zenithRange, tan5List, 'b-', label='$Atan(z) - Btan^3(z) + Ctan^5(z)$')
ax.plot(zenithRange, orianiList, 'y-', label='$Atan(z) - Btan^3(z)$')
ax.plot(zenithRange, corbardList, 'r-', label='Error function')
ax.plot(zenithRange, fullIntegrationList, 'c-', label='Full integration')
ax.plot(zenithRange, cassiniList_isotherm, 'g--', label='Cassini (isotherm)')
ax.plot(zenithRange, tan5List_isotherm, 'b--', label='$Atan(z) - Btan^3(z) + Ctan^5(z)$ (isotherm)')
ax.plot(zenithRange, orianiList_isotherm, 'y--', label='$Atan(z) - Btan^3(z)$ (isotherm)')
ax.plot(zenithRange, corbardList_isotherm, 'r--', label='Error function (isotherm)')
ax.tick_params(axis ='both', direction='in')
ax.legend()
plt.tight_layout()
plt.show()
plotRelative = 1
if plotRelative:
# RELATIVE TO FULL INTEGRATION
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
ax.set_xlim([30,85])
ax.set_xlabel('Zenith (degrees)', fontsize=18)
ax.set_ylabel(r'$\log_{10}(|(\Delta R - \Delta R_{int})|$ mas$^{-1})$', fontsize=18)
ax.axhline(np.log10(0.4), linestyle='-', linewidth=1, color='red')
ax.text(30.5, np.log10(0.4)+0.05, 'Residual dispersion requirement', fontsize=12)
ax.grid(True, which='major', alpha=0.3)
ax.grid(True, which='minor', alpha=0.2)
ax.plot(zenithRange, np.log10(np.abs(ppList-fullIntegrationList)), color='k', linestyle='-', linewidth=2, label='Plane-parallel')
ax.plot(zenithRange, np.log10(np.abs(cassiniList-fullIntegrationList)), color='g', linestyle=(0, (1, 1)), linewidth=2, label='Cassini')
ax.plot(zenithRange, np.log10(np.abs(orianiList-fullIntegrationList)), color='y', linestyle='-.', linewidth=2, label='Oriani')
ax.plot(zenithRange, np.log10(np.abs(tan5List-fullIntegrationList)), color='b', linestyle=(0, (5, 1)), linewidth=2, label='Oriani incl. fifth order')
ax.plot(zenithRange, np.log10(np.abs(np.asarray(corbardList)-fullIntegrationList)), color='r', linestyle=(0, (3, 1, 1, 1, 1, 1)), linewidth=2, label='Error function')
ax.plot(zenithRange, np.log10(np.abs(np.asarray(slalibList)-fullIntegrationList)), color='m', linewidth=2, label='SLALIB')
ax.plot(zenithRange, np.log10(np.abs(cassiniList-fullIntegrationList)), color='g', linestyle=(0, (1, 1)), linewidth=2)
ax.set_ylim(top=2.6, bottom=-7.2)
ax.tick_params(axis ='both', direction='in', labelsize=16)
ax.legend(fontsize=14, labelspacing=0.3, columnspacing=0.5, handletextpad=0.3, ncol=1, loc='lower right')
plt.tight_layout()
plt.show()
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env Python3
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
from matplotlib.colors import LogNorm
from tabulate import tabulate
import AstroAtmosphere
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: A comparison of various atmospheric refraction models.
Also these are used to calculate the difference in
atmospheric dispersion, given a set of conditions.
This is a reproduction of and expansion on Figure 1 of
Spàno (2014).
'''
class atmosphericModel:
def __init__(self, T, P, H, CO2, l1, l2):
self.T = T
self.P = P
self.H = H
self.CO2 = CO2
self.lrange = np.linspace(l1, l2, 100)
def BarrellAndSears(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.BarrellAndSears(l, T=self.T, p=self.P)-1)
def Ciddor(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.Ciddor(l, CO2=self.CO2)-1)
def Edlen1953(self, l=None):
if not l:
l = self.lrange
return(AstroAtmosphere.Edlen1953(l)-1)
def Edlen1966(self, l=None):
if not l:
l = self.lrange
return(AstroAtmosphere.Edlen1966(l)-1)
def Owens(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.Owens(l, T=self.T, p=self.P, RH=self.H)-1)
def BonschPotulski(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.BonschPotulski(l, T=self.T, p=self.P, CO2=self.CO2)-1)
def BirchDowns(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.BirchDowns(l)-1)
def PeckReeder(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.PeckReeder(l)-1)
def HohenkerkAndSinclair(self, l=None):
if not l:
l = self.lrange
return (AstroAtmosphere.HohenkerkAndSinclair(l, T=self.T, p=self.P, RH=self.H)-1)
def slalib(self, l=None):
if not l:
l = self.lrange
try:
from pyslalib import slalib
return(AstroAtmosphere.slalib(l, T=self.T, p=self.P, RH=self.H)-1)
except ImportError as e:
print('Could not import pyslalib.slalib. Using slower Python native function sla_refro().')
h=0
lat=-24
tlr=0.0065
eps=1e-10
zObs = np.deg2rad(45.07182)
p = self.P/100
R = []
if type(l) == float:
l = [l]
for wl in l:
R.append(AstroAtmosphere.sla_refro(zObs, h, self.T, p, self.H, wl, lat, tlr, eps))
if len(l) == 1:
return R[0]
else:
return np.asarray(R)
if __name__ == '__main__':
# Calculation of the refractivity of the different models
# multiplication by 1e8 to make the differentials more
# pronounced.
at = atmosphericModel(T=288.15, P=101325, H=0, CO2=300, l1=0.3, l2=2.5)
BSdata = 1e8 * at.BarrellAndSears()
CiddorData = 1e8 * at.Ciddor()
Edlen1953Data = 1e8 * at.Edlen1953()
Edlen1966Data = 1e8 * at.Edlen1966()
OwensData = 1e8 * at.Owens()
BonschPotulskiData = 1e8 * at.BonschPotulski()
BirchDownsData = 1e8 * at.BirchDowns()
PeckReederData = 1e8 * at.PeckReeder()
HSdata = 1e8 * at.HohenkerkAndSinclair()
slalibData = 1e8 * at.slalib()
# Plotting the results
fig = plt.figure(figsize=(9,5))
ax2 = fig.add_subplot(111)
ax2.grid(True, alpha=0.3)
ax2.plot(at.lrange, BSdata - CiddorData, 'b-', label='Barrell & Sears (1939)')
ax2.plot(at.lrange, CiddorData - CiddorData, 'k-', label='Ciddor (1996)')
ax2.plot(at.lrange, Edlen1953Data - CiddorData, 'r:', label='Edlen (1953)')
ax2.plot(at.lrange, Edlen1966Data - CiddorData, 'g--', label='Edlen (1966)')
ax2.plot(at.lrange, OwensData - CiddorData, 'm--', label='Owens (1967)')
ax2.plot(at.lrange, BonschPotulskiData - CiddorData, 'g-.', label='Bonsch & Potulski (1998)')
ax2.plot(at.lrange, BirchDownsData - CiddorData, 'r-.', label='Birch & Downs (1993, 1994)')
ax2.plot(at.lrange, PeckReederData - CiddorData, 'b--', label='Peck & Reeder (1972)')
ax2.plot(at.lrange, HSdata - CiddorData, 'y--', label='Hohenkerk & Sinclair (1985)')
ax2.plot(at.lrange, slalibData - CiddorData, 'y-', label='ZEMAX / SLALIB')
box = ax2.get_position()
ax2.set_position([box.x0, box.y0, box.width, box.height])
ax2.legend(loc='center left', bbox_to_anchor=(1, 0.5))
ax2.set_xlabel('Wavelength ($\mu m$)')
ax2.set_ylabel('$\Delta$n $\\times$ $10^{8}$')
ax2.set_ylim([-3,7])
plt.tight_layout()
plt.show()
# Let's compare the impact on the atmospheric dispersion.
# For the dispersion we must compare the delta for two lambda values
methodList = [ at.BarrellAndSears,
at.Ciddor,
at.Edlen1953,
at.Edlen1966,
at.Owens,
at.BonschPotulski,
at.BirchDowns,
at.PeckReeder,
at.HohenkerkAndSinclair,
at.slalib
]
filters = [ [0.800, 0.934], # I-band
[1.150, 1.345], # J-band
[1.490, 1.780], # H-band
[1.970, 2.320] # K-band
]
filterDispersion = []
# Iterate over the four MICADO broad band filters
for f in filters:
# Wavelengths of this particular filter
l1 = f[0]
l2 = f[1]
dispersionList = []
for m in methodList:
d1 = m(l=l1)
d2 = m(l=l2)
deltan = d1 - d2
# We assume, for sake of simplicity, a plane parallel atmosphere
# and calculate the dispersion in milli arcseconds.
dispersion = deltan * np.tan(np.deg2rad(45)) * 206265 * 1000
dispersionList.append(dispersion)
# We compare the atmospheric dispersion relative to the atmospheric
# dispersion as calculated using the Ciddor model.
dispersionList = np.asarray(dispersionList) - dispersionList[1]
filterDispersion.append(dispersionList)
# Print the results in a table.
methodStringList = ['Barrell & Sears (1939)',
'Ciddor (1996)',
'Edlen (1953)',
'Edlen (1966)',
'Owens (1967)',
'Bonsch & Potulski (1998)',
'Birch & Downs (1993, 1994)',
'Peck & Reeder (1972)',
'Hohenkerk & Sinclair (1985)',
'slalib']
tableData = []
for i in range(len(methodStringList)):
tableData.append([methodStringList[i], filterDispersion[0][i], filterDispersion[1][i], filterDispersion[2][i], filterDispersion[3][i]])
print(tabulate(tableData, headers=['Model', 'I-band\n(0.8 - 0.934)', 'J-band\n(1.15 - 1.345)', 'H-band\n(1.49 - 1.78)', 'K-band\n(1.97 - 2.32)']))
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env Python3
import numpy as np
import matplotlib.pyplot as plt
import AstroAtmosphere as atm
from ADC import *
'''
@author: Joost van den Born
@Contact: born@astron.nl
@Description: Caclulates the differential dispersion between the ADC and
the atmosphere following the ciddor and cassini dispersion
model.
This script generates figure 4 from the paper.
'''
# Initializing ADC object and defining the filter bands
adc = ADC()
bandName = ['I-band', 'J-band', 'H-band', 'K-band']
bandList = [(0.8, 0.934), (1.15, 1.345), (1.49, 1.78), (1.97, 2.32)]
# Some empty lists for later use
wavelengthList = []
atDispersionList = []
adcDispersionList = []
for i in range(len(bandList)):
# We want to find the dispersion of the atmosphere and
# and of the ADC, at z=30 deg.
lMin = bandList[i][0]
lMax = bandList[i][1]
z = 30 # degrees
lRange = np.linspace(lMin, lMax, 100)
wavelengthList.append(lRange)
# Set ADC position optimized for H-band, z=30 deg.
atDispersion = atm.quick_dispersion(lMin, lMax, z, conditions='CERRO_ARMAZONES') * 3.6e6
adcOptimumAngle = adc.optimize(atDispersion, lMin, lMax)
adc.setOrientation(adcOptimumAngle)
# Now iterate over the band to see how the differential dispersion changes
atDispersionValues = []
adcDispersionValues = []
for l in lRange:
atDispersionValues.append(atm.quick_dispersion(np.min(lRange), l, z, conditions='CERRO_ARMAZONES')* 3.6e6 )
adcDispersionValues.append(adc.calculateDispersion(np.min(lRange),l)[1])
atDispersionList.append(atDispersionValues)
adcDispersionList.append(adcDispersionValues)
# Calculating the differential dispersion
atDispersionList = np.asarray(atDispersionList)
adcDispersionList = np.asarray(adcDispersionList)
deltaDispersion = atDispersionList - adcDispersionList
# Plot the results
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
linestyleList = [':', '-.', '--', '-']
for i in range(len(bandList)):
ax.plot(wavelengthList[i], deltaDispersion[i], label=bandName[i], linestyle=linestyleList[i], linewidth=2, color='k')
ax.set_ylabel('Differential $\Delta$R (mas)', fontsize=18)
ax.set_xlabel('$\lambda$ ($\mu$m)', fontsize=18)
ax.grid(True, alpha=0.3)
ax.legend(fontsize=16, loc='lower right')
ax.tick_params(axis ='both', direction='in', labelsize=16)
plt.show()
+324
View File
@@ -0,0 +1,324 @@
#!/usr/bin/env Python
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
import AstroAtmosphere as atm
import sys
from ADC import *
'''
@author: Joost van den Born
@Description: Calculates differential refraction (dispersion) between stars with a different
effective temperature. Similar to Gubler & Tytler 1998
'''
def blackbody(l, T):
'''
Calculates the intensity output of a star with effective temperature T at wavelength l
'''
h = 6.626070150e-34 # J*s
k = 1.38064852e-23 # J/K
c = 299792458 # m/s
return 2 * np.pi * h * c**2 / l**5 * 1 / (np.exp( h * c / (l * k * T)) - 1)
def effectiveWavelength(T, lmin, lmax):
'''
Calculates effective wavelength of a star from the effective temperature.
'''
lmin = lmin/1e6
lmax = lmax/1e6
_l = np.linspace(lmin, lmax, 1000)
_I = blackbody(_l, T)
return np.sum(_I*_l)/np.sum(_I)*1e6
if __name__ == '__main__':
# Parameters at Cerro Armazones
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 60 # degrees
offset = 0#.285/2 # degrees, ADC offset to simulate finite step size
S2temps = [2500, 4000, 5500, 7000, 8500]
S1temps = np.linspace(2500, 1e4, 100)
########################################################################################
########################################################################################
# 1. We look at the differential refraction through the atmosphere, without an ADC
# at = atm.dispersion(lat, h)
at = atm.refraction(lat, h)
at.setReducedHeight(P, at.rho(P, T, H, xc) )
# Calculating the differential atmospheric refraction for stars of different temperatures
data = []
for T2 in S2temps:
# Properties of star 2
l_eff_2 = effectiveWavelength(T2, l1, l2)
n2 = at.n_tph(l_eff_2, T, P, H, xc)
R2 = at.cassini(n2, zenith)
# Calculating the differences with star 1
dataEntry = []
for T1 in S1temps:
l_eff_1 = effectiveWavelength(T1, l1, l2)
n1 = at.n_tph(l_eff_1, T, P, H, xc)
R1 = at.cassini(n1, zenith)
diffRef = (R1 - R2) * 3.6e9
dataEntry.append(diffRef)
data.append(dataEntry)
# Plotting the results
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
ax2 = ax.twiny()
ax.grid(True, alpha=0.3)
linestylesList = [':', '-.', '--', '-', (0, (3, 1, 1, 1, 1, 1))]
for i in range(len(data)):
ax.plot(S1temps, data[i], label=str(S2temps[i]) + ' K', linestyle=linestylesList[i], color='k', linewidth=2)
S1temp_ticks = ax.get_xticks()
S1temp_tick_loc = ax.get_xticks()
l_eff_ticks = []
for T1 in S1temp_ticks:
l_eff_ticks.append(round( (effectiveWavelength(T1, l1, l2)*1000-1620),1))
ax2.set_xticklabels(l_eff_ticks)
ax2.set_xlim(np.min(S1temps), np.max(S1temps))
ax2.set_xlabel('$\lambda_{eff} - 1620$ (nm) ', fontsize=18)
ax2.tick_params(axis ='both', direction='in', labelsize=16)
ax.set_xlabel('Temperature of Star 1 (K)', fontsize=18)
ax.set_ylabel('$\Delta$R ($\mu$as)', fontsize=18)
ax.legend(fontsize=16, labelspacing=0.3)
ax.set_xlim(np.min(S1temps), np.max(S1temps))
ax.tick_params(axis ='both', direction='in', labelsize=16)
# plt.show()
########################################################################################
########################################################################################
########################################################################################
########################################################################################
# 2. We look now at the differential refraction through the atmosphere, including an ADC
at = atm.refraction(lat, h)
at.setReducedHeight(P, at.rho(P, T, H, xc) )
adc = ADC()
# Conditions at the band cut-on and cut-off wavelength
n_l1 = at.n_tph(l1, T, P, H, xc)
n_l2 = at.n_tph(l2, T, P, H, xc)
atd = atm.dispersion(lat, h)
atd.setReducedHeight(P, at.rho(P, T, H, xc) )
# Optimize ADC for broad band edges at zenith distance.
atm_disp_full_band = atd.cassini(n_l1, n_l2, zenith)
print(atm_disp_full_band*3.6e6)
adcOptimumAngle = adc.optimize(atm_disp_full_band*3.6e6, l1, l2)
adc.setOrientation(adcOptimumAngle+offset)
# Calculating the differential atmospheric refraction for stars of different temperatures
data = []
for T2 in S2temps:
# Properties of star 2
l_eff_2 = effectiveWavelength(T2, l1, l2)
n2 = at.n_tph(l_eff_2, T, P, H, xc)
R2_atm = at.cassini(n2, zenith)
R2_adc = adc.calculateDispersion(l_eff_2, l1, refraction=True)
# Calculating the differences with star 1
dataEntry = []
for T1 in S1temps:
l_eff_1 = effectiveWavelength(T1, l1, l2)
n1 = at.n_tph(l_eff_1, T, P, H, xc)
R1_atm = at.cassini(n1, zenith)
R1_adc = adc.calculateDispersion(l_eff_1, l1, refraction=True)
diffRef = ( (R1_adc[1] - R2_adc[1]) + ((R1_atm - R2_atm) * 3.6e6 ) ) * 1e3
dataEntry.append(diffRef)
data.append(dataEntry)
# Plotting the results
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111)
ax2 = ax.twiny()
ax.grid(True, alpha=0.3)
linestylesList = [':', '-.', '--', '-', (0, (3, 1, 1, 1, 1, 1))]
for i in range(len(data)):
ax.plot(S1temps, data[i], label=str(S2temps[i]) + ' K', linestyle=linestylesList[i], color='k', linewidth=2)
S1temp_ticks = ax.get_xticks()
S1temp_tick_loc = ax.get_xticks()
l_eff_ticks = []
for T1 in S1temp_ticks:
l_eff_ticks.append(round( (effectiveWavelength(T1, l1, l2)*1000-1620),1))
ax2.set_xticklabels(l_eff_ticks)
ax2.set_xlim(np.min(S1temps), np.max(S1temps))
ax2.set_xlabel('$\lambda_{eff} - 1620$ (nm) ', fontsize=18)
ax2.tick_params(axis ='both', direction='in', labelsize=16)
ax.set_xlabel('Temperature of Star 1 (K)', fontsize=18)
ax.set_ylabel('$\Delta$R ($\mu$as)', fontsize=18)
ax.legend(fontsize=16, labelspacing=0.3, loc='lower right')
ax.set_xlim(np.min(S1temps), np.max(S1temps))
ax.tick_params(axis ='both', direction='in', labelsize=16)
plt.show()
########################################################################################
########################################################################################
########################################################################################
########################################################################################
# 3. Now we look at the differential dispersion, without an ADC
at = atm.dispersion(lat, h)
at.setReducedHeight(P, at.rho(P, T, H, xc) )
# Conditions at the band cut-on and cut-off wavelength
n_l1 = at.n_tph(l1, T, P, H, xc)
n_l2 = at.n_tph(l2, T, P, H, xc)
# Optimize ADC for broad band edges at zenith distance.
atm_disp_full_band = at.cassini(n_l1, n_l2, zenith)
print(atm_disp_full_band*3.6e6)
# Check that we can use dispersion? If not, how to calculate refraction...?
# We plot below the difference in atmospheric dispersion of the two stars, incl. an ADC.
data = []
for T2 in S2temps:
l_eff_2 = effectiveWavelength(T2, l1, l2)
n2 = at.n_tph(l_eff_2, T, P, H, xc)
dataEntry = []
for T1 in S1temps:
l_eff_1 = effectiveWavelength(T1, l1, l2)
n1 = at.n_tph(l_eff_1, T, P, H, xc)
atm_disp = at.cassini(n1, n2, zenith) * 3.6e9
dataEntry.append(atm_disp)
data.append(dataEntry)
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
fig2 = plt.figure(figsize=(9,6))
ax = fig2.add_subplot(111)
ax2 = ax.twiny()
ax.grid(True, alpha=0.3)
linestylesList = [':', '-.', '--', '-', (0, (3, 1, 1, 1, 1, 1))]
for i in range(len(data)):
ax.plot(S1temps, data[i], label=str(S2temps[i]) + ' K', linestyle=linestylesList[i], color='k', linewidth=2)
S1temp_ticks = ax.get_xticks()
S1temp_tick_loc = ax.get_xticks()
l_eff_ticks = []
for T1 in S1temp_ticks:
l_eff_ticks.append(round( (effectiveWavelength(T1, l1, l2)*1000-1620),1))
# ax2.plot(S1temps, data[0], alpha=0)
ax2.set_xticklabels(l_eff_ticks)
ax2.set_xlim(np.min(S1temps), np.max(S1temps))
ax2.set_xlabel('$\lambda_{eff} - 1620$ (nm) ', fontsize=18)
ax2.tick_params(axis ='both', direction='in', labelsize=16)
ax.set_xlabel('Temperature of Star 1 (K)', fontsize=18)
ax.set_ylabel('$\Delta(\Delta$R) ($\mu$as), excl. ADC', fontsize=18)
ax.legend(fontsize=16, labelspacing=0.3, loc='lower right')
ax.set_xlim(np.min(S1temps), np.max(S1temps))
ax.tick_params(axis ='both', direction='in', labelsize=16)
# plt.show()
########################################################################################
########################################################################################
########################################################################################
########################################################################################
########################################################################################
# 4. Now we add the ADC and look again at the differential refraction, but on the detector
at = atm.dispersion(lat, h)
at.setReducedHeight(P, at.rho(P, T, H, xc) )
adc = ADC()
# Conditions at the band cut-on and cut-off wavelength
n_l1 = at.n_tph(l1, T, P, H, xc)
n_l2 = at.n_tph(l2, T, P, H, xc)
# Optimize ADC for broad band edges at zenith distance.
atm_disp_full_band = at.cassini(n_l1, n_l2, zenith)
print(atm_disp_full_band*3.6e6)
adcOptimumAngle = adc.optimize(atm_disp_full_band*3.6e6, l1, l2)
adc.setOrientation(adcOptimumAngle+offset)
# Check that we can use dispersion? If not, how to calculate refraction...?
# We plot below the difference in atmospheric dispersion of the two stars, incl. an ADC.
data = []
for T2 in S2temps:
l_eff_2 = effectiveWavelength(T2, l1, l2)
n2 = at.n_tph(l_eff_2, T, P, H, xc)
atm_disp2 = at.cassini(n_l1, n2, zenith) * 3.6e6
adc_disp2 = adc.calculateDispersion(l1, l_eff_2)[1]
dataEntry = []
for T1 in S1temps:
l_eff_1 = effectiveWavelength(T1, l1, l2)
n1 = at.n_tph(l_eff_1, T, P, H, xc)
atm_disp1 = at.cassini(n_l1, n1, zenith) * 3.6e6
adc_disp1 = adc.calculateDispersion(l1,l_eff_1)[1]
diffDisp = ((adc_disp1 - adc_disp2) - (atm_disp1 - atm_disp2)) * 1e3
dataEntry.append(diffDisp)
data.append(dataEntry)
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
fig2 = plt.figure(figsize=(9,6))
ax = fig2.add_subplot(111)
ax2 = ax.twiny()
ax.grid(True, alpha=0.3)
linestylesList = [':', '-.', '--', '-', (0, (3, 1, 1, 1, 1, 1))]
for i in range(len(data)):
ax.plot(S1temps, data[i], label=str(S2temps[i]) + ' K', linestyle=linestylesList[i], color='k', linewidth=2)
S1temp_ticks = ax.get_xticks()
S1temp_tick_loc = ax.get_xticks()
l_eff_ticks = []
for T in S1temp_ticks:
l_eff_ticks.append(round( (effectiveWavelength(T, l1, l2)*1000-1620),1))
# ax2.plot(S1temps, data[0], alpha=0)
ax2.set_xticklabels(l_eff_ticks)
ax2.set_xlim(np.min(S1temps), np.max(S1temps))
ax2.set_xlabel('$\lambda_{eff} - 1620$ (nm) ', fontsize=18)
ax2.tick_params(axis ='both', direction='in', labelsize=16)
ax.set_xlabel('Temperature of Star 1 (K)', fontsize=18)
ax.set_ylabel('$\Delta(\Delta$R) ($\mu$as), incl. ADC', fontsize=18)
ax.legend(fontsize=16, labelspacing=0.3, loc='lower right')
ax.set_xlim(np.min(S1temps), np.max(S1temps))
ax.tick_params(axis ='both', direction='in', labelsize=16)
plt.show()
########################################################################################
########################################################################################
########################################################################################
+353
View File
@@ -0,0 +1,353 @@
#!/usr/bin/env Python
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
from AstroAtmosphere import *
from tabulate import tabulate
'''
@ Author: Joost van den Born
@ Contact: born@astron.nl
@ Description: An exploration of the parameter space for the Ciddor-Cassini model.
The results here are given in table 3 and Appendix A of the paper.
'''
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
at = Observatory()
pdrFigs = False
errorSpace = False
generate_images = True
T = 279.65 # k
P = 71200 # Pa
H = 0.22
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 30 # degrees
rho = at.rho(p=P, T=T, RH=H, xc=xc)
disp = dispersion(lat, h)
disp.setReducedHeight(P, rho)
# Change dl, dT, dP, dRH, dCO2 to the values of interest.
n1 = at.n_tph(l=1.49, T=279.65, p=71200, RH=0.22, xc=450)
n2 = at.n_tph(l=1.78, T=279.65, p=71200, RH=0.22, xc=450)
R1ca = at.dn_tph(l=1.49, T=279.65, p=71200, RH=0.22, xc=450,
dl=0.001, dT=0.4, dP=10, dRH=0.01, dCO2=20)
R2ca = at.dn_tph(l=1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl=0.001, dT=0.4, dP=10, dRH=0.01, dCO2=20)
# print (n1, at.dn_tph(l=1.49, T=288.0, p=101325, RH=0.0, xc=450, dl=0.0, dT=0.5, dP=0.0, dRH=0.0, dCO2=0) )
# print(disp.cassiniError( zenith, l1, l2, T, P, H, xc, dl1=0.001, dl2=0.001, dT=0.4, dP=10, dRH=0.01, dCO2=20 )*3.6e9)
zenith_true = 30 # degrees
dispValue = disp.cassini(n1, n2, zenith_true)
defaultDispCA = disp.cassini(n1, n2, zenith_true)*3.6e6
errorDispValue = disp.cassiniError(zenith, l1, l2, T, P, H, xc, dl1=0.001, dl2=0.001, dT=0.4, dP=10, dRH=0.01, dCO2=20 )
print ('Dispersion between 1.49 and 1.78 micron at z = %i deg: %.3f +/- %.3f mas' %(zenith_true, dispValue*3.6e6, errorDispValue*3.6e6))
R1s = at.n_tph(l=1.49, T=288.15, p=101325, RH=0.0, xc=450)
R2s = at.n_tph(l=1.78, T=288.15, p=101325, RH=0.0, xc=450)
# Varying the parameters in the non-errror formula:
if pdrFigs:
Trange = np.linspace(262, 298, 1000)
Prange = np.linspace(69000, 73000, 1000)
Hrange = np.linspace(0.1, 0.3, 1000)
fig = plt.figure()
# Variation in T
dispListS, dispListCA = [], []
for t in Trange:
R1ca = at.n_tph(l=1.49, T=t, p=71200, RH=0.22, xc=450)
R2ca = at.n_tph(l=1.78, T=t, p=71200, RH=0.22, xc=450)
dispValueS = disp.cassini(R1s, R2s, zenith_true) * 3.6e6
dispValueCA = disp.cassini(R1ca, R2ca, zenith_true) * 3.6e6
dispListS.append(dispValueS)
dispListCA.append(dispValueCA)
ax1 = fig.add_subplot(131)
ax1.grid(True, alpha=0.3)
ax1.plot(Trange, np.abs(dispListCA-defaultDispCA), 'r-', label='Cerro Armazones (p=712 mbar, T=6.5C, H=22%)')
ax1.set_xlabel('Temperature (K)')
ax1.set_ylabel('Dispersion (mas)')
ax1.tick_params(axis ='both', direction='in')
# Variation in P
dispListS, dispListCA = [], []
for p in Prange:
R1ca = at.n_tph(l=1.49, T=279.65, p=p, RH=0.22, xc=450)
R2ca = at.n_tph(l=1.78, T=279.65, p=p, RH=0.22, xc=450)
dispValueS = disp.cassini(R1s, R2s, zenith_true) * 3.6e6
dispValueCA = disp.cassini(R1ca, R2ca, zenith_true) * 3.6e6
dispListS.append(dispValueS)
dispListCA.append(dispValueCA)
ax2 = fig.add_subplot(132)
ax2.grid(True, alpha=0.3)
ax2.plot(Prange/100, np.abs(dispListCA-defaultDispCA), 'r-', label='Cerro Armazones (p=712 mbar, T=6.5C, H=22%)')
ax2.set_xlabel('Pressure (mbar)')
ax2.set_ylabel('Dispersion (mas)')
ax2.tick_params(axis ='both', direction='in')
# Variation in T
dispListS, dispListCA = [], []
for h in Hrange:
R1ca = at.n_tph(l=1.49, T=279.65, p=71200, RH=h, xc=450)
R2ca = at.n_tph(l=1.78, T=279.65, p=71200, RH=h, xc=450)
dispValueS = disp.cassini(R1s, R2s, zenith_true) * 3.6e6
dispValueCA = disp.cassini(R1ca, R2ca, zenith_true) * 3.6e6
dispListS.append(dispValueS)
dispListCA.append(dispValueCA)
ax3 = fig.add_subplot(133)
ax3.grid(True, alpha=0.3)
ax3.plot(Hrange*100, np.abs(dispListCA-defaultDispCA), 'r-', label='Cerro Armazones (p=712 mbar, T=6.5C, H=22%)')
ax3.set_xlabel('Humidity (%)')
ax3.set_ylabel('Dispersion (mas)')
ax3.tick_params(axis ='both', direction='in')
plt.tight_layout()
plt.show()
# Plotting variations in parameters with corresponding errors.
if errorSpace:
Trange = np.linspace(-50, 50, 100)
Prange = np.linspace(-50, 50, 10)
Hrange = np.linspace(-0.025, 0.025, 10)
fig = plt.figure()
# Variation in T
errorRange = [0.1, 0.2, 0.3, 1.0]
errorRange = errorRange[::-1]
ax1 = fig.add_subplot(111)
ax1.grid(True, alpha=0.3)
for e in range(len(errorRange)):
dispList = []
errorList = []
for t in Trange:
R1n = at.n_tph(l=1.49, T=279.65+t, p=71200, RH=0.22, xc=450)
R2n = at.n_tph(l=1.78, T=279.65+t, p=71200, RH=0.22, xc=450)
dispValue = disp.cassini(R1n, R2n, zenith_true) * 3.6e6
errorValue = disp.cassiniError(zenith_true, l1, l2, T, P, H, xc, dl1=0.0, dl2=0.0, dT=errorRange[e], dP=0, dRH=0, dCO2=0 ) * 3.6e6
dispList.append(dispValue)
errorList.append(errorValue)
dispList = np.asarray(dispList)
errorList = np.asarray(errorList)
ax1.fill_between(Trange+279.65, dispList-errorList, dispList+errorList, label='$\delta p$ = %s $^{\circ}$C' %(errorRange[e]))
ax1.plot(Trange+279.65, dispList, 'k-')
ax1.set_xlabel('Temperature (K)')
ax1.set_ylabel('Dispersion (mas)')
ax1.set_xlim([np.min(Trange)+279.65,np.max(Trange)+279.65])
ax1.set_ylim([19,32])
handles, labels = ax1.get_legend_handles_labels()
ax1.legend(handles[::-1], labels[::-1], title='Error in measurements')
plt.tight_layout()
plt.show()
# Varying the parameters in the uncertainty formula:
if generate_images:
################################################
# Listing some values and the results in a table
pList = [101325, 101325, 71200, 71200, 71200]
TList = [288.15, 288.15, 279.65, 279.65, 279.65]
HList = [0, 0, 0.22, 0.22, 0.6]
CO2List = [450] * len(pList)
lList = [1.35] * len(pList)
dpList = [0, 20, 20, 10, 20]
dTlist = [0, 0.2, 0.2, 0.1, 0.2]
dRHlist = [0, 0.02, 0.02, 0.01, 0.02]
dCO2list = [0, 20, 20, 10, 20]
dlList = [0, 0.001, 0.001, 0.001, 0.001]
refractionList, sigmaList = [], []
for i in range(len(pList)):
n = at.n_tph(l=lList[i], T=TList[i], p=pList[i], RH=HList[i], xc=CO2List[i])
s = at.dn_tph(l=lList[i], T=TList[i], p=pList[i], RH=HList[i], xc=CO2List[i],
dl=dlList[i], dT=dTlist[i], dP=dpList[i], dRH=dRHlist[i], dCO2=dCO2list[i])
refractionList.append((n-1)*1e8)
sigmaList.append(s*1e8)
tableData = []
for i in range(len(pList)):
tableData.append([pList[i], TList[i], HList[i], CO2List[i], lList[i], dpList[i], dTlist[i], dRHlist[i], dCO2list[i], dlList[i], refractionList[i], sigmaList[i]])
print('REFRACTION: ')
print (tabulate(tableData, headers = ['p', 'T', 'H', 'x_c', 'l', 'dp', 'dT', 'dH', 'dx_c', 'dl', 'n', 's']))
#################################################
print ('\n\n\n')
################################################
# Listing some values and the results in a table
pList = [101325, 101325, 101325, 101325, 101325]
TList = [288.15, 288.15, 288.15, 288.15, 288.15]
HList = [0.0, 0.0, 0.0, 0.0, 0.0]
CO2List = [450] * len(pList)
# lList = [1.35] * len(pList)
dpList = [0, 10, 10, 20, 100]
dTlist = [0, 0.1, 0.2, 0.2, 1.0]
dRHlist = [0, 0.01, 0.01, 0.02, 0.05]
dCO2list = [0, 20, 20, 20, 20]
# dlList = [0, 0.001, 0.001, 0.001, 0.001] # Uncomment this and comment the next
# line to check the impact with uncertainty in wavelength
dlList = [0, 0, 0, 0, 0]
dzList = [0] * len(pList)
dispersionList, errorList = [], []
for i in range(len(pList)):
zenith_true = 30 # degrees
n1 = at.n_tph(l=1.49, T=TList[i], p=pList[i], RH=HList[i], xc=CO2List[i])
n2 = at.n_tph(l=1.78, T=TList[i], p=pList[i], RH=HList[i], xc=CO2List[i])
dn1 = at.dn_tph(l=1.49, T=TList[i], p=pList[i], RH=HList[i], xc=CO2List[i],
dl=dlList[i], dT=dTlist[i], dP=dpList[i], dRH=dRHlist[i], dCO2=dCO2list[i])
dn2 = at.dn_tph(l=1.78, T=TList[i], p=pList[i], RH=HList[i], xc=CO2List[i],
dl=dlList[i], dT=dTlist[i], dP=dpList[i], dRH=dRHlist[i], dCO2=dCO2list[i])
dispValue = disp.cassini(n1, n2, zenith_true) * 3.6e6
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, TList[i], pList[i], HList[i], CO2List[i],
dl1=dlList[i], dl2=dlList[i], dT=dTlist[i], dP=dpList[i], dRH=dRHlist[i], dCO2=dCO2list[i], dz=dzList[i] ) * 3.6e9
dispersionList.append(dispValue)
errorList.append(errorDispValue)
tableData = []
for i in range(len(pList)):
tableData.append([pList[i], TList[i], HList[i], CO2List[i], dpList[i], dTlist[i], dRHlist[i], dCO2list[i], dlList[i], dispersionList[i], errorList[i]])
print('DISPERSION: ')
print (tabulate(tableData, headers = ['p', 'T', 'H', 'x_c', 'dp', 'dT', 'dH', 'dx_c', 'dl', 'dispersion', 'error']))
# Variation of the uncertainty in P
pRange = np.linspace(0, 100, 1000)
errorList = []
for p in pRange:
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl1=0.001, dl2=0.001, dT=0.2, dP=p, dRH=0.01, dCO2=20, dz=0 ) * 3.6e9
errorList.append(errorDispValue)
fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.plot(pRange, errorList, 'k-')
ax.set_xlabel('$\sigma_p$ (Pa)')
ax.set_ylabel('$\sigma_{\Delta R}$ ($\mu$as)')
ax.set_xlim([np.min(pRange), np.max(pRange)])
ax.set_ylim([np.min(errorList)-0.05*(np.max(errorList) - np.min(errorList)), np.max(errorList)])
ax.tick_params(axis ='both', direction='in')
plt.tight_layout()
# Variation of the uncertainty in T
TRange = np.linspace(0.0, 1, 1000)
errorList = []
for T in TRange:
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl1=0.001, dl2=0.001, dT=T, dP=10, dRH=0.01, dCO2=20, dz=0 ) * 3.6e9
errorList.append(errorDispValue)
fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.plot(TRange, errorList, 'k-')
ax.set_xlabel('$\sigma_T$ (K)')
ax.set_ylabel('$\sigma_{\Delta R}$ ($\mu$as)')
ax.set_xlim([np.min(TRange), np.max(TRange)])
ax.set_ylim([np.min(errorList)-0.05*(np.max(errorList) - np.min(errorList)), np.max(errorList)])
ax.tick_params(axis ='both', direction='in')
plt.tight_layout()
# Variation of the uncertainty in H
HRange = np.linspace(0.000, 0.05, 1000)
errorList = []
for H in HRange:
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl1=0.001, dl2=0.001, dT=0.2, dP=10, dRH=H, dCO2=20, dz=0 ) * 3.6e9
errorList.append(errorDispValue)
fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.plot(HRange, errorList, 'k-')
ax.set_xlabel('$\sigma_H$')
ax.set_ylabel('$\sigma_{\Delta R}$ ($\mu$as)')
ax.set_xlim([np.min(HRange), np.max(HRange)])
ax.set_ylim([np.min(errorList)-0.05*(np.max(errorList) - np.min(errorList)), np.max(errorList)])
ax.tick_params(axis ='both', direction='in')
plt.tight_layout()
# Variation of the uncertainty in xc
CO2Range = np.linspace(0, 200, 1000)
errorList = []
for CO2 in CO2Range:
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl1=0.001, dl2=0.001, dT=0.2, dP=10, dRH=0.01, dCO2=CO2, dz=0 ) * 3.6e9
errorList.append(errorDispValue)
fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.plot(CO2Range, errorList, 'k-')
ax.set_xlabel('$\sigma_{x_c}$ (ppm)')
ax.set_ylabel('$\sigma_{\Delta R}$ ($\mu$as)')
ax.set_xlim([np.min(CO2Range), np.max(CO2Range)])
ax.set_ylim([np.min(errorList)-0.05*(np.max(errorList) - np.min(errorList)), np.max(errorList)])
ax.tick_params(axis ='both', direction='in')
plt.tight_layout()
# Variation of the uncertainty in wavelength
lRange = np.linspace(0.0, 0.001, 1000)
errorList = []
for l in lRange:
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl1=l, dl2=l, dT=0.2, dP=10, dRH=0.01, dCO2=20, dz=0 ) * 3.6e9
errorList.append(errorDispValue)
fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.plot(lRange*1000, errorList, 'k-')
ax.set_xlabel('$\sigma_{\lambda}$ (nm)')
ax.set_ylabel('$\sigma_{\Delta R}$ ($\mu$as)')
ax.set_xlim([np.min(lRange)*1000, np.max(lRange)*1000])
ax.set_ylim([np.min(errorList)-0.05*(np.max(errorList) - np.min(errorList)), np.max(errorList)])
ax.tick_params(axis ='both', direction='in')
plt.tight_layout()
# Variation of the uncertainty in zenith
zRange = np.linspace(0, 60/3600, 1000)
errorList = []
for z in zRange:
errorDispValue = disp.cassiniError( zenith_true, 1.49, 1.78, T=279.65, p=71200, RH=0.22, xc=450,
dl1=0.001, dl2=0.001, dT=0.2, dP=10, dRH=0.01, dCO2=20, dz=z ) * 3.6e9
errorList.append(errorDispValue)
fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.grid(True, alpha=0.3)
ax.plot(zRange*3600, errorList, 'k-')
ax.set_xlim([np.min(zRange)*3600, np.max(zRange)*3600])
ax.set_ylim([np.min(errorList)-0.05*(np.max(errorList) - np.min(errorList)), np.max(errorList)])
ax.set_xlabel('$\sigma_z$ (arcseconds)')
ax.set_ylabel('$\sigma_{\Delta R}$ ($\mu$as)')
ax.tick_params(axis ='both', direction='in')
plt.tight_layout()
plt.show()
@@ -0,0 +1,85 @@
import numpy as np
from AstroAtmosphere import *
'''
Date: 02 May 2019
Author: Joost van den Born
Description: This script compares the uncertainty propagation results to a Monte Carlo method.
'''
# Parameters at Cerro Armazones
T = 288.15 # k
P = 101325 # Pa
H = 0.0
xc = 450 # ppm
lat = -24.5983 # degrees
h = 3064 # m
l1 = 1.49 # micron
l2 = 1.78 # micron
zenith = 45 # degrees
dl1 = 0
dl2 = 0
dT = 0
dP = 2
dRH = 0
dCO2= 0
dz = 1/3600 # degrees
# Initializing dispersion model
at = Observatory()
dPlist = [2, 20, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 200]
dTlist = [0, 0, 0, 0.02, 0.2, 2, 0, 0, 0, 0, 0, 0, 0.02, 0.2, 2 ]
dRHlist= [0, 0, 0, 0, 0, 0, 0.002, 0.02, 0.2, 0, 0, 0, 0.002, 0.02, 0.2]
dCO2list=[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 200, 2, 20, 200]
for i in range(len(dPlist)):
nerr = at.dn_tph(l1, T, P, H, xc, dl=0, dT=dTlist[i], dP=dPlist[i], dRH=dRHlist[i], dCO2=dCO2list[i])
print(nerr, dPlist[i], dTlist[i], dRHlist[i], dCO2list[i])
disp = dispersion(lat, h)
rho_obs = at.rho(P, T, H, xc)
disp.setReducedHeight(P, rho_obs)
zenith_true = 10
# We do a Monte Carlo error analysis on the dispersion.
print('MONTE-CARLO UNCERTAINTY ANALYSIS')
print('-'*35)
for j in range(len(dPlist)):
sigmaP = dPlist[j] # mbar
sigmaT = dTlist[j] # K
sigmaH = dRHlist[j]
sigmaCO2 = dCO2list[j] # ppm
N = 10000
l1sample = np.random.normal(l1, 0, N)
l2sample = np.random.normal(l2, 0, N)
Tsample = np.random.normal(T, sigmaT, N)
Psample = np.random.normal(P, sigmaP, N)
Hsample = np.random.normal(H, sigmaH, N)
CO2sample = np.random.normal(xc, sigmaCO2, N)
dzsample = np.random.normal(zenith_true, dz, N)
results = []
nList = []
for i in range(N):
# Calculate the refraction at l1 and l2
R1ca2 = at.n_tph(l=l1sample[i], T=Tsample[i], p=Psample[i], RH=Hsample[i], xc=CO2sample[i])
R2ca2 = at.n_tph(l=l2sample[i], T=Tsample[i], p=Psample[i], RH=Hsample[i], xc=CO2sample[i])
dispersionValue = disp.cassini(R1ca2, R2ca2, zenith_true)
results.append(dispersionValue)
nList.append(R1ca2)
cassErrMC = np.std(results)*3.6e9
nerrMC = np.std(nList)
print(nerrMC, dPlist[j], dTlist[j], dRHlist[j], dCO2list[j])
+2 -2
View File
@@ -21,14 +21,14 @@ def setup_package():
# write_version_py()
setup(name='AstroAtmosphere',
version='0.1.2',
version='1.4',
description='Collection of equations for refractive index, refraction and dispersion calculations of atmospheric air.',
long_description=__readme__,
long_description_content_type='text/markdown',
author='Joost van den Born',
author_email='born@astron.nl',
url='https://gitlab.astro-wise.org/micado/atmosphericmodel',
download_url='https://gitlab.astro-wise.org/micado/atmosphericmodel/-/archive/v_012/atmosphericmodel-v_012.tar.gz',
download_url='https://gitlab.astro-wise.org/micado/atmosphericmodel/-/archive/v1.4/atmosphericmodel-v1.4.tar.gz',
license='GNU General Public License v3',
data_files = [('',['LICENSE.txt'])],
package_dir={'AstroAtmosphere': 'AstroAtmosphere'},