92 std::cerr <<
"ERROR (EnergyBalanceModel::evaluateAirEnergyBalance): dt_sec must be greater than zero to run the air energy balance. Skipping..." << std::endl;
96 float air_temperature_reference = air_temperature_default;
98 context->
getGlobalData(
"air_temperature_reference", air_temperature_reference);
101 float air_humidity_reference = air_humidity_default;
103 context->
getGlobalData(
"air_humidity_reference", air_humidity_reference);
106 float wind_speed_reference = wind_speed_default;
108 context->
getGlobalData(
"wind_speed_reference", wind_speed_reference);
117 Patm = pressure_default;
120 float air_temperature_average;
122 context->
getGlobalData(
"air_temperature_average", air_temperature_average);
124 air_temperature_average = air_temperature_reference;
127 float air_moisture_average;
129 context->
getGlobalData(
"air_moisture_average", air_moisture_average);
130 std::cerr <<
"Reading air_moisture_average from global data: " << air_moisture_average << std::endl;
132 air_moisture_average = air_humidity_reference * esat_Pa(air_temperature_reference) / Patm;
135 float air_temperature_ABL = 0;
137 context->
getGlobalData(
"air_temperature_ABL", air_temperature_ABL);
140 float air_moisture_ABL;
142 context->
getGlobalData(
"air_moisture_ABL", air_moisture_ABL);
143 std::cerr <<
"Reading air_moisture_ABL from global data: " << air_moisture_ABL << std::endl;
145 air_moisture_ABL = air_humidity_reference * esat_Pa(air_temperature_reference) / Patm;
149 if (canopy_dimensions ==
make_vec3(0, 0, 0)) {
150 vec2 xbounds, ybounds, zbounds;
152 canopy_dimensions =
make_vec3(xbounds.
y - xbounds.
x, ybounds.
y - ybounds.
x, zbounds.
y - zbounds.
x);
154 assert(canopy_dimensions.
x > 0 && canopy_dimensions.
y > 0 && canopy_dimensions.
z > 0);
155 if (canopy_height_m == 0) {
156 canopy_height_m = canopy_dimensions.
z;
158 if (reference_height_m == 0) {
159 reference_height_m = canopy_height_m;
162 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;
164 std::cout <<
"Canopy dimensions: " << canopy_dimensions.
x <<
" x " << canopy_dimensions.
y <<
" x " << canopy_height_m << std::endl;
166 float displacement_height = 0.67f * canopy_height_m;
167 float zo_m = 0.1f * canopy_height_m;
168 float zo_h = 0.01f * canopy_height_m;
170 float abl_height_m = 1000.f;
173 float air_temperature_old = 0;
174 float air_moisture_old = 0;
175 float air_temperature_ABL_old = 0;
176 float air_moisture_ABL_old = 0;
177 while (time < time_advance_sec) {
178 float dt_actual = dt_sec;
179 if (time + dt_actual > time_advance_sec) {
180 dt_actual = time_advance_sec - time;
183 std::cout <<
"Air moisture average: " << air_moisture_average << std::endl;
189 float sensible_source_flux_W_m3;
191 sensible_source_flux_W_m3 /= canopy_dimensions.
x * canopy_dimensions.
y * canopy_height_m;
194 float moisture_source_flux_W_m3;
196 moisture_source_flux_W_m3 /= canopy_dimensions.
x * canopy_dimensions.
y * canopy_height_m;
197 float moisture_source_flux_mol_s_m3 = moisture_source_flux_W_m3 /
lambda_mol;
199 float rho_air_mol_m3 = Patm / (
R * air_temperature_average);
205 float H_s = sensible_source_flux_W_m3 * canopy_height_m;
206 float E_s = moisture_source_flux_mol_s_m3 * canopy_height_m;
209 float u_star_neutral =
von_Karman_constant * wind_speed_reference / std::log((reference_height_m - displacement_height) / zo_m);
212 float rho_air_kg_m3 = rho_air_mol_m3 * 0.02896f;
215 if (std::fabs(H_s) > 1e-6f) {
216 L = -rho_air_kg_m3 * cp_air_kg * std::pow(u_star_neutral, 3) * air_temperature_reference / (
von_Karman_constant * 9.81f * H_s);
220 auto psi_unstable_u = [&](
float zeta) {
221 float x = std::pow(1.f - 16.f * zeta, 0.25f);
222 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;
224 auto psi_unstable_h = [&](
float zeta) {
return 2.f * std::log((1.f + std::sqrt(1.f - 16.f * zeta)) / 2.f); };
225 auto psi_stable = [&](
float zeta) {
return -5.f * zeta; };
227 float zeta_r = (reference_height_m - displacement_height) / L;
228 float psi_m_r = (zeta_r < 0.f) ? psi_unstable_u(zeta_r) : psi_stable(zeta_r);
229 float psi_h_r = (zeta_r < 0.f) ? psi_unstable_h(zeta_r) : psi_stable(zeta_r);
232 float u_star =
von_Karman_constant * wind_speed_reference / (std::log((reference_height_m - displacement_height) / zo_m) - psi_m_r);
235 float theta_star = H_s / (rho_air_mol_m3 *
cp_air_mol * u_star);
236 float q_star = E_s / (rho_air_mol_m3 * u_star);
239 float ln_m_corr = std::log((reference_height_m - displacement_height) / zo_m) - psi_m_r;
240 float ln_h_corr = std::log((reference_height_m - displacement_height) / zo_h) - psi_h_r;
243 float ga = std::pow(
von_Karman_constant, 2) * wind_speed_reference / (ln_m_corr * ln_h_corr) * rho_air_mol_m3;
246 float air_moisture_reference = esat_Pa(air_temperature_reference) / Patm * air_humidity_reference;
251 float x_sat = esat_Pa(T_int) / Patm;
254 x_int = 0.99f * x_sat;
258 float H_canopy = sensible_source_flux_W_m3 * canopy_height_m;
260 if (air_temperature_ABL == 0.f) {
262 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;
263 const float A_e = 0.02f;
264 float w_e0 = A_e * w_star0;
265 g_e = rho_air_mol_m3 * w_e0;
267 air_temperature_ABL = (ga * air_temperature_average + g_e * air_temperature_reference) / (ga + g_e);
268 air_moisture_ABL = (ga * air_moisture_average + g_e * air_moisture_reference) / (ga + g_e);
272 if (H_canopy > 0.f) {
273 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);
275 float u_star_shear =
von_Karman_constant * wind_speed_reference / std::log((reference_height_m - displacement_height) / zo_m);
276 float w_e = std::fmax(0.01f * w_star, 0.005f * u_star_shear);
277 g_e = rho_air_mol_m3 * w_e;
287 float sensible_upper_flux_W_m2 =
cp_air_mol * ga * (air_temperature_average - T_int);
292 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);
293 float denominator_temp = 1.f + dt_actual * ga / (rho_air_mol_m3 * canopy_height_m);
294 air_temperature_average = numerator_temp / denominator_temp;
300 float E_top = ga * (air_moisture_average - x_int);
301 float f = std::pow(1.f - (1.f - 0.622f) * air_moisture_average, 2) / 0.622f;
302 float numer_can_x = air_moisture_average + dt_actual * f / (rho_air_mol_m3 * canopy_height_m) * (E_s + ga * x_int);
303 float denom_can_x = 1.f + dt_actual * f * ga / (rho_air_mol_m3 * canopy_height_m);
304 air_moisture_average = numer_can_x / denom_can_x;
307 float denom_abl_T = 1.f + dt_actual * (ga + g_e) / (rho_air_mol_m3 *
cp_air_mol * abl_height_m);
308 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));
309 air_temperature_ABL = num_abl_T / denom_abl_T;
314 float E_e = g_e * (air_moisture_ABL - air_moisture_reference);
315 float numer_abl_x = air_moisture_ABL + dt_actual / (rho_air_mol_m3 * abl_height_m) * (E_top - E_e);
316 float denom_abl_x = 1.f + dt_actual * (ga + g_e) / (rho_air_mol_m3 * abl_height_m);
317 air_moisture_ABL = numer_abl_x / denom_abl_x;
320 float canopy_storage = (air_moisture_average - air_moisture_old) * rho_air_mol_m3 * canopy_height_m / dt_actual;
322 float flux_out = E_top;
323 std::cerr << std::fixed <<
"CANOPY MOISTURE BUDGET: in=" << flux_in <<
" out=" << flux_out <<
" storage=" << canopy_storage <<
" residual=" << (flux_in - flux_out - canopy_storage) << std::endl;
326 float esat = esat_Pa(air_temperature_average) / Patm;
327 if (air_moisture_average > esat) {
328 std::cerr <<
"WARNING (EnergyBalanceModel::evaluateAirEnergyBalance): Air moisture exceeds saturation. Capping to saturation value." << std::endl;
329 air_moisture_average = 0.99f * esat;
331 esat = esat_Pa(air_temperature_ABL) / Patm;
332 if (air_moisture_ABL > esat) {
333 air_moisture_ABL = 0.99f * esat;
337 if (dt_actual > 0.5f * canopy_height_m * rho_air_mol_m3 / ga) {
338 std::cerr <<
"WARNING (EnergyBalanceModel::evaluateAirEnergyBalance): Time step is too large. The air energy balance may not converge properly." << std::endl;
341 float heat_from_Htop = dt_actual / (rho_air_mol_m3 *
cp_air_mol * abl_height_m) * sensible_upper_flux_W_m2;
342 float heat_from_entrainment = dt_actual * g_e * (air_temperature_reference - air_temperature_ABL) / (rho_air_mol_m3 * abl_height_m);
344 std::cerr <<
"ABL ENERGY UPDATE:\n"
345 <<
" T_ABL_n = " << air_temperature_ABL <<
"\n"
346 <<
" T_ref = " << air_temperature_reference <<
"\n"
347 <<
" g_e = " << g_e <<
" mol/m²/s\n"
348 <<
" Heat from H_top = " << heat_from_Htop <<
" K\n"
349 <<
" Heat from H_e = " << heat_from_entrainment <<
" K\n"
350 <<
" Numerator = " << num_abl_T <<
" K\n"
351 <<
" Denominator = " << denom_abl_T <<
"\n"
352 <<
" T_ABL_new = " << (num_abl_T / denom_abl_T) <<
"\n";
358 H_s = sensible_source_flux_W_m3 * canopy_height_m;
359 float H_top =
cp_air_mol * ga * (air_temperature_average - T_int);
360 float H_e =
cp_air_mol * g_e * (air_temperature_ABL - air_temperature_reference);
362 E_s = moisture_source_flux_mol_s_m3 * canopy_height_m;
363 E_top = ga * (air_moisture_average - x_int);
364 E_e = g_e * (air_moisture_ABL - air_moisture_reference);
367 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
368 <<
" 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;
372 const float tol = 1e-5f;
373 float S_can = rho_air_mol_m3 *
cp_air_mol * canopy_height_m * (air_temperature_average - air_temperature_old) / dt_actual;
374 if (fabs(H_s - H_top - S_can) > tol)
375 std::cerr <<
"Canopy budget error: " << (H_s - H_top - S_can) <<
"\n";
377 float S_abl = rho_air_mol_m3 *
cp_air_mol * abl_height_m * (air_temperature_ABL - air_temperature_ABL_old) / dt_actual;
378 if (fabs(H_top - H_e - S_abl) > tol)
379 std::cerr <<
"ABL budget error: " << (H_top - H_e - S_abl) <<
"\n";
383 S_can = rho_air_mol_m3 * canopy_height_m * (air_moisture_average - air_moisture_old) / dt_actual;
385 const float tol_m = 1e-6f;
386 if (std::fabs(E_s - E_top - S_can) > tol_m) {
387 std::cerr <<
"CANOPY MOISTURE BUDGET ERROR: " <<
"E_s - E_up - S_can = " << E_s - E_top - S_can <<
" mol/m2/s\n";
390 S_abl = rho_air_mol_m3 * abl_height_m * (air_moisture_ABL - air_moisture_ABL_old) / dt_actual;
392 if (std::fabs(E_top - E_e - S_abl) > tol_m) {
393 std::cerr <<
"ABL MOISTURE BUDGET ERROR: " <<
"E_up - E_e - S_abl = " << E_top - E_e - S_abl <<
" mol/m2/s\n";
400 context->
setPrimitiveData(UUIDs,
"air_temperature", air_temperature_average);
401 float air_humidity = air_moisture_average * Patm / esat_Pa(air_temperature_average);
405 context->
setGlobalData(
"air_temperature_average", air_temperature_average);
406 context->
setGlobalData(
"air_humidity_average", air_humidity);
407 context->
setGlobalData(
"air_moisture_average", air_moisture_average);
409 context->
setGlobalData(
"air_temperature_ABL", air_temperature_ABL);
410 float air_humidity_ABL = air_moisture_ABL * Patm / esat_Pa(air_temperature_ABL);
411 context->
setGlobalData(
"air_humidity_ABL", air_humidity_ABL);
414 float air_humidity_interface = x_int * Patm / esat_Pa(T_int);
415 context->
setGlobalData(
"air_humidity_interface", air_humidity_interface);
417 context->
setGlobalData(
"aerodynamic_resistance", rho_air_mol_m3 / ga);
419 std::cout <<
"Computed air temperature: " << air_temperature_average <<
" " << air_temperature_old << std::endl;
420 std::cout <<
"Computed air humidity: " << air_humidity << std::endl;
421 std::cout <<
"Computed air moisture: " << air_moisture_average <<
" " << air_moisture_old << std::endl;
423 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) {
425 std::cout <<
"Converged" << std::endl;
428 air_temperature_old = air_temperature_average;
429 air_moisture_old = air_moisture_average;
430 air_temperature_ABL_old = air_temperature_ABL;
431 air_moisture_ABL_old = air_moisture_ABL;