ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_LargeScaleForcingData.H
Go to the documentation of this file.
1 #ifndef ERF_LARGE_SCALE_FORCING_DATA_H_
2 #define ERF_LARGE_SCALE_FORCING_DATA_H_
3 
4 #include <string>
5 #include <iostream>
6 #include <sstream>
7 #include <fstream>
8 
9 #include <AMReX_ParmParse.H>
10 #include <AMReX_Print.H>
11 #include <AMReX_Gpu.H>
12 #include <AMReX_Geometry.H>
13 
14 #include <ERF_EOS.H>
15 #include <ERF_NumericalConstants.H>
16 #include <ERF_Interpolation_1D.H>
17 #include <ERF_HSEUtils.H>
18 #include <ERF_InputSoundingData.H>
19 
20 /**
21  * Data structure storing large-scale forcing data.
22  *
23  * This provides time-varying vertical profiles for large scale temperature,
24  * and water vapor tendencies, as well as observed large scale zonal and meridonal wind,
25  * and vertical subsidence.
26  * Profiles are read from a text file, interpolated to the model vertical grid, and then copied
27  * to device memory for use during time integration.
28  *
29  * This mirrors the lsf functionality as implemented in SAM.
30  */
32 public:
33  /**
34  * @brief Construct large-scale forcing metadata from the ERF input namespace.
35  */
37  {
38  amrex::ParmParse pp("erf");
39  pp.queryAdd("forcing_timescale", tau_lsf);
40  pp.queryAdd("large_scale_forcing_file", lsf_file);
41  }
42 
43  /**
44  * @brief Count the number of whitespace-separated columns in a line.
45  * @param str Input line to inspect.
46  * @return Number of columns in the line.
47  */
48  int get_columns_in_str (std::string &str)
49  {
50  std::istringstream iss(str);
51  std::string tmp;
52  // Get the number of columns in the file
53  int ncols = 0;
54  while (iss >> tmp) {
55  ncols+= 1;
56  }
57  return ncols;
58  }
59 
60 
61  /**
62  * @brief Read the forcing file and cache the raw time slices on host.
63  */
65  {
66  amrex::Print() << " Opening LSF file '" << lsf_file << "'" << std::endl;
67  std::ifstream ifs(lsf_file);
68  if (!ifs.is_open())
69  {
70  amrex::Error("Error opening input forcing file " + lsf_file);
71  }
72 
73  std::string line;
74 
75  // temporary vectors for storing the values at each time
76  amrex::Vector<amrex::Real> z_in, p_in, t_in, q_in, u_in, v_in, w_in;
77 
78  // skip first header line
79  std::getline(ifs, line);
80  // header should have 7 columns: z, p, tls, qls, uls, vls, wls
82 
83  num_times = 0;
84  nz_lsf = 0;
85 
86  amrex::Real file_start_time = zero; // first time in file, in days, other inputs are relative to this time
87 
88  amrex::Real pres0;
89  while(std::getline(ifs, line)) {
90  std::istringstream iss(line);
91 
92  // The start of each day has 6 columns: day, levels, pres0, "day, levels, pres0"
93  int ncol = get_columns_in_str(line);
94 
95  if (ncol == 6)
96  {
97  // this is a new set of inputs at a given time
98 
99  // have to read these as strings first to split on commas
100  std::string s_time, s_lev, s_pres0;
101  iss >> s_time >> s_lev >> s_pres0;
102  amrex::Real time = std::stod(s_time);
103  int nlev = std::stoi(s_lev);
104  pres0 = std::stod(s_pres0);
105 
106  // convert time to seconds (relative to first time), and pres from mb to Pa
107  if (num_times == 0)
108  {
109  file_start_time = time;
110  }
111  time = (time - file_start_time) * amrex::Real(86400.0);
112 
113  if (times_lsf.size() > 0 && time != times_lsf.back())
114  {
115  // if we started a new time, store data for previous time and reset tmp arrays
116  AMREX_ALWAYS_ASSERT(z_in.size() == nz_lsf + 1);
117  z_lsf.push_back(z_in);
118  p_lsf.push_back(p_in);
119  t_lsf.push_back(t_in);
120  q_lsf.push_back(q_in);
121  u_lsf.push_back(u_in);
122  v_lsf.push_back(v_in);
123  w_lsf.push_back(w_in);
124 
125  z_in.clear();
126  p_in.clear();
127  t_in.clear();
128  q_in.clear();
129  u_in.clear();
130  v_in.clear();
131  w_in.clear();
132  }
133 
134  times_lsf.push_back(time);
135  num_times++;
136 
137  pres0 *= amrex::Real(100.0);
138  if (nz_lsf == 0)
139  {
140  nz_lsf = nlev;
141  } else {
142  // make sure this has the same number of inputs.
143  // TODO: this probably doesn't have to be true since we are interpolating onto the grid?
144  AMREX_ALWAYS_ASSERT(nlev == nz_lsf);
145  }
146 
147  z_in.push_back(zero); // assuming zbot = 0.0 for now, this will get interpolated later once we have grid info
148  p_in.push_back(pres0);
149  t_in.push_back(zero); // assuming temperature change at surf is 0
150  q_in.push_back(zero);
151  u_in.push_back(zero);
152  v_in.push_back(zero);
153  w_in.push_back(zero);
154  }
155  else if (ncol == 7)
156  {
157  // this is defining a level at the current time
158  amrex::Real z, p, tls, qls, uls, vls, wls;
159  iss >> z >> p >> tls >> qls >> uls >> vls >> wls;
160 
161  if (z < zero) {
162  // if z < 0, then LSF is defined on pressure levels
163  use_z_grid = false;
164  }
165 
166  z_in.push_back(z);
167  p_in.push_back(p * amrex::Real(100.0));
168  t_in.push_back(tls);
169  q_in.push_back(qls);
170  u_in.push_back(uls);
171  v_in.push_back(vls);
172  w_in.push_back(wls);
173  }
174  else {
175  amrex::Error("unexpected line in input file: " + line);
176  }
177  }
178 
179  ifs.close();
180 
181  // store the last set from the EOF
182  AMREX_ALWAYS_ASSERT(z_in.size() == nz_lsf + 1);
183  z_lsf.push_back(z_in);
184  p_lsf.push_back(p_in);
185  t_lsf.push_back(t_in);
186  q_lsf.push_back(q_in);
187  u_lsf.push_back(u_in);
188  v_lsf.push_back(v_in);
189  w_lsf.push_back(w_in);
190 
191  num_times = times_lsf.size();
193  "repeated timestamp in the large-scale forcing file");
194  amrex::Print() << " Read " << nz_lsf << " levels at " << num_times << " times" << std::endl;
195 
196  // debugging print
197  if (verbose_print && !times_lsf.empty()) {
198  const int i = 0; // print only the first time block
199  amrex::Print() << " LSF at time = " << times_lsf[i] << std::endl;
200  for (int lev = 0; lev < nz_lsf + 1; lev++)
201  {
202  amrex::Print() << " level " << lev << ": z = " << z_lsf[i][lev] << " p = " << p_lsf[i][lev] << " tls = "
203  << t_lsf[i][lev] << " qls = " << q_lsf[i][lev] << " uls = " << u_lsf[i][lev] << " vls = "
204  << v_lsf[i][lev] << " wls = " << w_lsf[i][lev] << std::endl;
205  }
206  }
207  }
208 
209 
210  /**
211  * @brief Interpolate the forcing profiles onto the model grid.
212  * @param geom Geometry defining the vertical grid.
213  * @param zlevels_stag Staggered vertical coordinates used for interpolation.
214  * @param sounding Input sounding data used when the forcing file is on pressure levels.
215  */
216  void
217  interp_forcing (const amrex::GeometryData &geom,
218  const amrex::Vector<amrex::Real>& zlevels_stag,
219  const InputSoundingData &sounding)
220  {
221  const int klo = 0;
222  const int khi = geom.Domain().bigEnd()[AMREX_SPACEDIM-1];
223  const int Nz = geom.Domain().size()[AMREX_SPACEDIM-1];
224  const amrex::Real dz = geom.CellSize()[AMREX_SPACEDIM-1];
225 
226  const bool use_terrain = (zlevels_stag.size() > 0);
227  const amrex::Real zbot = (use_terrain) ? zlevels_stag[klo] : geom.ProbLo(AMREX_SPACEDIM-1);
228  const amrex::Real ztop = (use_terrain) ? zlevels_stag[khi+1] : geom.ProbHi(AMREX_SPACEDIM-1);
229 
230  AMREX_ALWAYS_ASSERT(num_times == static_cast<int>(times_lsf.size()));
231  // On pressure levels, cell k is placed by the sounding's hydrostatic pressure at its
232  // centre, pm_integ[k+1] (pm_integ[0] is the surface), so the sounding must be sampled
233  // on this same grid and integrated (sounding_type Ideal or Isentropic)
234  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(use_z_grid || static_cast<int>(sounding.pm_integ.size()) >= Nz+1,
235  "a large-scale forcing file on pressure levels needs the input sounding integrated "
236  "hydrostatically on the same grid (erf.sounding_type = Ideal or Isentropic)");
237 
238  z_int_lsf.resize(num_times);
239  t_int_lsf.resize(num_times);
240  q_int_lsf.resize(num_times);
241  u_int_lsf.resize(num_times);
242  v_int_lsf.resize(num_times);
243  w_int_lsf.resize(num_times);
244 
245  z_int_lsf_d.resize(num_times);
246  t_int_lsf_d.resize(num_times);
247  q_int_lsf_d.resize(num_times);
248  u_int_lsf_d.resize(num_times);
249  v_int_lsf_d.resize(num_times);
250  w_int_lsf_d.resize(num_times);
251  for (int itime = 0; itime < num_times; itime++)
252  {
253  z_int_lsf[itime].resize(Nz+1);
254  t_int_lsf[itime].resize(Nz+1);
255  q_int_lsf[itime].resize(Nz+1);
256  u_int_lsf[itime].resize(Nz+1);
257  v_int_lsf[itime].resize(Nz+1);
258  w_int_lsf[itime].resize(Nz+1);
259 
260  for (int k = 0; k < Nz; k++)
261  {
262  z_int_lsf[itime][k] = (use_terrain) ? myhalf * (zlevels_stag[k] + zlevels_stag[k+1])
263  : zbot + (k + myhalf) * dz;
264 
265  bool set = false;
266  for (int lk = 2; lk < nz_lsf + 1; lk++)
267  {
268  // Determine if grid cell k falls in bracket [lk-1, lk]:
269  // z-grid mode: cell z <= forcing z (z increases with lk)
270  // p-grid mode: cell p >= forcing p (p decreases with lk)
271  bool in_bracket = use_z_grid
272  ? (z_int_lsf[itime][k] <= z_lsf[itime][lk])
273  : (sounding.pm_integ[k+1] >= p_lsf[itime][lk]);
274 
275  if (in_bracket)
276  {
277  // Interpolation coordinate: z or p depending on mode
278  const auto& coord_lsf = use_z_grid ? z_lsf[itime] : p_lsf[itime];
279  amrex::Real coord_k = use_z_grid ? z_int_lsf[itime][k] : sounding.pm_integ[k+1];
280  amrex::Real coef = (coord_k - coord_lsf[lk - 1]) / (coord_lsf[lk] - coord_lsf[lk - 1]);
281 
282  t_int_lsf[itime][k] = t_lsf[itime][lk - 1] + (t_lsf[itime][lk] - t_lsf[itime][lk-1])*coef;
283  q_int_lsf[itime][k] = q_lsf[itime][lk - 1] + (q_lsf[itime][lk] - q_lsf[itime][lk-1])*coef;
284  u_int_lsf[itime][k] = u_lsf[itime][lk - 1] + (u_lsf[itime][lk] - u_lsf[itime][lk-1])*coef;
285  v_int_lsf[itime][k] = v_lsf[itime][lk - 1] + (v_lsf[itime][lk] - v_lsf[itime][lk-1])*coef;
286  w_int_lsf[itime][k] = w_lsf[itime][lk - 1] + (w_lsf[itime][lk] - w_lsf[itime][lk-1])*coef;
287  set = true;
288  break;
289  }
290  }
291 
292  if (set) {
293  continue;
294  }
295 
296  t_int_lsf[itime][k] = zero;
297  q_int_lsf[itime][k] = zero;
298  u_int_lsf[itime][k] = (k > 0) ? u_int_lsf[itime][k-1] : zero;
299  v_int_lsf[itime][k] = (k > 0) ? v_int_lsf[itime][k-1] : zero;
300  w_int_lsf[itime][k] = zero;
301  }
302 
303  z_int_lsf[itime][Nz] = ztop;
304  t_int_lsf[itime][Nz] = zero;
305  q_int_lsf[itime][Nz] = zero;
306  u_int_lsf[itime][Nz] = u_int_lsf[itime][Nz-1];
307  v_int_lsf[itime][Nz] = v_int_lsf[itime][Nz-1];
308  w_int_lsf[itime][Nz] = zero;
309 
310  host_to_device(itime);
311  }
312 
313  // debugging print
314  if (verbose_print && !times_lsf.empty()) {
315  const int i = 0; // print only the first time block
316  amrex::Print() << " INTERPOLATED LSF at time = " << times_lsf[i] << std::endl;
317  for (int k = 0; k <= Nz; k++)
318  {
319  amrex::Print() << " level " << k << ": z = " << z_int_lsf[i][k] << " tls = " << t_int_lsf[i][k]
320  << " qls = " << q_int_lsf[i][k] << " uls = " << u_int_lsf[i][k] << " vls = "
321  << v_int_lsf[i][k] << " wls = " << w_int_lsf[i][k] << std::endl;
322  }
323  }
324  }
325 
326  /**
327  * @brief Determine interpolation coefficients to apply tendencies for the given time
328  * @param time Elapsed simulation time in seconds.
329  * @param curr Index of the lower bracketing forcing time.
330  * @param next Index of the upper bracketing forcing time.
331  * @param coeff_curr Interpolation weight for the lower time slice.
332  * @param coeff_next Interpolation weight for the upper time slice.
333  */
334  void
336  int& curr,
337  int& next,
338  amrex::Real& coeff_curr,
339  amrex::Real& coeff_next) const
340  {
341  // helper function to get the current time indices of forcing data to use and
342  // coefficients based on the current simulation time.
343  curr = 0;
344  next = 0;
345  coeff_curr = one;
346  coeff_next = zero;
347 
348  // find the time index of forcing data to use
349  for (int nt = 1; nt < num_times; nt++)
350  {
351  if (time > times_lsf[nt])
352  {
353  curr = nt;
354  }
355  }
356 
357  if (curr == num_times - 1)
358  {
359  // use last set if time > last forcing time
360  next = num_times - 1;
361  coeff_curr = one;
362  coeff_next = zero;
363  } else {
364  next = curr + 1;
365  coeff_next = (time - times_lsf[curr]) / (times_lsf[next] - times_lsf[curr]);
366  coeff_curr = (one - coeff_next);
367  }
368  }
369 
370  /**
371  * @brief Copy one interpolated forcing slice from host memory to device memory.
372  * @param itime Time-slice index to copy.
373  */
374  void host_to_device (int itime)
375  {
376  const int nz = z_int_lsf[itime].size();
377  z_int_lsf_d[itime].resize(nz);
378  t_int_lsf_d[itime].resize(nz);
379  q_int_lsf_d[itime].resize(nz);
380  u_int_lsf_d[itime].resize(nz);
381  v_int_lsf_d[itime].resize(nz);
382  w_int_lsf_d[itime].resize(nz);
383 
384  amrex::Gpu::copy(amrex::Gpu::hostToDevice,
385  z_int_lsf[itime].begin(), z_int_lsf[itime].end(),
386  z_int_lsf_d[itime].begin());
387  amrex::Gpu::copy(amrex::Gpu::hostToDevice,
388  t_int_lsf[itime].begin(), t_int_lsf[itime].end(),
389  t_int_lsf_d[itime].begin());
390  amrex::Gpu::copy(amrex::Gpu::hostToDevice,
391  q_int_lsf[itime].begin(), q_int_lsf[itime].end(),
392  q_int_lsf_d[itime].begin());
393  amrex::Gpu::copy(amrex::Gpu::hostToDevice,
394  u_int_lsf[itime].begin(), u_int_lsf[itime].end(),
395  u_int_lsf_d[itime].begin());
396  amrex::Gpu::copy(amrex::Gpu::hostToDevice,
397  v_int_lsf[itime].begin(), v_int_lsf[itime].end(),
398  v_int_lsf_d[itime].begin());
399  amrex::Gpu::copy(amrex::Gpu::hostToDevice,
400  w_int_lsf[itime].begin(), w_int_lsf[itime].end(),
401  w_int_lsf_d[itime].begin());
402  }
403 
404  /**
405  * Relaxation time scale used by the large-scale forcing and nudging terms.
406  */
408  /**
409  * Path to the large-scale forcing input file.
410  */
411  std::string lsf_file = "";
412 
413  /**
414  * True when the forcing file is tabulated on height levels, false when it is
415  * tabulated on pressure levels.
416  */
417  bool use_z_grid = true;
418 
419  /**
420  * Whether to display the lsf at initialization (before and after interpolation)
421  */
422  bool verbose_print = false;
423 
424  /**
425  * Number of vertical levels and times found in the forcing file.
426  */
427  int nz_lsf = -1;
428  int num_times = -1;
429  /**
430  * Forcing times read from the file, converted to elapsed seconds.
431  */
432  amrex::Vector<amrex::Real> times_lsf;
433  /**
434  * Raw forcing profiles read from the input file, indexed by time then level.
435  */
436  amrex::Vector<amrex::Vector<amrex::Real>> z_lsf;
437  amrex::Vector<amrex::Vector<amrex::Real>> p_lsf;
438  amrex::Vector<amrex::Vector<amrex::Real>> t_lsf;
439  amrex::Vector<amrex::Vector<amrex::Real>> q_lsf;
440  amrex::Vector<amrex::Vector<amrex::Real>> u_lsf;
441  amrex::Vector<amrex::Vector<amrex::Real>> v_lsf;
442  amrex::Vector<amrex::Vector<amrex::Real>> w_lsf;
443 
444  /**
445  * Forcing profiles interpolated onto the model grid, indexed by time then level.
446  */
447  amrex::Vector<amrex::Vector<amrex::Real>> z_int_lsf;
448  amrex::Vector<amrex::Vector<amrex::Real>> t_int_lsf;
449  amrex::Vector<amrex::Vector<amrex::Real>> q_int_lsf;
450  amrex::Vector<amrex::Vector<amrex::Real>> u_int_lsf;
451  amrex::Vector<amrex::Vector<amrex::Real>> v_int_lsf;
452  amrex::Vector<amrex::Vector<amrex::Real>> w_int_lsf;
453 
454  /**
455  * Device copies of the interpolated forcing profiles.
456  */
457  amrex::Vector<amrex::Gpu::DeviceVector<amrex::Real>> z_int_lsf_d, t_int_lsf_d, q_int_lsf_d, u_int_lsf_d, v_int_lsf_d, w_int_lsf_d;
458 
459 };
460 #endif
const Real ztop
Definition: ERF_InitCustomPertVels_ParticleTests.H:4
const int klo
Definition: ERF_InitCustomPert_ABL.H:75
ParmParse pp("prob")
const int khi
Definition: ERF_InitCustomPert_Bubble.H:21
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_cloud_chamber_config.active, "Cloud Chamber: initializer reached without a parsed configuration")
const bool use_terrain
Definition: ERF_InitCustomPert_SLM.H:14
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
@ p
Definition: ERF_WSM6.H:280
@ tmp
Definition: ERF_AdvanceWSM6.cpp:116
Definition: ERF_InputSoundingData.H:23
amrex::Vector< amrex::Real > pm_integ
Hydrostatically integrated moist pressure profile.
Definition: ERF_InputSoundingData.H:512
Definition: ERF_LargeScaleForcingData.H:31
bool use_z_grid
Definition: ERF_LargeScaleForcingData.H:417
amrex::Vector< amrex::Real > times_lsf
Definition: ERF_LargeScaleForcingData.H:432
amrex::Vector< amrex::Vector< amrex::Real > > u_lsf
Definition: ERF_LargeScaleForcingData.H:440
void read_forcing_file()
Read the forcing file and cache the raw time slices on host.
Definition: ERF_LargeScaleForcingData.H:64
amrex::Vector< amrex::Gpu::DeviceVector< amrex::Real > > q_int_lsf_d
Definition: ERF_LargeScaleForcingData.H:457
bool verbose_print
Definition: ERF_LargeScaleForcingData.H:422
amrex::Vector< amrex::Vector< amrex::Real > > z_lsf
Definition: ERF_LargeScaleForcingData.H:436
amrex::Vector< amrex::Vector< amrex::Real > > q_lsf
Definition: ERF_LargeScaleForcingData.H:439
void get_forcing_time_coeffs(const amrex::Real &time, int &curr, int &next, amrex::Real &coeff_curr, amrex::Real &coeff_next) const
Determine interpolation coefficients to apply tendencies for the given time.
Definition: ERF_LargeScaleForcingData.H:335
std::string lsf_file
Definition: ERF_LargeScaleForcingData.H:411
int nz_lsf
Definition: ERF_LargeScaleForcingData.H:427
amrex::Vector< amrex::Vector< amrex::Real > > q_int_lsf
Definition: ERF_LargeScaleForcingData.H:449
amrex::Vector< amrex::Gpu::DeviceVector< amrex::Real > > v_int_lsf_d
Definition: ERF_LargeScaleForcingData.H:457
amrex::Vector< amrex::Vector< amrex::Real > > w_int_lsf
Definition: ERF_LargeScaleForcingData.H:452
amrex::Vector< amrex::Vector< amrex::Real > > v_lsf
Definition: ERF_LargeScaleForcingData.H:441
amrex::Vector< amrex::Vector< amrex::Real > > u_int_lsf
Definition: ERF_LargeScaleForcingData.H:450
amrex::Vector< amrex::Gpu::DeviceVector< amrex::Real > > t_int_lsf_d
Definition: ERF_LargeScaleForcingData.H:457
amrex::Vector< amrex::Vector< amrex::Real > > t_int_lsf
Definition: ERF_LargeScaleForcingData.H:448
amrex::Vector< amrex::Vector< amrex::Real > > p_lsf
Definition: ERF_LargeScaleForcingData.H:437
amrex::Vector< amrex::Vector< amrex::Real > > w_lsf
Definition: ERF_LargeScaleForcingData.H:442
amrex::Vector< amrex::Gpu::DeviceVector< amrex::Real > > z_int_lsf_d
Definition: ERF_LargeScaleForcingData.H:457
void interp_forcing(const amrex::GeometryData &geom, const amrex::Vector< amrex::Real > &zlevels_stag, const InputSoundingData &sounding)
Interpolate the forcing profiles onto the model grid.
Definition: ERF_LargeScaleForcingData.H:217
amrex::Vector< amrex::Vector< amrex::Real > > v_int_lsf
Definition: ERF_LargeScaleForcingData.H:451
int get_columns_in_str(std::string &str)
Count the number of whitespace-separated columns in a line.
Definition: ERF_LargeScaleForcingData.H:48
amrex::Real tau_lsf
Definition: ERF_LargeScaleForcingData.H:407
amrex::Vector< amrex::Gpu::DeviceVector< amrex::Real > > u_int_lsf_d
Definition: ERF_LargeScaleForcingData.H:457
amrex::Vector< amrex::Vector< amrex::Real > > t_lsf
Definition: ERF_LargeScaleForcingData.H:438
amrex::Vector< amrex::Vector< amrex::Real > > z_int_lsf
Definition: ERF_LargeScaleForcingData.H:447
void host_to_device(int itime)
Copy one interpolated forcing slice from host memory to device memory.
Definition: ERF_LargeScaleForcingData.H:374
amrex::Vector< amrex::Gpu::DeviceVector< amrex::Real > > w_int_lsf_d
Definition: ERF_LargeScaleForcingData.H:457
int num_times
Definition: ERF_LargeScaleForcingData.H:428
LargeScaleForcingData()
Construct large-scale forcing metadata from the ERF input namespace.
Definition: ERF_LargeScaleForcingData.H:36