(root)/src/core/modelcontroller.cpp - Rev 1222
Rev 1221 |
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/>.
********************************************************************************************/
/** ModelController is a helper class used to control the flow of operations during a model run.
The ModelController encapsulates the Model class and is the main control unit. It is used by the
iLand GUI as well as the command line version (ilandc).
*/
#include "global.h"
#include "modelcontroller.h"
#include <QObject>
#include "model.h"
#include "debugtimer.h"
#include "helper.h"
#include "version.h"
#include "expression.h"
#include "expressionwrapper.h"
#include "../output/outputmanager.h"
#include "species.h"
#include "speciesset.h"
#include "mapgrid.h"
#include "statdata.h"
#ifdef ILAND_GUI
#include "mainwindow.h" // for the debug message buffering
#endif
ModelController::ModelController()
{
mModel = NULL;
mPaused = false;
mRunning = false;
mHasError = false;
mYearsToRun = 0;
mViewerWindow = 0;
mDynamicOutputEnabled = false;
}
ModelController::~ModelController()
{
destroy();
}
void ModelController::connectSignals()
{
if (!mViewerWindow)
return;
#ifdef ILAND_GUI
connect(this,SIGNAL(bufferLogs(bool)), mViewerWindow, SLOT(bufferedLog(bool)));
#endif
}
/// prepare a list of all (active) species
QList<const Species*> ModelController::availableSpecies()
{
QList<const Species*> list;
if (mModel) {
SpeciesSet *set = mModel->speciesSet();
if (!set)
throw IException("there are 0 or more than one species sets.");
foreach (const Species *s, set->activeSpecies()) {
list.append(s);
}
}
return list;
}
bool ModelController::canCreate()
{
if (mModel)
return false;
return true;
}
bool ModelController::canDestroy()
{
return mModel != NULL;
}
bool ModelController::canRun()
{
if (mModel && mModel->isSetup())
return true;
return false;
}
bool ModelController::isRunning()
{
return mRunning;
}
bool ModelController::isFinished()
{
if (!mModel)
return false;
return canRun() && !isRunning() && mFinished;
}
bool ModelController::isPaused()
{
return mPaused;
}
int ModelController::currentYear() const
{
return GlobalSettings::instance()->currentYear();
}
void ModelController::setFileName(QString initFileName)
{
mInitFile = initFileName;
try {
GlobalSettings::instance()->loadProjectFile(mInitFile);
} catch(const IException &e) {
QString error_msg = e.message();
Helper::msg(error_msg);
mHasError = true;
mLastError = error_msg;
qDebug() << error_msg;
}
}
void ModelController::create()
{
if (!canCreate())
return;
emit bufferLogs(true);
qDebug() << "**************************************************";
qDebug() << "project-file:" << mInitFile;
qDebug() << "started at: " << QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss");
qDebug() << "iLand " << currentVersion() << " (" << svnRevision() << ")";
qDebug() << "**************************************************";
try {
mHasError = false;
DebugTimer::clearAllTimers();
mModel = new Model();
mModel->loadProject();
if (!mModel->isSetup()) {
mHasError = true;
mLastError = "An error occured during the loading of the project. Please check the logs.";
return;
}
// reset clock...
GlobalSettings::instance()->setCurrentYear(1); // reset clock
// initialization of trees, output on startup
mModel->beforeRun();
GlobalSettings::instance()->executeJSFunction("onAfterCreate");
} catch(const IException &e) {
QString error_msg = e.message();
Helper::msg(error_msg);
mLastError = error_msg;
mHasError = true;
qDebug() << error_msg;
}
emit bufferLogs(false);
qDebug() << "Model created.";
}
void ModelController::destroy()
{
if (canDestroy()) {
GlobalSettings::instance()->executeJSFunction("onBeforeDestroy");
Model *m = mModel;
mModel = 0;
delete m;
GlobalSettings::instance()->setCurrentYear(0);
qDebug() << "ModelController: Model destroyed.";
}
}
void ModelController::runloop()
{
static QTime sLastTime = QTime::currentTime();
#ifdef ILAND_GUI
// QApplication::processEvents();
#else
// QCoreApplication::processEvents();
#endif
if (mPaused)
return;
bool doStop = false;
mHasError = false;
if (GlobalSettings::instance()->currentYear()<=1) {
sLastTime = QTime::currentTime(); // reset clock at the beginning of the simulation
}
if (!mCanceled && GlobalSettings::instance()->currentYear() < mYearsToRun) {
emit bufferLogs(true);
mHasError = runYear(); // do the work!
mRunning = true;
emit year(GlobalSettings::instance()->currentYear());
if (!mHasError) {
int elapsed = sLastTime.msecsTo(QTime::currentTime());
int time=0;
if (currentYear()%50==0 && elapsed>10000)
time = 100; // a 100ms pause...
if (currentYear()%100==0 && elapsed>10000) {
time = 500; // a 500ms pause...
}
if (time>0) {
sLastTime = QTime::currentTime(); // reset clock
qDebug() << "--- little break ---- (after " << elapsed << "ms).";
//QTimer::singleShot(time,this, SLOT(runloop()));
}
} else {
doStop = true; // an error occured
mLastError = "An error occured while running the model. Please check the logs.";
mHasError = true;
}
} else {
doStop = true; // all years simulated
}
if (doStop || mCanceled) {
// finished
internalStop();
}
#ifdef ILAND_GUI
QApplication::processEvents();
#else
QCoreApplication::processEvents();
#endif
}
bool ModelController::internalRun()
{
// main loop
try {
while (mRunning && !mPaused && !mFinished) {
runloop(); // start the running loop
}
} catch (IException &e) {
#ifdef ILAND_GUI
Helper::msg(e.message());
#else
qDebug() << e.message();
#endif
}
return isFinished();
}
void ModelController::internalStop()
{
if (mRunning) {
GlobalSettings::instance()->outputManager()->save();
DebugTimer::printAllTimers();
saveDebugOutputs();
//if (GlobalSettings::instance()->dbout().isOpen())
// GlobalSettings::instance()->dbout().close();
mFinished = true;
}
mRunning = false;
mPaused = false; // in any case
emit bufferLogs(false); // stop buffering
emit finished(QString());
emit stateChanged();
}
void ModelController::run(int years)
{
if (!canRun())
return;
emit bufferLogs(true); // start buffering
DebugTimer many_runs(QString("Timer for %1 runs").arg(years));
mPaused = false;
mFinished = false;
mCanceled = false;
mYearsToRun = years;
//GlobalSettings::instance()->setCurrentYear(1); // reset clock
DebugTimer::clearAllTimers();
mRunning = true;
emit stateChanged();
qDebug() << "ModelControler: runloop started.";
internalRun();
emit stateChanged();
}
bool ModelController::runYear()
{
if (!canRun()) return false;
DebugTimer t("ModelController:runYear");
qDebug() << QDateTime::currentDateTime().toString("hh:mm:ss:") << "ModelController: run year" << currentYear();
if (GlobalSettings::instance()->settings().paramValueBool("debug_clear"))
GlobalSettings::instance()->clearDebugLists(); // clear debug data
bool err=false;
try {
emit bufferLogs(true);
GlobalSettings::instance()->executeJSFunction("onYearBegin");
mModel->runYear();
fetchDynamicOutput();
} catch(const IException &e) {
QString error_msg = e.message();
Helper::msg(error_msg);
qDebug() << error_msg;
err=true;
}
emit bufferLogs(false);
#ifdef ILAND_GUI
QApplication::processEvents();
#else
QCoreApplication::processEvents();
#endif
return err;
}
bool ModelController::pause()
{
if(!isRunning())
return mPaused;
if (mPaused) {
// currently in pause - mode -> continue
mPaused = false;
} else {
// currently running -> set to pause mode
GlobalSettings::instance()->outputManager()->save();
mPaused = true;
emit bufferLogs(false);
}
emit stateChanged();
return mPaused;
}
bool ModelController::continueRun()
{
mRunning = true;
emit stateChanged();
return internalRun();
}
void ModelController::cancel()
{
mCanceled = true;
internalStop();
emit stateChanged();
}
// this function is called when exceptions occur in multithreaded code.
QMutex error_mutex;
void ModelController::throwError(const QString msg)
{
QMutexLocker lock(&error_mutex); // serialize access
qDebug() << "ModelController: throwError reached:";
qDebug() << msg;
mLastError = msg;
mHasError = true;
emit bufferLogs(false);
emit bufferLogs(true); // start buffering again
emit finished(msg);
throw IException(msg); // raise error again
}
//////////////////////////////////////
// dynamic outut
//////////////////////////////////////
//////////////////////////////////////
void ModelController::setupDynamicOutput(QString fieldList)
{
mDynFieldList.clear();
if (!fieldList.isEmpty()) {
QRegExp rx("((?:\\[.+\\]|\\w+)\\.\\w+)");
int pos=0;
while ((pos = rx.indexIn(fieldList, pos)) != -1) {
mDynFieldList.append(rx.cap(1));
pos += rx.matchedLength();
}
//mDynFieldList = fieldList.split(QRegExp("(?:\\[.+\\]|\\w+)\\.\\w+"), QString::SkipEmptyParts);
mDynFieldList.prepend("count");
mDynFieldList.prepend("year"); // fixed fields.
}
mDynData.clear();
mDynData.append(mDynFieldList.join(";"));
mDynamicOutputEnabled = true;
}
QString ModelController::dynamicOutput()
{
return mDynData.join("\n");
}
const QStringList aggList = QStringList() << "mean" << "sum" << "min" << "max" << "p25" << "p50" << "p75" << "p5"<< "p10" << "p90" << "p95";
void ModelController::fetchDynamicOutput()
{
if (!mDynamicOutputEnabled || mDynFieldList.isEmpty())
return;
DebugTimer t("dynamic output");
QStringList var;
QString lastVar = "";
QVector<double> data;
AllTreeIterator at(mModel);
TreeWrapper tw;
int var_index;
StatData stat;
double value;
QStringList line;
Expression custom_expr;
bool simple_expression;
foreach (QString field, mDynFieldList) {
if (field=="count" || field=="year")
continue;
if (field.count()>0 && field.at(0)=='[') {
QRegExp rex("\\[(.+)\\]\\.(\\w+)");
rex.indexIn(field);
var = rex.capturedTexts();
var.pop_front(); // drop first element (contains the full string)
simple_expression = false;
} else {
var = field.split(QRegExp("\\W+"), QString::SkipEmptyParts);
simple_expression = true;
}
if (var.count()!=2)
throw IException(QString("Invalid variable name for dynamic output:") + field);
if (var.first()!=lastVar) {
// load new field
data.clear();
at.reset(); var_index = 0;
if (simple_expression) {
var_index = tw.variableIndex(var.first());
if (var_index<0)
throw IException(QString("Invalid variable name for dynamic output:") + var.first());
} else {
custom_expr.setExpression(var.first());
custom_expr.setModelObject(&tw);
}
while (Tree *t = at.next()) {
tw.setTree(t);
if (simple_expression)
value = tw.value(var_index);
else
value = custom_expr.execute();
data.push_back(value);
}
stat.setData(data);
}
// fetch data
var_index = aggList.indexOf(var[1]);
switch (var_index) {
case 0: value = stat.mean(); break;
case 1: value = stat.sum(); break;
case 2: value = stat.min(); break;
case 3: value = stat.max(); break;
case 4: value = stat.percentile25(); break;
case 5: value = stat.median(); break;
case 6: value = stat.percentile75(); break;
case 7: value = stat.percentile(5); break;
case 8: value = stat.percentile(10); break;
case 9: value = stat.percentile(90); break;
case 10: value = stat.percentile(95); break;
default: throw IException(QString("Invalid aggregate expression for dynamic output: %1\nallowed:%2")
.arg(var[1]).arg(aggList.join(" ")));
}
line+=QString::number(value);
}
line.prepend( QString::number(data.size()) );
line.prepend( QString::number(GlobalSettings::instance()->currentYear()) );
mDynData.append(line.join(";"));
}
void ModelController::saveDebugOutputs()
{
// save to files if switch is true
if (!GlobalSettings::instance()->settings().valueBool("system.settings.debugOutputAutoSave"))
return;
QString p = GlobalSettings::instance()->path("debug_", "temp");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreePartition, ";", p + "tree_partition.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreeGrowth, ";", p + "tree_growth.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dTreeNPP, ";", p + "tree_npp.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dStandGPP, ";", p + "stand_gpp.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dWaterCycle, ";", p + "water_cycle.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dDailyResponses, ";", p + "daily_responses.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dEstablishment, ";", p + "establishment.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dSaplingGrowth, ";", p + "saplinggrowth.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dCarbonCycle, ";", p + "carboncycle.csv");
GlobalSettings::instance()->debugDataTable(GlobalSettings::dPerformance, ";", p + "performance.csv");
Helper::saveToTextFile(p+"dynamic.csv", dynamicOutput());
Helper::saveToTextFile(p+ "version.txt", verboseVersion());
qDebug() << "saved debug outputs to" << p;
}
void ModelController::saveScreenshot(QString file_name)
{
#ifdef ILAND_GUI
if (!mViewerWindow)
return;
QImage img = mViewerWindow->screenshot();
img.save(GlobalSettings::instance()->path(file_name));
#else
Q_UNUSED(file_name);
#endif
}
void ModelController::paintMap(MapGrid *map, double min_value, double max_value)
{
#ifdef ILAND_GUI
if (mViewerWindow) {
mViewerWindow->paintGrid(map, "", GridViewRainbow, min_value, max_value);
qDebug() << "painted map grid" << map->name() << "min-value (blue):" << min_value << "max-value(red):" << max_value;
}
#else
Q_UNUSED(map);Q_UNUSED(min_value);Q_UNUSED(max_value);
#endif
}
void ModelController::addGrid(const FloatGrid *grid, const QString &name, const GridViewType view_type, double min_value, double max_value)
{
#ifdef ILAND_GUI
if (mViewerWindow) {
mViewerWindow->paintGrid(grid, name, view_type, min_value, max_value);
qDebug() << "painted grid min-value (blue):" << min_value << "max-value(red):" << max_value;
}
#else
Q_UNUSED(grid); Q_UNUSED(name); Q_UNUSED(view_type); Q_UNUSED(min_value);Q_UNUSED(max_value);
#endif
}
void ModelController::addLayers(const LayeredGridBase *layers, const QString &name)
{
#ifdef ILAND_GUI
if (mViewerWindow)
mViewerWindow->addLayers(layers, name);
//qDebug() << layers->names();
#else
Q_UNUSED(layers); Q_UNUSED(name);
#endif
}
void ModelController::removeLayers(const LayeredGridBase *layers)
{
#ifdef ILAND_GUI
if (mViewerWindow)
mViewerWindow->removeLayers(layers);
//qDebug() << layers->names();
#else
Q_UNUSED(layers);
#endif
}
void ModelController::setViewport(QPointF center_point, double scale_px_per_m)
{
#ifdef ILAND_GUI
if (mViewerWindow)
mViewerWindow->setViewport(center_point, scale_px_per_m);
#else
Q_UNUSED(center_point);Q_UNUSED(scale_px_per_m);
#endif
}
void ModelController::setUIShortcuts(QVariantMap shortcuts)
{
#ifdef ILAND_GUI
if (mViewerWindow)
mViewerWindow->setUIshortcuts(shortcuts);
#else
Q_UNUSED(shortcuts);
#endif
}
void ModelController::repaint()
{
#ifdef ILAND_GUI
if (mViewerWindow)
mViewerWindow->repaint();
#endif
}