Subversion Repositories public iLand

Rev

Rev 364 | Rev 368 | 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
 
367 werner 91
/// calculate combined VPD and soilwaterresponse for all species
92
/// on the RU. This is used for the calc. of the transpiration.
93
inline double WaterCycle::calculateSoilAtmosphereResponse(const double psi_kpa, const double vpd_kpa)
94
{
95
    const ResourceUnitSpecies *i;
96
    const QVector<ResourceUnitSpecies>::const_iterator iend = mRU->ruSpecies().end();
97
    double min_response;
98
    double total_response = 0;
99
    for (i=mRU->ruSpecies().begin();i<iend;++i) {
100
        if (i->LAIfactor()>0) {
101
            i->speciesResponse()->soilAtmosphereResponses(psi_kpa, vpd_kpa, min_response);
102
            total_response += min_response * i->LAIfactor();
103
        }
104
    }
105
    return total_response;
106
}
107
 
108
 
237 werner 109
/// Main Water Cycle function. This function triggers all water related tasks for
110
/// one simulation year.
299 werner 111
/// @sa http://iland.boku.ac.at/water+cycle
237 werner 112
void WaterCycle::run()
113
{
114
    // preparations (once a year)
367 werner 115
    getStandValues(); // fetch canopy characteristics from iLand (including weighted average for mCanopyConductance)
237 werner 116
    mCanopy.setStandParameters(mLAINeedle,
117
                               mLAIBroadleaved,
118
                               mCanopyConductance);
119
 
120
 
121
    // main loop over all days of the year
239 werner 122
    double prec_mm, prec_after_interception, prec_to_soil, et, excess;
338 werner 123
    const Climate *climate = mRU->climate();
124
    const ClimateDay *day = climate->begin();
125
    const ClimateDay *end = climate->end();
237 werner 126
    int doy=0;
127
    double total_excess = 0.;
128
    for (; day<end; ++day, ++doy) {
129
        // (1) precipitation of the day
130
        prec_mm = day->preciptitation;
131
        // (2) interception by the crown
132
        prec_after_interception = mCanopy.flow(prec_mm, day->temperature);
133
        // (3) storage in the snow pack
134
        prec_to_soil = mSnowPack.flow(prec_after_interception, day->temperature);
135
        // (4) add rest to soil
136
        mContent += prec_to_soil;
266 werner 137
 
138
        excess = 0.;
139
        if (mContent>mFieldCapacity) {
140
            // excess water runoff
141
            excess = mContent - mFieldCapacity;
142
            total_excess += excess;
143
            mContent = mFieldCapacity;
144
        }
145
 
237 werner 146
        // calculate the relative water content
255 werner 147
        mRelativeContent[doy] = currentRelContent();
367 werner 148
        double current_psi = psiFromHeight(mContent);
149
        mPsi[doy] = current_psi;
335 werner 150
        // (5) transpiration of the vegetation (and of water intercepted in canopy)
367 werner 151
        // calculate the LAI-weighted response values for soil water and vpd:
152
        double combined_response = calculateSoilAtmosphereResponse( current_psi, day->vpd);
153
        et = mCanopy.evapotranspiration3PG(day, climate->daylength_h(doy), combined_response);
238 werner 154
 
241 werner 155
        mContent -= et; // reduce content (transpiration)
338 werner 156
        // add intercepted water (that is *not* evaporated) again to the soil (or add to snow if temp too low -> call to snowpack)
157
        mContent += mSnowPack.add(mCanopy.interception(),day->temperature);
241 werner 158
 
338 werner 159
        // do not remove water below the PWP (fixed value)
266 werner 160
        if (mContent<mPermanentWiltingPoint) {
271 werner 161
            et -= mPermanentWiltingPoint - mContent; // reduce et (for bookkeeping)
266 werner 162
            mContent = mPermanentWiltingPoint;
237 werner 163
        }
266 werner 164
 
271 werner 165
        //DBGMODE(
239 werner 166
            if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dWaterCycle)) {
240 werner 167
                DebugList &out = GlobalSettings::instance()->debugList(day->id(), GlobalSettings::dWaterCycle);
239 werner 168
                // climatic variables
364 werner 169
                out << day->id() << mRU->index() << day->temperature << day->vpd << day->preciptitation << day->radiation;
239 werner 170
                // fluxes
271 werner 171
                out << prec_after_interception << prec_to_soil << et << mCanopy.evaporationCanopy()
172
                        << mRelativeContent[doy]*mSoilDepth << mPsi[doy] << excess;
239 werner 173
                // other states
174
                out << mSnowPack.snowPack();
364 werner 175
                //special sanity check:
176
                if (prec_to_soil>0. && mCanopy.interception()>0.)
177
                    if (mSnowPack.snowPack()==0. && day->preciptitation==0)
178
                        qDebug() << "watercontent increase without precipititaion";
239 werner 179
 
180
            }
271 werner 181
        //); // DBGMODE()
239 werner 182
 
237 werner 183
    }
184
}
185
 
186
 
187
namespace Water {
188
 
189
/** calculates the input/output of water that is stored in the snow pack.
190
  The approach is similar to Picus 1.3 and ForestBGC (Running, 1988).
191
  Returns the amount of water that exits the snowpack (precipitation, snow melt) */
192
double SnowPack::flow(const double &preciptitation_mm, const double &temperature)
193
{
194
    if (temperature>0.) {
195
        if (mSnowPack==0.)
196
            return preciptitation_mm; // no change
197
        else {
198
            // snow melts
199
            const double melting_coefficient = 0.7; // mm/°C
240 werner 200
            double melt = qMin(temperature * melting_coefficient, mSnowPack);
201
            mSnowPack -=melt;
237 werner 202
            return preciptitation_mm + melt;
203
        }
204
    } else {
205
        // snow:
206
        mSnowPack += preciptitation_mm;
207
        return 0.; // no output.
208
    }
209
 
210
}
211
 
212
 
334 werner 213
inline double SnowPack::add(const double &preciptitation_mm, const double &temperature)
214
{
215
    // do nothing for temps > 0°
216
    if (temperature>0.)
217
        return preciptitation_mm;
218
 
219
    // temps < 0°: add to snow
220
    mSnowPack += preciptitation_mm;
221
    return 0.;
222
}
223
 
237 werner 224
/** Interception in crown canopy.
225
    Calculates the amount of preciptitation that does not reach the ground and
226
    is stored in the canopy. The approach is adopted from Picus 1.3.
299 werner 227
    Returns the amount of precipitation (mm) that surpasses the canopy layer.
228
    @sa http://iland.boku.ac.at/water+cycle#precipitation_and_interception */
237 werner 229
double Canopy::flow(const double &preciptitation_mm, const double &temperature)
230
{
231
    // sanity checks
232
    mInterception = 0.;
271 werner 233
    mEvaporation = 0.;
237 werner 234
    if (!mLAI)
235
        return preciptitation_mm;
236
    if (!preciptitation_mm)
237
        return 0.;
238
    double max_interception_mm=0.; // maximum interception based on the current foliage
239
    double max_storage_mm=0.; // maximum storage in canopy
240
 
241
    if (mLAINeedle>0.) {
242
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
243
        double max_flow_needle = 0.9 * sqrt(1.03 - exp(-0.055*preciptitation_mm));
244
        max_interception_mm += preciptitation_mm *  (1. - max_flow_needle * mLAINeedle/mLAI);
245
        // (2) calculate maximum storage potential based on the current LAI
246
        double max_storage_needle = 4. * (1. - exp(-0.55*mLAINeedle) );
247
        max_storage_mm += max_storage_needle;
248
    }
249
 
250
    if (mLAIBroadleaved>0.) {
251
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
299 werner 252
        double max_flow_broad = 0.9 * pow(1.22 - exp(-0.055*preciptitation_mm), 0.35);
237 werner 253
        max_interception_mm += preciptitation_mm *  (1. - max_flow_broad * mLAIBroadleaved/mLAI);
254
        // (2) calculate maximum storage potential based on the current LAI
299 werner 255
        double max_storage_broad = 2. * (1. - exp(-0.5*mLAIBroadleaved) );
237 werner 256
        max_storage_mm += max_storage_broad;
257
    }
258
 
259
    // (3) calculate actual interception and store for evaporation calculation
260
    mInterception = qMin( max_storage_mm, max_interception_mm );
261
 
335 werner 262
    // (4) limit interception with amount of precipitation
263
    mInterception = qMin( mInterception, preciptitation_mm);
264
 
265
    // (5) reduce preciptitaion by the amount that is intercepted by the canopy
237 werner 266
    return preciptitation_mm - mInterception;
267
 
268
}
269
 
239 werner 270
/// sets up the canopy. fetch some global parameter values...
271
void Canopy::setup()
237 werner 272
{
240 werner 273
    mHeatCapacityAir = Model::settings().heatCapacityAir; // J/kg/°C
274
    mAirDensity = Model::settings().airDensity; // kg / m3
275
    double airPressure = Model::settings().airPressure; // mbar
276
    double heat_capacity_kJ = mHeatCapacityAir / 1000; // convert to: kJ/kg/°C
277
    const double latent_heat_water = 2450;// kJ/kg
278
    // calc psychrometric constant (in mbar/°C): see also http://en.wikipedia.org/wiki/Psychrometric_constant
279
    mPsychrometricConstant = heat_capacity_kJ*airPressure/latent_heat_water/0.622;
237 werner 280
}
281
 
282
void Canopy::setStandParameters(const double LAIneedle, const double LAIbroadleave, const double maxCanopyConductance)
283
{
284
    mLAINeedle = LAIneedle;
285
    mLAIBroadleaved=LAIbroadleave;
286
    mLAI=LAIneedle+LAIbroadleave;
239 werner 287
    mAvgMaxCanopyConductance = maxCanopyConductance;
237 werner 288
}
289
 
290
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
291
   The application of the equation follows broadly Running (1988).
292
   Returns the total sum of evaporation+transpiration in mm of the day. */
256 werner 293
double Canopy::evapotranspirationBGC(const ClimateDay *climate, const double daylength_h)
237 werner 294
{
238 werner 295
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
296
    double temperature = climate->temperature; // average temperature of the day (°C)
297
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
298
    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 299
    const double aerodynamic_resistance = 5.; // m/s: aerodynamic resistance of the canopy is considered being constant
300
    const double latent_heat = 2257000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
301
 
302
    // (1) calculate some intermediaries
303
    // current canopy conductance: is calculated following Landsberg & Waring
240 werner 304
    // note: here we use vpd again in kPa.
237 werner 305
    double current_canopy_conductance;
240 werner 306
    current_canopy_conductance = mAvgMaxCanopyConductance * exp(-2.5 * climate->vpd);
237 werner 307
 
240 werner 308
    // saturation vapor pressure (Running 1988, Eq. 1) in mbar
237 werner 309
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
310
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
311
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
312
 
239 werner 313
    double et; // transpiration in mm (follows Eq.(8) of Running, 1988).
314
    // note: RC (resistance of canopy) = 1/CC (conductance of canopy)
240 werner 315
    double dim = svp_slope + mPsychrometricConstant*(1. + 1. / (current_canopy_conductance*aerodynamic_resistance));
246 werner 316
    double dayl = daylength*mLAI / latent_heat;
238 werner 317
    double upper = svp_slope*rad + mHeatCapacityAir*mAirDensity * vpd_mbar/aerodynamic_resistance;
318
    et = upper / dim * dayl;
239 werner 319
 
320
    // now calculate the evaporation from intercepted preciptitaion in the canopy: 1+rc/ra -> 1
321
    if (mInterception>0.) {
271 werner 322
        double dim_evap = svp_slope + mPsychrometricConstant;
323
        double pot_evap = upper / dim_evap * dayl;
324
        double evap = qMin(pot_evap, mInterception);
335 werner 325
        mInterception -= evap; // reduce interception
271 werner 326
        mEvaporation = evap;
239 werner 327
    }
237 werner 328
    return et;
329
}
330
 
331
 
256 werner 332
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
333
   This version is based on 3PG. See the Visual Basic Code in 3PGjs.xls.
334
   Returns the total sum of evaporation+transpiration in mm of the day. */
367 werner 335
double Canopy::evapotranspiration3PG(const ClimateDay *climate, const double daylength_h, const double combined_response)
256 werner 336
{
337
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
338
    double temperature = climate->temperature; // average temperature of the day (°C)
339
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
340
    double rad = climate->radiation / daylength * 1000000; //convert from MJ/m2 (day sum) to average radiation flow W/m2 [MJ=MWs -> /s * 1,000,000
341
 
342
    //: Landsberg original: const double e20 = 2.2;  //rate of change of saturated VP with T at 20C
343
    const double VPDconv = 0.000622; //convert VPD to saturation deficit = 18/29/1000
344
    const double latent_heat = 2460000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
345
 
346
    double gBL  = 0.2; // boundary layer conductance
367 werner 347
    double gC = mAvgMaxCanopyConductance * combined_response;
256 werner 348
 
367 werner 349
 
256 werner 350
    double defTerm = mAirDensity * latent_heat * (vpd_mbar * VPDconv) * gBL;
351
        // saturation vapor pressure (Running 1988, Eq. 1) in mbar
352
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
353
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
354
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
355
 
356
    double div = (1. + svp_slope + gBL / gC);
357
    double Etransp = (svp_slope * rad + defTerm) / div;
335 werner 358
    double canopy_transpiration = Etransp / latent_heat * daylength;
256 werner 359
 
360
    if (mInterception>0.) {
361
        // we assume that for evaporation from leaf surface gBL/gC -> 0
362
        double div_evap = 1 + svp_slope;
363
        double evap = (svp_slope*rad + defTerm) / div_evap / latent_heat * daylength;
364
        evap = qMin(evap, mInterception);
335 werner 365
        mInterception -= evap; // reduce interception
366
        mEvaporation = evap; // evaporation from intercepted water
256 werner 367
    }
335 werner 368
    return canopy_transpiration;
256 werner 369
}
370
 
237 werner 371
} // end namespace