Subversion Repositories public iLand

Rev

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