diff --git a/.gitignore b/.gitignore index 5ca0973..05d4011 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store +__pychache__/ diff --git a/README.md b/README.md index ddb04a9..f2bfb9d 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,21 @@ Author info: *Joost van den Born*, *born@astron.nl* *Below you can find a set of models describing the atmospheric refraction and dispersion.* **Contents:** -- atmosphericModel.py : Python model that can be used to calculate the atmospheric refraction and dispersion -- refractionModel.py : Python script that compares the output of the different rerfraction models. +- atmosphericModel : Python model that can be used to calculate the atmospheric refraction and dispersion, the main content of this repository. +- refractionModel.py : Python script that compares the output of the different refraction models. Can be used as an example on how to use atmosphericModel.py -- 1976USSA.txt : Table with temperature, pressure, density and more as a function of altitude. - Values derived from the 1976 U.S. Standard Atmosphere. -- manual.pdf : Manual for using atmosphericModel.py - atmospheric refraction example: jupyter notebook example -- [Tech note]...models.pdf : Tech note describing the models. +**On the contents of the atmosphericModel package** +The package contains the following files: +- 1976USSA.txt : Data file of the 1976 US Standard Atmosphere. +- ciddorModel.py : Contains the `Observatory()` class to calculate the refractive index of atmospheric air, using the Ciddor (1996) model. +- dispersionModels.py : Contains various atmospheric models that can be used to calculate the atmospheric dispersion +- examples.py : Contains several quick functions, e.g. to quickly calculate the refractive index. +- refractionModels.py : Contains various atmospheric models that can be used to calculate the atmospheric refraction +- 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:** diff --git a/atmospheric refraction example.html b/atmospheric refraction example.html index 65634ed..7b94324 100644 --- a/atmospheric refraction example.html +++ b/atmospheric refraction example.html @@ -13286,6 +13286,57 @@ div#notebook { + +
+
+
+

There are also some quick calculation functions defined in the examples.py file, if you just want to know the refractive index, the refraction or the dispersion at standard atmospheric conditions (conditions='STANDARD') or at Cerro Armazones (conditions='CERRO_ARMAZONES').

+ +
+
+
+
+
+
In [6]:
+
+
+
# Quick refractive index
+nq = quick_refractive_index(l=1.49, conditions='STANDARD')
+print('Quick refractive index: {}'.format(nq))
+
+# Quick refraction
+rq = quick_refraction(l1, zenith_obs, conditions='CERRO_ARMAZONES') * 3.6e3
+print('Quick refraction: {:.3f} arcsec'.format(rq))
+
+# Quick dispersion
+dq = quick_dispersion(l1, l2, zenith_obs, conditions='CERRO_ARMAZONES') * 3.6e6
+print('Quick dispersion: {:.3f} mas'.format(dq))
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
Quick refractive index: 1.0002733131243147
+Quick refraction: 40.697 arcsec
+Quick dispersion: 31.019 mas
+
+
+
+ +
+
+
diff --git a/atmospheric refraction example.ipynb b/atmospheric refraction example.ipynb index 4d61886..a89f56d 100644 --- a/atmospheric refraction example.ipynb +++ b/atmospheric refraction example.ipynb @@ -159,6 +159,42 @@ "\n", "print ('The dispersion is %.03f +/- %.03f milli arc seconds' %(dispersion_obs, dispersion_unc))" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are also some quick calculation functions defined in the `examples.py` file, if you just want to know the refractive index, the refraction or the dispersion at standard atmospheric conditions (`conditions='STANDARD'`) or at Cerro Armazones (`conditions='CERRO_ARMAZONES'`)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quick refractive index: 1.0002733131243147\n", + "Quick refraction: 40.697 arcsec\n", + "Quick dispersion: 31.019 mas\n" + ] + } + ], + "source": [ + "# Quick refractive index\n", + "nq = quick_refractive_index(l=1.49, conditions='STANDARD')\n", + "print('Quick refractive index: {}'.format(nq))\n", + "\n", + "# Quick refraction\n", + "rq = quick_refraction(l1, zenith_obs, conditions='CERRO_ARMAZONES') * 3.6e3\n", + "print('Quick refraction: {:.3f} arcsec'.format(rq))\n", + "\n", + "# Quick dispersion\n", + "dq = quick_dispersion(l1, l2, zenith_obs, conditions='CERRO_ARMAZONES') * 3.6e6\n", + "print('Quick dispersion: {:.3f} mas'.format(dq))\n" + ] } ], "metadata": { diff --git a/atmosphericModel.py b/atmosphericModel.py deleted file mode 100644 index dc651e5..0000000 --- a/atmosphericModel.py +++ /dev/null @@ -1,1025 +0,0 @@ -#!/usr/bin/env Python3 -import numpy as np -from scipy import integrate - -''' -@ Author: Joost van den Born -@ Contact: born@astron.nl -@ Description: This script offers a complete model of the atmospheric refraction - and dispersion. It uses the Ciddor1996 paper to calculate the - index of refraction for a wide range of conditions, with corresponding - error propagation if needed. - The actual refraction is calculated by any of the following models: - - Plane parallel atmosphere - - Cassini spherical homogeneous atmosphere - - Other methods described in Corbard et al., 2019. -''' - - -class Observatory: - def __init__(self, dT=0.2, dP=20, dH=0.02, dCO2=20, dl=0.001): - self.dT = dT # in K - self.dP = dP # in Pa - self.dH = dH # relative humidity between 0 and 1 - self.dCO2 = dCO2 # in ppm - self.dl = dl # in micron - - def n_as(self, l): - ''' - Formula that calculate the refractive index for standard atmospheric - conditions, i.e. 15 C, 101,325 Pa, 0% RH, 450 ppm CO2. - It returns n_as, not (n_as - 1) * 1e8! - ''' - _s = 1 / l - _k0 = 238.0185 # um^-2 - _k1 = 5792105 # um^-2 - _k2 = 57.362 # um^-2 - _k3 = 167917 # um^-2 - return (_k1 / (_k0 - _s**2) + _k3 / (_k2 - _s**2)) / 1e8 + 1 - - - def n_axs(self, l, xc): - ''' - Formula that calculate the refractive index for standard atmospheric - conditions, i.e. 15 C, 101,325 Pa, 0% RH, but with xc ppm of CO2. - It returns n_axs, not (n_axs - 1) - ''' - _n_as = self.n_as(l) - return ( (_n_as - 1) * (1 + 0.534e-6 * (xc - 450) ) ) + 1 - - - def n_ws(self, l): - ''' - Formula that calculates the refractivity of air for standard moist conditions - as defined by B&S (1939), i.e. 20 C, 1333 Pa - Returns n_ws, not (n_ws - 1) * 1e8 - ''' - _s = 1 / l - _cf = 1.022 - _w0 = 295.235 # um^-2 - _w1 = 2.6422 # um^-2 - _w2 = -0.032380 # um^-4 - _w3 = 0.004028 # um^-6 - return (_cf * (_w0 + _w1 * _s**2 + _w2 * _s**4 + _w3 * _s**6) ) / 1e8 + 1 - - - def f(self, p, T): - ''' - Enhancement factor of water vapour in air - t in degrees celsius - p in Pascal (Pa) - ''' - _t = T - 273.15 - _a = 1.00062 - _b = 3.14e-8 # Pa^-1 - _c = 5.6e-7 # deg C^-2 - return _a + _b * p + _c * _t**2 - - - def svp(self, T): - ''' - Saturation vapour pressure in Pa. - ''' - _A = 1.2378847e-5 # K^-2 - _B = -1.9121316e-2 # K^-2 - _C = 33.93711047 - _D = -6.3431645e3 # K^-2 - return np.exp(_A*T**2 + _B*T + _C + _D/T) - - - def svp2(self, T): - ''' - Calculation of svp from IAPWS formula, based on documentation by NIST toolbox. - ''' - K1 = 1.16705214528e+03 - K2 = -7.24213167032e+05 - K3 = -1.70738469401e+01 - K4 = 1.20208247025e+04 - K5 = -3.23255503223e+06 - K6 = 1.49151086135e+01 - K7 = -4.82326573616e+03 - K8 = 4.05113405421e+05 - K9 = -2.38555575678e-01 - K10 = 6.50175348448e+02 - _W = T + K9 / (T - K10) - _A = _W**2 + K1*_W + K2 - _B = K3*_W**2 + K4*_W + K5 - _C = K6*_W**2 + K7*_W + K8 - _X = -_B + np.sqrt(_B**2 - 4*_A*_C) - return (2*_C / _X)**4 * 1e6 - - - def xw(self, p, T, RH): - ''' - Molar fraction of water vapour in moist air. - ''' - _f = self.f(p, T) - _svp = self.svp(T) - return _f * RH * _svp / p - - - def Z(self, p, T, RH, xw=''): - ''' - Compressibility of moist air - ''' - _a0 = 1.58123e-6 # K Pa^-1 - _a1 = -2.9331e-8 # Pa^-1 - _a2 = 1.1043e-10 # K^-1 Pa^-1 - _b0 = 5.707e-6 # K Pa^-1 - _b1 = -2.051e-8 # Pa^-1 - _c0 = 1.9898e-4 # K Pa^-1 - _c1 = -2.376e-6 # Pa^-1 - _d = 1.83e-11 # K^2 Pa^-2 - _e = -0.765e-8 # K^2 Pa^-2 - _t = T - 273.15 - if xw == '': - _xw = self.xw(p, T, RH) - else: - _xw = xw - Z = 1 - (p/T) * (_a0 + _a1*_t + _a2*_t**2 + (_b0 + _b1*_t)*_xw + (_c0 + _c1*_t)*_xw**2) + (p/T)**2 * (_d + _e*_xw**2) - return Z - - - def rho(self, p, T, RH, xc, xw=''): - ''' - Calculates the density of air for the given parameters. - T in Kelvin - p in Pascal - RH between 0 and 1 - xc in ppm - ''' - _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 - _R = 8.314510 # J mol^-1 K^-1, gas constant - if xw == '': - _xw = self.xw(p, T, RH) - else: - _xw = xw - _Z = self.Z(p, T, RH, xw=_xw) - return ( ( p * _Ma ) / (_Z * _R * T) ) * ( 1 - _xw * (1 - _Mw/_Ma)) - - - def n_tph(self, l, T, p, RH, xc): - ''' - Calculates the refraction of the atmosphere for any conditions. - ''' - _n_a = self.n_axs(l, xc) - _n_w = self.n_ws(l) - _La = (_n_a**2 - 1) / (_n_a**2 + 2) - _Lw = (_n_w**2 - 1) / (_n_w**2 + 2) - - # Calculate the density of the dry air components, using the partial pressure of the dry air, i.e. p_dry = p_atm - p_water - _rho_a = self.rho(p=p-RH*self.svp(T), T=T, RH=0, xc=xc) # Dry air component for the actual conditions. - _rho_axs = self.rho(p=101325, T=288.15, RH=0, xc=xc) # Dry air component at standard conditions. - - if RH == 0: - # nan is returned for _rho_w if we put in a (partial) pressure of 0 - _rho_w = 0 - else: - # Partial pressure of water vapour is p_water = h*svp - _rho_w = self.rho(p=RH*self.svp(T), T=T, RH=RH, xc=xc) # Moist air component for the actual conditions. - _rho_ws = self.rho(p=1333, T=293.15, RH=1333/self.svp(293.15), xc=xc) # Moist air component at standard conditions. - - _Ltotal = ( _rho_a / _rho_axs ) * _La + ( _rho_w / _rho_ws ) * _Lw - - # print 'Dry air component = ', (_rho_a/_rho_axs)*(_n_a-1)*1e8 - # print 'Wet component = ', ( _rho_w / _rho_ws ) * (_n_w - 1)*1e8 - # print 'Sum of components = ', (_rho_a/_rho_axs)*(_n_a-1)*1e8+( _rho_w / _rho_ws ) * (_n_w - 1)*1e8 - return np.sqrt( (1 + 2 * _Ltotal) / (1 - _Ltotal) ) - - - def dn_tph(self, l, T, p, RH, xc, dl=None, dT=None, dP=None, dRH=None, dCO2=None): - ''' - Calculates the uncertainty in the refractive index, following the Ciddor 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. - - Parameters: - l - Wavelength in micron (0.4 - 2.5 micron) - T - Temperature in Kelvin - p - Pressure in Pascals - RH - Relative humidity (no units, don't use percentage but value 0 24: + print('Using the Mathar model at this wavelength (l={:.3f}) is not recommended!'.format(l)) + + if l <= 2.65: + ######################################################################## + # model parameters for wavelengths between 1.3 and 2.5 microns + cref = [ 0.200192e-3, 0.113474e-9, -0.424595e-14, 0.100957e-16, -0.293315e-20, 0.307228e-24] # cm^j + cT = [ 0.588625e-1, -0.385766e-7, 0.888019e-10, -0.567650e-13, 0.166615e-16, -0.174845e-20] # cm^j · K + cTT = [-3.01513, 0.406167e-3, -0.514544e-6, 0.343161e-9, -0.101189e-12, 0.106749e-16] # cm^j · K^2 + cH = [-0.103945e-7, 0.136858e-11, -0.171039e-14, 0.112908e-17, -0.329925e-21, 0.344747e-25] # cm^j · %^-1 + cHH = [ 0.573256e-12, 0.186367e-16, -0.228150e-19, 0.150947e-22, -0.441214e-26, 0.461209e-30] # cm^j · %^-2 + cp = [ 0.267085e-8, 0.135941e-14, 0.135295e-18, 0.818218e-23, -0.222957e-26, 0.249964e-30] # cm^j · Pa^-1 + cpp = [ 0.609186e-17, 0.519024e-23, -0.419477e-27, 0.434120e-30, -0.122445e-33, 0.134816e-37] # cm^j · Pa^-2 + cTH = [ 0.497859e-4, -0.661752e-8, 0.832034e-11, -0.551793e-14, 0.161899e-17, -0.169901e-21] # cm^j · K · %^-1 + cTp = [ 0.779176e-6, 0.396499e-12, 0.395114e-16, 0.233587e-20, -0.636441e-24, 0.716868e-28] # cm^j · K · Pa^-1 + cHp = [-0.206567e-15, 0.106141e-20, -0.149982e-23, 0.984046e-27, -0.288266e-30, 0.299105e-34] # cm^j · %^-1 · Pa^-1 + + sref = 1e4/2.25 # cm^−1, reference wavenumber + Tref = 273.15+17.5 # K, reference temperature + pref = 75000 # Pa, reference pressure + Href = 10 #%, reference humdity in percent + ######################################################################## + + if l > 2.65 and l <= 4.275: + ######################################################################## + # model parameters for wavelengths between 2.8 and 4.2 microns + cref = [ 0.200049e-3, 0.145221e-9, 0.250951e-12, -0.745834e-15, -0.161432e-17, 0.352780e-20] # cm^j + cT = [ 0.588432e-1, -0.825182e-7, 0.137982e-9, 0.352420e-13, -0.730651e-15, -0.167911e-18] # cm^j · K + cTT = [-3.13579, 0.694124e-3, -0.500604e-6, -0.116668e-8, 0.209644e-11, 0.591037e-14] # cm^j · K^2 + cH = [-0.108142e-7, 0.230102e-11, -0.154652e-14, -0.323014e-17, 0.630616e-20, 0.173880e-22] # cm^j · %^-1 + cHH = [ 0.586812e-12, 0.312198e-16, -0.197792e-19, -0.461945e-22, 0.788398e-25, 0.245580e-27] # cm^j · %^-2 + cp = [ 0.266900e-8, 0.168162e-14, 0.353075e-17, -0.963455e-20, -0.223079e-22, 0.453166e-25] # cm^j · Pa^-1 + cpp = [ 0.608860e-17, 0.461560e-22, 0.184282e-24, -0.524471e-27, -0.121299e-29, 0.246512e-32] # cm^j · Pa^-2 + cTH = [ 0.517962e-4, -0.112149e-7, 0.776507e-11, 0.172569e-13, -0.320582e-16, -0.899435e-19] # cm^j · K · %^-1 + cTp = [ 0.778638e-6, 0.446396e-12, 0.784600e-15, -0.195151e-17, -0.542083e-20, 0.103530e-22] # cm^j · K · Pa^-1 + cHp = [-0.217243e-15, 0.104747e-20, -0.523689e-23, 0.817386e-26, 0.309913e-28, -0.363491e-31] # cm^j · %^-1 · Pa^-1 + + sref = 1e4/3.4 # cm^−1, reference wavenumber + Tref = 273.15+17.5 # K, reference temperature + pref = 75000 # Pa, reference pressure + Href = 10 #%, reference humdity in percent + ######################################################################## + + if l > 4.275 and l <= 6.35: + ######################################################################## + # model parameters for wavelengths between 4.35 and 5.2 microns + cref = [ 0.200020e-3, 0.275346e-9, 0.325702e-12, -0.693603e-14, 0.285610e-17, 0.338758e-18] # cm^j + cT = [ 0.590035e-1, -0.375764e-6, 0.134585e-9, 0.124316e-11, 0.508510e-13, -0.189245e-15] # cm^j · K + cTT = [-4.09830, 0.250037e-2, 0.275187e-6, -0.653398e-8, -0.310589e-9, 0.127747e-11] # cm^j · K^2 + cH = [-0.140463e-7, 0.839350e-11, -0.190929e-14, -0.121399e-16, -0.898863e-18, 0.364662e-20] # cm^j · %^-1 + cHH = [ 0.543605e-12, 0.112802e-15, -0.229979e-19, -0.191450e-21, -0.120352e-22, 0.500955e-25] # cm^j · %^-2 + cp = [ 0.266898e-8, 0.273629e-14, 0.463466e-17, -0.916894e-23, 0.136685e-21, 0.413687e-23] # cm^j · Pa^-1 + cpp = [ 0.610706e-17, 0.116620e-21, 0.244736e-24, -0.497682e-26, 0.742024e-29, 0.224625e-30] # cm^j · Pa^-2 + cTH = [ 0.674488e-4, -0.406775e-7, 0.289063e-11, 0.819898e-13, 0.468386e-14, -0.191182e-16] # cm^j · K · %^-1 + cTp = [ 0.778627e-6, 0.593296e-12, 0.145042e-14, 0.489815e-17, 0.327941e-19, 0.128020e-21] # cm^j · K · Pa^-1 + cHp = [-0.211676e-15, 0.487921e-20, -0.682545e-23, 0.942802e-25, -0.946422e-27, -0.153682e-29] # cm^j · %^-1 · Pa^-1 + σref = 1e4/4.8 # cm^−1, reference wavenumber + Tref = 273.15+17.5 # K, reference temperature + pref = 75000 # Pa, reference pressure + Href = 10 #%, reference humdity in percent + ######################################################################## + + if l > 6.35 and l <= 15.05: + ######################################################################## + # model parameters for wavelengths between 7.5 and 14.1 microns + cref = [ 0.199885e-3, 0.344739e-9, -0.273714e-12, 0.393383e-15, -0.569488e-17, 0.164556e-19] # cm^j + cT = [ 0.593900e-1, -0.172226e-5, 0.237654e-8, -0.381812e-11, 0.305050e-14, -0.157464e-16] # cm^j · K + cTT = [-6.50355, 0.103830e-1, -0.139464e-4, 0.220077e-7, -0.272412e-10, 0.126364e-12] # cm^j · K^2 + cH = [-0.221938e-7, 0.347377e-10, -0.465991e-13, 0.735848e-16, -0.897119e-19, 0.380817e-21] # cm^j · %^-1 + cHH = [ 0.393524e-12, 0.464083e-15, -0.621764e-18, 0.981126e-21, -0.121384e-23, 0.515111e-26] # cm^j · %^-2 + cp = [ 0.266809e-8, 0.695247e-15, 0.159070e-17, -0.303451e-20, -0.661489e-22, 0.178226e-24] # cm^j · Pa^-1 + cpp = [ 0.610508e-17, 0.227694e-22, 0.786323e-25, -0.174448e-27, -0.359791e-29, 0.978307e-32] # cm^j · Pa^-2 + cTH = [ 0.106776e-3, -0.168516e-6, 0.226201e-9, -0.356457e-12, 0.437980e-15, -0.194545e-17] # cm^j · K · %^-1 + cTp = [ 0.77368e-6, 0.216404e-12, 0.581805e-15, -0.189618e-17, -0.198869e-19, 0.589381e-22] # cm^j · K · Pa^-1 + cHp = [-0.206365e-15, 0.300234e-19, -0.426519e-22, 0.684306e-25, -0.467320e-29, 0.126117e-30] # cm^j · %^-1 · Pa^-1 + σref = 1e4/10.1 # cm^−1, reference wavenumber + Tref = 273.15+17.5 # K, reference temperature + pref = 75000 # Pa, reference pressure + Href = 10 #%, reference humdity in percent + ######################################################################## + + + if l > 15.05: + ######################################################################## + # model parameters for wavelengths between 16 and 24 microns + cref = [ 0.199436e-3, 0.299123e-8, -0.214862e-10, 0.143338e-12, 0.122398e-14, -0.114628e-16] # cm^j + # something seems to be wrong with cT... + cT = [ 0.621723e-1, -0.177074e-4, 0.152213e-6, -0.954584-9, -0.996706e-11, 0.921476e-13] # cm^j · K + cTT = [-23.2409, 0.108557, -0.102439e-2, 0.634072e-5, 0.762517e-7, -0.675587e-9 ] # cm^j · K^2 + cH = [-0.772707e-7, 0.347237e-9, -0.272675e-11, 0.170858e-13, 0.156889e-15, -0.150004e-17] # cm^j · %^-1 + cHH = [-0.326604e-12, 0.463606e-14, -0.364272e-16, 0.228756e-18, 0.209502e-20, -0.200547e-22] # cm^j · %^-2 + cp = [ 0.266827e-8, 0.120788e-14, 0.522646e-17, 0.783027e-19, 0.753235e-21, -0.228819e-24] # cm^j · Pa^-1 + cpp = [ 0.613675e-17, 0.585494e-22, 0.286055e-24, 0.425193e-26, 0.413455e-28, -0.812941e-32] # cm^j · Pa^-2 + cTH = [ 0.375974e-3, -0.171849e-5, 0.146704e-7, -0.917231e-10, -0.955922e-12, 0.880502e-14] # cm^j · K · %^-1 + cTp = [ 0.778436e-6, 0.461840e-12, 0.306229e-14, -0.623183e-16, -0.161119e-18, 0.800756e-20] # cm^j · K · Pa^-1 + cHp = [-0.272614e-15, 0.304662e-18, -0.239590e-20, 0.149285e-22, 0.136086e-24, -0.130999e-26] # cm^j · %^-1 · Pa^-1 + σref = 1e4/20 # cm^−1, reference wavenumber + Tref = 273.15+17.5 # K, reference temperature + pref = 75000 # Pa, reference pressure + Href = 10 #%, reference humdity in percent + ######################################################################## + + s = 1e4/l # cm^-1 + n = 1 + H = H*100 + + for j in range(0, 6): + n += ( cref[j] + cT[j]*(1/T-1/Tref) + cTT[j]*(1/T-1/Tref)**2 + + cH[j]*(H-Href) + cHH[j]*(H-Href)**2 + + cp[j]*(p-pref) + cpp[j]*(p-pref)**2 + + cTH[j]*(1/T-1/Tref)*(H-Href) + + cTp[j]*(1/T-1/Tref)*(p-pref) + + cHp[j]*(H-Href)*(p-pref) ) * (s-sref)**j + return n + + +def K2C(K): + ''' + Kelvin to Celsius conversion + ''' + return K - 273.15 + +def hPA2mmHg(hPa): + ''' + hPa to mmHg conversion + ''' + return 0.7500616827 * hPa \ No newline at end of file diff --git a/manual.pdf b/manual.pdf deleted file mode 100644 index 865eed4..0000000 Binary files a/manual.pdf and /dev/null differ