Subversion Repositories public iLand

Rev

Rev 720 | Rev 777 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 720 Rev 723
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/watercycle.cpp':
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/watercycle.cpp':
2
/********************************************************************************************
2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
6
**
7
**    This program is free software: you can redistribute it and/or modify
7
**    This program is free software: you can redistribute it and/or modify
8
**    it under the terms of the GNU General Public License as published by
8
**    it under the terms of the GNU General Public License as published by
9
**    the Free Software Foundation, either version 3 of the License, or
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
10
**    (at your option) any later version.
11
**
11
**
12
**    This program is distributed in the hope that it will be useful,
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
15
**    GNU General Public License for more details.
16
**
16
**
17
**    You should have received a copy of the GNU General Public License
17
**    You should have received a copy of the GNU General Public License
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
19
********************************************************************************************/
20
20
21
#include "global.h"
21
#include "global.h"
22
#include "watercycle.h"
22
#include "watercycle.h"
23
#include "climate.h"
23
#include "climate.h"
24
#include "resourceunit.h"
24
#include "resourceunit.h"
25
#include "species.h"
25
#include "species.h"
26
#include "model.h"
26
#include "model.h"
27
#include "helper.h"
27
#include "helper.h"
28
#include "modules.h"
28
#include "modules.h"
29
29
30
/** @class WaterCycle
30
/** @class WaterCycle
31
  @ingroup core
31
  @ingroup core
32
  simulates the water cycle on a ResourceUnit.
32
  simulates the water cycle on a ResourceUnit.
33
  The WaterCycle is simulated with a daily time step on the spatial level of a ResourceUnit. Related are
33
  The WaterCycle is simulated with a daily time step on the spatial level of a ResourceUnit. Related are
34
  the snow module (SnowPack), and Canopy module that simulates the interception (and evaporation) of precipitation and the
34
  the snow module (SnowPack), and Canopy module that simulates the interception (and evaporation) of precipitation and the
35
  transpiration from the canopy.
35
  transpiration from the canopy.
36
  The WaterCycle covers the "soil water bucket". Main entry function is run().
36
  The WaterCycle covers the "soil water bucket". Main entry function is run().
37

37

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