Subversion Repositories public iLand

Rev

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