ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_IBSEBSolar.H
Go to the documentation of this file.
1 #ifndef ERF_IBSEB_SOLAR_H
2 #define ERF_IBSEB_SOLAR_H
3 
4 #include <AMReX_REAL.H>
5 #include <AMReX_GpuQualifiers.H>
6 #include <AMReX_Algorithm.H>
7 #include <AMReX_Array4.H>
8 #include <limits>
10 #include <cmath>
11 
12 /**
13  * \file ERF_IBSEBSolar.H
14  * \brief Sun position, clear-sky shortwave, and the ray cast that decides
15  * whether a building face is in shadow.
16  *
17  * The solar geometry (Spencer's declination and equation of time, the hour
18  * angle from UTC and the site longitude, the zenith, the clockwise-from-north
19  * azimuth, the Earth-Sun distance) is kept here: the radiation models use
20  * the CESM orbital formulation of ERF_OrbCosZenith.H, which gives no
21  * azimuth and has no equation of time; orbital_hour_angle() turns its
22  * calendar day into the hour angle these functions take, for
23  * erf.ibseb.sun_mode = two_stream. The clear-sky formulas follow Bird (1984) as used by the SLUCM
24  * branch. The ray cast is new here.
25  *
26  * **Sun vector convention.** ``ibseb_sun_vector()`` returns the unit vector
27  * from a surface toward the sun, with azimuth measured clockwise from north:
28  * east is +x, north is +y, so ``s = (sin z sin a, sin z cos a, cos z)`` with
29  * z the zenith angle and a the azimuth. A face with outward normal n receives
30  * direct beam only when ``n . s > 0``.
31  *
32  * References: Spencer (1971); Duffie and Beckman (1991); Iqbal (1983);
33  * Bird and Hulstrom (1981).
34  */
35 namespace ibseb {
36 
37 /**
38  * Day angle [rad] of Spencer's (1971) series, 2 pi (day - 1) / 365. The
39  * series' coefficients were fitted with this day angle; the declination, the
40  * equation of time and the Earth-Sun distance below all use it, so day 366
41  * gives the same values as day 1.
42  */
43 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
45 {
46  return 2.0 * PI * (day_of_year - 1.0) / 365.0;
47 }
48 
49 /**
50  * Day of year at a UTC time: the start day plus the whole days elapsed since
51  * 00:00 UTC of that day (t_utc_s may exceed 86400 s in a multi-day run). The
52  * Spencer series repeat every 365 days, so the result needs no wrapping.
53  */
54 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
56 {
57  return day0 + std::floor(t_utc_s / 86400.0);
58 }
59 
60 /** Solar declination [rad] from the day of year (Spencer 1971). */
61 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
70 
71 /** Equation of time [minutes] from the day of year (Spencer 1971). */
72 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
79 
80 /**
81  * Hour angle [rad] from the UTC time of day and the site: solar time is UTC
82  * plus one hour per 15 degrees of longitude (east positive) plus the equation
83  * of time, and the hour angle is 15 degrees per hour from solar noon,
84  * negative in the morning. The time zone plays no part (it would cancel).
85  */
86 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
87 amrex::Real solar_hour_angle (amrex::Real time_utc_s, amrex::Real longitude_deg, amrex::Real day_of_year)
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 }
94 
95 /**
96  * Hour angle [rad] of the orbital formula the radiation models use
97  * (orbital_cos_zenith_instant() of ERF_OrbCosZenith.H): its
98  * ``cos z = sin(lat) sin(decl) - cos(lat) cos(decl) cos(2 pi frac(calday) + lon)``
99  * is the usual form with the hour angle ``2 pi frac(calday) + lon - pi``. The
100  * fraction of the calendar day is the UTC time of day and there is no equation
101  * of time, so local solar time is UTC plus one hour per 15 degrees of
102  * longitude. Wrapped to [-pi, pi), negative in the morning.
103  */
104 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
105 amrex::Real orbital_hour_angle (double calday, amrex::Real longitude_rad)
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 }
113 
114 /** Zenith angle [rad] from the latitude, declination and hour angle. */
115 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
124 
125 /**
126  * Azimuth [rad] clockwise from north (east is pi/2) from the latitude,
127  * declination, hour angle and zenith; north when the sun is at the zenith or
128  * the site is at a pole, where the azimuth is undefined.
129  */
130 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
132  amrex::Real hour_angle, amrex::Real zenith)
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 }
148 
149 /**
150  * Earth-Sun distance factor (d0/d)^2 that scales the solar constant for the
151  * day of year (Spencer 1971): about 1.034 at perihelion in early January,
152  * 0.967 at aphelion in early July, annual mean 1.
153  */
154 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
161 
162 /** Unit vector toward the sun from zenith and azimuth (east +x, north +y). */
163 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
164 void sun_vector (amrex::Real zenith, amrex::Real azimuth,
165  amrex::Real& sx, amrex::Real& sy, amrex::Real& sz)
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 }
172 
173 /**
174  * Clear-sky direct-normal irradiance [W/m2] (Bird form): the solar constant,
175  * corrected for the Earth-Sun distance, attenuated by ``tau^(1/cos z)``.
176  * Zero when the sun is below the horizon.
177  */
178 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
180  amrex::Real distance_factor)
181 {
182  if (cos_zenith <= 1.0e-3) return 0.0;
183  return S0 * distance_factor * std::pow(tau, 1.0 / cos_zenith);
184 }
185 
186 /**
187  * Clear-sky diffuse irradiance on a horizontal surface [W/m2]: a fraction
188  * ``k_d`` of what the direct beam lost to the atmosphere reaches the ground
189  * as diffuse light (Liu and Jordan form).
190  */
191 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
193  amrex::Real distance_factor, amrex::Real k_d)
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 }
199 
200 /**
201  * The column tops a ray cast on one rank sees: a k-collapsed map over this
202  * rank's patch of the level (its boxes grown by the halo of
203  * IBFaceSet::build_ray_map()), with the domain and the grid it is drawn on.
204  * An entry is the domain-relative k of the highest solid cell of its column,
205  * -1 where the column is fluid; column_top() turns it into a height. The
206  * patch holds the columns of other ranks and the periodic images within the
207  * halo, so the walk needs no communication. Where ``wrap_x`` (``wrap_y``) is
208  * set the patch spans the whole periodic domain in x (y) and a ray that
209  * leaves it comes back in on the other side; otherwise a ray that leaves the
210  * patch has left the map: the far field is open ground (see locate()). A
211  * ray is followed for as many columns as the halo's reach holds
212  * (``max_steps``), which is every column a ray no shallower than the map's
213  * floor can cross before it clears the tallest column or reaches the
214  * ground; a shallower ray stops there too, so a building beyond the halo's
215  * reach never shades, whether the map wraps or not. A level ray is followed
216  * for two rounds of the map and then counts as sky.
217  */
218 struct RayMap
219 {
220  amrex::Array4<const int> top; ///< Column tops over the patch (any k index; use ``top.begin[2]``)
221  int ilo = 0, jlo = 0; ///< Low corner of the domain, in cells
222  int nx = 0, ny = 0; ///< Columns of the domain in x and y
223  bool wrap_x = false, wrap_y = false;///< The patch spans the periodic domain in x / y
224  amrex::Real x_lo = 0.0, y_lo = 0.0; ///< Physical position of the domain's low corner [m]
225  amrex::Real dx = 0.0, dy = 0.0, dz = 0.0; ///< Cell sizes [m]
226  amrex::Real z_ground = 0.0; ///< Ground height [m]
227  amrex::Real z_max = 0.0; ///< Tallest column the map holds [m]; a ray above it meets nothing
228  int max_steps = 0; ///< Columns a ray at the map's floor elevation crosses before it clears z_max (the halo's reach in x plus y)
229 
230  /** Whether the map holds column ``(ci, cj)`` (wrapped into the patch where the patch spans the domain). */
231  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
232  bool locate (int& ci, int& cj) const noexcept
233  {
234  if (ci < top.begin[0] || ci >= top.end[0]) {
235  if (!wrap_x) { return false; }
236  ci = top.begin[0] + ((ci - top.begin[0]) % nx + nx) % nx;
237  }
238  if (cj < top.begin[1] || cj >= top.end[1]) {
239  if (!wrap_y) { return false; }
240  cj = top.begin[1] + ((cj - top.begin[1]) % ny + ny) % ny;
241  }
242  return true;
243  }
244 
245  /** Top height of column ``(ci, cj)`` of the map (the ground where it is fluid). */
246  AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
247  amrex::Real column_top (int ci, int cj) const noexcept
248  {
249  const int kt = top(ci, cj, top.begin[2]);
250  return (kt < 0) ? z_ground : z_ground + (kt + 1) * dz;
251  }
252 };
253 
254 /**
255  * Halo of a ray map, in cells: how far a ray no shallower than ``sz_min``
256  * (the sine of its elevation) can travel horizontally before it rises above
257  * ``height`` or falls that far, ``height / tan(elevation)``, rounded up and
258  * with one cell for the column it starts in. At least one cell, so a face
259  * always finds the column of its solid neighbour; a level ray would reach
260  * for ever, which the caller caps at the domain.
261  */
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 }
271 
272 /**
273  * Sine of the elevation of the shallowest ray of the hemisphere sampling
274  * (hemisphere_direction()) with ``n_az`` azimuths and ``n_el`` elevations:
275  * on a roof the ray furthest from the normal, ``cos(theta_max)``; on a wall,
276  * where a ray's sine of elevation is ``sin(theta) sin(phi)``, the ray
277  * nearest the normal at the azimuth nearest the horizontal,
278  * ``sin(theta_min) sin(pi / n_az)``. With an odd ``n_az`` a wall has level
279  * rays (``phi = pi``), which the walk follows for two rounds of the map.
280  */
281 inline amrex::Real hemisphere_min_sz (int n_az, int n_el)
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 }
288 
289 /**
290  * Whether the ray from a face toward the sun hits a building.
291  *
292  * Buildings are the column tops of the rank's ray map (RayMap), so a ray is
293  * blocked wherever its height on entering a column is below that column's
294  * top: buildings stand on the ground, so the lowest point of the ray inside
295  * a column is at its entry, and the height only grows along a ray toward a
296  * sun above the horizon. The walk over the columns is a 2D digital
297  * differential analyser. It stops when the ray rises above the tallest
298  * column or leaves the map (the far field is open ground; in a periodic
299  * direction the map spans, the ray wraps round instead).
300  *
301  * The start point is nudged a little along the ray so a face does not
302  * test its own column at its own height: a wall starts in its fluid cell's
303  * column, whose top is below the face (no overhangs), and a roof starts a
304  * hair above its own top.
305  *
306  * @return true when blocked (the face is in shadow).
307  */
308 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
310  amrex::Real sx, amrex::Real sy, amrex::Real sz, const RayMap& m)
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 }
349 
350 /// What a ray from a face ends on.
351 enum RayHit : int { RAY_SKY = 0, RAY_GROUND = 1, RAY_BUILDING = 2 };
352 
353 /**
354  * Where a ray from a face ends: sky, ground or a building, for any
355  * direction (the hemisphere sampling of the view fractions). The same column walk as
356  * ray_blocked(), with the height test made direction-aware:
357  *
358  * - rising or level rays (``sz >= 0``) are blocked by a column whose top is
359  * above the entry height; a level ray that leaves the map, or has crossed
360  * the domain twice where the map wraps, ends at the horizon, which counts as sky;
361  * - falling rays (``sz < 0``) are blocked by a solid column whose top is
362  * above the exit height (the ray descends inside the column) and reach
363  * the ground where the exit height is at or below ``z_ground``.
364  *
365  * Solid columns are those with a top above the ground. Leaving the map
366  * counts as sky for a rising ray and as ground for a falling one: the far
367  * field is open ground. Where the map spans a periodic direction the ray
368  * wraps round instead.
369  */
370 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
372  amrex::Real sx, amrex::Real sy, amrex::Real sz, const RayMap& m)
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 }
427 
428 /**
429  * Direction of the ``(ia, ie)`` sample of a cosine-weighted hemisphere
430  * around the outward normal of a face with direction ``dir`` and normal
431  * sign ``nsign`` (+1 along the axis, -1 against it). Stratified in the
432  * azimuth ``phi = 2 pi (ia + 1/2) / n_az`` and in ``u = (ie + 1/2) / n_el``
433  * with ``theta = asin(sqrt(u))`` from the normal, so every sample carries
434  * the same weight and the counts are view factors.
435  */
436 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
437 void hemisphere_direction (int dir, int nsign, int ia, int ie, int n_az, int n_el,
438  amrex::Real& sx, amrex::Real& sy, amrex::Real& sz)
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 }
454 
455 } // namespace ibseb
456 
457 #endif
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
amrex::Real gamma
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:9
Real height
Definition: ERF_InitCustomPert_SquallLine.H:37
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ theta
Definition: ERF_Kessler.H:26
@ t
Definition: ERF_WSM6.H:293
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
Definition: ERF_IBSEBBalance.H:51
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)
Definition: ERF_IBSEBSolar.H:371
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)
Definition: ERF_IBSEBSolar.H:437
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_zenith(amrex::Real latitude_deg, amrex::Real decl, amrex::Real hour_angle)
Definition: ERF_IBSEBSolar.H:116
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)
Definition: ERF_IBSEBSolar.H:179
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real orbital_hour_angle(double calday, amrex::Real longitude_rad)
Definition: ERF_IBSEBSolar.H:105
amrex::Real hemisphere_min_sz(int n_az, int n_el)
Definition: ERF_IBSEBSolar.H:281
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real equation_of_time(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:73
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real earth_sun_distance_factor(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:155
RayHit
What a ray from a face ends on.
Definition: ERF_IBSEBSolar.H:351
@ RAY_GROUND
Definition: ERF_IBSEBSolar.H:351
@ RAY_SKY
Definition: ERF_IBSEBSolar.H:351
@ RAY_BUILDING
Definition: ERF_IBSEBSolar.H:351
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)
Definition: ERF_IBSEBSolar.H:164
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)
Definition: ERF_IBSEBSolar.H:309
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)
Definition: ERF_IBSEBSolar.H:131
int ray_halo_cells(amrex::Real height, amrex::Real dx, amrex::Real sz_min)
Definition: ERF_IBSEBSolar.H:262
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real day_of_year_at(amrex::Real day0, amrex::Real t_utc_s)
Definition: ERF_IBSEBSolar.H:55
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_declination(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:62
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real spencer_day_angle(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:44
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)
Definition: ERF_IBSEBSolar.H:87
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)
Definition: ERF_IBSEBSolar.H:192
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
Definition: ERF_IBSEBSolar.H:219
amrex::Real x_lo
Definition: ERF_IBSEBSolar.H:224
amrex::Array4< const int > top
Column tops over the patch (any k index; use top.begin[2])
Definition: ERF_IBSEBSolar.H:220
int ilo
Definition: ERF_IBSEBSolar.H:221
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real column_top(int ci, int cj) const noexcept
Definition: ERF_IBSEBSolar.H:247
amrex::Real y_lo
Physical position of the domain's low corner [m].
Definition: ERF_IBSEBSolar.H:224
amrex::Real z_max
Tallest column the map holds [m]; a ray above it meets nothing.
Definition: ERF_IBSEBSolar.H:227
bool wrap_x
Definition: ERF_IBSEBSolar.H:223
int ny
Columns of the domain in x and y.
Definition: ERF_IBSEBSolar.H:222
amrex::Real dx
Definition: ERF_IBSEBSolar.H:225
amrex::Real z_ground
Ground height [m].
Definition: ERF_IBSEBSolar.H:226
bool wrap_y
The patch spans the periodic domain in x / y.
Definition: ERF_IBSEBSolar.H:223
int max_steps
Columns a ray at the map's floor elevation crosses before it clears z_max (the halo's reach in x plus...
Definition: ERF_IBSEBSolar.H:228
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool locate(int &ci, int &cj) const noexcept
Definition: ERF_IBSEBSolar.H:232
amrex::Real dy
Definition: ERF_IBSEBSolar.H:225
amrex::Real dz
Cell sizes [m].
Definition: ERF_IBSEBSolar.H:225
int jlo
Low corner of the domain, in cells.
Definition: ERF_IBSEBSolar.H:221
int nx
Definition: ERF_IBSEBSolar.H:222