#!/usr/bin/python
# Christoph Durr, CNRS, 2010 ---- implements the algorithm from
# Polynomial Time Algorithms for Minimum Energy Scheduling with
# Philippe Baptiste, Marek Chrobak, ESA, 136-150, 2007. However this
# implementation does not in time O(n^5), but in O(n^7). The places
# for improvement are the function P, were we tried here to stick
# closest to the recursion of the paper, as well as the function prev,
# which should be precomputed to improve running time.
# Usage: execute with two command line arguments. The first one
# contains release times/processing times/deadlines for all jobs, as
# in "37/8/46 38/2/47 10/5/25" for example and the second contains the
# boot-time L.
from sys import *
from copy import *
# global variables:
# n = number of jobs
# J = list of jobs
# L = large gap threshold
# -------------------------------------------------------------------- Job
class Job:
'''a job has release time r, processing time p, deadline d, name
i. Jobs are ordered according to increasing deadlines. '''
def __init__(self,r,p,d,i):
self.r=r
self.p=p
self.d=d
self.i=i
def __cmp__(self, oth):
return cmp((self.d,self.r,self.i), (oth.d, oth.r, oth.i))
def __str__(self):
return "" \
% (self.i, self.r, self.p, self.d)
def prev(k,t):
'''maximum rj among jobs j<=k with rjr:
r = rj
return r
# -------------------------------------------------------------------- Skeleton
class Skeleton:
'''a skeleton is the support of some schedule.
It consists of a non-empty ordered sequence of blocks.
A block is a pair of the form (starttime,endtime).'''
def __init__(self,b):
self.b = b
def __str__(self): return str(self.b)
def gaps(self): return len(self.b)-1
def start(self): return self.b[0][0]
def end(self): return self.b[-1][1]
def rightFill(self, p):
'''add support from the end of the skeleton to the right'''
s,e = self.b[-1]
self.b[-1] = (s,e+p)
def leftFill(self, dk, p):
'''add support from dk to the skeleton to the left,
possibly wrapping last blocks.'''
assert p>0
previousStart = self.start()
# create a new last block
(s1,e1) = (dk-p, dk)
while len(self.b)>0 and self.end() >= s1:
(s0,e0) = self.b.pop()
s1 -= e0-s0
self.b.append((s1,e1))
return s1>=previousStart
def __add__(s1,s2):
'''concatenate two skeletons'''
assert s1.end()<=s2.start(), "combine overlapping skeletons"
if s1.end()0 and J[i].r<=t]
if not pending:
return None # no job available
i = min(pending) # most urgent pending job
di = J[i].d
if di<=t:
return None # job expired
# next jobs to interrupt i
moreUrgent = [J[j].r for j in range(i) if p[j]>0 and j!=i]
Ci = t+p[i] # ideal completion time
# nextEvent: either completion of i,
# ... or end of block, or interruption
nextEvent = min(moreUrgent+[Ci, endBlock])
S.append((t, i, nextEvent))
p[i] -= nextEvent-t
t = nextEvent # move to next event
# verify that all jobs completed
if [i for i in p if p[i]>0]:
return None
return S
def printSchedule(S):
for (s,i,e) in S:
if s>=0: # don't show dummy job execution
print "" \
% (J[i].i, s, e)
# -------------------------------------------------------------------- U
UU = {} # memoization
def U(s,k,g):
'''Maximal makespan schedule over jobs j<=k with r_j>=r_s and at most
g gaps.'''
if (s,k,g) in UU:
return UU[s,k,g]
rs = J[s].r
if k<0:
# empty schedule
UU[s,k,g] = Skeleton([(rs,rs)])
return UU[s,k,g]
Usk1g = U(s,k-1,g)
rk = J[k].r
pk = J[k].p
dk = J[k].d
# k out of range, or pb unfeasible
if rk < rs or not Usk1g:
UU[s,k,g] = Usk1g
return UU[s,k,g]
# -- case 0
if rk > Usk1g.end():
best = Usk1g
else:
best = None # optimal solution
# we use a single loop so we can interrupt with a single break
for l,h in [(s,0)]+[(l,h) for l in range(k) for h in range(g+1)]:
# -- case 1
p,a = P(s,k,h,l)
if p>pk:
continue
if hpk-p and \
b.end()>prev(k-1,dk):
t = dk-(pk-p)
c = a + b
if c.leftFill(dk,pk-p):
best = c
break
# -- case 2
b = U(l,k-1,g-h)
if not b:
continue
if p prev(k-1,dk):
c = a + b
if c.leftFill(dk,pk-p):
best = c
break
# -- case 3
t = b.end() + pk - p
if p<=pk and \
rk <= b.end() and \
dk - b.end() > pk - p and \
b.end() > prev(k-1, t+1): #t+1 for frugality
c = a + b
c.rightFill(pk-p)
if best==None or c.end() > best.end():
best = c
UU[s,k,g] = best
return best
# -------------------------------------------------------------------- P
PP = {} # memoization
def P(s,k,g,l):
'''basically minimal processing time p such that the instance for
pk<-p has a (s,k)-schedule with makespan r_l. Returns couple
with this value and the schedule.'''
if (s,k,g,l) in PP:
return PP[s,k,g,l]
rs = J[s].r
rl = J[l].r
rk = J[k].r
pk = J[k].p
a = U(s,k-1,g)
if rs>rl or not a:
PP[s,k,g,l] = ((),None)
return PP[s,k,g,l]
if rs==rl or a.end()>=rl:
PP[s,k,g,l] = (0, Skeleton([(rs,rs)]))
return PP[s,k,g,l]
best = ((),None) # () == +infinity in python
for h in range(g+1):
a = U(s,k-1,h)
if not a:
continue
for j in range(k):
rj = J[j].r
if rs <= rj and rj <= rl and \
prev(k-1,rj) < a.end() and \
a.end() < rj and \
rk <= a.end():
p,b = P(j,k,g-h,l)
if p>pk:
continue
q = rj-a.end()+p
if qu.end()]
if not nextRelease:
alt = (L*g, u)
else:
rj,j = min(nextRelease)
alt = (L*g + rj - u.end() + E[j][0], u+E[j][1])
if alt" # remove dummy job induced cost
printSchedule(S)
for i in range(len(sk.b)-1):
s = sk.b[i][1]
e = sk.b[i+1][0]
c = min(e-s,L)
if s>=0: # remove gap after dummy job
print ""%(c,s,e)
else:
print " objvalue=0 objfct=feasibility >"
for j in J:
if j!=dummy:
print str(j)
print ""