ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
IBFaceSet Class Reference

#include <ERF_IBFaceSet.H>

Collaboration diagram for IBFaceSet:

Classes

struct  SunState
 Sun and irradiances of the last compute_shortwave() call, for reports. More...
 
struct  TwoStreamSun
 The two-stream sun of the next compute_shortwave() (sun_mode = two_stream). More...
 

Public Member Functions

 IBFaceSet (const IBSEBParams &params, int lev, int qv_comp=-1)
 
void build (const amrex::MultiFab &blanking, const amrex::Geometry &geom)
 
void add_outside_occluders (const std::vector< const IBFaceSet * > &below)
 
void set_two_stream_sun (amrex::Real declination, amrex::Real hour_angle, amrex::Real latitude_deg, amrex::Real toa_irradiance)
 
void map_buildings_to_level0 (const IBFaceSet &coarser, const amrex::IntVect &ratio)
 
int building_level0 (int b) const
 
void release_labels ()
 
ibseb::RayMap ray_map () const
 
amrex::IntVect ray_halo () const
 
bool ray_map_spans_domain () const
 
int n_faces () const
 
int n_buildings () const
 
int n_layers () const
 
void compute_view_fractions ()
 
void compute_longwave (const amrex::MultiFab &cons, const TwoStreamCanopyView &canopy=TwoStreamCanopyView{})
 
void assign_materials ()
 
void compute_ground (amrex::Real dt)
 
void solve_balance (amrex::Real dt)
 
void compute_sensible (const amrex::MultiFab &cons, const amrex::MultiFab &xvel, const amrex::MultiFab &yvel, const amrex::MultiFab &zvel, amrex::Real c_p, const amrex::MultiFab *olen_ground=nullptr, const amrex::MultiFab *pblh_ground=nullptr, amrex::Real z_i_bulk=0.0)
 
void add_heat_flux_to_source (amrex::MultiFab &source, const amrex::MultiFab &cons, const amrex::Geometry &geom, amrex::Real c_p, amrex::Real rdOcp) const
 
void compute_shortwave (amrex::Real time, const TwoStreamCanopyView &canopy=TwoStreamCanopyView{})
 
int top_sample_interface () const
 
bool has_faces () const
 
void check_canopy_layout (const amrex::MultiFab &cons, const TwoStreamCanopyView &canopy) const
 
void compute_shortwave_two_stream (const TwoStreamCanopyView &canopy)
 
void compute_longwave_two_stream (const amrex::MultiFab &cons, const TwoStreamCanopyView &canopy)
 
void scatter_field (const amrex::Gpu::DeviceVector< amrex::Real > &v, amrex::MultiFab &out) const
 
void dump_faces (const std::string &prefix) const
 
void scatter_diagnostics (amrex::MultiFab &nfaces, amrex::MultiFab &tskin) const
 
void write_state (const std::string &file) const
 
void read_state (const std::string &file)
 
void report (amrex::Real time, int step, bool write_csv) const
 
const SunState & sun () const
 
void print_debug_summary () const
 
void add_cost (double seconds)
 
void set_init_cost (double seconds)
 
const std::vector< int > & fab_start () const
 
void build_ray_map (const std::vector< const IBFaceSet * > &below)
 

Public Attributes

amrex::Gpu::DeviceVector< int > d_i
 
amrex::Gpu::DeviceVector< int > d_j
 
amrex::Gpu::DeviceVector< int > d_k
 Fluid cell of the face. More...
 
amrex::Gpu::DeviceVector< int > d_dir
 Face direction: 0 x, 1 y, 2 z. More...
 
amrex::Gpu::DeviceVector< int > d_side
 Solid neighbour at -1 (low) or +1 (high) side. More...
 
amrex::Gpu::DeviceVector< int > d_bid
 Building id (1-based) More...
 
amrex::Gpu::DeviceVector< int > d_mat
 Material id (0 = uniform inputs) More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_albedo
 
amrex::Gpu::DeviceVector< amrex::Real > d_emis
 Optical properties of the face. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_kth
 
amrex::Gpu::DeviceVector< amrex::Real > d_rhocp
 
amrex::Gpu::DeviceVector< amrex::Real > d_thick
 Slab conductivity [W/m/K], heat capacity [J/m3/K], thickness [m]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_area
 Face area [m2]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_xf
 
amrex::Gpu::DeviceVector< amrex::Real > d_yf
 
amrex::Gpu::DeviceVector< amrex::Real > d_zf
 Face centre [m]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_T_skin
 Skin temperature [K]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_T_slab
 Slab layers [K], face-major: [face * n_layers + layer]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_f_sky
 
amrex::Gpu::DeviceVector< amrex::Real > d_f_ground
 
amrex::Gpu::DeviceVector< amrex::Real > d_f_bldg
 View fractions, sum to 1. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_SW_abs
 
amrex::Gpu::DeviceVector< amrex::Real > d_LW_net
 
amrex::Gpu::DeviceVector< amrex::Real > d_H
 
amrex::Gpu::DeviceVector< amrex::Real > d_LE
 
amrex::Gpu::DeviceVector< amrex::Real > d_G
 
amrex::Gpu::DeviceVector< amrex::Real > d_Q_ext
 External incident flux [W/m2], e.g. fire radiation. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_shadow
 1 when the ray to the sun hits a building, else 0 More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_SW_direct_in
 Direct beam incident on the face. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_SW_diffuse_in
 Sky and ground-reflected diffuse incident. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_LW_down_in
 Incoming longwave on the face (sky, ground and walls) More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_T_air
 Air temperature of the fluid cell. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_theta_air
 Potential temperature of the fluid cell [K]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_rho
 Density of the fluid cell [kg/m3]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_U_tan
 Tangential wind speed at the fluid cell [m/s]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_ustar
 Friction velocity of the wall function [m/s]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_H_coeff
 H = H_coeff (theta_skin - theta_air) [W/m2/K]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_hbld
 Height of the face's building column [m]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_w_star
 Convective velocity scale of the step [m/s]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_olen
 Obukhov length used [m] (1e30 when neutral) More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_z_i
 Depth in w* [m]: mixed layer above a roof, building height for a wall. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_LW_ext
 Incoming longwave from sky and ground only [W/m2]. More...
 
amrex::Gpu::DeviceVector< amrex::Real > d_resid
 |balance| after the solve [W/m2] More...
 
amrex::Gpu::DeviceVector< int > d_niter
 Newton iterations of the last solve. More...
 
amrex::iMultiFab m_col_valid
 
amrex::iMultiFab m_ray_patch
 
amrex::Box m_patch_box
 This rank's patch (empty without boxes) More...
 
amrex::IntVect m_halo {AMREX_D_DECL(0, 0, 0)}
 Halo of the patch in cells. More...
 
bool m_wrap_x = false
 
bool m_wrap_y = false
 The patch spans the periodic domain in x / y. More...
 
bool m_map_spans_domain = false
 Every rank's patch spans the domain. More...
 
int m_max_steps = 0
 Columns a walk is followed for: the halo's reach in x plus y, before the domain cap. More...
 

Private Member Functions

void warn_fixed_sun_below_floor () const
 

Private Attributes

IBSEBParams m_params
 Copy of the inputs. More...
 
int m_lev = 0
 AMR level, for reports. More...
 
int m_qv_comp = -1
 State component of rho qv, -1 without a moisture model. More...
 
int m_nface = 0
 Faces owned by this rank. More...
 
int m_top_sample = -1
 Highest interface a face samples, over all ranks (-1: no faces) More...
 
int m_nbld = 0
 Buildings of the level (global) More...
 
std::vector< int > m_fab_start
 See fab_start() More...
 
std::vector< amrex::Long > m_bld_nface
 Faces per building. More...
 
std::vector< amrex::Real > m_bld_area
 Face area per building [m2]. More...
 
amrex::Long m_nface_dir [3] = {0, 0, 0}
 Faces per direction. More...
 
amrex::Real m_area_total = 0.0
 Total face area [m2]. More...
 
std::vector< amrex::Long > m_bld_ncol
 
std::vector< int > m_bld_ilo
 
std::vector< int > m_bld_ihi
 
std::vector< int > m_bld_jlo
 
std::vector< int > m_bld_jhi
 
std::vector< int > m_to_lev0
 
amrex::iMultiFab m_lab
 
amrex::Geometry m_geom
 Geometry of the level. More...
 
int m_nx = 0
 
int m_ny = 0
 
int m_nz = 0
 Cells of the domain in x, y and z. More...
 
amrex::IntVect m_dom_lo {AMREX_D_DECL(0, 0, 0)}
 Low corner of the level's domain. More...
 
amrex::Real m_x_lo = 0.0
 
amrex::Real m_y_lo = 0.0
 Domain origin [m]. More...
 
amrex::Real m_dx [3] = {0.0, 0.0, 0.0}
 Cell sizes [m]. More...
 
bool m_per_x = false
 
bool m_per_y = false
 Periodicity in x and y. More...
 
amrex::Real m_col_top_max = 0.0
 Tallest column the ray map holds [m] (the levels below included after add_outside_occluders()) More...
 
amrex::Real m_z_ground = 0.0
 Ground height [m] (flat, the domain bottom) More...
 
SunState m_sun
 
TwoStreamSun m_ts_sun
 
double m_cost_s = 0.0
 Wall-clock seconds spent in the per-step update on this rank. More...
 
amrex::Long m_cost_n = 0
 Number of per-step updates timed. More...
 
double m_init_cost_s = 0.0
 Wall-clock seconds of build() and compute_view_fractions() More...
 

Constructor & Destructor Documentation

◆ IBFaceSet()

IBFaceSet::IBFaceSet ( const IBSEBParams &  params,
int  lev,
int  qv_comp = -1 
)
inline

Construct an empty face set for one level; build() fills it.

Parameters
paramsInputs of the balance (copied; the set never reads ParmParse).
levAMR level the set belongs to, for reports only.
qv_compState component of rho qv (SolverChoice::moisture_indices.qv), negative without a moisture model. The air temperature and the Exner function of a fluid cell take its vapour through the equation of state, as the rest of ERF does; with no moisture model they take none.
101  : m_params(params), m_lev(lev), m_qv_comp(qv_comp) {}
int m_lev
AMR level, for reports.
Definition: ERF_IBFaceSet.H:568
int m_qv_comp
State component of rho qv, -1 without a moisture model.
Definition: ERF_IBFaceSet.H:569
IBSEBParams m_params
Copy of the inputs.
Definition: ERF_IBFaceSet.H:567

Member Function Documentation

◆ add_cost()

void IBFaceSet::add_cost ( double  seconds)
inline

Add the wall-clock time of one per-step update of this rank, for the cost line of report().

532 { m_cost_s += seconds; ++m_cost_n; }
double m_cost_s
Wall-clock seconds spent in the per-step update on this rank.
Definition: ERF_IBFaceSet.H:603
amrex::Long m_cost_n
Number of per-step updates timed.
Definition: ERF_IBFaceSet.H:604

◆ add_heat_flux_to_source()

void IBFaceSet::add_heat_flux_to_source ( amrex::MultiFab &  source,
const amrex::MultiFab &  cons,
const amrex::Geometry &  geom,
amrex::Real  c_p,
amrex::Real  rdOcp 
) const

Add the face heat flux to the temperature equation: every face deposits H A / (c_p V Pi) into the rho-theta source of its fluid cell (Pi the Exner function, so the flux heats the cell by H A / (c_p V) in temperature). Called after the sources are rebuilt at every slow stage; adds, never overwrites, and uses atomic adds because a corner cell receives several faces.

Face flux into the temperature equation. The rho-theta tendency of the fluid cell is H A / (c_p V Pi); with several faces on one cell the adds are atomic.

1359 {
1360  const auto dx = geom.CellSizeArray();
1361  const Real vol = dx[0] * dx[1] * dx[2];
1362  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
1363  const Real* pH = d_H.data(); const Real* pA = d_area.data();
1364  const int qv_comp = m_qv_comp;
1365  for (MFIter mfi(source); mfi.isValid(); ++mfi) {
1366  const int f0 = m_fab_start[mfi.LocalIndex()];
1367  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
1368  auto const& src = source.array(mfi);
1369  auto const& c = cons.const_array(mfi);
1370  ParallelFor(f1 - f0, [=] AMREX_GPU_DEVICE (int m) noexcept {
1371  const int f = f0 + m;
1372  const int i = pi[f], j = pj[f], k = pk[f];
1373  const Real exner = getExnergivenRTh(c(i, j, k, RhoTheta_comp), rdOcp, cell_qv(c, i, j, k, qv_comp));
1374  const Real tend = pH[f] * pA[f] / (c_p * vol * exner);
1375  Gpu::Atomic::AddNoRet(&src(i, j, k, RhoTheta_comp), tend);
1376  });
1377  }
1378  Gpu::streamSynchronize();
1379 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getExnergivenRTh(const amrex::Real rhotheta, const amrex::Real rdOcp, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:156
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:54
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
amrex::Real Real
Definition: ERF_ShocInterface.H:19
amrex::Gpu::DeviceVector< int > d_k
Fluid cell of the face.
Definition: ERF_IBFaceSet.H:455
amrex::Gpu::DeviceVector< amrex::Real > d_H
Definition: ERF_IBFaceSet.H:471
amrex::Gpu::DeviceVector< amrex::Real > d_area
Face area [m2].
Definition: ERF_IBFaceSet.H:462
amrex::Gpu::DeviceVector< int > d_j
Definition: ERF_IBFaceSet.H:455
std::vector< int > m_fab_start
See fab_start()
Definition: ERF_IBFaceSet.H:573
amrex::Gpu::DeviceVector< int > d_i
Definition: ERF_IBFaceSet.H:455
@ cons
Definition: ERF_IndexDefines.H:254
real(c_double), parameter, private pi
Definition: ERF_module_mp_morr_two_moment.F90:100
Here is the call graph for this function:

◆ add_outside_occluders()

void IBFaceSet::add_outside_occluders ( const std::vector< const IBFaceSet * > &  below)

Add the buildings this refined level does not hold to the ray map of its ray cast, from the column maps of the levels below.

build() takes the map from the level's own cells, so on a refined level that covers part of the domain the rays of the shortwave and the view fractions would pass through every building outside it. The ray map is rebuilt with the halo for the tallest column of every level, and each column of the patch that this level's grids do not cover takes the top of the finest level below that covers it (ERF::ibseb_check_refined_levels() has made sure a building is wholly inside or wholly outside each level), on the ratio x ratio columns of this level beneath the coarse one, at the same height. Only the ray cast reads the result: the building ids, the face heights of the wall function and the reports keep the level's own buildings. Call after build() of every level involved and before compute_view_fractions().

Parameters
belowFace sets of levels 0 to this level's minus one, in that order.

Rebuild the ray map with the buildings of the levels below (see the header and build_ray_map()).

792 {
793  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!below.empty(), "erf.ibseb: add_outside_occluders() needs the levels below");
794  build_ray_map(below);
796 }
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
void build_ray_map(const std::vector< const IBFaceSet * > &below)
Definition: ERF_IBFaceSet.cpp:624
void warn_fixed_sun_below_floor() const
Definition: ERF_IBFaceSet.cpp:754
Here is the call graph for this function:

◆ assign_materials()

void IBFaceSet::assign_materials ( )

Assign every face its material properties: from the material library by building id, or the uniform inputs. Called once after build(); fills d_mat, d_albedo, d_emis, d_k, d_rhocp, d_thick and initialises the slab layers to the skin temperature.

Materials per face from the library by building id, or the uniform inputs. Host work on the static geometry, once.

1074 {
1075  if (m_params.material_file.empty()) { return; }
1076  const Vector<IBSEBMaterial> table = read_ibseb_materials(m_params.material_file);
1077  auto find = [&](int id) -> const IBSEBMaterial& {
1078  for (const auto& m : table) { if (m.mat_id == id) return m; }
1079  Abort("erf.ibseb: material id " + std::to_string(id) + " is not in " + m_params.material_file);
1080  return table[0];
1081  };
1082  std::vector<int> h_b(m_nface);
1083  Gpu::copy(Gpu::deviceToHost, d_bid.begin(), d_bid.end(), h_b.begin());
1084  Gpu::streamSynchronize();
1085  std::vector<int> h_mat(m_nface);
1086  std::vector<Real> h_alb(m_nface), h_emi(m_nface), h_k(m_nface), h_rc(m_nface), h_th(m_nface);
1087  for (int n = 0; n < m_nface; ++n) {
1088  // erf.ibseb.material_by_building follows level 0's numbering; a
1089  // building only a refined level resolves (0 here) takes the default.
1090  const int b = m_to_lev0[h_b[n]];
1091  int id = m_params.material_default;
1092  if (b >= 1 && b <= static_cast<int>(m_params.material_by_building.size())) {
1093  id = m_params.material_by_building[b - 1];
1094  }
1095  const IBSEBMaterial& m = find(id);
1096  h_mat[n] = m.mat_id; h_alb[n] = m.albedo; h_emi[n] = m.emissivity;
1097  h_k[n] = m.k_therm; h_rc[n] = m.rho_cp; h_th[n] = m.thickness;
1098  }
1099  upload(d_mat, h_mat); upload(d_albedo, h_alb); upload(d_emis, h_emi);
1100  upload(d_kth, h_k); upload(d_rhocp, h_rc); upload(d_thick, h_th);
1101  Gpu::streamSynchronize();
1102  if (m_params.debug) {
1103  Print() << "[IBSEB DEBUG] lev=" << m_lev << " materials: " << table.size() << " in "
1104  << m_params.material_file << ", default id " << m_params.material_default;
1105  for (size_t b = 0; b < m_params.material_by_building.size(); ++b) {
1106  Print() << ", building " << b + 1 << " -> " << m_params.material_by_building[b];
1107  }
1108  Print() << "\n";
1109  for (const auto& m : table) {
1110  Print() << "[IBSEB DEBUG] material " << m.mat_id << " (" << m.name << "): albedo=" << m.albedo
1111  << " emissivity=" << m.emissivity << " k=" << m.k_therm << " W/m/K rho_cp=" << m.rho_cp
1112  << " J/m3/K thickness=" << m.thickness << " m\n";
1113  }
1114  }
1115 }
Vector< IBSEBMaterial > read_ibseb_materials(const std::string &file)
Definition: ERF_IBSEBMaterials.cpp:25
DiagnosticID id
Definition: ERF_Plotfile2DWaterPath.cpp:27
amrex::Gpu::DeviceVector< amrex::Real > d_thick
Slab conductivity [W/m/K], heat capacity [J/m3/K], thickness [m].
Definition: ERF_IBFaceSet.H:461
amrex::Gpu::DeviceVector< amrex::Real > d_albedo
Definition: ERF_IBFaceSet.H:460
amrex::Gpu::DeviceVector< int > d_mat
Material id (0 = uniform inputs)
Definition: ERF_IBFaceSet.H:459
std::vector< int > m_to_lev0
Definition: ERF_IBFaceSet.H:587
amrex::Gpu::DeviceVector< amrex::Real > d_emis
Optical properties of the face.
Definition: ERF_IBFaceSet.H:460
int m_nface
Faces owned by this rank.
Definition: ERF_IBFaceSet.H:570
amrex::Gpu::DeviceVector< int > d_bid
Building id (1-based)
Definition: ERF_IBFaceSet.H:458
amrex::Gpu::DeviceVector< amrex::Real > d_rhocp
Definition: ERF_IBFaceSet.H:461
amrex::Gpu::DeviceVector< amrex::Real > d_kth
Definition: ERF_IBFaceSet.H:461
Definition: ERF_IBSEBMaterials.H:24
amrex::Real albedo
Definition: ERF_IBSEBMaterials.H:26
amrex::Real k_therm
[W/m/K]
Definition: ERF_IBSEBMaterials.H:28
amrex::Real thickness
[m]
Definition: ERF_IBSEBMaterials.H:30
int mat_id
Definition: ERF_IBSEBMaterials.H:25
amrex::Real rho_cp
[J/m3/K]
Definition: ERF_IBSEBMaterials.H:29
amrex::Real emissivity
Definition: ERF_IBSEBMaterials.H:27
int material_default
Definition: ERF_IBSEBParams.H:123
std::string material_file
Definition: ERF_IBSEBParams.H:122
std::vector< int > material_by_building
Definition: ERF_IBSEBParams.H:124
bool debug
[IBSEB DEBUG] prints: set description at build, per-step summary, per-building rows
Definition: ERF_IBSEBParams.H:32
Here is the call graph for this function:

◆ build()

void IBFaceSet::build ( const amrex::MultiFab &  blanking,
const amrex::Geometry &  geom 
)

Detect this rank's faces from the cell-centred blanking and allocate every per-face array.

Runs on the host once at initialisation: the blanking is copied fab by fab (a pinned copy on GPU builds), the valid cells are scanned, and the arrays are uploaded to the device at the end. Must be called after the blanking has been built and its ghost cells filled, since the solid neighbour of a fluid cell on a box edge lives in a ghost cell. Stops when a solid cell outside the level's grids neighbours a cell of the level, fluid or solid: a building across the edge of a refined level.

Parameters
blankingCell-centred blanking of the level (needs at least one ghost cell, filled).
geomGeometry of the level: cell sizes for the face areas, domain and periodicity for the neighbour tests.

Detect the faces owned by this rank and allocate the per-face arrays.

The scan visits every valid cell of every local fab in MFIter order, once. A fluid cell (blanking < 0.5) contributes one face for each of its six neighbours that is solid (blanking >= 0.5); the neighbour may sit in a ghost cell, which is why the blanking must have its ghost cells filled. Neighbours outside the domain in a non-periodic direction are skipped, so a building against a non-periodic boundary has no face there. Solid cells contribute no face, only the column map (the highest solid cell of each column of the level's own boxes) that the building ids and the ray cast read. A solid neighbour outside the level's grids, of a fluid or a solid cell, means a building crosses the edge of a refined level; the scan counts them and stops the run once all ranks have reported.

Faces are appended in scan order, which makes them contiguous per fab; m_fab_start records where each fab's faces begin. The per-direction, per-building and total counts are reduced over all ranks here, once, so report() does not have to reduce static numbers every time. Nothing of the size of the domain is gathered: the column map is exchanged through its ghost cells, the building labels through theirs (see the header), and the one gather is of the distinct labels, one per building.

179 {
180  const Box& domain = geom.Domain();
181  const int nx = domain.length(0);
182  const int ny = domain.length(1);
183  const int ilo = domain.smallEnd(0);
184  const int jlo = domain.smallEnd(1);
185  const int klo = domain.smallEnd(2);
186  const auto dx = geom.CellSizeArray();
187  const auto plo = geom.ProbLoArray();
188  const Real face_area[3] = { dx[1] * dx[2], dx[0] * dx[2], dx[0] * dx[1] };
189  m_geom = geom;
190  m_nx = nx; m_ny = ny; m_nz = domain.length(2);
191  m_dom_lo = domain.smallEnd();
192  m_x_lo = plo[0]; m_y_lo = plo[1];
193  for (int d = 0; d < 3; ++d) { m_dx[d] = dx[d]; }
194  m_per_x = geom.isPeriodic(0); m_per_y = geom.isPeriodic(1);
195  m_z_ground = plo[2];
196 
197  // The face list of a cell must not depend on the decomposition: every
198  // valid cell has to see its six neighbours, which needs a filled ghost
199  // layer on the blanking.
200  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(blanking.nGrowVect().min() >= 1,
201  "erf.ibseb: the blanking needs at least one ghost cell");
202  // The labelling below gives every column its scan index plus one as a label, in an int.
203  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(Long(nx) * ny < Long(std::numeric_limits<int>::max()),
204  "erf.ibseb: the level has more columns than an int can label");
205 
206  // The level's own column map: one cell per column of each of the level's
207  // boxes, with one ghost cell for the neighbours the faces and the
208  // labelling read across boxes and periodic seams.
209  const BoxArray col_ba = column_box_array(blanking.boxArray(), domain);
210  m_col_valid.define(col_ba, blanking.DistributionMap(), 1, IntVect(AMREX_D_DECL(1, 1, 0)));
211  m_col_valid.setVal(-1);
212 
213  std::vector<int> h_i, h_j, h_k, h_dir, h_side, h_nbi, h_nbj;
214  std::vector<Real> h_area, h_xf, h_yf, h_zf;
215  // Solid neighbours outside the level's grids, of a fluid cell (a wall
216  // against the edge) or of a solid one (a building running across the
217  // edge, which a low roof can do with no fluid cell in between): a
218  // building that crosses the edge of a refined level, whose columns there
219  // this level cannot see.
220  const BoxArray& level_ba = blanking.boxArray();
221  Long n_outside = 0;
222  Long first_outside = std::numeric_limits<Long>::max(); // lowest such cell, as a linear domain index
223  auto solid_outside = [&] (const Array4<const Real>& b, const Box& vbox, const Box& fbox, const IntVect& nb) {
224  // A neighbour in the same valid box is in the level (the common case,
225  // and every case on level 0): no lookup.
226  if (vbox.contains(nb) || !fbox.contains(nb) || b(nb) < 0.5) { return false; }
227  IntVect nbw = nb;
228  for (int dp = 0; dp < 2; ++dp) {
229  if (!geom.isPeriodic(dp)) { continue; }
230  const int lo_d = domain.smallEnd(dp), len_d = domain.length(dp);
231  nbw[dp] = lo_d + ((nbw[dp] - lo_d) % len_d + len_d) % len_d;
232  }
233  if (level_ba.contains(nbw)) { return false; }
234  const IntVect r = nbw - domain.smallEnd();
235  first_outside = std::min(first_outside,
236  (Long(r[0]) * domain.length(1) + r[1]) * domain.length(2) + r[2]);
237  ++n_outside;
238  return true;
239  };
240 
241  m_fab_start.clear();
242  for (MFIter mfi(blanking); mfi.isValid(); ++mfi) {
243  m_fab_start.push_back(static_cast<int>(h_i.size()));
244  const Box& bx = mfi.validbox();
245  const HostFab hf(blanking[mfi]);
246  auto const& b = hf.array();
247  const Box& fbox = Box(b);
248  // The column map of this box: the highest solid cell of each of its
249  // columns, as a domain-relative k index, -1 where the column carries
250  // no solid cell. It is an index rather than a height so that it costs
251  // 4 bytes per column.
252  IArrayBox& cfab = m_col_valid[mfi];
253  HostInts top(cfab.box(), -1);
254  auto const& t = top.array();
255  const auto lo = lbound(bx);
256  const auto hi = ubound(bx);
257  for (int k = lo.z; k <= hi.z; ++k) {
258  for (int j = lo.y; j <= hi.y; ++j) {
259  for (int i = lo.x; i <= hi.x; ++i) {
260  if (b(i, j, k) >= 0.5) {
261  t(i, j, klo) = std::max(t(i, j, klo), k - klo);
262  for (int d = 0; d < 3; ++d) {
263  for (int s = -1; s <= 1; s += 2) {
264  IntVect nb(i, j, k);
265  nb[d] += s;
266  if (!geom.isPeriodic(d) && !domain.contains(nb)) { continue; }
267  solid_outside(b, bx, fbox, nb);
268  }
269  }
270  continue;
271  }
272  for (int d = 0; d < 3; ++d) {
273  for (int s = -1; s <= 1; s += 2) {
274  IntVect nb(i, j, k);
275  nb[d] += s;
276  if (!fbox.contains(nb)) { continue; }
277  if (!geom.isPeriodic(d) && !domain.contains(nb)) { continue; }
278  if (b(nb) < 0.5) { continue; }
279  solid_outside(b, bx, fbox, nb);
280  h_i.push_back(i); h_j.push_back(j); h_k.push_back(k);
281  h_dir.push_back(d); h_side.push_back(s);
282  // Face centre: the fluid cell centre moved half a cell toward the solid
283  // (plo is the position of the domain's low corner cell).
284  Real c[3] = { Real(plo[0] + (i - ilo + 0.5) * dx[0]), Real(plo[1] + (j - jlo + 0.5) * dx[1]), Real(plo[2] + (k - klo + 0.5) * dx[2]) };
285  c[d] += 0.5 * s * dx[d];
286  h_xf.push_back(c[0]); h_yf.push_back(c[1]); h_zf.push_back(c[2]);
287  // Column of the solid neighbour, unwrapped: it lies in this
288  // box or in its ghost cell, where the periodic image is.
289  h_nbi.push_back(nb[0]); h_nbj.push_back(nb[1]);
290  h_area.push_back(face_area[d]);
291  }
292  }
293  }}}
294  top.upload(cfab);
295  }
296  m_fab_start.push_back(static_cast<int>(h_i.size()));
297  m_nface = static_cast<int>(h_i.size());
298  m_col_valid.FillBoundary(geom.periodicity());
299 
300  // The column map, the building ids and the ray cast below see only the
301  // level's own cells, so a building must lie wholly inside the level.
302  // ERF::init_ibseb() checks that for the buildings of the level below; this
303  // catches one only the refined level resolves.
304  {
305  Long n_out_all = n_outside;
306  Long first_all = first_outside;
307  ParallelDescriptor::ReduceLongSum(n_out_all);
308  ParallelDescriptor::ReduceLongMin(first_all);
309  if (n_out_all > 0) {
310  const Long nyz = Long(domain.length(1)) * domain.length(2);
311  const int fi = static_cast<int>(first_all / nyz);
312  const int fj = static_cast<int>((first_all / domain.length(2)) % domain.length(1));
313  const int fk = static_cast<int>(first_all % domain.length(2));
314  std::ostringstream os;
315  os << "erf.ibseb: level " << m_lev << " has " << n_out_all
316  << " cell faces against solid cells outside its grids (the first solid one at x = "
317  << plo[0] + (fi + 0.5) * dx[0] << " m, y = " << plo[1] + (fj + 0.5) * dx[1]
318  << " m, z = " << plo[2] + (fk + 0.5) * dx[2] << " m)";
319  os << ": a building crosses the edge of the refined level. To fix the refined box, run"
320  " `python3 Exec/CanonicalTests/SEB/ibseb_refinement_box.py <inputs>`: it checks the deck's"
321  " erf.<name>.in_box_lo / in_box_hi against the height map and prints a box that no building"
322  " crosses (or `... <inputs> --all --fit tight|relaxed` for a box around every building).";
323  Abort(os.str());
324  }
325  }
326 
327  // The highest interface any face samples (two_stream_canopy_sample(): a roof its
328  // fluid cell's bottom, a ceiling and a wall the top of its cell too), counted from the domain
329  // bottom; -1 when the level has no faces. The two-stream beam the faces read needs
330  // no interface above it.
331  m_top_sample = -1;
332  for (int n = 0; n < m_nface; ++n) {
333  m_top_sample = std::max(m_top_sample, h_k[n] - domain.smallEnd(2) + ((h_dir[n] != 2 || h_side[n] > 0) ? 1 : 0));
334  }
335  ParallelDescriptor::ReduceIntMax(m_top_sample);
336 
337  // Tallest column of the level over all ranks: the height a rising ray
338  // stops at (one cell above the highest solid cell; the ground when the
339  // level holds no solid cell).
340  {
341  const int kmax = m_col_valid.max(0);
342  m_col_top_max = (kmax >= 0) ? Real(m_z_ground + (kmax + 1.0) * dx[2]) : Real(m_z_ground);
343  }
344 
345  // Host copies of the column map, ghost cells filled: the column of a
346  // face's solid neighbour is in its fab's box or its ghost ring.
347  std::vector<HostInts> tops;
348  std::vector<Box> vboxes;
349  for (MFIter mfi(m_col_valid); mfi.isValid(); ++mfi) {
350  tops.emplace_back(m_col_valid[mfi]);
351  vboxes.push_back(mfi.validbox());
352  }
353  const int nfab = static_cast<int>(tops.size());
354  AMREX_ALWAYS_ASSERT(nfab + 1 == static_cast<int>(m_fab_start.size()));
355  // Height of each face's building column, for the wall-function depth.
356  {
357  std::vector<Real> h_hb(h_i.size());
358  for (int li = 0; li < nfab; ++li) {
359  auto const& t = tops[li].array();
360  for (int n = m_fab_start[li]; n < m_fab_start[li + 1]; ++n) {
361  const int kt = t(h_nbi[n], h_nbj[n], klo);
362  h_hb[n] = (kt >= 0) ? (kt + 1.0) * dx[2] : 0.0;
363  }
364  }
365  upload(d_hbld, h_hb);
366  }
367 
368  // ---- Buildings: 4-connected solid columns, numbered in scan order ----
369  // Each box splits its solid columns into connected pieces (a depth-first
370  // flood fill); a piece's label starts as the lowest scan index plus one
371  // of its columns, and the pieces then take the lowest label of their
372  // neighbours across the box edges, through one ghost cell of m_lab,
373  // until no label changes anywhere: the pieces of one building all end
374  // with the scan index of its first column. The rounds are as many as a
375  // building's pieces are deep, so the exchange never depends on the
376  // number of columns.
377  m_lab.define(col_ba, blanking.DistributionMap(), 1, IntVect(AMREX_D_DECL(1, 1, 0)));
378  m_lab.setVal(0);
379  std::vector<std::vector<int>> piece_of(nfab); // per fab, per valid column: its piece, -1 where fluid
380  std::vector<std::vector<int>> piece_label(nfab); // per fab, per piece: its label
381  auto scan_label = [=] (int i, int j) { return (i - ilo) * ny + (j - jlo) + 1; };
382  for (int li = 0; li < nfab; ++li) {
383  const Box& vb = vboxes[li];
384  auto const& t = tops[li].array();
385  const auto lo = lbound(vb);
386  const int lx = vb.length(0), ly = vb.length(1);
387  auto idx = [=] (int i, int j) { return (j - lo.y) * lx + (i - lo.x); };
388  std::vector<int>& piece = piece_of[li];
389  piece.assign(static_cast<size_t>(lx) * ly, -1);
390  std::vector<int> stack;
391  for (int i = lo.x; i < lo.x + lx; ++i) {
392  for (int j = lo.y; j < lo.y + ly; ++j) {
393  if (t(i, j, klo) < 0 || piece[idx(i, j)] >= 0) { continue; }
394  const int p = static_cast<int>(piece_label[li].size());
395  int lab = scan_label(i, j);
396  piece[idx(i, j)] = p;
397  stack.clear();
398  stack.push_back(idx(i, j));
399  while (!stack.empty()) {
400  const int q = stack.back(); stack.pop_back();
401  const int qi = lo.x + q % lx, qj = lo.y + q / lx;
402  lab = std::min(lab, scan_label(qi, qj));
403  const int ni[4] = {qi - 1, qi + 1, qi, qi};
404  const int nj[4] = {qj, qj, qj - 1, qj + 1};
405  for (int n = 0; n < 4; ++n) {
406  if (!vb.contains(IntVect(ni[n], nj[n], klo))) { continue; }
407  const int r = idx(ni[n], nj[n]);
408  if (t(ni[n], nj[n], klo) >= 0 && piece[r] < 0) {
409  piece[r] = p;
410  stack.push_back(r);
411  }
412  }
413  }
414  piece_label[li].push_back(lab);
415  }
416  }
417  }
418  // Write the labels into the map, exchange, lower, repeat.
419  auto write_labels = [&] (const std::vector<std::vector<int>>& value) {
420  int li = 0;
421  for (MFIter mfi(m_lab); mfi.isValid(); ++mfi, ++li) {
422  IArrayBox& fab = m_lab[mfi];
423  HostInts L(fab.box(), 0);
424  auto const& a = L.array();
425  const Box& vb = vboxes[li];
426  const auto lo = lbound(vb);
427  const int lx = vb.length(0), ly = vb.length(1);
428  for (int j = lo.y; j < lo.y + ly; ++j) {
429  for (int i = lo.x; i < lo.x + lx; ++i) {
430  const int p = piece_of[li][(j - lo.y) * lx + (i - lo.x)];
431  if (p >= 0) { a(i, j, klo) = value[li][p]; }
432  }
433  }
434  L.upload(fab);
435  }
436  m_lab.FillBoundary(geom.periodicity());
437  };
438  int n_rounds = 0;
439  for (;;) {
440  write_labels(piece_label);
441  ++n_rounds;
442  bool changed = false;
443  int li = 0;
444  for (MFIter mfi(m_lab); mfi.isValid(); ++mfi, ++li) {
445  const HostInts L(m_lab[mfi]);
446  auto const& a = L.array();
447  const Box& vb = vboxes[li];
448  const auto lo = lbound(vb);
449  const auto hi = ubound(vb);
450  const int lx = vb.length(0);
451  for (int j = lo.y; j <= hi.y; ++j) {
452  for (int i = lo.x; i <= hi.x; ++i) {
453  if (i != lo.x && i != hi.x && j != lo.y && j != hi.y) { continue; } // an inner column has no neighbour outside the box
454  const int p = piece_of[li][(j - lo.y) * lx + (i - lo.x)];
455  if (p < 0) { continue; }
456  const int ni[4] = {i - 1, i + 1, i, i};
457  const int nj[4] = {j, j, j - 1, j + 1};
458  for (int n = 0; n < 4; ++n) {
459  const IntVect nb(ni[n], nj[n], klo);
460  if (vb.contains(nb) || !L.box.contains(nb)) { continue; }
461  const int g = a(nb);
462  if (g > 0 && g < piece_label[li][p]) { piece_label[li][p] = g; changed = true; }
463  }
464  }
465  }
466  }
467  ParallelDescriptor::ReduceBoolOr(changed);
468  if (!changed) { break; }
469  }
470  // The distinct labels, one per building, gathered from every rank and
471  // numbered in scan order; the pieces take the number of their label.
472  std::vector<int> labels_all;
473  {
474  std::set<int> mine;
475  for (const auto& pl : piece_label) { mine.insert(pl.begin(), pl.end()); }
476  const std::vector<int> labels_mine(mine.begin(), mine.end());
477  labels_all = all_gather(labels_mine);
478  std::sort(labels_all.begin(), labels_all.end());
479  labels_all.erase(std::unique(labels_all.begin(), labels_all.end()), labels_all.end());
480  }
481  m_nbld = static_cast<int>(labels_all.size());
482  std::vector<std::vector<int>> piece_id(nfab);
483  for (int li = 0; li < nfab; ++li) {
484  for (int lab : piece_label[li]) {
485  const auto it = std::lower_bound(labels_all.begin(), labels_all.end(), lab);
486  AMREX_ALWAYS_ASSERT(it != labels_all.end() && *it == lab);
487  piece_id[li].push_back(static_cast<int>(it - labels_all.begin()) + 1);
488  }
489  }
490  write_labels(piece_id);
491  // The id of each face: that of the column of its solid neighbour, in the
492  // fab's box or its ghost ring.
493  std::vector<int> h_bid(m_nface);
494  {
495  int li = 0;
496  for (MFIter mfi(m_lab); mfi.isValid(); ++mfi, ++li) {
497  const HostInts L(m_lab[mfi]);
498  auto const& a = L.array();
499  for (int n = m_fab_start[li]; n < m_fab_start[li + 1]; ++n) {
500  h_bid[n] = a(h_nbi[n], h_nbj[n], klo);
501  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(h_bid[n] >= 1, "erf.ibseb: a face's solid neighbour has no building id");
502  }
503  }
504  }
505  // Footprints, for the debug summary: columns and bounding box per building.
506  m_bld_ncol.assign(m_nbld + 1, 0);
507  m_bld_ilo.assign(m_nbld + 1, std::numeric_limits<int>::max()); m_bld_ihi.assign(m_nbld + 1, std::numeric_limits<int>::min());
508  m_bld_jlo.assign(m_nbld + 1, std::numeric_limits<int>::max()); m_bld_jhi.assign(m_nbld + 1, std::numeric_limits<int>::min());
509  for (int li = 0; li < nfab; ++li) {
510  const Box& vb = vboxes[li];
511  const auto lo = lbound(vb);
512  const int lx = vb.length(0), ly = vb.length(1);
513  for (int j = lo.y; j < lo.y + ly; ++j) {
514  for (int i = lo.x; i < lo.x + lx; ++i) {
515  const int p = piece_of[li][(j - lo.y) * lx + (i - lo.x)];
516  if (p < 0) { continue; }
517  const int bb = piece_id[li][p];
518  m_bld_ncol[bb] += 1;
519  m_bld_ilo[bb] = std::min(m_bld_ilo[bb], i); m_bld_ihi[bb] = std::max(m_bld_ihi[bb], i);
520  m_bld_jlo[bb] = std::min(m_bld_jlo[bb], j); m_bld_jhi[bb] = std::max(m_bld_jhi[bb], j);
521  }
522  }
523  }
524  ParallelDescriptor::ReduceLongSum(m_bld_ncol.data(), m_nbld + 1);
525  ParallelDescriptor::ReduceIntMin(m_bld_ilo.data(), m_nbld + 1); ParallelDescriptor::ReduceIntMax(m_bld_ihi.data(), m_nbld + 1);
526  ParallelDescriptor::ReduceIntMin(m_bld_jlo.data(), m_nbld + 1); ParallelDescriptor::ReduceIntMax(m_bld_jhi.data(), m_nbld + 1);
527  // The column labels, by which a refined level finds its buildings in the
528  // level below (map_buildings_to_level0()), are kept until then.
529  m_to_lev0.resize(m_nbld + 1);
530  for (int b = 0; b <= m_nbld; ++b) { m_to_lev0[b] = b; }
531  tops.clear();
532  piece_of.clear();
533 
534  // The ray map of this rank, from the level's own columns; a refined
535  // level rebuilds it with the levels below (add_outside_occluders()).
536  build_ray_map({});
537  if (m_params.debug) {
538  Print() << "[IBSEB DEBUG] lev=" << m_lev << " buildings labelled in " << n_rounds << " exchange round(s)\n";
539  }
540  if (m_lev == 0) { warn_fixed_sun_below_floor(); }
541 
542  // Static totals: per direction, per building, and the summed area.
543  std::vector<Long> nd(3, 0);
544  std::vector<Long> bn(m_nbld + 1, 0);
545  std::vector<Real> ba(m_nbld + 1, 0.0);
546  Real area = 0.0;
547  for (int n = 0; n < m_nface; ++n) {
548  nd[h_dir[n]] += 1;
549  bn[h_bid[n]] += 1;
550  ba[h_bid[n]] += h_area[n];
551  area += h_area[n];
552  }
553  ParallelDescriptor::ReduceLongSum(nd.data(), 3);
554  ParallelDescriptor::ReduceLongSum(bn.data(), m_nbld + 1);
555  ParallelDescriptor::ReduceRealSum(ba.data(), m_nbld + 1);
556  ParallelDescriptor::ReduceRealSum(area);
557  for (int d = 0; d < 3; ++d) { m_nface_dir[d] = nd[d]; }
558  m_bld_nface = bn;
559  m_bld_area = ba;
560  m_area_total = area;
561 
562  // Device arrays. The state starts uniform at T_skin_init (the slab too;
563  // the interior boundary is applied when the slab is advanced); the view
564  // fractions and fluxes start at zero and are filled by
565  // compute_view_fractions() and the per-step routines.
566  const size_t nf = static_cast<size_t>(m_nface);
567  upload(d_i, h_i); upload(d_j, h_j); upload(d_k, h_k);
568  upload(d_dir, h_dir); upload(d_side, h_side); upload(d_bid, h_bid);
569  upload(d_area, h_area);
570  upload(d_xf, h_xf); upload(d_yf, h_yf); upload(d_zf, h_zf);
571  fill(d_mat, nf, 0);
574  fill(d_kth, nf, m_params.k_therm);
575  fill(d_rhocp, nf, m_params.rho_cp);
578  fill(d_T_slab, nf * static_cast<size_t>(n_layers()), m_params.T_skin_init);
579  // View fractions: placeholders (a roof sees the whole sky, a wall half sky
580  // and half ground) that compute_view_fractions() replaces at initialisation.
581  {
582  std::vector<Real> fs(nf), fg(nf), fb(nf, 0.0);
583  for (size_t n = 0; n < nf; ++n) {
584  const bool roof = (h_dir[n] == 2);
585  fs[n] = roof ? 1.0 : 0.5;
586  fg[n] = roof ? 0.0 : 0.5;
587  }
588  upload(d_f_sky, fs); upload(d_f_ground, fg); upload(d_f_bldg, fb);
589  }
590  fill(d_shadow, nf, Real(0.0));
591  fill(d_SW_direct_in, nf, Real(0.0));
592  fill(d_SW_diffuse_in, nf, Real(0.0));
593  fill(d_LW_down_in, nf, Real(0.0));
596  fill(d_rho, nf, Real(1.0));
597  fill(d_U_tan, nf, Real(0.0));
598  fill(d_ustar, nf, Real(0.0));
599  fill(d_SW_abs, nf, Real(0.0));
600  fill(d_LW_net, nf, Real(0.0));
601  fill(d_H, nf, Real(0.0));
602  fill(d_LE, nf, Real(0.0));
603  fill(d_G, nf, Real(0.0));
605  fill(d_H_coeff, nf, Real(0.0));
606  fill(d_w_star, nf, Real(0.0));
607  fill(d_olen, nf, Real(1.0e30));
608  fill(d_z_i, nf, Real(0.0));
609  fill(d_LW_ext, nf, Real(0.0));
610  fill(d_resid, nf, Real(0.0));
611  fill(d_niter, nf, 0);
612  Gpu::streamSynchronize();
613 
615 }
const Real lx
Definition: ERF_InitCustomPertVels_CloudChamber.H:10
const int nx
Definition: ERF_InitCustomPertVels_CloudChamber.H:14
const int ny
Definition: ERF_InitCustomPertVels_CloudChamber.H:15
const Real ly
Definition: ERF_InitCustomPertVels_CloudChamber.H:11
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
const int klo
Definition: ERF_InitCustomPert_KE.H:9
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE int idx(int i, int j, int k, int nx, int ny)
Definition: ERF_InitForEnsemble.cpp:404
amrex::Gpu::DeviceVector< amrex::Real > d_resid
|balance| after the solve [W/m2]
Definition: ERF_IBFaceSet.H:493
amrex::Gpu::DeviceVector< amrex::Real > d_theta_air
Potential temperature of the fluid cell [K].
Definition: ERF_IBFaceSet.H:481
std::vector< int > m_bld_jhi
Definition: ERF_IBFaceSet.H:582
amrex::Gpu::DeviceVector< amrex::Real > d_hbld
Height of the face's building column [m].
Definition: ERF_IBFaceSet.H:487
int m_ny
Definition: ERF_IBFaceSet.H:592
amrex::Gpu::DeviceVector< amrex::Real > d_shadow
1 when the ray to the sun hits a building, else 0
Definition: ERF_IBFaceSet.H:474
amrex::Gpu::DeviceVector< amrex::Real > d_yf
Definition: ERF_IBFaceSet.H:463
amrex::iMultiFab m_lab
Definition: ERF_IBFaceSet.H:588
std::vector< amrex::Long > m_bld_ncol
Definition: ERF_IBFaceSet.H:581
amrex::Gpu::DeviceVector< amrex::Real > d_SW_abs
Definition: ERF_IBFaceSet.H:471
amrex::Real m_col_top_max
Tallest column the ray map holds [m] (the levels below included after add_outside_occluders())
Definition: ERF_IBFaceSet.H:597
void print_debug_summary() const
Definition: ERF_IBFaceSet.cpp:1688
amrex::Gpu::DeviceVector< amrex::Real > d_LW_ext
Incoming longwave from sky and ground only [W/m2].
Definition: ERF_IBFaceSet.H:492
bool m_per_x
Definition: ERF_IBFaceSet.H:596
amrex::Long m_nface_dir[3]
Faces per direction.
Definition: ERF_IBFaceSet.H:578
std::vector< int > m_bld_jlo
Definition: ERF_IBFaceSet.H:582
int m_nz
Cells of the domain in x, y and z.
Definition: ERF_IBFaceSet.H:592
amrex::Gpu::DeviceVector< amrex::Real > d_w_star
Convective velocity scale of the step [m/s].
Definition: ERF_IBFaceSet.H:488
amrex::Gpu::DeviceVector< amrex::Real > d_rho
Density of the fluid cell [kg/m3].
Definition: ERF_IBFaceSet.H:482
amrex::Gpu::DeviceVector< amrex::Real > d_T_skin
Skin temperature [K].
Definition: ERF_IBFaceSet.H:465
amrex::Gpu::DeviceVector< amrex::Real > d_LW_net
Definition: ERF_IBFaceSet.H:471
amrex::Gpu::DeviceVector< amrex::Real > d_z_i
Depth in w* [m]: mixed layer above a roof, building height for a wall.
Definition: ERF_IBFaceSet.H:490
amrex::Gpu::DeviceVector< int > d_side
Solid neighbour at -1 (low) or +1 (high) side.
Definition: ERF_IBFaceSet.H:457
int m_top_sample
Highest interface a face samples, over all ranks (-1: no faces)
Definition: ERF_IBFaceSet.H:571
std::vector< amrex::Real > m_bld_area
Face area per building [m2].
Definition: ERF_IBFaceSet.H:577
amrex::Gpu::DeviceVector< int > d_niter
Newton iterations of the last solve.
Definition: ERF_IBFaceSet.H:494
amrex::Gpu::DeviceVector< amrex::Real > d_SW_diffuse_in
Sky and ground-reflected diffuse incident.
Definition: ERF_IBFaceSet.H:476
amrex::Gpu::DeviceVector< amrex::Real > d_f_sky
Definition: ERF_IBFaceSet.H:468
amrex::iMultiFab m_col_valid
Definition: ERF_IBFaceSet.H:501
amrex::Gpu::DeviceVector< amrex::Real > d_olen
Obukhov length used [m] (1e30 when neutral)
Definition: ERF_IBFaceSet.H:489
amrex::Real m_z_ground
Ground height [m] (flat, the domain bottom)
Definition: ERF_IBFaceSet.H:598
amrex::Gpu::DeviceVector< int > d_dir
Face direction: 0 x, 1 y, 2 z.
Definition: ERF_IBFaceSet.H:456
amrex::Gpu::DeviceVector< amrex::Real > d_f_bldg
View fractions, sum to 1.
Definition: ERF_IBFaceSet.H:468
std::vector< int > m_bld_ilo
Definition: ERF_IBFaceSet.H:582
amrex::Gpu::DeviceVector< amrex::Real > d_T_slab
Slab layers [K], face-major: [face * n_layers + layer].
Definition: ERF_IBFaceSet.H:466
int n_layers() const
Definition: ERF_IBFaceSet.H:203
amrex::Real m_y_lo
Domain origin [m].
Definition: ERF_IBFaceSet.H:594
amrex::Gpu::DeviceVector< amrex::Real > d_SW_direct_in
Direct beam incident on the face.
Definition: ERF_IBFaceSet.H:475
amrex::Gpu::DeviceVector< amrex::Real > d_G
Definition: ERF_IBFaceSet.H:471
amrex::Gpu::DeviceVector< amrex::Real > d_T_air
Air temperature of the fluid cell.
Definition: ERF_IBFaceSet.H:479
amrex::Gpu::DeviceVector< amrex::Real > d_Q_ext
External incident flux [W/m2], e.g. fire radiation.
Definition: ERF_IBFaceSet.H:472
amrex::Real m_area_total
Total face area [m2].
Definition: ERF_IBFaceSet.H:579
bool m_per_y
Periodicity in x and y.
Definition: ERF_IBFaceSet.H:596
amrex::Gpu::DeviceVector< amrex::Real > d_f_ground
Definition: ERF_IBFaceSet.H:468
int m_nx
Definition: ERF_IBFaceSet.H:592
amrex::Gpu::DeviceVector< amrex::Real > d_LE
Definition: ERF_IBFaceSet.H:471
amrex::Gpu::DeviceVector< amrex::Real > d_H_coeff
H = H_coeff (theta_skin - theta_air) [W/m2/K].
Definition: ERF_IBFaceSet.H:485
int m_nbld
Buildings of the level (global)
Definition: ERF_IBFaceSet.H:572
amrex::Real m_dx[3]
Cell sizes [m].
Definition: ERF_IBFaceSet.H:595
amrex::Gpu::DeviceVector< amrex::Real > d_xf
Definition: ERF_IBFaceSet.H:463
amrex::Gpu::DeviceVector< amrex::Real > d_zf
Face centre [m].
Definition: ERF_IBFaceSet.H:463
amrex::Geometry m_geom
Geometry of the level.
Definition: ERF_IBFaceSet.H:591
std::vector< amrex::Long > m_bld_nface
Faces per building.
Definition: ERF_IBFaceSet.H:576
amrex::Gpu::DeviceVector< amrex::Real > d_LW_down_in
Incoming longwave on the face (sky, ground and walls)
Definition: ERF_IBFaceSet.H:478
amrex::IntVect m_dom_lo
Low corner of the level's domain.
Definition: ERF_IBFaceSet.H:593
std::vector< int > m_bld_ihi
Definition: ERF_IBFaceSet.H:582
amrex::Real m_x_lo
Definition: ERF_IBFaceSet.H:594
amrex::Gpu::DeviceVector< amrex::Real > d_ustar
Friction velocity of the wall function [m/s].
Definition: ERF_IBFaceSet.H:484
amrex::Gpu::DeviceVector< amrex::Real > d_U_tan
Tangential wind speed at the fluid cell [m/s].
Definition: ERF_IBFaceSet.H:483
@ fi
Definition: ERF_AdvanceMorrison.cpp:109
@ fs
Definition: ERF_AdvanceMorrison.cpp:124
@ fg
Definition: ERF_AdvanceMorrison.cpp:111
@ ni
Definition: ERF_Morrison.H:49
@ qi
Definition: ERF_Morrison.H:111
@ q
Definition: ERF_WSM6.H:294
@ t
Definition: ERF_WSM6.H:293
@ p
Definition: ERF_WSM6.H:301
real(c_double), parameter g
Definition: ERF_module_model_constants.F90:19
void fill(MultiFab &dst, int temperature_comp, int mixing_ratio_comp, int source_comp, const Sources &sources, Real missing_value)
Definition: ERF_NearSurfaceDiagnostics.cpp:42
amrex::Real Q_ext_uniform
Uniform external incident flux on every face [W/m2] (test hook)
Definition: ERF_IBSEBParams.H:143
amrex::Real k_therm
Uniform conductivity [W/m/K].
Definition: ERF_IBSEBParams.H:125
amrex::Real rho_cp
Uniform volumetric heat capacity [J/m3/K].
Definition: ERF_IBSEBParams.H:126
amrex::Real thickness
Uniform slab thickness [m].
Definition: ERF_IBSEBParams.H:127
amrex::Real albedo
Face shortwave albedo.
Definition: ERF_IBSEBParams.H:77
amrex::Real T_skin_init
Initial skin temperature [K].
Definition: ERF_IBSEBParams.H:28
amrex::Real emissivity
Face longwave emissivity.
Definition: ERF_IBSEBParams.H:79
Here is the call graph for this function:

◆ build_ray_map()

void IBFaceSet::build_ray_map ( const std::vector< const IBFaceSet * > &  below)

Build the ray map (m_ray_patch) from m_col_valid and the levels below. The halo is sized for the shallowest ray the map must follow to its end: the shallowest ray of the hemisphere sampling (ibseb::hemisphere_min_sz()) and a sun erf.ibseb.sun_min_elev_deg above the horizon, over the tallest column of this level and the levels below, height / tan(elevation) cells in x and in y (ibseb::ray_halo_cells()). A rising ray stops above the tallest column and a falling one at the ground, so within that reach the walk is exact, and beyond it only a sun lower than the floor is concerned, whose direct beam the clear-sky attenuation has all but removed; a building farther than the halo from a face then casts no shadow on it (the far field is open ground). The patch is this rank's boxes grown by the halo, clipped to the domain where it is not periodic and, where it is, reduced to one domain width when the grown box is at least that wide, in which case the walk wraps round and is exact for any reach.

Parameters
belowFace sets of the levels below (empty on level 0), each with its build() done.

Public because it launches a GPU lambda, which nvcc rejects in a private member function.

The ray map of this rank (see the header): the halo, this rank's patch, every rank's patch as one box array, and its contents from the levels below (coarsest first, so the finest level that covers a column has the last word) and then this level's own columns with their periodic images.

625 {
626  const Box& domain = m_geom.Domain();
627  // The tallest column of this level and of the levels below: the height
628  // a rising ray stops at, and what sizes the halo.
629  Real z_max = m_col_top_max;
630  for (const IBFaceSet* c : below) {
631  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(c->m_col_valid.ok(), "erf.ibseb: a level below has not been built");
632  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(c->m_geom.Domain().smallEnd(2) == domain.smallEnd(2) && c->m_z_ground == m_z_ground,
633  "erf.ibseb: the levels do not share the ground");
634  z_max = std::max(z_max, c->m_col_top_max);
635  }
636  m_col_top_max = z_max;
637  // The shallowest ray the map follows to its end: the hemisphere's and the
638  // sun's floor; the halo is its horizontal reach over the tallest column.
640  const Real sz_sun = std::sin(m_params.sun_min_elev_deg * PI / 180.0);
641  const Real sz_min = std::min(sz_view, sz_sun);
642  const Real height = z_max - m_z_ground;
643  const int halo[2] = { ibseb::ray_halo_cells(height, m_dx[0], sz_min), ibseb::ray_halo_cells(height, m_dx[1], sz_min) };
644  // The columns a ray at the floor can cross in x plus in y: what a walk is
645  // followed for, however the patch is cut or wrapped.
646  m_max_steps = static_cast<int>(std::min(Long(std::numeric_limits<int>::max() / 2), Long(halo[0]) + Long(halo[1])));
647 
648  // This rank's patch: its boxes grown by the halo. A halo wider than the
649  // domain adds nothing beyond one domain width: where the direction is
650  // periodic the patch is then the whole domain, wrapped; where it is not
651  // the patch stops at the domain's edge.
652  Box bbox;
653  for (MFIter mfi(m_col_valid); mfi.isValid(); ++mfi) {
654  if (bbox.ok()) { bbox.minBox(mfi.validbox()); } else { bbox = mfi.validbox(); }
655  }
656  Box patch;
657  m_wrap_x = m_wrap_y = false;
658  bool spans = true;
659  for (int d = 0; d < 2; ++d) { m_halo[d] = std::min(halo[d], domain.length(d)); }
660  m_halo[2] = 0;
661  if (bbox.ok()) {
662  patch = bbox;
663  for (int d = 0; d < 2; ++d) {
664  patch.grow(d, m_halo[d]);
665  if (m_geom.isPeriodic(d)) {
666  if (patch.length(d) >= domain.length(d)) {
667  patch.setRange(d, domain.smallEnd(d), domain.length(d));
668  (d == 0 ? m_wrap_x : m_wrap_y) = true;
669  }
670  } else {
671  patch.setSmall(d, std::max(patch.smallEnd(d), domain.smallEnd(d)));
672  patch.setBig(d, std::min(patch.bigEnd(d), domain.bigEnd(d)));
673  }
674  spans = spans && (patch.smallEnd(d) == domain.smallEnd(d) && patch.length(d) == domain.length(d));
675  }
676  }
677  m_patch_box = patch;
678  ParallelDescriptor::ReduceBoolAnd(spans);
679  m_map_spans_domain = spans;
680 
681  // Every rank's patch, one box each for the ranks that have boxes.
682  std::vector<int> mine;
683  if (patch.ok()) {
684  mine = { patch.smallEnd(0), patch.smallEnd(1), patch.smallEnd(2),
685  patch.bigEnd(0), patch.bigEnd(1), patch.bigEnd(2), ParallelDescriptor::MyProc() };
686  }
687  const std::vector<int> all = all_gather(mine);
688  BoxList bl;
689  Vector<int> pmap;
690  for (size_t n = 0; n + 7 <= all.size(); n += 7) {
691  bl.push_back(Box(IntVect(all[n], all[n + 1], all[n + 2]), IntVect(all[n + 3], all[n + 4], all[n + 5])));
692  pmap.push_back(all[n + 6]);
693  }
694  m_ray_patch.clear();
695  if (bl.isEmpty()) { return; }
696  const BoxArray patch_ba(std::move(bl));
697  const DistributionMapping patch_dm(pmap);
698  m_ray_patch.define(patch_ba, patch_dm, 1, 0);
699  m_ray_patch.setVal(-1);
700 
701  // The levels below: each column of the patch that a level covers takes
702  // that level's top, on the ratio x ratio columns of this level beneath
703  // the coarse one, at the same height; the finest level wins.
704  for (const IBFaceSet* c : below) {
705  const Box& cdom = c->m_geom.Domain();
706  const IntVect ratio(AMREX_D_DECL(domain.length(0) / cdom.length(0), domain.length(1) / cdom.length(1), 1));
707  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(ratio[0] * cdom.length(0) == domain.length(0) && ratio[1] * cdom.length(1) == domain.length(1),
708  "erf.ibseb: the domains of the levels are not refinements of one another");
709  iMultiFab crse(amrex::coarsen(patch_ba, ratio), patch_dm, 1, 0);
710  crse.setVal(-2); // -2: that level does not cover the column
711  crse.ParallelCopy(c->m_col_valid, 0, 0, 1, IntVect(0), IntVect(0), c->m_geom.periodicity());
712  const Real dz_c = c->m_dx[2], dz_f = m_dx[2];
713  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(std::abs(dz_c / dz_f - std::round(dz_c / dz_f)) < 1.0e-6,
714  "erf.ibseb: the vertical cell sizes of the levels are not a whole ratio");
715  const int rx = ratio[0], ry = ratio[1];
716  for (MFIter mfi(m_ray_patch); mfi.isValid(); ++mfi) {
717  const Box& pb = mfi.validbox();
718  auto const& p = m_ray_patch.array(mfi);
719  auto const& cr = crse.const_array(mfi);
720  ParallelFor(pb, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
721  const int kc = cr(amrex::coarsen(i, rx), amrex::coarsen(j, ry), k);
722  if (kc == -2) { return; }
723  // The same height here: the top is kc + 1 cells of that level
724  // up, a whole number of cells of this one (rounded to keep it
725  // so whatever the precision of the division).
726  p(i, j, k) = (kc < 0) ? -1 : static_cast<int>((kc + 1) * dz_c / dz_f + Real(0.5)) - 1;
727  });
728  }
729  }
730  // This level's own columns, with their periodic images.
731  m_ray_patch.ParallelCopy(m_col_valid, 0, 0, 1, IntVect(0), IntVect(0), m_geom.periodicity());
732  Gpu::streamSynchronize();
733 
734  if (m_params.debug) {
735  Print() << "[IBSEB DEBUG] lev=" << m_lev << " ray map: halo " << m_halo[0] << " x " << m_halo[1]
736  << " cells for rays " << std::asin(sz_min) * 180.0 / PI << " deg above the horizon over "
737  << height << " m (hemisphere " << std::asin(sz_view) * 180.0 / PI << " deg, sun floor "
738  << m_params.sun_min_elev_deg << " deg); " << (spans ? "every rank's patch spans the domain" : "the patches do not span the domain")
739  << "; " << below.size() << " level(s) below merged\n";
740  std::ostringstream os;
741  os << "[IBSEB DEBUG] lev=" << m_lev << " rank " << ParallelDescriptor::MyProc() << ": ray map patch " << patch
742  << (m_wrap_x ? " wraps in x" : "") << (m_wrap_y ? " wraps in y" : "")
743  << ", " << (patch.ok() ? patch.numPts() * sizeof(int) / 1024.0 : 0.0) << " kB\n";
744  AllPrint() << os.str();
745  }
746 }
Real height
Definition: ERF_InitCustomPert_SquallLine.H:37
constexpr amrex::Real PI
Definition: ERF_NumericalConstants.H:46
Definition: ERF_IBFaceSet.H:88
amrex::iMultiFab m_ray_patch
Definition: ERF_IBFaceSet.H:507
amrex::Box m_patch_box
This rank's patch (empty without boxes)
Definition: ERF_IBFaceSet.H:508
amrex::IntVect m_halo
Halo of the patch in cells.
Definition: ERF_IBFaceSet.H:509
int m_max_steps
Columns a walk is followed for: the halo's reach in x plus y, before the domain cap.
Definition: ERF_IBFaceSet.H:512
bool m_wrap_y
The patch spans the periodic domain in x / y.
Definition: ERF_IBFaceSet.H:510
bool m_map_spans_domain
Every rank's patch spans the domain.
Definition: ERF_IBFaceSet.H:511
bool m_wrap_x
Definition: ERF_IBFaceSet.H:510
amrex::Real hemisphere_min_sz(int n_az, int n_el)
Definition: ERF_IBSEBSolar.H:281
int ray_halo_cells(amrex::Real height, amrex::Real dx, amrex::Real sz_min)
Definition: ERF_IBSEBSolar.H:262
int view_n_az
Definition: ERF_IBSEBParams.H:85
amrex::Real sun_min_elev_deg
Definition: ERF_IBSEBParams.H:75
int view_n_el
Definition: ERF_IBSEBParams.H:86
Here is the call graph for this function:

◆ building_level0()

int IBFaceSet::building_level0 ( int  b) const
inline

Level 0's number for building b of this level (1-based; 0 for one only this level resolves).

180 { return m_to_lev0.at(b); }

◆ check_canopy_layout()

void IBFaceSet::check_canopy_layout ( const amrex::MultiFab &  cons,
const TwoStreamCanopyView &  canopy 
) const

Stop unless the fields of canopy (this level's two-stream view) lie on the columns of cons, with the same distribution: the two-stream kernels read the column of a face from the fabs with the local index of the face's own fab.

The fields of a two-stream view must lie on this level's columns, with the level's distribution, so that the fabs of local index n hold the columns of the faces fab_start()[n] .. fab_start()[n+1]-1 own: the interface fluxes on the level's own grids (with the top-of-atmosphere ghost, which a face whose cell is the top of the domain reads); the beam on its boxes in x and y with the interfaces from the surface up to at least top_sample_interface() in z; the cosine of the zenith on them flattened to index 0 in z.

1040 {
1042  "erf.ibseb.radiation = two_stream: level " + std::to_string(m_lev) + "'s two-stream view is incomplete");
1043  const BoxArray& ba = cons.boxArray();
1044  const DistributionMapping& dm = cons.DistributionMap();
1045  const auto& beam = *canopy.beam;
1046  const auto& cosz = *canopy.cos_zenith;
1047  const auto& flux = *canopy.fluxes;
1048  bool ok = flux.boxArray() == ba && flux.DistributionMap() == dm && flux.nComp() >= 4 && flux.nGrowVect()[2] >= 1 &&
1049  beam.boxArray().size() == ba.size() && beam.DistributionMap() == dm &&
1050  cosz.boxArray().size() == ba.size() && cosz.DistributionMap() == dm;
1051  // Same columns: the boxes agree in x and y.
1052  for (int n = 0; ok && n < static_cast<int>(ba.size()); ++n) {
1053  const Box& b = beam.boxArray()[n];
1054  const Box& c = cosz.boxArray()[n];
1055  for (int d = 0; d < 2; ++d) {
1056  ok = ok && b.smallEnd(d) == ba[n].smallEnd(d) && b.bigEnd(d) == ba[n].bigEnd(d) &&
1057  c.smallEnd(d) == ba[n].smallEnd(d) && c.bigEnd(d) == ba[n].bigEnd(d);
1058  }
1059  // The kernels read the beam at interfaces 0 .. top_sample_interface() and the
1060  // cosine of the zenith at index 0 in z.
1061  ok = ok && b.smallEnd(2) == 0 && b.bigEnd(2) >= top_sample_interface() && c.smallEnd(2) == 0;
1062  }
1064  "erf.ibseb.radiation = two_stream: level " + std::to_string(m_lev) +
1065  "'s two-stream beam, cosine of the zenith or interface fluxes are not laid out on the level's grids");
1066 }
int top_sample_interface() const
Definition: ERF_IBFaceSet.H:337
const amrex::MultiFab * fluxes
The level's rad_fluxes (SW up, SW down, LW up, LW down) [W/m^2].
Definition: ERF_TwoStreamCanopyForcing.H:48
const amrex::MultiFab * beam
Direct beam on a horizontal surface, interfaces 0 .. m_top [W/m^2].
Definition: ERF_TwoStreamCanopyForcing.H:46
const amrex::MultiFab * cos_zenith
Cosine of the zenith of the sweep, floored at zero (2D) [1].
Definition: ERF_TwoStreamCanopyForcing.H:47
bool valid() const noexcept
Definition: ERF_TwoStreamCanopyForcing.H:49
Here is the call graph for this function:

◆ compute_ground()

void IBFaceSet::compute_ground ( amrex::Real  dt)

Ground heat flux of the current step: advance every face's slab by dt with the skin temperature at the top and the interior temperature at the bottom (implicit, ERF_IBSEBSlab.H) and store the conduction into the slab, G, positive into the wall.

Slab conduction on every face: one kernel over the faces, each solving its own tridiagonal system with the skin at the top and the interior at the bottom.

1124 {
1125  const int nl = n_layers();
1126  const Real Tint = m_params.T_interior;
1127  const Real* pT = d_T_skin.data();
1128  const Real* pkt = d_kth.data(); const Real* prc = d_rhocp.data(); const Real* pth = d_thick.data();
1129  Real* pS = d_T_slab.data(); Real* pG = d_G.data();
1130  ParallelFor(m_nface, [=] AMREX_GPU_DEVICE (int f) noexcept {
1131  // Zero-initialized: only the first nl entries are ever used, but the
1132  // compiler cannot see that through the inlined solve and warns otherwise.
1134  for (int l = 0; l < nl; ++l) { T[l] = pS[f * nl + l]; }
1135  const Real dz = pth[f] / nl;
1136  pG[f] = ibseb::advance_slab_dirichlet(T, pT[f], Tint, pkt[f], prc[f], dz, dt, nl);
1137  for (int l = 0; l < nl; ++l) { pS[f * nl + l] = T[l]; }
1138  });
1139  Gpu::streamSynchronize();
1140 }
@ T
Definition: ERF_IndexDefines.H:168
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
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
constexpr int SLAB_MAX_LAYERS
Definition: ERF_IBSEBSlab.H:28
amrex::Real T_interior
Interior (deep) temperature [K].
Definition: ERF_IBSEBParams.H:29
Here is the call graph for this function:

◆ compute_longwave()

void IBFaceSet::compute_longwave ( const amrex::MultiFab &  cons,
const TwoStreamCanopyView &  canopy = TwoStreamCanopyView{} 
)

Longwave of the current step. Incoming on a face: f_sky LW_sky + f_ground LW_ground + f_bldg sigma T_skin^4, the sky term fixed or gray (sky_emissivity sigma T_air^4 with the air temperature of the face's fluid cell, its water vapour included) with the ground term eps_g sigma T_g^4, or with lw_mode = two_stream the longwave down and up at the face's height in the face's column (canopy); the building term is the isothermal-surroundings approximation. Net: eps (LW_in - sigma T_skin^4), positive into the face.

Parameters
consConserved state of the level, for the air temperature.
canopyWith lw_mode = two_stream, this step's two-stream view of the level (ERF_TwoStreamCanopyForcing.H), or an empty one before the first sweep, when the incoming and net longwave are zero.

Longwave of the current step. One kernel per fab over that fab's faces, reading the air temperature of the fluid cell from the conserved state (dry density, potential temperature and, with a moisture model, water vapour through the equation of state; cell_qv()). With lw_mode = two_stream the sky and ground terms come from the canopy forcing of the face's column, read from the 2D fab of the same local index (the level's grids flattened, on the same distribution).

936 {
937  if (m_params.lw_mode == "two_stream") {
939  return;
940  }
941  const Real sigma = ibseb::SIGMA_SB;
942  const bool gray = (m_params.lw_mode == "gray");
943  const Real lw_fix = m_params.lw_down, eps_sky = m_params.sky_emissivity;
944  const Real eps_g = m_params.emissivity_ground;
945  const Real* peps = d_emis.data();
946  const Real Tg4 = std::pow(m_params.T_ground, 4);
947  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
948  const Real* pfs = d_f_sky.data(); const Real* pfg = d_f_ground.data(); const Real* pfb = d_f_bldg.data();
949  const Real* pT = d_T_skin.data();
950  Real* pTa = d_T_air.data(); Real* pin = d_LW_down_in.data(); Real* pnet = d_LW_net.data();
951  Real* pext = d_LW_ext.data();
952  const int qv_comp = m_qv_comp;
953  for (MFIter mfi(cons); mfi.isValid(); ++mfi) {
954  const int f0 = m_fab_start[mfi.LocalIndex()];
955  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
956  auto const& c = cons.const_array(mfi);
957  ParallelFor(f1 - f0, [=] AMREX_GPU_DEVICE (int m) noexcept {
958  const int f = f0 + m;
959  const Real rho = c(pi[f], pj[f], pk[f], Rho_comp);
960  const Real qv = cell_qv(c, pi[f], pj[f], pk[f], qv_comp);
961  const Real Ta = getTgivenRandRTh(rho, c(pi[f], pj[f], pk[f], RhoTheta_comp), qv);
962  const Real lw_sky = gray ? eps_sky * sigma * Ta * Ta * Ta * Ta : lw_fix;
963  const Real Ts4 = pT[f] * pT[f] * pT[f] * pT[f];
964  // Sky and ground do not depend on the face; the wall term does
965  // (isothermal surroundings), so the balance keeps them apart.
966  const Real lw_ext = pfs[f] * lw_sky + pfg[f] * eps_g * sigma * Tg4;
967  const Real lw_in = lw_ext + pfb[f] * sigma * Ts4;
968  pTa[f] = Ta;
969  pext[f] = lw_ext;
970  pin[f] = lw_in;
971  pnet[f] = peps[f] * (lw_in - sigma * Ts4);
972  });
973  }
974  Gpu::streamSynchronize();
975 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getTgivenRandRTh(const amrex::Real rho, const amrex::Real rhotheta, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:46
#define Rho_comp
Definition: ERF_IndexDefines.H:53
amrex::Real sigma
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:11
void compute_longwave_two_stream(const amrex::MultiFab &cons, const TwoStreamCanopyView &canopy)
Definition: ERF_IBFaceSet.cpp:988
@ rho
Definition: ERF_Kessler.H:25
@ qv
Definition: ERF_Kessler.H:31
constexpr amrex::Real SIGMA_SB
Stefan-Boltzmann constant [W/m2/K4].
Definition: ERF_IBSEBBalance.H:54
amrex::Real T_ground
Ground temperature for the ground term [K].
Definition: ERF_IBSEBParams.H:98
amrex::Real sky_emissivity
gray: effective clear-sky emissivity
Definition: ERF_IBSEBParams.H:97
std::string lw_mode
Definition: ERF_IBSEBParams.H:95
amrex::Real emissivity_ground
Ground emissivity for the ground term.
Definition: ERF_IBSEBParams.H:80
amrex::Real lw_down
fixed: sky longwave [W/m2]
Definition: ERF_IBSEBParams.H:96
Here is the call graph for this function:

◆ compute_longwave_two_stream()

void IBFaceSet::compute_longwave_two_stream ( const amrex::MultiFab &  cons,
const TwoStreamCanopyView &  canopy 
)

Longwave of the current step with lw_mode = two_stream: the sky term is the longwave down of the face's column at the face's height (a roof's interface, a wall's mean of its cell's two; two_stream_canopy_sample()), the ground term its longwave up at the same height (the ground seen through the air in between), so the ground's temperature, emissivity and reflection of the sky are the column's (two_stream_face_sky()). Before the level's first sweep (an empty canopy, the initial report) the air temperature is read and the incoming and net longwave are zero.

989 {
990  const Real sigma = ibseb::SIGMA_SB;
991  const bool have = canopy.valid();
992  const int k0 = m_dom_lo[2];
993  const Real* peps = d_emis.data();
994  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
995  const int* pd = d_dir.data(); const int* ps = d_side.data();
996  const Real* pfs = d_f_sky.data(); const Real* pfg = d_f_ground.data(); const Real* pfb = d_f_bldg.data();
997  const Real* pT = d_T_skin.data();
998  Real* pTa = d_T_air.data(); Real* pin = d_LW_down_in.data(); Real* pnet = d_LW_net.data();
999  Real* pext = d_LW_ext.data();
1000  const int qv_comp = m_qv_comp;
1001  for (MFIter mfi(cons); mfi.isValid(); ++mfi) {
1002  const int f0 = m_fab_start[mfi.LocalIndex()];
1003  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
1004  auto const& c = cons.const_array(mfi);
1005  const auto beam = have ? canopy.beam->const_array(mfi) : Array4<const Real>{};
1006  const auto cosz = have ? canopy.cos_zenith->const_array(mfi) : Array4<const Real>{};
1007  const auto flux = have ? canopy.fluxes->const_array(mfi) : Array4<const Real>{};
1008  ParallelFor(f1 - f0, [=] AMREX_GPU_DEVICE (int m) noexcept {
1009  const int f = f0 + m;
1010  const Real rho = c(pi[f], pj[f], pk[f], Rho_comp);
1011  pTa[f] = getTgivenRandRTh(rho, c(pi[f], pj[f], pk[f], RhoTheta_comp),
1012  cell_qv(c, pi[f], pj[f], pk[f], qv_comp));
1013  if (!have) {
1014  pext[f] = 0.0; pin[f] = 0.0; pnet[f] = 0.0;
1015  return;
1016  }
1017  const Real Ts4 = pT[f] * pT[f] * pT[f] * pT[f];
1018  const TwoStreamFaceSky sky = two_stream_face_sky(pi[f], pj[f], pk[f], k0, pd[f], ps[f], beam, cosz, flux);
1019  const Real lw_ext = pfs[f] * sky.lw_down + pfg[f] * sky.lw_up;
1020  const Real lw_in = lw_ext + pfb[f] * sigma * Ts4;
1021  pext[f] = lw_ext;
1022  pin[f] = lw_in;
1023  pnet[f] = peps[f] * (lw_in - sigma * Ts4);
1024  });
1025  }
1026  Gpu::streamSynchronize();
1027 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE TwoStreamFaceSky two_stream_face_sky(int i, int j, int k, int k0, int dir, int side, const amrex::Array4< const amrex::Real > &beam, const amrex::Array4< const amrex::Real > &cosz, const amrex::Array4< const amrex::Real > &fluxes)
The column radiation a face at fluid cell (i,j,k) of direction dir, with its solid on side side,...
Definition: ERF_TwoStreamCanopyForcing.H:133
What one face takes from its column (all W/m^2).
Definition: ERF_TwoStreamCanopyForcing.H:112
amrex::Real lw_up
Longwave up at the face's height (the ground's emission and the air's below)
Definition: ERF_TwoStreamCanopyForcing.H:117
amrex::Real lw_down
Longwave down at the face's height.
Definition: ERF_TwoStreamCanopyForcing.H:115
Here is the call graph for this function:

◆ compute_sensible()

void IBFaceSet::compute_sensible ( const amrex::MultiFab &  cons,
const amrex::MultiFab &  xvel,
const amrex::MultiFab &  yvel,
const amrex::MultiFab &  zvel,
amrex::Real  c_p,
const amrex::MultiFab *  olen_ground = nullptr,
const amrex::MultiFab *  pblh_ground = nullptr,
amrex::Real  z_i_bulk = 0.0 
)

Sensible heat of the current step through a wall function on every face: the tangential wind of the fluid cell at half a cell from the wall gives u* with the roughness z0_wall, the skin-to-air potential-temperature difference gives theta* with z0h_wall, and H = rho c_p u* theta*, positive out of the face. Neutral, or with the surface layer's stability functions on roofs when asked. The latent flux is not modelled and stays zero.

Beyond neutral: with convective_velocity = deardorff the wind is sqrt(U_tan^2 + (beta w*)^2) with w* from the previous step's H (zero when H is into the face) and a depth that is the mixed layer above a roof or the building height for a wall; with stability_correction the roofs iterate the similarity functions on the face's own Obukhov length, seeded from the ground surface layer's 2D field at the face's column when given.

Fills d_H, d_ustar, d_U_tan, d_rho, d_theta_air, d_H_coeff (the coefficient of the skin-to-air potential-temperature difference that the balance keeps frozen), d_w_star, d_olen and d_z_i; T_air was read by compute_longwave(), which must run first in a step.

Parameters
consConserved state of the level (density, rho theta).
xvel,yvel,zvelFace-centred velocities of the level.
c_pSpecific heat of air [J/kg/K].
olen_groundGround surface layer's Obukhov length (2D field on the level's box array collapsed to k = 0), or null.
pblh_groundIts boundary-layer height field, or null.
z_i_bulkMixed-layer depth of the level from the bulk Richardson diagnostic or the fixed input [m].

Wall function on every face. One kernel per fab over that fab's faces: the cell-centred velocity of the fluid cell from its face values, its tangential part with respect to the wall, the neutral log law for u* and theta*, and on request two additions: a convective velocity scale in the wind (Beljaars' gustiness form with Deardorff's w* from the previous step's flux) and, on roofs, the surface layer's similarity functions iterated on the face's own Obukhov length. The skin temperature is converted to a potential temperature with the fluid cell's Exner function before the difference is taken.

The Obukhov length of a face is L = u*^2 theta / (kappa g theta*) (negative over a hot face). Walls stay on the log law even with the stability correction: the similarity functions assume a horizontal surface, and the convective scale is what carries free convection on a wall. The ground's 2D Obukhov field only seeds the roof iteration.

1237 {
1238  const Real z0 = m_params.z0_wall, z0h = m_params.z0h_wall;
1239  const bool stab = m_params.stability_correction;
1240  const bool conv = (m_params.convective_velocity == "deardorff");
1241  const bool seed = stab && (olen_ground != nullptr) && (m_params.obukhov_seed == "ground");
1242  const bool use_pblh = conv && (pblh_ground != nullptr) && (m_params.z_i_mode == "pblh");
1243  const Real beta = m_params.beta_conv;
1244  const Real relax = m_params.obukhov_relax;
1245  const Real zi_fallback = (z_i_bulk > 0.0) ? z_i_bulk : m_params.z_i;
1246  const Real dxa[3] = {m_dx[0], m_dx[1], m_dx[2]};
1247  const Real z_lo = m_z_ground;
1248  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
1249  const int* pd = d_dir.data();
1250  const Real* pT = d_T_skin.data(); const Real* phb = d_hbld.data(); const Real* pzf = d_zf.data();
1251  Real* pth = d_theta_air.data(); Real* prho = d_rho.data(); Real* pU = d_U_tan.data();
1252  Real* pus = d_ustar.data(); Real* pH = d_H.data(); Real* pTa = d_T_air.data();
1253  Real* pHc = d_H_coeff.data(); Real* pws = d_w_star.data(); Real* pol = d_olen.data(); Real* pzi = d_z_i.data();
1254  const int qv_comp = m_qv_comp;
1255  for (MFIter mfi(cons); mfi.isValid(); ++mfi) {
1256  const int f0 = m_fab_start[mfi.LocalIndex()];
1257  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
1258  auto const& c = cons.const_array(mfi);
1259  auto const& u = xvel.const_array(mfi);
1260  auto const& v = yvel.const_array(mfi);
1261  auto const& w = zvel.const_array(mfi);
1262  // The surface layer's 2D fields share the level's distribution with
1263  // the boxes collapsed to k = 0, so the same iterator indexes them.
1264  Array4<const Real> ol = seed ? olen_ground->const_array(mfi) : Array4<const Real>{};
1265  Array4<const Real> pb = use_pblh ? pblh_ground->const_array(mfi) : Array4<const Real>{};
1266  ParallelFor(f1 - f0, [=] AMREX_GPU_DEVICE (int m) noexcept {
1267  const int f = f0 + m;
1268  const int i = pi[f], j = pj[f], k = pk[f], d = pd[f];
1269  const Real rho = c(i, j, k, Rho_comp);
1270  const Real rth = c(i, j, k, RhoTheta_comp);
1271  const Real th = rth / rho;
1272  const Real Ta = getTgivenRandRTh(rho, rth, cell_qv(c, i, j, k, qv_comp));
1273  const Real th_skin = pT[f] * th / Ta; // skin temperature as a potential temperature
1274  // Cell-centred velocity and its tangential part with respect to the wall.
1275  Real U[3] = { Real(0.5) * (u(i, j, k) + u(i + 1, j, k)),
1276  Real(0.5) * (v(i, j, k) + v(i, j + 1, k)),
1277  Real(0.5) * (w(i, j, k) + w(i, j, k + 1)) };
1278  U[d] = 0.0;
1279  const Real Ut = amrex::max(std::sqrt(U[0] * U[0] + U[1] * U[1] + U[2] * U[2]), Real(1.0e-3));
1280  // Convective velocity scale from the previous step's flux out of the face.
1281  Real wstar = 0.0, depth = 0.0;
1282  if (conv && pH[f] > 0.0) {
1283  if (d == 2) {
1284  Real zi = use_pblh ? pb(i, j, 0) : zi_fallback;
1285  if (!(zi > 0.0 && zi < 1.0e5)) { zi = zi_fallback; }
1286  depth = amrex::max(zi - (pzf[f] - z_lo), phb[f]);
1287  } else {
1288  depth = phb[f];
1289  }
1290  wstar = std::cbrt(CONST_GRAV / th * pH[f] / (rho * c_p) * depth);
1291  }
1292  const Real Ut_eff = std::sqrt(Ut * Ut + beta * beta * wstar * wstar);
1293  const Real delta = 0.5 * dxa[d];
1294  const Real lnm = std::log(delta / z0), lnh = std::log(delta / z0h);
1295  Real ustar = KAPPA * Ut_eff / lnm;
1296  Real thstar = KAPPA * (th - th_skin) / lnh;
1297  Real lnh_eff = lnh;
1298  Real olen = 1.0e30;
1299  if (stab && d == 2) {
1300  // Roofs: the surface layer's similarity functions on the
1301  // face's own Obukhov length, a few fixed-point passes, seeded
1302  // from the ground's field at this column when available.
1303  similarity_funs sf;
1304  Real L = 1.0e30;
1305  if (seed) {
1306  const Real Lg = ol(i, j, 0);
1307  if (std::abs(Lg) > 0.0 && std::abs(Lg) < 1.0e5) { L = Lg; }
1308  }
1309  if (!(std::abs(L) < 1.0e5)) {
1310  L = ustar * ustar * th / (KAPPA * CONST_GRAV * thstar + 1.0e-20);
1311  }
1312  // Fixed point on L until u*, theta* and L agree to 1e-6, so
1313  // the stored values are mutually consistent (a fixed number
1314  // of passes leaves L one pass behind). u* is under-relaxed
1315  // between passes, as the surface layer's iteration is in
1316  // erf-model #3486, which keeps a strongly unstable roof from
1317  // oscillating at low wind; the converged values do not
1318  // depend on the factor.
1319  Real us_it = ustar;
1320  for (int it = 0; it < 50; ++it) {
1321  const Real zeta = delta / L;
1322  const Real psi_m = sf.calc_psi_m(zeta), psi_h = sf.calc_psi_h(zeta);
1323  const Real us_new = KAPPA * Ut_eff / amrex::max(lnm - psi_m, Real(0.1));
1324  us_it = (it == 0) ? us_new : (1.0 - relax) * us_it + relax * us_new;
1325  lnh_eff = amrex::max(lnh - psi_h, Real(0.1));
1326  thstar = KAPPA * (th - th_skin) / lnh_eff;
1327  const Real L_new = us_it * us_it * th / (KAPPA * CONST_GRAV * thstar + 1.0e-20);
1328  const bool done = std::abs(L_new - L) <= 1.0e-6 * std::abs(L_new) &&
1329  std::abs(us_new - us_it) <= 1.0e-6 * us_new;
1330  L = L_new;
1331  if (done) { us_it = us_new; break; }
1332  }
1333  ustar = us_it;
1334  olen = L;
1335  }
1336  pth[f] = th;
1337  pTa[f] = Ta;
1338  prho[f] = rho;
1339  pU[f] = Ut;
1340  pus[f] = ustar;
1341  pws[f] = wstar;
1342  pol[f] = olen;
1343  pzi[f] = depth;
1344  pHc[f] = rho * c_p * KAPPA * ustar / lnh_eff; // H = H_coeff (theta_skin - theta_air)
1345  pH[f] = -rho * c_p * ustar * thstar; // positive out of the face
1346  });
1347  }
1348  Gpu::streamSynchronize();
1349 }
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:71
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:72
Real z0
Definition: ERF_InitCustomPertVels_ScalarAdvDiff.H:12
Real w
Definition: ERF_Plotfile2DInterpolator.cpp:23
@ olen
Definition: ERF_SLM.H:104
@ ustar
Definition: ERF_SLM.H:28
@ U
Definition: ERF_IndexDefines.H:166
@ beta
Definition: ERF_SLM.H:152
@ xvel
Definition: ERF_IndexDefines.H:255
@ zvel
Definition: ERF_IndexDefines.H:257
@ yvel
Definition: ERF_IndexDefines.H:256
@ zi
Definition: ERF_AdvanceWDM6.cpp:277
std::string convective_velocity
"none" or "deardorff"
Definition: ERF_IBSEBParams.H:159
amrex::Real obukhov_relax
Under-relaxation of the roof's u*, theta* iteration (1 = none), as erf-model #3486.
Definition: ERF_IBSEBParams.H:164
amrex::Real z0_wall
Momentum roughness of the faces [m].
Definition: ERF_IBSEBParams.H:108
std::string z_i_mode
"bulk_ri", "pblh" or "fixed"
Definition: ERF_IBSEBParams.H:161
amrex::Real beta_conv
Gustiness factor on w*.
Definition: ERF_IBSEBParams.H:160
amrex::Real z_i
Fixed mixed-layer depth [m] (z_i_mode = fixed, and the fallback)
Definition: ERF_IBSEBParams.H:162
amrex::Real z0h_wall
Heat roughness of the faces [m].
Definition: ERF_IBSEBParams.H:109
bool stability_correction
Definition: ERF_IBSEBParams.H:110
std::string obukhov_seed
"ground" or "neutral"
Definition: ERF_IBSEBParams.H:158
Definition: ERF_MOSTUtils.H:67
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_m(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:132
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real calc_psi_h(amrex::Real zeta) const
Definition: ERF_MOSTUtils.H:151

◆ compute_shortwave()

void IBFaceSet::compute_shortwave ( amrex::Real  time,
const TwoStreamCanopyView &  canopy = TwoStreamCanopyView{} 
)

Shortwave of the current step: the sun's position (sun_mode), the irradiances of the prescribed provider or, with radiation = two_stream, those of the face's own column at its height (canopy), then per face the direct beam on the face (zero when the ray toward the sun hits a building), the diffuse light through the view fractions, and the absorbed sum.

Fills d_shadow, d_SW_direct_in, d_SW_diffuse_in and d_SW_abs; stores the sun vector and irradiances for the reports (with two_stream the means over all the level's faces of the direct-normal and diffuse irradiance each takes, written to every row of the level).

Parameters
timeSimulation time [s]; with sun_mode = solar it is added to time_zero_utc_s (with sun_mode = two_stream the sun comes from set_two_stream_sun()).
canopyWith radiation = two_stream, this step's two-stream view of the level (see compute_longwave()), or an empty one before the first sweep, when every face gets no shortwave.

Shortwave of the current step. The sun and the irradiances are scalars computed on the host from the provider inputs; the per-face work is one kernel: incidence cosine, ray cast against the column tops, direct and diffuse incident, absorbed.

With sun_mode = fixed the direct-normal irradiance and the horizontal diffuse are the inputs as given. With sun_mode = solar the sun follows the site and time and the clear-sky formulas give both irradiances; with sun_mode = two_stream the position and the top-of-atmosphere irradiance are the two-stream radiation's (set_two_stream_sun()) and the same clear-sky formulas give the irradiances. The sun below the horizon gives zero everywhere.

Diffuse on a face: f_sky * diffuse_h + f_ground * albedo_ground * (direct_h + diffuse_h), the second term being the ground-reflected part, with the direct on a horizontal surface direct_h = dni * cos z. The view fractions are the placeholders of build() until compute_view_fractions() has run.

1403 {
1404  // ---- Sun and irradiances (host scalars) ----
1405  SunState s;
1406  if (m_params.sun_mode == "fixed") {
1407  s.zenith = m_params.sun_zenith_deg * PI / 180.0;
1408  s.azimuth = m_params.sun_azimuth_deg * PI / 180.0;
1409  const Real cz = std::cos(s.zenith);
1410  s.dni = (cz > 0.0) ? m_params.sw_direct_normal : 0.0;
1411  s.diffuse_h = (cz > 0.0) ? m_params.sw_diffuse : 0.0;
1412  } else if (m_params.sun_mode == "two_stream") {
1414  "erf.ibseb.sun_mode = two_stream: the two-stream sun was not given before the shortwave");
1415  // Used once: the next shortwave needs the sun of its own time.
1416  m_ts_sun.set = false;
1417  const Real decl = m_ts_sun.decl;
1418  const Real ha = m_ts_sun.hour_angle;
1419  s.zenith = ibseb::solar_zenith(m_ts_sun.lat_deg, decl, ha);
1420  s.azimuth = ibseb::solar_azimuth(m_ts_sun.lat_deg, decl, ha, s.zenith);
1421  const Real cz = std::cos(s.zenith);
1422  // The irradiance carries the date's distance factor already.
1426  } else {
1427  const Real t_utc = m_params.time_zero_utc_s + time;
1428  // The date advances at each 00:00 UTC, so a run of several days follows the season.
1429  const Real day = ibseb::day_of_year_at(m_params.day_of_year, t_utc);
1430  const Real decl = ibseb::solar_declination(day);
1431  // Hour angle from UTC and the longitude (a time-zone offset would cancel).
1432  const Real ha = ibseb::solar_hour_angle(t_utc, m_params.longitude_deg, day);
1433  s.zenith = ibseb::solar_zenith(m_params.latitude_deg, decl, ha);
1434  s.azimuth = ibseb::solar_azimuth(m_params.latitude_deg, decl, ha, s.zenith);
1435  const Real cz = std::cos(s.zenith);
1436  const Real e0 = ibseb::earth_sun_distance_factor(day);
1440  }
1441  ibseb::sun_vector(s.zenith, s.azimuth, s.sx, s.sy, s.sz);
1442  m_sun = s;
1443 
1444  if (m_params.radiation == "two_stream") {
1446  return;
1447  }
1448 
1449  // ---- Per face ----
1450  const Real sx = s.sx, sy = s.sy, sz = s.sz;
1451  const Real dni = s.dni, dif_h = s.diffuse_h;
1452  const Real dir_h = dni * amrex::max(Real(0.0), sz);
1453  const Real alb_g = m_params.albedo_ground;
1454  const Real* palb = d_albedo.data();
1455  const ibseb::RayMap rm = ray_map();
1456  const int* pd = d_dir.data(); const int* ps = d_side.data();
1457  const Real* pxf = d_xf.data(); const Real* pyf = d_yf.data(); const Real* pzf = d_zf.data();
1458  const Real* pfs = d_f_sky.data(); const Real* pfg = d_f_ground.data();
1459  Real* psh = d_shadow.data(); Real* pdir = d_SW_direct_in.data();
1460  Real* pdif = d_SW_diffuse_in.data(); Real* pabs = d_SW_abs.data();
1461  ParallelFor(m_nface, [=] AMREX_GPU_DEVICE (int f) noexcept {
1462  // Outward normal: opposite to the side the solid is on.
1463  Real n[3] = {0.0, 0.0, 0.0};
1464  n[pd[f]] = -static_cast<Real>(ps[f]);
1465  const Real cosi = n[0] * sx + n[1] * sy + n[2] * sz;
1466  Real shadow = 0.0, direct = 0.0;
1467  if (sz > 0.0 && cosi > 0.0 && dni > 0.0) {
1468  shadow = ibseb::ray_blocked(pxf[f], pyf[f], pzf[f], sx, sy, sz, rm) ? 1.0 : 0.0;
1469  direct = dni * cosi * (1.0 - shadow);
1470  }
1471  const Real diffuse = pfs[f] * dif_h + pfg[f] * alb_g * (dir_h + dif_h);
1472  psh[f] = shadow;
1473  pdir[f] = direct;
1474  pdif[f] = diffuse;
1475  pabs[f] = (1.0 - palb[f]) * (direct + diffuse);
1476  });
1477  Gpu::streamSynchronize();
1478 
1479  if (m_params.debug) {
1480  Print() << "[IBSEB DEBUG] lev=" << m_lev << " sun: zenith=" << s.zenith * 180.0 / PI
1481  << " deg azimuth=" << s.azimuth * 180.0 / PI << " deg s=(" << s.sx << "," << s.sy << "," << s.sz
1482  << ") DNI=" << s.dni << " W/m2 diffuse_h=" << s.diffuse_h << " W/m2\n";
1483  }
1484 }
ibseb::RayMap ray_map() const
Definition: ERF_IBFaceSet.cpp:768
void compute_shortwave_two_stream(const TwoStreamCanopyView &canopy)
Definition: ERF_IBFaceSet.cpp:1508
SunState m_sun
Definition: ERF_IBFaceSet.H:599
TwoStreamSun m_ts_sun
Definition: ERF_IBFaceSet.H:602
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 earth_sun_distance_factor(amrex::Real day_of_year)
Definition: ERF_IBSEBSolar.H:155
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
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 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
amrex::Real lat_deg
Definition: ERF_IBFaceSet.H:601
amrex::Real S0
Definition: ERF_IBFaceSet.H:601
amrex::Real decl
Definition: ERF_IBFaceSet.H:601
amrex::Real hour_angle
Definition: ERF_IBFaceSet.H:601
bool set
Definition: ERF_IBFaceSet.H:601
std::string sun_mode
Definition: ERF_IBSEBParams.H:56
amrex::Real day_of_year
solar: day of year
Definition: ERF_IBSEBParams.H:63
amrex::Real sw_transmission
solar, two_stream: bulk clear-sky transmission (Bird)
Definition: ERF_IBSEBParams.H:66
amrex::Real longitude_deg
solar: site longitude, east positive
Definition: ERF_IBSEBParams.H:62
amrex::Real albedo_ground
Ground albedo for the reflected diffuse term.
Definition: ERF_IBSEBParams.H:78
std::string radiation
Definition: ERF_IBSEBParams.H:46
amrex::Real solar_constant
solar: [W/m2]
Definition: ERF_IBSEBParams.H:65
amrex::Real sw_direct_normal
fixed: direct-normal irradiance [W/m2]
Definition: ERF_IBSEBParams.H:59
amrex::Real time_zero_utc_s
solar: seconds after 00:00 UTC at simulation time zero (the sun follows UTC and the longitude)
Definition: ERF_IBSEBParams.H:64
amrex::Real sun_zenith_deg
fixed: zenith angle [deg]
Definition: ERF_IBSEBParams.H:57
amrex::Real sw_diffuse
fixed: diffuse irradiance on a horizontal surface [W/m2]
Definition: ERF_IBSEBParams.H:60
amrex::Real sun_azimuth_deg
fixed: azimuth clockwise from north [deg]; 180 = sun in the south
Definition: ERF_IBSEBParams.H:58
amrex::Real latitude_deg
solar: site latitude, north positive
Definition: ERF_IBSEBParams.H:61
amrex::Real sw_diffuse_coeff
solar, two_stream: share of the attenuated beam that arrives as diffuse light
Definition: ERF_IBSEBParams.H:67
Definition: ERF_IBSEBSolar.H:219
Here is the call graph for this function:

◆ compute_shortwave_two_stream()

void IBFaceSet::compute_shortwave_two_stream ( const TwoStreamCanopyView &  canopy)

Shortwave of the current step with radiation = two_stream, after compute_shortwave() has placed the sun (m_sun): per face, the direct-normal irradiance of its column at its height (two_stream_face_sky(): a roof's interface, a wall's mean of its cell's two), the beam over the column's own cosine of the zenith, on the face (shadowed by the same ray cast), the diffuse light of the sky there through f_sky, and the ground's reflection, the column's shortwave up at the face's height, through f_ground. One kernel per fab, reading the view's fields of the same local index.

The faces' sun (sun_mode) and the sweep's are required to be one sun (ERF::ibseb_check_sun_matches_two_stream()); the kernel checks it, column by column: a cosine of the zenith more than 1e-4 (1e-3 in single precision) from the faces' stops the run, since a direct beam divided by the wrong cosine and laid along the wrong direction would still look plausible.

Before the level's first sweep (an empty canopy, the initial report) every face gets no shortwave. The reported direct-normal and diffuse irradiances are the means over the level's faces of their columns'.

1509 {
1510  const Real sx = m_sun.sx, sy = m_sun.sy, sz = m_sun.sz;
1511  const Real* palb = d_albedo.data();
1512  const ibseb::RayMap rm = ray_map();
1513  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
1514  const int k0 = m_dom_lo[2];
1515  const int* pd = d_dir.data(); const int* ps = d_side.data();
1516  const Real* pxf = d_xf.data(); const Real* pyf = d_yf.data(); const Real* pzf = d_zf.data();
1517  const Real* pfs = d_f_sky.data(); const Real* pfg = d_f_ground.data();
1518  Real* psh = d_shadow.data(); Real* pdir = d_SW_direct_in.data();
1519  Real* pdif = d_SW_diffuse_in.data(); Real* pabs = d_SW_abs.data();
1520 
1521  if (!canopy.valid()) {
1522  ParallelFor(m_nface, [=] AMREX_GPU_DEVICE (int f) noexcept {
1523  psh[f] = 0.0; pdir[f] = 0.0; pdif[f] = 0.0; pabs[f] = 0.0;
1524  });
1525  Gpu::streamSynchronize();
1526  m_sun.dni = 0.0;
1527  m_sun.diffuse_h = 0.0;
1528  return;
1529  }
1530 
1531  // Sums of the columns' direct-normal and diffuse irradiance over the faces,
1532  // for the report, and the largest disagreement of the two suns.
1533  ReduceOps<ReduceOpSum, ReduceOpSum, ReduceOpMax> reduce_op;
1534  ReduceData<Real, Real, Real> reduce_data(reduce_op);
1535  using ReduceTuple = typename decltype(reduce_data)::Type;
1536  const Real sz_up = amrex::max(Real(0.0), sz);
1537  const int nfab = static_cast<int>(m_fab_start.size()) - 1;
1538  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(canopy.beam->local_size() == nfab,
1539  "erf.ibseb.radiation = two_stream: the two-stream beam holds another number of boxes than the faces' level");
1540  for (MFIter mfi(*canopy.beam); mfi.isValid(); ++mfi) {
1541  const int f0 = m_fab_start[mfi.LocalIndex()];
1542  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
1543  const auto beam = canopy.beam->const_array(mfi);
1544  const auto cosz = canopy.cos_zenith->const_array(mfi);
1545  const auto flux = canopy.fluxes->const_array(mfi);
1546  reduce_op.eval(f1 - f0, reduce_data, [=] AMREX_GPU_DEVICE (int m) noexcept -> ReduceTuple {
1547  const int f = f0 + m;
1548  const Real cz = cosz(pi[f], pj[f], 0);
1549  const TwoStreamFaceSky sky = two_stream_face_sky(pi[f], pj[f], pk[f], k0, pd[f], ps[f], beam, cosz, flux);
1550  const Real dni = sky.dni, dif_h = sky.diffuse, refl = sky.sw_up;
1551  // Outward normal: opposite to the side the solid is on.
1552  Real n[3] = {0.0, 0.0, 0.0};
1553  n[pd[f]] = -static_cast<Real>(ps[f]);
1554  const Real cosi = n[0] * sx + n[1] * sy + n[2] * sz;
1555  Real shadow = 0.0, direct = 0.0;
1556  if (sz > 0.0 && cosi > 0.0 && dni > 0.0) {
1557  shadow = ibseb::ray_blocked(pxf[f], pyf[f], pzf[f], sx, sy, sz, rm) ? 1.0 : 0.0;
1558  direct = dni * cosi * (1.0 - shadow);
1559  }
1560  const Real diffuse = pfs[f] * dif_h + pfg[f] * refl;
1561  psh[f] = shadow;
1562  pdir[f] = direct;
1563  pdif[f] = diffuse;
1564  pabs[f] = (1.0 - palb[f]) * (direct + diffuse);
1565  return {dni, dif_h, std::abs(cz - sz_up)};
1566  });
1567  }
1568  Gpu::streamSynchronize();
1569  const auto rv = reduce_data.value(reduce_op);
1570  Real sums[2] = {amrex::get<0>(rv), amrex::get<1>(rv)};
1571  Real sun_gap = amrex::get<2>(rv);
1572  ParallelDescriptor::ReduceRealSum(sums, 2);
1573  ParallelDescriptor::ReduceRealMax(sun_gap);
1574  const Real nf_all = static_cast<Real>(m_nface_dir[0] + m_nface_dir[1] + m_nface_dir[2]);
1575  m_sun.dni = (nf_all > 0.0) ? sums[0] / nf_all : Real(0.0);
1576  m_sun.diffuse_h = (nf_all > 0.0) ? sums[1] / nf_all : Real(0.0);
1577  // The two suns are one formula evaluated twice; a float calendar day resolves
1578  // about a second of the hour angle, hence the looser single-precision bound.
1579  const Real sun_tol = (sizeof(Real) == 8) ? Real(1.e-4) : Real(1.e-3);
1580  if (sun_gap > sun_tol) {
1581  Abort("erf.ibseb.radiation = two_stream: on level " + std::to_string(m_lev) + " the faces' sun (zenith "
1582  + std::to_string(m_sun.zenith * 180.0 / PI) + " deg) and the two-stream sweep's differ by up to "
1583  + std::to_string(sun_gap) + " in the cosine of the zenith angle (more than " + std::to_string(sun_tol)
1584  + "); the faces must take the sweep's sun"
1585  " (erf.ibseb.sun_mode = two_stream, or fixed at erf.fixed_solar_zenith_angle)");
1586  }
1587  if (m_params.debug) {
1588  Print() << "[IBSEB DEBUG] lev=" << m_lev << " sun: zenith=" << m_sun.zenith * 180.0 / PI
1589  << " deg azimuth=" << m_sun.azimuth * 180.0 / PI << " deg s=(" << m_sun.sx << "," << m_sun.sy << ","
1590  << m_sun.sz << "); two-stream columns: mean DNI=" << m_sun.dni
1591  << " W/m2 mean diffuse_h=" << m_sun.diffuse_h << " W/m2 over the faces, largest sun gap "
1592  << sun_gap << " in cos z\n";
1593  }
1594 }
amrex::Real diffuse_h
Diffuse on a horizontal surface [W/m2].
Definition: ERF_IBFaceSet.H:519
amrex::Real dni
Direct-normal irradiance [W/m2].
Definition: ERF_IBFaceSet.H:518
amrex::Real zenith
Definition: ERF_IBFaceSet.H:516
amrex::Real sy
Definition: ERF_IBFaceSet.H:517
amrex::Real sx
Definition: ERF_IBFaceSet.H:517
amrex::Real azimuth
[rad]
Definition: ERF_IBFaceSet.H:516
amrex::Real sz
Unit vector toward the sun.
Definition: ERF_IBFaceSet.H:517
amrex::Real sw_up
Shortwave up at the face's height (the ground's reflection, seen from there)
Definition: ERF_TwoStreamCanopyForcing.H:116
amrex::Real diffuse
Diffuse shortwave down at the face's height.
Definition: ERF_TwoStreamCanopyForcing.H:114
amrex::Real dni
Direct-normal irradiance at the face's height.
Definition: ERF_TwoStreamCanopyForcing.H:113

Referenced by compute_shortwave().

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

◆ compute_view_fractions()

void IBFaceSet::compute_view_fractions ( )

View fractions: sample a cosine-weighted hemisphere around every face's outward normal with view_n_az x view_n_el rays through the column walk and count where they end: sky, ground or a building. The three fractions sum to one and are view factors. Called once at initialisation; the geometry is static.

Hemisphere sampling for the view fractions. One kernel over the faces, each looping over its rays; the count of rays ending on the sky, the ground and a building over the total gives the three fractions. Roofs point up and never see the ground; a wall on flat open ground sees half sky and half ground, which the regression test checks.

887 {
888  const int n_az = m_params.view_n_az, n_el = m_params.view_n_el;
889  const ibseb::RayMap rm = ray_map();
890  const int* pd = d_dir.data(); const int* ps = d_side.data();
891  const Real* pxf = d_xf.data(); const Real* pyf = d_yf.data(); const Real* pzf = d_zf.data();
892  Real* pfs = d_f_sky.data(); Real* pfg = d_f_ground.data(); Real* pfb = d_f_bldg.data();
893  ParallelFor(m_nface, [=] AMREX_GPU_DEVICE (int f) noexcept {
894  int n_sky = 0, n_gnd = 0, n_bld = 0;
895  for (int ie = 0; ie < n_el; ++ie) {
896  for (int ia = 0; ia < n_az; ++ia) {
897  Real sx, sy, sz;
898  ibseb::hemisphere_direction(pd[f], -ps[f], ia, ie, n_az, n_el, sx, sy, sz);
899  const int hit = ibseb::ray_hit(pxf[f], pyf[f], pzf[f], sx, sy, sz, rm);
900  if (hit == ibseb::RAY_SKY) ++n_sky; else if (hit == ibseb::RAY_GROUND) ++n_gnd; else ++n_bld;
901  }
902  }
903  const Real inv = 1.0 / static_cast<Real>(n_az * n_el);
904  pfs[f] = n_sky * inv; pfg[f] = n_gnd * inv; pfb[f] = n_bld * inv;
905  });
906  Gpu::streamSynchronize();
907 
908  if (m_params.debug) {
909  std::vector<Real> fs(m_nface), fg(m_nface), fb(m_nface);
910  Gpu::copy(Gpu::deviceToHost, d_f_sky.begin(), d_f_sky.end(), fs.begin());
911  Gpu::copy(Gpu::deviceToHost, d_f_ground.begin(), d_f_ground.end(), fg.begin());
912  Gpu::copy(Gpu::deviceToHost, d_f_bldg.begin(), d_f_bldg.end(), fb.begin());
913  Gpu::streamSynchronize();
914  Real s[3] = {0.0, 0.0, 0.0};
915  for (int n = 0; n < m_nface; ++n) { s[0] += fs[n]; s[1] += fg[n]; s[2] += fb[n]; }
916  ParallelDescriptor::ReduceRealSum(s, 3);
917  // A refined level can hold no building at all.
918  const Real nf_all = amrex::max(Real(1.0), static_cast<Real>(m_nface_dir[0] + m_nface_dir[1] + m_nface_dir[2]));
919  Print() << "[IBSEB DEBUG] lev=" << m_lev << " view fractions: " << n_az * n_el
920  << " rays per face, mean f_sky=" << s[0] / nf_all << " f_ground=" << s[1] / nf_all
921  << " f_bldg=" << s[2] / nf_all << "\n";
922  }
923 }
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
@ RAY_GROUND
Definition: ERF_IBSEBSolar.H:351
@ RAY_SKY
Definition: ERF_IBSEBSolar.H:351
Here is the call graph for this function:

◆ dump_faces()

void IBFaceSet::dump_faces ( const std::string &  prefix) const

Write every face of this rank to <prefix>.rank<N>.csv with its geometry, view fractions, shadow flag, shortwave terms and skin temperature, for the regression tests and for users who want the raw list.

Per-rank face dump. Every rank writes its own file so no gather is needed; readers concatenate <prefix>.rank*.csv.

1631 {
1632  const int nf = m_nface;
1633  auto host = [&](const Gpu::DeviceVector<Real>& d) {
1634  std::vector<Real> h(nf);
1635  Gpu::copy(Gpu::deviceToHost, d.begin(), d.end(), h.begin());
1636  return h;
1637  };
1638  auto hosti = [&](const Gpu::DeviceVector<int>& d) {
1639  std::vector<int> h(nf);
1640  Gpu::copy(Gpu::deviceToHost, d.begin(), d.end(), h.begin());
1641  return h;
1642  };
1643  const auto i = hosti(d_i), j = hosti(d_j), k = hosti(d_k), dir = hosti(d_dir), side = hosti(d_side), bid = hosti(d_bid);
1644  const auto xf = host(d_xf), yf = host(d_yf), zf = host(d_zf), area = host(d_area);
1645  const auto fs = host(d_f_sky), fg = host(d_f_ground), fb = host(d_f_bldg);
1646  const auto sh = host(d_shadow), sdir = host(d_SW_direct_in), sdif = host(d_SW_diffuse_in), sabs = host(d_SW_abs);
1647  const auto T = host(d_T_skin), Ta = host(d_T_air), lwin = host(d_LW_down_in), lwn = host(d_LW_net);
1648  const auto th = host(d_theta_air), rho = host(d_rho), Ut = host(d_U_tan), us = host(d_ustar), H = host(d_H);
1649  const auto mat = hosti(d_mat);
1650  const auto alb = host(d_albedo), emi = host(d_emis), kk = host(d_kth), rc = host(d_rhocp), thk = host(d_thick), G = host(d_G);
1651  const auto Hc = host(d_H_coeff), Qx = host(d_Q_ext), LE = host(d_LE), lwe = host(d_LW_ext), res = host(d_resid);
1652  const auto nit = hosti(d_niter);
1653  const auto ws = host(d_w_star), ol = host(d_olen), zi = host(d_z_i), hb = host(d_hbld);
1654  const int nl = n_layers();
1655  std::vector<Real> slab(static_cast<size_t>(nf) * nl);
1656  Gpu::copy(Gpu::deviceToHost, d_T_slab.begin(), d_T_slab.end(), slab.begin());
1657  Gpu::streamSynchronize();
1658  std::ofstream f(prefix + ".rank" + std::to_string(ParallelDescriptor::MyProc()) + ".csv");
1659  f << "i,j,k,dir,side,bid,x_m,y_m,z_m,area_m2,f_sky,f_ground,f_bldg,shadow,SW_direct_in,SW_diffuse_in,SW_abs,T_skin,T_air,LW_in,LW_net,"
1660  "theta_air,rho,U_tan,ustar,H,mat,albedo,emissivity,k_therm,rho_cp,thickness,G,T_slab_top,T_slab_bottom,"
1661  "H_coeff,Q_ext,LE,LW_ext,resid,n_iter,w_star,Olen,z_i,h_bld";
1662  for (int l = 0; l < nl; ++l) { f << ",T_slab_" << l; }
1663  f << "\n";
1664  f << std::setprecision(12);
1665  for (int n = 0; n < nf; ++n) {
1666  f << i[n] << "," << j[n] << "," << k[n] << "," << dir[n] << "," << side[n] << "," << bid[n] << ","
1667  << xf[n] << "," << yf[n] << "," << zf[n] << "," << area[n] << ","
1668  << fs[n] << "," << fg[n] << "," << fb[n] << "," << sh[n] << ","
1669  << sdir[n] << "," << sdif[n] << "," << sabs[n] << "," << T[n] << ","
1670  << Ta[n] << "," << lwin[n] << "," << lwn[n] << ","
1671  << th[n] << "," << rho[n] << "," << Ut[n] << "," << us[n] << "," << H[n] << ","
1672  << mat[n] << "," << alb[n] << "," << emi[n] << "," << kk[n] << "," << rc[n] << "," << thk[n] << ","
1673  << G[n] << "," << slab[static_cast<size_t>(n) * nl] << "," << slab[static_cast<size_t>(n) * nl + nl - 1] << ","
1674  << Hc[n] << "," << Qx[n] << "," << LE[n] << "," << lwe[n] << "," << res[n] << "," << nit[n]
1675  << "," << ws[n] << "," << ol[n] << "," << zi[n] << "," << hb[n];
1676  for (int l = 0; l < nl; ++l) { f << "," << slab[static_cast<size_t>(n) * nl + l]; }
1677  f << "\n";
1678  }
1679 }
Real H
Definition: ERF_InitCustomPert_MovingTerrain.H:8
constexpr std::array< amrex::Real, num_veg_params > rc
Definition: ERF_SLM_DefaultParams.H:22

Referenced by report().

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

◆ fab_start()

const std::vector<int>& IBFaceSet::fab_start ( ) const
inline

First face of each local fab in MFIter order; size number of fabs + 1. Faces fab_start()[n] to fab_start()[n+1]-1 belong to the fab with local index n, which is what the scatter kernels rely on.

541 { return m_fab_start; }

◆ has_faces()

bool IBFaceSet::has_faces ( ) const
inline

Whether the level has faces on any rank.

340 { return m_top_sample >= 0; }

◆ map_buildings_to_level0()

void IBFaceSet::map_buildings_to_level0 ( const IBFaceSet &  coarser,
const amrex::IntVect &  ratio 
)

Number this refined level's buildings as level 0 numbers them.

Each level labels its buildings from its own cells, so the ids of a refined level differ from level 0's wherever the levels see different buildings: a building outside the refined level, or two that level 0 merges into one. Inputs that go by building (erf.ibseb.material_by_building) and the report's building_level0 column use level 0's numbering, so every building of this level is looked up in the labels of the level below, and through that level's own map in level 0's: each of its columns votes with the id under it and the majority wins. A building only this level resolves maps to 0 (the default material), with a warning. Call after build() of both levels; the level below keeps its labels until release_labels().

Parameters
coarserFace set of the level below, already mapped.
ratioRefinement ratio from the level below to this one.

Map this level's buildings to level 0's numbering through the labels of the level below (see the header). Every column of a building votes with the level-0 id of the coarse column under it (through the level below's own map), unlabelled coarse columns abstaining, and the most frequent id wins (the lowest on a tie). No single column is trusted: the coarse blanking is a volume average, so a column the coarse level leaves unlabelled (a spire alone in its coarse cell) cannot unmap a building the level below resolves through its other columns. A building none of whose columns lies under a labelled coarse column is one only this level resolves; it maps to 0, and a warning names it when the level below has buildings at all. The level below's ids are brought under this level's columns by a parallel copy; the votes, a few per building, are gathered from every rank and summed, so every rank reaches the same verdict.

815 {
817  "erf.ibseb: the level below released its labels before this level was mapped");
818  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(coarser.m_geom.Domain().smallEnd(2) == m_geom.Domain().smallEnd(2),
819  "erf.ibseb: the levels do not share the ground");
820  const int klo = m_geom.Domain().smallEnd(2);
821  const IntVect ratio_h(AMREX_D_DECL(ratio[0], ratio[1], 1));
822  iMultiFab crse(amrex::coarsen(m_lab.boxArray(), ratio_h), m_lab.DistributionMap(), 1, 0);
823  crse.setVal(0);
824  crse.ParallelCopy(coarser.m_lab, 0, 0, 1, IntVect(0), IntVect(0), coarser.m_geom.periodicity());
825  // This rank's votes: (this level's id, level 0's id) -> columns.
826  std::map<std::pair<int, int>, Long> votes;
827  for (MFIter mfi(m_lab); mfi.isValid(); ++mfi) {
828  const Box& vb = mfi.validbox();
829  const HostInts lab(m_lab[mfi]);
830  const HostInts cl(crse[mfi]);
831  auto const& a = lab.array();
832  auto const& c = cl.array();
833  const auto lo = lbound(vb);
834  const auto hi = ubound(vb);
835  for (int j = lo.y; j <= hi.y; ++j) {
836  for (int i = lo.x; i <= hi.x; ++i) {
837  const int b = a(i, j, klo);
838  if (b == 0) { continue; }
839  // amrex::coarsen, not a division: integer division truncates
840  // towards zero, which would send a negative index to the
841  // coarse column above the one that covers it.
842  const int bc = c(amrex::coarsen(i, ratio[0]), amrex::coarsen(j, ratio[1]), klo);
843  if (bc > 0 && coarser.m_to_lev0[bc] > 0) { ++votes[{b, coarser.m_to_lev0[bc]}]; }
844  }
845  }
846  }
847  std::vector<Long> mine;
848  for (const auto& [key, n] : votes) { mine.push_back(key.first); mine.push_back(key.second); mine.push_back(n); }
849  const std::vector<Long> all = all_gather(mine);
850  std::vector<std::map<int, Long>> tally(m_nbld + 1);
851  for (size_t n = 0; n + 3 <= all.size(); n += 3) { tally[static_cast<int>(all[n])][static_cast<int>(all[n + 1])] += all[n + 2]; }
852  std::vector<int> unmapped;
853  for (int b = 1; b <= m_nbld; ++b) {
854  int best = 0;
855  Long n_best = 0;
856  for (const auto& [id, n] : tally[b]) { // ascending id: the lowest wins a tie
857  if (n > n_best) { best = id; n_best = n; }
858  }
859  m_to_lev0[b] = best;
860  if (best == 0) { unmapped.push_back(b); }
861  }
862  if (!unmapped.empty() && coarser.m_nbld > 0) {
863  std::ostringstream os;
864  os << "erf.ibseb: level " << m_lev << " has " << unmapped.size()
865  << " building(s) that no labelled column of the level below lies under (level " << m_lev << " number";
866  for (int b : unmapped) { os << " " << b; }
867  os << "); they take erf.ibseb.material_default and building_level0 = 0 in the report";
868  // Every rank holds the same votes and reaches the same verdict.
869  if (ParallelDescriptor::IOProcessor()) { Warning(os.str().c_str()); }
870  }
871  if (m_params.debug) {
872  Print() << "[IBSEB DEBUG] lev=" << m_lev << " buildings as level 0 numbers them:";
873  for (int b = 1; b <= m_nbld; ++b) { Print() << " " << b << "->" << m_to_lev0[b]; }
874  Print() << "\n";
875  }
876 }
real(c_double), private bc
Definition: ERF_module_mp_morr_two_moment.F90:182
Here is the call graph for this function:

◆ n_buildings()

int IBFaceSet::n_buildings ( ) const
inline

Number of buildings of the level, the same on every rank.

201 { return m_nbld; }

◆ n_faces()

int IBFaceSet::n_faces ( ) const
inline

Number of faces owned by this rank (not the global count; see report()).

199 { return m_nface; }

◆ n_layers()

int IBFaceSet::n_layers ( ) const
inline

Conduction layers per face, from erf.ibseb.n_slab_layers.

203 { return m_params.n_slab_layers; }
int n_slab_layers
Conduction layers per face.
Definition: ERF_IBSEBParams.H:27

Referenced by dump_faces(), print_debug_summary(), read_state(), and write_state().

Here is the caller graph for this function:

◆ print_debug_summary()

void IBFaceSet::print_debug_summary ( ) const

Print the [IBSEB DEBUG] description of the set: the inputs, every rank's face count and per-fab ranges, the buildings with their footprint columns and bounding boxes, and the device memory of the arrays. Called once from build() when erf.ibseb.debug is set.

Debug description of the set, in the style of the fire module's [FIRE DEBUG] lines: what was read, what every rank holds, what the buildings look like, and what the arrays cost. Per-rank lines use AllPrint() so every rank reports; the rest is printed by the I/O rank.

1689 {
1690  const int nl = n_layers();
1691  Print() << "[IBSEB DEBUG] lev=" << m_lev << " inputs: n_slab_layers=" << nl
1692  << " T_skin_init=" << m_params.T_skin_init << " K"
1693  << " T_interior=" << m_params.T_interior << " K"
1694  << " csv_file=" << m_params.csv_file << " csv_int=" << m_params.csv_int << "\n";
1695  Print() << "[IBSEB DEBUG] lev=" << m_lev << " global faces=" << (m_nface_dir[0] + m_nface_dir[1] + m_nface_dir[2])
1696  << " (x=" << m_nface_dir[0] << " y=" << m_nface_dir[1] << " z=" << m_nface_dir[2] << ")"
1697  << " area=" << m_area_total << " m2 buildings=" << m_nbld << "\n";
1698  for (int b = 1; b <= m_nbld; ++b) {
1699  Print() << "[IBSEB DEBUG] building " << b << ": columns=" << m_bld_ncol[b]
1700  << " i=[" << m_bld_ilo[b] << "," << m_bld_ihi[b] << "]"
1701  << " j=[" << m_bld_jlo[b] << "," << m_bld_jhi[b] << "]"
1702  << " faces=" << m_bld_nface[b] << " area=" << m_bld_area[b] << " m2\n";
1703  }
1704  // Per-rank ownership: one line per rank with its fab ranges.
1705  const int nfab = static_cast<int>(m_fab_start.size()) - 1;
1706  const size_t bytes = static_cast<size_t>(m_nface) *
1707  (7 * sizeof(int) + (11 + static_cast<size_t>(nl)) * sizeof(Real));
1708  // The ray map: this rank's patch and its share of the column map and labels.
1709  size_t map_cells = static_cast<size_t>(m_patch_box.ok() ? m_patch_box.numPts() : 0);
1710  const size_t per_column = m_lab.ok() ? 2 : 1; // ok() iterates the fabs itself: not inside the loop below
1711  for (MFIter mfi(m_col_valid); mfi.isValid(); ++mfi) {
1712  map_cells += per_column * static_cast<size_t>(m_col_valid[mfi].box().numPts());
1713  }
1714  const size_t map_bytes = map_cells * sizeof(int);
1715  std::ostringstream os;
1716  os << "[IBSEB DEBUG] lev=" << m_lev << " rank " << ParallelDescriptor::MyProc()
1717  << ": faces=" << m_nface << " over " << nfab << " fab(s), device memory "
1718  << bytes / 1024.0 << " kB for the faces, " << map_bytes / 1024.0 << " kB for the ray map; fab ranges:";
1719  for (int f = 0; f < nfab; ++f) { os << " [" << m_fab_start[f] << "," << m_fab_start[f + 1] << ")"; }
1720  AllPrint() << os.str() << "\n";
1721 }
std::string csv_file
Per-building report.
Definition: ERF_IBSEBParams.H:30
int csv_int
Steps between report rows; <= 0 disables.
Definition: ERF_IBSEBParams.H:31
Here is the call graph for this function:

◆ ray_halo()

amrex::IntVect IBFaceSet::ray_halo ( ) const
inline

Halo of the ray map in cells (x, y), from the last build_ray_map().

193 { return m_halo; }

◆ ray_map()

ibseb::RayMap IBFaceSet::ray_map ( ) const

The ray map of this rank for the kernels of the shortwave and the view fractions: the column tops over this rank's patch and the grid they are drawn on (ibseb::RayMap). A rank without boxes has no patch and no faces.

769 {
770  ibseb::RayMap m;
771  if (m_patch_box.ok()) {
772  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(m_ray_patch.local_size() == 1, "erf.ibseb: the ray map holds no patch for this rank");
773  m.top = m_ray_patch.const_array(m_ray_patch.IndexArray()[0]);
774  }
775  m.ilo = m_dom_lo[0]; m.jlo = m_dom_lo[1];
776  m.nx = m_nx; m.ny = m_ny;
777  m.wrap_x = m_wrap_x; m.wrap_y = m_wrap_y;
778  m.x_lo = m_x_lo; m.y_lo = m_y_lo;
779  m.dx = m_dx[0]; m.dy = m_dx[1]; m.dz = m_dx[2];
780  m.z_ground = m_z_ground;
781  m.z_max = m_col_top_max;
783  return m;
784 }
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::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::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

Referenced by compute_shortwave(), and compute_shortwave_two_stream().

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

◆ ray_map_spans_domain()

bool IBFaceSet::ray_map_spans_domain ( ) const
inline

Whether every rank's patch spans the whole domain, so every ray is followed to its end.

196 { return m_map_spans_domain; }

◆ read_state()

void IBFaceSet::read_state ( const std::string &  file)

Read the face state back from a file written by write_state(), after build() has recreated the list from the blanking.

The I/O rank reads the table; every rank sends it the keys of its own faces; the I/O rank finds each one by binary search and sends the records back. Only the I/O rank ever holds the whole table, and a restart on any number of ranks finds every face. Stops when the file holds another number of slab layers than the deck's erf.ibseb.n_slab_layers, another domain size or another number of faces, or when a face of this list has no record: a checkpoint of other buildings. Collective: every rank must call it.

The inverse of write_state(): the I/O rank reads the table and checks the header against this level (every rank gets the header, so all stop together on a mismatch); each rank sends the keys of its own faces, the I/O rank looks them up by binary search and sends the records back; each rank then unpacks its records into the device arrays.

1910 {
1911  const int nl = n_layers();
1912  const int nrec = 2 + nl;
1913  const int nf = m_nface;
1914  const bool io = ParallelDescriptor::IOProcessor();
1915  const int root = ParallelDescriptor::IOProcessorNumber();
1916 
1917  // The I/O rank reads the whole file: the header (face count, slab layers,
1918  // domain size), the marker word and the table. What it found goes to every
1919  // rank as hdr[5], so all ranks stop together on any fault. The table is
1920  // read only when the header matches this level, so a header mismatch is
1921  // reported as such below rather than as a short file.
1922  enum : Long { ok = 1, cannot_open, bad_header, bad_marker, too_short, unsorted };
1923  const Long nface_level = m_nface_dir[0] + m_nface_dir[1] + m_nface_dir[2];
1924  std::array<Long, 6> hdr{};
1925  std::vector<Long> tkey;
1926  std::vector<double> tval;
1927  if (io) {
1928  std::ifstream f(file, std::ifstream::in | std::ifstream::binary);
1929  std::string tag;
1930  int version = 0;
1931  Long marker = 0;
1932  if (!f) {
1933  hdr[5] = cannot_open;
1934  } else if (!(f >> tag >> version >> hdr[0] >> hdr[1] >> hdr[2] >> hdr[3] >> hdr[4])
1935  || tag != "IBSEBFaces" || version != 1 || hdr[0] < 0) {
1936  hdr[5] = bad_header;
1937  } else {
1938  f.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
1939  f.read(reinterpret_cast<char*>(&marker), sizeof(Long));
1940  if (!f || marker != face_file_marker) {
1941  hdr[5] = bad_marker;
1942  } else if (hdr[1] != nl || hdr[2] != m_nx || hdr[3] != m_ny || hdr[4] != m_nz || hdr[0] != nface_level) {
1943  hdr[5] = ok; // the checks below say which header entry differs
1944  } else {
1945  const Long n = hdr[0];
1946  tkey.resize(n);
1947  tval.resize(static_cast<size_t>(n) * nrec);
1948  f.read(reinterpret_cast<char*>(tkey.data()), static_cast<std::streamsize>(n * sizeof(Long)));
1949  f.read(reinterpret_cast<char*>(tval.data()), static_cast<std::streamsize>(tval.size() * sizeof(double)));
1950  hdr[5] = f ? ok : too_short;
1951  for (Long m = 1; hdr[5] == ok && m < n; ++m) {
1952  if (tkey[m] <= tkey[m - 1]) { hdr[5] = unsorted; }
1953  }
1954  }
1955  }
1956  }
1957  ParallelDescriptor::Bcast(hdr.data(), hdr.size(), root);
1958  switch (hdr[5]) {
1959  case ok: break;
1960  case cannot_open: Abort("erf.ibseb: cannot open " + file + " for reading"); break;
1961  case bad_header: Abort("erf.ibseb: " + file + " is not a face-state file of this ERF (its first line should read"
1962  " 'IBSEBFaces 1 <faces> <layers> <nx> <ny> <nz>')"); break;
1963  case bad_marker: Abort("erf.ibseb: " + file + " was written on a machine with another byte order or integer"
1964  " width; restart from a checkpoint written on this kind of machine"); break;
1965  case too_short: Abort("erf.ibseb: " + file + " ends before the " + std::to_string(hdr[0])
1966  + " records its header announces"); break;
1967  default: Abort("erf.ibseb: the face keys of " + file + " are not in ascending order"); break;
1968  }
1969  if (hdr[1] != nl) {
1970  Abort("erf.ibseb: " + file + " holds " + std::to_string(hdr[1]) + " slab layers per face; the deck sets"
1971  " erf.ibseb.n_slab_layers = " + std::to_string(nl) + ". Restart with the checkpoint's value.");
1972  }
1973  if (hdr[2] != m_nx || hdr[3] != m_ny || hdr[4] != m_nz) {
1974  Abort("erf.ibseb: " + file + " was written for a different domain (" + std::to_string(hdr[2]) + " x "
1975  + std::to_string(hdr[3]) + " x " + std::to_string(hdr[4]) + " cells against " + std::to_string(m_nx)
1976  + " x " + std::to_string(m_ny) + " x " + std::to_string(m_nz) + " here)");
1977  }
1978  if (hdr[0] != nface_level) {
1979  Abort("erf.ibseb: " + file + " was written for a different building layout (" + std::to_string(hdr[0])
1980  + " faces against the " + std::to_string(nface_level)
1981  + " the blanking gives); restart from a checkpoint of the same buildings");
1982  }
1983 
1984  const Long ntot = hdr[0];
1985 
1986  // This rank's keys to the I/O rank.
1987  std::vector<int> h_i(nf), h_j(nf), h_k(nf), h_dir(nf), h_side(nf);
1988  Gpu::copy(Gpu::deviceToHost, d_i.begin(), d_i.end(), h_i.begin());
1989  Gpu::copy(Gpu::deviceToHost, d_j.begin(), d_j.end(), h_j.begin());
1990  Gpu::copy(Gpu::deviceToHost, d_k.begin(), d_k.end(), h_k.begin());
1991  Gpu::copy(Gpu::deviceToHost, d_dir.begin(), d_dir.end(), h_dir.begin());
1992  Gpu::copy(Gpu::deviceToHost, d_side.begin(), d_side.end(), h_side.begin());
1993  Gpu::streamSynchronize();
1994  std::vector<Long> key(nf);
1995  for (int n = 0; n < nf; ++n) {
1996  key[n] = face_key(h_i[n] - m_dom_lo[0], h_j[n] - m_dom_lo[1], h_k[n] - m_dom_lo[2],
1997  h_dir[n], h_side[n], m_ny, m_nz);
1998  }
1999  std::vector<int> counts, disp, vcounts, vdisp;
2000  gather_counts(nf, counts, disp);
2001  scale_counts(counts, disp, nrec, vcounts, vdisp);
2002  std::vector<Long> all_key(io ? ntot : 0);
2003  ParallelDescriptor::Gatherv(key.data(), nf, all_key.data(), counts, disp, root);
2004 
2005  // Look every face up; a face without a record means other buildings.
2006  std::vector<double> all_val(io ? ntot * nrec : 0);
2007  std::array<Long, 2> missing{0, -1}; // count, first key
2008  if (io) {
2009  for (Long m = 0; m < ntot; ++m) {
2010  const auto it = std::lower_bound(tkey.begin(), tkey.end(), all_key[m]);
2011  if (it == tkey.end() || *it != all_key[m]) {
2012  if (missing[0] == 0) { missing[1] = all_key[m]; }
2013  ++missing[0];
2014  continue;
2015  }
2016  const Long t = it - tkey.begin();
2017  std::copy_n(&tval[static_cast<size_t>(t) * nrec], nrec, &all_val[static_cast<size_t>(m) * nrec]);
2018  }
2019  }
2020  ParallelDescriptor::Bcast(missing.data(), missing.size(), root);
2021  if (missing[0] > 0) {
2022  const auto fc = face_from_key(missing[1], m_ny, m_nz);
2023  Abort("erf.ibseb: " + file + " was written for a different building layout (" + std::to_string(missing[0])
2024  + " faces of this blanking have no record there, the first at i=" + std::to_string(fc[0] + m_dom_lo[0])
2025  + " j=" + std::to_string(fc[1] + m_dom_lo[1]) + " k=" + std::to_string(fc[2] + m_dom_lo[2])
2026  + " dir=" + std::to_string(fc[3]) + " side=" + std::to_string(fc[4])
2027  + "); restart from a checkpoint of the same buildings");
2028  }
2029 
2030  // The records back to the ranks that asked for them, in the order they asked.
2031  std::vector<double> val(static_cast<size_t>(nf) * nrec);
2032 #ifdef AMREX_USE_MPI
2033  BL_MPI_REQUIRE(MPI_Scatterv(all_val.data(), vcounts.data(), vdisp.data(), MPI_DOUBLE,
2034  val.data(), nf * nrec, MPI_DOUBLE, root, ParallelDescriptor::Communicator()));
2035 #else
2036  val = all_val;
2037 #endif
2038  std::vector<Real> h_T(nf), h_H(nf), h_S(static_cast<size_t>(nf) * nl);
2039  for (int n = 0; n < nf; ++n) {
2040  const double* r = &val[static_cast<size_t>(n) * nrec];
2041  h_T[n] = static_cast<Real>(r[0]);
2042  h_H[n] = static_cast<Real>(r[1]);
2043  for (int l = 0; l < nl; ++l) { h_S[static_cast<size_t>(n) * nl + l] = static_cast<Real>(r[2 + l]); }
2044  }
2045  Gpu::copy(Gpu::hostToDevice, h_T.begin(), h_T.end(), d_T_skin.begin());
2046  Gpu::copy(Gpu::hostToDevice, h_H.begin(), h_H.end(), d_H.begin());
2047  Gpu::copy(Gpu::hostToDevice, h_S.begin(), h_S.end(), d_T_slab.begin());
2048  Gpu::streamSynchronize();
2049  if (m_params.debug) {
2050  Real tmin = 1.0e30, tmax = -1.0e30;
2051  for (int n = 0; n < nf; ++n) { tmin = std::min(tmin, h_T[n]); tmax = std::max(tmax, h_T[n]); }
2052  ParallelDescriptor::ReduceRealMin(tmin);
2053  ParallelDescriptor::ReduceRealMax(tmax);
2054  Print() << "[IBSEB DEBUG] lev=" << m_lev << " face state loaded: " << ntot << " records from " << file
2055  << ", T_skin in [" << tmin << ", " << tmax << "] K\n";
2056  }
2057 }
@ fc
Definition: ERF_AdvanceMorrison.cpp:141
Here is the call graph for this function:

◆ release_labels()

void IBFaceSet::release_labels ( )
inline

Free the column labels once the level above has been mapped (map_buildings_to_level0()).

183 { m_lab.clear(); }

◆ report()

void IBFaceSet::report ( amrex::Real  time,
int  step,
bool  write_csv 
) const

Print the [IBSEB] summary line and, when asked, append one row per building to erf.ibseb.csv_file.

The summary carries the global face counts per direction, the number of buildings, the total face area and the range of the skin temperature; the CSV rows carry per building the face count, the area, the area-weighted mean fluxes and skin temperature with its range, the largest balance residual, and the sun the step used, from its start (zenith, azimuth, direct-normal and diffuse irradiance), on a row stamped with the time at the end of the step. All numbers are reduced over the ranks; the file is written by the I/O rank only, with a header only when the file does not exist yet, so a restarted run appends. Every level appends to the same file, its rows marked by the level column. With erf.ibseb.dump_faces_file the rows come with a face dump, dump_faces() to <prefix>[.levN][.stepNNNNNN].rank<N>.csv: a refined level N adds .levN so it does not overwrite the level below, and erf.ibseb.dump_faces_tag_step the step.

Parameters
timeSimulation time [s] written to the rows.
stepStep number written to the rows.
write_csvWhether to append rows, or only print the summary.

Summary line and per-building CSV rows. The dynamic quantities (skin temperature range and area-weighted mean per building) are gathered on the host from the device arrays and reduced; the static counts and areas were reduced in build().

2067 {
2068  // Skin temperature: min, max and per-building mean over all ranks.
2069  std::vector<Real> h_T(m_nface), h_A(m_nface);
2070  std::vector<int> h_b(m_nface);
2071  Gpu::copy(Gpu::deviceToHost, d_T_skin.begin(), d_T_skin.end(), h_T.begin());
2072  Gpu::copy(Gpu::deviceToHost, d_area.begin(), d_area.end(), h_A.begin());
2073  Gpu::copy(Gpu::deviceToHost, d_bid.begin(), d_bid.end(), h_b.begin());
2074  Gpu::streamSynchronize();
2075 
2076  std::vector<Real> h_S(m_nface), h_sh(m_nface), h_L(m_nface), h_H(m_nface), h_G(m_nface), h_r(m_nface), h_Q(m_nface), h_w(m_nface);
2077  Gpu::copy(Gpu::deviceToHost, d_w_star.begin(), d_w_star.end(), h_w.begin());
2078  Gpu::copy(Gpu::deviceToHost, d_resid.begin(), d_resid.end(), h_r.begin());
2079  Gpu::copy(Gpu::deviceToHost, d_Q_ext.begin(), d_Q_ext.end(), h_Q.begin());
2080  Gpu::copy(Gpu::deviceToHost, d_SW_abs.begin(), d_SW_abs.end(), h_S.begin());
2081  Gpu::copy(Gpu::deviceToHost, d_shadow.begin(), d_shadow.end(), h_sh.begin());
2082  Gpu::copy(Gpu::deviceToHost, d_LW_net.begin(), d_LW_net.end(), h_L.begin());
2083  Gpu::copy(Gpu::deviceToHost, d_H.begin(), d_H.end(), h_H.begin());
2084  Gpu::copy(Gpu::deviceToHost, d_G.begin(), d_G.end(), h_G.begin());
2085  Gpu::streamSynchronize();
2086 
2087  std::vector<Real> bsum(m_nbld + 1, 0.0), bsw(m_nbld + 1, 0.0), bsh(m_nbld + 1, 0.0), blw(m_nbld + 1, 0.0), bH(m_nbld + 1, 0.0);
2088  std::vector<Real> bG(m_nbld + 1, 0.0), bQ(m_nbld + 1, 0.0), btmin(m_nbld + 1, 1.0e30), btmax(m_nbld + 1, -1.0e30), bres(m_nbld + 1, 0.0);
2089  Real tmin = 1.0e30, tmax = -1.0e30, sw_sum = 0.0, sw_max = 0.0, sh_area = 0.0, lw_sum = 0.0, H_sum = 0.0, G_sum = 0.0;
2090  Real res_max = 0.0, Q_sum = 0.0, w_max = 0.0;
2091  for (int n = 0; n < m_nface; ++n) {
2092  w_max = std::max(w_max, h_w[n]);
2093  G_sum += h_G[n] * h_A[n];
2094  Q_sum += h_Q[n] * h_A[n];
2095  res_max = std::max(res_max, h_r[n]);
2096  bG[h_b[n]] += h_G[n] * h_A[n];
2097  bQ[h_b[n]] += h_Q[n] * h_A[n];
2098  btmin[h_b[n]] = std::min(btmin[h_b[n]], h_T[n]);
2099  btmax[h_b[n]] = std::max(btmax[h_b[n]], h_T[n]);
2100  bres[h_b[n]] = std::max(bres[h_b[n]], h_r[n]);
2101  bsum[h_b[n]] += h_T[n] * h_A[n];
2102  bsw[h_b[n]] += h_S[n] * h_A[n];
2103  bsh[h_b[n]] += h_sh[n] * h_A[n];
2104  blw[h_b[n]] += h_L[n] * h_A[n];
2105  bH[h_b[n]] += h_H[n] * h_A[n];
2106  tmin = std::min(tmin, h_T[n]);
2107  tmax = std::max(tmax, h_T[n]);
2108  sw_sum += h_S[n] * h_A[n];
2109  sw_max = std::max(sw_max, h_S[n]);
2110  sh_area += h_sh[n] * h_A[n];
2111  lw_sum += h_L[n] * h_A[n];
2112  H_sum += h_H[n] * h_A[n];
2113  }
2114  ParallelDescriptor::ReduceRealSum(bsum.data(), m_nbld + 1);
2115  ParallelDescriptor::ReduceRealSum(bsw.data(), m_nbld + 1);
2116  ParallelDescriptor::ReduceRealSum(bsh.data(), m_nbld + 1);
2117  ParallelDescriptor::ReduceRealSum(blw.data(), m_nbld + 1);
2118  ParallelDescriptor::ReduceRealSum(bH.data(), m_nbld + 1);
2119  ParallelDescriptor::ReduceRealSum(bG.data(), m_nbld + 1);
2120  ParallelDescriptor::ReduceRealSum(bQ.data(), m_nbld + 1);
2121  ParallelDescriptor::ReduceRealMin(btmin.data(), m_nbld + 1);
2122  ParallelDescriptor::ReduceRealMax(btmax.data(), m_nbld + 1);
2123  ParallelDescriptor::ReduceRealMax(bres.data(), m_nbld + 1);
2124  ParallelDescriptor::ReduceRealMax(res_max);
2125  ParallelDescriptor::ReduceRealMax(w_max);
2126  ParallelDescriptor::ReduceRealSum(Q_sum);
2127  ParallelDescriptor::ReduceRealMin(tmin);
2128  ParallelDescriptor::ReduceRealMax(tmax);
2129  ParallelDescriptor::ReduceRealSum(sw_sum);
2130  ParallelDescriptor::ReduceRealMax(sw_max);
2131  ParallelDescriptor::ReduceRealSum(sh_area);
2132  ParallelDescriptor::ReduceRealSum(lw_sum);
2133  ParallelDescriptor::ReduceRealSum(H_sum);
2134  ParallelDescriptor::ReduceRealSum(G_sum);
2135  const Real G_mean = (m_area_total > 0.0) ? G_sum / m_area_total : 0.0;
2136  const Real sw_mean = (m_area_total > 0.0) ? sw_sum / m_area_total : 0.0;
2137  const Real sh_frac = (m_area_total > 0.0) ? sh_area / m_area_total : 0.0;
2138  const Real lw_mean = (m_area_total > 0.0) ? lw_sum / m_area_total : 0.0;
2139  const Real H_mean = (m_area_total > 0.0) ? H_sum / m_area_total : 0.0;
2140 
2141  const Long nf_all = m_nface_dir[0] + m_nface_dir[1] + m_nface_dir[2];
2142  Print() << "[IBSEB] lev=" << m_lev << " step=" << step << " t=" << time
2143  << " faces=" << nf_all
2144  << " (x=" << m_nface_dir[0] << " y=" << m_nface_dir[1] << " z=" << m_nface_dir[2] << ")"
2145  << " buildings=" << m_nbld
2146  << " area=" << std::setprecision(10) << m_area_total << " m2"
2147  << " T_skin_min=" << ((nf_all > 0) ? tmin : Real(0.0))
2148  << " T_skin_max=" << ((nf_all > 0) ? tmax : Real(0.0))
2149  << " SW_abs_mean=" << sw_mean << " SW_abs_max=" << sw_max
2150  << " shadow_frac=" << sh_frac << " LW_net_mean=" << lw_mean
2151  << " H_mean=" << H_mean << " G_mean=" << G_mean
2152  << " Q_ext_mean=" << ((m_area_total > 0.0) ? Q_sum / m_area_total : Real(0.0))
2153  << " resid_max=" << res_max
2154  << " w_star_max=" << w_max
2155  << " H_total_W=" << std::setprecision(12) << H_sum << "\n";
2156  // Cost line: the slowest rank's per-step time of the balance and its face
2157  // count, so the cost of the face list on a larger case can be estimated.
2158  {
2159  Real ms = (m_cost_n > 0) ? 1000.0 * m_cost_s / m_cost_n : 0.0;
2160  Real ms_max = ms; int nf_max = m_nface;
2161  ParallelDescriptor::ReduceRealMax(ms_max);
2162  ParallelDescriptor::ReduceIntMax(nf_max);
2163  Print() << "[IBSEB] cost: lev=" << m_lev << " advance_ms_per_step_max=" << std::setprecision(4) << ms_max
2164  << " faces_per_rank_max=" << nf_max << " ranks=" << ParallelDescriptor::NProcs()
2165  << " init_s=" << m_init_cost_s << "\n";
2166  }
2167 
2168  if (m_params.debug) {
2169  for (int b = 1; b <= m_nbld; ++b) {
2170  const Real tmean = (m_bld_area[b] > 0.0) ? bsum[b] / m_bld_area[b] : Real(0.0);
2171  const Real swm = (m_bld_area[b] > 0.0) ? bsw[b] / m_bld_area[b] : Real(0.0);
2172  const Real shf = (m_bld_area[b] > 0.0) ? bsh[b] / m_bld_area[b] : Real(0.0);
2173  const Real lwm = (m_bld_area[b] > 0.0) ? blw[b] / m_bld_area[b] : Real(0.0);
2174  const Real Hm = (m_bld_area[b] > 0.0) ? bH[b] / m_bld_area[b] : Real(0.0);
2175  const Real Gm = (m_bld_area[b] > 0.0) ? bG[b] / m_bld_area[b] : Real(0.0);
2176  Print() << "[IBSEB DEBUG] building " << b << ": faces=" << m_bld_nface[b]
2177  << " area=" << m_bld_area[b] << " m2 T_skin_mean=" << tmean << " K"
2178  << " [" << btmin[b] << ", " << btmax[b] << "]"
2179  << " SW_abs_mean=" << swm << " W/m2 shadow_frac=" << shf
2180  << " LW_net_mean=" << lwm << " W/m2 H_mean=" << Hm << " W/m2 G_mean=" << Gm
2181  << " W/m2 resid_max=" << bres[b] << " W/m2\n";
2182  }
2183  }
2184 
2185  if (write_csv && !m_params.dump_faces_file.empty()) {
2186  std::string prefix = m_params.dump_faces_file;
2187  // Each level has its own list; a refined level tags its files so it
2188  // does not overwrite the dump of the level below.
2189  if (m_lev > 0) { prefix += ".lev" + std::to_string(m_lev); }
2191  std::ostringstream os;
2192  os << prefix << ".step" << std::setw(6) << std::setfill('0') << step;
2193  prefix = os.str();
2194  }
2195  dump_faces(prefix);
2196  }
2197 
2198  if (!write_csv || !ParallelDescriptor::IOProcessor()) { return; }
2199  const std::string header =
2200  "time_s,step,level,building,n_faces,area_m2,T_skin_mean_K,SW_abs_mean_Wm2,shadow_frac,LW_net_mean_Wm2,H_mean_Wm2,"
2201  "G_mean_Wm2,Q_ext_mean_Wm2,T_skin_min_K,T_skin_max_K,resid_max_Wm2,"
2202  "sun_zenith_deg,sun_azimuth_deg,dni_Wm2,diffuse_h_Wm2,building_level0";
2203  bool need_header = true;
2204  {
2205  // A restarted run appends; a file another version of the report wrote
2206  // (other columns) would take rows its header does not describe.
2207  std::ifstream probe(m_params.csv_file);
2208  std::string first;
2209  if (probe.good() && std::getline(probe, first)) {
2210  if (!first.empty() && first.back() == '\r') { first.pop_back(); }
2211  if (first != header) {
2212  Abort("erf.ibseb: " + m_params.csv_file + " has another set of columns than this version writes ("
2213  + first.substr(0, 60) + "...); move it aside or set erf.ibseb.csv_file to a new name");
2214  }
2215  need_header = false;
2216  }
2217  }
2218  std::ofstream csv(m_params.csv_file, std::ios::app);
2219  if (need_header) { csv << header << "\n"; }
2220  for (int b = 1; b <= m_nbld; ++b) {
2221  const Real tmean = (m_bld_area[b] > 0.0) ? bsum[b] / m_bld_area[b] : Real(0.0);
2222  const Real swm = (m_bld_area[b] > 0.0) ? bsw[b] / m_bld_area[b] : Real(0.0);
2223  const Real shf = (m_bld_area[b] > 0.0) ? bsh[b] / m_bld_area[b] : Real(0.0);
2224  const Real lwm = (m_bld_area[b] > 0.0) ? blw[b] / m_bld_area[b] : Real(0.0);
2225  const Real Hm = (m_bld_area[b] > 0.0) ? bH[b] / m_bld_area[b] : Real(0.0);
2226  const Real Gm = (m_bld_area[b] > 0.0) ? bG[b] / m_bld_area[b] : Real(0.0);
2227  const Real Qm = (m_bld_area[b] > 0.0) ? bQ[b] / m_bld_area[b] : Real(0.0);
2228  csv << std::setprecision(10) << time << "," << step << "," << m_lev << "," << b << ","
2229  << m_bld_nface[b] << "," << m_bld_area[b] << "," << tmean << "," << swm << "," << shf << "," << lwm << "," << Hm << ","
2230  << Gm << "," << Qm << "," << btmin[b] << "," << btmax[b] << "," << bres[b] << ","
2231  << m_sun.zenith * 180.0 / PI << "," << m_sun.azimuth * 180.0 / PI << "," << m_sun.dni << "," << m_sun.diffuse_h << ","
2232  << m_to_lev0[b] << "\n";
2233  }
2234 }
Vector< Real > h_r(khi+2)
void dump_faces(const std::string &prefix) const
Definition: ERF_IBFaceSet.cpp:1630
double m_init_cost_s
Wall-clock seconds of build() and compute_view_fractions()
Definition: ERF_IBFaceSet.H:605
bool dump_faces_tag_step
Name each dump by its step (keeps every one) instead of overwriting.
Definition: ERF_IBSEBParams.H:34
std::string dump_faces_file
Per-rank face dump at report time (empty = off); a refined level N adds ".levN".
Definition: ERF_IBSEBParams.H:33
Here is the call graph for this function:

◆ scatter_diagnostics()

void IBFaceSet::scatter_diagnostics ( amrex::MultiFab &  nfaces,
amrex::MultiFab &  tskin 
) const

Scatter the faces into two cell-centred diagnostics for the plotfile.

Parameters
[out]nfacesNumber of wall faces touching each fluid cell (0 away from buildings; up to 3 at an outside corner, 6 in a one-cell slot).
[out]tskinMean skin temperature of those faces, 0 where there are none.

Both fields must be on the level's box array with no ghost cells. Faces of one fab are accumulated with atomic adds because a corner cell receives several faces.

Scatter the face count and the mean skin temperature into cell-centred fields. One kernel per fab over that fab's faces (contiguous, see fab_start()), with atomic adds since a corner cell collects several faces, then a kernel over the box divides the temperature sum by the count.

1731 {
1732  nfaces.setVal(0.0);
1733  tskin.setVal(0.0);
1734  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
1735  const Real* pT = d_T_skin.data();
1736  for (MFIter mfi(nfaces); mfi.isValid(); ++mfi) {
1737  const int f0 = m_fab_start[mfi.LocalIndex()];
1738  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
1739  auto const& n = nfaces.array(mfi);
1740  auto const& t = tskin.array(mfi);
1741  ParallelFor(f1 - f0, [=] AMREX_GPU_DEVICE (int m) noexcept {
1742  const int f = f0 + m;
1743  Gpu::Atomic::AddNoRet(&n(pi[f], pj[f], pk[f]), Real(1.0));
1744  Gpu::Atomic::AddNoRet(&t(pi[f], pj[f], pk[f]), pT[f]);
1745  });
1746  const Box& bx = mfi.validbox();
1747  ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
1748  if (n(i, j, k) > 0.0) { t(i, j, k) /= n(i, j, k); }
1749  });
1750  }
1751  Gpu::streamSynchronize();
1752 }
Here is the call graph for this function:

◆ scatter_field()

void IBFaceSet::scatter_field ( const amrex::Gpu::DeviceVector< amrex::Real > &  v,
amrex::MultiFab &  out 
) const

Scatter one per-face array into a cell-centred field as the mean over the faces touching each cell (zero where there are none).

Scatter one per-face array as a per-cell mean, the same accumulation as scatter_diagnostics() with the face count as the divisor.

1602 {
1603  MultiFab cnt(out.boxArray(), out.DistributionMap(), 1, 0);
1604  cnt.setVal(0.0);
1605  out.setVal(0.0);
1606  const int* pi = d_i.data(); const int* pj = d_j.data(); const int* pk = d_k.data();
1607  const Real* pv = v.data();
1608  for (MFIter mfi(out); mfi.isValid(); ++mfi) {
1609  const int f0 = m_fab_start[mfi.LocalIndex()];
1610  const int f1 = m_fab_start[mfi.LocalIndex() + 1];
1611  auto const& n = cnt.array(mfi);
1612  auto const& o = out.array(mfi);
1613  ParallelFor(f1 - f0, [=] AMREX_GPU_DEVICE (int m) noexcept {
1614  const int f = f0 + m;
1615  Gpu::Atomic::AddNoRet(&n(pi[f], pj[f], pk[f]), Real(1.0));
1616  Gpu::Atomic::AddNoRet(&o(pi[f], pj[f], pk[f]), pv[f]);
1617  });
1618  ParallelFor(mfi.validbox(), [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
1619  if (n(i, j, k) > 0.0) { o(i, j, k) /= n(i, j, k); }
1620  });
1621  }
1622  Gpu::streamSynchronize();
1623 }
Here is the call graph for this function:

◆ set_init_cost()

void IBFaceSet::set_init_cost ( double  seconds)
inline

Record the wall-clock time of the initialisation (build and view fractions).

534 { m_init_cost_s = seconds; }

◆ set_two_stream_sun()

void IBFaceSet::set_two_stream_sun ( amrex::Real  declination,
amrex::Real  hour_angle,
amrex::Real  latitude_deg,
amrex::Real  toa_irradiance 
)
inline

Give the sun of the next compute_shortwave() with erf.ibseb.sun_mode = two_stream: the two-stream radiation's declination and hour angle at that time, its site latitude and its top-of-atmosphere irradiance (the date's distance factor included). ERF::ibseb_set_two_stream_sun() calls it before every call; the call uses it up, so a shortwave without a fresh sun stops rather than reusing an old one.

154  {
155  m_ts_sun = {true, declination, hour_angle, latitude_deg, toa_irradiance};
156  }

◆ solve_balance()

void IBFaceSet::solve_balance ( amrex::Real  dt)

Prognostic balance of the current step. Per face: the slab's linear response to the skin temperature for this step, the Newton solve of ERF_IBSEBBalance.H with the shortwave, the external flux, the sky and ground longwave, the frozen wall-function coefficient and the latent flux, the slab advanced with the solution, and the fluxes (LW_in, LW_net, H, G) rewritten at the solution. Fills d_resid (|balance| at the end, non-zero only at a bound or the iteration cap) and d_niter. Needs compute_shortwave(), compute_longwave() and compute_sensible() of the same step first.

Parameters
dtAtmospheric time step [s], the slab's step.

Prognostic balance on every face: one kernel over the faces, each with its own slab response, Newton solve and slab advance. Everything the solve needs was stored by the three flux routines of the step, so no fab data is touched here. The fluxes are rewritten at the solution so that the dump, the reports and the heat deposited into the air all describe the same closed balance; d_resid holds what is left of it.

1152 {
1153  const int nl = n_layers();
1154  const Real Tint = m_params.T_interior;
1155  const Real Tmin = m_params.T_skin_min, Tmax = m_params.T_skin_max;
1156  const Real tol = m_params.newton_tol_K, max_step = m_params.newton_max_step_K;
1157  const int max_iter = m_params.newton_max_iter;
1158  const Real* pkt = d_kth.data(); const Real* prc = d_rhocp.data(); const Real* pth = d_thick.data();
1159  const Real* peps = d_emis.data(); const Real* pfb = d_f_bldg.data();
1160  const Real* psw = d_SW_abs.data(); const Real* pq = d_Q_ext.data(); const Real* pext = d_LW_ext.data();
1161  const Real* pHc = d_H_coeff.data(); const Real* pTa = d_T_air.data(); const Real* ptha = d_theta_air.data();
1162  const Real* pLE = d_LE.data();
1163  Real* pT = d_T_skin.data(); Real* pS = d_T_slab.data(); Real* pG = d_G.data();
1164  Real* pH = d_H.data(); Real* pin = d_LW_down_in.data(); Real* pnet = d_LW_net.data();
1165  Real* pres = d_resid.data(); int* pnit = d_niter.data();
1166  ParallelFor(m_nface, [=] AMREX_GPU_DEVICE (int f) noexcept {
1167  // Zero-initialized: only the first nl entries are ever used, but the
1168  // compiler cannot see that through the inlined solve and warns otherwise.
1170  for (int l = 0; l < nl; ++l) { T[l] = pS[f * nl + l]; }
1171  const Real dz = pth[f] / nl;
1172  // Conduction the implicit slab step will take for a skin temperature Ts: a Ts - b.
1173  Real a = 0.0, b = 0.0;
1174  ibseb::slab_skin_response(T, Tint, pkt[f], prc[f], dz, dt, nl, a, b);
1175  const Real exner = pTa[f] / ptha[f];
1176  const Real q_abs = peps[f] * pq[f];
1177  int n_it = 0; Real res = 0.0;
1178  const Real Ts = ibseb::solve_skin_balance(pT[f], psw[f], q_abs, pext[f], peps[f], pfb[f],
1179  pHc[f], exner, ptha[f], pLE[f], a, b,
1180  Tmin, Tmax, max_step, tol, max_iter, n_it, res);
1181  // Advance the slab with the solution and store every term at it.
1182  pG[f] = ibseb::advance_slab_dirichlet(T, Ts, Tint, pkt[f], prc[f], dz, dt, nl);
1183  for (int l = 0; l < nl; ++l) { pS[f * nl + l] = T[l]; }
1184  const Real Ts4 = Ts * Ts * Ts * Ts;
1185  pin[f] = pext[f] + pfb[f] * ibseb::SIGMA_SB * Ts4;
1186  pnet[f] = peps[f] * (pin[f] - ibseb::SIGMA_SB * Ts4);
1187  pH[f] = pHc[f] * (Ts / exner - ptha[f]);
1188  pT[f] = Ts;
1189  // The residual with the stored terms (the slab's own G, not a Ts - b),
1190  // so it reports the closure of what is written out.
1191  pres[f] = std::abs(psw[f] + q_abs + pnet[f] - pH[f] - pLE[f] - pG[f]);
1192  pnit[f] = n_it;
1193  });
1194  Gpu::streamSynchronize();
1195 
1196  if (m_params.debug) {
1197  std::vector<Real> h_r(m_nface), h_T(m_nface);
1198  std::vector<int> h_n(m_nface);
1199  Gpu::copy(Gpu::deviceToHost, d_resid.begin(), d_resid.end(), h_r.begin());
1200  Gpu::copy(Gpu::deviceToHost, d_T_skin.begin(), d_T_skin.end(), h_T.begin());
1201  Gpu::copy(Gpu::deviceToHost, d_niter.begin(), d_niter.end(), h_n.begin());
1202  Gpu::streamSynchronize();
1203  Real rmax = 0.0; int nmax = 0; Long nclamp = 0;
1204  for (int n = 0; n < m_nface; ++n) {
1205  rmax = std::max(rmax, h_r[n]); nmax = std::max(nmax, h_n[n]);
1206  if (h_T[n] <= Tmin || h_T[n] >= Tmax) { ++nclamp; }
1207  }
1208  ParallelDescriptor::ReduceRealMax(rmax);
1209  ParallelDescriptor::ReduceIntMax(nmax);
1210  ParallelDescriptor::ReduceLongSum(nclamp);
1211  Print() << "[IBSEB DEBUG] lev=" << m_lev << " balance: dt=" << dt << " s newton iterations max=" << nmax
1212  << " residual max=" << rmax << " W/m2 faces at a bound=" << nclamp << "\n";
1213  }
1214 }
@ pres
Definition: ERF_Kessler.H:28
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)
Definition: ERF_IBSEBSlab.H:105
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)
Definition: ERF_IBSEBBalance.H:80
amrex::Real newton_max_step_K
Largest Newton step [K].
Definition: ERF_IBSEBParams.H:142
amrex::Real newton_tol_K
Newton stops when a step changes T_skin by less than this [K].
Definition: ERF_IBSEBParams.H:141
amrex::Real T_skin_min
Lower bound of the skin temperature [K].
Definition: ERF_IBSEBParams.H:138
amrex::Real T_skin_max
Upper bound [K]; raise it for faces under a fire.
Definition: ERF_IBSEBParams.H:139
int newton_max_iter
Newton iteration cap per face and step.
Definition: ERF_IBSEBParams.H:140
Here is the call graph for this function:

◆ sun()

const SunState& IBFaceSet::sun ( ) const
inline
521 { return m_sun; }

◆ top_sample_interface()

int IBFaceSet::top_sample_interface ( ) const
inline

The highest interface of the level's vertical grid that any of its faces samples with radiation = two_stream (a roof the bottom of its fluid cell, a ceiling and a wall the top of its cell too), counted from the domain bottom, over all ranks; -1 when the level has no faces. The two-stream beam the faces read holds the interfaces up to there.

337 { return m_top_sample; }

◆ warn_fixed_sun_below_floor()

void IBFaceSet::warn_fixed_sun_below_floor ( ) const
private

Warn once when a fixed sun stands below erf.ibseb.sun_min_elev_deg and the map does not span the domain.

Warn when a fixed sun stands below erf.ibseb.sun_min_elev_deg and the ray map does not span the domain: the shadows of buildings beyond the halo are then not cast. Printed once per level by the I/O rank.

755 {
756  if (m_params.sun_mode != "fixed" || m_map_spans_domain) { return; }
757  const Real elev = 90.0 - m_params.sun_zenith_deg;
758  if (elev >= m_params.sun_min_elev_deg) { return; }
759  std::ostringstream os;
760  os << "erf.ibseb: the fixed sun stands " << elev << " deg above the horizon, below erf.ibseb.sun_min_elev_deg = "
761  << m_params.sun_min_elev_deg << ", and the ray map of level " << m_lev << " does not span the domain: a building"
762  " farther than " << m_halo[0] * m_dx[0] << " m in x or " << m_halo[1] * m_dx[1] << " m in y from a face casts no shadow"
763  " on it. Lower erf.ibseb.sun_min_elev_deg to the sun's elevation for exact shadows.";
764  if (ParallelDescriptor::IOProcessor()) { Warning(os.str().c_str()); }
765 }

◆ write_state()

void IBFaceSet::write_state ( const std::string &  file) const

Write the face state (skin temperature, sensible flux and slab temperatures) to file for the checkpoint: one record per face of the level, in one global order, written by the I/O rank.

The file holds one text line, IBSEBFaces 1 <nface> <n_layers> <nx> <ny> <nz> (the format version, the level's face count, the slab layers per face and the domain size in cells), then in binary a marker word (0x0102030405060708 as a 64-bit integer, so a file from a machine with another byte order is refused), the nface keys as 64-bit integers in ascending order, then the nface x (2 + n_layers) values as doubles: a face's skin temperature, its sensible flux (read as the previous step's by the convective velocity scale) and its slab layers from the skin inward. The key of a face is ((i ny + j) nz + k) 6 + 2 dir + (side > 0) with (i, j, k) counted from the domain's low corner, so it names the face whatever the rank count. The values are doubles whatever amrex::Real is, so a single-precision run reads a double-precision checkpoint and the other way round. The file costs 8 (3 + n_layers) bytes per face beyond its header line and marker, which is the data itself: the cell field it replaces covered the face-owning cells with whole boxes and was about eleven times larger on the FaceStorage skyscraper (erf-model/ERF#4019).

Every rank copies its faces to the host and sorts them by key; the I/O rank gathers the records, orders them and writes the file. Collective: every rank must call it.

One record per face, gathered on the I/O rank and written in key order: the header line, the sorted keys, then the values record by record. Every rank sorts its own faces first so the gathered runs are ordered; the I/O rank then merges them by sorting an index over all the records.

1819 {
1820  const int nl = n_layers();
1821  const int nrec = 2 + nl;
1822  const int nf = m_nface;
1823  const bool io = ParallelDescriptor::IOProcessor();
1824  const int root = ParallelDescriptor::IOProcessorNumber();
1825 
1826  std::vector<int> h_i(nf), h_j(nf), h_k(nf), h_dir(nf), h_side(nf);
1827  std::vector<Real> h_T(nf), h_H(nf), h_S(static_cast<size_t>(nf) * nl);
1828  Gpu::copy(Gpu::deviceToHost, d_i.begin(), d_i.end(), h_i.begin());
1829  Gpu::copy(Gpu::deviceToHost, d_j.begin(), d_j.end(), h_j.begin());
1830  Gpu::copy(Gpu::deviceToHost, d_k.begin(), d_k.end(), h_k.begin());
1831  Gpu::copy(Gpu::deviceToHost, d_dir.begin(), d_dir.end(), h_dir.begin());
1832  Gpu::copy(Gpu::deviceToHost, d_side.begin(), d_side.end(), h_side.begin());
1833  Gpu::copy(Gpu::deviceToHost, d_T_skin.begin(), d_T_skin.end(), h_T.begin());
1834  Gpu::copy(Gpu::deviceToHost, d_H.begin(), d_H.end(), h_H.begin());
1835  Gpu::copy(Gpu::deviceToHost, d_T_slab.begin(), d_T_slab.end(), h_S.begin());
1836  Gpu::streamSynchronize();
1837 
1838  // This rank's records in key order.
1839  std::vector<Long> my_key(nf);
1840  for (int n = 0; n < nf; ++n) {
1841  my_key[n] = face_key(h_i[n] - m_dom_lo[0], h_j[n] - m_dom_lo[1], h_k[n] - m_dom_lo[2],
1842  h_dir[n], h_side[n], m_ny, m_nz);
1843  }
1844  std::vector<int> order(nf);
1845  std::iota(order.begin(), order.end(), 0);
1846  std::sort(order.begin(), order.end(), [&] (int a, int b) { return my_key[a] < my_key[b]; });
1847  std::vector<Long> key(nf);
1848  std::vector<double> val(static_cast<size_t>(nf) * nrec);
1849  for (int m = 0; m < nf; ++m) {
1850  const int n = order[m];
1851  key[m] = my_key[n];
1852  double* r = &val[static_cast<size_t>(m) * nrec];
1853  r[0] = static_cast<double>(h_T[n]);
1854  r[1] = static_cast<double>(h_H[n]);
1855  for (int l = 0; l < nl; ++l) { r[2 + l] = static_cast<double>(h_S[static_cast<size_t>(n) * nl + l]); }
1856  }
1857 
1858  // Every rank's records on the I/O rank.
1859  std::vector<int> counts, disp, vcounts, vdisp;
1860  gather_counts(nf, counts, disp);
1861  scale_counts(counts, disp, nrec, vcounts, vdisp);
1862  Long ntot = 0;
1863  for (int c : counts) { ntot += c; }
1864  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(ntot * nrec < std::numeric_limits<int>::max(),
1865  "erf.ibseb: too many face records for one checkpoint file");
1866  std::vector<Long> all_key(io ? ntot : 0);
1867  std::vector<double> all_val(io ? ntot * nrec : 0);
1868  ParallelDescriptor::Gatherv(key.data(), nf, all_key.data(), counts, disp, root);
1869  ParallelDescriptor::Gatherv(val.data(), nf * nrec, all_val.data(), vcounts, vdisp, root);
1870 
1871  if (io) {
1872  std::vector<Long> perm(ntot);
1873  std::iota(perm.begin(), perm.end(), 0);
1874  std::sort(perm.begin(), perm.end(), [&] (Long a, Long b) { return all_key[a] < all_key[b]; });
1875  for (Long m = 1; m < ntot; ++m) {
1876  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(all_key[perm[m]] != all_key[perm[m - 1]],
1877  "erf.ibseb: two faces of the level share a checkpoint key");
1878  }
1879  std::vector<Long> skey(ntot);
1880  std::vector<double> sval(static_cast<size_t>(ntot) * nrec);
1881  for (Long m = 0; m < ntot; ++m) {
1882  skey[m] = all_key[perm[m]];
1883  std::copy_n(&all_val[static_cast<size_t>(perm[m]) * nrec], nrec, &sval[static_cast<size_t>(m) * nrec]);
1884  }
1885  std::ofstream f(file, std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
1886  if (!f) { Abort("erf.ibseb: cannot open " + file + " for writing"); }
1887  f << "IBSEBFaces 1 " << ntot << " " << nl << " " << m_nx << " " << m_ny << " " << m_nz << "\n";
1888  const Long marker = face_file_marker;
1889  f.write(reinterpret_cast<const char*>(&marker), sizeof(Long));
1890  f.write(reinterpret_cast<const char*>(skey.data()), static_cast<std::streamsize>(ntot * sizeof(Long)));
1891  f.write(reinterpret_cast<const char*>(sval.data()), static_cast<std::streamsize>(sval.size() * sizeof(double)));
1892  if (!f) { Abort("erf.ibseb: writing " + file + " failed"); }
1893  if (m_params.debug) {
1894  Print() << "[IBSEB DEBUG] lev=" << m_lev << " face state saved: " << ntot << " records x "
1895  << nrec << " values (" << ntot * nrec << " values, "
1896  << ntot * (sizeof(Long) + nrec * sizeof(double)) << " bytes of records) to " << file << "\n";
1897  }
1898  }
1899 }
Here is the call graph for this function:

Member Data Documentation

◆ d_albedo

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_albedo

◆ d_area

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_area

Face area [m2].

Referenced by add_heat_flux_to_source(), dump_faces(), and report().

◆ d_bid

amrex::Gpu::DeviceVector<int> IBFaceSet::d_bid

Building id (1-based)

Referenced by dump_faces(), and report().

◆ d_dir

amrex::Gpu::DeviceVector<int> IBFaceSet::d_dir

Face direction: 0 x, 1 y, 2 z.

Referenced by compute_shortwave(), compute_shortwave_two_stream(), dump_faces(), read_state(), and write_state().

◆ d_emis

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_emis

Optical properties of the face.

Referenced by dump_faces().

◆ d_f_bldg

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_f_bldg

View fractions, sum to 1.

Referenced by dump_faces().

◆ d_f_ground

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_f_ground

◆ d_f_sky

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_f_sky

◆ d_G

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_G

Referenced by dump_faces(), and report().

◆ d_H

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_H

◆ d_H_coeff

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_H_coeff

H = H_coeff (theta_skin - theta_air) [W/m2/K].

Referenced by dump_faces().

◆ d_hbld

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_hbld

Height of the face's building column [m].

Referenced by dump_faces().

◆ d_i

◆ d_j

◆ d_k

amrex::Gpu::DeviceVector<int> IBFaceSet::d_k

◆ d_kth

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_kth

Referenced by dump_faces().

◆ d_LE

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_LE

Referenced by dump_faces().

◆ d_LW_down_in

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_LW_down_in

Incoming longwave on the face (sky, ground and walls)

Referenced by dump_faces().

◆ d_LW_ext

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_LW_ext

Incoming longwave from sky and ground only [W/m2].

Referenced by dump_faces().

◆ d_LW_net

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_LW_net

Referenced by dump_faces(), and report().

◆ d_mat

amrex::Gpu::DeviceVector<int> IBFaceSet::d_mat

Material id (0 = uniform inputs)

Referenced by dump_faces().

◆ d_niter

amrex::Gpu::DeviceVector<int> IBFaceSet::d_niter

Newton iterations of the last solve.

Referenced by dump_faces().

◆ d_olen

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_olen

Obukhov length used [m] (1e30 when neutral)

Referenced by dump_faces().

◆ d_Q_ext

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_Q_ext

External incident flux [W/m2], e.g. fire radiation.

Referenced by dump_faces(), and report().

◆ d_resid

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_resid

|balance| after the solve [W/m2]

Referenced by dump_faces(), and report().

◆ d_rho

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_rho

Density of the fluid cell [kg/m3].

Referenced by dump_faces().

◆ d_rhocp

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_rhocp

Referenced by dump_faces().

◆ d_shadow

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_shadow

1 when the ray to the sun hits a building, else 0

Referenced by compute_shortwave(), compute_shortwave_two_stream(), dump_faces(), and report().

◆ d_side

amrex::Gpu::DeviceVector<int> IBFaceSet::d_side

Solid neighbour at -1 (low) or +1 (high) side.

Referenced by compute_shortwave(), compute_shortwave_two_stream(), dump_faces(), read_state(), and write_state().

◆ d_SW_abs

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_SW_abs

◆ d_SW_diffuse_in

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_SW_diffuse_in

Sky and ground-reflected diffuse incident.

Referenced by compute_shortwave(), compute_shortwave_two_stream(), and dump_faces().

◆ d_SW_direct_in

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_SW_direct_in

Direct beam incident on the face.

Referenced by compute_shortwave(), compute_shortwave_two_stream(), and dump_faces().

◆ d_T_air

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_T_air

Air temperature of the fluid cell.

Referenced by dump_faces().

◆ d_T_skin

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_T_skin

Skin temperature [K].

Referenced by dump_faces(), read_state(), report(), scatter_diagnostics(), and write_state().

◆ d_T_slab

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_T_slab

Slab layers [K], face-major: [face * n_layers + layer].

Referenced by dump_faces(), read_state(), and write_state().

◆ d_theta_air

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_theta_air

Potential temperature of the fluid cell [K].

Referenced by dump_faces().

◆ d_thick

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_thick

Slab conductivity [W/m/K], heat capacity [J/m3/K], thickness [m].

Referenced by dump_faces().

◆ d_U_tan

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_U_tan

Tangential wind speed at the fluid cell [m/s].

Referenced by dump_faces().

◆ d_ustar

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_ustar

Friction velocity of the wall function [m/s].

Referenced by dump_faces().

◆ d_w_star

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_w_star

Convective velocity scale of the step [m/s].

Referenced by dump_faces(), and report().

◆ d_xf

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_xf

◆ d_yf

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_yf

◆ d_z_i

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_z_i

Depth in w* [m]: mixed layer above a roof, building height for a wall.

Referenced by dump_faces().

◆ d_zf

amrex::Gpu::DeviceVector<amrex::Real> IBFaceSet::d_zf

Face centre [m].

Referenced by compute_shortwave(), compute_shortwave_two_stream(), and dump_faces().

◆ m_area_total

amrex::Real IBFaceSet::m_area_total = 0.0
private

Total face area [m2].

Referenced by print_debug_summary(), and report().

◆ m_bld_area

std::vector<amrex::Real> IBFaceSet::m_bld_area
private

Face area per building [m2].

Referenced by print_debug_summary(), and report().

◆ m_bld_ihi

std::vector<int> IBFaceSet::m_bld_ihi
private

Referenced by print_debug_summary().

◆ m_bld_ilo

std::vector<int> IBFaceSet::m_bld_ilo
private

Referenced by print_debug_summary().

◆ m_bld_jhi

std::vector<int> IBFaceSet::m_bld_jhi
private

Referenced by print_debug_summary().

◆ m_bld_jlo

std::vector<int> IBFaceSet::m_bld_jlo
private

Referenced by print_debug_summary().

◆ m_bld_ncol

std::vector<amrex::Long> IBFaceSet::m_bld_ncol
private

Referenced by print_debug_summary().

◆ m_bld_nface

std::vector<amrex::Long> IBFaceSet::m_bld_nface
private

Faces per building.

Referenced by print_debug_summary(), and report().

◆ m_col_top_max

amrex::Real IBFaceSet::m_col_top_max = 0.0
private

Tallest column the ray map holds [m] (the levels below included after add_outside_occluders())

◆ m_col_valid

amrex::iMultiFab IBFaceSet::m_col_valid

Referenced by print_debug_summary().

◆ m_cost_n

amrex::Long IBFaceSet::m_cost_n = 0
private

Number of per-step updates timed.

Referenced by add_cost(), and report().

◆ m_cost_s

double IBFaceSet::m_cost_s = 0.0
private

Wall-clock seconds spent in the per-step update on this rank.

Referenced by add_cost(), and report().

◆ m_dom_lo

amrex::IntVect IBFaceSet::m_dom_lo {AMREX_D_DECL(0, 0, 0)}
private

Low corner of the level's domain.

Referenced by compute_shortwave_two_stream(), read_state(), and write_state().

◆ m_dx

amrex::Real IBFaceSet::m_dx[3] = {0.0, 0.0, 0.0}
private

Cell sizes [m].

◆ m_fab_start

◆ m_geom

amrex::Geometry IBFaceSet::m_geom
private

Geometry of the level.

Referenced by map_buildings_to_level0().

◆ m_halo

amrex::IntVect IBFaceSet::m_halo {AMREX_D_DECL(0, 0, 0)}

Halo of the patch in cells.

Referenced by ray_halo().

◆ m_init_cost_s

double IBFaceSet::m_init_cost_s = 0.0
private

Wall-clock seconds of build() and compute_view_fractions()

Referenced by report(), and set_init_cost().

◆ m_lab

amrex::iMultiFab IBFaceSet::m_lab
private

◆ m_lev

int IBFaceSet::m_lev = 0
private

◆ m_map_spans_domain

bool IBFaceSet::m_map_spans_domain = false

Every rank's patch spans the domain.

Referenced by ray_map_spans_domain().

◆ m_max_steps

int IBFaceSet::m_max_steps = 0

Columns a walk is followed for: the halo's reach in x plus y, before the domain cap.

◆ m_nbld

int IBFaceSet::m_nbld = 0
private

Buildings of the level (global)

Referenced by map_buildings_to_level0(), n_buildings(), print_debug_summary(), and report().

◆ m_nface

int IBFaceSet::m_nface = 0
private

◆ m_nface_dir

amrex::Long IBFaceSet::m_nface_dir[3] = {0, 0, 0}
private

Faces per direction.

Referenced by compute_shortwave_two_stream(), print_debug_summary(), read_state(), and report().

◆ m_nx

int IBFaceSet::m_nx = 0
private

Referenced by read_state(), and write_state().

◆ m_ny

int IBFaceSet::m_ny = 0
private

Referenced by read_state(), and write_state().

◆ m_nz

int IBFaceSet::m_nz = 0
private

Cells of the domain in x, y and z.

Referenced by read_state(), and write_state().

◆ m_params

◆ m_patch_box

amrex::Box IBFaceSet::m_patch_box

This rank's patch (empty without boxes)

Referenced by print_debug_summary().

◆ m_per_x

bool IBFaceSet::m_per_x = false
private

◆ m_per_y

bool IBFaceSet::m_per_y = false
private

Periodicity in x and y.

◆ m_qv_comp

int IBFaceSet::m_qv_comp = -1
private

State component of rho qv, -1 without a moisture model.

Referenced by add_heat_flux_to_source().

◆ m_ray_patch

amrex::iMultiFab IBFaceSet::m_ray_patch

◆ m_sun

SunState IBFaceSet::m_sun
private

◆ m_to_lev0

std::vector<int> IBFaceSet::m_to_lev0
private

◆ m_top_sample

int IBFaceSet::m_top_sample = -1
private

Highest interface a face samples, over all ranks (-1: no faces)

Referenced by has_faces(), and top_sample_interface().

◆ m_ts_sun

TwoStreamSun IBFaceSet::m_ts_sun
private

◆ m_wrap_x

bool IBFaceSet::m_wrap_x = false

◆ m_wrap_y

bool IBFaceSet::m_wrap_y = false

The patch spans the periodic domain in x / y.

◆ m_x_lo

amrex::Real IBFaceSet::m_x_lo = 0.0
private

◆ m_y_lo

amrex::Real IBFaceSet::m_y_lo = 0.0
private

Domain origin [m].

◆ m_z_ground

amrex::Real IBFaceSet::m_z_ground = 0.0
private

Ground height [m] (flat, the domain bottom)


The documentation for this class was generated from the following files: