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>
8 #include <cmath>
9 
10 /**
11  * \file ERF_IBSEBSolar.H
12  * \brief Sun position, clear-sky shortwave, and the ray cast that decides
13  * whether a building face is in shadow.
14  *
15  * The solar geometry (Spencer's declination and equation of time, the hour
16  * angle from UTC and the site longitude, the zenith, the clockwise-from-north
17  * azimuth, the Earth-Sun distance) is kept here: the radiation models use
18  * the CESM orbital formulation of ERF_OrbCosZenith.H, which gives no
19  * azimuth. The clear-sky formulas follow Bird (1984) as used by the SLUCM
20  * branch. The ray cast is new here.
21  *
22  * **Sun vector convention.** ``ibseb_sun_vector()`` returns the unit vector
23  * from a surface toward the sun, with azimuth measured clockwise from north:
24  * east is +x, north is +y, so ``s = (sin z sin a, sin z cos a, cos z)`` with
25  * z the zenith angle and a the azimuth. A face with outward normal n receives
26  * direct beam only when ``n . s > 0``.
27  *
28  * References: Spencer (1971); Duffie and Beckman (1991); Iqbal (1983);
29  * Bird and Hulstrom (1981).
30  */
31 namespace ibseb {
32 
33 /** Solar declination [rad] from the day of year (Spencer 1971). */
34 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
43 
44 /** Equation of time [minutes] from the day of year (Spencer 1971). */
45 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
52 
53 /**
54  * Hour angle [rad] from the UTC time of day and the site: solar time is UTC
55  * plus one hour per 15 degrees of longitude (east positive) plus the equation
56  * of time, and the hour angle is 15 degrees per hour from solar noon,
57  * negative in the morning. The time zone plays no part (it would cancel).
58  */
59 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
60 amrex::Real solar_hour_angle (amrex::Real time_utc_s, amrex::Real longitude_deg, amrex::Real day_of_year)
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 }
67 
68 /** Zenith angle [rad] from the latitude, declination and hour angle. */
69 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
78 
79 /**
80  * Azimuth [rad] clockwise from north (east is pi/2) from the latitude,
81  * declination, hour angle and zenith; north when the sun is at the zenith or
82  * the site is at a pole, where the azimuth is undefined.
83  */
84 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
86  amrex::Real hour_angle, amrex::Real zenith)
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 }
102 
103 /**
104  * Earth-Sun distance factor (d0/d)^2 that scales the solar constant for the
105  * day of year (Spencer 1971): about 1.034 at perihelion in early January,
106  * 0.967 at aphelion in early July, annual mean 1.
107  */
108 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
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 }
115 
116 /** Unit vector toward the sun from zenith and azimuth (east +x, north +y). */
117 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
118 void sun_vector (amrex::Real zenith, amrex::Real azimuth,
119  amrex::Real& sx, amrex::Real& sy, amrex::Real& sz)
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 }
126 
127 /**
128  * Clear-sky direct-normal irradiance [W/m2] (Bird form): the solar constant,
129  * corrected for the Earth-Sun distance, attenuated by ``tau^(1/cos z)``.
130  * Zero when the sun is below the horizon.
131  */
132 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
134  amrex::Real distance_factor)
135 {
136  if (cos_zenith <= 1.0e-3) return 0.0;
137  return S0 * distance_factor * std::pow(tau, 1.0 / cos_zenith);
138 }
139 
140 /**
141  * Clear-sky diffuse irradiance on a horizontal surface [W/m2]: a fraction
142  * ``k_d`` of what the direct beam lost to the atmosphere reaches the ground
143  * as diffuse light (Liu and Jordan form).
144  */
145 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
147  amrex::Real distance_factor, amrex::Real k_d)
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 }
153 
154 /**
155  * Top height of a column of the level. The column map covers the bounding box
156  * of the built columns only (origin ``i0, j0`` in 0-based domain columns,
157  * ``bw x bh`` values indexed ``(ci - i0) * bh + (cj - j0)``); every other
158  * column is open ground. An entry is the domain-relative k of the highest
159  * solid cell of its column, or -1 where the column is fluid, so the top is
160  * one cell above that index and a fluid column sits at the ground.
161  */
162 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
163 amrex::Real column_top (const int* col_top, int ci, int cj,
164  int i0, int j0, int bw, int bh,
165  amrex::Real z_ground, amrex::Real dz)
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 }
172 
173 /**
174  * Whether the ray from a face toward the sun hits a building.
175  *
176  * Buildings are represented by the top height of the columns of the level
177  * (``col_top`` over the built bounding box, see column_top(), replicated on
178  * every rank), so a ray is blocked wherever its height on entering a
179  * column is below that column's top: buildings stand on the ground, so the
180  * lowest point of the ray inside a column is at its entry, and the height
181  * only grows along a ray toward a sun above the horizon. The walk over the
182  * columns is a 2D digital differential analyser. It stops when the ray
183  * rises above the tallest column, leaves the domain in a non-periodic
184  * direction, or has travelled ``max_path``.
185  *
186  * The start point is nudged a little along the ray so a face does not
187  * test its own column at its own height: a wall starts in its fluid cell's
188  * column, whose top is below the face (no overhangs), and a roof starts a
189  * hair above its own top.
190  *
191  * @return true when blocked (the face is in shadow).
192  */
193 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
196  const int* col_top, int nx, int ny, int i0, int j0, int bw, int bh,
198  bool per_x, bool per_y, amrex::Real z_ground, amrex::Real z_max, amrex::Real max_path)
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 }
240 
241 /// What a ray from a face ends on.
242 enum RayHit : int { RAY_SKY = 0, RAY_GROUND = 1, RAY_BUILDING = 2 };
243 
244 /**
245  * Where a ray from a face ends: sky, ground or a building, for any
246  * direction (the hemisphere sampling of the view fractions). The same column walk as
247  * ray_blocked(), with the height test made direction-aware:
248  *
249  * - rising or level rays (``sz >= 0``) are blocked by a column whose top is
250  * above the entry height; a level ray that crosses ``max_path`` without a
251  * hit ends at the horizon, which counts as sky;
252  * - falling rays (``sz < 0``) are blocked by a solid column whose top is
253  * above the exit height (the ray descends inside the column) and reach
254  * the ground where the exit height is at or below ``z_ground``.
255  *
256  * Solid columns are those with a top above the ground. The domain is
257  * wrapped in periodic directions; leaving it in a non-periodic direction
258  * counts as sky for a rising ray and as ground for a falling one.
259  */
260 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
263  const int* col_top, int nx, int ny, int i0, int j0, int bw, int bh,
265  bool per_x, bool per_y, amrex::Real z_ground, amrex::Real z_max,
266  amrex::Real max_path)
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 }
320 
321 /**
322  * Direction of the ``(ia, ie)`` sample of a cosine-weighted hemisphere
323  * around the outward normal of a face with direction ``dir`` and normal
324  * sign ``nsign`` (+1 along the axis, -1 against it). Stratified in the
325  * azimuth ``phi = 2 pi (ia + 1/2) / n_az`` and in ``u = (ie + 1/2) / n_el``
326  * with ``theta = asin(sqrt(u))`` from the normal, so every sample carries
327  * the same weight and the counts are view factors.
328  */
329 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
330 void hemisphere_direction (int dir, int nsign, int ia, int ie, int n_az, int n_el,
331  amrex::Real& sx, amrex::Real& sy, amrex::Real& sz)
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 }
347 
348 } // namespace ibseb
349 
350 #endif
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
amrex::Real gamma
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:9
Dimensionless numeric literals and pure mathematical constants.
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:39
amrex::Real Real
Definition: ERF_ShocInterface.H:19
@ theta
Definition: ERF_SLM.H:19
@ dz
Definition: ERF_AdvanceWDM6.cpp:272
@ t
Definition: ERF_WSM6.H:272
Definition: ERF_IBSEBBalance.H:51
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:330
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:70
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)
Definition: ERF_IBSEBSolar.H:261
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:133
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
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real equation_of_time(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:46
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real earth_sun_distance_factor(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:109
RayHit
What a ray from a face ends on.
Definition: ERF_IBSEBSolar.H:242
@ RAY_GROUND
Definition: ERF_IBSEBSolar.H:242
@ RAY_SKY
Definition: ERF_IBSEBSolar.H:242
@ RAY_BUILDING
Definition: ERF_IBSEBSolar.H:242
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:118
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)
Definition: ERF_IBSEBSolar.H:194
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:85
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real solar_declination(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:35
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:60
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:146
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