ReFRACtor
reference_vmr_apriori.cc
Go to the documentation of this file.
2 #include "wgs84_constant.h"
3 #include "old_constant.h"
4 #include "linear_interpolate.h"
5 #include "fp_logger.h"
6 
7 using namespace FullPhysics;
8 using namespace blitz;
9 using namespace boost::posix_time;
10 
14 std::map<std::string, double> lat_gradient =
15 {
16  {"CO2", 0.000 },
17  {"O3", 0.200 },
18  {"CO", 0.280 },
19  {"CH4", 0.033 },
20  {"NO2", 0.250 },
21  {"NH3", 0.200 },
22  {"HNO3", 0.100 },
23  {"H2CO", 0.200 },
24  {"HCN", 0.100 },
25  {"CH3F", 0.200 },
26  {"CH3Cl", 0.200 },
27  {"CF4", 0.200 },
28  {"CCl2F2", 0.200 },
29  {"CCl3F", 0.200 },
30  {"CH3CCl3", 0.200 },
31  {"CCl4", 0.200 },
32  {"C2H6", 0.300 },
33  {"C2H4", 0.300 },
34  {"C2H2", 0.300 },
35  {"CHClF2", 0.200 },
36  {"CH3Br", 0.200 },
37  {"HCOOH", 0.200 },
38  {"CHCl2F", 0.200 },
39  {"SF6", 0.300 },
40  {"F113", 0.300 },
41  {"F142b", 0.200 },
42  {"CH3OH", 0.200 },
43  {"CH3CHO", 0.200 },
44  {"CH3CN", 0.200 },
45  {"NF3", 0.300 },
46  {"CHF3", 0.200 },
47  {"f141b", 0.200 },
48  {"CH3COOH", 0.200 },
49  {"C3H8", 0.500 }
50 };
51 
52 std::map<std::string, double> secular_trend =
53 {
54  {"CO2", 0.0052},
55  {"N2O", 0.001},
56  {"CO", -0.006},
57  {"CH4", 0.003},
58  {"HF", -0.01}
59 };
60 
61 // Amplitude at surface at 50N
62 std::map<std::string, double> seasonal_amplitude =
63 {
64  {"CO2", 0.0081},
65  {"CO", 0.25},
66 };
67 
68 // Lapse rate threshold in deg C/km
69 const double lapse_rate_threshold = -2;
70 
71 // How many km above the level where the lapse rate should still does not exceed 2 degC/km
72 const double mean_lapse_rate_region_alt = 2;
73 
74 // Bounds of pressure levels to consider when searching for tropopause altitude
75 const double tropopause_min_press = 7500;
76 const double tropopause_max_press = 45000;
77 
78 ReferenceVmrApriori::ReferenceVmrApriori(const blitz::Array<double, 1>& Model_pressure,
79  const blitz::Array<double, 1>& Model_altitude,
80  const blitz::Array<double, 1>& Model_temperature,
81  const blitz::Array<double, 1>& Ref_altitude,
82  const double Ref_latitude,
83  const Time& Ref_time,
84  const double Ref_tropopause_altitude,
85  const double Obs_latitude,
86  const Time& Obs_time)
87 : model_pressure(Model_pressure), model_altitude(Model_altitude), model_temperature(Model_temperature),
88  ref_altitude(Ref_altitude), ref_latitude(Ref_latitude), ref_time(Ref_time), ref_tropopause_altitude(Ref_tropopause_altitude),
89  obs_latitude(Obs_latitude), obs_time(Obs_time)
90 {
91 }
92 
93 //-----------------------------------------------------------------------
98 //-----------------------------------------------------------------------
99 
101 {
102  // Equatorial radius (km)
103  double radius = OldConstant::wgs84_a.convert(units::km).value;
104 
105  // Precompute lapse rates for evaluation
106  blitz::Array<double, 1> lapse_rates(model_altitude.rows());
107  lapse_rates(0) = 0.0;
108  double min_temp_val = max(model_temperature);
109  double min_temp_idx = -1;
110  for (int i = 1; i < model_altitude.rows(); i++) {
111  // Lapse rate
112  lapse_rates(i) = (model_temperature(i) - model_temperature(i - 1)) /
113  (model_altitude(i) - model_altitude(i - 1));
114 
115  // Save minimum temperature in case lapse rate check fails, but only
116  // where we are within a valid preesure region
117  if( model_pressure(i) > tropopause_min_press && model_pressure(i) < tropopause_max_press &&
118  model_temperature(i) < min_temp_val ) {
119  min_temp_val = model_temperature(i);
120  min_temp_idx = i;
121  }
122  }
123 
124  // Find lowest level where lapse rate is below the threshold
125  int lr_idx = -1;
126  for (int chk_idx = 1; chk_idx < lapse_rates.rows(); chk_idx++) {
127  double lay_p = 0.5 * (model_pressure(chk_idx) + model_pressure(chk_idx - 1));
128  double lay_alt = 0.5 * (model_altitude(chk_idx) + model_altitude(chk_idx - 1));
129 
130  if( lay_p > tropopause_min_press && lay_p < tropopause_max_press &&
131  lapse_rates(chk_idx) > lapse_rate_threshold ) {
132 
133  // Check if mean lapse rate at the 2 km above the current level does not exceeed the threshold
134  double lr_sum = 0;
135  int avg_idx = chk_idx;
136  double avg_alt;
137  do {
138  avg_idx++;
139  avg_alt = 0.5 * (model_altitude(avg_idx) + model_altitude(avg_idx - 1));
140  lr_sum += lapse_rates(avg_idx);
141  } while (avg_idx < lapse_rates.rows() && avg_alt < (lay_alt + mean_lapse_rate_region_alt));
142 
143  double lr_mean = lr_sum / (avg_idx - chk_idx);
144  if (lr_mean >= lapse_rate_threshold) {
145  lr_idx = chk_idx;
146  break;
147  }
148  }
149  }
150 
151  // No tropopause found, use location of minimum temperature, we need at least to be two levels in for averaging
152  if (lr_idx < 2) {
153  Logger::warning() << "Could not determine tropopause altitude using lapse rate, falling back to minimum temperature";
154  lr_idx = min_temp_idx;
155  }
156 
157  // Altitude at which lapse rate == lr
158  double last_lr_alt = 0.5 * (model_altitude(lr_idx - 1) + model_altitude(lr_idx - 2));
159  double lr_alt = 0.5 * (model_altitude(lr_idx) + model_altitude(lr_idx - 1));
160 
161  double ztrop = last_lr_alt + (lr_alt - last_lr_alt) * (lapse_rate_threshold - lapse_rates(lr_idx-1)) /(lapse_rates(lr_idx) - lapse_rates(lr_idx - 1));
162  ztrop = ztrop / (1 - ztrop / radius); // convert H to Z
163 
164  return DoubleWithUnit(ztrop, "km");
165 }
166 
167 //-----------------------------------------------------------------------
171 //-----------------------------------------------------------------------
172 
173 const blitz::Array<double, 1> ReferenceVmrApriori::effective_altitude() const
174 {
175  double mod_tropo_alt = model_tropopause_altitude().convert(units::km).value;
176  Array<double, 1> effective_altitudes(model_altitude.shape());
177  for (int lev_idx = 0; lev_idx < model_altitude.rows(); lev_idx++) {
178  double zeff;
179  if(model_altitude(lev_idx) < mod_tropo_alt) {
180  // troposphere
181  zeff = model_altitude(lev_idx) * ref_tropopause_altitude / mod_tropo_alt;
182  } else {
183  // stratosphere
184  zeff = model_altitude(lev_idx) + (ref_tropopause_altitude - mod_tropo_alt) *
185  exp(-(model_altitude(lev_idx) - mod_tropo_alt) / 10.0);
186  zeff = zeff - exp(-std::pow(obs_latitude / 25.0, 4))
187  * 3.5 * mod_tropo_alt * std::pow(model_altitude(lev_idx) / mod_tropo_alt - 1, 2)
188  * exp(-(model_altitude(lev_idx) - mod_tropo_alt) / 9.0);
189  }
190 
191  // Don't let effective altitude exceeed limit of model altitude
192  if(zeff > model_altitude(model_altitude.rows() - 1)) {
193  zeff = model_altitude(model_altitude.rows() - 1);
194  }
195 
196  effective_altitudes(lev_idx) = zeff;
197  }
198  return effective_altitudes;
199 }
200 
201 //-----------------------------------------------------------------------
204 //-----------------------------------------------------------------------
205 
206 const double ReferenceVmrApriori::age_of_air(const double altitude) const
207 {
208  double mod_tropo_alt = model_tropopause_altitude().convert(units::km).value;
209 
210  double fl = obs_latitude / 22;
211  double aoa = 0.313 - 0.085 * exp(-std::pow((obs_latitude - 49) / 18, 2))
212  -0.268 * exp(-1.42 * altitude / (altitude + mod_tropo_alt)) * fl / sqrt(1 + std::pow(fl, 2));
213  if(altitude > mod_tropo_alt)
214  aoa = aoa + 7.0 * (altitude - mod_tropo_alt) / altitude;
215  return aoa;
216 }
217 
218 //-----------------------------------------------------------------------
221 //-----------------------------------------------------------------------
222 
223 const blitz::Array<double, 1> ReferenceVmrApriori::resample_to_model_grid(const blitz::Array<double, 1>& vmr) const
224 {
225  Array<double, 1> eff_altitude(effective_altitude());
226  if(vmr.rows() != ref_altitude.rows()) {
227  Exception err_msg;
228  err_msg << "Altitude size: " << ref_altitude.rows() << ", does not match "
229  << "vmr size: " << vmr.rows();
230  throw err_msg;
231  }
232 
233  LinearInterpolate<double, double> mod_grid_interp(ref_altitude.begin(), ref_altitude.end(), vmr.begin());
234  Array<double, 1> mod_grid_vmr(eff_altitude.shape());
235  for(int lev_idx = 0; lev_idx < eff_altitude.rows(); lev_idx++) {
236  mod_grid_vmr(lev_idx) = mod_grid_interp(eff_altitude(lev_idx));
237  }
238  return mod_grid_vmr;
239 }
240 
241 //-----------------------------------------------------------------------
249 //-----------------------------------------------------------------------
250 
251 const blitz::Array<double, 1> ReferenceVmrApriori::apply_latitude_gradient(const blitz::Array<double, 1>& vmr, const std::string& gas_name) const
252 {
253  double mod_tropo_alt = model_tropopause_altitude().convert(units::km).value;
254 
255  double gas_gradient;
256  try {
257  gas_gradient = lat_gradient.at(gas_name);
258  } catch(const std::out_of_range& exc) {
259  Logger::warning() << "apply_latitude_gradients: No latitude gradient found for: " << gas_name;
260  gas_gradient = 0.0;
261  }
262  double xref = gas_gradient * (ref_latitude / 15) / sqrt(1 + std::pow(ref_latitude/15, 2));
263  double xobs = gas_gradient * (obs_latitude / 15) / sqrt(1 + std::pow(obs_latitude/15, 2));
264 
265  Array<double, 1> grad_vmr(vmr.shape());
266  for(int lev_idx = 0; lev_idx < vmr.rows(); lev_idx++) {
267  double frac = 1.0 / (1.0 + std::pow(model_altitude(lev_idx) / mod_tropo_alt, 2));
268  grad_vmr(lev_idx) = vmr(lev_idx) * (1 + frac * xobs) / (1 + frac * xref);
269  }
270  return grad_vmr;
271 }
272 
278 
279 const blitz::Array<double, 1> ReferenceVmrApriori::apply_secular_trend(const blitz::Array<double, 1>& vmr, const std::string& gas_name) const
280 {
281  double time_diff = obs_time.frac_year() - ref_time.frac_year();
282 
283  double trend;
284  try {
285  trend = secular_trend.at(gas_name);
286  } catch(const std::out_of_range& exc) {
287  Logger::warning() << "apply_secular_trend: No secular trend found for: " << gas_name << "\n";
288  trend = 0.0;
289  }
290 
291  Array<double, 1> secular_vmr(vmr.shape());
292  for(int lev_idx = 0; lev_idx < vmr.rows(); lev_idx++) {
293  double aoa = age_of_air(model_altitude(lev_idx));
294  secular_vmr(lev_idx) = vmr(lev_idx) * (1 + trend * (time_diff - aoa));
295  if(gas_name == "HF")
296  secular_vmr(lev_idx) = secular_vmr(lev_idx) / (1.0 + exp((-time_diff + aoa - 16) / 5.0));
297  if(gas_name == "SF6")
298  secular_vmr(lev_idx) = 1.5 * secular_vmr(lev_idx) / (1.0 + exp((-time_diff + aoa - 4) / 9.0));
299  }
300 
301  return secular_vmr;
302 }
303 
308 
309 const blitz::Array<double, 1> ReferenceVmrApriori::apply_seasonal_cycle(const blitz::Array<double, 1>& vmr, const std::string& gas_name) const
310 {
311  double twopi = 2.0 * OldConstant::pi;
312 
313  // Remove year just to get how far into the year
314  double obs_year_frac = obs_time.frac_year() - ptime(obs_time).date().year();
315 
316  double amplitude;
317  try {
318  amplitude = seasonal_amplitude.at(gas_name);
319  } catch(const std::out_of_range& exc) {
320  Logger::warning() << "apply_seasonal_cycle: No seasonal cycle amplitude found for: " << gas_name << "\n";
321  amplitude = 0.0;
322  }
323 
324  Array<double, 1> seasonal_vmr(vmr.shape());
325  for(int lev_idx = 0; lev_idx < vmr.rows(); lev_idx++) {
326  double zobs = model_altitude(lev_idx);
327  double aoa = age_of_air(zobs);
328  double sca;
329  if (gas_name == "CO2") {
330  // seasonal variation
331  double sv = std::sin(twopi * (obs_year_frac - 0.834 - aoa));
332  // seasonal variation
333  double svnl = sv + 1.80 * exp(-std::pow((obs_latitude - 74) / 41, 2)) * (0.5 - std::pow(sv, 2));
334  sca = svnl * exp(-aoa/0.20) * (1 + 1.33 * exp(-std::pow((obs_latitude - 76) / 48, 2))*(zobs + 6.0) / (zobs + 1.4));
335  } else {
336  // basic seasonal variation
337  double sv = std::sin(twopi * (obs_year_frac - 0.89));
338  // latitude dependence
339  double svl = sv * (obs_latitude / 15) / sqrt(1 + std::pow(obs_latitude / 15, 2));
340  // altitude dependence
341  sca = svl * exp(-aoa / 1.60);
342  }
343  seasonal_vmr(lev_idx) = vmr(lev_idx) * (1.0 + sca * amplitude);
344  }
345  return seasonal_vmr;
346 }
347 
352 
353 const blitz::Array<double, 1> ReferenceVmrApriori::apriori_vmr(const blitz::Array<double, 1>& vmr, const std::string& gas_name) const
354 {
355  Array<double, 1> mod_grid_vmr = resample_to_model_grid(vmr);
356  Array<double, 1> lat_grad_vmr = apply_latitude_gradient(mod_grid_vmr, gas_name);
357  Array<double, 1> secular_trend_vmr = apply_secular_trend(lat_grad_vmr, gas_name);
358  Array<double, 1> seasonal_cycle_vmr = apply_seasonal_cycle(secular_trend_vmr, gas_name);
359  return seasonal_cycle_vmr;
360 }
const blitz::Array< double, 1 > resample_to_model_grid(const blitz::Array< double, 1 > &vmr) const
Resamples a VMR to the effective model altitude grid that accounts for the difference in tropopause a...
const blitz::Array< double, 1 > apply_seasonal_cycle(const blitz::Array< double, 1 > &vmr, const std::string &gas_name) const
Modifies the a priori vmr profile to account for the season of the observation/model.
const blitz::Array< double, 1 > apriori_vmr(const blitz::Array< double, 1 > &vmr, const std::string &gas_name) const
Creates the a priori VMR using the various transformation methods of the class.
const double lapse_rate_threshold
double frac_year() const
Calculate the fractional year.
Definition: fp_time.cc:49
const DoubleWithUnit wgs84_a(6378137.0000, units::m)
Equatorial radius.
DoubleWithUnit model_tropopause_altitude() const
Calculate the tropopause altitude for the model data used to initalize the class. ...
This is the base of the exception hierarchy for Full Physics code.
Definition: fp_exception.h:16
std::map< std::string, double > secular_trend
const double tropopause_min_press
ReferenceVmrApriori(const blitz::Array< double, 1 > &Model_pressure, const blitz::Array< double, 1 > &Model_altitude, const blitz::Array< double, 1 > &Model_temperature, const blitz::Array< double, 1 > &Ref_altitude, const double Ref_latitude, const Time &Ref_time, const double Ref_tropopause_altitude, const double Obs_latitude, const Time &Obs_time)
Apply value function to a blitz array.
std::map< std::string, double > lat_gradient
Inter-hemispheric gradients Positive values imply more in the NH than the SH than would be expected b...
const double mean_lapse_rate_region_alt
std::map< std::string, double > seasonal_amplitude
We frequently have a double with units associated with it.
DoubleWithUnit convert(const Unit &R) const
Convert to the given units.
This is a simple time class.
Definition: fp_time.h:29
const double tropopause_max_press
Contains classes to abstract away details in various Spurr Radiative Transfer software.
Definition: doxygen_python.h:1
const blitz::Array< double, 1 > apply_latitude_gradient(const blitz::Array< double, 1 > &vmr, const std::string &gas_name) const
Modifies the vmr profiles to account for the difference in latitude between the observation latitude ...
const blitz::Array< double, 1 > apply_secular_trend(const blitz::Array< double, 1 > &vmr, const std::string &gas_name) const
Modifies the a priori profiles on a gas-by-gas basis to account for the difference in time between t...
const blitz::Array< double, 1 > effective_altitude() const
Computes an altitude grid for resampling that takes into account a difference in the tropopause altit...
const Unit km("km", 1e3 *m)
static LogHelper warning()
Definition: logger.h:110
const double age_of_air(const double altitude) const
Computes the Age of Air at particular location (Altitude, Latitude) relative to the surface at 50N...

Copyright © 2017, California Institute of Technology.
ALL RIGHTS RESERVED.
U.S. Government Sponsorship acknowledged.
Generated Fri Aug 24 2018 15:44:10