Subversion Repositories public iLand

Rev

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

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