Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | |||
1033 | 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 | |||
908 | werner | 21 | #include "abe_global.h" |
807 | werner | 22 | #include "globalsettings.h" |
23 | |||
24 | |||
25 | #include "forestmanagementengine.h" |
||
808 | werner | 26 | #include "activity.h" |
811 | werner | 27 | #include "fmunit.h" |
28 | #include "fmstand.h" |
||
867 | werner | 29 | #include "fmstp.h" |
811 | werner | 30 | #include "agent.h" |
31 | #include "agenttype.h" |
||
813 | werner | 32 | #include "fomescript.h" |
33 | #include "scriptglobal.h" |
||
815 | werner | 34 | #include "fomescript.h" |
892 | werner | 35 | #include "scheduler.h" |
807 | werner | 36 | |
915 | werner | 37 | #include "unitout.h" |
922 | werner | 38 | #include "abestandout.h" |
932 | werner | 39 | #include "abestandremovalout.h" |
915 | werner | 40 | |
811 | werner | 41 | #include "debugtimer.h" |
42 | |||
863 | werner | 43 | // general iLand stuff |
44 | #include "xmlhelper.h" |
||
45 | #include "csvfile.h" |
||
46 | #include "model.h" |
||
47 | #include "mapgrid.h" |
||
867 | werner | 48 | #include "helper.h" |
878 | werner | 49 | #include "threadrunner.h" |
915 | werner | 50 | #include "outputmanager.h" |
863 | werner | 51 | |
904 | werner | 52 | #include "tree.h" |
1070 | werner | 53 | #include "resourceunit.h" |
904 | werner | 54 | |
915 | werner | 55 | |
56 | |||
909 | werner | 57 | Q_LOGGING_CATEGORY(abe, "abe") |
870 | werner | 58 | |
909 | werner | 59 | Q_LOGGING_CATEGORY(abeSetup, "abe.setup") |
884 | werner | 60 | |
907 | werner | 61 | namespace ABE { |
870 | werner | 62 | |
1095 | werner | 63 | /** @defgroup abe iLand agent based forest management engine (ABE) |
64 | ABE is the Agent Based management Engine that allows the simulation of both forest management activties (e.g., harvesting of trees) |
||
65 | and forest managers (e.g., deciding when and where to execute an activity). |
||
66 | The ABE framework relies heavily on a blend of C++ (for low-level management activties) and Javascript (for higher level definition of |
||
67 | management programs). |
||
68 | |||
69 | The smallest spatial entity is a forest stand (FMStand), which may be grouped into forest management unit (FMUnit). Forest managers (Agent) can select |
||
70 | stand treatment programs (FMSTP) for a unit. The management activities derive from a basic activity (Activity); specialized code exists |
||
71 | for various activities such as planting or thinning. A scheduler (Scheduler) keeps track of where and when to execute activities following |
||
72 | guidelines given by the management agent (Agent). Agents represent individual foresters that may be grouped into AgentTypes (e.g., farmers). |
||
73 | |||
74 | |||
75 | */ |
||
76 | |||
77 | |||
807 | werner | 78 | /** @class ForestManagementEngine |
1095 | werner | 79 | * @ingroup abe |
807 | werner | 80 | */ |
815 | werner | 81 | |
82 | ForestManagementEngine *ForestManagementEngine::singleton_fome_engine = 0; |
||
914 | werner | 83 | int ForestManagementEngine::mMaxStandId = -1; |
807 | werner | 84 | ForestManagementEngine::ForestManagementEngine() |
85 | { |
||
815 | werner | 86 | mScriptBridge = 0; |
87 | singleton_fome_engine = this; |
||
901 | werner | 88 | mCancel = false; |
915 | werner | 89 | setupOutputs(); // add ABE output definitions |
807 | werner | 90 | } |
91 | |||
815 | werner | 92 | ForestManagementEngine::~ForestManagementEngine() |
93 | { |
||
817 | werner | 94 | clear(); |
890 | werner | 95 | // script bridge: script ownership? |
96 | //if (mScriptBridge) |
||
97 | // delete mScriptBridge; |
||
815 | werner | 98 | singleton_fome_engine = 0; |
99 | } |
||
100 | |||
873 | werner | 101 | const MapGrid *ForestManagementEngine::standGrid() |
813 | werner | 102 | { |
873 | werner | 103 | return GlobalSettings::instance()->model()->standGrid(); |
104 | } |
||
863 | werner | 105 | |
873 | werner | 106 | |
107 | void ForestManagementEngine::setupScripting() |
||
108 | { |
||
909 | werner | 109 | // setup the ABE system |
863 | werner | 110 | const XmlHelper &xml = GlobalSettings::instance()->settings(); |
111 | |||
813 | werner | 112 | ScriptGlobal::setupGlobalScripting(); // general iLand scripting helper functions and such |
113 | |||
909 | werner | 114 | // the link between the scripting and the C++ side of ABE |
815 | werner | 115 | if (mScriptBridge) |
116 | delete mScriptBridge; |
||
117 | mScriptBridge = new FomeScript; |
||
118 | mScriptBridge->setupScriptEnvironment(); |
||
863 | werner | 119 | |
890 | werner | 120 | QString file_name = GlobalSettings::instance()->path(xml.value("model.management.abe.file")); |
873 | werner | 121 | QString code = Helper::loadTextFile(file_name); |
909 | werner | 122 | qCDebug(abeSetup) << "Loading script file" << file_name; |
873 | werner | 123 | QJSValue result = GlobalSettings::instance()->scriptEngine()->evaluate(code,file_name); |
124 | if (result.isError()) { |
||
125 | int lineno = result.property("lineNumber").toInt(); |
||
126 | QStringList code_lines = code.replace('\r', "").split('\n'); // remove CR, split by LF |
||
127 | QString code_part; |
||
128 | for (int i=std::max(0, lineno - 5); i<std::min(lineno+5, code_lines.count()); ++i) |
||
129 | code_part.append(QString("%1: %2 %3\n").arg(i).arg(code_lines[i]).arg(i==lineno?" <---- [ERROR]":"")); |
||
909 | werner | 130 | qCDebug(abeSetup) << "Javascript Error in file" << result.property("fileName").toString() << ":" << result.property("lineNumber").toInt() << ":" << result.toString() << ":\n" << code_part; |
873 | werner | 131 | } |
132 | } |
||
133 | |||
903 | werner | 134 | void ForestManagementEngine::prepareRun() |
135 | { |
||
914 | werner | 136 | mStandLayoutChanged = false; // can be changed by salvage operations / stand polygon changes |
903 | werner | 137 | } |
138 | |||
904 | werner | 139 | void ForestManagementEngine::finalizeRun() |
140 | { |
||
141 | // empty the harvest counter; it will be filled again |
||
142 | // during the (next) year. |
||
937 | werner | 143 | |
936 | werner | 144 | foreach (FMStand *stand, mStands) { |
904 | werner | 145 | stand->resetHarvestCounter(); |
936 | werner | 146 | } |
914 | werner | 147 | |
1157 | werner | 148 | foreach (FMUnit *unit, mUnits) { |
149 | unit->resetHarvestCounter(); |
||
150 | } |
||
151 | |||
914 | werner | 152 | // |
153 | if (mStandLayoutChanged) { |
||
154 | DebugTimer timer("ABE:stand_layout_update"); |
||
155 | // renew the internal stand grid |
||
156 | FMStand **fm = mFMStandGrid.begin(); |
||
157 | for (int *p = standGrid()->grid().begin(); p!=standGrid()->grid().end(); ++p, ++fm) |
||
158 | *fm = *p<0?0:mStandHash[*p]; |
||
159 | // renew neigborhood information in the stand grid |
||
160 | const_cast<MapGrid*>(standGrid())->updateNeighborList(); |
||
161 | // renew the spatial indices |
||
162 | const_cast<MapGrid*>(standGrid())->createIndex(); |
||
163 | mStandLayoutChanged = false; |
||
164 | |||
165 | // now check the stands |
||
1157 | werner | 166 | for (QVector<FMStand*>::iterator it=mStands.begin(); it!=mStands.end(); ++it) { |
167 | // renew area |
||
168 | (*it)->checkArea(); |
||
169 | // initial activity (if missing) |
||
934 | werner | 170 | if (!(*it)->currentActivity()) { |
171 | (*it)->initialize(); |
||
172 | } |
||
1157 | werner | 173 | } |
914 | werner | 174 | } |
936 | werner | 175 | |
904 | werner | 176 | } |
177 | |||
915 | werner | 178 | void ForestManagementEngine::setupOutputs() |
179 | { |
||
180 | if (GlobalSettings::instance()->outputManager()->find("abeUnit")) |
||
181 | return; // already set up |
||
182 | GlobalSettings::instance()->outputManager()->addOutput(new UnitOut); |
||
922 | werner | 183 | GlobalSettings::instance()->outputManager()->addOutput(new ABEStandOut); |
1074 | werner | 184 | GlobalSettings::instance()->outputManager()->addOutput(new ABEStandDetailsOut); |
932 | werner | 185 | GlobalSettings::instance()->outputManager()->addOutput(new ABEStandRemovalOut); |
915 | werner | 186 | } |
187 | |||
958 | werner | 188 | void ForestManagementEngine::runJavascript() |
189 | { |
||
190 | QJSValue handler = scriptEngine()->globalObject().property("run"); |
||
191 | if (handler.isCallable()) { |
||
1088 | werner | 192 | scriptBridge()->setExecutionContext(0, false); |
958 | werner | 193 | QJSValue result = handler.call(QJSValueList() << mCurrentYear); |
194 | if (FMSTP::verbose()) |
||
195 | qCDebug(abe) << "executing 'run' function for year" << mCurrentYear << ", result:" << result.toString(); |
||
196 | } |
||
197 | |||
198 | handler = scriptEngine()->globalObject().property("runStand"); |
||
199 | if (handler.isCallable()) { |
||
200 | qCDebug(abe) << "running the 'runStand' javascript function for" << mStands.size() << "stands."; |
||
201 | foreach (FMStand *stand, mStands) { |
||
202 | scriptBridge()->setExecutionContext(stand, true); |
||
203 | handler.call(QJSValueList() << mCurrentYear); |
||
204 | } |
||
205 | } |
||
206 | } |
||
207 | |||
876 | werner | 208 | AgentType *ForestManagementEngine::agentType(const QString &name) |
209 | { |
||
210 | for (int i=0;i<mAgentTypes.count();++i) |
||
211 | if (mAgentTypes[i]->name()==name) |
||
212 | return mAgentTypes[i]; |
||
213 | return 0; |
||
214 | } |
||
215 | |||
938 | werner | 216 | Agent *ForestManagementEngine::agent(const QString &name) |
217 | { |
||
218 | for (int i=0;i<mAgents.count();++i) |
||
219 | if (mAgents[i]->name()==name) |
||
220 | return mAgents[i]; |
||
221 | return 0; |
||
222 | } |
||
915 | werner | 223 | |
938 | werner | 224 | |
915 | werner | 225 | /*--------------------------------------------------------------------- |
226 | * multithreaded execution routines |
||
227 | ---------------------------------------------------------------------*/ |
||
228 | |||
229 | FMUnit *nc_execute_unit(FMUnit *unit) |
||
230 | { |
||
231 | if (ForestManagementEngine::instance()->isCancel()) |
||
232 | return unit; |
||
233 | |||
234 | //qDebug() << "called for unit" << unit; |
||
235 | const QMultiMap<FMUnit*, FMStand*> &stand_map = ForestManagementEngine::instance()->stands(); |
||
236 | QMultiMap<FMUnit*, FMStand*>::const_iterator it = stand_map.constFind(unit); |
||
237 | int executed = 0; |
||
238 | int total = 0; |
||
239 | while (it!=stand_map.constEnd() && it.key()==unit) { |
||
240 | it.value()->stp()->executeRepeatingActivities(it.value()); |
||
241 | if (it.value()->execute()) |
||
242 | ++executed; |
||
933 | werner | 243 | //MapGrid::freeLocksForStand( it.value()->id() ); |
915 | werner | 244 | if (ForestManagementEngine::instance()->isCancel()) |
245 | break; |
||
246 | |||
247 | ++it; |
||
248 | ++total; |
||
249 | } |
||
250 | if (ForestManagementEngine::instance()->isCancel()) |
||
251 | return unit; |
||
252 | |||
253 | if (FMSTP::verbose()) |
||
254 | qCDebug(abe) << "execute unit'" << unit->id() << "', ran" << executed << "of" << total; |
||
255 | |||
256 | // now run the scheduler |
||
257 | unit->scheduler()->run(); |
||
258 | |||
259 | // collect the harvests |
||
260 | it = stand_map.constFind(unit); |
||
261 | while (it!=stand_map.constEnd() && it.key()==unit) { |
||
262 | unit->addRealizedHarvest(it.value()->totalHarvest()); |
||
263 | ++it; |
||
264 | } |
||
265 | |||
266 | |||
267 | return unit; |
||
268 | } |
||
269 | |||
270 | FMUnit *nc_plan_update_unit(FMUnit *unit) |
||
271 | { |
||
272 | if (ForestManagementEngine::instance()->isCancel()) |
||
273 | return unit; |
||
274 | |||
275 | if (ForestManagementEngine::instance()->currentYear() % 10 == 0) { |
||
276 | qCDebug(abe) << "*** execute decadal plan update ***"; |
||
277 | unit->managementPlanUpdate(); |
||
977 | werner | 278 | unit->runAgent(); |
915 | werner | 279 | } |
280 | |||
281 | |||
282 | // first update happens *after* a full year of running ABE. |
||
283 | if (ForestManagementEngine::instance()->currentYear()>1) |
||
284 | unit->updatePlanOfCurrentYear(); |
||
285 | |||
286 | return unit; |
||
287 | } |
||
288 | |||
289 | |||
290 | |||
873 | werner | 291 | void ForestManagementEngine::setup() |
292 | { |
||
909 | werner | 293 | QLoggingCategory::setFilterRules("abe.debug=true\n" \ |
294 | "abe.setup.debug=true"); // enable *all* |
||
884 | werner | 295 | |
934 | werner | 296 | DebugTimer time_setup("ABE:setupScripting"); |
873 | werner | 297 | clear(); |
298 | |||
299 | // (1) setup the scripting environment and load all the javascript code |
||
300 | setupScripting(); |
||
901 | werner | 301 | if (isCancel()) { |
909 | werner | 302 | throw IException(QString("ABE-Error (setup): %1").arg(mLastErrorMessage)); |
901 | werner | 303 | } |
873 | werner | 304 | |
305 | if (!GlobalSettings::instance()->model()) |
||
306 | throw IException("No model created.... invalid operation."); |
||
934 | werner | 307 | |
873 | werner | 308 | // (2) spatial data (stands, units, ...) |
863 | werner | 309 | const MapGrid *stand_grid = GlobalSettings::instance()->model()->standGrid(); |
889 | werner | 310 | |
863 | werner | 311 | if (stand_grid==NULL || stand_grid->isValid()==false) |
909 | werner | 312 | throw IException("The ABE management model requires a valid stand grid."); |
863 | werner | 313 | |
934 | werner | 314 | const XmlHelper &xml = GlobalSettings::instance()->settings(); |
315 | |||
890 | werner | 316 | QString data_file_name = GlobalSettings::instance()->path(xml.value("model.management.abe.agentDataFile")); |
938 | werner | 317 | qCDebug(abeSetup) << "loading ABE agentDataFile" << data_file_name << "..."; |
863 | werner | 318 | CSVFile data_file(data_file_name); |
1208 | werner | 319 | if (data_file.isEmpty()) |
863 | werner | 320 | throw IException(QString("Stand-Initialization: the standDataFile file %1 is empty or missing!").arg(data_file_name)); |
321 | int ikey = data_file.columnIndex("id"); |
||
322 | int iunit = data_file.columnIndex("unit"); |
||
323 | int iagent = data_file.columnIndex("agent"); |
||
938 | werner | 324 | int iagent_type = data_file.columnIndex("agentType"); |
890 | werner | 325 | int istp = data_file.columnIndex("stp"); |
940 | werner | 326 | // unit properties |
327 | int ispeciescomp = data_file.columnIndex("speciesComposition"); |
||
328 | int ithinning = data_file.columnIndex("thinningIntensity"); |
||
329 | int irotation = data_file.columnIndex("U"); |
||
977 | werner | 330 | int iMAI = data_file.columnIndex("MAI"); |
940 | werner | 331 | int iharvest_mode = data_file.columnIndex("harvestMode"); |
332 | |||
333 | |||
938 | werner | 334 | if (ikey<0 || iunit<0) |
939 | werner | 335 | throw IException("setup ABE agentDataFile: one (or two) of the required columns 'id' or 'unit' not available."); |
938 | werner | 336 | if (iagent<0 && iagent_type<0) |
337 | throw IException("setup ABE agentDataFile: the columns 'agent' or 'agentType' are not available. You have to include at least one of the columns."); |
||
863 | werner | 338 | |
938 | werner | 339 | |
863 | werner | 340 | QList<QString> unit_codes; |
890 | werner | 341 | QHash<FMStand*, QString> initial_stps; |
863 | werner | 342 | for (int i=0;i<data_file.rowCount();++i) { |
343 | int stand_id = data_file.value(i,ikey).toInt(); |
||
344 | if (!stand_grid->isValid(stand_id)) |
||
345 | continue; // skip stands that are not in the map (e.g. when a smaller extent is simulated) |
||
890 | werner | 346 | if (FMSTP::verbose()) |
909 | werner | 347 | qCDebug(abeSetup) << "setting up stand" << stand_id; |
863 | werner | 348 | |
349 | // check agents |
||
938 | werner | 350 | QString agent_code = iagent>-1 ? data_file.value(i, iagent).toString() : QString(); |
351 | QString agent_type_code = iagent_type>-1 ? data_file.value(i, iagent_type).toString() : QString(); |
||
939 | werner | 352 | QString unit_id = data_file.value(i, iunit).toString(); |
353 | |||
938 | werner | 354 | Agent *ag=0; |
873 | werner | 355 | AgentType *at=0; |
938 | werner | 356 | if (agent_code.isEmpty() && agent_type_code.isEmpty()) |
357 | throw IException(QString("setup ABE agentDataFile row '%1': no code for columns 'agent' and 'agentType' available.").arg(i) ); |
||
358 | |||
359 | if (!agent_code.isEmpty()) { |
||
360 | // search for a specific agent |
||
361 | ag = agent(agent_code); |
||
362 | if (!ag) |
||
363 | throw IException(QString("Agent '%1' is not set up (row '%2')! Use the 'newAgent()' JS function of agent-types to add agent definitions.").arg(agent_code).arg(i)); |
||
364 | at = ag->type(); |
||
365 | |||
366 | } else { |
||
367 | // look up the agent type and create the agent on the fly |
||
863 | werner | 368 | // create the agent / agent type |
938 | werner | 369 | at = agentType(agent_type_code); |
876 | werner | 370 | if (!at) |
942 | werner | 371 | throw IException(QString("Agent type '%1' is not set up (row '%2')! Use the 'addAgentType()' JS function to add agent-type definitions.").arg(agent_type_code).arg(i)); |
873 | werner | 372 | |
977 | werner | 373 | if (!unit_codes.contains(unit_id)) { |
374 | // we create an agent for the unit only once (per unit) |
||
375 | ag = at->createAgent(); |
||
939 | werner | 376 | } |
863 | werner | 377 | } |
378 | |||
938 | werner | 379 | |
863 | werner | 380 | // check units |
381 | FMUnit *unit = 0; |
||
382 | if (!unit_codes.contains(unit_id)) { |
||
383 | // create the unit |
||
938 | werner | 384 | unit = new FMUnit(ag); |
863 | werner | 385 | unit->setId(unit_id); |
940 | werner | 386 | if (iharvest_mode>-1) |
387 | unit->setHarvestMode( data_file.value(i, iharvest_mode).toString()); |
||
388 | if (ithinning>-1) |
||
389 | unit->setThinningIntensity( data_file.value(i, ithinning).toInt() ); |
||
390 | if (irotation>-1) |
||
391 | unit->setU( data_file.value(i, irotation).toDouble() ); |
||
1070 | werner | 392 | if (iMAI>-1) |
977 | werner | 393 | unit->setAverageMAI(data_file.value(i, iMAI).toDouble()); |
940 | werner | 394 | if (ispeciescomp>-1) { |
395 | int index; |
||
396 | index = at->speciesCompositionIndex( data_file.value(i, ispeciescomp).toString() ); |
||
397 | if (index==-1) |
||
398 | throw IException(QString("The species composition '%1' for unit '%2' is not a valid composition type (agent type: '%3').").arg(data_file.value(i, ispeciescomp).toString()).arg(unit->id()).arg(at->name())); |
||
399 | unit->setTargetSpeciesCompositionIndex( index ); |
||
400 | } |
||
863 | werner | 401 | mUnits.append(unit); |
402 | unit_codes.append(unit_id); |
||
939 | werner | 403 | ag->addUnit(unit); // add the unit to the list of managed units of the agent |
863 | werner | 404 | } else { |
405 | // get unit by id ... in this case we have the same order of appending values |
||
406 | unit = mUnits[unit_codes.indexOf(unit_id)]; |
||
407 | } |
||
408 | |||
409 | // create stand |
||
410 | FMStand *stand = new FMStand(unit,stand_id); |
||
890 | werner | 411 | if (istp>-1) { |
412 | QString stp = data_file.value(i, istp).toString(); |
||
413 | initial_stps[stand] = stp; |
||
414 | } |
||
914 | werner | 415 | mMaxStandId = qMax(mMaxStandId, stand_id); |
863 | werner | 416 | |
417 | mUnitStandMap.insertMulti(unit,stand); |
||
873 | werner | 418 | mStands.append(stand); |
863 | werner | 419 | |
420 | } |
||
944 | werner | 421 | |
422 | // count the number of stands within each unit |
||
423 | foreach(FMUnit *unit, mUnits) |
||
424 | unit->setNumberOfStands( mUnitStandMap.count(unit) ); |
||
425 | |||
873 | werner | 426 | // set up the stand grid (visualizations)... |
427 | // set up a hash for helping to establish stand-id <-> fmstand-link |
||
914 | werner | 428 | mStandHash.clear(); |
970 | werner | 429 | for (int i=0;i<mStands.size(); ++i) { |
914 | werner | 430 | mStandHash[mStands[i]->id()] = mStands[i]; |
970 | werner | 431 | } |
873 | werner | 432 | |
896 | werner | 433 | mFMStandGrid.setup(standGrid()->grid().metricRect(), standGrid()->grid().cellsize()); |
882 | werner | 434 | mFMStandGrid.initialize(0); |
435 | FMStand **fm = mFMStandGrid.begin(); |
||
873 | werner | 436 | for (int *p = standGrid()->grid().begin(); p!=standGrid()->grid().end(); ++p, ++fm) |
914 | werner | 437 | *fm = *p<0?0:mStandHash[*p]; |
873 | werner | 438 | |
882 | werner | 439 | mStandLayers.setGrid(mFMStandGrid); |
878 | werner | 440 | mStandLayers.clearClasses(); |
873 | werner | 441 | mStandLayers.registerLayers(); |
442 | |||
890 | werner | 443 | // now initialize STPs (if they are defined in the init file) |
444 | for (QHash<FMStand*,QString>::iterator it=initial_stps.begin(); it!=initial_stps.end(); ++it) { |
||
445 | FMStand *s = it.key(); |
||
446 | FMSTP* stp = s->unit()->agent()->type()->stpByName(it.value()); |
||
447 | if (stp) { |
||
934 | werner | 448 | s->setSTP(stp); |
1058 | werner | 449 | } else { |
450 | qCDebug(abeSetup) << "Warning during reading of CSV setup file: the STP '" << it.value() << "' is not valid for Agenttype: " << s->unit()->agent()->type()->name(); |
||
890 | werner | 451 | } |
934 | werner | 452 | } |
938 | werner | 453 | qCDebug(abeSetup) << "ABE setup completed."; |
934 | werner | 454 | } |
455 | |||
456 | void ForestManagementEngine::initialize() |
||
457 | { |
||
458 | |||
459 | DebugTimer time_setup("ABE:setup"); |
||
460 | |||
461 | foreach (FMStand* stand, mStands) { |
||
462 | if (stand->stp()) { |
||
940 | werner | 463 | |
464 | stand->setU( stand->unit()->U() ); |
||
465 | stand->setThinningIntensity( stand->unit()->thinningIntensity() ); |
||
466 | stand->setTargetSpeciesIndex( stand->unit()->targetSpeciesIndex() ); |
||
467 | |||
934 | werner | 468 | stand->initialize(); |
469 | if (isCancel()) { |
||
470 | throw IException(QString("ABE-Error: init of stand %2: %1").arg(mLastErrorMessage).arg(stand->id())); |
||
471 | } |
||
901 | werner | 472 | } |
890 | werner | 473 | } |
474 | |||
873 | werner | 475 | // now initialize the agents.... |
939 | werner | 476 | foreach(Agent *ag, mAgents) { |
477 | ag->setup(); |
||
901 | werner | 478 | if (isCancel()) { |
939 | werner | 479 | throw IException(QString("ABE-Error: setup of agent '%2': %1").arg(mLastErrorMessage).arg(ag->name())); |
901 | werner | 480 | } |
481 | } |
||
915 | werner | 482 | |
483 | // run the initial planning unit setup |
||
484 | GlobalSettings::instance()->model()->threadExec().run(nc_plan_update_unit, mUnits); |
||
485 | |||
486 | |||
909 | werner | 487 | qCDebug(abeSetup) << "ABE setup complete." << mUnitStandMap.size() << "stands on" << mUnits.count() << "units, managed by" << mAgents.size() << "agents."; |
863 | werner | 488 | |
813 | werner | 489 | } |
490 | |||
811 | werner | 491 | void ForestManagementEngine::clear() |
492 | { |
||
873 | werner | 493 | qDeleteAll(mStands); // delete the stands |
494 | mStands.clear(); |
||
811 | werner | 495 | qDeleteAll(mUnits); // deletes the units |
496 | mUnits.clear(); |
||
873 | werner | 497 | mUnitStandMap.clear(); |
498 | |||
811 | werner | 499 | qDeleteAll(mAgents); |
500 | mAgents.clear(); |
||
501 | qDeleteAll(mAgentTypes); |
||
502 | mAgentTypes.clear(); |
||
870 | werner | 503 | qDeleteAll(mSTP); |
504 | mSTP.clear(); |
||
878 | werner | 505 | mCurrentYear = 0; |
901 | werner | 506 | mCancel = false; |
507 | mLastErrorMessage = QString(); |
||
811 | werner | 508 | } |
509 | |||
901 | werner | 510 | void ForestManagementEngine::abortExecution(const QString &message) |
511 | { |
||
512 | mLastErrorMessage = message; |
||
513 | mCancel = true; |
||
514 | } |
||
878 | werner | 515 | |
1089 | werner | 516 | void ForestManagementEngine::runOnInit(bool before_init) |
934 | werner | 517 | { |
1089 | werner | 518 | QString handler = before_init ? QStringLiteral("onInit") : QStringLiteral("onAfterInit"); |
519 | if (GlobalSettings::instance()->scriptEngine()->globalObject().hasProperty(handler)) { |
||
520 | QJSValue result = GlobalSettings::instance()->scriptEngine()->evaluate(QString("%1()").arg(handler)); |
||
934 | werner | 521 | if (result.isError()) |
1089 | werner | 522 | qCDebug(abeSetup) << "Javascript Error in global"<< handler << "-Handler:" << result.toString(); |
901 | werner | 523 | |
934 | werner | 524 | } |
525 | } |
||
901 | werner | 526 | |
527 | |||
934 | werner | 528 | |
529 | |||
875 | werner | 530 | /// this is the main function of the forest management engine. |
531 | /// the function is called every year. |
||
876 | werner | 532 | void ForestManagementEngine::run(int debug_year) |
875 | werner | 533 | { |
876 | werner | 534 | if (debug_year>-1) { |
535 | mCurrentYear++; |
||
536 | } else { |
||
537 | mCurrentYear = GlobalSettings::instance()->currentYear(); |
||
538 | } |
||
539 | // now re-evaluate stands |
||
909 | werner | 540 | if (FMSTP::verbose()) qCDebug(abe) << "ForestManagementEngine: run year" << mCurrentYear; |
875 | werner | 541 | |
958 | werner | 542 | |
903 | werner | 543 | prepareRun(); |
544 | |||
958 | werner | 545 | // execute an event handler before invoking the ABE core |
546 | runJavascript(); |
||
547 | |||
907 | werner | 548 | { |
549 | // launch the planning unit level update (annual and thorough analysis every ten years) |
||
909 | werner | 550 | DebugTimer plu("ABE:planUpdate"); |
977 | werner | 551 | GlobalSettings::instance()->model()->threadExec().run(nc_plan_update_unit, mUnits, true); |
903 | werner | 552 | } |
553 | |||
977 | werner | 554 | GlobalSettings::instance()->model()->threadExec().run(nc_execute_unit, mUnits, true); // force single thread operation for now |
901 | werner | 555 | if (isCancel()) { |
556 | throw IException(QString("ABE-Error: %1").arg(mLastErrorMessage)); |
||
557 | } |
||
878 | werner | 558 | |
916 | werner | 559 | // create outputs |
950 | werner | 560 | { |
561 | DebugTimer plu("ABE:outputs"); |
||
916 | werner | 562 | GlobalSettings::instance()->outputManager()->execute("abeUnit"); |
922 | werner | 563 | GlobalSettings::instance()->outputManager()->execute("abeStand"); |
1074 | werner | 564 | GlobalSettings::instance()->outputManager()->execute("abeStandDetail"); |
929 | werner | 565 | GlobalSettings::instance()->outputManager()->execute("abeStandRemoval"); |
950 | werner | 566 | } |
916 | werner | 567 | |
904 | werner | 568 | finalizeRun(); |
569 | |||
875 | werner | 570 | } |
571 | |||
811 | werner | 572 | |
813 | werner | 573 | |
811 | werner | 574 | |
867 | werner | 575 | void ForestManagementEngine::test() |
576 | { |
||
577 | // test code |
||
578 | try { |
||
579 | //Activity::setVerbose(true); |
||
580 | // setup the activities and the javascript environment... |
||
581 | GlobalSettings::instance()->resetScriptEngine(); // clear the script |
||
582 | ScriptGlobal::setupGlobalScripting(); // general iLand scripting helper functions and such |
||
869 | werner | 583 | if (mScriptBridge) |
584 | delete mScriptBridge; |
||
585 | mScriptBridge = new FomeScript; |
||
586 | mScriptBridge->setupScriptEnvironment(); |
||
867 | werner | 587 | |
588 | //setup(); |
||
589 | |||
590 | } catch (const IException &e) { |
||
591 | qDebug() << "An error occured:" << e.message(); |
||
592 | } |
||
872 | werner | 593 | QString file_name = "E:/Daten/iLand/modeling/abm/knowledge_base/test/test_stp.js"; |
594 | QString code = Helper::loadTextFile(file_name); |
||
595 | QJSValue result = GlobalSettings::instance()->scriptEngine()->evaluate(code,file_name); |
||
867 | werner | 596 | if (result.isError()) { |
872 | werner | 597 | int lineno = result.property("lineNumber").toInt(); |
598 | QStringList code_lines = code.replace('\r', "").split('\n'); // remove CR, split by LF |
||
599 | QString code_part; |
||
600 | for (int i=std::max(0, lineno - 5); i<std::min(lineno+5, code_lines.count()); ++i) |
||
601 | code_part.append(QString("%1: %2 %3\n").arg(i).arg(code_lines[i]).arg(i==lineno?" <---- [ERROR]":"")); |
||
602 | qDebug() << "Javascript Error in file" << result.property("fileName").toString() << ":" << result.property("lineNumber").toInt() << ":" << result.toString() << ":\n" << code_part; |
||
867 | werner | 603 | } |
604 | |||
605 | |||
870 | werner | 606 | // try { |
607 | // qDebug() << "*** test 1 ***"; |
||
608 | // FMSTP stp; |
||
609 | // stp.setVerbose(true); |
||
610 | // stp.setup(GlobalSettings::instance()->scriptEngine()->globalObject().property("stp"), "stp"); |
||
611 | // stp.dumpInfo(); |
||
867 | werner | 612 | |
870 | werner | 613 | // } catch (const IException &e) { |
614 | // qDebug() << "An error occured:" << e.message(); |
||
615 | // } |
||
616 | // try { |
||
617 | // qDebug() << "*** test 2 ***"; |
||
618 | // FMSTP stp2; |
||
619 | // stp2.setVerbose(true); |
||
620 | // stp2.setup(GlobalSettings::instance()->scriptEngine()->globalObject().property("degenerated"), "degenerated"); |
||
621 | // stp2.dumpInfo(); |
||
622 | // } catch (const IException &e) { |
||
623 | // qDebug() << "An error occured:" << e.message(); |
||
624 | // } |
||
867 | werner | 625 | |
870 | werner | 626 | // dump all objects: |
627 | foreach(FMSTP *stp, mSTP) |
||
628 | stp->dumpInfo(); |
||
867 | werner | 629 | |
873 | werner | 630 | setup(); |
867 | werner | 631 | qDebug() << "finished"; |
870 | werner | 632 | |
867 | werner | 633 | } |
634 | |||
896 | werner | 635 | QStringList ForestManagementEngine::evaluateClick(const QPointF coord, const QString &grid_name) |
636 | { |
||
901 | werner | 637 | Q_UNUSED(grid_name); // for the moment |
896 | werner | 638 | // find the stand at coord. |
639 | FMStand *stand = mFMStandGrid.constValueAt(coord); |
||
640 | if (stand) |
||
641 | return stand->info(); |
||
642 | return QStringList(); |
||
643 | } |
||
644 | |||
808 | werner | 645 | QJSEngine *ForestManagementEngine::scriptEngine() |
646 | { |
||
807 | werner | 647 | // use global engine from iLand |
648 | return GlobalSettings::instance()->scriptEngine(); |
||
649 | } |
||
870 | werner | 650 | |
873 | werner | 651 | FMSTP *ForestManagementEngine::stp(QString stp_name) const |
652 | { |
||
653 | for (QVector<FMSTP*>::const_iterator it = mSTP.constBegin(); it!=mSTP.constEnd(); ++it) |
||
654 | if ( (*it)->name() == stp_name ) |
||
655 | return *it; |
||
656 | return 0; |
||
657 | } |
||
870 | werner | 658 | |
884 | werner | 659 | FMStand *ForestManagementEngine::stand(int stand_id) const |
660 | { |
||
944 | werner | 661 | if (mStandHash.contains(stand_id)) |
662 | return mStandHash[stand_id]; |
||
663 | |||
664 | // exhaustive search... should not happen |
||
665 | qCDebug(abe) << "ForestManagementEngine::stand() fallback to exhaustive search."; |
||
884 | werner | 666 | for (QVector<FMStand*>::const_iterator it=mStands.constBegin(); it!=mStands.constEnd(); ++it) |
667 | if ( (*it)->id() == stand_id) |
||
668 | return *it; |
||
669 | return 0; |
||
670 | } |
||
873 | werner | 671 | |
1059 | werner | 672 | QStringList ForestManagementEngine::standIds() const |
673 | { |
||
674 | QStringList standids; |
||
675 | foreach(FMStand *s, mStands) |
||
676 | standids.push_back(QString::number(s->id())); |
||
677 | return standids; |
||
678 | } |
||
679 | |||
1064 | werner | 680 | void ForestManagementEngine::notifyTreeRemoval(Tree *tree, int reason) |
904 | werner | 681 | { |
682 | // we use an 'int' instead of Tree:TreeRemovalType because it does not work |
||
911 | werner | 683 | // with forward declaration (and I dont want to include the tree.h header in this class header). |
1070 | werner | 684 | FMStand *stand = mFMStandGrid[tree->position()]; |
929 | werner | 685 | if (stand) |
1064 | werner | 686 | stand->notifyTreeRemoval(tree, reason); |
1157 | werner | 687 | else |
688 | qDebug() << "ForestManagementEngine::notifyTreeRemoval(): tree not on stand at (metric coords): " << tree->position() << "ID:" << tree->id(); |
||
904 | werner | 689 | } |
878 | werner | 690 | |
1157 | werner | 691 | bool ForestManagementEngine::notifyBarkbeetleAttack(const ResourceUnit *ru, const double generations, int n_infested_px) |
1070 | werner | 692 | { |
1088 | werner | 693 | // find out which stands are within the resource unit |
1070 | werner | 694 | GridRunner<FMStand*> gr(mFMStandGrid, ru->boundingBox()); |
695 | QHash<FMStand*, bool> processed_items; |
||
696 | bool forest_changed = false; |
||
697 | while (FMStand **s=gr.next()) { |
||
698 | if (*s && !processed_items.contains(*s)) { |
||
699 | processed_items[*s] = true; |
||
1157 | werner | 700 | forest_changed |= (*s)->notifyBarkBeetleAttack(generations, n_infested_px); |
1070 | werner | 701 | } |
702 | } |
||
703 | return forest_changed; |
||
704 | } |
||
705 | |||
914 | werner | 706 | QMutex protect_split; |
707 | FMStand *ForestManagementEngine::splitExistingStand(FMStand *stand) |
||
708 | { |
||
709 | // get a new stand-id |
||
710 | // make sure that the Id is only used once. |
||
711 | QMutexLocker protector(&protect_split); |
||
712 | int new_stand_id = ++mMaxStandId; |
||
884 | werner | 713 | |
914 | werner | 714 | FMUnit *unit = const_cast<FMUnit*> (stand->unit()); |
715 | FMStand *new_stand = new FMStand(unit,new_stand_id); |
||
904 | werner | 716 | |
914 | werner | 717 | mUnitStandMap.insertMulti(unit,new_stand); |
718 | mStands.append(new_stand); |
||
719 | mStandHash[new_stand_id] = new_stand; |
||
720 | |||
944 | werner | 721 | unit->setNumberOfStands( mUnitStandMap.count(unit) ); |
722 | |||
914 | werner | 723 | mStandLayoutChanged = true; |
724 | |||
725 | return new_stand; |
||
726 | } |
||
727 | |||
728 | |||
729 | |||
870 | werner | 730 | } // namespace |