Subversion Repositories public iLand

Rev

Rev 1157 | Rev 1218 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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