Subversion Repositories public iLand

Rev

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