Subversion Repositories public iLand

Rev

Rev 142 | Rev 144 | 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.;
40 Werner 19
 
110 Werner 20
// lifecycle
3 Werner 21
Tree::Tree()
22
{
106 Werner 23
    mDbh = 0;
24
    mHeight = 0;
25
    mSpecies = 0;
107 Werner 26
    mRU = 0;
137 Werner 27
    mDebugging = false;
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
 
125 Werner 44
/// dumps some core variables of a tree to a string.
45
QString Tree::dump()
46
{
47
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
48
                            .arg(mId).arg(mSpecies->id()).arg(mDbh).arg(mHeight)
49
                            .arg(mPosition.x()).arg(mPosition.y())
50
                            .arg(mRU->index()).arg(mLRI);
51
    return result;
52
}
3 Werner 53
 
129 Werner 54
void Tree::dumpList(DebugList &rTargetList)
55
{
56
    rTargetList << mId << mSpecies->id() << mDbh << mHeight  << mPosition.x() << mPosition.y()   << mRU->index() << mLRI
136 Werner 57
                << mWoodyMass << mRootMass << mFoliageMass << mLeafArea;
129 Werner 58
}
59
 
38 Werner 60
void Tree::setup()
61
{
106 Werner 62
    if (mDbh<=0 || mHeight<=0)
38 Werner 63
        return;
64
    // check stamp
137 Werner 65
    Q_ASSERT_X(mSpecies!=0, "Tree::setup()", "species is NULL");
66
    mStamp = mSpecies->stamp(mDbh, mHeight);
110 Werner 67
 
137 Werner 68
    mFoliageMass = mSpecies->biomassFoliage(mDbh);
69
    mRootMass = mSpecies->biomassRoot(mDbh) + mFoliageMass; // coarse root (allometry) + fine root (estimated size: foliage)
70
    mWoodyMass = mSpecies->biomassWoody(mDbh);
110 Werner 71
 
137 Werner 72
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
73
    mLeafArea = mFoliageMass * mSpecies->specificLeafArea();
74
    mNPPReserve = 2*mFoliageMass; // initial value
75
    mDbhDelta = 0.1; // initial value: used in growth() to estimate diameter increment
38 Werner 76
}
39 Werner 77
 
110 Werner 78
//////////////////////////////////////////////////
79
////  Light functions (Pattern-stuff)
80
//////////////////////////////////////////////////
81
 
70 Werner 82
#define NOFULLDBG
77 Werner 83
//#define NOFULLOPT
39 Werner 84
 
40 Werner 85
 
77 Werner 86
void Tree::applyStamp()
87
{
106 Werner 88
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
77 Werner 89
 
106 Werner 90
    QPoint pos = mGrid->indexAt(mPosition);
91
    int offset = mStamp->offset();
77 Werner 92
    pos-=QPoint(offset, offset);
93
 
94
    float local_dom; // height of Z* on the current position
95
    int x,y;
96
    float value;
106 Werner 97
    int gr_stamp = mStamp->size();
77 Werner 98
    int grid_x, grid_y;
99
    float *grid_value;
106 Werner 100
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
77 Werner 101
        // todo: in this case we should use another algorithm!!!
102
        return;
103
    }
104
 
105
    for (y=0;y<gr_stamp; ++y) {
106
        grid_y = pos.y() + y;
106 Werner 107
        grid_value = mGrid->ptr(pos.x(), grid_y);
77 Werner 108
        for (x=0;x<gr_stamp;++x) {
109
            // suppose there is no stamping outside
110
            grid_x = pos.x() + x;
111
 
106 Werner 112
            local_dom = mHeightGrid->valueAtIndex(grid_x/5, grid_y/5);
113
            value = (*mStamp)(x,y); // stampvalue
77 Werner 114
            value = 1. - value*lafactor / local_dom; // calculated value
115
            value = qMax(value, 0.02f); // limit value
116
 
117
            *grid_value++ *= value;
118
        }
119
    }
120
 
121
    m_statPrint++; // count # of stamp applications...
122
}
123
 
62 Werner 124
 
74 Werner 125
/** heightGrid()
126
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
127
 
128
*/
129
void Tree::heightGrid()
130
{
131
    // height of Z*
106 Werner 132
    const float cellsize = mHeightGrid->cellsize();
74 Werner 133
 
106 Werner 134
    QPoint p = mHeightGrid->indexAt(mPosition); // pos of tree on height grid
135
    QPoint competition_grid = mGrid->indexAt(mPosition);
74 Werner 136
 
137
    int index_eastwest = competition_grid.x() % 5; // 4: very west, 0 east edge
138
    int index_northsouth = competition_grid.y() % 5; // 4: northern edge, 0: southern edge
139
    int dist[9];
140
    dist[3] = index_northsouth * 2 + 1; // south
141
    dist[1] = index_eastwest * 2 + 1; // west
142
    dist[5] = 10 - dist[3]; // north
143
    dist[7] = 10 - dist[1]; // east
144
    dist[8] = qMax(dist[5], dist[7]); // north-east
145
    dist[6] = qMax(dist[3], dist[7]); // south-east
146
    dist[0] = qMax(dist[3], dist[1]); // south-west
147
    dist[2] = qMax(dist[5], dist[1]); // north-west
75 Werner 148
    dist[4] = 0; // center cell
76 Werner 149
    /* 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:
150
       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
151
        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
152
    */
74 Werner 153
 
154
 
106 Werner 155
    int ringcount = int(floor(mHeight / cellsize)) + 1;
74 Werner 156
    int ix, iy;
157
    int ring;
158
    QPoint pos;
159
    float hdom;
160
 
161
    for (ix=-ringcount;ix<=ringcount;ix++)
162
        for (iy=-ringcount; iy<=+ringcount; iy++) {
163
        ring = qMax(abs(ix), abs(iy));
164
        QPoint pos(ix+p.x(), iy+p.y());
106 Werner 165
        if (mHeightGrid->isIndexValid(pos)) {
166
            float &rHGrid = mHeightGrid->valueAtIndex(pos);
167
            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
74 Werner 168
                continue;
169
            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
106 Werner 170
            hdom = mHeight - dist[direction];
74 Werner 171
            if (ring>1)
172
                hdom -= (ring-1)*10;
173
 
174
            rHGrid = qMax(rHGrid, hdom); // write value
175
        } // is valid
176
    } // for (y)
39 Werner 177
}
40 Werner 178
 
179
double Tree::readStamp()
180
{
106 Werner 181
    if (!mStamp)
51 Werner 182
        return 0.;
106 Werner 183
    const Stamp *stamp = mStamp->reader();
40 Werner 184
    if (!stamp)
185
        return 0.;
106 Werner 186
    QPoint pos = mGrid->indexAt(mPosition);
40 Werner 187
    int offset = stamp->offset();
188
    pos-=QPoint(offset, offset);
189
    QPoint p;
190
 
191
    int x,y;
192
    double sum=0.;
193
    for (x=0;x<stamp->size();++x) {
194
        for (y=0;y<stamp->size(); ++y) {
195
           p = pos + QPoint(x,y);
106 Werner 196
           if (mGrid->isIndexValid(p))
197
               sum += mGrid->valueAtIndex(p) * (*stamp)(x,y);
40 Werner 198
        }
199
    }
106 Werner 200
    float eigenvalue = mStamp->readSum() * lafactor;
201
    mLRI = sum - eigenvalue;// additive
202
    float dom_height = (*mHeightGrid)[mPosition];
53 Werner 203
    if (dom_height>0.)
106 Werner 204
       mLRI = mLRI / dom_height;
53 Werner 205
 
206
    //mImpact = sum + eigenvalue;// multiplicative
48 Werner 207
    // read dominance field...
53 Werner 208
 
106 Werner 209
    if (dom_height < mHeight) {
48 Werner 210
        // if tree is higher than Z*, the dominance height
211
        // a part of the crown is in "full light".
212
        // total value = zstar/treeheight*value + 1-zstar/height
213
        // reformulated to:
106 Werner 214
        mLRI =  mLRI * dom_height/mHeight ;
48 Werner 215
        m_statAboveZ++;
216
    }
106 Werner 217
    if (fabs(mLRI < 0.000001))
218
        mLRI = 0.f;
219
    qDebug() << "Tree #"<< id() << "value" << sum << "eigenvalue" << eigenvalue << "Impact" << mLRI;
220
    return mLRI;
40 Werner 221
}
222
 
58 Werner 223
 
107 Werner 224
void Tree::readStampMul()
78 Werner 225
{
106 Werner 226
    if (!mStamp)
107 Werner 227
        return;
106 Werner 228
    const Stamp *reader = mStamp->reader();
78 Werner 229
    if (!reader)
107 Werner 230
        return;
106 Werner 231
    QPoint pos_reader = mGrid->indexAt(mPosition);
78 Werner 232
 
233
    int offset_reader = reader->offset();
106 Werner 234
    int offset_writer = mStamp->offset();
78 Werner 235
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
236
 
237
    QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer);
238
    pos_reader-=QPoint(offset_reader, offset_reader);
239
    QPoint p;
240
 
241
    //float dom_height = (*m_dominanceGrid)[m_Position];
242
    float local_dom;
243
 
244
    int x,y;
245
    double sum=0.;
246
    double value, own_value;
247
    float *grid_value;
248
    int reader_size = reader->size();
249
    int rx = pos_reader.x();
250
    int ry = pos_reader.y();
251
    for (y=0;y<reader_size; ++y, ++ry) {
106 Werner 252
        grid_value = mGrid->ptr(rx, ry);
78 Werner 253
        for (x=0;x<reader_size;++x) {
254
 
255
            //p = pos_reader + QPoint(x,y);
256
            //if (m_grid->isIndexValid(p)) {
125 Werner 257
            local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5); // ry: gets ++ed in outer loop, rx not
78 Werner 258
            //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
125 Werner 259
 
260
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*lafactor / local_dom; // old: dom_height;
78 Werner 261
            own_value = qMax(own_value, 0.02);
262
            value =  *grid_value++ / own_value; // remove impact of focal tree
263
            //if (value>0.)
264
            sum += value * (*reader)(x,y);
265
 
266
            //} // isIndexValid
267
        }
268
    }
106 Werner 269
    mLRI = sum;
78 Werner 270
    // read dominance field...
271
    // this applies only if some trees are potentially *higher* than the dominant height grid
272
    //if (dom_height < m_Height) {
273
        // if tree is higher than Z*, the dominance height
274
        // a part of the crown is in "full light".
275
    //    m_statAboveZ++;
276
    //    mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
277
    //}
106 Werner 278
    if (mLRI > 1)
279
        mLRI = 1.f;
78 Werner 280
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
137 Werner 281
    mRU->addWLA((mLRI+1) * mLeafArea, mLeafArea);
58 Werner 282
}
283
 
40 Werner 284
void Tree::resetStatistics()
285
{
286
    m_statPrint=0;
105 Werner 287
    m_statCreated=0;
48 Werner 288
    m_statAboveZ=0;
40 Werner 289
    m_nextId=1;
290
}
107 Werner 291
 
110 Werner 292
//////////////////////////////////////////////////
293
////  Growth Functions
294
//////////////////////////////////////////////////
107 Werner 295
 
110 Werner 296
 
107 Werner 297
void Tree::grow()
298
{
113 Werner 299
    // step 1: get radiation from ressource unit: radiaton (MJ/tree/year) total intercepted radiation for this tree per year!
137 Werner 300
    double radiation = mRU->interceptedRadiation( (mLRI + 1) * mLeafArea);
113 Werner 301
    // step 2: get fraction of PARutilized, i.e. fraction of intercepted rad that is utiliziable (per year)
107 Werner 302
 
115 Werner 303
    double raw_gpp_per_rad = mRU->ressourceUnitSpecies(mSpecies).prod3PG().GPPperRad();
133 Werner 304
    // GPP (without aging-effect) [gC] / year -> kg/GPP (*0.001)
305
    double raw_gpp = raw_gpp_per_rad * radiation * 0.001;
113 Werner 306
    /*
307
    if (mRU->index()==3) {
308
        qDebug() << "tree production: radiation: " << radiation << "gpp/rad:" << raw_gpp_per_rad << "gpp" << raw_gpp << "LRI:" << mLRI << "LeafArea:" << mLeafArea;
309
    }*/
115 Werner 310
    // apply aging
311
    double gpp = raw_gpp * 0.6; // aging
312
    double npp = gpp * 0.47; // respiration loss
113 Werner 313
 
133 Werner 314
    DBGMODE(
137 Werner 315
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 316
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
317
            dumpList(out); // add tree headers
318
            out << radiation << raw_gpp << gpp << npp;
319
        }
320
    ); // DBGMODE()
321
 
115 Werner 322
    partitioning(npp);
323
 
143 Werner 324
    mStamp = mSpecies->stamp(mDbh, mHeight); // get new stamp for updated dimensions
110 Werner 325
 
107 Werner 326
}
327
 
117 Werner 328
 
329
// just used to test the DBG_IF_x macros...
330
QString test_cntr()
331
{
332
    static int cnt = 0;
333
    cnt++;
334
    return QString::number(cnt);
335
}
336
 
115 Werner 337
void Tree::partitioning(double npp)
338
{
119 Werner 339
    DBGMODE(
340
        if (mId==1)
341
            test_cntr();
342
    );
115 Werner 343
    double harshness = mRU->ressourceUnitSpecies(mSpecies).prod3PG().harshness();
344
    // add content of reserve pool
116 Werner 345
    npp += mNPPReserve;
136 Werner 346
    const double foliage_mass_allo = mSpecies->biomassFoliage(mDbh);
347
    const double reserve_size = 2 * foliage_mass_allo;
119 Werner 348
 
136 Werner 349
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
117 Werner 350
    // turnover rates
351
    const double &to_fol = mSpecies->turnoverLeaf();
352
    const double &to_root = mSpecies->turnoverRoot();
136 Werner 353
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 354
 
136 Werner 355
    double to_wood = reserve_size / (mWoodyMass + reserve_size);
356
 
117 Werner 357
    apct_root = harshness;
136 Werner 358
    double b_wf = mSpecies->allometricRatio_wf(); // ratio of allometric exponents... now fixed
117 Werner 359
 
360
    // Duursma 2007, Eq. (20)
136 Werner 361
    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 362
    apct_foliage = 1. - apct_root - apct_wood;
363
 
136 Werner 364
    // Change of biomass compartments
365
    // Roots
137 Werner 366
    double delta_root = apct_root * npp - mRootMass * to_root;
367
    mRootMass += delta_root;
119 Werner 368
 
136 Werner 369
    // Foliage
137 Werner 370
    double delta_foliage = apct_foliage * npp - mFoliageMass * to_fol;
371
    mFoliageMass += delta_foliage;
372
    mLeafArea = mFoliageMass * mSpecies->specificLeafArea(); // update leaf area
119 Werner 373
 
136 Werner 374
    // Woody compartments
375
    // (1) transfer to reserve pool
376
    double gross_woody = apct_wood * npp;
377
    double to_reserve = qMin(reserve_size, gross_woody);
378
    mNPPReserve = to_reserve;
379
    double net_woody = gross_woody - to_reserve;
137 Werner 380
    double net_stem = 0.;
136 Werner 381
    if (net_woody > 0.) {
382
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
137 Werner 383
        net_stem = net_woody * mSpecies->allometricFractionStem(mDbh);
384
        mWoodyMass += net_woody;
136 Werner 385
        //  (3) growth of diameter and height baseed on net stem increment
386
        grow_diameter(net_stem);
387
    }
119 Werner 388
 
129 Werner 389
    DBGMODE(
137 Werner 390
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
391
         && isDebugging() ) {
129 Werner 392
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
393
            dumpList(out); // add tree headers
136 Werner 394
            out << npp << apct_foliage << apct_wood << apct_root
137 Werner 395
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem;
396
     }
129 Werner 397
    ); // DBGMODE()
136 Werner 398
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
399
             + 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")
400
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
401
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 402
 
115 Werner 403
}
404
 
125 Werner 405
 
134 Werner 406
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 407
    Refer to XXX for equations and variables.
408
    This function updates the dbh and height of the tree.
409
  */
119 Werner 410
inline void Tree::grow_diameter(const double &net_stem_npp)
411
{
412
    // determine dh-ratio of increment
413
    // height increment is a function of light competition:
125 Werner 414
    double hd_growth = relative_height_growth(); // hd of height growth
115 Werner 415
 
134 Werner 416
    // Be careful with units!! this function calculates diameter increments in meter!
142 Werner 417
    const double mass_factor = mSpecies->volumeFactor() * mSpecies->density();
418
    double stem_mass = mass_factor * mDbh*mDbh * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 419
 
142 Werner 420
    double factor_diameter = 1. / (  mass_factor * (mDbh + mDbhDelta)*(mDbh + mDbhDelta) * ( 2. * mHeight/mDbh + hd_growth) );
125 Werner 421
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
422
 
423
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
142 Werner 424
    double stem_estimate = mass_factor * (mDbh + delta_d_estimate)*(mDbh + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 425
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 426
 
427
    // the final increment is then:
428
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
429
    DBG_IF_X(mId == 1 || d_increment<0., "Tree::grow_dimater", "increment < 0.", dump()
430
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
431
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 432
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 433
 
434
    DBGMODE(
142 Werner 435
        double res_final = mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp));
125 Werner 436
        DBG_IF_X(res_final > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump());
437
        DBG_IF_X(d_increment > 10. || d_increment*hd_growth/100. >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());
438
        //dbgstruct["sen_demand"]=sen_demand;
137 Werner 439
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 440
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 441
            dumpList(out); // add tree headers
143 Werner 442
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 443
        }
142 Werner 444
    ); // DBGMODE()
125 Werner 445
 
446
    d_increment = qMax(d_increment, 0.);
135 Werner 447
    d_increment *= 100; // from m to cm
125 Werner 448
 
449
    // update state variables
135 Werner 450
    mDbh += d_increment;
125 Werner 451
    mDbhDelta = d_increment; // save for next year's growth
138 Werner 452
    mHeight += d_increment * hd_growth / 100.; // d[cm]/100 * hd[m/m] = d[m] * hd[m/m] = h[m]
119 Werner 453
}
454
 
125 Werner 455
 
456
/// return the HD ratio of this year's increment based on the light status.
119 Werner 457
inline double Tree::relative_height_growth()
458
{
459
    double hd_low, hd_high;
460
    mSpecies->hdRange(mDbh, hd_low, hd_high);
461
 
125 Werner 462
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
463
    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));
464
 
465
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
466
    double hd_ratio = hd_high - (hd_high-hd_low)*mLRI;
467
    return hd_ratio;
119 Werner 468
}
141 Werner 469
 
470
 
471
//////////////////////////////////////////////////
472
////  value functions
473
//////////////////////////////////////////////////
474
 
475
const double Tree::volume() const
476
{
477
    /// @see Species::volumeFactor() for details
142 Werner 478
    const double volume_factor = mSpecies->volumeFactor();
479
    const double volume =  volume_factor * mDbh*mDbh*mHeight;
141 Werner 480
    return volume;
481
}