Subversion Repositories public iLand

Rev

Rev 368 | Rev 377 | 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 {
372 werner 81
        mCanopyConductance = Model::settings().boundaryLayerConductance; // defaults to 0.2
237 werner 82
    }
368 werner 83
    if (total_lai < Model::settings().laiThresholdForClosedStands) {
84
        // following Landsberg and Waring: when LAI is < 3 (default for laiThresholdForClosedStands), a linear "ramp" from 0 to 3 is assumed
299 werner 85
        // http://iland.boku.ac.at/water+cycle#transpiration_and_canopy_conductance
368 werner 86
        mCanopyConductance *= total_lai / Model::settings().laiThresholdForClosedStands;
237 werner 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;
368 werner 170
                out << combined_response; // combined response of all species on RU (min(water, vpd))
239 werner 171
                // fluxes
271 werner 172
                out << prec_after_interception << prec_to_soil << et << mCanopy.evaporationCanopy()
173
                        << mRelativeContent[doy]*mSoilDepth << mPsi[doy] << excess;
239 werner 174
                // other states
175
                out << mSnowPack.snowPack();
364 werner 176
                //special sanity check:
177
                if (prec_to_soil>0. && mCanopy.interception()>0.)
178
                    if (mSnowPack.snowPack()==0. && day->preciptitation==0)
179
                        qDebug() << "watercontent increase without precipititaion";
239 werner 180
 
181
            }
271 werner 182
        //); // DBGMODE()
239 werner 183
 
237 werner 184
    }
185
}
186
 
187
 
188
namespace Water {
189
 
190
/** calculates the input/output of water that is stored in the snow pack.
191
  The approach is similar to Picus 1.3 and ForestBGC (Running, 1988).
192
  Returns the amount of water that exits the snowpack (precipitation, snow melt) */
193
double SnowPack::flow(const double &preciptitation_mm, const double &temperature)
194
{
195
    if (temperature>0.) {
196
        if (mSnowPack==0.)
197
            return preciptitation_mm; // no change
198
        else {
199
            // snow melts
200
            const double melting_coefficient = 0.7; // mm/°C
240 werner 201
            double melt = qMin(temperature * melting_coefficient, mSnowPack);
202
            mSnowPack -=melt;
237 werner 203
            return preciptitation_mm + melt;
204
        }
205
    } else {
206
        // snow:
207
        mSnowPack += preciptitation_mm;
208
        return 0.; // no output.
209
    }
210
 
211
}
212
 
213
 
334 werner 214
inline double SnowPack::add(const double &preciptitation_mm, const double &temperature)
215
{
216
    // do nothing for temps > 0°
217
    if (temperature>0.)
218
        return preciptitation_mm;
219
 
220
    // temps < 0°: add to snow
221
    mSnowPack += preciptitation_mm;
222
    return 0.;
223
}
224
 
237 werner 225
/** Interception in crown canopy.
226
    Calculates the amount of preciptitation that does not reach the ground and
227
    is stored in the canopy. The approach is adopted from Picus 1.3.
299 werner 228
    Returns the amount of precipitation (mm) that surpasses the canopy layer.
229
    @sa http://iland.boku.ac.at/water+cycle#precipitation_and_interception */
237 werner 230
double Canopy::flow(const double &preciptitation_mm, const double &temperature)
231
{
232
    // sanity checks
233
    mInterception = 0.;
271 werner 234
    mEvaporation = 0.;
237 werner 235
    if (!mLAI)
236
        return preciptitation_mm;
237
    if (!preciptitation_mm)
238
        return 0.;
239
    double max_interception_mm=0.; // maximum interception based on the current foliage
240
    double max_storage_mm=0.; // maximum storage in canopy
241
 
242
    if (mLAINeedle>0.) {
243
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
244
        double max_flow_needle = 0.9 * sqrt(1.03 - exp(-0.055*preciptitation_mm));
245
        max_interception_mm += preciptitation_mm *  (1. - max_flow_needle * mLAINeedle/mLAI);
246
        // (2) calculate maximum storage potential based on the current LAI
247
        double max_storage_needle = 4. * (1. - exp(-0.55*mLAINeedle) );
248
        max_storage_mm += max_storage_needle;
249
    }
250
 
251
    if (mLAIBroadleaved>0.) {
252
        // (1) calculate maximum fraction of thru-flow the crown (based on precipitation)
299 werner 253
        double max_flow_broad = 0.9 * pow(1.22 - exp(-0.055*preciptitation_mm), 0.35);
237 werner 254
        max_interception_mm += preciptitation_mm *  (1. - max_flow_broad * mLAIBroadleaved/mLAI);
255
        // (2) calculate maximum storage potential based on the current LAI
299 werner 256
        double max_storage_broad = 2. * (1. - exp(-0.5*mLAIBroadleaved) );
237 werner 257
        max_storage_mm += max_storage_broad;
258
    }
259
 
260
    // (3) calculate actual interception and store for evaporation calculation
261
    mInterception = qMin( max_storage_mm, max_interception_mm );
262
 
335 werner 263
    // (4) limit interception with amount of precipitation
264
    mInterception = qMin( mInterception, preciptitation_mm);
265
 
266
    // (5) reduce preciptitaion by the amount that is intercepted by the canopy
237 werner 267
    return preciptitation_mm - mInterception;
268
 
269
}
270
 
239 werner 271
/// sets up the canopy. fetch some global parameter values...
272
void Canopy::setup()
237 werner 273
{
240 werner 274
    mAirDensity = Model::settings().airDensity; // kg / m3
237 werner 275
}
276
 
277
void Canopy::setStandParameters(const double LAIneedle, const double LAIbroadleave, const double maxCanopyConductance)
278
{
279
    mLAINeedle = LAIneedle;
280
    mLAIBroadleaved=LAIbroadleave;
281
    mLAI=LAIneedle+LAIbroadleave;
239 werner 282
    mAvgMaxCanopyConductance = maxCanopyConductance;
237 werner 283
}
284
 
285
 
286
 
256 werner 287
/** calculate the daily evaporation/transpiration using the Penman-Monteith-Equation.
288
   This version is based on 3PG. See the Visual Basic Code in 3PGjs.xls.
289
   Returns the total sum of evaporation+transpiration in mm of the day. */
367 werner 290
double Canopy::evapotranspiration3PG(const ClimateDay *climate, const double daylength_h, const double combined_response)
256 werner 291
{
292
    double vpd_mbar = climate->vpd * 10.; // convert from kPa to mbar
293
    double temperature = climate->temperature; // average temperature of the day (°C)
294
    double daylength = daylength_h * 3600.; // daylength in seconds (convert from length in hours)
295
    double rad = climate->radiation / daylength * 1000000; //convert from MJ/m2 (day sum) to average radiation flow W/m2 [MJ=MWs -> /s * 1,000,000
296
 
297
    //: Landsberg original: const double e20 = 2.2;  //rate of change of saturated VP with T at 20C
298
    const double VPDconv = 0.000622; //convert VPD to saturation deficit = 18/29/1000
299
    const double latent_heat = 2460000.; // Latent heat of vaporization. Energy required per unit mass of water vaporized [J kg-1]
300
 
368 werner 301
    double gBL  = Model::settings().boundaryLayerConductance; // boundary layer conductance
302
 
303
    // canopy conductance.
304
    // The species traits are weighted by LAI on the RU.
305
    // maximum canopy conductance: see getStandValues()
306
    // current response: see calculateSoilAtmosphereResponse(). This is basically a weighted average of min(water_response, vpd_response) for each species
367 werner 307
    double gC = mAvgMaxCanopyConductance * combined_response;
256 werner 308
 
367 werner 309
 
256 werner 310
    double defTerm = mAirDensity * latent_heat * (vpd_mbar * VPDconv) * gBL;
311
        // saturation vapor pressure (Running 1988, Eq. 1) in mbar
312
    double svp = 6.1078 * exp((17.269 * temperature) / (237.3 + temperature) );
313
    // the slope of svp is, thanks to http://www.wolframalpha.com/input/?i=derive+y%3D6.1078+exp+((17.269x)/(237.3%2Bx))
314
    double svp_slope = svp * ( 17.269/(237.3+temperature) - 17.269*temperature/((237.3+temperature)*(237.3+temperature)) );
315
 
316
    double div = (1. + svp_slope + gBL / gC);
317
    double Etransp = (svp_slope * rad + defTerm) / div;
335 werner 318
    double canopy_transpiration = Etransp / latent_heat * daylength;
256 werner 319
 
320
    if (mInterception>0.) {
321
        // we assume that for evaporation from leaf surface gBL/gC -> 0
322
        double div_evap = 1 + svp_slope;
323
        double evap = (svp_slope*rad + defTerm) / div_evap / latent_heat * daylength;
324
        evap = qMin(evap, mInterception);
335 werner 325
        mInterception -= evap; // reduce interception
326
        mEvaporation = evap; // evaporation from intercepted water
256 werner 327
    }
335 werner 328
    return canopy_transpiration;
256 werner 329
}
330
 
237 werner 331
} // end namespace