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 |