Blame view

src/scheduler/models.py 25.8 KB
7a1effdd   haribo   scheduler app cre...
1
from django.db import models
7a79e25b   haribo   Date: 19/05/2016
2
from pyrosapp.models import Schedule, Sequence, ScheduleHasSequences
06241f05   haribo   Class scheduler f...
3
4
from operator import attrgetter
from decimal import *
7a79e25b   haribo   Date: 19/05/2016
5
6
import time
from DateTime import DateTime
7a1effdd   haribo   scheduler app cre...
7

7a79e25b   haribo   Date: 19/05/2016
8
9
DEFAULT_PLAN_START = Decimal(2457485.250000)  # April 6th 2016, 18:00:00.0 UT
DEFAULT_PLAN_END = Decimal(2457485.916667)  # April 7th 2016, 10:00:00.0 UT
06241f05   haribo   Class scheduler f...
10

715fabb7   haribo   #3430 (100%)
11
12
13
14
PRECISION = Decimal(0.0000000001)

SIMULATION = False

7a79e25b   haribo   Date: 19/05/2016
15
16
17
MAX_OVERHEAD = 25
MAX_OVERHEAD_JD = Decimal(MAX_OVERHEAD / (24 * 60 * 60))

8e4ab234   haribo   #3485: Creation a...
18
19
20
'''
    Note : the following functions are necessary due to a too-high precision of Decimal objects
'''
7a79e25b   haribo   Date: 19/05/2016
21
22
23


def is_nearby_equal(a: Decimal, b: Decimal, precision: Decimal=PRECISION):
715fabb7   haribo   #3430 (100%)
24
25
26
27
28
29
    '''
        Compare the two decimal, according to the given precision
    '''
    return (True if abs(b - a) < PRECISION else False)


7a79e25b   haribo   Date: 19/05/2016
30
def is_nearby_sup_or_equal(a: Decimal, b: Decimal, precision: Decimal=PRECISION):
715fabb7   haribo   #3430 (100%)
31
32
33
34
35
36
37
38
    '''
        Compare the two decimal, according to the given precision
    '''
    if (a > b):
        return True
    return (True if abs(b - a) < PRECISION else False)


7a79e25b   haribo   Date: 19/05/2016
39
def is_nearby_less_or_equal(a: Decimal, b: Decimal, precision: Decimal=PRECISION):
715fabb7   haribo   #3430 (100%)
40
41
42
43
44
45
46
47
    '''
        Compare the two decimal, according to the given precision
    '''
    if (a < b):
        return True
    return (True if abs(b - a) < PRECISION else False)


06241f05   haribo   Class scheduler f...
48
49
50
51
52
class Interval:
    """
    Simple class that represents an interval of time
    Julian days should be used
    """
7a79e25b   haribo   Date: 19/05/2016
53

06241f05   haribo   Class scheduler f...
54
55
56
57
58
    def __init__(self, start, end):
        self._start = Decimal(start)
        self._end = Decimal(end)
        self.duration = Decimal(end - start)

06241f05   haribo   Class scheduler f...
59
    def __str__(self):
7a79e25b   haribo   Date: 19/05/2016
60
        print("[" + str(self.start) + " - " + str(self.end) + "]")
06241f05   haribo   Class scheduler f...
61

b87b6d9b   haribo   Finished tests fo...
62
63
64
    def _get_start(self):
        return self._start

06241f05   haribo   Class scheduler f...
65
66
    def _set_start(self, start):
        if start > self._end:
7a79e25b   haribo   Date: 19/05/2016
67
68
            raise ValueError(
                "Cannot set start (%d): must be lower than end (%d)" % (start, self._end))
06241f05   haribo   Class scheduler f...
69
70
        self._start = start
        self.duration = self._end - self._start
7a79e25b   haribo   Date: 19/05/2016
71

b87b6d9b   haribo   Finished tests fo...
72
73
74
    def _get_end(self):
        return self._end

06241f05   haribo   Class scheduler f...
75
76
    def _set_end(self, end):
        if end < self._start:
7a79e25b   haribo   Date: 19/05/2016
77
78
            raise ValueError(
                "Cannot set end (%d): must be bigger than start (%d)" % (end, self._start))
06241f05   haribo   Class scheduler f...
79
80
        self._end = end
        self.duration = self._end - self._start
7a79e25b   haribo   Date: 19/05/2016
81

b87b6d9b   haribo   Finished tests fo...
82
83
    start = property(fget=_get_start, fset=_set_start)
    end = property(fget=_get_end, fset=_set_end)
06241f05   haribo   Class scheduler f...
84

77816f10   haribo   Workflow implemen...
85

06241f05   haribo   Class scheduler f...
86
87
88
class Scheduler():
    """
   Role : create a planning for the following/current night
7a79e25b   haribo   Date: 19/05/2016
89
90
91

   Read in DB : Sequence, PyrosUser and parents
   Create in DB : Schedule
06241f05   haribo   Class scheduler f...
92
93
   Update in DB : Schedule, Sequence
   Delete in DB : None
7a79e25b   haribo   Date: 19/05/2016
94

06241f05   haribo   Class scheduler f...
95
96
   Entry point(s) :
           - make_schedule
8e4ab234   haribo   #3485: Creation a...
97
           - re_schedule
715fabb7   haribo   #3430 (100%)
98
           - simulate_schedule
06241f05   haribo   Class scheduler f...
99
100
101
102
    """

    """
    TODO:
715fabb7   haribo   #3430 (100%)
103
104
105
106
        - définition de plan_start et plan_end
        - calcul de la priorité
        - calcul des quotas
        - définir l'attribut 'flag' de Schedule
06241f05   haribo   Class scheduler f...
107
        - gestion du re-scheduling (en cas de nouvelle requete)
06241f05   haribo   Class scheduler f...
108
109
110
111
112
113
        - remplissage des espaces libres
        - gestion de l'historique (je sais pas si c'est ce module qui va s'en charger au final mais bon ...)

    """

    def __init__(self):
b87b6d9b   haribo   Finished tests fo...
114
        self.schedule = Schedule.objects.create()
06241f05   haribo   Class scheduler f...
115
        # TODO: quel est le "flag" dans le schedule ??
b87b6d9b   haribo   Finished tests fo...
116
        self.intervals = []
7a79e25b   haribo   Date: 19/05/2016
117
118
        self.max_overhead = MAX_OVERHEAD_JD

06241f05   haribo   Class scheduler f...
119
120
121
122
    def get_night_limits(self):
        '''
        determines and set plan_start and plan_end (beginning & end of the observation night)        
        '''
7a79e25b   haribo   Date: 19/05/2016
123
124
125
126
127
128

        # TODO: définir comment on calcule plan_start et plan_end (via quels
        # moyens)
        self.schedule.plan_start = DEFAULT_PLAN_START  # default value
        self.schedule.plan_end = DEFAULT_PLAN_END  # default value

77816f10   haribo   Workflow implemen...
129
130
131
132
133
134
    def set_night_limits(self, plan_start, plan_end):
        '''
        Sets given schedule start & end (in julian day)
        '''
        self.schedule.plan_start = Decimal(plan_start)
        self.schedule.plan_end = Decimal(plan_end)
7a79e25b   haribo   Date: 19/05/2016
135
136

    def make_schedule(self, first_schedule):
06241f05   haribo   Class scheduler f...
137
138
139
140
        '''
        ENTRY POINT

        Check all 'OBSERVABLE' sequences to create the most optimized planning for the following/current night
7a79e25b   haribo   Date: 19/05/2016
141

06241f05   haribo   Class scheduler f...
142
        It is assumed that all sequences that MUST and CAN be analyse have the OBSERVABLE status (e.g. : there must not be any PLANNED sequence at this point)        
7a79e25b   haribo   Date: 19/05/2016
143
144
145
146
147

        shs means 'ScheduleHasSequences'
        self.sequences is a list of tuples (sequence, shs)
        
        :returns : The new schedule
06241f05   haribo   Class scheduler f...
148
149
150
151
        
        :side-effect :
            - modify sequences status and dates in DB
        '''
7a79e25b   haribo   Date: 19/05/2016
152

715fabb7   haribo   #3430 (100%)
153
154
        global SIMULATION
        SIMULATION = False
7a79e25b   haribo   Date: 19/05/2016
155
156
157
158
159
160

        if first_schedule is False:
            self.copy_from_previous_schedule()
        else:
            self.schedule.plan_night_start = self.schedule.plan_start

06241f05   haribo   Class scheduler f...
161
        self.sequences = list(Sequence.objects.filter(status=Sequence.OBSERVABLE))
7a79e25b   haribo   Date: 19/05/2016
162
163
164
165
        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)]
715fabb7   haribo   #3430 (100%)
166
        self.compute_schedule()
7a79e25b   haribo   Date: 19/05/2016
167
168
        self.save_schedule()
        return self.schedule
715fabb7   haribo   #3430 (100%)
169

7a79e25b   haribo   Date: 19/05/2016
170
    def copy_from_previous_schedule(self):
8e4ab234   haribo   #3485: Creation a...
171
        '''
7a79e25b   haribo   Date: 19/05/2016
172
173
174
175
176
177
            Copy needed information from the previous schedule :
                - gets the executed sequences from the previous schedule and copy them into the new schedule
                - gets plan_start and plan_end
                - computes new plan_restart

            shs means 'ScheduleHasSequences'
8e4ab234   haribo   #3485: Creation a...
178
        '''
8e4ab234   haribo   #3485: Creation a...
179

7a79e25b   haribo   Date: 19/05/2016
180
181
182
183
184
185
186
187
        previous_sched = Schedule.objects.order_by('-created')[1]
        previous_exc_seq = previous_sched.sequences.filter(status=Sequence.EXECUTED)
        for seq in previous_exc_seq:
            shs = seq.shs
            shs.pk = None
            shs.schedule = self.schedule
            if SIMULATION == False:
                shs.save()
8e4ab234   haribo   #3485: Creation a...
188

7a79e25b   haribo   Date: 19/05/2016
189
190
191
192
193
194
        self.schedule.plan_night_start = previous_sched.plan_night_start
        self.schedule.plan_end = previous_sched.plan_end

        ''' Schedule starts in MAX_OVERHEAD seconds '''
        self.schedule.plan_start = DateTime(
            time.time()).JulianDay() + self.max_overhead
8e4ab234   haribo   #3485: Creation a...
195

715fabb7   haribo   #3430 (100%)
196
197
198
    def simulate_schedule(self, sequences):
        '''
        ENTRY POINT - SIMULATION
7a79e25b   haribo   Date: 19/05/2016
199

715fabb7   haribo   #3430 (100%)
200
        Do the same as make_schedule but do not touch the DB
7a79e25b   haribo   Date: 19/05/2016
201

715fabb7   haribo   #3430 (100%)
202
203
204
205
206
        :type sequences : list of Sequence
        :param sequences : sequences to plan

        :returns : a tuple (Schedule, list of sequences)
        '''
7a79e25b   haribo   Date: 19/05/2016
207

715fabb7   haribo   #3430 (100%)
208
209
        global SIMULATION
        SIMULATION = True
7a79e25b   haribo   Date: 19/05/2016
210
211

        self.schedule.plan_night_start = self.schedule.plan_start
715fabb7   haribo   #3430 (100%)
212
        self.sequences = sequences
7a79e25b   haribo   Date: 19/05/2016
213
214
215
216
        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)]
715fabb7   haribo   #3430 (100%)
217
218
        self.compute_schedule()
        return (self.schedule, self.sequences)
715fabb7   haribo   #3430 (100%)
219
220

    def compute_schedule(self):
7a79e25b   haribo   Date: 19/05/2016
221
222
        self.intervals.append(
            Interval(self.schedule.plan_start, self.schedule.plan_end))
b87b6d9b   haribo   Finished tests fo...
223
        self.check_sequences_validity()
06241f05   haribo   Class scheduler f...
224
225
226
227
        self.determine_priorities()
        self.remove_not_eligible_sequences()
        self.sort_by_jd2_and_priorities()
        self.organize_sequences()
b87b6d9b   haribo   Finished tests fo...
228

b87b6d9b   haribo   Finished tests fo...
229
230
231
    def check_sequences_validity(self):
        '''
        Checks come sequence attributes to validate their integrity
7a79e25b   haribo   Date: 19/05/2016
232

b87b6d9b   haribo   Finished tests fo...
233
234
235
236
        :side-effect :
            - remove invalid sequences from self.sequences
            - set INVALID status for invalid sequences in DB
        '''
7a79e25b   haribo   Date: 19/05/2016
237

b87b6d9b   haribo   Finished tests fo...
238
        ''' Note(1) '''
7a79e25b   haribo   Date: 19/05/2016
239
        for sequence, shs in list(self.sequences):
715fabb7   haribo   #3430 (100%)
240
            if sequence.jd1 < 0 or sequence.jd2 < 0 or is_nearby_less_or_equal(sequence.duration, 0) or sequence.jd2 - sequence.jd1 < sequence.duration:
7a79e25b   haribo   Date: 19/05/2016
241
                self.sequences.remove((sequence, shs))
b87b6d9b   haribo   Finished tests fo...
242
                sequence.status = Sequence.INVALID
715fabb7   haribo   #3430 (100%)
243
244
                if SIMULATION == False:
                    sequence.save()
7a79e25b   haribo   Date: 19/05/2016
245

06241f05   haribo   Class scheduler f...
246
247
248
249
    def determine_priorities(self):
        '''
        Computes sequences priority according to the user, the scientific program, ...        
        '''
7a79e25b   haribo   Date: 19/05/2016
250

06241f05   haribo   Class scheduler f...
251
252
        # TODO: définir comment on calcule la priorité
        pass
7a79e25b   haribo   Date: 19/05/2016
253

06241f05   haribo   Class scheduler f...
254
255
256
257
258
    def remove_not_eligible_sequences(self):
        '''
        Computes overlap between [jd1; jd2] and [plan_start; plan_end]
        Removes from self.sequences all the sequences that cannot be observed between plan_start and plan_end
        Set UNPLANNABLE sequences if jd2 < plan_start
7a79e25b   haribo   Date: 19/05/2016
259

06241f05   haribo   Class scheduler f...
260
261
262
        :side-effect :
            - remove unwanted sequences from self.sequences
        '''
7a79e25b   haribo   Date: 19/05/2016
263

715fabb7   haribo   #3430 (100%)
264
        ''' Note (1) '''
7a79e25b   haribo   Date: 19/05/2016
265
266
267
        for sequence, shs in list(self.sequences):
            overlap = min(self.schedule.plan_end, sequence.jd2) - \
                max(self.schedule.plan_start, sequence.jd1) - self.max_overhead
06241f05   haribo   Class scheduler f...
268
            if overlap < sequence.duration:
b87b6d9b   haribo   Finished tests fo...
269
                if sequence.jd1 < self.schedule.plan_start:
06241f05   haribo   Class scheduler f...
270
271
                    """ Note (2) """
                    sequence.status = Sequence.UNPLANNABLE
715fabb7   haribo   #3430 (100%)
272
273
                    if SIMULATION == False:
                        sequence.save()
7a79e25b   haribo   Date: 19/05/2016
274
                self.sequences.remove((sequence, shs))
06241f05   haribo   Class scheduler f...
275

06241f05   haribo   Class scheduler f...
276
277
278
279
    def sort_by_jd2_and_priorities(self):
        '''
        Sort by priority and jd2, priority being the main sorting parameter        
        '''
7a79e25b   haribo   Date: 19/05/2016
280
281
282

        self.sequences.sort(key=lambda x: x[0].jd2)
        self.sequences.sort(key=lambda x: x[0].priority)
06241f05   haribo   Class scheduler f...
283
284
285
286
287
288
289
290
291
292
293

    def organize_sequences(self):
        '''
        Main function of the Scheduler
        Arrange a maximum of observable sequences in the planning

        Algorithm (for each sequence) :
            - check quota (remove sequence from list if quota is too low)
            - select matching intervals
            - IF matching intervals => place sequence according to tPrefered
            - IF NO matching intervals => try to move other sequences to place this one
7a79e25b   haribo   Date: 19/05/2016
294

06241f05   haribo   Class scheduler f...
295
296
297
298
        :side-effect :
            - remove unwanted sequences from self.sequences
            - change status and dates of sequences in self.sequences (but not in DB yet)
        '''
7a79e25b   haribo   Date: 19/05/2016
299
300
301

        ''' Note (1) '''
        for sequence, shs in list(self.sequences):
06241f05   haribo   Class scheduler f...
302
303
            quota = self.determine_quota(sequence)
            if quota < sequence.duration:
7a79e25b   haribo   Date: 19/05/2016
304
                self.sequences.remove((sequence, shs))
06241f05   haribo   Class scheduler f...
305
306
307
308
                continue

            matching_intervals = self.get_matching_intervals(sequence)
            if len(matching_intervals) > 0:
7a79e25b   haribo   Date: 19/05/2016
309
                self.place_sequence(sequence, shs, matching_intervals)
06241f05   haribo   Class scheduler f...
310
311
                sequence_placed = True
            else:
7a79e25b   haribo   Date: 19/05/2016
312
                sequence_placed = self.try_shifting_sequences(sequence, shs)
06241f05   haribo   Class scheduler f...
313
            if sequence_placed == True:
7a79e25b   haribo   Date: 19/05/2016
314
                shs.status = Sequence.PLANNED
06241f05   haribo   Class scheduler f...
315
                self.update_quota(sequence)
7a79e25b   haribo   Date: 19/05/2016
316
317

    def determine_quota(self, sequence: Sequence) -> float:
06241f05   haribo   Class scheduler f...
318
319
320
321
322
323
        '''
        Determines the quota (in minutes) according to the current planning duration and the quota of the user and scientific program associated

        :returns : The quota (float)
        '''
        # TODO: définir comment on calcule le quota
7a79e25b   haribo   Date: 19/05/2016
324
325
326
327

        return sequence.request.pyros_user.quota  # default value

    def get_matching_intervals(self, sequence: Sequence):
06241f05   haribo   Class scheduler f...
328
329
        '''
        Find the intervals where the sequence could be inserted
7a79e25b   haribo   Date: 19/05/2016
330

06241f05   haribo   Class scheduler f...
331
332
        :returns : list of matching Intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
333

06241f05   haribo   Class scheduler f...
334
        matching_intervals = []
7a79e25b   haribo   Date: 19/05/2016
335

06241f05   haribo   Class scheduler f...
336
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
337
338
            overlap = min(sequence.jd2, interval.end) - \
                max(sequence.jd1, interval.start) - self.max_overhead
715fabb7   haribo   #3430 (100%)
339
            if overlap > sequence.duration or is_nearby_equal(overlap, sequence.duration):
06241f05   haribo   Class scheduler f...
340
                matching_intervals.append(interval)
7a79e25b   haribo   Date: 19/05/2016
341

06241f05   haribo   Class scheduler f...
342
        return matching_intervals
7a79e25b   haribo   Date: 19/05/2016
343
344

    def place_sequence(self, sequence: Sequence, shs: ScheduleHasSequences, matching_intervals):
06241f05   haribo   Class scheduler f...
345
346
        '''
        Place the sequence in the better interval, according to the t_prefered
7a79e25b   haribo   Date: 19/05/2016
347

06241f05   haribo   Class scheduler f...
348
349
350
351
352
353
354
        :type matching_intervals: list [Interval]
        :param matching_intervals: Intervals in which the sequence can be placed

        :side-effect :
            - changes self.intervals
            - change the sequence if it it placed
        '''
7a79e25b   haribo   Date: 19/05/2016
355

06241f05   haribo   Class scheduler f...
356
357
358
        if len(matching_intervals) == 0:
            raise ValueError("matching_intervals shall not be empty")

7a79e25b   haribo   Date: 19/05/2016
359
360
361
362
363
364
365
366
        prefered_interval = self.get_prefered_interval(
            sequence, matching_intervals)
        sequence_position_in_interval = self.get_sequence_position_in_interval(
            sequence, prefered_interval)
        self.insert_sequence_in_interval(
            sequence, shs, prefered_interval, sequence_position_in_interval)
        self.cut_interval(sequence, shs, prefered_interval)
        self.update_other_deltas(sequence, shs, prefered_interval)
06241f05   haribo   Class scheduler f...
367

7a79e25b   haribo   Date: 19/05/2016
368
    def get_prefered_interval(self, sequence: Sequence, matching_intervals) -> Interval:
06241f05   haribo   Class scheduler f...
369
        '''
b87b6d9b   haribo   Finished tests fo...
370
        Find the better interval, according to the t_prefered (get the nearest)
7a79e25b   haribo   Date: 19/05/2016
371

06241f05   haribo   Class scheduler f...
372
373
        :type matching_intervals: list [Interval]
        :param matching_intervals: Intervals in which the sequence can be placed
7a79e25b   haribo   Date: 19/05/2016
374

06241f05   haribo   Class scheduler f...
375
376
377
378
379
380
        :returns : An Interval that fits sequence.t_prefered at most
        '''

        if len(matching_intervals) == 0:
            raise ValueError("matching_intervals shall not be empty")

b87b6d9b   haribo   Finished tests fo...
381
        if sequence.t_prefered == 0 or len(matching_intervals) == 1:
06241f05   haribo   Class scheduler f...
382
383
384
            prefered_interval = matching_intervals[0]
        else:
            for index, interval in enumerate(matching_intervals):
715fabb7   haribo   #3430 (100%)
385
                if is_nearby_less_or_equal(interval.start, sequence.t_prefered) and is_nearby_less_or_equal(sequence.t_prefered, interval.end):
06241f05   haribo   Class scheduler f...
386
387
388
389
390
391
392
393
394
                    prefered_interval = interval
                    break
                elif sequence.t_prefered < interval.start:
                    if index == 0:
                        prefered_interval = interval
                    else:
                        prefered_interval = matching_intervals[index - 1]
                    break
        return prefered_interval
7a79e25b   haribo   Date: 19/05/2016
395
396

    def get_sequence_position_in_interval(self, sequence: Sequence, interval: Interval) -> str:
06241f05   haribo   Class scheduler f...
397
398
399
400
401
402
        '''
        Determines where the sequence will be inserted in the interval, regarding sequence.t_prefered

        :returns : A string in ["START", "END", "PREFERED"] describing where the sequence will be inserted in the interval
        '''

715fabb7   haribo   #3430 (100%)
403
404
        if is_nearby_less_or_equal(interval.start, sequence.t_prefered) and is_nearby_less_or_equal(sequence.t_prefered, interval.end):
            if is_nearby_less_or_equal(sequence.t_prefered - Decimal(0.5) * sequence.duration, interval.start):
06241f05   haribo   Class scheduler f...
405
                position_in_interval = "START"
715fabb7   haribo   #3430 (100%)
406
            elif is_nearby_sup_or_equal(sequence.t_prefered + Decimal(0.5) * sequence.duration, interval.end):
06241f05   haribo   Class scheduler f...
407
408
                position_in_interval = "END"
            else:
7a79e25b   haribo   Date: 19/05/2016
409
                position_in_interval = "PREFERED"
06241f05   haribo   Class scheduler f...
410
411
412
413
414
415
        else:
            if sequence.t_prefered < interval.start:
                position_in_interval = "START"
            else:
                position_in_interval = "END"
        return position_in_interval
06241f05   haribo   Class scheduler f...
416

7a79e25b   haribo   Date: 19/05/2016
417
    def insert_sequence_in_interval(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval, position: str):
06241f05   haribo   Class scheduler f...
418
419
420
421
        '''
        Inserts the sequence in the interval:
            - sets sequence.tsp and sequence.tep
            - sets sequence.deltaTL and sequence.deltaTR
7a79e25b   haribo   Date: 19/05/2016
422

06241f05   haribo   Class scheduler f...
423
424
        :param interval: Interval in which the sequence will be inserted
        :param position: String describing where the sequence will be inserted in the interval
7a79e25b   haribo   Date: 19/05/2016
425

06241f05   haribo   Class scheduler f...
426
427
428
        :side-effect :
            - modify sequence attributes (tsp, tep, deltaTL, deltaTR)
        '''
7a79e25b   haribo   Date: 19/05/2016
429

06241f05   haribo   Class scheduler f...
430
        if position not in ["START", "END", "PREFERED"]:
7a79e25b   haribo   Date: 19/05/2016
431
432
433
            raise ValueError(
                "position must be either 'START', 'END' or 'PREFERED'")

06241f05   haribo   Class scheduler f...
434
        if position == "START":
7a79e25b   haribo   Date: 19/05/2016
435
436
437
438
439
440
            shs.tsp = max(
                interval.start + self.max_overhead, sequence.jd1)
            shs.tep = shs.tsp + sequence.duration
            shs.deltaTL = 0
            shs.deltaTR = min(
                interval.end, sequence.jd2) - shs.tep
06241f05   haribo   Class scheduler f...
441
        elif position == "END":
7a79e25b   haribo   Date: 19/05/2016
442
443
444
445
446
            shs.tep = min(interval.end, sequence.jd2)
            shs.tsp = shs.tep - sequence.duration
            shs.deltaTL = shs.tsp - \
                max(interval.start + self.max_overhead, sequence.jd1)
            shs.deltaTR = 0
06241f05   haribo   Class scheduler f...
447
        else:
7a79e25b   haribo   Date: 19/05/2016
448
449
450
451
452
453
454
455
456
457
458
            shs.tsp = max(
                sequence.jd1, sequence.t_prefered - Decimal(0.5) * sequence.duration)
            if shs.tsp - interval.start < self.max_overhead:
                shs.tsp = interval.start + self.max_overhead
            shs.tep = shs.tsp + sequence.duration
            shs.deltaTL = shs.tsp - \
                max(interval.start + self.max_overhead, sequence.jd1)
            shs.deltaTR = min(
                interval.end, sequence.jd2) - shs.tep

    def cut_interval(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval):
06241f05   haribo   Class scheduler f...
459
460
461
        '''
        Separates the interval in two parts regarding to the sequence position
        Sorts the interval list in time order
7a79e25b   haribo   Date: 19/05/2016
462

06241f05   haribo   Class scheduler f...
463
        :param interval : the interval in which the sequence was added
7a79e25b   haribo   Date: 19/05/2016
464

06241f05   haribo   Class scheduler f...
465
466
467
468
469
        :side-effect :
            - removes interval from self.intervals
            - add created intervals to self.intervals
            - sorts self.intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
470
471
472
473
474

        interval_before_sequence = Interval(
            interval.start, shs.tsp - self.max_overhead)
        interval_after_sequence = Interval(shs.tep, interval.end)

06241f05   haribo   Class scheduler f...
475
476
477
478
479
480
481
        self.intervals.remove(interval)
        if interval_before_sequence.duration > 0:
            self.intervals.append(interval_before_sequence)
        if interval_after_sequence.duration > 0:
            self.intervals.append(interval_after_sequence)
        self.intervals.sort(key=lambda interval: interval.start, reverse=False)

7a79e25b   haribo   Date: 19/05/2016
482
    def update_other_deltas(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval):
06241f05   haribo   Class scheduler f...
483
484
        '''
        Update deltaTL and deltaTR of sequences planned near this sequence
7a79e25b   haribo   Date: 19/05/2016
485

b87b6d9b   haribo   Finished tests fo...
486
        :param interval: Interval in which the sequence was added
7a79e25b   haribo   Date: 19/05/2016
487

06241f05   haribo   Class scheduler f...
488
489
490
        :side-effect :
            - modify deltaTL and deltaTR of sequences before and after the interval
        '''
7a79e25b   haribo   Date: 19/05/2016
491
492
493
494
495
496
497
498

        for sequence_, shs_ in self.sequences:
            if shs_.status == Sequence.PLANNED:
                if is_nearby_equal(shs_.tep, interval.start):
                    sequence_before_interval, shs_b_i = sequence_, shs_
                elif is_nearby_equal(shs_.tsp - self.max_overhead, interval.end):
                    sequence_after_interval, shs_a_i = sequence_, shs_

b87b6d9b   haribo   Finished tests fo...
499
        if 'sequence_before_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
500
501
            shs_b_i.deltaTR = min(
                shs.tsp - self.max_overhead, sequence_before_interval.jd2) - shs_b_i.tep
b87b6d9b   haribo   Finished tests fo...
502
        if 'sequence_after_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
503
504
505
506
            shs_a_i.deltaTL = shs_a_i.tsp - self.max_overhead - \
                max(shs.tep, sequence_after_interval.jd1)

    def try_shifting_sequences(self, sequence: Sequence, shs: ScheduleHasSequences) -> bool:
06241f05   haribo   Class scheduler f...
507
508
        '''
        Tries to find a place in the planning for the sequence, moving the other sequences
7a79e25b   haribo   Date: 19/05/2016
509

06241f05   haribo   Class scheduler f...
510
        :returns : A boolean -> True if the sequence was placed, False otherwise
7a79e25b   haribo   Date: 19/05/2016
511

06241f05   haribo   Class scheduler f...
512
513
514
        :side-effect:
            - might change some sequences' deltaTL and/or deltaTR
        '''
7a79e25b   haribo   Date: 19/05/2016
515

b87b6d9b   haribo   Finished tests fo...
516
        potential_intervals = self.get_potential_intervals(sequence)
7a79e25b   haribo   Date: 19/05/2016
517
        # arrêté là
b87b6d9b   haribo   Finished tests fo...
518
        potential_intervals.sort(key=attrgetter("duration"), reverse=True)
06241f05   haribo   Class scheduler f...
519
520
        for interval in potential_intervals:
            ''' we get the adjacent sequences '''
7a79e25b   haribo   Date: 19/05/2016
521
522
523
524
525
526
527
528
529
530
531
            for sequence_, shs_ in self.sequences:
                if shs_.status == Sequence.PLANNED:
                    if is_nearby_equal(shs_.tep, interval.start):
                        sequence_before_interval, shs_b_i = sequence_, shs_
                    elif is_nearby_equal(shs_.tsp - self.max_overhead, interval.end):
                        sequence_after_interval, shs_a_i = sequence_, shs_

            available_duration = min(
                interval.end, sequence.jd2) - max(interval.start, sequence.jd1)
            missing_duration = sequence.duration - \
                available_duration + self.max_overhead
b87b6d9b   haribo   Finished tests fo...
532
            if 'sequence_before_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
533
534
                possible_move_to_left = min(
                    shs_b_i.deltaTL, interval.start - sequence.jd1)
b87b6d9b   haribo   Finished tests fo...
535
536
            else:
                possible_move_to_left = 0
7a79e25b   haribo   Date: 19/05/2016
537

b87b6d9b   haribo   Finished tests fo...
538
            if 'sequence_after_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
539
540
                possible_move_to_right = min(
                    shs_a_i.deltaTR, sequence.jd2 - interval.end)
b87b6d9b   haribo   Finished tests fo...
541
542
            else:
                possible_move_to_right = 0
06241f05   haribo   Class scheduler f...
543

715fabb7   haribo   #3430 (100%)
544
            if is_nearby_sup_or_equal(possible_move_to_left, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
545
546
                self.move_sequence(
                    sequence_before_interval, shs_b_i, missing_duration, "LEFT")
715fabb7   haribo   #3430 (100%)
547
            elif is_nearby_sup_or_equal(possible_move_to_right, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
548
549
                self.move_sequence(
                    sequence_after_interval, shs_a_i, missing_duration, "RIGHT")
715fabb7   haribo   #3430 (100%)
550
            elif is_nearby_sup_or_equal(possible_move_to_left + possible_move_to_right, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
551
552
553
554
                self.move_sequence(
                    sequence_before_interval, shs_b_i, possible_move_to_left, "LEFT")
                self.move_sequence(
                    sequence_after_interval, shs_a_i, missing_duration - possible_move_to_left, "RIGHT")
b87b6d9b   haribo   Finished tests fo...
555
556
            else:
                continue
06241f05   haribo   Class scheduler f...
557
558

            matching_intervals = self.get_matching_intervals(sequence)
7a79e25b   haribo   Date: 19/05/2016
559

06241f05   haribo   Class scheduler f...
560
            if len(matching_intervals) != 1:
7a79e25b   haribo   Date: 19/05/2016
561
562
563
                raise ValueError(
                    "There should be one and only one matching interval after shifting")
            self.place_sequence(sequence, shs, matching_intervals)
b87b6d9b   haribo   Finished tests fo...
564
565

            return True
7a79e25b   haribo   Date: 19/05/2016
566

06241f05   haribo   Class scheduler f...
567
        return False
7a79e25b   haribo   Date: 19/05/2016
568
569

    def get_potential_intervals(self, sequence: Sequence):
06241f05   haribo   Class scheduler f...
570
571
        '''
        Find the intervals where a part of the sequence could be inserted
7a79e25b   haribo   Date: 19/05/2016
572

06241f05   haribo   Class scheduler f...
573
574
        :returns : list of partially-matching Intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
575

06241f05   haribo   Class scheduler f...
576
        potential_intervals = []
7a79e25b   haribo   Date: 19/05/2016
577

06241f05   haribo   Class scheduler f...
578
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
579
580
            overlap = min(sequence.jd2, interval.end) - \
                max(sequence.jd1, interval.start) - self.max_overhead
06241f05   haribo   Class scheduler f...
581
582
            if overlap > 0:
                potential_intervals.append(interval)
7a79e25b   haribo   Date: 19/05/2016
583

06241f05   haribo   Class scheduler f...
584
585
        return potential_intervals

7a79e25b   haribo   Date: 19/05/2016
586
    def move_sequence(self, sequence: Sequence, shs: ScheduleHasSequences, time_shift: Decimal, direction: str):
06241f05   haribo   Class scheduler f...
587
588
        '''
        Moves the sequence in the wanted direction, decreasing its deltaTL or deltaTR.
7a79e25b   haribo   Date: 19/05/2016
589

06241f05   haribo   Class scheduler f...
590
591
592
        :param sequence: sequence to be moved
        :param time_shift: amplitude of the shift
        :param direction: "LEFT" or "RIGHT"
7a79e25b   haribo   Date: 19/05/2016
593

06241f05   haribo   Class scheduler f...
594
595
596
597
        :side-effect :
            - modify the sequence in self.sequences
            - changes the interval before and the interval after the sequence
        '''
7a79e25b   haribo   Date: 19/05/2016
598

06241f05   haribo   Class scheduler f...
599
600
        if direction not in ["LEFT", "RIGHT"]:
            raise ValueError("direction must be 'LEFT' or 'RIGHT'")
7a79e25b   haribo   Date: 19/05/2016
601
        if time_shift > (shs.deltaTL if direction == "LEFT" else shs.deltaTR):
06241f05   haribo   Class scheduler f...
602
            raise ValueError("Shift value is bigger than deltaT(R/L)")
7a79e25b   haribo   Date: 19/05/2016
603

06241f05   haribo   Class scheduler f...
604
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
605
            if is_nearby_equal(interval.end, shs.tsp - self.max_overhead):
06241f05   haribo   Class scheduler f...
606
                interval_before = interval
7a79e25b   haribo   Date: 19/05/2016
607
            elif is_nearby_equal(interval.start, shs.tep):
06241f05   haribo   Class scheduler f...
608
                interval_after = interval
7a79e25b   haribo   Date: 19/05/2016
609

06241f05   haribo   Class scheduler f...
610
611
        if direction == "LEFT":
            interval_before.end -= time_shift
b87b6d9b   haribo   Finished tests fo...
612
613
            if "interval_after" in locals():
                interval_after.start -= time_shift
7a79e25b   haribo   Date: 19/05/2016
614
615
616
617
            shs.tsp -= time_shift
            shs.tep -= time_shift
            shs.deltaTL -= time_shift
            shs.deltaTR += time_shift
06241f05   haribo   Class scheduler f...
618
        else:
b87b6d9b   haribo   Finished tests fo...
619
620
            if "interval_before" in locals():
                interval_before.end += time_shift
06241f05   haribo   Class scheduler f...
621
            interval_after.start += time_shift
7a79e25b   haribo   Date: 19/05/2016
622
623
624
625
            shs.tsp += time_shift
            shs.tep += time_shift
            shs.deltaTL += time_shift
            shs.deltaTR -= time_shift
715fabb7   haribo   #3430 (100%)
626
        if "interval_before" in locals() and is_nearby_equal(interval_before.duration, 0):
b87b6d9b   haribo   Finished tests fo...
627
            self.intervals.remove(interval_before)
715fabb7   haribo   #3430 (100%)
628
        if "interval_after" in locals() and is_nearby_equal(interval_after.duration, 0):
b87b6d9b   haribo   Finished tests fo...
629
630
            self.intervals.remove(interval_after)

7a79e25b   haribo   Date: 19/05/2016
631
    def update_quota(self, sequence: Sequence):
06241f05   haribo   Class scheduler f...
632
633
        '''
        Update the quota of the user / scientific program / whatever by substracting the sequence duration to the quotas
7a79e25b   haribo   Date: 19/05/2016
634

06241f05   haribo   Class scheduler f...
635
636
637
        :side-effect:
            - Modify User quota in DB
        '''
7a79e25b   haribo   Date: 19/05/2016
638

06241f05   haribo   Class scheduler f...
639
640
        # TODO: faire les vrais calculs de quota
        user = sequence.request.pyros_user
7a79e25b   haribo   Date: 19/05/2016
641
642
643
        # action par défaut qui correspond au code de self.determine_quota
        user.quota -= float(sequence.duration)

715fabb7   haribo   #3430 (100%)
644
645
        if SIMULATION == False:
            user.save()
7a79e25b   haribo   Date: 19/05/2016
646
647

    def save_schedule(self):
06241f05   haribo   Class scheduler f...
648
        '''
b87b6d9b   haribo   Finished tests fo...
649
        Final function : save in the db all modifications done to sequences, and the schedule
7a79e25b   haribo   Date: 19/05/2016
650

06241f05   haribo   Class scheduler f...
651
652
        :side-effect :
            - change sequences status and dates in DB
b87b6d9b   haribo   Finished tests fo...
653
            - add a schedule in the DB
06241f05   haribo   Class scheduler f...
654
        '''
7a79e25b   haribo   Date: 19/05/2016
655

8e4ab234   haribo   #3485: Creation a...
656
        self.schedule.save()
7a79e25b   haribo   Date: 19/05/2016
657
658
659
        for sequence, shs in self.sequences:
            shs.schedule = self.schedule
            shs.save()
06241f05   haribo   Class scheduler f...
660

b87b6d9b   haribo   Finished tests fo...
661
662
663
    def print_schedule(self):
        '''
        ONLY FOR DEBUG
7a79e25b   haribo   Date: 19/05/2016
664

b87b6d9b   haribo   Finished tests fo...
665
666
667
        Prints the planned sequences
        '''

7a79e25b   haribo   Date: 19/05/2016
668
669
670
        sequences = Sequence.objects.filter(
            shs__status=Sequence.PLANNED).order_by('shs__tsp')

b87b6d9b   haribo   Finished tests fo...
671
        print("---- There are %d sequence(s) planned ----" % len(sequences))
7a79e25b   haribo   Date: 19/05/2016
672

b87b6d9b   haribo   Finished tests fo...
673
674
        for sequence in sequences:
            print("name: %r\t, start: %d\t, end: %d\t, duration: %d\t, deltaTL: %d\t, deltaTR: %d\t"
7a79e25b   haribo   Date: 19/05/2016
675
                  % (sequence.name, sequence.shs.tsp, sequence.shs.tep, sequence.duration, sequence.shs.deltaTL, sequence.shs.deltaTR))
b87b6d9b   haribo   Finished tests fo...
676
677

        print("---- There are %d free interval(s) ----" % len(self.intervals))
7a79e25b   haribo   Date: 19/05/2016
678

b87b6d9b   haribo   Finished tests fo...
679
680
681
        for interval in self.intervals:
            print("start: %d\t, end: %d\t" % (interval.start, interval.end))

06241f05   haribo   Class scheduler f...
682
683
684
685
686
''' Notes

(1) list(self.sequences) creates a copy in order to modify self.sequences and still iterate on it without unexpected behavior
(2) UNPLANNABLE is a definitive status meaning that the sequence will never be able to be scheduled

7a79e25b   haribo   Date: 19/05/2016
687
'''