Subversion Repositories public iLand

Rev

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