Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1033 | werner | 1 | /******************************************************************************************** |
2 | ** iLand - an individual based forest landscape and disturbance model |
||
3 | ** http://iland.boku.ac.at |
||
4 | ** Copyright (C) 2009- Werner Rammer, Rupert Seidl |
||
5 | ** |
||
6 | ** This program is free software: you can redistribute it and/or modify |
||
7 | ** it under the terms of the GNU General Public License as published by |
||
8 | ** the Free Software Foundation, either version 3 of the License, or |
||
9 | ** (at your option) any later version. |
||
10 | ** |
||
11 | ** This program is distributed in the hope that it will be useful, |
||
12 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
14 | ** GNU General Public License for more details. |
||
15 | ** |
||
16 | ** You should have received a copy of the GNU General Public License |
||
17 | ** along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
18 | ********************************************************************************************/ |
||
19 | |||
959 | werner | 20 | #include "barkbeetlemodule.h" |
21 | |||
22 | #include "globalsettings.h" |
||
23 | #include "model.h" |
||
961 | werner | 24 | #include "modelcontroller.h" |
959 | werner | 25 | #include "resourceunit.h" |
1009 | werner | 26 | #include "species.h" |
1008 | werner | 27 | #include "debugtimer.h" |
1014 | werner | 28 | #include "outputmanager.h" |
1054 | werner | 29 | #include "climate.h" |
1070 | werner | 30 | #include "abe/forestmanagementengine.h" |
959 | werner | 31 | |
1010 | werner | 32 | |
1095 | werner | 33 | /** @defgroup beetlemodule iLand barkbeetle module |
34 | The bark beetle module is a disturbance module within the iLand framework. |
||
35 | |||
36 | See http://iland.boku.ac.at/barkbeetle for the science behind the module, |
||
37 | and http://iland.boku.ac.at/barkbeetle+module for the implementation/ using side. |
||
38 | */ |
||
39 | |||
40 | |||
1010 | werner | 41 | int BarkBeetleCell::total_infested = 0; |
1036 | werner | 42 | int BarkBeetleCell::max_iteration = 0; |
1010 | werner | 43 | |
1095 | werner | 44 | /** @class BarkBeetleModule |
45 | @ingroup beetlemodule |
||
46 | BarkBeetleModule is the main class for the bark beetle module. It calculates the development of generations, the spread and attack of beetles. |
||
47 | It operates on a 10m grid. |
||
1039 | werner | 48 | |
1095 | werner | 49 | */ |
1053 | werner | 50 | |
1095 | werner | 51 | |
959 | werner | 52 | BarkBeetleModule::BarkBeetleModule() |
53 | { |
||
961 | werner | 54 | mLayers.setGrid(mGrid); |
1008 | werner | 55 | mRULayers.setGrid(mRUGrid); |
1024 | werner | 56 | mSimulate = false; |
1037 | werner | 57 | mEnabled = false; |
1055 | werner | 58 | mYear = 0; |
961 | werner | 59 | |
959 | werner | 60 | } |
61 | |||
1039 | werner | 62 | BarkBeetleModule::~BarkBeetleModule() |
63 | { |
||
64 | } |
||
65 | |||
959 | werner | 66 | void BarkBeetleModule::setup() |
67 | { |
||
68 | // setup the wind grid |
||
69 | mGrid.setup(GlobalSettings::instance()->model()->heightGrid()->metricRect(), cellsize()); |
||
961 | werner | 70 | BarkBeetleCell cell; |
71 | mGrid.initialize(cell); |
||
959 | werner | 72 | |
1008 | werner | 73 | mRUGrid.setup(GlobalSettings::instance()->model()->RUgrid().metricRect(), GlobalSettings::instance()->model()->RUgrid().cellsize()); |
74 | BarkBeetleRUCell ru_cell; |
||
75 | mRUGrid.initialize(ru_cell); |
||
76 | |||
961 | werner | 77 | GlobalSettings::instance()->controller()->addLayers(&mLayers, "bb"); |
1008 | werner | 78 | GlobalSettings::instance()->controller()->addLayers(&mRULayers, "bbru"); |
959 | werner | 79 | |
1010 | werner | 80 | // load settings from the XML file |
81 | loadParameters(); |
||
961 | werner | 82 | |
959 | werner | 83 | } |
84 | |||
85 | void BarkBeetleModule::setup(const ResourceUnit *ru) |
||
86 | { |
||
1008 | werner | 87 | if (!ru) |
88 | return; |
||
1009 | werner | 89 | //qDebug() << "BB setup for RU" << ru->id(); |
959 | werner | 90 | |
1054 | werner | 91 | double ru_value = GlobalSettings::instance()->settings().valueDouble("modules.barkbeetle.backgroundInfestationProbability"); |
92 | // probabilistic OR-calculation: p_1ha = 1 - (1 - p_pixel)^n -> p_pixel = 1 - (1-p_ha)^(1/n) |
||
93 | // we need the probability of damage starting on a single 10m - pixel. |
||
94 | double pixel_value = 1. - pow(1. - ru_value, 1./( cellsize()*cellsize())); |
||
95 | |||
96 | // set all pixel within the resource unit |
||
97 | GridRunner<BarkBeetleCell> runner(mGrid, ru->boundingBox()); |
||
98 | while (BarkBeetleCell *cell = runner.next()) |
||
1157 | werner | 99 | cell->backgroundInfestationProbability = static_cast<float>(pixel_value); |
1054 | werner | 100 | |
959 | werner | 101 | } |
102 | |||
1009 | werner | 103 | void BarkBeetleModule::loadParameters() |
104 | { |
||
105 | const XmlHelper xml = GlobalSettings::instance()->settings().node("modules.barkbeetle"); |
||
1157 | werner | 106 | params.cohortsPerGeneration = xml.valueInt(".cohortsPerGeneration", params.cohortsPerGeneration); |
107 | params.cohortsPerSisterbrood = xml.valueInt(".cohortsPerSisterbrood", params.cohortsPerSisterbrood); |
||
1013 | werner | 108 | params.spreadKernelMaxDistance = xml.valueDouble(".spreadKernelMaxDistance", params.spreadKernelMaxDistance); |
1009 | werner | 109 | params.spreadKernelFormula = xml.value(".spreadKernelFormula", "100*(1-x)^4"); |
1157 | werner | 110 | params.minDbh = static_cast<float>( xml.valueDouble(".minimumDbh", params.minDbh) ); |
1013 | werner | 111 | mKernelPDF.setup(params.spreadKernelFormula,0.,params.spreadKernelMaxDistance); |
1009 | werner | 112 | params.backgroundInfestationProbability = xml.valueDouble(".backgroundInfestationProbability", params.backgroundInfestationProbability); |
1157 | werner | 113 | params.initialInfestationProbability = xml.valueDouble(".initialInfestationProbability", params.initialInfestationProbability); |
1066 | werner | 114 | params.stormInfestationProbability = xml.valueDouble(".stormInfestationProbability", params.stormInfestationProbability); |
115 | params.deadTreeSelectivity = xml.valueDouble(".deadTreeSelectivity", params.deadTreeSelectivity); |
||
1037 | werner | 116 | |
1066 | werner | 117 | |
1010 | werner | 118 | QString formula = xml.value(".colonizeProbabilityFormula", "0.1"); |
119 | mColonizeProbability.setExpression(formula); |
||
1009 | werner | 120 | |
1010 | werner | 121 | formula = xml.value(".winterMortalityFormula", "polygon(days, 0,0, 30, 0.6)"); |
122 | mWinterMortalityFormula.setExpression(formula); |
||
1037 | werner | 123 | |
1054 | werner | 124 | formula = xml.value(".outbreakClimateSensitivityFormula", "1"); |
125 | mOutbreakClimateSensitivityFormula.setExpression(formula); |
||
126 | double **p = &mClimateVariables[0]; |
||
127 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Pspring"); |
||
128 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Psummer"); |
||
129 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Pautumn"); |
||
130 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Pwinter"); |
||
131 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Tspring"); |
||
132 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Tsummer"); |
||
133 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Tautumn"); |
||
134 | *p++ = mOutbreakClimateSensitivityFormula.addVar("Twinter"); |
||
135 | mOutbreakClimateSensitivityFormula.parse(); |
||
136 | |||
1057 | werner | 137 | params.outbreakDurationMin = xml.valueDouble(".outbreakDurationMin", 0.); |
138 | params.outbreakDurationMax = xml.valueDouble(".outbreakDurationMax", 0.); |
||
139 | formula = xml.value(".outbreakDurationMortalityFormula", "0"); |
||
140 | mOutbreakDurationFormula.setExpression(formula); |
||
141 | |||
1054 | werner | 142 | QString ref_table_name = xml.value(".referenceClimate.tableName"); |
143 | QString ref_climate_values = xml.value(".referenceClimate.seasonalPrecipSum"); |
||
144 | QStringList tmp = ref_climate_values.split(','); |
||
145 | mRefClimateAverages.clear(); |
||
146 | foreach(QString v, tmp) mRefClimateAverages.push_back(v.toDouble()); |
||
147 | ref_climate_values = xml.value(".referenceClimate.seasonalTemperatureAverage"); |
||
148 | tmp = ref_climate_values.split(','); |
||
149 | foreach(QString v, tmp) mRefClimateAverages.push_back(v.toDouble()); |
||
150 | if (mRefClimateAverages.count()!=8) |
||
151 | throw IException("Barkbeetle Setup: Error: invalid values for seasonalPrecipSum or seasonalTemperatureAverage (4 ','-separated values expected)."); |
||
152 | |||
153 | mRefClimate = 0; |
||
154 | foreach(const Climate *clim, GlobalSettings::instance()->model()->climates()) |
||
155 | if (clim->name()==ref_table_name) { |
||
156 | mRefClimate = clim; break; |
||
157 | } |
||
158 | qDebug() << "refclimate" << mRefClimateAverages; |
||
159 | if (!mRefClimate) |
||
1055 | werner | 160 | throw IException(QString("Barkbeetle Setup: Error: a climate table '%1' (given in modules.barkbeetle.referenceClimate.tableName) for the barkbeetle reference climate does not exist.").arg(ref_table_name)); |
1054 | werner | 161 | |
1010 | werner | 162 | params.winterMortalityBaseLevel = xml.valueDouble(".baseWinterMortality", 0.5); |
1009 | werner | 163 | |
1014 | werner | 164 | mAfterExecEvent = xml.value(".onAfterBarkbeetle"); |
165 | |||
1039 | werner | 166 | |
1010 | werner | 167 | HeightGridValue *hgv = GlobalSettings::instance()->model()->heightGrid()->begin(); |
168 | for (BarkBeetleCell *b=mGrid.begin();b!=mGrid.end();++b, ++hgv) { |
||
169 | b->reset(); |
||
170 | //scanResourceUnitTrees(mGrid.indexOf(b)); // scan all - not efficient |
||
1024 | werner | 171 | // if (hgv->isValid()) { |
172 | // b->dbh = drandom()<0.6 ? nrandom(20.,40.) : 0.; // random landscape |
||
173 | // b->tree_stress = nrandom(0., 0.4); |
||
174 | // } |
||
1010 | werner | 175 | } |
176 | |||
1039 | werner | 177 | |
1024 | werner | 178 | yearBegin(); // also reset the "scanned" flags |
179 | |||
1009 | werner | 180 | } |
181 | |||
1013 | werner | 182 | void BarkBeetleModule::clearGrids() |
959 | werner | 183 | { |
1013 | werner | 184 | |
185 | for (BarkBeetleCell *b=mGrid.begin();b!=mGrid.end();++b) |
||
1037 | werner | 186 | b->reset(); |
1013 | werner | 187 | |
188 | BarkBeetleRUCell ru_cell; |
||
189 | mRUGrid.initialize(ru_cell); |
||
190 | |||
191 | BarkBeetleCell::resetCounters(); |
||
192 | stats.clear(); |
||
193 | |||
1039 | werner | 194 | |
1013 | werner | 195 | } |
196 | |||
1024 | werner | 197 | void BarkBeetleModule::loadAllVegetation() |
198 | { |
||
1037 | werner | 199 | // refetch vegetation information (if necessary) |
200 | foreach (const ResourceUnit *ru, GlobalSettings::instance()->model()->ruList()) { |
||
1024 | werner | 201 | scanResourceUnitTrees(ru->boundingBox().center()); |
1037 | werner | 202 | } |
203 | |||
204 | // save the damage information of the last year |
||
205 | for (BarkBeetleRUCell *bbru=mRUGrid.begin(); bbru!=mRUGrid.end(); ++bbru) { |
||
206 | bbru->killed_pixels = 0; // reset |
||
207 | } |
||
208 | |||
1039 | werner | 209 | |
210 | |||
1024 | werner | 211 | } |
212 | |||
1013 | werner | 213 | void BarkBeetleModule::run(int iteration) |
214 | { |
||
1157 | werner | 215 | DebugTimer t("barkbeetle:total"); |
1010 | werner | 216 | // reset statistics |
217 | BarkBeetleCell::resetCounters(); |
||
1013 | werner | 218 | int old_max_gen = stats.maxGenerations; |
1010 | werner | 219 | stats.clear(); |
1013 | werner | 220 | mIteration = iteration; |
1008 | werner | 221 | |
1010 | werner | 222 | // calculate the potential bark beetle generations for each resource unit |
1013 | werner | 223 | if (iteration==0) |
224 | calculateGenerations(); |
||
225 | else |
||
226 | stats.maxGenerations = old_max_gen; // save that value.... |
||
1010 | werner | 227 | |
1054 | werner | 228 | // outbreak probability |
229 | calculateOutbreakFactor(); |
||
230 | |||
1037 | werner | 231 | // load the vegetation (skipped if this is not the initial iteration) |
1024 | werner | 232 | loadAllVegetation(); |
233 | |||
1037 | werner | 234 | // background probability of infestation, calculation of antagonist levels |
1010 | werner | 235 | startSpread(); |
236 | |||
237 | // the spread of beetles (and attacking of trees) |
||
238 | barkbeetleSpread(); |
||
239 | |||
1064 | werner | 240 | // write back the effects of the bark beetle module to the forest in iLand |
1029 | werner | 241 | barkbeetleKill(); |
1010 | werner | 242 | |
1013 | werner | 243 | // create some outputs.... |
1066 | werner | 244 | qDebug() << "iter/background-inf/winter-mort/storm-inf/N spread/N landed/N infested: " << mIteration << stats.infestedBackground << stats.NWinterMortality << stats.infestedStorm << stats.NCohortsSpread << stats.NCohortsLanded << stats.NInfested; |
1014 | werner | 245 | GlobalSettings::instance()->outputManager()->execute("barkbeetle"); |
246 | //GlobalSettings::instance()->outputManager()->save(); |
||
1013 | werner | 247 | |
1036 | werner | 248 | // execute the after bark-beetle infestation event |
1014 | werner | 249 | if (!mAfterExecEvent.isEmpty()) { |
250 | // evaluate the javascript function... |
||
251 | GlobalSettings::instance()->executeJavascript(mAfterExecEvent); |
||
252 | } |
||
1013 | werner | 253 | |
959 | werner | 254 | } |
255 | |||
1044 | werner | 256 | void BarkBeetleModule::treeDeath(const Tree *tree) |
257 | { |
||
258 | // do nothing if the tree was killed by bark beetles |
||
259 | if (tree->isDeadBarkBeetle()) |
||
260 | return; |
||
1070 | werner | 261 | // we only process trees here that are either |
262 | // killed by storm or deliberately killed and dropped |
||
263 | // by management and are not are already salvaged |
||
264 | if ( !(tree->isDeadWind() || tree->isCutdown())) |
||
1044 | werner | 265 | return; |
1066 | werner | 266 | |
1070 | werner | 267 | |
1044 | werner | 268 | // ignore the death of trees that are too small or are not Norway spruce |
269 | if (tree->dbh()<params.minDbh || tree->species()->id()!=QStringLiteral("piab")) |
||
270 | return; |
||
271 | |||
272 | BarkBeetleCell &cell = mGrid.valueAt(tree->position()); |
||
1070 | werner | 273 | if (tree->isDeadWind()) |
274 | cell.deadtrees = BarkBeetleCell::StormDamage; |
||
275 | if (tree->isCutdown()) |
||
276 | cell.deadtrees = BarkBeetleCell::BeetleTrapTree; |
||
1044 | werner | 277 | |
278 | |||
279 | |||
280 | } |
||
281 | |||
1024 | werner | 282 | void BarkBeetleModule::scanResourceUnitTrees(const QPointF &position) |
1009 | werner | 283 | { |
284 | // if this resource unit was already scanned in this bark beetle event, then do nothing |
||
285 | // the flags are reset |
||
1024 | werner | 286 | if (!mRUGrid.coordValid(position)) |
1009 | werner | 287 | return; |
288 | |||
1024 | werner | 289 | if (mRUGrid.valueAt(position).scanned) |
1009 | werner | 290 | return; |
291 | |||
1024 | werner | 292 | ResourceUnit *ru = GlobalSettings::instance()->model()->ru(position); |
1009 | werner | 293 | if (!ru) |
294 | return; |
||
295 | |||
1037 | werner | 296 | BarkBeetleRUCell &ru_cell = mRUGrid.valueAt(position); |
297 | ru_cell.host_pixels=0; |
||
298 | |||
299 | // reset the DBH on all pixels within the resource unit |
||
300 | GridRunner<BarkBeetleCell> runner(mGrid, ru->boundingBox()); |
||
301 | while (runner.next()) |
||
302 | runner.current()->dbh=0.f; |
||
303 | |||
1009 | werner | 304 | QVector<Tree>::const_iterator tend = ru->trees().constEnd(); |
305 | for (QVector<Tree>::const_iterator t = ru->trees().constBegin(); t!=tend; ++t) { |
||
306 | if (!t->isDead() && t->species()->id()==QStringLiteral("piab") && t->dbh()>params.minDbh) { |
||
307 | |||
308 | const QPoint &tp = t->positionIndex(); |
||
309 | QPoint pcell(tp.x()/cPxPerHeight, tp.y()/cPxPerHeight); |
||
310 | |||
311 | BarkBeetleCell &bb=mGrid.valueAtIndex(pcell); |
||
1037 | werner | 312 | // count the host pixels (only once) |
313 | if (bb.dbh==0.f) |
||
314 | ru_cell.host_pixels++; |
||
315 | |||
1024 | werner | 316 | if (t->dbh() > bb.dbh) { |
1010 | werner | 317 | bb.dbh = t->dbh(); |
318 | bb.tree_stress = t->stressIndex(); |
||
319 | } |
||
1009 | werner | 320 | |
1037 | werner | 321 | |
1009 | werner | 322 | } |
323 | } |
||
324 | // set the "processed" flag |
||
1037 | werner | 325 | ru_cell.scanned = true; |
1009 | werner | 326 | } |
327 | |||
328 | |||
1037 | werner | 329 | |
959 | werner | 330 | void BarkBeetleModule::yearBegin() |
331 | { |
||
1024 | werner | 332 | // reset the scanned flag of resource units (force reload of stand structure) |
1037 | werner | 333 | for (BarkBeetleRUCell *bbru=mRUGrid.begin();bbru!=mRUGrid.end();++bbru) { |
1024 | werner | 334 | bbru->scanned = false; |
1070 | werner | 335 | bbru->infested=0; |
1037 | werner | 336 | } |
1065 | werner | 337 | |
338 | // reset the effect of wind-damaged trees and "fangbaueme" |
||
339 | for (BarkBeetleCell *c=mGrid.begin(); c!=mGrid.end(); ++c) |
||
340 | c->deadtrees = BarkBeetleCell::NoDeadTrees; |
||
341 | |||
1055 | werner | 342 | mYear = GlobalSettings::instance()->instance()->currentYear(); |
959 | werner | 343 | } |
961 | werner | 344 | |
1010 | werner | 345 | void BarkBeetleModule::calculateGenerations() |
346 | { |
||
347 | // calculate generations |
||
348 | DebugTimer d("BB:generations"); |
||
349 | ResourceUnit **ruptr = GlobalSettings::instance()->model()->RUgrid().begin(); |
||
350 | BarkBeetleRUCell *bbptr = mRUGrid.begin(); |
||
351 | while (bbptr<mRUGrid.end()) { |
||
352 | if (*ruptr) { |
||
353 | bbptr->scanned = false; |
||
1024 | werner | 354 | bbptr->killed_trees = false; |
1010 | werner | 355 | bbptr->generations = mGenerations.calculateGenerations(*ruptr); |
356 | bbptr->add_sister = mGenerations.hasSisterBrood(); |
||
357 | bbptr->cold_days = bbptr->cold_days_late + mGenerations.frostDaysEarly(); |
||
358 | bbptr->cold_days_late = mGenerations.frostDaysLate(); // save for next year |
||
359 | stats.maxGenerations = std::max(int(bbptr->generations), stats.maxGenerations); |
||
360 | } |
||
361 | ++bbptr; ++ruptr; |
||
961 | werner | 362 | |
1010 | werner | 363 | } |
364 | } |
||
365 | |||
1054 | werner | 366 | void BarkBeetleModule::calculateOutbreakFactor() |
367 | { |
||
1092 | werner | 368 | if (!mRefClimate) { |
369 | mRc = 1.; |
||
1054 | werner | 370 | return; |
1092 | werner | 371 | } |
1054 | werner | 372 | const double *t = mRefClimate->temperatureMonth(); |
373 | const double *p = mRefClimate->precipitationMonth(); |
||
374 | // Pspring, Psummer, Pautumn, Pwinter, Tspring, Tsummer, Tautumn, Twinter |
||
375 | // seasonal sum precip -> relative values |
||
376 | *mClimateVariables[0] = (p[2]+p[3]+p[4]) / mRefClimateAverages[0]; |
||
377 | *mClimateVariables[1] = (p[5]+p[6]+p[7]) / mRefClimateAverages[1]; |
||
378 | *mClimateVariables[2] = (p[8]+p[9]+p[10]) / mRefClimateAverages[2]; |
||
1055 | werner | 379 | *mClimateVariables[3] = (p[11]+p[0]+p[1]) / mRefClimateAverages[3]; // not really clean.... using all month of the current year |
1054 | werner | 380 | // temperatures (mean monthly temp) -> delta |
381 | *mClimateVariables[4] = (t[2]+t[3]+t[4])/3. - mRefClimateAverages[4]; |
||
382 | *mClimateVariables[5] = (t[5]+t[6]+t[7])/3. - mRefClimateAverages[5]; |
||
383 | *mClimateVariables[6] = (t[8]+t[9]+t[10])/3. - mRefClimateAverages[6]; |
||
384 | *mClimateVariables[7] = (t[11]+t[0]+t[1])/3. - mRefClimateAverages[7]; |
||
385 | |||
1055 | werner | 386 | mRc = qMax(mOutbreakClimateSensitivityFormula.execute(), 0.); |
387 | qDebug() << "Barkbeelte: rc:" << mRc; |
||
1054 | werner | 388 | |
389 | } |
||
390 | |||
1010 | werner | 391 | void BarkBeetleModule::startSpread() |
392 | { |
||
393 | // calculate winter mortality |
||
1055 | werner | 394 | // probability of infestation |
1010 | werner | 395 | for (BarkBeetleCell *b=mGrid.begin();b!=mGrid.end();++b) { |
396 | if (b->infested) { |
||
1021 | werner | 397 | stats.infestedStart++; |
1092 | werner | 398 | // base mortality (Mbg) |
1010 | werner | 399 | if (drandom()<params.winterMortalityBaseLevel) { |
400 | // the beetles on the pixel died |
||
401 | b->setInfested(false); |
||
402 | stats.NWinterMortality++; |
||
403 | } else { |
||
1092 | werner | 404 | // winter mortality - maybe the beetles die due to low winter temperatures (Mw) |
1024 | werner | 405 | int cold_days = mRUGrid.constValueAt(mGrid.cellCenterPoint(mGrid.indexOf(b))).cold_days; |
1010 | werner | 406 | double p_winter = mWinterMortalityFormula.calculate(cold_days); |
407 | if (drandom()<p_winter) { |
||
408 | b->setInfested(false); |
||
409 | stats.NWinterMortality++; |
||
410 | } |
||
411 | } |
||
412 | |||
1055 | werner | 413 | } else if (b->isPotentialHost()) { |
1157 | werner | 414 | if (mYear==1 && params.initialInfestationProbability>0.) { |
415 | if (drandom() < params.initialInfestationProbability) { |
||
416 | b->setInfested(true); |
||
1164 | werner | 417 | b->outbreakYear = 1 - irandom(0,4); // initial outbreaks has an age of 1-4 years |
1157 | werner | 418 | stats.infestedBackground++; |
419 | } |
||
420 | } else if (b->backgroundInfestationProbability>0.f) { |
||
421 | // calculate probability for an outbreak |
||
422 | double odds_base = b->backgroundInfestationProbability / (1. - b->backgroundInfestationProbability); |
||
423 | double p_mod = (odds_base*mRc) / (1. + odds_base*mRc); |
||
424 | if (drandom() < p_mod) { |
||
425 | // background activation: 10 px |
||
426 | //clumpedBackgroundActivation(mGrid.indexOf(b)); |
||
427 | b->setInfested(true); |
||
428 | b->outbreakYear = mYear; // this outbreak starts in the current year |
||
429 | stats.infestedBackground++; |
||
430 | } |
||
1055 | werner | 431 | } |
1010 | werner | 432 | } |
433 | |||
434 | b->n = 0; |
||
1024 | werner | 435 | b->killed=false; |
1157 | werner | 436 | b->killedYear = 0; |
1055 | werner | 437 | b->packageOutbreakYear = 0.f; |
1068 | werner | 438 | |
1010 | werner | 439 | } |
1064 | werner | 440 | |
441 | prepareInteractions(); |
||
1070 | werner | 442 | |
443 | // tell the forest management (at least if someone is interested) |
||
444 | // if bark beetle attacks are likely |
||
445 | ABE::ForestManagementEngine *abe = GlobalSettings::instance()->model()->ABEngine(); |
||
446 | if (abe) { |
||
447 | // reset the scanned flag of resource units (force reload of stand structure) |
||
448 | bool forest_changed = false; |
||
449 | for (BarkBeetleRUCell *bbru=mRUGrid.begin();bbru!=mRUGrid.end();++bbru) { |
||
450 | if (bbru->generations>=1. && bbru->infested>0) { |
||
451 | // notify about potential bb-attack |
||
452 | ResourceUnit *ru = GlobalSettings::instance()->model()->RUgrid()[mRUGrid.indexOf(bbru)]; |
||
453 | forest_changed |= abe->notifyBarkbeetleAttack(ru, bbru->generations, bbru->infested); |
||
454 | |||
455 | } |
||
456 | |||
457 | } |
||
458 | if (forest_changed) |
||
1077 | werner | 459 | prepareInteractions(true); |
1070 | werner | 460 | } |
461 | |||
1050 | werner | 462 | } |
1010 | werner | 463 | |
1157 | werner | 464 | int BarkBeetleModule::clumpedBackgroundActivation(QPoint start_idx) |
465 | { |
||
466 | // we assume to start the infestation by randomly activating 8 cells |
||
467 | // in the neighborhood of the starting point (a 5x5 grid) |
||
468 | QRect rect(start_idx-QPoint(2,2), start_idx+QPoint(2,2)); |
||
469 | if (!mGrid.isIndexValid(rect.topLeft()) || !mGrid.isIndexValid(rect.bottomRight())) |
||
470 | return 0; |
||
471 | GridRunner<BarkBeetleCell> runner(mGrid, rect); |
||
472 | int n_pot = 0; |
||
473 | while (runner.next()) |
||
474 | if (runner.current()->isHost()) |
||
475 | ++n_pot; |
||
476 | |||
477 | if (n_pot==0) |
||
478 | return 0; |
||
479 | runner.reset(); |
||
480 | double p_infest = 8. / n_pot; |
||
481 | int n_infest=0; |
||
482 | while (runner.next()) |
||
483 | if (runner.current()->isHost()) |
||
484 | if (drandom()<p_infest) { |
||
485 | runner.current()->setInfested(true); |
||
486 | runner.current()->outbreakYear = mYear; // this outbreak starts in the current year |
||
487 | stats.infestedBackground++; ++n_infest; |
||
488 | } |
||
489 | |||
490 | return n_infest; |
||
491 | } |
||
492 | |||
1077 | werner | 493 | void BarkBeetleModule::prepareInteractions(bool update_interaction) |
1050 | werner | 494 | { |
495 | // loop over all cells of the grid and decide |
||
496 | // for each pixel if it is in the proximinity of (attractive) deadwood |
||
497 | // we assume an influence within the 5x5 pixel neighborhood |
||
498 | |||
1077 | werner | 499 | if (!update_interaction && params.stormInfestationProbability<1.) { |
500 | // reduce the effect of wind-damaged trees for bark beetle spread (disable pixels with p=1-stormInfestationProbability), but do |
||
501 | // it only during the first pass |
||
502 | for (BarkBeetleCell *c=mGrid.begin(); c!=mGrid.end(); ++c) |
||
503 | if (c->deadtrees == BarkBeetleCell::StormDamage && drandom()>params.stormInfestationProbability) |
||
504 | c->deadtrees = BarkBeetleCell::NoDeadTrees; |
||
505 | } |
||
506 | |||
507 | |||
1050 | werner | 508 | for (int y=0;y<mGrid.sizeY();++y) |
509 | for (int x=0;x<mGrid.sizeX();++x) { |
||
1065 | werner | 510 | BarkBeetleCell &cell = mGrid.valueAtIndex(x,y); |
511 | if (cell.deadtrees==BarkBeetleCell::NoDeadTrees) { |
||
1050 | werner | 512 | int has_neighbors=0; |
513 | for (int dy=-2;dy<=2;++dy) |
||
514 | for (int dx=-2;dx<=2;++dx) |
||
1065 | werner | 515 | has_neighbors += mGrid.isIndexValid(x+dx,y+dy) ? (mGrid(x+dx,y+dy).deadtrees==BarkBeetleCell::StormDamage || mGrid(x+dx,y+dy).deadtrees==BarkBeetleCell::BeetleTrapTree ? 1: 0) : 0; |
1050 | werner | 516 | |
1065 | werner | 517 | if (has_neighbors>0) |
1092 | werner | 518 | cell.deadtrees = BarkBeetleCell::SinkInVicinity; |
1050 | werner | 519 | |
1065 | werner | 520 | |
1050 | werner | 521 | } |
1065 | werner | 522 | if (cell.deadtrees==BarkBeetleCell::StormDamage) { |
523 | // the pixel acts as a source |
||
524 | cell.setInfested(true); |
||
1066 | werner | 525 | cell.outbreakYear = mYear; // this outbreak starts in the current year |
526 | stats.infestedStorm++; |
||
1065 | werner | 527 | } |
1070 | werner | 528 | if (cell.infested) { |
529 | // record infestation for the resource unit |
||
530 | mRUGrid[mGrid.cellCenterPoint(QPoint(x,y))].infested++; |
||
531 | } |
||
1050 | werner | 532 | } |
1010 | werner | 533 | } |
534 | |||
1050 | werner | 535 | |
1010 | werner | 536 | void BarkBeetleModule::barkbeetleSpread() |
537 | { |
||
538 | DebugTimer t("BBSpread"); |
||
1057 | werner | 539 | |
540 | double ant_years = qMax(nrandom(params.outbreakDurationMin, params.outbreakDurationMax), 1.); |
||
1010 | werner | 541 | for (int generation=1;generation<=stats.maxGenerations;++generation) { |
542 | |||
543 | GridRunner<BarkBeetleCell> runner(mGrid, mGrid.rectangle()); |
||
544 | GridRunner<BarkBeetleCell> targeter(mGrid, mGrid.rectangle()); |
||
545 | while (BarkBeetleCell *b=runner.next()) { |
||
546 | if (!b->infested) |
||
547 | continue; |
||
548 | QPoint start_index = runner.currentIndex(); |
||
1024 | werner | 549 | BarkBeetleRUCell &bbru=mRUGrid.valueAt(runner.currentCoord()); |
1010 | werner | 550 | if (generation>bbru.generations) |
551 | continue; |
||
552 | |||
1085 | werner | 553 | // the number of packages is increased if there is a developed sisterbrood *and* one filial generation |
554 | // (Wermelinger and Seiffert, 1999, Wermelinger 2004). If more than one generation develops, we assume |
||
555 | // that the effect of sister broods is reduced. |
||
1082 | werner | 556 | int n_packets = params.cohortsPerGeneration; |
1085 | werner | 557 | if (bbru.generations<2. && bbru.add_sister) |
1082 | werner | 558 | n_packets = params.cohortsPerSisterbrood; |
1037 | werner | 559 | |
1055 | werner | 560 | // antagonists: |
561 | double t_ob = mYear - b->outbreakYear; |
||
1057 | werner | 562 | double p_antagonist_mort = mOutbreakDurationFormula.calculate(limit(t_ob / ant_years, 0., 1.)); |
1055 | werner | 563 | n_packets = qRound(n_packets * (1. - p_antagonist_mort)); |
564 | |||
1010 | werner | 565 | stats.NCohortsSpread += n_packets; |
566 | |||
567 | // mark this cell as "dead" (as the beetles have killed the host trees and now move on) |
||
1013 | werner | 568 | b->finishedSpread(mIteration>0 ? mIteration+1 : generation); |
1024 | werner | 569 | // mark the resource unit, that some killing is required |
570 | bbru.killed_trees = true; |
||
1157 | werner | 571 | stats.NAreaKilled++; |
1037 | werner | 572 | bbru.killed_pixels++; |
573 | bbru.host_pixels--; |
||
1010 | werner | 574 | |
575 | BarkBeetleCell *nb8[8]; |
||
576 | for (int i=0;i<n_packets;++i) { |
||
577 | // estimate distance and direction of spread |
||
578 | double rho = mKernelPDF.get(); // distance (m) |
||
579 | double phi = nrandom(0, 2*M_PI); // direction (degree) |
||
580 | // calculate the pixel |
||
581 | |||
582 | QPoint pos = start_index + QPoint(qRound( rho * sin(phi) / cHeightSize ), qRound( rho * cos(phi) / cHeightSize ) ); |
||
583 | // don't spread to the initial start pixel |
||
584 | if (start_index == pos) |
||
585 | continue; |
||
586 | targeter.setPosition(pos); |
||
587 | if (!targeter.isValid()) |
||
588 | continue; |
||
589 | |||
1064 | werner | 590 | |
591 | // effect of windthrown trees or "fangbaume" |
||
592 | if (targeter.current()->isNeutralized()) |
||
1066 | werner | 593 | if (drandom() < params.deadTreeSelectivity) |
594 | continue; |
||
1064 | werner | 595 | |
1010 | werner | 596 | BarkBeetleCell *target=0; |
597 | if (targeter.current()->isPotentialHost()) { |
||
598 | // found a potential host at the immediate target pixel |
||
599 | target = targeter.current(); |
||
600 | } else { |
||
601 | // get elements of the moore-neighborhood |
||
602 | // and look for a potential host |
||
603 | targeter.neighbors8(nb8); |
||
1164 | werner | 604 | int idx = irandom(0,8); |
1010 | werner | 605 | for (int j=0;j<8;j++) { |
606 | BarkBeetleCell *nb = nb8[ (idx+j) % 8 ]; |
||
607 | if (nb && nb->isPotentialHost()) { |
||
608 | target = nb; |
||
609 | break; |
||
610 | } |
||
611 | } |
||
612 | } |
||
613 | |||
614 | // attack the target pixel if a target could be identified |
||
615 | if (target) { |
||
616 | target->n++; |
||
1055 | werner | 617 | target->packageOutbreakYear += b->outbreakYear; |
1010 | werner | 618 | } |
619 | } |
||
620 | } // while |
||
621 | |||
622 | // now evaluate whether the landed beetles are able to infest the target trees |
||
623 | runner.reset(); |
||
624 | while (BarkBeetleCell *b=runner.next()) { |
||
625 | if (b->n > 0) { |
||
626 | stats.NCohortsLanded+=b->n; |
||
1021 | werner | 627 | stats.NPixelsLanded++; |
1010 | werner | 628 | // the cell is attacked by n packages. Calculate the probability that the beetles win. |
629 | // the probability is derived from an expression with the parameter "tree_stress" |
||
630 | double p_col = limit(mColonizeProbability.calculate(b->tree_stress), 0., 1.); |
||
631 | // the attack happens 'n' times, therefore the probability is higher |
||
632 | double p_ncol = 1. - pow(1.-p_col, b->n); |
||
1013 | werner | 633 | b->p_colonize = std::max(b->p_colonize, float(p_ncol)); |
1010 | werner | 634 | if (drandom() < p_ncol) { |
635 | // attack successful - the pixel gets infested |
||
1081 | werner | 636 | b->outbreakYear = b->n>0 ? b->packageOutbreakYear / float(b->n) : mYear; // b->n always >0, but just to silence compiler warning ;) |
1010 | werner | 637 | b->setInfested(true); |
638 | stats.NInfested++; |
||
1055 | werner | 639 | } else { |
640 | b->n = 0; // reset the counter |
||
641 | b->packageOutbreakYear = 0.f; |
||
1010 | werner | 642 | } |
643 | |||
644 | } |
||
645 | } |
||
1042 | werner | 646 | |
1037 | werner | 647 | } // for(generations) |
1039 | werner | 648 | |
1010 | werner | 649 | } |
650 | |||
1024 | werner | 651 | void BarkBeetleModule::barkbeetleKill() |
652 | { |
||
653 | int n_killed=0; |
||
1157 | werner | 654 | double basal_area=0.; |
655 | double volume=0.; |
||
1024 | werner | 656 | for (BarkBeetleRUCell *rucell=mRUGrid.begin(); rucell!=mRUGrid.end(); ++rucell) |
657 | if (rucell->killed_trees) { |
||
658 | // there are killed pixels within the resource unit.... |
||
659 | QVector<Tree> &tv = GlobalSettings::instance()->model()->RUgrid().constValueAtIndex(mRUGrid.indexOf(rucell))->trees(); |
||
660 | for (QVector<Tree>::const_iterator t=tv.constBegin(); t!=tv.constEnd(); ++t) { |
||
661 | if (!t->isDead() && t->dbh()>params.minDbh && t->species()->id()==QStringLiteral("piab")) { |
||
662 | // check if on killed pixel? |
||
1157 | werner | 663 | BarkBeetleCell &bbc = mGrid.valueAt(t->position()); |
664 | if (bbc.killed) { |
||
1024 | werner | 665 | // yes: kill the tree: |
666 | Tree *tree = const_cast<Tree*>(&(*t)); |
||
667 | n_killed++; |
||
668 | basal_area+=tree->basalArea(); |
||
1157 | werner | 669 | volume+=tree->volume(); |
670 | bbc.sum_volume_killed += tree->volume(); |
||
671 | |||
672 | if (!mSimulate) { // remove tree only if not in simulation mode |
||
673 | tree->setDeathReasonBarkBeetle(); |
||
1029 | werner | 674 | tree->removeDisturbance(0., 1., // 0% of the stem to soil, 100% to snag (keeps standing) |
675 | 0., 1., // 100% of branches to snags |
||
1202 | werner | 676 | 1.); // 100% of foliage to soil |
1157 | werner | 677 | } |
1010 | werner | 678 | |
1024 | werner | 679 | } |
680 | } |
||
681 | } |
||
682 | |||
683 | } |
||
684 | stats.NTreesKilled = n_killed; |
||
685 | stats.BasalAreaKilled = basal_area; |
||
1157 | werner | 686 | stats.VolumeKilled = volume; |
1024 | werner | 687 | |
1065 | werner | 688 | // reset the effect of wind-damaged trees and "fangbaueme" -> year begin |
689 | // for (BarkBeetleCell *c=mGrid.begin(); c!=mGrid.end(); ++c) |
||
690 | // c->deadtrees = BarkBeetleCell::NoDeadTrees; |
||
1064 | werner | 691 | |
1024 | werner | 692 | } |
693 | |||
694 | |||
961 | werner | 695 | //********************************************************************************* |
696 | //************************************ BarkBeetleLayers *************************** |
||
697 | //********************************************************************************* |
||
698 | |||
699 | |||
700 | double BarkBeetleLayers::value(const BarkBeetleCell &data, const int param_index) const |
||
701 | { |
||
702 | switch(param_index){ |
||
1036 | werner | 703 | case 0: return data.n; // grid value on pixel |
1009 | werner | 704 | case 1: return data.dbh; // diameter of host |
1010 | werner | 705 | case 2: return data.infested?1.:0.; // infested yes/no |
1085 | werner | 706 | case 3: return data.killed?1.:0.; // pixel has been killed in the (last) year |
707 | case 4: if (data.isHost()) { // dead |
||
1036 | werner | 708 | if (data.infested) |
709 | return data.max_iteration+1; // infested right now (will be dead soon next year) |
||
710 | else |
||
711 | return data.killedYear; // iteration when killed |
||
712 | } |
||
713 | return -1; // no host |
||
1085 | werner | 714 | case 5: return data.p_colonize; // probability of kill |
715 | case 6: return double(data.deadtrees); // availabilitiy of deadwood (spruce) |
||
716 | case 7: return data.backgroundInfestationProbability; |
||
717 | case 8: return GlobalSettings::instance()->currentYear() - data.outbreakYear; |
||
1157 | werner | 718 | case 9: return data.n_events; // number of events on a specific pixel |
719 | case 10: return data.sum_volume_killed; // total sum of trees killed for a pixel |
||
1007 | werner | 720 | default: throw IException(QString("invalid variable index for a BarkBeetleCell: %1").arg(param_index)); |
961 | werner | 721 | } |
722 | } |
||
723 | |||
724 | |||
1014 | werner | 725 | const QVector<LayeredGridBase::LayerElement> &BarkBeetleLayers::names() |
961 | werner | 726 | { |
1014 | werner | 727 | if (mNames.isEmpty()) |
728 | mNames = QVector<LayeredGridBase::LayerElement>() |
||
729 | << LayeredGridBase::LayerElement(QLatin1Literal("value"), QLatin1Literal("grid value of the pixel"), GridViewRainbow) |
||
730 | << LayeredGridBase::LayerElement(QLatin1Literal("dbh"), QLatin1Literal("diameter of thickest spruce tree on the 10m pixel"), GridViewRainbow) |
||
731 | << LayeredGridBase::LayerElement(QLatin1Literal("infested"), QLatin1Literal("infested pixels (1) are colonized by beetles."), GridViewHeat) |
||
1086 | werner | 732 | << LayeredGridBase::LayerElement(QLatin1Literal("killed"), QLatin1Literal("1 for pixels that have been killed (0 otherwise) in the current year (last execution of the module)."), GridViewRainbow) |
1082 | werner | 733 | << LayeredGridBase::LayerElement(QLatin1Literal("dead"), QLatin1Literal("iteration at which the treees on the pixel were killed (0: alive, -1: no host trees). \nNewly infested pixels are included (max iteration + 1)."), GridViewRainbow) |
1044 | werner | 734 | << LayeredGridBase::LayerElement(QLatin1Literal("p_killed"), QLatin1Literal("highest probability (within one year) that a pixel is colonized/killed (integrates the number of arriving beetles and the defense state) 0..1"), GridViewHeat) |
1066 | werner | 735 | << LayeredGridBase::LayerElement(QLatin1Literal("deadwood"), QLatin1Literal("10: trees killed by storm, 8: trap trees, 5: active vicinity of 5/8, 0: no dead trees"), GridViewRainbow) |
1055 | werner | 736 | << LayeredGridBase::LayerElement(QLatin1Literal("outbreakProbability"), QLatin1Literal("background infestation probability (p that outbreak starts at each 10m pixel per year) (does not include the interannual climate sensitivity)"), GridViewGray) |
1157 | werner | 737 | << LayeredGridBase::LayerElement(QLatin1Literal("outbreakAge"), QLatin1Literal("age of the outbreak that led to the infestation of the pixel."), GridViewGray) |
738 | << LayeredGridBase::LayerElement(QLatin1Literal("nEvents"), QLatin1Literal("number of events (total since start of simulation) that killed trees on a pixel."), GridViewReds) |
||
739 | << LayeredGridBase::LayerElement(QLatin1Literal("sumVolume"), QLatin1Literal("running sum of damages trees (volume, m3)."), GridViewReds); |
||
1014 | werner | 740 | return mNames; |
1008 | werner | 741 | |
961 | werner | 742 | } |
962 | werner | 743 | |
744 | bool BarkBeetleLayers::onClick(const QPointF &world_coord) const |
||
745 | { |
||
746 | qDebug() << "received click" << world_coord; |
||
747 | return true; // handled the click |
||
748 | } |
||
1008 | werner | 749 | |
750 | |||
751 | double BarkBeetleRULayers::value(const BarkBeetleRUCell &data, const int index) const |
||
752 | { |
||
753 | switch(index){ |
||
754 | case 0: return data.generations; // number of generation |
||
755 | default: throw IException(QString("invalid variable index for a BarkBeetleRUCell: %1").arg(index)); |
||
756 | } |
||
757 | |||
758 | } |
||
759 | |||
1014 | werner | 760 | const QVector<LayeredGridBase::LayerElement> &BarkBeetleRULayers::names() |
1008 | werner | 761 | { |
1014 | werner | 762 | if (mNames.isEmpty()) |
763 | mNames = QVector<LayeredGridBase::LayerElement>() |
||
1054 | werner | 764 | << LayeredGridBase::LayerElement(QLatin1Literal("generations"), QLatin1Literal("total number of bark beetle generations"), GridViewHeat); |
1014 | werner | 765 | return mNames; |
1008 | werner | 766 | } |
767 | |||
768 | bool BarkBeetleRULayers::onClick(const QPointF &world_coord) const |
||
769 | { |
||
770 | qDebug() << "received click" << world_coord; |
||
771 | return true; // handled the click |
||
772 | |||
773 | } |
||
1039 | werner | 774 | |
775 | |||
1042 | werner | 776 |