Blame view

src/scheduler/Scheduler.py 13.7 KB
06241f05   haribo   Class scheduler f...
1
from operator import attrgetter
bca9a283   Jeremy   Reworked the sche...
2
3
from .UserManager import UserManager
from .Interval import *
ff448d43   Jeremy   Update
4
from django.db.models import Q
715fabb7   haribo   #3430 (100%)
5
6

SIMULATION = False
ff448d43   Jeremy   Update
7
DEBUG_FILE = False
b87b6d9b   haribo   Finished tests fo...
8

bca9a283   Jeremy   Reworked the sche...
9
10
class Scheduler(IntervalManagement):
    REJECTED_ROOM = "Insufficient room for this sequence"
06241f05   haribo   Class scheduler f...
11
12

    def __init__(self):
bca9a283   Jeremy   Reworked the sche...
13
        super().__init__("Scheduler", "Scheduler")
b87b6d9b   haribo   Finished tests fo...
14
        self.schedule = Schedule.objects.create()
bca9a283   Jeremy   Reworked the sche...
15
        self.sequences = []
7a79e25b   haribo   Date: 19/05/2016
16

bca9a283   Jeremy   Reworked the sche...
17
18
    def log(self, message: str):
        self.logger.info(message)
3c179769   Jeremy   userSimulator / a...
19

bca9a283   Jeremy   Reworked the sche...
20
21
22
23
    def getNightLimits(self) -> tuple:
        start = getNightStart()
        end = getNightEnd()
        return (start, end)
7a79e25b   haribo   Date: 19/05/2016
24

bca9a283   Jeremy   Reworked the sche...
25
26
27
    def setNightLimits(self, plan_start: float, plan_end: float) -> int:
        self.schedule.plan_start = Decimal(plan_start)
        self.schedule.plan_end = Decimal(plan_end)
bca9a283   Jeremy   Reworked the sche...
28
        return 0
3c179769   Jeremy   userSimulator / a...
29

bca9a283   Jeremy   Reworked the sche...
30
31
    def isFirstSchedule(self) -> bool:
        return False
8e4ab234   haribo   #3485: Creation a...
32

ff448d43   Jeremy   Update
33
34
35
36
37
38
    def determinePlanStart(self, previous_sched):
        start = secondsToPreciseJulianDate(getPreciseCurrentTime())
        if start > previous_sched.plan_start + self.max_overhead:
            return start + self.max_overhead
        return previous_sched.plan_start

257abe9b   Jeremy   Added comments
39
40
41
    '''
        Copy information from current night previous schedule
    '''
bca9a283   Jeremy   Reworked the sche...
42
    def copyFromPrevious(self) -> int:
675fb3d5   Jeremy   Update scheduler ...
43
        if len(Schedule.objects.all()) == 1:
f7dd3df1   Jeremy   Update simulators...
44
            self.schedule.plan_night_start = self.schedule.plan_start
ff448d43   Jeremy   Update
45
46
            if DEBUG_FILE:
                self.log("No schedule found")
bca9a283   Jeremy   Reworked the sche...
47
            return 1
f7dd3df1   Jeremy   Update simulators...
48
49
        try:
            previous_sched = Schedule.objects.order_by('-created')[1]
ff448d43   Jeremy   Update
50
51
            previous_exc_seq = previous_sched.sequences.filter(Q(status=Sequence.EXECUTED) |
                                                               Q(status=Sequence.EXECUTING))
bca9a283   Jeremy   Reworked the sche...
52
        except:
f7dd3df1   Jeremy   Update simulators...
53
            self.schedule.plan_night_start = self.schedule.plan_start
ff448d43   Jeremy   Update
54
55
            if DEBUG_FILE:
                self.debug("Scheduler could not get information from previous schedule")
bca9a283   Jeremy   Reworked the sche...
56
            return 1
7a79e25b   haribo   Date: 19/05/2016
57
        for seq in previous_exc_seq:
ff448d43   Jeremy   Update
58
            shs = seq.shs.latest("schedule__created")
7a79e25b   haribo   Date: 19/05/2016
59
60
            shs.pk = None
            shs.schedule = self.schedule
bca9a283   Jeremy   Reworked the sche...
61
            shs.save()
ff448d43   Jeremy   Update
62
63
        adder = 0
        try:
3224f14a   Jeremy   Fixed some simula...
64
            executing = Sequence.objects.filter(status=Sequence.EXECUTING).get()
ff448d43   Jeremy   Update
65
            if executing:
3224f14a   Jeremy   Fixed some simula...
66
                s = executing.shs.latest("schedule__created")
c53a13e0   Jeremy   Updating a lot of...
67
                adder = s.tep - secondsToPreciseJulianDate(getPreciseCurrentTime())
3224f14a   Jeremy   Fixed some simula...
68
69
        except Exception as e:
            self.log("No executing sequence found " + str(e))
7a79e25b   haribo   Date: 19/05/2016
70
71
        self.schedule.plan_night_start = previous_sched.plan_night_start
        self.schedule.plan_end = previous_sched.plan_end
ff448d43   Jeremy   Update
72
        self.schedule.plan_start = self.determinePlanStart(previous_sched) + adder
bca9a283   Jeremy   Reworked the sche...
73
        return 0
8e4ab234   haribo   #3485: Creation a...
74

257abe9b   Jeremy   Added comments
75
76
77
    '''
        used for tests (entry point)
    '''
bca9a283   Jeremy   Reworked the sche...
78
    def simulateSchedule(self, sequences) -> tuple:
715fabb7   haribo   #3430 (100%)
79
80
        global SIMULATION
        SIMULATION = True
7a79e25b   haribo   Date: 19/05/2016
81
82

        self.schedule.plan_night_start = self.schedule.plan_start
bca9a283   Jeremy   Reworked the sche...
83
        self.sequences = list(sequences)
7a79e25b   haribo   Date: 19/05/2016
84
85
86
87
        shs_list = []
        for sequence in self.sequences:
            shs_list.append(ScheduleHasSequences(sequence=sequence, schedule=self.schedule))
        self.sequences = [(sequence, shs_list[index]) for index, sequence in enumerate(self.sequences)]
bca9a283   Jeremy   Reworked the sche...
88
        self.computeSchedule()
715fabb7   haribo   #3430 (100%)
89
        return (self.schedule, self.sequences)
715fabb7   haribo   #3430 (100%)
90

bca9a283   Jeremy   Reworked the sche...
91
92
93
    def computeSchedule(self) -> int:
        interval = Interval(self.schedule.plan_start, self.schedule.plan_end)
        self.intervals.append(interval)
ff448d43   Jeremy   Update
94
95
        if DEBUG_FILE:
            self.log("Interval created : " + str(interval.__dict__))
bca9a283   Jeremy   Reworked the sche...
96
97
98
99
100
101
        self.removeInvalidSequences()
        self.determinePriorities()
        self.removeNonEligible()
        self.sortSequences()
        self.placeSequences()
        return 0
7a79e25b   haribo   Date: 19/05/2016
102

257abe9b   Jeremy   Added comments
103
104
105
    '''
        Default entry point
    '''
675fb3d5   Jeremy   Update scheduler ...
106
107
108
109
110
111
112
113
114
115
    def makeSchedule(self) -> Schedule:
        global SIMULATION
        SIMULATION = False

        if self.isFirstSchedule():
            self.schedule.plan_night_start = self.schedule.plan_start
        else:
            if self.copyFromPrevious():
                self.schedule.plan_night_start = self.schedule.plan_start

ff448d43   Jeremy   Update
116
117
        self.sequences = list(Sequence.objects.filter(Q(status=Sequence.PLANNED) | Q(status=Sequence.TOBEPLANNED)
                                                      | Q(status=Sequence.PENDING)))
675fb3d5   Jeremy   Update scheduler ...
118
119
        self.sequences = [(sequence, ScheduleHasSequences(sequence=sequence, schedule=self.schedule))
                          for sequence in self.sequences]
ff448d43   Jeremy   Update
120
121
        if DEBUG_FILE:
            self.log(str(len(self.sequences)) + " sequences found")
675fb3d5   Jeremy   Update scheduler ...
122
123
        self.computeSchedule()
        self.saveSchedule()
ff448d43   Jeremy   Update
124
125
        if DEBUG_FILE:
            self.log("Saving schedule with " + str(len(self.schedule.sequences.all())) + " sequences")
675fb3d5   Jeremy   Update scheduler ...
126
127
128
129
        return self.schedule

    def saveSchedule(self) -> int:
        self.schedule.save()
ff448d43   Jeremy   Update
130
131
        for sequence, shs in self.sequences:
            sequence.status = Sequence.PLANNED
675fb3d5   Jeremy   Update scheduler ...
132
            shs.schedule = self.schedule
ff448d43   Jeremy   Update
133
            sequence.save()
675fb3d5   Jeremy   Update scheduler ...
134
            shs.save()
ff448d43   Jeremy   Update
135
136
        if DEBUG_FILE:
            self.logSchedule()
675fb3d5   Jeremy   Update scheduler ...
137
138
139
        return 0


bca9a283   Jeremy   Reworked the sche...
140
141
142
143
    '''
        JB: Using : list(self.sequences) makes a copy.
    '''
    def removeNonEligible(self) -> int:
7a79e25b   haribo   Date: 19/05/2016
144
        for sequence, shs in list(self.sequences):
bca9a283   Jeremy   Reworked the sche...
145
            overlap = Decimal(min(self.schedule.plan_end, sequence.jd2)) - Decimal(max(self.schedule.plan_start, sequence.jd1)) - self.max_overhead
06241f05   haribo   Class scheduler f...
146
            if overlap < sequence.duration:
b87b6d9b   haribo   Finished tests fo...
147
                if sequence.jd1 < self.schedule.plan_start:
06241f05   haribo   Class scheduler f...
148
                    sequence.status = Sequence.UNPLANNABLE
bca9a283   Jeremy   Reworked the sche...
149
                    if not SIMULATION:
715fabb7   haribo   #3430 (100%)
150
                        sequence.save()
7a79e25b   haribo   Date: 19/05/2016
151
                self.sequences.remove((sequence, shs))
ff448d43   Jeremy   Update
152
153
                if DEBUG_FILE:
                    self.log("Removing non eligible sequence")
bca9a283   Jeremy   Reworked the sche...
154
155
156
157
158
159
160
        return 0

    def removeInvalidSequences(self):
        for sequence,shs in list(self.sequences):
            if (sequence.jd1 < 0 or sequence.jd2 < 0 or
                    is_nearby_less_or_equal(sequence.duration, Decimal(0)) or
                    sequence.jd2 - sequence.jd1 < sequence.duration):
ff448d43   Jeremy   Update
161
162
                if DEBUG_FILE:
                    self.log("Removing sequence in removeInvalidSequences")
bca9a283   Jeremy   Reworked the sche...
163
164
165
166
167
                self.sequences.remove((sequence, shs))
                sequence.status = Sequence.INVALID
                if not SIMULATION:
                    sequence.save()
        return 0
06241f05   haribo   Class scheduler f...
168

bca9a283   Jeremy   Reworked the sche...
169
170
171
    def updateOtherDeltas(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval) -> int:
        seq_before, shs_b_i = self.findSequenceBefore(interval)
        seq_after, shs_a_i = self.findSequenceAfter(interval)
7a79e25b   haribo   Date: 19/05/2016
172

bca9a283   Jeremy   Reworked the sche...
173
174
175
176
177
        if seq_before and shs_b_i:
            shs_b_i.deltaTR = min(shs.tsp - self.max_overhead, seq_before.jd2) - shs_b_i.tep
        if seq_after and shs_a_i:
            shs_a_i.deltaTL = shs_a_i.tsp - self.max_overhead - max(shs.tep, seq_after.jd1)
        return 0
06241f05   haribo   Class scheduler f...
178

257abe9b   Jeremy   Added comments
179
180
181
    '''
        Function who place all the sequences in intervals
    '''
bca9a283   Jeremy   Reworked the sche...
182
    def placeSequences(self) -> int:
7a79e25b   haribo   Date: 19/05/2016
183
        for sequence, shs in list(self.sequences):
bca9a283   Jeremy   Reworked the sche...
184
185
            quota = UserManager.determineQuota(sequence)
            if not UserManager.isSufficient(quota, sequence):
eecfb779   haribo   Date: 26/05/2016
186
                shs.status = Sequence.REJECTED
bca9a283   Jeremy   Reworked the sche...
187
                shs.desc = UserManager.REJECTED
06241f05   haribo   Class scheduler f...
188
                continue
bca9a283   Jeremy   Reworked the sche...
189
            matching_intervals = self.getMatchingIntervals(sequence)
06241f05   haribo   Class scheduler f...
190
            if len(matching_intervals) > 0:
bca9a283   Jeremy   Reworked the sche...
191
192
193
                inter = self.placeSequence(sequence, shs, matching_intervals)
                if inter:
                    self.updateOtherDeltas(sequence, shs, inter)
06241f05   haribo   Class scheduler f...
194
195
                sequence_placed = True
            else:
bca9a283   Jeremy   Reworked the sche...
196
197
                sequence_placed = self.tryShiftingSequences(sequence, shs)
            if sequence_placed:
c53a13e0   Jeremy   Updating a lot of...
198
                shs.status = Sequence.PLANNED
bca9a283   Jeremy   Reworked the sche...
199
                self.decreaseQuota(sequence, sequence.duration)
eecfb779   haribo   Date: 26/05/2016
200
            else:
ff448d43   Jeremy   Update
201
202
                if DEBUG_FILE:
                    self.log("Removing sequence in place_sequences")
eecfb779   haribo   Date: 26/05/2016
203
                shs.status = Sequence.REJECTED
bca9a283   Jeremy   Reworked the sche...
204
205
206
207
208
                shs.desc = self.REJECTED_ROOM
        return 0

    def findSequenceBefore(self, interval: Interval):
        for seq, s in self.sequences:
c53a13e0   Jeremy   Updating a lot of...
209
            if s.status == Sequence.PLANNED:
bca9a283   Jeremy   Reworked the sche...
210
211
212
213
214
215
                if is_nearby_equal(s.tep, interval.start):
                    return (seq, s)
        return (None, None)

    def findSequenceAfter(self, interval: Interval):
        for seq, s in self.sequences:
c53a13e0   Jeremy   Updating a lot of...
216
            if s.status == Sequence.PLANNED:
bca9a283   Jeremy   Reworked the sche...
217
218
219
                if is_nearby_equal(s.tsp - self.max_overhead, interval.end):
                    return (seq, s)
        return (None, None)
06241f05   haribo   Class scheduler f...
220

65149de7   Jeremy   Update
221
    '''
bca9a283   Jeremy   Reworked the sche...
222
223
        pm(l/r) = Possible Move (Left / Right)
        shs_(b/a)_i = shs (before/after) interval
65149de7   Jeremy   Update
224
    '''
bca9a283   Jeremy   Reworked the sche...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    def tryShiftingSequences(self, sequence: Sequence, shs: ScheduleHasSequences) -> bool:
        potential = self.getPotentialIntervals(sequence)
        potential.sort(key=attrgetter("duration"), reverse=True)
        pml = Decimal(0)
        pmr = Decimal(0)

        for interval in potential:
            seq_before, shs_b_i = self.findSequenceBefore(interval)
            seq_after, shs_a_i = self.findSequenceAfter(interval)

            available = min(interval.end, sequence.jd2) - max(interval.start, sequence.jd1)
            missing = sequence.duration - available + self.max_overhead
            if seq_before and shs_b_i:
                pml = min(shs_b_i.deltaTL, interval.start - sequence.jd1)
            if seq_after and shs_a_i:
                pmr = min(shs_a_i.deltaTR, sequence.jd2 - interval.end)

            if is_nearby_sup_or_equal(pml, missing):
                self.moveSequence(seq_before, shs_b_i, missing, "LEFT")
            elif is_nearby_sup_or_equal(pmr, missing):
                self.moveSequence(seq_after, shs_a_i, missing, "RIGHT")
            elif is_nearby_sup_or_equal(pml + pmr, missing):
                self.moveSequence(seq_before, shs_b_i, pml, "LEFT")
                self.moveSequence(seq_after, shs_a_i, missing - pml, "RIGHT")
b87b6d9b   haribo   Finished tests fo...
249
250
            else:
                continue
bca9a283   Jeremy   Reworked the sche...
251
252
253
254
255
256
            matching = self.getMatchingIntervals(sequence)
            if len(matching) != 1:
                raise ValueError("There should be one and only one matching interval after shifting")
            inter = self.placeSequence(sequence, shs, matching)
            if inter:
                self.updateOtherDeltas(sequence, shs, inter)
b87b6d9b   haribo   Finished tests fo...
257
            return True
06241f05   haribo   Class scheduler f...
258
        return False
7a79e25b   haribo   Date: 19/05/2016
259

257abe9b   Jeremy   Added comments
260
261
262
    '''
        Move the sequence tsp (time start planned) and tep (time end planned) left or right
    '''
bca9a283   Jeremy   Reworked the sche...
263
264
265
    def moveSequence(self, sequence: Sequence, shs: ScheduleHasSequences, time_shift: Decimal, direction: str) -> int:
        interval_before = None
        interval_after = None
7a79e25b   haribo   Date: 19/05/2016
266

06241f05   haribo   Class scheduler f...
267
        if direction not in ["LEFT", "RIGHT"]:
bca9a283   Jeremy   Reworked the sche...
268
            return 1
7a79e25b   haribo   Date: 19/05/2016
269
        if time_shift > (shs.deltaTL if direction == "LEFT" else shs.deltaTR):
bca9a283   Jeremy   Reworked the sche...
270
            return 1
06241f05   haribo   Class scheduler f...
271
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
272
            if is_nearby_equal(interval.end, shs.tsp - self.max_overhead):
06241f05   haribo   Class scheduler f...
273
                interval_before = interval
7a79e25b   haribo   Date: 19/05/2016
274
            elif is_nearby_equal(interval.start, shs.tep):
06241f05   haribo   Class scheduler f...
275
                interval_after = interval
06241f05   haribo   Class scheduler f...
276
277
        if direction == "LEFT":
            interval_before.end -= time_shift
bca9a283   Jeremy   Reworked the sche...
278
            if interval_after:
b87b6d9b   haribo   Finished tests fo...
279
                interval_after.start -= time_shift
7a79e25b   haribo   Date: 19/05/2016
280
281
282
283
            shs.tsp -= time_shift
            shs.tep -= time_shift
            shs.deltaTL -= time_shift
            shs.deltaTR += time_shift
06241f05   haribo   Class scheduler f...
284
        else:
bca9a283   Jeremy   Reworked the sche...
285
            if interval_before:
b87b6d9b   haribo   Finished tests fo...
286
                interval_before.end += time_shift
06241f05   haribo   Class scheduler f...
287
            interval_after.start += time_shift
7a79e25b   haribo   Date: 19/05/2016
288
289
290
291
            shs.tsp += time_shift
            shs.tep += time_shift
            shs.deltaTL += time_shift
            shs.deltaTR -= time_shift
bca9a283   Jeremy   Reworked the sche...
292
293
294
295
296
297
298
        if interval_after:
            if is_nearby_less_or_equal(interval_after.duration, self.max_overhead):
                self.intervals.remove(interval_after)
        if interval_before:
            if is_nearby_less_or_equal(interval_before.duration, self.max_overhead):
                self.intervals.remove(interval_before)
        return 0
b87b6d9b   haribo   Finished tests fo...
299

bca9a283   Jeremy   Reworked the sche...
300
301
302
303
304
    '''
        Sort by jd2 and priority -> (main sorting value)
    '''
    def sortSequences(self) -> int:
        self.sequences.sort(key=lambda x: x[0].jd2)
675fb3d5   Jeremy   Update scheduler ...
305
        self.sequences.sort(key=lambda x: x[0].priority if x[0].priority else 0)
bca9a283   Jeremy   Reworked the sche...
306
        return 0
7a79e25b   haribo   Date: 19/05/2016
307

bca9a283   Jeremy   Reworked the sche...
308
309
    def determinePriorities(self) -> int:
        return 0
7a79e25b   haribo   Date: 19/05/2016
310

bca9a283   Jeremy   Reworked the sche...
311
312
313
314
315
    def decreaseQuota(self, sequence: Sequence, quota: float) -> int:
        user = UserManager(sequence.request.pyros_user)
        if SIMULATION:
            return 0
        return user.decreaseQuota(Decimal(quota))
7a79e25b   haribo   Date: 19/05/2016
316

bca9a283   Jeremy   Reworked the sche...
317
318
319
320
    def isEmptySchedule(self) -> bool:
        if len(self.sequences) == 0:
            return True
        return False
7a79e25b   haribo   Date: 19/05/2016
321

257abe9b   Jeremy   Added comments
322
323
324
    '''
        DEBUG FUNCTIONS
    '''
675fb3d5   Jeremy   Update scheduler ...
325
326
327
328
329
330
331
332
333
    def logSequence(self, sequence):
        self.log("Logging sequence : ")
        s = sequence.shs.latest("schedule__created")
        if s.schedule == self.schedule:
            self.log("--> name: %r, start: %f, end: %f, duration: %f, deltaTL: %f, deltaTR: %f"
                  % (sequence.name, s.tsp, s.tep, sequence.duration, s.deltaTL, s.deltaTR))
        self.log("------ end ------")
        return 0

bca9a283   Jeremy   Reworked the sche...
334
    def logSchedule(self) -> int:
c53a13e0   Jeremy   Updating a lot of...
335
        sequences = Sequence.objects.filter(shs__status=Sequence.PLANNED).order_by('shs__tsp').distinct()
bca9a283   Jeremy   Reworked the sche...
336
        self.log("There are %d sequence(s) planned" % len(sequences))
b87b6d9b   haribo   Finished tests fo...
337
        for sequence in sequences:
675fb3d5   Jeremy   Update scheduler ...
338
339
340
            s = sequence.shs.latest("schedule__created")
            self.log("--> Pk: %d name: %r, shs PK: %d, start: %f, end: %f, duration: %f, deltaTL: %f, deltaTR: %f"
                  % (sequence.pk, sequence.name, s.pk, s.tsp, s.tep, sequence.duration, s.deltaTL, s.deltaTR))
bca9a283   Jeremy   Reworked the sche...
341
        self.log("There are %d free intervals" % len(self.intervals))
b87b6d9b   haribo   Finished tests fo...
342
        for interval in self.intervals:
675fb3d5   Jeremy   Update scheduler ...
343
            self.log("--> start: %f, end: %f" % (interval.start, interval.end))
bca9a283   Jeremy   Reworked the sche...
344
        return 0