Subversion Repositories public iLand

Rev

Rev 664 | Rev 697 | 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
 
534 werner 21
/** @class ResourceUnit
22
  ResourceUnit is the spatial unit that encapsulates a forest stand and links to several environmental components
23
  (Climate, Soil, Water, ...).
24
 
25
  */
26
#include <QtCore>
27
#include "global.h"
28
 
29
#include "resourceunit.h"
30
#include "resourceunitspecies.h"
31
#include "speciesset.h"
32
#include "species.h"
33
#include "production3pg.h"
34
#include "model.h"
35
#include "climate.h"
36
#include "watercycle.h"
37
#include "snag.h"
38
#include "soil.h"
39
#include "helper.h"
40
 
41
ResourceUnit::~ResourceUnit()
42
{
43
    if (mWater)
44
        delete mWater;
45
    mWater = 0;
46
    if (mSnag)
47
        delete mSnag;
48
    if (mSoil)
49
        delete mSoil;
50
 
51
    mSnag = 0;
52
    mSoil = 0;
53
}
54
 
55
ResourceUnit::ResourceUnit(const int index)
56
{
57
    qDeleteAll(mRUSpecies);
58
    mSpeciesSet = 0;
59
    mClimate = 0;
60
    mPixelCount=0;
61
    mStockedArea = 0;
62
    mStockedPixelCount = 0;
63
    mIndex = index;
64
    mSaplingHeightMap = 0;
65
    mEffectiveArea_perWLA = 0.;
66
    mWater = new WaterCycle();
67
    mSnag = 0;
68
    mSoil = 0;
569 werner 69
    mID = 0;
534 werner 70
}
71
 
72
void ResourceUnit::setup()
73
{
74
    mWater->setup(this);
75
 
76
    if (mSnag)
77
        delete mSnag;
78
    mSnag=0;
79
    if (mSoil)
80
        delete mSoil;
81
    mSoil=0;
82
    if (Model::settings().carbonCycleEnabled) {
591 werner 83
        mSoil = new Soil(this);
534 werner 84
        mSnag = new Snag;
85
        mSnag->setup(this);
86
        const XmlHelper &xml=GlobalSettings::instance()->settings();
87
 
88
        // setup contents of the soil of the RU; use values for C and N (kg/ha)
89
        mSoil->setInitialState(CNPool(xml.valueDouble("model.site.youngLabileC", -1),
90
                                      xml.valueDouble("model.site.youngLabileN", -1),
91
                                      xml.valueDouble("model.site.youngLabileDecompRate", -1)),
92
                               CNPool(xml.valueDouble("model.site.youngRefractoryC", -1),
93
                                      xml.valueDouble("model.site.youngRefractoryN", -1),
94
                                      xml.valueDouble("model.site.youngRefractoryDecompRate", -1)),
95
                               CNPair(xml.valueDouble("model.site.somC", -1), xml.valueDouble("model.site.somN", -1)));
96
    }
97
 
98
    // setup variables
99
    mUnitVariables.nitrogenAvailable = GlobalSettings::instance()->settings().valueDouble("model.site.availableNitrogen", 40);
100
 
101
    // if dynamic coupling of soil nitrogen is enabled, the calculate a starting value for available n.
102
    if (mSoil && Model::settings().useDynamicAvailableNitrogen && Model::settings().carbonCycleEnabled) {
103
        mSoil->setClimateFactor(1.);
104
        mSoil->calculateYear();
105
        mUnitVariables.nitrogenAvailable = mSoil->availableNitrogen();
106
    }
664 werner 107
    mHasDeadTrees = false;
534 werner 108
    mAverageAging = 0.;
109
 
110
}
111
void ResourceUnit::setBoundingBox(const QRectF &bb)
112
{
113
    mBoundingBox = bb;
114
    mCornerCoord = GlobalSettings::instance()->model()->grid()->indexAt(bb.topLeft());
115
}
116
 
117
/// set species and setup the species-per-RU-data
118
void ResourceUnit::setSpeciesSet(SpeciesSet *set)
119
{
120
    mSpeciesSet = set;
121
    qDeleteAll(mRUSpecies);
122
 
123
    //mRUSpecies.resize(set->count()); // ensure that the vector space is not relocated
124
    for (int i=0;i<set->count();i++) {
125
        Species *s = const_cast<Species*>(mSpeciesSet->species(i));
126
        if (!s)
127
            throw IException("ResourceUnit::setSpeciesSet: invalid index!");
128
 
129
        ResourceUnitSpecies *rus = new ResourceUnitSpecies();
130
        mRUSpecies.push_back(rus);
131
        rus->setup(s, this);
132
        /* be careful: setup() is called with a pointer somewhere to the content of the mRUSpecies container.
133
           If the container memory is relocated (QVector), the pointer gets invalid!!!
134
           Therefore, a resize() is called before the loop (no resize()-operations during the loop)! */
135
        //mRUSpecies[i].setup(s,this); // setup this element
136
 
137
    }
138
}
139
 
140
ResourceUnitSpecies &ResourceUnit::resourceUnitSpecies(const Species *species)
141
{
142
    return *mRUSpecies[species->index()];
143
}
144
 
145
Tree &ResourceUnit::newTree()
146
{
147
    // start simple: just append to the vector...
148
    if (mTrees.isEmpty())
149
        mTrees.reserve(100); // reserve a junk of memory for trees
150
 
151
    mTrees.append(Tree());
152
    return mTrees.back();
153
}
154
int ResourceUnit::newTreeIndex()
155
{
156
    // start simple: just append to the vector...
157
    mTrees.append(Tree());
158
    return mTrees.count()-1;
159
}
160
 
161
/// remove dead trees from tree list
162
/// reduce size of vector if lots of space is free
163
/// tests showed that this way of cleanup is very fast,
164
/// because no memory allocations are performed (simple memmove())
165
/// when trees are moved.
166
void ResourceUnit::cleanTreeList()
167
{
664 werner 168
    if (!mHasDeadTrees)
169
        return;
170
 
534 werner 171
    QVector<Tree>::iterator last=mTrees.end()-1;
172
    QVector<Tree>::iterator current = mTrees.begin();
173
    while (last>=current && (*last).isDead())
174
        --last;
175
 
176
    while (current<last) {
177
        if ((*current).isDead()) {
178
            *current = *last; // copy data!
179
            --last; //
180
            while (last>=current && (*last).isDead())
181
                --last;
182
        }
183
        ++current;
184
    }
185
    ++last; // last points now to the first dead tree
186
 
187
    // free ressources
188
    if (last!=mTrees.end()) {
189
        mTrees.erase(last, mTrees.end());
190
        if (mTrees.capacity()>100) {
191
            if (mTrees.count() / double(mTrees.capacity()) < 0.2) {
192
                //int target_size = mTrees.count()*2;
193
                //qDebug() << "reduce size from "<<mTrees.capacity() << "to" << target_size;
194
                //mTrees.reserve(qMax(target_size, 100));
664 werner 195
                if (logLevelDebug())
196
                    qDebug() << "reduce tree storage of RU" << index() << " from " << mTrees.capacity() << "to" << mTrees.count();
534 werner 197
                mTrees.squeeze();
198
            }
199
        }
200
    }
664 werner 201
    mHasDeadTrees = false; // reset flag
534 werner 202
}
203
 
204
void ResourceUnit::newYear()
205
{
206
    mAggregatedWLA = 0.;
207
    mAggregatedLA = 0.;
208
    mAggregatedLR = 0.;
209
    mEffectiveArea = 0.;
210
    mPixelCount = mStockedPixelCount = 0;
211
    snagNewYear();
609 werner 212
    if (mSoil)
213
        mSoil->newYear();
534 werner 214
    // clear statistics global and per species...
215
    QList<ResourceUnitSpecies*>::const_iterator i;
216
    QList<ResourceUnitSpecies*>::const_iterator iend = mRUSpecies.constEnd();
217
    mStatistics.clear();
218
    for (i=mRUSpecies.constBegin(); i!=iend; ++i) {
219
        (*i)->statisticsDead().clear();
220
        (*i)->statisticsMgmt().clear();
662 werner 221
        (*i)->changeSapling().newYear();
534 werner 222
    }
223
 
224
}
225
 
226
/** production() is the "stand-level" part of the biomass production (3PG).
227
    - The amount of radiation intercepted by the stand is calculated
228
    - the water cycle is calculated
229
    - statistics for each species are cleared
230
    - The 3PG production for each species and ressource unit is called (calculates species-responses and NPP production)
231
    see also: http://iland.boku.ac.at/individual+tree+light+availability */
232
void ResourceUnit::production()
233
{
234
 
235
    if (mAggregatedWLA==0 || mPixelCount==0) {
236
        // nothing to do...
237
        return;
238
    }
239
 
240
    // the pixel counters are filled during the height-grid-calculations
241
    mStockedArea = 100. * mStockedPixelCount; // m2 (1 height grid pixel = 10x10m)
242
 
243
    // calculate the leaf area index (LAI)
244
    double LAI = mAggregatedLA / mStockedArea;
245
    // calculate the intercepted radiation fraction using the law of Beer Lambert
246
    const double k = Model::settings().lightExtinctionCoefficient;
247
    double interception_fraction = 1. - exp(-k * LAI);
248
    mEffectiveArea = mStockedArea * interception_fraction; // m2
249
 
250
    // calculate the total weighted leaf area on this RU:
251
    mLRI_modification = interception_fraction *  mStockedArea / mAggregatedWLA; // p_WLA
252
    if (mLRI_modification == 0.)
253
        qDebug() << "lri modifaction==0!";
254
 
611 werner 255
    if (logLevelDebug()) {
534 werner 256
    DBGMODE(qDebug() << QString("production: LAI: %1 (intercepted fraction: %2, stocked area: %4). LRI-Multiplier: %3")
257
            .arg(LAI)
258
            .arg(interception_fraction)
259
            .arg(mLRI_modification)
260
            .arg(mStockedArea);
261
    );
611 werner 262
    }
534 werner 263
 
264
    // calculate LAI fractions
265
    QList<ResourceUnitSpecies*>::const_iterator i;
266
    QList<ResourceUnitSpecies*>::const_iterator iend = mRUSpecies.constEnd();
267
    double ru_lai = leafAreaIndex();
268
    if (ru_lai < 1.)
269
        ru_lai = 1.;
270
    // note: LAIFactors are only 1 if sum of LAI is > 1. (see WaterCycle)
271
    for (i=mRUSpecies.constBegin(); i!=iend; ++i) {
272
         (*i)->setLAIfactor((*i)->statistics().leafAreaIndex() / ru_lai);
273
    }
274
 
275
    // soil water model - this determines soil water contents needed for response calculations
276
    {
277
    mWater->run();
278
    }
279
 
280
    // invoke species specific calculation (3PG)
281
    for (i=mRUSpecies.constBegin(); i!=iend; ++i) {
282
        (*i)->calculate(); // CALCULATE 3PG
283
        if (logLevelInfo() &&  (*i)->LAIfactor()>0)
284
            qDebug() << "ru" << mIndex << "species" << (*i)->species()->id() << "LAIfraction" << (*i)->LAIfactor() << "raw_gpp_m2"
285
                     << (*i)->prod3PG().GPPperArea() << "area:" << productiveArea() << "gpp:"
286
                     << productiveArea()*(*i)->prod3PG().GPPperArea()
287
                     << "aging(lastyear):" << averageAging() << "f_env,yr:" << (*i)->prod3PG().fEnvYear();
288
    }
289
}
290
 
291
void ResourceUnit::calculateInterceptedArea()
292
{
293
    if (mAggregatedLR==0) {
294
        mEffectiveArea_perWLA = 0.;
295
        return;
296
    }
297
    Q_ASSERT(mAggregatedLR>0.);
298
    mEffectiveArea_perWLA = mEffectiveArea / mAggregatedLR;
299
    if (logLevelDebug()) qDebug() << "RU: aggregated lightresponse:" << mAggregatedLR  << "eff.area./wla:" << mEffectiveArea_perWLA;
300
}
301
 
302
// function is called immediately before the growth of individuals
303
void ResourceUnit::beforeGrow()
304
{
305
    mAverageAging = 0.;
306
}
307
 
308
// function is called after finishing the indivdual growth / mortality.
309
void ResourceUnit::afterGrow()
310
{
311
    mAverageAging = leafArea()>0.?mAverageAging/leafArea():0; // calculate aging value (calls to addAverageAging() by individual trees)
312
    if (mAverageAging>0. && mAverageAging<0.00001)
313
        qDebug() << "ru" << mIndex << "aging <0.00001";
314
    if (mAverageAging<0. || mAverageAging>1.)
315
        qDebug() << "Average aging invalid: (RU, LAI):" << index() << mStatistics.leafAreaIndex();
316
}
317
 
318
void ResourceUnit::yearEnd()
319
{
320
    // calculate statistics for all tree species of the ressource unit
321
    int c = mRUSpecies.count();
322
    for (int i=0;i<c; i++) {
323
        mRUSpecies[i]->statisticsDead().calculate(); // calculate the dead trees
324
        mRUSpecies[i]->statisticsMgmt().calculate(); // stats of removed trees
325
        mRUSpecies[i]->updateGWL(); // get sum of dead trees (died + removed)
326
        mRUSpecies[i]->statistics().calculate(); // calculate the living (and add removed volume to gwl)
327
        mStatistics.add(mRUSpecies[i]->statistics());
328
    }
329
    mStatistics.calculate(); // aggreagte on stand level
330
 
331
}
332
 
333
void ResourceUnit::addTreeAgingForAllTrees()
334
{
335
    mAverageAging = 0.;
336
    foreach(const Tree &t, mTrees) {
337
        addTreeAging(t.leafArea(), t.species()->aging(t.height(), t.age()));
338
    }
339
 
340
}
341
 
342
/// refresh of tree based statistics.
343
/// WARNING: this function is only called once (during startup).
344
/// see function "yearEnd()" above!!!
345
void ResourceUnit::createStandStatistics()
346
{
347
    // clear statistics (ru-level and ru-species level)
348
    mStatistics.clear();
349
    for (int i=0;i<mRUSpecies.count();i++) {
350
        mRUSpecies[i]->statistics().clear();
351
        mRUSpecies[i]->statisticsDead().clear();
352
        mRUSpecies[i]->statisticsMgmt().clear();
353
    }
354
 
355
    // add all trees to the statistics objects of the species
356
    foreach(const Tree &t, mTrees) {
357
        if (!t.isDead())
358
            resourceUnitSpecies(t.species()).statistics().add(&t, 0);
359
    }
360
    // summarize statistics for the whole resource unit
361
    for (int i=0;i<mRUSpecies.count();i++) {
362
        mRUSpecies[i]->statistics().calculate();
363
        mStatistics.add(mRUSpecies[i]->statistics());
364
    }
365
    mStatistics.calculate();
575 werner 366
    mAverageAging = mStatistics.leafAreaIndex()>0.?mAverageAging / (mStatistics.leafAreaIndex()*stockableArea()):0.;
534 werner 367
    if (mAverageAging<0. || mAverageAging>1.)
368
        qDebug() << "Average aging invalid: (RU, LAI):" << index() << mStatistics.leafAreaIndex();
369
}
370
 
371
void ResourceUnit::setMaxSaplingHeightAt(const QPoint &position, const float height)
372
{
373
    Q_ASSERT(mSaplingHeightMap);
374
    int pixel_index = cPxPerRU*(position.x()-mCornerCoord.x())+(position.y()-mCornerCoord.y());
375
    if (pixel_index<0 || pixel_index>=cPxPerRU*cPxPerRU) {
376
        qDebug() << "setSaplingHeightAt-Error for position" << position << "for RU at" << boundingBox() << "with corner" << mCornerCoord;
377
    } else {
378
        if (mSaplingHeightMap[pixel_index]<height)
379
            mSaplingHeightMap[pixel_index]=height;
380
    }
381
}
382
 
383
/// clear all saplings of all species on a given position (after recruitment)
384
void ResourceUnit::clearSaplings(const QPoint &position)
385
{
386
    foreach(ResourceUnitSpecies* rus, mRUSpecies)
387
        rus->clearSaplings(position);
388
 
389
}
390
 
662 werner 391
/// kill all saplings within a given rect
392
void ResourceUnit::clearSaplings(const QRectF pixel_rect, const bool remove_from_soil)
393
{
394
    foreach(ResourceUnitSpecies* rus, mRUSpecies) {
395
        rus->changeSapling().clearSaplings(pixel_rect, remove_from_soil);
396
    }
397
 
398
}
399
 
600 werner 400
float ResourceUnit::saplingHeightForInit(const QPoint &position) const
401
{
402
    double maxh = 0.;
403
    foreach(ResourceUnitSpecies* rus, mRUSpecies)
404
        maxh = qMax(maxh, rus->sapling().heightAt(position));
405
    return maxh;
406
}
534 werner 407
 
408
void ResourceUnit::calculateCarbonCycle()
409
{
410
    if (!snag())
411
        return;
412
 
413
    // (1) calculate the snag dynamics
414
    // because all carbon/nitrogen-flows from trees to the soil are routed through the snag-layer,
415
    // all soil inputs (litter + deadwood) are collected in the Snag-object.
416
    snag()->calculateYear();
417
    soil()->setClimateFactor( snag()->climateFactor() ); // the climate factor is only calculated once
418
    soil()->setSoilInput( snag()->labileFlux(), snag()->refractoryFlux());
419
    soil()->calculateYear(); // update the ICBM/2N model
420
    // use available nitrogen?
421
    if (Model::settings().useDynamicAvailableNitrogen)
422
        mUnitVariables.nitrogenAvailable = soil()->availableNitrogen();
423
 
424
    // debug output
425
    if (GlobalSettings::instance()->isDebugEnabled(GlobalSettings::dCarbonCycle) && !snag()->isEmpty()) {
426
        DebugList &out = GlobalSettings::instance()->debugList(index(), GlobalSettings::dCarbonCycle);
605 werner 427
        out << index() << id(); // resource unit index and id
534 werner 428
        out << snag()->debugList(); // snag debug outs
429
        out << soil()->debugList(); // ICBM/2N debug outs
430
    }
431
 
432
}
600 werner 433
 
434