Rev 552 | Rev 557 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | |||
468 | werner | 2 | #include "snag.h" |
3 | #include "tree.h" |
||
4 | #include "species.h" |
||
5 | #include "globalsettings.h" |
||
6 | #include "expression.h" |
||
490 | werner | 7 | // for calculation of climate decomposition |
8 | #include "resourceunit.h" |
||
9 | #include "watercycle.h" |
||
10 | #include "climate.h" |
||
541 | werner | 11 | #include "model.h" |
468 | werner | 12 | |
13 | /** @class Snag |
||
14 | Snag deals with carbon / nitrogen fluxes from the forest until the reach soil pools. |
||
490 | werner | 15 | Snag lives on the level of the ResourceUnit; carbon fluxes from trees enter Snag, and parts of the biomass of snags |
468 | werner | 16 | is subsequently forwarded to the soil sub model. |
522 | werner | 17 | Carbon is stored in three classes (depending on the size) |
528 | werner | 18 | The Snag dynamics class uses the following species parameter: |
19 | cnFoliage, cnFineroot, cnWood, snagHalflife, snagKSW |
||
468 | werner | 20 | |
21 | */ |
||
22 | // static variables |
||
528 | werner | 23 | double Snag::mDBHLower = -1.; |
522 | werner | 24 | double Snag::mDBHHigher = 0.; |
25 | double Snag::mCarbonThreshold[] = {0., 0., 0.}; |
||
26 | |||
534 | werner | 27 | double CNPair::biomassCFraction = biomassCFraction; // get global from globalsettings.h |
468 | werner | 28 | |
534 | werner | 29 | /// add biomass and weigh the parameter_value with the current C-content of the pool |
30 | void CNPool::addBiomass(const double biomass, const double CNratio, const double parameter_value) |
||
31 | { |
||
32 | if (biomass==0.) |
||
33 | return; |
||
34 | double new_c = biomass*biomassCFraction; |
||
35 | double p_old = C / (new_c + C); |
||
36 | mParameter = mParameter*p_old + parameter_value*(1.-p_old); |
||
37 | CNPair::addBiomass(biomass, CNratio); |
||
38 | } |
||
39 | |||
40 | // increase pool (and weigh the value) |
||
41 | void CNPool::operator+=(const CNPool &s) |
||
42 | { |
||
43 | if (s.C==0.) |
||
44 | return; |
||
45 | mParameter = parameter(s); // calculate weighted parameter |
||
46 | C+=s.C; |
||
47 | N+=s.N; |
||
48 | } |
||
49 | |||
50 | double CNPool::parameter(const CNPool &s) const |
||
51 | { |
||
52 | if (s.C==0.) |
||
53 | return parameter(); |
||
54 | double p_old = C / (s.C + C); |
||
55 | double result = mParameter*p_old + s.parameter()*(1.-p_old); |
||
56 | return result; |
||
57 | } |
||
58 | |||
59 | |||
522 | werner | 60 | void Snag::setupThresholds(const double lower, const double upper) |
61 | { |
||
62 | if (mDBHLower == lower) |
||
63 | return; |
||
64 | mDBHLower = lower; |
||
65 | mDBHHigher = upper; |
||
66 | mCarbonThreshold[0] = lower / 2.; |
||
67 | mCarbonThreshold[1] = lower + (upper - lower)/2.; |
||
68 | mCarbonThreshold[2] = upper + (upper - lower)/2.; |
||
69 | //# threshold levels for emptying out the dbh-snag-classes |
||
70 | //# derived from Psme woody allometry, converted to C, with a threshold level set to 10% |
||
71 | //# values in kg! |
||
72 | for (int i=0;i<3;i++) |
||
73 | mCarbonThreshold[i] = 0.10568*pow(mCarbonThreshold[i],2.4247)*0.5*0.1; |
||
74 | } |
||
75 | |||
76 | |||
468 | werner | 77 | Snag::Snag() |
78 | { |
||
490 | werner | 79 | mRU = 0; |
534 | werner | 80 | CNPair::setCFraction(biomassCFraction); |
468 | werner | 81 | } |
82 | |||
490 | werner | 83 | void Snag::setup( const ResourceUnit *ru) |
468 | werner | 84 | { |
490 | werner | 85 | mRU = ru; |
86 | mClimateFactor = 0.; |
||
468 | werner | 87 | // branches |
88 | mBranchCounter=0; |
||
89 | for (int i=0;i<3;i++) { |
||
90 | mTimeSinceDeath[i] = 0.; |
||
91 | mNumberOfSnags[i] = 0.; |
||
522 | werner | 92 | mAvgDbh[i] = 0.; |
93 | mAvgHeight[i] = 0.; |
||
94 | mAvgVolume[i] = 0.; |
||
95 | mKSW[i] = 0.; |
||
96 | mCurrentKSW[i] = 0.; |
||
468 | werner | 97 | } |
475 | werner | 98 | mTotalSnagCarbon = 0.; |
528 | werner | 99 | if (mDBHLower<=0) |
100 | throw IException("Snag::setupThresholds() not called or called with invalid parameters."); |
||
468 | werner | 101 | } |
102 | |||
475 | werner | 103 | // debug outputs |
104 | QList<QVariant> Snag::debugList() |
||
105 | { |
||
106 | // list columns |
||
107 | // for three pools |
||
108 | QList<QVariant> list; |
||
109 | |||
523 | werner | 110 | // totals |
111 | list << mTotalSnagCarbon << mTotalIn.C << mTotalToAtm.C << mSWDtoSoil.C << mSWDtoSoil.N; |
||
477 | werner | 112 | // fluxes to labile soil pool and to refractory soil pool |
524 | werner | 113 | list << mLabileFlux.C << mLabileFlux.N << mRefractoryFlux.C << mRefractoryFlux.N; |
475 | werner | 114 | |
115 | for (int i=0;i<3;i++) { |
||
116 | // pools "swdx_c", "swdx_n", "swdx_count", "swdx_tsd", "toswdx_c", "toswdx_n" |
||
117 | list << mSWD[i].C << mSWD[i].N << mNumberOfSnags[i] << mTimeSinceDeath[i] << mToSWD[i].C << mToSWD[i].N; |
||
524 | werner | 118 | list << mAvgDbh[i] << mAvgHeight[i] << mAvgVolume[i]; |
475 | werner | 119 | } |
120 | |||
540 | werner | 121 | // branch/coarse wood pools (5 yrs) |
122 | for (int i=0;i<5;i++) { |
||
123 | list << mOtherWood[i].C << mOtherWood[i].N; |
||
124 | } |
||
125 | // list << mOtherWood[mBranchCounter].C << mOtherWood[mBranchCounter].N |
||
126 | // << mOtherWood[(mBranchCounter+1)%5].C << mOtherWood[(mBranchCounter+1)%5].N |
||
127 | // << mOtherWood[(mBranchCounter+2)%5].C << mOtherWood[(mBranchCounter+2)%5].N |
||
128 | // << mOtherWood[(mBranchCounter+3)%5].C << mOtherWood[(mBranchCounter+3)%5].N |
||
129 | // << mOtherWood[(mBranchCounter+4)%5].C << mOtherWood[(mBranchCounter+4)%5].N; |
||
475 | werner | 130 | return list; |
131 | } |
||
132 | |||
468 | werner | 133 | void Snag::newYear() |
134 | { |
||
135 | for (int i=0;i<3;i++) { |
||
136 | mToSWD[i].clear(); // clear transfer pools to standing-woody-debris |
||
522 | werner | 137 | mCurrentKSW[i] = 0.; |
468 | werner | 138 | } |
139 | mLabileFlux.clear(); |
||
140 | mRefractoryFlux.clear(); |
||
476 | werner | 141 | mTotalToAtm.clear(); |
142 | mTotalToExtern.clear(); |
||
143 | mTotalIn.clear(); |
||
477 | werner | 144 | mSWDtoSoil.clear(); |
468 | werner | 145 | } |
146 | |||
490 | werner | 147 | /// calculate the dynamic climate modifier for decomposition 're' |
522 | werner | 148 | /// calculation is done on the level of ResourceUnit because the water content per day is needed. |
490 | werner | 149 | double Snag::calculateClimateFactors() |
150 | { |
||
151 | double ft, fw; |
||
152 | double f_sum = 0.; |
||
552 | werner | 153 | int iday=0; |
553 | werner | 154 | // calculate the water-factor for each month (see Adair et al 2008) |
155 | double fw_month[12]; |
||
156 | double ratio; |
||
157 | for (int m=0;m<12;m++) { |
||
158 | if (mRU->waterCycle()->potentialEvapotranspiration()[m]>0.) |
||
159 | ratio = mRU->climate()->precipitationMonth()[m] / mRU->waterCycle()->potentialEvapotranspiration()[m]; |
||
160 | else |
||
161 | ratio = 0; |
||
162 | fw_month[m] = 1. / (1. + 30.*exp(-8.5*ratio)); |
||
163 | qDebug() <<"month"<< m << "PET" << mRU->waterCycle()->potentialEvapotranspiration()[m] << "prec" <<mRU->climate()->precipitationMonth()[m]; |
||
164 | } |
||
165 | |||
552 | werner | 166 | for (const ClimateDay *day=mRU->climate()->begin(); day!=mRU->climate()->end(); ++day, ++iday) |
490 | werner | 167 | { |
168 | ft = exp(308.56*(1./56.02-1./((273.+day->temperature)-227.13))); // empirical variable Q10 model of Lloyd and Taylor (1994), see also Adair et al. (2008) |
||
553 | werner | 169 | fw = fw_month[day->month-1]; |
540 | werner | 170 | |
490 | werner | 171 | f_sum += ft*fw; |
172 | } |
||
173 | // the climate factor is defined as the arithmentic annual mean value |
||
174 | mClimateFactor = f_sum / double(mRU->climate()->daysOfYear()); |
||
175 | return mClimateFactor; |
||
176 | } |
||
177 | |||
522 | werner | 178 | /// do the yearly calculation |
179 | /// see http://iland.boku.ac.at/snag+dynamics |
||
526 | werner | 180 | void Snag::calculateYear() |
468 | werner | 181 | { |
522 | werner | 182 | mSWDtoSoil.clear(); |
532 | werner | 183 | const double climate_factor_re = calculateClimateFactors(); // calculate anyway, because also the soil module needs it (and currently one can have Snag and Soil only as a couple) |
477 | werner | 184 | if (isEmpty()) // nothing to do |
475 | werner | 185 | return; |
186 | |||
468 | werner | 187 | // process branches: every year one of the five baskets is emptied and transfered to the refractory soil pool |
540 | werner | 188 | mRefractoryFlux+=mOtherWood[mBranchCounter]; |
189 | |||
190 | mOtherWood[mBranchCounter].clear(); |
||
468 | werner | 191 | mBranchCounter= (mBranchCounter+1) % 5; // increase index, roll over to 0. |
540 | werner | 192 | // decay of branches/coarse roots |
193 | for (int i=0;i<5;i++) { |
||
194 | if (mOtherWood[i].C>0.) { |
||
195 | double survive_rate = exp(- climate_factor_re * mOtherWood[i].parameter() ); // parameter: the "kyr" value... |
||
196 | mOtherWood[i].C *= survive_rate; |
||
197 | } |
||
198 | } |
||
468 | werner | 199 | |
200 | // process standing snags. |
||
201 | // the input of the current year is in the mToSWD-Pools |
||
202 | for (int i=0;i<3;i++) { |
||
203 | |||
522 | werner | 204 | // update the swd-pool with this years' input |
205 | if (!mToSWD[i].isEmpty()) { |
||
206 | // update decay rate (apply average yearly input to the state parameters) |
||
207 | mKSW[i] = mKSW[i]*(mSWD[i].C/(mSWD[i].C+mToSWD[i].C)) + mCurrentKSW[i]*(mToSWD[i].C/(mSWD[i].C+mToSWD[i].C)); |
||
208 | //move content to the SWD pool |
||
209 | mSWD[i] += mToSWD[i]; |
||
210 | } |
||
475 | werner | 211 | |
522 | werner | 212 | if (mSWD[i].C > 0) { |
213 | // reduce the Carbon (note: the N stays, thus the CN ratio changes) |
||
214 | // use the decay rate that is derived as a weighted average of all standing woody debris |
||
523 | werner | 215 | double survive_rate = exp(-mKSW[i] *climate_factor_re * 1. ); // 1: timestep |
216 | mTotalToAtm.C += mSWD[i].C * (1. - survive_rate); |
||
217 | mSWD[i].C *= survive_rate; |
||
468 | werner | 218 | |
522 | werner | 219 | // transition to downed woody debris |
220 | // update: use negative exponential decay, species parameter: half-life |
||
221 | // modified for the climatic effect on decomposition, i.e. if decomp is slower, snags stand longer and vice versa |
||
222 | // this is loosely oriented on Standcarb2 (http://andrewsforest.oregonstate.edu/pubs/webdocs/models/standcarb2.htm), |
||
223 | // where lag times for cohort transitions are linearly modified with re although here individual good or bad years have |
||
224 | // an immediate effect, the average climatic influence should come through (and it is inherently transient) |
||
225 | // note that swd.hl is species-specific, and thus a weighted average over the species in the input (=mortality) |
||
226 | // needs to be calculated, followed by a weighted update of the previous swd.hl. |
||
227 | // As weights here we use stem number, as the processes here pertain individual snags |
||
228 | // calculate the transition probability of SWD to downed dead wood |
||
468 | werner | 229 | |
522 | werner | 230 | double half_life = mHalfLife[i] / climate_factor_re; |
231 | double rate = -M_LN2 / half_life; // M_LN2: math. constant |
||
232 | |||
233 | // higher decay rate for the class with smallest diameters |
||
234 | if (i==0) |
||
235 | rate*=2.; |
||
236 | |||
523 | werner | 237 | double transfer = 1. - exp(rate); |
522 | werner | 238 | |
468 | werner | 239 | // calculate flow to soil pool... |
522 | werner | 240 | mSWDtoSoil += mSWD[i] * transfer; |
241 | mRefractoryFlux += mSWD[i] * transfer; |
||
242 | mSWD[i] *= (1.-transfer); // reduce pool |
||
468 | werner | 243 | // calculate the stem number of remaining snags |
522 | werner | 244 | mNumberOfSnags[i] = mNumberOfSnags[i] * (1. - transfer); |
523 | werner | 245 | |
246 | mTimeSinceDeath[i] += 1.; |
||
522 | werner | 247 | // if stems<0.5, empty the whole cohort into DWD, i.e. release the last bit of C and N and clear the stats |
248 | // also, if the Carbon of an average snag is less than 10% of the original average tree |
||
249 | // (derived from allometries for the three diameter classes), the whole cohort is emptied out to DWD |
||
250 | if (mNumberOfSnags[i] < 0.5 || mSWD[i].C / mNumberOfSnags[i] < mCarbonThreshold[i]) { |
||
251 | // clear the pool: add the rest to the soil, clear statistics of the pool |
||
468 | werner | 252 | mRefractoryFlux += mSWD[i]; |
522 | werner | 253 | mSWDtoSoil += mSWD[i]; |
468 | werner | 254 | mSWD[i].clear(); |
522 | werner | 255 | mAvgDbh[i] = 0.; |
256 | mAvgHeight[i] = 0.; |
||
257 | mAvgVolume[i] = 0.; |
||
258 | mKSW[i] = 0.; |
||
259 | mCurrentKSW[i] = 0.; |
||
260 | mHalfLife[i] = 0.; |
||
261 | mTimeSinceDeath[i] = 0.; |
||
468 | werner | 262 | } |
522 | werner | 263 | |
468 | werner | 264 | } |
522 | werner | 265 | |
468 | werner | 266 | } |
522 | werner | 267 | // total carbon in the snag-container on the RU *after* processing is the content of the |
475 | werner | 268 | // standing woody debris pools + the branches |
269 | mTotalSnagCarbon = mSWD[0].C + mSWD[1].C + mSWD[2].C + |
||
540 | werner | 270 | mOtherWood[0].C + mOtherWood[1].C + mOtherWood[2].C + mOtherWood[3].C + mOtherWood[4].C; |
468 | werner | 271 | } |
272 | |||
273 | /// foliage and fineroot litter is transferred during tree growth. |
||
274 | void Snag::addTurnoverLitter(const Tree *tree, const double litter_foliage, const double litter_fineroot) |
||
275 | { |
||
534 | werner | 276 | mLabileFlux.addBiomass(litter_foliage, tree->species()->cnFoliage(), tree->species()->snagKyl()); |
277 | mLabileFlux.addBiomass(litter_fineroot, tree->species()->cnFineroot(), tree->species()->snagKyl()); |
||
468 | werner | 278 | } |
279 | |||
280 | /// after the death of the tree the five biomass compartments are processed. |
||
281 | void Snag::addMortality(const Tree *tree) |
||
282 | { |
||
528 | werner | 283 | const Species *species = tree->species(); |
468 | werner | 284 | |
285 | // immediate flows: 100% of foliage, 100% of fine roots: they go to the labile pool |
||
534 | werner | 286 | mLabileFlux.addBiomass(tree->biomassFoliage(), species->cnFoliage(), tree->species()->snagKyl()); |
287 | mLabileFlux.addBiomass(tree->biomassFineRoot(), species->cnFineroot(), tree->species()->snagKyl()); |
||
468 | werner | 288 | |
540 | werner | 289 | // branches and coarse roots are equally distributed over five years: |
290 | double biomass_rest = (tree->biomassBranch()+tree->biomassCoarseRoot()) * 0.2; |
||
468 | werner | 291 | for (int i=0;i<5; i++) |
540 | werner | 292 | mOtherWood[i].addBiomass(biomass_rest, species->cnWood(), tree->species()->snagKyr()); |
468 | werner | 293 | |
540 | werner | 294 | // just for book-keeping: keep track of all inputs into branches / roots / swd |
295 | mTotalIn.addBiomass(tree->biomassBranch() + tree->biomassCoarseRoot() + tree->biomassStem(), species->cnWood()); |
||
468 | werner | 296 | // stem biomass is transferred to the standing woody debris pool (SWD), increase stem number of pool |
522 | werner | 297 | int pi = poolIndex(tree->dbh()); // get right transfer pool |
298 | |||
299 | // update statistics - stemnumber-weighted averages |
||
300 | // note: here the calculations are repeated for every died trees (i.e. consecutive weighting ... but delivers the same results) |
||
301 | double p_old = mNumberOfSnags[pi] / (mNumberOfSnags[pi] + 1); // weighting factor for state vars (based on stem numbers) |
||
302 | double p_new = 1. / (mNumberOfSnags[pi] + 1); // weighting factor for added tree (p_old + p_new = 1). |
||
303 | mAvgDbh[pi] = mAvgDbh[pi]*p_old + tree->dbh()*p_new; |
||
304 | mAvgHeight[pi] = mAvgHeight[pi]*p_old + tree->height()*p_new; |
||
305 | mAvgVolume[pi] = mAvgVolume[pi]*p_old + tree->volume()*p_new; |
||
306 | mTimeSinceDeath[pi] = mTimeSinceDeath[pi]*p_old + 1.*p_new; |
||
528 | werner | 307 | mHalfLife[pi] = mHalfLife[pi]*p_old + species->snagHalflife()* p_new; |
522 | werner | 308 | |
309 | // average the decay rate (ksw); this is done based on the carbon content |
||
310 | // aggregate all trees that die in the current year (and save weighted decay rates to CurrentKSW) |
||
311 | if (tree->biomassStem()==0) |
||
312 | throw IException("Snag::addMortality: tree without stem biomass!!"); |
||
313 | p_old = mToSWD[pi].C / (mToSWD[pi].C + tree->biomassStem()* biomassCFraction); |
||
314 | p_new =tree->biomassStem()* biomassCFraction / (mToSWD[pi].C + tree->biomassStem()* biomassCFraction); |
||
534 | werner | 315 | mCurrentKSW[pi] = mCurrentKSW[pi]*p_old + species->snagKsw() * p_new; |
522 | werner | 316 | mNumberOfSnags[pi]++; |
523 | werner | 317 | |
318 | // finally add the biomass |
||
534 | werner | 319 | CNPool &to_swd = mToSWD[pi]; |
320 | to_swd.addBiomass(tree->biomassStem(), species->cnWood(), tree->species()->snagKyr()); |
||
468 | werner | 321 | } |
322 | |||
323 | /// add residual biomass of 'tree' after harvesting. |
||
522 | werner | 324 | /// remove_{stem, branch, foliage}_fraction: percentage of biomass compartment that is *removed* by the harvest operation (i.e.: not to stay in the system) |
528 | werner | 325 | /// records on harvested biomass is collected (mTotalToExtern-pool). |
468 | werner | 326 | void Snag::addHarvest(const Tree* tree, const double remove_stem_fraction, const double remove_branch_fraction, const double remove_foliage_fraction ) |
327 | { |
||
528 | werner | 328 | const Species *species = tree->species(); |
468 | werner | 329 | |
330 | // immediate flows: 100% of residual foliage, 100% of fine roots: they go to the labile pool |
||
534 | werner | 331 | mLabileFlux.addBiomass(tree->biomassFoliage() * (1. - remove_foliage_fraction), species->cnFoliage(), tree->species()->snagKyl()); |
332 | mLabileFlux.addBiomass(tree->biomassFineRoot(), species->cnFineroot(), tree->species()->snagKyl()); |
||
540 | werner | 333 | |
528 | werner | 334 | // for branches, add all biomass that remains in the forest to the soil |
534 | werner | 335 | mRefractoryFlux.addBiomass(tree->biomassBranch()*(1.-remove_branch_fraction), species->cnWood(), tree->species()->snagKyr()); |
528 | werner | 336 | // the same treatment for stem residuals |
534 | werner | 337 | mRefractoryFlux.addBiomass(tree->biomassStem() * remove_stem_fraction, species->cnWood(), tree->species()->snagKyr()); |
468 | werner | 338 | |
540 | werner | 339 | // split the corase wood biomass into parts (slower decay) |
340 | double biomass_rest = (tree->biomassCoarseRoot()) * 0.2; |
||
341 | for (int i=0;i<5; i++) |
||
342 | mOtherWood[i].addBiomass(biomass_rest, species->cnWood(), tree->species()->snagKyr()); |
||
343 | |||
344 | |||
528 | werner | 345 | // for book-keeping... |
346 | mTotalToExtern.addBiomass(tree->biomassFoliage()*remove_foliage_fraction + |
||
347 | tree->biomassBranch()*remove_branch_fraction + |
||
348 | tree->biomassStem()*remove_stem_fraction, species->cnWood()); |
||
468 | werner | 349 | } |
350 | |||
534 | werner | 351 | |
352 | |||
353 |