17#include "../include/EnergyBalanceModel.h"
21#ifdef HELIOS_CUDA_AVAILABLE
22#include <cuda_runtime.h>
29using namespace helios;
35 temperature_default = 300;
37 wind_speed_default = 1.f;
39 air_temperature_default = 300;
41 air_humidity_default = 0.5;
43 pressure_default = 101000;
49 heatcapacity_default = 0;
51 surface_humidity_default = 1;
53 air_energy_balance_enabled =
false;
60#ifdef HELIOS_CUDA_AVAILABLE
62 gpu_acceleration_enabled =
true;
65 initializeGPUAcceleration();
85 std::cout <<
"Running energy balance model..." << std::flush;
90 std::cerr <<
"WARNING (EnergyBalanceModel::run): No primitives have been added to the context. There is nothing to simulate. Exiting..." << std::endl;
94 evaluateSurfaceEnergyBalance(UUIDs, dt);
97 std::cout <<
"done." << std::endl;
101#ifdef HELIOS_CUDA_AVAILABLE
102void EnergyBalanceModel::initializeGPUAcceleration() {
105 cudaError_t err = cudaGetDeviceCount(&deviceCount);
107 if (err != cudaSuccess || deviceCount == 0) {
110 std::cout <<
"INFO (EnergyBalanceModel): CUDA runtime unavailable (" << cudaGetErrorString(err) <<
"). Using OpenMP CPU implementation." << std::endl;
112 gpu_acceleration_enabled =
false;
118 std::cout <<
"INFO (EnergyBalanceModel): GPU acceleration enabled (" << deviceCount <<
" device(s) found)." << std::endl;
120 gpu_acceleration_enabled =
true;
124void EnergyBalanceModel::evaluateSurfaceEnergyBalance(
const std::vector<uint> &UUIDs,
float dt) {
125#ifdef HELIOS_CUDA_AVAILABLE
126 if (gpu_acceleration_enabled) {
127 evaluateSurfaceEnergyBalance_GPU(UUIDs, dt);
129 evaluateSurfaceEnergyBalance_CPU(UUIDs, dt);
133 evaluateSurfaceEnergyBalance_CPU(UUIDs, dt);
137#ifdef HELIOS_CUDA_AVAILABLE
138void EnergyBalanceModel::enableGPUAcceleration() {
140 initializeGPUAcceleration();
143void EnergyBalanceModel::disableGPUAcceleration() {
144 gpu_acceleration_enabled =
false;
147bool EnergyBalanceModel::isGPUAccelerationEnabled()
const {
148 return gpu_acceleration_enabled;
159 helios_runtime_error(
"ERROR (EnergyBalanceModel::evaluateAirEnergyBalance): dt_sec must be greater than zero to run the air energy balance.");
166 float air_temperature_reference = air_temperature_default;
168 context->
getGlobalData(
"air_temperature_reference", air_temperature_reference);
171 float air_humidity_reference = air_humidity_default;
173 context->
getGlobalData(
"air_humidity_reference", air_humidity_reference);
176 float wind_speed_reference = wind_speed_default;
178 context->
getGlobalData(
"wind_speed_reference", wind_speed_reference);
187 Patm = pressure_default;
190 float air_temperature_average;
192 context->
getGlobalData(
"air_temperature_average", air_temperature_average);
194 air_temperature_average = air_temperature_reference;
197 float air_moisture_average;
199 context->
getGlobalData(
"air_moisture_average", air_moisture_average);
201 air_moisture_average = air_humidity_reference * esat_Pa(air_temperature_reference) / Patm;
204 float air_temperature_ABL = 0;
206 context->
getGlobalData(
"air_temperature_ABL", air_temperature_ABL);
209 float air_moisture_ABL;
211 context->
getGlobalData(
"air_moisture_ABL", air_moisture_ABL);
213 air_moisture_ABL = air_humidity_reference * esat_Pa(air_temperature_reference) / Patm;
217 if (canopy_dimensions ==
make_vec3(0, 0, 0)) {
218 vec2 xbounds, ybounds, zbounds;
220 canopy_dimensions =
make_vec3(xbounds.
y - xbounds.
x, ybounds.
y - ybounds.
x, zbounds.
y - zbounds.
x);
222 assert(canopy_dimensions.
x > 0 && canopy_dimensions.
y > 0 && canopy_dimensions.
z > 0);
223 if (canopy_height_m == 0) {
224 canopy_height_m = canopy_dimensions.
z;
226 if (reference_height_m == 0) {
227 reference_height_m = canopy_height_m;
230 std::cout <<
"Read in initial values. Ta = " << air_temperature_average <<
"; e = " << air_moisture_average <<
"; " << air_moisture_average / esat_Pa(air_temperature_reference) * Patm << std::endl;
232 std::cout <<
"Canopy dimensions: " << canopy_dimensions.
x <<
" x " << canopy_dimensions.
y <<
" x " << canopy_height_m << std::endl;
234 float displacement_height = 0.67f * canopy_height_m;
235 float zo_m = 0.1f * canopy_height_m;
236 float zo_h = 0.01f * canopy_height_m;
238 float abl_height_m = 1000.f;
241 float air_temperature_old = 0;
242 float air_moisture_old = 0;
243 float air_temperature_ABL_old = 0;
244 float air_moisture_ABL_old = 0;
245 while (time < time_advance_sec) {
246 float dt_actual = dt_sec;
247 if (time + dt_actual > time_advance_sec) {
248 dt_actual = time_advance_sec - time;
251 std::cout <<
"Air moisture average: " << air_moisture_average << std::endl;
257 float sensible_source_flux_W_m3;
259 sensible_source_flux_W_m3 /= canopy_dimensions.
x * canopy_dimensions.
y * canopy_height_m;
262 float moisture_source_flux_W_m3;
264 moisture_source_flux_W_m3 /= canopy_dimensions.
x * canopy_dimensions.
y * canopy_height_m;
265 float moisture_source_flux_mol_s_m3 = moisture_source_flux_W_m3 /
lambda_mol;
267 float rho_air_mol_m3 = Patm / (
R * air_temperature_average);
273 float H_s = sensible_source_flux_W_m3 * canopy_height_m;
274 float E_s = moisture_source_flux_mol_s_m3 * canopy_height_m;
277 float u_star_neutral =
von_Karman_constant * wind_speed_reference / std::log((reference_height_m - displacement_height) / zo_m);
280 float rho_air_kg_m3 = rho_air_mol_m3 * 0.02896f;
283 if (std::fabs(H_s) > 1e-6f) {
284 L = -rho_air_kg_m3 * cp_air_kg * std::pow(u_star_neutral, 3) * air_temperature_reference / (
von_Karman_constant * 9.81f * H_s);
288 auto psi_unstable_u = [&](
float zeta) {
289 float x = std::pow(1.f - 16.f * zeta, 0.25f);
290 return 2.f * std::log((1.f + x) / 2.f) + std::log((1.f + x * x) / 2.f) - 2.f * std::atan(x) + 0.5f *
M_PI;
292 auto psi_unstable_h = [&](
float zeta) {
return 2.f * std::log((1.f + std::sqrt(1.f - 16.f * zeta)) / 2.f); };
293 auto psi_stable = [&](
float zeta) {
return -5.f * zeta; };
295 float zeta_r = (reference_height_m - displacement_height) / L;
296 float psi_m_r = (zeta_r < 0.f) ? psi_unstable_u(zeta_r) : psi_stable(zeta_r);
297 float psi_h_r = (zeta_r < 0.f) ? psi_unstable_h(zeta_r) : psi_stable(zeta_r);
300 float u_star =
von_Karman_constant * wind_speed_reference / (std::log((reference_height_m - displacement_height) / zo_m) - psi_m_r);
303 float theta_star = H_s / (rho_air_mol_m3 *
cp_air_mol * u_star);
304 float q_star = E_s / (rho_air_mol_m3 * u_star);
307 float ln_m_corr = std::log((reference_height_m - displacement_height) / zo_m) - psi_m_r;
308 float ln_h_corr = std::log((reference_height_m - displacement_height) / zo_h) - psi_h_r;
311 float ga = std::pow(
von_Karman_constant, 2) * wind_speed_reference / (ln_m_corr * ln_h_corr) * rho_air_mol_m3;
314 float air_moisture_reference = esat_Pa(air_temperature_reference) / Patm * air_humidity_reference;
319 float x_sat = esat_Pa(T_int) / Patm;
322 x_int = 0.99f * x_sat;
326 float H_canopy = sensible_source_flux_W_m3 * canopy_height_m;
328 if (air_temperature_ABL == 0.f) {
330 float w_star0 = H_canopy > 0.f ? std::pow(9.81f * H_canopy * canopy_height_m / (rho_air_mol_m3 *
cp_air_mol * air_temperature_reference), 1.f / 3.f) : 0.f;
331 const float A_e = 0.02f;
332 float w_e0 = A_e * w_star0;
333 g_e = rho_air_mol_m3 * w_e0;
335 air_temperature_ABL = (ga * air_temperature_average + g_e * air_temperature_reference) / (ga + g_e);
336 air_moisture_ABL = (ga * air_moisture_average + g_e * air_moisture_reference) / (ga + g_e);
340 if (H_canopy > 0.f) {
341 w_star = std::pow(9.81f * H_canopy * canopy_height_m / (rho_air_mol_m3 *
cp_air_mol * air_temperature_ABL), 1.f / 3.f);
343 float u_star_shear =
von_Karman_constant * wind_speed_reference / std::log((reference_height_m - displacement_height) / zo_m);
344 float w_e = std::fmax(0.01f * w_star, 0.005f * u_star_shear);
345 g_e = rho_air_mol_m3 * w_e;
355 float sensible_upper_flux_W_m2 =
cp_air_mol * ga * (air_temperature_average - T_int);
360 float numerator_temp = air_temperature_average + dt_actual / (rho_air_mol_m3 *
cp_air_mol * canopy_height_m) * (H_s +
cp_air_mol * ga * T_int);
361 float denominator_temp = 1.f + dt_actual * ga / (rho_air_mol_m3 * canopy_height_m);
362 air_temperature_average = numerator_temp / denominator_temp;
368 float E_top = ga * (air_moisture_average - x_int);
369 float f = std::pow(1.f - (1.f - 0.622f) * air_moisture_average, 2) / 0.622f;
370 float numer_can_x = air_moisture_average + dt_actual * f / (rho_air_mol_m3 * canopy_height_m) * (E_s + ga * x_int);
371 float denom_can_x = 1.f + dt_actual * f * ga / (rho_air_mol_m3 * canopy_height_m);
372 air_moisture_average = numer_can_x / denom_can_x;
375 float denom_abl_T = 1.f + dt_actual * (ga + g_e) / (rho_air_mol_m3 *
cp_air_mol * abl_height_m);
376 float num_abl_T = air_temperature_ABL + dt_actual / (rho_air_mol_m3 *
cp_air_mol * abl_height_m) * (sensible_upper_flux_W_m2 +
cp_air_mol * g_e * (air_temperature_reference - air_temperature_ABL));
377 air_temperature_ABL = num_abl_T / denom_abl_T;
382 float E_e = g_e * (air_moisture_ABL - air_moisture_reference);
383 float numer_abl_x = air_moisture_ABL + dt_actual / (rho_air_mol_m3 * abl_height_m) * (E_top - E_e);
384 float denom_abl_x = 1.f + dt_actual * (ga + g_e) / (rho_air_mol_m3 * abl_height_m);
385 air_moisture_ABL = numer_abl_x / denom_abl_x;
388 float esat = esat_Pa(air_temperature_average) / Patm;
389 if (air_moisture_average > esat) {
390 warnings.
addWarning(
"air_moisture_exceeds_saturation",
"Air moisture exceeds saturation. Capping to saturation value.");
391 air_moisture_average = 0.99f * esat;
393 esat = esat_Pa(air_temperature_ABL) / Patm;
394 if (air_moisture_ABL > esat) {
395 air_moisture_ABL = 0.99f * esat;
399 if (dt_actual > 0.5f * canopy_height_m * rho_air_mol_m3 / ga) {
400 warnings.
addWarning(
"timestep_too_large",
"Time step is too large. The air energy balance may not converge properly.");
403 float heat_from_Htop = dt_actual / (rho_air_mol_m3 *
cp_air_mol * abl_height_m) * sensible_upper_flux_W_m2;
404 float heat_from_entrainment = dt_actual * g_e * (air_temperature_reference - air_temperature_ABL) / (rho_air_mol_m3 * abl_height_m);
406 std::cerr <<
"ABL ENERGY UPDATE:\n"
407 <<
" T_ABL_n = " << air_temperature_ABL <<
"\n"
408 <<
" T_ref = " << air_temperature_reference <<
"\n"
409 <<
" g_e = " << g_e <<
" mol/m²/s\n"
410 <<
" Heat from H_top = " << heat_from_Htop <<
" K\n"
411 <<
" Heat from H_e = " << heat_from_entrainment <<
" K\n"
412 <<
" Numerator = " << num_abl_T <<
" K\n"
413 <<
" Denominator = " << denom_abl_T <<
"\n"
414 <<
" T_ABL_new = " << (num_abl_T / denom_abl_T) <<
"\n";
420 H_s = sensible_source_flux_W_m3 * canopy_height_m;
421 float H_top =
cp_air_mol * ga * (air_temperature_average - T_int);
422 float H_e =
cp_air_mol * g_e * (air_temperature_ABL - air_temperature_reference);
424 E_s = moisture_source_flux_mol_s_m3 * canopy_height_m;
425 E_top = ga * (air_moisture_average - x_int);
426 E_e = g_e * (air_moisture_ABL - air_moisture_reference);
429 std::cout << std::fixed << std::setprecision(5) <<
"step=" << time <<
" T_c=" << air_temperature_average <<
" T_int=" << T_int <<
" T_b=" << air_temperature_ABL <<
" H_s=" << H_s <<
" H_top=" << H_top <<
" H_e=" << H_e
430 <<
" E_s=" << E_s <<
" E_top=" << E_top <<
" E_e=" << E_e <<
" psi_m=" << psi_m_r <<
" psi_h=" << psi_h_r <<
" u*=" << u_star <<
" L=" << L << std::endl;
434 const float tol = 1e-5f;
435 float S_can = rho_air_mol_m3 *
cp_air_mol * canopy_height_m * (air_temperature_average - air_temperature_old) / dt_actual;
436 if (fabs(H_s - H_top - S_can) > tol)
437 std::cerr <<
"Canopy budget error: " << (H_s - H_top - S_can) <<
"\n";
439 float S_abl = rho_air_mol_m3 *
cp_air_mol * abl_height_m * (air_temperature_ABL - air_temperature_ABL_old) / dt_actual;
440 if (fabs(H_top - H_e - S_abl) > tol)
441 std::cerr <<
"ABL budget error: " << (H_top - H_e - S_abl) <<
"\n";
445 S_can = rho_air_mol_m3 * canopy_height_m * (air_moisture_average - air_moisture_old) / dt_actual;
447 const float tol_m = 1e-6f;
448 if (std::fabs(E_s - E_top - S_can) > tol_m) {
449 std::cerr <<
"CANOPY MOISTURE BUDGET ERROR: " <<
"E_s - E_up - S_can = " << E_s - E_top - S_can <<
" mol/m2/s\n";
452 S_abl = rho_air_mol_m3 * abl_height_m * (air_moisture_ABL - air_moisture_ABL_old) / dt_actual;
454 if (std::fabs(E_top - E_e - S_abl) > tol_m) {
455 std::cerr <<
"ABL MOISTURE BUDGET ERROR: " <<
"E_up - E_e - S_abl = " << E_top - E_e - S_abl <<
" mol/m2/s\n";
462 context->
setPrimitiveData(UUIDs,
"air_temperature", air_temperature_average);
463 float air_humidity = air_moisture_average * Patm / esat_Pa(air_temperature_average);
467 context->
setGlobalData(
"air_temperature_average", air_temperature_average);
468 context->
setGlobalData(
"air_humidity_average", air_humidity);
469 context->
setGlobalData(
"air_moisture_average", air_moisture_average);
471 context->
setGlobalData(
"air_temperature_ABL", air_temperature_ABL);
472 float air_humidity_ABL = air_moisture_ABL * Patm / esat_Pa(air_temperature_ABL);
473 context->
setGlobalData(
"air_humidity_ABL", air_humidity_ABL);
476 float air_humidity_interface = x_int * Patm / esat_Pa(T_int);
477 context->
setGlobalData(
"air_humidity_interface", air_humidity_interface);
479 context->
setGlobalData(
"aerodynamic_resistance", rho_air_mol_m3 / ga);
481 std::cout <<
"Computed air temperature: " << air_temperature_average <<
" " << air_temperature_old << std::endl;
482 std::cout <<
"Computed air humidity: " << air_humidity << std::endl;
483 std::cout <<
"Computed air moisture: " << air_moisture_average <<
" " << air_moisture_old << std::endl;
485 if (time > 0 && std::abs(air_temperature_average - air_temperature_old) / air_temperature_old < 0.003f && std::abs(air_moisture_average - air_moisture_old) / air_moisture_old < 0.003f) {
487 std::cout <<
"Converged" << std::endl;
490 air_temperature_old = air_temperature_average;
491 air_moisture_old = air_moisture_average;
492 air_temperature_ABL_old = air_temperature_ABL;
493 air_moisture_ABL_old = air_moisture_ABL;
499 warnings.
report(std::cerr);
507 message_flag =
false;
511 if (std::find(radiation_bands.begin(), radiation_bands.end(), band) == radiation_bands.end()) {
512 radiation_bands.emplace_back(band);
517 for (
auto &band: bands) {
528 if (canopy_height_m < 0) {
529 helios_runtime_error(
"ERROR (EnergyBalanceModel::enableAirEnergyBalance): Canopy height must be greater than or equal to zero.");
530 }
else if (canopy_height_m != 0 && reference_height_m < canopy_height_m) {
531 helios_runtime_error(
"ERROR (EnergyBalanceModel::enableAirEnergyBalance): Reference height must be greater than or equal to canopy height.");
534 air_energy_balance_enabled =
true;
535 this->canopy_height_m = canopy_height_m;
536 this->reference_height_m = reference_height_m;
541 if (strcmp(label,
"boundarylayer_conductance_out") == 0 || strcmp(label,
"vapor_pressure_deficit") == 0 || strcmp(label,
"storage_flux") == 0 || strcmp(label,
"net_radiation_flux") == 0) {
542 output_prim_data.emplace_back(label);
544 std::cerr <<
"WARNING (EnergyBalanceModel::optionalOutputPrimitiveData): unknown output primitive data '" << label <<
"' will be ignored." << std::endl;
554 size_t assumed_default_TL = 0;
555 size_t assumed_default_U = 0;
556 size_t assumed_default_L = 0;
557 size_t assumed_default_p = 0;
558 size_t assumed_default_Ta = 0;
559 size_t assumed_default_rh = 0;
560 size_t assumed_default_gH = 0;
561 size_t assumed_default_gs = 0;
562 size_t assumed_default_Qother = 0;
563 size_t assumed_default_heatcapacity = 0;
564 size_t assumed_default_fs = 0;
565 size_t twosided_0 = 0;
566 size_t twosided_1 = 0;
570 size_t Nprimitives = UUIDs.size();
572 for (
uint UUID: UUIDs) {
576 assumed_default_TL++;
586 assumed_default_Ta++;
591 assumed_default_rh++;
596 assumed_default_gH++;
611 assumed_default_gs++;
616 assumed_default_heatcapacity++;
621 assumed_default_Qother++;
638 }
else if (Ne == 2) {
647 assumed_default_fs++;
651 std::cout <<
"--- Energy Balance Model Default Value Report ---" << std::endl;
653 std::cout <<
"surface temperature (initial guess): " << assumed_default_TL <<
" of " << Nprimitives <<
" used default value of " << temperature_default
656 " primitive data did not exist"
658 std::cout <<
"air pressure: " << assumed_default_p <<
" of " << Nprimitives <<
" used default value of " << pressure_default
661 " primitive data did not exist"
663 std::cout <<
"air temperature: " << assumed_default_Ta <<
" of " << Nprimitives <<
" used default value of " << air_temperature_default
666 " primitive data did not exist"
668 std::cout <<
"air humidity: " << assumed_default_rh <<
" of " << Nprimitives <<
" used default value of " << air_humidity_default
671 " primitive data did not exist"
673 std::cout <<
"boundary-layer conductance: " << assumed_default_gH <<
" of " << Nprimitives
674 <<
" calculated boundary-layer conductance from Polhausen equation because "
675 "boundarylayer_conductance"
676 " primitive data did not exist"
678 if (assumed_default_gH > 0) {
679 std::cout <<
" - wind speed: " << assumed_default_U <<
" of " << assumed_default_gH <<
" using Polhausen equation used default value of " << wind_speed_default
682 " primitive data did not exist"
684 std::cout <<
" - object length: " << assumed_default_L <<
" of " << assumed_default_gH
685 <<
" using Polhausen equation used the primitive/object length/area to calculate object length because "
687 " primitive data did not exist"
690 std::cout <<
"moisture conductance: " << assumed_default_gs <<
" of " << Nprimitives <<
" used default value of " << gS_default
692 "moisture_conductance"
693 " primitive data did not exist"
695 std::cout <<
"surface humidity: " << assumed_default_fs <<
" of " << Nprimitives <<
" used default value of " << surface_humidity_default
698 " primitive data did not exist"
700 std::cout <<
"two-sided flag: " << twosided_0 <<
" of " << Nprimitives <<
" used two-sided flag=0; " << twosided_1 <<
" of " << Nprimitives <<
" used two-sided flag=1 (default)" << std::endl;
701 std::cout <<
"evaporating faces: " << Ne_1 <<
" of " << Nprimitives <<
" used Ne = 1 (default); " << Ne_2 <<
" of " << Nprimitives <<
" used Ne = 2" << std::endl;
703 std::cout <<
"------------------------------------------------------" << std::endl;
707static inline float evaluateEnergyBalance_CPU(
float T,
float R,
float Qother,
float eps,
float Ta,
float ea,
float pressure,
float gH,
float gS,
uint Nsides,
float stomatal_sidedness,
float heatcapacity,
float surfacehumidity,
float dt,
711 float Rout = float(Nsides) * eps * 5.67e-8F * T * T * T * T;
717 float es = 611.0f * expf(17.502f * (T - 273.f) / (T - 273.f + 240.97f));
718 float gM = 1.08f * gH * gS * (stomatal_sidedness / (1.08f * gH + gS * stomatal_sidedness) + (1.f - stomatal_sidedness) / (1.08f * gH + gS * (1.f - stomatal_sidedness)));
719 if (gH == 0 && gS == 0) {
723 float QL = gM *
lambda_mol * (es - ea * surfacehumidity) / pressure;
728 storage = heatcapacity * (T - Tprev) / dt;
732 return R - Rout - QH - QL - Qother - storage;
734void EnergyBalanceModel::evaluateSurfaceEnergyBalance_CPU(
const std::vector<uint> &UUIDs,
float dt) {
742 if (radiation_bands.empty()) {
746 const uint Nprimitives = UUIDs.size();
748 std::vector<float> Rn(Nprimitives, 0);
749 std::vector<float> emissivity(Nprimitives);
751 for (
size_t u = 0; u < Nprimitives; u++) {
752 emissivity.at(u) = 1.f;
756 for (
int b = 0; b < radiation_bands.size(); b++) {
757 for (
size_t u = 0; u < Nprimitives; u++) {
758 size_t p = UUIDs.at(u);
761 snprintf(str,
sizeof(str),
"radiation_flux_%s", radiation_bands.at(b).c_str());
763 helios_runtime_error(
"ERROR (EnergyBalanceModel::run): No radiation was found in the context for band " + std::string(radiation_bands.at(b)) +
". Did you run the radiation model for this band?");
765 helios_runtime_error(
"ERROR (EnergyBalanceModel::run): Radiation primitive data for band " + std::string(radiation_bands.at(b)) +
" does not have the correct type of 'float'");
771 snprintf(str,
sizeof(str),
"emissivity_%s", radiation_bands.at(b).c_str());
775 warnings.
addWarning(
"missing_emissivity",
"Primitive data 'emissivity_" + std::string(radiation_bands.at(b)) +
"' not set, using default (1.0)");
783 std::vector<float> To(Nprimitives);
784 std::vector<float>
R(Nprimitives);
785 std::vector<float> Qother(Nprimitives);
786 std::vector<float> eps(Nprimitives);
787 std::vector<float> Ta(Nprimitives);
788 std::vector<float> ea(Nprimitives);
789 std::vector<float> pressure(Nprimitives);
790 std::vector<float> gH(Nprimitives);
791 std::vector<float> gS(Nprimitives);
792 std::vector<uint> Nsides(Nprimitives);
793 std::vector<float> stomatal_sidedness(Nprimitives);
794 std::vector<float> heatcapacity(Nprimitives);
795 std::vector<float> surfacehumidity(Nprimitives);
797 bool calculated_blconductance_used =
false;
798 bool primitive_length_used =
false;
801 for (
uint u = 0; u < Nprimitives; u++) {
802 size_t p = UUIDs.at(u);
808 To[u] = temperature_default;
809 warnings.
addWarning(
"missing_surface_temperature",
"Primitive data 'temperature' not set, using default (" + std::to_string(temperature_default) +
" K)");
819 warnings.
addWarning(
"air_temperature_likely_celsius",
"Value of " + std::to_string(Ta[u]) +
" in 'air_temperature' is very small - should be in Kelvin, using default (" + std::to_string(air_temperature_default) +
" K)");
820 Ta[u] = air_temperature_default;
823 Ta[u] = air_temperature_default;
824 warnings.
addWarning(
"missing_air_temperature",
"Primitive data 'air_temperature' not set, using default (" + std::to_string(air_temperature_default) +
" K)");
832 warnings.
addWarning(
"air_humidity_out_of_range_high",
"Value of " + std::to_string(hr) +
" in 'air_humidity' > 1.0 - should be fractional [0,1], using default (" + std::to_string(air_humidity_default) +
")");
833 hr = air_humidity_default;
834 }
else if (hr < 0.f) {
835 warnings.
addWarning(
"air_humidity_out_of_range_low",
"Value of " + std::to_string(hr) +
" in 'air_humidity' < 0.0 - should be fractional [0,1], using default (" + std::to_string(air_humidity_default) +
")");
836 hr = air_humidity_default;
839 hr = air_humidity_default;
840 warnings.
addWarning(
"missing_air_humidity",
"Primitive data 'air_humidity' not set, using default (" + std::to_string(air_humidity_default) +
")");
844 float esat = esat_Pa(Ta[u]);
850 if (pressure[u] < 10000.f) {
852 std::cout <<
"WARNING (EnergyBalanceModel::run): Value of " << pressure[u] <<
" given in 'air_pressure' primitive data is very small. "
853 <<
"Values should be given in units of Pascals. Assuming default value of " << pressure_default << std::endl;
855 pressure[u] = pressure_default;
858 pressure[u] = pressure_default;
863 Nsides[u] = (twosided_flag == 0) ? 1 : 2;
866 stomatal_sidedness[u] = 0.f;
873 stomatal_sidedness[u] = 0.f;
874 }
else if (flag == 2) {
875 stomatal_sidedness[u] = 0.5f;
888 U = wind_speed_default;
889 warnings.
addWarning(
"missing_wind_speed",
"Primitive data 'wind_speed' not set, using default (" + std::to_string(wind_speed_default) +
" m/s)");
898 primitive_length_used =
true;
905 primitive_length_used =
true;
908 gH[u] = 0.135f * sqrt(U / L) * float(Nsides[u]);
909 calculated_blconductance_used =
true;
923 Qother[u] = Qother_default;
930 heatcapacity[u] = heatcapacity_default;
937 surfacehumidity[u] = surface_humidity_default;
941 eps[u] = emissivity.at(u);
948 warnings.
report(std::cout,
true);
951 if (calculated_blconductance_used) {
952 auto it = find(output_prim_data.begin(), output_prim_data.end(),
"boundarylayer_conductance_out");
953 if (it == output_prim_data.end()) {
954 output_prim_data.emplace_back(
"boundarylayer_conductance_out");
959 if (message_flag && primitive_length_used) {
960 std::cout <<
"WARNING (EnergyBalanceModel::run): The length of a primitive that is not a member of a compound object "
961 <<
"was used to calculate the boundary-layer conductance. This often results in incorrect values because "
962 <<
"the length should be that of the object (e.g., leaf, stem) not the primitive. "
963 <<
"Make sure this is what you intended." << std::endl;
970 static bool openmp_warning_issued =
false;
971 if (message_flag && !openmp_warning_issued) {
972 std::cout <<
"WARNING (EnergyBalanceModel): OpenMP not available. Using serial CPU implementation. "
973 <<
"Performance will be significantly slower. Consider installing OpenMP for parallel execution." << std::endl;
974 openmp_warning_issued =
true;
979 std::vector<float> T(Nprimitives);
983 int num_threads = omp_get_max_threads();
984 std::vector<int> thread_convergence_failures(num_threads, 0);
986 int convergence_failures = 0;
991#pragma omp parallel for schedule(dynamic, 64)
993 for (
int p = 0; p < (int) Nprimitives; p++) {
996 const float err_max = 0.0001f;
997 const uint max_iter = 100;
1000 float T_old_old = To[p];
1001 float T_old = T_old_old;
1005 float resid_old = evaluateEnergyBalance_CPU(T_old,
R[p], Qother[p], eps[p], Ta[p], ea[p], pressure[p], gH[p], gS[p], Nsides[p], stomatal_sidedness[p], heatcapacity[p], surfacehumidity[p], dt, To[p]);
1006 float resid_old_old = evaluateEnergyBalance_CPU(T_old_old,
R[p], Qother[p], eps[p], Ta[p], ea[p], pressure[p], gH[p], gS[p], Nsides[p], stomatal_sidedness[p], heatcapacity[p], surfacehumidity[p], dt, To[p]);
1013 while (err > err_max && iter < max_iter) {
1015 if (resid_old == resid_old_old) {
1020 T_new = fabs((T_old_old * resid_old - T_old * resid_old_old) / (resid_old - resid_old_old));
1022 resid = evaluateEnergyBalance_CPU(T_new,
R[p], Qother[p], eps[p], Ta[p], ea[p], pressure[p], gH[p], gS[p], Nsides[p], stomatal_sidedness[p], heatcapacity[p], surfacehumidity[p], dt, To[p]);
1024 resid_old_old = resid_old;
1027 err = fabs(T_old - T_old_old) / fabs(T_old_old);
1036 if (err > err_max) {
1038 int thread_id = omp_get_thread_num();
1039 thread_convergence_failures[thread_id]++;
1041 convergence_failures++;
1050 int total_failures = 0;
1051 for (
int i = 0; i < num_threads; i++) {
1052 total_failures += thread_convergence_failures[i];
1054 if (total_failures > 0 && message_flag) {
1055 std::cout <<
"WARNING (EnergyBalanceModel::solveEnergyBalance): Energy balance did not converge for " << total_failures <<
" primitives." << std::endl;
1058 if (convergence_failures > 0 && message_flag) {
1059 std::cout <<
"WARNING (EnergyBalanceModel::solveEnergyBalance): Energy balance did not converge for " << convergence_failures <<
" primitives." << std::endl;
1065 for (
uint u = 0; u < Nprimitives; u++) {
1066 size_t UUID = UUIDs.at(u);
1069 T[u] = temperature_default;
1074 float QH =
cp_air_mol * gH[u] * (T[u] - Ta[u]);
1077 float es = esat_Pa(T[u]);
1078 float gM = 1.08f * gH[u] * gS[u] * (stomatal_sidedness[u] / (1.08f * gH[u] + gS[u] * stomatal_sidedness[u]) + (1.f - stomatal_sidedness[u]) / (1.08f * gH[u] + gS[u] * (1.f - stomatal_sidedness[u])));
1079 if (gH[u] == 0 && gS[u] == 0) {
1082 float QL =
lambda_mol * gM * (es - ea[u]) / pressure[u];
1085 for (
const auto &data_label: output_prim_data) {
1086 if (data_label ==
"boundarylayer_conductance_out") {
1088 }
else if (data_label ==
"vapor_pressure_deficit") {
1089 float vpd = (es - ea[u]) / pressure[u];
1091 }
else if (data_label ==
"net_radiation_flux") {
1092 float Rnet =
R[u] - float(Nsides[u]) * eps[u] * 5.67e-8F * std::pow(T[u], 4);
1094 }
else if (data_label ==
"storage_flux") {
1095 float storage = 0.f;
1097 storage = heatcapacity[u] * (T[u] - To[u]) / dt;