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 orbital_hour_angle (double calday, amrex::Real longitude_rad)
 
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 
263 : int { RAY_SKY = 0, RAY_GROUND = 1, RAY_BUILDING = 2 };
@ RAY_GROUND
Definition: ERF_IBSEBSolar.H:263
@ RAY_SKY
Definition: ERF_IBSEBSolar.H:263
@ RAY_BUILDING
Definition: ERF_IBSEBSolar.H:263

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:131
@ alpha
Definition: ERF_SLM.H:151
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
constexpr int SLAB_MAX_LAYERS
Definition: ERF_IBSEBSlab.H:28

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).

169 {
170  if (cos_zenith <= 1.0e-3) return 0.0;
171  const amrex::Real tr = std::pow(tau, 1.0 / cos_zenith);
172  return k_d * S0 * distance_factor * cos_zenith * (1.0 - tr);
173 }

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.

156 {
157  if (cos_zenith <= 1.0e-3) return 0.0;
158  return S0 * distance_factor * std::pow(tau, 1.0 / cos_zenith);
159 }

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.

187 {
188  ci -= i0; cj -= j0;
189  if (ci < 0 || ci >= bw || cj < 0 || cj >= bh) { return z_ground; }
190  const int kt = col_top[ci * bh + cj];
191  return (kt < 0) ? z_ground : z_ground + (kt + 1) * dz;
192 }
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.

131 {
132  const amrex::Real G = 2.0 * PI * (day_of_year - 1.0) / 365.0;
133  return 1.000110 + 0.034221 * std::cos(G) + 0.001280 * std::sin(G)
134  + 0.000719 * std::cos(2.0 * G) + 0.000077 * std::sin(2.0 * G);
135 }
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46

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).

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

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.

353 {
354  const amrex::Real u = (ie + 0.5) / n_el;
355  const amrex::Real theta = std::asin(std::sqrt(u));
356  const amrex::Real phi = 2.0 * PI * (ia + 0.5) / n_az;
357  const amrex::Real cn = std::cos(theta), ct = std::sin(theta) * std::cos(phi), st = std::sin(theta) * std::sin(phi);
358  // Normal and two tangents for each axis.
359  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};
360  n[dir] = nsign;
361  if (dir == 0) { t1[1] = 1.0; t2[2] = 1.0; }
362  else if (dir == 1) { t1[0] = 1.0; t2[2] = 1.0; }
363  else { t1[0] = 1.0; t2[1] = 1.0; }
364  sx = cn * n[0] + ct * t1[0] + st * t2[0];
365  sy = cn * n[1] + ct * t1[1] + st * t2[1];
366  sz = cn * n[2] + ct * t1[2] + st * t2[2];
367 }
@ theta
Definition: ERF_Kessler.H:26

Referenced by IBFaceSet::compute_view_fractions().

Here is the caller graph for this function:

◆ orbital_hour_angle()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::orbital_hour_angle ( double  calday,
amrex::Real  longitude_rad 
)

Hour angle [rad] of the orbital formula the radiation models use (orbital_cos_zenith_instant() of ERF_OrbCosZenith.H): its cos z = sin(lat) sin(decl) - cos(lat) cos(decl) cos(2 pi frac(calday) + lon) is the usual form with the hour angle 2 pi frac(calday) + lon - pi. The fraction of the calendar day is the UTC time of day and there is no equation of time, so local solar time is UTC plus one hour per 15 degrees of longitude. Wrapped to [-pi, pi), negative in the morning.

81 {
82  const double two_pi = 2.0 * double(PI);
83  double h = two_pi * (calday - std::floor(calday)) + double(longitude_rad) - double(PI);
84  h = std::fmod(h + double(PI), two_pi);
85  if (h < 0.0) { h += two_pi; }
86  return static_cast<amrex::Real>(h - double(PI));
87 }

Referenced by ERF::ibseb_set_two_stream_sun().

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).
220 {
221  if (sz <= 0.0) return true; // sun at or below the horizon
222  if (z0 >= z_max) return false; // nothing stands this high
223  const amrex::Real eps = 1.0e-6 * amrex::min(dx, dy);
224  const amrex::Real t0 = 1.0e-3 * amrex::min(dx, dy);
225  amrex::Real x = x0 + t0 * sx, y = y0 + t0 * sy, z = z0 + t0 * sz;
226  int ci = static_cast<int>(std::floor((x - x_lo) / dx));
227  int cj = static_cast<int>(std::floor((y - y_lo) / dy));
228  const int stepx = (sx > 0.0) ? 1 : ((sx < 0.0) ? -1 : 0);
229  const int stepy = (sy > 0.0) ? 1 : ((sy < 0.0) ? -1 : 0);
230  const amrex::Real big = 1.0e30;
231  const amrex::Real tdx = (stepx != 0) ? dx / std::abs(sx) : big;
232  const amrex::Real tdy = (stepy != 0) ? dy / std::abs(sy) : big;
233  // Distance along the ray to the next column boundary in x and in y.
234  amrex::Real tmx = big, tmy = big;
235  if (stepx > 0) tmx = ((x_lo + (ci + 1) * dx) - x) / sx;
236  else if (stepx < 0) tmx = ((x_lo + ci * dx) - x) / sx;
237  if (stepy > 0) tmy = ((y_lo + (cj + 1) * dy) - y) / sy;
238  else if (stepy < 0) tmy = ((y_lo + cj * dy) - y) / sy;
239  amrex::Real t = 0.0;
240  const int max_iter = 4 * (nx + ny) + 8;
241  for (int it = 0; it < max_iter; ++it) {
242  // Column (ci, cj) with the ray entering at height z.
243  if (ci < 0 || ci >= nx) {
244  if (!per_x) return false;
245  ci = (ci % nx + nx) % nx;
246  }
247  if (cj < 0 || cj >= ny) {
248  if (!per_y) return false;
249  cj = (cj % ny + ny) % ny;
250  }
251  if (z + eps < column_top(col_top, ci, cj, i0, j0, bw, bh, z_ground, dz)) return true;
252  // Step to the next column.
253  if (tmx < tmy) { t = tmx; tmx += tdx; ci += stepx; }
254  else { t = tmy; tmy += tdy; cj += stepy; }
255  z = z0 + t0 * sz + t * sz;
256  if (z >= z_max) return false;
257  if (t > max_path) return false;
258  }
259  return false;
260 }
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:184
real(c_double), parameter t0
Definition: ERF_module_model_constants.F90:39

Referenced by IBFaceSet::compute_shortwave(), and IBFaceSet::compute_shortwave_two_stream().

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.

288 {
289  const amrex::Real eps = 1.0e-6 * amrex::min(dx, dy);
290  if (sz > 0.0 && z0 >= z_max) return RAY_SKY;
291  const amrex::Real t0 = 1.0e-3 * amrex::min(dx, dy);
292  amrex::Real x = x0 + t0 * sx, y = y0 + t0 * sy, z = z0 + t0 * sz;
293  int ci = static_cast<int>(std::floor((x - x_lo) / dx));
294  int cj = static_cast<int>(std::floor((y - y_lo) / dy));
295  const int stepx = (sx > 0.0) ? 1 : ((sx < 0.0) ? -1 : 0);
296  const int stepy = (sy > 0.0) ? 1 : ((sy < 0.0) ? -1 : 0);
297  const amrex::Real big = 1.0e30;
298  const amrex::Real tdx = (stepx != 0) ? dx / std::abs(sx) : big;
299  const amrex::Real tdy = (stepy != 0) ? dy / std::abs(sy) : big;
300  amrex::Real tmx = big, tmy = big;
301  if (stepx > 0) tmx = ((x_lo + (ci + 1) * dx) - x) / sx;
302  else if (stepx < 0) tmx = ((x_lo + ci * dx) - x) / sx;
303  if (stepy > 0) tmy = ((y_lo + (cj + 1) * dy) - y) / sy;
304  else if (stepy < 0) tmy = ((y_lo + cj * dy) - y) / sy;
305  if (stepx == 0 && stepy == 0) { // vertical ray
306  if (sz > 0.0) return RAY_SKY;
307  // straight down from a wall face never happens (the face is vertical);
308  // from a roof the hemisphere points up. Treat as ground for safety.
309  return RAY_GROUND;
310  }
311  amrex::Real t = 0.0;
312  const int max_iter = 4 * (nx + ny) + 8;
313  for (int it = 0; it < max_iter; ++it) {
314  if (ci < 0 || ci >= nx) {
315  if (!per_x) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
316  ci = (ci % nx + nx) % nx;
317  }
318  if (cj < 0 || cj >= ny) {
319  if (!per_y) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
320  cj = (cj % ny + ny) % ny;
321  }
322  const amrex::Real top = column_top(col_top, ci, cj, i0, j0, bw, bh, z_ground, dz);
323  const bool solid = (top > z_ground + eps);
324  const amrex::Real t_next = amrex::min(tmx, tmy);
325  const amrex::Real z_out = z0 + t0 * sz + t_next * sz;
326  if (sz >= 0.0) {
327  if (solid && z + eps < top) return RAY_BUILDING;
328  } else {
329  if (solid && z + eps < top) return RAY_BUILDING; // entered below the top
330  if (solid && z_out < top - eps) return RAY_BUILDING; // descends into it
331  if (z_out <= z_ground + eps) return RAY_GROUND;
332  }
333  if (tmx < tmy) { t = tmx; tmx += tdx; ci += stepx; }
334  else { t = tmy; tmy += tdy; cj += stepy; }
335  z = z_out;
336  if (sz > 0.0 && z >= z_max) return RAY_SKY;
337  if (t > max_path) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
338  }
339  return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
340 }

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; }
110  // Zero-initialized: only the first N entries are ever used, but the
111  // compiler cannot see that through the inlined solve and warns otherwise.
113  for (int l = 0; l < N; ++l) { W[l] = T[l]; }
114  const amrex::Real G0 = advance_slab_dirichlet(W, 0.0, T_int, k, rho_cp, dz, dt, N);
115  for (int l = 0; l < N; ++l) { W[l] = T[l]; }
116  const amrex::Real G1 = advance_slab_dirichlet(W, 1.0, T_int, k, rho_cp, dz, dt, N);
117  a = G1 - G0;
118  b = -G0;
119 }
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
@ W
Definition: ERF_ObsNudgingSeries.H:36

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.

108 {
109  const amrex::Real lat = latitude_deg * PI / 180.0;
110  const amrex::Real sz = std::sin(zenith);
111  const amrex::Real cz = std::cos(zenith);
112  if (sz < 1.0e-6) { return 0.0; }
113  const amrex::Real cos_lat = std::cos(lat);
114  if (std::abs(cos_lat) < 1.0e-6) { return 0.0; }
115  // The hour angle is negative before solar noon, when the sun is east of
116  // south, hence the sign for the clockwise-from-north convention.
117  const amrex::Real sin_az = -std::sin(hour_angle) * std::cos(decl) / sz;
118  const amrex::Real cos_az = (std::sin(decl) - cz * std::sin(lat)) / (sz * cos_lat);
119  amrex::Real az = std::atan2(sin_az, cos_az);
120  if (az < 0.0) { az += 2.0 * PI; }
121  return az;
122 }

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).

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

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).

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

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.

92 {
93  const amrex::Real lat = latitude_deg * PI / 180.0;
94  amrex::Real cz = std::sin(lat) * std::sin(decl) + std::cos(lat) * std::cos(decl) * std::cos(hour_angle);
95  if (cz < -1.0) { cz = -1.0; }
96  if (cz > 1.0) { cz = 1.0; }
97  return std::acos(cz);
98 }

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).

141 {
142  const amrex::Real s = std::sin(zenith);
143  sx = s * std::sin(azimuth);
144  sy = s * std::cos(azimuth);
145  sz = std::cos(zenith);
146 }

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

◆ 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().