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

Classes

struct  RayMap
 

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 spencer_day_angle (amrex::Real day_of_year)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real day_of_year_at (amrex::Real day0, amrex::Real t_utc_s)
 
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)
 
int ray_halo_cells (amrex::Real height, amrex::Real dx, amrex::Real sz_min)
 
amrex::Real hemisphere_min_sz (int n_az, int n_el)
 
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 RayMap &m)
 
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 RayMap &m)
 
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 
351 : int { RAY_SKY = 0, RAY_GROUND = 1, RAY_BUILDING = 2 };
@ RAY_GROUND
Definition: ERF_IBSEBSolar.H:351
@ RAY_SKY
Definition: ERF_IBSEBSolar.H:351
@ RAY_BUILDING
Definition: ERF_IBSEBSolar.H:351

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:168
@ alpha
Definition: ERF_SLM.H:151
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
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).

194 {
195  if (cos_zenith <= 1.0e-3) return 0.0;
196  const amrex::Real tr = std::pow(tau, 1.0 / cos_zenith);
197  return k_d * S0 * distance_factor * cos_zenith * (1.0 - tr);
198 }

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.

181 {
182  if (cos_zenith <= 1.0e-3) return 0.0;
183  return S0 * distance_factor * std::pow(tau, 1.0 / cos_zenith);
184 }

Referenced by IBFaceSet::compute_shortwave().

Here is the caller graph for this function:

◆ day_of_year_at()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real ibseb::day_of_year_at ( amrex::Real  day0,
amrex::Real  t_utc_s 
)

Day of year at a UTC time: the start day plus the whole days elapsed since 00:00 UTC of that day (t_utc_s may exceed 86400 s in a multi-day run). The Spencer series repeat every 365 days, so the result needs no wrapping.

56 {
57  return day0 + std::floor(t_utc_s / 86400.0);
58 }

Referenced by IBFaceSet::compute_shortwave().

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.

156 {
157  const amrex::Real G = spencer_day_angle(day_of_year);
158  return 1.000110 + 0.034221 * std::cos(G) + 0.001280 * std::sin(G)
159  + 0.000719 * std::cos(2.0 * G) + 0.000077 * std::sin(2.0 * G);
160 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real spencer_day_angle(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:44

Referenced by IBFaceSet::compute_shortwave().

Here is the call graph for this function:
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).

74 {
75  const amrex::Real gamma = spencer_day_angle(day_of_year);
76  return 229.18 * (0.000075 + 0.001868 * std::cos(gamma) - 0.032077 * std::sin(gamma)
77  - 0.014615 * std::cos(2.0 * gamma) - 0.040849 * std::sin(2.0 * gamma));
78 }

Referenced by solar_hour_angle().

Here is the call graph for this function:
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.

439 {
440  const amrex::Real u = (ie + 0.5) / n_el;
441  const amrex::Real theta = std::asin(std::sqrt(u));
442  const amrex::Real phi = 2.0 * PI * (ia + 0.5) / n_az;
443  const amrex::Real cn = std::cos(theta), ct = std::sin(theta) * std::cos(phi), st = std::sin(theta) * std::sin(phi);
444  // Normal and two tangents for each axis.
445  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};
446  n[dir] = nsign;
447  if (dir == 0) { t1[1] = 1.0; t2[2] = 1.0; }
448  else if (dir == 1) { t1[0] = 1.0; t2[2] = 1.0; }
449  else { t1[0] = 1.0; t2[1] = 1.0; }
450  sx = cn * n[0] + ct * t1[0] + st * t2[0];
451  sy = cn * n[1] + ct * t1[1] + st * t2[1];
452  sz = cn * n[2] + ct * t1[2] + st * t2[2];
453 }
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46
@ theta
Definition: ERF_Kessler.H:26
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real phi(amrex::Real d, amrex::Real z0, amrex::Real psi) noexcept
Definition: ERF_ImmersedWallCell.H:142

Referenced by IBFaceSet::compute_view_fractions().

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

◆ hemisphere_min_sz()

amrex::Real ibseb::hemisphere_min_sz ( int  n_az,
int  n_el 
)
inline

Sine of the elevation of the shallowest ray of the hemisphere sampling (hemisphere_direction()) with n_az azimuths and n_el elevations: on a roof the ray furthest from the normal, cos(theta_max); on a wall, where a ray's sine of elevation is sin(theta) sin(phi), the ray nearest the normal at the azimuth nearest the horizontal, sin(theta_min) sin(pi / n_az). With an odd n_az a wall has level rays (phi = pi), which the walk follows for two rounds of the map.

282 {
283  const amrex::Real u_min = 0.5 / n_el, u_max = (n_el - 0.5) / n_el;
284  const amrex::Real roof = std::sqrt(1.0 - u_max);
285  const amrex::Real wall = std::sqrt(u_min) * std::sin(PI / n_az);
286  return amrex::min(roof, wall);
287 }

Referenced by IBFaceSet::build_ray_map().

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.

106 {
107  const double two_pi = 2.0 * double(PI);
108  double h = two_pi * (calday - std::floor(calday)) + double(longitude_rad) - double(PI);
109  h = std::fmod(h + double(PI), two_pi);
110  if (h < 0.0) { h += two_pi; }
111  return static_cast<amrex::Real>(h - double(PI));
112 }

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 RayMap &  m 
)

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

Buildings are the column tops of the rank's ray map (RayMap), 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 or leaves the map (the far field is open ground; in a periodic direction the map spans, the ray wraps round instead).

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).
311 {
312  if (sz <= 0.0) return true; // sun at or below the horizon
313  if (z0 >= m.z_max) return false; // nothing stands this high
314  const amrex::Real dx = m.dx, dy = m.dy;
315  const amrex::Real eps = 1.0e-6 * amrex::min(dx, dy);
316  const amrex::Real t0 = 1.0e-3 * amrex::min(dx, dy);
317  amrex::Real x = x0 + t0 * sx, y = y0 + t0 * sy, z = z0 + t0 * sz;
318  int ci = m.ilo + static_cast<int>(std::floor((x - m.x_lo) / dx));
319  int cj = m.jlo + static_cast<int>(std::floor((y - m.y_lo) / dy));
320  const int stepx = (sx > 0.0) ? 1 : ((sx < 0.0) ? -1 : 0);
321  const int stepy = (sy > 0.0) ? 1 : ((sy < 0.0) ? -1 : 0);
322  const amrex::Real big = 1.0e30;
323  const amrex::Real tdx = (stepx != 0) ? dx / std::abs(sx) : big;
324  const amrex::Real tdy = (stepy != 0) ? dy / std::abs(sy) : big;
325  // Distance along the ray to the next column boundary in x and in y.
326  amrex::Real tmx = big, tmy = big;
327  if (stepx > 0) tmx = ((m.x_lo + (ci - m.ilo + 1) * dx) - x) / sx;
328  else if (stepx < 0) tmx = ((m.x_lo + (ci - m.ilo) * dx) - x) / sx;
329  if (stepy > 0) tmy = ((m.y_lo + (cj - m.jlo + 1) * dy) - y) / sy;
330  else if (stepy < 0) tmy = ((m.y_lo + (cj - m.jlo) * dy) - y) / sy;
331  // Columns crossed before the ray passes the tallest one, plus a margin,
332  // and never more than the halo's reach (see RayMap).
333  const amrex::Real t_max = (m.z_max - z) / sz;
334  const amrex::Real steps = amrex::min(t_max * (std::abs(sx) / dx + std::abs(sy) / dy), amrex::Real(m.max_steps));
335  const int max_iter = static_cast<int>(amrex::min(amrex::Real(1.0e9), steps)) + 8;
336  for (int it = 0; it < max_iter; ++it) {
337  // Column (ci, cj) with the ray entering at height z.
338  if (!m.locate(ci, cj)) return false;
339  if (z + eps < m.column_top(ci, cj)) return true;
340  // Step to the next column.
341  amrex::Real t;
342  if (tmx < tmy) { t = tmx; tmx += tdx; ci += stepx; }
343  else { t = tmy; tmy += tdy; cj += stepy; }
344  z = z0 + t0 * sz + t * sz;
345  if (z >= m.z_max) return false;
346  }
347  return false;
348 }
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
const Real dy
Definition: ERF_InitCustomPert_ABL.H:45
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
@ t
Definition: ERF_WSM6.H:293
real(c_double), parameter t0
Definition: ERF_module_model_constants.F90:39
real(c_double), private ci
Definition: ERF_module_mp_morr_two_moment.F90:203

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_halo_cells()

int ibseb::ray_halo_cells ( amrex::Real  height,
amrex::Real  dx,
amrex::Real  sz_min 
)
inline

Halo of a ray map, in cells: how far a ray no shallower than sz_min (the sine of its elevation) can travel horizontally before it rises above height or falls that far, height / tan(elevation), rounded up and with one cell for the column it starts in. At least one cell, so a face always finds the column of its solid neighbour; a level ray would reach for ever, which the caller caps at the domain.

263 {
264  if (height <= 0.0) { return 1; }
265  const amrex::Real cz = std::sqrt(amrex::max(amrex::Real(0.0), amrex::Real(1.0) - sz_min * sz_min));
266  if (sz_min <= 0.0 || cz <= 0.0) { return std::numeric_limits<int>::max() / 4; }
267  const amrex::Real reach = height * cz / sz_min / dx;
268  if (reach >= amrex::Real(std::numeric_limits<int>::max() / 8)) { return std::numeric_limits<int>::max() / 4; }
269  return amrex::max(1, static_cast<int>(std::ceil(reach)) + 1);
270 }
Real height
Definition: ERF_InitCustomPert_SquallLine.H:37

Referenced by IBFaceSet::build_ray_map().

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 RayMap &  m 
)

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 leaves the map, or has crossed the domain twice where the map wraps, 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. Leaving the map counts as sky for a rising ray and as ground for a falling one: the far field is open ground. Where the map spans a periodic direction the ray wraps round instead.

373 {
374  const amrex::Real dx = m.dx, dy = m.dy;
375  const amrex::Real eps = 1.0e-6 * amrex::min(dx, dy);
376  if (sz > 0.0 && z0 >= m.z_max) return RAY_SKY;
377  const amrex::Real t0 = 1.0e-3 * amrex::min(dx, dy);
378  amrex::Real x = x0 + t0 * sx, y = y0 + t0 * sy, z = z0 + t0 * sz;
379  int ci = m.ilo + static_cast<int>(std::floor((x - m.x_lo) / dx));
380  int cj = m.jlo + static_cast<int>(std::floor((y - m.y_lo) / dy));
381  const int stepx = (sx > 0.0) ? 1 : ((sx < 0.0) ? -1 : 0);
382  const int stepy = (sy > 0.0) ? 1 : ((sy < 0.0) ? -1 : 0);
383  const amrex::Real big = 1.0e30;
384  const amrex::Real tdx = (stepx != 0) ? dx / std::abs(sx) : big;
385  const amrex::Real tdy = (stepy != 0) ? dy / std::abs(sy) : big;
386  amrex::Real tmx = big, tmy = big;
387  if (stepx > 0) tmx = ((m.x_lo + (ci - m.ilo + 1) * dx) - x) / sx;
388  else if (stepx < 0) tmx = ((m.x_lo + (ci - m.ilo) * dx) - x) / sx;
389  if (stepy > 0) tmy = ((m.y_lo + (cj - m.jlo + 1) * dy) - y) / sy;
390  else if (stepy < 0) tmy = ((m.y_lo + (cj - m.jlo) * dy) - y) / sy;
391  if (stepx == 0 && stepy == 0) { // vertical ray
392  if (sz > 0.0) return RAY_SKY;
393  // straight down from a wall face never happens (the face is vertical);
394  // from a roof the hemisphere points up. Treat as ground for safety.
395  return RAY_GROUND;
396  }
397  // Columns crossed before the ray passes the tallest column or reaches
398  // the ground, plus a margin, and never more than the halo's reach (see
399  // RayMap). A ray within 1e-9 of level (phi = pi of an odd n_az gives sz
400  // of 1e-16) is a level ray and gets two rounds of the map.
401  const bool level = (std::abs(sz) < 1.0e-9);
402  const amrex::Real t_max = (sz > 0.0) ? (m.z_max - z) / sz : ((sz < 0.0) ? (z - m.z_ground) / (-sz) : big);
403  const amrex::Real rounds = amrex::Real(2 * ((m.top.end[0] - m.top.begin[0]) + (m.top.end[1] - m.top.begin[1])));
404  const amrex::Real steps = level ? rounds
405  : amrex::min(t_max * (std::abs(sx) / dx + std::abs(sy) / dy), amrex::Real(m.max_steps));
406  const int max_iter = static_cast<int>(amrex::min(amrex::Real(1.0e9), steps)) + 8;
407  for (int it = 0; it < max_iter; ++it) {
408  if (!m.locate(ci, cj)) return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
409  const amrex::Real top = m.column_top(ci, cj);
410  const bool solid = (top > m.z_ground + eps);
411  const amrex::Real t_next = amrex::min(tmx, tmy);
412  const amrex::Real z_out = z0 + t0 * sz + t_next * sz;
413  if (sz >= 0.0) {
414  if (solid && z + eps < top) return RAY_BUILDING;
415  } else {
416  if (solid && z + eps < top) return RAY_BUILDING; // entered below the top
417  if (solid && z_out < top - eps) return RAY_BUILDING; // descends into it
418  if (z_out <= m.z_ground + eps) return RAY_GROUND;
419  }
420  if (tmx < tmy) { tmx += tdx; ci += stepx; }
421  else { tmy += tdy; cj += stepy; }
422  z = z_out;
423  if (sz > 0.0 && z >= m.z_max) return RAY_SKY;
424  }
425  return (sz >= 0.0) ? RAY_SKY : RAY_GROUND;
426 }

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.

133 {
134  const amrex::Real lat = latitude_deg * PI / 180.0;
135  const amrex::Real sz = std::sin(zenith);
136  const amrex::Real cz = std::cos(zenith);
137  if (sz < 1.0e-6) { return 0.0; }
138  const amrex::Real cos_lat = std::cos(lat);
139  if (std::abs(cos_lat) < 1.0e-6) { return 0.0; }
140  // The hour angle is negative before solar noon, when the sun is east of
141  // south, hence the sign for the clockwise-from-north convention.
142  const amrex::Real sin_az = -std::sin(hour_angle) * std::cos(decl) / sz;
143  const amrex::Real cos_az = (std::sin(decl) - cz * std::sin(lat)) / (sz * cos_lat);
144  amrex::Real az = std::atan2(sin_az, cos_az);
145  if (az < 0.0) { az += 2.0 * PI; }
146  return az;
147 }

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

63 {
64  const amrex::Real gamma = spencer_day_angle(day_of_year);
65  return 0.006918
66  - 0.399912 * std::cos(gamma) + 0.070257 * std::sin(gamma)
67  - 0.006758 * std::cos(2.0 * gamma) + 0.000907 * std::sin(2.0 * gamma)
68  - 0.002697 * std::cos(3.0 * gamma) + 0.00148 * std::sin(3.0 * gamma);
69 }

Referenced by IBFaceSet::compute_shortwave().

Here is the call graph for this function:
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).

88 {
89  amrex::Real t_utc_h = std::fmod(time_utc_s / 3600.0, 24.0);
90  if (t_utc_h < 0.0) { t_utc_h += 24.0; }
91  const amrex::Real t_solar_h = t_utc_h + longitude_deg / 15.0 + equation_of_time(day_of_year) / 60.0;
92  return 15.0 * (t_solar_h - 12.0) * PI / 180.0;
93 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real equation_of_time(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:73

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.

117 {
118  const amrex::Real lat = latitude_deg * PI / 180.0;
119  amrex::Real cz = std::sin(lat) * std::sin(decl) + std::cos(lat) * std::cos(decl) * std::cos(hour_angle);
120  if (cz < -1.0) { cz = -1.0; }
121  if (cz > 1.0) { cz = 1.0; }
122  return std::acos(cz);
123 }

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:

◆ spencer_day_angle()

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

Day angle [rad] of Spencer's (1971) series, 2 pi (day - 1) / 365. The series' coefficients were fitted with this day angle; the declination, the equation of time and the Earth-Sun distance below all use it, so day 366 gives the same values as day 1.

45 {
46  return 2.0 * PI * (day_of_year - 1.0) / 365.0;
47 }

Referenced by earth_sun_distance_factor(), equation_of_time(), and solar_declination().

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

166 {
167  const amrex::Real s = std::sin(zenith);
168  sx = s * std::sin(azimuth);
169  sy = s * std::cos(azimuth);
170  sz = std::cos(zenith);
171 }

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