Rev 121 | Rev 125 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | |||
117 | Werner | 2 | #include "global.h" |
3 | #include "tree.h" |
||
3 | Werner | 4 | |
83 | Werner | 5 | #include "grid.h" |
3 | Werner | 6 | |
83 | Werner | 7 | #include "stamp.h" |
90 | Werner | 8 | #include "species.h" |
107 | Werner | 9 | #include "ressourceunit.h" |
38 | Werner | 10 | |
110 | Werner | 11 | // static varaibles |
106 | Werner | 12 | FloatGrid *Tree::mGrid = 0; |
13 | FloatGrid *Tree::mHeightGrid = 0; |
||
40 | Werner | 14 | int Tree::m_statPrint=0; |
48 | Werner | 15 | int Tree::m_statAboveZ=0; |
105 | Werner | 16 | int Tree::m_statCreated=0; |
40 | Werner | 17 | int Tree::m_nextId=0; |
53 | Werner | 18 | float Tree::lafactor = 1.; |
106 | Werner | 19 | int Tree::mDebugid = -1; |
40 | Werner | 20 | |
110 | Werner | 21 | // lifecycle |
3 | Werner | 22 | Tree::Tree() |
23 | { |
||
106 | Werner | 24 | mDbh = 0; |
25 | mHeight = 0; |
||
26 | mSpecies = 0; |
||
107 | Werner | 27 | mRU = 0; |
106 | Werner | 28 | mId = m_nextId++; |
105 | Werner | 29 | m_statCreated++; |
3 | Werner | 30 | } |
38 | Werner | 31 | |
15 | Werner | 32 | /** get distance and direction between two points. |
38 | Werner | 33 | returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */ |
3 | Werner | 34 | float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle) |
35 | { |
||
36 | float dx = PEnd.x() - PStart.x(); |
||
37 | float dy = PEnd.y() - PStart.y(); |
||
38 | float d = sqrt(dx*dx + dy*dy); |
||
39 | // direction: |
||
40 | rAngle = atan2(dx, dy); |
||
41 | return d; |
||
42 | } |
||
43 | |||
44 | |||
38 | Werner | 45 | void Tree::setup() |
46 | { |
||
106 | Werner | 47 | if (mDbh<=0 || mHeight<=0) |
38 | Werner | 48 | return; |
49 | // check stamp |
||
106 | Werner | 50 | Q_ASSERT_X(mSpecies!=0, "Tree::setup()", "species is NULL"); |
51 | mStamp = mSpecies->stamp(mDbh, mHeight); |
||
110 | Werner | 52 | |
53 | calcBiomassCompartments(); |
||
115 | Werner | 54 | mNPPReserve = mLeafMass; // initial value |
110 | Werner | 55 | |
38 | Werner | 56 | } |
39 | Werner | 57 | |
110 | Werner | 58 | ////////////////////////////////////////////////// |
59 | //// Light functions (Pattern-stuff) |
||
60 | ////////////////////////////////////////////////// |
||
61 | |||
70 | Werner | 62 | #define NOFULLDBG |
77 | Werner | 63 | //#define NOFULLOPT |
64 | /* |
||
39 | Werner | 65 | void Tree::applyStamp() |
66 | { |
||
67 | Q_ASSERT(m_grid!=0); |
||
68 | if (!m_stamp) |
||
69 | return; |
||
70 | |||
40 | Werner | 71 | QPoint pos = m_grid->indexAt(m_Position); |
72 | int offset = m_stamp->offset(); |
||
73 | pos-=QPoint(offset, offset); |
||
74 | QPoint p; |
||
75 | |||
60 | Werner | 76 | float local_dom; // height of Z* on the current position |
40 | Werner | 77 | int x,y; |
58 | Werner | 78 | float value; |
51 | Werner | 79 | QPoint dbg(10,20); |
70 | Werner | 80 | #ifndef NOFULLDBG |
81 | qDebug() <<"indexstampx indexstamy gridx gridy local_dom_height stampvalue 1-value*la/dom grid_before gridvalue_after"; |
||
82 | #endif |
||
40 | Werner | 83 | for (x=0;x<m_stamp->size();++x) { |
84 | for (y=0;y<m_stamp->size(); ++y) { |
||
85 | p = pos + QPoint(x,y); |
||
51 | Werner | 86 | // debug pixel |
61 | Werner | 87 | //if (p==dbg) |
88 | // qDebug() << "#" << id() << "value;"<<(*m_stamp)(x,y)<<"domH"<<dom; |
||
51 | Werner | 89 | |
90 | if (m_grid->isIndexValid(p)) { |
||
91 | // mulitplicative: |
||
53 | Werner | 92 | //m_grid->valueAtIndex(p)*=(*m_stamp)(x,y); |
51 | Werner | 93 | // additiv: |
58 | Werner | 94 | // multiplicatie, v2 |
75 | Werner | 95 | // a optimization for 2m vs 10m grids: local_dom = m_dominanceGrid->valueAtIndex(p.x()/5, p.y()/5); |
96 | // effect is about 20% of the application time |
||
60 | Werner | 97 | local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) ); // todo: could be done more effectively (here 2 transormations are performed)... |
98 | if (local_dom<=0.f) { |
||
61 | Werner | 99 | //qDebug() << "invalid height at " << m_grid->cellCoordinates(p) << "of" << local_dom; |
100 | local_dom = 2.; |
||
60 | Werner | 101 | } |
102 | value = (*m_stamp)(x,y); |
||
103 | value = 1. - value*lafactor / local_dom; |
||
59 | Werner | 104 | value = qMax(value, 0.02f); |
70 | Werner | 105 | #ifndef NOFULLDBG |
106 | qDebug() << x << y << p.x() << p.y() << local_dom << (*m_stamp)(x,y) << 1. - (*m_stamp)(x,y)*lafactor / local_dom << m_grid->valueAtIndex(p) << m_grid->valueAtIndex(p)*value; |
||
107 | #endif |
||
108 | |||
58 | Werner | 109 | m_grid->valueAtIndex(p)*= value; |
51 | Werner | 110 | } |
40 | Werner | 111 | } |
112 | } |
||
58 | Werner | 113 | |
114 | m_statPrint++; // count # of stamp applications... |
||
115 | } |
||
77 | Werner | 116 | */ |
58 | Werner | 117 | |
77 | Werner | 118 | void Tree::applyStamp() |
119 | { |
||
106 | Werner | 120 | Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0); |
77 | Werner | 121 | |
106 | Werner | 122 | QPoint pos = mGrid->indexAt(mPosition); |
123 | int offset = mStamp->offset(); |
||
77 | Werner | 124 | pos-=QPoint(offset, offset); |
125 | |||
126 | float local_dom; // height of Z* on the current position |
||
127 | int x,y; |
||
128 | float value; |
||
106 | Werner | 129 | int gr_stamp = mStamp->size(); |
77 | Werner | 130 | int grid_x, grid_y; |
131 | float *grid_value; |
||
106 | Werner | 132 | if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) { |
77 | Werner | 133 | // todo: in this case we should use another algorithm!!! |
134 | return; |
||
135 | } |
||
136 | |||
137 | for (y=0;y<gr_stamp; ++y) { |
||
138 | grid_y = pos.y() + y; |
||
106 | Werner | 139 | grid_value = mGrid->ptr(pos.x(), grid_y); |
77 | Werner | 140 | for (x=0;x<gr_stamp;++x) { |
141 | // suppose there is no stamping outside |
||
142 | grid_x = pos.x() + x; |
||
143 | |||
106 | Werner | 144 | local_dom = mHeightGrid->valueAtIndex(grid_x/5, grid_y/5); |
145 | value = (*mStamp)(x,y); // stampvalue |
||
77 | Werner | 146 | value = 1. - value*lafactor / local_dom; // calculated value |
147 | value = qMax(value, 0.02f); // limit value |
||
148 | |||
149 | *grid_value++ *= value; |
||
150 | } |
||
151 | } |
||
152 | |||
153 | m_statPrint++; // count # of stamp applications... |
||
154 | } |
||
155 | |||
74 | Werner | 156 | /* |
157 | void Tree::heightGrid_old() |
||
58 | Werner | 158 | { |
59 | Werner | 159 | // height of Z* |
74 | Werner | 160 | const float cellsize = m_dominanceGrid->cellsize(); |
62 | Werner | 161 | if (m_Height < cellsize/2.) { |
59 | Werner | 162 | float &dom = m_dominanceGrid->valueAt(m_Position); // modifyable reference |
163 | dom = qMax(dom, m_Height/2.f); |
||
164 | } else { |
||
165 | QPoint p = m_dominanceGrid->indexAt(m_Position); // pos of tree on height grid |
||
62 | Werner | 166 | |
167 | int ringcount = int(floor(m_Height / cellsize)); |
||
59 | Werner | 168 | int ix, iy; |
169 | int ring; |
||
170 | QPoint pos; |
||
62 | Werner | 171 | float hdom; |
172 | float h_out = fmod(m_Height, cellsize) / 2.; |
||
59 | Werner | 173 | for (ix=-ringcount;ix<=ringcount;ix++) |
174 | for (iy=-ringcount; iy<=+ringcount; iy++) { |
||
175 | ring = qMax(abs(ix), abs(iy)); |
||
176 | QPoint pos(ix+p.x(), iy+p.y()); |
||
177 | if (m_dominanceGrid->isIndexValid(pos)) { |
||
178 | // apply value.... |
||
62 | Werner | 179 | if (ring==0) { |
180 | hdom = m_Height- cellsize/4.; |
||
181 | } else if (ring==abs(ringcount)) { |
||
182 | // outermost ring: use again height/2. |
||
183 | hdom = h_out; |
||
184 | } else { |
||
185 | hdom = m_Height- ring*cellsize; |
||
186 | } |
||
187 | m_dominanceGrid->valueAtIndex(pos) = qMax(m_dominanceGrid->valueAtIndex(pos), hdom); |
||
59 | Werner | 188 | } |
45 | Werner | 189 | |
59 | Werner | 190 | } |
191 | } |
||
192 | |||
74 | Werner | 193 | } */ |
194 | |||
195 | /** heightGrid() |
||
196 | This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid. |
||
197 | |||
198 | */ |
||
199 | void Tree::heightGrid() |
||
200 | { |
||
201 | // height of Z* |
||
106 | Werner | 202 | const float cellsize = mHeightGrid->cellsize(); |
74 | Werner | 203 | |
106 | Werner | 204 | QPoint p = mHeightGrid->indexAt(mPosition); // pos of tree on height grid |
205 | QPoint competition_grid = mGrid->indexAt(mPosition); |
||
74 | Werner | 206 | |
207 | int index_eastwest = competition_grid.x() % 5; // 4: very west, 0 east edge |
||
208 | int index_northsouth = competition_grid.y() % 5; // 4: northern edge, 0: southern edge |
||
209 | int dist[9]; |
||
210 | dist[3] = index_northsouth * 2 + 1; // south |
||
211 | dist[1] = index_eastwest * 2 + 1; // west |
||
212 | dist[5] = 10 - dist[3]; // north |
||
213 | dist[7] = 10 - dist[1]; // east |
||
214 | dist[8] = qMax(dist[5], dist[7]); // north-east |
||
215 | dist[6] = qMax(dist[3], dist[7]); // south-east |
||
216 | dist[0] = qMax(dist[3], dist[1]); // south-west |
||
217 | dist[2] = qMax(dist[5], dist[1]); // north-west |
||
75 | Werner | 218 | dist[4] = 0; // center cell |
76 | Werner | 219 | /* the scheme of indices is as follows: if sign(ix)= -1, if ix<0, 0 for ix=0, 1 for ix>0 (detto iy), then: |
220 | index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above. |
||
221 | e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2 |
||
222 | */ |
||
74 | Werner | 223 | |
224 | |||
106 | Werner | 225 | int ringcount = int(floor(mHeight / cellsize)) + 1; |
74 | Werner | 226 | int ix, iy; |
227 | int ring; |
||
228 | QPoint pos; |
||
229 | float hdom; |
||
230 | |||
231 | for (ix=-ringcount;ix<=ringcount;ix++) |
||
232 | for (iy=-ringcount; iy<=+ringcount; iy++) { |
||
233 | ring = qMax(abs(ix), abs(iy)); |
||
234 | QPoint pos(ix+p.x(), iy+p.y()); |
||
106 | Werner | 235 | if (mHeightGrid->isIndexValid(pos)) { |
236 | float &rHGrid = mHeightGrid->valueAtIndex(pos); |
||
237 | if (rHGrid > mHeight) // skip calculation if grid is higher than tree |
||
74 | Werner | 238 | continue; |
239 | int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y) |
||
106 | Werner | 240 | hdom = mHeight - dist[direction]; |
74 | Werner | 241 | if (ring>1) |
242 | hdom -= (ring-1)*10; |
||
243 | |||
244 | rHGrid = qMax(rHGrid, hdom); // write value |
||
245 | } // is valid |
||
246 | } // for (y) |
||
39 | Werner | 247 | } |
40 | Werner | 248 | |
249 | double Tree::readStamp() |
||
250 | { |
||
106 | Werner | 251 | if (!mStamp) |
51 | Werner | 252 | return 0.; |
106 | Werner | 253 | const Stamp *stamp = mStamp->reader(); |
40 | Werner | 254 | if (!stamp) |
255 | return 0.; |
||
106 | Werner | 256 | QPoint pos = mGrid->indexAt(mPosition); |
40 | Werner | 257 | int offset = stamp->offset(); |
258 | pos-=QPoint(offset, offset); |
||
259 | QPoint p; |
||
260 | |||
261 | int x,y; |
||
262 | double sum=0.; |
||
263 | for (x=0;x<stamp->size();++x) { |
||
264 | for (y=0;y<stamp->size(); ++y) { |
||
265 | p = pos + QPoint(x,y); |
||
106 | Werner | 266 | if (mGrid->isIndexValid(p)) |
267 | sum += mGrid->valueAtIndex(p) * (*stamp)(x,y); |
||
40 | Werner | 268 | } |
269 | } |
||
106 | Werner | 270 | float eigenvalue = mStamp->readSum() * lafactor; |
271 | mLRI = sum - eigenvalue;// additive |
||
272 | float dom_height = (*mHeightGrid)[mPosition]; |
||
53 | Werner | 273 | if (dom_height>0.) |
106 | Werner | 274 | mLRI = mLRI / dom_height; |
53 | Werner | 275 | |
276 | //mImpact = sum + eigenvalue;// multiplicative |
||
48 | Werner | 277 | // read dominance field... |
53 | Werner | 278 | |
106 | Werner | 279 | if (dom_height < mHeight) { |
48 | Werner | 280 | // if tree is higher than Z*, the dominance height |
281 | // a part of the crown is in "full light". |
||
282 | // total value = zstar/treeheight*value + 1-zstar/height |
||
283 | // reformulated to: |
||
106 | Werner | 284 | mLRI = mLRI * dom_height/mHeight ; |
48 | Werner | 285 | m_statAboveZ++; |
286 | } |
||
106 | Werner | 287 | if (fabs(mLRI < 0.000001)) |
288 | mLRI = 0.f; |
||
289 | qDebug() << "Tree #"<< id() << "value" << sum << "eigenvalue" << eigenvalue << "Impact" << mLRI; |
||
290 | return mLRI; |
||
40 | Werner | 291 | } |
292 | |||
78 | Werner | 293 | /* |
58 | Werner | 294 | double Tree::readStampMul() |
295 | { |
||
296 | if (!m_stamp) |
||
297 | return 0.; |
||
298 | const Stamp *reader = m_stamp->reader(); |
||
299 | if (!reader) |
||
300 | return 0.; |
||
301 | QPoint pos_reader = m_grid->indexAt(m_Position); |
||
302 | |||
303 | int offset_reader = reader->offset(); |
||
304 | int offset_writer = m_stamp->offset(); |
||
305 | int d_offset = offset_writer - offset_reader; |
||
306 | |||
307 | QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer); |
||
308 | pos_reader-=QPoint(offset_reader, offset_reader); |
||
309 | QPoint p; |
||
310 | |||
311 | float dom_height = (*m_dominanceGrid)[m_Position]; |
||
70 | Werner | 312 | float local_dom; |
58 | Werner | 313 | |
314 | int x,y; |
||
315 | double sum=0.; |
||
316 | double value, own_value; |
||
317 | for (x=0;x<reader->size();++x) { |
||
318 | for (y=0;y<reader->size(); ++y) { |
||
319 | p = pos_reader + QPoint(x,y); |
||
320 | if (m_grid->isIndexValid(p)) { |
||
70 | Werner | 321 | local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) ); |
322 | own_value = 1. - m_stamp->offsetValue(x,y,d_offset)*lafactor /local_dom; // old: dom_height; |
||
59 | Werner | 323 | own_value = qMax(own_value, 0.02); |
58 | Werner | 324 | value = m_grid->valueAtIndex(p) / own_value; // remove impact of focal tree |
325 | if (value>0.) |
||
326 | sum += value * (*reader)(x,y); |
||
327 | //value = (1. - m_stamp->offsetValue(x,y,d_offset)/dom_height); |
||
328 | //value = (1 - m_grid->valueAtIndex(p)) / (m_stamp->offsetValue(x,y,d_offset)/dom_height * lafactor); |
||
329 | if (isDebugging()) { |
||
330 | qDebug() << "Tree" << id() << "Coord" << p << "gridvalue" << m_grid->valueAtIndex(p) << "value" << value << "reader" << (*reader)(x,y) << "writer" << m_stamp->offsetValue(x,y,d_offset); |
||
331 | } |
||
332 | |||
333 | } |
||
334 | } |
||
335 | } |
||
336 | mImpact = sum; |
||
337 | // read dominance field... |
||
338 | if (dom_height < m_Height) { |
||
339 | // if tree is higher than Z*, the dominance height |
||
340 | // a part of the crown is in "full light". |
||
341 | m_statAboveZ++; |
||
342 | mImpact = 1. - (1. - mImpact)*dom_height/m_Height; |
||
343 | } |
||
344 | if (mImpact > 1) |
||
345 | mImpact = 1.f; |
||
61 | Werner | 346 | //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact; |
58 | Werner | 347 | return mImpact; |
78 | Werner | 348 | }*/ |
349 | |||
107 | Werner | 350 | void Tree::readStampMul() |
78 | Werner | 351 | { |
106 | Werner | 352 | if (!mStamp) |
107 | Werner | 353 | return; |
106 | Werner | 354 | const Stamp *reader = mStamp->reader(); |
78 | Werner | 355 | if (!reader) |
107 | Werner | 356 | return; |
106 | Werner | 357 | QPoint pos_reader = mGrid->indexAt(mPosition); |
78 | Werner | 358 | |
359 | int offset_reader = reader->offset(); |
||
106 | Werner | 360 | int offset_writer = mStamp->offset(); |
78 | Werner | 361 | int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells |
362 | |||
363 | QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer); |
||
364 | pos_reader-=QPoint(offset_reader, offset_reader); |
||
365 | QPoint p; |
||
366 | |||
367 | //float dom_height = (*m_dominanceGrid)[m_Position]; |
||
368 | float local_dom; |
||
369 | |||
370 | int x,y; |
||
371 | double sum=0.; |
||
372 | double value, own_value; |
||
373 | float *grid_value; |
||
374 | int reader_size = reader->size(); |
||
375 | int rx = pos_reader.x(); |
||
376 | int ry = pos_reader.y(); |
||
377 | for (y=0;y<reader_size; ++y, ++ry) { |
||
106 | Werner | 378 | grid_value = mGrid->ptr(rx, ry); |
78 | Werner | 379 | for (x=0;x<reader_size;++x) { |
380 | |||
381 | //p = pos_reader + QPoint(x,y); |
||
382 | //if (m_grid->isIndexValid(p)) { |
||
106 | Werner | 383 | local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5); |
78 | Werner | 384 | //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) ); |
106 | Werner | 385 | own_value = 1. - mStamp->offsetValue(x,y,d_offset)*lafactor /local_dom; // old: dom_height; |
78 | Werner | 386 | own_value = qMax(own_value, 0.02); |
387 | value = *grid_value++ / own_value; // remove impact of focal tree |
||
388 | //if (value>0.) |
||
389 | sum += value * (*reader)(x,y); |
||
390 | |||
391 | //} // isIndexValid |
||
392 | } |
||
393 | } |
||
106 | Werner | 394 | mLRI = sum; |
78 | Werner | 395 | // read dominance field... |
396 | // this applies only if some trees are potentially *higher* than the dominant height grid |
||
397 | //if (dom_height < m_Height) { |
||
398 | // if tree is higher than Z*, the dominance height |
||
399 | // a part of the crown is in "full light". |
||
400 | // m_statAboveZ++; |
||
401 | // mImpact = 1. - (1. - mImpact)*dom_height/m_Height; |
||
402 | //} |
||
106 | Werner | 403 | if (mLRI > 1) |
404 | mLRI = 1.f; |
||
78 | Werner | 405 | //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact; |
110 | Werner | 406 | mRU->addWLA(mLRI * mLeafArea, mLeafArea); |
58 | Werner | 407 | } |
408 | |||
40 | Werner | 409 | void Tree::resetStatistics() |
410 | { |
||
411 | m_statPrint=0; |
||
105 | Werner | 412 | m_statCreated=0; |
48 | Werner | 413 | m_statAboveZ=0; |
40 | Werner | 414 | m_nextId=1; |
415 | } |
||
107 | Werner | 416 | |
110 | Werner | 417 | ////////////////////////////////////////////////// |
418 | //// Growth Functions |
||
419 | ////////////////////////////////////////////////// |
||
107 | Werner | 420 | |
110 | Werner | 421 | /// evaluate allometries and calculate LeafArea |
422 | void Tree::calcBiomassCompartments() |
||
423 | { |
||
424 | mLeafMass = mSpecies->biomassLeaf(mDbh); |
||
425 | mRootMass = mSpecies->biomassRoot(mDbh); |
||
426 | mStemMass = mSpecies->biomassStem(mDbh); |
||
112 | Werner | 427 | // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg] |
110 | Werner | 428 | mLeafArea = mLeafMass * mSpecies->specificLeafArea(); |
429 | } |
||
430 | |||
431 | |||
107 | Werner | 432 | void Tree::grow() |
433 | { |
||
113 | Werner | 434 | // step 1: get radiation from ressource unit: radiaton (MJ/tree/year) total intercepted radiation for this tree per year! |
107 | Werner | 435 | double radiation = mRU->interceptedRadiation(mLRI * mLeafArea); |
113 | Werner | 436 | // step 2: get fraction of PARutilized, i.e. fraction of intercepted rad that is utiliziable (per year) |
107 | Werner | 437 | |
115 | Werner | 438 | double raw_gpp_per_rad = mRU->ressourceUnitSpecies(mSpecies).prod3PG().GPPperRad(); |
113 | Werner | 439 | // GPP (without aging-effect) [gC] / year |
440 | double raw_gpp = raw_gpp_per_rad * radiation; |
||
441 | /* |
||
442 | if (mRU->index()==3) { |
||
443 | qDebug() << "tree production: radiation: " << radiation << "gpp/rad:" << raw_gpp_per_rad << "gpp" << raw_gpp << "LRI:" << mLRI << "LeafArea:" << mLeafArea; |
||
444 | }*/ |
||
115 | Werner | 445 | // apply aging |
446 | double gpp = raw_gpp * 0.6; // aging |
||
447 | double npp = gpp * 0.47; // respiration loss |
||
113 | Werner | 448 | |
115 | Werner | 449 | partitioning(npp); |
450 | |||
110 | Werner | 451 | calcBiomassCompartments(); |
452 | |||
107 | Werner | 453 | } |
454 | |||
117 | Werner | 455 | |
456 | // just used to test the DBG_IF_x macros... |
||
457 | QString test_cntr() |
||
458 | { |
||
459 | static int cnt = 0; |
||
460 | cnt++; |
||
461 | return QString::number(cnt); |
||
462 | } |
||
463 | |||
118 | Werner | 464 | #if !defined(DBGMODE) |
465 | # ifndef QT_NO_DEBUG |
||
466 | # define DBGMODE(stmts) { stmts } |
||
467 | # else |
||
468 | # define DBGMODE(stmts) qt_noop() |
||
469 | # endif |
||
470 | #endif |
||
471 | |||
115 | Werner | 472 | void Tree::partitioning(double npp) |
473 | { |
||
119 | Werner | 474 | DBGMODE( |
475 | if (mId==1) |
||
476 | test_cntr(); |
||
477 | ); |
||
115 | Werner | 478 | double harshness = mRU->ressourceUnitSpecies(mSpecies).prod3PG().harshness(); |
479 | // add content of reserve pool |
||
116 | Werner | 480 | npp += mNPPReserve; |
119 | Werner | 481 | const double reserve_size = mLeafMass; |
482 | |||
117 | Werner | 483 | double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) |
484 | // turnover rates |
||
485 | const double &to_fol = mSpecies->turnoverLeaf(); |
||
486 | const double &to_wood = mSpecies->turnoverStem(); |
||
487 | const double &to_root = mSpecies->turnoverRoot(); |
||
116 | Werner | 488 | |
117 | Werner | 489 | apct_root = harshness; |
490 | |||
491 | double b_wf = 1.32; // ratio of allometric exponents... now fixed |
||
492 | |||
493 | // Duursma 2007, Eq. (20) |
||
494 | apct_wood = (mLeafMass * to_wood / npp + b_wf*(1.-apct_root) - b_wf * to_fol/npp) / ( mStemMass / mStemMass + b_wf ); |
||
495 | apct_foliage = 1. - apct_root - apct_wood; |
||
496 | |||
119 | Werner | 497 | // senescence demands of the compartemnts |
498 | double senescence_foliage = mLeafMass * to_fol; |
||
499 | double senescence_stem = mStemMass * to_wood; |
||
500 | double senescence_root = mRootMass * to_root; |
||
501 | |||
502 | // brutto compartment increments |
||
503 | double gross_foliage = npp * apct_foliage; |
||
504 | double gross_stem = npp * apct_wood; |
||
505 | double gross_root = npp * apct_root; |
||
506 | |||
507 | // netto increments |
||
508 | double net_foliage = gross_foliage - senescence_foliage; |
||
509 | double net_root = gross_root - senescence_root; |
||
510 | double net_stem = gross_stem - senescence_stem; |
||
511 | |||
512 | // flow back to reserve pool: |
||
513 | double to_reserve = qMin(reserve_size, net_stem); |
||
514 | net_stem -= to_reserve; |
||
515 | |||
516 | // update of compartments |
||
517 | mLeafMass += net_foliage; |
||
518 | mRootMass += net_root; |
||
519 | |||
520 | // calculate the dimension of growth |
||
521 | grow_diameter(net_stem); |
||
522 | |||
117 | Werner | 523 | //DBG_IF(apct_foliage<0, "Tree::partitioning", "foliage out of range"); |
121 | Werner | 524 | //DBG_IF_X(mId==1, "Tree::partitioning", "foliage out of range", test_cntr()); |
525 | |||
115 | Werner | 526 | } |
527 | |||
119 | Werner | 528 | inline void Tree::grow_diameter(const double &net_stem_npp) |
529 | { |
||
530 | // determine dh-ratio of increment |
||
531 | // height increment is a function of light competition: |
||
532 | double rel_height_growth = relative_height_growth(); // [0..1] |
||
115 | Werner | 533 | |
123 | Werner | 534 | |
119 | Werner | 535 | } |
536 | |||
537 | inline double Tree::relative_height_growth() |
||
538 | { |
||
539 | double hd_low, hd_high; |
||
540 | mSpecies->hdRange(mDbh, hd_low, hd_high); |
||
541 | |||
542 | // scale according to LRI: if having much light (LRI=1), the result is hd_low (for open grown trees) |
||
543 | double result = hd_high - (hd_high-hd_low)*mLRI; |
||
544 | return result; |
||
545 | } |