Subversion Repositories public iLand

Rev

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

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