Blame view

src/scheduler/Scheduler.py 14 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
    def makeSchedule(self) -> Schedule:
        global SIMULATION
        SIMULATION = False

        if self.isFirstSchedule():
            self.schedule.plan_night_start = self.schedule.plan_start
        else:
ebdda77e   Quentin Durand   small changes in ...
113
114
            self.copyFromPrevious()          #TODO trycatch a faire
            self.schedule.plan_night_start = self.schedule.plan_start
675fb3d5   Jeremy   Update scheduler ...
115

ebdda77e   Quentin Durand   small changes in ...
116
        # List of sequences (PLANNED,  TOBEPLANNED, PENDING)
ff448d43   Jeremy   Update
117
118
        self.sequences = list(Sequence.objects.filter(Q(status=Sequence.PLANNED) | Q(status=Sequence.TOBEPLANNED)
                                                      | Q(status=Sequence.PENDING)))
ebdda77e   Quentin Durand   small changes in ...
119
120
121
122
123
124
125
126
        # List of tuples (sequence, ScheduleHasSequences) for each sequence above and for current schedule
        self.sequences = [
            (
                sequence,
                ScheduleHasSequences(sequence=sequence, schedule=self.schedule)
            )
            for sequence in self.sequences
        ]
ff448d43   Jeremy   Update
127
128
        if DEBUG_FILE:
            self.log(str(len(self.sequences)) + " sequences found")
675fb3d5   Jeremy   Update scheduler ...
129
130
        self.computeSchedule()
        self.saveSchedule()
ff448d43   Jeremy   Update
131
132
        if DEBUG_FILE:
            self.log("Saving schedule with " + str(len(self.schedule.sequences.all())) + " sequences")
675fb3d5   Jeremy   Update scheduler ...
133
134
135
136
        return self.schedule

    def saveSchedule(self) -> int:
        self.schedule.save()
ff448d43   Jeremy   Update
137
138
        for sequence, shs in self.sequences:
            sequence.status = Sequence.PLANNED
675fb3d5   Jeremy   Update scheduler ...
139
            shs.schedule = self.schedule
ff448d43   Jeremy   Update
140
            sequence.save()
675fb3d5   Jeremy   Update scheduler ...
141
            shs.save()
ff448d43   Jeremy   Update
142
143
        if DEBUG_FILE:
            self.logSchedule()
675fb3d5   Jeremy   Update scheduler ...
144
145
146
        return 0


bca9a283   Jeremy   Reworked the sche...
147
148
149
150
    '''
        JB: Using : list(self.sequences) makes a copy.
    '''
    def removeNonEligible(self) -> int:
7a79e25b   haribo   Date: 19/05/2016
151
        for sequence, shs in list(self.sequences):
bca9a283   Jeremy   Reworked the sche...
152
            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...
153
            if overlap < sequence.duration:
b87b6d9b   haribo   Finished tests fo...
154
                if sequence.jd1 < self.schedule.plan_start:
06241f05   haribo   Class scheduler f...
155
                    sequence.status = Sequence.UNPLANNABLE
bca9a283   Jeremy   Reworked the sche...
156
                    if not SIMULATION:
715fabb7   haribo   #3430 (100%)
157
                        sequence.save()
7a79e25b   haribo   Date: 19/05/2016
158
                self.sequences.remove((sequence, shs))
ff448d43   Jeremy   Update
159
160
                if DEBUG_FILE:
                    self.log("Removing non eligible sequence")
bca9a283   Jeremy   Reworked the sche...
161
162
163
164
165
166
167
        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
168
169
                if DEBUG_FILE:
                    self.log("Removing sequence in removeInvalidSequences")
bca9a283   Jeremy   Reworked the sche...
170
171
172
173
174
                self.sequences.remove((sequence, shs))
                sequence.status = Sequence.INVALID
                if not SIMULATION:
                    sequence.save()
        return 0
06241f05   haribo   Class scheduler f...
175

bca9a283   Jeremy   Reworked the sche...
176
177
178
    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
179

bca9a283   Jeremy   Reworked the sche...
180
181
182
183
184
        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...
185

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

    def findSequenceBefore(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
220
221
222
                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...
223
            if s.status == Sequence.PLANNED:
bca9a283   Jeremy   Reworked the sche...
224
225
226
                if is_nearby_equal(s.tsp - self.max_overhead, interval.end):
                    return (seq, s)
        return (None, None)
06241f05   haribo   Class scheduler f...
227

65149de7   Jeremy   Update
228
    '''
bca9a283   Jeremy   Reworked the sche...
229
230
        pm(l/r) = Possible Move (Left / Right)
        shs_(b/a)_i = shs (before/after) interval
65149de7   Jeremy   Update
231
    '''
bca9a283   Jeremy   Reworked the sche...
232
233
    def tryShiftingSequences(self, sequence: Sequence, shs: ScheduleHasSequences) -> bool:
        potential = self.getPotentialIntervals(sequence)
2e0c8f94   Unknown   New install scrip...
234
        potential.sort(key=attrgetter("duration"), reverse=True) #sort the list by decreasing duration
bca9a283   Jeremy   Reworked the sche...
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
        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...
256
257
            else:
                continue
bca9a283   Jeremy   Reworked the sche...
258
259
260
261
262
263
            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...
264
            return True
06241f05   haribo   Class scheduler f...
265
        return False
7a79e25b   haribo   Date: 19/05/2016
266

257abe9b   Jeremy   Added comments
267
268
269
    '''
        Move the sequence tsp (time start planned) and tep (time end planned) left or right
    '''
bca9a283   Jeremy   Reworked the sche...
270
271
272
    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
273

06241f05   haribo   Class scheduler f...
274
        if direction not in ["LEFT", "RIGHT"]:
bca9a283   Jeremy   Reworked the sche...
275
            return 1
7a79e25b   haribo   Date: 19/05/2016
276
        if time_shift > (shs.deltaTL if direction == "LEFT" else shs.deltaTR):
bca9a283   Jeremy   Reworked the sche...
277
            return 1
06241f05   haribo   Class scheduler f...
278
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
279
            if is_nearby_equal(interval.end, shs.tsp - self.max_overhead):
06241f05   haribo   Class scheduler f...
280
                interval_before = interval
7a79e25b   haribo   Date: 19/05/2016
281
            elif is_nearby_equal(interval.start, shs.tep):
06241f05   haribo   Class scheduler f...
282
                interval_after = interval
06241f05   haribo   Class scheduler f...
283
284
        if direction == "LEFT":
            interval_before.end -= time_shift
bca9a283   Jeremy   Reworked the sche...
285
            if interval_after:
b87b6d9b   haribo   Finished tests fo...
286
                interval_after.start -= time_shift
7a79e25b   haribo   Date: 19/05/2016
287
288
289
290
            shs.tsp -= time_shift
            shs.tep -= time_shift
            shs.deltaTL -= time_shift
            shs.deltaTR += time_shift
06241f05   haribo   Class scheduler f...
291
        else:
bca9a283   Jeremy   Reworked the sche...
292
            if interval_before:
b87b6d9b   haribo   Finished tests fo...
293
                interval_before.end += time_shift
06241f05   haribo   Class scheduler f...
294
            interval_after.start += time_shift
7a79e25b   haribo   Date: 19/05/2016
295
296
297
298
            shs.tsp += time_shift
            shs.tep += time_shift
            shs.deltaTL += time_shift
            shs.deltaTR -= time_shift
bca9a283   Jeremy   Reworked the sche...
299
300
301
302
303
304
305
        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...
306

bca9a283   Jeremy   Reworked the sche...
307
308
309
310
311
    '''
        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 ...
312
        self.sequences.sort(key=lambda x: x[0].priority if x[0].priority else 0)
bca9a283   Jeremy   Reworked the sche...
313
        return 0
7a79e25b   haribo   Date: 19/05/2016
314

2e0c8f94   Unknown   New install scrip...
315
    def determinePriorities(self) -> int: #TODO
bca9a283   Jeremy   Reworked the sche...
316
        return 0
7a79e25b   haribo   Date: 19/05/2016
317

bca9a283   Jeremy   Reworked the sche...
318
319
320
321
322
    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
323

bca9a283   Jeremy   Reworked the sche...
324
325
326
327
    def isEmptySchedule(self) -> bool:
        if len(self.sequences) == 0:
            return True
        return False
7a79e25b   haribo   Date: 19/05/2016
328

257abe9b   Jeremy   Added comments
329
330
331
    '''
        DEBUG FUNCTIONS
    '''
675fb3d5   Jeremy   Update scheduler ...
332
333
334
335
336
337
338
339
340
    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...
341
    def logSchedule(self) -> int:
c53a13e0   Jeremy   Updating a lot of...
342
        sequences = Sequence.objects.filter(shs__status=Sequence.PLANNED).order_by('shs__tsp').distinct()
bca9a283   Jeremy   Reworked the sche...
343
        self.log("There are %d sequence(s) planned" % len(sequences))
b87b6d9b   haribo   Finished tests fo...
344
        for sequence in sequences:
675fb3d5   Jeremy   Update scheduler ...
345
346
347
            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...
348
        self.log("There are %d free intervals" % len(self.intervals))
b87b6d9b   haribo   Finished tests fo...
349
        for interval in self.intervals:
675fb3d5   Jeremy   Update scheduler ...
350
            self.log("--> start: %f, end: %f" % (interval.start, interval.end))
bca9a283   Jeremy   Reworked the sche...
351
        return 0