16#ifdef HELIOS_CUDA_AVAILABLE
18#include <cuda_runtime.h>
23#define CUDA_CHECK_ERROR(ans) \
25 gpuAssert((ans), __FILE__, __LINE__); \
27inline void gpuAssert(cudaError_t code,
const char *file,
int line,
bool abort =
true) {
28 if (code != cudaSuccess) {
29 fprintf(stderr,
"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
35__device__
float evaluateEnergyBalance(
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,
float Tprev) {
38 float Rout = float(Nsides) * eps * 5.67e-8F * T * T * T * T;
44 float es = 611.0f * expf(17.502f * (T - 273.f) / (T - 273.f + 240.97f));
45 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)));
46 if (gH == 0 && gS == 0) {
50 float QL = gM *
lambda_mol * (es - ea * surfacehumidity) / pressure;
55 storage = heatcapacity * (T - Tprev) / dt;
59 return R - Rout - QH - QL - Qother - storage;
62__global__
void solveEnergyBalance(
uint Nprimitives,
float *To,
float *
R,
float *Qother,
float *eps,
float *Ta,
float *ea,
float *pressure,
float *gH,
float *gS,
uint *Nsides,
float *stomatal_sidedness,
float *TL,
float *heatcapacity,
63 float *surfacehumidity,
float dt) {
65 uint p = blockIdx.x * blockDim.x + threadIdx.x;
67 if (p >= Nprimitives) {
73 float err_max = 0.0001;
76 float T_old_old = To[p];
78 float T_old = T_old_old;
81 float resid_old = evaluateEnergyBalance(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]);
82 float resid_old_old = evaluateEnergyBalance(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]);
87 while (err > err_max && iter < max_iter) {
89 if (resid_old == resid_old_old) {
94 T = fabs((T_old_old * resid_old - T_old * resid_old_old) / (resid_old - resid_old_old));
96 resid = evaluateEnergyBalance(T,
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]);
98 resid_old_old = resid_old;
103 err = fabs(T_old - T_old_old) / fabs(T_old_old);
112 printf(
"WARNING (EnergyBalanceModel::solveEnergyBalance): Energy balance did not converge.\n");
118void EnergyBalanceModel::evaluateSurfaceEnergyBalance_GPU(
const std::vector<uint> &UUIDs,
float dt) {
128 if (radiation_bands.empty()) {
133 const uint Nprimitives = UUIDs.size();
135 std::vector<float> Rn;
136 Rn.resize(Nprimitives, 0);
138 std::vector<float> emissivity;
139 emissivity.resize(Nprimitives);
140 for (
size_t u = 0; u < Nprimitives; u++) {
141 emissivity.at(u) = 1.f;
144 for (
int b = 0; b < radiation_bands.size(); b++) {
145 for (
size_t u = 0; u < Nprimitives; u++) {
146 size_t p = UUIDs.at(u);
149 sprintf(str,
"radiation_flux_%s", radiation_bands.at(b).c_str());
151 helios::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?");
153 helios::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'");
159 sprintf(str,
"emissivity_%s", radiation_bands.at(b).c_str());
163 warnings.
addWarning(
"missing_emissivity",
"Primitive data 'emissivity_" + std::string(radiation_bands.at(b)) +
"' not set, using default (1.0)");
172 float *To = (
float *) malloc(Nprimitives *
sizeof(
float));
174 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_To, Nprimitives *
sizeof(
float)));
176 float *
R = (
float *) malloc(Nprimitives *
sizeof(
float));
178 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_R, Nprimitives *
sizeof(
float)));
180 float *Qother = (
float *) malloc(Nprimitives *
sizeof(
float));
182 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_Qother, Nprimitives *
sizeof(
float)));
184 float *eps = (
float *) malloc(Nprimitives *
sizeof(
float));
186 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_eps, Nprimitives *
sizeof(
float)));
188 float *Ta = (
float *) malloc(Nprimitives *
sizeof(
float));
190 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_Ta, Nprimitives *
sizeof(
float)));
192 float *ea = (
float *) malloc(Nprimitives *
sizeof(
float));
194 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_ea, Nprimitives *
sizeof(
float)));
196 float *pressure = (
float *) malloc(Nprimitives *
sizeof(
float));
198 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_pressure, Nprimitives *
sizeof(
float)));
200 float *gH = (
float *) malloc(Nprimitives *
sizeof(
float));
202 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_gH, Nprimitives *
sizeof(
float)));
204 float *gS = (
float *) malloc(Nprimitives *
sizeof(
float));
206 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_gS, Nprimitives *
sizeof(
float)));
208 uint *Nsides = (
uint *) malloc(Nprimitives *
sizeof(
uint));
210 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_Nsides, Nprimitives *
sizeof(
uint)));
212 float *stomatal_sidedness = (
float *) malloc(Nprimitives *
sizeof(
float));
213 float *d_stomatal_sidedness;
214 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_stomatal_sidedness, Nprimitives *
sizeof(
float)));
216 float *heatcapacity = (
float *) malloc(Nprimitives *
sizeof(
float));
217 float *d_heatcapacity;
218 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_heatcapacity, Nprimitives *
sizeof(
float)));
220 float *surfacehumidity = (
float *) malloc(Nprimitives *
sizeof(
float));
221 float *d_surfacehumidity;
222 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_surfacehumidity, Nprimitives *
sizeof(
float)));
224 bool calculated_blconductance_used =
false;
225 bool primitive_length_used =
false;
227 for (
uint u = 0; u < Nprimitives; u++) {
228 size_t p = UUIDs.at(u);
234 To[u] = temperature_default;
235 warnings.
addWarning(
"missing_surface_temperature",
"Primitive data 'temperature' not set, using default (" + std::to_string(temperature_default) +
" K)");
245 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)");
246 Ta[u] = air_temperature_default;
249 Ta[u] = air_temperature_default;
250 warnings.
addWarning(
"missing_air_temperature",
"Primitive data 'air_temperature' not set, using default (" + std::to_string(air_temperature_default) +
" K)");
258 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) +
")");
259 hr = air_humidity_default;
260 }
else if (hr < 0.f) {
261 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) +
")");
262 hr = air_humidity_default;
265 hr = air_humidity_default;
266 warnings.
addWarning(
"missing_air_humidity",
"Primitive data 'air_humidity' not set, using default (" + std::to_string(air_humidity_default) +
")");
270 float esat = esat_Pa(Ta[u]);
276 if (pressure[u] < 10000.f) {
278 std::cout <<
"WARNING (EnergyBalanceModel::run): Value of " << pressure[u] <<
" given in 'air_pressure' primitive data is very small. Values should be given in units of Pascals. Assuming default value of " << pressure_default
281 pressure[u] = pressure_default;
284 pressure[u] = pressure_default;
289 Nsides[u] = (twosided_flag == 0) ? 1 : 2;
292 stomatal_sidedness[u] = 0.f;
300 stomatal_sidedness[u] = 0.f;
301 }
else if (flag == 2) {
302 stomatal_sidedness[u] = 0.5f;
316 U = wind_speed_default;
317 warnings.
addWarning(
"missing_wind_speed",
"Primitive data 'wind_speed' not set, using default (" + std::to_string(wind_speed_default) +
" m/s)");
326 primitive_length_used =
true;
333 primitive_length_used =
true;
336 gH[u] = 0.135f * sqrt(U / L) * float(Nsides[u]);
338 calculated_blconductance_used =
true;
352 Qother[u] = Qother_default;
359 heatcapacity[u] = heatcapacity_default;
366 surfacehumidity[u] = surface_humidity_default;
370 eps[u] = emissivity.at(u);
377 warnings.
report(std::cout,
true);
380 if (calculated_blconductance_used) {
381 auto it = find(output_prim_data.begin(), output_prim_data.end(),
"boundarylayer_conductance_out");
382 if (it == output_prim_data.end()) {
383 output_prim_data.emplace_back(
"boundarylayer_conductance_out");
388 if (message_flag && primitive_length_used) {
390 <<
"WARNING (EnergyBalanceModel::run): The length of a primitive that is not a member of a compound object was used to calculate the boundary-layer conductance. This often results in incorrect values because the length should be that of the object (e.g., leaf, stem) not the primitive. Make sure this is what you intended."
395 CUDA_CHECK_ERROR(cudaMemcpy(d_To, To, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
396 CUDA_CHECK_ERROR(cudaMemcpy(d_R,
R, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
397 CUDA_CHECK_ERROR(cudaMemcpy(d_Qother, Qother, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
398 CUDA_CHECK_ERROR(cudaMemcpy(d_eps, eps, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
399 CUDA_CHECK_ERROR(cudaMemcpy(d_Ta, Ta, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
400 CUDA_CHECK_ERROR(cudaMemcpy(d_ea, ea, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
401 CUDA_CHECK_ERROR(cudaMemcpy(d_pressure, pressure, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
402 CUDA_CHECK_ERROR(cudaMemcpy(d_gH, gH, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
403 CUDA_CHECK_ERROR(cudaMemcpy(d_gS, gS, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
404 CUDA_CHECK_ERROR(cudaMemcpy(d_Nsides, Nsides, Nprimitives *
sizeof(
uint), cudaMemcpyHostToDevice));
405 CUDA_CHECK_ERROR(cudaMemcpy(d_stomatal_sidedness, stomatal_sidedness, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
406 CUDA_CHECK_ERROR(cudaMemcpy(d_heatcapacity, heatcapacity, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
407 CUDA_CHECK_ERROR(cudaMemcpy(d_surfacehumidity, surfacehumidity, Nprimitives *
sizeof(
float), cudaMemcpyHostToDevice));
409 float *T = (
float *) malloc(Nprimitives *
sizeof(
float));
411 CUDA_CHECK_ERROR(cudaMalloc((
void **) &d_T, Nprimitives *
sizeof(
float)));
414 dim3 dimBlock(64, 1);
415 dim3 dimGrid(ceil(Nprimitives / 64.f));
417 solveEnergyBalance<<<dimGrid, dimBlock>>>(Nprimitives, d_To, d_R, d_Qother, d_eps, d_Ta, d_ea, d_pressure, d_gH, d_gS, d_Nsides, d_stomatal_sidedness, d_T, d_heatcapacity, d_surfacehumidity, dt);
419 CUDA_CHECK_ERROR(cudaPeekAtLastError());
420 CUDA_CHECK_ERROR(cudaDeviceSynchronize());
422 CUDA_CHECK_ERROR(cudaMemcpy(T, d_T, Nprimitives *
sizeof(
float), cudaMemcpyDeviceToHost));
424 for (
uint u = 0; u < Nprimitives; u++) {
425 size_t UUID = UUIDs.at(u);
428 T[u] = temperature_default;
433 float QH =
cp_air_mol * gH[u] * (T[u] - Ta[u]);
436 float es = esat_Pa(T[u]);
437 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])));
438 if (gH[u] == 0 && gS[u] == 0) {
441 float QL =
lambda_mol * gM * (es - ea[u]) / pressure[u];
444 for (
const auto &data_label: output_prim_data) {
445 if (data_label ==
"boundarylayer_conductance_out") {
447 }
else if (data_label ==
"vapor_pressure_deficit") {
448 float vpd = (es - ea[u]) / pressure[u];
450 }
else if (data_label ==
"net_radiation_flux") {
451 float Rnet =
R[u] - float(Nsides[u]) * eps[u] * 5.67e-8F * std::pow(T[u], 4);
453 }
else if (data_label ==
"storage_flux") {
456 storage = heatcapacity[u] * (T[u] - To[u]) / dt;
473 free(stomatal_sidedness);
475 free(surfacehumidity);
479 CUDA_CHECK_ERROR(cudaFree(d_To));
480 CUDA_CHECK_ERROR(cudaFree(d_R));
481 CUDA_CHECK_ERROR(cudaFree(d_Qother));
482 CUDA_CHECK_ERROR(cudaFree(d_eps));
483 CUDA_CHECK_ERROR(cudaFree(d_Ta));
484 CUDA_CHECK_ERROR(cudaFree(d_ea));
485 CUDA_CHECK_ERROR(cudaFree(d_pressure));
486 CUDA_CHECK_ERROR(cudaFree(d_gH));
487 CUDA_CHECK_ERROR(cudaFree(d_gS));
488 CUDA_CHECK_ERROR(cudaFree(d_Nsides));
489 CUDA_CHECK_ERROR(cudaFree(d_stomatal_sidedness));
490 CUDA_CHECK_ERROR(cudaFree(d_heatcapacity));
491 CUDA_CHECK_ERROR(cudaFree(d_surfacehumidity));
492 CUDA_CHECK_ERROR(cudaFree(d_T));