Subversion Repositories public iLand

Rev

Rev 297 | Rev 299 | 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"
189 iland 9
#include "resourceunit.h"
151 iland 10
#include "model.h"
38 Werner 11
 
110 Werner 12
// static varaibles
106 Werner 13
FloatGrid *Tree::mGrid = 0;
151 iland 14
HeightGrid *Tree::mHeightGrid = 0;
40 Werner 15
int Tree::m_statPrint=0;
48 Werner 16
int Tree::m_statAboveZ=0;
105 Werner 17
int Tree::m_statCreated=0;
40 Werner 18
int Tree::m_nextId=0;
19
 
158 werner 20
 
257 werner 21
 
158 werner 22
/** get distance and direction between two points.
23
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
24
float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle)
151 iland 25
{
158 werner 26
    float dx = PEnd.x() - PStart.x();
27
    float dy = PEnd.y() - PStart.y();
28
    float d = sqrt(dx*dx + dy*dy);
29
    // direction:
30
    rAngle = atan2(dx, dy);
31
    return d;
151 iland 32
}
33
 
158 werner 34
 
110 Werner 35
// lifecycle
3 Werner 36
Tree::Tree()
37
{
149 werner 38
    mDbh = mHeight = 0;
39
    mRU = 0; mSpecies = 0;
169 werner 40
    mFlags = mAge = 0;
276 werner 41
    mOpacity=mFoliageMass=mWoodyMass=mCoarseRootMass=mFineRootMass=mLeafArea=0.;
159 werner 42
    mDbhDelta=mNPPReserve=mLRI=mStressIndex=0.;
264 werner 43
    mLightResponse = 0.;
106 Werner 44
    mId = m_nextId++;
105 Werner 45
    m_statCreated++;
3 Werner 46
}
38 Werner 47
 
158 werner 48
void Tree::setGrid(FloatGrid* gridToStamp, Grid<HeightGridValue> *dominanceGrid)
3 Werner 49
{
158 werner 50
    mGrid = gridToStamp; mHeightGrid = dominanceGrid;
3 Werner 51
}
52
 
125 Werner 53
/// dumps some core variables of a tree to a string.
54
QString Tree::dump()
55
{
56
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
159 werner 57
                            .arg(mId).arg(species()->id()).arg(mDbh).arg(mHeight)
156 werner 58
                            .arg(position().x()).arg(position().y())
125 Werner 59
                            .arg(mRU->index()).arg(mLRI);
60
    return result;
61
}
3 Werner 62
 
129 Werner 63
void Tree::dumpList(DebugList &rTargetList)
64
{
159 werner 65
    rTargetList << mId << species()->id() << mDbh << mHeight  << position().x() << position().y()   << mRU->index() << mLRI
276 werner 66
                << mWoodyMass << mCoarseRootMass << mFoliageMass << mLeafArea;
129 Werner 67
}
68
 
38 Werner 69
void Tree::setup()
70
{
106 Werner 71
    if (mDbh<=0 || mHeight<=0)
38 Werner 72
        return;
73
    // check stamp
159 werner 74
    Q_ASSERT_X(species()!=0, "Tree::setup()", "species is NULL");
75
    mStamp = species()->stamp(mDbh, mHeight);
110 Werner 76
 
159 werner 77
    mFoliageMass = species()->biomassFoliage(mDbh);
276 werner 78
    mCoarseRootMass = species()->biomassRoot(mDbh); // coarse root (allometry)
79
    mFineRootMass = mFoliageMass * species()->finerootFoliageRatio(); //  fine root (size defined  by finerootFoliageRatio)
159 werner 80
    mWoodyMass = species()->biomassWoody(mDbh);
110 Werner 81
 
137 Werner 82
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
159 werner 83
    mLeafArea = mFoliageMass * species()->specificLeafArea();
276 werner 84
    mOpacity = 1. - exp(- Model::settings().lightExtinctionCoefficientOpacity * mLeafArea / mStamp->crownArea());
85
    mNPPReserve = (1+species()->finerootFoliageRatio())*mFoliageMass; // initial value
137 Werner 86
    mDbhDelta = 0.1; // initial value: used in growth() to estimate diameter increment
38 Werner 87
}
39 Werner 88
 
110 Werner 89
//////////////////////////////////////////////////
90
////  Light functions (Pattern-stuff)
91
//////////////////////////////////////////////////
92
 
70 Werner 93
#define NOFULLDBG
77 Werner 94
//#define NOFULLOPT
39 Werner 95
 
40 Werner 96
 
158 werner 97
void Tree::applyLIP()
77 Werner 98
{
144 Werner 99
    if (!mStamp)
100
        return;
106 Werner 101
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
156 werner 102
    QPoint pos = mPositionIndex;
106 Werner 103
    int offset = mStamp->offset();
77 Werner 104
    pos-=QPoint(offset, offset);
105
 
106
    float local_dom; // height of Z* on the current position
107
    int x,y;
108
    float value;
106 Werner 109
    int gr_stamp = mStamp->size();
77 Werner 110
    int grid_x, grid_y;
111
    float *grid_value;
106 Werner 112
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
77 Werner 113
        // todo: in this case we should use another algorithm!!!
114
        return;
115
    }
116
 
117
    for (y=0;y<gr_stamp; ++y) {
118
        grid_y = pos.y() + y;
106 Werner 119
        grid_value = mGrid->ptr(pos.x(), grid_y);
77 Werner 120
        for (x=0;x<gr_stamp;++x) {
121
            // suppose there is no stamping outside
122
            grid_x = pos.x() + x;
123
 
151 iland 124
            local_dom = mHeightGrid->valueAtIndex(grid_x/5, grid_y/5).height;
106 Werner 125
            value = (*mStamp)(x,y); // stampvalue
149 werner 126
            value = 1. - value*mOpacity / local_dom; // calculated value
77 Werner 127
            value = qMax(value, 0.02f); // limit value
128
 
129
            *grid_value++ *= value;
130
        }
131
    }
132
 
133
    m_statPrint++; // count # of stamp applications...
134
}
135
 
155 werner 136
/// helper function for gluing the edges together
137
/// index: index at grid
138
/// count: number of pixels that are the simulation area (e.g. 100m and 2m pixel -> 50)
139
/// buffer: size of buffer around simulation area (in pixels)
295 werner 140
inline int torusIndex(int index, int count, int buffer, int ru_index)
155 werner 141
{
295 werner 142
    return buffer + ru_index + (index-buffer+count)%count;
155 werner 143
}
62 Werner 144
 
155 werner 145
 
146
/** Apply LIPs. This "Torus" functions wraps the influence at the edges of a 1ha simulation area.
147
  */
158 werner 148
void Tree::applyLIP_torus()
155 werner 149
{
150
    if (!mStamp)
151
        return;
152
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
295 werner 153
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
154
    QPoint pos = QPoint((mPositionIndex.x()-bufferOffset)%50 + bufferOffset,
155
                        (mPositionIndex.y()-bufferOffset)%50 + bufferOffset); // offset within the ha
156
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos.x(), mPositionIndex.y() - pos.y()); // offset of the corner of the resource index
155 werner 157
 
158
    int offset = mStamp->offset();
159
    pos-=QPoint(offset, offset);
160
 
161
    float local_dom; // height of Z* on the current position
162
    int x,y;
163
    float value;
164
    int gr_stamp = mStamp->size();
165
    int grid_x, grid_y;
166
    float *grid_value;
167
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
168
        // todo: in this case we should use another algorithm!!! necessary????
169
        return;
170
    }
295 werner 171
 
155 werner 172
    int xt, yt; // wraparound coordinates
173
    for (y=0;y<gr_stamp; ++y) {
174
        grid_y = pos.y() + y;
295 werner 175
        yt = torusIndex(grid_y, 50,bufferOffset, ru_offset.y()); // 50 cells per 100m
155 werner 176
        for (x=0;x<gr_stamp;++x) {
177
            // suppose there is no stamping outside
178
            grid_x = pos.x() + x;
295 werner 179
            xt = torusIndex(grid_x,50,bufferOffset, ru_offset.x());
155 werner 180
 
181
            local_dom = mHeightGrid->valueAtIndex(xt/5,yt/5).height;
182
            value = (*mStamp)(x,y); // stampvalue
183
            value = 1. - value*mOpacity / local_dom; // calculated value
184
            value = qMax(value, 0.02f); // limit value
185
 
186
            grid_value = mGrid->ptr(xt, yt); // use wraparound coordinates
187
            *grid_value *= value;
188
        }
189
    }
190
 
191
    m_statPrint++; // count # of stamp applications...
192
}
193
 
74 Werner 194
/** heightGrid()
195
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
196
*/
197
void Tree::heightGrid()
198
{
199
    // height of Z*
106 Werner 200
    const float cellsize = mHeightGrid->cellsize();
74 Werner 201
 
156 werner 202
    QPoint p = QPoint(mPositionIndex.x()/5, mPositionIndex.y()/5); // pos of tree on height grid
74 Werner 203
 
151 iland 204
    // count trees that are on height-grid cells (used for stockable area)
285 werner 205
    mHeightGrid->valueAtIndex(p).increaseCount();
151 iland 206
 
156 werner 207
    int index_eastwest = mPositionIndex.x() % 5; // 4: very west, 0 east edge
208
    int index_northsouth = mPositionIndex.y() % 5; // 4: northern edge, 0: southern edge
74 Werner 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)) {
151 iland 236
            float &rHGrid = mHeightGrid->valueAtIndex(pos).height;
106 Werner 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
 
155 werner 249
 
250
 
158 werner 251
void Tree::readLIF()
40 Werner 252
{
106 Werner 253
    if (!mStamp)
155 werner 254
        return;
255
    const Stamp *reader = mStamp->reader();
256
    if (!reader)
257
        return;
156 werner 258
    QPoint pos_reader = mPositionIndex;
155 werner 259
 
260
    int offset_reader = reader->offset();
261
    int offset_writer = mStamp->offset();
262
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
263
 
264
    QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer);
265
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 266
    QPoint p;
267
 
155 werner 268
    //float dom_height = (*m_dominanceGrid)[m_Position];
269
    float local_dom;
270
 
40 Werner 271
    int x,y;
272
    double sum=0.;
155 werner 273
    double value, own_value;
274
    float *grid_value;
275
    int reader_size = reader->size();
276
    int rx = pos_reader.x();
277
    int ry = pos_reader.y();
278
    for (y=0;y<reader_size; ++y, ++ry) {
279
        grid_value = mGrid->ptr(rx, ry);
280
        for (x=0;x<reader_size;++x) {
281
 
282
            //p = pos_reader + QPoint(x,y);
283
            //if (m_grid->isIndexValid(p)) {
284
            local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5).height; // ry: gets ++ed in outer loop, rx not
285
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
286
 
287
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
288
            own_value = qMax(own_value, 0.02);
289
            value =  *grid_value++ / own_value; // remove impact of focal tree
290
            //if (value>0.)
291
            sum += value * (*reader)(x,y);
292
 
293
            //} // isIndexValid
40 Werner 294
        }
295
    }
155 werner 296
    mLRI = sum;
48 Werner 297
    // read dominance field...
155 werner 298
    // this applies only if some trees are potentially *higher* than the dominant height grid
299
    //if (dom_height < m_Height) {
48 Werner 300
        // if tree is higher than Z*, the dominance height
301
        // a part of the crown is in "full light".
155 werner 302
    //    m_statAboveZ++;
303
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
304
    //}
305
    if (mLRI > 1.)
306
        mLRI = 1.;
206 werner 307
 
308
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 309
    mRU->addWLA(mLeafArea, mLRI);
206 werner 310
 
212 werner 311
 
155 werner 312
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
206 werner 313
    //mRU->addWLA(mLRI*mLeafArea, mLeafArea);
40 Werner 314
}
315
 
158 werner 316
void Tree::heightGrid_torus()
155 werner 317
{
318
    // height of Z*
319
    const float cellsize = mHeightGrid->cellsize();
58 Werner 320
 
156 werner 321
    QPoint p = QPoint(mPositionIndex.x()/5, mPositionIndex.y()/5); // pos of tree on height grid
295 werner 322
    int bufferOffset = mHeightGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
155 werner 323
 
324
    // count trees that are on height-grid cells (used for stockable area)
285 werner 325
    mHeightGrid->valueAtIndex(p).increaseCount();
155 werner 326
 
295 werner 327
    // torus coordinates
328
    p.setX((p.x()-bufferOffset)%10 + bufferOffset);
329
    p.setY((p.y()-bufferOffset)%10 + bufferOffset);
330
    QPoint ru_offset =QPoint(mPositionIndex.x()/5 - p.x(), mPositionIndex.y()/5 - p.y());
331
 
156 werner 332
    int index_eastwest = mPositionIndex.x() % 5; // 4: very west, 0 east edge
333
    int index_northsouth = mPositionIndex.y() % 5; // 4: northern edge, 0: southern edge
155 werner 334
    int dist[9];
335
    dist[3] = index_northsouth * 2 + 1; // south
336
    dist[1] = index_eastwest * 2 + 1; // west
337
    dist[5] = 10 - dist[3]; // north
338
    dist[7] = 10 - dist[1]; // east
339
    dist[8] = qMax(dist[5], dist[7]); // north-east
340
    dist[6] = qMax(dist[3], dist[7]); // south-east
341
    dist[0] = qMax(dist[3], dist[1]); // south-west
342
    dist[2] = qMax(dist[5], dist[1]); // north-west
343
    dist[4] = 0; // center cell
344
    /* 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:
345
       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
346
        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
347
    */
348
 
349
 
350
    int ringcount = int(floor(mHeight / cellsize)) + 1;
351
    int ix, iy;
352
    int ring;
353
    float hdom;
354
    for (ix=-ringcount;ix<=ringcount;ix++)
355
        for (iy=-ringcount; iy<=+ringcount; iy++) {
356
        ring = qMax(abs(ix), abs(iy));
357
        QPoint pos(ix+p.x(), iy+p.y());
295 werner 358
        QPoint p_torus(torusIndex(pos.x(),10,bufferOffset,ru_offset.x()),
359
                       torusIndex(pos.y(),10,bufferOffset,ru_offset.y()));
360
        if (mHeightGrid->isIndexValid(p_torus)) {
361
            float &rHGrid = mHeightGrid->valueAtIndex(p_torus.x(),p_torus.y()).height;
155 werner 362
            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
363
                continue;
364
            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
365
            hdom = mHeight - dist[direction];
366
            if (ring>1)
367
                hdom -= (ring-1)*10;
368
 
369
            rHGrid = qMax(rHGrid, hdom); // write value
370
        } // is valid
371
    } // for (y)
372
}
373
 
374
/// Torus version of read stamp (glued edges)
158 werner 375
void Tree::readLIF_torus()
78 Werner 376
{
106 Werner 377
    if (!mStamp)
107 Werner 378
        return;
106 Werner 379
    const Stamp *reader = mStamp->reader();
78 Werner 380
    if (!reader)
107 Werner 381
        return;
295 werner 382
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
78 Werner 383
 
295 werner 384
    QPoint pos_reader = QPoint((mPositionIndex.x()-bufferOffset)%50 + bufferOffset,
385
                               (mPositionIndex.y()-bufferOffset)%50 + bufferOffset); // offset within the ha
386
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos_reader.x(), mPositionIndex.y() - pos_reader.y()); // offset of the corner of the resource index
387
 
78 Werner 388
    int offset_reader = reader->offset();
106 Werner 389
    int offset_writer = mStamp->offset();
78 Werner 390
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
391
 
392
    pos_reader-=QPoint(offset_reader, offset_reader);
393
 
394
    float local_dom;
395
 
396
    int x,y;
397
    double sum=0.;
398
    double value, own_value;
399
    float *grid_value;
400
    int reader_size = reader->size();
401
    int rx = pos_reader.x();
402
    int ry = pos_reader.y();
155 werner 403
    int xt, yt; // wrapped coords
404
 
78 Werner 405
    for (y=0;y<reader_size; ++y, ++ry) {
106 Werner 406
        grid_value = mGrid->ptr(rx, ry);
78 Werner 407
        for (x=0;x<reader_size;++x) {
295 werner 408
            xt = torusIndex(rx+x,50, bufferOffset, ru_offset.x());
409
            yt = torusIndex(ry+y,50, bufferOffset, ru_offset.y());
155 werner 410
            grid_value = mGrid->ptr(xt,yt);
78 Werner 411
            //p = pos_reader + QPoint(x,y);
412
            //if (m_grid->isIndexValid(p)) {
155 werner 413
            local_dom = mHeightGrid->valueAtIndex(xt/5, yt/5).height; // ry: gets ++ed in outer loop, rx not
78 Werner 414
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
125 Werner 415
 
149 werner 416
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 417
            own_value = qMax(own_value, 0.02);
155 werner 418
            value =  *grid_value / own_value; // remove impact of focal tree
78 Werner 419
            //if (value>0.)
420
            sum += value * (*reader)(x,y);
421
 
422
            //} // isIndexValid
423
        }
424
    }
106 Werner 425
    mLRI = sum;
148 iland 426
    if (mLRI > 1.)
427
        mLRI = 1.;
78 Werner 428
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 429
 
430
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 431
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 432
}
433
 
155 werner 434
 
40 Werner 435
void Tree::resetStatistics()
436
{
437
    m_statPrint=0;
105 Werner 438
    m_statCreated=0;
48 Werner 439
    m_statAboveZ=0;
40 Werner 440
    m_nextId=1;
441
}
107 Werner 442
 
251 werner 443
void Tree::calcLightResponse()
444
{
445
    // calculate a light response from lri:
298 werner 446
    // http://iland.boku.ac.at/individual+tree+light+availability
251 werner 447
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.);
274 werner 448
    mLightResponse = mSpecies->lightResponse(lri);
251 werner 449
    mRU->addLR(mLeafArea, mLightResponse);
450
 
451
}
452
 
110 Werner 453
//////////////////////////////////////////////////
454
////  Growth Functions
455
//////////////////////////////////////////////////
107 Werner 456
 
227 werner 457
/** grow() is the main function of the yearly tree growth.
458
  The main steps are:
298 werner 459
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
460
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 461
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
462
  Additionally, the age of the tree is increased and the mortality sub routine is executed.*/
107 Werner 463
void Tree::grow()
464
{
159 werner 465
    TreeGrowthData d;
169 werner 466
    mAge++; // increase age
230 werner 467
    // step 1: get "interception area" of the tree individual [m2]
468
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
469
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 470
 
230 werner 471
    // step 2: calculate GPP of the tree based
472
    // (1) get the amount of GPP for a "unit area" of the tree species
473
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
474
    // (2) GPP (without aging-effect) in kg Biomass / year
475
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 476
 
227 werner 477
    // apply aging according to the state of the individuum
169 werner 478
    const double aging_factor = mSpecies->aging(mHeight, mAge);
227 werner 479
    double gpp = raw_gpp * aging_factor; //
480
    d.NPP = gpp * 0.47; // respiration loss, cf. Waring et al 1998.
113 Werner 481
 
279 werner 482
    //DBGMODE(
137 Werner 483
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 484
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
485
            dumpList(out); // add tree headers
251 werner 486
            out << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 487
        }
279 werner 488
    //); // DBGMODE()
217 werner 489
    if (d.NPP>0.)
490
        partitioning(d); // split npp to compartments and grow (diameter, height)
133 Werner 491
 
200 werner 492
    if (Model::settings().mortalityEnabled)
493
        mortality(d);
110 Werner 494
 
159 werner 495
    mStressIndex = d.stress_index;
180 werner 496
 
497
    if (!isDead())
257 werner 498
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 499
 
107 Werner 500
}
501
 
227 werner 502
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 503
  Conceptionally, the algorithm is based on Duursma, 2007.
504
  @sa http://iland.boku.ac.at/allocation */
159 werner 505
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 506
{
164 werner 507
    if (isDebugging())
508
        enableDebugging(true);
159 werner 509
    double npp = d.NPP;
115 Werner 510
    // add content of reserve pool
116 Werner 511
    npp += mNPPReserve;
159 werner 512
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 513
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 514
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 515
 
136 Werner 516
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
117 Werner 517
    // turnover rates
159 werner 518
    const double &to_fol = species()->turnoverLeaf();
519
    const double &to_root = species()->turnoverRoot();
136 Werner 520
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 521
 
136 Werner 522
 
163 werner 523
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
524
 
227 werner 525
    apct_root = mRU->resourceUnitSpecies(species()).prod3PG().rootFraction();
261 werner 526
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 527
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 528
 
529
    // Duursma 2007, Eq. (20)
167 werner 530
    apct_wood = (foliage_mass_allo*to_wood/npp + b_wf*(1.-apct_root) - b_wf*foliage_mass_allo*to_fol/npp) / ( foliage_mass_allo/mWoodyMass + b_wf );
163 werner 531
    if (apct_wood<0)
532
        apct_wood = 0.;
117 Werner 533
    apct_foliage = 1. - apct_root - apct_wood;
534
 
163 werner 535
 
536
    //DBGMODE(
537
            if (apct_foliage<0 || apct_wood<0)
538
                qDebug() << "transfer to foliage or wood < 0";
539
             if (npp<0)
540
                 qDebug() << "NPP < 0";
541
         //   );
542
 
136 Werner 543
    // Change of biomass compartments
276 werner 544
    double sen_root = mFineRootMass * to_root;
545
    double sen_foliage = mFoliageMass * to_fol;
298 werner 546
 
136 Werner 547
    // Roots
298 werner 548
    // http://iland.boku.ac.at/allocation#belowground_NPP
276 werner 549
    mFineRootMass -= sen_root; // reduce only fine root pool
550
    double delta_root = apct_root * npp;
551
    // 1st, refill the fine root pool
552
    double fineroot_miss = mFoliageMass * mSpecies->finerootFoliageRatio() - mFineRootMass;
553
    if (fineroot_miss>0.){
554
        double delta_fineroot = qMin(fineroot_miss, delta_root);
555
        mFineRootMass += delta_fineroot;
556
        delta_root -= delta_fineroot;
557
    }
558
    // 2nd, the rest of NPP allocated to roots go to coarse roots
559
    mCoarseRootMass += delta_root;
119 Werner 560
 
136 Werner 561
    // Foliage
159 werner 562
    double delta_foliage = apct_foliage * npp - sen_foliage;
137 Werner 563
    mFoliageMass += delta_foliage;
217 werner 564
    if (_isnan(mFoliageMass))
565
        qDebug() << "foliage mass invalid!";
163 werner 566
    if (mFoliageMass<0.) mFoliageMass=0.; // limit to zero
567
 
159 werner 568
    mLeafArea = mFoliageMass * species()->specificLeafArea(); // update leaf area
119 Werner 569
 
271 werner 570
    // stress index: different varaints at denominator: to_fol*foliage_mass = leafmass to rebuild,
198 werner 571
    // foliage_mass_allo: simply higher chance for stress
271 werner 572
    // note: npp = NPP + reserve (see above)
276 werner 573
    d.stress_index =qMax(1. - (npp) / ( to_fol*foliage_mass_allo + to_root*foliage_mass_allo*species()->finerootFoliageRatio() + reserve_size), 0.);
198 werner 574
 
136 Werner 575
    // Woody compartments
298 werner 576
    // see also: http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth
136 Werner 577
    // (1) transfer to reserve pool
578
    double gross_woody = apct_wood * npp;
579
    double to_reserve = qMin(reserve_size, gross_woody);
580
    mNPPReserve = to_reserve;
581
    double net_woody = gross_woody - to_reserve;
137 Werner 582
    double net_stem = 0.;
164 werner 583
    mDbhDelta = 0.;
165 werner 584
 
585
 
136 Werner 586
    if (net_woody > 0.) {
587
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
159 werner 588
        net_stem = net_woody * species()->allometricFractionStem(mDbh);
589
        d.NPP_stem = net_stem;
137 Werner 590
        mWoodyMass += net_woody;
136 Werner 591
        //  (3) growth of diameter and height baseed on net stem increment
159 werner 592
        grow_diameter(d);
136 Werner 593
    }
119 Werner 594
 
279 werner 595
    //DBGMODE(
137 Werner 596
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
597
         && isDebugging() ) {
129 Werner 598
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
599
            dumpList(out); // add tree headers
136 Werner 600
            out << npp << apct_foliage << apct_wood << apct_root
276 werner 601
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem << d.stress_index;
137 Werner 602
     }
144 Werner 603
 
279 werner 604
    //); // DBGMODE()
144 Werner 605
    //DBGMODE(
276 werner 606
      if (mWoodyMass<0. || mWoodyMass>10000 || mFoliageMass<0. || mFoliageMass>1000. || mCoarseRootMass<0. || mCoarseRootMass>10000
144 Werner 607
         || mNPPReserve>2000.) {
608
         qDebug() << "Tree:partitioning: invalid pools.";
609
         qDebug() << GlobalSettings::instance()->debugListCaptions(GlobalSettings::DebugOutputs(0));
610
         DebugList dbg; dumpList(dbg);
611
         qDebug() << dbg;
612
     } //);
613
 
136 Werner 614
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
615
             + QString("npp %1 npp_reserve %9 sen_fol %2 sen_stem %3 sen_root %4 net_fol %5 net_stem %6 net_root %7 to_reserve %8")
616
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
617
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 618
 
115 Werner 619
}
620
 
125 Werner 621
 
134 Werner 622
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 623
    Refer to XXX for equations and variables.
624
    This function updates the dbh and height of the tree.
227 werner 625
    The equations are based on dbh in meters! */
159 werner 626
inline void Tree::grow_diameter(TreeGrowthData &d)
119 Werner 627
{
628
    // determine dh-ratio of increment
629
    // height increment is a function of light competition:
125 Werner 630
    double hd_growth = relative_height_growth(); // hd of height growth
153 werner 631
    double d_m = mDbh / 100.; // current diameter in [m]
159 werner 632
    double net_stem_npp = d.NPP_stem;
633
 
153 werner 634
    const double d_delta_m = mDbhDelta / 100.; // increment of last year in [m]
115 Werner 635
 
159 werner 636
    const double mass_factor = species()->volumeFactor() * species()->density();
153 werner 637
    double stem_mass = mass_factor * d_m*d_m * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 638
 
153 werner 639
    // factor is in diameter increment per NPP [m/kg]
640
    double factor_diameter = 1. / (  mass_factor * (d_m + d_delta_m)*(d_m + d_delta_m) * ( 2. * mHeight/d_m + hd_growth) );
125 Werner 641
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
642
 
643
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
153 werner 644
    double stem_estimate = mass_factor * (d_m + delta_d_estimate)*(d_m + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 645
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 646
 
647
    // the final increment is then:
648
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
144 Werner 649
    DBG_IF_X(d_increment<0. || d_increment>0.1, "Tree::grow_dimater", "increment out of range.", dump()
125 Werner 650
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
651
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 652
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 653
 
654
    DBGMODE(
153 werner 655
        double res_final = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp));
125 Werner 656
        DBG_IF_X(res_final > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump());
153 werner 657
        DBG_IF_X(d_increment > 10. || d_increment*hd_growth >10., "Tree::grow_diameter", "growth out of bound:",QString("d-increment %1 h-increment %2 ").arg(d_increment).arg(d_increment*hd_growth/100.) + dump());
158 werner 658
 
137 Werner 659
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 660
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 661
            dumpList(out); // add tree headers
143 Werner 662
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 663
        }
153 werner 664
 
142 Werner 665
    ); // DBGMODE()
125 Werner 666
 
667
    d_increment = qMax(d_increment, 0.);
668
 
669
    // update state variables
153 werner 670
    mDbh += d_increment*100; // convert from [m] to [cm]
671
    mDbhDelta = d_increment*100; // save for next year's growth
672
    mHeight += d_increment * hd_growth;
158 werner 673
 
674
    // update state of LIP stamp and opacity
159 werner 675
    mStamp = species()->stamp(mDbh, mHeight); // get new stamp for updated dimensions
158 werner 676
    // calculate the CrownFactor which reflects the opacity of the crown
200 werner 677
    const double k=Model::settings().lightExtinctionCoefficientOpacity;
678
    mOpacity = 1. - exp(-k * mLeafArea / mStamp->crownArea());
158 werner 679
 
119 Werner 680
}
681
 
125 Werner 682
 
683
/// return the HD ratio of this year's increment based on the light status.
119 Werner 684
inline double Tree::relative_height_growth()
685
{
686
    double hd_low, hd_high;
687
    mSpecies->hdRange(mDbh, hd_low, hd_high);
688
 
125 Werner 689
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
690
    DBG_IF_X(hd_low < 20 || hd_high>250, "Tree::relative_height_growth", "hd out of range ", dump() + QString(" hd-low: %1 hd-high: %2").arg(hd_low).arg(hd_high));
691
 
692
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
693
    double hd_ratio = hd_high - (hd_high-hd_low)*mLRI;
694
    return hd_ratio;
119 Werner 695
}
141 Werner 696
 
278 werner 697
/** This function is called if a tree dies.
698
  @sa ResourceUnit::cleanTreeList(), remove() */
277 werner 699
void Tree::die(TreeGrowthData *d)
700
{
701
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
702
    mRU->resourceUnitSpecies(species()).statisticsDead().add(this, d); // add tree to statistics
703
}
704
 
278 werner 705
void Tree::remove()
706
{
707
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
708
    mRU->resourceUnitSpecies(species()).statisticsMgmt().add(this, 0);
709
}
710
 
159 werner 711
void Tree::mortality(TreeGrowthData &d)
712
{
163 werner 713
    // death if leaf area is 0
714
    if (mFoliageMass<0.00001)
715
        die();
716
 
159 werner 717
    double p_death,  p_stress;
170 werner 718
    p_stress = d.stress_index * species()->deathProb_stress();
719
    //if (d.stress_index>0)
720
    //    p_stress = species()->deathProb_stress();
159 werner 721
    p_death = species()->deathProb_intrinsic() + p_stress;
190 werner 722
    double p = drandom(); //0..1
159 werner 723
    if (p<p_death) {
724
        // die...
725
        die();
726
    }
727
}
141 Werner 728
 
729
//////////////////////////////////////////////////
730
////  value functions
731
//////////////////////////////////////////////////
732
 
145 Werner 733
double Tree::volume() const
141 Werner 734
{
735
    /// @see Species::volumeFactor() for details
159 werner 736
    const double volume_factor = species()->volumeFactor();
157 werner 737
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 738
    return volume;
739
}
180 werner 740
 
741
double Tree::basalArea() const
742
{
743
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
744
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
745
    return b;
746
}