ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
StationSampler Class Reference

#include <ERF_StationSampler.H>

Collaboration diagram for StationSampler:

Public Member Functions

 StationSampler (const std::string &pp_prefix)
 
bool empty () const
 
amrex::Vector< Station > & stations ()
 
const amrex::Vector< Station > & stations () const
 
int numColumns () const
 
void buildColumns ()
 
void appendRow (amrex::Real time, amrex::Real epoch_time, const amrex::Vector< amrex::Real > &values)
 
void flush ()
 
void setRestart (bool is_restart)
 
void checkRestartFiles () const
 
void setWriteTimestamp (bool write_timestamp, const std::string &datetime_format)
 
int bufferSteps () const
 
bool lowHeightWarned () const
 
void setLowHeightWarned ()
 
bool levelsReported () const
 
void setLevelsReported ()
 
bool stencilsValidFor (const amrex::Vector< amrex::BoxArray > &ba, int finest_level) const
 
void rememberStencilGrids (const amrex::Vector< amrex::BoxArray > &ba, int finest_level)
 

Private Member Functions

void writeHeader (const Station &station, std::ostream &os) const
 
std::string columnSignature (const Station &station) const
 
void checkHeaderMatches (const Station &station, const std::string &filename) const
 

Private Attributes

amrex::Vector< Stationm_stations
 
int m_ncolumns = 0
 
int m_buffer_steps = 100
 
amrex::Vector< amrex::Realm_times
 
amrex::Vector< amrex::Realm_epoch_times
 
amrex::Vector< amrex::Realm_rows
 
amrex::Vector< amrex::BoxArray > m_stencil_grids
 
bool m_is_restart = false
 
bool m_low_height_warned = false
 
bool m_levels_reported = false
 
bool m_write_timestamp = false
 
std::string m_datetime_format
 
std::string m_dir = "Output_Stations"
 
bool m_opened = false
 

Constructor & Destructor Documentation

◆ StationSampler()

StationSampler::StationSampler ( const std::string &  pp_prefix)
explicit
212 {
213  ParmParse pp(pp_prefix);
214 
215  Vector<std::string> names;
216  const int nstation = pp.countval("station_names");
217  if (nstation > 0) {
218  pp.getarr("station_names", names, 0, nstation);
219  }
220 
221  pp.queryAdd("station_buffer_steps", m_buffer_steps);
222  if (m_buffer_steps < 1) { m_buffer_steps = 1; }
223 
224  pp.queryAdd("station_output_dir", m_dir);
225 
226  for (const auto& name : names)
227  {
228  check_station_name(name);
229  for (const auto& earlier : m_stations) {
230  if (earlier.name == name) {
231  amrex::Abort("Station '" + name + "' is named twice in erf.station_names; the "
232  "two would write the same file");
233  }
234  }
235 
236  Station station;
237  station.name = name;
238 
239  ParmParse pps(pp_prefix + "." + name);
240 
241  const int nfield = pps.countval("field");
242  if (nfield <= 0) {
243  Abort(station_key_error(name, "field", "must name at least one variable"));
244  }
245  Vector<std::string> fields;
246  pps.getarr("field", fields, 0, nfield);
247  for (const auto& field : fields) {
248  StationVar var;
249  var.name = field;
250  station.vars.push_back(var);
251  }
252 
253  // A station is placed either in lat/lon or in domain coordinates, never both.
254  const std::string lon_key = pps.contains("long") ? "long" : "lon";
255  const bool has_lat = pps.contains("lat");
256  const bool has_lon = pps.contains("long") || pps.contains("lon");
257  const bool has_x = pps.contains("x");
258  const bool has_y = pps.contains("y");
259 
260  if ((has_lat || has_lon) && (has_x || has_y)) {
261  Abort("Station '" + name + "': specify either lat/long or x/y, not both");
262  }
263 
264  if (has_lat || has_lon) {
265  if (!has_lat || !has_lon) {
266  Abort("Station '" + name + "': lat and long must both be given");
267  }
268  const int nlat = pps.countval("lat");
269  const int nlon = pps.countval(lon_key.c_str());
270  if (nlat != nlon) {
271  Abort("Station '" + name + "': lat has " + std::to_string(nlat) +
272  " values but " + lon_key + " has " + std::to_string(nlon) +
273  "; they are paired, so the counts must match");
274  }
275  Vector<Real> lat, lon;
276  pps.getarr("lat", lat, 0, nlat);
277  pps.getarr(lon_key.c_str(), lon, 0, nlon);
278  for (int i = 0; i < nlat; ++i) {
279  StationLoc loc;
280  loc.use_latlon = true;
281  loc.req_lat = lat[i];
282  loc.req_lon = lon[i];
283  station.locs.push_back(loc);
284  }
285  } else {
286  if (!has_x || !has_y) {
287  Abort("Station '" + name + "': give either lat/long, or x and y in domain coordinates");
288  }
289  const int nx = pps.countval("x");
290  const int ny = pps.countval("y");
291  if (nx != ny) {
292  Abort("Station '" + name + "': x has " + std::to_string(nx) +
293  " values but y has " + std::to_string(ny) +
294  "; they are paired, so the counts must match");
295  }
296  Vector<Real> xv, yv;
297  pps.getarr("x", xv, 0, nx);
298  pps.getarr("y", yv, 0, ny);
299  for (int i = 0; i < nx; ++i) {
300  StationLoc loc;
301  loc.use_latlon = false;
302  loc.x = xv[i];
303  loc.y = yv[i];
304  station.locs.push_back(loc);
305  }
306  }
307 
308  // Heights come in one of two ways, and the difference is only what the
309  // zero of the column is: .height_agl measures from the local terrain,
310  // .height_abs from the bottom of the domain, in the same z the geometry
311  // is given in. Both are read into the one list the rest of the sampler
312  // uses, with a flag saying which zero it is, so nothing downstream has
313  // to ask which key the user typed.
314  const int n_agl = pps.countval("height_agl");
315  const int n_abs = pps.countval("height_abs");
316 
317  if (n_agl > 0 && n_abs > 0) {
318  Abort("Station '" + name + "': give heights as either erf." + name +
319  ".height_agl (above the local terrain) or erf." + name +
320  ".height_abs (in the model's z coordinate), not both");
321  }
322  if (pps.contains("height")) {
323  Abort("Station '" + name + "': erf." + name + ".height is not a key; say "
324  "erf." + name + ".height_agl for metres above the local terrain, or erf." +
325  name + ".height_abs for metres in the model's z coordinate");
326  }
327 
328  station.heights_are_agl = (n_abs == 0);
329  const char* height_key = station.heights_are_agl ? "height_agl" : "height_abs";
330  const int nheight = std::max(n_agl, n_abs);
331  if (nheight > 0) {
332  pps.getarr(height_key, station.heights, 0, nheight);
333  }
334 
335  m_stations.push_back(std::move(station));
336  }
337 }
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
ParmParse pp("prob")
std::string name
Definition: ERF_Plotfile2DCatalog.cpp:101
amrex::Vector< Station > m_stations
Definition: ERF_StationSampler.H:227
int m_buffer_steps
Definition: ERF_StationSampler.H:229
std::string m_dir
Definition: ERF_StationSampler.H:246
Definition: ERF_StationSampler.H:86
amrex::Real req_lat
Definition: ERF_StationSampler.H:90
amrex::Real req_lon
Definition: ERF_StationSampler.H:91
amrex::Real x
Definition: ERF_StationSampler.H:94
bool use_latlon
Definition: ERF_StationSampler.H:87
amrex::Real y
Definition: ERF_StationSampler.H:95
Definition: ERF_StationSampler.H:76
std::string name
Definition: ERF_StationSampler.H:77
Definition: ERF_StationSampler.H:133
amrex::Vector< StationLoc > locs
Definition: ERF_StationSampler.H:136
bool heights_are_agl
Definition: ERF_StationSampler.H:142
std::string name
Definition: ERF_StationSampler.H:134
amrex::Vector< amrex::Real > heights
Definition: ERF_StationSampler.H:141
amrex::Vector< StationVar > vars
Definition: ERF_StationSampler.H:135
Here is the call graph for this function:

Member Function Documentation

◆ appendRow()

void StationSampler::appendRow ( amrex::Real  time,
amrex::Real  epoch_time,
const amrex::Vector< amrex::Real > &  values 
)
366 {
367  AMREX_ALWAYS_ASSERT(static_cast<int>(values.size()) == m_ncolumns);
368 
369  if (ParallelDescriptor::IOProcessor()) {
370  m_times.push_back(time);
371  m_epoch_times.push_back(epoch_time);
372  m_rows.insert(m_rows.end(), values.begin(), values.end());
373 
374  if (static_cast<int>(m_times.size()) >= m_buffer_steps) { flush(); }
375  }
376 }
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
int m_ncolumns
Definition: ERF_StationSampler.H:228
amrex::Vector< amrex::Real > m_epoch_times
Definition: ERF_StationSampler.H:233
amrex::Vector< amrex::Real > m_rows
Definition: ERF_StationSampler.H:234
void flush()
Definition: ERF_StationSampler.cpp:605
amrex::Vector< amrex::Real > m_times
Definition: ERF_StationSampler.H:232
Here is the call graph for this function:

◆ bufferSteps()

int StationSampler::bufferSteps ( ) const
inline
191 { return m_buffer_steps; }

◆ buildColumns()

void StationSampler::buildColumns ( )
346 {
347  m_ncolumns = 0;
348  for (auto& station : m_stations) {
349  station.columns.clear();
350  for (int il = 0; il < static_cast<int>(station.locs.size()); ++il) {
351  station.locs[il].col_begin = static_cast<int>(station.columns.size());
352  for (int iv = 0; iv < static_cast<int>(station.vars.size()); ++iv) {
353  if (station.vars[iv].is_2d) { station.columns.push_back(m_ncolumns++); }
354  }
355  for (int ih = 0; ih < static_cast<int>(station.heights.size()); ++ih) {
356  for (int iv = 0; iv < static_cast<int>(station.vars.size()); ++iv) {
357  if (!station.vars[iv].is_2d) { station.columns.push_back(m_ncolumns++); }
358  }
359  }
360  }
361  }
362 }

◆ checkHeaderMatches()

void StationSampler::checkHeaderMatches ( const Station station,
const std::string &  filename 
) const
private
493 {
494  const std::vector<std::string> found = read_station_header(filename);
495  const std::string found_format = find_format_line(found);
496  const std::string want_format = columnSignature(station);
497 
498  if (found_format == want_format) { return; }
499 
500  if (found_format.empty()) {
501  Abort("Station '" + station.name + "': " + filename + " has no '# format:' line, so it "
502  "was not written by this version of ERF's station output and cannot be appended "
503  "to. Move it aside and start a new series.");
504  }
505 
506  // A tag mismatch is a different thing from a hash mismatch and deserves a
507  // different message: nothing about the run is wrong, the file is just old.
508  const std::string found_tag = found_format.substr(0, found_format.find(' '));
509  if (found_tag != std::string(station_format_tag)) {
510  Abort("Station '" + station.name + "': " + filename + " is in station file format '" +
511  found_tag + "' and this ERF writes '" + station_format_tag + "'. Move it aside "
512  "and start a new series.");
513  }
514 
515  // Same format, different columns. Point at the first column line that
516  // differs, which is the useful thing to say; the signature is what decided.
517  std::ostringstream expected;
518  writeHeader(station, expected);
519  std::vector<std::string> want;
520  {
521  std::istringstream is(expected.str());
522  std::string line;
523  while (std::getline(is, line)) { want.push_back(line); }
524  }
525 
526  // The signature lines differ by construction, so they say nothing a reader
527  // does not already know; what is worth reporting is the first column that
528  // differs.
529  auto without_signature = [](const std::vector<std::string>& lines) {
530  std::vector<std::string> out;
531  for (const auto& line : lines) {
532  if (line.compare(0, 10, "# format: ") != 0) { out.push_back(line); }
533  }
534  return out;
535  };
536  const std::vector<std::string> want_cmp = without_signature(want);
537  const std::vector<std::string> found_cmp = without_signature(found);
538 
539  std::string detail = "\n the difference is not one the header spells out";
540  for (std::size_t i = 0; i < want_cmp.size() || i < found_cmp.size(); ++i) {
541  const bool have_want = (i < want_cmp.size());
542  const bool have_found = (i < found_cmp.size());
543  if (have_want && have_found && want_cmp[i] == found_cmp[i]) { continue; }
544  if (have_want && have_found) {
545  detail = "\n the file says: " + found_cmp[i] +
546  "\n this run would write: " + want_cmp[i];
547  } else if (have_found) {
548  detail = "\n the file has a column this run does not: " + found_cmp[i];
549  } else {
550  detail = "\n this run has a column the file does not: " + want_cmp[i];
551  }
552  break;
553  }
554 
555  Abort("Station '" + station.name + "': " + filename + " describes a different set of "
556  "columns than this restart would write -- a changed field, location, height or "
557  "units list -- so appending to it would produce columns its header does not "
558  "describe. Change the configuration back, or move the file aside and start a "
559  "new series." + detail);
560 }
std::string columnSignature(const Station &station) const
Definition: ERF_StationSampler.cpp:392
void writeHeader(const Station &station, std::ostream &os) const
Definition: ERF_StationSampler.cpp:430

◆ checkRestartFiles()

void StationSampler::checkRestartFiles ( ) const
571 {
572  if (!m_is_restart) { return; }
573  if (!ParallelDescriptor::IOProcessor()) { return; }
574 
575  for (const auto& station : m_stations) {
576  const std::string filename = m_dir + "/" + station.name + ".dat";
577  if (FileExists(filename)) { checkHeaderMatches(station, filename); }
578  }
579 }
bool m_is_restart
Definition: ERF_StationSampler.H:240
void checkHeaderMatches(const Station &station, const std::string &filename) const
Definition: ERF_StationSampler.cpp:492

◆ columnSignature()

std::string StationSampler::columnSignature ( const Station station) const
private
393 {
394  std::ostringstream os;
395  os << std::setprecision(sigprecision);
396  os << station_format_tag << '|' << station.name
397  << "|ts=" << (m_write_timestamp ? 1 : 0);
398 
399  auto describe = [&](const StationVar& var, Real height, bool with_height)
400  {
401  os << '|' << var.name << ':' << var.units;
402  if (with_height) {
403  os << ':' << height << (station.heights_are_agl ? "agl" : "abs");
404  } else {
405  os << ":2d";
406  }
407  if (var.is_missing) { os << ":na"; }
408  };
409 
410  for (const auto& loc : station.locs) {
411  os << "|@";
412  if (loc.use_latlon) {
413  os << "ll=" << loc.req_lat << ',' << loc.req_lon;
414  } else {
415  os << "xy=" << loc.x << ',' << loc.y;
416  }
417  for (const auto& var : station.vars) {
418  if (var.is_2d) { describe(var, Real(0.0), false); }
419  }
420  for (const auto height : station.heights) {
421  for (const auto& var : station.vars) {
422  if (!var.is_2d) { describe(var, height, true); }
423  }
424  }
425  }
426  return std::string(station_format_tag) + " " + signature_hash(os.str());
427 }
Real height
Definition: ERF_InitCustomPert_SquallLine.H:33
amrex::Real Real
Definition: ERF_ShocInterface.H:19
bool m_write_timestamp
Definition: ERF_StationSampler.H:243
std::string units
Definition: ERF_StationSampler.H:78
bool is_missing
Definition: ERF_StationSampler.H:80
bool is_2d
Definition: ERF_StationSampler.H:79

◆ empty()

bool StationSampler::empty ( ) const
inline
160 { return m_stations.empty(); }

◆ flush()

void StationSampler::flush ( )
606 {
607  if (!ParallelDescriptor::IOProcessor()) { return; }
608 
609  const int nrow = static_cast<int>(m_times.size());
610  if (nrow == 0) { return; }
611 
612  if (!m_opened) {
613  if (!UtilCreateDirectory(m_dir, 0755)) { CreateDirectoryFailed(m_dir); }
614  }
615 
616  for (const auto& station : m_stations)
617  {
618  const std::string filename = m_dir + "/" + station.name + ".dat";
619 
620  // A run that is restarting appends to the file it wrote before, so the
621  // series is continuous; the restart is marked in a comment rather than
622  // by starting a new file. A run that is not restarting starts afresh.
623  const bool append = m_opened || (m_is_restart && FileExists(filename));
624 
625  // The first thing a restart writes into an existing file: drop anything
626  // the earlier run wrote past the checkpoint this run restarted from.
627  // That the file is the same series was settled at setup, by
628  // checkRestartFiles.
629  if (append && !m_opened) {
630  const int dropped = truncate_station_file(filename, m_times[0]);
631  if (dropped > 0) {
632  Print() << "Station output: dropped " << dropped << " row(s) at or after t = "
633  << m_times[0] << " from " << filename << ", written by the run before "
634  << "the restart past the checkpoint it restarted from" << std::endl;
635  }
636  }
637 
638  std::ofstream os(filename, append ? (std::ios::out | std::ios::app)
639  : (std::ios::out | std::ios::trunc));
640  if (!os.good()) { FileOpenFailed(filename); }
641 
642  if (!m_opened) {
643  if (append) {
644  os << "# restarted run resumes at t = "
645  << std::setprecision(timeprecision) << m_times[0] << "\n";
646  } else {
647  writeHeader(station, os);
648  }
649  }
650 
651  for (int irow = 0; irow < nrow; ++irow) {
652  os << std::setw(datwidth) << std::setprecision(timeprecision) << m_times[irow];
653  if (m_write_timestamp) {
654  // Every column has to be one whitespace-delimited field for the
655  // file to be readable by column, and the usual datetime formats
656  // put a space between the date and the time.
657  std::string stamp = getTimestamp(static_cast<double>(m_epoch_times[irow]),
658  m_datetime_format, false);
659  std::replace(stamp.begin(), stamp.end(), ' ', 'T');
660  os << std::setw(datwidth) << stamp;
661  }
662  for (const int icol : station.columns) {
663  os << std::setw(datwidth) << std::setprecision(datprecision)
664  << m_rows[static_cast<std::size_t>(irow)*m_ncolumns + icol];
665  }
666  os << "\n";
667  }
668  os.close();
669  }
670 
671  m_opened = true;
672  m_times.clear();
673  m_epoch_times.clear();
674  m_rows.clear();
675 }
AMREX_FORCE_INLINE std::string getTimestamp(const double epoch_real, const std::string &datetime_format, bool add_long_frac=true)
Definition: ERF_EpochTime.H:125
std::string m_datetime_format
Definition: ERF_StationSampler.H:244
bool m_opened
Definition: ERF_StationSampler.H:247
Here is the call graph for this function:

◆ levelsReported()

bool StationSampler::levelsReported ( ) const
inline
200 { return m_levels_reported; }
bool m_levels_reported
Definition: ERF_StationSampler.H:242

◆ lowHeightWarned()

bool StationSampler::lowHeightWarned ( ) const
inline
195 { return m_low_height_warned; }
bool m_low_height_warned
Definition: ERF_StationSampler.H:241

◆ numColumns()

int StationSampler::numColumns ( ) const
inline
165 { return m_ncolumns; }

◆ rememberStencilGrids()

void StationSampler::rememberStencilGrids ( const amrex::Vector< amrex::BoxArray > &  ba,
int  finest_level 
)
599 {
600  m_stencil_grids.resize(finest_level+1);
601  for (int lev = 0; lev <= finest_level; ++lev) { m_stencil_grids[lev] = ba[lev]; }
602 }
amrex::Vector< amrex::BoxArray > m_stencil_grids
Definition: ERF_StationSampler.H:238

◆ setLevelsReported()

void StationSampler::setLevelsReported ( )
inline
201 { m_levels_reported = true; }

◆ setLowHeightWarned()

void StationSampler::setLowHeightWarned ( )
inline
196 { m_low_height_warned = true; }

◆ setRestart()

void StationSampler::setRestart ( bool  is_restart)
inline
179 { m_is_restart = is_restart; }

◆ setWriteTimestamp()

void StationSampler::setWriteTimestamp ( bool  write_timestamp,
const std::string &  datetime_format 
)
inline
186  {
187  m_write_timestamp = write_timestamp;
188  m_datetime_format = datetime_format;
189  }

◆ stations() [1/2]

amrex::Vector<Station>& StationSampler::stations ( )
inline
162 { return m_stations; }

◆ stations() [2/2]

const amrex::Vector<Station>& StationSampler::stations ( ) const
inline
163 { return m_stations; }

◆ stencilsValidFor()

bool StationSampler::stencilsValidFor ( const amrex::Vector< amrex::BoxArray > &  ba,
int  finest_level 
) const
589 {
590  if (static_cast<int>(m_stencil_grids.size()) != finest_level+1) { return false; }
591  for (int lev = 0; lev <= finest_level; ++lev) {
592  if (m_stencil_grids[lev] != ba[lev]) { return false; }
593  }
594  return true;
595 }

◆ writeHeader()

void StationSampler::writeHeader ( const Station station,
std::ostream &  os 
) const
private
431 {
432  // Coordinates are written with enough digits that a reader can tell two
433  // neighbouring stations apart
434  os << std::setprecision(datprecision);
435  os << "# ERF station time series\n";
436  // The one line a restart compares. Everything below it is for the reader.
437  os << "# format: " << columnSignature(station) << "\n";
438  os << "# station: " << station.name << "\n";
439 
440  int col = 1;
441  os << "# column " << col++ << ": time [s]\n";
442  if (m_write_timestamp) {
443  os << "# column " << col++ << ": UTC timestamp (" << m_datetime_format
444  << ", with 'T' where the format has a space, so the column is one field)\n";
445  }
446 
447  const bool station_heights_are_agl = station.heights_are_agl;
448 
449  auto describe = [&](const StationLoc& loc, const StationVar& var, Real height, bool with_height)
450  {
451  os << "# column " << col++ << ": " << var.name;
452  if (!var.units.empty()) { os << " [" << var.units << "]"; }
453  if (loc.use_latlon) {
454  os << " at requested lat=" << loc.req_lat << " long=" << loc.req_lon
455  << " (sampled lat=" << loc.got_lat << " long=" << loc.got_lon << ")";
456  }
457  os << " at x=" << loc.x << " y=" << loc.y;
458  if (with_height) {
459  os << ", height=" << height
460  << (station_heights_are_agl ? " m above local terrain" : " m (absolute, model z)");
461  }
462  if (var.is_missing) { os << " [NOT AVAILABLE in this run: constant " << var.missing_value << "]"; }
463  os << "\n";
464  };
465 
466  for (const auto& loc : station.locs) {
467  for (const auto& var : station.vars) {
468  if (var.is_2d) { describe(loc, var, Real(0.0), false); }
469  }
470  for (const auto height : station.heights) {
471  for (const auto& var : station.vars) {
472  if (!var.is_2d) { describe(loc, var, height, true); }
473  }
474  }
475  }
476  os << "#\n";
477  os << "# Values are bilinearly interpolated in the horizontal and linearly\n";
478  os << "# interpolated in the vertical, from the finest level that covers the\n";
479  os << "# interpolation stencil.\n";
480 }
amrex::Real got_lat
Definition: ERF_StationSampler.H:99
amrex::Real got_lon
Definition: ERF_StationSampler.H:100

Member Data Documentation

◆ m_buffer_steps

int StationSampler::m_buffer_steps = 100
private

Referenced by bufferSteps().

◆ m_datetime_format

std::string StationSampler::m_datetime_format
private

Referenced by setWriteTimestamp().

◆ m_dir

std::string StationSampler::m_dir = "Output_Stations"
private

◆ m_epoch_times

amrex::Vector<amrex::Real> StationSampler::m_epoch_times
private

◆ m_is_restart

bool StationSampler::m_is_restart = false
private

Referenced by setRestart().

◆ m_levels_reported

bool StationSampler::m_levels_reported = false
private

◆ m_low_height_warned

bool StationSampler::m_low_height_warned = false
private

◆ m_ncolumns

int StationSampler::m_ncolumns = 0
private

Referenced by numColumns().

◆ m_opened

bool StationSampler::m_opened = false
private

◆ m_rows

amrex::Vector<amrex::Real> StationSampler::m_rows
private

◆ m_stations

amrex::Vector<Station> StationSampler::m_stations
private

Referenced by empty(), and stations().

◆ m_stencil_grids

amrex::Vector<amrex::BoxArray> StationSampler::m_stencil_grids
private

◆ m_times

amrex::Vector<amrex::Real> StationSampler::m_times
private

◆ m_write_timestamp

bool StationSampler::m_write_timestamp = false
private

Referenced by setWriteTimestamp().


The documentation for this class was generated from the following files: