Subversion Repositories public iLand

Rev

Rev 338 | Rev 364 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1
 
237 werner 2
#include "global.h"
3
#include "watercycle.h"
4
#include "climate.h"
5
#include "resourceunit.h"
6
#include "species.h"
239 werner 7
#include "model.h"
237 werner 8
 
9
WaterCycle::WaterCycle()
10
{
266 werner 11
    mSoilDepth = 0;
237 werner 12
}
13
 
239 werner 14
void WaterCycle::setup(const ResourceUnit *ru)
237 werner 15
{
16
    mRU = ru;
17
    // get values...
266 werner 18
    mFieldCapacity = 0.; // on top
281 werner 19
    const XmlHelper &xml=GlobalSettings::instance()->settings();
20
    mSoilDepth = xml.valueDouble("model.site.soilDepth", 0.) * 10; // convert from cm to mm
338 werner 21
    double pct_sand = xml.valueDouble("model.site.pctSand");
22
    double pct_silt = xml.valueDouble("model.site.pctSilt");
23
    double pct_clay = xml.valueDouble("model.site.pctClay");
24
    if (pct_sand + pct_silt + pct_clay != 100.)
25
        throw IException(QString("Setup Watercycle: soil composition percentages do not sum up to 100. Sand: %1, Silt: %2 Clay: %3").arg(pct_sand).arg(pct_silt).arg(pct_clay));
26
 
27
    // calculate soil characteristics based on empirical functions (Schwalm & Ek, 2004)
28
    // note: the variables are percentages [0..100]
29
    mPsi_ref = -exp((1.54 - 0.0095*pct_sand + 0.0063*pct_silt) * log(10)) * 0.000098; // Eq. 83
30
    mPsi_koeff_b = -( 3.1 + 0.157*pct_clay - 0.003*pct_sand );  // Eq. 84
31
    mRho_ref = 0.01 * (50.5 - 0.142*pct_sand - 0.037*pct_clay); // Eq. 78
240 werner 32
    mCanopy.setup();
339 werner 33
 
34
    mPermanentWiltingPoint = heightFromPsi(-4000); // maximum psi is set to a constant of -4MPa
266 werner 35
    mFieldCapacity = heightFromPsi(-15);
36
    mContent = mFieldCapacity; // start with full water content (in the middle of winter)
339 werner 37
    qDebug() << "setup of water: Psi_ref (kPa)" << mPsi_ref << "Rho_ref" << mRho_ref << "coeff. b" << mPsi_koeff_b;
237 werner 38
}
39
 
331 werner 40
/** function to calculate the water pressure [saugspannung] for a given amount of water.
338 werner 41
    returns water potential in kPa.
42
  see http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance */
266 werner 43
inline double WaterCycle::psiFromHeight(const double mm) const
44
{
45
    // psi_x = psi_ref * ( rho_x / rho_ref) ^ b
46
    if (mm<0.001)
47
        return -100000000;
48
    double psi_x = mPsi_ref * pow((mm / mSoilDepth / mRho_ref),mPsi_koeff_b);
338 werner 49
    return psi_x; // pis
266 werner 50
}
51
 
331 werner 52
/// calculate the height of the water column for a given pressure
338 werner 53
/// return water amount in mm
54
/// see http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance
266 werner 55
inline double WaterCycle::heightFromPsi(const double psi_kpa) const
56
{
57
    // rho_x = rho_ref * (psi_x / psi_ref)^(1/b)
58
    double h = mSoilDepth * mRho_ref * pow(psi_kpa / mPsi_ref, 1./mPsi_koeff_b);
59
    return h;
60
}
61
 
331 werner 62
/// get canopy characteristics of the resource unit.
63
/// It is important, that species-statistics are valid when this function is called (LAI)!
237 werner 64
void WaterCycle::getStandValues()
65
{
246 werner 66
    mLAINeedle=mLAIBroadleaved=0.;
237 werner 67
    mCanopyConductance=0.;
68
    double lai;
69
    foreach(const ResourceUnitSpecies &rus, mRU->ruSpecies()) {
70
        lai = rus.constStatistics().leafAreaIndex();
71
        if (rus.species()->isConiferous())
72
            mLAINeedle+=lai;
73
        else
74
            mLAIBroadleaved+=lai;
268 werner 75
        mCanopyConductance += rus.species()->canopyConductance() * lai; // weigh with LAI
237 werner 76
    }
77
    double total_lai = mLAIBroadleaved+mLAINeedle;
78
    if (total_lai>0.) {
79
        mCanopyConductance /= total_lai;
268 werner 80
    } else {
81
        mCanopyConductance = 0.02;
237 werner 82
    }
240 werner 83
    if (total_lai < 3.) {
237 werner 84
        // following Landsberg and Waring: when LAI is < 3, a linear "ramp" from 0 to 3 is assumed
299 werner 85
        // http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance
237 werner 86
        mCanopyConductance *= total_lai / 3.;
87
    }
88
    qDebug() << "WaterCycle:getStandValues: LAI needle" << mLAINeedle << "LAI Broadl:"<< mLAIBroadleaved << "weighted avg. Conductance (m/2):" << mCanopyConductance;
89
}
90
 
91
/// Main Water Cycle function. This function triggers all water related tasks for
92
/// one simulation year.
299 werner 93
/// @sa http://iland.boku.ac.at/water+cycle
237 werner 94
void WaterCycle::run()
95
{
96
    // preparations (once a year)
97
    getStandValues(); // fetch canopy characteristics from iLand
98
    mCanopy.setStandParameters(mLAINeedle,
99
                               mLAIBroadleaved,
100
                               mCanopyConductance);
101
 
102
 
103
    // main loop over all days of the year
239 werner 104
    double prec_mm, prec_after_interception, prec_to_soil, et, excess;
338 werner 105
    const Climate *climate = mRU->climate();
106
    const ClimateDay *day = climate->begin();
107
    const ClimateDay *end = climate->end();
237 werner 108
    int doy=0;
109
    double total_excess = 0.;
110
    for (; day<end; ++day, ++doy) {
111
        // (1) precipitation of the day
112
        prec_mm = day->preciptitation;
113
        // (2) interception by the crown
114
        prec_after_interception = mCanopy.flow(prec_mm, day->temperature);
115
        // (3) storage in the snow pack
116
        prec_to_soil = mSnowPack.flow(prec_after_interception, day->temperature);
117
        // (4) add rest to soil
118
        mContent += prec_to_soil;
266 werner 119
 
120
        excess = 0.;
121
        if (mContent>mFieldCapacity) {
122
            // excess water runoff
123
            excess = mContent - mFieldCapacity;
124
            total_excess += excess;
125
            mContent = mFieldCapacity;
126
        }
127
 
237 werner 128
        // calculate the relative water content
255 werner 129
        mRelativeContent[doy] = currentRelContent();
266 werner 130
        mPsi[doy] = psiFromHeight(mContent);
335 werner 131
        // (5) transpiration of the vegetation (and of water intercepted in canopy)
338 werner 132
        et = mCanopy.evapotranspiration3PG(day, climate->daylength_h(doy));
238 werner 133
 
241 werner 134
        mContent -= et; // reduce content (transpiration)
338 werner 135
        // add intercepted water (that is *not* evaporated) again to the soil (or add to snow if temp too low -> call to snowpack)
136
        mContent += mSnowPack.add(mCanopy.interception(),day->temperature);
241 werner 137
 
338 werner 138
        // do not remove water below the PWP (fixed value)
266 werner 139
        if (mContent<mPermanentWiltingPoint) {
271 werner 140
            et -= mPermanentWiltingPoint - mContent; // reduce et (for bookkeeping)
266 werner 141
            mContent = mPermanentWiltingPoint;
237 werner 142
        }
266 werner 143
 
271 werner 144
        //DBGMODE(
239 werner 145
            if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dWaterCycle)) {
240 werner 146
                DebugList &out = GlobalSettings::instance()->debugList(day->id(), GlobalSettings::dWaterCycle);
239 werner 147
                // climatic variables
240 werner 148
                out << day->id() << day->temperature << day->vpd << day->preciptitation << day->radiation;
239 werner 149
                // fluxes
271 werner 150
                out << prec_after_interception << prec_to_soil << et << mCanopy.evaporationCanopy()
151
                        << mRelativeContent[doy]*mSoilDepth << mPsi[doy] << excess;
239 werner 152
                // other states
153
                out << mSnowPack.snowPack();
154
 
155
            }
271 werner 156
        //); // DBGMODE()
239 werner 157
 
237 werner 158
    }
159
}
160
 
161
 
162
namespace Water {
163
 
164
/** calculates the input/output of water that is stored in the snow pack.
165
  The approach is similar to Picus 1.3 and ForestBGC (Running, 1988).
166
  Returns the amount of water that exits the snowpack (precipitation, snow melt) */
167
double SnowPack::flow(const double &preciptitation_mm, const double &temperature)
168
{
169
    if (temperature>0.) {
170
        if (mSnowPack==0.)
171
            return preciptitation_mm; // no change
172
        else {
173
            // snow melts
174
            const double melting_coefficient = 0.7; // mm/°C
240 werner 175
            double melt = qMin(temperature * melting_coefficient, mSnowPack);
176
            mSnowPack -=melt;
237 werner 177
            return preciptitation_mm + melt;
178
        }
179
    } else {
180
        // snow:
181
        mSnowPack += preciptitation_mm;
182
        return 0.; // no output.
183
    }
184
 
185
}
186
 
187
 
334 werner 188
inline double SnowPack::add(const double &preciptitation_mm, const double &temperature)
189
{
190
    // do nothing for temps > 0°
191
    if (temperature>0.)
192
        return preciptitation_mm;
193
 
194
    // temps < 0°: add to snow
195
    mSnowPack += preciptitation_mm;
196
    return 0.;
197
}
198
 
237 werner 199
/** Interception in crown canopy.
200
    Calculates the amount of preciptitation that does not reach the ground and
201
    is stored in the canopy. The approach is adopted from Picus 1.3.
299 werner 202
    Returns the amount of precipitation (mm) that surpasses the canopy layer.
203
    @sa http://iland.boku.ac.at/water+cycle#precipitation_and_interception */
237 werner 204
double Canopy::flow(const double &preciptitation_mm, const double &temperature)
205
{
206
    // sanity checks
207
    mInterception = 0.;
271 werner 208
    mEvaporation = 0.;
237 werner 209
    if (!mLAI)
210
        return preciptitation_mm;
211
    if (!preciptitation_mm)
212
        return 0.;
213
    double max_interception_mm=0.; // maximum interception based on the current foliage
214
    double max_storage_mm=0.; // maximum storage in canopy
215
 
216
    if (mLAINeedle>0.) {
217
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
218
        double max_flow_needle = 0.9 * sqrt(1.03 - exp(-0.055*preciptitation_mm));
219
        max_interception_mm += preciptitation_mm *  (1. - max_flow_needle * mLAINeedle/mLAI);
220
        // (2) calculate maximum storage potential based on the current LAI
221
        double max_storage_needle = 4. * (1. - exp(-0.55*mLAINeedle) );
222
        max_storage_mm += max_storage_needle;
223
    }
224
 
225
    if (mLAIBroadleaved>0.) {
226
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
299 werner 227
        double max_flow_broad = 0.9 * pow(1.22 - exp(-0.055*preciptitation_mm), 0.35);
237 werner 228
        max_interception_mm += preciptitation_mm *  (1. - max_flow_broad * mLAIBroadleaved/mLAI);
229
        // (2) calculate maximum storage potential based on the current LAI
299 werner 230
        double max_storage_broad = 2. * (1. - exp(-0.5*mLAIBroadleaved) );
237 werner 231
        max_storage_mm += max_storage_broad;
232
    }
233
 
234
    // (3) calculate actual interception and store for evaporation calculation
235
    mInterception = qMin( max_storage_mm, max_interception_mm );
236
 
335 werner 237
    // (4) limit interception with amount of precipitation
238
    mInterception = qMin( mInterception, preciptitation_mm);
239
 
240
    // (5) reduce preciptitaion by the amount that is intercepted by the canopy
237 werner 241
    return preciptitation_mm - mInterception;
242
 
243
}
244
 
239 werner 245
/// sets up the canopy. fetch some global parameter values...
246
void Canopy::setup()
237 werner 247
{
240 werner 248
    mHeatCapacityAir = Model::settings().heatCapacityAir; // J/kg/°C
249
    mAirDensity = Model::settings().airDensity; // kg / m3
250
    double airPressure = Model::settings().airPressure; // mbar
251
    double heat_capacity_kJ = mHeatCapacityAir / 1000; // convert to: kJ/kg/°C
252
    const double latent_heat_water = 2450;// kJ/kg
253
    // calc psychrometric constant (in mbar/°C): see also http://en.wikipedia.org/wiki/Psychrometric_constant
254
    mPsychrometricConstant = heat_capacity_kJ*airPressure/latent_heat_water/0.622;
237 werner 255
}
256
 
257
void Canopy::setStandParameters(const double LAIneedle, const double LAIbroadleave, const double maxCanopyConductance)
258
{
259
    mLAINeedle = LAIneedle;
260
    mLAIBroadleaved=LAIbroadleave;
261
    mLAI=LAIneedle+LAIbroadleave;
239 werner 262
    mAvgMaxCanopyConductance = maxCanopyConductance;
237 werner 263
}
264
 
265
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
266
   The application of the equation follows broadly Running (1988).
267
   Returns the total sum of evaporation+transpiration in mm of the day. */
256 werner 268
double Canopy::evapotranspirationBGC(const ClimateDay *climate, const double daylength_h)
237 werner 269
{
238 werner 270
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
271
    double temperature = climate->temperature; // average temperature of the day (°C)
272
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
273
    double rad = climate->radiation / daylength * 1000000; //convert from MJ/m2 (day sum) to average radiation flow W/m2 [MJ=MWs -> /s * 1,000,000
237 werner 274
    const double aerodynamic_resistance = 5.; // m/s: aerodynamic resistance of the canopy is considered being constant
275
    const double latent_heat = 2257000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
276
 
277
    // (1) calculate some intermediaries
278
    // current canopy conductance: is calculated following Landsberg & Waring
240 werner 279
    // note: here we use vpd again in kPa.
237 werner 280
    double current_canopy_conductance;
240 werner 281
    current_canopy_conductance = mAvgMaxCanopyConductance * exp(-2.5 * climate->vpd);
237 werner 282
 
240 werner 283
    // saturation vapor pressure (Running 1988, Eq. 1) in mbar
237 werner 284
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
285
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
286
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
287
 
239 werner 288
    double et; // transpiration in mm (follows Eq.(8) of Running, 1988).
289
    // note: RC (resistance of canopy) = 1/CC (conductance of canopy)
240 werner 290
    double dim = svp_slope + mPsychrometricConstant*(1. + 1. / (current_canopy_conductance*aerodynamic_resistance));
246 werner 291
    double dayl = daylength*mLAI / latent_heat;
238 werner 292
    double upper = svp_slope*rad + mHeatCapacityAir*mAirDensity * vpd_mbar/aerodynamic_resistance;
293
    et = upper / dim * dayl;
239 werner 294
 
295
    // now calculate the evaporation from intercepted preciptitaion in the canopy: 1+rc/ra -> 1
296
    if (mInterception>0.) {
271 werner 297
        double dim_evap = svp_slope + mPsychrometricConstant;
298
        double pot_evap = upper / dim_evap * dayl;
299
        double evap = qMin(pot_evap, mInterception);
335 werner 300
        mInterception -= evap; // reduce interception
271 werner 301
        mEvaporation = evap;
239 werner 302
    }
237 werner 303
    return et;
304
}
305
 
306
 
256 werner 307
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
308
   This version is based on 3PG. See the Visual Basic Code in 3PGjs.xls.
309
   Returns the total sum of evaporation+transpiration in mm of the day. */
310
double Canopy::evapotranspiration3PG(const ClimateDay *climate, const double daylength_h)
311
{
312
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
313
    double temperature = climate->temperature; // average temperature of the day (°C)
314
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
315
    double rad = climate->radiation / daylength * 1000000; //convert from MJ/m2 (day sum) to average radiation flow W/m2 [MJ=MWs -> /s * 1,000,000
316
 
317
    //: Landsberg original: const double e20 = 2.2;  //rate of change of saturated VP with T at 20C
318
    const double VPDconv = 0.000622; //convert VPD to saturation deficit = 18/29/1000
319
    const double latent_heat = 2460000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
320
 
321
    double gBL  = 0.2; // boundary layer conductance
322
    // canopy conductance scales linearly from 0 to LAI=3 and is constant for LAI > 3.
323
    double gC = mAvgMaxCanopyConductance * exp(-2.5 * climate->vpd);
324
    if (mLAI<3.)
325
        gC *= mLAI / 3.;
326
 
327
    double defTerm = mAirDensity * latent_heat * (vpd_mbar * VPDconv) * gBL;
328
        // saturation vapor pressure (Running 1988, Eq. 1) in mbar
329
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
330
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
331
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
332
 
333
    double div = (1. + svp_slope + gBL / gC);
334
    double Etransp = (svp_slope * rad + defTerm) / div;
335 werner 335
    double canopy_transpiration = Etransp / latent_heat * daylength;
256 werner 336
 
337
    if (mInterception>0.) {
338
        // we assume that for evaporation from leaf surface gBL/gC -> 0
339
        double div_evap = 1 + svp_slope;
340
        double evap = (svp_slope*rad + defTerm) / div_evap / latent_heat * daylength;
341
        evap = qMin(evap, mInterception);
335 werner 342
        mInterception -= evap; // reduce interception
343
        mEvaporation = evap; // evaporation from intercepted water
256 werner 344
    }
335 werner 345
    return canopy_transpiration;
256 werner 346
}
347
 
237 werner 348
} // end namespace