Subversion Repositories public iLand

Rev

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