(root)/src/abe/forestmanagementengine.cpp - Rev 1089
Rev 1088 |
Rev 1095 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/********************************************************************************************
** iLand - an individual based forest landscape and disturbance model
** http://iland.boku.ac.at
** Copyright (C) 2009- Werner Rammer, Rupert Seidl
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
********************************************************************************************/
#include "abe_global.h"
#include "globalsettings.h"
#include "forestmanagementengine.h"
#include "activity.h"
#include "fmunit.h"
#include "fmstand.h"
#include "fmstp.h"
#include "agent.h"
#include "agenttype.h"
#include "fomescript.h"
#include "scriptglobal.h"
#include "fomescript.h"
#include "scheduler.h"
#include "unitout.h"
#include "abestandout.h"
#include "abestandremovalout.h"
#include "debugtimer.h"
// general iLand stuff
#include "xmlhelper.h"
#include "csvfile.h"
#include "model.h"
#include "mapgrid.h"
#include "helper.h"
#include "threadrunner.h"
#include "outputmanager.h"
#include "tree.h"
#include "resourceunit.h"
Q_LOGGING_CATEGORY(abe, "abe")
Q_LOGGING_CATEGORY(abeSetup, "abe.setup")
namespace ABE {
/** @class ForestManagementEngine
*/
ForestManagementEngine *ForestManagementEngine::singleton_fome_engine = 0;
int ForestManagementEngine::mMaxStandId = -1;
ForestManagementEngine::ForestManagementEngine()
{
mScriptBridge = 0;
singleton_fome_engine = this;
mCancel = false;
setupOutputs(); // add ABE output definitions
}
ForestManagementEngine::~ForestManagementEngine()
{
clear();
// script bridge: script ownership?
//if (mScriptBridge)
// delete mScriptBridge;
singleton_fome_engine = 0;
}
const MapGrid *ForestManagementEngine::standGrid()
{
return GlobalSettings::instance()->model()->standGrid();
}
void ForestManagementEngine::setupScripting()
{
// setup the ABE system
const XmlHelper &xml = GlobalSettings::instance()->settings();
ScriptGlobal::setupGlobalScripting(); // general iLand scripting helper functions and such
// the link between the scripting and the C++ side of ABE
if (mScriptBridge)
delete mScriptBridge;
mScriptBridge = new FomeScript;
mScriptBridge->setupScriptEnvironment();
QString file_name = GlobalSettings::instance()->path(xml.value("model.management.abe.file"));
QString code = Helper::loadTextFile(file_name);
qCDebug(abeSetup) << "Loading script file" << file_name;
QJSValue result = GlobalSettings::instance()->scriptEngine()->evaluate(code,file_name);
if (result.isError()) {
int lineno = result.property("lineNumber").toInt();
QStringList code_lines = code.replace('\r', "").split('\n'); // remove CR, split by LF
QString code_part;
for (int i=std::max(0, lineno - 5); i<std::min(lineno+5, code_lines.count()); ++i)
code_part.append(QString("%1: %2 %3\n").arg(i).arg(code_lines[i]).arg(i==lineno?" <---- [ERROR]":""));
qCDebug(abeSetup) << "Javascript Error in file" << result.property("fileName").toString() << ":" << result.property("lineNumber").toInt() << ":" << result.toString() << ":\n" << code_part;
}
}
void ForestManagementEngine::prepareRun()
{
mStandLayoutChanged = false; // can be changed by salvage operations / stand polygon changes
}
void ForestManagementEngine::finalizeRun()
{
// empty the harvest counter; it will be filled again
// during the (next) year.
foreach (FMStand *stand, mStands) {
stand->resetHarvestCounter();
}
//
if (mStandLayoutChanged) {
DebugTimer timer("ABE:stand_layout_update");
// renew the internal stand grid
FMStand **fm = mFMStandGrid.begin();
for (int *p = standGrid()->grid().begin(); p!=standGrid()->grid().end(); ++p, ++fm)
*fm = *p<0?0:mStandHash[*p];
// renew neigborhood information in the stand grid
const_cast<MapGrid*>(standGrid())->updateNeighborList();
// renew the spatial indices
const_cast<MapGrid*>(standGrid())->createIndex();
mStandLayoutChanged = false;
// now check the stands
for (QVector<FMStand*>::iterator it=mStands.begin(); it!=mStands.end(); ++it)
if (!(*it)->currentActivity()) {
(*it)->initialize();
}
}
}
void ForestManagementEngine::setupOutputs()
{
if (GlobalSettings::instance()->outputManager()->find("abeUnit"))
return; // already set up
GlobalSettings::instance()->outputManager()->addOutput(new UnitOut);
GlobalSettings::instance()->outputManager()->addOutput(new ABEStandOut);
GlobalSettings::instance()->outputManager()->addOutput(new ABEStandDetailsOut);
GlobalSettings::instance()->outputManager()->addOutput(new ABEStandRemovalOut);
}
void ForestManagementEngine::runJavascript()
{
QJSValue handler = scriptEngine()->globalObject().property("run");
if (handler.isCallable()) {
scriptBridge()->setExecutionContext(0, false);
QJSValue result = handler.call(QJSValueList() << mCurrentYear);
if (FMSTP::verbose())
qCDebug(abe) << "executing 'run' function for year" << mCurrentYear << ", result:" << result.toString();
}
handler = scriptEngine()->globalObject().property("runStand");
if (handler.isCallable()) {
qCDebug(abe) << "running the 'runStand' javascript function for" << mStands.size() << "stands.";
foreach (FMStand *stand, mStands) {
scriptBridge()->setExecutionContext(stand, true);
handler.call(QJSValueList() << mCurrentYear);
}
}
}
AgentType *ForestManagementEngine::agentType(const QString &name)
{
for (int i=0;i<mAgentTypes.count();++i)
if (mAgentTypes[i]->name()==name)
return mAgentTypes[i];
return 0;
}
Agent *ForestManagementEngine::agent(const QString &name)
{
for (int i=0;i<mAgents.count();++i)
if (mAgents[i]->name()==name)
return mAgents[i];
return 0;
}
/*---------------------------------------------------------------------
* multithreaded execution routines
---------------------------------------------------------------------*/
FMUnit *nc_execute_unit(FMUnit *unit)
{
if (ForestManagementEngine::instance()->isCancel())
return unit;
//qDebug() << "called for unit" << unit;
const QMultiMap<FMUnit*, FMStand*> &stand_map = ForestManagementEngine::instance()->stands();
QMultiMap<FMUnit*, FMStand*>::const_iterator it = stand_map.constFind(unit);
int executed = 0;
int total = 0;
while (it!=stand_map.constEnd() && it.key()==unit) {
it.value()->stp()->executeRepeatingActivities(it.value());
if (it.value()->execute())
++executed;
//MapGrid::freeLocksForStand( it.value()->id() );
if (ForestManagementEngine::instance()->isCancel())
break;
++it;
++total;
}
if (ForestManagementEngine::instance()->isCancel())
return unit;
if (FMSTP::verbose())
qCDebug(abe) << "execute unit'" << unit->id() << "', ran" << executed << "of" << total;
// now run the scheduler
unit->scheduler()->run();
// collect the harvests
it = stand_map.constFind(unit);
while (it!=stand_map.constEnd() && it.key()==unit) {
unit->addRealizedHarvest(it.value()->totalHarvest());
++it;
}
return unit;
}
FMUnit *nc_plan_update_unit(FMUnit *unit)
{
if (ForestManagementEngine::instance()->isCancel())
return unit;
if (ForestManagementEngine::instance()->currentYear() % 10 == 0) {
qCDebug(abe) << "*** execute decadal plan update ***";
unit->managementPlanUpdate();
unit->runAgent();
}
// first update happens *after* a full year of running ABE.
if (ForestManagementEngine::instance()->currentYear()>1)
unit->updatePlanOfCurrentYear();
return unit;
}
void ForestManagementEngine::setup()
{
QLoggingCategory::setFilterRules("abe.debug=true\n" \
"abe.setup.debug=true"); // enable *all*
DebugTimer time_setup("ABE:setupScripting");
clear();
// (1) setup the scripting environment and load all the javascript code
setupScripting();
if (isCancel()) {
throw IException(QString("ABE-Error (setup): %1").arg(mLastErrorMessage));
}
if (!GlobalSettings::instance()->model())
throw IException("No model created.... invalid operation.");
// (2) spatial data (stands, units, ...)
const MapGrid *stand_grid = GlobalSettings::instance()->model()->standGrid();
if (stand_grid==NULL || stand_grid->isValid()==false)
throw IException("The ABE management model requires a valid stand grid.");
const XmlHelper &xml = GlobalSettings::instance()->settings();
QString data_file_name = GlobalSettings::instance()->path(xml.value("model.management.abe.agentDataFile"));
qCDebug(abeSetup) << "loading ABE agentDataFile" << data_file_name << "...";
CSVFile data_file(data_file_name);
if (data_file.rowCount()==0)
throw IException(QString("Stand-Initialization: the standDataFile file %1 is empty or missing!").arg(data_file_name));
int ikey = data_file.columnIndex("id");
int iunit = data_file.columnIndex("unit");
int iagent = data_file.columnIndex("agent");
int iagent_type = data_file.columnIndex("agentType");
int istp = data_file.columnIndex("stp");
// unit properties
int ispeciescomp = data_file.columnIndex("speciesComposition");
int ithinning = data_file.columnIndex("thinningIntensity");
int irotation = data_file.columnIndex("U");
int iMAI = data_file.columnIndex("MAI");
int iharvest_mode = data_file.columnIndex("harvestMode");
if (ikey<0 || iunit<0)
throw IException("setup ABE agentDataFile: one (or two) of the required columns 'id' or 'unit' not available.");
if (iagent<0 && iagent_type<0)
throw IException("setup ABE agentDataFile: the columns 'agent' or 'agentType' are not available. You have to include at least one of the columns.");
QList<QString> unit_codes;
QHash<FMStand*, QString> initial_stps;
for (int i=0;i<data_file.rowCount();++i) {
int stand_id = data_file.value(i,ikey).toInt();
if (!stand_grid->isValid(stand_id))
continue; // skip stands that are not in the map (e.g. when a smaller extent is simulated)
if (FMSTP::verbose())
qCDebug(abeSetup) << "setting up stand" << stand_id;
// check agents
QString agent_code = iagent>-1 ? data_file.value(i, iagent).toString() : QString();
QString agent_type_code = iagent_type>-1 ? data_file.value(i, iagent_type).toString() : QString();
QString unit_id = data_file.value(i, iunit).toString();
Agent *ag=0;
AgentType *at=0;
if (agent_code.isEmpty() && agent_type_code.isEmpty())
throw IException(QString("setup ABE agentDataFile row '%1': no code for columns 'agent' and 'agentType' available.").arg(i) );
if (!agent_code.isEmpty()) {
// search for a specific agent
ag = agent(agent_code);
if (!ag)
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));
at = ag->type();
} else {
// look up the agent type and create the agent on the fly
// create the agent / agent type
at = agentType(agent_type_code);
if (!at)
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));
if (!unit_codes.contains(unit_id)) {
// we create an agent for the unit only once (per unit)
ag = at->createAgent();
}
}
// check units
FMUnit *unit = 0;
if (!unit_codes.contains(unit_id)) {
// create the unit
unit = new FMUnit(ag);
unit->setId(unit_id);
if (iharvest_mode>-1)
unit->setHarvestMode( data_file.value(i, iharvest_mode).toString());
if (ithinning>-1)
unit->setThinningIntensity( data_file.value(i, ithinning).toInt() );
if (irotation>-1)
unit->setU( data_file.value(i, irotation).toDouble() );
if (iMAI>-1)
unit->setAverageMAI(data_file.value(i, iMAI).toDouble());
if (ispeciescomp>-1) {
int index;
index = at->speciesCompositionIndex( data_file.value(i, ispeciescomp).toString() );
if (index==-1)
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()));
unit->setTargetSpeciesCompositionIndex( index );
}
mUnits.append(unit);
unit_codes.append(unit_id);
ag->addUnit(unit); // add the unit to the list of managed units of the agent
} else {
// get unit by id ... in this case we have the same order of appending values
unit = mUnits[unit_codes.indexOf(unit_id)];
}
// create stand
FMStand *stand = new FMStand(unit,stand_id);
if (istp>-1) {
QString stp = data_file.value(i, istp).toString();
initial_stps[stand] = stp;
}
mMaxStandId = qMax(mMaxStandId, stand_id);
mUnitStandMap.insertMulti(unit,stand);
mStands.append(stand);
}
// count the number of stands within each unit
foreach(FMUnit *unit, mUnits)
unit->setNumberOfStands( mUnitStandMap.count(unit) );
// set up the stand grid (visualizations)...
// set up a hash for helping to establish stand-id <-> fmstand-link
mStandHash.clear();
for (int i=0;i<mStands.size(); ++i) {
mStandHash[mStands[i]->id()] = mStands[i];
}
mFMStandGrid.setup(standGrid()->grid().metricRect(), standGrid()->grid().cellsize());
mFMStandGrid.initialize(0);
FMStand **fm = mFMStandGrid.begin();
for (int *p = standGrid()->grid().begin(); p!=standGrid()->grid().end(); ++p, ++fm)
*fm = *p<0?0:mStandHash[*p];
mStandLayers.setGrid(mFMStandGrid);
mStandLayers.clearClasses();
mStandLayers.registerLayers();
// now initialize STPs (if they are defined in the init file)
for (QHash<FMStand*,QString>::iterator it=initial_stps.begin(); it!=initial_stps.end(); ++it) {
FMStand *s = it.key();
FMSTP* stp = s->unit()->agent()->type()->stpByName(it.value());
if (stp) {
s->setSTP(stp);
} else {
qCDebug(abeSetup) << "Warning during reading of CSV setup file: the STP '" << it.value() << "' is not valid for Agenttype: " << s->unit()->agent()->type()->name();
}
}
qCDebug(abeSetup) << "ABE setup completed.";
}
void ForestManagementEngine::initialize()
{
DebugTimer time_setup("ABE:setup");
foreach (FMStand* stand, mStands) {
if (stand->stp()) {
stand->setU( stand->unit()->U() );
stand->setThinningIntensity( stand->unit()->thinningIntensity() );
stand->setTargetSpeciesIndex( stand->unit()->targetSpeciesIndex() );
stand->initialize();
if (isCancel()) {
throw IException(QString("ABE-Error: init of stand %2: %1").arg(mLastErrorMessage).arg(stand->id()));
}
}
}
// now initialize the agents....
foreach(Agent *ag, mAgents) {
ag->setup();
if (isCancel()) {
throw IException(QString("ABE-Error: setup of agent '%2': %1").arg(mLastErrorMessage).arg(ag->name()));
}
}
// run the initial planning unit setup
GlobalSettings::instance()->model()->threadExec().run(nc_plan_update_unit, mUnits);
qCDebug(abeSetup) << "ABE setup complete." << mUnitStandMap.size() << "stands on" << mUnits.count() << "units, managed by" << mAgents.size() << "agents.";
}
void ForestManagementEngine::clear()
{
qDeleteAll(mStands); // delete the stands
mStands.clear();
qDeleteAll(mUnits); // deletes the units
mUnits.clear();
mUnitStandMap.clear();
qDeleteAll(mAgents);
mAgents.clear();
qDeleteAll(mAgentTypes);
mAgentTypes.clear();
qDeleteAll(mSTP);
mSTP.clear();
mCurrentYear = 0;
mCancel = false;
mLastErrorMessage = QString();
}
void ForestManagementEngine::abortExecution(const QString &message)
{
mLastErrorMessage = message;
mCancel = true;
}
void ForestManagementEngine::runOnInit(bool before_init)
{
QString handler = before_init ? QStringLiteral("onInit") : QStringLiteral("onAfterInit");
if (GlobalSettings::instance()->scriptEngine()->globalObject().hasProperty(handler)) {
QJSValue result = GlobalSettings::instance()->scriptEngine()->evaluate(QString("%1()").arg(handler));
if (result.isError())
qCDebug(abeSetup) << "Javascript Error in global"<< handler << "-Handler:" << result.toString();
}
}
/// this is the main function of the forest management engine.
/// the function is called every year.
void ForestManagementEngine::run(int debug_year)
{
if (debug_year>-1) {
mCurrentYear++;
} else {
mCurrentYear = GlobalSettings::instance()->currentYear();
}
// now re-evaluate stands
if (FMSTP::verbose()) qCDebug(abe) << "ForestManagementEngine: run year" << mCurrentYear;
prepareRun();
// execute an event handler before invoking the ABE core
runJavascript();
{
// launch the planning unit level update (annual and thorough analysis every ten years)
DebugTimer plu("ABE:planUpdate");
GlobalSettings::instance()->model()->threadExec().run(nc_plan_update_unit, mUnits, true);
}
GlobalSettings::instance()->model()->threadExec().run(nc_execute_unit, mUnits, true); // force single thread operation for now
if (isCancel()) {
throw IException(QString("ABE-Error: %1").arg(mLastErrorMessage));
}
// create outputs
{
DebugTimer plu("ABE:outputs");
GlobalSettings::instance()->outputManager()->execute("abeUnit");
GlobalSettings::instance()->outputManager()->execute("abeStand");
GlobalSettings::instance()->outputManager()->execute("abeStandDetail");
GlobalSettings::instance()->outputManager()->execute("abeStandRemoval");
}
finalizeRun();
}
void ForestManagementEngine::test()
{
// test code
try {
//Activity::setVerbose(true);
// setup the activities and the javascript environment...
GlobalSettings::instance()->resetScriptEngine(); // clear the script
ScriptGlobal::setupGlobalScripting(); // general iLand scripting helper functions and such
if (mScriptBridge)
delete mScriptBridge;
mScriptBridge = new FomeScript;
mScriptBridge->setupScriptEnvironment();
//setup();
} catch (const IException &e) {
qDebug() << "An error occured:" << e.message();
}
QString file_name = "E:/Daten/iLand/modeling/abm/knowledge_base/test/test_stp.js";
QString code = Helper::loadTextFile(file_name);
QJSValue result = GlobalSettings::instance()->scriptEngine()->evaluate(code,file_name);
if (result.isError()) {
int lineno = result.property("lineNumber").toInt();
QStringList code_lines = code.replace('\r', "").split('\n'); // remove CR, split by LF
QString code_part;
for (int i=std::max(0, lineno - 5); i<std::min(lineno+5, code_lines.count()); ++i)
code_part.append(QString("%1: %2 %3\n").arg(i).arg(code_lines[i]).arg(i==lineno?" <---- [ERROR]":""));
qDebug() << "Javascript Error in file" << result.property("fileName").toString() << ":" << result.property("lineNumber").toInt() << ":" << result.toString() << ":\n" << code_part;
}
// try {
// qDebug() << "*** test 1 ***";
// FMSTP stp;
// stp.setVerbose(true);
// stp.setup(GlobalSettings::instance()->scriptEngine()->globalObject().property("stp"), "stp");
// stp.dumpInfo();
// } catch (const IException &e) {
// qDebug() << "An error occured:" << e.message();
// }
// try {
// qDebug() << "*** test 2 ***";
// FMSTP stp2;
// stp2.setVerbose(true);
// stp2.setup(GlobalSettings::instance()->scriptEngine()->globalObject().property("degenerated"), "degenerated");
// stp2.dumpInfo();
// } catch (const IException &e) {
// qDebug() << "An error occured:" << e.message();
// }
// dump all objects:
foreach(FMSTP *stp, mSTP)
stp->dumpInfo();
setup();
qDebug() << "finished";
}
QStringList ForestManagementEngine::evaluateClick(const QPointF coord, const QString &grid_name)
{
Q_UNUSED(grid_name); // for the moment
// find the stand at coord.
FMStand *stand = mFMStandGrid.constValueAt(coord);
if (stand)
return stand->info();
return QStringList();
}
QJSEngine *ForestManagementEngine::scriptEngine()
{
// use global engine from iLand
return GlobalSettings::instance()->scriptEngine();
}
FMSTP *ForestManagementEngine::stp(QString stp_name) const
{
for (QVector<FMSTP*>::const_iterator it = mSTP.constBegin(); it!=mSTP.constEnd(); ++it)
if ( (*it)->name() == stp_name )
return *it;
return 0;
}
FMStand *ForestManagementEngine::stand(int stand_id) const
{
if (mStandHash.contains(stand_id))
return mStandHash[stand_id];
// exhaustive search... should not happen
qCDebug(abe) << "ForestManagementEngine::stand() fallback to exhaustive search.";
for (QVector<FMStand*>::const_iterator it=mStands.constBegin(); it!=mStands.constEnd(); ++it)
if ( (*it)->id() == stand_id)
return *it;
return 0;
}
QStringList ForestManagementEngine::standIds() const
{
QStringList standids;
foreach(FMStand *s, mStands)
standids.push_back(QString::number(s->id()));
return standids;
}
void ForestManagementEngine::notifyTreeRemoval(Tree *tree, int reason)
{
// we use an 'int' instead of Tree:TreeRemovalType because it does not work
// with forward declaration (and I dont want to include the tree.h header in this class header).
FMStand *stand = mFMStandGrid[tree->position()];
if (stand)
stand->notifyTreeRemoval(tree, reason);
}
bool ForestManagementEngine::notifyBarkbeetleAttack(const ResourceUnit *ru, const double generations, double n_infested_ha)
{
// find out which stands are within the resource unit
GridRunner<FMStand*> gr(mFMStandGrid, ru->boundingBox());
QHash<FMStand*, bool> processed_items;
bool forest_changed = false;
while (FMStand **s=gr.next()) {
if (*s && !processed_items.contains(*s)) {
processed_items[*s] = true;
forest_changed |= (*s)->notifyBarkBeetleAttack(generations, n_infested_ha);
}
}
return forest_changed;
}
QMutex protect_split;
FMStand *ForestManagementEngine::splitExistingStand(FMStand *stand)
{
// get a new stand-id
// make sure that the Id is only used once.
QMutexLocker protector(&protect_split);
int new_stand_id = ++mMaxStandId;
FMUnit *unit = const_cast<FMUnit*> (stand->unit());
FMStand *new_stand = new FMStand(unit,new_stand_id);
mUnitStandMap.insertMulti(unit,new_stand);
mStands.append(new_stand);
mStandHash[new_stand_id] = new_stand;
unit->setNumberOfStands( mUnitStandMap.count(unit) );
mStandLayoutChanged = true;
return new_stand;
}
} // namespace