//--------------------------------------------------------------------------- private static double SoilMoistureMultiplier(AnnualClimate weather, ISpecies species, double dryDays) { double sppAllowableDrought = SpeciesData.MaxDrought[species]; double growDays = 0.0; double maxDrought; double Soil_Moist_GF = 0.0; growDays = weather.EndGrowing - weather.BeginGrowing + 1.0; if (growDays < 2.0) { PlugIn.ModelCore.UI.WriteLine("Begin Grow = {0}, End Grow = {1}", weather.BeginGrowing, weather.EndGrowing); throw new System.ApplicationException("Error: Too few growing days."); } //Calc species soil moisture multipliers maxDrought = sppAllowableDrought * growDays; if (maxDrought < dryDays) { Soil_Moist_GF = 0.0; } else { Soil_Moist_GF = System.Math.Sqrt((double)(maxDrought - dryDays) / maxDrought); } //PlugIn.ModelCore.UI.WriteLine("BeginGrow={0}, EndGrow={1}, dryDays={2}, maxDrought={3}", weather.BeginGrowing, weather.EndGrowing, dryDays, maxDrought); return(Soil_Moist_GF); }
//--------------------------------------------------------------------------- private static double SoilMoistureMultiplier(AnnualClimate weather, ISpecies species, double dryDays) //Calc soil moisture multipliers based on Degree_Day (supplied by calc_temperature()), //dryDays (supplied by MOIST). { double sppAllowableDrought = SpeciesData.MaxDrought[species]; double growDays = 0.0; double maxDrought; double Soil_Moist_GF = 0.0; growDays = weather.EndGrowing - weather.BeginGrowing + 1.0; if (growDays < 2.0) { PlugIn.ModelCore.UI.WriteLine("Begin Grow = {0}, End Grow = {1}", weather.BeginGrowing, weather.EndGrowing); throw new System.ApplicationException("Error: Too few growing days."); } //Calc species soil moisture multipliers maxDrought = sppAllowableDrought * growDays; //PlugIn.ModelCore.UI.WriteLine("SppMaxDr={0:0.00}, growDays={1:0.0}, dryDays={2:0.0}.", sppAllowableDrought, growDays, dryDays); if (maxDrought < dryDays) { Soil_Moist_GF = 0.0; } else { Soil_Moist_GF = System.Math.Sqrt((double)(maxDrought - dryDays) / maxDrought); } return(Soil_Moist_GF); }
//--------------------------------------------------------------------------- private static double BotkinDegreeDayMultiplier(AnnualClimate weather, ISpecies species) { //Calc species degree day multipliers //Botkin et al. 1972. J. Ecol. 60:849 - 87 double max_Grow_Deg_Days = SpeciesData.GDDmax[species]; double min_Grow_Deg_Days = SpeciesData.GDDmin[species]; double Deg_Day_GF = 0.0; double Deg_Days = (double)weather.GrowingDegreeDays; double totalGDD = max_Grow_Deg_Days - min_Grow_Deg_Days; Deg_Day_GF = (4.0 * (Deg_Days - min_Grow_Deg_Days) * (max_Grow_Deg_Days - Deg_Days)) / (totalGDD * totalGDD); if (Deg_Day_GF < 0) { Deg_Day_GF = 0.0; } return(Deg_Day_GF); }
//--------------------------------------------------------------------------- private static double BotkinDegreeDayMultiplier(AnnualClimate weather, ISpecies species) { //Calc species degree day multipliers //Botkin et al. 1972. J. Ecol. 60:849 - 87 double max_Grow_Deg_Days = SpeciesData.GDDmax[species]; double min_Grow_Deg_Days = SpeciesData.GDDmin[species]; double Deg_Day_GF = 0.0; double Deg_Days = (double)weather.GrowingDegreeDays; double totalGDD = max_Grow_Deg_Days - min_Grow_Deg_Days; Deg_Day_GF = (4.0 * (Deg_Days - min_Grow_Deg_Days) * (max_Grow_Deg_Days - Deg_Days)) / (totalGDD * totalGDD); if (Deg_Day_GF < 0) { Deg_Day_GF = 0.0; } //PlugIn.ModelCore.UI.WriteLine("SppMaxDD={0:0.00}, sppMinGDD={1:0.0}, actualGDD={2:0}, gddM={3:0.00}.", max_Grow_Deg_Days, min_Grow_Deg_Days, Deg_Days, Deg_Day_GF); return(Deg_Day_GF); }
//--------------------------------------------------------------------------- private static double CalculateSoilMoisture(AnnualClimate_Monthly weather, IEcoregion ecoregion, int year, ActiveSite site) // Calculate fraction of growing season with unfavorable soil moisture // for growth (Dry_Days_in_Grow_Seas) used in SoilMoistureMultiplier to determine soil // moisture growth multipliers. // // Simulates method of Thorthwaite and Mather (1957) as modified by Pastor and Post (1984). // // This method necessary to estimate annual soil moisture at the ECOREGION scale, whereas // the SiteVar AvailableWater exists at the site level and is updated monthly. //field_cap = centimeters of water the soil can hold at field capacity //field_dry = centimeters of water below which tree growth stops // (-15 bars) // NOTE: Because the original LINKAGES calculations were based on a 100 cm rooting depth, // 100 cm are used here, although soil depth may be given differently for Century // calculations. //beg_grow_seas = year day on which the growing season begins //end_grow_seas = year day on which the growing season ends //latitude = latitude of region (degrees north) { double xFieldCap, // waterAvail, // aExponentET, // oldWaterAvail, // monthlyRain, // potWaterLoss, // potentialET, tempFac, // xAccPotWaterLoss, // changeSoilMoisture, // oldJulianDay, // dryDayInterp; // double fieldCapacity = SiteVars.SoilFieldCapacity[site] * (double)SiteVars.SoilDepth[site]; // ClimateRegionData.SoilDepth[ecoregion]; double wiltingPoint = SiteVars.SoilWiltingPoint[site] * (double)SiteVars.SoilDepth[site]; // ClimateRegionData.SoilDepth[ecoregion]; //double fieldCapacity = ClimateRegionData.FieldCapacity[ecoregion] * 100.0; //double wiltingPoint = ClimateRegionData.WiltingPoint[ecoregion] * 100.0; //Initialize water content of soil in January to Field_Cap (mm) xFieldCap = 10.0 * fieldCapacity; waterAvail = fieldCapacity; //Initialize Thornwaithe parameters: // //TE = temperature efficiency //aExponentET = exponent of evapotranspiration function //pot_et = potential evapotranspiration //aet = actual evapotranspiration //acc_pot_water_loss = accumulated potential water loss double actualET = 0.0; double accPotWaterLoss = 0.0; double tempEfficiency = 0.0; for (int i = 0; i < 12; i++) { tempFac = 0.2 * weather.MonthlyTemp[i]; if (tempFac > 0.0) { tempEfficiency += System.Math.Pow(tempFac, 1.514); } } aExponentET = 0.675 * System.Math.Pow(tempEfficiency, 3) - 77.1 * (tempEfficiency * tempEfficiency) + 17920.0 * tempEfficiency + 492390.0; aExponentET *= (0.000001); //Initialize the number of dry days and current day of year int dryDays = 0; double julianDay = 15.0; double annualPotentialET = 0.0; for (int i = 0; i < 12; i++) { double daysInMonth = AnnualClimate.DaysInMonth(i, year); oldWaterAvail = waterAvail; monthlyRain = weather.MonthlyPrecip[i]; tempFac = 10.0 * weather.MonthlyTemp[i]; //Calc potential evapotranspiriation (potentialET) Thornwaite and Mather, //1957. Climatology 10:83 - 311. if (tempFac > 0.0) { potentialET = 1.6 * (System.Math.Pow((tempFac / tempEfficiency), aExponentET)) * AnnualClimate.LatitudeCorrection(i, PlugIn.Parameters.Latitude); //ClimateRegionData.Latitude[ecoregion]); } else { potentialET = 0.0; } annualPotentialET += potentialET; //Calc potential water loss this month potWaterLoss = monthlyRain - potentialET; //If monthlyRain doesn't satisfy potentialET, add this month's potential //water loss to accumulated water loss from soil if (potWaterLoss < 0.0) { accPotWaterLoss += potWaterLoss; xAccPotWaterLoss = accPotWaterLoss * 10; //Calc water retained in soil given so much accumulated potential //water loss Pastor and Post. 1984. Can. J. For. Res. 14:466:467. waterAvail = fieldCapacity * System.Math.Exp((.000461 - 1.10559 / xFieldCap) * (-1.0 * xAccPotWaterLoss)); if (waterAvail < 0.0) { waterAvail = 0.0; } //changeSoilMoisture - during this month changeSoilMoisture = waterAvail - oldWaterAvail; //Calc actual evapotranspiration (AET) if soil water is drawn down actualET += (monthlyRain - changeSoilMoisture); } //If monthlyRain satisfies potentialET, don't draw down soil water else { waterAvail = oldWaterAvail + potWaterLoss; if (waterAvail >= fieldCapacity) { waterAvail = fieldCapacity; } changeSoilMoisture = waterAvail - oldWaterAvail; //If soil partially recharged, reduce accumulated potential //water loss accordingly accPotWaterLoss += changeSoilMoisture; //If soil completely recharged, reset accumulated potential //water loss to zero if (waterAvail >= fieldCapacity) { accPotWaterLoss = 0.0; } //If soil water is not drawn upon, add potentialET to AET actualET += potentialET; } oldJulianDay = julianDay; julianDay += daysInMonth; dryDayInterp = 0.0; //Increment number of dry days, truncate //at end of growing season if ((julianDay > weather.BeginGrowing) && (oldJulianDay < weather.EndGrowing)) { if ((oldWaterAvail >= wiltingPoint) && (waterAvail >= wiltingPoint)) { dryDayInterp += 0.0; // NONE below wilting point } else if ((oldWaterAvail > wiltingPoint) && (waterAvail < wiltingPoint)) { dryDayInterp = daysInMonth * (wiltingPoint - waterAvail) / (oldWaterAvail - waterAvail); if ((oldJulianDay < weather.BeginGrowing) && (julianDay > weather.BeginGrowing)) { if ((julianDay - weather.BeginGrowing) < dryDayInterp) { dryDayInterp = julianDay - weather.BeginGrowing; } } if ((oldJulianDay < weather.EndGrowing) && (julianDay > weather.EndGrowing)) { dryDayInterp = weather.EndGrowing - julianDay + dryDayInterp; } if (dryDayInterp < 0.0) { dryDayInterp = 0.0; } } else if ((oldWaterAvail < wiltingPoint) && (waterAvail > wiltingPoint)) { dryDayInterp = daysInMonth * (wiltingPoint - oldWaterAvail) / (waterAvail - oldWaterAvail); if ((oldJulianDay < weather.BeginGrowing) && (julianDay > weather.BeginGrowing)) { dryDayInterp = oldJulianDay + dryDayInterp - weather.BeginGrowing; } if (dryDayInterp < 0.0) { dryDayInterp = 0.0; } if ((oldJulianDay < weather.EndGrowing) && (julianDay > weather.EndGrowing)) { if ((weather.EndGrowing - oldJulianDay) < dryDayInterp) { dryDayInterp = weather.EndGrowing - oldJulianDay; } } } else // ALL below wilting point { dryDayInterp = daysInMonth; if ((oldJulianDay < weather.BeginGrowing) && (julianDay > weather.BeginGrowing)) { dryDayInterp = julianDay - weather.BeginGrowing; } if ((oldJulianDay < weather.EndGrowing) && (julianDay > weather.EndGrowing)) { dryDayInterp = weather.EndGrowing - oldJulianDay; } } dryDays += (int)dryDayInterp; } } //END MONTHLY CALCULATIONS //Convert AET from cm to mm //actualET *= 10.0; //Calculate AET multiplier //(used to be done in decomp) //float aetMf = min((double)AET,600.0); //AET_Mult = (-1. * aetMf) / (-1200. + aetMf); return(dryDays); }
public static void Run(int year, int month, double liveBiomass, Site site, out double baseFlow, out double stormFlow, out double AET) { //Originally from h2olos.f of CENTURY model //Water Submodel for Century - written by Bill Parton // Updated from Fortran 4 - rm 2/92 // Rewritten by Bill Pulliam - 9/94 // Rewritten by Melissa Lucash- 11/2014 //PlugIn.ModelCore.UI.WriteLine("month={0}.", Main.Month); //...Initialize Local Variables double addToSoil = 0.0; double bareSoilEvap = 0.0; baseFlow = 0.0; double relativeWaterContent = 0.0; double snow = 0.0; stormFlow = 0.0; double actualET = 0.0; double remainingPET = 0.0; double availableWaterMax = 0.0; //amount of water available after precipitation and snowmelt (over-estimate of available water) //double availableWaterMin = 0.0; //amount of water available after stormflow (runoff) evaporation and transpiration, but before baseflow/leaching (under-estimate of available water) double availableWater = 0.0; //amount of water deemed available to the trees, which will be the average between the max and min double priorWaterAvail = SiteVars.AvailableWater[site]; double waterFull = 0.0; //...Calculate external inputs IEcoregion ecoregion = PlugIn.ModelCore.Ecoregion[site]; double litterBiomass = (SiteVars.SurfaceStructural[site].Carbon + SiteVars.SurfaceMetabolic[site].Carbon) * 2.0; double deadBiomass = SiteVars.SurfaceDeadWood[site].Carbon / 0.47; double soilWaterContent = SiteVars.SoilWaterContent[site]; double liquidSnowpack = SiteVars.LiquidSnowPack[site]; H2Oinputs = ClimateRegionData.AnnualWeather[ecoregion].MonthlyPrecip[month]; //rain + irract in cm; Precipitation = ClimateRegionData.AnnualWeather[ecoregion].MonthlyPrecip[month]; //rain + irract in cm; tave = ClimateRegionData.AnnualWeather[ecoregion].MonthlyTemp[month]; tmax = ClimateRegionData.AnnualWeather[ecoregion].MonthlyMaxTemp[month]; tmin = ClimateRegionData.AnnualWeather[ecoregion].MonthlyMinTemp[month]; pet = ClimateRegionData.AnnualWeather[ecoregion].MonthlyPET[month]; daysInMonth = AnnualClimate.DaysInMonth(month, year); beginGrowing = ClimateRegionData.AnnualWeather[ecoregion].BeginGrowing; endGrowing = ClimateRegionData.AnnualWeather[ecoregion].EndGrowing; double wiltingPoint = SiteVars.SoilWiltingPoint[site]; double soilDepth = SiteVars.SoilDepth[site]; double fieldCapacity = SiteVars.SoilFieldCapacity[site]; double stormFlowFraction = SiteVars.SoilStormFlowFraction[site]; double baseFlowFraction = SiteVars.SoilBaseFlowFraction[site]; double drain = SiteVars.SoilDrain[site]; //...Calculating snow pack first. Occurs when mean monthly air temperature is equal to or below freezing, // precipitation is in the form of snow. if (tmin <= 0.0) // Use tmin to dictate whether it snows or rains. { snow = H2Oinputs; H2Oinputs = 0.0; liquidSnowpack += snow; //only tracking liquidsnowpack (water equivalent) and not the actual amount of snow on the ground (i.e. not snowpack). //PlugIn.ModelCore.UI.WriteLine("Let it snow!! snow={0}, liquidsnowpack={1}.", snow, liquidSnowpack); } else { //PH: Accumulate precipitation and snowmelt before adding to soil so that interception and soil evaporation can come out first addToSoil += H2Oinputs; //soilWaterContent += H2Oinputs; //PlugIn.ModelCore.UI.WriteLine("Let it rain and add it to soil! rain={0}, soilWaterContent={1}.", H2Oinputs, soilWaterContent); } //...Then melt snow if there is snow on the ground and air temperature (tmax) is above minimum. if (liquidSnowpack > 0.0 && tmax > 0.0) { //...Calculate the amount of snow to melt: //This relationship ultimately derives from http://www.nps.gov/yose/planyourvisit/climate.htm which described the relationship between snow melting and air temp. //Documentation for the regression equation is in spreadsheet called WaterCalcs.xls by M. Lucash double snowMeltFraction = Math.Max((tmax * 0.05) + 0.024, 0.0);//This equation assumes a linear increase in the fraction of snow that melts as a function of air temp. if (snowMeltFraction > 1.0) { snowMeltFraction = 1.0; } //PH: addToSoil += liquidSnowpack * snowMeltFraction; //PH: Add melted snow to addToSoil. Amount of liquidsnowpack that melts = liquidsnowpack multiplied by the fraction that melts. //addToSoil = liquidSnowpack * snowMeltFraction; //Amount of liquidsnowpack that melts = liquidsnowpack multiplied by the fraction that melts. //Subtracted melted snow from snowpack and add it to the soil liquidSnowpack = liquidSnowpack - addToSoil; //PH: Add to soil later. //soilWaterContent += addToSoil; } //Calculate the max amout of water available to trees, an over-estimate of the water available to trees. It only reflects precip and melting of precip. //PH: available water may need to be calculated differently with my proposed changes, but I moved this variable down for now so that soilEvaporation comes out first. //availableWaterMax = soilWaterContent; //...Evaporate water from the snow pack (rewritten by Pulliam 9/94) //...Coefficient 0.87 relates to heat of fusion for ice vs. liquid water if (liquidSnowpack > 0.0) { //...Calculate cm of snow that remaining pet energy can evaporate: double evaporatedSnow = pet * 0.87; //...Don't evaporate more snow than actually exists: if (evaporatedSnow > liquidSnowpack) { evaporatedSnow = liquidSnowpack; } liquidSnowpack = liquidSnowpack - evaporatedSnow; //...Decrement remaining pet by energy used to evaporate snow: //PH: CENTURY code divides evaporatedSnow by 0.87 so it matches the PET used to melt snow. remainingPET = pet - evaporatedSnow / 0.87; //remainingPET = pet - evaporatedSnow; if (remainingPET < 0.0) { remainingPET = 0.0; } //Subtract evaporated snowfrom the soil water content //PH: Take evaporated snow out of snowmelt instead of soil...or is that double counting evaporation? Could this cause addToSoil to go negative? //It is already taken out of the snowpack, and is used to decrement PET so that it won't affect AET addToSoil -= evaporatedSnow; if (addToSoil < 0.0) { addToSoil = 0.0; } //soilWaterContent -= evaporatedSnow; } // ******************************************************** //...Calculate bare soil water loss and interception when air temperature is above freezing and no snow cover. //...Mofified 9/94 to allow interception when t < 0 but no snow cover, Pulliam //PH: I moved this up to remove intercepted precipitation and bare soil evaporation from accumulated precipitation and snowmelt before it goes to the soil. if (liquidSnowpack <= 0.0) { //...Calculate total canopy cover and litter, put cap on effects: double standingBiomass = liveBiomass + deadBiomass; if (standingBiomass > 800.0) { standingBiomass = 800.0; } if (litterBiomass > 400.0) { litterBiomass = 400.0; } //...canopy interception, fraction of precip (canopyIntercept): double canopyIntercept = ((0.0003 * litterBiomass) + (0.0006 * standingBiomass)) * OtherData.WaterLossFactor1; //...Bare soil evaporation, fraction of precip (bareSoilEvap): bareSoilEvap = 0.5 * System.Math.Exp((-0.002 * litterBiomass) - (0.004 * standingBiomass)) * OtherData.WaterLossFactor2; //...Calculate total surface evaporation losses, maximum allowable is 0.4 * pet. -rm 6/94 remainingPET = pet; double soilEvaporation = System.Math.Min(((bareSoilEvap + canopyIntercept) * H2Oinputs), (0.4 * remainingPET)); //Subtract soil evaporation from soil water content //PH: Subtract soilEvaporation from addToSoil so it won't drive down soil water. //PH: SoilEvaporation represents water that evaporates before reaching soil, so should not be subtracted from soil. addToSoil -= soilEvaporation; //soilWaterContent -= soilEvaporation; } //PH: Add liquid water to soil soilWaterContent += addToSoil; //Calculate the max amout of water available to trees, an over-estimate of the water available to trees. It only reflects precip and melting of precip. //PH: Moved down so that soilEvaporation comes out first. availableWaterMax = soilWaterContent; // ******************************************************** // Calculate actual evapotranspiration. This equation is derived from the stand equation for calculating AET from PET // Bergström, 1992 // ******************************************************** //PH: Moved up to take evapotranspiration out before excess drains away. This is different from the CENTURY approach, where evaporation is taken out of the add first, but is //less complex because it does not require partitioning the evaporation if evapotranspiration exceeds addToSoil. double waterEmpty = wiltingPoint * soilDepth; waterFull = soilDepth * fieldCapacity; //units of cm if (soilWaterContent > waterFull) { actualET = remainingPET; } else { actualET = Math.Max(remainingPET * ((soilWaterContent - waterEmpty) / (waterFull - waterEmpty)), 0.0); } if (actualET < 0.0) { actualET = 0.0; } AET = actualET; // ******************************************************** //PlugIn.ModelCore.UI.WriteLine("AET {0} = ", AET); //Subtract transpiration from soil water content soilWaterContent -= actualET; //Allow excess water to run off during storm events (stormflow) double waterMovement = 0.0; if (soilWaterContent > waterFull) { waterMovement = Math.Max((soilWaterContent - waterFull), 0.0); // How much water should move during a storm event, which is based on how much water the soil can hold. soilWaterContent = waterFull; //...Compute storm flow. stormFlow = waterMovement * stormFlowFraction; // ******************************************************** //Subtract stormflow from soil water //PH: I don't see why this should come out of soil water. It should come out of excess water //soilWaterContent -= stormFlow; //PlugIn.ModelCore.UI.WriteLine("Water Runs Off. stormflow={0}.", stormFlow); // ******************************************************** } // ******************************************************** //PH: add new variable to track excess water and calclulate baseFlow //PH: Remove stormFlow from from excess water waterMovement -= stormFlow; holdingTank += waterMovement; // ******************************************************** // ******************************************************** //PH: moved up to take out soil evaporation and interception before water is added to soil. //...Calculate bare soil water loss and interception when air temperature is above freezing and no snow cover. //...Mofified 9/94 to allow interception when t < 0 but no snow cover, Pulliam //if (liquidSnowpack <= 0.0) //{ //...Calculate total canopy cover and litter, put cap on effects: // double standingBiomass = liveBiomass + deadBiomass; // // if (standingBiomass > 800.0) standingBiomass = 800.0; // if (litterBiomass > 400.0) litterBiomass = 400.0; //...canopy interception, fraction of precip (canopyIntercept): //double canopyIntercept = ((0.0003 * litterBiomass) + (0.0006 * standingBiomass)) * OtherData.WaterLossFactor1; //...Bare soil evaporation, fraction of precip (bareSoilEvap): //bareSoilEvap = 0.5 * System.Math.Exp((-0.002 * litterBiomass) - (0.004 * standingBiomass)) * OtherData.WaterLossFactor2; //...Calculate total surface evaporation losses, maximum allowable is 0.4 * pet. -rm 6/94 //remainingPET = pet; //double soilEvaporation = System.Math.Min(((bareSoilEvap + canopyIntercept) * H2Oinputs), (0.4 * remainingPET)); //Subtract soil evaporation from soil water content //soilWaterContent -= soilEvaporation; //} // Calculate actual evapotranspiration. This equation is derived from the stand equation for calculating AET from PET // Bergström, 1992 //double waterEmpty = wiltingPoint * soilDepth; //if (soilWaterContent > waterFull) // actualET = remainingPET; //else //{ // actualET = Math.Max(remainingPET * ((soilWaterContent - waterEmpty) / (waterFull - waterEmpty)), 0.0); //} //if (actualET < 0.0) // actualET = 0.0; //AET = actualET; //PlugIn.ModelCore.UI.WriteLine("AET {0} = ", AET); //Subtract transpiration from soil water content //soilWaterContent -= actualET; // ******************************************************** // ******************************************************** //Leaching occurs. Drain baseflow fraction from holding tank. //PH: Now baseflow comes from holding tank. baseFlow = holdingTank * baseFlowFraction; //baseFlow = soilWaterContent * baseFlowFraction; //Subtract baseflow from soil water //PH: Subtract from holding tank instead. To not deplete soil water but still allow estimation of baseFlow. holdingTank -= baseFlow; //soilWaterContent -= baseFlow; // ******************************************************** //Calculate the amount of available water after all the evapotranspiration and leaching has taken place (minimum available water) availableWater = Math.Max(soilWaterContent - waterEmpty, 0.0); //Calculate the final amount of available water to the trees, which is the average of the max and min //PH: availableWater is affected by my changes, and soilWaterContent should be higher now. Therefore, I propose calculating using soilWaterContent directly instead //availableWater = soilWaterContent - waterEmpty; // Here calculate available water at the midpoint of the month //availableWater = (availableWaterMax + availableWaterMin)/ 2.0; // Compute the ratio of precipitation to PET double ratioPrecipPET = 0.0; if (pet > 0.0) { ratioPrecipPET = availableWater / pet; //assumes that the ratio is the amount of incoming precip divided by PET. } SiteVars.AnnualPPT_AET[site] += actualET; // RMS: Currently using this to test AET by itself // Precipitation - actualET; SiteVars.AnnualClimaticWaterDeficit[site] += (pet - actualET) * 10.0; // Convert to mm, the standard definition //PlugIn.ModelCore.UI.WriteLine("Month={0}, PET={1}, AET={2}.", month, pet, actualET); SiteVars.LiquidSnowPack[site] = liquidSnowpack; SiteVars.WaterMovement[site] = waterMovement; SiteVars.AvailableWater[site] = availableWater; //available to plants for growth SiteVars.SoilWaterContent[site] = soilWaterContent; SiteVars.SoilTemperature[site] = CalculateSoilTemp(tmin, tmax, liveBiomass, litterBiomass, month); SiteVars.DecayFactor[site] = CalculateDecayFactor((int)OtherData.WType, SiteVars.SoilTemperature[site], relativeWaterContent, ratioPrecipPET, month); SiteVars.AnaerobicEffect[site] = CalculateAnaerobicEffect(drain, ratioPrecipPET, pet, tave); if (month == 0) { SiteVars.DryDays[site] = 0; } else { SiteVars.DryDays[site] += CalculateDryDays(month, beginGrowing, endGrowing, waterEmpty, availableWater, priorWaterAvail); } return; }
public static void Run(int year, int month, double liveBiomass, Site site, out double baseFlow, out double stormFlow, out double AET) { //Originally from h2olos.f of CENTURY model //Water Submodel for Century - written by Bill Parton // Updated from Fortran 4 - rm 2/92 // Rewritten by Bill Pulliam - 9/94 // Rewritten by Melissa Lucash- 11/2014 //PlugIn.ModelCore.UI.WriteLine("month={0}.", Century.Month); //...Initialize Local Variables double addToSoil = 0.0; double bareSoilEvap = 0.0; baseFlow = 0.0; double relativeWaterContent = 0.0; double snow = 0.0; stormFlow = 0.0; double actualET = 0.0; double remainingPET = 0.0; double availableWaterMax = 0.0; //amount of water available after precipitation and snowmelt (over-estimate of available water) double availableWaterMin = 0.0; //amount of water available after stormflow (runoff) evaporation and transpiration, but before baseflow/leaching (under-estimate of available water) double availableWater = 0.0; //amount of water deemed available to the trees, which will be the average between the max and min double priorWaterAvail = SiteVars.AvailableWater[site]; //...Calculate external inputs IEcoregion ecoregion = PlugIn.ModelCore.Ecoregion[site]; double litterBiomass = (SiteVars.SurfaceStructural[site].Carbon + SiteVars.SurfaceMetabolic[site].Carbon) * 2.0; double deadBiomass = SiteVars.SurfaceDeadWood[site].Carbon / 0.47; double soilWaterContent = SiteVars.SoilWaterContent[site]; double liquidSnowpack = SiteVars.LiquidSnowPack[site]; H2Oinputs = ClimateRegionData.AnnualWeather[ecoregion].MonthlyPrecip[month]; //rain + irract in cm; //PlugIn.ModelCore.UI.WriteLine("SoilWater. WaterInputs={0:0.00}, .", H2Oinputs); tave = ClimateRegionData.AnnualWeather[ecoregion].MonthlyTemp[month]; //PlugIn.ModelCore.UI.WriteLine("SoilWater. AvgTemp={0:0.00}, .", tave); tmax = ClimateRegionData.AnnualWeather[ecoregion].MonthlyMaxTemp[month]; tmin = ClimateRegionData.AnnualWeather[ecoregion].MonthlyMinTemp[month]; pet = ClimateRegionData.AnnualWeather[ecoregion].MonthlyPET[month]; daysInMonth = AnnualClimate.DaysInMonth(month, year); beginGrowing = ClimateRegionData.AnnualWeather[ecoregion].BeginGrowing; endGrowing = ClimateRegionData.AnnualWeather[ecoregion].EndGrowing; double wiltingPoint = SiteVars.SoilWiltingPoint[site]; //ClimateRegionData.WiltingPoint[ecoregion]; double soilDepth = SiteVars.SoilDepth[site]; // ClimateRegionData.SoilDepth[ecoregion]; double fieldCapacity = SiteVars.SoilFieldCapacity[site]; //ClimateRegionData.FieldCapacity[ecoregion]; double stormFlowFraction = SiteVars.SoilStormFlowFraction[site]; // ClimateRegionData.StormFlowFraction[ecoregion]; double baseFlowFraction = SiteVars.SoilBaseFlowFraction[site]; //ClimateRegionData.BaseFlowFraction[ecoregion]; double drain = SiteVars.SoilDrain[site]; // ClimateRegionData.Drain[ecoregion]; //...Calculating snow pack first. Occurs when mean monthly air temperature is equal to or below freezing, // precipitation is in the form of snow. if (tmin <= 0.0) // Use tmin to dictate whether it snows or rains. { snow = H2Oinputs; H2Oinputs = 0.0; liquidSnowpack += snow; //only tracking liquidsnowpack (water equivalent) and not the actual amount of snow on the ground (i.e. not snowpack). //PlugIn.ModelCore.UI.WriteLine("Let it snow!! snow={0}, liquidsnowpack={1}.", snow, liquidSnowpack); } else { soilWaterContent += H2Oinputs; //PlugIn.ModelCore.UI.WriteLine("Let it rain and add it to soil! rain={0}, soilWaterContent={1}.", H2Oinputs, soilWaterContent); } //...Then melt snow if there is snow on the ground and air temperature (tmax) is above minimum. if (liquidSnowpack > 0.0 && tmax > 0.0) { //...Calculate the amount of snow to melt: //This relationship ultimately derives from http://www.nps.gov/yose/planyourvisit/climate.htm which described the relationship between snow melting and air temp. //Documentation for the regression equation is in spreadsheet called WaterCalcs.xls by M. Lucash double snowMeltFraction = Math.Max((tmax * 0.05) + 0.024, 0.0);//This equation assumes a linear increase in the fraction of snow that melts as a function of air temp. if (snowMeltFraction > 1.0) { snowMeltFraction = 1.0; } addToSoil = liquidSnowpack * snowMeltFraction; //Amount of liquidsnowpack that melts = liquidsnowpack multiplied by the fraction that melts. //Subtracted melted snow from snowpack and add it to the soil liquidSnowpack = liquidSnowpack - addToSoil; soilWaterContent += addToSoil; } //Calculate the max amout of water available to trees, an over-estimate of the water available to trees. It only reflects precip and melting of precip. availableWaterMax = soilWaterContent; //...Evaporate water from the snow pack (rewritten by Pulliam 9/94) //...Coefficient 0.87 relates to heat of fusion for ice vs. liquid water if (liquidSnowpack > 0.0) { //...Calculate cm of snow that remaining pet energy can evaporate: double evaporatedSnow = pet * 0.87; //...Don't evaporate more snow than actually exists: if (evaporatedSnow > liquidSnowpack) { evaporatedSnow = liquidSnowpack; } liquidSnowpack = liquidSnowpack - evaporatedSnow; //...Decrement remaining pet by energy used to evaporate snow: remainingPET = pet - evaporatedSnow; if (remainingPET < 0.0) { remainingPET = 0.0; } //Subtract evaporated snowfrom the soil water content soilWaterContent -= evaporatedSnow; } //Allow excess water to run off during storm events (stormflow) double waterFull = soilDepth * fieldCapacity; //units of cm double waterMovement = 0.0; if (soilWaterContent > waterFull) { waterMovement = Math.Max((soilWaterContent - waterFull), 0.0); // How much water should move during a storm event, which is based on how much water the soil can hold. soilWaterContent = waterFull; //...Compute storm flow. stormFlow = waterMovement * stormFlowFraction; //Subtract stormflow from soil water soilWaterContent -= stormFlow; //PlugIn.ModelCore.UI.WriteLine("Water Runs Off. stormflow={0}.", stormFlow); } //...Calculate bare soil water loss and interception when air temperature is above freezing and no snow cover. //...Mofified 9/94 to allow interception when t < 0 but no snow cover, Pulliam if (liquidSnowpack <= 0.0) { //...Calculate total canopy cover and litter, put cap on effects: double standingBiomass = liveBiomass + deadBiomass; if (standingBiomass > 800.0) { standingBiomass = 800.0; } if (litterBiomass > 400.0) { litterBiomass = 400.0; } //...canopy interception, fraction of precip (canopyIntercept): double canopyIntercept = ((0.0003 * litterBiomass) + (0.0006 * standingBiomass)) * OtherData.WaterLossFactor1; //...Bare soil evaporation, fraction of precip (bareSoilEvap): bareSoilEvap = 0.5 * System.Math.Exp((-0.002 * litterBiomass) - (0.004 * standingBiomass)) * OtherData.WaterLossFactor2; //...Calculate total surface evaporation losses, maximum allowable is 0.4 * pet. -rm 6/94 remainingPET = pet; double soilEvaporation = System.Math.Min(((bareSoilEvap + canopyIntercept) * H2Oinputs), (0.4 * remainingPET)); //Subtract soil evaporation from soil water content soilWaterContent -= soilEvaporation; } // Calculate actual evapotranspiration. This equation is derived from the stand equation for calculating AET from PET // Bergström, 1992 double waterEmpty = wiltingPoint * soilDepth; if (soilWaterContent > waterFull) { actualET = remainingPET; } else { actualET = Math.Max(remainingPET * ((soilWaterContent - waterEmpty) / (waterFull - waterEmpty)), 0.0); } if (actualET < 0.0) { actualET = 0.0; } AET = actualET; //Subtract transpiration from soil water content soilWaterContent -= actualET; //Leaching occurs. Drain baseflow fraction from holding tank. baseFlow = soilWaterContent * baseFlowFraction; //Subtract baseflow from soil water soilWaterContent -= baseFlow; //Calculate the amount of available water after all the evapotranspiration and leaching has taken place (minimum available water) availableWaterMin = Math.Max(soilWaterContent - waterEmpty, 0.0); //Calculate the final amount of available water to the trees, which is the average of the max and min availableWater = (availableWaterMax + availableWaterMin) / 2.0; // Compute the ratio of precipitation to PET double ratioPrecipPET = 0.0; if (pet > 0.0) { ratioPrecipPET = availableWater / pet; //assumes that the ratio is the amount of incoming precip divided by PET. } SiteVars.AnnualPPT_AET[site] = H2Oinputs - actualET; SiteVars.AnnualSoilMoisture[site] = pet - actualET; SiteVars.LiquidSnowPack[site] = liquidSnowpack; SiteVars.WaterMovement[site] = waterMovement; SiteVars.AvailableWater[site] = availableWater; //available to plants for growth SiteVars.SoilWaterContent[site] = soilWaterContent; SiteVars.SoilTemperature[site] = CalculateSoilTemp(tmin, tmax, liveBiomass, litterBiomass, month); SiteVars.DecayFactor[site] = CalculateDecayFactor((int)OtherData.WType, SiteVars.SoilTemperature[site], relativeWaterContent, ratioPrecipPET, month); SiteVars.AnaerobicEffect[site] = CalculateAnaerobicEffect(drain, ratioPrecipPET, pet, tave); if (month == 0) { SiteVars.DryDays[site] = 0; } else { SiteVars.DryDays[site] += CalculateDryDays(month, beginGrowing, endGrowing, waterEmpty, availableWater, priorWaterAvail); } return; }