Subversion Repositories public iLand

Rev

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