Blame view

src/scheduler/models.py 25.9 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))

eecfb779   haribo   Date: 26/05/2016
18
19
20
REJECTED_QUOTA = "Insufficient quota"
REJECTED_ROOM = "Insufficient room for this sequence"

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


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


7a79e25b   haribo   Date: 19/05/2016
33
def is_nearby_sup_or_equal(a: Decimal, b: Decimal, precision: Decimal=PRECISION):
715fabb7   haribo   #3430 (100%)
34
35
36
37
38
39
40
41
    '''
        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
42
def is_nearby_less_or_equal(a: Decimal, b: Decimal, precision: Decimal=PRECISION):
715fabb7   haribo   #3430 (100%)
43
44
45
46
47
48
49
50
    '''
        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...
51
52
53
54
55
class Interval:
    """
    Simple class that represents an interval of time
    Julian days should be used
    """
7a79e25b   haribo   Date: 19/05/2016
56

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

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

b87b6d9b   haribo   Finished tests fo...
65
66
67
    def _get_start(self):
        return self._start

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

b87b6d9b   haribo   Finished tests fo...
75
76
77
    def _get_end(self):
        return self._end

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

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

77816f10   haribo   Workflow implemen...
88

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

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

06241f05   haribo   Class scheduler f...
98
99
   Entry point(s) :
           - make_schedule
715fabb7   haribo   #3430 (100%)
100
           - simulate_schedule
06241f05   haribo   Class scheduler f...
101
102
103
104
    """

    """
    TODO:
715fabb7   haribo   #3430 (100%)
105
106
107
108
        - 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...
109
        - remplissage des espaces libres
06241f05   haribo   Class scheduler f...
110
111
112
113

    """

    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

eecfb779   haribo   Date: 26/05/2016
142
        It is assumed that all sequences that MUST and CAN be analyse have the OBSERVABLE status        
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
        self.schedule.plan_night_start = previous_sched.plan_night_start
        self.schedule.plan_end = previous_sched.plan_end

        ''' Schedule starts in MAX_OVERHEAD seconds '''
9774228b   haribo   Date: 22/06/2016
193
        self.schedule.plan_start = DateTime(time.time()).JulianDay() + self.max_overhead
8e4ab234   haribo   #3485: Creation a...
194

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

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

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

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

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

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

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

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

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

b87b6d9b   haribo   Finished tests fo...
237
        ''' Note(1) '''
7a79e25b   haribo   Date: 19/05/2016
238
        for sequence, shs in list(self.sequences):
715fabb7   haribo   #3430 (100%)
239
            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
240
                self.sequences.remove((sequence, shs))
b87b6d9b   haribo   Finished tests fo...
241
                sequence.status = Sequence.INVALID
715fabb7   haribo   #3430 (100%)
242
243
                if SIMULATION == False:
                    sequence.save()
7a79e25b   haribo   Date: 19/05/2016
244

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

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

06241f05   haribo   Class scheduler f...
253
254
255
256
257
    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
258

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

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

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

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

    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
293

06241f05   haribo   Class scheduler f...
294
295
296
297
        :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
298
299
300

        ''' Note (1) '''
        for sequence, shs in list(self.sequences):
06241f05   haribo   Class scheduler f...
301
302
            quota = self.determine_quota(sequence)
            if quota < sequence.duration:
eecfb779   haribo   Date: 26/05/2016
303
304
                shs.status = Sequence.REJECTED
                shs.desc = REJECTED_QUOTA
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:
eecfb779   haribo   Date: 26/05/2016
314
                shs.status = Sequence.PENDING
06241f05   haribo   Class scheduler f...
315
                self.update_quota(sequence)
eecfb779   haribo   Date: 26/05/2016
316
317
318
            else:
                shs.status = Sequence.REJECTED
                shs.desc = REJECTED_ROOM
7a79e25b   haribo   Date: 19/05/2016
319
320

    def determine_quota(self, sequence: Sequence) -> float:
06241f05   haribo   Class scheduler f...
321
322
323
324
325
326
        '''
        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
327
328
329
330

        return sequence.request.pyros_user.quota  # default value

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

06241f05   haribo   Class scheduler f...
334
335
        :returns : list of matching Intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
336

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

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

06241f05   haribo   Class scheduler f...
345
        return matching_intervals
7a79e25b   haribo   Date: 19/05/2016
346
347

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

06241f05   haribo   Class scheduler f...
351
352
353
354
355
356
357
        :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
358

06241f05   haribo   Class scheduler f...
359
360
361
        if len(matching_intervals) == 0:
            raise ValueError("matching_intervals shall not be empty")

7a79e25b   haribo   Date: 19/05/2016
362
363
364
365
366
367
368
369
        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...
370

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

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

06241f05   haribo   Class scheduler f...
378
379
380
381
382
383
        :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...
384
        if sequence.t_prefered == 0 or len(matching_intervals) == 1:
06241f05   haribo   Class scheduler f...
385
386
387
            prefered_interval = matching_intervals[0]
        else:
            for index, interval in enumerate(matching_intervals):
715fabb7   haribo   #3430 (100%)
388
                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...
389
390
391
392
393
                    prefered_interval = interval
                    break
                elif sequence.t_prefered < interval.start:
                    if index == 0:
                        prefered_interval = interval
92039557   haribo   Minor fix : t_pre...
394
395
                    elif interval.start + self.max_overhead - sequence.t_prefered < sequence.t_prefered - matching_intervals[index - 1].end:
                        prefered_interval = interval
06241f05   haribo   Class scheduler f...
396
397
398
399
                    else:
                        prefered_interval = matching_intervals[index - 1]
                    break
        return prefered_interval
7a79e25b   haribo   Date: 19/05/2016
400
401

    def get_sequence_position_in_interval(self, sequence: Sequence, interval: Interval) -> str:
06241f05   haribo   Class scheduler f...
402
403
404
405
406
407
        '''
        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%)
408
409
        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...
410
                position_in_interval = "START"
715fabb7   haribo   #3430 (100%)
411
            elif is_nearby_sup_or_equal(sequence.t_prefered + Decimal(0.5) * sequence.duration, interval.end):
06241f05   haribo   Class scheduler f...
412
413
                position_in_interval = "END"
            else:
7a79e25b   haribo   Date: 19/05/2016
414
                position_in_interval = "PREFERED"
06241f05   haribo   Class scheduler f...
415
416
417
418
419
420
        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...
421

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

06241f05   haribo   Class scheduler f...
428
429
        :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
430

06241f05   haribo   Class scheduler f...
431
432
433
        :side-effect :
            - modify sequence attributes (tsp, tep, deltaTL, deltaTR)
        '''
7a79e25b   haribo   Date: 19/05/2016
434

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

06241f05   haribo   Class scheduler f...
439
        if position == "START":
7a79e25b   haribo   Date: 19/05/2016
440
441
442
443
444
445
            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...
446
        elif position == "END":
7a79e25b   haribo   Date: 19/05/2016
447
448
449
450
451
            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...
452
        else:
7a79e25b   haribo   Date: 19/05/2016
453
454
455
456
457
458
459
460
461
462
463
            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...
464
465
466
        '''
        Separates the interval in two parts regarding to the sequence position
        Sorts the interval list in time order
7a79e25b   haribo   Date: 19/05/2016
467

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

06241f05   haribo   Class scheduler f...
470
471
472
473
474
        :side-effect :
            - removes interval from self.intervals
            - add created intervals to self.intervals
            - sorts self.intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
475
476
477
478
479

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

06241f05   haribo   Class scheduler f...
480
481
482
483
484
485
486
        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
487
    def update_other_deltas(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval):
06241f05   haribo   Class scheduler f...
488
489
        '''
        Update deltaTL and deltaTR of sequences planned near this sequence
7a79e25b   haribo   Date: 19/05/2016
490

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

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

        for sequence_, shs_ in self.sequences:
eecfb779   haribo   Date: 26/05/2016
498
            if shs_.status == Sequence.PENDING:
7a79e25b   haribo   Date: 19/05/2016
499
500
501
502
503
                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...
504
        if 'sequence_before_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
505
506
            shs_b_i.deltaTR = min(
                shs.tsp - self.max_overhead, sequence_before_interval.jd2) - shs_b_i.tep
b87b6d9b   haribo   Finished tests fo...
507
        if 'sequence_after_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
508
509
510
511
            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...
512
513
        '''
        Tries to find a place in the planning for the sequence, moving the other sequences
7a79e25b   haribo   Date: 19/05/2016
514

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

06241f05   haribo   Class scheduler f...
517
518
519
        :side-effect:
            - might change some sequences' deltaTL and/or deltaTR
        '''
7a79e25b   haribo   Date: 19/05/2016
520

b87b6d9b   haribo   Finished tests fo...
521
        potential_intervals = self.get_potential_intervals(sequence)
b87b6d9b   haribo   Finished tests fo...
522
        potential_intervals.sort(key=attrgetter("duration"), reverse=True)
06241f05   haribo   Class scheduler f...
523
524
        for interval in potential_intervals:
            ''' we get the adjacent sequences '''
7a79e25b   haribo   Date: 19/05/2016
525
            for sequence_, shs_ in self.sequences:
eecfb779   haribo   Date: 26/05/2016
526
                if shs_.status == Sequence.PENDING:
7a79e25b   haribo   Date: 19/05/2016
527
528
529
530
531
532
533
534
535
                    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...
536
            if 'sequence_before_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
537
538
                possible_move_to_left = min(
                    shs_b_i.deltaTL, interval.start - sequence.jd1)
b87b6d9b   haribo   Finished tests fo...
539
540
            else:
                possible_move_to_left = 0
7a79e25b   haribo   Date: 19/05/2016
541

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

715fabb7   haribo   #3430 (100%)
548
            if is_nearby_sup_or_equal(possible_move_to_left, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
549
550
                self.move_sequence(
                    sequence_before_interval, shs_b_i, missing_duration, "LEFT")
715fabb7   haribo   #3430 (100%)
551
            elif is_nearby_sup_or_equal(possible_move_to_right, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
552
553
                self.move_sequence(
                    sequence_after_interval, shs_a_i, missing_duration, "RIGHT")
715fabb7   haribo   #3430 (100%)
554
            elif is_nearby_sup_or_equal(possible_move_to_left + possible_move_to_right, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
555
556
557
558
                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...
559
560
            else:
                continue
06241f05   haribo   Class scheduler f...
561
562

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

06241f05   haribo   Class scheduler f...
564
            if len(matching_intervals) != 1:
7a79e25b   haribo   Date: 19/05/2016
565
566
567
                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...
568
569

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

06241f05   haribo   Class scheduler f...
571
        return False
7a79e25b   haribo   Date: 19/05/2016
572
573

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

06241f05   haribo   Class scheduler f...
577
578
        :returns : list of partially-matching Intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
579

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

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

06241f05   haribo   Class scheduler f...
588
589
        return potential_intervals

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

06241f05   haribo   Class scheduler f...
594
595
596
        :param sequence: sequence to be moved
        :param time_shift: amplitude of the shift
        :param direction: "LEFT" or "RIGHT"
7a79e25b   haribo   Date: 19/05/2016
597

06241f05   haribo   Class scheduler f...
598
599
600
601
        :side-effect :
            - modify the sequence in self.sequences
            - changes the interval before and the interval after the sequence
        '''
7a79e25b   haribo   Date: 19/05/2016
602

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

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

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

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

06241f05   haribo   Class scheduler f...
639
640
641
        :side-effect:
            - Modify User quota in DB
        '''
7a79e25b   haribo   Date: 19/05/2016
642

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

715fabb7   haribo   #3430 (100%)
648
649
        if SIMULATION == False:
            user.save()
7a79e25b   haribo   Date: 19/05/2016
650
651

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

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

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

b87b6d9b   haribo   Finished tests fo...
665
666
667
    def print_schedule(self):
        '''
        ONLY FOR DEBUG
7a79e25b   haribo   Date: 19/05/2016
668

b87b6d9b   haribo   Finished tests fo...
669
670
671
        Prints the planned sequences
        '''

7a79e25b   haribo   Date: 19/05/2016
672
        sequences = Sequence.objects.filter(
eecfb779   haribo   Date: 26/05/2016
673
            shs__status=Sequence.PENDING).order_by('shs__tsp')
7a79e25b   haribo   Date: 19/05/2016
674

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

b87b6d9b   haribo   Finished tests fo...
677
678
        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
679
                  % (sequence.name, sequence.shs.tsp, sequence.shs.tep, sequence.duration, sequence.shs.deltaTL, sequence.shs.deltaTR))
b87b6d9b   haribo   Finished tests fo...
680
681

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

b87b6d9b   haribo   Finished tests fo...
683
684
685
        for interval in self.intervals:
            print("start: %d\t, end: %d\t" % (interval.start, interval.end))

06241f05   haribo   Class scheduler f...
686
687
688
689
690
''' 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
691
'''