ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_FirePrerequisites.cpp File Reference
#include <ERF_FirePrerequisites.H>
#include <ERF.H>
#include <ERF_SurfaceLayer.H>
#include <ERF_IndexDefines.H>
#include <AMReX_ParmParse.H>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
Include dependency graph for ERF_FirePrerequisites.cpp:

Functions

int fire_anchor_level (int anchor_level, int finest_level)
 The AMR level the fire grid refines: erf.fire.anchor_level, or the finest level when it is -1 (the default). More...
 
void verify_fire_precip_source (const std::string &engine_name, const SurfacePrecipAccumulationSources &sources)
 Start-up check for an engine that reads the rain of every column (FireEngine::wants_surface_precip): the microphysics scheme must provide surface precipitation accumulators. More...
 
void verify_fire_prerequisites (const ERF &erf, int lev, const SurfaceLayer *surface_layer, int grid_ratio, bool needs_surface_layer)
 Verify the ERF configuration the fire coupling needs. More...
 

Function Documentation

◆ fire_anchor_level()

int fire_anchor_level ( int  anchor_level,
int  finest_level 
)

The AMR level the fire grid refines: erf.fire.anchor_level, or the finest level when it is -1 (the default).

Aborts, naming the input, when the level is above the finest level.

Parameters
[in]anchor_levelerf.fire.anchor_level (-1 = the finest level)
[in]finest_levelFinest level of the run at start-up
53 {
54  const int lev = (anchor_level < 0) ? finest_level : anchor_level;
55  if (lev > finest_level) {
56  Abort("[FIRE] erf.fire.anchor_level = " + std::to_string(anchor_level)
57  + " is above the finest level of this run (" + std::to_string(finest_level)
58  + "). Set it to a level from 0 to " + std::to_string(finest_level)
59  + ", or leave it unset for the finest level.");
60  }
61  return lev;
62 }

Referenced by ERF::InitData_post().

Here is the caller graph for this function:

◆ verify_fire_precip_source()

void verify_fire_precip_source ( const std::string &  engine_name,
const SurfacePrecipAccumulationSources &  sources 
)

Start-up check for an engine that reads the rain of every column (FireEngine::wants_surface_precip): the microphysics scheme must provide surface precipitation accumulators.

Aborts, naming the engine and erf.moisture_model, when the sources are empty (no moisture model, Kessler_NoRain, saturation adjustment only, ...). Called after the microphysics of the fire grid's level is initialised.

Parameters
[in]engine_nameThe engine's name (FireEngine::name)
[in]sourcesMicrophysics::Get_Surface_Precip_Accumulation_Ptrs(lev)
66 {
67  if (surface_precip_has_any_source(sources)) { return; }
68  std::string moisture_model = "None";
69  ParmParse pp_erf("erf");
70  pp_erf.query("moisture_model", moisture_model);
71  Abort("[FIRE] The fire engine \"" + engine_name + "\" takes the rain of every column from the "
72  "microphysics surface precipitation accumulators, but erf.moisture_model = \"" + moisture_model
73  + "\" provides none (Kessler, SAM, Morrison, WSM6 and WDM6 with precipitation do; Kessler_NoRain, "
74  "SatAdj, MoistNoCondensation and None do not). Choose one of those, or set the engine's inputs so "
75  "it does not read the rain.");
76 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool surface_precip_has_any_source(const SurfacePrecipAccumulationSources &src) noexcept
Definition: ERF_SurfacePrecipitation.H:61

Referenced by ERF::InitData_post().

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

◆ verify_fire_prerequisites()

void verify_fire_prerequisites ( const ERF &  erf,
int  lev,
const SurfaceLayer *  surface_layer,
int  grid_ratio,
bool  needs_surface_layer = true 
)

Verify the ERF configuration the fire coupling needs.

Called once at startup from FireCoupler::initialize() after the SurfaceLayer is ready, before the engine's own checks. Aborts with clear diagnostic messages if any requirement is not met.

Parameters
[in]erfERF instance (for grids, dmap, Geom access)
[in]levAMR level the fire grid refines (fire_anchor_level)
[in]surface_layerPointer to initialized SurfaceLayer
[in]grid_ratioerf.fire.grid_ratio, fire cells per atmospheric cell in x and y

Checks performed (in order):

  1. SurfaceLayer pointer is not null (zlo.type = surface_layer), unless needs_surface_layer is false (FireEngine::needs_surface_layer)
  2. 0 <= lev <= finest level
  3. On a level above 0: static refinement (erf.regrid_int <= 0)
  4. Every box of the level spans its full height (no z-decomposition) 7b. The level's boxes cover one rectangle in x and y
  5. grid_ratio >= 1
  6. All boxes divisible by grid_ratio in x and y
  7. Distribution map size matches grid size
  8. Domain z, x and y indices start at 0
83 {
84  // Check 1: Surface layer BC type is "surface_layer", for an engine that reads it
86  !needs_surface_layer || surface_layer != nullptr,
87  "fire module requires SurfaceLayer. "
88  "Check: phys_bc_type[zlo] == surface_layer AND erf.most.z0 is set");
89 
90  // Check 2: the level the fire grid refines exists. fire_anchor_level()
91  // resolves the input and aborts above the finest level; this is the backstop
92  // for any other caller.
93  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(lev >= 0 && lev <= erf.finestLevel(),
94  "[FIRE] Internal error: the fire grid's level is outside 0..finest_level");
95 
96  // Check 3: static refinement. The fire grid is built once from the boxes of
97  // its level, and its heat, moisture and smoke go into that level's source,
98  // so the level must keep its boxes for the whole run. Level 0 never regrids.
99  if (lev > 0) {
100  ParmParse pp_erf("erf");
101  int regrid_int = -1;
102  pp_erf.query("regrid_int", regrid_int);
103  if (regrid_int > 0) {
104  Abort("[FIRE] The fire grid on level " + std::to_string(lev)
105  + " is built once from that level's boxes, so the refinement must not change, but "
106  "erf.regrid_int = " + std::to_string(regrid_int) + " regrids it. Use static refinement "
107  "boxes with erf.regrid_int = -1, or set erf.fire.anchor_level = 0.");
108  }
109  }
110 
111  // Check 7: No z-decomposition (all z-levels on same rank)
112  // Use the public boxArray() accessor instead of the protected grids[] member
113  const BoxArray& ba = erf.boxArray(lev);
114  const Box& domain = erf.Geom(lev).Domain();
115  int domain_nz = domain.length(2);
116 
117  for (int i = 0; i < ba.size(); ++i) {
118  const Box& b = ba[i];
119  int box_nz = b.length(2);
120  // Build the message string before passing to the macro
121  std::string msg = std::string("[FIRE] Cannot decompose in z direction. ")
122  + "Every box on level " + std::to_string(lev)
123  + " must span the level's full height, since the fire model interpolates "
124  "through whole columns: set amr.max_grid_size_z = " + std::to_string(domain_nz)
125  + " (or larger), and on a refined level use a refinement box that reaches the "
126  "domain top (erf.<box>.in_box_hi without a z bound)";
127  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(box_nz == domain_nz, msg.c_str());
128  }
129 
130  // Check 7b: the boxes cover one rectangle. The fire grid treats the edges of
131  // their bounding rectangle as its domain edges and fills its ghost cells
132  // there; a hole or a second patch would leave cells with no neighbour data.
133  {
134  BoxList bl_2d;
135  for (int i = 0; i < ba.size(); ++i) {
136  Box b = ba[i];
137  b.setSmall(2, 0);
138  b.setBig(2, 0);
139  bl_2d.push_back(b);
140  }
141  const BoxArray ba_2d(std::move(bl_2d));
142  const Box region = ba_2d.minimalBox();
143  if (region.numPts() != ba_2d.numPts()) {
144  Abort("[FIRE] The boxes of level " + std::to_string(lev) + " cover "
145  + std::to_string(ba_2d.numPts()) + " of the " + std::to_string(region.numPts())
146  + " columns of their bounding rectangle, but the fire grid needs one rectangle. "
147  "Refine one rectangle on that level (a single refinement box), or set "
148  "erf.fire.anchor_level to a level whose boxes form one.");
149  }
150  }
151 
152  // Check 8: grid_ratio >= 1
154  grid_ratio >= 1,
155  "[FIRE] Fire grid_ratio requires value >= 1. Set: erf.fire.grid_ratio >= 1");
156 
157  // Check 9: All boxes divisible by grid_ratio in x,y (box_ratio_advice says
158  // which inputs to change).
159  int C = grid_ratio;
160  for (int i = 0; i < ba.size(); ++i) {
161  const Box& b = ba[i];
162  int nx = b.length(0);
163  int ny = b.length(1);
164  if (nx % C != 0 || ny % C != 0) {
165  Abort("[FIRE] Box sizes not divisible by grid_ratio: box " + std::to_string(i)
166  + " of level " + std::to_string(lev) + " is " + std::to_string(nx) + " x "
167  + std::to_string(ny) + " cells, and every x,y box length on that level must "
168  "be divisible by erf.fire.grid_ratio = " + std::to_string(C) + ". "
169  + box_ratio_advice(C, lev));
170  }
171  }
172 
173  // Check 10: DistributionMapping size matches grid size
174  // Use the public boxArray() accessor here too
176  erf.DistributionMap(lev).size() == erf.boxArray(lev).size(),
177  "[FIRE] Internal error: dmap size != grids size");
178 
179  // Check 11: Domain z-index starts at 0
181  erf.Geom(lev).Domain().smallEnd(2) == 0,
182  "[FIRE] Domain z requires start at index 0");
183 
184  // Check 11b: the level's domain starts at index 0 in x and y.
185  //
186  // A fire cell maps back to its atmospheric cell by i_a = i_f / C + atm_lo,
187  // with atm_lo the lower corner of the fire region in the level's index space
188  // (FireGrid::atm_lo), in ERF_FireGrid.H, ERF_FireAtmCoupling.H,
189  // ERF_FireWindExtract.cpp, ERF_FireSurfaceHeight.cpp and the engine's own maps; create_fire_grid() takes
190  // the region's physical position from ProbLo plus its index offset from the
191  // domain's corner. Every AMReX level has a zero-based domain, so this
192  // asserts the convention rather than relying on it silently.
194  erf.Geom(lev).Domain().smallEnd(0) == 0 && erf.Geom(lev).Domain().smallEnd(1) == 0,
195  "[FIRE] Domain x and y require start at index 0: the fire-to-atmosphere "
196  "index mapping assumes a zero-based domain");
197 }
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")

Referenced by FireCoupler::initialize().

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