Rev 496 | Rev 503 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | |||
189 | iland | 2 | /** @class ResourceUnit |
3 | ResourceUnit is the spatial unit that encapsulates a forest stand and links to several environmental components |
||
92 | Werner | 4 | (Climate, Soil, Water, ...). |
5 | |||
6 | */ |
||
7 | #include <QtCore> |
||
8 | #include "global.h" |
||
9 | |||
189 | iland | 10 | #include "resourceunit.h" |
229 | werner | 11 | #include "resourceunitspecies.h" |
111 | Werner | 12 | #include "speciesset.h" |
13 | #include "species.h" |
||
113 | Werner | 14 | #include "production3pg.h" |
200 | werner | 15 | #include "model.h" |
208 | werner | 16 | #include "climate.h" |
241 | werner | 17 | #include "watercycle.h" |
18 | #include "helper.h" |
||
92 | Werner | 19 | |
241 | werner | 20 | ResourceUnit::~ResourceUnit() |
21 | { |
||
22 | if (mWater) |
||
23 | delete mWater; |
||
24 | } |
||
111 | Werner | 25 | |
189 | iland | 26 | ResourceUnit::ResourceUnit(const int index) |
92 | Werner | 27 | { |
455 | werner | 28 | qDeleteAll(mRUSpecies); |
94 | Werner | 29 | mSpeciesSet = 0; |
208 | werner | 30 | mClimate = 0; |
331 | werner | 31 | mPixelCount=0; |
453 | werner | 32 | mStockedArea = 0; |
33 | mStockedPixelCount = 0; |
||
113 | Werner | 34 | mIndex = index; |
450 | werner | 35 | mSaplingHeightMap = 0; |
482 | werner | 36 | mEffectiveArea_perWLA = 0.; |
241 | werner | 37 | mWater = new WaterCycle(); |
38 | |||
157 | werner | 39 | mTrees.reserve(100); // start with space for 100 trees. |
92 | Werner | 40 | } |
105 | Werner | 41 | |
241 | werner | 42 | void ResourceUnit::setup() |
43 | { |
||
44 | mWater->setup(this); |
||
281 | werner | 45 | // setup variables |
46 | mUnitVariables.nitrogenAvailable = GlobalSettings::instance()->settings().valueDouble("model.site.availableNitrogen", 40); |
||
376 | werner | 47 | mAverageAging = 0.; |
281 | werner | 48 | |
241 | werner | 49 | } |
451 | werner | 50 | void ResourceUnit::setBoundingBox(const QRectF &bb) |
51 | { |
||
52 | mBoundingBox = bb; |
||
53 | mCornerCoord = GlobalSettings::instance()->model()->grid()->indexAt(bb.topLeft()); |
||
54 | } |
||
241 | werner | 55 | |
111 | Werner | 56 | /// set species and setup the species-per-RU-data |
189 | iland | 57 | void ResourceUnit::setSpeciesSet(SpeciesSet *set) |
111 | Werner | 58 | { |
59 | mSpeciesSet = set; |
||
455 | werner | 60 | qDeleteAll(mRUSpecies); |
61 | |||
62 | //mRUSpecies.resize(set->count()); // ensure that the vector space is not relocated |
||
111 | Werner | 63 | for (int i=0;i<set->count();i++) { |
64 | Species *s = const_cast<Species*>(mSpeciesSet->species(i)); |
||
65 | if (!s) |
||
189 | iland | 66 | throw IException("ResourceUnit::setSpeciesSet: invalid index!"); |
229 | werner | 67 | |
455 | werner | 68 | ResourceUnitSpecies *rus = new ResourceUnitSpecies(); |
69 | mRUSpecies.push_back(rus); |
||
70 | rus->setup(s, this); |
||
229 | werner | 71 | /* be careful: setup() is called with a pointer somewhere to the content of the mRUSpecies container. |
72 | If the container memory is relocated (QVector), the pointer gets invalid!!! |
||
73 | Therefore, a resize() is called before the loop (no resize()-operations during the loop)! */ |
||
455 | werner | 74 | //mRUSpecies[i].setup(s,this); // setup this element |
277 | werner | 75 | |
111 | Werner | 76 | } |
77 | } |
||
78 | |||
200 | werner | 79 | ResourceUnitSpecies &ResourceUnit::resourceUnitSpecies(const Species *species) |
111 | Werner | 80 | { |
455 | werner | 81 | return *mRUSpecies[species->index()]; |
111 | Werner | 82 | } |
83 | |||
189 | iland | 84 | Tree &ResourceUnit::newTree() |
105 | Werner | 85 | { |
86 | // start simple: just append to the vector... |
||
87 | mTrees.append(Tree()); |
||
88 | return mTrees.back(); |
||
89 | } |
||
287 | werner | 90 | int ResourceUnit::newTreeIndex() |
91 | { |
||
92 | // start simple: just append to the vector... |
||
93 | mTrees.append(Tree()); |
||
94 | return mTrees.count()-1; |
||
95 | } |
||
107 | Werner | 96 | |
157 | werner | 97 | /// remove dead trees from tree list |
98 | /// reduce size of vector if lots of space is free |
||
99 | /// tests showed that this way of cleanup is very fast, |
||
100 | /// because no memory allocations are performed (simple memmove()) |
||
101 | /// when trees are moved. |
||
189 | iland | 102 | void ResourceUnit::cleanTreeList() |
157 | werner | 103 | { |
104 | QVector<Tree>::iterator last=mTrees.end()-1; |
||
105 | QVector<Tree>::iterator current = mTrees.begin(); |
||
158 | werner | 106 | while (last>=current && (*last).isDead()) |
157 | werner | 107 | --last; |
107 | Werner | 108 | |
157 | werner | 109 | while (current<last) { |
158 | werner | 110 | if ((*current).isDead()) { |
157 | werner | 111 | *current = *last; // copy data! |
112 | --last; // |
||
158 | werner | 113 | while (last>=current && (*last).isDead()) |
157 | werner | 114 | --last; |
115 | } |
||
116 | ++current; |
||
117 | } |
||
118 | ++last; // last points now to the first dead tree |
||
119 | |||
120 | // free ressources |
||
278 | werner | 121 | if (last!=mTrees.end()) { |
122 | mTrees.erase(last, mTrees.end()); |
||
123 | if (mTrees.capacity()>100) { |
||
124 | if (mTrees.count() / double(mTrees.capacity()) < 0.2) { |
||
484 | werner | 125 | //int target_size = mTrees.count()*2; |
126 | //qDebug() << "reduce size from "<<mTrees.capacity() << "to" << target_size; |
||
127 | //mTrees.reserve(qMax(target_size, 100)); |
||
128 | qDebug() << "reduce tree storage of RU" << index() << " from " << mTrees.capacity() << "to" << mTrees.count(); |
||
129 | mTrees.squeeze(); |
||
278 | werner | 130 | } |
157 | werner | 131 | } |
132 | } |
||
133 | } |
||
134 | |||
189 | iland | 135 | void ResourceUnit::newYear() |
107 | Werner | 136 | { |
251 | werner | 137 | mAggregatedWLA = 0.; |
138 | mAggregatedLA = 0.; |
||
139 | mAggregatedLR = 0.; |
||
140 | mEffectiveArea = 0.; |
||
482 | werner | 141 | mAverageAging = 0.; |
151 | iland | 142 | mPixelCount = mStockedPixelCount = 0; |
111 | Werner | 143 | // clear statistics global and per species... |
455 | werner | 144 | QList<ResourceUnitSpecies*>::const_iterator i; |
145 | QList<ResourceUnitSpecies*>::const_iterator iend = mRUSpecies.constEnd(); |
||
278 | werner | 146 | mStatistics.clear(); |
455 | werner | 147 | for (i=mRUSpecies.constBegin(); i!=iend; ++i) { |
148 | (*i)->statisticsDead().clear(); |
||
149 | (*i)->statisticsMgmt().clear(); |
||
475 | werner | 150 | (*i)->snagNewYear(); |
278 | werner | 151 | } |
152 | |||
107 | Werner | 153 | } |
110 | Werner | 154 | |
112 | Werner | 155 | /** production() is the "stand-level" part of the biomass production (3PG). |
156 | - The amount of radiation intercepted by the stand is calculated |
||
331 | werner | 157 | - the water cycle is calculated |
158 | - statistics for each species are cleared |
||
159 | - The 3PG production for each species and ressource unit is called (calculates species-responses and NPP production) |
||
298 | werner | 160 | see also: http://iland.boku.ac.at/individual+tree+light+availability */ |
189 | iland | 161 | void ResourceUnit::production() |
110 | Werner | 162 | { |
241 | werner | 163 | |
151 | iland | 164 | if (mAggregatedWLA==0 || mPixelCount==0) { |
112 | Werner | 165 | // nothing to do... |
166 | return; |
||
167 | } |
||
151 | iland | 168 | |
169 | // the pixel counters are filled during the height-grid-calculations |
||
230 | werner | 170 | mStockedArea = 100. * mStockedPixelCount; // m2 (1 height grid pixel = 10x10m) |
171 | |||
112 | Werner | 172 | // calculate the leaf area index (LAI) |
151 | iland | 173 | double LAI = mAggregatedLA / mStockedArea; |
112 | Werner | 174 | // calculate the intercepted radiation fraction using the law of Beer Lambert |
200 | werner | 175 | const double k = Model::settings().lightExtinctionCoefficient; |
112 | Werner | 176 | double interception_fraction = 1. - exp(-k * LAI); |
251 | werner | 177 | mEffectiveArea = mStockedArea * interception_fraction; // m2 |
112 | Werner | 178 | |
230 | werner | 179 | // calculate the total weighted leaf area on this RU: |
251 | werner | 180 | mLRI_modification = interception_fraction * mStockedArea / mAggregatedWLA; |
265 | werner | 181 | if (mLRI_modification == 0.) |
182 | qDebug() << "lri modifaction==0!"; |
||
205 | werner | 183 | |
251 | werner | 184 | |
185 | DBGMODE(qDebug() << QString("production: LAI: %1 (intercepted fraction: %2, stocked area: %4). LRI-Multiplier: %3") |
||
230 | werner | 186 | .arg(LAI) |
187 | .arg(interception_fraction) |
||
251 | werner | 188 | .arg(mLRI_modification) |
230 | werner | 189 | .arg(mStockedArea); |
190 | ); |
||
367 | werner | 191 | |
192 | // calculate LAI fractions |
||
455 | werner | 193 | QList<ResourceUnitSpecies*>::const_iterator i; |
194 | QList<ResourceUnitSpecies*>::const_iterator iend = mRUSpecies.constEnd(); |
||
502 | werner | 195 | double ru_lai = leafAreaIndex(); |
196 | if (ru_lai < 1.) |
||
197 | ru_lai = 1.; |
||
198 | // note: LAIFactors are only 1 if sum of LAI is > 1. (see WaterCycle) |
||
455 | werner | 199 | for (i=mRUSpecies.constBegin(); i!=iend; ++i) { |
502 | werner | 200 | (*i)->setLAIfactor((*i)->statistics().leafAreaIndex() / ru_lai); |
367 | werner | 201 | } |
202 | |||
241 | werner | 203 | // soil water model - this determines soil water contents needed for response calculations |
204 | { |
||
205 | DebugTimer tw("water:run"); |
||
206 | mWater->run(); |
||
207 | } |
||
112 | Werner | 208 | |
209 | // invoke species specific calculation (3PG) |
||
455 | werner | 210 | for (i=mRUSpecies.constBegin(); i!=iend; ++i) { |
496 | werner | 211 | (*i)->calculate(); // CALCULATE 3PG |
455 | werner | 212 | if (logLevelInfo() && (*i)->LAIfactor()>0) |
213 | qDebug() << "ru" << mIndex << "species" << (*i)->species()->id() << "LAIfraction" << (*i)->LAIfactor() << "raw_gpp_m2" |
||
214 | << (*i)->prod3PG().GPPperArea() << "area:" << productiveArea() << "gpp:" |
||
215 | << productiveArea()*(*i)->prod3PG().GPPperArea() |
||
216 | << "aging(lastyear):" << averageAging() << "f_env,yr:" << (*i)->prod3PG().fEnvYear(); |
||
112 | Werner | 217 | } |
110 | Werner | 218 | } |
219 | |||
251 | werner | 220 | void ResourceUnit::calculateInterceptedArea() |
221 | { |
||
265 | werner | 222 | if (mAggregatedLR==0) { |
223 | mEffectiveArea_perWLA = 0.; |
||
224 | return; |
||
225 | } |
||
251 | werner | 226 | Q_ASSERT(mAggregatedLR>0.); |
227 | mEffectiveArea_perWLA = mEffectiveArea / mAggregatedLR; |
||
431 | werner | 228 | if (logLevelDebug()) qDebug() << "RU: aggregated lightresponse:" << mAggregatedLR << "eff.area./wla:" << mEffectiveArea_perWLA; |
251 | werner | 229 | } |
230 | |||
376 | werner | 231 | // function is called immediately before the growth of individuals |
232 | void ResourceUnit::beforeGrow() |
||
233 | { |
||
234 | mAverageAging = 0.; |
||
235 | } |
||
236 | |||
237 | // function is called after finishing the indivdual growth / mortality. |
||
238 | void ResourceUnit::afterGrow() |
||
239 | { |
||
240 | mAverageAging = leafArea()>0.?mAverageAging/leafArea():0; // calculate aging value (calls to addAverageAging() by individual trees) |
||
241 | if (mAverageAging>0. && mAverageAging<0.00001) |
||
242 | qDebug() << "ru" << mIndex << "aging <0.00001"; |
||
482 | werner | 243 | if (mAverageAging<0. || mAverageAging>1.) |
244 | qDebug() << "Average aging invalid: (RU, LAI):" << index() << mStatistics.leafAreaIndex(); |
||
376 | werner | 245 | } |
246 | |||
189 | iland | 247 | void ResourceUnit::yearEnd() |
180 | werner | 248 | { |
249 | // calculate statistics for all tree species of the ressource unit |
||
250 | int c = mRUSpecies.count(); |
||
251 | for (int i=0;i<c; i++) { |
||
455 | werner | 252 | mRUSpecies[i]->statisticsDead().calculate(); // calculate the dead trees |
253 | mRUSpecies[i]->statisticsMgmt().calculate(); // stats of removed trees |
||
254 | mRUSpecies[i]->updateGWL(); // get sum of dead trees (died + removed) |
||
255 | mRUSpecies[i]->statistics().calculate(); // calculate the living (and add removed volume to gwl) |
||
256 | mStatistics.add(mRUSpecies[i]->statistics()); |
||
180 | werner | 257 | } |
258 | mStatistics.calculate(); // aggreagte on stand level |
||
482 | werner | 259 | |
180 | werner | 260 | } |
261 | |||
482 | werner | 262 | void ResourceUnit::addTreeAgingForAllTrees() |
263 | { |
||
264 | mAverageAging = 0.; |
||
265 | foreach(const Tree &t, mTrees) { |
||
266 | addTreeAging(t.leafArea(), t.species()->aging(t.height(), t.age())); |
||
267 | } |
||
268 | |||
269 | } |
||
270 | |||
241 | werner | 271 | /// refresh of tree based statistics. |
482 | werner | 272 | /// WARNING: this function is only called once (during startup). |
273 | /// see function "yearEnd()" above!!! |
||
240 | werner | 274 | void ResourceUnit::createStandStatistics() |
275 | { |
||
241 | werner | 276 | // clear statistics (ru-level and ru-species level) |
240 | werner | 277 | mStatistics.clear(); |
262 | werner | 278 | for (int i=0;i<mRUSpecies.count();i++) { |
455 | werner | 279 | mRUSpecies[i]->statistics().clear(); |
280 | mRUSpecies[i]->statisticsDead().clear(); |
||
281 | mRUSpecies[i]->statisticsMgmt().clear(); |
||
262 | werner | 282 | } |
241 | werner | 283 | |
284 | // add all trees to the statistics objects of the species |
||
240 | werner | 285 | foreach(const Tree &t, mTrees) { |
286 | if (!t.isDead()) |
||
257 | werner | 287 | resourceUnitSpecies(t.species()).statistics().add(&t, 0); |
240 | werner | 288 | } |
241 | werner | 289 | // summarize statistics for the whole resource unit |
240 | werner | 290 | for (int i=0;i<mRUSpecies.count();i++) { |
455 | werner | 291 | mRUSpecies[i]->statistics().calculate(); |
292 | mStatistics.add(mRUSpecies[i]->statistics()); |
||
240 | werner | 293 | } |
331 | werner | 294 | mStatistics.calculate(); |
376 | werner | 295 | mAverageAging = mStatistics.leafAreaIndex()>0.?mAverageAging / (mStatistics.leafAreaIndex()*area()):0.; |
482 | werner | 296 | if (mAverageAging<0. || mAverageAging>1.) |
297 | qDebug() << "Average aging invalid: (RU, LAI):" << index() << mStatistics.leafAreaIndex(); |
||
240 | werner | 298 | } |
452 | werner | 299 | |
461 | werner | 300 | void ResourceUnit::setMaxSaplingHeightAt(const QPoint &position, const float height) |
452 | werner | 301 | { |
302 | Q_ASSERT(mSaplingHeightMap); |
||
303 | int pixel_index = cPxPerRU*(position.x()-mCornerCoord.x())+(position.y()-mCornerCoord.y()); |
||
461 | werner | 304 | if (pixel_index<0 || pixel_index>=cPxPerRU*cPxPerRU) { |
453 | werner | 305 | qDebug() << "setSaplingHeightAt-Error for position" << position << "for RU at" << boundingBox() << "with corner" << mCornerCoord; |
461 | werner | 306 | } else { |
307 | if (mSaplingHeightMap[pixel_index]<height) |
||
308 | mSaplingHeightMap[pixel_index]=height; |
||
309 | } |
||
452 | werner | 310 | } |
311 | |||
454 | werner | 312 | /// clear all saplings of all species on a given position (after recruitment) |
313 | void ResourceUnit::clearSaplings(const QPoint &position) |
||
314 | { |
||
455 | werner | 315 | foreach(ResourceUnitSpecies* rus, mRUSpecies) |
316 | rus->clearSaplings(position); |
||
454 | werner | 317 | |
318 | } |
||
319 |