mirror of
https://gitlab.astro-wise.org/micado/atmosphericmodel.git
synced 2026-10-29 20:51:05 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a084d1635 | ||
|
|
f1aa2ee903 | ||
|
|
8f9a0bbbea | ||
|
|
cce60766fc | ||
|
|
2b1835c814 | ||
|
|
1abb7af4e9 | ||
|
|
c1f94e4d95 | ||
|
|
83d0c9ea45 | ||
|
|
502183889b | ||
|
|
e9e4b4ccd6 | ||
|
|
b024ae5d3a | ||
|
|
a21288554b | ||
|
|
e5e20e3584 | ||
|
|
9b24c2248b | ||
|
|
43bc8e5788 | ||
|
|
9df4333d51 | ||
|
|
e76ff46d5a | ||
|
|
8d50899db6 |
No files matched your search
@@ -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
@@ -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
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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.'
|
||||
# }
|
||||
}
|
||||
@@ -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(n * 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
|
||||
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,7 +463,7 @@ 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
|
||||
@@ -130,7 +472,7 @@ class refraction(Observatory):
|
||||
_n_h = 1 + (n0 - 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( (n0 * 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)
|
||||
@@ -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)
|
||||
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,15 @@ 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
|
||||
|
||||
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,24 +68,51 @@ 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
|
||||
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):
|
||||
'''
|
||||
From Edlen, 1966
|
||||
@@ -77,53 +120,73 @@ def Edlen1966(l):
|
||||
|
||||
At standard conditions only
|
||||
|
||||
Input:
|
||||
l - Wavelength in microns
|
||||
Parameters
|
||||
----------
|
||||
l : float
|
||||
Wavelength in microns
|
||||
|
||||
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
|
||||
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)
|
||||
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):
|
||||
'''
|
||||
From Bonsch & Potulski, 1998
|
||||
Equations 6, 7, 8
|
||||
|
||||
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
|
||||
CO2 : float
|
||||
Particles per million of CO2
|
||||
|
||||
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)
|
||||
@@ -136,6 +199,7 @@ def BonschPotulski(l, T=288.15, p=101325, CO2=450):
|
||||
n = Rtp + 1
|
||||
return n
|
||||
|
||||
|
||||
def BirchDowns(l):
|
||||
'''
|
||||
From Birch & Downs, 1993 and 1994 (correction)
|
||||
@@ -143,17 +207,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 +230,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 +246,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 +359,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))
|
||||
@@ -331,7 +495,7 @@ def Mathar(l, T=288.15, p=101325, H=0):
|
||||
|
||||
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 +507,57 @@ 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):
|
||||
'''
|
||||
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 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)
|
||||
@@ -0,0 +1 @@
|
||||
include AstroAtmosphere/data/1976USSA.txt
|
||||
@@ -1,66 +1,133 @@
|
||||
# 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.
|
||||
- `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
|
||||
- `observatories` : Contains a dictionary with the average atmospheric conditions for various large observatories around the world.
|
||||
|
||||
|
||||
|
||||
## The Ciddor refractivity model
|
||||
|
||||
**On the atmospheric refraction / atmospheric dispersion models:**
|
||||
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
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# 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 +164,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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/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
|
||||
return(AstroAtmosphere.slalib(l, T=self.T, p=self.P, RH=self.H)-1)
|
||||
|
||||
|
||||
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)']))
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
########################################################################################
|
||||
########################################################################################
|
||||
########################################################################################
|
||||
@@ -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])
|
||||
|
||||
@@ -21,14 +21,14 @@ def setup_package():
|
||||
# write_version_py()
|
||||
|
||||
setup(name='AstroAtmosphere',
|
||||
version='0.1.2',
|
||||
version='1.3',
|
||||
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.3/atmosphericmodel-v1.3.tar.gz',
|
||||
license='GNU General Public License v3',
|
||||
data_files = [('',['LICENSE.txt'])],
|
||||
package_dir={'AstroAtmosphere': 'AstroAtmosphere'},
|
||||
|
||||
Reference in new issue
Block a user