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
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:
eecfb779   haribo   Date: 26/05/2016
304
305
                shs.status = Sequence.REJECTED
                shs.desc = REJECTED_QUOTA
06241f05   haribo   Class scheduler f...
306
307
308
309
                continue

            matching_intervals = self.get_matching_intervals(sequence)
            if len(matching_intervals) > 0:
7a79e25b   haribo   Date: 19/05/2016
310
                self.place_sequence(sequence, shs, matching_intervals)
06241f05   haribo   Class scheduler f...
311
312
                sequence_placed = True
            else:
7a79e25b   haribo   Date: 19/05/2016
313
                sequence_placed = self.try_shifting_sequences(sequence, shs)
06241f05   haribo   Class scheduler f...
314
            if sequence_placed == True:
eecfb779   haribo   Date: 26/05/2016
315
                shs.status = Sequence.PENDING
06241f05   haribo   Class scheduler f...
316
                self.update_quota(sequence)
eecfb779   haribo   Date: 26/05/2016
317
318
319
            else:
                shs.status = Sequence.REJECTED
                shs.desc = REJECTED_ROOM
7a79e25b   haribo   Date: 19/05/2016
320
321

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

        return sequence.request.pyros_user.quota  # default value

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

06241f05   haribo   Class scheduler f...
590
591
        return potential_intervals

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

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

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

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

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

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

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

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

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

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

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

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

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

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

b87b6d9b   haribo   Finished tests fo...
671
672
673
        Prints the planned sequences
        '''

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

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

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

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

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

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