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 |