Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
671 werner 1
/********************************************************************************************
2
**    iLand - an individual based forest landscape and disturbance model
3
**    http://iland.boku.ac.at
4
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**
6
**    This program is free software: you can redistribute it and/or modify
7
**    it under the terms of the GNU General Public License as published by
8
**    the Free Software Foundation, either version 3 of the License, or
9
**    (at your option) any later version.
10
**
11
**    This program is distributed in the hope that it will be useful,
12
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    GNU General Public License for more details.
15
**
16
**    You should have received a copy of the GNU General Public License
17
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
********************************************************************************************/
19
 
237 werner 20
#include "global.h"
21
#include "watercycle.h"
22
#include "climate.h"
23
#include "resourceunit.h"
24
#include "species.h"
239 werner 25
#include "model.h"
808 werner 26
#include "debugtimer.h"
646 werner 27
#include "modules.h"
237 werner 28
 
697 werner 29
/** @class WaterCycle
30
  @ingroup core
31
  simulates the water cycle on a ResourceUnit.
32
  The WaterCycle is simulated with a daily time step on the spatial level of a ResourceUnit. Related are
33
  the snow module (SnowPack), and Canopy module that simulates the interception (and evaporation) of precipitation and the
34
  transpiration from the canopy.
35
  The WaterCycle covers the "soil water bucket". Main entry function is run().
36
 
37
  See http://iland.boku.ac.at/water+cycle
38
  */
39
 
237 werner 40
WaterCycle::WaterCycle()
41
{
266 werner 42
    mSoilDepth = 0;
496 werner 43
    mLastYear = -1;
237 werner 44
}
45
 
239 werner 46
void WaterCycle::setup(const ResourceUnit *ru)
237 werner 47
{
48
    mRU = ru;
49
    // get values...
266 werner 50
    mFieldCapacity = 0.; // on top
281 werner 51
    const XmlHelper &xml=GlobalSettings::instance()->settings();
52
    mSoilDepth = xml.valueDouble("model.site.soilDepth", 0.) * 10; // convert from cm to mm
338 werner 53
    double pct_sand = xml.valueDouble("model.site.pctSand");
54
    double pct_silt = xml.valueDouble("model.site.pctSilt");
55
    double pct_clay = xml.valueDouble("model.site.pctClay");
572 werner 56
    if (fabs(100. - (pct_sand + pct_silt + pct_clay)) > 0.01)
338 werner 57
        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));
58
 
59
    // calculate soil characteristics based on empirical functions (Schwalm & Ek, 2004)
60
    // note: the variables are percentages [0..100]
802 werner 61
    mPsi_sat = -exp((1.54 - 0.0095*pct_sand + 0.0063*pct_silt) * log(10)) * 0.000098; // Eq. 83
338 werner 62
    mPsi_koeff_b = -( 3.1 + 0.157*pct_clay - 0.003*pct_sand );  // Eq. 84
802 werner 63
    mTheta_sat = 0.01 * (50.5 - 0.142*pct_sand - 0.037*pct_clay); // Eq. 78
240 werner 64
    mCanopy.setup();
339 werner 65
 
66
    mPermanentWiltingPoint = heightFromPsi(-4000); // maximum psi is set to a constant of -4MPa
379 werner 67
    if (xml.valueBool("model.settings.waterUseSoilSaturation",false)==false) {
68
        mFieldCapacity = heightFromPsi(-15);
69
    } else {
70
        // =-EXP((1.54-0.0095* pctSand +0.0063* pctSilt)*LN(10))*0.000098
71
        double psi_sat = -exp((1.54-0.0095 * pct_sand + 0.0063*pct_silt)*log(10.))*0.000098;
72
        mFieldCapacity = heightFromPsi(psi_sat);
431 werner 73
        if (logLevelDebug()) qDebug() << "psi: saturation " << psi_sat << "field capacity:" << mFieldCapacity;
379 werner 74
    }
75
 
266 werner 76
    mContent = mFieldCapacity; // start with full water content (in the middle of winter)
802 werner 77
    if (logLevelDebug()) qDebug() << "setup of water: Psi_sat (kPa)" << mPsi_sat << "Theta_sat" << mTheta_sat << "coeff. b" << mPsi_koeff_b;
566 werner 78
    mCanopyConductance = 0.;
496 werner 79
    mLastYear = -1;
621 werner 80
 
81
    // canopy settings
82
    mCanopy.mNeedleFactor = xml.valueDouble("model.settings.interceptionStorageNeedle", 4.);
83
    mCanopy.mDecidousFactor = xml.valueDouble("model.settings.interceptionStorageBroadleaf", 2.);
84
    mSnowPack.mSnowTemperature = xml.valueDouble("model.settings.snowMeltTemperature", 0.);
1157 werner 85
 
86
    mTotalET = mTotalExcess = mSnowRad = 0.;
87
    mSnowDays = 0;
237 werner 88
}
89
 
331 werner 90
/** function to calculate the water pressure [saugspannung] for a given amount of water.
338 werner 91
    returns water potential in kPa.
92
  see http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance */
266 werner 93
inline double WaterCycle::psiFromHeight(const double mm) const
94
{
95
    // psi_x = psi_ref * ( rho_x / rho_ref) ^ b
96
    if (mm<0.001)
97
        return -100000000;
802 werner 98
    double psi_x = mPsi_sat * pow((mm / mSoilDepth / mTheta_sat),mPsi_koeff_b);
338 werner 99
    return psi_x; // pis
266 werner 100
}
101
 
331 werner 102
/// calculate the height of the water column for a given pressure
338 werner 103
/// return water amount in mm
104
/// see http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance
266 werner 105
inline double WaterCycle::heightFromPsi(const double psi_kpa) const
106
{
107
    // rho_x = rho_ref * (psi_x / psi_ref)^(1/b)
802 werner 108
    double h = mSoilDepth * mTheta_sat * pow(psi_kpa / mPsi_sat, 1./mPsi_koeff_b);
266 werner 109
    return h;
110
}
111
 
331 werner 112
/// get canopy characteristics of the resource unit.
113
/// It is important, that species-statistics are valid when this function is called (LAI)!
237 werner 114
void WaterCycle::getStandValues()
115
{
246 werner 116
    mLAINeedle=mLAIBroadleaved=0.;
237 werner 117
    mCanopyConductance=0.;
553 werner 118
    const double ground_vegetationCC = 0.02;
237 werner 119
    double lai;
720 werner 120
    foreach(const ResourceUnitSpecies *rus, mRU->ruSpecies()) {
455 werner 121
        lai = rus->constStatistics().leafAreaIndex();
122
        if (rus->species()->isConiferous())
237 werner 123
            mLAINeedle+=lai;
124
        else
125
            mLAIBroadleaved+=lai;
455 werner 126
        mCanopyConductance += rus->species()->canopyConductance() * lai; // weigh with LAI
237 werner 127
    }
128
    double total_lai = mLAIBroadleaved+mLAINeedle;
502 werner 129
 
130
    // handle cases with LAI < 1 (use generic "ground cover characteristics" instead)
723 werner 131
    /* The LAI used here is derived from the "stockable" area (and not the stocked area).
132
       If the stand has gaps, the available trees are "thinned" across the whole area. Otherwise (when stocked area is used)
133
       the LAI would overestimate the transpiring canopy. However, the current solution overestimates e.g. the interception.
134
       If the "thinned out" LAI is below one, the rest (i.e. the gaps) are thought to be covered by ground vegetation.
135
    */
502 werner 136
    if (total_lai<1.) {
553 werner 137
        mCanopyConductance+=(ground_vegetationCC)*(1. - total_lai);
502 werner 138
        total_lai = 1.;
237 werner 139
    }
502 werner 140
    mCanopyConductance /= total_lai;
141
 
368 werner 142
    if (total_lai < Model::settings().laiThresholdForClosedStands) {
143
        // following Landsberg and Waring: when LAI is < 3 (default for laiThresholdForClosedStands), a linear "ramp" from 0 to 3 is assumed
299 werner 144
        // http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance
368 werner 145
        mCanopyConductance *= total_lai / Model::settings().laiThresholdForClosedStands;
237 werner 146
    }
431 werner 147
    if (logLevelInfo()) qDebug() << "WaterCycle:getStandValues: LAI needle" << mLAINeedle << "LAI Broadl:"<< mLAIBroadleaved << "weighted avg. Conductance (m/2):" << mCanopyConductance;
237 werner 148
}
149
 
502 werner 150
/// calculate responses for ground vegetation, i.e. for "unstocked" areas.
151
/// this duplicates calculations done in Species.
152
/// @return Minimum of vpd and soilwater response for default
153
inline double WaterCycle::calculateBaseSoilAtmosphereResponse(const double psi_kpa, const double vpd_kpa)
154
{
155
    // constant parameters used for ground vegetation:
503 werner 156
    const double mPsiMin = 1.5; // MPa
502 werner 157
    const double mRespVpdExponent = -0.6;
158
    // see SpeciesResponse::soilAtmosphereResponses()
159
    double water_resp;
160
    // see Species::soilwaterResponse:
161
    const double psi_mpa = psi_kpa / 1000.; // convert to MPa
162
    water_resp = limit( 1. - psi_mpa / mPsiMin, 0., 1.);
163
    // see species::vpdResponse
164
 
165
    double vpd_resp;
166
    vpd_resp =  exp(mRespVpdExponent * vpd_kpa);
167
    return qMin(water_resp, vpd_resp);
168
}
169
 
367 werner 170
/// calculate combined VPD and soilwaterresponse for all species
171
/// on the RU. This is used for the calc. of the transpiration.
172
inline double WaterCycle::calculateSoilAtmosphereResponse(const double psi_kpa, const double vpd_kpa)
173
{
174
    double min_response;
502 werner 175
    double total_response = 0; // LAI weighted minimum response for all speices on the RU
176
    double total_lai_factor = 0.;
720 werner 177
    foreach(const ResourceUnitSpecies *rus, mRU->ruSpecies()) {
178
        if (rus->LAIfactor()>0.) {
502 werner 179
            // retrieve the minimum of VPD / soil water response for that species
455 werner 180
            rus->speciesResponse()->soilAtmosphereResponses(psi_kpa, vpd_kpa, min_response);
181
            total_response += min_response * rus->LAIfactor();
502 werner 182
            total_lai_factor += rus->LAIfactor();
367 werner 183
        }
184
    }
455 werner 185
 
502 werner 186
    if (total_lai_factor<1.) {
187
        // the LAI is below 1: the rest is considered as "ground vegetation"
188
        total_response += calculateBaseSoilAtmosphereResponse(psi_kpa, vpd_kpa) * (1. - total_lai_factor);
189
    }
190
 
377 werner 191
    // add an aging factor to the total response (averageAging: leaf area weighted mean aging value):
192
    // conceptually: response = min(vpd_response, water_response)*aging
503 werner 193
    if (total_lai_factor==1.)
194
        total_response *= mRU->averageAging(); // no ground cover: use aging value for all LA
195
    else if (total_lai_factor>0. && mRU->averageAging()>0.)
196
        total_response *= (1.-total_lai_factor)*1. + (total_lai_factor * mRU->averageAging()); // between 0..1: a part of the LAI is "ground cover" (aging=1)
502 werner 197
 
720 werner 198
    DBGMODE(
199
          if (mRU->averageAging()>1. || mRU->averageAging()<0. || total_response<0 || total_response>1.)
200
             qDebug() << "water cycle: average aging invalid. aging:" << mRU->averageAging() << "total response" << total_response << "total lai factor:" << total_lai_factor;
484 werner 201
    );
482 werner 202
 
203
    //DBG_IF(mRU->averageAging()>1. || mRU->averageAging()<0.,"water cycle", "average aging invalid!" );
367 werner 204
    return total_response;
205
}
206
 
207
 
237 werner 208
/// Main Water Cycle function. This function triggers all water related tasks for
209
/// one simulation year.
299 werner 210
/// @sa http://iland.boku.ac.at/water+cycle
237 werner 211
void WaterCycle::run()
212
{
496 werner 213
    // necessary?
214
    if (GlobalSettings::instance()->currentYear() == mLastYear)
215
        return;
626 werner 216
    DebugTimer tw("water:run");
646 werner 217
    WaterCycleData add_data;
626 werner 218
 
237 werner 219
    // preparations (once a year)
367 werner 220
    getStandValues(); // fetch canopy characteristics from iLand (including weighted average for mCanopyConductance)
237 werner 221
    mCanopy.setStandParameters(mLAINeedle,
222
                               mLAIBroadleaved,
223
                               mCanopyConductance);
224
 
225
    // main loop over all days of the year
239 werner 226
    double prec_mm, prec_after_interception, prec_to_soil, et, excess;
338 werner 227
    const Climate *climate = mRU->climate();
228
    const ClimateDay *day = climate->begin();
229
    const ClimateDay *end = climate->end();
237 werner 230
    int doy=0;
1157 werner 231
    mTotalExcess = 0.;
232
    mTotalET = 0.;
233
    mSnowRad = 0.;
234
    mSnowDays = 0;
237 werner 235
    for (; day<end; ++day, ++doy) {
236
        // (1) precipitation of the day
237
        prec_mm = day->preciptitation;
238
        // (2) interception by the crown
777 werner 239
        prec_after_interception = mCanopy.flow(prec_mm);
237 werner 240
        // (3) storage in the snow pack
241
        prec_to_soil = mSnowPack.flow(prec_after_interception, day->temperature);
646 werner 242
        // save extra data (used by e.g. fire module)
243
        add_data.water_to_ground[doy] = prec_to_soil;
244
        add_data.snow_cover[doy] = mSnowPack.snowPack();
1157 werner 245
        if (mSnowPack.snowPack()>0.) {
246
            mSnowRad += day->radiation;
247
            mSnowDays++;
248
        }
249
 
237 werner 250
        // (4) add rest to soil
251
        mContent += prec_to_soil;
266 werner 252
 
253
        excess = 0.;
254
        if (mContent>mFieldCapacity) {
255
            // excess water runoff
256
            excess = mContent - mFieldCapacity;
1157 werner 257
            mTotalExcess += excess;
266 werner 258
            mContent = mFieldCapacity;
259
        }
260
 
367 werner 261
        double current_psi = psiFromHeight(mContent);
262
        mPsi[doy] = current_psi;
553 werner 263
 
335 werner 264
        // (5) transpiration of the vegetation (and of water intercepted in canopy)
367 werner 265
        // calculate the LAI-weighted response values for soil water and vpd:
680 werner 266
        double interception_before_transpiration = mCanopy.interception();
367 werner 267
        double combined_response = calculateSoilAtmosphereResponse( current_psi, day->vpd);
268
        et = mCanopy.evapotranspiration3PG(day, climate->daylength_h(doy), combined_response);
680 werner 269
        // if there is some flow from intercepted water to the ground -> add to "water_to_the_ground"
270
        if (mCanopy.interception() < interception_before_transpiration)
271
            add_data.water_to_ground[doy]+= interception_before_transpiration - mCanopy.interception();
238 werner 272
 
241 werner 273
        mContent -= et; // reduce content (transpiration)
338 werner 274
        // add intercepted water (that is *not* evaporated) again to the soil (or add to snow if temp too low -> call to snowpack)
275
        mContent += mSnowPack.add(mCanopy.interception(),day->temperature);
241 werner 276
 
338 werner 277
        // do not remove water below the PWP (fixed value)
266 werner 278
        if (mContent<mPermanentWiltingPoint) {
271 werner 279
            et -= mPermanentWiltingPoint - mContent; // reduce et (for bookkeeping)
266 werner 280
            mContent = mPermanentWiltingPoint;
237 werner 281
        }
266 werner 282
 
1157 werner 283
        mTotalET += et;
546 werner 284
 
271 werner 285
        //DBGMODE(
239 werner 286
            if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dWaterCycle)) {
240 werner 287
                DebugList &out = GlobalSettings::instance()->debugList(day->id(), GlobalSettings::dWaterCycle);
239 werner 288
                // climatic variables
605 werner 289
                out << day->id() << mRU->index() << mRU->id() << day->temperature << day->vpd << day->preciptitation << day->radiation;
368 werner 290
                out << combined_response; // combined response of all species on RU (min(water, vpd))
239 werner 291
                // fluxes
271 werner 292
                out << prec_after_interception << prec_to_soil << et << mCanopy.evaporationCanopy()
540 werner 293
                        << mContent << mPsi[doy] << excess;
239 werner 294
                // other states
295
                out << mSnowPack.snowPack();
364 werner 296
                //special sanity check:
297
                if (prec_to_soil>0. && mCanopy.interception()>0.)
298
                    if (mSnowPack.snowPack()==0. && day->preciptitation==0)
299
                        qDebug() << "watercontent increase without precipititaion";
239 werner 300
 
301
            }
271 werner 302
        //); // DBGMODE()
239 werner 303
 
237 werner 304
    }
646 werner 305
    // call external modules
306
    GlobalSettings::instance()->model()->modules()->calculateWater(mRU, &add_data);
496 werner 307
    mLastYear = GlobalSettings::instance()->currentYear();
308
 
237 werner 309
}
310
 
311
 
312
namespace Water {
313
 
314
/** calculates the input/output of water that is stored in the snow pack.
315
  The approach is similar to Picus 1.3 and ForestBGC (Running, 1988).
316
  Returns the amount of water that exits the snowpack (precipitation, snow melt) */
317
double SnowPack::flow(const double &preciptitation_mm, const double &temperature)
318
{
621 werner 319
    if (temperature>mSnowTemperature) {
237 werner 320
        if (mSnowPack==0.)
321
            return preciptitation_mm; // no change
322
        else {
323
            // snow melts
945 werner 324
            const double melting_coefficient = 0.7; // mm/C
621 werner 325
            double melt = qMin( (temperature-mSnowTemperature) * melting_coefficient, mSnowPack);
240 werner 326
            mSnowPack -=melt;
237 werner 327
            return preciptitation_mm + melt;
328
        }
329
    } else {
330
        // snow:
331
        mSnowPack += preciptitation_mm;
332
        return 0.; // no output.
333
    }
334
 
335
}
336
 
337
 
334 werner 338
inline double SnowPack::add(const double &preciptitation_mm, const double &temperature)
339
{
945 werner 340
    // do nothing for temps > 0 C
621 werner 341
    if (temperature>mSnowTemperature)
334 werner 342
        return preciptitation_mm;
343
 
945 werner 344
    // temps < 0 C: add to snow
334 werner 345
    mSnowPack += preciptitation_mm;
346
    return 0.;
347
}
348
 
237 werner 349
/** Interception in crown canopy.
350
    Calculates the amount of preciptitation that does not reach the ground and
351
    is stored in the canopy. The approach is adopted from Picus 1.3.
299 werner 352
    Returns the amount of precipitation (mm) that surpasses the canopy layer.
353
    @sa http://iland.boku.ac.at/water+cycle#precipitation_and_interception */
777 werner 354
double Canopy::flow(const double &preciptitation_mm)
237 werner 355
{
356
    // sanity checks
357
    mInterception = 0.;
271 werner 358
    mEvaporation = 0.;
237 werner 359
    if (!mLAI)
360
        return preciptitation_mm;
361
    if (!preciptitation_mm)
362
        return 0.;
363
    double max_interception_mm=0.; // maximum interception based on the current foliage
945 werner 364
    double max_storage_mm=0.; // maximum storage in canopy (current LAI)
365
    double max_storage_potentital = 0.; // storage capacity at very high LAI
237 werner 366
 
367
    if (mLAINeedle>0.) {
368
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
369
        double max_flow_needle = 0.9 * sqrt(1.03 - exp(-0.055*preciptitation_mm));
370
        max_interception_mm += preciptitation_mm *  (1. - max_flow_needle * mLAINeedle/mLAI);
371
        // (2) calculate maximum storage potential based on the current LAI
945 werner 372
        //     by weighing the needle/decidious storage capacity
373
        max_storage_potentital += mNeedleFactor * mLAINeedle/mLAI;
237 werner 374
    }
375
 
376
    if (mLAIBroadleaved>0.) {
377
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
299 werner 378
        double max_flow_broad = 0.9 * pow(1.22 - exp(-0.055*preciptitation_mm), 0.35);
946 werner 379
        max_interception_mm += preciptitation_mm *  (1. - max_flow_broad) * mLAIBroadleaved/mLAI;
237 werner 380
        // (2) calculate maximum storage potential based on the current LAI
945 werner 381
        max_storage_potentital += mDecidousFactor * mLAIBroadleaved/mLAI;
237 werner 382
    }
383
 
945 werner 384
    // the extent to which the maximum stoarge capacity is exploited, depends on LAI:
385
    max_storage_mm = max_storage_potentital * (1. - exp(-0.5 * mLAI));
386
 
237 werner 387
    // (3) calculate actual interception and store for evaporation calculation
388
    mInterception = qMin( max_storage_mm, max_interception_mm );
389
 
335 werner 390
    // (4) limit interception with amount of precipitation
945 werner 391
    mInterception = qMin( mInterception, preciptitation_mm );
335 werner 392
 
393
    // (5) reduce preciptitaion by the amount that is intercepted by the canopy
237 werner 394
    return preciptitation_mm - mInterception;
395
 
396
}
397
 
239 werner 398
/// sets up the canopy. fetch some global parameter values...
399
void Canopy::setup()
237 werner 400
{
240 werner 401
    mAirDensity = Model::settings().airDensity; // kg / m3
237 werner 402
}
403
 
404
void Canopy::setStandParameters(const double LAIneedle, const double LAIbroadleave, const double maxCanopyConductance)
405
{
406
    mLAINeedle = LAIneedle;
407
    mLAIBroadleaved=LAIbroadleave;
408
    mLAI=LAIneedle+LAIbroadleave;
239 werner 409
    mAvgMaxCanopyConductance = maxCanopyConductance;
553 werner 410
 
411
    // clear aggregation containers
562 werner 412
    for (int i=0;i<12;++i) mET0[i]=0.;
553 werner 413
 
237 werner 414
}
415
 
416
 
417
 
256 werner 418
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
419
   This version is based on 3PG. See the Visual Basic Code in 3PGjs.xls.
420
   Returns the total sum of evaporation+transpiration in mm of the day. */
367 werner 421
double Canopy::evapotranspiration3PG(const ClimateDay *climate, const double daylength_h, const double combined_response)
256 werner 422
{
423
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
945 werner 424
    double temperature = climate->temperature; // average temperature of the day (degree C)
256 werner 425
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
426
    double rad = climate->radiation / daylength * 1000000; //convert from MJ/m2 (day sum) to average radiation flow W/m2 [MJ=MWs -> /s * 1,000,000
427
 
561 werner 428
    // the radiation: based on linear empirical function
429
    const double qa = -90.;
430
    const double qb = 0.8;
431
    double net_rad = qa + qb*rad;
432
 
256 werner 433
    //: Landsberg original: const double e20 = 2.2;  //rate of change of saturated VP with T at 20C
965 werner 434
    const double VPDconv = 0.000622; //convert VPD to saturation deficit = 18/29/1000 = molecular weight of H2O/molecular weight of air
256 werner 435
    const double latent_heat = 2460000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
436
 
368 werner 437
    double gBL  = Model::settings().boundaryLayerConductance; // boundary layer conductance
438
 
439
    // canopy conductance.
440
    // The species traits are weighted by LAI on the RU.
441
    // maximum canopy conductance: see getStandValues()
442
    // current response: see calculateSoilAtmosphereResponse(). This is basically a weighted average of min(water_response, vpd_response) for each species
367 werner 443
    double gC = mAvgMaxCanopyConductance * combined_response;
256 werner 444
 
367 werner 445
 
256 werner 446
    double defTerm = mAirDensity * latent_heat * (vpd_mbar * VPDconv) * gBL;
447
 
561 werner 448
    //  with temperature-dependent  slope of  vapor pressure saturation curve
449
    // (following  Allen et al. (1998),  http://www.fao.org/docrep/x0490e/x0490e07.htm#atmospheric%20parameters)
450
    // svp_slope in mbar.
621 werner 451
    //double svp_slope = 4098. * (6.1078 * exp(17.269 * temperature / (temperature + 237.3))) / ((237.3+temperature)*(237.3+temperature));
561 werner 452
 
621 werner 453
    // alternatively: very simple variant (following here the original 3PG code). This
454
    // keeps yields +- same results for summer, but slightly lower values in winter (2011/03/16)
455
    double svp_slope = 2.2;
456
 
256 werner 457
    double div = (1. + svp_slope + gBL / gC);
561 werner 458
    double Etransp = (svp_slope * net_rad + defTerm) / div;
335 werner 459
    double canopy_transpiration = Etransp / latent_heat * daylength;
256 werner 460
 
562 werner 461
    // calculate reference evapotranspiration
462
    // see Adair et al 2008
463
    const double psychrometric_const = 0.0672718682328237; // kPa/degC
464
    const double windspeed = 2.; // m/s
465
    double net_rad_mj_day = net_rad*daylength/1000000.; // convert W/m2 again to MJ/m2*day
466
    double et0_day = 0.408*svp_slope*net_rad_mj_day  + psychrometric_const*900./(temperature+273.)*windspeed*climate->vpd;
467
    double et0_div = svp_slope+psychrometric_const*(1.+0.34*windspeed);
468
    et0_day = et0_day / et0_div;
469
    mET0[climate->month-1] += et0_day;
553 werner 470
 
256 werner 471
    if (mInterception>0.) {
472
        // we assume that for evaporation from leaf surface gBL/gC -> 0
562 werner 473
        double div_evap = 1. + svp_slope;
620 werner 474
        double evap_canopy_potential = (svp_slope*net_rad + defTerm) / div_evap / latent_heat * daylength;
475
        // reduce the amount of transpiration on a wet day based on the approach of
476
        // Wigmosta et al (1994). see http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance
477
 
478
        double ratio_T_E = canopy_transpiration / evap_canopy_potential;
479
        double evap_canopy = qMin(evap_canopy_potential, mInterception);
480
 
481
        // for interception -> 0, the canopy transpiration is unchanged
482
        canopy_transpiration = (evap_canopy_potential - evap_canopy) * ratio_T_E;
483
 
562 werner 484
        mInterception -= evap_canopy; // reduce interception
485
        mEvaporation = evap_canopy; // evaporation from intercepted water
620 werner 486
 
256 werner 487
    }
335 werner 488
    return canopy_transpiration;
256 werner 489
}
490
 
237 werner 491
} // end namespace