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