ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
main.cpp File Reference
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <AMReX.H>
#include <AMReX_BLProfiler.H>
#include <AMReX_ParallelDescriptor.H>
#include "ERF.H"
#include "ERF_InputsName.H"
Include dependency graph for main.cpp:

Functions

void add_par ()
 
static void erf_flush_fortran_units ()
 
void install_exit_trap ()
 
int main (int argc, char *argv[])
 

Variables

std::string inputs_name
 

Function Documentation

◆ add_par()

void add_par ( )

Function to set the refine_grid_layout flags to (1,1,0) by default since the ERF default is different from the amrex default (1,1,1) Also set max_grid_size to very large since the only reason for chopping grids is if Nprocs > Ngrids, and set no_box_split_dir to 2 so that grids are never decomposed in the vertical direction

35  {
36  ParmParse pp("amr");
37  ParmParse pp_erf("erf");
38 
39  // Set the refine_grid_layout flags to (1,1,0) by default
40  pp.add("refine_grid_layout_x",1);
41  pp.add("refine_grid_layout_y",1);
42  pp.add("refine_grid_layout_z",0);
43 
44  // no_box_split_dir tells amrex that the grids must never be split in that
45  // direction; max_grid_size and refine_grid_layout are then ignored there.
46  // ERF sets this to 2 (the z-direction) by default so that no grid is ever
47  // decomposed in the vertical -- no two grids then share a face normal to z, so
48  // no column is chopped between them, which is required, e.g., by the implicit
49  // vertical diffusion solves and by the implicit acoustic substepping. (Several
50  // grids may still sit over the same column, as long as they do not touch; what
51  // those solves cannot do is split one contiguous column at a box seam.) Setting
52  // amr.no_box_split_dir = -1 in the inputs file restores the amrex behavior of
53  // allowing grids to be chopped in every direction; ERF::ReadParameters rejects
54  // that if any level uses erf.substepping_type = Implicit.
55  int no_box_split_dir = 2;
56  pp.queryAdd("no_box_split_dir",no_box_split_dir);
57 
58  // n_proper is the minimum number of coarse cells between coarse-fine boundaries
59  // between levels (ell and ell+1) and levels (ell-1 and ell). We want this to be
60  // greater than or equal to the stencil width (a function of spatial order) divided by
61  // ref_ratio (which can be 2,3 or 4). This ensures that fillpatch at level (ell)
62  // does not need to reach beyond level (ell-1). Here to be conservative we set this to 2
63  // (rather than the amrex default of 1).
64  pp.add("n_proper",2);
65 
66  int max_grid_size = 2048;
67  pp.queryAdd("max_grid_size",max_grid_size);
68 
69  // This will set the default value of blocking_factor to be 1, but will allow
70  // the user to override it in the inputs file or on command line
71  int blocking_factor = 1;
72  pp.queryAdd("blocking_factor",blocking_factor);
73 
74  int n_error_buf = 0;
75  pp.queryAdd("n_error_buf",n_error_buf);
76 
77  amrex::Vector<int> legacy_n_cell;
78  amrex::Vector<int> erf_n_cell;
79  const bool has_legacy_n_cell = pp.queryarr("n_cell", legacy_n_cell);
80  const bool has_erf_n_cell = pp_erf.queryarr("n_cell", erf_n_cell);
81  if (has_erf_n_cell && has_legacy_n_cell) {
82  amrex::Abort("erf.n_cell and amr.n_cell are both specified. Please use only one!");
83  }
84  if (has_erf_n_cell) {
85  pp.addarr("n_cell", erf_n_cell);
86  }
87 
88  ParmParse pp_geometry("geometry");
89  amrex::Vector<amrex::Real> legacy_prob_lo;
90  amrex::Vector<amrex::Real> legacy_prob_hi;
91  amrex::Vector<amrex::Real> legacy_prob_extent;
92  amrex::Vector<int> legacy_is_periodic;
93  amrex::Vector<amrex::Real> erf_prob_lo;
94  amrex::Vector<amrex::Real> erf_prob_hi;
95  amrex::Vector<amrex::Real> erf_prob_extent;
96  amrex::Vector<int> erf_is_periodic;
97 
98  const bool has_legacy_prob_lo = pp_geometry.queryarr("prob_lo", legacy_prob_lo);
99  const bool has_legacy_prob_hi = pp_geometry.queryarr("prob_hi", legacy_prob_hi);
100  const bool has_legacy_prob_extent = pp_geometry.queryarr("prob_extent", legacy_prob_extent);
101  const bool has_legacy_is_periodic = pp_geometry.queryarr("is_periodic", legacy_is_periodic);
102 
103  const bool has_erf_prob_lo = pp_erf.queryarr("prob_lo", erf_prob_lo);
104  const bool has_erf_prob_hi = pp_erf.queryarr("prob_hi", erf_prob_hi);
105  const bool has_erf_prob_extent = pp_erf.queryarr("prob_extent", erf_prob_extent);
106  const bool has_erf_is_periodic = pp_erf.queryarr("is_periodic", erf_is_periodic);
107 
108  if (has_erf_prob_lo && has_legacy_prob_lo) {
109  amrex::Abort("erf.prob_lo and geometry.prob_lo are both specified. Please use only one!");
110  }
111  if ((has_erf_prob_hi || has_erf_prob_extent) && !has_erf_prob_lo) {
112  amrex::Abort("ERF prefixed geometry requires erf.prob_lo together with erf.prob_hi or erf.prob_extent.");
113  }
114  if (has_erf_is_periodic && has_legacy_is_periodic) {
115  amrex::Abort("erf.is_periodic and geometry.is_periodic are both specified. Please use only one!");
116  }
117  if (has_erf_prob_hi && has_legacy_prob_hi) {
118  amrex::Abort("erf.prob_hi and geometry.prob_hi are both specified. Please use only one!");
119  }
120  if (has_erf_prob_extent && has_legacy_prob_extent) {
121  amrex::Abort("erf.prob_extent and geometry.prob_extent are both specified. Please use only one!");
122  }
123  if (has_erf_prob_hi || has_erf_prob_extent) {
124  if (has_legacy_prob_hi || has_legacy_prob_extent) {
125  amrex::Abort("ERF prefixed geometry and legacy geometry.prob_hi/prob_extent are both specified. Please use only one form.");
126  }
127  }
128  if (has_erf_prob_hi && has_erf_prob_extent) {
129  amrex::Vector<amrex::Real> derived_prob_hi(erf_prob_extent.size(), 0.0);
130  for (int i = 0; i < static_cast<int>(erf_prob_extent.size()); ++i) {
131  derived_prob_hi[i] = (i < static_cast<int>(erf_prob_lo.size()) ? erf_prob_lo[i] : 0.0) + erf_prob_extent[i];
132  }
134  derived_prob_hi.size() == erf_prob_hi.size(),
135  "erf.prob_hi and erf.prob_extent have inconsistent lengths.");
136  // Allow for rounding in prob_lo + prob_extent, relative to the inputs' magnitude
137  for (int i = 0; i < static_cast<int>(erf_prob_hi.size()); ++i) {
138  const amrex::Real lo = (i < static_cast<int>(erf_prob_lo.size()) ? erf_prob_lo[i] : 0.0);
139  const amrex::Real scale = std::max({std::abs(lo), std::abs(erf_prob_extent[i]), std::abs(erf_prob_hi[i])});
141  if (std::abs(derived_prob_hi[i] - erf_prob_hi[i]) > tol) {
142  amrex::Abort("erf.prob_hi and erf.prob_extent are both specified but inconsistent.");
143  }
144  }
145  }
146 
147  if (has_erf_prob_lo) {
148  pp_geometry.addarr("prob_lo", erf_prob_lo);
149  }
150  if (has_erf_prob_hi) {
151  pp_geometry.addarr("prob_hi", erf_prob_hi);
152  } else if (has_erf_prob_extent) {
153  amrex::Vector<amrex::Real> derived_prob_hi(erf_prob_extent.size(), 0.0);
154  for (int i = 0; i < static_cast<int>(erf_prob_extent.size()); ++i) {
155  derived_prob_hi[i] = (i < static_cast<int>(erf_prob_lo.size()) ? erf_prob_lo[i] : 0.0) + erf_prob_extent[i];
156  }
157  pp_geometry.addarr("prob_hi", derived_prob_hi);
158  }
159  if (has_erf_is_periodic) {
160  pp_geometry.addarr("is_periodic", erf_is_periodic);
161  }
162 }
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
ParmParse pp("prob")
amrex::Real Real
Definition: ERF_ShocInterface.H:19
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12

Referenced by main().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ erf_flush_fortran_units()

static void erf_flush_fortran_units ( )
static
168 {}

Referenced by install_exit_trap().

Here is the caller graph for this function:

◆ install_exit_trap()

void install_exit_trap ( )

Make a run that leaves through exit() before amrex::Finalize report failure.

A normal run calls amrex::Finalize before main returns, so a process that reaches exit() while AMReX is still initialised has stopped part-way – with no final plotfile or checkpoint – whatever status it asked for. The ways in are a Fortran STOP in a coupled library (Noah-MP has 27, all exit status 0) and the exit() calls left in ERF itself, some of which also pass 0. Run under Open MPI's launcher such a run is still flagged, because the rank never calls MPI_Finalize; run directly, or under a launcher that does not enforce that, it reports success. Every return from main after amrex::Initialize goes through amrex::Finalize, so this never fires on a normal exit.

amrex::Abort ends in MPI_Abort, or std::_Exit in a serial build. Open MPI's MPI_Abort kills the job without exit(), but MPICH's (and Cray MPICH's) ends in exit(errorcode), so there the trap runs after an abort as well: the run is still reported as a failure, with status 1 in place of the abort's code, and the message below allows for it.

A Fortran STOP or an exit() fires on the one rank that hit the problem; ending only that rank would leave the others blocked in their next collective until the launcher notices (Open MPI does; srun without –kill-on-bad-exit and some PMI setups may not). So the trap calls MPI_Abort to tear the whole job down, guarded so that an MPI whose own MPI_Abort ends in exit() (MPICH, Cray MPICH) cannot re-enter it, and skipped when MPI is not initialised or already finalised.

std::_Exit is the only way to change the status from here, and it skips the rest of the exit sequence, which is what would flush the output buffers. Standard output is fully buffered when it is redirected to a file, so flush everything first – C++ streams, C stdio and the Fortran units – or the lines that explain the stop are lost.

201 {
202  std::atexit([]() {
203  if (amrex::Initialized()) {
204  std::cout.flush();
205  std::clog.flush();
206  std::cerr.flush();
208  std::fflush(nullptr);
209  std::fputs("ERF: exit() was called while AMReX was still initialised, so the run "
210  "stopped before finishing; the reason should be printed above (a "
211  "Fortran STOP in a coupled library such as Noah-MP, an error exit in "
212  "ERF, or an amrex::Abort under an MPI whose MPI_Abort calls exit()). "
213  "Reporting failure whatever exit status was requested.\n",
214  stderr);
215  std::fflush(stderr);
216 #ifdef AMREX_USE_MPI
217  // atexit handlers run single-threaded, so a plain flag is enough of a
218  // reentrancy guard for the MPI_Abort-calls-exit() implementations.
219  static bool aborting = false;
220  int mpi_up = 0, mpi_done = 0;
221  MPI_Initialized(&mpi_up);
222  MPI_Finalized(&mpi_done);
223  if (mpi_up && !mpi_done && !aborting) {
224  aborting = true;
225  MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
226  }
227 #endif
228  std::_Exit(EXIT_FAILURE);
229  }
230  });
231 }
static void erf_flush_fortran_units()
Definition: main.cpp:168

Referenced by main().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int main ( int  argc,
char *  argv[] 
)

Main driver – creates the ERF object, calls ERF.InitData() and ERF.Evolve()

237 {
238 
239 // For the early exits below, which all come before amrex::Initialize (and, with
240 // WW3 coupling, before amrex::MPMD::Initialize): MPI is ours to finalize.
241 auto finalize_mpi_and_return = [](int code) {
242 #ifdef AMREX_USE_MPI
243  MPI_Finalize();
244 #endif
245  return code;
246 };
247 
248 #if defined(AMREX_MPI_THREAD_MULTIPLE)
249  int requested = MPI_THREAD_MULTIPLE;
250  int provided = -1;
251  MPI_Init_thread(&argc, &argv, requested, &provided);
252 #elif defined(AMREX_USE_MPI)
253  MPI_Init(&argc, &argv);
254 #endif
255 
256  if (argc < 2) {
257  // Print usage and exit with error code if no input file was provided.
258  ERF::print_usage(MPI_COMM_WORLD, std::cout);
260  MPI_COMM_WORLD, "No input file provided. Exiting!!");
261  return finalize_mpi_and_return(1);
262  }
263 
264  // Look for "-h" or "--help" flag and print usage
265  for (auto i = 1; i < argc; i++) {
266  const std::string param(argv[i]);
267  if ((param == "--help") || (param == "-h") || (param == "--usage")) {
268  ERF::print_banner(MPI_COMM_WORLD, std::cout);
269  ERF::print_usage(MPI_COMM_WORLD, std::cout);
270  return finalize_mpi_and_return(0);
271  }
272  }
273 
274  if (argc >= 2) {
275  for (auto i = 1; i < argc; i++) {
276  if (std::string(argv[i]) == "--describe") {
277  ERF::writeBuildInfo(std::cout);
278  return finalize_mpi_and_return(0);
279  }
280  }
281  }
282 
283  if (!strchr(argv[1], '=') && !amrex::FileSystem::Exists(std::string(argv[1])))
284  {
285  // Print usage and exit with error code if we cannot find the input file
286  ERF::print_usage(MPI_COMM_WORLD, std::cout);
288  MPI_COMM_WORLD, "Input file does not exist = " +
289  std::string(argv[1]) + ". Exiting!!");
290  return finalize_mpi_and_return(1);
291  }
292 
293 #ifdef ERF_USE_WW3_COUPLING
294  MPI_Comm comm = amrex::MPMD::Initialize(argc, argv);
295  amrex::Initialize(argc,argv,true,comm,add_par);
296 #else
297  amrex::Initialize(argc,argv,true,MPI_COMM_WORLD,add_par);
298 #endif
299 
301 
302  // Only when argv[1] is actually an inputs file (same test the surrounding code uses).
303  if (argc > 1 && std::string(argv[1]).find('=') == std::string::npos) {
304  CheckForDuplicateInputs(argv[1]);
305  }
306 
307 #ifdef ERF_USE_KOKKOS
308  // Initialize kokkos
309  if (!Kokkos::is_initialized()) {
310  Kokkos::initialize(Kokkos::InitializationSettings()
311  .set_device_id(amrex::Gpu::Device::deviceId()));
312  }
313 #endif
314 
315  // Save the inputs file name for later.
316  if (!strchr(argv[1], '=')) {
317  inputs_name = argv[1];
318  }
319 
320  // timer for profiling
321  BL_PROFILE_VAR("main()", pmain);
322 
323  // wallclock time
324  const double strt_total = amrex::second();
325 
326  {
327  // constructor - reads in parameters from inputs file
328  // - sizes multilevel arrays and data structures
329  ERF erf;
330 
331  // initialize AMR data
332  erf.InitData();
333 
334  // advance solution to final time
335  erf.Evolve();
336 
337  // wallclock time
338  double end_total = amrex::second() - strt_total;
339 
340  // print wallclock time
341  ParallelDescriptor::ReduceRealMax(end_total ,ParallelDescriptor::IOProcessorNumber());
342  if (erf.Verbose()) {
343  amrex::Print() << "\nTotal Time: " << end_total << '\n';
344  }
345  }
346 
347  // destroy timer for profiling
348  BL_PROFILE_VAR_STOP(pmain);
349 #ifdef ERF_USE_WW3_COUPLING
350  MPI_Barrier(MPI_COMM_WORLD);
351 #endif
352 
353 #ifdef ERF_USE_KOKKOS
354  Kokkos::finalize();
355 #endif
356 
357  amrex::Finalize();
358 #ifdef AMREX_USE_MPI
359 #ifdef ERF_USE_WW3_COUPLING
360  // MPI was initialized above, before amrex::MPMD::Initialize, so MPMD::Finalize
361  // frees its communicator but leaves MPI_Finalize to us.
362  amrex::MPMD::Finalize();
363 #endif
364  MPI_Finalize();
365 #endif
366 }
void CheckForDuplicateInputs(const std::string &inputs_file)
Definition: ERF_InputsName.H:272
void Evolve()
Definition: ERF.cpp:152
static void print_banner(MPI_Comm, std::ostream &)
Definition: ERF_ConsoleIO.cpp:65
void InitData()
Definition: ERF.cpp:935
static void print_error(MPI_Comm, const std::string &msg)
Definition: ERF_ConsoleIO.cpp:48
static void writeBuildInfo(std::ostream &os)
Definition: ERF_WriteJobInfo.cpp:183
static void print_usage(MPI_Comm, std::ostream &)
Definition: ERF_ConsoleIO.cpp:31
void add_par()
Definition: main.cpp:35
std::string inputs_name
Definition: main.cpp:24
void install_exit_trap()
Definition: main.cpp:200
Definition: ERF_InterpolationUtils.H:15
Here is the call graph for this function:

Variable Documentation

◆ inputs_name

std::string inputs_name

Referenced by main(), and ERF::writeJobInfo().