Subversion Repositories public iLand

Rev

Rev 121 | Rev 125 | 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"
38 Werner 10
 
110 Werner 11
// static varaibles
106 Werner 12
FloatGrid *Tree::mGrid = 0;
13
FloatGrid *Tree::mHeightGrid = 0;
40 Werner 14
int Tree::m_statPrint=0;
48 Werner 15
int Tree::m_statAboveZ=0;
105 Werner 16
int Tree::m_statCreated=0;
40 Werner 17
int Tree::m_nextId=0;
53 Werner 18
float Tree::lafactor = 1.;
106 Werner 19
int Tree::mDebugid = -1;
40 Werner 20
 
110 Werner 21
// lifecycle
3 Werner 22
Tree::Tree()
23
{
106 Werner 24
    mDbh = 0;
25
    mHeight = 0;
26
    mSpecies = 0;
107 Werner 27
    mRU = 0;
106 Werner 28
    mId = m_nextId++;
105 Werner 29
    m_statCreated++;
3 Werner 30
}
38 Werner 31
 
15 Werner 32
/** get distance and direction between two points.
38 Werner 33
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
3 Werner 34
float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle)
35
{
36
    float dx = PEnd.x() - PStart.x();
37
    float dy = PEnd.y() - PStart.y();
38
    float d = sqrt(dx*dx + dy*dy);
39
    // direction:
40
    rAngle = atan2(dx, dy);
41
    return d;
42
}
43
 
44
 
38 Werner 45
void Tree::setup()
46
{
106 Werner 47
    if (mDbh<=0 || mHeight<=0)
38 Werner 48
        return;
49
    // check stamp
106 Werner 50
   Q_ASSERT_X(mSpecies!=0, "Tree::setup()", "species is NULL");
51
   mStamp = mSpecies->stamp(mDbh, mHeight);
110 Werner 52
 
53
   calcBiomassCompartments();
115 Werner 54
   mNPPReserve = mLeafMass; // initial value
110 Werner 55
 
38 Werner 56
}
39 Werner 57
 
110 Werner 58
//////////////////////////////////////////////////
59
////  Light functions (Pattern-stuff)
60
//////////////////////////////////////////////////
61
 
70 Werner 62
#define NOFULLDBG
77 Werner 63
//#define NOFULLOPT
64
/*
39 Werner 65
void Tree::applyStamp()
66
{
67
    Q_ASSERT(m_grid!=0);
68
    if (!m_stamp)
69
        return;
70
 
40 Werner 71
    QPoint pos = m_grid->indexAt(m_Position);
72
    int offset = m_stamp->offset();
73
    pos-=QPoint(offset, offset);
74
    QPoint p;
75
 
60 Werner 76
    float local_dom; // height of Z* on the current position
40 Werner 77
    int x,y;
58 Werner 78
    float value;
51 Werner 79
    QPoint dbg(10,20);
70 Werner 80
    #ifndef NOFULLDBG
81
    qDebug() <<"indexstampx indexstamy gridx gridy local_dom_height stampvalue 1-value*la/dom grid_before gridvalue_after";
82
    #endif
40 Werner 83
    for (x=0;x<m_stamp->size();++x) {
84
        for (y=0;y<m_stamp->size(); ++y) {
85
           p = pos + QPoint(x,y);
51 Werner 86
           // debug pixel
61 Werner 87
           //if (p==dbg)
88
           //    qDebug() << "#" << id() << "value;"<<(*m_stamp)(x,y)<<"domH"<<dom;
51 Werner 89
 
90
           if (m_grid->isIndexValid(p)) {
91
               // mulitplicative:
53 Werner 92
               //m_grid->valueAtIndex(p)*=(*m_stamp)(x,y);
51 Werner 93
               // additiv:
58 Werner 94
               // multiplicatie, v2
75 Werner 95
               // a optimization for 2m vs 10m grids: local_dom = m_dominanceGrid->valueAtIndex(p.x()/5, p.y()/5);
96
               // effect is about 20% of the application time
60 Werner 97
               local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) ); // todo: could be done more effectively (here 2 transormations are performed)...
98
               if (local_dom<=0.f) {
61 Werner 99
                   //qDebug() << "invalid height at " << m_grid->cellCoordinates(p) << "of" << local_dom;
100
                   local_dom = 2.;
60 Werner 101
               }
102
               value = (*m_stamp)(x,y);
103
               value = 1. - value*lafactor / local_dom;
59 Werner 104
               value = qMax(value, 0.02f);
70 Werner 105
#ifndef NOFULLDBG
106
                qDebug() << x << y << p.x() << p.y() << local_dom << (*m_stamp)(x,y) << 1. - (*m_stamp)(x,y)*lafactor / local_dom  << m_grid->valueAtIndex(p) << m_grid->valueAtIndex(p)*value;
107
#endif
108
 
58 Werner 109
               m_grid->valueAtIndex(p)*= value;
51 Werner 110
           }
40 Werner 111
        }
112
    }
58 Werner 113
 
114
    m_statPrint++; // count # of stamp applications...
115
}
77 Werner 116
*/
58 Werner 117
 
77 Werner 118
void Tree::applyStamp()
119
{
106 Werner 120
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
77 Werner 121
 
106 Werner 122
    QPoint pos = mGrid->indexAt(mPosition);
123
    int offset = mStamp->offset();
77 Werner 124
    pos-=QPoint(offset, offset);
125
 
126
    float local_dom; // height of Z* on the current position
127
    int x,y;
128
    float value;
106 Werner 129
    int gr_stamp = mStamp->size();
77 Werner 130
    int grid_x, grid_y;
131
    float *grid_value;
106 Werner 132
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
77 Werner 133
        // todo: in this case we should use another algorithm!!!
134
        return;
135
    }
136
 
137
    for (y=0;y<gr_stamp; ++y) {
138
        grid_y = pos.y() + y;
106 Werner 139
        grid_value = mGrid->ptr(pos.x(), grid_y);
77 Werner 140
        for (x=0;x<gr_stamp;++x) {
141
            // suppose there is no stamping outside
142
            grid_x = pos.x() + x;
143
 
106 Werner 144
            local_dom = mHeightGrid->valueAtIndex(grid_x/5, grid_y/5);
145
            value = (*mStamp)(x,y); // stampvalue
77 Werner 146
            value = 1. - value*lafactor / local_dom; // calculated value
147
            value = qMax(value, 0.02f); // limit value
148
 
149
            *grid_value++ *= value;
150
        }
151
    }
152
 
153
    m_statPrint++; // count # of stamp applications...
154
}
155
 
74 Werner 156
 /*
157
void Tree::heightGrid_old()
58 Werner 158
{
59 Werner 159
    // height of Z*
74 Werner 160
    const float cellsize = m_dominanceGrid->cellsize();
62 Werner 161
    if (m_Height < cellsize/2.) {
59 Werner 162
        float &dom = m_dominanceGrid->valueAt(m_Position); // modifyable reference
163
        dom = qMax(dom, m_Height/2.f);
164
    } else {
165
        QPoint p = m_dominanceGrid->indexAt(m_Position); // pos of tree on height grid
62 Werner 166
 
167
        int ringcount = int(floor(m_Height / cellsize));
59 Werner 168
        int ix, iy;
169
        int ring;
170
        QPoint pos;
62 Werner 171
        float hdom;
172
        float h_out = fmod(m_Height, cellsize) / 2.;
59 Werner 173
        for (ix=-ringcount;ix<=ringcount;ix++)
174
            for (iy=-ringcount; iy<=+ringcount; iy++) {
175
            ring = qMax(abs(ix), abs(iy));
176
            QPoint pos(ix+p.x(), iy+p.y());
177
            if (m_dominanceGrid->isIndexValid(pos)) {
178
                // apply value....
62 Werner 179
                if (ring==0) {
180
                    hdom = m_Height- cellsize/4.;
181
                } else if (ring==abs(ringcount)) {
182
                    // outermost ring: use again height/2.
183
                    hdom = h_out;
184
                } else {
185
                    hdom = m_Height- ring*cellsize;
186
                }
187
                m_dominanceGrid->valueAtIndex(pos) = qMax(m_dominanceGrid->valueAtIndex(pos), hdom);
59 Werner 188
            }
45 Werner 189
 
59 Werner 190
        }
191
    }
192
 
74 Werner 193
} */
194
 
195
/** heightGrid()
196
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
197
 
198
*/
199
void Tree::heightGrid()
200
{
201
    // height of Z*
106 Werner 202
    const float cellsize = mHeightGrid->cellsize();
74 Werner 203
 
106 Werner 204
    QPoint p = mHeightGrid->indexAt(mPosition); // pos of tree on height grid
205
    QPoint competition_grid = mGrid->indexAt(mPosition);
74 Werner 206
 
207
    int index_eastwest = competition_grid.x() % 5; // 4: very west, 0 east edge
208
    int index_northsouth = competition_grid.y() % 5; // 4: northern edge, 0: southern edge
209
    int dist[9];
210
    dist[3] = index_northsouth * 2 + 1; // south
211
    dist[1] = index_eastwest * 2 + 1; // west
212
    dist[5] = 10 - dist[3]; // north
213
    dist[7] = 10 - dist[1]; // east
214
    dist[8] = qMax(dist[5], dist[7]); // north-east
215
    dist[6] = qMax(dist[3], dist[7]); // south-east
216
    dist[0] = qMax(dist[3], dist[1]); // south-west
217
    dist[2] = qMax(dist[5], dist[1]); // north-west
75 Werner 218
    dist[4] = 0; // center cell
76 Werner 219
    /* 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:
220
       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
221
        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
222
    */
74 Werner 223
 
224
 
106 Werner 225
    int ringcount = int(floor(mHeight / cellsize)) + 1;
74 Werner 226
    int ix, iy;
227
    int ring;
228
    QPoint pos;
229
    float hdom;
230
 
231
    for (ix=-ringcount;ix<=ringcount;ix++)
232
        for (iy=-ringcount; iy<=+ringcount; iy++) {
233
        ring = qMax(abs(ix), abs(iy));
234
        QPoint pos(ix+p.x(), iy+p.y());
106 Werner 235
        if (mHeightGrid->isIndexValid(pos)) {
236
            float &rHGrid = mHeightGrid->valueAtIndex(pos);
237
            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
74 Werner 238
                continue;
239
            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
106 Werner 240
            hdom = mHeight - dist[direction];
74 Werner 241
            if (ring>1)
242
                hdom -= (ring-1)*10;
243
 
244
            rHGrid = qMax(rHGrid, hdom); // write value
245
        } // is valid
246
    } // for (y)
39 Werner 247
}
40 Werner 248
 
249
double Tree::readStamp()
250
{
106 Werner 251
    if (!mStamp)
51 Werner 252
        return 0.;
106 Werner 253
    const Stamp *stamp = mStamp->reader();
40 Werner 254
    if (!stamp)
255
        return 0.;
106 Werner 256
    QPoint pos = mGrid->indexAt(mPosition);
40 Werner 257
    int offset = stamp->offset();
258
    pos-=QPoint(offset, offset);
259
    QPoint p;
260
 
261
    int x,y;
262
    double sum=0.;
263
    for (x=0;x<stamp->size();++x) {
264
        for (y=0;y<stamp->size(); ++y) {
265
           p = pos + QPoint(x,y);
106 Werner 266
           if (mGrid->isIndexValid(p))
267
               sum += mGrid->valueAtIndex(p) * (*stamp)(x,y);
40 Werner 268
        }
269
    }
106 Werner 270
    float eigenvalue = mStamp->readSum() * lafactor;
271
    mLRI = sum - eigenvalue;// additive
272
    float dom_height = (*mHeightGrid)[mPosition];
53 Werner 273
    if (dom_height>0.)
106 Werner 274
       mLRI = mLRI / dom_height;
53 Werner 275
 
276
    //mImpact = sum + eigenvalue;// multiplicative
48 Werner 277
    // read dominance field...
53 Werner 278
 
106 Werner 279
    if (dom_height < mHeight) {
48 Werner 280
        // if tree is higher than Z*, the dominance height
281
        // a part of the crown is in "full light".
282
        // total value = zstar/treeheight*value + 1-zstar/height
283
        // reformulated to:
106 Werner 284
        mLRI =  mLRI * dom_height/mHeight ;
48 Werner 285
        m_statAboveZ++;
286
    }
106 Werner 287
    if (fabs(mLRI < 0.000001))
288
        mLRI = 0.f;
289
    qDebug() << "Tree #"<< id() << "value" << sum << "eigenvalue" << eigenvalue << "Impact" << mLRI;
290
    return mLRI;
40 Werner 291
}
292
 
78 Werner 293
/*
58 Werner 294
double Tree::readStampMul()
295
{
296
    if (!m_stamp)
297
        return 0.;
298
    const Stamp *reader = m_stamp->reader();
299
    if (!reader)
300
        return 0.;
301
    QPoint pos_reader = m_grid->indexAt(m_Position);
302
 
303
    int offset_reader = reader->offset();
304
    int offset_writer = m_stamp->offset();
305
    int d_offset = offset_writer - offset_reader;
306
 
307
    QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer);
308
    pos_reader-=QPoint(offset_reader, offset_reader);
309
    QPoint p;
310
 
311
    float dom_height = (*m_dominanceGrid)[m_Position];
70 Werner 312
    float local_dom;
58 Werner 313
 
314
    int x,y;
315
    double sum=0.;
316
    double value, own_value;
317
    for (x=0;x<reader->size();++x) {
318
        for (y=0;y<reader->size(); ++y) {
319
            p = pos_reader + QPoint(x,y);
320
            if (m_grid->isIndexValid(p)) {
70 Werner 321
                local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
322
                own_value = 1. - m_stamp->offsetValue(x,y,d_offset)*lafactor /local_dom; // old: dom_height;
59 Werner 323
                own_value = qMax(own_value, 0.02);
58 Werner 324
                value =  m_grid->valueAtIndex(p) / own_value; // remove impact of focal tree
325
                if (value>0.)
326
                    sum += value * (*reader)(x,y);
327
                //value = (1. - m_stamp->offsetValue(x,y,d_offset)/dom_height);
328
                //value = (1 - m_grid->valueAtIndex(p)) / (m_stamp->offsetValue(x,y,d_offset)/dom_height * lafactor);
329
                if (isDebugging()) {
330
                    qDebug() << "Tree" << id() << "Coord" << p << "gridvalue" << m_grid->valueAtIndex(p) << "value" << value << "reader" << (*reader)(x,y) << "writer" << m_stamp->offsetValue(x,y,d_offset);
331
                }
332
 
333
            }
334
        }
335
    }
336
    mImpact = sum;
337
    // read dominance field...
338
    if (dom_height < m_Height) {
339
        // if tree is higher than Z*, the dominance height
340
        // a part of the crown is in "full light".
341
        m_statAboveZ++;
342
        mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
343
    }
344
    if (mImpact > 1)
345
        mImpact = 1.f;
61 Werner 346
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
58 Werner 347
    return mImpact;
78 Werner 348
}*/
349
 
107 Werner 350
void Tree::readStampMul()
78 Werner 351
{
106 Werner 352
    if (!mStamp)
107 Werner 353
        return;
106 Werner 354
    const Stamp *reader = mStamp->reader();
78 Werner 355
    if (!reader)
107 Werner 356
        return;
106 Werner 357
    QPoint pos_reader = mGrid->indexAt(mPosition);
78 Werner 358
 
359
    int offset_reader = reader->offset();
106 Werner 360
    int offset_writer = mStamp->offset();
78 Werner 361
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
362
 
363
    QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer);
364
    pos_reader-=QPoint(offset_reader, offset_reader);
365
    QPoint p;
366
 
367
    //float dom_height = (*m_dominanceGrid)[m_Position];
368
    float local_dom;
369
 
370
    int x,y;
371
    double sum=0.;
372
    double value, own_value;
373
    float *grid_value;
374
    int reader_size = reader->size();
375
    int rx = pos_reader.x();
376
    int ry = pos_reader.y();
377
    for (y=0;y<reader_size; ++y, ++ry) {
106 Werner 378
        grid_value = mGrid->ptr(rx, ry);
78 Werner 379
        for (x=0;x<reader_size;++x) {
380
 
381
            //p = pos_reader + QPoint(x,y);
382
            //if (m_grid->isIndexValid(p)) {
106 Werner 383
            local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5);
78 Werner 384
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
106 Werner 385
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*lafactor /local_dom; // old: dom_height;
78 Werner 386
            own_value = qMax(own_value, 0.02);
387
            value =  *grid_value++ / own_value; // remove impact of focal tree
388
            //if (value>0.)
389
            sum += value * (*reader)(x,y);
390
 
391
            //} // isIndexValid
392
        }
393
    }
106 Werner 394
    mLRI = sum;
78 Werner 395
    // read dominance field...
396
    // this applies only if some trees are potentially *higher* than the dominant height grid
397
    //if (dom_height < m_Height) {
398
        // if tree is higher than Z*, the dominance height
399
        // a part of the crown is in "full light".
400
    //    m_statAboveZ++;
401
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
402
    //}
106 Werner 403
    if (mLRI > 1)
404
        mLRI = 1.f;
78 Werner 405
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
110 Werner 406
    mRU->addWLA(mLRI * mLeafArea, mLeafArea);
58 Werner 407
}
408
 
40 Werner 409
void Tree::resetStatistics()
410
{
411
    m_statPrint=0;
105 Werner 412
    m_statCreated=0;
48 Werner 413
    m_statAboveZ=0;
40 Werner 414
    m_nextId=1;
415
}
107 Werner 416
 
110 Werner 417
//////////////////////////////////////////////////
418
////  Growth Functions
419
//////////////////////////////////////////////////
107 Werner 420
 
110 Werner 421
/// evaluate allometries and calculate LeafArea
422
void Tree::calcBiomassCompartments()
423
{
424
    mLeafMass = mSpecies->biomassLeaf(mDbh);
425
    mRootMass = mSpecies->biomassRoot(mDbh);
426
    mStemMass = mSpecies->biomassStem(mDbh);
112 Werner 427
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
110 Werner 428
    mLeafArea = mLeafMass * mSpecies->specificLeafArea();
429
}
430
 
431
 
107 Werner 432
void Tree::grow()
433
{
113 Werner 434
    // step 1: get radiation from ressource unit: radiaton (MJ/tree/year) total intercepted radiation for this tree per year!
107 Werner 435
    double radiation = mRU->interceptedRadiation(mLRI * mLeafArea);
113 Werner 436
    // step 2: get fraction of PARutilized, i.e. fraction of intercepted rad that is utiliziable (per year)
107 Werner 437
 
115 Werner 438
    double raw_gpp_per_rad = mRU->ressourceUnitSpecies(mSpecies).prod3PG().GPPperRad();
113 Werner 439
    // GPP (without aging-effect) [gC] / year
440
    double raw_gpp = raw_gpp_per_rad * radiation;
441
    /*
442
    if (mRU->index()==3) {
443
        qDebug() << "tree production: radiation: " << radiation << "gpp/rad:" << raw_gpp_per_rad << "gpp" << raw_gpp << "LRI:" << mLRI << "LeafArea:" << mLeafArea;
444
    }*/
115 Werner 445
    // apply aging
446
    double gpp = raw_gpp * 0.6; // aging
447
    double npp = gpp * 0.47; // respiration loss
113 Werner 448
 
115 Werner 449
    partitioning(npp);
450
 
110 Werner 451
    calcBiomassCompartments();
452
 
107 Werner 453
}
454
 
117 Werner 455
 
456
// just used to test the DBG_IF_x macros...
457
QString test_cntr()
458
{
459
    static int cnt = 0;
460
    cnt++;
461
    return QString::number(cnt);
462
}
463
 
118 Werner 464
#if !defined(DBGMODE)
465
#  ifndef QT_NO_DEBUG
466
#    define DBGMODE(stmts) { stmts }
467
#  else
468
#    define DBGMODE(stmts) qt_noop()
469
#  endif
470
#endif
471
 
115 Werner 472
void Tree::partitioning(double npp)
473
{
119 Werner 474
    DBGMODE(
475
        if (mId==1)
476
            test_cntr();
477
    );
115 Werner 478
    double harshness = mRU->ressourceUnitSpecies(mSpecies).prod3PG().harshness();
479
    // add content of reserve pool
116 Werner 480
    npp += mNPPReserve;
119 Werner 481
    const double reserve_size = mLeafMass;
482
 
117 Werner 483
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1)
484
    // turnover rates
485
    const double &to_fol = mSpecies->turnoverLeaf();
486
    const double &to_wood = mSpecies->turnoverStem();
487
    const double &to_root = mSpecies->turnoverRoot();
116 Werner 488
 
117 Werner 489
    apct_root = harshness;
490
 
491
    double b_wf = 1.32; // ratio of allometric exponents... now fixed
492
 
493
    // Duursma 2007, Eq. (20)
494
    apct_wood = (mLeafMass * to_wood / npp + b_wf*(1.-apct_root) - b_wf * to_fol/npp) / ( mStemMass / mStemMass + b_wf );
495
    apct_foliage = 1. - apct_root - apct_wood;
496
 
119 Werner 497
    // senescence demands of the compartemnts
498
    double senescence_foliage = mLeafMass * to_fol;
499
    double senescence_stem = mStemMass * to_wood;
500
    double senescence_root = mRootMass * to_root;
501
 
502
    // brutto compartment increments
503
    double gross_foliage = npp * apct_foliage;
504
    double gross_stem = npp * apct_wood;
505
    double gross_root = npp * apct_root;
506
 
507
    // netto increments
508
    double net_foliage = gross_foliage - senescence_foliage;
509
    double net_root = gross_root - senescence_root;
510
    double net_stem = gross_stem - senescence_stem;
511
 
512
    // flow back to reserve pool:
513
    double to_reserve = qMin(reserve_size, net_stem);
514
    net_stem -= to_reserve;
515
 
516
    // update of compartments
517
    mLeafMass += net_foliage;
518
    mRootMass += net_root;
519
 
520
    // calculate the dimension of growth
521
    grow_diameter(net_stem);
522
 
117 Werner 523
    //DBG_IF(apct_foliage<0, "Tree::partitioning", "foliage out of range");
121 Werner 524
    //DBG_IF_X(mId==1, "Tree::partitioning", "foliage out of range", test_cntr());
525
 
115 Werner 526
}
527
 
119 Werner 528
inline void Tree::grow_diameter(const double &net_stem_npp)
529
{
530
    // determine dh-ratio of increment
531
    // height increment is a function of light competition:
532
    double rel_height_growth = relative_height_growth(); // [0..1]
115 Werner 533
 
123 Werner 534
 
119 Werner 535
}
536
 
537
inline double Tree::relative_height_growth()
538
{
539
    double hd_low, hd_high;
540
    mSpecies->hdRange(mDbh, hd_low, hd_high);
541
 
542
    // scale according to LRI: if having much light (LRI=1), the result is hd_low (for open grown trees)
543
    double result = hd_high - (hd_high-hd_low)*mLRI;
544
    return result;
545
}