1 /* This file is part of the KDE project
2 * Copyright (C) 2009, 2010, 2012 Dag Andersen <danders@get2net.dk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 // clazy:excludeall=qstring-arg
21 #include "KPlatoRCPSScheduler.h"
22
23 #include "kptproject.h"
24 #include "kptschedule.h"
25 #include "kptresource.h"
26 #include "kpttask.h"
27 #include "kptrelation.h"
28 #include "kptdebug.h"
29
30 #include <librcps.h>
31
32 #include <QString>
33 #include <QTimer>
34 #include <QMutexLocker>
35 #include <QLocale>
36
37 #include <KLocalizedString>
38 #include <KFormat>
39
40 #include <iostream>
41
42 #define GENERATION_MIN_LIMIT 5000
43
44 #define PROGRESS_CALLBACK_FREQUENCY 100
45 #define PROGRESS_MAX_VALUE 120000
46 #define PROGRESS_INIT_VALUE 12000
47 #define PROGRESS_INIT_STEP 2000
48
49 /* low weight == late, high weight == early */
50 #define WEIGHT_ASAP 50
51 #define WEIGHT_ALAP 1
52 #define WEIGHT_CONSTRAINT 1000
53 #define WEIGHT_FINISH 1000
54
55 #define GROUP_TARGETTIME 1
56 #define GROUP_CONSTRAINT 2
57
58
59 class ProgressInfo
60 {
61 public:
ProgressInfo()62 explicit ProgressInfo() : init(true), base(0), progress(0)
63 {
64 fitness.group = 0;
65 fitness.weight = 0;
66 }
67 bool init;
68 int base;
69 int progress;
70 struct rcps_fitness fitness;
71 };
72
73
KPlatoRCPSScheduler(Project * project,ScheduleManager * sm,ulong granularity,QObject * parent)74 KPlatoRCPSScheduler::KPlatoRCPSScheduler(Project *project, ScheduleManager *sm, ulong granularity, QObject *parent)
75 : SchedulerThread(project, sm, parent),
76 result(-1),
77 m_schedule(0),
78 m_recalculate(false),
79 m_usePert(false),
80 m_backward(false),
81 m_problem(0),
82 m_timeunit(granularity / 1000),
83 m_offsetFromTime_t(0),
84 m_progressinfo(new ProgressInfo())
85 {
86 connect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), project, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
87 emit sigCalculationStarted(project, sm);
88
89 connect(this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), project, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)));
90 }
91
~KPlatoRCPSScheduler()92 KPlatoRCPSScheduler::~KPlatoRCPSScheduler()
93 {
94 delete m_progressinfo;
95 qDeleteAll(m_duration_info_list);
96 qDeleteAll(m_weight_info_list);
97 rcps_problem_free(m_problem);
98 }
99
progress_callback(int generations,struct rcps_fitness fitness,void * arg)100 int KPlatoRCPSScheduler::progress_callback(int generations, struct rcps_fitness fitness, void *arg)
101 {
102 if (arg == 0) {
103 return -1;
104 }
105 KPlatoRCPSScheduler *self = static_cast<KPlatoRCPSScheduler*>(arg);
106 //debugPlan<<"KPlatoRCPSScheduler::progress_callback"<<generations<<fitness<<arg;
107 return self->progress(generations, fitness);
108 }
109
progress(int generations,struct rcps_fitness fitness)110 int KPlatoRCPSScheduler::progress(int generations, struct rcps_fitness fitness)
111 {
112 if (m_haltScheduling) {
113 debugPlan<<"KPlatoRCPSScheduler::progress:"<<"halt";
114 return -1;
115 }
116 if (m_stopScheduling) {
117 m_schedule->logWarning(i18n("Scheduling halted after %1 generations", generations), 1);
118 debugPlan<<"KPlatoRCPSScheduler::progress:"<<"stop";
119 return -1;
120 }
121 // std::cout << "Progress after: " << generations << " generations\n";
122 if (m_progressinfo->init) {
123 if (generations == 0) {
124 m_progressinfo->progress += PROGRESS_INIT_STEP;
125 } else {
126 m_progressinfo->progress = PROGRESS_INIT_VALUE;
127 m_progressinfo->init = false;
128 // std::cout << "Population generated: "<< generations << "\n";
129 }
130 } else {
131 m_progressinfo->progress = PROGRESS_INIT_VALUE + generations;
132 }
133 // detect change in fitness
134 if (rcps_fitness_cmp(&m_progressinfo->fitness, &fitness) != 0) {
135 // std::cout << "Fitness changed in generation: " << generations << " group=["<<m_progressinfo->fitness.group<<"->"<<fitness.group<<"]"<<" weight=["<<m_progressinfo->fitness.weight<<"->"<<fitness.weight<<"]\n";
136 m_progressinfo->fitness = fitness;
137 m_progressinfo->base = generations;
138 }
139 m_manager->setProgress(m_progressinfo->progress);
140 setProgress(m_progressinfo->progress);
141 // stop if fitness does not change in GENERATION_MIN_LIMIT generations
142 /* int result = (generations >= m_progressinfo->base + GENERATION_MIN_LIMIT ? 1 : 0);
143 if (result) {
144 //debugPlan<<"KPlatoRCPSScheduler::progress, stop after"<<generations<<"generations, progress:"<<m_progressinfo->progress;
145 m_schedule->logDebug(QString("Acceptable solution found after %1 generations").arg(generations), 1);
146 std::cout << "Acceptable solution found after " << generations << " generations\n";
147 }*/
148 return 0;
149 }
150
duration_callback(int direction,int time,int nominal_duration,void * arg)151 int KPlatoRCPSScheduler::duration_callback(int direction, int time, int nominal_duration, void *arg)
152 {
153 //debugPlan<<"plan_duration:"<<direction<<time<<nominal_duration<<arg;
154 if (arg == 0) {
155 return nominal_duration;
156 }
157 KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>(arg);
158 return info->self->duration(direction, time, nominal_duration, info);
159 }
160
duration(int direction,int time,int nominal_duration,KPlatoRCPSScheduler::duration_info * info)161 int KPlatoRCPSScheduler::duration(int direction, int time, int nominal_duration, KPlatoRCPSScheduler::duration_info *info)
162 {
163 if (m_haltScheduling || m_manager == 0) {
164 return nominal_duration;
165 }
166 ++(info->calls);
167 if (info->cache.contains(QPair<int, int>(time, direction))) {
168 return info->cache[ QPair<int, int>(time, direction) ];
169 }
170 if (m_manager->recalculate() && info->task->completion().isFinished()) {
171 return 0;
172 }
173 int dur = 0;
174 if (info->task->constraint() == Node::FixedInterval) {
175 // duration may depend on daylight saving so we need to calculate
176 // NOTE: dur may not be correct if time != info->task->constraintStartTime, let's see what happens...
177 dur = (info->task->constraintEndTime() - info->task->constraintStartTime()).seconds() / m_timeunit;
178 info->task->schedule()->logDebug(QString("Fixed interval: Time=%1, duration=%2 (%3, %4)").arg(time).arg(dur).arg(fromRcpsTime(time).toString()).arg(Duration((qint64)(dur) * m_timeunit * 1000).toDouble(Duration::Unit_h)));
179 } else if (info->estimatetype == Estimate::Type_Effort) {
180 if (info->requests.isEmpty()) {
181 dur = info->estimate.seconds() / m_timeunit;
182 } else {
183 dur = info->task->requests().duration(
184 info->requests,
185 fromRcpsTime(time),
186 info->estimate,
187 0, /*no schedule*/
188 m_backward ? ! direction : direction
189 ).seconds() / m_timeunit;
190 //debugPlan<<info->task->name()<< QString("duration_callback effort: backward=%5, direction=%6 (direction=%7); Time=%1, duration=%2 (%3, %4)").arg(time).arg(dur).arg(fromRcpsTime(time).toString()).arg(Duration((qint64)(dur) * m_timeunit * 1000).toDouble(Duration::Unit_h)).arg(m_backward).arg(direction).arg(m_backward ? !direction : direction);
191 }
192 } else {
193 dur = info->task->length(
194 fromRcpsTime(time),
195 info->estimate,
196 0, /*no schedule*/
197 m_backward ? ! direction : direction
198 ).seconds() / m_timeunit;
199 }
200 info->cache[ QPair<int, int>(time, direction) ] = dur;
201 info->task->schedule()->logDebug(QString("duration_callback: Time=%1, duration=%2 (%3, %4)").arg(time).arg(dur).arg(fromRcpsTime(time).toString()).arg(Duration((qint64)(dur) * m_timeunit * 1000).toDouble(Duration::Unit_h)));
202 return dur;
203 }
204
weight_callback(int time,int duration,struct rcps_fitness * nominal_weight,void * weight_arg,void * fitness_arg)205 int KPlatoRCPSScheduler::weight_callback(int time, int duration, struct rcps_fitness *nominal_weight, void* weight_arg, void* fitness_arg)
206 {
207 //debugPlan<<"plan_weight:"<<time<<nominal_weight<<arg;
208 if (weight_arg == 0) {
209 nominal_weight->weight *= time;
210 return 0;
211 }
212 KPlatoRCPSScheduler::weight_info *winfo = static_cast<KPlatoRCPSScheduler::weight_info*>(weight_arg);
213 KPlatoRCPSScheduler::fitness_info *finfo = static_cast<KPlatoRCPSScheduler::fitness_info*>(fitness_arg);
214 return winfo->self->weight(time, duration, nominal_weight, winfo, finfo);
215 }
216
fitness_callback_init(void * arg)217 void *KPlatoRCPSScheduler::fitness_callback_init(void *arg)
218 {
219 Q_ASSERT(arg);
220 KPlatoRCPSScheduler::fitness_info *info = static_cast<KPlatoRCPSScheduler::fitness_info*>(arg);
221 Q_ASSERT(info);
222 fitness_info *finfo = new fitness_info;
223 finfo->self = info->self;
224 // debugPlan<<info->self;
225 return finfo;
226 }
227
fitness_callback_result(struct rcps_fitness * fit,void * arg)228 int KPlatoRCPSScheduler::fitness_callback_result(struct rcps_fitness *fit, void *arg)
229 {
230 KPlatoRCPSScheduler::fitness_info *info = static_cast<KPlatoRCPSScheduler::fitness_info*>(arg);
231 info->self->fitness(fit, info);
232 delete info;
233 return 0;
234 }
235
fitness(struct rcps_fitness * fit,KPlatoRCPSScheduler::fitness_info * info)236 int KPlatoRCPSScheduler::fitness(struct rcps_fitness *fit, KPlatoRCPSScheduler::fitness_info *info)
237 {
238 /* std::cout << ">-------------------------------------------\n";
239 std::cout << "Sequence: ";
240 foreach (Task *t, info->jobs) { std::cout << (t ? t->name().toLocal8Bit().data() : "End") << ", "; }
241 std::cout << "\n";
242 debugPlan<<info->map;*/
243 QMultiMap<int, QPair<int, Task*> >::const_iterator it = info->map.constFind(GROUP_CONSTRAINT);
244 if (it != info->map.constEnd()) {
245 // constraint
246 fit->group = GROUP_CONSTRAINT;
247 for (; it.key() == GROUP_CONSTRAINT && it != info->map.constEnd(); ++it) {
248 fit->weight += it.value().first;
249 QString s = it.value().second ? it.value().second->name() : "End node";
250 // std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
251 // m_schedule->logDebug(QString("%3: %1 %2").arg(it.key()).arg(it.value().first).arg(it.value().second->name()));
252 }
253 // std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
254 return 0;
255 }
256 it = info->map.constFind(GROUP_TARGETTIME);
257 if (it != info->map.constEnd()) {
258 // missed target time
259 fit->group = GROUP_TARGETTIME;
260 for (; it.key() == GROUP_TARGETTIME && it != info->map.constEnd(); ++it) {
261 fit->weight += it.value().first;
262 QString s = it.value().second ? it.value().second->name() : "End node";
263 // std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
264 // m_schedule->logDebug(QString("%3: %1 %2").arg(it.key()).arg(it.value().first).arg(it.value().second->name()));
265 }
266 // std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
267 return 0;
268 }
269 fit->group = 0;
270 for (it = info->map.constBegin(); it != info->map.constEnd(); ++it) {
271 fit->weight += it.value().first;
272 QString s = it.value().second ? it.value().second->name() : "End node";
273 // std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
274 // m_schedule->logDebug(QString("%3: %1 %2").arg(it.key()).arg(it.value().first).arg(it.value().second->name()));
275 }
276 // std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
277 return 0;
278 }
279
weight(int time,int duration,struct rcps_fitness * nominal_weight,KPlatoRCPSScheduler::weight_info * info,KPlatoRCPSScheduler::fitness_info * finfo)280 int KPlatoRCPSScheduler::weight(int time, int duration, struct rcps_fitness *nominal_weight, KPlatoRCPSScheduler::weight_info* info, KPlatoRCPSScheduler::fitness_info* finfo)
281 {
282 if (m_haltScheduling || m_manager == 0) {
283 return 0;
284 }
285 if (m_manager->recalculate() && info->task->completion().isFinished()) {
286 return 0;
287 }
288 struct rcps_fitness &f = *nominal_weight;
289 f.group = 0;
290 f.weight = time;
291 if (info->isEndJob) {
292 if (info->finish == 0) {
293 info->finish = time;
294 /* const char *s = QString("First : %1 %2 %3 End job").arg(time, 10).arg(duration, 10).arg(w, 10).toLatin1();
295 std::cout<<s<<"\n";*/
296 }
297 /* w = WEIGHT_FINISH * info->finish / (time > 0 ? time : 1);
298 if (time > info->targettime) {
299 w = w + (WEIGHT_CONSTRAINT * (time - info->targettime));
300 }*/
301 if (time > info->targettime) {
302 f.group = GROUP_TARGETTIME;
303 f.weight = time - info->targettime;
304 }
305
306 /* const char *s = QString("End job: %1 %2 %3 End job target: %4").arg(time, 10).arg(duration, 10).arg(w, 10).arg(info->targettime).toLatin1();
307 std::cout<<s<<"\n";*/
308 } else {
309 if (m_backward) {
310 switch (info->task->constraint()) {
311 case Node::FinishNotLater:
312 if (info->targettime > time) {
313 f.group = GROUP_CONSTRAINT;
314 f.weight = WEIGHT_CONSTRAINT * (info->targettime - time);
315 }
316 break;
317 case Node::MustFinishOn:
318 if (info->targettime != time) {
319 f.group = GROUP_CONSTRAINT;
320 f.weight = WEIGHT_CONSTRAINT * abs(info->targettime - time);
321 }
322 break;
323 case Node::StartNotEarlier:
324 if (info->targettime < time) {
325 f.group = GROUP_CONSTRAINT;
326 f.weight = WEIGHT_CONSTRAINT * (time - info->targettime);
327 }
328 break;
329 case Node::MustStartOn:
330 case Node::FixedInterval:
331 if (info->targettime != time) {
332 f.group = GROUP_CONSTRAINT;
333 f.weight = WEIGHT_CONSTRAINT * abs(info->targettime - time);
334 }
335 break;
336 default:
337 break;
338 }
339 /* const char *s = QString("Backward: %1 %2 %3 %4 (target: %5)").arg(time, 10).arg(duration, 10).arg(w, 10).arg(info->task->name()).arg(info->targettime).toLatin1();
340 std::cout<<s<<"\n";*/
341 } else {
342 switch (info->task->constraint()) {
343 case Node::StartNotEarlier:
344 if (time < info->targettime) {
345 f.group = GROUP_CONSTRAINT;
346 f.weight = WEIGHT_CONSTRAINT * (info->targettime - time);
347 }
348 break;
349 case Node::MustStartOn:
350 case Node::FixedInterval:
351 if (info->targettime != time) {
352 f.group = GROUP_CONSTRAINT;
353 f.weight = WEIGHT_CONSTRAINT * (abs(info->targettime - time));
354 }
355 break;
356 case Node::FinishNotLater:
357 // std::cout << "FNL " << info->task->name().toLocal8Bit().data() << ": end="<<time+duration<<" target="<<info->targettime<<"\n";
358 if (time + duration > info->targettime) {
359 f.group = GROUP_CONSTRAINT;
360 f.weight = WEIGHT_CONSTRAINT * (time - info->targettime);
361 }
362 // std::cout << info->task->name().toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";
363 break;
364 case Node::MustFinishOn:
365 // std::cout << "MSO " << info->task->name().toLocal8Bit().data() << ": end="<<time+duration<<" target="<<info->targettime<<"\n";
366 if (info->targettime != time + duration) {
367 f.group = GROUP_CONSTRAINT;
368 f.weight = WEIGHT_CONSTRAINT * abs(info->targettime - time);
369 }
370 // std::cout << info->task->name().toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";
371 break;
372 default:
373 break;
374 }
375 /* const char *s = QString("Forward: %1 %2 %3 %4 (target: %5)").arg(time, 10).arg(duration, 10).arg(w, 10).arg(info->task->name()).arg(info->targettime).toLatin1();
376 std::cout<<s<<"\n";*/
377 }
378 }
379 // QString s = info->task ? info->task->name() : "End node";
380 if (finfo) {
381 finfo->map.insert(f.group, QPair<int, Task*>(f.weight, info->task));
382 finfo->jobs << info->task;
383 // debugPlan<<s<<":"<<finfo->map;
384 }// else debugPlan<<s<<":"<<"No finfo!";
385 /* std::cout << "Weight: " << s.toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";*/
386 return 0;
387 }
388
run()389 void KPlatoRCPSScheduler::run()
390 {
391 if (m_haltScheduling) {
392 deleteLater();
393 return;
394 }
395 if (m_stopScheduling) {
396 return;
397 }
398 { // mutex -->
399 m_projectMutex.lock();
400 m_managerMutex.lock();
401
402 m_project = new Project();
403 loadProject(m_project, m_pdoc);
404 m_project->setName("Schedule: " + m_project->name()); //Debug
405 m_project->stopcalculation = false;
406 m_manager = m_project->scheduleManager(m_mainmanagerId);
407 Q_CHECK_PTR(m_manager);
408 Q_ASSERT(m_manager->expected());
409 Q_ASSERT(m_manager != m_mainmanager);
410 Q_ASSERT(m_manager->scheduleId() == m_mainmanager->scheduleId());
411 Q_ASSERT(m_manager->expected() != m_mainmanager->expected());
412 m_manager->setName("Schedule: " + m_manager->name()); //Debug
413 m_schedule = m_manager->expected();
414
415 connect(m_manager, SIGNAL(sigLogAdded(Schedule::Log)), this, SLOT(slotAddLog(Schedule::Log)));
416
417 m_project->initiateCalculation(*m_schedule);
418 m_project->initiateCalculationLists(*m_schedule);
419
420 m_problem = rcps_problem_new();
421 rcps_problem_setfitness_mode(m_problem, FITNESS_WEIGHT);
422
423 m_usePert = m_manager->usePert();
424 m_recalculate = m_manager->recalculate();
425 if (m_recalculate) {
426 m_starttime = m_manager->recalculateFrom();
427 m_backward = false;
428 } else {
429 m_backward = m_manager->schedulingDirection();
430 m_starttime = m_backward ? m_project->constraintEndTime() : m_project->constraintStartTime();
431 }
432 m_targettime = m_backward ? m_project->constraintStartTime() : m_project->constraintEndTime();
433
434 m_project->setCurrentSchedule(m_manager->expected()->id());
435
436 m_schedule->setPhaseName(0, i18n("Init"));
437 QLocale locale;
438 if (! m_backward) {
439 m_schedule->logDebug(QString("Schedule project using RCPS Scheduler, starting at %1, granularity %2 sec").arg(locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat)).arg(m_timeunit), 0);
440 if (m_recalculate) {
441 m_schedule->logInfo(i18n("Re-calculate project from start time: %1", locale.toString(m_starttime, QLocale::ShortFormat)), 0);
442 } else {
443 m_schedule->logInfo(i18n("Schedule project from start time: %1", locale.toString(m_starttime, QLocale::ShortFormat)), 0);
444 }
445 } else {
446 m_schedule->logDebug(QString("Schedule project backward using RCPS Scheduler, starting at %1, granularity %2 sec").arg(locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat)).arg(m_timeunit), 0);
447 m_schedule->logInfo(i18n("Schedule project from end time: %1", locale.toString(m_starttime, QLocale::ShortFormat)), 0);
448 }
449
450 m_managerMutex.unlock();
451 m_projectMutex.unlock();
452 } // <--- mutex
453 m_progressinfo->progress += PROGRESS_INIT_STEP / 5;
454 setProgress(m_progressinfo->progress);
455
456 result = kplatoToRCPS();
457 if (result != 0) {
458 m_schedule->logError(i18n("Failed to build a valid RCPS project"));
459 setProgress(PROGRESS_MAX_VALUE);
460 return;
461 }
462 m_schedule->setPhaseName(1, i18n("Schedule"));
463
464 setMaxProgress(PROGRESS_MAX_VALUE);
465 solve();
466 if (m_haltScheduling) {
467 deleteLater();
468 return;
469 }
470 if (result != 0) {
471 m_schedule->logError(i18n("Invalid scheduling solution. Result: %1", result), 1);
472 }
473 kplatoFromRCPS();
474 setProgress(PROGRESS_MAX_VALUE);
475 }
476
check()477 int KPlatoRCPSScheduler::check()
478 {
479 return rcps_check(m_problem);
480 }
481
solve()482 void KPlatoRCPSScheduler::solve()
483 {
484 debugPlan<<"KPlatoRCPSScheduler::solve()";
485 struct rcps_solver *s = rcps_solver_new();
486 rcps_solver_set_progress_callback(s, PROGRESS_CALLBACK_FREQUENCY, this, &KPlatoRCPSScheduler::progress_callback);
487 rcps_solver_set_duration_callback(s, &KPlatoRCPSScheduler::duration_callback);
488
489 rcps_problem_set_weight_callback(m_problem, &KPlatoRCPSScheduler::weight_callback);
490 fitness_init_arg.self = this;
491 rcps_problem_set_fitness_callback(m_problem, &KPlatoRCPSScheduler::fitness_callback_init, &fitness_init_arg, &KPlatoRCPSScheduler::fitness_callback_result);
492
493 Q_ASSERT(check() == 0);
494
495 rcps_solver_setparam(s, SOLVER_PARAM_POPSIZE, 1000);
496
497 rcps_solver_solve(s, m_problem);
498 result = rcps_solver_getwarnings(s);
499 rcps_solver_free(s);
500 }
501
kplatoToRCPS()502 int KPlatoRCPSScheduler::kplatoToRCPS()
503 {
504 addResources();
505 addTasks();
506 addDependencies();
507 addRequests();
508 setWeights();
509 setConstraints();
510
511 int r = check();
512 return r;
513 }
514
taskFromRCPSForward(struct rcps_job * job,Task * task,QMap<Node *,QList<ResourceRequest * >> & resourcemap)515 void KPlatoRCPSScheduler::taskFromRCPSForward(struct rcps_job *job, Task *task, QMap<Node*, QList<ResourceRequest*> > &resourcemap)
516 {
517 if (m_haltScheduling || m_manager == 0) {
518 return;
519 }
520 QLocale locale;
521 Schedule *cs = task->currentSchedule();
522 Q_ASSERT(cs);
523 struct rcps_mode *mode = rcps_mode_get(job, rcps_job_getmode_res(job));
524 /* get the duration of this mode */
525 KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>(rcps_mode_get_cbarg(mode));
526 qint64 dur = 0;
527 qint64 st = rcps_job_getstart_res(job);
528 if (info == 0) {
529 dur = rcps_mode_getduration(mode);
530 } else {
531 cs->logDebug(QString("Task '%1' estimate: %2").arg(task->name()).arg(task->estimate()->value(Estimate::Use_Expected, false).toString()), 1);
532 cs->logDebug(QString("Task '%1' duration called %2 times, cached values: %3").arg(rcps_job_getname(job)).arg(info->calls).arg(info->cache.count()));
533
534 dur = duration_callback(0, st, rcps_mode_getduration(mode), info);
535
536 for (QMap<QPair<int, int>, int>::ConstIterator it = info->cache.constBegin(); it != info->cache.constEnd(); ++it) {
537 cs->logDebug(QString("Task '%1' start: %2, duration: %3 (%4, %5 hours)").arg(rcps_job_getname(job)).arg(it.key().first).arg(it.value()).arg(fromRcpsTime(it.key().first).toString()).arg((double)(it.value())/60.0), 1);
538 }
539 }
540 DateTime start = m_starttime.addSecs(st * m_timeunit);
541 DateTime end = start + Duration(dur * m_timeunit * 1000);
542 cs->logDebug(QString("Task '%1' start=%2, duration=%3: %4 - %5").arg(rcps_job_getname(job)).arg(st).arg(dur).arg(locale.toString(start, QLocale::ShortFormat)).arg(locale.toString(end, QLocale::ShortFormat)), 1);
543 task->setStartTime(start);
544 task->setEndTime(end);
545 for (int reqs = 0; reqs < rcps_request_count(mode); ++reqs) {
546 struct rcps_request *req = rcps_request_get(mode, reqs);
547 struct rcps_alternative *alt = rcps_alternative_get(req, rcps_request_getalternative_res(req));
548 int amount = rcps_alternative_getamount(alt);
549 struct rcps_resource *res = rcps_alternative_getresource(alt);
550
551 cs->logDebug(QString("Job %1: resource %2 is %3 available").arg(rcps_job_getname(job)).arg(rcps_resource_getname(res)).arg(amount), 1);
552
553 // do actual appointments etc
554 ResourceRequest *r = m_requestmap.value(req);
555 if (r == 0) {
556 cs->logWarning(i18n("No resource request is registered"), 1);
557 continue;
558 }
559 resourcemap[ task ] << r;
560 cs->logDebug(QString("Make appointments to resource %1").arg(r->resource()->name()), 1);
561 r->makeAppointment(cs, amount);
562 }
563 if (m_recalculate) {
564 if (task->completion().isFinished()) {
565 task->copySchedule();
566 if (m_manager) {
567 cs->logDebug(QString("Task is completed, copied schedule: %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
568 }
569 } else if (task->completion().isStarted()) {
570 task->copyAppointments(DateTime(), start);
571 if (m_manager) {
572 cs->logDebug(QString("Task is %4% completed, copied appointments from %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(start, QLocale::ShortFormat)).arg(task->completion().percentFinished()), 1);
573 }
574 }
575 }
576 cs->setScheduled(true);
577 if (task->estimate()->type() == Estimate::Type_Effort) {
578 if (task->appointmentStartTime().isValid()) {
579 task->setStartTime(task->appointmentStartTime());
580 }
581 if (task->appointmentEndTime().isValid()) {
582 task->setEndTime(task->appointmentEndTime());
583 }
584 if (info && info->requests.isEmpty()) {
585 cs->setResourceError(true);
586 cs->logError(i18n("No resource has been allocated"), 1);
587 }
588 } else if (task->estimate()->calendar()) {
589 DateTime t = task->estimate()->calendar()->firstAvailableAfter(task->startTime(), task->endTime());
590 if (t.isValid()) {
591 task->setStartTime(t);
592 }
593 t = task->estimate()->calendar()->firstAvailableBefore(task->endTime(), task->startTime());
594 if (t.isValid()) {
595 task->setEndTime(t);
596 }
597 } //else Fixed duration
598 task->setDuration(task->endTime() - task->startTime());
599 cs->logInfo(i18n("Scheduled task to start at %1 and finish at %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
600 }
601
kplatoFromRCPS()602 void KPlatoRCPSScheduler::kplatoFromRCPS()
603 {
604 if (m_backward) {
605 kplatoFromRCPSBackward();
606 } else {
607 kplatoFromRCPSForward();
608 }
609 }
610
kplatoFromRCPSForward()611 void KPlatoRCPSScheduler::kplatoFromRCPSForward()
612 {
613 //debugPlan<<"KPlatoRCPSScheduler::kplatoFromRCPSForward:";
614 MainSchedule *cs = static_cast<MainSchedule*>(m_project->currentSchedule());
615 QMap<Node*, QList<ResourceRequest*> > resourcemap;
616 int count = rcps_job_count(m_problem);
617 int step = (PROGRESS_MAX_VALUE - m_progressinfo->progress) / count;
618 DateTime projectstart = m_starttime.addSecs(rcps_job_getstart_res(m_jobstart) * m_timeunit);
619 for (int i = 0; i < count; ++i) {
620 m_progressinfo->progress += step;
621 m_manager->setProgress(m_progressinfo->progress);
622 setProgress(m_progressinfo->progress);
623
624 struct rcps_job *job = rcps_job_get(m_problem, i);
625 Task *task = m_taskmap.value(job);
626 if (task == 0) {
627 continue; //might be dummy task for lag, ...
628 }
629 taskFromRCPSForward(job, task, resourcemap);
630
631 if (projectstart > task->startTime()) {
632 projectstart = task->startTime();
633 }
634 }
635 qint64 st = rcps_job_getstart_res(m_jobstart) * m_timeunit;
636 DateTime start = m_starttime.addSecs(st);
637 qint64 et = rcps_job_getstart_res(m_jobend) * m_timeunit;
638 DateTime end = m_starttime.addSecs(et);
639 m_project->setStartTime(projectstart);
640 m_project->setEndTime(end);
641
642 adjustSummaryTasks(m_schedule->summaryTasks());
643
644 calculatePertValues(resourcemap);
645
646 QLocale locale;
647 cs->logInfo(i18n("Project scheduled to start at %1 and finish at %2", locale.toString(projectstart, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat)), 1);
648
649 if (m_manager) {
650 cs->logDebug(QString("Project scheduling finished at %1").arg(QDateTime::currentDateTime().toString()), 1);
651 m_project->finishCalculation(*m_manager);
652 m_manager->scheduleChanged(cs);
653 }
654 }
655
taskFromRCPSBackward(struct rcps_job * job,Task * task,QMap<Node *,QList<ResourceRequest * >> & resourcemap)656 void KPlatoRCPSScheduler::taskFromRCPSBackward(struct rcps_job *job, Task *task, QMap<Node*, QList<ResourceRequest*> > &resourcemap)
657 {
658 if (m_haltScheduling || m_manager == 0) {
659 return;
660 }
661 QLocale locale;
662 Schedule *cs = task->currentSchedule();
663 Q_ASSERT(cs);
664 struct rcps_mode *mode = rcps_mode_get(job, rcps_job_getmode_res(job));
665 /* get the duration of this mode */
666 KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>(rcps_mode_get_cbarg(mode));
667 qint64 dur = 0;
668 qint64 st = rcps_job_getstart_res(job);
669 if (info == 0) {
670 dur = rcps_mode_getduration(mode);
671 } else {
672 cs->logDebug(QString("Task '%1' estimate: %2").arg(task->name()).arg(task->estimate()->value(Estimate::Use_Expected, false).toString()), 1);
673 cs->logDebug(QString("Task '%1' duration called %2 times, cached values: %3").arg(rcps_job_getname(job)).arg(info->calls).arg(info->cache.count()));
674
675 dur = duration_callback(0, st, rcps_mode_getduration(mode), info);
676
677 for (QMap<QPair<int, int>, int>::ConstIterator it = info->cache.constBegin(); it != info->cache.constEnd(); ++it) {
678 cs->logDebug(QString("Task '%1' start: %2, duration: %3 (%4, %5 hours)").arg(rcps_job_getname(job)).arg(it.key().first).arg(it.value()).arg(fromRcpsTime(it.key().first).toString()).arg((double)(it.value())/60.0), 1);
679 }
680 }
681 DateTime end = fromRcpsTime(st);
682 DateTime start = fromRcpsTime(st + dur);
683 cs->logDebug(QString("Task '%1' start=%2, duration=%3: %4 - %5").arg(rcps_job_getname(job)).arg(st).arg(dur).arg(locale.toString(start, QLocale::ShortFormat)).arg(locale.toString(end, QLocale::ShortFormat)), 1);
684 task->setStartTime(start);
685 task->setEndTime(end);
686 for (int reqs = 0; reqs < rcps_request_count(mode); ++reqs) {
687 struct rcps_request *req = rcps_request_get(mode, reqs);
688 struct rcps_alternative *alt = rcps_alternative_get(req, rcps_request_getalternative_res(req));
689 int amount = rcps_alternative_getamount(alt);
690 struct rcps_resource *res = rcps_alternative_getresource(alt);
691
692 cs->logDebug(QString("Job %1: resource %2 is %3 available").arg(rcps_job_getname(job)).arg(rcps_resource_getname(res)).arg(amount), 1);
693
694 // do actual appointments etc
695 ResourceRequest *r = m_requestmap.value(req);
696 if (r == 0) {
697 cs->logWarning(i18n("No resource request is registered"), 1);
698 continue;
699 }
700 resourcemap[ task ] << r;
701 cs->logDebug(QString("Make appointments to resource %1").arg(r->resource()->name()), 1);
702 r->makeAppointment(cs, amount);
703 }
704 if (m_recalculate) {
705 if (task->completion().isFinished()) {
706 task->copySchedule();
707 if (m_manager) {
708 cs->logDebug(QString("Task is completed, copied schedule: %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
709 }
710 } else if (task->completion().isStarted()) {
711 task->copyAppointments(DateTime(), start);
712 if (m_manager) {
713 cs->logDebug(QString("Task is %4% completed, copied appointments from %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(start, QLocale::ShortFormat)).arg(task->completion().percentFinished()), 1);
714 }
715 }
716 }
717 cs->setScheduled(true);
718 if (task->estimate()->type() == Estimate::Type_Effort) {
719 if (task->appointmentStartTime().isValid()) {
720 task->setStartTime(task->appointmentStartTime());
721 }
722 if (task->appointmentEndTime().isValid()) {
723 task->setEndTime(task->appointmentEndTime());
724 }
725 if (info && info->requests.isEmpty()) {
726 cs->setResourceError(true);
727 cs->logError(i18n("No resource has been allocated"), 1);
728 }
729 } else if (task->estimate()->calendar()) {
730 DateTime t = task->estimate()->calendar()->firstAvailableAfter(task->startTime(), task->endTime());
731 if (t.isValid()) {
732 task->setStartTime(t);
733 }
734 t = task->estimate()->calendar()->firstAvailableBefore(task->endTime(), task->startTime());
735 if (t.isValid()) {
736 task->setEndTime(t);
737 }
738 } //else Fixed duration
739 task->setDuration(task->endTime() - task->startTime());
740 cs->logInfo(i18n("Scheduled task to start at %1 and finish at %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
741 }
742
743
kplatoFromRCPSBackward()744 void KPlatoRCPSScheduler::kplatoFromRCPSBackward()
745 {
746 QLocale locale;
747 //debugPlan<<"KPlatoRCPSScheduler::kplatoFromRCPSBackward:";
748 MainSchedule *cs = static_cast<MainSchedule*>(m_project->currentSchedule());
749 QMap<Node*, QList<ResourceRequest*> > resourcemap;
750 int count = rcps_job_count(m_problem);
751 int step = (PROGRESS_MAX_VALUE - m_progressinfo->progress) / count;
752 DateTime projectstart = fromRcpsTime(rcps_job_getstart_res(m_jobend));
753 for (int i = 0; i < count; ++i) {
754 m_progressinfo->progress += step;
755 m_manager->setProgress(m_progressinfo->progress);
756 setProgress(m_progressinfo->progress);
757
758 struct rcps_job *job = rcps_job_get(m_problem, i);
759 Task *task = m_taskmap.value(job);
760 if (task == 0) {
761 continue; //might be dummy task for lag, ...
762 }
763 taskFromRCPSBackward(job, task, resourcemap);
764
765 if (projectstart > task->startTime()) {
766 projectstart = task->startTime();
767 }
768 }
769 DateTime end = fromRcpsTime(rcps_job_getstart_res(m_jobstart));
770 m_project->setStartTime(projectstart);
771 m_project->setEndTime(end);
772 cs->logInfo(i18n("Project scheduled to start at %1 and finish at %2", locale.toString(projectstart, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat)), 1);
773 if (projectstart < m_project->constraintStartTime()) {
774 cs->setConstraintError(true);
775 cs->logError(i18n("Must start project early in order to finish in time: %1", locale.toString(m_project->constraintStartTime(), QLocale::ShortFormat)), 1);
776 }
777 adjustSummaryTasks(m_schedule->summaryTasks());
778
779 calculatePertValues(resourcemap);
780
781 if (m_manager) {
782 cs->logDebug(QString("Project scheduling finished at %1").arg(QDateTime::currentDateTime().toString()), 1);
783 m_project->finishCalculation(*m_manager);
784 m_manager->scheduleChanged(cs);
785 }
786 }
787
adjustSummaryTasks(const QList<Node * > & nodes)788 void KPlatoRCPSScheduler::adjustSummaryTasks(const QList<Node*> &nodes)
789 {
790 foreach (Node *n, nodes) {
791 adjustSummaryTasks(n->childNodeIterator());
792 if (n->parentNode()->type() == Node::Type_Summarytask) {
793 DateTime pt = n->parentNode()->startTime();
794 DateTime nt = n->startTime();
795 if (! pt.isValid() || pt > nt) {
796 n->parentNode()->setStartTime(nt);
797 }
798 pt = n->parentNode()->endTime();
799 nt = n->endTime();
800 if (! pt.isValid() || pt < nt) {
801 n->parentNode()->setEndTime(nt);
802 }
803 }
804 }
805 }
806
calculatePertValues(const QMap<Node *,QList<ResourceRequest * >> & map)807 void KPlatoRCPSScheduler::calculatePertValues(const QMap<Node*, QList<ResourceRequest*> > &map)
808 {
809 if (m_manager) {
810 m_schedule->setPhaseName(2, i18nc("Project Evaluation and Review Technique", "PERT"));
811 }
812 foreach (Node *n, m_project->allNodes()) {
813 if (n->type() != Node::Type_Task && n->type() != Node::Type_Milestone) {
814 continue;
815 }
816 Task *t = static_cast<Task*>(n);
817 if (n->isStartNode()) {
818 (void)calculateLateStuff(map, static_cast<Task*>(n));
819 }
820 if (n->isEndNode()) {
821 (void)calculateEarlyStuff(map, static_cast<Task*>(n));
822 }
823 switch (n->constraint()) {
824 case Node::StartNotEarlier:
825 n->schedule()->setNegativeFloat(n->startTime() < n->constraintStartTime()
826 ? n->startTime() - n->constraintStartTime()
827 : Duration::zeroDuration);
828 break;
829 case Node::MustStartOn:
830 case Node::FixedInterval:
831 n->schedule()->setNegativeFloat(n->startTime() > n->constraintStartTime()
832 ? n->startTime() - n->constraintStartTime()
833 : n->constraintStartTime() - n->startTime());
834 break;
835 case Node::FinishNotLater:
836 n->schedule()->setNegativeFloat(n->endTime() > n->constraintEndTime()
837 ? n->endTime() - n->constraintEndTime()
838 : Duration::zeroDuration);
839 break;
840 case Node::MustFinishOn:
841 n->schedule()->setNegativeFloat(n->endTime() > n->constraintEndTime()
842 ? n->endTime() - n->constraintEndTime()
843 : n->constraintEndTime() - n->endTime());
844 break;
845 default:
846 break;
847 }
848 if (t->negativeFloat() != 0) {
849 n->schedule()->setConstraintError(true);
850 n->schedule()->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", n->constraintToString(true), KFormat().formatDuration(t->negativeFloat().milliseconds())));
851 }
852
853 }
854 }
855
calculateLateStuff(const QMap<Node *,QList<ResourceRequest * >> & map,Task * task)856 Duration KPlatoRCPSScheduler::calculateLateStuff(const QMap<Node*, QList<ResourceRequest*> > &map, Task *task)
857 {
858 Schedule *cs = task->currentSchedule();
859 Duration pf = m_project->endTime() - m_project->startTime();
860 QList<Relation*> lst = task->dependChildNodes() + task->childProxyRelations();
861 if (lst.isEmpty()) {
862 // end node
863 DateTime end = task->endTime();
864 if (task->estimate()->type() == Estimate::Type_Effort) {
865 foreach (ResourceRequest *r, map.value(static_cast<Node*>(task))) {
866 DateTime x = r->resource()->availableBefore(m_project->endTime(), task->endTime(), 0);
867 cs->logDebug(QString("Resource '%1' available before %2: %3").arg(r->resource()->name()).arg(m_project->endTime().toString()).arg(x.toString()), 2);
868 if (x.isValid() && x > end) {
869 end = x;
870 }
871 }
872 } else if (task->estimate()->calendar()) {
873 end = task->estimate()->calendar()->firstAvailableBefore(m_project->endTime(), task->endTime());
874 cs->logDebug(QString("Calendar work time before %1: %2").arg(m_project->endTime().toString()).arg(end.toString()), 2);
875 }
876 // TODO must calculate backwards to get late *start* of task
877 pf = end.isValid() ? end - task->endTime() : m_project->endTime() - task->endTime();
878 task->setFreeFloat(pf);
879 } else {
880 Duration prev = pf;
881 Duration x = pf;
882 foreach (Relation *r, lst) {
883 prev = qMin(prev, calculateLateStuff(map, static_cast<Task*>(r->child())));
884 DateTime end = task->endTime();
885 if (task->estimate()->type() == Estimate::Type_Effort) {
886 foreach (ResourceRequest *req, map.value(static_cast<Node*>(task))) {
887 DateTime y = req->resource()->availableBefore(r->child()->startTime(), task->endTime(), 0);
888 cs->logDebug(QString("Resource '%1' available before %2: %3").arg(req->resource()->name()).arg(r->child()->startTime().toString()).arg(y.toString()), 2);
889 if (y.isValid() && y > end) {
890 end = y;
891 }
892 }
893 } else if (task->estimate()->calendar()) {
894 end = task->estimate()->calendar()->firstAvailableBefore(r->child()->startTime(), task->endTime());
895 cs->logDebug(QString("Calendar work time before %1: %2").arg(r->child()->startTime().toString()).arg(end.toString()), 2);
896 }
897 x = qMin(x, end.isValid() ? end - task->endTime() : r->child()->startTime() - task->endTime());
898 }
899 task->setFreeFloat(x);
900 // TODO must calculate backwards to get late *start* of task
901 pf = prev + x;
902 }
903 task->setPositiveFloat(pf);
904 task->setLateFinish(task->endTime() + pf);
905 task->setLateStart(task->lateFinish() - (task->endTime() - task->startTime()));
906 QLocale locale;
907 cs->logDebug(QString("Late start %1, late finish %2, positive float %3").arg(locale.toString(task->lateStart(), QLocale::ShortFormat)).arg(locale.toString(task->lateFinish(), QLocale::ShortFormat)).arg(pf.toString()), 2);
908 return pf;
909 }
910
calculateEarlyStuff(const QMap<Node *,QList<ResourceRequest * >> & map,Task * task)911 Duration KPlatoRCPSScheduler::calculateEarlyStuff(const QMap<Node*, QList<ResourceRequest*> > &map, Task *task)
912 {
913 Schedule *cs = task->currentSchedule();
914 Duration tot = m_project->endTime() - m_project->startTime();
915 QList<Relation*> lst = task->dependParentNodes() + task->parentProxyRelations();
916 if (lst.isEmpty()) {
917 // start node
918 DateTime earlystart = task->startTime();
919 if (task->estimate()->type() == Estimate::Type_Effort) {
920 foreach (ResourceRequest *r, map.value(static_cast<Node*>(task))) {
921 DateTime x = r->resource()->availableAfter(m_project->startTime(), task->startTime(), 0);
922 cs->logDebug(QString("Resource '%1' available after %2 (%4): %3").arg(r->resource()->name()).arg(m_project->startTime().toString()).arg(x.toString()).arg(task->startTime().toString()), 2);
923 if (x.isValid() && x < earlystart) {
924 earlystart = x;
925 }
926 }
927 } else if (task->estimate()->calendar()) {
928 earlystart = task->estimate()->calendar()->firstAvailableAfter(m_project->startTime(), task->startTime());
929 cs->logDebug(QString("Calendar work time after %1: %2").arg(m_project->startTime().toString()).arg(earlystart.toString()), 2);
930 }
931 // TODO must calculate forward to get early *????* of task
932 tot = earlystart.isValid() ? task->startTime() - earlystart : task->startTime() - m_project->startTime();
933 } else {
934 Duration prev = tot;
935 Duration x = tot;
936 foreach (Relation *r, lst) {
937 prev = qMin(prev, calculateEarlyStuff(map, static_cast<Task*>(r->parent())));
938 DateTime earlystart = task->startTime();
939 if (task->estimate()->type() == Estimate::Type_Effort) {
940 foreach (ResourceRequest *req, map.value(static_cast<Node*>(task))) {
941 DateTime y = req->resource()->availableAfter(r->parent()->endTime(), task->startTime(), 0);
942 cs->logDebug(QString("Resource '%1' available after %2: %3").arg(req->resource()->name()).arg(r->parent()->endTime().toString()).arg(y.toString()), 2);
943 if (y.isValid() && y < earlystart) {
944 earlystart = y;
945 }
946 }
947 } else if (task->estimate()->calendar()) {
948 earlystart = task->estimate()->calendar()->firstAvailableAfter(r->parent()->endTime(), task->startTime());
949 cs->logDebug(QString("Calendar work time after %1: %2").arg(r->parent()->endTime().toString()).arg(earlystart.toString()), 2);
950 }
951 x = qMin(x, earlystart.isValid() ? task->startTime() - earlystart : task->startTime() - r->parent()->startTime());
952 }
953 // TODO must calculate backwards to get late *start* of task
954 tot = prev + x;
955 }
956 task->setEarlyStart(task->startTime() - tot);
957 task->setEarlyFinish(task->earlyStart() + (task->endTime() - task->startTime()));
958 QLocale locale;
959 cs->logDebug(QString("Early start %1, early finish %2").arg(locale.toString(task->earlyStart(), QLocale::ShortFormat)).arg(locale.toString(task->earlyFinish(), QLocale::ShortFormat)), 2);
960 return tot;
961 }
962
addResource(KPlato::Resource * r)963 struct rcps_resource *KPlatoRCPSScheduler::addResource(KPlato::Resource *r)
964 {
965 if (m_resourcemap.values().contains(r)) {
966 warnPlan<<r->name()<<"already exist";
967 return 0;
968 }
969 struct rcps_resource *res = rcps_resource_new();
970 rcps_resource_setname(res, r->name().toLocal8Bit().data());
971 rcps_resource_setavail(res, r->units());
972 rcps_resource_add(m_problem, res);
973 m_resourcemap[res] = r;
974 return res;
975 }
976
addResources()977 void KPlatoRCPSScheduler::addResources()
978 {
979 debugPlan;
980 QList<Resource*> list = m_project->resourceList();
981 for (int i = 0; i < list.count(); ++i) {
982 addResource(list.at(i));
983 }
984 }
985
toRcpsTime(const DateTime & time) const986 int KPlatoRCPSScheduler::toRcpsTime(const DateTime &time) const
987 {
988 return (m_backward ? time.secsTo(m_starttime) : m_starttime.secsTo(time)) / m_timeunit;
989 }
990
fromRcpsTime(int time) const991 DateTime KPlatoRCPSScheduler::fromRcpsTime(int time) const
992 {
993 return m_starttime.addSecs((m_backward ? -time : time) * m_timeunit);
994 }
995
addTask(KPlato::Task * task)996 struct rcps_job *KPlatoRCPSScheduler::addTask(KPlato::Task *task)
997 {
998 struct rcps_job *job = rcps_job_new();
999 rcps_job_setname(job, task->name().toLocal8Bit().data());
1000 rcps_job_add(m_problem, job);
1001 m_taskmap[job] = task;
1002 return job;
1003 }
1004
addTasks()1005 void KPlatoRCPSScheduler::addTasks()
1006 {
1007 debugPlan;
1008 // Add a start job
1009 m_jobstart = rcps_job_new();
1010 rcps_job_setname(m_jobstart, "RCPS start job");
1011 rcps_job_add(m_problem, m_jobstart);
1012 struct rcps_mode *mode = rcps_mode_new();
1013 rcps_mode_add(m_jobstart, mode);
1014
1015 QList<Node*> list = m_project->allNodes();
1016 for (int i = 0; i < list.count(); ++i) {
1017 Node *n = list.at(i);
1018 switch (n->type()) {
1019 case Node::Type_Summarytask:
1020 m_schedule->insertSummaryTask(n);
1021 break;
1022 case Node::Type_Task:
1023 case Node::Type_Milestone:
1024 addTask(static_cast<Task*>(n));
1025 break;
1026 default:
1027 break;
1028 }
1029 }
1030 // Add an end job
1031 m_jobend = rcps_job_new();
1032 rcps_job_setname(m_jobend, "RCPS end job");
1033 rcps_job_add(m_problem, m_jobend);
1034 mode = rcps_mode_new();
1035 rcps_mode_add(m_jobend, mode);
1036 // add a weight callback argument
1037 struct KPlatoRCPSScheduler::weight_info *info = new KPlatoRCPSScheduler::weight_info;
1038 info->self = this;
1039 info->task = 0;
1040 info->targettime = toRcpsTime(m_targettime);
1041 info->isEndJob = true;
1042 info->finish = 0;
1043
1044 rcps_mode_set_weight_cbarg(mode, info);
1045 m_weight_info_list[ m_jobend ] = info;
1046
1047 for(int i = 0; i < rcps_job_count(m_problem); ++i) {
1048 debugPlan<<"Task:"<<rcps_job_getname(rcps_job_get(m_problem, i));
1049 }
1050 }
1051
addJob(const QString & name,int duration)1052 struct rcps_job *KPlatoRCPSScheduler::addJob(const QString &name, int duration)
1053 {
1054 struct rcps_job *job = rcps_job_new();
1055 rcps_job_setname(job, name.toLocal8Bit().data());
1056 rcps_job_add(m_problem, job);
1057 struct rcps_mode *mode = rcps_mode_new();
1058 rcps_mode_setduration(mode, duration);
1059 rcps_mode_add(job, mode);
1060 return job;
1061 }
1062
addDependenciesForward(struct rcps_job * job,KPlato::Task * task)1063 void KPlatoRCPSScheduler::addDependenciesForward(struct rcps_job *job, KPlato::Task *task)
1064 {
1065 if (task->dependParentNodes().isEmpty() && task->parentProxyRelations().isEmpty()) {
1066 rcps_job_successor_add(m_jobstart, job, SUCCESSOR_FINISH_START);
1067 }
1068 if (task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty()) {
1069 rcps_job_successor_add(job, m_jobend, SUCCESSOR_FINISH_START);
1070 }
1071 foreach (Relation *r, task->dependChildNodes()) {
1072 Node *n = r->child();
1073 if (n == 0 || n->type() == Node::Type_Summarytask) {
1074 continue;
1075 }
1076 int type = SUCCESSOR_FINISH_START;
1077 switch (r->type()) {
1078 case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
1079 case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
1080 case Relation::StartStart: type = SUCCESSOR_START_START; break;
1081 }
1082 if (r->lag() == Duration::zeroDuration) {
1083 rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
1084 } else {
1085 // Add a dummy job to represent the lag
1086 struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
1087 rcps_job_successor_add(job, dummy, type);
1088 int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
1089 rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
1090 }
1091 }
1092 foreach (Relation *r, task->childProxyRelations()) {
1093 Node *n = r->child();
1094 if (n == 0 || n->type() == Node::Type_Summarytask) {
1095 continue;
1096 }
1097 int type = SUCCESSOR_FINISH_START;
1098 switch (r->type()) {
1099 case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
1100 case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
1101 case Relation::StartStart: type = SUCCESSOR_START_START; break;
1102 }
1103 if (r->lag() == Duration::zeroDuration) {
1104 rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
1105 } else {
1106 // Add a dummy job to represent the lag
1107 struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
1108 rcps_job_successor_add(job, dummy, type);
1109 int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
1110 rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
1111 }
1112 }
1113 }
1114
addDependenciesBackward(struct rcps_job * job,KPlato::Task * task)1115 void KPlatoRCPSScheduler::addDependenciesBackward(struct rcps_job *job, KPlato::Task *task)
1116 {
1117 if (task->dependParentNodes().isEmpty() && task->parentProxyRelations().isEmpty()) {
1118 rcps_job_successor_add(job, m_jobend, SUCCESSOR_FINISH_START);
1119 //debugPlan<<rcps_job_getname(job)<<"->"<<rcps_job_getname(m_jobend);
1120 }
1121 if (task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty()) {
1122 rcps_job_successor_add(m_jobstart, job, SUCCESSOR_FINISH_START);
1123 //debugPlan<<rcps_job_getname(m_jobstart)<<"->"<<rcps_job_getname(job);
1124 }
1125 foreach (Relation *r, task->dependParentNodes()) {
1126 Node *n = r->parent();
1127 if (n == 0 || n->type() == Node::Type_Summarytask) {
1128 continue;
1129 }
1130 int type = SUCCESSOR_FINISH_START;
1131 switch (r->type()) {
1132 case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
1133 case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
1134 case Relation::StartStart: type = SUCCESSOR_START_START; break;
1135 }
1136 if (r->lag() == Duration::zeroDuration) {
1137 rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
1138 //debugPlan<<rcps_job_getname(job)<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<type;
1139 } else {
1140 // Add a dummy job to represent the lag
1141 struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
1142 rcps_job_successor_add(job, dummy, type);
1143 debugPlan<<rcps_job_getname(job)<<"->"<<"dummy lag"<<type;
1144 int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
1145 rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
1146 //debugPlan<<"dummy lag"<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<t;
1147 }
1148 }
1149 foreach (Relation *r, task->parentProxyRelations()) {
1150 Node *n = r->parent();
1151 if (n == 0 || n->type() == Node::Type_Summarytask) {
1152 continue;
1153 }
1154 int type = SUCCESSOR_FINISH_START;
1155 switch (r->type()) {
1156 case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
1157 case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
1158 case Relation::StartStart: type = SUCCESSOR_START_START; break;
1159 }
1160 if (r->lag() == Duration::zeroDuration) {
1161 rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
1162 //debugPlan<<rcps_job_getname(job)<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<type;
1163 } else {
1164 // Add a dummy job to represent the lag
1165 struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
1166 rcps_job_successor_add(job, dummy, type);
1167 debugPlan<<rcps_job_getname(job)<<"->"<<"dummy lag"<<type;
1168 int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
1169 rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
1170 //debugPlan<<"dummy lag"<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<t;
1171 }
1172 }
1173 }
1174
addDependencies()1175 void KPlatoRCPSScheduler::addDependencies()
1176 {
1177 debugPlan;
1178 QMap<struct rcps_job*, Task*> ::const_iterator it = m_taskmap.constBegin();
1179 for (; it != m_taskmap.constEnd(); ++it) {
1180 if (m_backward) {
1181 addDependenciesBackward(it.key(), it.value());
1182 } else {
1183 addDependenciesForward(it.key(), it.value());
1184 }
1185 }
1186 }
1187
addRequests()1188 void KPlatoRCPSScheduler::addRequests()
1189 {
1190 debugPlan;
1191 QMap<struct rcps_job*, Task*> ::const_iterator it = m_taskmap.constBegin();
1192 for (; it != m_taskmap.constEnd(); ++it) {
1193 addRequest(it.key(), it.value());
1194 }
1195 }
1196
addRequest(rcps_job * job,Task * task)1197 void KPlatoRCPSScheduler::addRequest(rcps_job *job, Task *task)
1198 {
1199 debugPlan;
1200 struct rcps_mode *mode = rcps_mode_new();
1201 rcps_mode_add(job, mode);
1202 // add a weight callback argument
1203 struct KPlatoRCPSScheduler::weight_info *wi = new KPlatoRCPSScheduler::weight_info;
1204 wi->self = this;
1205 wi->task = task;
1206 wi->targettime = 0;
1207 wi->isEndJob = false;
1208 wi->finish = 0;
1209
1210 rcps_mode_set_weight_cbarg(mode, wi);
1211 m_weight_info_list[ job ] = wi;
1212
1213 if (task->constraint() != Node::FixedInterval) {
1214 if (task->type() == Node::Type_Milestone || task->estimate() == 0 || (m_recalculate && task->completion().isFinished())) {
1215 rcps_mode_setduration(mode, 0);
1216 return;
1217 }
1218 if (task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() == 0) {
1219 // Fixed duration, no duration callback needed
1220 rcps_mode_setduration(mode, task->estimate()->value(Estimate::Use_Expected, m_usePert).seconds() / m_timeunit);
1221 return;
1222 }
1223 }
1224 /* set the argument for the duration callback */
1225 struct KPlatoRCPSScheduler::duration_info *info = new KPlatoRCPSScheduler::duration_info;
1226 info->self = this;
1227 info->calls = 0;
1228 info->task = task;
1229 if (m_recalculate && task->completion().isStarted()) {
1230 info->estimate = task->completion().remainingEffort();
1231 } else {
1232 info->estimate = task->estimate()->value(Estimate::Use_Expected, m_usePert);
1233 }
1234 info->requests = task->requests().resourceRequests(); // returns team members (not team resource itself)
1235 info->estimatetype = task->estimate()->type();
1236
1237 rcps_mode_set_cbarg(mode, info);
1238 m_duration_info_list[ job ] = info;
1239
1240
1241 foreach (ResourceRequest *rr, info->requests) {
1242 Resource *r = rr->resource();
1243 if (r->type() == Resource::Type_Team) {
1244 warnPlan<<"There should not be any request to a team resource:"<<r->name();
1245 continue;
1246 }
1247 struct rcps_request *req = rcps_request_new();
1248 rcps_request_add(mode, req);
1249 m_requestmap[ req ] = rr;
1250 struct rcps_alternative *alt = rcps_alternative_new();
1251 rcps_alternative_setresource(alt, m_resourcemap.key(r));
1252 rcps_alternative_setamount(alt, (double)rr->units() * 100 / r->units());
1253 rcps_alternative_add(req, alt);
1254 }
1255 }
1256
setConstraints()1257 void KPlatoRCPSScheduler::setConstraints()
1258 {
1259 for (QMap<struct rcps_job*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it) {
1260 Task *task = it.value();
1261 struct rcps_job *job = it.key();
1262 struct weight_info *wi = m_weight_info_list.value(job);
1263 struct duration_info *di = m_duration_info_list.value(job);
1264 switch (task->constraint()) {
1265 case Node::MustStartOn:
1266 case Node::StartNotEarlier:
1267 wi->targettime = toRcpsTime(task->constraintStartTime());
1268 if (m_backward) {
1269 int d = 0;
1270 if (di) {
1271 // as m_backward == true, DURATION_BACKWARD in rcps means forward in plan
1272 d = duration(DURATION_BACKWARD, wi->targettime, 0, di);
1273 }
1274 wi->targettime -= d;
1275 }
1276 rcps_job_setearliest_start(job, wi->targettime);
1277 task->currentSchedule()->logDebug(QString("%2 %3 %4: %5 (rcps=%6)")
1278 .arg(task->constraintToString())
1279 .arg(m_backward?"backward":"forward")
1280 .arg(task->constraintStartTime().toString())
1281 .arg(fromRcpsTime(wi->targettime).toString())
1282 .arg(wi->targettime));
1283 break;
1284 case Node::MustFinishOn:
1285 wi->targettime = toRcpsTime(task->constraintEndTime());
1286 if (! m_backward) {
1287 int d = 0;
1288 if (di) {
1289 d = duration(DURATION_BACKWARD, wi->targettime, 0, di);
1290 }
1291 rcps_job_setearliest_start(job, wi->targettime - d);
1292 }
1293 break;
1294 case Node::FinishNotLater:
1295 wi->targettime = toRcpsTime(task->constraintEndTime());
1296 if (m_backward) {
1297 rcps_job_setearliest_start(job, wi->targettime);
1298 }
1299 break;
1300 case Node::FixedInterval:
1301 wi->targettime = m_backward ? toRcpsTime(task->constraintEndTime()) : toRcpsTime(task->constraintStartTime());
1302 rcps_job_setearliest_start(job, wi->targettime);
1303 break;
1304 default:
1305 break;
1306 }
1307 }
1308 }
1309
setWeights()1310 void KPlatoRCPSScheduler::setWeights()
1311 {
1312 for (QMap<struct rcps_job*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it) {
1313 Task *task = it.value();
1314 struct rcps_job *job = it.key();
1315 if (m_backward) {
1316 switch (task->constraint()) {
1317 case Node::ASAP:
1318 rcps_job_setweight(job, WEIGHT_ALAP);
1319 break;
1320 case Node::ALAP:
1321 rcps_job_setweight(job, WEIGHT_ASAP);
1322 break;
1323 case Node::StartNotEarlier:
1324 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1325 break;
1326 case Node::MustStartOn:
1327 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1328 break;
1329 case Node::FinishNotLater:
1330 rcps_job_setearliest_start(job, toRcpsTime(task->constraintEndTime()));
1331 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1332 break;
1333 case Node::MustFinishOn:
1334 rcps_job_setearliest_start(job, toRcpsTime(task->constraintEndTime()));
1335 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1336 break;
1337 case Node::FixedInterval:
1338 rcps_job_setearliest_start(job, toRcpsTime(task->constraintEndTime()));
1339 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1340 break;
1341 default:
1342 rcps_job_setweight(job, WEIGHT_ASAP);
1343 break;
1344 }
1345 } else {
1346 switch (task->constraint()) {
1347 case Node::ASAP:
1348 rcps_job_setweight(job, WEIGHT_ASAP);
1349 break;
1350 case Node::ALAP:
1351 rcps_job_setweight(job, WEIGHT_ALAP);
1352 break;
1353 case Node::StartNotEarlier:
1354 rcps_job_setearliest_start(job, toRcpsTime(task->constraintStartTime()));
1355 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1356 break;
1357 case Node::MustStartOn:
1358 rcps_job_setearliest_start(job, toRcpsTime(task->constraintStartTime()));
1359 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1360 break;
1361 case Node::FinishNotLater:
1362 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1363 break;
1364 case Node::MustFinishOn:
1365 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1366 break;
1367 case Node::FixedInterval:
1368 rcps_job_setearliest_start(job, toRcpsTime(task->constraintStartTime()));
1369 rcps_job_setweight(job, WEIGHT_CONSTRAINT);
1370 break;
1371 default:
1372 rcps_job_setweight(job, WEIGHT_ASAP);
1373 break;
1374 }
1375 }
1376 }
1377 }
1378