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
********************************************************************************************/
19
 
908 werner 20
#include "abe_global.h"
875 werner 21
#include "scheduler.h"
22
 
23
#include "fmstand.h"
24
#include "activity.h"
25
#include "fmunit.h"
878 werner 26
#include "fmstp.h"
889 werner 27
#include "forestmanagementengine.h"
921 werner 28
#include "agent.h"
29
#include "agenttype.h"
875 werner 30
 
889 werner 31
#include "mapgrid.h"
904 werner 32
#include "expression.h"
889 werner 33
 
907 werner 34
namespace ABE {
875 werner 35
 
1095 werner 36
/** @class Scheduler
37
    @ingroup abe
38
    The Scheduler class implements the logic of scheduling the when and what of activties.
875 werner 39
 
1095 werner 40
  */
41
 
42
 
889 werner 43
void Scheduler::addTicket(FMStand *stand, ActivityFlags *flags, double prob_schedule, double prob_execute)
875 werner 44
{
878 werner 45
    if (FMSTP::verbose())
939 werner 46
        qCDebug(abe)<< "ticked added for stand" << stand->id();
920 werner 47
 
905 werner 48
    flags->setIsPending(true);
889 werner 49
    SchedulerItem *item = new SchedulerItem();
50
    item->stand = stand;
51
    item->flags = flags;
930 werner 52
    item->enterYear = ForestManagementEngine::instance()->currentYear();
942 werner 53
    item->optimalYear = item->enterYear + flags->activity()->optimalSchedule(stand->U())- stand->absoluteAge();
957 werner 54
    item->scheduledYear = item->optimalYear;
930 werner 55
    // estimate growth from now to the optimal time - we assume that growth of the last decade continues
56
    int t = item->optimalYear - item->enterYear; // in t years harvest is optimal
57
    double time_factor = 0.;
58
    if (stand->volume()>0.)
59
        time_factor = t* stand->meanAnnualIncrement()/stand->volume();
60
    item->harvest = stand->scheduledHarvest() * (1. + time_factor);
61
    item->harvestPerHa = item->harvest / stand->area();
909 werner 62
    item->harvestType = flags->isFinalHarvest()? EndHarvest : Thinning;
889 werner 63
    item->scheduleScore = prob_schedule;
64
    item->harvestScore = prob_execute;
65
    item->forbiddenTo = 0;
66
    item->calculate(); // set score
920 werner 67
    mItems.push_back(item);
875 werner 68
}
69
 
70
 
889 werner 71
void Scheduler::run()
72
{
73
    // update the plan if necessary...
892 werner 74
    if (FMSTP::verbose() && mItems.size()>0)
909 werner 75
        qCDebug(abe) << "running scheduler for unit" << mUnit->id() << ". # of active items:" << mItems.size();
889 werner 76
 
892 werner 77
    double harvest_in_queue = 0.;
921 werner 78
    double total_final_harvested = mExtraHarvest;
79
    double total_thinning_harvested = 0.;
1157 werner 80
    //mExtraHarvest = 0.;
921 werner 81
    if (FMSTP::verbose() && total_final_harvested>0.)
82
        qCDebug(abe) << "Got extra harvest (e.g. salvages), m3=" << total_final_harvested;
892 werner 83
 
954 werner 84
    int current_year = ForestManagementEngine::instance()->currentYear();
85
 
889 werner 86
    // update the schedule probabilities....
892 werner 87
    QList<SchedulerItem*>::iterator it = mItems.begin();
88
    while (it!=mItems.end()) {
889 werner 89
        SchedulerItem *item = *it;
954 werner 90
 
889 werner 91
        double p_sched = item->flags->activity()->scheduleProbability(item->stand);
92
        item->scheduleScore = p_sched;
93
        item->calculate();
892 werner 94
        if (item->stand->trace())
909 werner 95
            qCDebug(abe) << item->stand->context() << "scheduler scores (harvest schedule total): " << item->harvestScore << item->scheduleScore << item->score;
892 werner 96
 
889 werner 97
        // drop item if no schedule to happen any more
98
        if (item->score == 0.) {
99
            if (item->stand->trace())
909 werner 100
                qCDebug(abe) << item->stand->context() << "dropped activity" << item->flags->activity()->name() << "from scheduler.";
929 werner 101
 
102
            item->flags->setIsPending(false);
103
            item->flags->setActive(false);
104
 
889 werner 105
            item->stand->afterExecution(true); // execution canceled
892 werner 106
            it = mItems.erase(it);
889 werner 107
            delete item;
892 werner 108
        } else {
954 werner 109
 
110
            // handle item
111
            harvest_in_queue += item->harvest;
892 werner 112
            ++it;
889 werner 113
        }
114
    }
115
 
956 werner 116
    if (mUnit->agent()->schedulerOptions().useScheduler)
117
        updateCurrentPlan();
954 werner 118
 
889 werner 119
    // sort the probabilities, highest probs go first....
956 werner 120
    //qSort(mItems);
121
    //qSort(mItems.begin(), mItems.end(), )
122
    std::sort(mItems.begin(), mItems.end(), ItemComparator());
910 werner 123
    if (FMSTP::verbose())
124
        dump();
889 werner 125
 
892 werner 126
    int no_executed = 0;
127
    double harvest_scheduled = 0.;
889 werner 128
    // now execute the activities with the highest ranking...
892 werner 129
 
130
    it = mItems.begin();
131
    while (it!=mItems.end()) {
889 werner 132
        SchedulerItem *item = *it;
936 werner 133
        // ignore stands that are currently banned (only for final harvests)
134
        if (item->forbiddenTo > current_year && item->flags->isFinalHarvest()) {
893 werner 135
            ++it;
889 werner 136
            continue;
893 werner 137
        }
889 werner 138
 
955 werner 139
        if (item->scheduledYear > current_year)
140
            break; // finished! TODO: check if this works ok ;)
141
 
892 werner 142
        bool remove = false;
921 werner 143
        bool final_harvest = item->flags->isFinalHarvest();
907 werner 144
        //
921 werner 145
        double rel_harvest;
146
        if (final_harvest)
147
            rel_harvest = total_final_harvested / mUnit->area() / mFinalCutTarget;
148
        else
149
            rel_harvest = total_thinning_harvested/mUnit->area() / mThinningTarget;
955 werner 150
 
151
 
956 werner 152
        double min_exec_probability = 0; // calculateMinProbability( rel_harvest );
957 werner 153
        rel_harvest = (total_final_harvested+total_thinning_harvested)/ mUnit->area() / (mFinalCutTarget+mThinningTarget);
958 werner 154
        if (rel_harvest > mUnit->agent()->schedulerOptions().maxHarvestLevel)
956 werner 155
            break;
907 werner 156
 
958 werner 157
        if (rel_harvest + item->harvest/mUnit->area()/(mFinalCutTarget+mThinningTarget) > mUnit->agent()->schedulerOptions().maxHarvestLevel) {
957 werner 158
            // including the *current* harvest, the threshold would be exceeded -> draw a random number
159
            if (drandom() <0.5)
160
                break;
161
        }
956 werner 162
 
957 werner 163
 
907 werner 164
        if (item->score >= min_exec_probability) {
165
 
889 werner 166
            // execute activity:
892 werner 167
            if (item->stand->trace())
930 werner 168
                qCDebug(abe) << item->stand->context() << "execute activity" << item->flags->activity()->name() << "score" << item->score << "planned harvest:" << item->harvest;
169
            harvest_scheduled += item->harvest;
892 werner 170
 
889 werner 171
            bool executed = item->flags->activity()->execute(item->stand);
921 werner 172
            if (final_harvest)
173
                total_final_harvested += item->stand->totalHarvest();
174
            else
175
                total_thinning_harvested += item->stand->totalHarvest();
905 werner 176
 
889 werner 177
            item->flags->setIsPending(false);
901 werner 178
            if (!item->flags->activity()->isRepeatingActivity()) {
179
                item->flags->setActive(false);
180
                item->stand->afterExecution(!executed); // check what comes next for the stand
181
            }
892 werner 182
            no_executed++;
183
 
889 werner 184
            // flag neighbors of the stand, if a clearcut happened
185
            // this is to avoid large unforested areas
921 werner 186
            if (executed && final_harvest) {
909 werner 187
                if (FMSTP::verbose()) qCDebug(abe) << item->stand->context() << "ran final harvest -> flag neighbors";
974 werner 188
                // simple rule: do not allow harvests for neighboring stands for 7 years
189
                item->forbiddenTo = current_year + 7;
889 werner 190
                QList<int> neighbors = ForestManagementEngine::instance()->standGrid()->neighborsOf(item->stand->id());
191
                for (QList<SchedulerItem*>::iterator nit = mItems.begin(); nit!=mItems.end(); ++nit)
192
                    if (neighbors.contains((*nit)->stand->id()))
957 werner 193
                        (*nit)->forbiddenTo = current_year + 7;
889 werner 194
 
195
            }
196
 
892 werner 197
            remove = true;
889 werner 198
 
199
        }
892 werner 200
        if (remove) {
201
            // removing item from scheduler
202
            if (item->stand->trace())
909 werner 203
                qCDebug(abe) << item->stand->context() << "removing activity" << item->flags->activity()->name() << "from scheduler.";
892 werner 204
            it = mItems.erase(it);
205
            delete item;
206
 
207
        } else {
208
            ++it;
209
        }
889 werner 210
    }
892 werner 211
    if (FMSTP::verbose() && no_executed>0)
909 werner 212
        qCDebug(abe) << "scheduler finished for" << mUnit->id() << ". # of items executed (n/volume):" << no_executed << "(" << harvest_scheduled << "m3), total:" << mItems.size() << "(" << harvest_in_queue << "m3)";
892 werner 213
 
889 werner 214
}
215
 
905 werner 216
bool Scheduler::forceHarvest(const FMStand *stand, const int max_years)
217
{
218
    // check if we have the stand in the list:
951 werner 219
    for (QList<SchedulerItem*>::const_iterator nit = mItems.constBegin(); nit!=mItems.constEnd(); ++nit) {
220
        const SchedulerItem *item = *nit;
221
        if (item->stand == stand)
222
            if (abs(item->optimalYear -  GlobalSettings::instance()->currentYear()) < max_years ) {
223
                item->flags->setExecuteImmediate(true);
224
                return true;
225
            }
226
    }
227
    return false;
905 werner 228
}
229
 
907 werner 230
void Scheduler::addExtraHarvest(const FMStand *stand, const double volume, Scheduler::HarvestType type)
231
{
910 werner 232
    Q_UNUSED(stand); Q_UNUSED(type); // at least for now
907 werner 233
    mExtraHarvest += volume;
234
}
235
 
921 werner 236
double Scheduler::plannedHarvests(double &rFinal, double &rThinning)
915 werner 237
{
921 werner 238
    rFinal = 0.; rThinning = 0.;
915 werner 239
    int current_year = ForestManagementEngine::instance()->currentYear();
240
    for (QList<SchedulerItem*>::const_iterator nit = mItems.constBegin(); nit!=mItems.constEnd(); ++nit)
1032 werner 241
        if ((*nit)->optimalYear < current_year + 10) {
921 werner 242
            if ((*nit)->flags->isFinalHarvest()) {
243
                rFinal += (*nit)->harvest; // scheduled harvest in m3
244
            } else {
245
                rThinning += (*nit)->harvest;
246
            }
1032 werner 247
        }
915 werner 248
 
921 werner 249
    return rFinal + rThinning;
250
 
915 werner 251
}
252
 
889 werner 253
double Scheduler::scoreOf(const int stand_id) const
254
{
255
    // lookup stand in scheduler list
256
    SchedulerItem *item = 0;
257
    for (QList<SchedulerItem*>::const_iterator nit = mItems.constBegin(); nit!=mItems.constEnd(); ++nit)
258
        if ((*nit)->stand->id() == stand_id) {
259
            item = *nit;
260
            break;
261
        }
262
    if (!item)
263
        return -1;
264
 
265
    return item->score;
266
}
267
 
903 werner 268
QStringList Scheduler::info(const int stand_id) const
269
{
270
    SchedulerItem *si = item(stand_id);
271
    if (!si)
272
        return QStringList();
273
    QStringList lines = QStringList();
274
    lines << "-";
909 werner 275
    lines << QString("type: %1").arg(si->harvestType==Thinning?QStringLiteral("Thinning"):QStringLiteral("End harvest"));
903 werner 276
    lines << QString("schedule score: %1").arg(si->scheduleScore);
277
    lines << QString("total score: %1").arg(si->score);
278
    lines << QString("scheduled vol/ha: %1").arg(si->harvestPerHa);
279
    lines << QString("postponed to year: %1").arg(si->forbiddenTo);
280
    lines << QString("in scheduler since: %1").arg(si->enterYear);
281
    lines << "/-";
282
    return lines;
283
}
284
 
955 werner 285
void Scheduler::updateCurrentPlan()
286
{
956 werner 287
    if (mItems.isEmpty())
955 werner 288
        return;
289
    double scheduled_harvest[MAX_YEARS];
290
    double state[MAX_YEARS];
954 werner 291
 
955 werner 292
    for (int i=0;i<MAX_YEARS;++i) {
293
        scheduled_harvest[i]=0.;
294
        state[i] = 0.;
295
    }
296
 
297
    scheduled_harvest[0] = mExtraHarvest; // salvaging
298
    mSchedule.clear();
299
    int current_year = ForestManagementEngine::instance()->currentYear();
300
    int max_year = 0;
301
    double total_plan = mExtraHarvest;
302
    for (QList<SchedulerItem*>::const_iterator it=mItems.begin(); it!=mItems.end(); ++it) {
303
        SchedulerItem *item = *it;
304
        mSchedule.insert(qMax(item->optimalYear, current_year), item);
305
        total_plan += item->harvest;
306
        int year_index = qMin(qMax(0, item->optimalYear-current_year),MAX_YEARS-1);
307
        scheduled_harvest[ year_index ] += item->harvest;
308
        max_year = qMax(max_year, year_index);
309
    }
310
 
311
    double mean_harvest = total_plan / (max_year + 1.);
312
    double level = (mFinalCutTarget + mThinningTarget) * mUnit->area();
313
 
314
    level = qMax(level, mean_harvest);
315
 
316
    for (int i=0;i<MAX_YEARS;++i)
317
        state[i] = scheduled_harvest[i]>level? 1. : 0.;
318
 
956 werner 319
    int max_iter = mItems.size() * 10;
955 werner 320
    bool updated = false;
321
    do {
322
 
956 werner 323
        updated = false;
955 werner 324
        do {
325
            // look for a relocate candidate and relocate
326
 
327
            // look for the highest planned harvest
328
            int year=-1; double max_harvest = -1.;
329
            for (int i=0;i<MAX_YEARS;++i) {
330
                if (scheduled_harvest[i]>max_harvest && state[i] == 1.) {
331
                    year = i;
332
                    max_harvest = scheduled_harvest[i];
333
                }
334
            }
335
            // if no further slot is found, then stop
336
            if (year==-1)
337
                break;
338
            // if the maximum harvest in the next x years is below the current plan,
339
            // then we simply call it a day (and execute everything on its "optimal" point in time)
340
            if (max_harvest < level)
341
                break;
342
            state[year] = -1.; // processed
343
            // pick an element of that year and try to find another year
1164 werner 344
            int pick = irandom(0, mSchedule.count(year + current_year));
955 werner 345
            QMultiHash<int, SchedulerItem*>::iterator i = mSchedule.find(year + current_year);
346
            while (i!=mSchedule.end() && i.key()==year+current_year) {
347
                if (pick--==0) // select 'pick'ed element
348
                    break;
349
                ++i;
350
            }
1157 werner 351
            if (i==mSchedule.end() || i.key()!=year+current_year) {
352
                qCDebug(abe) << "updateCurrentPlan(): no item found for year" << year << ", #elements:" << mSchedule.count(year + current_year);
353
                break;
354
            }
955 werner 355
 
356
            SchedulerItem *item = i.value();
357
            // try to change something only if the years' schedule is above the level without the focal item
358
            if (scheduled_harvest[year]-item->harvest > level ) {
359
                //
360
                int calendar_year = year + current_year;
361
                int dist = -1;
362
                do {
363
                    double value = item->flags->activity()->scheduleProbability(item->stand, calendar_year + dist);
364
                    if (value>0. && year+dist>=0 && year+dist<MAX_YEARS) {
365
                        if (state[year+dist] == 0.) {
366
                            // simple: finish!
367
                            mSchedule.erase(i);
368
                            scheduled_harvest[year] -= item->harvest;
369
                            scheduled_harvest[year+dist] += item->harvest;
370
                            mSchedule.insert(calendar_year+dist, item);
371
                            updated = true;
372
                            // reset also the processed flag
373
                            state[year] = scheduled_harvest[year]>level? 1. : 0.;
374
                            state[year+dist] = scheduled_harvest[year+dist]>level? 1. : 0.;
375
                            break;
376
                        }
377
                    }
378
                    // series of: -1 +1 -2 +2 -3 +3 ...
379
                    if (dist<0)
956 werner 380
                        dist = -dist; // switch sign
955 werner 381
                    else
956 werner 382
                        dist = - (dist+1); // switch sign and add 1
383
                } while (dist<MAX_YEARS);
955 werner 384
                if (updated)
385
                    break;
386
            } // end if
387
            if (--max_iter<0) {
956 werner 388
                qCDebug(abe) << "scheduler: max iterations reached in updateCurrentPlan()";
955 werner 389
                break;
390
            }
391
 
392
 
393
        } while (1==1); // continue until no further candidate exists or a relocate happened
394
 
395
    } while (updated); // stop when no new candidate is found
396
 
397
    // write back the execution plan....
398
    for (QMultiHash<int, SchedulerItem*>::iterator it=mSchedule.begin(); it!=mSchedule.end(); ++it)
399
        it.value()->scheduledYear = it.key();
400
 
1063 werner 401
    if (FMSTP::verbose())
402
        dump();
955 werner 403
}
404
 
405
 
1063 werner 406
void Scheduler::dump() const
910 werner 407
{
408
    if(mItems.isEmpty())
409
        return;
410
    qCDebug(abe)<< "***** Scheduler items **** Unit:" << mUnit->id();
956 werner 411
    qCDebug(abe)<< "stand.id, scheduled.year, score, opt.year, act.name, planned.harvest";
1063 werner 412
    QList<SchedulerItem*>::const_iterator it = mItems.begin();
910 werner 413
    while (it!=mItems.end()) {
414
        SchedulerItem *item = *it;
956 werner 415
        qCDebug(abe) << QString("%1, %2, %3, %4, %5, %6").arg(item->stand->id()).arg(item->scheduledYear).arg(item->score).arg(item->optimalYear)
951 werner 416
                        .arg(item->flags->activity()->name())
417
                        .arg(item->harvest);
910 werner 418
        ++it;
419
    }
420
}
421
 
903 werner 422
Scheduler::SchedulerItem *Scheduler::item(const int stand_id) const
423
{
424
    for (QList<SchedulerItem*>::const_iterator nit = mItems.constBegin(); nit!=mItems.constEnd(); ++nit)
425
        if ((*nit)->stand->id() == stand_id) {
426
            return *nit;
427
        }
428
    return 0;
429
}
430
 
956 werner 431
 
889 werner 432
bool Scheduler::SchedulerItem::operator<(const Scheduler::SchedulerItem &item)
433
{
910 werner 434
    // sort *descending*, i.e. after sorting, the item with the highest score is in front.
951 werner 435
    //    if (this->score == item.score)
436
    //        return this->enterYear < item.enterYear; // higher prob. for items that entered earlier TODO: change to due/overdue
955 werner 437
    if (this->scheduledYear==item.scheduledYear)
438
        return this->score > item.score;
439
    else
440
        return this->scheduledYear < item.scheduledYear;
889 werner 441
}
442
 
443
void Scheduler::SchedulerItem::calculate()
444
{
445
    if (flags->isExecuteImmediate())
446
        score = 1.1; // above 1
447
    else
448
        score = scheduleScore * harvestScore;
929 werner 449
 
450
    if (score<0.)
451
        score = 0.;
889 werner 452
}
453
 
454
 
904 werner 455
// **************************************************************************************
951 werner 456
QStringList SchedulerOptions::mAllowedProperties = QStringList()
958 werner 457
        << "minScheduleHarvest" << "maxScheduleHarvest" << "maxHarvestLevel"
951 werner 458
        << "useSustainableHarvest" << "scheduleRebounceDuration" << "deviationDecayRate"
958 werner 459
        << "enabled" << "harvestIntensity";
951 werner 460
 
904 werner 461
 
462
void SchedulerOptions::setup(QJSValue jsvalue)
463
{
464
    useScheduler = false;
939 werner 465
    if (!jsvalue.isObject()) {
466
        qCDebug(abeSetup) << "Scheduler options are not an object:" << jsvalue.toString();
904 werner 467
        return;
939 werner 468
    }
951 werner 469
    FMSTP::checkObjectProperties(jsvalue, mAllowedProperties, "setup of scheduler options");
470
 
904 werner 471
    minScheduleHarvest = FMSTP::valueFromJs(jsvalue, "minScheduleHarvest","0").toNumber();
472
    maxScheduleHarvest = FMSTP::valueFromJs(jsvalue, "maxScheduleHarvest","10000").toNumber();
958 werner 473
    maxHarvestLevel = FMSTP::valueFromJs(jsvalue, "maxHarvestLevel","2").toNumber();
1051 werner 474
    qCDebug(abe) << "maxHarvestLevel" << maxHarvestLevel;
921 werner 475
    useSustainableHarvest = FMSTP::valueFromJs(jsvalue, "useSustainableHarvest", "1").toNumber();
476
    if (useSustainableHarvest<0. || useSustainableHarvest>1.)
477
        throw IException("Setup of scheduler-options: invalid value for 'useSustainableHarvest' (0..1 allowed).");
478
 
956 werner 479
    harvestIntensity = FMSTP::valueFromJs(jsvalue, "harvestIntensity", "1").toNumber();
904 werner 480
    scheduleRebounceDuration = FMSTP::valueFromJs(jsvalue, "scheduleRebounceDuration", "5").toNumber();
915 werner 481
    if (scheduleRebounceDuration==0.)
482
        throw IException("Setup of scheduler-options: '0' is not a valid value for 'scheduleRebounceDuration'!");
922 werner 483
    // calculate the "tau" of a exponential decay function based on the provided half-time
484
    scheduleRebounceDuration = scheduleRebounceDuration / log(2.);
921 werner 485
    deviationDecayRate = FMSTP::valueFromJs(jsvalue, "deviationDecayRate","0").toNumber();
486
    if (deviationDecayRate==1.)
915 werner 487
        throw IException("Setup of scheduler-options: '0' is not a valid value for 'deviationDecayRate'!");
921 werner 488
    deviationDecayRate = 1. - deviationDecayRate; // if eg value is 0.05 -> multiplier 0.95
956 werner 489
    useScheduler = FMSTP::boolValueFromJs(jsvalue, "enabled", true);
904 werner 490
 
491
}
492
 
956 werner 493
bool Scheduler::ItemComparator::operator()(const Scheduler::SchedulerItem *lx, const Scheduler::SchedulerItem *rx) const
494
{
495
    if (lx->scheduledYear==rx->scheduledYear)
496
        return lx->score > rx->score;
497
    else
498
        return lx->scheduledYear < rx->scheduledYear;
904 werner 499
 
956 werner 500
}
501
 
502
 
875 werner 503
} // namespace