Subversion Repositories public iLand

Rev

Rev 165 | Rev 169 | 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"
107 Werner 9
#include "ressourceunit.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
 
159 werner 20
/// internal data structure which is passed between function
21
struct TreeGrowthData
22
{
23
    double NPP; ///< total NPP
24
    double NPP_stem;  ///< NPP used for growth of stem (dbh,h)
25
    double stress_index; ///< stress index used for mortality calculation
26
};
158 werner 27
 
28
/** get distance and direction between two points.
29
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
30
float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle)
151 iland 31
{
158 werner 32
    float dx = PEnd.x() - PStart.x();
33
    float dy = PEnd.y() - PStart.y();
34
    float d = sqrt(dx*dx + dy*dy);
35
    // direction:
36
    rAngle = atan2(dx, dy);
37
    return d;
151 iland 38
}
39
 
158 werner 40
 
110 Werner 41
// lifecycle
3 Werner 42
Tree::Tree()
43
{
149 werner 44
    mDbh = mHeight = 0;
45
    mRU = 0; mSpecies = 0;
157 werner 46
    mFlags = 0;
149 werner 47
    mOpacity=mFoliageMass=mWoodyMass=mRootMass=mLeafArea=0.;
159 werner 48
    mDbhDelta=mNPPReserve=mLRI=mStressIndex=0.;
106 Werner 49
    mId = m_nextId++;
105 Werner 50
    m_statCreated++;
3 Werner 51
}
38 Werner 52
 
158 werner 53
void Tree::setGrid(FloatGrid* gridToStamp, Grid<HeightGridValue> *dominanceGrid)
3 Werner 54
{
158 werner 55
    mGrid = gridToStamp; mHeightGrid = dominanceGrid;
3 Werner 56
}
57
 
125 Werner 58
/// dumps some core variables of a tree to a string.
59
QString Tree::dump()
60
{
61
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
159 werner 62
                            .arg(mId).arg(species()->id()).arg(mDbh).arg(mHeight)
156 werner 63
                            .arg(position().x()).arg(position().y())
125 Werner 64
                            .arg(mRU->index()).arg(mLRI);
65
    return result;
66
}
3 Werner 67
 
129 Werner 68
void Tree::dumpList(DebugList &rTargetList)
69
{
159 werner 70
    rTargetList << mId << species()->id() << mDbh << mHeight  << position().x() << position().y()   << mRU->index() << mLRI
136 Werner 71
                << mWoodyMass << mRootMass << mFoliageMass << mLeafArea;
129 Werner 72
}
73
 
38 Werner 74
void Tree::setup()
75
{
106 Werner 76
    if (mDbh<=0 || mHeight<=0)
38 Werner 77
        return;
78
    // check stamp
159 werner 79
    Q_ASSERT_X(species()!=0, "Tree::setup()", "species is NULL");
80
    mStamp = species()->stamp(mDbh, mHeight);
110 Werner 81
 
159 werner 82
    mFoliageMass = species()->biomassFoliage(mDbh);
83
    mRootMass = species()->biomassRoot(mDbh) + mFoliageMass; // coarse root (allometry) + fine root (estimated size: foliage)
84
    mWoodyMass = species()->biomassWoody(mDbh);
110 Werner 85
 
137 Werner 86
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
159 werner 87
    mLeafArea = mFoliageMass * species()->specificLeafArea();
149 werner 88
    mOpacity = 1. - exp(-0.5 * mLeafArea / mStamp->crownArea());
137 Werner 89
    mNPPReserve = 2*mFoliageMass; // initial value
90
    mDbhDelta = 0.1; // initial value: used in growth() to estimate diameter increment
38 Werner 91
}
39 Werner 92
 
110 Werner 93
//////////////////////////////////////////////////
94
////  Light functions (Pattern-stuff)
95
//////////////////////////////////////////////////
96
 
70 Werner 97
#define NOFULLDBG
77 Werner 98
//#define NOFULLOPT
39 Werner 99
 
40 Werner 100
 
158 werner 101
void Tree::applyLIP()
77 Werner 102
{
144 Werner 103
    if (!mStamp)
104
        return;
106 Werner 105
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
156 werner 106
    QPoint pos = mPositionIndex;
106 Werner 107
    int offset = mStamp->offset();
77 Werner 108
    pos-=QPoint(offset, offset);
109
 
110
    float local_dom; // height of Z* on the current position
111
    int x,y;
112
    float value;
106 Werner 113
    int gr_stamp = mStamp->size();
77 Werner 114
    int grid_x, grid_y;
115
    float *grid_value;
106 Werner 116
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
77 Werner 117
        // todo: in this case we should use another algorithm!!!
118
        return;
119
    }
120
 
121
    for (y=0;y<gr_stamp; ++y) {
122
        grid_y = pos.y() + y;
106 Werner 123
        grid_value = mGrid->ptr(pos.x(), grid_y);
77 Werner 124
        for (x=0;x<gr_stamp;++x) {
125
            // suppose there is no stamping outside
126
            grid_x = pos.x() + x;
127
 
151 iland 128
            local_dom = mHeightGrid->valueAtIndex(grid_x/5, grid_y/5).height;
106 Werner 129
            value = (*mStamp)(x,y); // stampvalue
149 werner 130
            value = 1. - value*mOpacity / local_dom; // calculated value
77 Werner 131
            value = qMax(value, 0.02f); // limit value
132
 
133
            *grid_value++ *= value;
134
        }
135
    }
136
 
137
    m_statPrint++; // count # of stamp applications...
138
}
139
 
155 werner 140
/// helper function for gluing the edges together
141
/// index: index at grid
142
/// count: number of pixels that are the simulation area (e.g. 100m and 2m pixel -> 50)
143
/// buffer: size of buffer around simulation area (in pixels)
144
int torusIndex(int index, int count, int buffer)
145
{
146
    return buffer + (index-buffer+count)%count;
147
}
62 Werner 148
 
155 werner 149
 
150
/** Apply LIPs. This "Torus" functions wraps the influence at the edges of a 1ha simulation area.
151
  */
158 werner 152
void Tree::applyLIP_torus()
155 werner 153
{
154
    if (!mStamp)
155
        return;
156
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
157
 
156 werner 158
    QPoint pos = mPositionIndex;
155 werner 159
    int offset = mStamp->offset();
160
    pos-=QPoint(offset, offset);
161
 
162
    float local_dom; // height of Z* on the current position
163
    int x,y;
164
    float value;
165
    int gr_stamp = mStamp->size();
166
    int grid_x, grid_y;
167
    float *grid_value;
168
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
169
        // todo: in this case we should use another algorithm!!! necessary????
170
        return;
171
    }
172
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
173
    int xt, yt; // wraparound coordinates
174
    for (y=0;y<gr_stamp; ++y) {
175
        grid_y = pos.y() + y;
176
        yt = torusIndex(grid_y, 50,bufferOffset); // 50 cells per 100m
177
        for (x=0;x<gr_stamp;++x) {
178
            // suppose there is no stamping outside
179
            grid_x = pos.x() + x;
180
            xt = torusIndex(grid_x,50,bufferOffset);
181
 
182
            local_dom = mHeightGrid->valueAtIndex(xt/5,yt/5).height;
183
            value = (*mStamp)(x,y); // stampvalue
184
            value = 1. - value*mOpacity / local_dom; // calculated value
185
            value = qMax(value, 0.02f); // limit value
186
 
187
            grid_value = mGrid->ptr(xt, yt); // use wraparound coordinates
188
            *grid_value *= value;
189
        }
190
    }
191
 
192
    m_statPrint++; // count # of stamp applications...
193
}
194
 
74 Werner 195
/** heightGrid()
196
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
197
*/
198
void Tree::heightGrid()
199
{
200
    // height of Z*
106 Werner 201
    const float cellsize = mHeightGrid->cellsize();
74 Werner 202
 
156 werner 203
    QPoint p = QPoint(mPositionIndex.x()/5, mPositionIndex.y()/5); // pos of tree on height grid
74 Werner 204
 
151 iland 205
    // count trees that are on height-grid cells (used for stockable area)
206
    mHeightGrid->valueAtIndex(p).count++;
207
 
156 werner 208
    int index_eastwest = mPositionIndex.x() % 5; // 4: very west, 0 east edge
209
    int index_northsouth = mPositionIndex.y() % 5; // 4: northern edge, 0: southern edge
74 Werner 210
    int dist[9];
211
    dist[3] = index_northsouth * 2 + 1; // south
212
    dist[1] = index_eastwest * 2 + 1; // west
213
    dist[5] = 10 - dist[3]; // north
214
    dist[7] = 10 - dist[1]; // east
215
    dist[8] = qMax(dist[5], dist[7]); // north-east
216
    dist[6] = qMax(dist[3], dist[7]); // south-east
217
    dist[0] = qMax(dist[3], dist[1]); // south-west
218
    dist[2] = qMax(dist[5], dist[1]); // north-west
75 Werner 219
    dist[4] = 0; // center cell
76 Werner 220
    /* 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:
221
       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
222
        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
223
    */
74 Werner 224
 
225
 
106 Werner 226
    int ringcount = int(floor(mHeight / cellsize)) + 1;
74 Werner 227
    int ix, iy;
228
    int ring;
229
    QPoint pos;
230
    float hdom;
231
 
232
    for (ix=-ringcount;ix<=ringcount;ix++)
233
        for (iy=-ringcount; iy<=+ringcount; iy++) {
234
        ring = qMax(abs(ix), abs(iy));
235
        QPoint pos(ix+p.x(), iy+p.y());
106 Werner 236
        if (mHeightGrid->isIndexValid(pos)) {
151 iland 237
            float &rHGrid = mHeightGrid->valueAtIndex(pos).height;
106 Werner 238
            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
74 Werner 239
                continue;
240
            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
106 Werner 241
            hdom = mHeight - dist[direction];
74 Werner 242
            if (ring>1)
243
                hdom -= (ring-1)*10;
244
 
245
            rHGrid = qMax(rHGrid, hdom); // write value
246
        } // is valid
247
    } // for (y)
39 Werner 248
}
40 Werner 249
 
155 werner 250
 
251
 
158 werner 252
void Tree::readLIF()
40 Werner 253
{
106 Werner 254
    if (!mStamp)
155 werner 255
        return;
256
    const Stamp *reader = mStamp->reader();
257
    if (!reader)
258
        return;
156 werner 259
    QPoint pos_reader = mPositionIndex;
155 werner 260
 
261
    int offset_reader = reader->offset();
262
    int offset_writer = mStamp->offset();
263
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
264
 
265
    QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer);
266
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 267
    QPoint p;
268
 
155 werner 269
    //float dom_height = (*m_dominanceGrid)[m_Position];
270
    float local_dom;
271
 
40 Werner 272
    int x,y;
273
    double sum=0.;
155 werner 274
    double value, own_value;
275
    float *grid_value;
276
    int reader_size = reader->size();
277
    int rx = pos_reader.x();
278
    int ry = pos_reader.y();
279
    for (y=0;y<reader_size; ++y, ++ry) {
280
        grid_value = mGrid->ptr(rx, ry);
281
        for (x=0;x<reader_size;++x) {
282
 
283
            //p = pos_reader + QPoint(x,y);
284
            //if (m_grid->isIndexValid(p)) {
285
            local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5).height; // ry: gets ++ed in outer loop, rx not
286
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
287
 
288
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
289
            own_value = qMax(own_value, 0.02);
290
            value =  *grid_value++ / own_value; // remove impact of focal tree
291
            //if (value>0.)
292
            sum += value * (*reader)(x,y);
293
 
294
            //} // isIndexValid
40 Werner 295
        }
296
    }
155 werner 297
    mLRI = sum;
48 Werner 298
    // read dominance field...
155 werner 299
    // this applies only if some trees are potentially *higher* than the dominant height grid
300
    //if (dom_height < m_Height) {
48 Werner 301
        // if tree is higher than Z*, the dominance height
302
        // a part of the crown is in "full light".
155 werner 303
    //    m_statAboveZ++;
304
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
305
    //}
306
    if (mLRI > 1.)
307
        mLRI = 1.;
308
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
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;
78 Werner 416
    // read dominance field...
417
    // this applies only if some trees are potentially *higher* than the dominant height grid
418
    //if (dom_height < m_Height) {
419
        // if tree is higher than Z*, the dominance height
420
        // a part of the crown is in "full light".
421
    //    m_statAboveZ++;
422
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
423
    //}
148 iland 424
    if (mLRI > 1.)
425
        mLRI = 1.;
78 Werner 426
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
148 iland 427
    mRU->addWLA(mLRI*mLeafArea, mLeafArea);
58 Werner 428
}
429
 
155 werner 430
 
40 Werner 431
void Tree::resetStatistics()
432
{
433
    m_statPrint=0;
105 Werner 434
    m_statCreated=0;
48 Werner 435
    m_statAboveZ=0;
40 Werner 436
    m_nextId=1;
437
}
107 Werner 438
 
110 Werner 439
//////////////////////////////////////////////////
440
////  Growth Functions
441
//////////////////////////////////////////////////
107 Werner 442
 
110 Werner 443
 
107 Werner 444
void Tree::grow()
445
{
159 werner 446
    TreeGrowthData d;
147 werner 447
    // step 1: get radiation from ressource unit: radiation (MJ/tree/year) total intercepted radiation for this tree per year!
448
    double radiation = mRU->interceptedRadiation(mLeafArea, mLRI);
113 Werner 449
    // step 2: get fraction of PARutilized, i.e. fraction of intercepted rad that is utiliziable (per year)
107 Werner 450
 
159 werner 451
    double raw_gpp_per_rad = mRU->ressourceUnitSpecies(species()).prod3PG().GPPperRad();
164 werner 452
    // GPP (without aging-effect) [gC] / year -> kg Biomass /GPP (*0.001 *2)
453
    double raw_gpp = raw_gpp_per_rad * radiation * 0.001 * 2;
161 werner 454
 
115 Werner 455
    // apply aging
456
    double gpp = raw_gpp * 0.6; // aging
164 werner 457
    d.NPP = gpp * 0.47; // respiration loss, transformation kg
113 Werner 458
 
133 Werner 459
    DBGMODE(
137 Werner 460
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 461
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
462
            dumpList(out); // add tree headers
161 werner 463
            out << radiation << raw_gpp << gpp << d.NPP;
133 Werner 464
        }
465
    ); // DBGMODE()
466
 
161 werner 467
 
159 werner 468
    partitioning(d); // split npp to compartments and grow (diameter, height)
115 Werner 469
 
159 werner 470
    mortality(d);
110 Werner 471
 
159 werner 472
    mStressIndex = d.stress_index;
107 Werner 473
}
474
 
117 Werner 475
 
159 werner 476
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 477
{
164 werner 478
    if (isDebugging())
479
        enableDebugging(true);
159 werner 480
    double npp = d.NPP;
481
    double harshness = mRU->ressourceUnitSpecies(species()).prod3PG().harshness();
115 Werner 482
    // add content of reserve pool
116 Werner 483
    npp += mNPPReserve;
159 werner 484
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
136 Werner 485
    const double reserve_size = 2 * foliage_mass_allo;
163 werner 486
    double refill_reserve = qMin(reserve_size, 2.*mFoliageMass); // not always try to refill reserve 100%
119 Werner 487
 
136 Werner 488
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
117 Werner 489
    // turnover rates
159 werner 490
    const double &to_fol = species()->turnoverLeaf();
491
    const double &to_root = species()->turnoverRoot();
136 Werner 492
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 493
 
136 Werner 494
 
163 werner 495
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
496
 
117 Werner 497
    apct_root = harshness;
159 werner 498
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents... now fixed
117 Werner 499
 
500
    // Duursma 2007, Eq. (20)
167 werner 501
    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 502
    if (apct_wood<0)
503
        apct_wood = 0.;
117 Werner 504
    apct_foliage = 1. - apct_root - apct_wood;
505
 
163 werner 506
 
507
    //DBGMODE(
508
            if (apct_foliage<0 || apct_wood<0)
509
                qDebug() << "transfer to foliage or wood < 0";
510
             if (npp<0)
511
                 qDebug() << "NPP < 0";
512
         //   );
513
 
136 Werner 514
    // Change of biomass compartments
159 werner 515
    double sen_root = mRootMass*to_root;
516
    double sen_foliage = mFoliageMass*to_fol;
136 Werner 517
    // Roots
159 werner 518
    double delta_root = apct_root * npp - sen_root;
137 Werner 519
    mRootMass += delta_root;
119 Werner 520
 
136 Werner 521
    // Foliage
159 werner 522
    double delta_foliage = apct_foliage * npp - sen_foliage;
137 Werner 523
    mFoliageMass += delta_foliage;
163 werner 524
    if (mFoliageMass<0.) mFoliageMass=0.; // limit to zero
525
 
159 werner 526
    mLeafArea = mFoliageMass * species()->specificLeafArea(); // update leaf area
119 Werner 527
 
159 werner 528
    // stress index
167 werner 529
    d.stress_index =qMax(1. - (npp-reserve_size) / to_fol*foliage_mass_allo, 0.);
159 werner 530
 
136 Werner 531
    // Woody compartments
532
    // (1) transfer to reserve pool
533
    double gross_woody = apct_wood * npp;
534
    double to_reserve = qMin(reserve_size, gross_woody);
535
    mNPPReserve = to_reserve;
536
    double net_woody = gross_woody - to_reserve;
137 Werner 537
    double net_stem = 0.;
164 werner 538
    mDbhDelta = 0.;
165 werner 539
 
540
 
136 Werner 541
    if (net_woody > 0.) {
542
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
159 werner 543
        net_stem = net_woody * species()->allometricFractionStem(mDbh);
544
        d.NPP_stem = net_stem;
137 Werner 545
        mWoodyMass += net_woody;
136 Werner 546
        //  (3) growth of diameter and height baseed on net stem increment
159 werner 547
        grow_diameter(d);
136 Werner 548
    }
119 Werner 549
 
129 Werner 550
    DBGMODE(
137 Werner 551
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
552
         && isDebugging() ) {
129 Werner 553
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
554
            dumpList(out); // add tree headers
136 Werner 555
            out << npp << apct_foliage << apct_wood << apct_root
137 Werner 556
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem;
557
     }
144 Werner 558
 
129 Werner 559
    ); // DBGMODE()
144 Werner 560
    //DBGMODE(
561
      if (mWoodyMass<0. || mWoodyMass>10000 || mFoliageMass<0. || mFoliageMass>1000. || mRootMass<0. || mRootMass>10000
562
         || mNPPReserve>2000.) {
563
         qDebug() << "Tree:partitioning: invalid pools.";
564
         qDebug() << GlobalSettings::instance()->debugListCaptions(GlobalSettings::DebugOutputs(0));
565
         DebugList dbg; dumpList(dbg);
566
         qDebug() << dbg;
567
     } //);
568
 
136 Werner 569
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
570
             + 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")
571
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
572
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 573
 
115 Werner 574
}
575
 
125 Werner 576
 
134 Werner 577
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 578
    Refer to XXX for equations and variables.
579
    This function updates the dbh and height of the tree.
153 werner 580
    The equations are based on dbh in meters!
125 Werner 581
  */
159 werner 582
inline void Tree::grow_diameter(TreeGrowthData &d)
119 Werner 583
{
584
    // determine dh-ratio of increment
585
    // height increment is a function of light competition:
125 Werner 586
    double hd_growth = relative_height_growth(); // hd of height growth
153 werner 587
    double d_m = mDbh / 100.; // current diameter in [m]
159 werner 588
    double net_stem_npp = d.NPP_stem;
589
 
153 werner 590
    const double d_delta_m = mDbhDelta / 100.; // increment of last year in [m]
115 Werner 591
 
159 werner 592
    const double mass_factor = species()->volumeFactor() * species()->density();
153 werner 593
    double stem_mass = mass_factor * d_m*d_m * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 594
 
153 werner 595
    // factor is in diameter increment per NPP [m/kg]
596
    double factor_diameter = 1. / (  mass_factor * (d_m + d_delta_m)*(d_m + d_delta_m) * ( 2. * mHeight/d_m + hd_growth) );
125 Werner 597
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
598
 
599
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
153 werner 600
    double stem_estimate = mass_factor * (d_m + delta_d_estimate)*(d_m + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 601
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 602
 
603
    // the final increment is then:
604
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
144 Werner 605
    DBG_IF_X(d_increment<0. || d_increment>0.1, "Tree::grow_dimater", "increment out of range.", dump()
125 Werner 606
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
607
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 608
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 609
 
610
    DBGMODE(
153 werner 611
        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 612
        DBG_IF_X(res_final > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump());
153 werner 613
        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 614
 
137 Werner 615
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 616
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 617
            dumpList(out); // add tree headers
143 Werner 618
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 619
        }
153 werner 620
 
142 Werner 621
    ); // DBGMODE()
125 Werner 622
 
623
    d_increment = qMax(d_increment, 0.);
624
 
625
    // update state variables
153 werner 626
    mDbh += d_increment*100; // convert from [m] to [cm]
627
    mDbhDelta = d_increment*100; // save for next year's growth
628
    mHeight += d_increment * hd_growth;
158 werner 629
 
630
    // update state of LIP stamp and opacity
159 werner 631
    mStamp = species()->stamp(mDbh, mHeight); // get new stamp for updated dimensions
158 werner 632
    // calculate the CrownFactor which reflects the opacity of the crown
633
    mOpacity = 1. - exp(-0.7 * mLeafArea / mStamp->crownArea());
634
 
119 Werner 635
}
636
 
125 Werner 637
 
638
/// return the HD ratio of this year's increment based on the light status.
119 Werner 639
inline double Tree::relative_height_growth()
640
{
641
    double hd_low, hd_high;
642
    mSpecies->hdRange(mDbh, hd_low, hd_high);
643
 
125 Werner 644
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
645
    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));
646
 
647
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
648
    double hd_ratio = hd_high - (hd_high-hd_low)*mLRI;
649
    return hd_ratio;
119 Werner 650
}
141 Werner 651
 
159 werner 652
void Tree::mortality(TreeGrowthData &d)
653
{
163 werner 654
    // death if leaf area is 0
655
    if (mFoliageMass<0.00001)
656
        die();
657
 
159 werner 658
    double p_death,  p_stress;
167 werner 659
    //p_stress = d.stress_index * species()->deathProb_stress();
660
    if (d.stress_index>0)
661
        p_stress = species()->deathProb_stress();
159 werner 662
    p_death = species()->deathProb_intrinsic() + p_stress;
663
    double p = random(); //0..1
664
    if (p<p_death) {
665
        // die...
666
        die();
667
    }
668
}
141 Werner 669
 
670
//////////////////////////////////////////////////
671
////  value functions
672
//////////////////////////////////////////////////
673
 
145 Werner 674
double Tree::volume() const
141 Werner 675
{
676
    /// @see Species::volumeFactor() for details
159 werner 677
    const double volume_factor = species()->volumeFactor();
157 werner 678
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 679
    return volume;
680
}