Subversion Repositories public iLand

Rev

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