Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
671 werner 1
/********************************************************************************************
2
**    iLand - an individual based forest landscape and disturbance model
3
**    http://iland.boku.ac.at
4
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**
6
**    This program is free software: you can redistribute it and/or modify
7
**    it under the terms of the GNU General Public License as published by
8
**    the Free Software Foundation, either version 3 of the License, or
9
**    (at your option) any later version.
10
**
11
**    This program is distributed in the hope that it will be useful,
12
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    GNU General Public License for more details.
15
**
16
**    You should have received a copy of the GNU General Public License
17
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
********************************************************************************************/
19
 
595 werner 20
#include "sapling.h"
21
#include "model.h"
22
#include "species.h"
23
#include "resourceunit.h"
24
#include "resourceunitspecies.h"
25
#include "tree.h"
26
 
27
/** @class Sapling
697 werner 28
  @ingroup core
595 werner 29
    Sapling stores saplings per species and resource unit and computes sapling growth (before recruitment).
30
    http://iland.boku.ac.at/sapling+growth+and+competition
31
    Saplings are established in a separate step (@sa Regeneration). If sapling reach a height of 4m, they are recruited and become "real" iLand-trees.
32
    Within the regeneration layer, a cohort-approach is applied.
33
 
34
  */
35
 
36
double Sapling::mRecruitmentVariation = 0.1; // +/- 10%
1063 werner 37
double Sapling::mBrowsingPressure = 0.;
595 werner 38
 
39
Sapling::Sapling()
40
{
41
    mRUS = 0;
42
    clearStatistics();
663 werner 43
    mAdded = 0;
595 werner 44
}
45
 
663 werner 46
// reset statistics, called at newYear
47
void Sapling::clearStatistics()
48
{
49
    // mAdded: removed
50
    mRecruited=mDied=mLiving=0;
51
    mSumDbhDied=0.;
52
    mAvgHeight=0.;
53
    mAvgAge=0.;
54
    mAvgDeltaHPot=mAvgHRealized=0.;
55
}
56
 
1063 werner 57
void Sapling::updateBrowsingPressure()
58
{
59
    if (GlobalSettings::instance()->settings().valueBool("model.settings.browsing.enabled"))
60
        Sapling::mBrowsingPressure = GlobalSettings::instance()->settings().valueDouble("model.settings.browsing.browsingPressure");
61
    else
62
        Sapling::mBrowsingPressure = 0.;
63
}
64
 
595 werner 65
/// get the *represented* (Reineke's Law) number of trees (N/ha)
66
double Sapling::livingStemNumber(double &rAvgDbh, double &rAvgHeight, double &rAvgAge) const
67
{
68
    double total = 0.;
69
    double dbh_sum = 0.;
70
    double h_sum = 0.;
71
    double age_sum = 0.;
72
    const SaplingGrowthParameters &p = mRUS->species()->saplingGrowthParameters();
1111 werner 73
    for (QVector<SaplingTreeOld>::const_iterator it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
595 werner 74
        float dbh = it->height / p.hdSapling * 100.f;
75
        if (dbh<1.) // minimum size: 1cm
76
            continue;
77
        double n = p.representedStemNumber(dbh); // one cohort on the pixel represents that number of trees
78
        dbh_sum += n*dbh;
79
        h_sum += n*it->height;
80
        age_sum += n*it->age.age;
81
        total += n;
82
    }
83
    if (total>0.) {
84
        dbh_sum /= total;
85
        h_sum /= total;
86
        age_sum /= total;
87
    }
88
    rAvgDbh = dbh_sum;
89
    rAvgHeight = h_sum;
90
    rAvgAge = age_sum;
91
    return total;
92
}
93
 
967 werner 94
double Sapling::representedStemNumber(float height) const
95
{
96
    const SaplingGrowthParameters &p = mRUS->species()->saplingGrowthParameters();
97
    float dbh = height / p.hdSapling * 100.f;
98
    double n = p.representedStemNumber(dbh);
99
    return n;
100
}
101
 
595 werner 102
/// maintenance function to clear dead/recruited saplings from storage
103
void Sapling::cleanupStorage()
104
{
1111 werner 105
    QVector<SaplingTreeOld>::iterator forw=mSaplingTrees.begin();
106
    QVector<SaplingTreeOld>::iterator back;
595 werner 107
 
108
    // seek last valid
109
    for (back=mSaplingTrees.end()-1; back>=mSaplingTrees.begin(); --back)
110
        if ((*back).isValid())
111
            break;
112
 
113
    if (back<mSaplingTrees.begin()) {
114
        mSaplingTrees.clear(); // no valid trees available
115
        return;
116
    }
117
 
118
    while (forw < back) {
119
        if (!(*forw).isValid()) {
120
            *forw = *back; // copy (fill gap)
121
            while (back>forw) // seek next valid
122
                if ((*--back).isValid())
123
                    break;
124
        }
125
        ++forw;
126
    }
127
    if (back != mSaplingTrees.end()-1) {
128
        // free resources...
129
        mSaplingTrees.erase(back+1, mSaplingTrees.end());
130
    }
131
}
132
 
133
// not a very good way of checking if sapling is present
134
// maybe better: use also a (local) maximum sapling height grid
135
// maybe better: use a bitset:
136
// position: index of pixel on LIF (absolute index)
137
bool Sapling::hasSapling(const QPoint &position) const
138
{
139
    const QPoint &offset = mRUS->ru()->cornerPointOffset();
140
    int index = (position.x()- offset.x())*cPxPerRU + (position.y() - offset.y());
802 werner 141
    if (index<0)
142
        qDebug() << "Sapling error";
595 werner 143
    return mSapBitset[index];
144
    /*
145
    float *target = GlobalSettings::instance()->model()->grid()->ptr(position.x(), position.y());
146
    QVector<SaplingTree>::const_iterator it;
147
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
148
        if (it->pixel==target)
149
            return true;
150
    }
151
    return false;
152
    */
153
}
154
 
600 werner 155
/// retrieve the height of the sapling at the location 'position' (given in LIF-coordinates)
156
/// this is quite expensive and only done for initialization
157
double Sapling::heightAt(const QPoint &position) const
158
{
159
    if (!hasSapling(position))
160
        return 0.;
161
    // ok, we'll have to search through all saplings
1111 werner 162
    QVector<SaplingTreeOld>::const_iterator it;
600 werner 163
    float *lif_ptr = GlobalSettings::instance()->model()->grid()->ptr(position.x(), position.y());
164
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
165
        if (it->isValid() && it->pixel == lif_ptr)
166
            return it->height;
167
    }
168
    return 0.;
595 werner 169
 
600 werner 170
}
171
 
172
 
941 werner 173
void Sapling::setBit(const QPoint &pos_index, bool value)
695 werner 174
{
175
    int index = (pos_index.x() - mRUS->ru()->cornerPointOffset().x()) * cPxPerRU +(pos_index.y() - mRUS->ru()->cornerPointOffset().y());
941 werner 176
    mSapBitset.set(index,value); // set bit: now there is a sapling there
695 werner 177
}
178
 
901 werner 179
/// add a sapling at the given position (index on the LIF grid, i.e. 2x2m)
951 werner 180
int Sapling::addSapling(const QPoint &pos_lif, const float height, const int age)
595 werner 181
{
182
    // adds a sapling...
1111 werner 183
    mSaplingTrees.push_back(SaplingTreeOld());
184
    SaplingTreeOld &t = mSaplingTrees.back();
911 werner 185
    t.height = height; // default is 5cm height
951 werner 186
    t.age.age = age;
595 werner 187
    Grid<float> &lif_map = *GlobalSettings::instance()->model()->grid();
188
    t.pixel = lif_map.ptr(pos_lif.x(), pos_lif.y());
941 werner 189
    setBit(pos_lif, true);
595 werner 190
    mAdded++;
911 werner 191
    return mSaplingTrees.count()-1; // index of the newly added tree.
595 werner 192
}
193
 
194
/// clear  saplings on a given position (after recruitment)
195
void Sapling::clearSaplings(const QPoint &position)
196
{
197
    float *target = GlobalSettings::instance()->model()->grid()->ptr(position.x(), position.y());
1111 werner 198
    QVector<SaplingTreeOld>::const_iterator it;
595 werner 199
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
200
        if (it->pixel==target) {
201
            // trick: use a const iterator to avoid a deep copy of the vector; then do an ugly const_cast to actually write the data
662 werner 202
            //const SaplingTree &t = *it;
203
            //const_cast<SaplingTree&>(t).pixel=0;
1111 werner 204
            clearSapling(const_cast<SaplingTreeOld&>(*it), false); // kill sapling and move carbon to soil
595 werner 205
        }
206
    }
941 werner 207
    setBit(position, false); // clear bit: now there is no sapling on this position
208
    //int index = (position.x() - mRUS->ru()->cornerPointOffset().x()) * cPxPerRU +(position.y() - mRUS->ru()->cornerPointOffset().y());
209
    //mSapBitset.set(index,false); // clear bit: now there is no sapling on this position
595 werner 210
 
211
}
212
 
662 werner 213
/// clear  saplings within a given rectangle
214
void Sapling::clearSaplings(const QRectF &rectangle, const bool remove_biomass)
215
{
1111 werner 216
    QVector<SaplingTreeOld>::const_iterator it;
662 werner 217
    FloatGrid *grid = GlobalSettings::instance()->model()->grid();
218
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
902 werner 219
        if (rectangle.contains(grid->cellCenterPoint(it->coords()))) {
1111 werner 220
            clearSapling(const_cast<SaplingTreeOld&>(*it), remove_biomass);
662 werner 221
        }
222
    }
223
}
224
 
1111 werner 225
void Sapling::clearSapling(SaplingTreeOld &tree, const bool remove)
662 werner 226
{
941 werner 227
    QPoint p=tree.coords();
662 werner 228
    tree.pixel=0;
941 werner 229
    setBit(p, false); // no tree left
662 werner 230
    if (!remove) {
231
        // killing of saplings:
232
        // if remove=false, then remember dbh/number of trees (used later in calculateGrowth() to estimate carbon flow)
233
        mDied++;
234
        mSumDbhDied+=tree.height / mRUS->species()->saplingGrowthParameters().hdSapling * 100.;
235
    }
236
 
237
}
238
 
911 werner 239
//
240
void Sapling::clearSapling(int index, const bool remove)
241
{
242
    Q_ASSERT(index < mSaplingTrees.count());
243
    clearSapling(mSaplingTrees[index], remove);
244
}
245
 
595 werner 246
/// growth function for an indivudal sapling.
247
/// returns true, if sapling survives, false if sapling dies or is recruited to iLand.
248
/// see also http://iland.boku.ac.at/recruitment
1111 werner 249
bool Sapling::growSapling(SaplingTreeOld &tree, const double f_env_yr, Species* species)
595 werner 250
{
251
    QPoint p=GlobalSettings::instance()->model()->grid()->indexOf(tree.pixel);
1068 werner 252
    //GlobalSettings::instance()->model()->heightGrid()[Grid::index5(tree.pixel-GlobalSettings::instance()->model()->grid()->begin())];
595 werner 253
 
254
    // (1) calculate height growth potential for the tree (uses linerization of expressions...)
802 werner 255
    double h_pot = species->saplingGrowthParameters().heightGrowthPotential.calculate(tree.height); // TODO check if this can be source of crashes (race condition)
595 werner 256
    double delta_h_pot = h_pot - tree.height;
257
 
258
    // (2) reduce height growth potential with species growth response f_env_yr and with light state (i.e. LIF-value) of home-pixel.
259
    double lif_value = *tree.pixel;
260
    double h_height_grid = GlobalSettings::instance()->model()->heightGrid()->valueAtIndex(p.x()/cPxPerHeight, p.y()/cPxPerHeight).height;
1157 werner 261
    if (h_height_grid==0.)
595 werner 262
        throw IException(QString("growSapling: height grid at %1/%2 has value 0").arg(p.x()).arg(p.y()));
263
 
816 werner 264
    double rel_height = tree.height / h_height_grid;
265
 
595 werner 266
    double lif_corrected = mRUS->species()->speciesSet()->LRIcorrection(lif_value, rel_height); // correction based on height
683 werner 267
 
595 werner 268
    double lr = mRUS->species()->lightResponse(lif_corrected); // species specific light response (LUI, light utilization index)
269
 
270
    double delta_h_factor = f_env_yr * lr; // relative growth
271
 
272
    if (h_pot<0. || delta_h_pot<0. || lif_corrected<0. || lif_corrected>1. || delta_h_factor<0. || delta_h_factor>1. )
273
        qDebug() << "invalid values in Sapling::growSapling";
274
 
1063 werner 275
    // check browsing
276
    if (mBrowsingPressure>0. && tree.height<=2.f) {
277
        double p = mRUS->species()->saplingGrowthParameters().browsingProbability;
278
        // calculate modifed annual browsing probability via odds-ratios
279
        // odds = p/(1-p) -> odds_mod = odds * browsingPressure -> p_mod = odds_mod /( 1 + odds_mod) === p*pressure/(1-p+p*pressure)
280
        double p_browse = p*mBrowsingPressure / (1. - p + p*mBrowsingPressure);
281
        if (drandom() < p_browse) {
282
            delta_h_factor = 0.;
283
        }
284
    }
285
 
595 werner 286
    // check mortality of saplings
287
    if (delta_h_factor < species->saplingGrowthParameters().stressThreshold) {
288
        tree.age.stress_years++;
289
        if (tree.age.stress_years > species->saplingGrowthParameters().maxStressYears) {
290
            // sapling dies...
662 werner 291
            clearSapling(tree, false); // false: put carbon to the soil
595 werner 292
            return false;
293
        }
294
    } else {
295
        tree.age.stress_years=0; // reset stress counter
296
    }
297
    DBG_IF(delta_h_pot*delta_h_factor < 0.f || delta_h_pot*delta_h_factor > 2., "Sapling::growSapling", "inplausible height growth.");
298
 
299
    // grow
300
    tree.height += delta_h_pot * delta_h_factor;
301
    tree.age.age++; // increase age of sapling by 1
302
 
303
    // recruitment?
304
    if (tree.height > 4.f) {
305
        mRecruited++;
306
 
307
        ResourceUnit *ru = const_cast<ResourceUnit*> (mRUS->ru());
308
        float dbh = tree.height / species->saplingGrowthParameters().hdSapling * 100.f;
309
        // the number of trees to create (result is in trees per pixel)
310
        double n_trees = species->saplingGrowthParameters().representedStemNumber(dbh);
311
        int to_establish = (int) n_trees;
863 werner 312
 
595 werner 313
        // if n_trees is not an integer, choose randomly if we should add a tree.
314
        // e.g.: n_trees = 2.3 -> add 2 trees with 70% probability, and add 3 trees with p=30%.
707 werner 315
        if (drandom() < (n_trees-to_establish) || to_establish==0)
595 werner 316
            to_establish++;
317
 
318
        // add a new tree
319
        for (int i=0;i<to_establish;i++) {
320
            Tree &bigtree = ru->newTree();
321
            bigtree.setPosition(p);
322
            // add variation: add +/-10% to dbh and *independently* to height.
707 werner 323
            bigtree.setDbh(dbh * nrandom(1. - mRecruitmentVariation, 1. + mRecruitmentVariation));
324
            bigtree.setHeight(tree.height * nrandom(1. - mRecruitmentVariation, 1. + mRecruitmentVariation));
595 werner 325
            bigtree.setSpecies( species );
326
            bigtree.setAge(tree.age.age,tree.height);
327
            bigtree.setRU(ru);
328
            bigtree.setup();
855 werner 329
            const Tree *t = &bigtree;
330
            mRUS->statistics().add(t, 0); // count the newly created trees already in the stats
595 werner 331
        }
332
        // clear all regeneration from this pixel (including this tree)
662 werner 333
        clearSapling(tree, true); // remove this tree (but do not move biomass to soil)
1162 werner 334
//        ru->clearSaplings(p); // remove all other saplings on the same pixel
855 werner 335
 
595 werner 336
        return false;
337
    }
338
    // book keeping (only for survivors)
339
    mLiving++;
340
    mAvgHeight+=tree.height;
341
    mAvgAge+=tree.age.age;
342
    mAvgDeltaHPot+=delta_h_pot;
343
    mAvgHRealized += delta_h_pot * delta_h_factor;
344
    return true;
345
 
346
}
347
 
608 werner 348
 
662 werner 349
/** main growth function for saplings.
350
    Statistics are cleared at the beginning of the year.
351
*/
595 werner 352
void Sapling::calculateGrowth()
353
{
354
    Q_ASSERT(mRUS);
821 werner 355
    if (mSaplingTrees.count()==0)
595 werner 356
        return;
357
 
358
    ResourceUnit *ru = const_cast<ResourceUnit*> (mRUS->ru() );
359
    Species *species = const_cast<Species*>(mRUS->species());
360
 
361
    // calculate necessary growth modifier (this is done only once per year)
362
    mRUS->calculate(true); // calculate the 3pg module (this is done only if that did not happen up to now); true: call comes from regeneration
363
    double f_env_yr = mRUS->prod3PG().fEnvYear();
364
 
365
    mLiving=0;
1111 werner 366
    QVector<SaplingTreeOld>::const_iterator it;
595 werner 367
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
1111 werner 368
        const SaplingTreeOld &tree = *it;
595 werner 369
        if (tree.height<0)
370
            qDebug() << "Sapling::calculateGrowth(): h<0";
821 werner 371
        // if sapling is still living check execute growth routine
595 werner 372
        if (tree.isValid()) {
821 werner 373
            // growing (increases mLiving if tree did not die, mDied otherwise)
1111 werner 374
            if (growSapling(const_cast<SaplingTreeOld&>(tree), f_env_yr, species)) {
595 werner 375
                // set the sapling height to the maximum value on the current pixel
1162 werner 376
//                ru->setMaxSaplingHeightAt(tree.coords(),tree.height);
595 werner 377
            }
378
        }
379
    }
380
    if (mLiving) {
381
        mAvgHeight /= double(mLiving);
382
        mAvgAge /= double(mLiving);
383
        mAvgDeltaHPot /= double(mLiving);
384
        mAvgHRealized /= double(mLiving);
385
    }
386
    // calculate carbon balance
608 werner 387
    CNPair old_state = mCarbonLiving;
595 werner 388
    mCarbonLiving.clear();
608 werner 389
 
595 werner 390
    CNPair dead_wood, dead_fine; // pools for mortality
391
    // average dbh
392
    if (mLiving) {
1157 werner 393
        // calculate the avg dbh and number of stems
595 werner 394
        double avg_dbh = mAvgHeight / species->saplingGrowthParameters().hdSapling * 100.;
395
        double n = mLiving * species->saplingGrowthParameters().representedStemNumber( avg_dbh );
396
        // woody parts: stem, branchse and coarse roots
397
        double woody_bm = species->biomassWoody(avg_dbh) + species->biomassBranch(avg_dbh) + species->biomassRoot(avg_dbh);
398
        double foliage = species->biomassFoliage(avg_dbh);
399
        double fineroot = foliage*species->finerootFoliageRatio();
400
 
401
        mCarbonLiving.addBiomass( woody_bm*n, species->cnWood()  );
402
        mCarbonLiving.addBiomass( foliage*n, species->cnFoliage()  );
403
        mCarbonLiving.addBiomass( fineroot*n, species->cnFineroot()  );
608 werner 404
 
595 werner 405
        // turnover
802 werner 406
        if (mRUS->ru()->snag())
407
            mRUS->ru()->snag()->addTurnoverLitter(species, foliage*species->turnoverLeaf(), fineroot*species->turnoverRoot());
595 werner 408
 
409
        // calculate the "mortality from competition", i.e. carbon that stems from reduction of stem numbers
410
        // from Reinekes formula.
411
        //
412
        if (avg_dbh>1.) {
413
            double avg_dbh_before = (mAvgHeight - mAvgHRealized) / species->saplingGrowthParameters().hdSapling * 100.;
414
            double n_before = mLiving * species->saplingGrowthParameters().representedStemNumber( qMax(1.,avg_dbh_before) );
415
            if (n<n_before) {
416
                dead_wood.addBiomass( woody_bm * (n_before-n), species->cnWood() );
417
                dead_fine.addBiomass( foliage * (n_before-n), species->cnFoliage()  );
418
                dead_fine.addBiomass( fineroot * (n_before-n), species->cnFineroot()  );
419
            }
420
        }
421
 
422
    }
423
    if (mDied) {
424
        double avg_dbh_dead = mSumDbhDied / double(mDied);
425
        double n = mDied * species->saplingGrowthParameters().representedStemNumber( avg_dbh_dead );
426
        // woody parts: stem, branchse and coarse roots
427
 
428
        dead_wood.addBiomass( ( species->biomassWoody(avg_dbh_dead) + species->biomassBranch(avg_dbh_dead) + species->biomassRoot(avg_dbh_dead)) * n, species->cnWood()  );
429
        double foliage = species->biomassFoliage(avg_dbh_dead)*n;
430
 
431
        dead_fine.addBiomass( foliage, species->cnFoliage()  );
432
        dead_fine.addBiomass( foliage*species->finerootFoliageRatio(), species->cnFineroot()  );
433
    }
434
    if (!dead_wood.isEmpty() || !dead_fine.isEmpty())
802 werner 435
        if (mRUS->ru()->snag())
436
            mRUS->ru()->snag()->addToSoil(species, dead_wood, dead_fine);
595 werner 437
 
608 werner 438
    // calculate net growth:
439
    // delta of stocks
625 werner 440
    mCarbonGain = mCarbonLiving + dead_fine + dead_wood - old_state;
441
    if (mCarbonGain.C < 0)
442
        mCarbonGain.clear();
608 werner 443
 
595 werner 444
    if (mSaplingTrees.count() > mLiving*1.3)
445
        cleanupStorage();
446
 
1163 werner 447
//    mRUS->statistics().add(this);
615 werner 448
    GlobalSettings::instance()->systemStatistics()->saplingCount+=mLiving;
449
    GlobalSettings::instance()->systemStatistics()->newSaplings+=mAdded;
663 werner 450
    mAdded = 0; // reset
451
 
595 werner 452
    //qDebug() << ru->index() << species->id()<< ": (living/avg.height):" <<  mLiving << mAvgHeight;
453
}
454
 
455
/// fill a grid with the maximum height of saplings per pixel (2x2m).
456
/// this function is used for visualization only
457
void Sapling::fillMaxHeightGrid(Grid<float> &grid) const
458
{
1111 werner 459
    QVector<SaplingTreeOld>::const_iterator it;
595 werner 460
    for (it = mSaplingTrees.begin(); it!=mSaplingTrees.end(); ++it) {
461
        if (it->isValid()) {
902 werner 462
             QPoint p=it->coords();
595 werner 463
             if (grid.valueAtIndex(p)<it->height)
464
                 grid.valueAtIndex(p) = it->height;
465
        }
466
    }
467
 
468
}
600 werner 469
 
608 werner 470
 
471
 
695 werner 472