Subversion Repositories public iLand

Rev

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