Subversion Repositories public iLand

Rev

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

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