(root)/src/abe/activity.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/>.
********************************************************************************************/
#include "abe_global.h"
#include <QJSValueIterator>
#include "activity.h"
#include "fmstand.h"
#include "fmstp.h"
#include "fomescript.h"
#include "fomewrapper.h"
#include "forestmanagementengine.h"
#include "expression.h"
#include "debugtimer.h"
// include derived activity types
#include "actgeneral.h"
#include "actscheduled.h"
#include "actplanting.h"
#include "actthinning.h"
namespace ABE {
/** @class Activity
@ingroup abe
An activity is the basic silvicultural building block; it holds state information and defines basic capabilities
of all activities (such as having a given Schedule, or Events).
*/
// statics
QStringList Activity::mAllowedProperties = QStringList() << "schedule" << "constraint" << "type";
/***************************************************************************/
/*************************** Schedule ***********************************/
/***************************************************************************/
void Schedule::setup(const QJSValue &js_value)
{
clear();
if (js_value.isObject()) {
tmin = FMSTP::valueFromJs(js_value, "min", "-1").toInt();
tmax = FMSTP::valueFromJs(js_value, "max", "-1").toInt();
topt = FMSTP::valueFromJs(js_value, "opt", "-1").toInt();
tminrel = FMSTP::valueFromJs(js_value, "minRel", "-1").toNumber();
tmaxrel = FMSTP::valueFromJs(js_value, "maxRel", "-1").toNumber();
toptrel = FMSTP::valueFromJs(js_value, "optRel", "-1").toNumber();
repeat_interval = FMSTP::valueFromJs(js_value, "repeatInterval", "1").toInt();
// switches
force_execution = FMSTP::boolValueFromJs(js_value, "force", false);
repeat = FMSTP::boolValueFromJs(js_value, "repeat", false);
absolute = FMSTP::boolValueFromJs(js_value, "absolute", false);
if (!repeat) {
if (tmin>-1 && tmax>-1 && topt==-1)
topt = (tmax+tmin) / 2;
if (tmin>-1 && tmax>-1 && topt>-1 && (topt<tmin || topt>tmax))
throw IException(QString("Error in setting up schedule: 'opt' either missing or out of range: %1").arg(FomeScript::JStoString(js_value)));
if (tminrel>-1 && tmaxrel>-1 && toptrel>-1 && (toptrel<tminrel || toptrel>tmaxrel))
throw IException(QString("Error in setting up schedule: 'opt' either missing or out of range: %1").arg(FomeScript::JStoString(js_value)));
if (tminrel*tmaxrel < 0. || tmin*tmax<0.)
throw IException(QString("Error in setting up schedule: min and max required: %1").arg(FomeScript::JStoString(js_value)));
if (topt==-1 && toptrel==-1.)
throw IException(QString("Error in setting up schedule: neither 'opt' nor 'optRel' point can be derived in: %1").arg(FomeScript::JStoString(js_value)));
}
} else if (js_value.isNumber()) {
topt = js_value.toNumber();
} else {
throw IException(QString("Error in setting up schedule/timing. Invalid javascript object: %1").arg(FomeScript::JStoString(js_value)));
}
}
QString Schedule::dump() const
{
if (repeat)
return QString("schedule: Repeating every %1 years.").arg(repeat_interval);
else
return QString("schedule: tmin/topt/tmax %1/%2/%3\nrelative: min/opt/max %4/%5/%6\nforce: %7").arg(tmin).arg(topt).arg(tmax)
.arg(tminrel).arg(toptrel).arg(tmaxrel).arg(force_execution);
}
double Schedule::value(const FMStand *stand, const int specific_year)
{
double U = stand->U();
double current;
double current_year = ForestManagementEngine::instance()->currentYear();
if (specific_year>=0)
current_year = specific_year;
// absolute age: years since the start of the rotation
current = stand->absoluteAge();
if (absolute)
current = current_year;
double current_rel = current / U;
if (repeat) {
// handle special case of repeating activities.
// we execute the repeating activity if repeatInterval years elapsed
// since the last execution.
if (int(current_year) % repeat_interval == 0)
return 1.; // yes, execute this year
else
return 0.; // do not execute this year.
}
// force execution: if age already higher than max, then always evaluate to 1.
if (tmax>-1. && current >= tmax && force_execution)
return 1;
if (tmaxrel>-1. && current_rel >= tmaxrel && force_execution)
return 1;
if (tmin>-1. && current < tmin) return 0.;
if (tmax>-1. && current > tmax) return -1.; // expired
if (tminrel>-1. && current_rel < tminrel) return 0.;
if (tmaxrel>-1. && current_rel > tmaxrel) return -1.; // expired
// optimal time
if (topt > -1. && fabs(current-topt) <= 0.5)
return 1;
if (topt > -1. && current > topt) {
if (force_execution)
return 1.;
else
return -1.; // expired
}
if (tmin>-1. && tmax > -1.) {
if (topt > -1.) {
// linear interpolation
if (current<=topt)
return topt==tmin?1.:(current-tmin)/(topt-tmin);
if (force_execution)
return 1.; // keep the high probability.
else
return topt==tmax?1.:(tmax-current)/(tmax-topt); // decreasing probabilitiy again
} else {
return 1.; // no optimal time: everything between min and max is fine!
}
}
// there is an optimal absoulte point in time defined, but not reached
if (topt > -1)
return 0.;
// optimal time
if (toptrel>-1. && fabs(current_rel-toptrel)*U <= 0.5)
return 1.;
// min/max relative time
if (tminrel>-1. && tmaxrel>-1.) {
if (toptrel > -1.) {
// linear interpolation
if (current_rel<=toptrel)
return toptrel==tminrel?1.:(current_rel-tminrel)/(toptrel-tminrel);
else
return toptrel==tmaxrel?1.:(tmaxrel-current_rel)/(tmaxrel-toptrel);
} else {
return 1.; // no optimal time: everything between min and max is fine!
}
}
// there is an optimal relative point in time defined, but not reached yet.
if (toptrel>-1.)
return 0.;
qCDebug(abe) << "Schedule::value: unexpected combination. U" << U << "age" << current << ", schedule:" << this->dump();
return 0.;
}
double Schedule::minValue(const double U) const
{
if (absolute) return tmin;
if (repeat) return 100000000.;
if (tmin>-1) return tmin;
if (tminrel>-1.) return tminrel * U; // assume a fixed U of 100yrs
if (repeat) return -1.; // repeating executions are treated specially
if (topt>-1) return topt;
return toptrel * U;
}
double Schedule::maxValue(const double U) const
{
if (absolute) return tmax;
if (tmax>-1) return tmax;
if (tmaxrel>-1.) return tmaxrel * U; // assume a fixed U of 100yrs
if (repeat) return -1.; // repeating executions are treated specially
if (topt>-1) return topt;
return toptrel * U;
}
double Schedule::optimalValue(const double U) const
{
if (topt>-1) return topt;
if (toptrel>-1) return toptrel*U;
if (tmin>-1 && tmax>-1) return (tmax+tmin)/2.;
if (tminrel>-1 && tmaxrel>-1) return (tmaxrel+tminrel)/2. * U;
if (force_execution) return tmax;
return toptrel*U;
}
/***************************************************************************/
/************************** Events ************************************/
/***************************************************************************/
void Events::clear()
{
mEvents.clear();
}
void Events::setup(QJSValue &js_value, QStringList event_names)
{
mInstance = js_value; // save the object that contains the events
foreach (QString event, event_names) {
QJSValue val = FMSTP::valueFromJs(js_value, event);
if (val.isCallable()) {
mEvents[event] = js_value; // save the event functions (and the name of the property that the function is assigned to)
}
}
}
QJSValue Events::run(const QString event, FMStand *stand, QJSValueList *params)
{
if (mEvents.contains(event)) {
if (stand)
FomeScript::setExecutionContext(stand);
QJSValue func = mEvents[event].property(event);
QJSValue result;
if (func.isCallable()) {
DebugTimer t("ABE:JSEvents:run");
if (params)
result = func.callWithInstance(mInstance, *params);
else
result = func.callWithInstance(mInstance);
if (FMSTP::verbose() || (stand && stand->trace()))
qCDebug(abe) << (stand?stand->context():QString("<no stand>")) << "invoking javascript event" << event << " result: " << result.toString();
}
//qDebug() << "event called:" << event << "result:" << result.toString();
if (result.isError()) {
throw IException(QString("%3 Javascript error in event %1: %2").arg(event).arg(result.toString()).arg(stand?stand->context():"----"));
}
return result;
}
return QJSValue();
}
bool Events::hasEvent(const QString &event) const
{
return mEvents.contains(event);
}
QString Events::dump()
{
QString event_list = "Registered events: ";
foreach (QString event, mEvents.keys())
event_list.append(event).append(" ");
return event_list;
}
/***************************************************************************/
/************************* Constraints ***********************************/
/***************************************************************************/
void Constraints::setup(QJSValue &js_value)
{
mConstraints.clear();
if ((js_value.isArray() || js_value.isObject()) && !js_value.isCallable()) {
QJSValueIterator it(js_value);
while (it.hasNext()) {
it.next();
if (it.name()==QStringLiteral("length"))
continue;
mConstraints.append(DynamicExpression());
DynamicExpression &item = mConstraints.last();
item.setup(it.value());
}
} else {
mConstraints.append(DynamicExpression());
DynamicExpression &item = mConstraints.last();
item.setup(js_value);
}
}
double Constraints::evaluate(FMStand *stand)
{
if (mConstraints.isEmpty())
return 1.; // no constraints to evaluate
double p;
double p_min = 1;
for (int i=0;i<mConstraints.count();++i) {
p = mConstraints.at(i).evaluate(stand);
if (p == 0.) {
if (stand->trace())
qCDebug(abe) << stand->context() << "constraint" << mConstraints.at(i).dump() << "did not pass.";
return 0.; // one constraint failed
} else {
// save the lowest value...
p_min = std::min(p, p_min);
}
}
return p_min; // all constraints passed, return the lowest returned value...
}
QStringList Constraints::dump()
{
QStringList info;
for (int i=0;i<mConstraints.count();++i){
info << QString("constraint: %1").arg(mConstraints[i].dump());
}
return info;
}
DynamicExpression::~DynamicExpression()
{
if (expr)
delete expr;
}
void DynamicExpression::setup(const QJSValue &js_value)
{
filter_type = ftInvalid;
if (expr) delete expr;
expr=0;
if (js_value.isCallable()) {
func = js_value;
filter_type = ftJavascript;
return;
}
if (js_value.isString()) {
// we assume this is an expression
QString exprstr = js_value.toString();
// replace "." with "__" in variables (our Expression engine is
// not able to cope with the "."-notation
exprstr = exprstr.replace("activity.", "activity__");
exprstr = exprstr.replace("stand.", "stand__");
exprstr = exprstr.replace("site.", "site__");
exprstr = exprstr.replace("unit.", "unit__");
// add ....
expr = new Expression(exprstr);
filter_type = ftExpression;
return;
}
}
bool DynamicExpression::evaluate(FMStand *stand) const
{
switch (filter_type) {
case ftInvalid: return true; // message?
case ftExpression: {
FOMEWrapper wrapper(stand);
double result;
try {
result = expr->execute(0, &wrapper); // using execute, we're in strict mode, i.e. wrong variables are reported.
//result = expr->calculate(wrapper);
} catch (IException &e) {
// throw a nicely formatted error message
e.add(QString("in filter (expr: '%2') for stand %1.").
arg(stand->id()).
arg(expr->expression()) );
throw;
}
if (FMSTP::verbose())
qCDebug(abe) << stand->context() << "evaluate constraint (expr:" << expr->expression() << ") for stand" << stand->id() << ":" << result;
return result > 0.;
}
case ftJavascript: {
// call javascript function
// provide the execution context
FomeScript::setExecutionContext(stand);
QJSValue result = const_cast<QJSValue&>(func).call();
if (result.isError()) {
throw IException(QString("Erron in evaluating constraint (JS) for stand %1: %2").
arg(stand->id()).
arg(result.toString()));
}
if (FMSTP::verbose())
qCDebug(abe) << "evaluate constraint (JS) for stand" << stand->id() << ":" << result.toString();
// convert boolean result to 1 - 0
if (result.isBool())
return result.toBool()==true?1.:0;
else
return result.toNumber();
}
}
return true;
}
QString DynamicExpression::dump() const
{
switch (filter_type){
case ftInvalid: return "Invalid";
case ftExpression: return expr->expression();
case ftJavascript: return func.toString();
default: return "invalid filter type!";
}
}
/***************************************************************************/
/*************************** Activity ************************************/
/***************************************************************************/
Activity::Activity(const FMSTP *parent)
{
mProgram = parent;
mIndex = 0;
mBaseActivity = ActivityFlags(this);
mBaseActivity.setActive(true);
mBaseActivity.setEnabled(true);
}
Activity::~Activity()
{
}
Activity *Activity::createActivity(const QString &type, FMSTP *stp)
{
Activity *act = 0;
if (type=="general")
act = new ActGeneral(stp);
if (type=="scheduled")
act = new ActScheduled(stp);
if (type=="planting")
act = new ActPlanting(stp);
if (type=="salvage")
act = new ActSalvage(stp);
if (type=="thinning")
act = new ActThinning(stp);
if (!act) {
throw IException(QString("Error: the activity type '%1' is not a valid type.").arg(type));
}
return act;
}
QString Activity::type() const
{
return "base";
}
void Activity::setup(QJSValue value)
{
mSchedule.setup(FMSTP::valueFromJs(value, "schedule", "", "setup activity"));
if (FMSTP::verbose())
qCDebug(abeSetup) << mSchedule.dump();
// setup of events
mEvents.clear();
mEvents.setup(value, QStringList() << "onCreate" << "onSetup" << "onEnter" << "onExit" << "onExecute" << "onExecuted" << "onCancel");
if (FMSTP::verbose())
qCDebug(abeSetup) << "Events: " << mEvents.dump();
// setup of constraints
QJSValue constraints = FMSTP::valueFromJs(value, "constraint");
if (!constraints.isUndefined())
mConstraints.setup(constraints);
// enabledIf property
QJSValue enabled_if = FMSTP::valueFromJs(value, "enabledIf");
if (!enabled_if.isUndefined())
mEnabledIf.setup(enabled_if);
}
double Activity::scheduleProbability(FMStand *stand, const int specific_year)
{
// return a value between 0 and 1; return -1 if the activity is expired.
return schedule().value(stand, specific_year);
}
double Activity::execeuteProbability(FMStand *stand)
{
// check the standard constraints and return true when all constraints are fulfilled (or no constraints set)
return constraints().evaluate(stand);
}
bool Activity::execute(FMStand *stand)
{
// execute the "onExecute" event
events().run(QStringLiteral("onExecute"), stand);
return true;
}
bool Activity::evaluate(FMStand *stand)
{
// execute the "onEvaluate" event: the execution is canceled, if the function returns false.
bool cancel = events().run(QStringLiteral("onEvaluate"), stand).toBool();
return !cancel;
}
void Activity::evaluateDyanamicExpressions(FMStand *stand)
{
// evaluate the enabled-if property and set the enabled flag of the stand (i.e. the ActivityFlags)
if (mEnabledIf.isValid()) {
bool result = mEnabledIf.evaluate(stand);
stand->flags(mIndex).setEnabled(result);
}
}
QStringList Activity::info()
{
QStringList lines;
lines << QString("Activity '%1': type '%2'").arg(name()).arg(type());
lines << "Events" << "-" << events().dump() << "/-";
lines << "Schedule" << "-" << schedule().dump() << "/-";
lines << "Constraints" << "-" << constraints().dump() << "/-";
return lines;
}
ActivityFlags &Activity::standFlags(FMStand *stand)
{
// use the base data item if no specific stand is provided
if (!stand)
return mBaseActivity;
// return the flags associated with the specific stand
return stand->flags(mIndex);
}
} // namespace