Subversion Repositories public iLand

Rev

Rev 387 | Rev 389 | 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
 
78 Werner 417
    for (y=0;y<reader_size; ++y, ++ry) {
106 Werner 418
        grid_value = mGrid->ptr(rx, ry);
78 Werner 419
        for (x=0;x<reader_size;++x) {
387 werner 420
            xt = torusIndex(rx+x,cPxPerRU, bufferOffset, ru_offset.x());
421
            yt = torusIndex(ry+y,cPxPerRU, bufferOffset, ru_offset.y());
155 werner 422
            grid_value = mGrid->ptr(xt,yt);
78 Werner 423
            //p = pos_reader + QPoint(x,y);
424
            //if (m_grid->isIndexValid(p)) {
387 werner 425
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight, yt/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
78 Werner 426
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
125 Werner 427
 
149 werner 428
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 429
            own_value = qMax(own_value, 0.02);
155 werner 430
            value =  *grid_value / own_value; // remove impact of focal tree
321 werner 431
            //if (_isnan(value))
432
            //    qDebug() << "isnan" << id();
78 Werner 433
            //if (value>0.)
434
            sum += value * (*reader)(x,y);
435
 
436
            //} // isIndexValid
437
        }
438
    }
106 Werner 439
    mLRI = sum;
321 werner 440
    if (_isnan(mLRI)) {
441
        qDebug() << "LRI invalid (nan)!" << id();
442
        mLRI=0.;
443
        //qDebug() << reader->dump();
444
    }
148 iland 445
    if (mLRI > 1.)
446
        mLRI = 1.;
78 Werner 447
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 448
 
449
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 450
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 451
}
452
 
155 werner 453
 
40 Werner 454
void Tree::resetStatistics()
455
{
456
    m_statPrint=0;
105 Werner 457
    m_statCreated=0;
48 Werner 458
    m_statAboveZ=0;
40 Werner 459
    m_nextId=1;
460
}
107 Werner 461
 
251 werner 462
void Tree::calcLightResponse()
463
{
464
    // calculate a light response from lri:
298 werner 465
    // http://iland.boku.ac.at/individual+tree+light+availability
251 werner 466
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.);
274 werner 467
    mLightResponse = mSpecies->lightResponse(lri);
251 werner 468
    mRU->addLR(mLeafArea, mLightResponse);
469
 
470
}
471
 
110 Werner 472
//////////////////////////////////////////////////
473
////  Growth Functions
474
//////////////////////////////////////////////////
107 Werner 475
 
227 werner 476
/** grow() is the main function of the yearly tree growth.
477
  The main steps are:
298 werner 478
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
479
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 480
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
387 werner 481
  Further activties: * the age of the tree is increased
482
                     * the mortality sub routine is executed
483
                     * seeds are produced */
107 Werner 484
void Tree::grow()
485
{
159 werner 486
    TreeGrowthData d;
169 werner 487
    mAge++; // increase age
230 werner 488
    // step 1: get "interception area" of the tree individual [m2]
489
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
490
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 491
 
230 werner 492
    // step 2: calculate GPP of the tree based
493
    // (1) get the amount of GPP for a "unit area" of the tree species
494
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
495
    // (2) GPP (without aging-effect) in kg Biomass / year
496
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 497
 
227 werner 498
    // apply aging according to the state of the individuum
388 werner 499
    const double aging_factor = mSpecies->aging(mHeight, mAge);
376 werner 500
    mRU->addTreeAging(mLeafArea, aging_factor);
227 werner 501
    double gpp = raw_gpp * aging_factor; //
502
    d.NPP = gpp * 0.47; // respiration loss, cf. Waring et al 1998.
113 Werner 503
 
279 werner 504
    //DBGMODE(
137 Werner 505
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 506
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
507
            dumpList(out); // add tree headers
299 werner 508
            out << mLRI * mRU->LRImodifier() << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 509
        }
279 werner 510
    //); // DBGMODE()
217 werner 511
    if (d.NPP>0.)
512
        partitioning(d); // split npp to compartments and grow (diameter, height)
133 Werner 513
 
387 werner 514
    // mortality
200 werner 515
    if (Model::settings().mortalityEnabled)
516
        mortality(d);
110 Werner 517
 
159 werner 518
    mStressIndex = d.stress_index;
180 werner 519
 
520
    if (!isDead())
257 werner 521
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 522
 
387 werner 523
    // regeneration
524
    mSpecies->seedProduction(mHeight, mPositionIndex);
525
 
107 Werner 526
}
527
 
227 werner 528
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 529
  Conceptionally, the algorithm is based on Duursma, 2007.
530
  @sa http://iland.boku.ac.at/allocation */
159 werner 531
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 532
{
164 werner 533
    if (isDebugging())
534
        enableDebugging(true);
159 werner 535
    double npp = d.NPP;
115 Werner 536
    // add content of reserve pool
116 Werner 537
    npp += mNPPReserve;
159 werner 538
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 539
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 540
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 541
 
136 Werner 542
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
117 Werner 543
    // turnover rates
159 werner 544
    const double &to_fol = species()->turnoverLeaf();
545
    const double &to_root = species()->turnoverRoot();
136 Werner 546
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 547
 
136 Werner 548
 
163 werner 549
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
550
 
227 werner 551
    apct_root = mRU->resourceUnitSpecies(species()).prod3PG().rootFraction();
261 werner 552
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 553
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 554
 
555
    // Duursma 2007, Eq. (20)
167 werner 556
    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 557
    if (apct_wood<0)
558
        apct_wood = 0.;
117 Werner 559
    apct_foliage = 1. - apct_root - apct_wood;
560
 
163 werner 561
 
562
    //DBGMODE(
563
            if (apct_foliage<0 || apct_wood<0)
564
                qDebug() << "transfer to foliage or wood < 0";
565
             if (npp<0)
566
                 qDebug() << "NPP < 0";
567
         //   );
568
 
136 Werner 569
    // Change of biomass compartments
276 werner 570
    double sen_root = mFineRootMass * to_root;
571
    double sen_foliage = mFoliageMass * to_fol;
298 werner 572
 
136 Werner 573
    // Roots
298 werner 574
    // http://iland.boku.ac.at/allocation#belowground_NPP
276 werner 575
    mFineRootMass -= sen_root; // reduce only fine root pool
576
    double delta_root = apct_root * npp;
577
    // 1st, refill the fine root pool
578
    double fineroot_miss = mFoliageMass * mSpecies->finerootFoliageRatio() - mFineRootMass;
579
    if (fineroot_miss>0.){
580
        double delta_fineroot = qMin(fineroot_miss, delta_root);
581
        mFineRootMass += delta_fineroot;
582
        delta_root -= delta_fineroot;
583
    }
584
    // 2nd, the rest of NPP allocated to roots go to coarse roots
585
    mCoarseRootMass += delta_root;
119 Werner 586
 
136 Werner 587
    // Foliage
159 werner 588
    double delta_foliage = apct_foliage * npp - sen_foliage;
137 Werner 589
    mFoliageMass += delta_foliage;
217 werner 590
    if (_isnan(mFoliageMass))
591
        qDebug() << "foliage mass invalid!";
163 werner 592
    if (mFoliageMass<0.) mFoliageMass=0.; // limit to zero
593
 
159 werner 594
    mLeafArea = mFoliageMass * species()->specificLeafArea(); // update leaf area
119 Werner 595
 
271 werner 596
    // stress index: different varaints at denominator: to_fol*foliage_mass = leafmass to rebuild,
198 werner 597
    // foliage_mass_allo: simply higher chance for stress
271 werner 598
    // note: npp = NPP + reserve (see above)
276 werner 599
    d.stress_index =qMax(1. - (npp) / ( to_fol*foliage_mass_allo + to_root*foliage_mass_allo*species()->finerootFoliageRatio() + reserve_size), 0.);
198 werner 600
 
136 Werner 601
    // Woody compartments
298 werner 602
    // see also: http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth
136 Werner 603
    // (1) transfer to reserve pool
604
    double gross_woody = apct_wood * npp;
605
    double to_reserve = qMin(reserve_size, gross_woody);
606
    mNPPReserve = to_reserve;
607
    double net_woody = gross_woody - to_reserve;
137 Werner 608
    double net_stem = 0.;
164 werner 609
    mDbhDelta = 0.;
165 werner 610
 
611
 
136 Werner 612
    if (net_woody > 0.) {
613
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
159 werner 614
        net_stem = net_woody * species()->allometricFractionStem(mDbh);
615
        d.NPP_stem = net_stem;
137 Werner 616
        mWoodyMass += net_woody;
136 Werner 617
        //  (3) growth of diameter and height baseed on net stem increment
159 werner 618
        grow_diameter(d);
136 Werner 619
    }
119 Werner 620
 
279 werner 621
    //DBGMODE(
137 Werner 622
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
623
         && isDebugging() ) {
129 Werner 624
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
625
            dumpList(out); // add tree headers
136 Werner 626
            out << npp << apct_foliage << apct_wood << apct_root
276 werner 627
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem << d.stress_index;
137 Werner 628
     }
144 Werner 629
 
279 werner 630
    //); // DBGMODE()
144 Werner 631
    //DBGMODE(
276 werner 632
      if (mWoodyMass<0. || mWoodyMass>10000 || mFoliageMass<0. || mFoliageMass>1000. || mCoarseRootMass<0. || mCoarseRootMass>10000
144 Werner 633
         || mNPPReserve>2000.) {
634
         qDebug() << "Tree:partitioning: invalid pools.";
635
         qDebug() << GlobalSettings::instance()->debugListCaptions(GlobalSettings::DebugOutputs(0));
636
         DebugList dbg; dumpList(dbg);
637
         qDebug() << dbg;
638
     } //);
639
 
136 Werner 640
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
641
             + 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")
642
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
643
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 644
 
115 Werner 645
}
646
 
125 Werner 647
 
134 Werner 648
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 649
    Refer to XXX for equations and variables.
650
    This function updates the dbh and height of the tree.
227 werner 651
    The equations are based on dbh in meters! */
159 werner 652
inline void Tree::grow_diameter(TreeGrowthData &d)
119 Werner 653
{
654
    // determine dh-ratio of increment
655
    // height increment is a function of light competition:
125 Werner 656
    double hd_growth = relative_height_growth(); // hd of height growth
153 werner 657
    double d_m = mDbh / 100.; // current diameter in [m]
159 werner 658
    double net_stem_npp = d.NPP_stem;
659
 
153 werner 660
    const double d_delta_m = mDbhDelta / 100.; // increment of last year in [m]
115 Werner 661
 
159 werner 662
    const double mass_factor = species()->volumeFactor() * species()->density();
153 werner 663
    double stem_mass = mass_factor * d_m*d_m * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 664
 
153 werner 665
    // factor is in diameter increment per NPP [m/kg]
666
    double factor_diameter = 1. / (  mass_factor * (d_m + d_delta_m)*(d_m + d_delta_m) * ( 2. * mHeight/d_m + hd_growth) );
125 Werner 667
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
668
 
669
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
153 werner 670
    double stem_estimate = mass_factor * (d_m + delta_d_estimate)*(d_m + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 671
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 672
 
673
    // the final increment is then:
674
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
144 Werner 675
    DBG_IF_X(d_increment<0. || d_increment>0.1, "Tree::grow_dimater", "increment out of range.", dump()
125 Werner 676
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
677
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 678
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 679
 
303 werner 680
    //DBGMODE(
326 werner 681
       // double res_final = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp));
682
        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 683
        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 684
 
137 Werner 685
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 686
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 687
            dumpList(out); // add tree headers
143 Werner 688
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 689
        }
153 werner 690
 
303 werner 691
    //); // DBGMODE()
125 Werner 692
 
693
    d_increment = qMax(d_increment, 0.);
694
 
695
    // update state variables
153 werner 696
    mDbh += d_increment*100; // convert from [m] to [cm]
697
    mDbhDelta = d_increment*100; // save for next year's growth
698
    mHeight += d_increment * hd_growth;
158 werner 699
 
700
    // update state of LIP stamp and opacity
159 werner 701
    mStamp = species()->stamp(mDbh, mHeight); // get new stamp for updated dimensions
158 werner 702
    // calculate the CrownFactor which reflects the opacity of the crown
200 werner 703
    const double k=Model::settings().lightExtinctionCoefficientOpacity;
704
    mOpacity = 1. - exp(-k * mLeafArea / mStamp->crownArea());
158 werner 705
 
119 Werner 706
}
707
 
125 Werner 708
 
709
/// return the HD ratio of this year's increment based on the light status.
119 Werner 710
inline double Tree::relative_height_growth()
711
{
712
    double hd_low, hd_high;
713
    mSpecies->hdRange(mDbh, hd_low, hd_high);
714
 
125 Werner 715
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
716
    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));
717
 
718
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
326 werner 719
    // use the corrected LRI (see tracker#11)
720
    double lri = limit(mLRI * mRU->LRImodifier(),0.,1.);
721
    double hd_ratio = hd_high - (hd_high-hd_low)*lri;
125 Werner 722
    return hd_ratio;
119 Werner 723
}
141 Werner 724
 
278 werner 725
/** This function is called if a tree dies.
726
  @sa ResourceUnit::cleanTreeList(), remove() */
277 werner 727
void Tree::die(TreeGrowthData *d)
728
{
729
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
730
    mRU->resourceUnitSpecies(species()).statisticsDead().add(this, d); // add tree to statistics
731
}
732
 
278 werner 733
void Tree::remove()
734
{
735
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
736
    mRU->resourceUnitSpecies(species()).statisticsMgmt().add(this, 0);
737
}
738
 
159 werner 739
void Tree::mortality(TreeGrowthData &d)
740
{
163 werner 741
    // death if leaf area is 0
742
    if (mFoliageMass<0.00001)
743
        die();
744
 
308 werner 745
    double p_death,  p_stress, p_intrinsic;
746
    p_intrinsic = species()->deathProb_intrinsic();
747
    p_stress = species()->deathProb_stress(d.stress_index);
748
    p_death =  p_intrinsic + p_stress;
190 werner 749
    double p = drandom(); //0..1
159 werner 750
    if (p<p_death) {
751
        // die...
752
        die();
753
    }
754
}
141 Werner 755
 
756
//////////////////////////////////////////////////
757
////  value functions
758
//////////////////////////////////////////////////
759
 
145 Werner 760
double Tree::volume() const
141 Werner 761
{
762
    /// @see Species::volumeFactor() for details
159 werner 763
    const double volume_factor = species()->volumeFactor();
157 werner 764
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 765
    return volume;
766
}
180 werner 767
 
768
double Tree::basalArea() const
769
{
770
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
771
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
772
    return b;
773
}