Subversion Repositories public iLand

Rev

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