Subversion Repositories public iLand

Rev

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