Subversion Repositories public iLand

Rev

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

Rev Author Line No. Line
1
 
595 werner 2
#include "sapling.h"
3
#include "model.h"
4
#include "species.h"
5
#include "resourceunit.h"
6
#include "resourceunitspecies.h"
7
#include "tree.h"
8
 
9
/** @class Sapling
10
    Sapling stores saplings per species and resource unit and computes sapling growth (before recruitment).
11
    http://iland.boku.ac.at/sapling+growth+and+competition
12
    Saplings are established in a separate step (@sa Regeneration). If sapling reach a height of 4m, they are recruited and become "real" iLand-trees.
13
    Within the regeneration layer, a cohort-approach is applied.
14
 
15
  */
16
 
17
double Sapling::mRecruitmentVariation = 0.1; // +/- 10%
18
 
19
Sapling::Sapling()
20
{
21
    mRUS = 0;
22
    clearStatistics();
23
}
24
 
25
/// get the *represented* (Reineke's Law) number of trees (N/ha)
26
double Sapling::livingStemNumber(double &rAvgDbh, double &rAvgHeight, double &rAvgAge) const
27
{
28
    double total = 0.;
29
    double dbh_sum = 0.;
30
    double h_sum = 0.;
31
    double age_sum = 0.;
32
    const SaplingGrowthParameters &p = mRUS->species()->saplingGrowthParameters();
33
    for (QVector<SaplingTree>::const_iterator it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
34
        float dbh = it->height / p.hdSapling * 100.f;
35
        if (dbh<1.) // minimum size: 1cm
36
            continue;
37
        double n = p.representedStemNumber(dbh); // one cohort on the pixel represents that number of trees
38
        dbh_sum += n*dbh;
39
        h_sum += n*it->height;
40
        age_sum += n*it->age.age;
41
        total += n;
42
    }
43
    if (total>0.) {
44
        dbh_sum /= total;
45
        h_sum /= total;
46
        age_sum /= total;
47
    }
48
    rAvgDbh = dbh_sum;
49
    rAvgHeight = h_sum;
50
    rAvgAge = age_sum;
51
    return total;
52
}
53
 
54
/// maintenance function to clear dead/recruited saplings from storage
55
void Sapling::cleanupStorage()
56
{
57
    QVector<SaplingTree>::iterator forw=mSaplingTrees.begin();
58
    QVector<SaplingTree>::iterator back;
59
 
60
    // seek last valid
61
    for (back=mSaplingTrees.end()-1; back>=mSaplingTrees.begin(); --back)
62
        if ((*back).isValid())
63
            break;
64
 
65
    if (back<mSaplingTrees.begin()) {
66
        mSaplingTrees.clear(); // no valid trees available
67
        return;
68
    }
69
 
70
    while (forw < back) {
71
        if (!(*forw).isValid()) {
72
            *forw = *back; // copy (fill gap)
73
            while (back>forw) // seek next valid
74
                if ((*--back).isValid())
75
                    break;
76
        }
77
        ++forw;
78
    }
79
    if (back != mSaplingTrees.end()-1) {
80
        // free resources...
81
        mSaplingTrees.erase(back+1, mSaplingTrees.end());
82
    }
83
}
84
 
85
// not a very good way of checking if sapling is present
86
// maybe better: use also a (local) maximum sapling height grid
87
// maybe better: use a bitset:
88
// position: index of pixel on LIF (absolute index)
89
bool Sapling::hasSapling(const QPoint &position) const
90
{
91
    const QPoint &offset = mRUS->ru()->cornerPointOffset();
92
    int index = (position.x()- offset.x())*cPxPerRU + (position.y() - offset.y());
93
    return mSapBitset[index];
94
    /*
95
    float *target = GlobalSettings::instance()->model()->grid()->ptr(position.x(), position.y());
96
    QVector<SaplingTree>::const_iterator it;
97
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
98
        if (it->pixel==target)
99
            return true;
100
    }
101
    return false;
102
    */
103
}
104
 
600 werner 105
/// retrieve the height of the sapling at the location 'position' (given in LIF-coordinates)
106
/// this is quite expensive and only done for initialization
107
double Sapling::heightAt(const QPoint &position) const
108
{
109
    if (!hasSapling(position))
110
        return 0.;
111
    // ok, we'll have to search through all saplings
112
    QVector<SaplingTree>::const_iterator it;
113
    float *lif_ptr = GlobalSettings::instance()->model()->grid()->ptr(position.x(), position.y());
114
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
115
        if (it->isValid() && it->pixel == lif_ptr)
116
            return it->height;
117
    }
118
    return 0.;
595 werner 119
 
600 werner 120
}
121
 
122
 
595 werner 123
void Sapling::addSapling(const QPoint &pos_lif)
124
{
125
    // adds a sapling...
126
    mSaplingTrees.push_back(SaplingTree());
127
    SaplingTree &t = mSaplingTrees.back();
128
    t.height = 0.05; // start with 5cm height
129
    Grid<float> &lif_map = *GlobalSettings::instance()->model()->grid();
130
    t.pixel = lif_map.ptr(pos_lif.x(), pos_lif.y());
131
    int index = (pos_lif.x() - mRUS->ru()->cornerPointOffset().x()) * cPxPerRU +(pos_lif.y() - mRUS->ru()->cornerPointOffset().y());
132
    mSapBitset.set(index,true); // set bit: now there is a sapling there
133
    mAdded++;
134
}
135
 
136
/// clear  saplings on a given position (after recruitment)
137
void Sapling::clearSaplings(const QPoint &position)
138
{
139
    float *target = GlobalSettings::instance()->model()->grid()->ptr(position.x(), position.y());
140
    QVector<SaplingTree>::const_iterator it;
141
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
142
        if (it->pixel==target) {
143
            // trick: use a const iterator to avoid a deep copy of the vector; then do an ugly const_cast to actually write the data
144
            const SaplingTree &t = *it;
145
            const_cast<SaplingTree&>(t).pixel=0;
146
        }
147
    }
148
    int index = (position.x() - mRUS->ru()->cornerPointOffset().x()) * cPxPerRU +(position.y() - mRUS->ru()->cornerPointOffset().y());
149
    mSapBitset.set(index,false); // clear bit: now there is no sapling on this position
150
 
151
}
152
 
153
/// growth function for an indivudal sapling.
154
/// returns true, if sapling survives, false if sapling dies or is recruited to iLand.
155
/// see also http://iland.boku.ac.at/recruitment
156
bool Sapling::growSapling(SaplingTree &tree, const double f_env_yr, Species* species)
157
{
158
    QPoint p=GlobalSettings::instance()->model()->grid()->indexOf(tree.pixel);
159
 
160
    // (1) calculate height growth potential for the tree (uses linerization of expressions...)
161
    double h_pot = species->saplingGrowthParameters().heightGrowthPotential.calculate(tree.height);
162
    double delta_h_pot = h_pot - tree.height;
163
 
164
    // (2) reduce height growth potential with species growth response f_env_yr and with light state (i.e. LIF-value) of home-pixel.
165
    double lif_value = *tree.pixel;
166
    double h_height_grid = GlobalSettings::instance()->model()->heightGrid()->valueAtIndex(p.x()/cPxPerHeight, p.y()/cPxPerHeight).height;
167
    if (h_height_grid==0)
168
        throw IException(QString("growSapling: height grid at %1/%2 has value 0").arg(p.x()).arg(p.y()));
169
    double rel_height = 4. / h_height_grid;
170
 
171
    double lif_corrected = mRUS->species()->speciesSet()->LRIcorrection(lif_value, rel_height); // correction based on height
172
    // Note: difference to trees: no "LRIcorrection"
173
    double lr = mRUS->species()->lightResponse(lif_corrected); // species specific light response (LUI, light utilization index)
174
 
175
    double delta_h_factor = f_env_yr * lr; // relative growth
176
 
177
    if (h_pot<0. || delta_h_pot<0. || lif_corrected<0. || lif_corrected>1. || delta_h_factor<0. || delta_h_factor>1. )
178
        qDebug() << "invalid values in Sapling::growSapling";
179
 
180
    // check mortality of saplings
181
    if (delta_h_factor < species->saplingGrowthParameters().stressThreshold) {
182
        tree.age.stress_years++;
183
        if (tree.age.stress_years > species->saplingGrowthParameters().maxStressYears) {
184
            // sapling dies...
185
            tree.pixel=0;
186
            mDied++;
187
            mSumDbhDied+=tree.height / mRUS->species()->saplingGrowthParameters().hdSapling * 100.;
188
            return false;
189
        }
190
    } else {
191
        tree.age.stress_years=0; // reset stress counter
192
    }
193
    DBG_IF(delta_h_pot*delta_h_factor < 0.f || delta_h_pot*delta_h_factor > 2., "Sapling::growSapling", "inplausible height growth.");
194
 
195
    // grow
196
    tree.height += delta_h_pot * delta_h_factor;
197
    tree.age.age++; // increase age of sapling by 1
198
 
199
    // recruitment?
200
    if (tree.height > 4.f) {
201
        mRecruited++;
202
 
203
        ResourceUnit *ru = const_cast<ResourceUnit*> (mRUS->ru());
204
        float dbh = tree.height / species->saplingGrowthParameters().hdSapling * 100.f;
205
        // the number of trees to create (result is in trees per pixel)
206
        double n_trees = species->saplingGrowthParameters().representedStemNumber(dbh);
207
        int to_establish = (int) n_trees;
208
        // if n_trees is not an integer, choose randomly if we should add a tree.
209
        // e.g.: n_trees = 2.3 -> add 2 trees with 70% probability, and add 3 trees with p=30%.
210
        if (drandom() < (n_trees-to_establish) || to_establish==0)
211
            to_establish++;
212
 
213
        // add a new tree
214
        for (int i=0;i<to_establish;i++) {
215
            Tree &bigtree = ru->newTree();
216
            bigtree.setPosition(p);
217
            // add variation: add +/-10% to dbh and *independently* to height.
218
            bigtree.setDbh(dbh * nrandom(1. - mRecruitmentVariation, 1. + mRecruitmentVariation));
219
            bigtree.setHeight(tree.height * nrandom(1. - mRecruitmentVariation, 1. + mRecruitmentVariation));
220
            bigtree.setSpecies( species );
221
            bigtree.setAge(tree.age.age,tree.height);
222
            bigtree.setRU(ru);
223
            bigtree.setup();
224
        }
225
        // clear all regeneration from this pixel (including this tree)
226
        ru->clearSaplings(p);
227
        return false;
228
    }
229
    // book keeping (only for survivors)
230
    mLiving++;
231
    mAvgHeight+=tree.height;
232
    mAvgAge+=tree.age.age;
233
    mAvgDeltaHPot+=delta_h_pot;
234
    mAvgHRealized += delta_h_pot * delta_h_factor;
235
    return true;
236
 
237
}
238
 
608 werner 239
 
595 werner 240
void Sapling::calculateGrowth()
241
{
242
    Q_ASSERT(mRUS);
243
    if (mLiving==0 && mAdded==0)
244
        return;
245
 
246
    clearStatistics();
247
    ResourceUnit *ru = const_cast<ResourceUnit*> (mRUS->ru() );
248
    Species *species = const_cast<Species*>(mRUS->species());
249
 
250
    // calculate necessary growth modifier (this is done only once per year)
251
    mRUS->calculate(true); // calculate the 3pg module (this is done only if that did not happen up to now); true: call comes from regeneration
252
    double f_env_yr = mRUS->prod3PG().fEnvYear();
253
 
254
    mLiving=0;
255
    QVector<SaplingTree>::const_iterator it;
256
    for (it = mSaplingTrees.constBegin(); it!=mSaplingTrees.constEnd(); ++it) {
257
        const SaplingTree &tree = *it;
258
        if (tree.height<0)
259
            qDebug() << "Sapling::calculateGrowth(): h<0";
260
        if (tree.isValid()) {
261
            // growing
262
            if (growSapling(const_cast<SaplingTree&>(tree), f_env_yr, species)) {
263
                // set the sapling height to the maximum value on the current pixel
264
                QPoint p=GlobalSettings::instance()->model()->grid()->indexOf(tree.pixel);
265
                ru->setMaxSaplingHeightAt(p,tree.height);
266
            }
267
        }
268
    }
269
    if (mLiving) {
270
        mAvgHeight /= double(mLiving);
271
        mAvgAge /= double(mLiving);
272
        mAvgDeltaHPot /= double(mLiving);
273
        mAvgHRealized /= double(mLiving);
274
    }
275
    // calculate carbon balance
608 werner 276
    CNPair old_state = mCarbonLiving;
595 werner 277
    mCarbonLiving.clear();
608 werner 278
 
595 werner 279
    CNPair dead_wood, dead_fine; // pools for mortality
280
    // average dbh
281
    if (mLiving) {
282
        // calculate the avg dbh number of stems
283
        double avg_dbh = mAvgHeight / species->saplingGrowthParameters().hdSapling * 100.;
284
        double n = mLiving * species->saplingGrowthParameters().representedStemNumber( avg_dbh );
285
        // woody parts: stem, branchse and coarse roots
286
        double woody_bm = species->biomassWoody(avg_dbh) + species->biomassBranch(avg_dbh) + species->biomassRoot(avg_dbh);
287
        double foliage = species->biomassFoliage(avg_dbh);
288
        double fineroot = foliage*species->finerootFoliageRatio();
289
 
290
        mCarbonLiving.addBiomass( woody_bm*n, species->cnWood()  );
291
        mCarbonLiving.addBiomass( foliage*n, species->cnFoliage()  );
292
        mCarbonLiving.addBiomass( fineroot*n, species->cnFineroot()  );
608 werner 293
 
595 werner 294
        // turnover
295
        mRUS->ru()->snag()->addTurnoverLitter(species, foliage*species->turnoverLeaf(), fineroot*species->turnoverRoot());
296
 
297
        // calculate the "mortality from competition", i.e. carbon that stems from reduction of stem numbers
298
        // from Reinekes formula.
299
        //
300
        if (avg_dbh>1.) {
301
            double avg_dbh_before = (mAvgHeight - mAvgHRealized) / species->saplingGrowthParameters().hdSapling * 100.;
302
            double n_before = mLiving * species->saplingGrowthParameters().representedStemNumber( qMax(1.,avg_dbh_before) );
303
            if (n<n_before) {
304
                dead_wood.addBiomass( woody_bm * (n_before-n), species->cnWood() );
305
                dead_fine.addBiomass( foliage * (n_before-n), species->cnFoliage()  );
306
                dead_fine.addBiomass( fineroot * (n_before-n), species->cnFineroot()  );
307
            }
308
        }
309
 
310
    }
311
    if (mDied) {
312
        double avg_dbh_dead = mSumDbhDied / double(mDied);
313
        double n = mDied * species->saplingGrowthParameters().representedStemNumber( avg_dbh_dead );
314
        // woody parts: stem, branchse and coarse roots
315
 
316
        dead_wood.addBiomass( ( species->biomassWoody(avg_dbh_dead) + species->biomassBranch(avg_dbh_dead) + species->biomassRoot(avg_dbh_dead)) * n, species->cnWood()  );
317
        double foliage = species->biomassFoliage(avg_dbh_dead)*n;
318
 
319
        dead_fine.addBiomass( foliage, species->cnFoliage()  );
320
        dead_fine.addBiomass( foliage*species->finerootFoliageRatio(), species->cnFineroot()  );
321
    }
322
    if (!dead_wood.isEmpty() || !dead_fine.isEmpty())
323
        mRUS->ru()->snag()->addToSoil(species, dead_wood, dead_fine);
324
 
608 werner 325
    // calculate net growth:
326
    // delta of stocks
327
    mCarbonGain = mCarbonLiving + dead_fine + dead_wood - old_state;
328
 
595 werner 329
    if (mSaplingTrees.count() > mLiving*1.3)
330
        cleanupStorage();
331
 
332
    mRUS->statistics().add(this);
333
    //qDebug() << ru->index() << species->id()<< ": (living/avg.height):" <<  mLiving << mAvgHeight;
334
}
335
 
336
/// fill a grid with the maximum height of saplings per pixel (2x2m).
337
/// this function is used for visualization only
338
void Sapling::fillMaxHeightGrid(Grid<float> &grid) const
339
{
340
    QVector<SaplingTree>::const_iterator it;
341
    for (it = mSaplingTrees.begin(); it!=mSaplingTrees.end(); ++it) {
342
        if (it->isValid()) {
343
             QPoint p=GlobalSettings::instance()->model()->grid()->indexOf(it->pixel);
344
             if (grid.valueAtIndex(p)<it->height)
345
                 grid.valueAtIndex(p) = it->height;
346
        }
347
    }
348
 
349
}
600 werner 350
 
608 werner 351
 
352