// If a parameter is given, we try to connect to the given server. // This enables us to test the remote capabilites of ScrollView. int main(int argc, char** argv) { const char* server_name; if (argc > 1) { server_name = argv[1]; } else { server_name = "localhost"; } SVPaint svp(server_name); }
/***************************************************************************** Function name: IceEnergyBalance() Purpose : Calculate the surface energy balance for the snow pack Required : double TSurf - new estimate of effective surface temperature Returns : double RestTerm - Rest term in the energy balance Modifies : double *RefreezeEnergy - Refreeze energy (W/m2) double *VaporMassFlux - Mass flux of water vapor to or from the intercepted snow (m/s) Comments : Reference: Bras, R. A., Hydrology, an introduction to hydrologic science, Addisson Wesley, Inc., Reading, etc., 1990. Modifications: 16-Jul-04 Renamed VaporMassFlux to vapor_flux to denote fact that its units are m/timestep rather than kg/m2s. Created new variable VaporMassFlux with units of kg/m2s. After VaporMassFlux is computed, vapor_flux is derived from it by unit conversion. vapor_flux is the variable that is passed in/out of this function. TJB 24-Aug-04 Added logic to handle blowing_flux and vapor_flux. TJB 28-Sep-04 Added Ra_used to store the aerodynamic resistance used in flux calculations. TJB 04-Oct-04 Merged with Laura Bowling's updated lake model code. Now blowing snow sublimation is calculated for lakes. TJB 2006-Sep-23 Replaced redundant STEFAN constant with STEFAN_B. TJB 2006-Nov-07 Removed LAKE_MODEL option. TJB *****************************************************************************/ double IceEnergyBalance::calculate(double TSurf) { const char *Routine = "IceEnergyBalance"; double Density; /* Density of water/ice at TMean (kg/m3) */ double EsSnow; /* saturated vapor pressure in the snow pack (Pa) */ double Ls; /* Latent heat of sublimation (J/kg) */ double NetRad; /* Net radiation exchange at surface (W/m2) */ double RestTerm; /* Rest term in surface energy balance (W/m2) */ double TMean; /* Average temperature for time step (C) */ double qnull; double VaporMassFlux; /* Total mass flux of water vapor to or from snow (kg/m2s) */ double BlowingMassFlux; /* Mass flux of water vapor to or from blowing snow (kg/m2s) */ double SurfaceMassFlux; /* Mass flux of water vapor to or from snow pack (kg/m2s) */ /* Calculate active temp for energy balance as average of old and new */ /* TMean = 0.5 * (OldTSurf + TSurf); */ TMean = TSurf; Density = RHO_W; /* Correct aerodynamic conductance for stable conditions Note: If air temp >> snow temp then aero_cond -> 0 (i.e. very stable) velocity (vel_2m) is expected to be in m/sec */ /* Apply the stability correction to the aerodynamic resistance NOTE: In the old code 2m was passed instead of Z-Displacement. I (bart) think that it is more correct to calculate ALL fluxes at the same reference level */ if (Wind > 0.0) *Ra_used = Ra / StabilityCorrection(Z, 0.f, TMean, Tair, Wind, Z0); /* *Ra_used = Ra / StabilityCorrection(2.f, 0.f, TMean, Tair, Wind, Z0);*/ else *Ra_used = HUGE_RESIST; /* Calculate longwave exchange and net radiation */ *LongRadOut = LongRadIn - STEFAN_B * (TMean+273.15) * (TMean+273.15) * (TMean+273.15) * (TMean+273.15); NetRad = ShortRad + *LongRadOut; /* Calculate the sensible heat flux */ *SensibleHeat = AirDens * CP_PM * (Tair - TMean) / *Ra_used; /* Calculate the mass flux of ice to or from the surface layer */ /* Calculate the saturated vapor pressure in the snow pack, (Equation 3.32, Bras 1990) */ EsSnow = svp(TMean) /* * 1000. */; /* EsSnow = 610.78 * exp((double)((17.269 * TMean) / (237.3 + TMean))); */ /* if (TMean < 0.0) */ /* EsSnow *= 1.0 + .00972 * TMean + .000042 * pow((double)TMean,(double)2.0); */ /* Calculate sublimation terms and latent heat flux */ /* blowing_flux was calculated outside of the root_brent iteration */ BlowingMassFlux = *blowing_flux * Density / (Dt * SECPHOUR); latent_heat_from_snow(AirDens, EactAir, Lv, Press, Ra, TMean, Vpd, LatentHeat, LatentHeatSub, &VaporMassFlux, &BlowingMassFlux, &SurfaceMassFlux); /* Convert sublimation terms from kg/m2s to m/timestep */ *vapor_flux = VaporMassFlux * Dt * SECPHOUR / Density; *surface_flux = SurfaceMassFlux * Dt * SECPHOUR / Density; /* Calculate advected heat flux from rain WORK IN PROGRESS: Should the following read (Tair - Tsurf) ?? */ // Temporary fix for lake model. *AdvectedEnergy = (CH_WATER * Tair * Rain) / (Dt*SECPHOUR); //*AdvectedEnergy = 0.0; /* Calculate change in cold content */ /* No change in cold content in lake model */ /* Changes for lake model start here. Actually, equals qo-Io (P&H eq. 7)*/ /* because Io (SWnet) is included in NetRad below. */ qnull = (1/AvgCond)*(Tfreeze - TMean + SWconducted); *qf = qnull; /* Changes for lake model end here. */ /* Calculate net energy exchange at the snow surface */ RestTerm = ( NetRad + *SensibleHeat + *LatentHeat + *LatentHeatSub + *AdvectedEnergy + qnull ); *RefreezeEnergy = (SurfaceLiquidWater * Lf * Density)/(Dt * SECPHOUR); /* Melting, or partially refreeze surface water. */ if (TSurf == 0.0 && RestTerm > -(*RefreezeEnergy)) { *RefreezeEnergy = -RestTerm; /* available energy input over cold content used to melt, i.e. Qrf is negative value (energy out of pack)*/ RestTerm = 0.0; } else { /* Pack is getting colder. */ RestTerm += *RefreezeEnergy; /* add this positive value to the pack */ } return RestTerm; }
void latent_heat_from_snow(double AirDens, double EactAir, double Lv, double Press, double Ra, double TMean, double Vpd, double *LatentHeat, double *LatentHeatSublimation, double *VaporMassFlux, double *BlowingMassFlux, double *SurfaceMassFlux) { /********************************************************************** latent_heat_from_snow.c Laura Bowling Split out of the snowpack energy balance, this subroutine computes the latent heat from the snowpack. Modifications: 11-18-02 Modified to handle the effects of blowing snow. LCB 16-Jul-04 Moved the calculation of BlowingMassFlux back into this function. Added "month" to the parameter list and added declaration of *veg_lib, to enable this calculation to occur. Modified calculations of all sublimation terms to ensure that VaporMassFlux, BlowingMassFlux, and SurfaceMassFlux consistently have units of kg/m2s. TJB 05-Aug-04 Moved the calculation of BlowingMassFlux back out of this function into surface_fluxes(), as part of merge with Laura Bowling's latest code. TJB ***********************************************************************/ double EsSnow; double Ls; EsSnow = svp(TMean); // SurfaceMassFlux and BlowingMassFlux in kg/m2s *SurfaceMassFlux = AirDens * ( EPS / Press ) * ( EactAir - EsSnow ) / Ra; if ( Vpd == 0.0 && *SurfaceMassFlux < 0.0 ) *SurfaceMassFlux = 0.0; /* Calculate total latent heat flux */ *VaporMassFlux = *SurfaceMassFlux + *BlowingMassFlux; if ( TMean >= 0.0 ) { /* Melt conditions: use latent heat of vaporization */ *LatentHeat = Lv * (*VaporMassFlux); *LatentHeatSublimation = 0; } else { /* Accumulation: use latent heat of sublimation (Eq. 3.19, Bras 1990 */ Ls = (677. - 0.07 * TMean) * JOULESPCAL * GRAMSPKG; *LatentHeatSublimation = Ls * (*VaporMassFlux); *LatentHeat = 0; } }
/****************************************************************************** * @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 Initialize atmospheric variables for the model and snow time steps. *****************************************************************************/ void vic_force(atmos_data_struct *atmos, dmy_struct *dmy, FILE **infile, veg_con_struct *veg_con, veg_hist_struct **veg_hist, soil_con_struct *soil_con) { extern option_struct options; extern param_set_struct param_set; extern global_param_struct global_param; extern parameters_struct param; extern size_t NR, NF; size_t i; size_t j; size_t v; size_t rec; size_t uidx; double t_offset; double **forcing_data; double ***veg_hist_data; double avgJulyAirTemp; double *Tfactor; bool *AboveTreeLine; /******************************* Check that required inputs were supplied *******************************/ if (!param_set.TYPE[AIR_TEMP].SUPPLIED) { log_err("Air temperature must be supplied as a forcing"); } if (!param_set.TYPE[PREC].SUPPLIED) { log_err("Precipitation must be supplied as a forcing"); } if (!param_set.TYPE[SWDOWN].SUPPLIED) { log_err("Downward shortwave radiation must be supplied as a forcing"); } if (!param_set.TYPE[LWDOWN].SUPPLIED) { log_err("Downward longwave radiation must be supplied as a forcing"); } if (!param_set.TYPE[PRESSURE].SUPPLIED) { log_err("Atmospheric pressure must be supplied as a forcing"); } if (!param_set.TYPE[VP].SUPPLIED) { log_err("Vapor ressure must be supplied as a forcing"); } if (!param_set.TYPE[WIND].SUPPLIED) { log_err("Wind speed must be supplied as a forcing"); } /******************************* Miscellaneous initialization *******************************/ /* Assign local copies of some variables */ avgJulyAirTemp = soil_con->avgJulyAirTemp; Tfactor = soil_con->Tfactor; AboveTreeLine = soil_con->AboveTreeLine; /* Assign N_ELEM for veg-dependent forcings */ if (param_set.TYPE[ALBEDO].SUPPLIED) { param_set.TYPE[ALBEDO].N_ELEM = veg_con[0].vegetat_type_num; } if (param_set.TYPE[LAI_IN].SUPPLIED) { param_set.TYPE[LAI_IN].N_ELEM = veg_con[0].vegetat_type_num; } if (param_set.TYPE[FCANOPY].SUPPLIED) { param_set.TYPE[FCANOPY].N_ELEM = veg_con[0].vegetat_type_num; } /******************************* read in meteorological data *******************************/ forcing_data = read_forcing_data(infile, global_param, &veg_hist_data); log_info("Read meteorological forcing file"); /**************************************************** Variables in the atmos_data structure ****************************************************/ t_offset = Tfactor[0]; for (i = 1; i < options.SNOW_BAND; i++) { if (Tfactor[i] < t_offset) { t_offset = Tfactor[i]; } } for (rec = 0; rec < global_param.nrecs; rec++) { for (i = 0; i < NF; i++) { uidx = rec * NF + i; // temperature in Celsius atmos[rec].air_temp[i] = forcing_data[AIR_TEMP][uidx]; // precipitation in mm/period atmos[rec].prec[i] = forcing_data[PREC][uidx]; // downward shortwave in W/m2 atmos[rec].shortwave[i] = forcing_data[SWDOWN][uidx]; // downward longwave in W/m2 atmos[rec].longwave[i] = forcing_data[LWDOWN][uidx]; // pressure in Pa atmos[rec].pressure[i] = forcing_data[PRESSURE][uidx] * PA_PER_KPA; // vapor pressure in Pa atmos[rec].vp[i] = forcing_data[VP][uidx] * PA_PER_KPA; // vapor pressure deficit in Pa atmos[rec].vpd[i] = svp(atmos[rec].air_temp[i]) - atmos[rec].vp[i]; // air density in kg/m3 atmos[rec].density[i] = air_density(atmos[rec].air_temp[i], atmos[rec].pressure[i]); // wind speed in m/s atmos[rec].wind[i] = forcing_data[WIND][uidx]; // snow flag atmos[rec].snowflag[i] = will_it_snow(&(atmos[rec].air_temp[i]), t_offset, param.SNOW_MAX_SNOW_TEMP, &(atmos[rec].prec[i]), 1); // Optional inputs if (options.LAKES) { // Channel inflow from upstream (into lake) if (param_set.TYPE[CHANNEL_IN].SUPPLIED) { atmos[rec].channel_in[i] = forcing_data[CHANNEL_IN][uidx]; } else { atmos[rec].channel_in[i] = 0; } } if (options.CARBON) { // Atmospheric CO2 concentration atmos[rec].Catm[i] = forcing_data[CATM][uidx]; // Fraction of shortwave that is direct atmos[rec].fdir[i] = forcing_data[FDIR][uidx]; // photosynthetically active radiation atmos[rec].par[i] = forcing_data[PAR][uidx]; // Cosine of solar zenith angle atmos[rec].coszen[i] = compute_coszen(soil_con->lat, soil_con->lng, soil_con->time_zone_lng, dmy[rec].day_in_year, dmy[rec].dayseconds); } } if (NF > 1) { atmos[rec].air_temp[NR] = average(atmos[rec].air_temp, NF); // For precipitation put total atmos[rec].prec[NR] = average(atmos[rec].prec, NF) * NF; atmos[rec].shortwave[NR] = average(atmos[rec].shortwave, NF); atmos[rec].longwave[NR] = average(atmos[rec].longwave, NF); atmos[rec].pressure[NR] = average(atmos[rec].pressure, NF); atmos[rec].vp[NR] = average(atmos[rec].vp, NF); atmos[rec].vpd[NR] = average(atmos[rec].vpd, NF); atmos[rec].density[NR] = average(atmos[rec].density, NF); atmos[rec].wind[NR] = average(atmos[rec].wind, NF); atmos[rec].snowflag[NR] = false; for (i = 0; i < NF; i++) { if (atmos[rec].snowflag[i] == true) { atmos[rec].snowflag[NR] = true; } } if (options.LAKES) { atmos[rec].channel_in[NR] = average(atmos[rec].channel_in, NF) * NF; } if (options.CARBON) { atmos[rec].Catm[NR] = average(atmos[rec].Catm, NF); atmos[rec].fdir[NR] = average(atmos[rec].fdir, NF); atmos[rec].par[NR] = average(atmos[rec].par, NF); // for coszen, use value at noon atmos[rec].coszen[NR] = compute_coszen(soil_con->lat, soil_con->lng, soil_con->time_zone_lng, dmy[rec].day_in_year, SEC_PER_DAY / 2); } } } /**************************************************** Variables in the veg_hist structure ****************************************************/ /* First, assign default climatology */ for (rec = 0; rec < global_param.nrecs; rec++) { for (v = 0; v <= veg_con[0].vegetat_type_num; v++) { for (i = 0; i < NF; i++) { veg_hist[rec][v].albedo[i] = veg_con[v].albedo[dmy[rec].month - 1]; veg_hist[rec][v].displacement[i] = veg_con[v].displacement[dmy[rec].month - 1]; veg_hist[rec][v].fcanopy[i] = veg_con[v].fcanopy[dmy[rec].month - 1]; veg_hist[rec][v].LAI[i] = veg_con[v].LAI[dmy[rec].month - 1]; veg_hist[rec][v].roughness[i] = veg_con[v].roughness[dmy[rec].month - 1]; } } } /* Next, overwrite with veg_hist values, validate, and average */ for (rec = 0; rec < global_param.nrecs; rec++) { for (v = 0; v <= veg_con[0].vegetat_type_num; v++) { for (i = 0; i < NF; i++) { uidx = rec * NF + i; if (param_set.TYPE[ALBEDO].SUPPLIED && options.ALB_SRC == FROM_VEGHIST) { if (veg_hist_data[ALBEDO][v][uidx] != NODATA_VH) { veg_hist[rec][v].albedo[i] = veg_hist_data[ALBEDO][v][uidx]; } } if (param_set.TYPE[LAI_IN].SUPPLIED && options.LAI_SRC == FROM_VEGHIST) { if (veg_hist_data[LAI_IN][v][uidx] != NODATA_VH) { veg_hist[rec][v].LAI[i] = veg_hist_data[LAI_IN][v][uidx]; } } if (param_set.TYPE[FCANOPY].SUPPLIED && options.FCAN_SRC == FROM_VEGHIST) { if (veg_hist_data[FCANOPY][v][uidx] != NODATA_VH) { veg_hist[rec][v].fcanopy[i] = veg_hist_data[FCANOPY][v][uidx]; } } // Check on fcanopy if (veg_hist[rec][v].fcanopy[i] < MIN_FCANOPY) { log_warn( "rec %zu, veg %zu substep %zu fcanopy %f < minimum of %f; setting = %f\n", rec, v, i, veg_hist[rec][v].fcanopy[i], MIN_FCANOPY, MIN_FCANOPY); veg_hist[rec][v].fcanopy[i] = MIN_FCANOPY; } } if (NF > 1) { veg_hist[rec][v].albedo[NR] = average(veg_hist[rec][v].albedo, NF); veg_hist[rec][v].displacement[NR] = average( veg_hist[rec][v].displacement, NF); veg_hist[rec][v].fcanopy[NR] = average( veg_hist[rec][v].fcanopy, NF); veg_hist[rec][v].LAI[NR] = average(veg_hist[rec][v].LAI, NF); veg_hist[rec][v].roughness[NR] = average( veg_hist[rec][v].roughness, NF); } } } /**************************************************** Free forcing_data and veg_hist_data structures ****************************************************/ for (i = 0; i < N_FORCING_TYPES; i++) { if (param_set.TYPE[i].SUPPLIED) { if (i != ALBEDO && i != LAI_IN && i != FCANOPY) { free(forcing_data[i]); } else { for (j = 0; j < param_set.TYPE[i].N_ELEM; j++) { free(veg_hist_data[i][j]); } free(veg_hist_data[i]); } } } free(forcing_data); free(veg_hist_data); /**************************************************** Compute treeline based on July average temperature ****************************************************/ if (options.COMPUTE_TREELINE) { if (!(options.JULY_TAVG_SUPPLIED && avgJulyAirTemp == -999)) { compute_treeline(atmos, dmy, avgJulyAirTemp, Tfactor, AboveTreeLine); } } }
/***************************************************************************** Function name: SnowInterception() Purpose : Calculate snow interception and release by the canopy Required : int Dt - Model timestep (hours) double F - Fractional coverage double LAI - Leaf Area Index double MaxInt - Maximum rainfall interception storage (m) double Ra - Aerodynamic resistance (uncorrected for stability) (s/m) double *Ra_used - Aerodynamic resistance after adjustments (s/m) double AirDens - Density of air (kg/m3) double EactAir - Actual vapor pressure of air (Pa) double Lv - Latent heat of vaporization (J/kg3) PIXRAD *LocalRad - Components of radiation balance for current pixel (W/m2) double Press - Air pressure (Pa) double Tair - Air temperature (C) double Vpd - Vapor pressure deficit (Pa) double Wind - Wind speed (m/s) double *RainFall - Amount of rain (m) double *Snowfall - Amount of snow (m) double *IntRain - Intercepted rain (m) double *IntSnow - Snow water equivalent of intercepted snow (m) double *TempIntStorage - Temporary storage for snowmelt and rainfall involved in mass release calculations (m) double *VaporMassFlux - Vapor mass flux to/from intercepted snow (m/timestep) double *Tcanopy - Canopy temperature (C) double *MeltEnergy - Energy used in heating and melting of the snow (W/m2) Returns : none Modifies : double *Ra_used - Aerodynamic resistance after adjustments (s/m) double *RainFall - Amount of rain (m) double *Snowfall - Amount of snow (m) double *IntRain - Intercepted rain (m) double *IntSnow - Snow water equivalent of intercepted snow (m) double *TempIntStorage - Temporary storage for snowmelt and rainfall involved in mass release calculations (m) double *VaporMassFlux - Vapor mass flux to/from intercepted snow (m/timestep) double *Tcanopy - Canopy temperature (C) Comments : Only the top canopy layer is taken into account for snow interception. Snow interception by lower canopy is disregarded. Rain water CAN be intercepted by lower canopy layers (similar to InterceptionStorage()). Of course: NO vegetation -> NO interception Modifications: 06-98 included maximum structural loading to prevent the model from loading the canopy with more snow than it can handle structurally. PXS 09-98 aerodynamic resistances in the canopy when snow has been intercepted is increased by a factor of 10: include REF Journal of Hydrology, 1998 KAC, GO'D 28-Sep-04 Added Ra_used to store the aerodynamic resistance actually used in flux calculations. TJB *****************************************************************************/ void snow_intercept(double Dt, double F, double LAI, double MaxInt, double Ra, double *Ra_used, double AirDens, double EactAir, double Lv, double Shortwave, double Longwave, double Press, double Tair, double Vpd, double Wind, double *RainFall, double *SnowFall, double *IntRain, double *IntSnow, double *TempIntStorage, double *VaporMassFlux, double *Tcanopy, double *MeltEnergy, int month, int rec, int hour) { double AdvectedEnergy; /* Energy advected by the rain (W/m2) */ double BlownSnow; /* Depth of snow blown of the canopy (m) */ double DeltaSnowInt; /* Change in the physical swe of snow interceped on the branches. (m) */ double Drip; /* Amount of drip from intercepted snow as a result of snowmelt (m) */ double ExcessSnowMelt; /* Snowmelt in excess of the water holding capacity of the tree (m) */ double EsSnow; /* saturated vapor pressure in the snow pack (Pa) */ double InitialSnowInt; /* Initial intercepted snow (m) */ double InitialWaterInt; /* Initial intercepted water (snow and rain) (m) */ double LatentHeat; /* Latent heat flux (W/m2) */ double LongOut; /* Longwave radiation emitted by canopy (W/m2) */ double Ls; /* Latent heat of sublimation (J/(kg K) */ double MassBalanceError; /* Mass blalnce to make sure no water is being destroyed/created (m) */ double MaxWaterInt; /* Water interception capacity (m) */ double MaxSnowInt; /* Snow interception capacity (m) */ double NetRadiation; double PotSnowMelt; /* Potential snow melt (m) */ double RainThroughFall; /* Amount of rain reaching to the ground (m) */ double RefreezeEnergy; /* Energy available for refreezing or melt */ double ReleasedMass; /* Amount of mass release of intercepted snow (m) */ double SensibleHeat; /* Sensible heat flux (W/m2) */ double SnowThroughFall; /* Amount of snow reaching to the ground (m) */ double Tmp; /* Temporary variable */ double Imax1; /* maxium water intecept regardless of temp */ double IntRainFract; /* Fraction of intercpeted water which is liquid */ double IntSnowFract; /* Fraction of intercepted water which is solid */ double Overload; /* temp variable to calculated structural overloading */ /* Convert Units from VIC (mm -> m) */ *RainFall /= 1000.; *SnowFall /= 1000.; *IntRain /= 1000.; MaxInt /= 1000.; /* Initialize Drip, H2O balance, and mass release variables. */ InitialWaterInt = *IntSnow + *IntRain; *IntSnow /= F; *IntRain /= F; InitialSnowInt = *IntSnow; Drip = 0.0; ReleasedMass = 0.0; /* Determine the maximum snow interception water equivalent. Kobayashi, D., 1986, Snow Accumulation on a Narrow Board, Cold Regions Science and Technology, (13), pp. 239-245. Figure 4. */ Imax1 = 4.0* LAI_SNOW_MULTIPLIER * LAI; if (Tair < -1.0 && Tair > -3.0) MaxSnowInt = (Tair*3.0/2.0) + (11.0/2.0); else if (Tair > -1.0) MaxSnowInt = 4.0; else MaxSnowInt = 1.0; /* therefore LAI_ratio decreases as temp decreases */ MaxSnowInt *= LAI_SNOW_MULTIPLIER * LAI; /* Calculate snow interception. */ DeltaSnowInt = (1-*IntSnow/MaxSnowInt) * *SnowFall; if (DeltaSnowInt + *IntSnow > MaxSnowInt) DeltaSnowInt = MaxSnowInt - *IntSnow; if (DeltaSnowInt < 0.0) DeltaSnowInt = 0.0; /* Reduce the amount of intercepted snow if windy and cold. Ringyo Shikenjo Tokyo, #54, 1952. Bulletin of the Govt. Forest Exp. Station, Govt. Forest Exp. Station, Meguro, Tokyo, Japan. FORSTX 634.9072 R475r #54. Page 146, Figure 10. Reduce the amount of intercepted snow if snowing, windy, and cold (< -3 to -5 C). Schmidt and Troendle 1992 western snow conference paper. */ if (Tair < -3.0 && DeltaSnowInt > 0.0 && Wind > 1.0) { BlownSnow = (0.2 * Wind - 0.2) * DeltaSnowInt; if (BlownSnow >= DeltaSnowInt) BlownSnow = DeltaSnowInt; DeltaSnowInt -= BlownSnow; } /* now update snowfall and total accumulated intercepted snow amounts */ if (*IntSnow + DeltaSnowInt > Imax1) DeltaSnowInt =0.0; /* pixel depth */ SnowThroughFall = (*SnowFall - DeltaSnowInt) * F + (*SnowFall) * (1 - F); /* physical depth */ *IntSnow += DeltaSnowInt; /* Calculate amount of rain intercepted on branches and stored in intercepted snow. */ /* physical depth */ MaxWaterInt = LIQUID_WATER_CAPACITY * (*IntSnow) + MaxInt; if ((*IntRain + *RainFall) <= MaxWaterInt) { /* physical depth */ *IntRain += *RainFall; /* pixel depth */ RainThroughFall = *RainFall * (1 - F); } else { /* pixel depth */ RainThroughFall = (*IntRain + *RainFall - MaxWaterInt) * F + (*RainFall * (1 - F)); /* physical depth */ *IntRain = MaxWaterInt; } /* at this point we have calculated the amount of snowfall intercepted and the amount of rainfall intercepted. These values have been appropriately subtracted from SnowFall and RainFall to determine SnowThroughfall and RainThroughfall. However, we can end up with the condition that the total intercepted rain plus intercepted snow is greater than the maximum bearing capacity of the tree regardless of air temp (Imax1). The following routine will adjust *IntRain and *IntSnow by triggering mass release due to overloading. Of course since *IntRain and *IntSnow are mixed, we need to slough them of as fixed fractions */ if (*IntRain + *IntSnow > Imax1) { /*then trigger structural unloading*/ Overload = (*IntSnow + *IntRain) - Imax1; IntRainFract= *IntRain/(*IntRain + *IntSnow); IntSnowFract = *IntSnow/(*IntRain + *IntSnow); *IntRain = *IntRain - Overload*IntRainFract; *IntSnow = *IntSnow - Overload*IntSnowFract; RainThroughFall = RainThroughFall + (Overload*IntRainFract)*F; SnowThroughFall = SnowThroughFall + (Overload*IntSnowFract)*F; } /* The canopy temperature is assumed to be equal to the air temperature if the air temperature is below 0C, otherwise the canopy temperature is equal to 0C */ if (Tair > 0.) *Tcanopy = 0.; else *Tcanopy = Tair; /* Calculate the net radiation at the canopy surface, using the canopy temperature. The outgoing longwave is subtracted twice, because the canopy radiates in two directions */ Tmp = *Tcanopy + 273.15; LongOut = STEFAN_B * (Tmp * Tmp * Tmp * Tmp); NetRadiation = (1.-NEW_SNOW_ALB)*Shortwave + Longwave - 2 * F * LongOut; NetRadiation /= F; /* Calculate the vapor mass flux between the canopy and the surrounding air mass */ EsSnow = svp(*Tcanopy); *Ra_used = Ra; /** Added division by 10 to incorporate change in canopy resistance due to smoothing by intercepted snow **/ *VaporMassFlux = AirDens * (0.622/Press) * (EactAir - EsSnow) / *Ra_used / 10.0; *VaporMassFlux /= RHO_W; if (Vpd == 0.0 && *VaporMassFlux < 0.0) *VaporMassFlux = 0.0; /* Calculate the latent heat flux */ Ls = (677. - 0.07 * *Tcanopy) * 4.1868 * 1000.; LatentHeat = Ls * *VaporMassFlux * RHO_W; /* Calculate the sensible heat flux */ SensibleHeat = AirDens * Cp * (Tair - *Tcanopy) / *Ra_used; /** Multiply Ra by 10 to reflect the value used in calculating VaporMassFlux **/ *Ra_used *= 10.0; /* Calculate the advected energy */ AdvectedEnergy = (4186.8 * Tair * *RainFall)/(Dt * SECPHOUR); /* Calculate the amount of energy available for refreezing */ RefreezeEnergy = SensibleHeat + LatentHeat + NetRadiation + AdvectedEnergy; RefreezeEnergy *= Dt * SECPHOUR; /* if RefreezeEnergy is positive it means energy is available to melt the intercepted snow in the canopy. If it is negative, it means that intercepted water will be refrozen */ /* Update maximum water interception storage */ MaxWaterInt = LIQUID_WATER_CAPACITY * (*IntSnow) + MaxInt; /* Convert the vapor mass flux from a flux to a depth per interval */ *VaporMassFlux *= Dt * SECPHOUR; if (RefreezeEnergy > 0.0) { if (-(*VaporMassFlux) > *IntRain) { *VaporMassFlux = -(*IntRain); *IntRain = 0.; } else *IntRain += *VaporMassFlux; PotSnowMelt = min((RefreezeEnergy/Lf/RHO_W), *IntSnow); *MeltEnergy -= (Lf * PotSnowMelt * RHO_W) / (Dt *SECPHOUR); if ((*IntRain + PotSnowMelt) <= MaxWaterInt) { *IntSnow -= PotSnowMelt; *IntRain += PotSnowMelt; PotSnowMelt = 0.0; } else { ExcessSnowMelt = PotSnowMelt + *IntRain - MaxWaterInt; *IntSnow -= MaxWaterInt - (*IntRain); *IntRain = MaxWaterInt; if (*IntSnow < 0.0) *IntSnow = 0.0; if (SnowThroughFall > 0.0 && InitialSnowInt <= MIN_INTERCEPTION_STORAGE) { /* Water in excess of MaxWaterInt has been generated. If it is snowing and there was little intercepted snow at the beginning of the time step ( <= MIN_INTERCEPTION_STORAGE), then allow the snow to melt as it is intercepted */ Drip += ExcessSnowMelt; *IntSnow -= ExcessSnowMelt; if (*IntSnow < 0.0) *IntSnow = 0.0; } else /* Else, SnowThroughFall = 0.0 or SnowThroughFall > 0.0 and there is a substantial amount of intercepted snow at the beginning of the time step ( > MIN_INTERCEPTION_STORAGE). Snow melt may generate mass release. */ *TempIntStorage += ExcessSnowMelt; MassRelease(IntSnow, TempIntStorage, &ReleasedMass, &Drip); } /* If intercepted snow has melted, add the water it held to drip */ MaxWaterInt = LIQUID_WATER_CAPACITY * (*IntSnow) + MaxInt; if (*IntRain > MaxWaterInt) { Drip += *IntRain - MaxWaterInt; *IntRain = MaxWaterInt; } } else /* else (RefreezeEnergy <= 0.0) */ { /* Reset *TempIntStorage to 0.0 when energy balance is negative */ *TempIntStorage = 0.0; /* Refreeze as much surface water as you can */ if (RefreezeEnergy > - (*IntRain) * Lf) { *IntSnow += fabs(RefreezeEnergy) / Lf; *IntRain -= fabs(RefreezeEnergy) / Lf; *MeltEnergy += (fabs(RefreezeEnergy) * RHO_W) / (Dt *SECPHOUR); RefreezeEnergy = 0.0; } else { /* All of the water in the surface layer has been frozen. */ *IntSnow += *IntRain; /* Added on April 8 as a test */ /* RefreezeEnergy += *IntRain*Lf; */ /* *VaporMassFlux = MAX(*VaporMassFlux, */ /* RefreezeEnergy/(Ls * RHO_W)); */ /* Energy released by freezing of intercepted water is added to the MeltEnergy */ *MeltEnergy += (Lf * *IntRain * RHO_W) / (Dt *SECPHOUR); *IntRain = 0.0; } if (-(*VaporMassFlux) > *IntSnow) { *VaporMassFlux = -(*IntSnow); *IntSnow = 0.0; } else *IntSnow += *VaporMassFlux; } *IntSnow *= F; *IntRain *= F; *MeltEnergy *= F; *VaporMassFlux *= F; Drip *= F; ReleasedMass *= F; /* Calculate intercepted H2O balance. */ MassBalanceError = (InitialWaterInt - (*IntSnow + *IntRain)) + (*SnowFall + *RainFall) - (SnowThroughFall + RainThroughFall + Drip + ReleasedMass) + *VaporMassFlux; *RainFall = RainThroughFall + Drip; *SnowFall = SnowThroughFall + ReleasedMass; /* Convert Units to VIC (m -> mm) */ *VaporMassFlux *= -1.; *RainFall *= 1000.; *SnowFall *= 1000.; *IntRain *= 1000.; }
/****************************************************************************** * @brief Calculate sublimation from blowing snow *****************************************************************************/ double CalcBlowingSnow(double Dt, double Tair, unsigned LastSnow, double SurfaceLiquidWater, double Wind, double Ls, double AirDens, double EactAir, double ZO, double Zrh, double snowdepth, double lag_one, double sigma_slope, double Tsnow, int iveg, int Nveg, double fe, double displacement, double roughness, double *TotalTransport) { extern parameters_struct param; extern option_struct options; /* Local variables: */ double Age; double U10, Uo, prob_occurence; double es, Ros, F; double SubFlux; double Diffusivity; double ushear; double Tk; double utshear; int p; double upper, lower, Total; double area; double sigma_w; double Zo_salt; double ratio, wind10; double Uveg, hv, Nd; double Transport; /*******************************************************************/ /* Calculate some general variables, that don't depend on wind speed. */ /* Age in hours */ Age = LastSnow * Dt / SEC_PER_HOUR; /* Saturation density of water vapor, Liston A-8 */ es = svp(Tair); Tk = Tair + CONST_TKFRZ; Ros = CONST_EPS * es / (CONST_RDAIR * Tk); /* Diffusivity in m2/s, Liston eq. A-7 */ Diffusivity = (2.06e-5) * pow(Tk / 273., 1.75); // Essery et al. 1999, eq. 6 (m*s/kg) F = (Ls / (param.BLOWING_KA * Tk)) * (Ls * Tk / CONST_RDAIR - 1.); F += 1. / (Diffusivity * Ros); /* grid cell 10 m wind speed = 50th percentile wind */ /* Wind speed at 2 m above snow was passed to this function. */ wind10 = Wind * log(10. / ZO) / log((2 + ZO) / ZO); /* Check for bare soil case. */ if (iveg == Nveg) { fe = 1500; sigma_slope = .0002; } // sigma_w/uo: ratio = (2.44 - (0.43) * lag_one) * sigma_slope; sigma_w = wind10 * ratio; Uo = wind10; /*********** Parameters for roughness above snow. *****************/ hv = (3. / 2.) * displacement; Nd = (4. / 3.) * (roughness / displacement); /*******************************************************************/ /** Begin loop through wind probability function. */ Total = 0.0; *TotalTransport = 0.0; area = 1. / (double) param.BLOWING_NUMINCS; if (snowdepth > 0.0) { if (options.BLOWING_SPATIAL_WIND && sigma_w != 0.) { for (p = 0; p < param.BLOWING_NUMINCS; p++) { SubFlux = lower = upper = 0.0; /* Find the limits of integration. */ if (p == 0) { lower = -9999; upper = Uo + sigma_w * log(2. * (p + 1) * area); } else if (p > 0 && p < param.BLOWING_NUMINCS / 2) { lower = Uo + sigma_w * log(2. * (p) * area); upper = Uo + sigma_w * log(2. * (p + 1) * area); } else if (p < (param.BLOWING_NUMINCS - 1) && p >= (double) param.BLOWING_NUMINCS / 2) { lower = Uo - sigma_w * log(2. - 2. * (p * area)); upper = Uo - sigma_w * log(2. - 2. * ((p + 1.) * area)); } else if (p == param.BLOWING_NUMINCS - 1) { lower = Uo - sigma_w * log(2. - 2. * (p * area)); upper = 9999; } if (lower > upper) { /* Could happen if lower > Uo*2 */ lower = upper; log_err("Error with probability boundaries"); } /* Find expected value of wind speed for the interval. */ U10 = Uo; if (lower >= Uo) { U10 = -0.5 * ((upper + sigma_w) * exp((-1. / sigma_w) * (upper - Uo)) - (lower + sigma_w) * exp((-1. / sigma_w) * (lower - Uo))) / area; } else if (upper <= Uo) { U10 = 0.5 * ((upper - sigma_w) * exp((1. / sigma_w) * (upper - Uo)) - (lower - sigma_w) * exp((1. / sigma_w) * (lower - Uo))) / area; } else { log_err("Problem with probability ranges: Increment = %d, " "integration limits = %f - %f", p, upper, lower); } if (U10 < 0.4) { U10 = .4; } if (U10 > 25.) { U10 = 25.; } /*******************************************************************/ /* Calculate parameters for probability of blowing snow occurence. */ /* ( Li and Pomeroy 1997) */ if (snowdepth < hv) { Uveg = U10 / sqrt(1. + 170 * Nd * (hv - snowdepth)); } else { Uveg = U10; } prob_occurence = get_prob(Tair, Age, SurfaceLiquidWater, Uveg); /*******************************************************************/ /* Calculate threshold shear stress. Send 0 for constant or */ /* 1 for variable threshold after Li and Pomeroy (1997) */ utshear = get_thresh(Tair, SurfaceLiquidWater, ZO); /* Iterate to find actual shear stress during saltation. */ shear_stress(U10, ZO, &ushear, &Zo_salt, utshear); if (ushear > utshear) { SubFlux = CalcSubFlux(EactAir, es, Zrh, AirDens, utshear, ushear, fe, Tsnow, Tair, U10, Zo_salt, F, &Transport); } else { SubFlux = 0.0; Transport = 0.0; } Total += (1. / (double) param.BLOWING_NUMINCS) * SubFlux * prob_occurence; *TotalTransport += (1. / (double) param.BLOWING_NUMINCS) * Transport * prob_occurence; } } else { U10 = Uo; /*******************************************************************/ /* Calculate parameters for probability of blowing snow occurence. */ /* ( Li and Pomeroy 1997) */ if (snowdepth < hv) { Uveg = U10 / sqrt(1. + 170 * Nd * (hv - snowdepth)); } else { Uveg = U10; } prob_occurence = get_prob(Tair, Age, SurfaceLiquidWater, Uveg); /*******************************************************************/ /* Calculate threshold shear stress. Send 0 for constant or */ /* 1 for variable threshold after Li and Pomeroy (1997) */ utshear = get_thresh(Tair, SurfaceLiquidWater, ZO); /* Iterate to find actual shear stress during saltation. */ shear_stress(Uo, ZO, &ushear, &Zo_salt, utshear); if (ushear > utshear) { SubFlux = CalcSubFlux(EactAir, es, Zrh, AirDens, utshear, ushear, fe, Tsnow, Tair, Uo, Zo_salt, F, &Transport); } else { SubFlux = 0.0; Transport = 0.0; } Total = SubFlux * prob_occurence; *TotalTransport = Transport * prob_occurence; } } if (Total < -.00005) { Total = -.00005; } return Total; }
/***************************************************************************** Function name: CalcBlowingSnow() Purpose : Calculate sublimation from blowing snow Required : double Dt; Model time step (hours) double Tair; Air temperature (C) int LastSnow; Time steps since last snowfall. double SurfaceLiquidWater; Liquid water in the surface layer (m) double Wind; Wind speed (m/s), 2 m above snow double Ls; Latent heat of sublimation (J/kg) double AirDens; Density of air (kg/m3) double Lv; Latent heat of vaporization (J/kg3) double Press; Air pressure (Pa) double EactAir; Actual vapor pressure of air (Pa) double ZO; Snow roughness height (m) Returns : BlowingMassFlux Modifies : Comments : Called from SnowPackEnergyBalance Reference: *****************************************************************************/ double CalcBlowingSnow( double Dt, double Tair, int LastSnow, double SurfaceLiquidWater, double Wind, double Ls, double AirDens, double Press, double EactAir, double ZO, double Zrh, double snowdepth, float lag_one, float sigma_slope, double Tsnow, int iveg, int Nveg, float fe, double displacement, double roughness, double *TotalTransport) { /* Local variables: */ double Age; double U10, Uo, prob_occurence; double es, Ros, F; double SubFlux; double Diffusivity; double ushear, Qsalt, hsalt, phi_s, psi_s; double Tk; double Lv; double T, ztop; double ut10, utshear; int p; double upper, lower, Total; double area; double sigma_w; double undersat_2; double b, temp2; /* SBSM scaling parameter. */ double temp, temp3; double Zo_salt; double ratio, wind10; double Uveg, hv, Nd; double Transport; int count=0; Lv = (2.501e6 - 0.002361e6 * Tsnow); /*******************************************************************/ /* Calculate some general variables, that don't depend on wind speed. */ /* Age in hours */ Age = LastSnow*(Dt); /* Saturation density of water vapor, Liston A-8 */ es = svp(Tair); Tk = Tair + KELVIN; Ros = 0.622*es/(287*Tk); /* Diffusivity in m2/s, Liston eq. A-7 */ Diffusivity = (2.06e-5) * pow(Tk/273.,1.75); // Essery et al. 1999, eq. 6 (m*s/kg) F = (Ls/(Ka*Tk))*(Ls*MW/(R*Tk) - 1.); F += 1./(Diffusivity*Ros); /* grid cell 10 m wind speed = 50th percentile wind */ /* Wind speed at 2 m above snow was passed to this function. */ wind10 = Wind*log(10./ZO)/log((2+ZO)/ZO); // fprintf(stderr,"wind=%f, Uo=%f\n",Wind, Uo); /* Check for bare soil case. */ if(iveg == Nveg) { fe = 1500; sigma_slope = .0002; } // sigma_w/uo: ratio = (2.44 - (0.43)*lag_one)*sigma_slope; // sigma_w = wind10/(.69+(1/ratio)); // Uo = sigma_w/ratio; sigma_w = wind10*ratio; Uo = wind10; /*********** Parameters for roughness above snow. *****************/ hv = (3./2.)*displacement; Nd = (4./3.)*(roughness/displacement); /*******************************************************************/ /** Begin loop through wind probability function. */ Total = 0.0; *TotalTransport = 0.0; area = 1./NUMINCS; if(snowdepth > 0.0) { if(SPATIAL_WIND && sigma_w != 0.) { for(p= 0; p< NUMINCS; p++) { SubFlux = lower = upper = 0.0; /* Find the limits of integration. */ if(p==0) { lower = -9999; upper = Uo + sigma_w*log(2.*(p+1)*area); } else if(p > 0 && p < NUMINCS/2) { lower = Uo + sigma_w*log(2.*(p)*area); upper = Uo + sigma_w*log(2.*(p+1)*area); } else if(p < (NUMINCS-1) && p >= NUMINCS/2) { lower = Uo - sigma_w*log(2.-2.*(p*area)); upper = Uo - sigma_w*log(2.-2.*((p+1.)*area)); } else if(p == NUMINCS-1) { lower = Uo - sigma_w*log(2.-2.*(p*area)); upper = 9999; } if(lower > upper) {/* Could happen if lower > Uo*2 */ lower = upper; fprintf(stderr,"Warning: Error with probability boundaries in CalcBlowingSnow()\n"); } /* Find expected value of wind speed for the interval. */ U10 = Uo; if(lower >= Uo ) U10 = -0.5*((upper+sigma_w)*exp((-1./sigma_w)*(upper - Uo)) - (lower+sigma_w)*exp((-1./sigma_w)*(lower - Uo)))/area; else if(upper <= Uo ) U10 = 0.5*((upper-sigma_w)*exp((1./sigma_w)*(upper - Uo)) - (lower-sigma_w)*exp((1./sigma_w)*(lower - Uo)))/area; else { fprintf(stderr,"ERROR in CalcBlowingSnow.c: Problem with probability ranges\n"); fprintf(stderr," Increment = %d, integration limits = %f - %f\n",p,upper, lower); return ( ERROR ); } if(U10 < 0.4) U10 = .4; if(U10 > 25.) U10 = 25.; /*******************************************************************/ /* Calculate parameters for probability of blowing snow occurence. */ /* ( Li and Pomeroy 1997) */ if(snowdepth < hv) { Uveg = U10/sqrt(1.+ 170*Nd*(hv - snowdepth)); } else Uveg = U10; // fprintf(stderr, "Uveg = %f, U10 = %f\n",Uveg, U10); prob_occurence = get_prob(Tair, Age, SurfaceLiquidWater, Uveg); // printf("prob=%f\n",prob_occurence); /*******************************************************************/ /* Calculate threshold shear stress. Send 0 for constant or */ /* 1 for variable threshold after Li and Pomeroy (1997) */ utshear = get_thresh(Tair, SurfaceLiquidWater, ZO, VAR_THRESHOLD); /* Iterate to find actual shear stress during saltation. */ shear_stress(U10, ZO, &ushear, &Zo_salt, utshear); if(ushear > utshear) { SubFlux = CalcSubFlux(EactAir, es, Zrh, AirDens, utshear,ushear, fe, Tsnow, Tair, U10, Zo_salt, F, &Transport); } else { SubFlux=0.0; Transport = 0.0; } Total += (1./NUMINCS)*SubFlux*prob_occurence; *TotalTransport += (1./NUMINCS)*Transport*prob_occurence; } } else { U10=Uo; /*******************************************************************/ /* Calculate parameters for probability of blowing snow occurence. */ /* ( Li and Pomeroy 1997) */ if(snowdepth < hv) Uveg = U10/sqrt(1.+ 170*Nd*(hv - snowdepth)); else Uveg = U10; prob_occurence = get_prob(Tair, Age, SurfaceLiquidWater, Uveg); /*******************************************************************/ /* Calculate threshold shear stress. Send 0 for constant or */ /* 1 for variable threshold after Li and Pomeroy (1997) */ utshear = get_thresh(Tair, SurfaceLiquidWater, ZO, VAR_THRESHOLD); /* Iterate to find actual shear stress during saltation. */ shear_stress(Uo, ZO, &ushear, &Zo_salt, utshear); if(ushear > utshear) { SubFlux = CalcSubFlux(EactAir, es, Zrh, AirDens, utshear,ushear, fe, Tsnow, Tair, Uo, Zo_salt, F, &Transport); } else { SubFlux=0.0; Transport = 0.0; } Total = SubFlux*prob_occurence; *TotalTransport = Transport*prob_occurence; } } if(Total < -.00005) Total = -.00005; return Total; }