Blame view

src/scheduler/Scheduler.py 13.3 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

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

bca9a283   Jeremy   Reworked the sche...
72
    def simulateSchedule(self, sequences) -> tuple:
715fabb7   haribo   #3430 (100%)
73
74
        global SIMULATION
        SIMULATION = True
7a79e25b   haribo   Date: 19/05/2016
75
76

        self.schedule.plan_night_start = self.schedule.plan_start
bca9a283   Jeremy   Reworked the sche...
77
        self.sequences = list(sequences)
7a79e25b   haribo   Date: 19/05/2016
78
79
80
81
        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...
82
        self.computeSchedule()
715fabb7   haribo   #3430 (100%)
83
        return (self.schedule, self.sequences)
715fabb7   haribo   #3430 (100%)
84

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

675fb3d5   Jeremy   Update scheduler ...
97
98
99
100
101
102
103
104
105
106
    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
107
108
        self.sequences = list(Sequence.objects.filter(Q(status=Sequence.PLANNED) | Q(status=Sequence.TOBEPLANNED)
                                                      | Q(status=Sequence.PENDING)))
675fb3d5   Jeremy   Update scheduler ...
109
110
        self.sequences = [(sequence, ScheduleHasSequences(sequence=sequence, schedule=self.schedule))
                          for sequence in self.sequences]
ff448d43   Jeremy   Update
111
112
        if DEBUG_FILE:
            self.log(str(len(self.sequences)) + " sequences found")
675fb3d5   Jeremy   Update scheduler ...
113
114
        self.computeSchedule()
        self.saveSchedule()
ff448d43   Jeremy   Update
115
116
        if DEBUG_FILE:
            self.log("Saving schedule with " + str(len(self.schedule.sequences.all())) + " sequences")
675fb3d5   Jeremy   Update scheduler ...
117
118
119
120
        return self.schedule

    def saveSchedule(self) -> int:
        self.schedule.save()
ff448d43   Jeremy   Update
121
122
        for sequence, shs in self.sequences:
            sequence.status = Sequence.PLANNED
675fb3d5   Jeremy   Update scheduler ...
123
            shs.schedule = self.schedule
ff448d43   Jeremy   Update
124
            sequence.save()
675fb3d5   Jeremy   Update scheduler ...
125
            shs.save()
ff448d43   Jeremy   Update
126
127
        if DEBUG_FILE:
            self.logSchedule()
675fb3d5   Jeremy   Update scheduler ...
128
129
130
        return 0


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

bca9a283   Jeremy   Reworked the sche...
160
161
162
    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
163

bca9a283   Jeremy   Reworked the sche...
164
165
166
167
168
        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...
169

bca9a283   Jeremy   Reworked the sche...
170
    def placeSequences(self) -> int:
7a79e25b   haribo   Date: 19/05/2016
171
        for sequence, shs in list(self.sequences):
bca9a283   Jeremy   Reworked the sche...
172
173
            quota = UserManager.determineQuota(sequence)
            if not UserManager.isSufficient(quota, sequence):
eecfb779   haribo   Date: 26/05/2016
174
                shs.status = Sequence.REJECTED
bca9a283   Jeremy   Reworked the sche...
175
                shs.desc = UserManager.REJECTED
06241f05   haribo   Class scheduler f...
176
                continue
bca9a283   Jeremy   Reworked the sche...
177
            matching_intervals = self.getMatchingIntervals(sequence)
06241f05   haribo   Class scheduler f...
178
            if len(matching_intervals) > 0:
bca9a283   Jeremy   Reworked the sche...
179
180
181
                inter = self.placeSequence(sequence, shs, matching_intervals)
                if inter:
                    self.updateOtherDeltas(sequence, shs, inter)
06241f05   haribo   Class scheduler f...
182
183
                sequence_placed = True
            else:
bca9a283   Jeremy   Reworked the sche...
184
185
                sequence_placed = self.tryShiftingSequences(sequence, shs)
            if sequence_placed:
c53a13e0   Jeremy   Updating a lot of...
186
                shs.status = Sequence.PLANNED
bca9a283   Jeremy   Reworked the sche...
187
                self.decreaseQuota(sequence, sequence.duration)
eecfb779   haribo   Date: 26/05/2016
188
            else:
ff448d43   Jeremy   Update
189
190
                if DEBUG_FILE:
                    self.log("Removing sequence in place_sequences")
eecfb779   haribo   Date: 26/05/2016
191
                shs.status = Sequence.REJECTED
bca9a283   Jeremy   Reworked the sche...
192
193
194
195
196
                shs.desc = self.REJECTED_ROOM
        return 0

    def findSequenceBefore(self, interval: Interval):
        for seq, s in self.sequences:
c53a13e0   Jeremy   Updating a lot of...
197
            if s.status == Sequence.PLANNED:
bca9a283   Jeremy   Reworked the sche...
198
199
200
201
202
203
                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...
204
            if s.status == Sequence.PLANNED:
bca9a283   Jeremy   Reworked the sche...
205
206
207
                if is_nearby_equal(s.tsp - self.max_overhead, interval.end):
                    return (seq, s)
        return (None, None)
06241f05   haribo   Class scheduler f...
208

65149de7   Jeremy   Update
209
    '''
bca9a283   Jeremy   Reworked the sche...
210
211
        pm(l/r) = Possible Move (Left / Right)
        shs_(b/a)_i = shs (before/after) interval
65149de7   Jeremy   Update
212
    '''
bca9a283   Jeremy   Reworked the sche...
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
    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...
237
238
            else:
                continue
bca9a283   Jeremy   Reworked the sche...
239
240
241
242
243
244
            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...
245
            return True
06241f05   haribo   Class scheduler f...
246
        return False
7a79e25b   haribo   Date: 19/05/2016
247

bca9a283   Jeremy   Reworked the sche...
248
249
250
    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
251

06241f05   haribo   Class scheduler f...
252
        if direction not in ["LEFT", "RIGHT"]:
bca9a283   Jeremy   Reworked the sche...
253
            return 1
7a79e25b   haribo   Date: 19/05/2016
254
        if time_shift > (shs.deltaTL if direction == "LEFT" else shs.deltaTR):
bca9a283   Jeremy   Reworked the sche...
255
            return 1
06241f05   haribo   Class scheduler f...
256
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
257
            if is_nearby_equal(interval.end, shs.tsp - self.max_overhead):
06241f05   haribo   Class scheduler f...
258
                interval_before = interval
7a79e25b   haribo   Date: 19/05/2016
259
            elif is_nearby_equal(interval.start, shs.tep):
06241f05   haribo   Class scheduler f...
260
                interval_after = interval
06241f05   haribo   Class scheduler f...
261
262
        if direction == "LEFT":
            interval_before.end -= time_shift
bca9a283   Jeremy   Reworked the sche...
263
            if interval_after:
b87b6d9b   haribo   Finished tests fo...
264
                interval_after.start -= time_shift
7a79e25b   haribo   Date: 19/05/2016
265
266
267
268
            shs.tsp -= time_shift
            shs.tep -= time_shift
            shs.deltaTL -= time_shift
            shs.deltaTR += time_shift
06241f05   haribo   Class scheduler f...
269
        else:
bca9a283   Jeremy   Reworked the sche...
270
            if interval_before:
b87b6d9b   haribo   Finished tests fo...
271
                interval_before.end += time_shift
06241f05   haribo   Class scheduler f...
272
            interval_after.start += time_shift
7a79e25b   haribo   Date: 19/05/2016
273
274
275
276
            shs.tsp += time_shift
            shs.tep += time_shift
            shs.deltaTL += time_shift
            shs.deltaTR -= time_shift
bca9a283   Jeremy   Reworked the sche...
277
278
279
280
281
282
283
        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...
284

bca9a283   Jeremy   Reworked the sche...
285
286
287
288
289
    '''
        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 ...
290
        self.sequences.sort(key=lambda x: x[0].priority if x[0].priority else 0)
bca9a283   Jeremy   Reworked the sche...
291
        return 0
7a79e25b   haribo   Date: 19/05/2016
292

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

bca9a283   Jeremy   Reworked the sche...
296
297
298
299
300
    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
301

bca9a283   Jeremy   Reworked the sche...
302
303
304
305
    def isEmptySchedule(self) -> bool:
        if len(self.sequences) == 0:
            return True
        return False
7a79e25b   haribo   Date: 19/05/2016
306

675fb3d5   Jeremy   Update scheduler ...
307
308
309
310
311
312
313
314
315
    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...
316
    def logSchedule(self) -> int:
c53a13e0   Jeremy   Updating a lot of...
317
        sequences = Sequence.objects.filter(shs__status=Sequence.PLANNED).order_by('shs__tsp').distinct()
bca9a283   Jeremy   Reworked the sche...
318
        self.log("There are %d sequence(s) planned" % len(sequences))
b87b6d9b   haribo   Finished tests fo...
319
        for sequence in sequences:
675fb3d5   Jeremy   Update scheduler ...
320
321
322
            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...
323
        self.log("There are %d free intervals" % len(self.intervals))
b87b6d9b   haribo   Finished tests fo...
324
        for interval in self.intervals:
675fb3d5   Jeremy   Update scheduler ...
325
            self.log("--> start: %f, end: %f" % (interval.start, interval.end))
bca9a283   Jeremy   Reworked the sche...
326
        return 0