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

#include <ERF_TopographicShading.H>

Collaboration diagram for TopographicShading:

Classes

struct  Level
 

Public Types

enum  RadiationField {
  CosZen , Shortwave , DirectVis , DirectNir ,
  DiffuseVis , DiffuseNir , Longwave , NRad
}
 
enum  Diagnostic {
  Shadow , DirectFactor , Incidence , UnshadedSW ,
  TerrainSW , NDiag
}
 
enum  GeometryField {
  Height , MapX , MapY , Latitude ,
  Longitude , CosAlpha , SinAlpha , NGeom
}
 

Public Member Functions

void define_level (int lev, const amrex::BoxArray &ba2d, const amrex::DistributionMapping &dm, const amrex::Geometry &geom)
 
bool geometry_ready (int lev) const
 
bool forcing_ready (int lev) const
 
void initialize_geometry (int lev, const TopographicShadingChoice &choice, const amrex::MultiFab &z_nd, const amrex::MultiFab &map_x, const amrex::MultiFab &map_y, const amrex::MultiFab *lat, const amrex::MultiFab *lon)
 
amrex::Vector< amrex::MultiFab * > raw_fields (int lev)
 
amrex::MultiFab * raw (int lev)
 
const amrex::MultiFab * diagnostics (int lev) const
 
void set_sun (int lev, double calday, double declin)
 
void interpolate_from_coarse (int lev)
 
void apply (int lev, const TopographicShadingChoice &choice, const amrex::Vector< amrex::MultiFab * > &destinations)
 

Private Attributes

amrex::Vector< std::unique_ptr< Level > > m_levels
 

Detailed Description

Surface-only correction of the radiation supplied to land models. The atmospheric fluxes and heating remain the column solver's outputs. Raw forcing is retained separately, including across radiation intervals, so a refined level never interpolates an already corrected parent flux.

Member Enumeration Documentation

◆ Diagnostic

Enumerator
Shadow 
DirectFactor 
Incidence 
UnshadedSW 
TerrainSW 
NDiag 
@ Shadow
Definition: ERF_TopographicShading.H:19
@ NDiag
Definition: ERF_TopographicShading.H:19
@ DirectFactor
Definition: ERF_TopographicShading.H:19
@ UnshadedSW
Definition: ERF_TopographicShading.H:19
@ Incidence
Definition: ERF_TopographicShading.H:19
@ TerrainSW
Definition: ERF_TopographicShading.H:19

◆ GeometryField

Enumerator
Height 
MapX 
MapY 
Latitude 
Longitude 
CosAlpha 
SinAlpha 
NGeom 
@ CosAlpha
Definition: ERF_TopographicShading.H:20
@ NGeom
Definition: ERF_TopographicShading.H:20
@ MapX
Definition: ERF_TopographicShading.H:20
@ Longitude
Definition: ERF_TopographicShading.H:20
@ MapY
Definition: ERF_TopographicShading.H:20
@ Height
Definition: ERF_TopographicShading.H:20
@ SinAlpha
Definition: ERF_TopographicShading.H:20
@ Latitude
Definition: ERF_TopographicShading.H:20

◆ RadiationField

Enumerator
CosZen 
Shortwave 
DirectVis 
DirectNir 
DiffuseVis 
DiffuseNir 
Longwave 
NRad 
@ DirectNir
Definition: ERF_TopographicShading.H:18
@ CosZen
Definition: ERF_TopographicShading.H:18
@ Shortwave
Definition: ERF_TopographicShading.H:18
@ DirectVis
Definition: ERF_TopographicShading.H:18
@ Longwave
Definition: ERF_TopographicShading.H:18
@ DiffuseNir
Definition: ERF_TopographicShading.H:18
@ NRad
Definition: ERF_TopographicShading.H:18
@ DiffuseVis
Definition: ERF_TopographicShading.H:18

Member Function Documentation

◆ apply()

void TopographicShading::apply ( int  lev,
const TopographicShadingChoice &  choice,
const amrex::Vector< amrex::MultiFab * > &  destinations 
)
218 {
220  auto& l = *m_levels.at(lev);
221  const Box dom = l.geom.Domain();
222  const int ilo = dom.smallEnd(0), ihi = dom.bigEnd(0);
223  const int jlo = dom.smallEnd(1), jhi = dom.bigEnd(1);
224  const bool px = l.geom.isPeriodic(0), py = l.geom.isPeriodic(1);
225  const auto dx = l.geom.CellSizeArray();
226  const double calday = l.calday, declin = l.declin;
227  Array4<const Real> terrain;
228  for (MFIter mfi(l.patch); mfi.isValid(); ++mfi) { terrain = l.patch.const_array(mfi); }
229  for (MFIter mfi(l.raw); mfi.isValid(); ++mfi) {
230  auto g = l.ground.const_array(mfi);
231  auto r = l.raw.const_array(mfi);
232  auto diag = l.diagnostic.array(mfi);
233  GpuArray<Array4<Real>, NRad> out;
234  for (int n = 0; n < NRad; ++n) {
235  out[n] = n < int(destinations.size()) && destinations[n]
236  ? destinations[n]->array(mfi) : Array4<Real>{};
237  }
238  ParallelFor(mfi.validbox(), [=] AMREX_GPU_DEVICE (int i, int j, int k) {
239  auto s = erf_topography::sun_direction(calday, declin, g(i,j,k,Latitude), g(i,j,k,Longitude));
240  if (choice.fixed_mu > 0) {
241  const Real az = choice.fixed_azimuth*PI/Real(180);
242  const Real h = std::sqrt(Real(1)-choice.fixed_mu*choice.fixed_mu);
243  s = {h*std::sin(az), h*std::cos(az), choice.fixed_mu};
244  }
245  const Real east = s.x, north = s.y;
246  s.x = g(i,j,k,CosAlpha)*east + g(i,j,k,SinAlpha)*north;
247  s.y = -g(i,j,k,SinAlpha)*east + g(i,j,k,CosAlpha)*north;
248  const Real dxp = dx[0]/g(i,j,k,MapX), dyp = dx[1]/g(i,j,k,MapY);
249  const int im = px ? erf_topography::wrap(i-1,ilo,ihi-ilo+1) : amrex::max(i-1,ilo);
250  const int ip = px ? erf_topography::wrap(i+1,ilo,ihi-ilo+1) : amrex::min(i+1,ihi);
251  const int jm = py ? erf_topography::wrap(j-1,jlo,jhi-jlo+1) : amrex::max(j-1,jlo);
252  const int jp = py ? erf_topography::wrap(j+1,jlo,jhi-jlo+1) : amrex::min(j+1,jhi);
253  const Real hx = (terrain(ip,j,0)-terrain(im,j,0))/(dxp*(px ? 2 : amrex::max(1,ip-im)));
254  const Real hy = (terrain(i,jp,0)-terrain(i,jm,0))/(dyp*(py ? 2 : amrex::max(1,jp-jm)));
255  // RRTMGP's direct flux uses its interval-averaged cosine. The
256  // midpoint sun supplies direction only; scaling by its cosine
257  // would amplify the averaged flux near sunrise or sunset.
258  const Real mu0 = r(i,j,k,CosZen);
259  const bool has_direct_sun = mu0 > Real(1.e-4);
260  const bool midpoint_daytime = s.z > Real(1.e-4);
261  const Real inc = midpoint_daytime ? erf_topography::incidence(hx,hy,s) : Real(0);
262  const bool shadow = has_direct_sun && choice.topo_shading &&
263  (!midpoint_daytime || erf_topography::shadowed(
264  i,j,terrain,ilo,ihi,jlo,jhi,px,py,dxp,dyp,s,choice.shadlen));
265  const Real factor = shadow ? Real(0) :
266  ((has_direct_sun && choice.slope_rad) ? inc/mu0 : Real(1));
267  const Real direct = r(i,j,k,DirectVis) + r(i,j,k,DirectNir);
268  const Real diffuse = r(i,j,k,DiffuseVis) + r(i,j,k,DiffuseNir);
269  // Preserve the exact raw broadband flux when no adjustment is needed.
270  const Real sw = factor == Real(1) ? r(i,j,k,Shortwave) :
271  erf_topography::corrected_shortwave(direct,diffuse,factor);
272  diag(i,j,k,Shadow) = shadow ? Real(1) : Real(0);
273  diag(i,j,k,DirectFactor) = factor;
274  diag(i,j,k,Incidence) = inc;
275  diag(i,j,k,UnshadedSW) = r(i,j,k,Shortwave);
276  diag(i,j,k,TerrainSW) = sw;
277  for (int n = 0; n < NRad; ++n) {
278  if (out[n]) {
279  out[n](i,j,0) = n == Shortwave ? sw :
280  ((n == DirectVis || n == DirectNir) ? factor*r(i,j,k,n) : r(i,j,k,n));
281  }
282  }
283  });
284  }
285 }
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
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);})
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46
amrex::Real Real
Definition: ERF_ShocInterface.H:19
amrex::Vector< std::unique_ptr< Level > > m_levels
Definition: ERF_TopographicShading.H:47
bool geometry_ready(int lev) const
Definition: ERF_TopographicShading.cpp:89
bool forcing_ready(int lev) const
Definition: ERF_TopographicShading.cpp:94
Definition: ERF_ConsoleIO.cpp:17
Definition: ERF_TopographicShadingKernels.H:10
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool shadowed(int i, int j, const amrex::Array4< const Real > &terrain, int ilo, int ihi, int jlo, int jhi, bool periodic_x, bool periodic_y, Real dx, Real dy, Sun s, Real search_length)
Definition: ERF_TopographicShadingKernels.H:43
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE Real incidence(Real hx, Real hy, Sun s)
Definition: ERF_TopographicShadingKernels.H:30
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE int wrap(int i, int lo, int n)
Definition: ERF_TopographicShadingKernels.H:36
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE Sun sun_direction(double calday, double declin, Real lat_deg, Real lon_deg)
Definition: ERF_TopographicShadingKernels.H:19
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE Real corrected_shortwave(Real direct, Real diffuse, Real factor)
Definition: ERF_TopographicShadingKernels.H:86
real(c_double), parameter g
Definition: ERF_module_model_constants.F90:19
amrex::Real shadlen
Definition: ERF_RadStruct.H:106
amrex::Real fixed_mu
Definition: ERF_RadStruct.H:107
amrex::Real fixed_azimuth
Definition: ERF_RadStruct.H:108
bool topo_shading
Definition: ERF_RadStruct.H:105
Here is the call graph for this function:

◆ define_level()

void TopographicShading::define_level ( int  lev,
const amrex::BoxArray &  ba2d,
const amrex::DistributionMapping &  dm,
const amrex::Geometry &  geom 
)
67 {
68  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(ba.isDisjoint(),
69  "Topographic radiation requires horizontally disjoint column boxes; "
70  "set amr.no_box_split_dir = 2");
71  if (lev >= int(m_levels.size())) { m_levels.resize(lev+1); }
72  m_levels[lev] = std::make_unique<Level>();
73  auto& l = *m_levels[lev];
74  l.geom = geom;
75  l.ground.define(ba, dm, NGeom, 0);
76  l.raw.define(ba, dm, NRad, 0);
77  l.diagnostic.define(ba, dm, NDiag, 0);
78  l.raw.setVal(missing);
79  l.diagnostic.setVal(-999);
80  for (int n = 0; n < NRad; ++n) {
81  l.aliases.push_back(std::make_unique<MultiFab>(l.raw, make_alias, n, 1));
82  }
83  // Descendant caches include this level's terrain outside their own grids.
84  for (int f = lev+1; f < int(m_levels.size()); ++f) {
85  if (m_levels[f]) { m_levels[f]->geometry_ready = false; }
86  }
87 }
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
Here is the call graph for this function:

◆ diagnostics()

const MultiFab * TopographicShading::diagnostics ( int  lev) const
195 {
196  return lev < int(m_levels.size()) && m_levels[lev] ? &m_levels[lev]->diagnostic : nullptr;
197 }

◆ forcing_ready()

bool TopographicShading::forcing_ready ( int  lev) const
95 {
96  return lev < int(m_levels.size()) && m_levels[lev] && m_levels[lev]->forcing_ready;
97 }

◆ geometry_ready()

bool TopographicShading::geometry_ready ( int  lev) const
90 {
91  return lev < int(m_levels.size()) && m_levels[lev] && m_levels[lev]->geometry_ready;
92 }

◆ initialize_geometry()

void TopographicShading::initialize_geometry ( int  lev,
const TopographicShadingChoice &  choice,
const amrex::MultiFab &  z_nd,
const amrex::MultiFab &  map_x,
const amrex::MultiFab &  map_y,
const amrex::MultiFab *  lat,
const amrex::MultiFab *  lon 
)
102 {
103  auto& l = *m_levels.at(lev);
104  const Box dom = makeSlab(l.geom.Domain(), 2, 0);
105  const auto dx = l.geom.CellSizeArray();
106  l.ground.setVal(missing);
107  if (lev > 0) {
109  prolong(l.ground, m_levels[lev-1]->ground,
110  horizontal_ratio(m_levels[lev-1]->geom, l.geom), NGeom);
111  }
112  l.ground.ParallelCopy(map_x, 0, MapX, 1);
113  l.ground.ParallelCopy(map_y, 0, MapY, 1);
114  l.ground.setVal(choice.latitude, Latitude, 1);
115  l.ground.setVal(choice.longitude, Longitude, 1);
116  l.ground.setVal(1, CosAlpha, 1);
117  l.ground.setVal(0, SinAlpha, 1);
118  if (lat && lon) {
119  l.ground.ParallelCopy(*lat, 0, Latitude, 1);
120  l.ground.ParallelCopy(*lon, 0, Longitude, 1);
121  MultiFab c(l.ground, make_alias, CosAlpha, 1);
122  MultiFab s(l.ground, make_alias, SinAlpha, 1);
123  fill_grid_rotation(c, s, *lat, *lon, l.geom);
124  }
125  const int kbot = l.geom.Domain().smallEnd(2);
126  for (MFIter mfi(l.ground); mfi.isValid(); ++mfi) {
127  // An elevated nested box has no ground of its own. Use the ancestor's.
128  if (z_nd.boxArray()[mfi.index()].smallEnd(2) != kbot) { continue; }
129  const auto z = z_nd.const_array(mfi);
130  const auto g = l.ground.array(mfi);
131  ParallelFor(mfi.validbox(), [=] AMREX_GPU_DEVICE (int i, int j, int k) {
132  g(i,j,k,Height) = Real(0.25)*(z(i,j,kbot) + z(i+1,j,kbot)
133  + z(i,j+1,kbot) + z(i+1,j+1,kbot));
134  });
135  }
136  AMREX_ALWAYS_ASSERT(l.ground.min(Height) > missing);
137  AMREX_ALWAYS_ASSERT(l.ground.min(MapX) > 0 && l.ground.min(MapY) > 0);
138 
139  // One search patch per owning rank, enlarged to include every possible ray
140  // and the slope stencil. ParallelCopy brings remote terrain across MPI
141  // boundaries once, at initialization/regrid, rather than at every sun update.
142  // This patch bounds all boxes owned by the rank, not just their individual
143  // ray neighborhoods. SFC or knapsack placement can assign distant boxes to
144  // one rank, making its patch nearly the full 2D domain and increasing cache
145  // memory and copy traffic even when shadlen is small. apply() currently reads
146  // one contiguous terrain patch per rank; a per-box cache would need a
147  // corresponding change there.
148  const auto& ba = l.ground.boxArray();
149  const auto& dm = l.ground.DistributionMap();
150  BoxList boxes;
151  Vector<int> owners;
152  const Real max_map[] = {l.ground.max(MapX), l.ground.max(MapY)};
153  for (int rank = 0; rank < ParallelDescriptor::NProcs(); ++rank) {
154  Box patch;
155  for (int b = 0; b < int(ba.size()); ++b) {
156  if (dm[b] == rank) { patch.minBox(ba[b]); }
157  }
158  if (!patch.ok()) { continue; }
159  for (int d = 0; d < 2; ++d) {
160  const Real cells = choice.topo_shading ? choice.shadlen*max_map[d]/dx[d] : 0;
161  const int ng = int(std::min(Real(dom.length(d)), std::ceil(cells))) + 2;
162  patch.grow(d, ng);
163  // Wrapped coordinates must be addressable in this one patch.
164  if (l.geom.isPeriodic(d) &&
165  (patch.smallEnd(d) < dom.smallEnd(d) || patch.bigEnd(d) > dom.bigEnd(d))) {
166  patch.setSmall(d, dom.smallEnd(d));
167  patch.setBig(d, dom.bigEnd(d));
168  }
169  }
170  boxes.push_back(patch & dom);
171  owners.push_back(rank);
172  }
173  l.patch.define(BoxArray(std::move(boxes)), DistributionMapping(std::move(owners)), 1, 0);
174  l.patch.setVal(missing);
175  for (int c = 0; c < lev; ++c) {
177  prolong(l.patch, m_levels[c]->ground, horizontal_ratio(m_levels[c]->geom, l.geom), 1);
178  }
179  l.patch.ParallelCopy(l.ground, Height, 0, 1);
180  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(l.patch.min(0) > missing,
181  "Topographic search patch contains terrain not supplied by any AMR level");
182  l.geometry_ready = true;
183 }
void fill_grid_rotation(MultiFab &cos_alpha, MultiFab &sin_alpha, const MultiFab &lat, const MultiFab &lon, const Geometry &geom)
Definition: ERF_LatLonMap.cpp:82
@ ng
Definition: ERF_Morrison.H:51
amrex::Real longitude
Definition: ERF_RadStruct.H:110
amrex::Real latitude
Definition: ERF_RadStruct.H:109
Here is the call graph for this function:

◆ interpolate_from_coarse()

void TopographicShading::interpolate_from_coarse ( int  lev)
208 {
209  AMREX_ALWAYS_ASSERT(lev > 0 && forcing_ready(lev-1));
210  auto& l = *m_levels.at(lev);
211  const auto& c = *m_levels[lev-1];
212  prolong(l.raw, c.raw, horizontal_ratio(c.geom, l.geom), NRad);
213  set_sun(lev, c.calday, c.declin);
214 }
void set_sun(int lev, double calday, double declin)
Definition: ERF_TopographicShading.cpp:199
Here is the call graph for this function:

◆ raw()

MultiFab * TopographicShading::raw ( int  lev)
192 { return &m_levels.at(lev)->raw; }

◆ raw_fields()

Vector< MultiFab * > TopographicShading::raw_fields ( int  lev)
186 {
187  Vector<MultiFab*> fields;
188  for (auto& f : m_levels.at(lev)->aliases) { fields.push_back(f.get()); }
189  return fields;
190 }

◆ set_sun()

void TopographicShading::set_sun ( int  lev,
double  calday,
double  declin 
)
200 {
201  auto& l = *m_levels.at(lev);
202  l.calday = calday;
203  l.declin = declin;
204  l.forcing_ready = true;
205 }

Member Data Documentation

◆ m_levels

amrex::Vector<std::unique_ptr<Level> > TopographicShading::m_levels
private

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