Subversion Repositories public iLand

Rev

Rev 339 | Rev 367 | 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
364 werner 148
                out << day->id() << mRU->index() << 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();
364 werner 154
                //special sanity check:
155
                if (prec_to_soil>0. && mCanopy.interception()>0.)
156
                    if (mSnowPack.snowPack()==0. && day->preciptitation==0)
157
                        qDebug() << "watercontent increase without precipititaion";
239 werner 158
 
159
            }
271 werner 160
        //); // DBGMODE()
239 werner 161
 
237 werner 162
    }
163
}
164
 
165
 
166
namespace Water {
167
 
168
/** calculates the input/output of water that is stored in the snow pack.
169
  The approach is similar to Picus 1.3 and ForestBGC (Running, 1988).
170
  Returns the amount of water that exits the snowpack (precipitation, snow melt) */
171
double SnowPack::flow(const double &preciptitation_mm, const double &temperature)
172
{
173
    if (temperature>0.) {
174
        if (mSnowPack==0.)
175
            return preciptitation_mm; // no change
176
        else {
177
            // snow melts
178
            const double melting_coefficient = 0.7; // mm/°C
240 werner 179
            double melt = qMin(temperature * melting_coefficient, mSnowPack);
180
            mSnowPack -=melt;
237 werner 181
            return preciptitation_mm + melt;
182
        }
183
    } else {
184
        // snow:
185
        mSnowPack += preciptitation_mm;
186
        return 0.; // no output.
187
    }
188
 
189
}
190
 
191
 
334 werner 192
inline double SnowPack::add(const double &preciptitation_mm, const double &temperature)
193
{
194
    // do nothing for temps > 0°
195
    if (temperature>0.)
196
        return preciptitation_mm;
197
 
198
    // temps < 0°: add to snow
199
    mSnowPack += preciptitation_mm;
200
    return 0.;
201
}
202
 
237 werner 203
/** Interception in crown canopy.
204
    Calculates the amount of preciptitation that does not reach the ground and
205
    is stored in the canopy. The approach is adopted from Picus 1.3.
299 werner 206
    Returns the amount of precipitation (mm) that surpasses the canopy layer.
207
    @sa http://iland.boku.ac.at/water+cycle#precipitation_and_interception */
237 werner 208
double Canopy::flow(const double &preciptitation_mm, const double &temperature)
209
{
210
    // sanity checks
211
    mInterception = 0.;
271 werner 212
    mEvaporation = 0.;
237 werner 213
    if (!mLAI)
214
        return preciptitation_mm;
215
    if (!preciptitation_mm)
216
        return 0.;
217
    double max_interception_mm=0.; // maximum interception based on the current foliage
218
    double max_storage_mm=0.; // maximum storage in canopy
219
 
220
    if (mLAINeedle>0.) {
221
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
222
        double max_flow_needle = 0.9 * sqrt(1.03 - exp(-0.055*preciptitation_mm));
223
        max_interception_mm += preciptitation_mm *  (1. - max_flow_needle * mLAINeedle/mLAI);
224
        // (2) calculate maximum storage potential based on the current LAI
225
        double max_storage_needle = 4. * (1. - exp(-0.55*mLAINeedle) );
226
        max_storage_mm += max_storage_needle;
227
    }
228
 
229
    if (mLAIBroadleaved>0.) {
230
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
299 werner 231
        double max_flow_broad = 0.9 * pow(1.22 - exp(-0.055*preciptitation_mm), 0.35);
237 werner 232
        max_interception_mm += preciptitation_mm *  (1. - max_flow_broad * mLAIBroadleaved/mLAI);
233
        // (2) calculate maximum storage potential based on the current LAI
299 werner 234
        double max_storage_broad = 2. * (1. - exp(-0.5*mLAIBroadleaved) );
237 werner 235
        max_storage_mm += max_storage_broad;
236
    }
237
 
238
    // (3) calculate actual interception and store for evaporation calculation
239
    mInterception = qMin( max_storage_mm, max_interception_mm );
240
 
335 werner 241
    // (4) limit interception with amount of precipitation
242
    mInterception = qMin( mInterception, preciptitation_mm);
243
 
244
    // (5) reduce preciptitaion by the amount that is intercepted by the canopy
237 werner 245
    return preciptitation_mm - mInterception;
246
 
247
}
248
 
239 werner 249
/// sets up the canopy. fetch some global parameter values...
250
void Canopy::setup()
237 werner 251
{
240 werner 252
    mHeatCapacityAir = Model::settings().heatCapacityAir; // J/kg/°C
253
    mAirDensity = Model::settings().airDensity; // kg / m3
254
    double airPressure = Model::settings().airPressure; // mbar
255
    double heat_capacity_kJ = mHeatCapacityAir / 1000; // convert to: kJ/kg/°C
256
    const double latent_heat_water = 2450;// kJ/kg
257
    // calc psychrometric constant (in mbar/°C): see also http://en.wikipedia.org/wiki/Psychrometric_constant
258
    mPsychrometricConstant = heat_capacity_kJ*airPressure/latent_heat_water/0.622;
237 werner 259
}
260
 
261
void Canopy::setStandParameters(const double LAIneedle, const double LAIbroadleave, const double maxCanopyConductance)
262
{
263
    mLAINeedle = LAIneedle;
264
    mLAIBroadleaved=LAIbroadleave;
265
    mLAI=LAIneedle+LAIbroadleave;
239 werner 266
    mAvgMaxCanopyConductance = maxCanopyConductance;
237 werner 267
}
268
 
269
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
270
   The application of the equation follows broadly Running (1988).
271
   Returns the total sum of evaporation+transpiration in mm of the day. */
256 werner 272
double Canopy::evapotranspirationBGC(const ClimateDay *climate, const double daylength_h)
237 werner 273
{
238 werner 274
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
275
    double temperature = climate->temperature; // average temperature of the day (°C)
276
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
277
    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 278
    const double aerodynamic_resistance = 5.; // m/s: aerodynamic resistance of the canopy is considered being constant
279
    const double latent_heat = 2257000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
280
 
281
    // (1) calculate some intermediaries
282
    // current canopy conductance: is calculated following Landsberg & Waring
240 werner 283
    // note: here we use vpd again in kPa.
237 werner 284
    double current_canopy_conductance;
240 werner 285
    current_canopy_conductance = mAvgMaxCanopyConductance * exp(-2.5 * climate->vpd);
237 werner 286
 
240 werner 287
    // saturation vapor pressure (Running 1988, Eq. 1) in mbar
237 werner 288
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
289
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
290
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
291
 
239 werner 292
    double et; // transpiration in mm (follows Eq.(8) of Running, 1988).
293
    // note: RC (resistance of canopy) = 1/CC (conductance of canopy)
240 werner 294
    double dim = svp_slope + mPsychrometricConstant*(1. + 1. / (current_canopy_conductance*aerodynamic_resistance));
246 werner 295
    double dayl = daylength*mLAI / latent_heat;
238 werner 296
    double upper = svp_slope*rad + mHeatCapacityAir*mAirDensity * vpd_mbar/aerodynamic_resistance;
297
    et = upper / dim * dayl;
239 werner 298
 
299
    // now calculate the evaporation from intercepted preciptitaion in the canopy: 1+rc/ra -> 1
300
    if (mInterception>0.) {
271 werner 301
        double dim_evap = svp_slope + mPsychrometricConstant;
302
        double pot_evap = upper / dim_evap * dayl;
303
        double evap = qMin(pot_evap, mInterception);
335 werner 304
        mInterception -= evap; // reduce interception
271 werner 305
        mEvaporation = evap;
239 werner 306
    }
237 werner 307
    return et;
308
}
309
 
310
 
256 werner 311
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
312
   This version is based on 3PG. See the Visual Basic Code in 3PGjs.xls.
313
   Returns the total sum of evaporation+transpiration in mm of the day. */
314
double Canopy::evapotranspiration3PG(const ClimateDay *climate, const double daylength_h)
315
{
316
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
317
    double temperature = climate->temperature; // average temperature of the day (°C)
318
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
319
    double rad = climate->radiation / daylength * 1000000; //convert from MJ/m2 (day sum) to average radiation flow W/m2 [MJ=MWs -> /s * 1,000,000
320
 
321
    //: Landsberg original: const double e20 = 2.2;  //rate of change of saturated VP with T at 20C
322
    const double VPDconv = 0.000622; //convert VPD to saturation deficit = 18/29/1000
323
    const double latent_heat = 2460000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
324
 
325
    double gBL  = 0.2; // boundary layer conductance
326
    // canopy conductance scales linearly from 0 to LAI=3 and is constant for LAI > 3.
327
    double gC = mAvgMaxCanopyConductance * exp(-2.5 * climate->vpd);
328
    if (mLAI<3.)
329
        gC *= mLAI / 3.;
330
 
331
    double defTerm = mAirDensity * latent_heat * (vpd_mbar * VPDconv) * gBL;
332
        // saturation vapor pressure (Running 1988, Eq. 1) in mbar
333
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
334
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
335
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
336
 
337
    double div = (1. + svp_slope + gBL / gC);
338
    double Etransp = (svp_slope * rad + defTerm) / div;
335 werner 339
    double canopy_transpiration = Etransp / latent_heat * daylength;
256 werner 340
 
341
    if (mInterception>0.) {
342
        // we assume that for evaporation from leaf surface gBL/gC -> 0
343
        double div_evap = 1 + svp_slope;
344
        double evap = (svp_slope*rad + defTerm) / div_evap / latent_heat * daylength;
345
        evap = qMin(evap, mInterception);
335 werner 346
        mInterception -= evap; // reduce interception
347
        mEvaporation = evap; // evaporation from intercepted water
256 werner 348
    }
335 werner 349
    return canopy_transpiration;
256 werner 350
}
351
 
237 werner 352
} // end namespace