Blame view

src/scheduler/Scheduler.py 27 KB
7a1effdd   haribo   scheduler app cre...
1
from django.db import models
ddf59dd4   haribo   Remaniement :
2
from common.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
import time
7a1effdd   haribo   scheduler app cre...
6

7a79e25b   haribo   Date: 19/05/2016
7
8
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...
9

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

SIMULATION = False

1b341a0f   haribo   Date: 19/07/2016
14
TIMESTAMP_JD = 2440587.500000
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
    def get_night_limits(self):
        '''
3c179769   Jeremy   userSimulator / a...
121
        determines and set plan_start and plan_end (beginning & end of the observation night)
06241f05   haribo   Class scheduler f...
122
        '''
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

3c179769   Jeremy   userSimulator / a...
142
        It is assumed that all sequences that MUST and CAN be analyzed have the OBSERVABLE status
7a79e25b   haribo   Date: 19/05/2016
143
144
145

        shs means 'ScheduleHasSequences'
        self.sequences is a list of tuples (sequence, shs)
3c179769   Jeremy   userSimulator / a...
146

7a79e25b   haribo   Date: 19/05/2016
147
        :returns : The new schedule
3c179769   Jeremy   userSimulator / a...
148

06241f05   haribo   Class scheduler f...
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

64fc9a89   Etienne Pallier   - un peu de refac...
156
157
        #if first_schedule is False:
        if first_schedule:
7a79e25b   haribo   Date: 19/05/2016
158
            self.schedule.plan_night_start = self.schedule.plan_start
64fc9a89   Etienne Pallier   - un peu de refac...
159
160
        else:
            self.copy_from_previous_schedule()
7a79e25b   haribo   Date: 19/05/2016
161

06241f05   haribo   Class scheduler f...
162
        self.sequences = list(Sequence.objects.filter(status=Sequence.OBSERVABLE))
64fc9a89   Etienne Pallier   - un peu de refac...
163
        # Add to each sequence its schedule id
7a79e25b   haribo   Date: 19/05/2016
164
        shs_list = []
3c179769   Jeremy   userSimulator / a...
165

64fc9a89   Etienne Pallier   - un peu de refac...
166
167
        #EP improved:
        '''
7a79e25b   haribo   Date: 19/05/2016
168
169
170
        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)]
64fc9a89   Etienne Pallier   - un peu de refac...
171
172
        '''
        self.sequences = [(sequence, ScheduleHasSequences(sequence=sequence, schedule=self.schedule)) for sequence in self.sequences]
3c179769   Jeremy   userSimulator / a...
173

715fabb7   haribo   #3430 (100%)
174
        self.compute_schedule()
7a79e25b   haribo   Date: 19/05/2016
175
176
        self.save_schedule()
        return self.schedule
715fabb7   haribo   #3430 (100%)
177

7a79e25b   haribo   Date: 19/05/2016
178
    def copy_from_previous_schedule(self):
8e4ab234   haribo   #3485: Creation a...
179
        '''
7a79e25b   haribo   Date: 19/05/2016
180
181
            Copy needed information from the previous schedule :
                - gets the executed sequences from the previous schedule and copy them into the new schedule
64fc9a89   Etienne Pallier   - un peu de refac...
182
183
                - gets plan_night_start and plan_end
                - computes new plan_restart (plan_start)
7a79e25b   haribo   Date: 19/05/2016
184
185

            shs means 'ScheduleHasSequences'
3c179769   Jeremy   userSimulator / a...
186

64fc9a89   Etienne Pallier   - un peu de refac...
187
188
            Side effects:
            - create new shs entries
8e4ab234   haribo   #3485: Creation a...
189
        '''
8e4ab234   haribo   #3485: Creation a...
190

64fc9a89   Etienne Pallier   - un peu de refac...
191
        # Get all EXECUTED sequences from last schedule
7a79e25b   haribo   Date: 19/05/2016
192
193
        previous_sched = Schedule.objects.order_by('-created')[1]
        previous_exc_seq = previous_sched.sequences.filter(status=Sequence.EXECUTED)
64fc9a89   Etienne Pallier   - un peu de refac...
194

3c179769   Jeremy   userSimulator / a...
195
        # Associate each EXECUTED sequence to the new schedule in DB (by creating a new shs entry for each sequence)
7a79e25b   haribo   Date: 19/05/2016
196
197
198
199
200
201
        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...
202

7a79e25b   haribo   Date: 19/05/2016
203
204
205
206
        self.schedule.plan_night_start = previous_sched.plan_night_start
        self.schedule.plan_end = previous_sched.plan_end

        ''' Schedule starts in MAX_OVERHEAD seconds '''
1b341a0f   haribo   Date: 19/07/2016
207
        self.schedule.plan_start = time.time() / 86400 + TIMESTAMP_JD + self.max_overhead
8e4ab234   haribo   #3485: Creation a...
208

715fabb7   haribo   #3430 (100%)
209
210
211
    def simulate_schedule(self, sequences):
        '''
        ENTRY POINT - SIMULATION
7a79e25b   haribo   Date: 19/05/2016
212

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

715fabb7   haribo   #3430 (100%)
215
216
217
218
219
        :type sequences : list of Sequence
        :param sequences : sequences to plan

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

715fabb7   haribo   #3430 (100%)
221
222
        global SIMULATION
        SIMULATION = True
7a79e25b   haribo   Date: 19/05/2016
223
224

        self.schedule.plan_night_start = self.schedule.plan_start
715fabb7   haribo   #3430 (100%)
225
        self.sequences = sequences
7a79e25b   haribo   Date: 19/05/2016
226
227
228
229
        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%)
230
231
        self.compute_schedule()
        return (self.schedule, self.sequences)
715fabb7   haribo   #3430 (100%)
232
233

    def compute_schedule(self):
64fc9a89   Etienne Pallier   - un peu de refac...
234
235
        #EP TODO: est-on sur ici que self.intervals est VIDE ???
        # Create a unique big empty available interval that takes all the night duration [plan_start,plan_end]
7a79e25b   haribo   Date: 19/05/2016
236
237
        self.intervals.append(
            Interval(self.schedule.plan_start, self.schedule.plan_end))
3c179769   Jeremy   userSimulator / a...
238
239

        '''
64fc9a89   Etienne Pallier   - un peu de refac...
240
241
242
243
244
245
246
        EP:
        Uniquement à cause des sequences de Alain Klotz (qui sont parfois invalides pour Pyros) ???
        TODO: cette étape pourra être supprimée en production, car les sequences fabriquées par Pyros seront valides
        '''
        #EP renamed
        #self.check_sequences_validity()
        self.remove_invalid_sequences()
3c179769   Jeremy   userSimulator / a...
247

06241f05   haribo   Class scheduler f...
248
        self.determine_priorities()
3c179769   Jeremy   userSimulator / a...
249

64fc9a89   Etienne Pallier   - un peu de refac...
250
        self.remove_non_eligible_sequences()
3c179769   Jeremy   userSimulator / a...
251

06241f05   haribo   Class scheduler f...
252
        self.sort_by_jd2_and_priorities()
3c179769   Jeremy   userSimulator / a...
253
254

        #EP renamed:
64fc9a89   Etienne Pallier   - un peu de refac...
255
256
257
258
259
260
        #self.organize_sequences()
        self.place_sequences()

    #EP renamed:
    #def check_sequences_validity(self):
    def remove_invalid_sequences(self):
b87b6d9b   haribo   Finished tests fo...
261
262
        '''
        Checks come sequence attributes to validate their integrity
7a79e25b   haribo   Date: 19/05/2016
263

64fc9a89   Etienne Pallier   - un peu de refac...
264
        :side-effects :
b87b6d9b   haribo   Finished tests fo...
265
266
267
            - remove invalid sequences from self.sequences
            - set INVALID status for invalid sequences in DB
        '''
7a79e25b   haribo   Date: 19/05/2016
268

b87b6d9b   haribo   Finished tests fo...
269
        ''' Note(1) '''
7a79e25b   haribo   Date: 19/05/2016
270
        for sequence, shs in list(self.sequences):
715fabb7   haribo   #3430 (100%)
271
            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
272
                self.sequences.remove((sequence, shs))
b87b6d9b   haribo   Finished tests fo...
273
                sequence.status = Sequence.INVALID
715fabb7   haribo   #3430 (100%)
274
275
                if SIMULATION == False:
                    sequence.save()
7a79e25b   haribo   Date: 19/05/2016
276

06241f05   haribo   Class scheduler f...
277
278
    def determine_priorities(self):
        '''
3c179769   Jeremy   userSimulator / a...
279
        Computes sequences priority according to the user, the scientific program, ...
06241f05   haribo   Class scheduler f...
280
        '''
7a79e25b   haribo   Date: 19/05/2016
281

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

64fc9a89   Etienne Pallier   - un peu de refac...
285
    def remove_non_eligible_sequences(self):
06241f05   haribo   Class scheduler f...
286
287
288
289
        '''
        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
290

06241f05   haribo   Class scheduler f...
291
292
        :side-effect :
            - remove unwanted sequences from self.sequences
64fc9a89   Etienne Pallier   - un peu de refac...
293
            - mark them as UNPLANNABLE (in DB)
06241f05   haribo   Class scheduler f...
294
        '''
7a79e25b   haribo   Date: 19/05/2016
295

715fabb7   haribo   #3430 (100%)
296
        ''' Note (1) '''
7a79e25b   haribo   Date: 19/05/2016
297
298
299
        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...
300
            if overlap < sequence.duration:
b87b6d9b   haribo   Finished tests fo...
301
                if sequence.jd1 < self.schedule.plan_start:
06241f05   haribo   Class scheduler f...
302
303
                    """ Note (2) """
                    sequence.status = Sequence.UNPLANNABLE
715fabb7   haribo   #3430 (100%)
304
305
                    if SIMULATION == False:
                        sequence.save()
7a79e25b   haribo   Date: 19/05/2016
306
                self.sequences.remove((sequence, shs))
06241f05   haribo   Class scheduler f...
307

06241f05   haribo   Class scheduler f...
308
309
    def sort_by_jd2_and_priorities(self):
        '''
3c179769   Jeremy   userSimulator / a...
310
        Sort by priority and jd2, priority being the main sorting parameter
06241f05   haribo   Class scheduler f...
311
        '''
7a79e25b   haribo   Date: 19/05/2016
312
313
314

        self.sequences.sort(key=lambda x: x[0].jd2)
        self.sequences.sort(key=lambda x: x[0].priority)
06241f05   haribo   Class scheduler f...
315

64fc9a89   Etienne Pallier   - un peu de refac...
316
317
318
    #EP renamed
    #def organize_sequences(self):
    def place_sequences(self):
06241f05   haribo   Class scheduler f...
319
320
321
322
323
324
325
326
327
        '''
        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
328

06241f05   haribo   Class scheduler f...
329
330
331
332
        :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
333
334
335

        ''' Note (1) '''
        for sequence, shs in list(self.sequences):
06241f05   haribo   Class scheduler f...
336
337
            quota = self.determine_quota(sequence)
            if quota < sequence.duration:
eecfb779   haribo   Date: 26/05/2016
338
339
                shs.status = Sequence.REJECTED
                shs.desc = REJECTED_QUOTA
06241f05   haribo   Class scheduler f...
340
341
342
343
                continue

            matching_intervals = self.get_matching_intervals(sequence)
            if len(matching_intervals) > 0:
7a79e25b   haribo   Date: 19/05/2016
344
                self.place_sequence(sequence, shs, matching_intervals)
06241f05   haribo   Class scheduler f...
345
346
                sequence_placed = True
            else:
7a79e25b   haribo   Date: 19/05/2016
347
                sequence_placed = self.try_shifting_sequences(sequence, shs)
06241f05   haribo   Class scheduler f...
348
            if sequence_placed == True:
eecfb779   haribo   Date: 26/05/2016
349
                shs.status = Sequence.PENDING
06241f05   haribo   Class scheduler f...
350
                self.update_quota(sequence)
eecfb779   haribo   Date: 26/05/2016
351
352
353
            else:
                shs.status = Sequence.REJECTED
                shs.desc = REJECTED_ROOM
7a79e25b   haribo   Date: 19/05/2016
354
355

    def determine_quota(self, sequence: Sequence) -> float:
06241f05   haribo   Class scheduler f...
356
357
358
359
360
361
        '''
        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
362
363
364
365

        return sequence.request.pyros_user.quota  # default value

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

06241f05   haribo   Class scheduler f...
369
370
        :returns : list of matching Intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
371

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

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

06241f05   haribo   Class scheduler f...
380
        return matching_intervals
7a79e25b   haribo   Date: 19/05/2016
381
382

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

06241f05   haribo   Class scheduler f...
386
387
388
389
390
391
392
        :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
393

06241f05   haribo   Class scheduler f...
394
395
396
        if len(matching_intervals) == 0:
            raise ValueError("matching_intervals shall not be empty")

7a79e25b   haribo   Date: 19/05/2016
397
398
399
400
401
402
403
404
        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...
405

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

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

06241f05   haribo   Class scheduler f...
413
414
415
416
417
418
        :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...
419
        if sequence.t_prefered == 0 or len(matching_intervals) == 1:
06241f05   haribo   Class scheduler f...
420
421
422
            prefered_interval = matching_intervals[0]
        else:
            for index, interval in enumerate(matching_intervals):
715fabb7   haribo   #3430 (100%)
423
                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...
424
425
426
427
428
                    prefered_interval = interval
                    break
                elif sequence.t_prefered < interval.start:
                    if index == 0:
                        prefered_interval = interval
92039557   haribo   Minor fix : t_pre...
429
430
                    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...
431
432
433
434
                    else:
                        prefered_interval = matching_intervals[index - 1]
                    break
        return prefered_interval
7a79e25b   haribo   Date: 19/05/2016
435
436

    def get_sequence_position_in_interval(self, sequence: Sequence, interval: Interval) -> str:
06241f05   haribo   Class scheduler f...
437
438
439
440
441
442
        '''
        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%)
443
444
        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...
445
                position_in_interval = "START"
715fabb7   haribo   #3430 (100%)
446
            elif is_nearby_sup_or_equal(sequence.t_prefered + Decimal(0.5) * sequence.duration, interval.end):
06241f05   haribo   Class scheduler f...
447
448
                position_in_interval = "END"
            else:
7a79e25b   haribo   Date: 19/05/2016
449
                position_in_interval = "PREFERED"
06241f05   haribo   Class scheduler f...
450
451
452
453
454
455
        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...
456

7a79e25b   haribo   Date: 19/05/2016
457
    def insert_sequence_in_interval(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval, position: str):
06241f05   haribo   Class scheduler f...
458
459
460
461
        '''
        Inserts the sequence in the interval:
            - sets sequence.tsp and sequence.tep
            - sets sequence.deltaTL and sequence.deltaTR
7a79e25b   haribo   Date: 19/05/2016
462

06241f05   haribo   Class scheduler f...
463
464
        :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
465

06241f05   haribo   Class scheduler f...
466
467
468
        :side-effect :
            - modify sequence attributes (tsp, tep, deltaTL, deltaTR)
        '''
7a79e25b   haribo   Date: 19/05/2016
469

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

06241f05   haribo   Class scheduler f...
474
        if position == "START":
7a79e25b   haribo   Date: 19/05/2016
475
476
477
478
479
480
            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...
481
        elif position == "END":
7a79e25b   haribo   Date: 19/05/2016
482
483
484
485
486
            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...
487
        else:
7a79e25b   haribo   Date: 19/05/2016
488
489
490
491
492
493
494
495
496
497
498
            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...
499
500
501
        '''
        Separates the interval in two parts regarding to the sequence position
        Sorts the interval list in time order
7a79e25b   haribo   Date: 19/05/2016
502

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

06241f05   haribo   Class scheduler f...
505
506
507
508
509
        :side-effect :
            - removes interval from self.intervals
            - add created intervals to self.intervals
            - sorts self.intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
510
511
512
513
514

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

06241f05   haribo   Class scheduler f...
515
516
517
518
519
520
521
        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
522
    def update_other_deltas(self, sequence: Sequence, shs: ScheduleHasSequences, interval: Interval):
06241f05   haribo   Class scheduler f...
523
524
        '''
        Update deltaTL and deltaTR of sequences planned near this sequence
7a79e25b   haribo   Date: 19/05/2016
525

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

06241f05   haribo   Class scheduler f...
528
529
530
        :side-effect :
            - modify deltaTL and deltaTR of sequences before and after the interval
        '''
7a79e25b   haribo   Date: 19/05/2016
531
532

        for sequence_, shs_ in self.sequences:
eecfb779   haribo   Date: 26/05/2016
533
            if shs_.status == Sequence.PENDING:
7a79e25b   haribo   Date: 19/05/2016
534
535
536
537
538
                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...
539
        if 'sequence_before_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
540
541
            shs_b_i.deltaTR = min(
                shs.tsp - self.max_overhead, sequence_before_interval.jd2) - shs_b_i.tep
b87b6d9b   haribo   Finished tests fo...
542
        if 'sequence_after_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
543
544
545
546
            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...
547
548
        '''
        Tries to find a place in the planning for the sequence, moving the other sequences
7a79e25b   haribo   Date: 19/05/2016
549

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

06241f05   haribo   Class scheduler f...
552
553
554
        :side-effect:
            - might change some sequences' deltaTL and/or deltaTR
        '''
7a79e25b   haribo   Date: 19/05/2016
555

b87b6d9b   haribo   Finished tests fo...
556
        potential_intervals = self.get_potential_intervals(sequence)
b87b6d9b   haribo   Finished tests fo...
557
        potential_intervals.sort(key=attrgetter("duration"), reverse=True)
06241f05   haribo   Class scheduler f...
558
559
        for interval in potential_intervals:
            ''' we get the adjacent sequences '''
7a79e25b   haribo   Date: 19/05/2016
560
            for sequence_, shs_ in self.sequences:
eecfb779   haribo   Date: 26/05/2016
561
                if shs_.status == Sequence.PENDING:
7a79e25b   haribo   Date: 19/05/2016
562
563
564
565
566
567
568
569
570
                    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...
571
            if 'sequence_before_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
572
573
                possible_move_to_left = min(
                    shs_b_i.deltaTL, interval.start - sequence.jd1)
b87b6d9b   haribo   Finished tests fo...
574
575
            else:
                possible_move_to_left = 0
7a79e25b   haribo   Date: 19/05/2016
576

b87b6d9b   haribo   Finished tests fo...
577
            if 'sequence_after_interval' in locals():
7a79e25b   haribo   Date: 19/05/2016
578
579
                possible_move_to_right = min(
                    shs_a_i.deltaTR, sequence.jd2 - interval.end)
b87b6d9b   haribo   Finished tests fo...
580
581
            else:
                possible_move_to_right = 0
06241f05   haribo   Class scheduler f...
582

715fabb7   haribo   #3430 (100%)
583
            if is_nearby_sup_or_equal(possible_move_to_left, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
584
585
                self.move_sequence(
                    sequence_before_interval, shs_b_i, missing_duration, "LEFT")
715fabb7   haribo   #3430 (100%)
586
            elif is_nearby_sup_or_equal(possible_move_to_right, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
587
588
                self.move_sequence(
                    sequence_after_interval, shs_a_i, missing_duration, "RIGHT")
715fabb7   haribo   #3430 (100%)
589
            elif is_nearby_sup_or_equal(possible_move_to_left + possible_move_to_right, missing_duration):
7a79e25b   haribo   Date: 19/05/2016
590
591
592
593
                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...
594
595
            else:
                continue
06241f05   haribo   Class scheduler f...
596
597

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

06241f05   haribo   Class scheduler f...
599
            if len(matching_intervals) != 1:
7a79e25b   haribo   Date: 19/05/2016
600
601
602
                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...
603
604

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

06241f05   haribo   Class scheduler f...
606
        return False
7a79e25b   haribo   Date: 19/05/2016
607
608

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

06241f05   haribo   Class scheduler f...
612
613
        :returns : list of partially-matching Intervals
        '''
7a79e25b   haribo   Date: 19/05/2016
614

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

06241f05   haribo   Class scheduler f...
617
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
618
619
            overlap = min(sequence.jd2, interval.end) - \
                max(sequence.jd1, interval.start) - self.max_overhead
06241f05   haribo   Class scheduler f...
620
621
            if overlap > 0:
                potential_intervals.append(interval)
7a79e25b   haribo   Date: 19/05/2016
622

06241f05   haribo   Class scheduler f...
623
624
        return potential_intervals

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

06241f05   haribo   Class scheduler f...
629
630
631
        :param sequence: sequence to be moved
        :param time_shift: amplitude of the shift
        :param direction: "LEFT" or "RIGHT"
7a79e25b   haribo   Date: 19/05/2016
632

06241f05   haribo   Class scheduler f...
633
634
635
636
        :side-effect :
            - modify the sequence in self.sequences
            - changes the interval before and the interval after the sequence
        '''
7a79e25b   haribo   Date: 19/05/2016
637

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

06241f05   haribo   Class scheduler f...
643
        for interval in self.intervals:
7a79e25b   haribo   Date: 19/05/2016
644
            if is_nearby_equal(interval.end, shs.tsp - self.max_overhead):
06241f05   haribo   Class scheduler f...
645
                interval_before = interval
7a79e25b   haribo   Date: 19/05/2016
646
            elif is_nearby_equal(interval.start, shs.tep):
06241f05   haribo   Class scheduler f...
647
                interval_after = interval
7a79e25b   haribo   Date: 19/05/2016
648

06241f05   haribo   Class scheduler f...
649
650
        if direction == "LEFT":
            interval_before.end -= time_shift
b87b6d9b   haribo   Finished tests fo...
651
652
            if "interval_after" in locals():
                interval_after.start -= time_shift
7a79e25b   haribo   Date: 19/05/2016
653
654
655
656
            shs.tsp -= time_shift
            shs.tep -= time_shift
            shs.deltaTL -= time_shift
            shs.deltaTR += time_shift
06241f05   haribo   Class scheduler f...
657
        else:
b87b6d9b   haribo   Finished tests fo...
658
659
            if "interval_before" in locals():
                interval_before.end += time_shift
06241f05   haribo   Class scheduler f...
660
            interval_after.start += time_shift
7a79e25b   haribo   Date: 19/05/2016
661
662
663
664
            shs.tsp += time_shift
            shs.tep += time_shift
            shs.deltaTL += time_shift
            shs.deltaTR -= time_shift
715fabb7   haribo   #3430 (100%)
665
        if "interval_before" in locals() and is_nearby_equal(interval_before.duration, 0):
b87b6d9b   haribo   Finished tests fo...
666
            self.intervals.remove(interval_before)
715fabb7   haribo   #3430 (100%)
667
        if "interval_after" in locals() and is_nearby_equal(interval_after.duration, 0):
b87b6d9b   haribo   Finished tests fo...
668
669
            self.intervals.remove(interval_after)

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

06241f05   haribo   Class scheduler f...
674
675
676
        :side-effect:
            - Modify User quota in DB
        '''
7a79e25b   haribo   Date: 19/05/2016
677

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

715fabb7   haribo   #3430 (100%)
683
684
        if SIMULATION == False:
            user.save()
7a79e25b   haribo   Date: 19/05/2016
685
686

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

06241f05   haribo   Class scheduler f...
690
691
        :side-effect :
            - change sequences status and dates in DB
b87b6d9b   haribo   Finished tests fo...
692
            - add a schedule in the DB
06241f05   haribo   Class scheduler f...
693
        '''
7a79e25b   haribo   Date: 19/05/2016
694

8e4ab234   haribo   #3485: Creation a...
695
        self.schedule.save()
7a79e25b   haribo   Date: 19/05/2016
696
697
698
        for sequence, shs in self.sequences:
            shs.schedule = self.schedule
            shs.save()
06241f05   haribo   Class scheduler f...
699

b87b6d9b   haribo   Finished tests fo...
700
701
702
    def print_schedule(self):
        '''
        ONLY FOR DEBUG
7a79e25b   haribo   Date: 19/05/2016
703

b87b6d9b   haribo   Finished tests fo...
704
705
706
        Prints the planned sequences
        '''

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

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

b87b6d9b   haribo   Finished tests fo...
712
713
        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
714
                  % (sequence.name, sequence.shs.tsp, sequence.shs.tep, sequence.duration, sequence.shs.deltaTL, sequence.shs.deltaTR))
b87b6d9b   haribo   Finished tests fo...
715
716

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

b87b6d9b   haribo   Finished tests fo...
718
719
720
        for interval in self.intervals:
            print("start: %d\t, end: %d\t" % (interval.start, interval.end))

06241f05   haribo   Class scheduler f...
721
722
723
724
725
''' 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
726
'''