Subversion Repositories public iLand

Rev

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