/****************************************************************************** * @brief Interpolate a set of N points by fitting a polynomial of degree N-1 *****************************************************************************/ void polint(double xa[], double ya[], int n, double x, double *y, double *dy) { int i, m, ns; double den, dif, dift, ho, hp, w; double *c = NULL; double *d = NULL; ns = 1; dif = fabs(x - xa[1]); c = (double *)malloc((size_t) ((n + 1) * sizeof(double))); check_alloc_status(c, "Memory allocation error."); d = (double *)malloc((size_t) ((n + 1) * sizeof(double))); check_alloc_status(d, "Memory allocation error."); for (i = 1; i <= n; i++) { if ((dift = fabs(x - xa[i])) < dif) { ns = i; dif = dift; } c[i] = ya[i]; d[i] = ya[i]; } *y = ya[ns--]; for (m = 1; m < n; m++) { for (i = 1; i <= n - m; i++) { ho = xa[i] - x; hp = xa[i + m] - x; w = c[i + 1] - d[i]; if ((den = ho - hp) == 0.0) { log_err("interpolation error"); } den = w / den; d[i] = hp * den; c[i] = ho * den; } *y += (*dy = (2 * ns < (n - m) ? c[ns + 1] : d[ns--])); } free(d); free(c); }
/****************************************************************************** * @brief Get netCDF dimension. *****************************************************************************/ void get_nc_var_attr(char *nc_name, char *var_name, char *attr_name, char **attr) { int nc_id; int var_id; int status; size_t attr_len; // open the netcdf file status = nc_open(nc_name, NC_NOWRITE, &nc_id); check_nc_status(status, "Error opening %s", nc_name); // get variable id status = nc_inq_varid(nc_id, var_name, &var_id); check_nc_status(status, "Error getting variable id %s in %s", var_name, nc_name); // get size of the attribute status = nc_inq_attlen(nc_id, var_id, attr_name, &attr_len); check_nc_status(status, "Error getting attribute length for %s:%s in %s", var_name, attr_name, nc_name); // allocate memory for attribute *attr = malloc((attr_len + 1) * sizeof(**attr)); check_alloc_status(*attr, "Memory allocation error."); // read attribute text status = nc_get_att_text(nc_id, var_id, attr_name, *attr); check_nc_status(status, "Error getting netCDF attribute %s for var %s in %s", attr_name, var_name, nc_name); // we need to null terminate the string ourselves according to NetCDF docs (*attr)[attr_len] = '\0'; // close the netcdf file status = nc_close(nc_id); check_nc_status(status, "Error closing %s", nc_name); }
/****************************************************************************** * @brief Calculate the transpiration from the canopy. *****************************************************************************/ void transpiration(layer_data_struct *layer, veg_var_struct *veg_var, unsigned short veg_class, double rad, double vpd, double net_short, double air_temp, double ra, double dryFrac, double delta_t, double elevation, double *Wmax, double *Wcr, double *Wpwp, double *layerevap, double *frost_fract, double *root, double shortwave, double Catm, double *CanopLayerBnd) { extern veg_lib_struct *vic_run_veg_lib; extern option_struct options; extern parameters_struct param; size_t i; size_t frost_area; double gsm_inv; /* soil moisture stress factor */ double moist1, moist2; /* tmp holding of moisture */ double evap; /* tmp holding for evap total */ double Wcr1; /* tmp holding of critical water for upper layers */ double root_sum; /* proportion of roots in moist>Wcr zones */ double spare_evap; /* evap for 2nd distribution */ double avail_moist[MAX_LAYERS]; /* moisture available for trans */ double ice[MAX_LAYERS]; double gc; double *gsLayer = NULL; size_t cidx; /********************************************************************** EVAPOTRANSPIRATION Calculation of the evapotranspirations 2.18 First part: Soil moistures and root fractions of both layers influence each other Re-written to allow for multi-layers. **********************************************************************/ /************************************************** Set ice content in all individual layers **************************************************/ for (i = 0; i < options.Nlayer; i++) { ice[i] = 0; for (frost_area = 0; frost_area < options.Nfrost; frost_area++) { ice[i] += layer[i].ice[frost_area] * frost_fract[frost_area]; } } /************************************************** Compute moisture content in combined upper layers **************************************************/ moist1 = 0.0; Wcr1 = 0.0; for (i = 0; i < options.Nlayer - 1; i++) { if (root[i] > 0.) { avail_moist[i] = 0; for (frost_area = 0; frost_area < options.Nfrost; frost_area++) { avail_moist[i] += ((layer[i].moist - layer[i].ice[frost_area]) * frost_fract[frost_area]); } moist1 += avail_moist[i]; Wcr1 += Wcr[i]; } else { avail_moist[i] = 0.; } } /***************************************** Compute moisture content in lowest layer *****************************************/ i = options.Nlayer - 1; moist2 = 0; for (frost_area = 0; frost_area < options.Nfrost; frost_area++) { moist2 += ((layer[i].moist - layer[i].ice[frost_area]) * frost_fract[frost_area]); } avail_moist[i] = moist2; /** Set photosynthesis inhibition factor **/ if (layer[0].moist > vic_run_veg_lib[veg_class].Wnpp_inhib * Wmax[0]) { veg_var->NPPfactor = vic_run_veg_lib[veg_class].NPPfactor_sat + (1 - vic_run_veg_lib[veg_class].NPPfactor_sat) * (Wmax[0] - layer[0].moist) / (Wmax[0] - vic_run_veg_lib[ veg_class]. Wnpp_inhib * Wmax[0]); } else { veg_var->NPPfactor = 1.0; } /****************************************************************** CASE 1: Moisture in both layers exceeds Wcr, or Moisture in layer with more than half of the roots exceeds Wcr. Potential evapotranspiration not hindered by soil dryness. If layer with less than half the roots is dryer than Wcr, extra evaporation is taken from the wetter layer. Otherwise layers contribute to evapotransipration based on root fraction. ******************************************************************/ if (options.SHARE_LAYER_MOIST && ((moist1 >= Wcr1 && moist2 >= Wcr[options.Nlayer - 1] && Wcr1 > 0.) || (moist1 >= Wcr1 && (1 - root[options.Nlayer - 1]) >= 0.5) || (moist2 >= Wcr[options.Nlayer - 1] && root[options.Nlayer - 1] >= 0.5))) { gsm_inv = 1.0; /* compute whole-canopy stomatal resistance */ if (!options.CARBON || options.RC_MODE == RC_JARVIS) { /* Jarvis scheme, using resistance factors from Wigmosta et al., 1994 */ veg_var->rc = calc_rc(vic_run_veg_lib[veg_class].rmin, net_short, vic_run_veg_lib[veg_class].RGL, air_temp, vpd, veg_var->LAI, gsm_inv, false); if (options.CARBON) { for (cidx = 0; cidx < options.Ncanopy; cidx++) { if (veg_var->LAI > 0) { veg_var->rsLayer[cidx] = veg_var->rc / veg_var->LAI; } else { veg_var->rsLayer[cidx] = param.HUGE_RESIST; } if (veg_var->rsLayer[cidx] > param.CANOPY_RSMAX) { veg_var->rsLayer[cidx] = param.CANOPY_RSMAX; } } } } else { /* Compute rc based on photosynthetic demand from Knorr 1997 */ calc_rc_ps(vic_run_veg_lib[veg_class].Ctype, vic_run_veg_lib[veg_class].MaxCarboxRate, vic_run_veg_lib[veg_class].MaxETransport, vic_run_veg_lib[veg_class].CO2Specificity, veg_var->NscaleFactor, air_temp, shortwave, veg_var->aPARLayer, elevation, Catm, CanopLayerBnd, veg_var->LAI, gsm_inv, vpd, veg_var->rsLayer, &(veg_var->rc)); } /* compute transpiration */ evap = penman(air_temp, elevation, rad, vpd, ra, veg_var->rc, vic_run_veg_lib[veg_class].rarc) * delta_t / CONST_CDAY * dryFrac; /** divide up evap based on root distribution **/ /** Note the indexing of the roots **/ root_sum = 1.0; spare_evap = 0.0; for (i = 0; i < options.Nlayer; i++) { if (avail_moist[i] >= Wcr[i]) { layerevap[i] = evap * (double) root[i]; } else { if (avail_moist[i] >= Wpwp[i]) { gsm_inv = (avail_moist[i] - Wpwp[i]) / (Wcr[i] - Wpwp[i]); } else { gsm_inv = 0.0; } layerevap[i] = evap * gsm_inv * (double) root[i]; root_sum -= root[i]; spare_evap = evap * (double) root[i] * (1.0 - gsm_inv); } } /** Assign excess evaporation to wetter layer **/ if (spare_evap > 0.0) { for (i = 0; i < options.Nlayer; i++) { if (avail_moist[i] >= Wcr[i]) { layerevap[i] += (double) root[i] * spare_evap / root_sum; } } } } /********************************************************************* CASE 2: Independent evapotranspirations Evapotranspiration is restricted by low soil moisture. Evaporation is computed independantly from each soil layer. *********************************************************************/ else { /* Initialize conductances for aggregation over soil layers */ gc = 0; if (options.CARBON) { gsLayer = calloc(options.Ncanopy, sizeof(*gsLayer)); check_alloc_status(gsLayer, "Memory allocation error."); for (cidx = 0; cidx < options.Ncanopy; cidx++) { gsLayer[cidx] = 0; } } for (i = 0; i < options.Nlayer; i++) { /** Set evaporation restriction factor **/ if (avail_moist[i] >= Wcr[i]) { gsm_inv = 1.0; } else if (avail_moist[i] >= Wpwp[i] && avail_moist[i] < Wcr[i]) { gsm_inv = (avail_moist[i] - Wpwp[i]) / (Wcr[i] - Wpwp[i]); } else { gsm_inv = 0.0; } if (gsm_inv > 0.0) { /* compute whole-canopy stomatal resistance */ if (!options.CARBON || options.RC_MODE == RC_JARVIS) { /* Jarvis scheme, using resistance factors from Wigmosta et al., 1994 */ veg_var->rc = calc_rc(vic_run_veg_lib[veg_class].rmin, net_short, vic_run_veg_lib[veg_class].RGL, air_temp, vpd, veg_var->LAI, gsm_inv, false); if (options.CARBON) { for (cidx = 0; cidx < options.Ncanopy; cidx++) { if (veg_var->LAI > 0) { veg_var->rsLayer[cidx] = veg_var->rc / veg_var->LAI; } else { veg_var->rsLayer[cidx] = param.HUGE_RESIST; } if (veg_var->rsLayer[cidx] > param.CANOPY_RSMAX) { veg_var->rsLayer[cidx] = param.CANOPY_RSMAX; } } } } else { /* Compute rc based on photosynthetic demand from Knorr 1997 */ calc_rc_ps(vic_run_veg_lib[veg_class].Ctype, vic_run_veg_lib[veg_class].MaxCarboxRate, vic_run_veg_lib[veg_class].MaxETransport, vic_run_veg_lib[veg_class].CO2Specificity, veg_var->NscaleFactor, air_temp, shortwave, veg_var->aPARLayer, elevation, Catm, CanopLayerBnd, veg_var->LAI, gsm_inv, vpd, veg_var->rsLayer, &(veg_var->rc)); } /* compute transpiration */ layerevap[i] = penman(air_temp, elevation, rad, vpd, ra, veg_var->rc, vic_run_veg_lib[veg_class].rarc) * delta_t / CONST_CDAY * dryFrac * (double) root[i]; if (veg_var->rc > 0) { gc += 1 / (veg_var->rc); } else { gc += param.HUGE_RESIST; } if (options.CARBON) { for (cidx = 0; cidx < options.Ncanopy; cidx++) { if (veg_var->rsLayer[cidx] > 0) { gsLayer[cidx] += 1 / (veg_var->rsLayer[cidx]); } else { gsLayer[cidx] += param.HUGE_RESIST; } } } } else { layerevap[i] = 0.0; gc += 0; if (options.CARBON) { for (cidx = 0; cidx < options.Ncanopy; cidx++) { gsLayer[cidx] += 0; } } } } // end loop over layers /* Now, take the inverse of the conductance */ if (gc > 0) { veg_var->rc = 1 / gc; } else { veg_var->rc = param.HUGE_RESIST; } if (veg_var->rc > param.CANOPY_RSMAX) { veg_var->rc = param.CANOPY_RSMAX; } if (options.CARBON) { for (cidx = 0; cidx < options.Ncanopy; cidx++) { if (gsLayer[cidx] > 0) { veg_var->rsLayer[cidx] = 1 / gsLayer[cidx]; } else { veg_var->rsLayer[cidx] = param.HUGE_RESIST; } if (veg_var->rsLayer[cidx] > param.CANOPY_RSMAX) { veg_var->rsLayer[cidx] = param.CANOPY_RSMAX; } } } if (options.CARBON) { free((char *) gsLayer); } } /**************************************************************** Check that evapotransipration does not cause soil moisture to fall below wilting point. ****************************************************************/ for (i = 0; i < options.Nlayer; i++) { if (ice[i] > 0) { if (ice[i] >= Wpwp[i]) { // ice content greater than wilting point can use all unfrozen moist if (layerevap[i] > avail_moist[i]) { layerevap[i] = avail_moist[i]; } } else { // ice content less than wilting point restrict loss of unfrozen moist if (layerevap[i] > layer[i].moist - Wpwp[i]) { layerevap[i] = layer[i].moist - Wpwp[i]; } } } else { // No ice restrict loss of unfrozen moist if (layerevap[i] > layer[i].moist - Wpwp[i]) { layerevap[i] = layer[i].moist - Wpwp[i]; } } if (layerevap[i] < 0.0) { layerevap[i] = 0.0; } } }
/****************************************************************************** * @brief Allocate memory for the atmos data structure. *****************************************************************************/ void alloc_atmos(atmos_data_struct *atmos) { extern option_struct options; atmos->air_temp = calloc(NR + 1, sizeof(*(atmos->air_temp))); check_alloc_status(atmos->air_temp, "Memory allocation error."); atmos->density = calloc(NR + 1, sizeof(*(atmos->density))); check_alloc_status(atmos->density, "Memory allocation error."); atmos->longwave = calloc(NR + 1, sizeof(*(atmos->longwave))); check_alloc_status(atmos->longwave, "Memory allocation error."); atmos->prec = calloc(NR + 1, sizeof(*(atmos->prec))); check_alloc_status(atmos->prec, "Memory allocation error."); atmos->pressure = calloc(NR + 1, sizeof(*(atmos->pressure))); check_alloc_status(atmos->pressure, "Memory allocation error."); atmos->shortwave = calloc(NR + 1, sizeof(*(atmos->shortwave))); check_alloc_status(atmos->shortwave, "Memory allocation error."); atmos->snowflag = calloc(NR + 1, sizeof(*(atmos->snowflag))); check_alloc_status(atmos->snowflag, "Memory allocation error."); atmos->vp = calloc(NR + 1, sizeof(*(atmos->vp))); check_alloc_status(atmos->vp, "Memory allocation error."); atmos->vpd = calloc(NR + 1, sizeof(*(atmos->vpd))); check_alloc_status(atmos->vpd, "Memory allocation error."); atmos->wind = calloc(NR + 1, sizeof(*(atmos->wind))); check_alloc_status(atmos->wind, "Memory allocation error."); if (options.LAKES) { atmos->channel_in = calloc(NR + 1, sizeof(*(atmos->channel_in))); check_alloc_status(atmos->channel_in, "Memory allocation error."); } if (options.CARBON) { atmos->Catm = calloc(NR + 1, sizeof(*(atmos->Catm))); check_alloc_status(atmos->Catm, "Memory allocation error."); atmos->coszen = calloc(NR + 1, sizeof(*(atmos->coszen))); check_alloc_status(atmos->coszen, "Memory allocation error."); atmos->fdir = calloc(NR + 1, sizeof(*(atmos->fdir))); check_alloc_status(atmos->fdir, "Memory allocation error."); atmos->par = calloc(NR + 1, sizeof(*(atmos->par))); check_alloc_status(atmos->par, "Memory allocation error."); } }
/****************************************************************************** * @brief Read atmospheric forcing data. *****************************************************************************/ void vic_force(void) { extern size_t NF; extern size_t NR; extern size_t current; extern force_data_struct *force; extern dmy_struct *dmy; extern domain_struct global_domain; extern domain_struct local_domain; extern filenames_struct filenames; extern global_param_struct global_param; extern option_struct options; extern soil_con_struct *soil_con; extern veg_con_map_struct *veg_con_map; extern veg_con_struct **veg_con; extern veg_hist_struct **veg_hist; extern parameters_struct param; extern param_set_struct param_set; double *t_offset = NULL; double *dvar = NULL; size_t i; size_t j; size_t v; size_t band; int vidx; size_t d3count[3]; size_t d3start[3]; size_t d4count[4]; size_t d4start[4]; double *Tfactor; // allocate memory for variables to be read dvar = malloc(local_domain.ncells_active * sizeof(*dvar)); check_alloc_status(dvar, "Memory allocation error."); // for now forcing file is determined by the year sprintf(filenames.forcing[0], "%s%4d.nc", filenames.f_path_pfx[0], dmy[current].year); // global_param.forceoffset[0] resets every year since the met file restarts // every year if (current > 1 && (dmy[current].year != dmy[current - 1].year)) { global_param.forceoffset[0] = 0; } // only the time slice changes for the met file reads. The rest is constant d3start[1] = 0; d3start[2] = 0; d3count[0] = 1; d3count[1] = global_domain.n_ny; d3count[2] = global_domain.n_nx; // Air temperature: tas for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[AIR_TEMP].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].air_temp[j] = (double) dvar[i]; } } // Precipitation: prcp for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[PREC].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].prec[j] = (double) dvar[i]; } } // Downward solar radiation: dswrf for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[SWDOWN].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].shortwave[j] = (double) dvar[i]; } } // Downward longwave radiation: dlwrf for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[LWDOWN].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].longwave[j] = (double) dvar[i]; } } // Wind speed: wind for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[WIND].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].wind[j] = (double) dvar[i]; } } // vapor pressure: vp for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[VP].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].vp[j] = (double) dvar[i]; } } // Pressure: pressure for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[PRESSURE].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].pressure[j] = (double) dvar[i]; } } // Optional inputs if (options.LAKES) { // Channel inflow to lake d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[CHANNEL_IN].varname, d3start, d3count, dvar); for (j = 0; j < NF; j++) { for (i = 0; i < local_domain.ncells_active; i++) { force[i].channel_in[j] = (double) dvar[i]; } } } if (options.CARBON) { // Atmospheric CO2 mixing ratio for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[CATM].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].Catm[j] = (double) dvar[i]; } } // Cosine of solar zenith angle for (j = 0; j < NF; j++) { for (i = 0; i < local_domain.ncells_active; i++) { force[i].coszen[j] = compute_coszen( local_domain.locations[i].latitude, local_domain.locations[i].longitude, soil_con[i].time_zone_lng, dmy[current].day_in_year, dmy[current].dayseconds); } } // Fraction of shortwave that is direct for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[FDIR].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].fdir[j] = (double) dvar[i]; } } // Photosynthetically active radiation for (j = 0; j < NF; j++) { d3start[0] = global_param.forceskip[0] + global_param.forceoffset[0] + j; get_scatter_nc_field_double(filenames.forcing[0], param_set.TYPE[PAR].varname, d3start, d3count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { force[i].par[j] = (double) dvar[i]; } } } // Update the offset counter global_param.forceoffset[0] += NF; // Initialize the veg_hist structure with the current climatological // vegetation parameters. This may be overwritten with the historical // forcing time series. for (i = 0; i < local_domain.ncells_active; i++) { for (v = 0; v < options.NVEGTYPES; v++) { vidx = veg_con_map[i].vidx[v]; if (vidx != NODATA_VEG) { for (j = 0; j < NF; j++) { veg_hist[i][vidx].albedo[j] = veg_con[i][vidx].albedo[dmy[current].month - 1]; veg_hist[i][vidx].displacement[j] = veg_con[i][vidx].displacement[dmy[current].month - 1]; veg_hist[i][vidx].fcanopy[j] = veg_con[i][vidx].fcanopy[dmy[current].month - 1]; veg_hist[i][vidx].LAI[j] = veg_con[i][vidx].LAI[dmy[current].month - 1]; veg_hist[i][vidx].roughness[j] = veg_con[i][vidx].roughness[dmy[current].month - 1]; } } } } // Read veg_hist file if (options.LAI_SRC == FROM_VEGHIST || options.FCAN_SRC == FROM_VEGHIST || options.ALB_SRC == FROM_VEGHIST) { // for now forcing file is determined by the year sprintf(filenames.forcing[1], "%s%4d.nc", filenames.f_path_pfx[1], dmy[current].year); // global_param.forceoffset[1] resets every year since the met file restarts // every year if (current > 1 && (dmy[current].year != dmy[current - 1].year)) { global_param.forceoffset[1] = 0; } // only the time slice changes for the met file reads. The rest is constant d4start[2] = 0; d4start[3] = 0; d4count[0] = 1; d4count[1] = 1; d4count[2] = global_domain.n_ny; d4count[3] = global_domain.n_nx; // Leaf Area Index: lai if (options.LAI_SRC == FROM_VEGHIST) { for (j = 0; j < NF; j++) { d4start[0] = global_param.forceskip[1] + global_param.forceoffset[1] + j; for (v = 0; v < options.NVEGTYPES; v++) { d4start[1] = v; get_scatter_nc_field_double(filenames.forcing[1], "lai", d4start, d4count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { vidx = veg_con_map[i].vidx[v]; if (vidx != NODATA_VEG) { veg_hist[i][vidx].LAI[j] = (double) dvar[i]; } } } } } // Partial veg cover fraction: fcov if (options.FCAN_SRC == FROM_VEGHIST) { for (j = 0; j < NF; j++) { d4start[0] = global_param.forceskip[1] + global_param.forceoffset[1] + j; for (v = 0; v < options.NVEGTYPES; v++) { d4start[1] = v; get_scatter_nc_field_double(filenames.forcing[1], "fcov", d4start, d4count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { vidx = veg_con_map[i].vidx[v]; if (vidx != NODATA_VEG) { veg_hist[i][vidx].fcanopy[j] = (double) dvar[i]; } } } } } // Albedo: alb if (options.ALB_SRC == FROM_VEGHIST) { for (j = 0; j < NF; j++) { d4start[0] = global_param.forceskip[1] + global_param.forceoffset[1] + j; for (v = 0; v < options.NVEGTYPES; v++) { d4start[1] = v; get_scatter_nc_field_double(filenames.forcing[1], "alb", d4start, d4count, dvar); for (i = 0; i < local_domain.ncells_active; i++) { vidx = veg_con_map[i].vidx[v]; if (vidx != NODATA_VEG) { veg_hist[i][vidx].albedo[j] = (double) dvar[i]; } } } } } // Update the offset counter global_param.forceoffset[1] += NF; } // allocate memory for t_offset t_offset = malloc(local_domain.ncells_active * sizeof(*t_offset)); check_alloc_status(t_offset, "Memory allocation error."); for (i = 0; i < local_domain.ncells_active; i++) { if (options.SNOW_BAND > 1) { Tfactor = soil_con[i].Tfactor; t_offset[i] = Tfactor[0]; for (band = 1; band < options.SNOW_BAND; band++) { if (Tfactor[band] < t_offset[i]) { t_offset[i] = Tfactor[band]; } } } else { t_offset[i] = 0; } } // Convert forcings into what we need and calculate missing ones for (i = 0; i < local_domain.ncells_active; i++) { for (j = 0; j < NF; j++) { // pressure in Pa force[i].pressure[j] *= PA_PER_KPA; // vapor pressure in Pa force[i].vp[j] *= PA_PER_KPA; // vapor pressure deficit in Pa force[i].vpd[j] = svp(force[i].air_temp[j]) - force[i].vp[j]; if (force[i].vpd[j] < 0) { force[i].vpd[j] = 0; force[i].vp[j] = svp(force[i].air_temp[j]); } // air density in kg/m3 force[i].density[j] = air_density(force[i].air_temp[j], force[i].pressure[j]); // snow flag force[i].snowflag[j] = will_it_snow(&(force[i].air_temp[j]), t_offset[i], param.SNOW_MAX_SNOW_TEMP, &(force[i].prec[j]), 1); } // Check on fcanopy for (v = 0; v < options.NVEGTYPES; v++) { vidx = veg_con_map[i].vidx[v]; if (vidx != NODATA_VEG) { for (j = 0; j < NF; j++) { if ((veg_hist[i][vidx].fcanopy[j] < MIN_FCANOPY) && ((current == 0) || (options.FCAN_SRC == FROM_VEGHIST))) { // Only issue this warning once if not using veg hist fractions log_warn( "cell %zu, veg` %d substep %zu fcanopy %f < minimum of %f; setting = %f", i, vidx, j, veg_hist[i][vidx].fcanopy[j], MIN_FCANOPY, MIN_FCANOPY); veg_hist[i][vidx].fcanopy[j] = MIN_FCANOPY; } } } } } // Put average value in NR field for (i = 0; i < local_domain.ncells_active; i++) { force[i].air_temp[NR] = average(force[i].air_temp, NF); // For precipitation put total force[i].prec[NR] = average(force[i].prec, NF) * NF; force[i].shortwave[NR] = average(force[i].shortwave, NF); force[i].longwave[NR] = average(force[i].longwave, NF); force[i].pressure[NR] = average(force[i].pressure, NF); force[i].wind[NR] = average(force[i].wind, NF); force[i].vp[NR] = average(force[i].vp, NF); force[i].vpd[NR] = (svp(force[i].air_temp[NR]) - force[i].vp[NR]); force[i].density[NR] = air_density(force[i].air_temp[NR], force[i].pressure[NR]); force[i].snowflag[NR] = will_it_snow(force[i].air_temp, t_offset[i], param.SNOW_MAX_SNOW_TEMP, force[i].prec, NF); for (v = 0; v < options.NVEGTYPES; v++) { vidx = veg_con_map[i].vidx[v]; if (vidx != NODATA_VEG) { // not the correct way to calculate average albedo in general, // but leave for now (it's correct if albedo is constant over // the model step) veg_hist[i][vidx].albedo[NR] = average(veg_hist[i][vidx].albedo, NF); veg_hist[i][vidx].displacement[NR] = average( veg_hist[i][vidx].displacement, NF); veg_hist[i][vidx].fcanopy[NR] = average( veg_hist[i][vidx].fcanopy, NF); veg_hist[i][vidx].LAI[NR] = average(veg_hist[i][vidx].LAI, NF); veg_hist[i][vidx].roughness[NR] = average( veg_hist[i][vidx].roughness, NF); } } // Optional inputs if (options.LAKES) { force[i].channel_in[NR] = average(force[i].channel_in, NF) * NF; } if (options.CARBON) { force[i].Catm[NR] = average(force[i].Catm, NF); force[i].fdir[NR] = average(force[i].fdir, NF); force[i].par[NR] = average(force[i].par, NF); // for coszen, use value at noon force[i].coszen[NR] = compute_coszen( local_domain.locations[i].latitude, local_domain.locations[i].longitude, soil_con[i].time_zone_lng, dmy[current].day_in_year, SEC_PER_DAY / 2); } } // cleanup free(dvar); }
/****************************************************************************** * @brief This routine computes all surface fluxes ******************************************************************************/ int surface_fluxes(bool overstory, double BareAlbedo, double ice0, double moist0, double surf_atten, double *Melt, double *Le, double *aero_resist, double *displacement, double *gauge_correction, double *out_prec, double *out_rain, double *out_snow, double *ref_height, double *roughness, double *snow_inflow, double *wind, double *root, size_t Nlayers, size_t Nveg, unsigned short band, double dp, unsigned short iveg, unsigned short veg_class, force_data_struct *force, dmy_struct *dmy, energy_bal_struct *energy, global_param_struct *gp, cell_data_struct *cell, snow_data_struct *snow, soil_con_struct *soil_con, veg_var_struct *veg_var, double lag_one, double sigma_slope, double fetch, double *CanopLayerBnd) { extern veg_lib_struct *vic_run_veg_lib; extern option_struct options; extern parameters_struct param; int MAX_ITER_GRND_CANOPY; int ErrorFlag; int INCLUDE_SNOW = false; int UNSTABLE_CNT; int UNSTABLE_SNOW = false; int N_steps; int UnderStory; size_t hidx; // index of initial element of atmos array size_t step_inc; // number of atmos array elements to skip per surface fluxes step size_t endhidx; // index of final element of atmos array double step_dt; // time length of surface fluxes step (in seconds) size_t lidx; int over_iter; int under_iter; int q; double Ls; double LongUnderIn; // inmoing LW to ground surface double LongUnderOut; // outgoing LW from ground surface double NetLongSnow; // net LW over snowpack double NetShortSnow; // net SW over understory double NetShortGrnd; // net SW over snow-free surface double OldTSurf; // previous snow surface temperature double ShortUnderIn; // incoming SW to understory double Tair; // air temperature double Tcanopy; // canopy air temperature double Tgrnd; // soil surface temperature double Tsurf; // ground surface temperature double VPDcanopy; // vapor pressure deficit in canopy/atmos double VPcanopy; // vapor pressure in canopy/atmos double coverage; // mid-step snow cover fraction double delta_coverage; // change in snow cover fraction double delta_snow_heat; // change in snowpack heat storage double last_Tcanopy; double last_snow_coverage; // previous snow covered area double last_snow_flux; double last_tol_under; // previous surface iteration tol double last_tol_over; // previous overstory iteration tol double ppt; // precipitation/melt reaching soil surface double rainfall; // rainfall double snowfall; // snowfall double snow_flux; // heat flux through snowpack double snow_grnd_flux; // ground heat flux into snowpack double tol_under; double tol_over; double *aero_resist_used; double *inflow; layer_data_struct *layer; // Step-specific quantities double step_Wdew; double step_melt; double step_melt_energy; /* energy used to reduce snow coverage */ double step_out_prec; double step_out_rain; double step_out_snow; double step_ppt; double step_prec; // Quantities that need to be summed or averaged over multiple snow steps // energy structure double store_AlbedoOver; double store_AlbedoUnder; double store_AtmosLatent; double store_AtmosLatentSub; double store_AtmosSensible; double store_LongOverIn; double store_LongUnderIn; double store_LongUnderOut; double store_NetLongAtmos; double store_NetLongOver; double store_NetLongUnder; double store_NetShortAtmos; double store_NetShortGrnd; double store_NetShortOver; double store_NetShortUnder; double store_ShortOverIn; double store_ShortUnderIn; double store_advected_sensible; double store_advection; double store_canopy_advection; double store_canopy_latent; double store_canopy_latent_sub; double store_canopy_sensible; double store_canopy_refreeze; double store_deltaCC; double store_deltaH; double store_fusion; double store_grnd_flux; double store_latent; double store_latent_sub; double store_melt_energy; double store_refreeze_energy; double store_sensible; double store_snow_flux; // snow structure double store_canopy_vapor_flux; double store_melt; double store_vapor_flux; double store_blowing_flux; double store_surface_flux; // veg_var structure double store_canopyevap; double store_throughfall; // cell structure double store_layerevap[MAX_LAYERS]; double store_ppt; double store_aero_cond_used[2]; double store_pot_evap; // Structures holding values for current snow step energy_bal_struct snow_energy; // energy fluxes at snowpack surface energy_bal_struct soil_energy; // energy fluxes at soil surface veg_var_struct snow_veg_var; // veg fluxes/storages in presence of snow veg_var_struct soil_veg_var; // veg fluxes/storages in soil energy balance snow_data_struct step_snow; layer_data_struct step_layer[MAX_LAYERS]; // Structures holding values for current iteration energy_bal_struct iter_snow_energy; // energy fluxes at snowpack surface energy_bal_struct iter_soil_energy; // energy fluxes at soil surface veg_var_struct iter_snow_veg_var; // veg fluxes/storages in presence of snow veg_var_struct iter_soil_veg_var; // veg fluxes/storages in soil energy balance snow_data_struct iter_snow; layer_data_struct iter_layer[MAX_LAYERS]; double iter_aero_resist[3]; double iter_aero_resist_veg[3]; double iter_aero_resist_used[2]; double iter_pot_evap; #ifdef VCS_V5 double iter_pot_evap_veg; double iter_pot_evap_soil; #endif // handle bisection of understory solution double store_tol_under; double A_tol_under; // handle bisection of overstory solution double store_tol_over; // Carbon cycling double dryFrac; double *LAIlayer = NULL; double *faPAR = NULL; size_t cidx; double store_gc; double *store_gsLayer = NULL; double store_Ci; double store_GPP; double store_Rdark; double store_Rphoto; double store_Rmaint; double store_Rgrowth; double store_Raut; double store_NPP; if (options.CLOSE_ENERGY) { MAX_ITER_GRND_CANOPY = 10; } else { MAX_ITER_GRND_CANOPY = 0; } if (options.CARBON) { store_gsLayer = calloc(options.Ncanopy, sizeof(*store_gsLayer)); check_alloc_status(store_gsLayer, "Memory allocation error."); } /*********************************************************************** Set temporary variables for convenience ***********************************************************************/ aero_resist_used = cell->aero_resist; inflow = &(cell->inflow); layer = cell->layer; /*********************************************************************** Set temporary variables - preserves original values until iterations are completed ***********************************************************************/ energy->advection = 0; energy->deltaCC = 0; if (snow->swq > 0) { snow_flux = energy->snow_flux; } else { snow_flux = -(energy->grnd_flux + energy->deltaH + energy->fusion); } energy->refreeze_energy = 0; coverage = snow->coverage; snow_energy = (*energy); soil_energy = (*energy); iter_soil_energy = (*energy); snow_veg_var = (*veg_var); soil_veg_var = (*veg_var); step_snow = (*snow); for (lidx = 0; lidx < Nlayers; lidx++) { step_layer[lidx] = layer[lidx]; } for (lidx = 0; lidx < Nlayers; lidx++) { step_layer[lidx].evap = 0; } soil_veg_var.canopyevap = 0; snow_veg_var.canopyevap = 0; soil_veg_var.throughfall = 0; snow_veg_var.throughfall = 0; /******************************** Set-up sub-time step controls (May eventually want to set this up so that it is also true if frozen soils are present) ********************************/ if (snow->swq > 0 || snow->snow_canopy > 0 || force->snowflag[NR]) { hidx = 0; step_inc = 1; endhidx = hidx + NF; step_dt = gp->snow_dt; } else { hidx = NR; step_inc = 1; endhidx = hidx + step_inc; step_dt = gp->dt; } /******************************************* Initialize sub-model time step variables *******************************************/ // energy structure store_AlbedoOver = 0; store_AlbedoUnder = 0; store_AtmosLatent = 0; store_AtmosLatentSub = 0; store_AtmosSensible = 0; store_LongOverIn = 0; store_LongUnderIn = 0; store_LongUnderOut = 0; store_NetLongAtmos = 0; store_NetLongOver = 0; store_NetLongUnder = 0; store_NetShortAtmos = 0; store_NetShortGrnd = 0; store_NetShortOver = 0; store_NetShortUnder = 0; store_ShortOverIn = 0; store_ShortUnderIn = 0; store_advected_sensible = 0; store_advection = 0; store_canopy_advection = 0; store_canopy_latent = 0; store_canopy_latent_sub = 0; store_canopy_sensible = 0; store_canopy_refreeze = 0; store_deltaCC = 0; store_deltaH = 0; store_fusion = 0; store_grnd_flux = 0; store_latent = 0; store_latent_sub = 0; store_melt_energy = 0; store_refreeze_energy = 0; store_sensible = 0; store_snow_flux = 0; // snow structure last_snow_coverage = snow->coverage; store_canopy_vapor_flux = 0; store_melt = 0; store_vapor_flux = 0; store_surface_flux = 0; store_blowing_flux = 0; // veg_var and cell structures store_throughfall = 0.; store_canopyevap = 0.; for (lidx = 0; lidx < options.Nlayer; lidx++) { store_layerevap[lidx] = 0.; } step_Wdew = veg_var->Wdew; // misc store_ppt = 0; store_aero_cond_used[0] = 0; store_aero_cond_used[1] = 0; (*snow_inflow) = 0; store_pot_evap = 0; N_steps = 0; // Carbon cycling if (options.CARBON) { store_gc = 0; for (cidx = 0; cidx < options.Ncanopy; cidx++) { store_gsLayer[cidx] = 0; } store_Ci = 0; store_GPP = 0; store_Rdark = 0; store_Rphoto = 0; store_Rmaint = 0; store_Rgrowth = 0; store_Raut = 0; store_NPP = 0; } /************************* Compute surface fluxes *************************/ do { /** Solve energy balance for all sub-model time steps **/ /* set air temperature and precipitation for this snow band */ Tair = force->air_temp[hidx] + soil_con->Tfactor[band]; step_prec = force->prec[hidx] * soil_con->Pfactor[band]; // initialize ground surface temperaure Tgrnd = energy->T[0]; // initialize canopy terms Tcanopy = Tair; VPcanopy = force->vp[hidx]; VPDcanopy = force->vpd[hidx]; over_iter = 0; tol_over = 999; last_Tcanopy = 999; last_snow_flux = 999; // compute LAI and absorbed PAR per canopy layer if (options.CARBON && iveg < Nveg) { LAIlayer = calloc(options.Ncanopy, sizeof(*LAIlayer)); check_alloc_status(LAIlayer, "Memory allocation error."); faPAR = calloc(options.Ncanopy, sizeof(*faPAR)); check_alloc_status(faPAR, "Memory allocation error."); /* Compute absorbed PAR per ground area per canopy layer (W/m2) normalized to PAR = 1 W, i.e. the canopy albedo in the PAR range (alb_total ~ 0.45*alb_par + 0.55*alb_other) */ faparl(CanopLayerBnd, veg_var->LAI, soil_con->AlbedoPar, force->coszen[hidx], force->fdir[hidx], LAIlayer, faPAR); /* Convert to absolute (unnormalized) absorbed PAR per leaf area per canopy layer (umol(photons)/m2 leaf area / s); dividing by Epar converts PAR from W to umol(photons)/s */ veg_var->aPAR = 0; for (cidx = 0; cidx < options.Ncanopy; cidx++) { if (LAIlayer[cidx] > 1e-10) { veg_var->aPARLayer[cidx] = (force->par[hidx] / param.PHOTO_EPAR) * faPAR[cidx] / LAIlayer[cidx]; veg_var->aPAR += force->par[hidx] * faPAR[cidx] / LAIlayer[cidx]; } else { veg_var->aPARLayer[cidx] = force->par[hidx] / param.PHOTO_EPAR * faPAR[cidx] / 1e-10; veg_var->aPAR += force->par[hidx] * faPAR[cidx] / 1e-10; } } free((char*) LAIlayer); free((char*) faPAR); } // Compute mass flux of blowing snow if (!overstory && options.BLOWING && step_snow.swq > 0.) { Ls = calc_latent_heat_of_sublimation(step_snow.surf_temp); step_snow.blowing_flux = CalcBlowingSnow(step_dt, Tair, step_snow.last_snow, step_snow.surf_water, wind[2], Ls, force->density[hidx], force->vp[hidx], roughness[2], ref_height[2], step_snow.depth, lag_one, sigma_slope, step_snow.surf_temp, iveg, Nveg, fetch, displacement[1], roughness[1], &step_snow.transport); if ((int) step_snow.blowing_flux == ERROR) { return (ERROR); } step_snow.blowing_flux *= step_dt / CONST_RHOFW; /* m/time step */ } else { step_snow.blowing_flux = 0.0; } do { /** Iterate for overstory solution **/ over_iter++; last_tol_over = tol_over; under_iter = 0; tol_under = 999; UnderStory = 999; UNSTABLE_CNT = 0; // bisect understory A_tol_under = 999; store_tol_under = 999; store_tol_over = 999; do { /** Iterate for understory solution - itererates to find snow flux **/ under_iter++; last_tol_under = tol_under; if (last_Tcanopy != 999) { Tcanopy = (last_Tcanopy + Tcanopy) / 2.; } last_Tcanopy = Tcanopy; // update understory energy balance terms for iteration if (last_snow_flux != 999) { if ((fabs(store_tol_under) > fabs(A_tol_under) && A_tol_under != 999 && fabs(store_tol_under - A_tol_under) > 1.) || tol_under < 0) { // stepped the correct way UNSTABLE_CNT++; if (UNSTABLE_CNT > 3 || tol_under < 0) { UNSTABLE_SNOW = true; } } else if (!INCLUDE_SNOW) { // stepped the wrong way snow_flux = (last_snow_flux + iter_soil_energy.snow_flux) / 2.; } } last_snow_flux = snow_flux; A_tol_under = store_tol_under; snow_grnd_flux = -snow_flux; // Initialize structures for new iteration iter_snow_energy = snow_energy; iter_soil_energy = soil_energy; iter_snow_veg_var = snow_veg_var; iter_soil_veg_var = soil_veg_var; iter_snow = step_snow; for (lidx = 0; lidx < Nlayers; lidx++) { iter_layer[lidx] = step_layer[lidx]; } iter_snow_veg_var.Wdew = step_Wdew; iter_soil_veg_var.Wdew = step_Wdew; iter_snow_veg_var.canopyevap = 0; iter_soil_veg_var.canopyevap = 0; for (lidx = 0; lidx < Nlayers; lidx++) { iter_layer[lidx].evap = 0; } for (q = 0; q < 3; q++) { iter_aero_resist[q] = aero_resist[q]; iter_aero_resist_veg[q] = aero_resist[q]; } iter_aero_resist_used[0] = aero_resist_used[0]; iter_aero_resist_used[1] = aero_resist_used[1]; iter_snow.canopy_vapor_flux = 0; iter_snow.vapor_flux = 0; iter_snow.surface_flux = 0; /* iter_snow.blowing_flux has already been reset to step_snow.blowing_flux */ LongUnderOut = iter_soil_energy.LongUnderOut; dryFrac = -1; /** Solve snow accumulation, ablation and interception **/ step_melt = solve_snow(overstory, BareAlbedo, LongUnderOut, param.SNOW_MIN_RAIN_TEMP, param.SNOW_MAX_SNOW_TEMP, Tcanopy, Tgrnd, Tair, step_prec, snow_grnd_flux, &energy->AlbedoUnder, Le, &LongUnderIn, &NetLongSnow, &NetShortGrnd, &NetShortSnow, &ShortUnderIn, &OldTSurf, iter_aero_resist, iter_aero_resist_used, &coverage, &delta_coverage, &delta_snow_heat, displacement, gauge_correction, &step_melt_energy, &step_out_prec, &step_out_rain, &step_out_snow, &step_ppt, &rainfall, ref_height, roughness, snow_inflow, &snowfall, &surf_atten, wind, root, UNSTABLE_SNOW, Nveg, iveg, band, step_dt, hidx, veg_class, &UnderStory, CanopLayerBnd, &dryFrac, dmy, force, &(iter_snow_energy), iter_layer, &(iter_snow), soil_con, &(iter_snow_veg_var)); if (step_melt == ERROR) { return (ERROR); } /* Check that the snow surface temperature was estimated, if not prepare to include thin snowpack in the estimation of the snow-free surface energy balance */ if ((iter_snow.surf_temp == 999 || UNSTABLE_SNOW) && iter_snow.swq > 0) { INCLUDE_SNOW = UnderStory + 1; iter_soil_energy.advection = iter_snow_energy.advection; iter_snow.surf_temp = step_snow.surf_temp; step_melt_energy = 0; } else { INCLUDE_SNOW = false; } if (iter_snow.snow) { iter_aero_resist_veg[0] = iter_aero_resist_used[0]; iter_aero_resist_veg[1] = iter_aero_resist_used[1]; } /************************************************** Solve Energy Balance Components at Soil Surface **************************************************/ Tsurf = calc_surf_energy_bal((*Le), LongUnderIn, NetLongSnow, NetShortGrnd, NetShortSnow, OldTSurf, ShortUnderIn, iter_snow.albedo, iter_snow_energy.latent, iter_snow_energy.latent_sub, iter_snow_energy.sensible, Tcanopy, VPDcanopy, VPcanopy, delta_coverage, dp, ice0, step_melt_energy, moist0, iter_snow.coverage, (step_snow.depth + iter_snow.depth) / 2., BareAlbedo, surf_atten, iter_aero_resist, iter_aero_resist_veg, iter_aero_resist_used, displacement, &step_melt, &step_ppt, rainfall, ref_height, roughness, snowfall, wind, root, INCLUDE_SNOW, UnderStory, options.Nnode, Nveg, step_dt, hidx, iveg, (int) overstory, veg_class, CanopLayerBnd, &dryFrac, force, dmy, &iter_soil_energy, iter_layer, &(iter_snow), soil_con, &iter_soil_veg_var); if ((int) Tsurf == ERROR) { // Return error flag to skip rest of grid cell return (ERROR); } if (INCLUDE_SNOW) { /* store melt from thin snowpack */ step_ppt += step_melt; } /***************************************** Compute energy balance with atmosphere *****************************************/ if (iter_snow.snow && overstory) { // do this if overstory is active and energy balance is closed Tcanopy = calc_atmos_energy_bal( iter_snow_energy.canopy_sensible, iter_soil_energy.sensible, iter_snow_energy.canopy_latent, iter_soil_energy.latent, iter_snow_energy.canopy_latent_sub, iter_soil_energy.latent_sub, iter_snow_energy.NetLongOver, iter_soil_energy.NetLongUnder, iter_snow_energy.NetShortOver, iter_soil_energy.NetShortUnder, iter_aero_resist_veg[1], Tair, force->density[hidx], &iter_soil_energy.AtmosError, &iter_soil_energy.AtmosLatent, &iter_soil_energy.AtmosLatentSub, &iter_soil_energy.NetLongAtmos, &iter_soil_energy.NetShortAtmos, &iter_soil_energy.AtmosSensible, &iter_soil_energy.Tcanopy_fbflag, &iter_soil_energy.Tcanopy_fbcount); /* iterate to find Tcanopy which will solve the atmospheric energy balance. Since I do not know vp in the canopy, use the sum of latent heats from the ground and foliage, and iterate on the temperature used for the sensible heat flux from the canopy air to the mixing level */ if ((int) Tcanopy == ERROR) { // Return error flag to skip rest of grid cell return (ERROR); } } else { // else put surface fluxes into atmospheric flux storage so that // the model will continue to function iter_soil_energy.AtmosLatent = iter_soil_energy.latent; iter_soil_energy.AtmosLatentSub = iter_soil_energy.latent_sub; iter_soil_energy.AtmosSensible = iter_soil_energy.sensible; iter_soil_energy.NetLongAtmos = iter_soil_energy.NetLongUnder; iter_soil_energy.NetShortAtmos = iter_soil_energy.NetShortUnder; } iter_soil_energy.Tcanopy = Tcanopy; iter_snow_energy.Tcanopy = Tcanopy; /***************************************** Compute iteration tolerance statistics *****************************************/ // compute understory tolerance if (INCLUDE_SNOW || (iter_snow.swq == 0 && delta_coverage == 0)) { store_tol_under = 0; tol_under = 0; } else { store_tol_under = snow_flux - iter_soil_energy.snow_flux; tol_under = fabs(store_tol_under); } if (fabs(tol_under - last_tol_under) < param.TOL_GRND && tol_under > 1.) { tol_under = -999; } // compute overstory tolerance if (overstory && iter_snow.snow) { store_tol_over = Tcanopy - last_Tcanopy; tol_over = fabs(store_tol_over); } else { store_tol_over = 0; tol_over = 0; } } while ((fabs(tol_under - last_tol_under) > param.TOL_GRND) && (tol_under != 0) && (under_iter < MAX_ITER_GRND_CANOPY)); } while ((fabs(tol_over - last_tol_over) > param.TOL_OVER && overstory) && (tol_over != 0) && (over_iter < MAX_ITER_GRND_CANOPY)); /************************************** Compute GPP, Raut, and NPP **************************************/ if (options.CARBON) { if (iveg < Nveg && !step_snow.snow && dryFrac > 0) { canopy_assimilation(vic_run_veg_lib[veg_class].Ctype, vic_run_veg_lib[veg_class].MaxCarboxRate, vic_run_veg_lib[veg_class].MaxETransport, vic_run_veg_lib[veg_class].CO2Specificity, iter_soil_veg_var.NscaleFactor, Tair, force->shortwave[hidx], iter_soil_veg_var.aPARLayer, soil_con->elevation, force->Catm[hidx], CanopLayerBnd, veg_var->LAI, "rs", iter_soil_veg_var.rsLayer, &(iter_soil_veg_var.rc), &(iter_soil_veg_var.Ci), &(iter_soil_veg_var.GPP), &(iter_soil_veg_var.Rdark), &(iter_soil_veg_var.Rphoto), &(iter_soil_veg_var.Rmaint), &(iter_soil_veg_var.Rgrowth), &(iter_soil_veg_var.Raut), &(iter_soil_veg_var.NPP)); /* Adjust by fraction of canopy that was dry and account for any other inhibition`*/ dryFrac *= iter_soil_veg_var.NPPfactor; iter_soil_veg_var.GPP *= dryFrac; iter_soil_veg_var.Rdark *= dryFrac; iter_soil_veg_var.Rphoto *= dryFrac; iter_soil_veg_var.Rmaint *= dryFrac; iter_soil_veg_var.Rgrowth *= dryFrac; iter_soil_veg_var.Raut *= dryFrac; iter_soil_veg_var.NPP *= dryFrac; /* Adjust by veg cover fraction */ iter_soil_veg_var.GPP *= iter_soil_veg_var.fcanopy; iter_soil_veg_var.Rdark *= iter_soil_veg_var.fcanopy; iter_soil_veg_var.Rphoto *= iter_soil_veg_var.fcanopy; iter_soil_veg_var.Rmaint *= iter_soil_veg_var.fcanopy; iter_soil_veg_var.Rgrowth *= iter_soil_veg_var.fcanopy; iter_soil_veg_var.Raut *= iter_soil_veg_var.fcanopy; iter_soil_veg_var.NPP *= iter_soil_veg_var.fcanopy; } else { iter_soil_veg_var.rc = param.HUGE_RESIST; for (cidx = 0; cidx < options.Ncanopy; cidx++) { iter_soil_veg_var.rsLayer[cidx] = param.HUGE_RESIST; } iter_soil_veg_var.Ci = 0; iter_soil_veg_var.GPP = 0; iter_soil_veg_var.Rdark = 0; iter_soil_veg_var.Rphoto = 0; iter_soil_veg_var.Rmaint = 0; iter_soil_veg_var.Rgrowth = 0; iter_soil_veg_var.Raut = 0; iter_soil_veg_var.NPP = 0; } } /************************************** Compute Potential Evap **************************************/ compute_pot_evap(gp->model_steps_per_day, vic_run_veg_lib[veg_class].rmin, iter_soil_veg_var.albedo, force->shortwave[hidx], iter_soil_energy.NetLongAtmos, vic_run_veg_lib[veg_class].RGL, Tair, VPDcanopy, iter_soil_veg_var.LAI, soil_con->elevation, iter_aero_resist_veg, vic_run_veg_lib[veg_class].overstory, vic_run_veg_lib[veg_class].rarc, iter_soil_veg_var.fcanopy, iter_aero_resist_used[0], &iter_pot_evap #ifdef VCS_V5 , &iter_pot_evap_veg, &iter_pot_evap_soil #endif ); #ifdef VCS_V5 cell->VCS.pot_evap_veg_daily += iter_pot_evap_veg; cell->VCS.pot_evap_soil_daily += iter_pot_evap_soil; cell->VCS.pot_evap_total_daily += iter_pot_evap; #endif /************************************** Store sub-model time step variables **************************************/ snow_energy = iter_snow_energy; soil_energy = iter_soil_energy; snow_veg_var = iter_snow_veg_var; soil_veg_var = iter_soil_veg_var; step_snow = iter_snow; for (lidx = 0; lidx < options.Nlayer; lidx++) { step_layer[lidx] = iter_layer[lidx]; } if (iveg != Nveg) { if (step_snow.snow) { store_throughfall += snow_veg_var.throughfall; store_canopyevap += snow_veg_var.canopyevap; soil_veg_var.Wdew = snow_veg_var.Wdew; } else { store_throughfall += soil_veg_var.throughfall; store_canopyevap += soil_veg_var.canopyevap; snow_veg_var.Wdew = soil_veg_var.Wdew; } step_Wdew = soil_veg_var.Wdew; if (options.CARBON) { store_gc += 1 / soil_veg_var.rc; for (cidx = 0; cidx < options.Ncanopy; cidx++) { store_gsLayer[cidx] += 1 / soil_veg_var.rsLayer[cidx]; } store_Ci += soil_veg_var.Ci; store_GPP += soil_veg_var.GPP; store_Rdark += soil_veg_var.Rdark; store_Rphoto += soil_veg_var.Rphoto; store_Rmaint += soil_veg_var.Rmaint; store_Rgrowth += soil_veg_var.Rgrowth; store_Raut += soil_veg_var.Raut; store_NPP += soil_veg_var.NPP; } } for (lidx = 0; lidx < options.Nlayer; lidx++) { store_layerevap[lidx] += step_layer[lidx].evap; } store_ppt += step_ppt; if (iter_aero_resist_used[0] > 0) { store_aero_cond_used[0] += 1 / iter_aero_resist_used[0]; } else { store_aero_cond_used[0] += param.HUGE_RESIST; } if (iter_aero_resist_used[1] > 0) { store_aero_cond_used[1] += 1 / iter_aero_resist_used[1]; } else { store_aero_cond_used[1] += param.HUGE_RESIST; } if (iveg != Nveg) { store_canopy_vapor_flux += step_snow.canopy_vapor_flux; } store_melt += step_melt; store_vapor_flux += step_snow.vapor_flux; store_surface_flux += step_snow.surface_flux; store_blowing_flux += step_snow.blowing_flux; out_prec[0] += step_out_prec; out_rain[0] += step_out_rain; out_snow[0] += step_out_snow; if (INCLUDE_SNOW) { /* copy needed flux terms to the snowpack */ snow_energy.advected_sensible = soil_energy.advected_sensible; snow_energy.advection = soil_energy.advection; snow_energy.deltaCC = soil_energy.deltaCC; snow_energy.latent = soil_energy.latent; snow_energy.latent_sub = soil_energy.latent_sub; snow_energy.refreeze_energy = soil_energy.refreeze_energy; snow_energy.sensible = soil_energy.sensible; snow_energy.snow_flux = soil_energy.snow_flux; } store_AlbedoOver += snow_energy.AlbedoOver; store_AlbedoUnder += soil_energy.AlbedoUnder; store_AtmosLatent += soil_energy.AtmosLatent; store_AtmosLatentSub += soil_energy.AtmosLatentSub; store_AtmosSensible += soil_energy.AtmosSensible; store_LongOverIn += snow_energy.LongOverIn; store_LongUnderIn += LongUnderIn; store_LongUnderOut += soil_energy.LongUnderOut; store_NetLongAtmos += soil_energy.NetLongAtmos; store_NetLongOver += snow_energy.NetLongOver; store_NetLongUnder += soil_energy.NetLongUnder; store_NetShortAtmos += soil_energy.NetShortAtmos; store_NetShortGrnd += NetShortGrnd; store_NetShortOver += snow_energy.NetShortOver; store_NetShortUnder += soil_energy.NetShortUnder; store_ShortOverIn += snow_energy.ShortOverIn; store_ShortUnderIn += soil_energy.ShortUnderIn; store_canopy_advection += snow_energy.canopy_advection; store_canopy_latent += snow_energy.canopy_latent; store_canopy_latent_sub += snow_energy.canopy_latent_sub; store_canopy_sensible += snow_energy.canopy_sensible; store_canopy_refreeze += snow_energy.canopy_refreeze; store_deltaH += soil_energy.deltaH; store_fusion += soil_energy.fusion; store_grnd_flux += soil_energy.grnd_flux; store_latent += soil_energy.latent; store_latent_sub += soil_energy.latent_sub; store_melt_energy += step_melt_energy; store_sensible += soil_energy.sensible; if (step_snow.swq == 0 && INCLUDE_SNOW) { if (last_snow_coverage == 0 && step_prec > 0) { last_snow_coverage = 1; } store_advected_sensible += snow_energy.advected_sensible * last_snow_coverage; store_advection += snow_energy.advection * last_snow_coverage; store_deltaCC += snow_energy.deltaCC * last_snow_coverage; store_snow_flux += soil_energy.snow_flux * last_snow_coverage; store_refreeze_energy += snow_energy.refreeze_energy * last_snow_coverage; } else if (step_snow.snow || INCLUDE_SNOW) { store_advected_sensible += snow_energy.advected_sensible * (step_snow.coverage + delta_coverage); store_advection += snow_energy.advection * (step_snow.coverage + delta_coverage); store_deltaCC += snow_energy.deltaCC * (step_snow.coverage + delta_coverage); store_snow_flux += soil_energy.snow_flux * (step_snow.coverage + delta_coverage); store_refreeze_energy += snow_energy.refreeze_energy * (step_snow.coverage + delta_coverage); } store_pot_evap += iter_pot_evap; /* increment time step */ N_steps++; hidx += step_inc; } while (hidx < endhidx); /************************************************ Store snow variables for sub-model time steps ************************************************/ (*snow) = step_snow; snow->vapor_flux = store_vapor_flux; snow->blowing_flux = store_blowing_flux; snow->surface_flux = store_surface_flux; snow->canopy_vapor_flux = store_canopy_vapor_flux; (*Melt) = store_melt; snow->melt = store_melt; ppt = store_ppt; /****************************************************** Store energy flux averages for sub-model time steps ******************************************************/ (*energy) = soil_energy; energy->AlbedoOver = store_AlbedoOver / (double) N_steps; energy->AlbedoUnder = store_AlbedoUnder / (double) N_steps; energy->AtmosLatent = store_AtmosLatent / (double) N_steps; energy->AtmosLatentSub = store_AtmosLatentSub / (double) N_steps; energy->AtmosSensible = store_AtmosSensible / (double) N_steps; energy->LongOverIn = store_LongOverIn / (double) N_steps; energy->LongUnderIn = store_LongUnderIn / (double) N_steps; energy->LongUnderOut = store_LongUnderOut / (double) N_steps; energy->NetLongAtmos = store_NetLongAtmos / (double) N_steps; energy->NetLongOver = store_NetLongOver / (double) N_steps; energy->NetLongUnder = store_NetLongUnder / (double) N_steps; energy->NetShortAtmos = store_NetShortAtmos / (double) N_steps; energy->NetShortGrnd = store_NetShortGrnd / (double) N_steps; energy->NetShortOver = store_NetShortOver / (double) N_steps; energy->NetShortUnder = store_NetShortUnder / (double) N_steps; energy->ShortOverIn = store_ShortOverIn / (double) N_steps; energy->ShortUnderIn = store_ShortUnderIn / (double) N_steps; energy->advected_sensible = store_advected_sensible / (double) N_steps; energy->canopy_advection = store_canopy_advection / (double) N_steps; energy->canopy_latent = store_canopy_latent / (double) N_steps; energy->canopy_latent_sub = store_canopy_latent_sub / (double) N_steps; energy->canopy_refreeze = store_canopy_refreeze / (double) N_steps; energy->canopy_sensible = store_canopy_sensible / (double) N_steps; energy->deltaH = store_deltaH / (double) N_steps; energy->fusion = store_fusion / (double) N_steps; energy->grnd_flux = store_grnd_flux / (double) N_steps; energy->latent = store_latent / (double) N_steps; energy->latent_sub = store_latent_sub / (double) N_steps; energy->melt_energy = store_melt_energy / (double) N_steps; energy->sensible = store_sensible / (double) N_steps; if (snow->snow || INCLUDE_SNOW) { energy->advection = store_advection / (double) N_steps; energy->deltaCC = store_deltaCC / (double) N_steps; energy->refreeze_energy = store_refreeze_energy / (double) N_steps; energy->snow_flux = store_snow_flux / (double) N_steps; } energy->Tfoliage = snow_energy.Tfoliage; energy->Tfoliage_fbflag = snow_energy.Tfoliage_fbflag; energy->Tfoliage_fbcount = snow_energy.Tfoliage_fbcount; /********************************************************** Store vegetation variable sums for sub-model time steps **********************************************************/ if (iveg != Nveg) { veg_var->throughfall = store_throughfall; veg_var->canopyevap = store_canopyevap; if (snow->snow) { veg_var->Wdew = snow_veg_var.Wdew; } else { veg_var->Wdew = soil_veg_var.Wdew; } } /********************************************************** Store soil layer variables for sub-model time steps **********************************************************/ for (lidx = 0; lidx < Nlayers; lidx++) { layer[lidx] = step_layer[lidx]; layer[lidx].evap = store_layerevap[lidx]; } if (store_aero_cond_used[0] > 0 && store_aero_cond_used[0] < param.HUGE_RESIST) { aero_resist_used[0] = 1 / (store_aero_cond_used[0] / (double) N_steps); } else if (store_aero_cond_used[0] >= param.HUGE_RESIST) { aero_resist_used[0] = 0; } else { aero_resist_used[0] = param.HUGE_RESIST; } if (store_aero_cond_used[1] > 0 && store_aero_cond_used[1] < param.HUGE_RESIST) { aero_resist_used[1] = 1 / (store_aero_cond_used[1] / (double) N_steps); } else if (store_aero_cond_used[1] >= param.HUGE_RESIST) { aero_resist_used[1] = 0; } else { aero_resist_used[1] = param.HUGE_RESIST; } cell->pot_evap = store_pot_evap; /********************************************************** Store carbon cycle variable sums for sub-model time steps **********************************************************/ if (options.CARBON && iveg != Nveg) { veg_var->rc = 1 / store_gc / (double) N_steps; for (cidx = 0; cidx < options.Ncanopy; cidx++) { veg_var->rsLayer[cidx] = 1 / store_gsLayer[cidx] / (double) N_steps; } veg_var->Ci = store_Ci / (double) N_steps; veg_var->GPP = store_GPP / (double) N_steps; veg_var->Rdark = store_Rdark / (double) N_steps; veg_var->Rphoto = store_Rphoto / (double) N_steps; veg_var->Rmaint = store_Rmaint / (double) N_steps; veg_var->Rgrowth = store_Rgrowth / (double) N_steps; veg_var->Raut = store_Raut / (double) N_steps; veg_var->NPP = store_NPP / (double) N_steps; free((char *) (store_gsLayer)); soil_carbon_balance(soil_con, energy, cell, veg_var); // Update running total annual NPP if (veg_var->NPP > 0) { veg_var->AnnualNPP += veg_var->NPP * CONST_MWC / MOLE_PER_KMOLE * gp->dt; } } /******************************************************** Compute Runoff, Baseflow, and Soil Moisture Transport ********************************************************/ (*inflow) = ppt; ErrorFlag = runoff(cell, energy, soil_con, ppt, soil_con->frost_fract, options.Nnode); return(ErrorFlag); }