ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_FireWindExtract.cpp File Reference
Include dependency graph for ERF_FireWindExtract.cpp:

Functions

AMREX_GPU_DEVICE AMREX_FORCE_INLINE void column_wind_at_height (const Array4< const Real > &xvel, const Array4< const Real > &yvel, const Array4< const Real > &z_cc, int ia, int ja, int nz, Real z_target, Real z_ground, Real &u_out, Real &v_out, bool below_first_log=false, Real z0_log=Real(0.1)) noexcept
 Wind at a target height within one atmospheric column. More...
 
void fill_fire_wind_from_interpolation (MultiFab &fire_wind_ref, MultiFab &fire_wind_extract_z_mf, const MultiFab &xvel_mf, const MultiFab &yvel_mf, const MultiFab &z_phys_cc_mf, const MultiFab &fire_surface_z_mf, const MultiFab &fire_col_ground_mf, const FireGrid &fg, Real z_ref, int nz, const MultiFab *z_ref_cell_mf, int wind_interp, const MultiFab *col_open, const MultiFab *col_roof, Real z_sample, Real z0_log, bool below_first_log)
 
void extract_fire_wind_k0 (amrex::MultiFab &u_k0, amrex::MultiFab &v_k0, amrex::MultiFab &h_k0, const amrex::MultiFab &xvel, const amrex::MultiFab &yvel, const amrex::MultiFab &z_phys_cc, const amrex::MultiFab *z_phys_nd, const amrex::Geometry &geom)
 The wind at the lowest cell centre of each atmospheric column, and its height above that column's ground. More...
 

Function Documentation

◆ column_wind_at_height()

AMREX_GPU_DEVICE AMREX_FORCE_INLINE void column_wind_at_height ( const Array4< const Real > &  xvel,
const Array4< const Real > &  yvel,
const Array4< const Real > &  z_cc,
int  ia,
int  ja,
int  nz,
Real  z_target,
Real  z_ground,
Real &  u_out,
Real &  v_out,
bool  below_first_log = false,
Real  z0_log = Real(0.1) 
)
noexcept

Wind at a target height within one atmospheric column.

Brackets the target height by bisection on the column's terrain-following cell-centre heights, averages the face velocities to cell centres at the two bracketing levels, and interpolates linearly between them. A target above the highest cell centre takes that level's wind. A target below the lowest cell centre takes the lowest centre's wind as it is (below_first_log = false, which with a 20 m first cell hands the fire the 10 m wind as its 6.1 m wind, 12 % high at z0 = 0.1 m), or scaled down a neutral log profile of roughness z0_log (below_first_log = true), U(z) = U(z_1) ln(z/z0) / ln(z_1/z0), the better form for a first cell above the reference height. z_ground is the ground height the column heights are measured from; z_target - z_ground is the height above ground.

27 {
28  // Bracket by bisection; z_cc is monotonically increasing in k.
29  int k_lo;
30  // Log-profile factor for a target below the lowest centre, from floored
31  // heights (both arguments valid whether or not the branch is taken, so
32  // nothing speculated under the fpe traps can raise a flag).
33  Real below_scale = Real(1.0);
34  if (z_target <= z_cc(ia, ja, 0)) {
35  k_lo = 0;
36  if (below_first_log) {
37  constexpr Real z_tiny = Real(1.0e-30);
38  const Real z0 = std::abs(z0_log) + z_tiny;
39  const Real z1 = amrex::max(z_cc(ia, ja, 0) - z_ground, z0 * Real(1.000001));
40  const Real zt = amrex::max(amrex::min(z_target - z_ground, z1), z0);
41  below_scale = std::log(zt / z0) / std::log(z1 / z0);
42  }
43  } else if (z_target >= z_cc(ia, ja, nz - 1)) {
44  k_lo = nz - 2;
45  } else {
46  int lo = 0;
47  int hi = nz - 1;
48  while (hi - lo > 1) {
49  const int mid = (lo + hi) / 2;
50  if (z_cc(ia, ja, mid) <= z_target) { lo = mid; } else { hi = mid; }
51  }
52  k_lo = lo;
53  }
54  k_lo = amrex::max(0, amrex::min(k_lo, nz - 2));
55  const int k_hi = k_lo + 1;
56 
57  const Real z_lo = z_cc(ia, ja, k_lo);
58  const Real z_hi = z_cc(ia, ja, k_hi);
59  Real alpha = Real(0.0);
60  if (z_hi > z_lo) {
61  alpha = (z_target - z_lo) / (z_hi - z_lo);
62  alpha = amrex::max(Real(0.0), amrex::min(Real(1.0), alpha));
63  }
64 
65  const Real u_lo = Real(0.5) * (xvel(ia, ja, k_lo) + xvel(ia + 1, ja, k_lo));
66  const Real v_lo = Real(0.5) * (yvel(ia, ja, k_lo) + yvel(ia, ja + 1, k_lo));
67  const Real u_hi = Real(0.5) * (xvel(ia, ja, k_hi) + xvel(ia + 1, ja, k_hi));
68  const Real v_hi = Real(0.5) * (yvel(ia, ja, k_hi) + yvel(ia, ja + 1, k_hi));
69 
70  u_out = below_scale * (u_lo + alpha * (u_hi - u_lo));
71  v_out = below_scale * (v_lo + alpha * (v_hi - v_lo));
72 }
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ alpha
Definition: ERF_SLM.H:151
@ xvel
Definition: ERF_IndexDefines.H:255
@ yvel
Definition: ERF_IndexDefines.H:256

Referenced by fill_fire_wind_from_interpolation().

Here is the caller graph for this function:

◆ extract_fire_wind_k0()

void extract_fire_wind_k0 ( amrex::MultiFab &  u_k0,
amrex::MultiFab &  v_k0,
amrex::MultiFab &  h_k0,
const amrex::MultiFab &  xvel,
const amrex::MultiFab &  yvel,
const amrex::MultiFab &  z_phys_cc,
const amrex::MultiFab *  z_phys_nd,
const amrex::Geometry &  geom 
)

The wind at the lowest cell centre of each atmospheric column, and its height above that column's ground.

For an engine that drives its fire with the k = 0 wind of the atmospheric columns rather than a wind interpolated to a height (LSFIRE does). The face velocities are averaged to the k = 0 cell centre; the height is that centre minus the column's ground, the mean of its four k = 0 nodes (the ground compute_fire_surface_height uses), or minus the domain floor without nodal heights.

Parameters
[out]u_k0,v_k0Wind [m/s] on the k = 0 slabs of the level (one per box of the level, the BoxArray of FireForcing::T_k0), with any ghost cells: those are filled with FillBoundary
[out]h_k0Height of that wind above the column's ground [m], laid out as u_k0
[in]xvel,yvelFace velocities of the level (FireForcing::xvel, yvel)
[in]z_phys_ccCell-centre height of the level [m]
[in]z_phys_ndNodal height of the level [m], or nullptr (flat floor)
[in]geomGeometry of the level
268 {
269  using namespace amrex;
270  const int k0 = geom.Domain().smallEnd(2);
271  const Real z_floor = geom.ProbLo(2);
272  const bool use_znd = (z_phys_nd != nullptr);
273  for (MFIter mfi(u_k0); mfi.isValid(); ++mfi) {
274  const Box bx = mfi.validbox();
275  const auto u = xvel.const_array(mfi);
276  const auto v = yvel.const_array(mfi);
277  const auto zcc = z_phys_cc.const_array(mfi);
278  const Array4<Real const> znd = use_znd ? z_phys_nd->const_array(mfi) : Array4<Real const>{};
279  const auto us = u_k0.array(mfi);
280  const auto vs = v_k0.array(mfi);
281  const auto hs = h_k0.array(mfi);
282  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int) noexcept
283  {
284  us(i,j,0) = Real(0.5) * (u(i,j,k0) + u(i+1,j,k0));
285  vs(i,j,0) = Real(0.5) * (v(i,j,k0) + v(i,j+1,k0));
286  const Real zsfc = use_znd ? Real(0.25) * (znd(i,j,k0) + znd(i+1,j,k0) + znd(i,j+1,k0) + znd(i+1,j+1,k0))
287  : z_floor;
288  hs(i,j,0) = zcc(i,j,k0) - zsfc;
289  });
290  }
291  u_k0.FillBoundary(geom.periodicity());
292  v_k0.FillBoundary(geom.periodicity());
293  h_k0.FillBoundary(geom.periodicity());
294 }
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
Definition: ERF_ConsoleIO.cpp:17
Here is the call graph for this function:

◆ fill_fire_wind_from_interpolation()

void fill_fire_wind_from_interpolation ( MultiFab &  fire_wind_ref,
MultiFab &  fire_wind_extract_z_mf,
const MultiFab &  xvel_mf,
const MultiFab &  yvel_mf,
const MultiFab &  z_phys_cc_mf,
const MultiFab &  fire_surface_z_mf,
const MultiFab &  fire_col_ground_mf,
const FireGrid &  fg,
Real  z_ref,
int  nz,
const MultiFab *  z_ref_cell_mf,
int  wind_interp,
const MultiFab *  col_open,
const MultiFab *  col_roof,
Real  z_sample,
Real  z0_log,
bool  below_first_log 
)
92 {
93  const int C = fg.C;
94  // Fire cell (i_f, j_f) lies in atmospheric cell (i_f / C + lo_x, j_f / C + lo_y)
95  const int lo_x = fg.atm_lo[0];
96  const int lo_y = fg.atm_lo[1];
97  const bool bilinear = (wind_interp == 1);
98  const bool open_weights = bilinear && (col_open != nullptr) && (col_roof != nullptr);
99  const bool resample = (z_sample > Real(0.0));
100 
101  for (MFIter mfi(fire_wind_ref, amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi) {
102  const Box& bx = mfi.tilebox();
103  Array4<Real> fire_wind = fire_wind_ref.array(mfi);
104  Array4<Real> extract_z = fire_wind_extract_z_mf.array(mfi);
105  Array4<const Real> xvel = xvel_mf.array(mfi);
106  Array4<const Real> yvel = yvel_mf.array(mfi);
107  Array4<const Real> z_phys_cc = z_phys_cc_mf.array(mfi);
108  Array4<const Real> z_surf_arr = fire_surface_z_mf.const_array(mfi);
109  Array4<const Real> z_col_arr = fire_col_ground_mf.const_array(mfi);
110 
111  // Per-cell sampling height, when the engine supplies one
112  Array4<const Real> z_ref_cell_arr;
113  if (z_ref_cell_mf != nullptr) {
114  z_ref_cell_arr = z_ref_cell_mf->const_array(mfi);
115  }
116  const bool per_cell_ht = (z_ref_cell_mf != nullptr);
117 
118  // Open-column weights: the 2D fields carry one ghost cell, which is
119  // all the bilinear stencil reaches; clamp to their box regardless.
120  Array4<const Real> open_arr, roof_arr;
121  int io_min = 0, io_max = 0, jo_min = 0, jo_max = 0;
122  if (open_weights) {
123  open_arr = col_open->const_array(mfi);
124  roof_arr = col_roof->const_array(mfi);
125  const Box& obox = Box(open_arr);
126  io_min = obox.smallEnd(0); io_max = obox.bigEnd(0);
127  jo_min = obox.smallEnd(1); jo_max = obox.bigEnd(1);
128  }
129 
130  // Bilinear reaches one atmospheric cell beyond the column holding the
131  // fire cell, and the face average reaches one further in x or y. Both
132  // land in the atmospheric fab's ghost region, which carries two cells
133  // for z_phys_cc and more for the velocities; clamp anyway so a fab
134  // without them degrades to one-sided rather than reading past the end.
135  const Box& zbox = Box(z_phys_cc);
136  const int ia_min = zbox.smallEnd(0);
137  const int ia_max = zbox.bigEnd(0) - 1;
138  const int ja_min = zbox.smallEnd(1);
139  const int ja_max = zbox.bigEnd(1) - 1;
140 
141  amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (const IntVect& iv) {
142  const int i_f = iv[0];
143  const int j_f = iv[1];
144 
145  // Wind reference height for this cell: its own, or the global one
146  Real z_ref_cell = z_ref;
147  if (per_cell_ht) { z_ref_cell = z_ref_cell_arr(i_f, j_f, 0); }
148 
149  // Height actually sampled and the log-law factor that brings the
150  // sampled wind down to the target height (1 without resampling).
151  // The log ratio is written so that every argument is valid whether
152  // or not it is selected: clang evaluates the ternary's arm
153  // speculatively in Release, and with resampling off z_sample is 0,
154  // so log(0) raised the divide-by-zero flag and tripped
155  // amrex.fpe_trap_zero on the first step. abs and the tiny offset
156  // are not selects, so nothing is folded back into the arm; with
157  // resampling on the values are unchanged.
158  constexpr Real z_tiny = Real(1.0e-30);
159  const Real z0_safe = std::abs(z0_log) + z_tiny;
160  const Real log_ref = std::log((std::abs(z_ref_cell) + z_tiny) / z0_safe);
161  const Real log_sample = std::log((std::abs(z_sample) + z_tiny) / z0_safe);
162  const Real z_at = resample ? z_sample : z_ref_cell;
163  const Real scale = resample ? log_ref / (log_sample + z_tiny) : Real(1.0);
164 
165  if (!bilinear) {
166  // Nearest atmospheric column: every fire cell in a column shares
167  // one wind vector, so the fire-grid wind is piecewise constant
168  // on atmospheric cells.
169  const int i_a = amrex::max(ia_min, amrex::min(i_f / C + lo_x, ia_max));
170  const int j_a = amrex::max(ja_min, amrex::min(j_f / C + lo_y, ja_max));
171 
172  // Ground elevation of this fire cell's atmospheric column. This
173  // is the terrain surface, not the first cell centre, which sits
174  // half an atmospheric cell higher and would push the extraction
175  // height up by an amount that varies with vertical resolution.
176  const Real z_target = z_surf_arr(i_f, j_f, 0) + z_at;
177  extract_z(i_f, j_f, 0) = z_target;
178 
179  Real u_out, v_out;
180  column_wind_at_height(xvel, yvel, z_phys_cc, i_a, j_a, nz,
181  z_target, z_surf_arr(i_f, j_f, 0), u_out, v_out,
182  below_first_log, z0_log);
183  fire_wind(i_f, j_f, 0, 0) = scale * u_out;
184  fire_wind(i_f, j_f, 0, 1) = scale * v_out;
185  return;
186  }
187 
188  // Bilinear between the four surrounding atmospheric columns. The
189  // fire cell centre sits at continuous atmospheric cell-centre
190  // coordinate (i_f + 0.5)/C - 0.5 relative to the region's corner; the
191  // weights come from that, the column indices add the corner.
192  const Real gx = (Real(i_f) + Real(0.5)) / Real(C) - Real(0.5);
193  const Real gy = (Real(j_f) + Real(0.5)) / Real(C) - Real(0.5);
194  const int i0_local = static_cast<int>(std::floor(gx));
195  const int j0_local = static_cast<int>(std::floor(gy));
196  const Real wx = gx - Real(i0_local);
197  const Real wy = gy - Real(j0_local);
198  const int i0 = i0_local + lo_x;
199  const int j0 = j0_local + lo_y;
200 
201  Real u_sum = Real(0.0);
202  Real v_sum = Real(0.0);
203  Real z_sum = Real(0.0);
204 
205  // Open-column factors and their bilinear sum; fall back to the
206  // plain weights when every column is closed.
207  Real f_open[4] = {Real(1.0), Real(1.0), Real(1.0), Real(1.0)};
208  Real w_norm = Real(1.0);
209  if (open_weights) {
210  Real wsum = Real(0.0);
211  for (int c = 0; c < 4; ++c) {
212  const int di = (c & 1);
213  const int dj = ((c >> 1) & 1);
214  const int io = amrex::max(io_min, amrex::min(i0 + di, io_max));
215  const int jo = amrex::max(jo_min, amrex::min(j0 + dj, jo_max));
216  const Real w = (di ? wx : (Real(1.0) - wx)) * (dj ? wy : (Real(1.0) - wy));
217  f_open[c] = (roof_arr(io, jo, 0) > z_at) ? open_arr(io, jo, 0) : Real(1.0);
218  wsum += w * f_open[c];
219  }
220  if (wsum > Real(1.0e-6)) {
221  w_norm = Real(1.0) / wsum;
222  } else {
223  for (int c = 0; c < 4; ++c) { f_open[c] = Real(1.0); }
224  }
225  }
226 
227  for (int c = 0; c < 4; ++c) {
228  const int di = (c & 1);
229  const int dj = ((c >> 1) & 1);
230  const int ia = amrex::max(ia_min, amrex::min(i0 + di, ia_max));
231  const int ja = amrex::max(ja_min, amrex::min(j0 + dj, ja_max));
232 
233  const Real w = (di ? wx : (Real(1.0) - wx)) * (dj ? wy : (Real(1.0) - wy))
234  * f_open[c] * w_norm;
235 
236  // Each column is sampled at the same height above *its own*
237  // ground: near the surface the profile is anchored to the local
238  // terrain, so blending at one absolute height would sample an
239  // upslope neighbour far higher above ground than asked for.
240  const Real z_target_c = z_col_arr(i_f, j_f, 0, c) + z_at;
241 
242  Real u_c, v_c;
243  column_wind_at_height(xvel, yvel, z_phys_cc, ia, ja, nz,
244  z_target_c, z_col_arr(i_f, j_f, 0, c), u_c, v_c,
245  below_first_log, z0_log);
246 
247  u_sum += w * u_c;
248  v_sum += w * v_c;
249  z_sum += w * z_target_c;
250  }
251 
252  extract_z(i_f, j_f, 0) = z_sum;
253  fire_wind(i_f, j_f, 0, 0) = scale * u_sum;
254  fire_wind(i_f, j_f, 0, 1) = scale * v_sum;
255  });
256  }
257 }
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void column_wind_at_height(const Array4< const Real > &xvel, const Array4< const Real > &yvel, const Array4< const Real > &z_cc, int ia, int ja, int nz, Real z_target, Real z_ground, Real &u_out, Real &v_out, bool below_first_log=false, Real z0_log=Real(0.1)) noexcept
Wind at a target height within one atmospheric column.
Definition: ERF_FireWindExtract.cpp:20
Real w
Definition: ERF_Plotfile2DInterpolator.cpp:23
@ fg
Definition: ERF_AdvanceMorrison.cpp:111
real(c_double), private di
Definition: ERF_module_mp_morr_two_moment.F90:203
Here is the call graph for this function: