Subversion Repositories public iLand

Rev

Rev 1072 | Rev 1088 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1
 
671 werner 2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
7
**    This program is free software: you can redistribute it and/or modify
8
**    it under the terms of the GNU General Public License as published by
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
11
**
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
16
**
17
**    You should have received a copy of the GNU General Public License
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
117 Werner 20
#include "global.h"
21
#include "tree.h"
3 Werner 22
 
83 Werner 23
#include "grid.h"
3 Werner 24
 
83 Werner 25
#include "stamp.h"
90 Werner 26
#include "species.h"
189 iland 27
#include "resourceunit.h"
151 iland 28
#include "model.h"
468 werner 29
#include "snag.h"
38 Werner 30
 
904 werner 31
#include "forestmanagementengine.h"
1044 werner 32
#include "modules.h"
904 werner 33
 
1071 werner 34
 
35
 
110 Werner 36
// static varaibles
106 Werner 37
FloatGrid *Tree::mGrid = 0;
151 iland 38
HeightGrid *Tree::mHeightGrid = 0;
40 Werner 39
int Tree::m_statPrint=0;
48 Werner 40
int Tree::m_statAboveZ=0;
105 Werner 41
int Tree::m_statCreated=0;
40 Werner 42
int Tree::m_nextId=0;
43
 
1071 werner 44
#ifdef ALT_TREE_MORTALITY
45
double _stress_threshold = 0.05;
46
int _stress_years = 5;
47
double _stress_death_prob = 0.33;
48
#endif
49
 
697 werner 50
/** @class Tree
51
    @ingroup core
52
    A tree is the basic simulation entity of iLand and represents a single tree.
53
    Trees in iLand are designed to be lightweight, thus the list of stored properties is limited. Basic properties
54
    are dimensions (dbh, height), biomass pools (stem, leaves, roots), the reserve NPP pool. Additionally, the location and species are stored.
55
    A Tree has a height of at least 4m; trees below this threshold are covered by the regeneration layer (see Sapling).
56
    Trees are stored in lists managed at the resource unit level.
158 werner 57
 
697 werner 58
  */
257 werner 59
 
158 werner 60
/** get distance and direction between two points.
61
  returns the distance (m), and the angle between PStart and PEnd (radians) in referenced param rAngle. */
62
float dist_and_direction(const QPointF &PStart, const QPointF &PEnd, float &rAngle)
151 iland 63
{
158 werner 64
    float dx = PEnd.x() - PStart.x();
65
    float dy = PEnd.y() - PStart.y();
66
    float d = sqrt(dx*dx + dy*dy);
67
    // direction:
68
    rAngle = atan2(dx, dy);
69
    return d;
151 iland 70
}
71
 
158 werner 72
 
110 Werner 73
// lifecycle
3 Werner 74
Tree::Tree()
75
{
149 werner 76
    mDbh = mHeight = 0;
77
    mRU = 0; mSpecies = 0;
169 werner 78
    mFlags = mAge = 0;
276 werner 79
    mOpacity=mFoliageMass=mWoodyMass=mCoarseRootMass=mFineRootMass=mLeafArea=0.;
159 werner 80
    mDbhDelta=mNPPReserve=mLRI=mStressIndex=0.;
264 werner 81
    mLightResponse = 0.;
975 werner 82
    mStamp=0;
106 Werner 83
    mId = m_nextId++;
105 Werner 84
    m_statCreated++;
3 Werner 85
}
38 Werner 86
 
407 werner 87
float Tree::crownRadius() const
88
{
89
    Q_ASSERT(mStamp!=0);
90
    return mStamp->crownRadius();
91
}
92
 
476 werner 93
float Tree::biomassBranch() const
94
{
95
    return mSpecies->biomassBranch(mDbh);
96
}
97
 
158 werner 98
void Tree::setGrid(FloatGrid* gridToStamp, Grid<HeightGridValue> *dominanceGrid)
3 Werner 99
{
158 werner 100
    mGrid = gridToStamp; mHeightGrid = dominanceGrid;
3 Werner 101
}
102
 
667 werner 103
// calculate the thickness of the bark of the tree
104
double Tree::barkThickness() const
105
{
106
    return mSpecies->barkThickness(mDbh);
107
}
108
 
125 Werner 109
/// dumps some core variables of a tree to a string.
110
QString Tree::dump()
111
{
112
    QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8")
159 werner 113
                            .arg(mId).arg(species()->id()).arg(mDbh).arg(mHeight)
156 werner 114
                            .arg(position().x()).arg(position().y())
125 Werner 115
                            .arg(mRU->index()).arg(mLRI);
116
    return result;
117
}
3 Werner 118
 
129 Werner 119
void Tree::dumpList(DebugList &rTargetList)
120
{
159 werner 121
    rTargetList << mId << species()->id() << mDbh << mHeight  << position().x() << position().y()   << mRU->index() << mLRI
276 werner 122
                << mWoodyMass << mCoarseRootMass << mFoliageMass << mLeafArea;
129 Werner 123
}
124
 
38 Werner 125
void Tree::setup()
126
{
975 werner 127
    if (mDbh<=0 || mHeight<=0) {
128
        throw IException(QString("Error: trying to set up a tree with invalid dimensions: dbh: %1 height: %2 id: %3 RU-index: %4").arg(mDbh).arg(mHeight).arg(mId).arg(mRU->index()));
129
    }
38 Werner 130
    // check stamp
159 werner 131
    Q_ASSERT_X(species()!=0, "Tree::setup()", "species is NULL");
132
    mStamp = species()->stamp(mDbh, mHeight);
505 werner 133
    if (!mStamp) {
134
        throw IException("Tree::setup() with invalid stamp!");
135
    }
110 Werner 136
 
159 werner 137
    mFoliageMass = species()->biomassFoliage(mDbh);
276 werner 138
    mCoarseRootMass = species()->biomassRoot(mDbh); // coarse root (allometry)
139
    mFineRootMass = mFoliageMass * species()->finerootFoliageRatio(); //  fine root (size defined  by finerootFoliageRatio)
159 werner 140
    mWoodyMass = species()->biomassWoody(mDbh);
110 Werner 141
 
137 Werner 142
    // LeafArea[m2] = LeafMass[kg] * specificLeafArea[m2/kg]
159 werner 143
    mLeafArea = mFoliageMass * species()->specificLeafArea();
276 werner 144
    mOpacity = 1. - exp(- Model::settings().lightExtinctionCoefficientOpacity * mLeafArea / mStamp->crownArea());
145
    mNPPReserve = (1+species()->finerootFoliageRatio())*mFoliageMass; // initial value
780 werner 146
    mDbhDelta = 0.1f; // initial value: used in growth() to estimate diameter increment
376 werner 147
 
38 Werner 148
}
39 Werner 149
 
388 werner 150
void Tree::setAge(const int age, const float treeheight)
151
{
152
    mAge = age;
153
    if (age==0) {
154
        // estimate age using the tree height
155
        mAge = mSpecies->estimateAge(treeheight);
156
    }
157
}
158
 
110 Werner 159
//////////////////////////////////////////////////
160
////  Light functions (Pattern-stuff)
161
//////////////////////////////////////////////////
162
 
70 Werner 163
#define NOFULLDBG
77 Werner 164
//#define NOFULLOPT
39 Werner 165
 
40 Werner 166
 
158 werner 167
void Tree::applyLIP()
77 Werner 168
{
144 Werner 169
    if (!mStamp)
170
        return;
106 Werner 171
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
156 werner 172
    QPoint pos = mPositionIndex;
106 Werner 173
    int offset = mStamp->offset();
77 Werner 174
    pos-=QPoint(offset, offset);
175
 
176
    float local_dom; // height of Z* on the current position
177
    int x,y;
401 werner 178
    float value, z, z_zstar;
106 Werner 179
    int gr_stamp = mStamp->size();
705 werner 180
 
106 Werner 181
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
407 werner 182
        // this should not happen because of the buffer
77 Werner 183
        return;
184
    }
705 werner 185
    int grid_y = pos.y();
77 Werner 186
    for (y=0;y<gr_stamp; ++y) {
403 werner 187
 
705 werner 188
        float *grid_value_ptr = mGrid->ptr(pos.x(), grid_y);
189
        int grid_x = pos.x();
190
        for (x=0;x<gr_stamp;++x, ++grid_x, ++grid_value_ptr) {
77 Werner 191
            // suppose there is no stamping outside
106 Werner 192
            value = (*mStamp)(x,y); // stampvalue
705 werner 193
            //if (value>0.f) {
194
                local_dom = (*mHeightGrid)(grid_x/cPxPerHeight, grid_y/cPxPerHeight).height;
884 werner 195
                z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
705 werner 196
                z_zstar = (z>=local_dom)?1.f:z/local_dom;
197
                value = 1. - value*mOpacity * z_zstar; // calculated value
198
                value = std::max(value, 0.02f); // limit value
77 Werner 199
 
705 werner 200
                *grid_value_ptr *= value;
201
            //}
403 werner 202
 
77 Werner 203
        }
403 werner 204
        grid_y++;
77 Werner 205
    }
206
 
207
    m_statPrint++; // count # of stamp applications...
208
}
209
 
155 werner 210
/// helper function for gluing the edges together
211
/// index: index at grid
212
/// count: number of pixels that are the simulation area (e.g. 100m and 2m pixel -> 50)
213
/// buffer: size of buffer around simulation area (in pixels)
295 werner 214
inline int torusIndex(int index, int count, int buffer, int ru_index)
155 werner 215
{
295 werner 216
    return buffer + ru_index + (index-buffer+count)%count;
155 werner 217
}
62 Werner 218
 
155 werner 219
 
220
/** Apply LIPs. This "Torus" functions wraps the influence at the edges of a 1ha simulation area.
221
  */
158 werner 222
void Tree::applyLIP_torus()
155 werner 223
{
224
    if (!mStamp)
225
        return;
226
    Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0);
295 werner 227
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
387 werner 228
    QPoint pos = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU  + bufferOffset,
229
                        (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 230
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos.x(), mPositionIndex.y() - pos.y()); // offset of the corner of the resource index
155 werner 231
 
232
    int offset = mStamp->offset();
233
    pos-=QPoint(offset, offset);
234
 
235
    float local_dom; // height of Z* on the current position
236
    int x,y;
237
    float value;
238
    int gr_stamp = mStamp->size();
239
    int grid_x, grid_y;
240
    float *grid_value;
241
    if (!mGrid->isIndexValid(pos) || !mGrid->isIndexValid(pos+QPoint(gr_stamp, gr_stamp))) {
242
        // todo: in this case we should use another algorithm!!! necessary????
243
        return;
244
    }
407 werner 245
    float z, z_zstar;
155 werner 246
    int xt, yt; // wraparound coordinates
247
    for (y=0;y<gr_stamp; ++y) {
248
        grid_y = pos.y() + y;
387 werner 249
        yt = torusIndex(grid_y, cPxPerRU,bufferOffset, ru_offset.y()); // 50 cells per 100m
155 werner 250
        for (x=0;x<gr_stamp;++x) {
251
            // suppose there is no stamping outside
252
            grid_x = pos.x() + x;
387 werner 253
            xt = torusIndex(grid_x,cPxPerRU,bufferOffset, ru_offset.x());
155 werner 254
 
387 werner 255
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight,yt/cPxPerHeight).height;
407 werner 256
 
884 werner 257
            z = std::max(mHeight - (*mStamp).distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
407 werner 258
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 259
            value = (*mStamp)(x,y); // stampvalue
407 werner 260
            value = 1. - value*mOpacity * z_zstar; // calculated value
261
            // old: value = 1. - value*mOpacity / local_dom; // calculated value
155 werner 262
            value = qMax(value, 0.02f); // limit value
263
 
264
            grid_value = mGrid->ptr(xt, yt); // use wraparound coordinates
265
            *grid_value *= value;
266
        }
267
    }
268
 
269
    m_statPrint++; // count # of stamp applications...
270
}
271
 
74 Werner 272
/** heightGrid()
273
  This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
274
*/
275
void Tree::heightGrid()
276
{
277
 
387 werner 278
    QPoint p = QPoint(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight); // pos of tree on height grid
74 Werner 279
 
151 iland 280
    // count trees that are on height-grid cells (used for stockable area)
285 werner 281
    mHeightGrid->valueAtIndex(p).increaseCount();
401 werner 282
    if (mHeight > mHeightGrid->valueAtIndex(p).height)
283
        mHeightGrid->valueAtIndex(p).height=mHeight;
406 werner 284
 
285
    int r = mStamp->reader()->offset(); // distance between edge and the center pixel. e.g.: if r = 2 -> stamp=5x5
286
    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
287
    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
288
    if (index_eastwest - r < 0) { // east
410 werner 289
        mHeightGrid->valueAtIndex(p.x()-1, p.y()).height=qMax(mHeightGrid->valueAtIndex(p.x()-1, p.y()).height,mHeight);
406 werner 290
    }
291
    if (index_eastwest + r >= cPxPerHeight) {  // west
410 werner 292
        mHeightGrid->valueAtIndex(p.x()+1, p.y()).height=qMax(mHeightGrid->valueAtIndex(p.x()+1, p.y()).height,mHeight);
406 werner 293
    }
294
    if (index_northsouth - r < 0) {  // south
410 werner 295
        mHeightGrid->valueAtIndex(p.x(), p.y()-1).height=qMax(mHeightGrid->valueAtIndex(p.x(), p.y()-1).height,mHeight);
406 werner 296
    }
297
    if (index_northsouth + r >= cPxPerHeight) {  // north
410 werner 298
        mHeightGrid->valueAtIndex(p.x(), p.y()+1).height=qMax(mHeightGrid->valueAtIndex(p.x(), p.y()+1).height,mHeight);
406 werner 299
    }
300
 
301
 
401 werner 302
    // without spread of the height grid
151 iland 303
 
401 werner 304
//    // height of Z*
305
//    const float cellsize = mHeightGrid->cellsize();
306
//
307
//    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
308
//    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
309
//    int dist[9];
310
//    dist[3] = index_northsouth * 2 + 1; // south
311
//    dist[1] = index_eastwest * 2 + 1; // west
312
//    dist[5] = 10 - dist[3]; // north
313
//    dist[7] = 10 - dist[1]; // east
314
//    dist[8] = qMax(dist[5], dist[7]); // north-east
315
//    dist[6] = qMax(dist[3], dist[7]); // south-east
316
//    dist[0] = qMax(dist[3], dist[1]); // south-west
317
//    dist[2] = qMax(dist[5], dist[1]); // north-west
318
//    dist[4] = 0; // center cell
319
//    /* 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:
320
//       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
321
//        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
322
//    */
323
//
324
//
325
//    int ringcount = int(floor(mHeight / cellsize)) + 1;
326
//    int ix, iy;
327
//    int ring;
328
//    float hdom;
329
//
330
//    for (ix=-ringcount;ix<=ringcount;ix++)
331
//        for (iy=-ringcount; iy<=+ringcount; iy++) {
332
//        ring = qMax(abs(ix), abs(iy));
333
//        QPoint pos(ix+p.x(), iy+p.y());
334
//        if (mHeightGrid->isIndexValid(pos)) {
335
//            float &rHGrid = mHeightGrid->valueAtIndex(pos).height;
336
//            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
337
//                continue;
338
//            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
339
//            hdom = mHeight - dist[direction];
340
//            if (ring>1)
341
//                hdom -= (ring-1)*10;
342
//
343
//            rHGrid = qMax(rHGrid, hdom); // write value
344
//        } // is valid
345
//    } // for (y)
39 Werner 346
}
40 Werner 347
 
407 werner 348
void Tree::heightGrid_torus()
349
{
350
    // height of Z*
155 werner 351
 
407 werner 352
    QPoint p = QPoint(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight); // pos of tree on height grid
353
    int bufferOffset = mHeightGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer (i.e.: size of buffer in height-pixels)
354
    p.setX((p.x()-bufferOffset)%10 + bufferOffset); // 10: 10 x 10m pixeln in 100m
355
    p.setY((p.y()-bufferOffset)%10 + bufferOffset);
155 werner 356
 
407 werner 357
 
358
    // torus coordinates: ru_offset = coords of lower left corner of 1ha patch
359
    QPoint ru_offset =QPoint(mPositionIndex.x()/cPxPerHeight - p.x(), mPositionIndex.y()/cPxPerHeight - p.y());
360
 
361
    // count trees that are on height-grid cells (used for stockable area)
362
    HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x(),10,bufferOffset,ru_offset.x()),
363
                                                   torusIndex(p.y(),10,bufferOffset,ru_offset.y()));
364
    v.increaseCount();
365
    v.height = qMax(v.height, mHeight);
366
 
367
 
368
    int r = mStamp->reader()->offset(); // distance between edge and the center pixel. e.g.: if r = 2 -> stamp=5x5
369
    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
370
    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
371
    if (index_eastwest - r < 0) { // east
372
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x()-1,10,bufferOffset,ru_offset.x()),
373
                                                       torusIndex(p.y(),10,bufferOffset,ru_offset.y()));
410 werner 374
        v.height = qMax(v.height, mHeight);
407 werner 375
    }
376
    if (index_eastwest + r >= cPxPerHeight) {  // west
377
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x()+1,10,bufferOffset,ru_offset.x()),
378
                                                       torusIndex(p.y(),10,bufferOffset,ru_offset.y()));
410 werner 379
        v.height = qMax(v.height, mHeight);
407 werner 380
    }
381
    if (index_northsouth - r < 0) {  // south
382
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x(),10,bufferOffset,ru_offset.x()),
383
                                                       torusIndex(p.y()-1,10,bufferOffset,ru_offset.y()));
410 werner 384
        v.height = qMax(v.height, mHeight);
407 werner 385
    }
386
    if (index_northsouth + r >= cPxPerHeight) {  // north
387
        HeightGridValue &v = mHeightGrid->valueAtIndex(torusIndex(p.x(),10,bufferOffset,ru_offset.x()),
388
                                                       torusIndex(p.y()+1,10,bufferOffset,ru_offset.y()));
410 werner 389
        v.height = qMax(v.height, mHeight);
407 werner 390
    }
391
 
392
 
393
 
394
 
395
//    int index_eastwest = mPositionIndex.x() % cPxPerHeight; // 4: very west, 0 east edge
396
//    int index_northsouth = mPositionIndex.y() % cPxPerHeight; // 4: northern edge, 0: southern edge
397
//    int dist[9];
398
//    dist[3] = index_northsouth * 2 + 1; // south
399
//    dist[1] = index_eastwest * 2 + 1; // west
400
//    dist[5] = 10 - dist[3]; // north
401
//    dist[7] = 10 - dist[1]; // east
402
//    dist[8] = qMax(dist[5], dist[7]); // north-east
403
//    dist[6] = qMax(dist[3], dist[7]); // south-east
404
//    dist[0] = qMax(dist[3], dist[1]); // south-west
405
//    dist[2] = qMax(dist[5], dist[1]); // north-west
406
//    dist[4] = 0; // center cell
407
//    /* 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:
408
//       index = 4 + 3*sign(ix) + sign(iy) transforms combinations of directions to unique ids (0..8), which are used above.
409
//        e.g.: sign(ix) = -1, sign(iy) = 1 (=north-west) -> index = 4 + -3 + 1 = 2
410
//    */
411
//
412
//
413
//    int ringcount = int(floor(mHeight / cellsize)) + 1;
414
//    int ix, iy;
415
//    int ring;
416
//    float hdom;
417
//    for (ix=-ringcount;ix<=ringcount;ix++)
418
//        for (iy=-ringcount; iy<=+ringcount; iy++) {
419
//        ring = qMax(abs(ix), abs(iy));
420
//        QPoint pos(ix+p.x(), iy+p.y());
421
//        QPoint p_torus(torusIndex(pos.x(),10,bufferOffset,ru_offset.x()),
422
//                       torusIndex(pos.y(),10,bufferOffset,ru_offset.y()));
423
//        if (mHeightGrid->isIndexValid(p_torus)) {
424
//            float &rHGrid = mHeightGrid->valueAtIndex(p_torus.x(),p_torus.y()).height;
425
//            if (rHGrid > mHeight) // skip calculation if grid is higher than tree
426
//                continue;
427
//            int direction = 4 + (ix?(ix<0?-3:3):0) + (iy?(iy<0?-1:1):0); // 4 + 3*sgn(x) + sgn(y)
428
//            hdom = mHeight - dist[direction];
429
//            if (ring>1)
430
//                hdom -= (ring-1)*10;
431
//
432
//            rHGrid = qMax(rHGrid, hdom); // write value
433
//        } // is valid
434
//    } // for (y)
435
}
436
 
437
 
718 werner 438
/** reads the light influence field value for a tree.
439
    The LIF field is scanned within the crown area of the focal tree, and the influence of
440
    the focal tree is "subtracted" from the LIF values.
441
    Finally, the "LRI correction" is applied.
442
    see http://iland.boku.ac.at/competition+for+light for details.
443
  */
158 werner 444
void Tree::readLIF()
40 Werner 445
{
106 Werner 446
    if (!mStamp)
155 werner 447
        return;
448
    const Stamp *reader = mStamp->reader();
449
    if (!reader)
450
        return;
156 werner 451
    QPoint pos_reader = mPositionIndex;
780 werner 452
    const float outside_area_factor = 0.1f; //
155 werner 453
 
454
    int offset_reader = reader->offset();
455
    int offset_writer = mStamp->offset();
456
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
457
 
458
    pos_reader-=QPoint(offset_reader, offset_reader);
40 Werner 459
 
155 werner 460
    float local_dom;
461
 
40 Werner 462
    int x,y;
463
    double sum=0.;
155 werner 464
    double value, own_value;
465
    float *grid_value;
403 werner 466
    float z, z_zstar;
155 werner 467
    int reader_size = reader->size();
468
    int rx = pos_reader.x();
469
    int ry = pos_reader.y();
470
    for (y=0;y<reader_size; ++y, ++ry) {
471
        grid_value = mGrid->ptr(rx, ry);
472
        for (x=0;x<reader_size;++x) {
473
 
718 werner 474
            const HeightGridValue &hgv = mHeightGrid->constValueAtIndex((rx+x)/cPxPerHeight, ry/cPxPerHeight); // the height grid value, ry: gets ++ed in outer loop, rx not
475
            local_dom = hgv.height;
884 werner 476
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
403 werner 477
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
155 werner 478
 
403 werner 479
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
155 werner 480
            own_value = qMax(own_value, 0.02);
481
            value =  *grid_value++ / own_value; // remove impact of focal tree
720 werner 482
            // additional punishment if pixel is outside:
718 werner 483
            if (hgv.isForestOutside())
720 werner 484
               value *= outside_area_factor;
403 werner 485
 
486
            //qDebug() << x << y << local_dom << z << z_zstar << own_value << value << *(grid_value-1) << (*reader)(x,y) << mStamp->offsetValue(x,y,d_offset);
155 werner 487
            //if (value>0.)
488
            sum += value * (*reader)(x,y);
489
 
40 Werner 490
        }
491
    }
155 werner 492
    mLRI = sum;
426 werner 493
    // LRI correction...
494
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
495
    if (hrel<1.)
496
        mLRI = species()->speciesSet()->LRIcorrection(mLRI, hrel);
497
 
403 werner 498
 
155 werner 499
    if (mLRI > 1.)
500
        mLRI = 1.;
206 werner 501
 
502
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 503
    mRU->addWLA(mLeafArea, mLRI);
206 werner 504
 
155 werner 505
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
40 Werner 506
}
507
 
155 werner 508
/// Torus version of read stamp (glued edges)
158 werner 509
void Tree::readLIF_torus()
78 Werner 510
{
106 Werner 511
    if (!mStamp)
107 Werner 512
        return;
106 Werner 513
    const Stamp *reader = mStamp->reader();
78 Werner 514
    if (!reader)
107 Werner 515
        return;
295 werner 516
    int bufferOffset = mGrid->indexAt(QPointF(0.,0.)).x(); // offset of buffer
78 Werner 517
 
387 werner 518
    QPoint pos_reader = QPoint((mPositionIndex.x()-bufferOffset)%cPxPerRU + bufferOffset,
519
                               (mPositionIndex.y()-bufferOffset)%cPxPerRU + bufferOffset); // offset within the ha
295 werner 520
    QPoint ru_offset = QPoint(mPositionIndex.x() - pos_reader.x(), mPositionIndex.y() - pos_reader.y()); // offset of the corner of the resource index
521
 
78 Werner 522
    int offset_reader = reader->offset();
106 Werner 523
    int offset_writer = mStamp->offset();
78 Werner 524
    int d_offset = offset_writer - offset_reader; // offset on the *stamp* to the crown-cells
525
 
526
    pos_reader-=QPoint(offset_reader, offset_reader);
527
 
528
    float local_dom;
529
 
530
    int x,y;
531
    double sum=0.;
532
    double value, own_value;
533
    float *grid_value;
407 werner 534
    float z, z_zstar;
78 Werner 535
    int reader_size = reader->size();
536
    int rx = pos_reader.x();
537
    int ry = pos_reader.y();
155 werner 538
    int xt, yt; // wrapped coords
539
 
397 werner 540
    for (y=0;y<reader_size; ++y) {
541
        yt = torusIndex(ry+y,cPxPerRU, bufferOffset, ru_offset.y());
78 Werner 542
        for (x=0;x<reader_size;++x) {
387 werner 543
            xt = torusIndex(rx+x,cPxPerRU, bufferOffset, ru_offset.x());
155 werner 544
            grid_value = mGrid->ptr(xt,yt);
407 werner 545
 
387 werner 546
            local_dom = mHeightGrid->valueAtIndex(xt/cPxPerHeight, yt/cPxPerHeight).height; // ry: gets ++ed in outer loop, rx not
884 werner 547
            z = std::max(mHeight - reader->distanceToCenter(x,y), 0.f); // distance to center = height (45 degree line)
407 werner 548
            z_zstar = (z>=local_dom)?1.f:z/local_dom;
125 Werner 549
 
407 werner 550
            own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity * z_zstar;
551
            // old: own_value = 1. - mStamp->offsetValue(x,y,d_offset)*mOpacity / local_dom; // old: dom_height;
78 Werner 552
            own_value = qMax(own_value, 0.02);
407 werner 553
            value =  *grid_value++ / own_value; // remove impact of focal tree
554
 
397 werner 555
            // debug for one tree in HJA
556
            //if (id()==178020)
557
            //    qDebug() << x << y << xt << yt << *grid_value << local_dom << own_value << value << (*reader)(x,y);
321 werner 558
            //if (_isnan(value))
559
            //    qDebug() << "isnan" << id();
397 werner 560
            if (value * (*reader)(x,y)>1.)
561
                qDebug() << "LIFTorus: value>1.";
78 Werner 562
            sum += value * (*reader)(x,y);
563
 
564
            //} // isIndexValid
565
        }
566
    }
106 Werner 567
    mLRI = sum;
426 werner 568
 
569
    // LRI correction...
570
    double hrel = mHeight / mHeightGrid->valueAtIndex(mPositionIndex.x()/cPxPerHeight, mPositionIndex.y()/cPxPerHeight).height;
571
    if (hrel<1.)
572
        mLRI = species()->speciesSet()->LRIcorrection(mLRI, hrel);
573
 
574
 
615 werner 575
    if (isnan(mLRI)) {
321 werner 576
        qDebug() << "LRI invalid (nan)!" << id();
577
        mLRI=0.;
578
        //qDebug() << reader->dump();
579
    }
148 iland 580
    if (mLRI > 1.)
581
        mLRI = 1.;
78 Werner 582
    //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
205 werner 583
 
584
    // Finally, add LRI of this Tree to the ResourceUnit!
251 werner 585
    mRU->addWLA(mLeafArea, mLRI);
58 Werner 586
}
587
 
155 werner 588
 
40 Werner 589
void Tree::resetStatistics()
590
{
591
    m_statPrint=0;
105 Werner 592
    m_statCreated=0;
48 Werner 593
    m_statAboveZ=0;
40 Werner 594
    m_nextId=1;
595
}
107 Werner 596
 
1072 werner 597
#ifdef ALT_TREE_MORTALITY
1071 werner 598
void Tree::mortalityParams(double dbh_inc_threshold, int stress_years, double stress_mort_prob)
599
{
600
    _stress_threshold = dbh_inc_threshold;
601
    _stress_years = stress_years;
602
    _stress_death_prob = stress_mort_prob;
603
    qDebug() << "Alternative Mortality enabled: threshold" << dbh_inc_threshold << ", years:" << _stress_years << ", level:" << _stress_death_prob;
604
}
1072 werner 605
#endif
1071 werner 606
 
251 werner 607
void Tree::calcLightResponse()
608
{
609
    // calculate a light response from lri:
298 werner 610
    // http://iland.boku.ac.at/individual+tree+light+availability
470 werner 611
    double lri = limit(mLRI * mRU->LRImodifier(), 0., 1.); // Eq. (3)
612
    mLightResponse = mSpecies->lightResponse(lri); // Eq. (4)
251 werner 613
    mRU->addLR(mLeafArea, mLightResponse);
614
 
615
}
616
 
110 Werner 617
//////////////////////////////////////////////////
618
////  Growth Functions
619
//////////////////////////////////////////////////
107 Werner 620
 
227 werner 621
/** grow() is the main function of the yearly tree growth.
622
  The main steps are:
298 werner 623
  - Production of GPP/NPP   @sa http://iland.boku.ac.at/primary+production http://iland.boku.ac.at/individual+tree+light+availability
624
  - Partitioning of NPP to biomass compartments of the tree @sa http://iland.boku.ac.at/allocation
227 werner 625
  - Growth of the stem http://iland.boku.ac.at/stem+growth (???)
387 werner 626
  Further activties: * the age of the tree is increased
627
                     * the mortality sub routine is executed
628
                     * seeds are produced */
107 Werner 629
void Tree::grow()
630
{
159 werner 631
    TreeGrowthData d;
169 werner 632
    mAge++; // increase age
230 werner 633
    // step 1: get "interception area" of the tree individual [m2]
634
    // the sum of all area of all trees of a unit equal the total stocked area * interception_factor(Beer-Lambert)
635
    double effective_area = mRU->interceptedArea(mLeafArea, mLightResponse);
107 Werner 636
 
230 werner 637
    // step 2: calculate GPP of the tree based
638
    // (1) get the amount of GPP for a "unit area" of the tree species
639
    double raw_gpp_per_area = mRU->resourceUnitSpecies(species()).prod3PG().GPPperArea();
640
    // (2) GPP (without aging-effect) in kg Biomass / year
641
    double raw_gpp = raw_gpp_per_area * effective_area;
161 werner 642
 
227 werner 643
    // apply aging according to the state of the individuum
388 werner 644
    const double aging_factor = mSpecies->aging(mHeight, mAge);
376 werner 645
    mRU->addTreeAging(mLeafArea, aging_factor);
227 werner 646
    double gpp = raw_gpp * aging_factor; //
608 werner 647
    d.NPP = gpp * cAutotrophicRespiration; // respiration loss (0.47), cf. Waring et al 1998.
113 Werner 648
 
279 werner 649
    //DBGMODE(
137 Werner 650
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeNPP) && isDebugging()) {
133 Werner 651
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeNPP);
652
            dumpList(out); // add tree headers
299 werner 653
            out << mLRI * mRU->LRImodifier() << mLightResponse << effective_area << raw_gpp << gpp << d.NPP << aging_factor;
133 Werner 654
        }
279 werner 655
    //); // DBGMODE()
217 werner 656
    if (d.NPP>0.)
657
        partitioning(d); // split npp to compartments and grow (diameter, height)
133 Werner 658
 
387 werner 659
    // mortality
1071 werner 660
#ifdef ALT_TREE_MORTALITY
661
    // alternative variant of tree mortality (note: mStrssIndex used otherwise)
662
    altMortality(d);
663
 
664
#else
200 werner 665
    if (Model::settings().mortalityEnabled)
666
        mortality(d);
110 Werner 667
 
159 werner 668
    mStressIndex = d.stress_index;
1071 werner 669
#endif
180 werner 670
 
671
    if (!isDead())
257 werner 672
        mRU->resourceUnitSpecies(species()).statistics().add(this, &d);
277 werner 673
 
387 werner 674
    // regeneration
460 werner 675
    mSpecies->seedProduction(mAge, mHeight, mPositionIndex);
387 werner 676
 
107 Werner 677
}
678
 
227 werner 679
/** partitioning of this years assimilates (NPP) to biomass compartments.
298 werner 680
  Conceptionally, the algorithm is based on Duursma, 2007.
681
  @sa http://iland.boku.ac.at/allocation */
159 werner 682
inline void Tree::partitioning(TreeGrowthData &d)
115 Werner 683
{
159 werner 684
    double npp = d.NPP;
115 Werner 685
    // add content of reserve pool
116 Werner 686
    npp += mNPPReserve;
159 werner 687
    const double foliage_mass_allo = species()->biomassFoliage(mDbh);
276 werner 688
    const double reserve_size = foliage_mass_allo * (1. + mSpecies->finerootFoliageRatio());
297 werner 689
    double refill_reserve = qMin(reserve_size, (1. + mSpecies->finerootFoliageRatio())*mFoliageMass); // not always try to refill reserve 100%
119 Werner 690
 
136 Werner 691
    double apct_wood, apct_root, apct_foliage; // allocation percentages (sum=1) (eta)
468 werner 692
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
117 Werner 693
    // turnover rates
159 werner 694
    const double &to_fol = species()->turnoverLeaf();
695
    const double &to_root = species()->turnoverRoot();
136 Werner 696
    // the turnover rate of wood depends on the size of the reserve pool:
116 Werner 697
 
136 Werner 698
 
163 werner 699
    double to_wood = refill_reserve / (mWoodyMass + refill_reserve);
700
 
468 werner 701
    apct_root = rus.prod3PG().rootFraction();
261 werner 702
    d.NPP_above = d.NPP * (1. - apct_root); // aboveground: total NPP - fraction to roots
298 werner 703
    double b_wf = species()->allometricRatio_wf(); // ratio of allometric exponents (b_woody / b_foliage)
117 Werner 704
 
705
    // Duursma 2007, Eq. (20)
167 werner 706
    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 );
902 werner 707
 
708
    apct_wood = limit(apct_wood, 0., 1.-apct_root);
709
 
117 Werner 710
    apct_foliage = 1. - apct_root - apct_wood;
711
 
163 werner 712
 
902 werner 713
    DBGMODE(
163 werner 714
            if (apct_foliage<0 || apct_wood<0)
715
                qDebug() << "transfer to foliage or wood < 0";
716
             if (npp<0)
717
                 qDebug() << "NPP < 0";
902 werner 718
            );
163 werner 719
 
136 Werner 720
    // Change of biomass compartments
276 werner 721
    double sen_root = mFineRootMass * to_root;
722
    double sen_foliage = mFoliageMass * to_fol;
521 werner 723
    if (ru()->snag())
588 werner 724
        ru()->snag()->addTurnoverLitter(this->species(), sen_foliage, sen_root);
298 werner 725
 
136 Werner 726
    // Roots
298 werner 727
    // http://iland.boku.ac.at/allocation#belowground_NPP
276 werner 728
    mFineRootMass -= sen_root; // reduce only fine root pool
729
    double delta_root = apct_root * npp;
730
    // 1st, refill the fine root pool
731
    double fineroot_miss = mFoliageMass * mSpecies->finerootFoliageRatio() - mFineRootMass;
732
    if (fineroot_miss>0.){
733
        double delta_fineroot = qMin(fineroot_miss, delta_root);
734
        mFineRootMass += delta_fineroot;
735
        delta_root -= delta_fineroot;
736
    }
737
    // 2nd, the rest of NPP allocated to roots go to coarse roots
595 werner 738
    double max_coarse_root = species()->biomassRoot(mDbh);
276 werner 739
    mCoarseRootMass += delta_root;
595 werner 740
    if (mCoarseRootMass > max_coarse_root) {
741
        // if the coarse root pool exceeds the value given by the allometry, then the
742
        // surplus is accounted as turnover
743
        if (ru()->snag())
744
            ru()->snag()->addTurnoverWood(species(), mCoarseRootMass-max_coarse_root);
119 Werner 745
 
595 werner 746
        mCoarseRootMass = max_coarse_root;
747
    }
748
 
136 Werner 749
    // Foliage
159 werner 750
    double delta_foliage = apct_foliage * npp - sen_foliage;
137 Werner 751
    mFoliageMass += delta_foliage;
615 werner 752
    if (isnan(mFoliageMass))
217 werner 753
        qDebug() << "foliage mass invalid!";
163 werner 754
    if (mFoliageMass<0.) mFoliageMass=0.; // limit to zero
755
 
159 werner 756
    mLeafArea = mFoliageMass * species()->specificLeafArea(); // update leaf area
119 Werner 757
 
271 werner 758
    // stress index: different varaints at denominator: to_fol*foliage_mass = leafmass to rebuild,
198 werner 759
    // foliage_mass_allo: simply higher chance for stress
271 werner 760
    // note: npp = NPP + reserve (see above)
276 werner 761
    d.stress_index =qMax(1. - (npp) / ( to_fol*foliage_mass_allo + to_root*foliage_mass_allo*species()->finerootFoliageRatio() + reserve_size), 0.);
198 werner 762
 
136 Werner 763
    // Woody compartments
298 werner 764
    // see also: http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth
136 Werner 765
    // (1) transfer to reserve pool
766
    double gross_woody = apct_wood * npp;
767
    double to_reserve = qMin(reserve_size, gross_woody);
768
    mNPPReserve = to_reserve;
769
    double net_woody = gross_woody - to_reserve;
137 Werner 770
    double net_stem = 0.;
164 werner 771
    mDbhDelta = 0.;
165 werner 772
 
773
 
136 Werner 774
    if (net_woody > 0.) {
775
        // (2) calculate part of increment that is dedicated to the stem (which is a function of diameter)
159 werner 776
        net_stem = net_woody * species()->allometricFractionStem(mDbh);
777
        d.NPP_stem = net_stem;
137 Werner 778
        mWoodyMass += net_woody;
136 Werner 779
        //  (3) growth of diameter and height baseed on net stem increment
159 werner 780
        grow_diameter(d);
136 Werner 781
    }
119 Werner 782
 
279 werner 783
    //DBGMODE(
137 Werner 784
     if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreePartition)
785
         && isDebugging() ) {
129 Werner 786
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreePartition);
787
            dumpList(out); // add tree headers
136 Werner 788
            out << npp << apct_foliage << apct_wood << apct_root
276 werner 789
                    << delta_foliage << net_woody << delta_root << mNPPReserve << net_stem << d.stress_index;
137 Werner 790
     }
144 Werner 791
 
279 werner 792
    //); // DBGMODE()
497 werner 793
    DBGMODE(
428 werner 794
      if (mWoodyMass<0. || mWoodyMass>50000 || mFoliageMass<0. || mFoliageMass>2000. || mCoarseRootMass<0. || mCoarseRootMass>30000
393 werner 795
         || mNPPReserve>4000.) {
389 werner 796
         qDebug() << "Tree:partitioning: invalid or unlikely pools.";
144 Werner 797
         qDebug() << GlobalSettings::instance()->debugListCaptions(GlobalSettings::DebugOutputs(0));
798
         DebugList dbg; dumpList(dbg);
799
         qDebug() << dbg;
497 werner 800
     } );
144 Werner 801
 
136 Werner 802
    /*DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump()
803
             + 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")
804
               .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root)
805
               .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) );*/
129 Werner 806
 
115 Werner 807
}
808
 
125 Werner 809
 
134 Werner 810
/** Determination of diamter and height growth based on increment of the stem mass (@p net_stem_npp).
125 Werner 811
    Refer to XXX for equations and variables.
812
    This function updates the dbh and height of the tree.
227 werner 813
    The equations are based on dbh in meters! */
159 werner 814
inline void Tree::grow_diameter(TreeGrowthData &d)
119 Werner 815
{
816
    // determine dh-ratio of increment
817
    // height increment is a function of light competition:
125 Werner 818
    double hd_growth = relative_height_growth(); // hd of height growth
153 werner 819
    double d_m = mDbh / 100.; // current diameter in [m]
159 werner 820
    double net_stem_npp = d.NPP_stem;
821
 
153 werner 822
    const double d_delta_m = mDbhDelta / 100.; // increment of last year in [m]
115 Werner 823
 
159 werner 824
    const double mass_factor = species()->volumeFactor() * species()->density();
153 werner 825
    double stem_mass = mass_factor * d_m*d_m * mHeight; // result: kg, dbh[cm], h[meter]
123 Werner 826
 
153 werner 827
    // factor is in diameter increment per NPP [m/kg]
828
    double factor_diameter = 1. / (  mass_factor * (d_m + d_delta_m)*(d_m + d_delta_m) * ( 2. * mHeight/d_m + hd_growth) );
125 Werner 829
    double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment
830
 
831
    // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
153 werner 832
    double stem_estimate = mass_factor * (d_m + delta_d_estimate)*(d_m + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth);
137 Werner 833
    double stem_residual = stem_estimate - (stem_mass + net_stem_npp);
125 Werner 834
 
835
    // the final increment is then:
836
    double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11)
463 werner 837
    double res_final  = 0.;
838
    if (fabs(stem_residual) > 1.) {
465 werner 839
 
463 werner 840
        // calculate final residual in stem
841
        res_final = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp));
842
        if (fabs(res_final)>1.) {
465 werner 843
            // for large errors in stem biomass due to errors in diameter increment (> 1kg), we solve the increment iteratively.
463 werner 844
            // first, increase increment with constant step until we overestimate the first time
845
            // then,
846
            d_increment = 0.02; // start with 2cm increment
847
            bool reached_error = false;
848
            double step=0.01; // step-width 1cm
849
            double est_stem;
850
            do {
851
                est_stem = mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth); // estimate with current increment
852
                stem_residual = est_stem - (stem_mass + net_stem_npp);
853
 
854
                if (fabs(stem_residual) <1.) // finished, if stem residual below 1kg
855
                    break;
856
                if (stem_residual > 0.) {
857
                    d_increment -= step;
858
                    reached_error=true;
859
                } else {
860
                    d_increment += step;
861
                }
862
                if (reached_error)
863
                    step /= 2.;
864
            } while (step>0.00001); // continue until diameter "accuracy" falls below 1/100mm
865
        }
866
    }
867
 
868
    if (d_increment<0.f)
869
        qDebug() << "Tree::grow_diameter: d_inc < 0.";
144 Werner 870
    DBG_IF_X(d_increment<0. || d_increment>0.1, "Tree::grow_dimater", "increment out of range.", dump()
125 Werner 871
             + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6")
872
               .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment)
142 Werner 873
               .arg( mass_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp)) ));
125 Werner 874
 
303 werner 875
    //DBGMODE(
463 werner 876
        // do not calculate res_final twice if already done
877
        DBG_IF_X( (res_final==0.?fabs(mass_factor * (d_m + d_increment)*(d_m + d_increment)*(mHeight + d_increment*hd_growth)-((stem_mass + net_stem_npp))):res_final) > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump());
153 werner 878
        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 879
 
137 Werner 880
        if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dTreeGrowth) && isDebugging() ) {
126 Werner 881
            DebugList &out = GlobalSettings::instance()->debugList(mId, GlobalSettings::dTreeGrowth);
129 Werner 882
            dumpList(out); // add tree headers
143 Werner 883
            out << net_stem_npp << stem_mass << hd_growth << factor_diameter << delta_d_estimate*100 << d_increment*100;
126 Werner 884
        }
153 werner 885
 
303 werner 886
    //); // DBGMODE()
125 Werner 887
 
888
    d_increment = qMax(d_increment, 0.);
889
 
890
    // update state variables
153 werner 891
    mDbh += d_increment*100; // convert from [m] to [cm]
892
    mDbhDelta = d_increment*100; // save for next year's growth
893
    mHeight += d_increment * hd_growth;
158 werner 894
 
895
    // update state of LIP stamp and opacity
159 werner 896
    mStamp = species()->stamp(mDbh, mHeight); // get new stamp for updated dimensions
158 werner 897
    // calculate the CrownFactor which reflects the opacity of the crown
200 werner 898
    const double k=Model::settings().lightExtinctionCoefficientOpacity;
899
    mOpacity = 1. - exp(-k * mLeafArea / mStamp->crownArea());
158 werner 900
 
119 Werner 901
}
902
 
125 Werner 903
 
904
/// return the HD ratio of this year's increment based on the light status.
119 Werner 905
inline double Tree::relative_height_growth()
906
{
907
    double hd_low, hd_high;
908
    mSpecies->hdRange(mDbh, hd_low, hd_high);
909
 
125 Werner 910
    DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump());
949 werner 911
    DBG_IF_X(hd_low < 10 || 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));
125 Werner 912
 
913
    // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
326 werner 914
    // use the corrected LRI (see tracker#11)
915
    double lri = limit(mLRI * mRU->LRImodifier(),0.,1.);
916
    double hd_ratio = hd_high - (hd_high-hd_low)*lri;
125 Werner 917
    return hd_ratio;
119 Werner 918
}
141 Werner 919
 
278 werner 920
/** This function is called if a tree dies.
921
  @sa ResourceUnit::cleanTreeList(), remove() */
277 werner 922
void Tree::die(TreeGrowthData *d)
923
{
924
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
664 werner 925
    mRU->treeDied();
468 werner 926
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
927
    rus.statisticsDead().add(this, d); // add tree to statistics
1064 werner 928
    notifyTreeRemoved(TreeDeath);
521 werner 929
    if (ru()->snag())
930
        ru()->snag()->addMortality(this);
277 werner 931
}
932
 
903 werner 933
/// remove a tree (most likely due to harvest) from the system.
564 werner 934
void Tree::remove(double removeFoliage, double removeBranch, double removeStem )
278 werner 935
{
936
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
1050 werner 937
    setIsHarvested();
664 werner 938
    mRU->treeDied();
468 werner 939
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
940
    rus.statisticsMgmt().add(this, 0);
1064 werner 941
    notifyTreeRemoved(TreeHarvest);
903 werner 942
 
521 werner 943
    if (ru()->snag())
564 werner 944
        ru()->snag()->addHarvest(this, removeStem, removeBranch, removeFoliage);
278 werner 945
}
946
 
713 werner 947
/// remove the tree due to an special event (disturbance)
903 werner 948
/// this is +- the same as die().
713 werner 949
void Tree::removeDisturbance(const double stem_to_soil_fraction, const double stem_to_snag_fraction, const double branch_to_soil_fraction, const double branch_to_snag_fraction, const double foliage_to_soil_fraction)
950
{
951
    setFlag(Tree::TreeDead, true); // set flag that tree is dead
952
    mRU->treeDied();
953
    ResourceUnitSpecies &rus = mRU->resourceUnitSpecies(species());
714 werner 954
    rus.statisticsDead().add(this, 0);
1064 werner 955
    notifyTreeRemoved(TreeDisturbance);
903 werner 956
 
713 werner 957
    if (ru()->snag())
958
        ru()->snag()->addDisturbance(this, stem_to_snag_fraction, stem_to_soil_fraction, branch_to_snag_fraction, branch_to_soil_fraction, foliage_to_soil_fraction);
959
}
960
 
903 werner 961
/// remove a part of the biomass of the tree, e.g. due to fire.
962
void Tree::removeBiomassOfTree(const double removeFoliageFraction, const double removeBranchFraction, const double removeStemFraction)
668 werner 963
{
964
    mFoliageMass *= 1. - removeFoliageFraction;
965
    mWoodyMass *= (1. - removeStemFraction);
966
    // we have a problem with the branches: this currently cannot be done properly!
766 werner 967
    (void) removeBranchFraction; // silence warning
668 werner 968
}
969
 
975 werner 970
void Tree::setHeight(const float height)
971
{
972
    if (height<=0. || height>150.)
973
        qWarning() << "trying to set tree height to invalid value:" << height << " for tree on RU:" << (mRU?mRU->boundingBox():QRect());
974
    mHeight=height;
975
}
976
 
159 werner 977
void Tree::mortality(TreeGrowthData &d)
978
{
163 werner 979
    // death if leaf area is 0
980
    if (mFoliageMass<0.00001)
981
        die();
982
 
308 werner 983
    double p_death,  p_stress, p_intrinsic;
984
    p_intrinsic = species()->deathProb_intrinsic();
985
    p_stress = species()->deathProb_stress(d.stress_index);
986
    p_death =  p_intrinsic + p_stress;
707 werner 987
    double p = drandom(); //0..1
159 werner 988
    if (p<p_death) {
989
        // die...
990
        die();
991
    }
992
}
141 Werner 993
 
1072 werner 994
#ifdef ALT_TREE_MORTALITY
1071 werner 995
void Tree::altMortality(TreeGrowthData &d)
996
{
997
    // death if leaf area is 0
998
    if (mFoliageMass<0.00001)
999
        die();
1000
 
1001
    double  p_intrinsic, p_stress=0.;
1002
    p_intrinsic = species()->deathProb_intrinsic();
1003
 
1004
    if (mDbhDelta < _stress_threshold) {
1005
        mStressIndex++;
1006
        if (mStressIndex> _stress_years)
1007
            p_stress = _stress_death_prob;
1008
    } else
1009
        mStressIndex = 0;
1010
 
1011
    double p = drandom(); //0..1
1012
    if (p<p_intrinsic + p_stress) {
1013
        // die...
1014
        die();
1015
    }
1016
}
1072 werner 1017
#endif
1071 werner 1018
 
1064 werner 1019
void Tree::notifyTreeRemoved(TreeRemovalType reason)
903 werner 1020
{
1021
    // add the volume of the current tree to the height grid
1077 werner 1022
    // this information is used to track the removed volume for stands based on grids (and for salvaging operations)
909 werner 1023
    ABE::ForestManagementEngine *abe = GlobalSettings::instance()->model()->ABEngine();
1024
    if (abe)
1064 werner 1025
        abe->notifyTreeRemoval(this, (int)reason);
1044 werner 1026
 
1027
    // tell disturbance modules that a tree died
1028
    GlobalSettings::instance()->model()->modules()->treeDeath(this, (int) reason);
903 werner 1029
}
1030
 
141 Werner 1031
//////////////////////////////////////////////////
1032
////  value functions
1033
//////////////////////////////////////////////////
1034
 
145 Werner 1035
double Tree::volume() const
141 Werner 1036
{
1037
    /// @see Species::volumeFactor() for details
159 werner 1038
    const double volume_factor = species()->volumeFactor();
157 werner 1039
    const double volume =  volume_factor * mDbh*mDbh*mHeight * 0.0001; // dbh in cm: cm/100 * cm/100 = cm*cm * 0.0001 = m2
141 Werner 1040
    return volume;
1041
}
180 werner 1042
 
579 werner 1043
/// return the basal area in m2
180 werner 1044
double Tree::basalArea() const
1045
{
1046
    // A = r^2 * pi = d/2*pi; from cm->m: d/200
1047
    const double b = (mDbh/200.)*(mDbh/200.)*M_PI;
1048
    return b;
1049
}
668 werner 1050