Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

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