ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ibseb Namespace Reference

Enumerations

enum  RayHit : int { RAY_SKY = 0 , RAY_GROUND = 1 , RAY_BUILDING = 2 }
 What a ray from a face ends on. More...
 

Functions

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solve_skin_balance (amrex::Real T_start, amrex::Real SW_abs, amrex::Real Q_abs, amrex::Real LW_ext, amrex::Real emis, amrex::Real f_bldg, amrex::Real C_H, amrex::Real exner, amrex::Real theta_air, amrex::Real LE, amrex::Real slab_a, amrex::Real slab_b, amrex::Real T_min, amrex::Real T_max, amrex::Real max_step, amrex::Real tol, int max_iter, int &n_iter, amrex::Real &resid)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real advance_slab_dirichlet (amrex::Real *T, amrex::Real T_skin, amrex::Real T_int, amrex::Real k, amrex::Real rho_cp, amrex::Real dz, amrex::Real dt, int N)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void slab_skin_response (const amrex::Real *T, amrex::Real T_int, amrex::Real k, amrex::Real rho_cp, amrex::Real dz, amrex::Real dt, int N, amrex::Real &a, amrex::Real &b)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_declination (amrex::Real day_of_year)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real equation_of_time (amrex::Real day_of_year)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_hour_angle (amrex::Real time_utc_s, amrex::Real longitude_deg, amrex::Real day_of_year)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_zenith (amrex::Real latitude_deg, amrex::Real decl, amrex::Real hour_angle)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_azimuth (amrex::Real latitude_deg, amrex::Real decl, amrex::Real hour_angle, amrex::Real zenith)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real earth_sun_distance_factor (amrex::Real day_of_year)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void sun_vector (amrex::Real zenith, amrex::Real azimuth, amrex::Real &sx, amrex::Real &sy, amrex::Real &sz)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real clear_sky_dni (amrex::Real cos_zenith, amrex::Real S0, amrex::Real tau, amrex::Real distance_factor)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real clear_sky_diffuse_h (amrex::Real cos_zenith, amrex::Real S0, amrex::Real tau, amrex::Real distance_factor, amrex::Real k_d)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real column_top (const int *col_top, int ci, int cj, int i0, int j0, int bw, int bh, amrex::Real z_ground, amrex::Real dz)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool ray_blocked (amrex::Real x0, amrex::Real y0, amrex::Real z0, amrex::Real sx, amrex::Real sy, amrex::Real sz, const int *col_top, int nx, int ny, int i0, int j0, int bw, int bh, amrex::Real x_lo, amrex::Real y_lo, amrex::Real dx, amrex::Real dy, amrex::Real dz, bool per_x, bool per_y, amrex::Real z_ground, amrex::Real z_max, amrex::Real max_path)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE int ray_hit (amrex::Real x0, amrex::Real y0, amrex::Real z0, amrex::Real sx, amrex::Real sy, amrex::Real sz, const int *col_top, int nx, int ny, int i0, int j0, int bw, int bh, amrex::Real x_lo, amrex::Real y_lo, amrex::Real dx, amrex::Real dy, amrex::Real dz, bool per_x, bool per_y, amrex::Real z_ground, amrex::Real z_max, amrex::Real max_path)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void hemisphere_direction (int dir, int nsign, int ia, int ie, int n_az, int n_el, amrex::Real &sx, amrex::Real &sy, amrex::Real &sz)
 

Variables

constexpr amrex::Real SIGMA_SB = 5.670374419e-8
 Stefan-Boltzmann constant [W/m2/K4]. More...
 
constexpr int SLAB_MAX_LAYERS = 32
 

Enumeration Type Documentation

◆ RayHit

enum ibseb::RayHit : int

What a ray from a face ends on.

Enumerator
RAY_SKY 
RAY_GROUND 
RAY_BUILDING 
242 : int { RAY_SKY = 0, RAY_GROUND = 1, RAY_BUILDING = 2 };
@ RAY_GROUND
Definition: ERF_IBSEBSolar.H:242
@ RAY_SKY
Definition: ERF_IBSEBSolar.H:242
@ RAY_BUILDING
Definition: ERF_IBSEBSolar.H:242

Function Documentation

◆ advance_slab_dirichlet()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::advance_slab_dirichlet ( amrex::Real T,
amrex::Real  T_skin,
amrex::Real  T_int,
amrex::Real  k,
amrex::Real  rho_cp,
amrex::Real  dz,
amrex::Real  dt,
int  N 
)

Advance one slab column by one step and return the conduction into it.

Solves, for layers l = 0 .. N-1 with implicit Euler, rho_cp dz (T_l^{n+1} - T_l^n) / dt = flux_in - flux_out, the fluxes between neighbouring centres being k (T_{l-1} - T_l) / dz, the flux from the skin 2 k (T_skin - T_0) / dz and the flux to the interior 2 k (T_{N-1} - T_int) / dz. Unconditionally stable.

Parameters
[in,out]TLayer temperatures [K], N values, top first.
[in]T_skinSkin temperature at the top [K] (Dirichlet).
[in]T_intInterior temperature at the bottom [K] (Dirichlet).
[in]kThermal conductivity [W/m/K].
[in]rho_cpVolumetric heat capacity [J/m3/K].
[in]dzLayer thickness [m].
[in]dtTime step [s].
[in]NNumber of layers, at most SLAB_MAX_LAYERS.
Returns
Conduction into the slab through the skin at the new time, 2 k (T_skin - T_0^{n+1}) / dz [W/m2], positive into the slab.
54 {
55  if (N < 1 || N > SLAB_MAX_LAYERS) return 0.0;
56  const amrex::Real Fourier = k * dt / (rho_cp * dz * dz); // interior Fourier number
57  // Coefficients of a l T_{l-1} + b_l T_l + c_l T_{l+1} = d_l (all-plus form).
59  // Row 0: skin above at half spacing (coefficient 2 Fourier), layer 1 below.
60  {
61  const amrex::Real b0 = 1.0 + 2.0 * Fourier + ((N > 1) ? Fourier : 2.0 * Fourier);
62  const amrex::Real c0 = (N > 1) ? -Fourier : 0.0;
63  const amrex::Real d0 = T[0] + 2.0 * Fourier * T_skin + ((N > 1) ? 0.0 : 2.0 * Fourier * T_int);
64  alpha[0] = c0 / b0;
65  gamma[0] = d0 / b0;
66  }
67  // Interior rows.
68  for (int l = 1; l < N - 1; ++l) {
69  const amrex::Real a = -Fourier, b = 1.0 + 2.0 * Fourier, c = -Fourier;
70  const amrex::Real bm = b - a * alpha[l - 1];
71  gamma[l] = (T[l] - a * gamma[l - 1]) / bm;
72  alpha[l] = c / bm;
73  }
74  // Last row (N > 1): interior below at half spacing (coefficient 2 Fourier).
75  if (N > 1) {
76  const int l = N - 1;
77  const amrex::Real a = -Fourier, b = 1.0 + Fourier + 2.0 * Fourier;
78  const amrex::Real bm = b - a * alpha[l - 1];
79  gamma[l] = (T[l] + 2.0 * Fourier * T_int - a * gamma[l - 1]) / bm;
80  alpha[l] = 0.0;
81  }
82  // Back substitution.
83  T[N - 1] = gamma[N - 1];
84  for (int l = N - 2; l >= 0; --l) { T[l] = gamma[l] - alpha[l] * T[l + 1]; }
85  return 2.0 * k * (T_skin - T[0]) / dz;
86 }
amrex::Real gamma
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:9
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ T
Definition: ERF_IndexDefines.H:128
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
constexpr int SLAB_MAX_LAYERS
Definition: ERF_IBSEBSlab.H:28
real(kind=kind_phys), parameter, private alpha
Definition: ERF_module_mp_wdm6.F90:62

Referenced by IBFaceSet::compute_ground(), slab_skin_response(), and IBFaceSet::solve_balance().

Here is the caller graph for this function:

◆ clear_sky_diffuse_h()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::clear_sky_diffuse_h ( amrex::Real  cos_zenith,
amrex::Real  S0,
amrex::Real  tau,
amrex::Real  distance_factor,
amrex::Real  k_d 
)

Clear-sky diffuse irradiance on a horizontal surface [W/m2]: a fraction k_d of what the direct beam lost to the atmosphere reaches the ground as diffuse light (Liu and Jordan form).

148 {
149  if (cos_zenith <= 1.0e-3) return 0.0;
150  const amrex::Real tr = std::pow(tau, 1.0 / cos_zenith);
151  return k_d * S0 * distance_factor * cos_zenith * (1.0 - tr);
152 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ clear_sky_dni()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::clear_sky_dni ( amrex::Real  cos_zenith,
amrex::Real  S0,
amrex::Real  tau,
amrex::Real  distance_factor 
)

Clear-sky direct-normal irradiance [W/m2] (Bird form): the solar constant, corrected for the Earth-Sun distance, attenuated by tau^(1/cos z). Zero when the sun is below the horizon.

135 {
136  if (cos_zenith <= 1.0e-3) return 0.0;
137  return S0 * distance_factor * std::pow(tau, 1.0 / cos_zenith);
138 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ column_top()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::column_top ( const int *  col_top,
int  ci,
int  cj,
int  i0,
int  j0,
int  bw,
int  bh,
amrex::Real  z_ground,
amrex::Real  dz 
)

Top height of a column of the level. The column map covers the bounding box of the built columns only (origin i0, j0 in 0-based domain columns, bw x bh values indexed (ci - i0) * bh + (cj - j0)); every other column is open ground. An entry is the domain-relative k of the highest solid cell of its column, or -1 where the column is fluid, so the top is one cell above that index and a fluid column sits at the ground.

166 {
167  ci -= i0; cj -= j0;
168  if (ci < 0 || ci >= bw || cj < 0 || cj >= bh) { return z_ground; }
169  const int kt = col_top[ci * bh + cj];
170  return (kt < 0) ? z_ground : z_ground + (kt + 1) * dz;
171 }
real(c_double), private ci
Definition: ERF_module_mp_morr_two_moment.F90:203

Referenced by ray_blocked(), ray_hit(), and ERF::resolve_station_stencils().

Here is the caller graph for this function:

◆ earth_sun_distance_factor()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::earth_sun_distance_factor ( amrex::Real  day_of_year)

Earth-Sun distance factor (d0/d)^2 that scales the solar constant for the day of year (Spencer 1971): about 1.034 at perihelion in early January, 0.967 at aphelion in early July, annual mean 1.

110 {
111  const amrex::Real G = 2.0 * PI * (day_of_year - 1.0) / 365.0;
112  return 1.000110 + 0.034221 * std::cos(G) + 0.001280 * std::sin(G)
113  + 0.000719 * std::cos(2.0 * G) + 0.000077 * std::sin(2.0 * G);
114 }
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:39

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ equation_of_time()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::equation_of_time ( amrex::Real  day_of_year)

Equation of time [minutes] from the day of year (Spencer 1971).

47 {
48  const amrex::Real gamma = 2.0 * PI * (day_of_year - 1.0) / 365.25;
49  return 229.18 * (0.000075 + 0.001868 * std::cos(gamma) - 0.032077 * std::sin(gamma)
50  - 0.014615 * std::cos(2.0 * gamma) - 0.040849 * std::sin(2.0 * gamma));
51 }

Referenced by solar_hour_angle().

Here is the caller graph for this function:

◆ hemisphere_direction()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void ibseb::hemisphere_direction ( int  dir,
int  nsign,
int  ia,
int  ie,
int  n_az,
int  n_el,
amrex::Real sx,
amrex::Real sy,
amrex::Real sz 
)

Direction of the (ia, ie) sample of a cosine-weighted hemisphere around the outward normal of a face with direction dir and normal sign nsign (+1 along the axis, -1 against it). Stratified in the azimuth phi = 2 pi (ia + 1/2) / n_az and in u = (ie + 1/2) / n_el with theta = asin(sqrt(u)) from the normal, so every sample carries the same weight and the counts are view factors.

332 {
333  const amrex::Real u = (ie + 0.5) / n_el;
334  const amrex::Real theta = std::asin(std::sqrt(u));
335  const amrex::Real phi = 2.0 * PI * (ia + 0.5) / n_az;
336  const amrex::Real cn = std::cos(theta), ct = std::sin(theta) * std::cos(phi), st = std::sin(theta) * std::sin(phi);
337  // Normal and two tangents for each axis.
338  amrex::Real n[3] = {0.0, 0.0, 0.0}, t1[3] = {0.0, 0.0, 0.0}, t2[3] = {0.0, 0.0, 0.0};
339  n[dir] = nsign;
340  if (dir == 0) { t1[1] = 1.0; t2[2] = 1.0; }
341  else if (dir == 1) { t1[0] = 1.0; t2[2] = 1.0; }
342  else { t1[0] = 1.0; t2[1] = 1.0; }
343  sx = cn * n[0] + ct * t1[0] + st * t2[0];
344  sy = cn * n[1] + ct * t1[1] + st * t2[1];
345  sz = cn * n[2] + ct * t1[2] + st * t2[2];
346 }
@ theta
Definition: ERF_SLM.H:19

Referenced by IBFaceSet::compute_view_fractions().

Here is the caller graph for this function:

◆ ray_blocked()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool ibseb::ray_blocked ( amrex::Real  x0,
amrex::Real  y0,
amrex::Real  z0,
amrex::Real  sx,
amrex::Real  sy,
amrex::Real  sz,
const int *  col_top,
int  nx,
int  ny,
int  i0,
int  j0,
int  bw,
int  bh,
amrex::Real  x_lo,
amrex::Real  y_lo,
amrex::Real  dx,
amrex::Real  dy,
amrex::Real  dz,
bool  per_x,
bool  per_y,
amrex::Real  z_ground,
amrex::Real  z_max,
amrex::Real  max_path 
)

Whether the ray from a face toward the sun hits a building.

Buildings are represented by the top height of the columns of the level (col_top over the built bounding box, see column_top(), replicated on every rank), so a ray is blocked wherever its height on entering a column is below that column's top: buildings stand on the ground, so the lowest point of the ray inside a column is at its entry, and the height only grows along a ray toward a sun above the horizon. The walk over the columns is a 2D digital differential analyser. It stops when the ray rises above the tallest column, leaves the domain in a non-periodic direction, or has travelled max_path.

The start point is nudged a little along the ray so a face does not test its own column at its own height: a wall starts in its fluid cell's column, whose top is below the face (no overhangs), and a roof starts a hair above its own top.

Returns
true when blocked (the face is in shadow).
199 {
200  if (sz <= 0.0) return true; // sun at or below the horizon
201  if (z0 >= z_max) return false; // nothing stands this high
202  const amrex::Real eps = 1.0e-6 * amrex::min(dx, dy);
203  const amrex::Real t0 = 1.0e-3 * amrex::min(dx, dy);
204  amrex::Real x = x0 + t0 * sx, y = y0 + t0 * sy, z = z0 + t0 * sz;
205  int ci = static_cast<int>(std::floor((x - x_lo) / dx));
206  int cj = static_cast<int>(std::floor((y - y_lo) / dy));
207  const int stepx = (sx > 0.0) ? 1 : ((sx < 0.0) ? -1 : 0);
208  const int stepy = (sy > 0.0) ? 1 : ((sy < 0.0) ? -1 : 0);
209  const amrex::Real big = 1.0e30;
210  const amrex::Real tdx = (stepx != 0) ? dx / std::abs(sx) : big;
211  const amrex::Real tdy = (stepy != 0) ? dy / std::abs(sy) : big;
212  // Distance along the ray to the next column boundary in x and in y.
213  amrex::Real tmx = big, tmy = big;
214  if (stepx > 0) tmx = ((x_lo + (ci + 1) * dx) - x) / sx;
215  else if (stepx < 0) tmx = ((x_lo + ci * dx) - x) / sx;
216  if (stepy > 0) tmy = ((y_lo + (cj + 1) * dy) - y) / sy;
217  else if (stepy < 0) tmy = ((y_lo + cj * dy) - y) / sy;
218  amrex::Real t = 0.0;
219  const int max_iter = 4 * (nx + ny) + 8;
220  for (int it = 0; it < max_iter; ++it) {
221  // Column (ci, cj) with the ray entering at height z.
222  if (ci < 0 || ci >= nx) {
223  if (!per_x) return false;
224  ci = (ci % nx + nx) % nx;
225  }
226  if (cj < 0 || cj >= ny) {
227  if (!per_y) return false;
228  cj = (cj % ny + ny) % ny;
229  }
230  if (z + eps < column_top(col_top, ci, cj, i0, j0, bw, bh, z_ground, dz)) return true;
231  // Step to the next column.
232  if (tmx < tmy) { t = tmx; tmx += tdx; ci += stepx; }
233  else { t = tmy; tmy += tdy; cj += stepy; }
234  z = z0 + t0 * sz + t * sz;
235  if (z >= z_max) return false;
236  if (t > max_path) return false;
237  }
238  return false;
239 }
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:8
const Real dy
Definition: ERF_InitCustomPert_ABL.H:45
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
@ t
Definition: ERF_WSM6.H:272
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real column_top(const int *col_top, int ci, int cj, int i0, int j0, int bw, int bh, amrex::Real z_ground, amrex::Real dz)
Definition: ERF_IBSEBSolar.H:163
real(c_double), parameter t0
Definition: ERF_module_model_constants.F90:39

Referenced by IBFaceSet::compute_shortwave().

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

◆ ray_hit()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE int ibseb::ray_hit ( amrex::Real  x0,
amrex::Real  y0,
amrex::Real  z0,
amrex::Real  sx,
amrex::Real  sy,
amrex::Real  sz,
const int *  col_top,
int  nx,
int  ny,
int  i0,
int  j0,
int  bw,
int  bh,
amrex::Real  x_lo,
amrex::Real  y_lo,
amrex::Real  dx,
amrex::Real  dy,
amrex::Real  dz,
bool  per_x,
bool  per_y,
amrex::Real  z_ground,
amrex::Real  z_max,
amrex::Real  max_path 
)

Where a ray from a face ends: sky, ground or a building, for any direction (the hemisphere sampling of the view fractions). The same column walk as ray_blocked(), with the height test made direction-aware:

  • rising or level rays (sz >= 0) are blocked by a column whose top is above the entry height; a level ray that crosses max_path without a hit ends at the horizon, which counts as sky;
  • falling rays (sz < 0) are blocked by a solid column whose top is above the exit height (the ray descends inside the column) and reach the ground where the exit height is at or below z_ground.

Solid columns are those with a top above the ground. The domain is wrapped in periodic directions; leaving it in a non-periodic direction counts as sky for a rising ray and as ground for a falling one.

267 {
268  const amrex::Real eps = 1.0e-6 * amrex::min(dx, dy);
269  if (sz > 0.0 && z0 >= z_max) return RAY_SKY;
270  const amrex::Real t0 = 1.0e-3 * amrex::min(dx, dy);
271  amrex::Real x = x0 + t0 * sx, y = y0 + t0 * sy, z = z0 + t0 * sz;
272  int ci = static_cast<int>(std::floor((x - x_lo) / dx));
273  int cj = static_cast<int>(std::floor((y - y_lo) / dy));
274  const int stepx = (sx > 0.0) ? 1 : ((sx < 0.0) ? -1 : 0);
275  const int stepy = (sy > 0.0) ? 1 : ((sy < 0.0) ? -1 : 0);
276  const amrex::Real big = 1.0e30;
277  const amrex::Real tdx = (stepx != 0) ? dx / std::abs(sx) : big;
278  const amrex::Real tdy = (stepy != 0) ? dy / std::abs(sy) : big;
279  amrex::Real tmx = big, tmy = big;
280  if (stepx > 0) tmx = ((x_lo + (ci + 1) * dx) - x) / sx;
281  else if (stepx < 0) tmx = ((x_lo + ci * dx) - x) / sx;
282  if (stepy > 0) tmy = ((y_lo + (cj + 1) * dy) - y) / sy;
283  else if (stepy < 0) tmy = ((y_lo + cj * dy) - y) / sy;
284  if (stepx == 0 && stepy == 0) { // vertical ray
285  if (sz > 0.0) return RAY_SKY;
286  // straight down from a wall face never happens (the face is vertical);
287  // from a roof the hemisphere points up. Treat as ground for safety.
288  return RAY_GROUND;
289  }
290  amrex::Real t = 0.0;
291  const int max_iter = 4 * (nx + ny) + 8;
292  for (int it = 0; it < max_iter; ++it) {
293  if (ci < 0 || ci >= nx) {
294  if (!per_x) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
295  ci = (ci % nx + nx) % nx;
296  }
297  if (cj < 0 || cj >= ny) {
298  if (!per_y) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
299  cj = (cj % ny + ny) % ny;
300  }
301  const amrex::Real top = column_top(col_top, ci, cj, i0, j0, bw, bh, z_ground, dz);
302  const bool solid = (top > z_ground + eps);
303  const amrex::Real t_next = amrex::min(tmx, tmy);
304  const amrex::Real z_out = z0 + t0 * sz + t_next * sz;
305  if (sz >= 0.0) {
306  if (solid && z + eps < top) return RAY_BUILDING;
307  } else {
308  if (solid && z + eps < top) return RAY_BUILDING; // entered below the top
309  if (solid && z_out < top - eps) return RAY_BUILDING; // descends into it
310  if (z_out <= z_ground + eps) return RAY_GROUND;
311  }
312  if (tmx < tmy) { t = tmx; tmx += tdx; ci += stepx; }
313  else { t = tmy; tmy += tdy; cj += stepy; }
314  z = z_out;
315  if (sz > 0.0 && z >= z_max) return RAY_SKY;
316  if (t > max_path) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
317  }
318  return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
319 }

Referenced by IBFaceSet::compute_view_fractions().

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

◆ slab_skin_response()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void ibseb::slab_skin_response ( const amrex::Real T,
amrex::Real  T_int,
amrex::Real  k,
amrex::Real  rho_cp,
amrex::Real  dz,
amrex::Real  dt,
int  N,
amrex::Real a,
amrex::Real b 
)

Linear response of the implicit slab step to the skin temperature.

The step of advance_slab_dirichlet() is linear in T_skin, so the conduction it returns is G = a T_skin - b for the slab's current state, interior temperature and time step. The two coefficients come from two trial steps on copies of the column (T_skin = 0 and T_skin = 1), the same arithmetic as the real step, so a balance solved with this response and then advanced with the resulting skin temperature closes to rounding. The column itself is not modified.

Parameters
[in]TLayer temperatures [K] at the start of the step.
[out]aSlope of G with respect to T_skin [W/m2/K], positive.
[out]bOffset: G at T_skin = 0 is -b [W/m2]. Other arguments as in advance_slab_dirichlet().
108 {
109  if (N < 1 || N > SLAB_MAX_LAYERS) { a = 0.0; b = 0.0; return; }
111  for (int l = 0; l < N; ++l) { W[l] = T[l]; }
112  const amrex::Real G0 = advance_slab_dirichlet(W, 0.0, T_int, k, rho_cp, dz, dt, N);
113  for (int l = 0; l < N; ++l) { W[l] = T[l]; }
114  const amrex::Real G1 = advance_slab_dirichlet(W, 1.0, T_int, k, rho_cp, dz, dt, N);
115  a = G1 - G0;
116  b = -G0;
117 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real advance_slab_dirichlet(amrex::Real *T, amrex::Real T_skin, amrex::Real T_int, amrex::Real k, amrex::Real rho_cp, amrex::Real dz, amrex::Real dt, int N)
Definition: ERF_IBSEBSlab.H:51

Referenced by IBFaceSet::solve_balance().

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

◆ solar_azimuth()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::solar_azimuth ( amrex::Real  latitude_deg,
amrex::Real  decl,
amrex::Real  hour_angle,
amrex::Real  zenith 
)

Azimuth [rad] clockwise from north (east is pi/2) from the latitude, declination, hour angle and zenith; north when the sun is at the zenith or the site is at a pole, where the azimuth is undefined.

87 {
88  const amrex::Real lat = latitude_deg * PI / 180.0;
89  const amrex::Real sz = std::sin(zenith);
90  const amrex::Real cz = std::cos(zenith);
91  if (sz < 1.0e-6) { return 0.0; }
92  const amrex::Real cos_lat = std::cos(lat);
93  if (std::abs(cos_lat) < 1.0e-6) { return 0.0; }
94  // The hour angle is negative before solar noon, when the sun is east of
95  // south, hence the sign for the clockwise-from-north convention.
96  const amrex::Real sin_az = -std::sin(hour_angle) * std::cos(decl) / sz;
97  const amrex::Real cos_az = (std::sin(decl) - cz * std::sin(lat)) / (sz * cos_lat);
98  amrex::Real az = std::atan2(sin_az, cos_az);
99  if (az < 0.0) { az += 2.0 * PI; }
100  return az;
101 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ solar_declination()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::solar_declination ( amrex::Real  day_of_year)

Solar declination [rad] from the day of year (Spencer 1971).

36 {
37  const amrex::Real gamma = 2.0 * PI * (day_of_year - 1.0) / 365.25;
38  return 0.006918
39  - 0.399912 * std::cos(gamma) + 0.070257 * std::sin(gamma)
40  - 0.006758 * std::cos(2.0 * gamma) + 0.000907 * std::sin(2.0 * gamma)
41  - 0.00248 * std::cos(3.0 * gamma) + 0.00031 * std::sin(3.0 * gamma);
42 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ solar_hour_angle()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::solar_hour_angle ( amrex::Real  time_utc_s,
amrex::Real  longitude_deg,
amrex::Real  day_of_year 
)

Hour angle [rad] from the UTC time of day and the site: solar time is UTC plus one hour per 15 degrees of longitude (east positive) plus the equation of time, and the hour angle is 15 degrees per hour from solar noon, negative in the morning. The time zone plays no part (it would cancel).

61 {
62  amrex::Real t_utc_h = std::fmod(time_utc_s / 3600.0, 24.0);
63  if (t_utc_h < 0.0) { t_utc_h += 24.0; }
64  const amrex::Real t_solar_h = t_utc_h + longitude_deg / 15.0 + equation_of_time(day_of_year) / 60.0;
65  return 15.0 * (t_solar_h - 12.0) * PI / 180.0;
66 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real equation_of_time(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:46

Referenced by IBFaceSet::compute_shortwave().

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

◆ solar_zenith()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::solar_zenith ( amrex::Real  latitude_deg,
amrex::Real  decl,
amrex::Real  hour_angle 
)

Zenith angle [rad] from the latitude, declination and hour angle.

71 {
72  const amrex::Real lat = latitude_deg * PI / 180.0;
73  amrex::Real cz = std::sin(lat) * std::sin(decl) + std::cos(lat) * std::cos(decl) * std::cos(hour_angle);
74  if (cz < -1.0) { cz = -1.0; }
75  if (cz > 1.0) { cz = 1.0; }
76  return std::acos(cz);
77 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ solve_skin_balance()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::solve_skin_balance ( amrex::Real  T_start,
amrex::Real  SW_abs,
amrex::Real  Q_abs,
amrex::Real  LW_ext,
amrex::Real  emis,
amrex::Real  f_bldg,
amrex::Real  C_H,
amrex::Real  exner,
amrex::Real  theta_air,
amrex::Real  LE,
amrex::Real  slab_a,
amrex::Real  slab_b,
amrex::Real  T_min,
amrex::Real  T_max,
amrex::Real  max_step,
amrex::Real  tol,
int  max_iter,
int &  n_iter,
amrex::Real resid 
)

Solve the face balance for the skin temperature.

Parameters
[in]T_startStarting guess, the skin temperature of the previous step [K].
[in]SW_absAbsorbed shortwave [W/m2].
[in]Q_absAbsorbed external flux eps Q_ext [W/m2].
[in]LW_extIncoming longwave from sky and ground [W/m2].
[in]emisFace emissivity.
[in]f_bldgBuilding view fraction of the face.
[in]C_HSensible coefficient rho c_p kappa u* / ln(delta/z0h) [W/m2/K].
[in]exnerExner function of the fluid cell, T_a / theta_a.
[in]theta_airPotential temperature of the fluid cell [K].
[in]LELatent flux, frozen [W/m2].
[in]slab_a,slab_bSlab response G = a T_s - b [W/m2/K], [W/m2].
[in]T_min,T_maxBounds on the skin temperature [K].
[in]max_stepLargest Newton step [K].
[in]tolConvergence: stop when a step changes T_s by less than this [K].
[in]max_iterIteration cap.
[out]n_iterIterations taken.
[out]resid|F| at the returned temperature [W/m2]; non-zero when a bound was hit or the cap was reached.
Returns
The skin temperature [K], within the bounds.
88 {
89  using amrex::Real;
90  const Real e_eff = emis * (Real(1.0) - f_bldg) * SIGMA_SB; // effective emission coefficient
91  auto residual = [&](Real T) {
92  const Real T4 = T * T * T * T;
93  return SW_abs + Q_abs + emis * LW_ext - e_eff * T4
94  - C_H * (T / exner - theta_air) - LE - (slab_a * T - slab_b);
95  };
96  Real T = amrex::max(amrex::min(T_start, T_max), T_min);
97  n_iter = 0;
98  Real F = residual(T);
99  for (int it = 0; it < max_iter; ++it) {
100  ++n_iter;
101  const Real Fp = -Real(4.0) * e_eff * T * T * T - C_H / exner - slab_a;
102  Real step = -F / Fp; // Fp < 0 always
103  step = amrex::max(amrex::min(step, max_step), -max_step);
104  const Real T_new = amrex::max(amrex::min(T + step, T_max), T_min);
105  const Real dT = std::abs(T_new - T);
106  T = T_new;
107  F = residual(T);
108  if (dT < tol) break;
109  }
110  resid = std::abs(F);
111  return T;
112 }
constexpr amrex::Real SIGMA_SB
Stefan-Boltzmann constant [W/m2/K4].
Definition: ERF_IBSEBBalance.H:54

Referenced by IBFaceSet::solve_balance().

Here is the caller graph for this function:

◆ sun_vector()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void ibseb::sun_vector ( amrex::Real  zenith,
amrex::Real  azimuth,
amrex::Real sx,
amrex::Real sy,
amrex::Real sz 
)

Unit vector toward the sun from zenith and azimuth (east +x, north +y).

120 {
121  const amrex::Real s = std::sin(zenith);
122  sx = s * std::sin(azimuth);
123  sy = s * std::cos(azimuth);
124  sz = std::cos(zenith);
125 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

Variable Documentation

◆ SIGMA_SB

constexpr amrex::Real ibseb::SIGMA_SB = 5.670374419e-8
constexpr

Stefan-Boltzmann constant [W/m2/K4].

Referenced by IBFaceSet::compute_longwave(), IBFaceSet::solve_balance(), and solve_skin_balance().

◆ SLAB_MAX_LAYERS

constexpr int ibseb::SLAB_MAX_LAYERS = 32
constexpr

Largest number of layers the stack arrays allow. The kernels keep three such arrays per thread, so the bound is kept small for GPU occupancy; 32 layers resolve any wall (erf.ibseb.n_slab_layers is checked against it).

Referenced by advance_slab_dirichlet(), IBFaceSet::compute_ground(), slab_skin_response(), and IBFaceSet::solve_balance().