Merge branch 'simanneal' of PSSAI_Team/JobShopScheduling into devel
This commit is contained in:
commit
22196bdfa6
4
inputdata/sample
Normal file
4
inputdata/sample
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
3 3
|
||||||
|
0 4 1 6 2 1
|
||||||
|
1 3 0 13 2 4
|
||||||
|
1 2 2 5 0 3
|
197
src/Generator/generator.py
Normal file
197
src/Generator/generator.py
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
from Parser import JobShopProblem as Problem
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def pull_fwd(solution):
|
||||||
|
"""
|
||||||
|
Pull a task from a pseudo-random position to the position of
|
||||||
|
a random task forward. If the task directly in front is part
|
||||||
|
of the same job, pull that instead. The first task can never
|
||||||
|
be pulled forward. Will not rectify solutions.
|
||||||
|
|
||||||
|
Returns the modified solution and the tasks index.
|
||||||
|
"""
|
||||||
|
old_idx = random.randint(1, len(solution)-1)
|
||||||
|
#print("old_idx" + str(old_idx))
|
||||||
|
while(solution[old_idx][1][0] == solution[old_idx-1][1][0]):
|
||||||
|
old_idx -= 1
|
||||||
|
#Catch case of the op to be pulled being 0
|
||||||
|
#print("old_idx: " + str(old_idx))
|
||||||
|
if(old_idx == 0):
|
||||||
|
return pull_fwd(solution)
|
||||||
|
new_idx = random.randint(0, old_idx-1)
|
||||||
|
for task in solution[new_idx:old_idx]:
|
||||||
|
if(task[1][0] == solution[old_idx][1][0]):
|
||||||
|
#break
|
||||||
|
return pull_fwd(solution)
|
||||||
|
#else:
|
||||||
|
task = solution[old_idx]
|
||||||
|
solution.remove(task)
|
||||||
|
solution.insert(new_idx, task)
|
||||||
|
return rectify(solution, new_idx)
|
||||||
|
|
||||||
|
|
||||||
|
def accept(solution):
|
||||||
|
"""
|
||||||
|
Accept the current generated solution and evaluate it.
|
||||||
|
Maybe skip this during the first step to generate a more
|
||||||
|
random solution.
|
||||||
|
"""
|
||||||
|
return tighten(solution)
|
||||||
|
|
||||||
|
|
||||||
|
def tighten(solution):
|
||||||
|
"""
|
||||||
|
Try to remove any holes in the schedule.
|
||||||
|
"""
|
||||||
|
|
||||||
|
global problem
|
||||||
|
|
||||||
|
bound = len(solution)
|
||||||
|
for i in range(0,bound):
|
||||||
|
jobs = ( task for task in solution[i-1:bound:-1] if task[1][0] == solution[i][1][0] )
|
||||||
|
machs = ( task for task in solution[i-1:bound:-1] if problem[task[1]][1] == problem[solution[i][1]][1] )
|
||||||
|
job = next(jobs,None)
|
||||||
|
mach = next(machs,None)
|
||||||
|
times = []
|
||||||
|
if job:
|
||||||
|
times += [job[0] + problem[job[1]][0]]
|
||||||
|
if mach:
|
||||||
|
times += [mach[0] + problem[job[1]][0]]
|
||||||
|
if times:
|
||||||
|
solution[i] = (max(times), solution[i][1])
|
||||||
|
solution.sort()
|
||||||
|
return solution
|
||||||
|
|
||||||
|
def rectify(solution, idx):
|
||||||
|
"""
|
||||||
|
Transform solution by adapting the begin times and delaying
|
||||||
|
tasks on the same machine if affected.
|
||||||
|
"""
|
||||||
|
solution[idx] = (solution[idx+1][0],) + solution[idx][1:]
|
||||||
|
|
||||||
|
update_begin(solution, idx)
|
||||||
|
correct_indices(solution, idx)
|
||||||
|
for i in range(idx, len(solution)-1):
|
||||||
|
#print("i: " + str(i))
|
||||||
|
correct_machine(solution, i)
|
||||||
|
#print(solution)
|
||||||
|
correct_precedence(solution, i)
|
||||||
|
#print(solution)
|
||||||
|
return solution
|
||||||
|
|
||||||
|
def update_begin(solution, idx):
|
||||||
|
"""
|
||||||
|
Update the start time of the given task wrt machine and job.
|
||||||
|
"""
|
||||||
|
|
||||||
|
global problem
|
||||||
|
task = solution[idx]
|
||||||
|
|
||||||
|
if(idx == 0):
|
||||||
|
solution[idx] = (0,) + solution[idx][1:]
|
||||||
|
return
|
||||||
|
|
||||||
|
#find the next task with condition=true, if exists
|
||||||
|
machine = ( x for x in solution[idx-1::-1] if problem[x[1]][1] == problem[task[1]][1] )
|
||||||
|
prev_mach = next(machine, None) #returns the task or None
|
||||||
|
|
||||||
|
job = ( x for x in solution[idx-1::-1] if task[1][0] == x[1][0] )
|
||||||
|
prev_job = next(job, None)
|
||||||
|
end_mach = 0
|
||||||
|
end_job = 0
|
||||||
|
if prev_mach:
|
||||||
|
end_mach = problem[prev_mach[1]][0] + prev_mach[0]
|
||||||
|
if prev_job:
|
||||||
|
end_job = problem[prev_job[1]][0] + prev_job[0]
|
||||||
|
solution[idx] = (max(end_mach, end_job, task[0]),) + solution[idx][1:]
|
||||||
|
|
||||||
|
|
||||||
|
def correct_indices(solution, idx):
|
||||||
|
"""
|
||||||
|
Adapt solution to reestablish ascending order of execution times.
|
||||||
|
"""
|
||||||
|
task = solution[idx]
|
||||||
|
tasks = [ x for x in solution[idx:] if x[0] < task[0]]
|
||||||
|
if tasks:
|
||||||
|
solution.remove(task)
|
||||||
|
solution.insert(idx + len(tasks), task)
|
||||||
|
#[1,3,2] -> idx = 1, len([2])=1
|
||||||
|
|
||||||
|
def correct_machine(solution, idx):
|
||||||
|
"""
|
||||||
|
Check conflicts on machines and correct if needed.
|
||||||
|
"""
|
||||||
|
task = solution[idx]
|
||||||
|
end = problem[task[1]][0] + task[0]
|
||||||
|
possible_conf = ( x for x in solution[idx+1:] if problem[x[1]][1] == problem[task[1]][1])
|
||||||
|
conflict = next(( x for x in possible_conf if x[0] < end ), None)
|
||||||
|
if(conflict):
|
||||||
|
idx = solution.index(conflict)
|
||||||
|
solution[idx] = (end,) + solution[idx][1:]
|
||||||
|
correct_indices(solution,idx)
|
||||||
|
|
||||||
|
|
||||||
|
def correct_precedence(solution, idx):
|
||||||
|
"""
|
||||||
|
Check precedence relation and correct if needed.
|
||||||
|
"""
|
||||||
|
task = solution[idx]
|
||||||
|
end = problem[task[1]][0] + task[0]
|
||||||
|
possible_conf = ( x for x in solution[idx+1:] if x[1][0] == task[1][0] )
|
||||||
|
conflict = next(( x for x in possible_conf if x[0] < end or x[1][1] < task[1][1]), None)
|
||||||
|
if(conflict):
|
||||||
|
idx = solution.index(conflict)
|
||||||
|
#print("idx->" + str(idx))
|
||||||
|
if(conflict[0] < end):
|
||||||
|
solution[idx] = (end,) + solution[idx][1:]
|
||||||
|
correct_indices(solution,idx)
|
||||||
|
if(conflict[1][1] < task[1][1]):
|
||||||
|
new_start = solution[idx][0] + solution[idx][1][1]
|
||||||
|
#print("new_start: " + str(new_start))
|
||||||
|
solution.remove(task)
|
||||||
|
task = (new_start,) + task[1:]
|
||||||
|
solution.insert(idx, task)
|
||||||
|
|
||||||
|
def generate(old_solution, steps, p=0.01):
|
||||||
|
"""
|
||||||
|
Generate a new solution from an existing solution with a
|
||||||
|
specified number of max steps.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
print("Max steps: " + str(steps))
|
||||||
|
print("Accept probability: " + str(p))
|
||||||
|
sys.stdout.write("Start generation... ")
|
||||||
|
solution = old_solution[:]
|
||||||
|
option = pull_fwd #do at least one pull
|
||||||
|
for i in range(0, steps):
|
||||||
|
solution = option(solution)
|
||||||
|
if(option == accept):
|
||||||
|
break
|
||||||
|
option = pull_fwd if p < random.random() else accept
|
||||||
|
if ((i * 100) % steps == 0):
|
||||||
|
sys.stdout.write(str(i*100/steps) + "%... ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stdout.write("Done\n")
|
||||||
|
if option != accept:
|
||||||
|
accept(solution)
|
||||||
|
return solution
|
||||||
|
|
||||||
|
def mock():
|
||||||
|
"""
|
||||||
|
Reads a mock problem and creates the corresponding enumerated
|
||||||
|
solution. Should clean up the namespace afterwards.
|
||||||
|
"""
|
||||||
|
global problem
|
||||||
|
from Parser.js2_style import parse_file as mockload
|
||||||
|
from SchedulingAlgorithms.enumerate import enumerate as mockenum
|
||||||
|
problem = mockload('../inputdata/sample')
|
||||||
|
solution = mockenum(problem)
|
||||||
|
del mockload
|
||||||
|
del mockenum
|
||||||
|
return solution
|
||||||
|
|
||||||
|
def init(in_problem):
|
||||||
|
global problem
|
||||||
|
problem = in_problem
|
10
src/SchedulingAlgorithms/enumerate.py
Normal file
10
src/SchedulingAlgorithms/enumerate.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from Parser import JobShopProblem as Problem
|
||||||
|
|
||||||
|
def enumerate(problem):
|
||||||
|
schedule = ( (job, task) for job in range(0, problem.jobs) for task in range(0, len(problem.get_tasks_by_job(job))) )
|
||||||
|
begin = 0
|
||||||
|
solution = []
|
||||||
|
for task in schedule:
|
||||||
|
solution.append((begin, task))
|
||||||
|
begin += problem[task][0]
|
||||||
|
return solution
|
36
src/SchedulingAlgorithms/simanneal.py
Normal file
36
src/SchedulingAlgorithms/simanneal.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from Generator.generator import generate
|
||||||
|
from Generator.generator import init as gen_init
|
||||||
|
from SchedulingAlgorithms.enumerate import enumerate as enum
|
||||||
|
from math import e
|
||||||
|
from random import random
|
||||||
|
|
||||||
|
def anneal(max_temp = 300, max_steps = 250, accept_prob=0.01):
|
||||||
|
global problem
|
||||||
|
gen_init(problem)
|
||||||
|
temp = max_temp
|
||||||
|
initial = enum(problem)
|
||||||
|
current = generate(initial, problem.machines * problem.jobs * 10, 0) #Complete the iteration once fully.
|
||||||
|
del initial
|
||||||
|
for step in range(0, max_steps):
|
||||||
|
new = generate(current, problem.machines * problem.jobs, accept_prob)
|
||||||
|
new_end = rate(new)
|
||||||
|
curr_end = rate(current)
|
||||||
|
p = 1 / ( 1 + (e ** ((curr_end - new_end)/temp)))
|
||||||
|
if (new_end < curr_end) or (p < random()):
|
||||||
|
current = new
|
||||||
|
print("Old: " + str(curr_end) + " New: " + str(new_end))
|
||||||
|
temp = ((max_temp-1)/(max_steps**2))*(step-max_steps)**2+1
|
||||||
|
print("Iteration: "+ str(step) + " Temperature: " + str(temp))
|
||||||
|
return current
|
||||||
|
|
||||||
|
def rate(solution):
|
||||||
|
global problem
|
||||||
|
last_tasks = []
|
||||||
|
for i in range(0,problem.jobs):
|
||||||
|
last_tasks += [next(( x for x in solution[::-1] if x[1][0] == i), [])]
|
||||||
|
end_times = [ problem[x[1]][0] + x[0] for x in last_tasks]
|
||||||
|
return max(end_times)
|
||||||
|
|
||||||
|
def init(in_problem):
|
||||||
|
global problem
|
||||||
|
problem = in_problem
|
Loading…
Reference in a new issue