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< Station > m_stations
 
int m_ncolumns = 0
 
int m_buffer_steps = 100
 
amrex::Vector< amrex::Real > m_times
 
amrex::Vector< amrex::Real > m_epoch_times
 
amrex::Vector< amrex::Real > m_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
223 {
224  ParmParse pp(pp_prefix);
225 
226  Vector<std::string> names;
227  const int nstation = pp.countval("station_names");
228  if (nstation > 0) {
229  pp.getarr("station_names", names, 0, nstation);
230  }
231 
232  pp.queryAdd("station_buffer_steps", m_buffer_steps);
233  if (m_buffer_steps < 1) { m_buffer_steps = 1; }
234 
235  pp.queryAdd("station_output_dir", m_dir);
236 
237  for (const auto& name : names)
238  {
239  check_station_name(name);
240  for (const auto& earlier : m_stations) {
241  if (earlier.name == name) {
242  amrex::Abort("Station '" + name + "' is named twice in erf.station_names; the "
243  "two would write the same file");
244  }
245  }
246 
247  Station station;
248  station.name = name;
249 
250  ParmParse pps(pp_prefix + "." + name);
251 
252  const int nfield = pps.countval("field");
253  if (nfield <= 0) {
254  Abort(station_key_error(name, "field", "must name at least one variable"));
255  }
256  Vector<std::string> fields;
257  pps.getarr("field", fields, 0, nfield);
258  for (const auto& field : fields) {
259  StationVar var;
260  var.name = field;
261  station.vars.push_back(var);
262  }
263 
264  // A station is placed either in lat/lon or in domain coordinates, never both.
265  const std::string lon_key = pps.contains("long") ? "long" : "lon";
266  const bool has_lat = pps.contains("lat");
267  const bool has_lon = pps.contains("long") || pps.contains("lon");
268  const bool has_x = pps.contains("x");
269  const bool has_y = pps.contains("y");
270 
271  if ((has_lat || has_lon) && (has_x || has_y)) {
272  Abort("Station '" + name + "': specify either lat/long or x/y, not both");
273  }
274 
275  if (has_lat || has_lon) {
276  if (!has_lat || !has_lon) {
277  Abort("Station '" + name + "': lat and long must both be given");
278  }
279  const int nlat = pps.countval("lat");
280  const int nlon = pps.countval(lon_key.c_str());
281  if (nlat != nlon) {
282  Abort("Station '" + name + "': lat has " + std::to_string(nlat) +
283  " values but " + lon_key + " has " + std::to_string(nlon) +
284  "; they are paired, so the counts must match");
285  }
286  Vector<Real> lat, lon;
287  pps.getarr("lat", lat, 0, nlat);
288  pps.getarr(lon_key.c_str(), lon, 0, nlon);
289  for (int i = 0; i < nlat; ++i) {
290  StationLoc loc;
291  loc.use_latlon = true;
292  loc.req_lat = lat[i];
293  loc.req_lon = lon[i];
294  station.locs.push_back(loc);
295  }
296  } else {
297  if (!has_x || !has_y) {
298  Abort("Station '" + name + "': give either lat/long, or x and y in domain coordinates");
299  }
300  const int nx = pps.countval("x");
301  const int ny = pps.countval("y");
302  if (nx != ny) {
303  Abort("Station '" + name + "': x has " + std::to_string(nx) +
304  " values but y has " + std::to_string(ny) +
305  "; they are paired, so the counts must match");
306  }
307  Vector<Real> xv, yv;
308  pps.getarr("x", xv, 0, nx);
309  pps.getarr("y", yv, 0, ny);
310  for (int i = 0; i < nx; ++i) {
311  StationLoc loc;
312  loc.use_latlon = false;
313  loc.x = xv[i];
314  loc.y = yv[i];
315  station.locs.push_back(loc);
316  }
317  }
318 
319  // Heights come in one of two ways, and the difference is only what the
320  // zero of the column is: .height_agl measures from the local terrain,
321  // .height_abs from the bottom of the domain, in the same z the geometry
322  // is given in. Both are read into the one list the rest of the sampler
323  // uses, with a flag saying which zero it is, so nothing downstream has
324  // to ask which key the user typed.
325  const int n_agl = pps.countval("height_agl");
326  const int n_abs = pps.countval("height_abs");
327 
328  if (n_agl > 0 && n_abs > 0) {
329  Abort("Station '" + name + "': give heights as either erf." + name +
330  ".height_agl (above the local terrain) or erf." + name +
331  ".height_abs (in the model's z coordinate), not both");
332  }
333  if (pps.contains("height")) {
334  Abort("Station '" + name + "': erf." + name + ".height is not a key; say "
335  "erf." + name + ".height_agl for metres above the local terrain, or erf." +
336  name + ".height_abs for metres in the model's z coordinate");
337  }
338 
339  station.heights_are_agl = (n_abs == 0);
340  const char* height_key = station.heights_are_agl ? "height_agl" : "height_abs";
341  const int nheight = std::max(n_agl, n_abs);
342  if (nheight > 0) {
343  pps.getarr(height_key, station.heights, 0, nheight);
344  }
345 
346  m_stations.push_back(std::move(station));
347  }
348 }
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:105
amrex::Vector< Station > m_stations
Definition: ERF_StationSampler.H:203
int m_buffer_steps
Definition: ERF_StationSampler.H:205
std::string m_dir
Definition: ERF_StationSampler.H:222
Definition: ERF_StationSampler.H:62
amrex::Real req_lat
Definition: ERF_StationSampler.H:66
amrex::Real req_lon
Definition: ERF_StationSampler.H:67
amrex::Real x
Definition: ERF_StationSampler.H:70
bool use_latlon
Definition: ERF_StationSampler.H:63
amrex::Real y
Definition: ERF_StationSampler.H:71
Definition: ERF_StationSampler.H:52
std::string name
Definition: ERF_StationSampler.H:53
Definition: ERF_StationSampler.H:109
amrex::Vector< StationLoc > locs
Definition: ERF_StationSampler.H:112
bool heights_are_agl
Definition: ERF_StationSampler.H:118
std::string name
Definition: ERF_StationSampler.H:110
amrex::Vector< amrex::Real > heights
Definition: ERF_StationSampler.H:117
amrex::Vector< StationVar > vars
Definition: ERF_StationSampler.H:111
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 
)
377 {
378  AMREX_ALWAYS_ASSERT(static_cast<int>(values.size()) == m_ncolumns);
379 
380  if (ParallelDescriptor::IOProcessor()) {
381  m_times.push_back(time);
382  m_epoch_times.push_back(epoch_time);
383  m_rows.insert(m_rows.end(), values.begin(), values.end());
384 
385  if (static_cast<int>(m_times.size()) >= m_buffer_steps) { flush(); }
386  }
387 }
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
int m_ncolumns
Definition: ERF_StationSampler.H:204
amrex::Vector< amrex::Real > m_epoch_times
Definition: ERF_StationSampler.H:209
amrex::Vector< amrex::Real > m_rows
Definition: ERF_StationSampler.H:210
void flush()
Definition: ERF_StationSampler.cpp:616
amrex::Vector< amrex::Real > m_times
Definition: ERF_StationSampler.H:208
Here is the call graph for this function:

◆ bufferSteps()

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

◆ buildColumns()

void StationSampler::buildColumns ( )
357 {
358  m_ncolumns = 0;
359  for (auto& station : m_stations) {
360  station.columns.clear();
361  for (int il = 0; il < static_cast<int>(station.locs.size()); ++il) {
362  station.locs[il].col_begin = static_cast<int>(station.columns.size());
363  for (int iv = 0; iv < static_cast<int>(station.vars.size()); ++iv) {
364  if (station.vars[iv].is_2d) { station.columns.push_back(m_ncolumns++); }
365  }
366  for (int ih = 0; ih < static_cast<int>(station.heights.size()); ++ih) {
367  for (int iv = 0; iv < static_cast<int>(station.vars.size()); ++iv) {
368  if (!station.vars[iv].is_2d) { station.columns.push_back(m_ncolumns++); }
369  }
370  }
371  }
372  }
373 }

◆ checkHeaderMatches()

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

◆ checkRestartFiles()

void StationSampler::checkRestartFiles ( ) const
582 {
583  if (!m_is_restart) { return; }
584  if (!ParallelDescriptor::IOProcessor()) { return; }
585 
586  for (const auto& station : m_stations) {
587  const std::string filename = m_dir + "/" + station.name + ".dat";
588  if (FileExists(filename)) { checkHeaderMatches(station, filename); }
589  }
590 }
bool m_is_restart
Definition: ERF_StationSampler.H:216
void checkHeaderMatches(const Station &station, const std::string &filename) const
Definition: ERF_StationSampler.cpp:503

◆ columnSignature()

std::string StationSampler::columnSignature ( const Station &  station) const
private
404 {
405  std::ostringstream os;
406  os << std::setprecision(sigprecision);
407  os << station_format_tag << '|' << station.name
408  << "|ts=" << (m_write_timestamp ? 1 : 0);
409 
410  auto describe = [&](const StationVar& var, Real height, bool with_height)
411  {
412  os << '|' << var.name << ':' << var.units;
413  if (with_height) {
414  os << ':' << height << (station.heights_are_agl ? "agl" : "abs");
415  } else {
416  os << ":2d";
417  }
418  if (var.is_missing) { os << ":na"; }
419  };
420 
421  for (const auto& loc : station.locs) {
422  os << "|@";
423  if (loc.use_latlon) {
424  os << "ll=" << loc.req_lat << ',' << loc.req_lon;
425  } else {
426  os << "xy=" << loc.x << ',' << loc.y;
427  }
428  for (const auto& var : station.vars) {
429  if (var.is_2d) { describe(var, Real(0.0), false); }
430  }
431  for (const auto height : station.heights) {
432  for (const auto& var : station.vars) {
433  if (!var.is_2d) { describe(var, height, true); }
434  }
435  }
436  }
437  return std::string(station_format_tag) + " " + signature_hash(os.str());
438 }
Real height
Definition: ERF_InitCustomPert_SquallLine.H:33
amrex::Real Real
Definition: ERF_ShocInterface.H:19
bool m_write_timestamp
Definition: ERF_StationSampler.H:219
std::string units
Definition: ERF_StationSampler.H:54
bool is_missing
Definition: ERF_StationSampler.H:56
bool is_2d
Definition: ERF_StationSampler.H:55

◆ empty()

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

◆ flush()

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

◆ levelsReported()

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

◆ lowHeightWarned()

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

◆ numColumns()

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

◆ rememberStencilGrids()

void StationSampler::rememberStencilGrids ( const amrex::Vector< amrex::BoxArray > &  ba,
int  finest_level 
)
610 {
611  m_stencil_grids.resize(finest_level+1);
612  for (int lev = 0; lev <= finest_level; ++lev) { m_stencil_grids[lev] = ba[lev]; }
613 }
amrex::Vector< amrex::BoxArray > m_stencil_grids
Definition: ERF_StationSampler.H:214

◆ setLevelsReported()

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

◆ setLowHeightWarned()

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

◆ setRestart()

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

◆ setWriteTimestamp()

void StationSampler::setWriteTimestamp ( bool  write_timestamp,
const std::string &  datetime_format 
)
inline
162  {
163  m_write_timestamp = write_timestamp;
164  m_datetime_format = datetime_format;
165  }

◆ stations() [1/2]

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

◆ stations() [2/2]

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

◆ stencilsValidFor()

bool StationSampler::stencilsValidFor ( const amrex::Vector< amrex::BoxArray > &  ba,
int  finest_level 
) const
600 {
601  if (static_cast<int>(m_stencil_grids.size()) != finest_level+1) { return false; }
602  for (int lev = 0; lev <= finest_level; ++lev) {
603  if (m_stencil_grids[lev] != ba[lev]) { return false; }
604  }
605  return true;
606 }

◆ writeHeader()

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

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: