Subversion Repositories public iLand

Rev

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
********************************************************************************************/
908 werner 19
#include "abe_global.h"
808 werner 20
#include <QJSValueIterator>
807 werner 21
 
22
#include "activity.h"
811 werner 23
#include "fmstand.h"
870 werner 24
#include "fmstp.h"
25
#include "fomescript.h"
26
#include "fomewrapper.h"
875 werner 27
#include "forestmanagementengine.h"
811 werner 28
 
870 werner 29
#include "expression.h"
901 werner 30
#include "debugtimer.h"
870 werner 31
 
901 werner 32
 
891 werner 33
// include derived activity types
34
#include "actgeneral.h"
35
#include "actscheduled.h"
902 werner 36
#include "actplanting.h"
923 werner 37
#include "actthinning.h"
884 werner 38
 
907 werner 39
namespace ABE {
870 werner 40
 
1095 werner 41
/** @class Activity
42
    @ingroup abe
43
    An activity is the basic silvicultural building block; it holds state information and defines basic capabilities
44
    of all activities (such as having a given Schedule, or Events).
45
 
46
  */
47
 
951 werner 48
// statics
49
QStringList Activity::mAllowedProperties = QStringList() << "schedule" << "constraint" << "type";
50
 
870 werner 51
/***************************************************************************/
52
/***************************   Schedule  ***********************************/
53
/***************************************************************************/
54
 
55
 
963 werner 56
void Schedule::setup(const QJSValue &js_value)
870 werner 57
{
58
    clear();
59
    if (js_value.isObject()) {
60
        tmin = FMSTP::valueFromJs(js_value, "min", "-1").toInt();
61
        tmax = FMSTP::valueFromJs(js_value, "max", "-1").toInt();
62
        topt = FMSTP::valueFromJs(js_value, "opt", "-1").toInt();
63
        tminrel = FMSTP::valueFromJs(js_value, "minRel", "-1").toNumber();
64
        tmaxrel = FMSTP::valueFromJs(js_value, "maxRel", "-1").toNumber();
65
        toptrel = FMSTP::valueFromJs(js_value, "optRel", "-1").toNumber();
871 werner 66
        repeat_interval = FMSTP::valueFromJs(js_value, "repeatInterval", "1").toInt();
67
        // switches
68
        force_execution = FMSTP::boolValueFromJs(js_value, "force", false);
69
        repeat = FMSTP::boolValueFromJs(js_value, "repeat", false);
872 werner 70
        absolute = FMSTP::boolValueFromJs(js_value, "absolute", false);
871 werner 71
        if (!repeat) {
1063 werner 72
 
871 werner 73
            if (tmin>-1 && tmax>-1 && topt==-1)
74
                topt = (tmax+tmin) / 2;
75
            if (tmin>-1 && tmax>-1 && topt>-1 && (topt<tmin || topt>tmax))
1063 werner 76
                throw IException(QString("Error in setting up schedule: 'opt' either missing or out of range: %1").arg(FomeScript::JStoString(js_value)));
871 werner 77
            if (tminrel>-1 && tmaxrel>-1 && toptrel>-1 && (toptrel<tminrel || toptrel>tmaxrel))
1063 werner 78
                throw IException(QString("Error in setting up schedule: 'opt' either missing or out of range: %1").arg(FomeScript::JStoString(js_value)));
871 werner 79
            if (tminrel*tmaxrel < 0. || tmin*tmax<0.)
1063 werner 80
                throw IException(QString("Error in setting up schedule: min and max required: %1").arg(FomeScript::JStoString(js_value)));
870 werner 81
 
871 werner 82
            if (topt==-1 && toptrel==-1.)
1063 werner 83
                throw IException(QString("Error in setting up schedule: neither 'opt' nor 'optRel' point can be derived in: %1").arg(FomeScript::JStoString(js_value)));
871 werner 84
        }
85
 
870 werner 86
    } else if (js_value.isNumber()) {
87
        topt = js_value.toNumber();
88
    } else {
1063 werner 89
        throw IException(QString("Error in setting up schedule/timing. Invalid javascript object: %1").arg(FomeScript::JStoString(js_value)));
870 werner 90
    }
91
}
92
 
93
QString Schedule::dump() const
94
{
871 werner 95
    if (repeat)
896 werner 96
        return QString("schedule: Repeating every %1 years.").arg(repeat_interval);
871 werner 97
    else
902 werner 98
        return QString("schedule: tmin/topt/tmax %1/%2/%3\nrelative: min/opt/max %4/%5/%6\nforce: %7").arg(tmin).arg(topt).arg(tmax)
871 werner 99
                .arg(tminrel).arg(toptrel).arg(tmaxrel).arg(force_execution);
870 werner 100
}
101
 
955 werner 102
double Schedule::value(const FMStand *stand, const int specific_year)
870 werner 103
{
942 werner 104
    double U = stand->U();
897 werner 105
    double current;
106
    double current_year = ForestManagementEngine::instance()->currentYear();
955 werner 107
    if (specific_year>=0)
108
        current_year = specific_year;
902 werner 109
    // absolute age: years since the start of the rotation
110
    current = stand->absoluteAge();
897 werner 111
 
872 werner 112
    if (absolute)
897 werner 113
        current = current_year;
885 werner 114
 
115
    double current_rel = current / U;
897 werner 116
    if (repeat) {
117
        // handle special case of repeating activities.
118
        // we execute the repeating activity if repeatInterval years elapsed
119
        // since the last execution.
120
        if (int(current_year) % repeat_interval == 0)
121
            return 1.; // yes, execute this year
122
        else
123
            return 0.; // do not execute this year.
124
 
125
    }
870 werner 126
    // force execution: if age already higher than max, then always evaluate to 1.
903 werner 127
    if (tmax>-1. && current >= tmax && force_execution)
870 werner 128
        return 1;
903 werner 129
    if (tmaxrel>-1. && current_rel >= tmaxrel && force_execution)
870 werner 130
        return 1;
131
 
872 werner 132
    if (tmin>-1. && current < tmin) return 0.;
897 werner 133
    if (tmax>-1. && current > tmax) return -1.; // expired
885 werner 134
    if (tminrel>-1. && current_rel < tminrel) return 0.;
897 werner 135
    if (tmaxrel>-1. && current_rel > tmaxrel) return -1.; // expired
870 werner 136
 
885 werner 137
    // optimal time
138
    if (topt > -1. && fabs(current-topt) <= 0.5)
139
        return 1;
902 werner 140
    if (topt > -1. && current > topt) {
141
        if (force_execution)
142
            return 1.;
143
        else
144
            return -1.; // expired
145
    }
885 werner 146
 
870 werner 147
    if (tmin>-1. && tmax > -1.) {
148
        if (topt > -1.) {
885 werner 149
            // linear interpolation
872 werner 150
            if (current<=topt)
151
                return topt==tmin?1.:(current-tmin)/(topt-tmin);
892 werner 152
            if (force_execution)
153
                return 1.; // keep the high probability.
870 werner 154
            else
892 werner 155
                return topt==tmax?1.:(tmax-current)/(tmax-topt); // decreasing probabilitiy again
870 werner 156
        } else {
157
            return 1.; // no optimal time: everything between min and max is fine!
158
        }
159
    }
887 werner 160
    // there is an optimal absoulte point in time defined, but not reached
161
    if (topt > -1)
162
        return 0.;
163
 
885 werner 164
    // optimal time
165
    if (toptrel>-1. && fabs(current_rel-toptrel)*U <= 0.5)
166
        return 1.;
167
 
168
    // min/max relative time
870 werner 169
    if (tminrel>-1. && tmaxrel>-1.) {
170
        if (toptrel > -1.) {
885 werner 171
            // linear interpolation
172
            if (current_rel<=toptrel)
173
                return toptrel==tminrel?1.:(current_rel-tminrel)/(toptrel-tminrel);
870 werner 174
            else
885 werner 175
                return toptrel==tmaxrel?1.:(tmaxrel-current_rel)/(tmaxrel-toptrel);
870 werner 176
        } else {
177
            return 1.; // no optimal time: everything between min and max is fine!
178
        }
179
    }
887 werner 180
    // there is an optimal relative point in time defined, but not reached yet.
181
    if (toptrel>-1.)
182
        return 0.;
183
 
909 werner 184
    qCDebug(abe) << "Schedule::value: unexpected combination. U" << U << "age" << current << ", schedule:" << this->dump();
870 werner 185
    return 0.;
186
}
187
 
875 werner 188
double Schedule::minValue(const double U) const
871 werner 189
{
955 werner 190
    if (absolute) return tmin;
901 werner 191
    if (repeat) return 100000000.;
871 werner 192
    if (tmin>-1) return tmin;
875 werner 193
    if (tminrel>-1.) return tminrel * U; // assume a fixed U of 100yrs
871 werner 194
    if (repeat) return -1.; // repeating executions are treated specially
195
    if (topt>-1) return topt;
875 werner 196
    return toptrel * U;
871 werner 197
}
198
 
875 werner 199
double Schedule::maxValue(const double U) const
200
{
955 werner 201
    if (absolute) return tmax;
875 werner 202
    if (tmax>-1) return tmax;
203
    if (tmaxrel>-1.) return tmaxrel * U; // assume a fixed U of 100yrs
204
    if (repeat) return -1.; // repeating executions are treated specially
205
    if (topt>-1) return topt;
206
    return toptrel * U;
207
 
208
}
209
 
910 werner 210
double Schedule::optimalValue(const double U) const
211
{
212
    if (topt>-1) return topt;
213
    if (toptrel>-1) return toptrel*U;
214
    if (tmin>-1 && tmax>-1) return (tmax+tmin)/2.;
215
    if (tminrel>-1 && tmaxrel>-1) return (tmaxrel+tminrel)/2. * U;
216
    if (force_execution) return tmax;
217
    return toptrel*U;
218
}
219
 
870 werner 220
/***************************************************************************/
221
/**************************     Events  ************************************/
222
/***************************************************************************/
223
 
224
void Events::clear()
225
{
226
    mEvents.clear();
227
}
228
 
229
void Events::setup(QJSValue &js_value, QStringList event_names)
230
{
887 werner 231
    mInstance = js_value; // save the object that contains the events
870 werner 232
    foreach (QString event, event_names) {
888 werner 233
        QJSValue val = FMSTP::valueFromJs(js_value, event);
876 werner 234
        if (val.isCallable()) {
887 werner 235
            mEvents[event] = js_value; // save the event functions (and the name of the property that the function is assigned to)
876 werner 236
        }
870 werner 237
    }
238
}
239
 
1070 werner 240
QJSValue Events::run(const QString event, FMStand *stand, QJSValueList *params)
870 werner 241
{
242
    if (mEvents.contains(event)) {
893 werner 243
        if (stand)
244
            FomeScript::setExecutionContext(stand);
876 werner 245
        QJSValue func = mEvents[event].property(event);
246
        QJSValue result;
887 werner 247
        if (func.isCallable()) {
909 werner 248
            DebugTimer t("ABE:JSEvents:run");
901 werner 249
 
1070 werner 250
            if (params)
251
                result = func.callWithInstance(mInstance, *params);
252
            else
253
                result = func.callWithInstance(mInstance);
1032 werner 254
            if (FMSTP::verbose() || (stand && stand->trace()))
909 werner 255
                qCDebug(abe) << (stand?stand->context():QString("<no stand>")) << "invoking javascript event" << event << " result: " << result.toString();
887 werner 256
        }
876 werner 257
 
258
        //qDebug() << "event called:" << event << "result:" << result.toString();
870 werner 259
        if (result.isError()) {
893 werner 260
            throw IException(QString("%3 Javascript error in event %1: %2").arg(event).arg(result.toString()).arg(stand?stand->context():"----"));
870 werner 261
        }
1061 werner 262
        return result;
870 werner 263
    }
1061 werner 264
    return QJSValue();
870 werner 265
}
266
 
891 werner 267
bool Events::hasEvent(const QString &event) const
268
{
269
    return mEvents.contains(event);
270
}
271
 
870 werner 272
QString Events::dump()
273
{
274
    QString event_list = "Registered events: ";
275
    foreach (QString event, mEvents.keys())
276
        event_list.append(event).append(" ");
277
    return event_list;
278
}
279
 
280
/***************************************************************************/
281
/*************************  Constraints  ***********************************/
282
/***************************************************************************/
283
 
284
void Constraints::setup(QJSValue &js_value)
285
{
286
    mConstraints.clear();
287
    if ((js_value.isArray() || js_value.isObject()) && !js_value.isCallable()) {
288
        QJSValueIterator it(js_value);
289
        while (it.hasNext()) {
290
            it.next();
902 werner 291
            if (it.name()==QStringLiteral("length"))
292
                continue;
944 werner 293
            mConstraints.append(DynamicExpression());
294
            DynamicExpression &item = mConstraints.last();
870 werner 295
            item.setup(it.value());
296
        }
297
    } else {
944 werner 298
        mConstraints.append(DynamicExpression());
299
        DynamicExpression &item = mConstraints.last();
870 werner 300
        item.setup(js_value);
301
 
302
    }
303
}
304
 
889 werner 305
double Constraints::evaluate(FMStand *stand)
870 werner 306
{
307
    if (mConstraints.isEmpty())
889 werner 308
        return 1.; // no constraints to evaluate
309
    double p;
310
    double p_min = 1;
311
    for (int i=0;i<mConstraints.count();++i) {
312
        p = mConstraints.at(i).evaluate(stand);
313
        if (p == 0.) {
887 werner 314
            if (stand->trace())
909 werner 315
                qCDebug(abe) << stand->context() << "constraint" << mConstraints.at(i).dump() << "did not pass.";
889 werner 316
            return 0.; // one constraint failed
317
        } else {
318
            // save the lowest value...
319
            p_min = std::min(p, p_min);
887 werner 320
        }
889 werner 321
    }
322
    return p_min; // all constraints passed, return the lowest returned value...
870 werner 323
}
324
 
325
QStringList Constraints::dump()
326
{
327
    QStringList info;
328
    for (int i=0;i<mConstraints.count();++i){
896 werner 329
        info << QString("constraint: %1").arg(mConstraints[i].dump());
870 werner 330
    }
331
    return info;
332
}
333
 
334
 
944 werner 335
DynamicExpression::~DynamicExpression()
870 werner 336
{
337
    if (expr)
338
        delete expr;
339
}
340
 
963 werner 341
void DynamicExpression::setup(const QJSValue &js_value)
870 werner 342
{
343
    filter_type = ftInvalid;
344
    if (expr) delete expr;
345
    expr=0;
346
 
347
    if (js_value.isCallable()) {
348
        func = js_value;
349
        filter_type = ftJavascript;
350
        return;
351
    }
352
    if (js_value.isString()) {
353
        // we assume this is an expression
354
 
355
        QString exprstr = js_value.toString();
356
        // replace "." with "__" in variables (our Expression engine is
357
        // not able to cope with the "."-notation
358
        exprstr = exprstr.replace("activity.", "activity__");
359
        exprstr = exprstr.replace("stand.", "stand__");
360
        exprstr = exprstr.replace("site.", "site__");
944 werner 361
        exprstr = exprstr.replace("unit.", "unit__");
870 werner 362
        // add ....
363
        expr = new Expression(exprstr);
364
        filter_type = ftExpression;
365
        return;
366
 
367
    }
368
}
369
 
944 werner 370
bool DynamicExpression::evaluate(FMStand *stand) const
870 werner 371
{
372
    switch (filter_type) {
373
    case ftInvalid: return true; // message?
374
    case ftExpression: {
375
            FOMEWrapper wrapper(stand);
376
            double result;
377
            try {
929 werner 378
                result = expr->execute(0, &wrapper); // using execute, we're in strict mode, i.e. wrong variables are reported.
379
                //result = expr->calculate(wrapper);
870 werner 380
            } catch (IException &e) {
381
                // throw a nicely formatted error message
382
                e.add(QString("in filter (expr: '%2') for stand %1.").
383
                              arg(stand->id()).
384
                              arg(expr->expression()) );
385
                throw;
386
            }
387
 
388
            if (FMSTP::verbose())
909 werner 389
                qCDebug(abe) << stand->context() << "evaluate constraint (expr:" << expr->expression() << ") for stand" << stand->id() << ":" << result;
870 werner 390
            return result > 0.;
391
 
392
        }
393
    case ftJavascript: {
394
        // call javascript function
395
        // provide the execution context
396
        FomeScript::setExecutionContext(stand);
397
        QJSValue result = const_cast<QJSValue&>(func).call();
398
        if (result.isError()) {
399
            throw IException(QString("Erron in evaluating constraint  (JS) for stand %1: %2").
400
                             arg(stand->id()).
401
                             arg(result.toString()));
402
        }
403
        if (FMSTP::verbose())
909 werner 404
            qCDebug(abe) << "evaluate constraint (JS) for stand" << stand->id() << ":" << result.toString();
934 werner 405
        // convert boolean result to 1 - 0
406
        if (result.isBool())
407
            return result.toBool()==true?1.:0;
408
        else
409
            return result.toNumber();
870 werner 410
    }
411
 
412
    }
413
    return true;
414
}
415
 
944 werner 416
QString DynamicExpression::dump() const
870 werner 417
{
418
    switch (filter_type){
419
    case ftInvalid: return "Invalid";
420
    case ftExpression: return expr->expression();
421
    case ftJavascript: return func.toString();
422
    default: return "invalid filter type!";
423
    }
424
}
425
 
426
 
427
 
428
/***************************************************************************/
429
/***************************  Activity  ************************************/
430
/***************************************************************************/
431
 
432
Activity::Activity(const FMSTP *parent)
433
{
434
    mProgram = parent;
871 werner 435
    mIndex = 0;
875 werner 436
    mBaseActivity = ActivityFlags(this);
878 werner 437
    mBaseActivity.setActive(true);
438
    mBaseActivity.setEnabled(true);
870 werner 439
}
440
 
441
Activity::~Activity()
442
{
443
 
444
}
445
 
891 werner 446
Activity *Activity::createActivity(const QString &type, FMSTP *stp)
447
{
448
    Activity *act = 0;
449
 
450
    if (type=="general")
451
        act = new ActGeneral(stp);
452
 
453
    if (type=="scheduled")
454
        act = new ActScheduled(stp);
455
 
902 werner 456
    if (type=="planting")
457
        act = new ActPlanting(stp);
458
 
905 werner 459
    if (type=="salvage")
460
        act = new ActSalvage(stp);
461
 
923 werner 462
    if (type=="thinning")
463
        act = new ActThinning(stp);
464
 
891 werner 465
    if (!act) {
466
        throw IException(QString("Error: the activity type '%1' is not a valid type.").arg(type));
467
    }
468
 
469
    return act;
470
}
471
 
871 werner 472
QString Activity::type() const
870 werner 473
{
474
    return "base";
475
}
476
 
477
void Activity::setup(QJSValue value)
478
{
479
    mSchedule.setup(FMSTP::valueFromJs(value, "schedule", "", "setup activity"));
890 werner 480
    if (FMSTP::verbose())
909 werner 481
        qCDebug(abeSetup) << mSchedule.dump();
870 werner 482
 
483
    // setup of events
484
    mEvents.clear();
914 werner 485
    mEvents.setup(value, QStringList() << "onCreate" << "onSetup" << "onEnter" << "onExit" << "onExecute" << "onExecuted" << "onCancel");
890 werner 486
    if (FMSTP::verbose())
909 werner 487
        qCDebug(abeSetup) << "Events: " << mEvents.dump();
870 werner 488
 
489
    // setup of constraints
888 werner 490
    QJSValue constraints = FMSTP::valueFromJs(value, "constraint");
870 werner 491
    if (!constraints.isUndefined())
492
        mConstraints.setup(constraints);
493
 
944 werner 494
    // enabledIf property
495
    QJSValue enabled_if = FMSTP::valueFromJs(value, "enabledIf");
496
    if (!enabled_if.isUndefined())
497
        mEnabledIf.setup(enabled_if);
870 werner 498
}
499
 
955 werner 500
double Activity::scheduleProbability(FMStand *stand, const int specific_year)
875 werner 501
{
897 werner 502
    // return a value between 0 and 1; return -1 if the activity is expired.
955 werner 503
    return schedule().value(stand, specific_year);
875 werner 504
}
505
 
889 werner 506
double Activity::execeuteProbability(FMStand *stand)
875 werner 507
{
508
    // check the standard constraints and return true when all constraints are fulfilled (or no constraints set)
509
    return constraints().evaluate(stand);
510
}
511
 
870 werner 512
bool Activity::execute(FMStand *stand)
513
{
514
    // execute the "onExecute" event
887 werner 515
    events().run(QStringLiteral("onExecute"), stand);
870 werner 516
    return true;
517
}
518
 
891 werner 519
bool Activity::evaluate(FMStand *stand)
520
{
904 werner 521
    // execute the "onEvaluate" event: the execution is canceled, if the function returns false.
1061 werner 522
    bool cancel = events().run(QStringLiteral("onEvaluate"), stand).toBool();
904 werner 523
    return !cancel;
891 werner 524
}
525
 
944 werner 526
void Activity::evaluateDyanamicExpressions(FMStand *stand)
527
{
528
    // evaluate the enabled-if property and set the enabled flag of the stand (i.e. the ActivityFlags)
529
    if (mEnabledIf.isValid()) {
530
        bool result = mEnabledIf.evaluate(stand);
531
        stand->flags(mIndex).setEnabled(result);
532
    }
533
}
534
 
870 werner 535
QStringList Activity::info()
536
{
537
    QStringList lines;
896 werner 538
    lines << QString("Activity '%1': type '%2'").arg(name()).arg(type());
539
    lines << "Events" << "-" << events().dump() << "/-";
540
    lines << "Schedule" << "-" << schedule().dump() << "/-";
541
    lines << "Constraints" << "-" << constraints().dump() << "/-";
870 werner 542
    return lines;
543
}
544
 
545
 
871 werner 546
ActivityFlags &Activity::standFlags(FMStand *stand)
547
{
548
    // use the base data item if no specific stand is provided
549
    if (!stand)
550
        return mBaseActivity;
551
 
552
    // return the flags associated with the specific stand
553
    return stand->flags(mIndex);
554
}
555
 
556
 
870 werner 557
} // namespace
558