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