1.3.64
 
Loading...
Searching...
No Matches
EnergyBalanceModel.cu
Go to the documentation of this file.
1
16#ifdef HELIOS_CUDA_AVAILABLE
17
18#include <cuda_runtime.h>
19#include "EnergyBalanceModel.h"
20
21using namespace std;
22
23#define CUDA_CHECK_ERROR(ans) \
24 { \
25 gpuAssert((ans), __FILE__, __LINE__); \
26 }
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);
30 if (abort)
31 exit(code);
32 }
33}
34
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) {
36
37 // Outgoing emission flux
38 float Rout = float(Nsides) * eps * 5.67e-8F * T * T * T * T;
39
40 // Sensible heat flux
41 float QH = cp_air_mol * gH * (T - Ta); // (see Campbell and Norman Eq. 6.8)
42
43 // Latent heat flux
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) { // if somehow both go to zero, can get NaN
47 gM = 0;
48 }
49
50 float QL = gM * lambda_mol * (es - ea * surfacehumidity) / pressure;
51
52 // Storage heat flux
53 float storage = 0.f;
54 if (dt > 0) {
55 storage = heatcapacity * (T - Tprev) / dt;
56 }
57
58 // Residual
59 return R - Rout - QH - QL - Qother - storage;
60}
61
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) {
64
65 uint p = blockIdx.x * blockDim.x + threadIdx.x;
66
67 if (p >= Nprimitives) {
68 return;
69 }
70
71 float T;
72
73 float err_max = 0.0001;
74 uint max_iter = 100;
75
76 float T_old_old = To[p];
77
78 float T_old = T_old_old;
79 T_old_old = 400.f;
80
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]);
83
84 float resid = 100;
85 float err = resid;
86 uint iter = 0;
87 while (err > err_max && iter < max_iter) {
88
89 if (resid_old == resid_old_old) { // this condition will cause NaN
90 err = 0;
91 break;
92 }
93
94 T = fabs((T_old_old * resid_old - T_old * resid_old_old) / (resid_old - resid_old_old));
95
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]);
97
98 resid_old_old = resid_old;
99 resid_old = resid;
100
101 // err = fabs(resid);
102 // err = fabs(resid_old-resid_old_old)/fabs(resid_old_old);
103 err = fabs(T_old - T_old_old) / fabs(T_old_old);
104
105 T_old_old = T_old;
106 T_old = T;
107
108 iter++;
109 }
110
111 if (err > err_max) {
112 printf("WARNING (EnergyBalanceModel::solveEnergyBalance): Energy balance did not converge.\n");
113 }
114
115 TL[p] = T;
116}
117
118void EnergyBalanceModel::evaluateSurfaceEnergyBalance_GPU(const std::vector<uint> &UUIDs, float dt) {
119
120 // Create warning aggregator for default value warnings
122 warnings.setEnabled(message_flag);
123
124 //---- Sum up to get total absorbed radiation across all bands ----//
125
126 // Look through all flux primitive data in the context and sum them up in vector Rn. Each element of Rn corresponds to a primitive.
127
128 if (radiation_bands.empty()) {
129 helios::helios_runtime_error("ERROR (EnergyBalanceModel::run): No radiation bands were found.");
130 }
131
132
133 const uint Nprimitives = UUIDs.size();
134
135 std::vector<float> Rn;
136 Rn.resize(Nprimitives, 0);
137
138 std::vector<float> emissivity;
139 emissivity.resize(Nprimitives);
140 for (size_t u = 0; u < Nprimitives; u++) {
141 emissivity.at(u) = 1.f;
142 }
143
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);
147
148 char str[50];
149 sprintf(str, "radiation_flux_%s", radiation_bands.at(b).c_str());
150 if (!context->doesPrimitiveDataExist(p, 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?");
152 } else if (context->getPrimitiveDataType(str) != helios::HELIOS_TYPE_FLOAT) {
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'");
154 }
155 float R;
156 context->getPrimitiveData(p, str, R);
157 Rn.at(u) += R;
158
159 sprintf(str, "emissivity_%s", radiation_bands.at(b).c_str());
160 if (context->doesPrimitiveDataExist(p, str) && context->getPrimitiveDataType(str) == helios::HELIOS_TYPE_FLOAT) {
161 context->getPrimitiveData(p, str, emissivity.at(u));
162 } else {
163 warnings.addWarning("missing_emissivity", "Primitive data 'emissivity_" + std::string(radiation_bands.at(b)) + "' not set, using default (1.0)");
164 }
165 }
166 }
167
168 //---- Set up temperature solution ----//
169
170 // To,R,Qother,eps,U,L,Ta,ea,pressure,gS,Nsides
171
172 float *To = (float *) malloc(Nprimitives * sizeof(float));
173 float *d_To;
174 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_To, Nprimitives * sizeof(float)));
175
176 float *R = (float *) malloc(Nprimitives * sizeof(float));
177 float *d_R;
178 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_R, Nprimitives * sizeof(float)));
179
180 float *Qother = (float *) malloc(Nprimitives * sizeof(float));
181 float *d_Qother;
182 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_Qother, Nprimitives * sizeof(float)));
183
184 float *eps = (float *) malloc(Nprimitives * sizeof(float));
185 float *d_eps;
186 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_eps, Nprimitives * sizeof(float)));
187
188 float *Ta = (float *) malloc(Nprimitives * sizeof(float));
189 float *d_Ta;
190 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_Ta, Nprimitives * sizeof(float)));
191
192 float *ea = (float *) malloc(Nprimitives * sizeof(float));
193 float *d_ea;
194 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_ea, Nprimitives * sizeof(float)));
195
196 float *pressure = (float *) malloc(Nprimitives * sizeof(float));
197 float *d_pressure;
198 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_pressure, Nprimitives * sizeof(float)));
199
200 float *gH = (float *) malloc(Nprimitives * sizeof(float));
201 float *d_gH;
202 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_gH, Nprimitives * sizeof(float)));
203
204 float *gS = (float *) malloc(Nprimitives * sizeof(float));
205 float *d_gS;
206 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_gS, Nprimitives * sizeof(float)));
207
208 uint *Nsides = (uint *) malloc(Nprimitives * sizeof(uint));
209 uint *d_Nsides;
210 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_Nsides, Nprimitives * sizeof(uint)));
211
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)));
215
216 float *heatcapacity = (float *) malloc(Nprimitives * sizeof(float));
217 float *d_heatcapacity;
218 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_heatcapacity, Nprimitives * sizeof(float)));
219
220 float *surfacehumidity = (float *) malloc(Nprimitives * sizeof(float));
221 float *d_surfacehumidity;
222 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_surfacehumidity, Nprimitives * sizeof(float)));
223
224 bool calculated_blconductance_used = false;
225 bool primitive_length_used = false;
226
227 for (uint u = 0; u < Nprimitives; u++) {
228 size_t p = UUIDs.at(u);
229
230 // Initial guess for surface temperature
231 if (context->doesPrimitiveDataExist(p, "temperature") && context->getPrimitiveDataType("temperature") == helios::HELIOS_TYPE_FLOAT) {
232 context->getPrimitiveData(p, "temperature", To[u]);
233 } else {
234 To[u] = temperature_default;
235 warnings.addWarning("missing_surface_temperature", "Primitive data 'temperature' not set, using default (" + std::to_string(temperature_default) + " K)");
236 }
237 if (To[u] == 0) { // can't have To equal to 0
238 To[u] = 300;
239 }
240
241 // Air temperature
242 if (context->doesPrimitiveDataExist(p, "air_temperature") && context->getPrimitiveDataType("air_temperature") == helios::HELIOS_TYPE_FLOAT) {
243 context->getPrimitiveData(p, "air_temperature", Ta[u]);
244 if (Ta[u] < 250.f) {
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;
247 }
248 } else {
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)");
251 }
252
253 // Air relative humidity
254 float hr;
255 if (context->doesPrimitiveDataExist(p, "air_humidity") && context->getPrimitiveDataType("air_humidity") == helios::HELIOS_TYPE_FLOAT) {
256 context->getPrimitiveData(p, "air_humidity", hr);
257 if (hr > 1.f) {
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;
263 }
264 } else {
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) + ")");
267 }
268
269 // Air vapor pressure
270 float esat = esat_Pa(Ta[u]);
271 ea[u] = hr * esat; // Definition of vapor pressure (see Campbell and Norman pp. 42 Eq. 3.11)
272
273 // Air pressure
274 if (context->doesPrimitiveDataExist(p, "air_pressure") && context->getPrimitiveDataType("air_pressure") == helios::HELIOS_TYPE_FLOAT) {
275 context->getPrimitiveData(p, "air_pressure", pressure[u]);
276 if (pressure[u] < 10000.f) {
277 if (message_flag) {
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
279 << std::endl;
280 }
281 pressure[u] = pressure_default;
282 }
283 } else {
284 pressure[u] = pressure_default;
285 }
286
287 // Number of sides emitting radiation - check material first, then primitive data
288 uint twosided_flag = context->getPrimitiveTwosidedFlag(p, 1);
289 Nsides[u] = (twosided_flag == 0) ? 1 : 2;
290
291 // Number of evaporating/transpiring faces
292 stomatal_sidedness[u] = 0.f; // if Nsides=1, force this to be 0 (all stomata on upper surface)
293 if (Nsides[u] == 2 && context->doesPrimitiveDataExist(p, "stomatal_sidedness") && context->getPrimitiveDataType("stomatal_sidedness") == helios::HELIOS_TYPE_FLOAT) {
294 context->getPrimitiveData(p, "stomatal_sidedness", stomatal_sidedness[u]);
295 // this is for backward compatability prior to v1.3.17
296 } else if (Nsides[u] == 2 && context->doesPrimitiveDataExist(p, "evaporating_faces") && context->getPrimitiveDataType("evaporating_faces") == helios::HELIOS_TYPE_UINT) {
297 uint flag;
298 context->getPrimitiveData(p, "evaporating_faces", flag);
299 if (flag == 1) { // stomata on one side
300 stomatal_sidedness[u] = 0.f;
301 } else if (flag == 2) {
302 stomatal_sidedness[u] = 0.5f;
303 }
304 }
305
306 // Boundary-layer conductance to heat
307 if (context->doesPrimitiveDataExist(p, "boundarylayer_conductance") && context->getPrimitiveDataType("boundarylayer_conductance") == helios::HELIOS_TYPE_FLOAT) {
308 context->getPrimitiveData(p, "boundarylayer_conductance", gH[u]);
309 } else {
310
311 // Wind speed
312 float U;
313 if (context->doesPrimitiveDataExist(p, "wind_speed") && context->getPrimitiveDataType("wind_speed") == helios::HELIOS_TYPE_FLOAT) {
314 context->getPrimitiveData(p, "wind_speed", U);
315 } else {
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)");
318 }
319
320 // Characteristic size of primitive
321 float L;
322 if (context->doesPrimitiveDataExist(p, "object_length") && context->getPrimitiveDataType("object_length") == helios::HELIOS_TYPE_FLOAT) {
323 context->getPrimitiveData(p, "object_length", L);
324 if (L == 0) {
325 L = sqrt(context->getPrimitiveArea(p));
326 primitive_length_used = true;
327 }
328 } else if (context->getPrimitiveParentObjectID(p) > 0) {
329 uint objID = context->getPrimitiveParentObjectID(p);
330 L = sqrt(context->getObjectArea(objID));
331 } else {
332 L = sqrt(context->getPrimitiveArea(p));
333 primitive_length_used = true;
334 }
335
336 gH[u] = 0.135f * sqrt(U / L) * float(Nsides[u]);
337
338 calculated_blconductance_used = true;
339 }
340
341 // Moisture conductance
342 if (context->doesPrimitiveDataExist(p, "moisture_conductance") && context->getPrimitiveDataType("moisture_conductance") == helios::HELIOS_TYPE_FLOAT) {
343 context->getPrimitiveData(p, "moisture_conductance", gS[u]);
344 } else {
345 gS[u] = gS_default;
346 }
347
348 // Other fluxes
349 if (context->doesPrimitiveDataExist(p, "other_surface_flux") && context->getPrimitiveDataType("other_surface_flux") == helios::HELIOS_TYPE_FLOAT) {
350 context->getPrimitiveData(p, "other_surface_flux", Qother[u]);
351 } else {
352 Qother[u] = Qother_default;
353 }
354
355 // Object heat capacity
356 if (context->doesPrimitiveDataExist(p, "heat_capacity") && context->getPrimitiveDataType("heat_capacity") == helios::HELIOS_TYPE_FLOAT) {
357 context->getPrimitiveData(p, "heat_capacity", heatcapacity[u]);
358 } else {
359 heatcapacity[u] = heatcapacity_default;
360 }
361
362 // Surface humidity
363 if (context->doesPrimitiveDataExist(p, "surface_humidity") && context->getPrimitiveDataType("surface_humidity") == helios::HELIOS_TYPE_FLOAT) {
364 context->getPrimitiveData(p, "surface_humidity", surfacehumidity[u]);
365 } else {
366 surfacehumidity[u] = surface_humidity_default;
367 }
368
369 // Emissivity
370 eps[u] = emissivity.at(u);
371
372 // Net absorbed radiation
373 R[u] = Rn.at(u);
374 }
375
376 // Report all accumulated warnings
377 warnings.report(std::cout, true); // true = compact mode
378
379 // if we used the calculated boundary-layer conductance, enable output primitive data "boundarylayer_conductance_out" so that it can be used by other plug-ins
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");
384 }
385 }
386
387 // if the length of a primitive that is not a member of an object was used, issue a warning
388 if (message_flag && primitive_length_used) {
389 std::cout
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."
391 << std::endl;
392 }
393
394 // To,R,Qother,eps,U,L,Ta,ea,pressure,gS,Nsides
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));
408
409 float *T = (float *) malloc(Nprimitives * sizeof(float));
410 float *d_T;
411 CUDA_CHECK_ERROR(cudaMalloc((void **) &d_T, Nprimitives * sizeof(float)));
412
413 // launch kernel
414 dim3 dimBlock(64, 1);
415 dim3 dimGrid(ceil(Nprimitives / 64.f));
416
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);
418
419 CUDA_CHECK_ERROR(cudaPeekAtLastError());
420 CUDA_CHECK_ERROR(cudaDeviceSynchronize());
421
422 CUDA_CHECK_ERROR(cudaMemcpy(T, d_T, Nprimitives * sizeof(float), cudaMemcpyDeviceToHost));
423
424 for (uint u = 0; u < Nprimitives; u++) {
425 size_t UUID = UUIDs.at(u);
426
427 if (T[u] != T[u]) {
428 T[u] = temperature_default;
429 }
430
431 context->setPrimitiveData(UUID, "temperature", T[u]);
432
433 float QH = cp_air_mol * gH[u] * (T[u] - Ta[u]);
434 context->setPrimitiveData(UUID, "sensible_flux", QH);
435
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) { // if somehow both go to zero, can get NaN
439 gM = 0;
440 }
441 float QL = lambda_mol * gM * (es - ea[u]) / pressure[u];
442 context->setPrimitiveData(UUID, "latent_flux", QL);
443
444 for (const auto &data_label: output_prim_data) {
445 if (data_label == "boundarylayer_conductance_out") {
446 context->setPrimitiveData(UUID, "boundarylayer_conductance_out", gH[u]);
447 } else if (data_label == "vapor_pressure_deficit") {
448 float vpd = (es - ea[u]) / pressure[u];
449 context->setPrimitiveData(UUID, "vapor_pressure_deficit", vpd);
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);
452 context->setPrimitiveData(UUID, "net_radiation_flux", Rnet);
453 } else if (data_label == "storage_flux") {
454 float storage = 0.f;
455 if (dt > 0) {
456 storage = heatcapacity[u] * (T[u] - To[u]) / dt;
457 }
458 context->setPrimitiveData(UUID, "storage_flux", storage);
459 }
460 }
461 }
462
463 free(To);
464 free(R);
465 free(Qother);
466 free(eps);
467 free(Ta);
468 free(ea);
469 free(pressure);
470 free(gH);
471 free(gS);
472 free(Nsides);
473 free(stomatal_sidedness);
474 free(heatcapacity);
475 free(surfacehumidity);
476 free(T);
477
478
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));
493}
494
495#endif // HELIOS_CUDA_AVAILABLE