mirror of
https://gitlab.astro-wise.org/micado/atmosphericmodel.git
synced 2026-10-30 05:01:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1835c814 | ||
|
|
1abb7af4e9 | ||
|
|
c1f94e4d95 | ||
|
|
83d0c9ea45 | ||
|
|
502183889b | ||
|
|
e9e4b4ccd6 | ||
|
|
b024ae5d3a | ||
|
|
a21288554b | ||
|
|
e5e20e3584 | ||
|
|
9b24c2248b | ||
|
|
43bc8e5788 | ||
|
|
9df4333d51 | ||
|
|
e76ff46d5a | ||
|
|
8d50899db6 |
No files matched your search
@@ -5,6 +5,6 @@ from .ciddorModel import *
|
|||||||
from .dispersionModels import *
|
from .dispersionModels import *
|
||||||
from .refractionModels import *
|
from .refractionModels import *
|
||||||
from .refractionModels import *
|
from .refractionModels import *
|
||||||
from .examples import *
|
from .misc import *
|
||||||
from .refractivityModels import *
|
from .refractivityModels import *
|
||||||
|
from .neoslalib import *
|
||||||
+208
-19
@@ -16,6 +16,53 @@ from scipy import integrate
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
class Observatory:
|
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):
|
def __init__(self, dT=0.2, dP=20, dH=0.02, dCO2=20, dl=0.001):
|
||||||
self.dT = dT # in K
|
self.dT = dT # in K
|
||||||
self.dP = dP # in Pa
|
self.dP = dP # in Pa
|
||||||
@@ -28,6 +75,17 @@ class Observatory:
|
|||||||
Formula that calculate the refractive index for standard atmospheric
|
Formula that calculate the refractive index for standard atmospheric
|
||||||
conditions, i.e. 15 C, 101,325 Pa, 0% RH, 450 ppm CO2.
|
conditions, i.e. 15 C, 101,325 Pa, 0% RH, 450 ppm CO2.
|
||||||
It returns n_as, not (n_as - 1) * 1e8!
|
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
|
_s = 1 / l
|
||||||
_k0 = 238.0185 # um^-2
|
_k0 = 238.0185 # um^-2
|
||||||
@@ -42,6 +100,18 @@ class Observatory:
|
|||||||
Formula that calculate the refractive index for standard atmospheric
|
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.
|
conditions, i.e. 15 C, 101,325 Pa, 0% RH, but with xc ppm of CO2.
|
||||||
It returns n_axs, not (n_axs - 1)
|
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)
|
_n_as = self.n_as(l)
|
||||||
return ( (_n_as - 1) * (1 + 0.534e-6 * (xc - 450) ) ) + 1
|
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
|
Formula that calculates the refractivity of air for standard moist conditions
|
||||||
as defined by B&S (1939), i.e. 20 C, 1333 Pa
|
as defined by B&S (1939), i.e. 20 C, 1333 Pa
|
||||||
Returns n_ws, not (n_ws - 1) * 1e8
|
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
|
_s = 1 / l
|
||||||
_cf = 1.022
|
_cf = 1.022
|
||||||
@@ -65,8 +145,18 @@ class Observatory:
|
|||||||
def f(self, p, T):
|
def f(self, p, T):
|
||||||
'''
|
'''
|
||||||
Enhancement factor of water vapour in air
|
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
|
_t = T - 273.15
|
||||||
_a = 1.00062
|
_a = 1.00062
|
||||||
@@ -78,6 +168,16 @@ class Observatory:
|
|||||||
def svp(self, T):
|
def svp(self, T):
|
||||||
'''
|
'''
|
||||||
Saturation vapour pressure in Pa.
|
Saturation vapour pressure in Pa.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
T : float
|
||||||
|
Atmospheric temperature in Kelvin
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
svp : float
|
||||||
|
Saturation vapour pressure
|
||||||
'''
|
'''
|
||||||
_A = 1.2378847e-5 # K^-2
|
_A = 1.2378847e-5 # K^-2
|
||||||
_B = -1.9121316e-2 # K^-2
|
_B = -1.9121316e-2 # K^-2
|
||||||
@@ -88,7 +188,18 @@ class Observatory:
|
|||||||
|
|
||||||
def svp2(self, T):
|
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
|
K1 = 1.16705214528e+03
|
||||||
K2 = -7.24213167032e+05
|
K2 = -7.24213167032e+05
|
||||||
@@ -111,6 +222,20 @@ class Observatory:
|
|||||||
def xw(self, p, T, RH):
|
def xw(self, p, T, RH):
|
||||||
'''
|
'''
|
||||||
Molar fraction of water vapour in moist air.
|
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)
|
_f = self.f(p, T)
|
||||||
_svp = self.svp(T)
|
_svp = self.svp(T)
|
||||||
@@ -120,6 +245,20 @@ class Observatory:
|
|||||||
def Z(self, p, T, RH, xw=''):
|
def Z(self, p, T, RH, xw=''):
|
||||||
'''
|
'''
|
||||||
Compressibility of moist air
|
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
|
_a0 = 1.58123e-6 # K Pa^-1
|
||||||
_a1 = -2.9331e-8 # Pa^-1
|
_a1 = -2.9331e-8 # Pa^-1
|
||||||
@@ -142,10 +281,25 @@ class Observatory:
|
|||||||
def rho(self, p, T, RH, xc, xw=''):
|
def rho(self, p, T, RH, xc, xw=''):
|
||||||
'''
|
'''
|
||||||
Calculates the density of air for the given parameters.
|
Calculates the density of air for the given parameters.
|
||||||
T in Kelvin
|
|
||||||
p in Pascal
|
Parameters
|
||||||
RH between 0 and 1
|
----------
|
||||||
xc in ppm
|
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
|
_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
|
_Mw = 0.018015 # kg/mol
|
||||||
@@ -161,6 +315,25 @@ class Observatory:
|
|||||||
def n_tph(self, l, T, p, RH, xc):
|
def n_tph(self, l, T, p, RH, xc):
|
||||||
'''
|
'''
|
||||||
Calculates the refraction of the atmosphere for any conditions.
|
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_a = self.n_axs(l, xc)
|
||||||
_n_w = self.n_ws(l)
|
_n_w = self.n_ws(l)
|
||||||
@@ -193,18 +366,34 @@ class Observatory:
|
|||||||
by using fully propagated error analysis.
|
by using fully propagated error analysis.
|
||||||
This involves almost endless use of the chain rule and evaluation of both the moist
|
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.
|
air and dry air components. Note: dry air does not contain any water, i.e. H=0, x_w=0, etc.
|
||||||
|
|
||||||
Parameters:
|
Parameters
|
||||||
l - Wavelength in micron (0.4 - 2.5 micron)
|
----------
|
||||||
T - Temperature in Kelvin
|
l : float
|
||||||
p - Pressure in Pascals
|
Wavelength of light in microns
|
||||||
RH - Relative humidity (no units, don't use percentage but value 0<x<1)
|
p : float
|
||||||
xc - CO2 density in ppm
|
Atmospheric pressure in Pascal
|
||||||
dl - Uncertainty in the blue wavelength
|
T : float
|
||||||
dT - Uncertainty in the temperature, default: 0.2 K
|
Atmospheric temperature in Kelvin
|
||||||
dP - Uncertainty in the pressure, default: 20 Pa
|
RH : float
|
||||||
dRH - Uncertainty in the relative humidity, default: 0.02
|
Atmospheric relative humidity (0<=RH<=1)
|
||||||
dCO2- Uncertainty in the CO2 density, default: 20
|
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:
|
if dP == None:
|
||||||
|
|||||||
+591
-200
@@ -3,6 +3,7 @@ import numpy as np
|
|||||||
from scipy import integrate
|
from scipy import integrate
|
||||||
import os
|
import os
|
||||||
from .ciddorModel import Observatory
|
from .ciddorModel import Observatory
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ Author: Joost van den Born
|
@ Author: Joost van den Born
|
||||||
@ Contact: born@astron.nl
|
@ Contact: born@astron.nl
|
||||||
@@ -13,33 +14,100 @@ from .ciddorModel import Observatory
|
|||||||
The actual refraction is calculated by any of the following models:
|
The actual refraction is calculated by any of the following models:
|
||||||
- Plane parallel atmosphere
|
- Plane parallel atmosphere
|
||||||
- Cassini spherical homogeneous atmosphere
|
- Cassini spherical homogeneous atmosphere
|
||||||
|
- Mathar's Barometric exponential model
|
||||||
- Other methods described in Corbard et al., 2019.
|
- Other methods described in Corbard et al., 2019.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
class dispersion(Observatory):
|
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):
|
def __init__(self, lat, h):
|
||||||
Observatory.__init__(self)
|
Observatory.__init__(self)
|
||||||
self.h = h # height above sea level in meters
|
self.h = h # height above sea level in meters
|
||||||
self.lat = lat # Latitude of the observer in degrees
|
self.lat = lat # Latitude of the observer in degrees
|
||||||
self.rc = self.set_rc() * 1000 # Radius of curvature of the earth at the observer
|
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):
|
def cassini(self, n1, n2, zenith, reduced_height=None):
|
||||||
'''
|
'''
|
||||||
Refraction of spherical atmosphere, derived from geometry using Sine law
|
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:
|
if reduced_height:
|
||||||
H = reduced_height
|
H = reduced_height
|
||||||
@@ -51,194 +119,63 @@ class dispersion(Observatory):
|
|||||||
return np.degrees(_R1) - np.degrees(_R2)
|
return np.degrees(_R1) - np.degrees(_R2)
|
||||||
|
|
||||||
|
|
||||||
|
def cassiniError(self, zenith, l1, l2, T, p, RH, xc, dl1=0, dl2=0, dT=0.2, dP=20,
|
||||||
def tan5(self, n1, n2, zenith, reduced_height=None):
|
dRH=0.02, dCO2=20, dz=0, lat=None, h=None):
|
||||||
'''
|
'''
|
||||||
Same as oriani's theorem, but including a higher order term.
|
Calculates the uncertainty in the atmospheric dispersion,
|
||||||
Found in Corbard et al., 2019.
|
following the Cassini model, by using fully propagated error analysis.
|
||||||
'''
|
This involves almost endless use of the chain rule and evaluation
|
||||||
if reduced_height:
|
of both the moist air and dry air components.
|
||||||
H = reduced_height
|
Note: dry air does not contain any water, i.e. H=0, x_w=0, etc.
|
||||||
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]
|
|
||||||
|
|
||||||
|
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):
|
Returns
|
||||||
'''
|
-------
|
||||||
Classic refraction formula with two tan(z) terms. This does not assume any
|
dRR : float
|
||||||
information about the structure of the atmosphere. Found with Oriani's theorem.
|
Uncertainty in the 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 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
|
|
||||||
'''
|
'''
|
||||||
if lat == None:
|
if lat == None:
|
||||||
lat = self.lat
|
lat = self.lat
|
||||||
@@ -291,7 +228,7 @@ class dispersion(Observatory):
|
|||||||
_rho_obs = self.rho(p, T, RH, xc)
|
_rho_obs = self.rho(p, T, RH, xc)
|
||||||
self.rc = self.set_rc() * 1000
|
self.rc = self.set_rc() * 1000
|
||||||
self.H = self.set_H(p, _rho_obs)
|
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_a = p - RH*self.svp(T)
|
||||||
_p_w = RH*self.svp(T)
|
_p_w = RH*self.svp(T)
|
||||||
_x_w_a = self.xw(_p_a, T, _RH_a)
|
_x_w_a = self.xw(_p_a, T, _RH_a)
|
||||||
@@ -438,7 +375,7 @@ class dispersion(Observatory):
|
|||||||
|
|
||||||
|
|
||||||
# Partial derivatives for derivation to z
|
# Partial derivatives for derivation to z
|
||||||
_zenith = np.deg2rad(z)
|
_zenith = np.deg2rad(zenith)
|
||||||
_dz = np.deg2rad(dz)
|
_dz = np.deg2rad(dz)
|
||||||
_R = self.rc
|
_R = self.rc
|
||||||
_H = self.H
|
_H = self.H
|
||||||
@@ -447,4 +384,458 @@ class dispersion(Observatory):
|
|||||||
# Final error:
|
# Final error:
|
||||||
_dispersionError = np.sqrt( (_dddl1 * dl1)**2 + (_dddl2 * dl2)**2 + (_dddT * dT)**2 + (_dddP * dP)**2 + (_dddRH * dRH)**2 + (_dddCO2 * dCO2)**2 + (_dddz * _dz)**2)
|
_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)
|
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,364 @@
|
|||||||
|
#!/usr/bin/env Python3
|
||||||
|
from .ciddorModel import Observatory
|
||||||
|
from .refractionModels import refraction
|
||||||
|
from .dispersionModels import dispersion
|
||||||
|
from .neoslalib import sla_refro
|
||||||
|
|
||||||
|
'''
|
||||||
|
@ Author: Joost van den Born
|
||||||
|
@ Contact: born@astron.nl
|
||||||
|
@ Description: Some functions for quick testing.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
'STANDARD' or 'CERRO_ARMAZONES', refers
|
||||||
|
to the standard atmospheric conditions.
|
||||||
|
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 == 'STANDARD':
|
||||||
|
# Parameters at Cerro Armazones
|
||||||
|
T = 288.15 # k
|
||||||
|
p = 101325 # Pa
|
||||||
|
RH = 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
|
||||||
|
RH = 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=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
|
||||||
|
'STANDARD' or 'CERRO_ARMAZONES', refers
|
||||||
|
to the standard atmospheric conditions.
|
||||||
|
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 == 'STANDARD':
|
||||||
|
# Parameters at Cerro Armazones
|
||||||
|
T = 288.15 # k
|
||||||
|
p = 101325 # Pa
|
||||||
|
RH = 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
|
||||||
|
RH = 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=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
|
||||||
|
'STANDARD' or 'CERRO_ARMAZONES', refers
|
||||||
|
to the standard atmospheric conditions.
|
||||||
|
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 == 'STANDARD':
|
||||||
|
# Parameters at Cerro Armazones
|
||||||
|
T = 288.15 # k
|
||||||
|
p = 101325 # Pa
|
||||||
|
RH = 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
|
||||||
|
RH = 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=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
|
||||||
|
'STANDARD' or 'CERRO_ARMAZONES', refers
|
||||||
|
to the standard atmospheric conditions.
|
||||||
|
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-10)
|
||||||
|
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 == 'STANDARD':
|
||||||
|
# Parameters at Cerro Armazones
|
||||||
|
T = 288.15 # k
|
||||||
|
p = 101325 # Pa
|
||||||
|
RH = 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
|
||||||
|
RH = 0.22
|
||||||
|
xc = 450 # ppm
|
||||||
|
lat = -24.5983 # degrees
|
||||||
|
h = 3064 # m
|
||||||
|
|
||||||
|
# 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
|
||||||
|
'STANDARD' or 'CERRO_ARMAZONES', refers
|
||||||
|
to the standard atmospheric conditions.
|
||||||
|
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-10)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3,20 +3,82 @@ import numpy as np
|
|||||||
from scipy import integrate
|
from scipy import integrate
|
||||||
import os
|
import os
|
||||||
from .ciddorModel import Observatory
|
from .ciddorModel import Observatory
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ Author: Joost van den Born
|
@ Author: Joost van den Born
|
||||||
@ Contact: born@astron.nl
|
@ Contact: born@astron.nl
|
||||||
@ Description: This script offers a complete model of the atmospheric refraction
|
@ Description: This script offers a complete model of the atmospheric refraction
|
||||||
and dispersion. It uses the Ciddor1996 paper to calculate the
|
and dispersion. It uses the Ciddor1996 paper to calculate the
|
||||||
index of refraction for a wide range of conditions, with corresponding
|
index of refraction for a wide range of conditions, with corresponding error propagation if needed.
|
||||||
error propagation if needed.
|
|
||||||
The actual refraction is calculated by any of the following models:
|
The actual refraction is calculated by any of the following models:
|
||||||
- Plane parallel atmosphere
|
- Plane parallel atmosphere
|
||||||
- Cassini spherical homogeneous atmosphere
|
- Cassini spherical homogeneous atmosphere
|
||||||
|
- Mathar's Barometric exponential model
|
||||||
- Other methods described in Corbard et al., 2019.
|
- Other methods described in Corbard et al., 2019.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
class refraction(Observatory):
|
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):
|
def __init__(self, lat, h):
|
||||||
Observatory.__init__(self)
|
Observatory.__init__(self)
|
||||||
self.h = h # height above sea level in meters
|
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.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
|
# 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):
|
def cassini(self, n1, zenith, reduced_height=None):
|
||||||
'''
|
'''
|
||||||
Refraction of spherical atmosphere, derived from geometry using Sine law
|
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:
|
if reduced_height:
|
||||||
H = reduced_height
|
H = reduced_height
|
||||||
@@ -50,39 +116,46 @@ class refraction(Observatory):
|
|||||||
return np.degrees(_R1)
|
return np.degrees(_R1)
|
||||||
|
|
||||||
|
|
||||||
|
def cassiniError(self, n, dn, zenith, dz=0, reduced_height=None):
|
||||||
def tan5(self, n1, zenith, reduced_height=None):
|
|
||||||
'''
|
'''
|
||||||
Same as oriani's theorem, but including a higher order term.
|
Calculates the uncertainty in the refraction angle in degrees.
|
||||||
Found in Corbard et al., 2019.
|
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:
|
if reduced_height:
|
||||||
H = reduced_height
|
_H = reduced_height
|
||||||
else:
|
else:
|
||||||
H = self.H
|
_H = self.H
|
||||||
|
_zenith = np.deg2rad(zenith)
|
||||||
_a = n1 - 1
|
_dz = np.deg2rad(dz)
|
||||||
_b = H / self.rc
|
_R = 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
|
_dRdn = (_R * np.sin(_zenith)) / ((_R + _H) * np.sqrt(1 - ((n**2 * _R**2 * np.sin(_zenith)**2)/(_R + _H)**2)))
|
||||||
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):
|
return np.degrees(_dR)
|
||||||
'''
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def corbard(self, n1, zenith, reduced_height=None):
|
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.
|
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
|
Oriani's theorem can be derived from this equation by 'keeping only the three first
|
||||||
terms of its asymptotic expansion'.
|
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:
|
if reduced_height:
|
||||||
H = reduced_height
|
H = reduced_height
|
||||||
@@ -102,15 +191,268 @@ class refraction(Observatory):
|
|||||||
return np.degrees(_R)
|
return np.degrees(_R)
|
||||||
|
|
||||||
|
|
||||||
|
def gammaCoefficients(self, n1, reduced_height=None):
|
||||||
def refractionIntegral(self, n0, z0, R0=None, useStandardAtmosphere=True, heightData=None, rhoData=None):
|
|
||||||
'''
|
'''
|
||||||
Using data that gives temperature, pressure and density as a function of height,
|
Submethod for matharExponential(). Calculates the gamma
|
||||||
the gladstone-dale relation is invoked to calculate the path of a light ray
|
coefficients.
|
||||||
along the atmosphere. This is allows us to also include atmospheric activity in
|
|
||||||
|
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.
|
different atmosphere layers.
|
||||||
Recommended use with the US Standard Atmosphere from 1976.
|
Recommended use with the US Standard Atmosphere from 1976.
|
||||||
USSA1976 data generated from code supplied by http://www.pdas.com/atmos.html
|
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:
|
if not R0:
|
||||||
R0 = self.rc
|
R0 = self.rc
|
||||||
@@ -121,7 +463,7 @@ class refraction(Observatory):
|
|||||||
_Dens = rhoData
|
_Dens = rhoData
|
||||||
_h = heightData
|
_h = heightData
|
||||||
|
|
||||||
z0 = np.deg2rad(z0)
|
zenith = np.deg2rad(zenith)
|
||||||
_Dens = np.asarray(_Dens)
|
_Dens = np.asarray(_Dens)
|
||||||
_h = np.asarray(_h)
|
_h = np.asarray(_h)
|
||||||
_R = R0 + _h * 1000
|
_R = R0 + _h * 1000
|
||||||
@@ -130,7 +472,7 @@ class refraction(Observatory):
|
|||||||
_n_h = 1 + (n0 - 1) / _Dens[0] * _Dens
|
_n_h = 1 + (n0 - 1) / _Dens[0] * _Dens
|
||||||
|
|
||||||
# Calculation of the zenith angle for given height and refractive index, from the refractive invariant
|
# 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
|
# Calculation of log space derivative of n and r
|
||||||
_lnn = np.log(_n_h)
|
_lnn = np.log(_n_h)
|
||||||
@@ -146,32 +488,35 @@ class refraction(Observatory):
|
|||||||
return np.degrees(np.sum(_Integrand))
|
return np.degrees(np.sum(_Integrand))
|
||||||
|
|
||||||
|
|
||||||
|
def setReducedHeight(self, p, rho):
|
||||||
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,
|
Sets the reduced atmospheric height.
|
||||||
taking into account the latitude of the observer and the corresponding
|
|
||||||
ellipsoidality of the earth.
|
Parameters
|
||||||
|
----------
|
||||||
|
p : float
|
||||||
|
Atmospheric pressure in Pascal
|
||||||
|
rho : float
|
||||||
|
Density of the atmospheric air at the observer
|
||||||
'''
|
'''
|
||||||
_A = 6378.137 # km
|
self.H = self.set_H(p, rho) # Reduced height of the atmosphere assuming ideal gas law
|
||||||
_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):
|
||||||
|
|
||||||
def set_H(self, P, rho):
|
|
||||||
'''
|
'''
|
||||||
Calculates the reduced height of the atmosphere, assuming ideal gas law.
|
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)
|
_phi = np.deg2rad(self.lat)
|
||||||
_c1 = 5.2790414e-3
|
_c1 = 5.2790414e-3
|
||||||
@@ -181,40 +526,56 @@ class refraction(Observatory):
|
|||||||
_g0 = 9.780327 # m/s^2
|
_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)
|
_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
|
_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)
|
_phi = np.deg2rad(self.lat)
|
||||||
_c1 = 5.2790414e-3
|
|
||||||
_c2 = 2.32718e-5
|
_rc0 = (_A*_B)**2 / (_A**2 * np.cos(_phi)**2 + _B**2 * np.sin(_phi)**2)**(3/2)
|
||||||
_c3 = 1.262e-7
|
return _rc0 + self.h/1000
|
||||||
_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, 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:
|
if reduced_height:
|
||||||
_H = reduced_height
|
H = reduced_height
|
||||||
else:
|
else:
|
||||||
_H = self.H
|
H = self.H
|
||||||
_zenith = np.deg2rad(zenith)
|
|
||||||
_dz = np.deg2rad(dz)
|
_a = n1 - 1
|
||||||
_R = self.rc
|
_b = H / self.rc
|
||||||
_dRdn = (_R * np.sin(_zenith)) / ((_R + _H) * np.sqrt(1 - ((n**2 * _R**2 * np.sin(_zenith)**2)/(_R + _H)**2)))
|
_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)
|
|
||||||
|
|
||||||
return np.degrees(_dR)
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env Python3
|
#!/usr/bin/env Python3
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ Author: Joost van den Born
|
@ Author: Joost van den Born
|
||||||
@ Contact: born@astron.nl
|
@ Contact: born@astron.nl
|
||||||
@@ -10,28 +9,36 @@ import numpy as np
|
|||||||
atmospheric dispersion, given a set of conditions.
|
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):
|
||||||
'''
|
'''
|
||||||
From Barrell & Sears, 1939
|
From Barrell & Sears, 1939
|
||||||
Equation 7.7, page 52
|
Equation 7.7, page 52
|
||||||
For atmospheric air (i.e. CO2 = 300 ppm)
|
For atmospheric air (i.e. CO2 = 300 ppm)
|
||||||
This equation is applicable for the ranges T=10-30C and p=720-800 mmHg.
|
This equation is applicable for the ranges T=10-30C and p=720-800 mmHg.
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - Wavelength in microns
|
----------
|
||||||
T - Temperature in Kelvin
|
l : float
|
||||||
p - Atmospheric pressure in Pa
|
Wavelength in microns
|
||||||
H - fractional humidity (0=<H=<1)
|
T : float
|
||||||
|
Temperature in Kelvin
|
||||||
|
p : float
|
||||||
|
Atmospheric pressure in Pascal
|
||||||
|
RH : float
|
||||||
|
Relative humidity (0=<H=<1)
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
T = K2C(T)
|
T = K2C(T)
|
||||||
P = hPA2mmHg(p/100)
|
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) * RH / (1 + 0.003661*T)
|
||||||
n = R/1e6 + 1
|
n = R/1e6 + 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def Ciddor(l, CO2=450):
|
def Ciddor(l, CO2=450):
|
||||||
'''
|
'''
|
||||||
From Ciddor, 1996
|
From Ciddor, 1996
|
||||||
@@ -39,11 +46,15 @@ def Ciddor(l, CO2=450):
|
|||||||
At default conditions only. For more flexibility use
|
At default conditions only. For more flexibility use
|
||||||
Observatory.n_tph()
|
Observatory.n_tph()
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - Wavelength in microns
|
----------
|
||||||
|
l : float
|
||||||
|
Wavelength in microns
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
s = 1 / l
|
s = 1 / l
|
||||||
R = 5792105 / (238.0185 - s**2) + 167917 / (57.362 - s**2)
|
R = 5792105 / (238.0185 - s**2) + 167917 / (57.362 - s**2)
|
||||||
@@ -52,6 +63,7 @@ def Ciddor(l, CO2=450):
|
|||||||
n = R/1e8 + 1
|
n = R/1e8 + 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def Edlen1953(l):
|
def Edlen1953(l):
|
||||||
'''
|
'''
|
||||||
From Edlen, 1953
|
From Edlen, 1953
|
||||||
@@ -59,17 +71,22 @@ def Edlen1953(l):
|
|||||||
|
|
||||||
At standard conditions only
|
At standard conditions only
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - Wavelength in microns
|
----------
|
||||||
|
l : float
|
||||||
|
Wavelength in microns
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
s = 1 / l
|
s = 1 / l
|
||||||
R = 6432.8 + 2949810 / (146 - s**2) + 25540/(41-s**2)
|
R = 6432.8 + 2949810 / (146 - s**2) + 25540/(41-s**2)
|
||||||
n = R/1e8 + 1
|
n = R/1e8 + 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def Edlen1966(l):
|
def Edlen1966(l):
|
||||||
'''
|
'''
|
||||||
From Edlen, 1966
|
From Edlen, 1966
|
||||||
@@ -77,53 +94,73 @@ def Edlen1966(l):
|
|||||||
|
|
||||||
At standard conditions only
|
At standard conditions only
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - Wavelength in microns
|
----------
|
||||||
|
l : float
|
||||||
|
Wavelength in microns
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
s = 1 / l
|
s = 1 / l
|
||||||
R = 8342.13 + 2406030 / (130 - s**2) + 15997 / (38.9 - s**2)
|
R = 8342.13 + 2406030 / (130 - s**2) + 15997 / (38.9 - s**2)
|
||||||
n = R/1e8 + 1
|
n = R/1e8 + 1
|
||||||
return n
|
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
|
From Owens, 1967
|
||||||
Equation 32
|
Equation 32
|
||||||
Note that the pressure P and H are in torricelli (Torr),
|
Note that the pressure P and H are in torricelli (Torr),
|
||||||
however, this is almost precisely 1 Torr = 1 mmHg
|
however, this is almost precisely 1 Torr = 1 mmHg
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - wavelength in micron
|
----------
|
||||||
T - Temperature in Kelvin
|
l : float
|
||||||
p - Atmospheric pressure in Pa
|
wavelength in micron
|
||||||
H - Humdity (0<=H<=1)
|
T : float
|
||||||
|
Temperature in Kelvin
|
||||||
|
p : float
|
||||||
|
Atmospheric pressure in Pa
|
||||||
|
RH : float
|
||||||
|
Relative humdity (0<=H<=1)
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
P = hPA2mmHg(p/100)
|
P = hPA2mmHg(p/100)
|
||||||
T = K2C(T)
|
T = K2C(T)
|
||||||
s = 1 / l
|
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
|
n = R/1e8 + 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def BonschPotulski(l, T=288.15, p=101325, CO2=450):
|
def BonschPotulski(l, T=288.15, p=101325, CO2=450):
|
||||||
'''
|
'''
|
||||||
From Bonsch & Potulski, 1998
|
From Bonsch & Potulski, 1998
|
||||||
Equations 6, 7, 8
|
Equations 6, 7, 8
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - Wavelength in microns
|
----------
|
||||||
T - Temperature in Kelvin
|
l : float
|
||||||
p - Atmospheric pressure in Pa
|
Wavelength in microns
|
||||||
CO2 - Particles per million of CO2
|
T : float
|
||||||
|
Temperature in Kelvin
|
||||||
|
p : float
|
||||||
|
Atmospheric pressure in Pa
|
||||||
|
CO2 : float
|
||||||
|
Particles per million of CO2
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
x = CO2 / 1e6
|
x = CO2 / 1e6
|
||||||
T = K2C(T)
|
T = K2C(T)
|
||||||
@@ -136,6 +173,7 @@ def BonschPotulski(l, T=288.15, p=101325, CO2=450):
|
|||||||
n = Rtp + 1
|
n = Rtp + 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def BirchDowns(l):
|
def BirchDowns(l):
|
||||||
'''
|
'''
|
||||||
From Birch & Downs, 1993 and 1994 (correction)
|
From Birch & Downs, 1993 and 1994 (correction)
|
||||||
@@ -143,17 +181,22 @@ def BirchDowns(l):
|
|||||||
|
|
||||||
Only at standard conditions
|
Only at standard conditions
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - wavelength in micron
|
----------
|
||||||
|
l : float
|
||||||
|
wavelength in micron
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
s = 1 / l
|
s = 1 / l
|
||||||
R = 8342.54 + 2406147 / (130 - s**2) + 15998 / (38.9-s**2)
|
R = 8342.54 + 2406147 / (130 - s**2) + 15998 / (38.9-s**2)
|
||||||
n = R/1e8 + 1
|
n = R/1e8 + 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def PeckReeder(l):
|
def PeckReeder(l):
|
||||||
'''
|
'''
|
||||||
From Peck & Reeder, 1972
|
From Peck & Reeder, 1972
|
||||||
@@ -161,11 +204,15 @@ def PeckReeder(l):
|
|||||||
|
|
||||||
Only at standard conditions
|
Only at standard conditions
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - wavelength in micron
|
----------
|
||||||
|
l : float
|
||||||
|
wavelength in micron
|
||||||
|
|
||||||
Returns:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
n : float
|
||||||
|
Refractive index of atmospheric air at the given conditions
|
||||||
'''
|
'''
|
||||||
s = 1 / l
|
s = 1 / l
|
||||||
R = 8060.51 + 2480990 / (132.274 - s**2) + 17455.7 / (39.32957 - s**2)
|
R = 8060.51 + 2480990 / (132.274 - s**2) + 17455.7 / (39.32957 - s**2)
|
||||||
@@ -173,25 +220,106 @@ def PeckReeder(l):
|
|||||||
return n
|
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
|
SLALIB uses the same atmospheric model as ZEMAX
|
||||||
based on the works of Hohenkerk & Sinclair and Seidelmann.
|
based on the works of Hohenkerk & Sinclair and Seidelmann.
|
||||||
They take the B&S refraction formula as a basis.
|
They take the B&S refraction formula as a basis.
|
||||||
Requires the pySLALIB package to work.
|
Requires the pySLALIB package to work.
|
||||||
|
|
||||||
Input:
|
Parameters
|
||||||
l - Wavelength in microns
|
----------
|
||||||
T - Temperature in Kelvin
|
l : float
|
||||||
p - Atmospheric pressure in Pa
|
Wavelength in microns
|
||||||
H - fractional humidity (0=<H=<1)
|
T : float
|
||||||
h - Altitude of the observer (default=0 m)
|
Temperature in Kelvin
|
||||||
lat - Latitude of the observer (default=-24)
|
p : float
|
||||||
tlr - temperature lapse rate (default=0.0065)
|
Atmospheric pressure in Pa
|
||||||
eps - precision at which the calculation is aborted (default=1e-10)
|
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:
|
Returns
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
-------
|
||||||
|
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:
|
try:
|
||||||
from pyslalib import slalib
|
from pyslalib import slalib
|
||||||
@@ -205,27 +333,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:
|
if type(l) == float:
|
||||||
l = [l]
|
l = [l]
|
||||||
for wl in 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:
|
if len(l) == 1:
|
||||||
return R[0]+1
|
return R[0]+1
|
||||||
else:
|
else:
|
||||||
return np.asarray(R)+1
|
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
|
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
|
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:
|
Parameters
|
||||||
n - Refractive index of atmospheric air at the given conditions
|
----------
|
||||||
|
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:
|
if l < 1.3 or l > 24:
|
||||||
print('Using the Mathar model at this wavelength (l={:.3f}) is not recommended!'.format(l))
|
print('Using the Mathar model at this wavelength (l={:.3f}) is not recommended!'.format(l))
|
||||||
@@ -331,7 +469,7 @@ def Mathar(l, T=288.15, p=101325, H=0):
|
|||||||
|
|
||||||
s = 1e4/l # cm^-1
|
s = 1e4/l # cm^-1
|
||||||
n = 1
|
n = 1
|
||||||
H = H*100
|
H = RH*100
|
||||||
|
|
||||||
for j in range(0, 6):
|
for j in range(0, 6):
|
||||||
n += ( cref[j] + cT[j]*(1/T-1/Tref) + cTT[j]*(1/T-1/Tref)**2
|
n += ( cref[j] + cT[j]*(1/T-1/Tref) + cTT[j]*(1/T-1/Tref)**2
|
||||||
@@ -346,11 +484,33 @@ def Mathar(l, T=288.15, p=101325, H=0):
|
|||||||
def K2C(K):
|
def K2C(K):
|
||||||
'''
|
'''
|
||||||
Kelvin to Celsius conversion
|
Kelvin to Celsius conversion
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
K : float
|
||||||
|
Temperature in Kelvin
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
C : float
|
||||||
|
Temperature in Celsius
|
||||||
'''
|
'''
|
||||||
return K - 273.15
|
return K - 273.15
|
||||||
|
|
||||||
|
|
||||||
def hPA2mmHg(hPa):
|
def hPA2mmHg(hPa):
|
||||||
'''
|
'''
|
||||||
hPa to mmHg conversion
|
hPa to mmHg conversion
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
hPa : float
|
||||||
|
Atmospheric pressure in hPa (hecto Pascal)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
mmHg : float
|
||||||
|
Atmospheric pressure in mmHg
|
||||||
'''
|
'''
|
||||||
return 0.7500616827 * hPa
|
return 0.7500616827 * hPa
|
||||||
|
|
||||||
@@ -1,66 +1,131 @@
|
|||||||
|
# AstroAtmosphere
|
||||||
|
|
||||||
**Note:**
|
**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).
|
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:
|
||||||
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.
|
*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:**
|
```python
|
||||||
- AstroAtmosphere : Python package that can be used to calculate the atmospheric refraction and dispersion, the main content of this repository.
|
from AstroAtmosphere import *
|
||||||
- atmospheric refraction example: jupyter notebook example
|
|
||||||
- `setup.py` : Install script to install the AstroAtmosphere package locally.
|
# 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:
|
The package contains the following files:
|
||||||
- `/data/1976USSA.txt` : Data file of the 1976 US Standard Atmosphere.
|
- `/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.
|
- `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
|
- `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.
|
- `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
|
- `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).
|
- `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:**
|
## The Ciddor refractivity model
|
||||||
|
|
||||||
Dispersion model from Ciddor, 1996, calculates the index of refraction of air using the following parameters:
|
The refractivity model from Ciddor, 1996, calculates the index of refraction of air using the following parameters:
|
||||||
- Temperature
|
- T - Temperature in Kelvin
|
||||||
- Pressure
|
- p - Pressure in Pascal
|
||||||
- Relative humidity
|
- RH - Relative humidity in fractional units
|
||||||
- CO2 density
|
- xc - CO2 density in parts per million
|
||||||
|
|
||||||
|
It is invoked by first initializing an `Observatory()` object. The atmospheric conditions can be parsed into the `n_tph()` function.
|
||||||
|
|
||||||
|
```python
|
||||||
|
at = Observatory()
|
||||||
|
n = at.n_tph(l=0.55, T=273.15, p=101325, RH=0.2, xc=300)
|
||||||
|
```
|
||||||
|
|
||||||
|
Basic functionality of other refractivity models is included as well and is discussed below.
|
||||||
|
|
||||||
|
|
||||||
|
## The atmospheric refraction and atmospheric dispersion models
|
||||||
|
|
||||||
|
If the atmospheric refraction is denoted as R(n), then atmospheric dispersion is defined as R(n1) - R(n2). Thus they are very similar in function and use.
|
||||||
|
|
||||||
|
|
||||||
**On the atmospheric refraction / atmospheric dispersion models:**
|
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`)
|
||||||
Available models:
|
- `planeParallel()` - Plane-parallel atmosphere refraction model
|
||||||
- Full integration using the refractive integral (requires detailed information about the atmosphere, e.g. `AstroAtmosphere/data/1976USSA.txt`)
|
- `cassini()` - Cassini's homogeneous atmosphere refraction model
|
||||||
- Plane-parallel atmosphere refraction model
|
- `oriani()` - Oriani's theorem ( Atan(z) + Btan^3(z) )
|
||||||
- Cassini's homogeneous atmosphere refraction model
|
- `tan5()` - Oriani's theorem expanded ( Atan(z) + Btan^3(z) + Ctan^5(z) )
|
||||||
- Oriani's theorem ( Atan(z) + Btan^3(z) )
|
- `corbard()` - Error function refraction model
|
||||||
- Oriani's theorem expanded ( Atan(z) + Btan^3(z) + Ctan^5(z) )
|
- `matharExponential()` - Barometric exponential model by R. Mathar.
|
||||||
- Error function refraction model
|
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
Input parameters for the spherical atmosphere models (all but the first):
|
||||||
- Temperature [K]
|
- T - Temperature [K]
|
||||||
- Pressure [Pa]
|
- p - Pressure [Pa]
|
||||||
- Relative humidity
|
- RH - Relative humidity
|
||||||
- CO2 density [ppm]
|
- xc - CO2 density [ppm]
|
||||||
- Latitude [deg]
|
- lat - Latitude [deg]
|
||||||
- Height above sea level [m]
|
- h - Height above sea level [m]
|
||||||
- Wavelength(s) [um]
|
- l - Wavelength(s) [um]
|
||||||
- Zenith angle [deg]
|
- 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()`).
|
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.
|
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 +162,58 @@ Cassini dispersion model:
|
|||||||
| dCO2 | CO2 density | ppm |
|
| dCO2 | CO2 density | ppm |
|
||||||
| dz | Zenith angle | deg |
|
| 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()` | Only at standard atmosphere. Function requires wavelength only. |
|
||||||
|
| 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 that assume default conditions for the atmosphere. Then only the wavelength and zenith angle are required to calculate the refraction and dispersion.
|
||||||
|
|
||||||
|
Presently, only the `'STANDARD'` and `'CERRO_ARMAZONES'` flags are known. Contact the author to add additional standard conditions.
|
||||||
|
|
||||||
|
```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')
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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()`.
|
||||||
+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()
|
# write_version_py()
|
||||||
|
|
||||||
setup(name='AstroAtmosphere',
|
setup(name='AstroAtmosphere',
|
||||||
version='0.1.2',
|
version='1.2.1',
|
||||||
description='Collection of equations for refractive index, refraction and dispersion calculations of atmospheric air.',
|
description='Collection of equations for refractive index, refraction and dispersion calculations of atmospheric air.',
|
||||||
long_description=__readme__,
|
long_description=__readme__,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
author='Joost van den Born',
|
author='Joost van den Born',
|
||||||
author_email='born@astron.nl',
|
author_email='born@astron.nl',
|
||||||
url='https://gitlab.astro-wise.org/micado/atmosphericmodel',
|
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.2.1/atmosphericmodel-v1.2.1.tar.gz',
|
||||||
license='GNU General Public License v3',
|
license='GNU General Public License v3',
|
||||||
data_files = [('',['LICENSE.txt'])],
|
data_files = [('',['LICENSE.txt'])],
|
||||||
package_dir={'AstroAtmosphere': 'AstroAtmosphere'},
|
package_dir={'AstroAtmosphere': 'AstroAtmosphere'},
|
||||||
|
|||||||
Reference in new issue
Block a user